Records map
Reach for this for any spatial view — pins on a geographic basemap or on a fixed background image, one widget for both. The selected record docks as a detail pane rather than a bespoke summary panel. A verb on this screen has three homes and none of them is the bar: the primary one is the Fab (fab), view verbs like Export are the view-options surface's header affordance, and a record's own actions belong to the pushed record surface — see Detail's hosting knob.
Spec of record: design-system/components/data/MapView.d.ts
Variants
Rendered scenarios from playground/scenarios.dart — the
same registry the golden baselines gate on, so what you see here is
what CI holds the widget to.
Live
Real widgets, built from the real kit. Turn a knob and it rebuilds; the theme bar above applies the design system's own tokens — including anything you changed under Customize — and the device selector uses the same sizes the golden baselines gate on, so what you see here is what CI holds the widget to.
Press Focus on any preview to take it to the full window — at a desktop width that renders most devices at 1:1, which is when the widget's own controls become clickable. The bar always states the device and the scale, so a shrunk preview says it is shrunk. Esc leaves.
Marks within the radius merge into a count badge. The selected pin never clusters.
Collision distance in logical pixels.
Tap a pin. At 960 and wider the record docks as a side pane; below that it rises as a bottom deck.
Shows when you tap a pin — the pane is where the selected record docks — press Focus to make the preview interactive.
Which nav the compact posture mounts — the drawer behind the hamburger, or a bottom bar.
The primary tier, and on a records destination the ONLY loud action the bar's neighborhood has. The scaffold renders it in the thumb zone at every window class; a screen never places it itself.
Actions in the drawer's own footer. The drawer carries them at EVERY window class — at 960 and wider it exists only because they do, since the expanded shell otherwise mounts a standing inline rail instead.
Shows when you open the drawer — the hamburger below 960, an edge swipe at 960 and wider, where the rail head's menu button toggles the rail instead. The actions sit in the drawer's own footer; the resting frame is identical either way. — press Focus to make the preview interactive.
The app's identity mark, and nothing else — it never opens a picker. At 960 and wider it sits beside the menu button in the rail's head at the same place in both postures, overhanging the collapsed rail so the bar beside it steps in. Below 960 the drawer's header band states it.
The scope — one value of the app's own kind (here an Area) — as the FIRST item of the bar's subtitle, ahead of the meta. With a picker it is a quiet text button with a chevron; on a phone the whole title block opens the picker as a bottom sheet. A pushed record's bar shows it read-only.
Global commands with no page ("Send feedback", "Help") pinned in the rail's footer under the footer destinations — the same row as a destination, but never lit and never counted in the destination index.
An organization's or partner's logo at the very bottom of the rail and the drawer — identity, never a control. Collapsed it shows its compact square, or nothing when the host gives none.
The Group and Columns panels beside the built-in Filter. `RecordsChrome.panels` is host-contributed and view-agnostic — the same cluster a table gets. OFF leaves Filter standing alone, which is a real posture: the tab row is absent at a single available control, because one control needs no tabs.
Shows when you open the view-options pane (tap Filter's control) — panels are TABS of that one pane, so the resting bar looks identical either way and only the pane's content differs — press Focus to make the preview interactive.
Dart for these values
screen_records_map() What you can do in it
Capability the preview already carries that no knob can express — you reach it by using the widget. Worth knowing before concluding something is missing: most of this is only clickable once the preview is at Focus size.
- **Search** — type in the field; the table narrows live. Collapsing search never clears the query, which survives as a removable
search:…chip. - **Filter** — the funnel opens the view-options pane. Facets are type-appropriate: enums and text offer distinct values, numbers a min/max, dates a From/To.
- **Chips** — every applied filter renders as a removable chip, and Clear all removes the lot.
- **Narrowed to zero** states the narrowing and offers Clear, rather than the author-a-record empty state.
- **Tap a pin** to select it; the record docks as a side pane at 960 and wider, and rises as a bottom deck below that.
- **Drag the deck** between its two detents — peek shows the card, half shows the detail.
- **Tap a cluster badge** to zoom to fit its members. Coincident pins never separate, by design.
- **Tap the background** to deselect. Every transition reports through one channel.
What it is made of
The widgets this screen assembles, with their full API below. They used to live on a second entry of their own; one thing you choose gets one page.
AppMapView widget
The spatial record view — pins on a geographic basemap OR on a fixed background image, per `design-system/components/data/MapView.d.ts`: *"ONE widget, two modes… only the coordinate space differs."* The mode carries the coordinate space and viewport configuration; chrome, pins, pane, selection and…
Full dartdoc
The spatial record view — pins on a geographic basemap OR on a fixed background image, per `design-system/components/data/MapView.d.ts`: *"ONE widget, two modes… only the coordinate space differs."* The mode carries the coordinate space and viewport configuration; chrome, pins, pane, selection and placement are written once against it. The view owns the transient interaction state — which pin is selected and whether a create-placement is in progress — while the host owns the data consequences via onCreateAt, and owns the dataset itself (search, filter, result count) exactly as it does for `AppDataTable`. Pins are never edited or re-dragged on the map — the FORM owns position (`MapView.d.ts`); there is no move flow. **Selection** opens the detail pane (when detailPanelBuilder is set) and enlarges the tapped pin; every transition — tap, re-tap, background tap, pane dismiss — reports through onSelectPin, and a host holding selectedKey controls it outright. **Create** drops a reticle at the viewport center and reports the committed MapPoint via onCreateAt. **Camera, geo mode:** frames the pins exactly once, then stays put — if the first build already has pins it frames them immediately (empty → center on GeoMode.initialCenter; one → center on that pin; many → bounds-fit, padded); pins streaming in after an empty first build are framed once on the rebuild that first sees them. After that the camera is stable: selection, panning, and new pins never re-frame it. **Camera, image mode:** the pan/zoom viewport opens at the background's natural size and is kept stable across rebuilds by one transformation controller.
AppMapViewdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| cluster | bool | true | Collapses pins closer than clusterRadius px into a count badge (`MapView.d.ts`: default true). Distance-based — any two marks within the radius merge, so coincident pins can never half-bury each other — and the SELECTED pin never clusters. Tapping a badge zooms to fit its members. |
| clusterRadius | double | kDefaultClusterRadius | The merge distance in screen px (the spec's default 46). |
| deckRest | MapDeckRest peek · header | MapDeckRest.peek | The deck's FIRST rest (`MapView.d.ts` THE DECK MAY REST AT THE RECORD'S HEADER, #1018): MapDeckRest.peek, the default, opens the deck at the small PEEK carrying peekBuilder's card. MapDeckRest.header opens it at the MEASURED height of the detailPanelBuilder body's header — an `AppDetailView` reports… |
| detailPanelBuilder | Widget Function(BuildContext context, String rowId)? | null | Builds the detail pane body for a selected row id. Null disables the pane; a pin tap then selects only for a host that listens through onSelectPin (a records map body docking its record in the floorplan's own pane, `MapView.d.ts` THE MAP'S RECORD IS THE RECORDS PANE), and otherwise only fires… |
| detailPosture | MapDetailPosture auto · side · deck | MapDetailPosture.auto | Where the record pane stands (the spec's `detailPosture`): MapDetailPosture.auto, the default, docks it at the side at the expanded window class and decks it below; `side` / `deck` force one. The visible rect every fit and the selection nudge target follows the posture that renders. |
| emptyActionLabel | String? | null | Empty-state action label (renders only with onEmptyAction). |
| emptyActionNavigates | bool | false | THE ACTION THAT ONLY OPENS A PAGE (`ActionStrip.d.ts`, #1749): the empty face's action pushes another screen and writes nothing, so it reports a link to assistive technology. Declared, never inferred from onEmptyAction. Default false. |
| emptyLabel | String? | null | Zero-pin empty-state title. Null renders no empty overlay (the bare surface is the empty face). With a label, the designed composition renders centered: the label plus at most one action. |
| formatRules | List<FormatRule<T>>? | null | THE LOCATION COLUMN'S FORMAT RULES (`FormatRules.d.ts`, #977; `MapView.d.ts` ONE CHANNEL PER JOB, #972): the first holding rule's `color` is a pin's FILL (its category) and its `glyph` the head's glyph; `paint` and the text treatments do not reach a mark. The rule's row is the pin's MapPin.row and… |
| frameEpoch | int | 0 | Programmatic fit-all (the spec's `frameEpoch`): whenever this value CHANGES on a mounted view the camera re-frames to fit the currently visible pins — the control stack's fit-all, host-triggered. The host bumps it when its surface re-enters the foreground (a records destination being revisited), so… |
| fullscreen | bool? | null | Controlled fullscreen state — whether the host is showing the map full screen, which the toggle's glyph and title state. Null reads as not full screen. The HOST owns what fullscreen means (route, overlay); the view only reports the request. |
| gestureHandling | MapGestureHandling direct · cooperative | MapGestureHandling.direct | Who owns a one-finger drag and a plain wheel (the spec's `gestureHandling`, COOPERATIVE GESTURES): MapGestureHandling.direct — the default — the map; MapGestureHandling.cooperative the enclosing scroller. A host chooses cooperative only where an enclosing scroller competes for the drag — the… |
| initialSelectedRowId | String? | null | Row id selected on first build, or null. Silently a no-op highlight-wise if no pin matches — the detail pane still opens for the id (e.g. a not-yet-synced deep link). |
| layers | MapLayers? | null | THE LAYERS CONTROL (`MapView.d.ts` `layers`, THE MAP'S FLOATS ARE SMALL CARDS, #894): the host's named basemaps and, since A DRAWING IS A LAYER (#1084), its drawings — the one up now and the choice. Two or more options render the Layers group, LAST in the control stack, in EITHER mode — an anchored… |
| legend | List<MapLegendGroup>? | null | THE PIN LEGEND (`MapView.d.ts` THE LEGEND DECODES THE PINS, #593): the key to the pin encoding, one group per channel, drawn on one plate at the visible rect's bottom-start. It opens OPEN except at the compact window class, where it opens closed to its one button, and the user shows and hides it… |
| legendOpen | bool? | null | THE LEGEND'S OPEN STATE MAY BE THE HOST'S (`MapView.d.ts`, #1058): set (true or false), the legend's state on every build — its show and hide controls report the state they ask for through onLegendOpenChanged and change nothing themselves, so a remount renders the host's value at once. Null, the… |
| loading | bool | false | THE MAP MAY STATE ITS LOAD (`MapView.d.ts`, #1085): true lays ONE pulsing skeleton plate over the whole surface, inert and announced as loading. It outranks the empty face, and the meta, the legend, the control stack, the scale, the attribution and the add Fab stand down; whatever pins holds lies… |
| meta | String? | null | The quiet top-start annotation (the spec's `meta`): a SENTENCE in the UI font on a float card, each figure at 600 in ink ("14 pins", THE MAP'S FLOATS ARE SMALL CARDS, #894). Null or empty renders none. |
| mode * | MapMode | — | The coordinate space + viewport configuration — GeoMode or ImageMode. |
| onBoundsChange | void Function(MapViewBounds bounds, double zoom, {required bool userGesture})? | null | THE VIEWPORT REPORT, MODE-NEUTRAL (`MapView.d.ts`, 2026-09-27): every camera move reported as the VISIBLE RECT plus zoom — the spec's widened `onViewChange` payload and a dashboard harness's viewport scope — in the coordinate space of mode: GeoViewBounds (lat/lng, the flutter_map zoom) under… |
| onCreateAt | void Function(MapPoint point)? | null | Invoked when a create-placement is committed, with the viewport-center coordinate. Null disables the create affordance. |
| onEmptyAction | VoidCallback? | null | Empty-state action callback (renders only with emptyActionLabel). |
| onFullscreenChange | ValueChanged<bool>? | null | The host's report of the fullscreen toggle, and its PRESENCE renders the toggle (`MapView.d.ts` THE CONTROL STACK IS ONE ANSWER: full screen is the host's to offer, because only a host knows what it covers). Null renders no toggle — a dashboard map tile, whose focus toggle is its one way to enlarge. |
| onLegendOpenChanged | ValueChanged<bool>? | null | Reports the state the reader's show or hide press asks for, whether or not legendOpen is set — never a legendOpen change, the mount, or the legend standing down while the deck is up or while placing (which is not a close: the legend returns in the state it had). |
| onLegendToggle | void Function(String group, String entry)? | null | Makes every legend entry a TOGGLE: a tap reports `(group, entry)` and the HOST narrows its own dataset, then hands back the pins that survive and each entry's MapLegendEntry.shown. The view owns no visibility state of its own — on a records map the toggle IS the Filter tab's facet (one narrowing, so… |
| onOpenRecord | void Function(String rowId)? | null | Escalates the open pane's record (its id) to the host's full-screen detail surface. Non-null renders the expand/maximize affordance in the pane's ONE chrome row, leading the ✕ — the top-corner surface affordance `DetailView.d.ts` specifies, never a button on the record's own action row. Null omits… |
| onPinTap | void Function(T row)? | null | Invoked with the tapped pin's typed row. |
| onPlacingChange | ValueChanged<bool>? | null | Observes every placing transition the view initiates — begin (FAB), commit, cancel, and a pin tap abandoning the flow. The controlled half's `onChanged`; also fired in uncontrolled mode so a host can mirror the state without owning it. |
| onSelectPin | void Function(String? key, T? row)? | null | Reports EVERY selection transition (the spec's `onSelectPin(key|null)`) — a pin tap (its key + typed row), a re-tap, a background tap, and the pane's dismiss (all null, null). The one observation channel; without it a host cannot know the pane closed. Its PRESENCE makes a pin tap select and mark the… |
| onViewChange | void Function(MapPoint center)? | null | Fires on every viewport change with the coordinate under the reticle — a narrowed binding of the spec's `onViewChange` (`{center, zoom}`): center only, because no consumer reads zoom yet; widen it additively when one does. The picker pattern's live readout consumes it. |
| peekBuilder | Widget Function(BuildContext context, String rowId)? | null | Builds the deck's PEEK content for a selected row (`MapView.d.ts` PeekCard) — `PinPeekCard` with the identifying facts. Non-null gives the compact deck its PEEK rest height: it opens at `--map-deck-peek` showing the card's header row, and swaps to detailPanelBuilder's body on the way up to… |
| pins * | List<MapPin<T>> | — | Pins to plot. Every pin's MapPin.point variant must agree with mode; the view asserts this in debug builds. |
| placeGlyph | KitIconRef | KitGlyph.plus | The glyph the placement reticle's head carries (`MapView.d.ts` `placeIcon`, #961) — KitGlyph.plus by default. A host whose new record is a typed mark (a hazard, a hydrant) names that mark's glyph, so the reticle previews the pin the commit will draw. |
| placing | bool? | null | Controlled placing mode (the spec's `placing` — "the host can also enter it from a toolbar"). Null keeps placing uncontrolled (the internal FAB flow). `true` shows the reticle; with onCreateAt set the confirm/cancel bar renders and CAN COMMIT (the controlled flow is a full flow, not a dead end);… |
| selectedKey | String? | null | Controlled selection (the spec's `selectedKey`). While non-null-aware hosts keep using initialSelectedRowId, a host that passes this owns the selection outright: a change (including to null) adopts, and every internal transition still reports through onSelectPin so the host's copy never drifts. |
| showZoom | bool? | null | Renders the ± pair in the control stack (`MapView.d.ts` THE CONTROL STACK IS ONE ANSWER, 2026-09-27). Null — the default — FOLLOWS THE POINTER: shown while a fine pointer is attached (AppPointerPosture.desk, where a wheel zooms but nothing pinches), hidden on a touch-only surface, where pinch is… |
PinPeekCard widget
Blessed compact content for a pin surface's detail panel. A status dot + title header, then a short label/value list of the record's key fields.
Full dartdoc
Blessed compact content for a pin surface's detail panel. A status dot + title header, then a short label/value list of the record's key fields. The deck's PEEK (MapViewTheme.sheetPeekHeight, the spec's `--map-deck-peek`, 96) shows the HEADER ROW — the dot, the title line and the Open affordance — with the facts below the fold, one drag away (THE DECK PEEKS SMALL AND DRAGS FREE, #831). Hosts pass a builder returning this to `PinSurfaceScaffold` / `MapSurface` / `DrawingSurface`'s `detailPanelBuilder`; the full record is reachable via onExpand (or the host's own expanded layout once the sheet is dragged open). The deck's PEEK detent RELIES on onExpand: it draws no `expand / close` control row, so at peek this card carries the only escalate affordance. Keep fields short — this is a peek, not the record. Paints from DesignTokens like the rest of the map anatomy. Fields are the detail view's own AppDetailField (D-01 / the spec's `PeekCardProps` — `fields` are DetailView fields), rendered in the label-rail form (detailFieldValue + detailUsesLabelOver) rather than the kit definition list, because a peek panel is width-constrained where a detail view is not.
PinPeekCarddefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| fields | List<AppDetailField<T>> | const [] | The short label/value list rendered below the header. Keep this to the record's key fields — a peek, not the full detail view. |
| onExpand | VoidCallback? | null | Invoked by the trailing chevron affordance that opens the full record. Null hides the affordance. |
| row * | T | — | The row every field extractor in fields reads from. |
| statusColor | Color? | null | Mirrors the pin's marker color. Null hides the leading status dot. |
| title * | String | — | The header line — the record's primary label. Ellipsizes to a single line rather than wrapping, so the header row the peek shows never grows past its budget. |
MapTileScope widget
Provides a MapTileConfig to descendants (e.g. the location picker). Mirrors `LocationCaptureScope` — the app installs it once at the composition root.
MapTileScopedefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| child * | dynamic | — | |
| config * | MapTileConfig | — | The basemap tile source. |
LatLongPicker widget
A map screen for picking a lat/long — the picker pattern over AppMapView (one-map-view spec, phase 2). This surface IS the map view's pin-dropper, hosted as a route: the same bobbing reticle, the same ✓ confirm / ✕ cancel placement bar, the same snap-to-me control in the viewport stack — so placing…
Full dartdoc
A map screen for picking a lat/long — the picker pattern over AppMapView (one-map-view spec, phase 2). This surface IS the map view's pin-dropper, hosted as a route: the same bobbing reticle, the same ✓ confirm / ✕ cancel placement bar, the same snap-to-me control in the viewport stack — so placing a point reads identically whether the user is on the records map or inside a form. The pattern owns only what the component deliberately does not: which tile template renders (the satellite/street choice, read off MapTileScope and offered through the control stack's LAYERS group), the GPS fix behind the locate control, and where the committed point goes (the route's result). The live coordinate rides the component's own meta float rather than bespoke overlay chrome — and so does the picker's TRANSIENT LINE: a refused "use my location" (a denied permission, services off, no fix in time) shows its sentence there for the modeline linger and moves nothing (#793). With no LocationCaptureScope the locate control is ABSENT, never broken. The web twin is `web/packages/ui/src/patterns/LatLongPickerView.tsx`. THE EMPTY FIELD'S OPENING CAMERA (CaptureField.d.ts, #1373). With no initial value the picker opens on fallbackCenter, else on the WORLD VIEW (`_worldZoom` over (0, 0)) — never region zoom over open ocean — and, with a LocationCaptureScope, runs ONE locate on open. A fix moves the still-untouched camera to the device at _pickerZoom, through the map view's async-arrival move (a changed `GeoMode.initialCenter`, M-02), which already stands down once the user has moved. A refusal, a null fix or a throw is SILENT — it is not a press — and the camera stays where it opened. Until the user's first move the meta chip carries the cue (`FormVocabulary.latLongLocating`, `latLongNoLocation`) instead of a bare (0, 0).
LatLongPickerdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| fallbackCenter | CaptureLocationCenter? | null | The host's place to open an EMPTY field on (`FormView.d.ts` `CaptureLocationPicker.fallbackCenter`) — a project's centroid, a site's address — at its own zoom, else _pickerZoom. Read only when initial is null; shown while the opening locate is pending, refused or unavailable. Null opens an empty… |
| initial * | LatLng? | — | Initial camera center (existing value), or null for an EMPTY field, which opens on fallbackCenter or the world view and locates once. |
| tileProvider | TileProvider? | null | Explicit tile provider, overriding everything below it. Widget tests pass a stub here so they never hit the network. When null, AppMapView resolves tiles through the ambient `MediaCacheScope` if one is installed, and otherwise flutter_map's default network-only provider. |
ImagePointPicker widget
A full-screen surface for placing a point on a drawing — the picker pattern over AppMapView in IMAGE mode, the image-space twin of `LatLongPicker`. The SAME placement grammar as the location picker: the map view's own create-flow posture — the bobbing reticle over the viewport center, the placement…
Full dartdoc
A full-screen surface for placing a point on a drawing — the picker pattern over AppMapView in IMAGE mode, the image-space twin of `LatLongPicker`. The SAME placement grammar as the location picker: the map view's own create-flow posture — the bobbing reticle over the viewport center, the placement bar's ✓ confirm / ✕ cancel — hosted as a route, so placing a point reads identically on a records map, a location field and here. The pattern owns only what the component does not: the route chrome (Cancel and `FormVocabulary.xyPickerTitle`), where the camera opens (on initial at the one-pin scale through `ImageMode.initialCenter`, else the whole drawing), the live readout in the meta float (imagePointLabel), and THE MANUAL ROW below the map — a quiet toggle revealing `X %` / `Y %` and a Done that commits the typed pair, clamped into `0..100`, for a position the reader already knows. The web twin is `web/packages/ui/src/patterns/ImagePointPickerView.tsx`.
ImagePointPickerdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| drawing * | ImageMode | — | The drawing the point is placed on. |
| initial | ImagePoint? | null | The stored point the camera opens on, or null to open on the whole drawing. |
Types its knobs take
These are not things you choose — they are the shapes the knobs above expect. They live under this entry rather than in the navigation for exactly that reason.
AppDetailField support
One typed field in an AppDetailSectionKind.fields section — a definition-list entry over the record type T. A field is pure typed metadata: a label (the definition term, on the label rail), a type deciding the value grammar (shared with the table — an ID chip, a data-font number, a status word…),…
| Field | Type | Default | What it does |
|---|---|---|---|
| danger | bool Function(T record)? | null | Per-record danger predicate — when it returns true the value paints DesignTokens.danger AND takes em weight, in **every** grammar (an overdue date, a blocked status, a negative balance), not only on enums. Danger only where action is demanded; null never paints the value danger. |
| emptyLabel | String? | null | The word rendered in place of the em-dash when this field's value is empty ('Unassigned', 'No date'). Null (the default) renders the em-dash mark. Either way the field renders — an empty value never disappears unless its section hides empty fields (AppDetailSection.hideEmpty). |
| formatRules | List<FormatRule<T>>? | null | FORMAT RULES (`DetailView.d.ts` `DetailField.formatRules`, `FormatRules.d.ts`, #977) — the SAME list the record's table column declares (`AppColumn.formatRules`), so one value reads one way in a row and in a detail. The field's value is the subject; `of` reads the record the view shows. Reaches the… |
| formatRulesOf | FormatRulesOf<Object?>? | null | THROUGH A REFERENCE (`DetailView.d.ts` `DetailField.formatRulesOf`, `FormatRules.d.ts`, #1032): a field that shows a reference wears the REFERENCED row's rules. `resolve` is handed the field's value and the record the view shows, and the target row is the rules' row. Walked after formatRules, only… |
| label * | String | — | The definition term — rendered on the label rail in DesignTokens.label (micro-label), the `<dt>` of the definition list. |
| locationLabel | String? Function(T record)? | null | AppCellType.latLong only: the place the host resolved for this record's point ("North gate, Pad 4"), drawn beside the pin IN PLACE OF the coordinates (`DetailView.d.ts` A LOCATION READS AS A PLACE, #929). The coordinates stay the tooltip and join the accessible name, and the copy button still copies… |
| locationPreview | bool | false | AppCellType.latLong only: draw a still map of the point UNDER the readout (`DetailView.d.ts` A LOCATION FIELD MAY SHOW ITS MAP, #1281) — the point centered at the map's one-pin zoom with one pin, on the ambient MapTileScope's satellite raster, else the graticule plate. It is for a detail that reads… |
| measure | AppDetailMeasure? | null | How this field's value wraps (`DetailView.d.ts` LONG TEXT FILLS ITS BOX, #909). Null takes the section's AppDetailSection.measure, else AppDetailMeasure.fill — the value wraps at its field box and keeps its own line breaks. AppDetailMeasure.reading caps it at 65ch of the body role, never wider than… |
| numberFormat | AppNumberFormat? | null | How an AppCellType.number or AppCellType.percent value reads — `DataTable.d.ts` NumberFormat, UNCHANGED and through the table's own formatter (appCellContent), because a value does not change meaning by moving from a row to a detail. Declare the same format the column showing this datum declares.… |
| onEdit | VoidCallback? | null | Per-field quick-edit callback. When set the row shows a trailing quiet edit affordance (a ghost pencil AppIconButton) and firing it invokes this. Null (the default) renders a read-only row with no affordance. |
| onNavigate | void Function(T record)? | null | Navigation handler for an AppCellType.link field — the value renders accent-painted with a trailing chevron on a ≥DesignTokens.touch target, and firing it invokes this with the record. Null renders the inert link form (the reference still SHOWS; it just does not pretend to navigate). Unlike onEdit… |
| options | List<FieldOption>? | null | AN OPTION'S LABEL READS IN THE DETAIL (`DetailView.d.ts` `DetailField.options`, 2026-10-20, #1635): the SAME list the field's form editor and its table column take (`AppColumn.options`), so one datum prints one string on the form, the cell and the detail. Read by AppCellType.enumValue,… |
| refLabel | String? Function(String id)? | null | AppCellType.ref / AppCellType.refList: a stored key's label (`DetailView.d.ts` `DetailField.refLabel`, EVERY FORM TYPE HAS A READ FACE, #1645) — the same resolver the field's form editor and its table column read (`AppColumn.refLabel`). A null or blank answer leaves the key UNRESOLVED: the key… |
| render | Widget Function(T record)? | null | Full custom value builder — the escape hatch mirroring `AppColumn.render`. When non-null it wins over type, emptyLabel and the empty mark entirely: the caller owns the whole value widget. Use sparingly; the canonical grammars (type) cover the common cases, and a bespoke value in a definition list is… |
| span | int | 1 | Column span in the field grid — 1 (the default), 2 (two tracks), or spanFull (the whole row at every track count). Clamped to the live track count, so a span never overflows the grid; ignored by the inline layouts, whose value already fills the row. |
| status | AppStatusMarkTone? Function(T record)? | null | STATUS (`DetailView.d.ts` `DetailField.status`, Contained, 2026-09-29, #893) — the value is a stored status, rendered as the STATUS MARK (AppStatusMark) in the tone this returns for the record: the word with its marker, or the filled danger tag for AppStatusMarkTone.blocked. It WINS over danger —… |
| tier | AppDetailFieldTier | AppDetailFieldTier.primary | A FIELD MAY STEP ASIDE BY ITS TIER (`DetailView.d.ts` `DetailField.tier`, 2026-10-24, #1805). AppDetailFieldTier.primary (the default) renders as before; AppDetailFieldTier.secondary is hidden behind its section's note row until the reader shows it, then in its authored place. It needs no… |
| type | AppCellType | AppCellType.text | How the value renders — the shared AppCellType grammar. Defaults to AppCellType.text. Ignored when render is supplied. |
| unitOf | AppNumberUnit? Function(T record)? | null | THE ROW'S OWN UNIT (`DetailView.d.ts` `DetailField.unitOf`, #884) — the same accessor the record's column declares (`AppColumn.unitOf`), read against the record the view shows. A present result replaces numberFormat's unit; null or blank leaves it standing; with no numberFormat the unit trails the… |
| units | List<QuantityUnit>? | null | AppCellType.quantity: the unit choices (`FieldSpec.units`, `DetailView.d.ts` `DetailField.units`, #1645), read for each code's label (`150 psig`). Ignored by every other type. Null: a unit reads as its code. |
| value * | Object? Function(T record) | — | Typed value accessor — reads this field's value from a record. Its return type pairs with type (a `String` for id/text/enumValue/person, a `num` for number/progress, a `String`/`DateTime` for date, an `ImageProvider?` for image). A closure over the typed record — never a map lookup. |
CaptureLocationCenter support
The host's FALLBACK CENTER for an empty location field — `FormView.d.ts`'s `CaptureLocationCenter` (CaptureField.d.ts THE EMPTY FIELD'S OPENING CAMERA, steady-core #1373): where the full-screen picker opens when the field has no value, while its opening locate is pending, refused or unavailable. A…
| Field | Type | Default | What it does |
|---|---|---|---|
| center * | LatLng | — | The point to open on. |
| zoom | double? | null | The zoom to open at; null takes the picker's region zoom (13). |
FormatRule support
One FORMAT RULE (`FormatRules.d.ts` THE FORMAT RULE): a match and its effects. A rule list is ORDERED and the FIRST holding rule formats the value; a rule with no match always holds, so written last it is the column's default.
| Field | Type | Default | What it does |
|---|---|---|---|
| color | KitColor? | null | A palette name or a color parsed from data — on a map pin, its FILL. |
| em | bool | false | The word at the em weight. |
| glyph | KitIconRef? | null | A glyph — on a map pin, the head's glyph. |
| italic | bool | false | The word italic. |
| label | String? | null | What the rule means, in the host's words — read by a legend, a chip and the accessible name. |
| match | FormatMatch<T>? | null | The predicate, or null for "always holds" (the default rule). |
| muted | bool | false | The word at ink40 (ignored under FormatPaint.ink and FormatPaint.tag). |
| paint | FormatPaint | FormatPaint.mark | How color lands on a value (FormatPaint.mark by default). |
| strike | bool | false | The word struck through, in its own ink. |
ImageMode support
Fixed-background mode — pins live in normalized image space. The spec names its use cases directly: engineering drawings, site plans, floor plans.
| Field | Type | Default | What it does |
|---|---|---|---|
| background * | ImageProvider? | — | The raster drawing the pins are placed on — null under ImageMode.pdf. Any ImageProvider; the view resolves its natural size before the first pin renders, because normalized coordinates cannot be projected without it. |
| initialCenter | ImagePoint? | null | Where a map with NO PINS opens — `MapView.d.ts` `defaultCenter` under image mode (AN IMAGE MAP MAY OPEN ON A POINT, 2026-10-08): the camera centered on this point at the one-pin scale (twice the contain fit, the scale a lone pin mounts at) instead of the whole drawing. With pins the pins frame as… |
| semanticLabel | String? | null | Accessible name for the background (the spec's `alt`). Null renders without a semantics label. |
| Field | Type | Default | What it does |
|---|---|---|---|
| fallback | ImageProvider? | null | THE PDF IS PREFERRED (`MapView.d.ts`, 2026-10-09): under ImageMode.pdf, a raster of the same sheet — the fallback the view draws when the page cannot be read (corrupt, password-protected, an engine that fails). The page is the better source: it re-rasterizes sharp at every zoom, and markup needs its… |
| initialCenter | ImagePoint? | null | Where a map with NO PINS opens — `MapView.d.ts` `defaultCenter` under image mode (AN IMAGE MAP MAY OPEN ON A POINT, 2026-10-08): the camera centered on this point at the one-pin scale (twice the contain fit, the scale a lone pin mounts at) instead of the whole drawing. With pins the pins frame as… |
| markup | MarkupBinding? | null | MARKUP OVER A DRAWING (`MapView.d.ts`, 2026-10-09; `Markup.d.ts`): real PDF annotations over the ImageMode.pdf page, in the page's own space inside the map's zoom. Drawn only while the view draws the page from the PDF (never over fallback); the map offers no rotation, so the binding's `onRotate` is… |
| page | int | 0 | |
| renderer | PdfPageRenderer? | null | |
| semanticLabel | String? | null | Accessible name for the background (the spec's `alt`). Null renders without a semantics label. |
| source * | PdfPageSource | — | |
| space | PdfPointSpace | PdfPointSpace.drawn | A DRAWING'S POINTS MAY BE THE PAGE'S (`MapView.d.ts`, 2026-10-20): the space every point about an ImageMode.pdf drawing is stated in — each pin's, initialCenter, the placed point and the bounds report. PdfPointSpace.page is the page's unrotated crop box, and the view applies the file's `/Rotate`;… |
ImagePoint support
An image-relative coordinate (MapMode `image`), both axes normalized to `[0, 1]` per the spec — `(0, 0)` is the background's top-left, `(1, 1)` its bottom-right. Normalized 0-1, NOT the percent `[0, 100]` the deleted `DrawingPoint` carried: the spec says 0-1, nothing consumed the percent shape, and…
| Field | Type | Default | What it does |
|---|---|---|---|
| x * | double | — | Horizontal position, `0` = left edge, `1` = right edge. |
| y * | double | — | Vertical position, `0` = top edge, `1` = bottom edge. |
KitIconRef support
A glyph, by NAME — what every glyph-bearing kit API takes (a pin, a legend key, a nav item, an action, an empty face, a category card). Two shapes implement it: - a KitGlyph — a glyph in the kit's own name table, checked by the compiler (`KitGlyph.syringe`); - KitIconRef.named — any name as a…
| Field | Type | Default | What it does |
|---|---|---|---|
| name * | String | — |
MapLayers support
THE LAYERS CONTROL's channel (`MapView.d.ts` `MapLayers`, THE MAP'S FLOATS ARE SMALL CARDS, Contained, #894; A DRAWING IS A LAYER, #1084): the host's named basemaps and drawings, the one up now, and the choice. Two or more options render the Layers group, LAST in the control stack; fewer render…
| Field | Type | Default | What it does |
|---|---|---|---|
| current * | String | — | The key of the layer up now — the menu's check. |
| onChange * | ValueChanged<String> | — | Reports the chosen option's key. The host swaps its channel. |
| options * | List<MapLayerOption> | — | The choices, in menu order. Fewer than two: no Layers group. |
MapLegendGroup support
One group of an `AppMapView`'s pin legend — one channel of the encoding (a category color, a type glyph, a status), laid as ONE ROW of keys after its optional title.
| Field | Type | Default | What it does |
|---|---|---|---|
| entries * | List<MapLegendEntry> | — | The keys, in the order the host gives them. |
| key * | String | — | The group's identity — reported back through `AppMapView.onLegendToggle`. On a records map it is the facet column's key. |
| mark | MapLegendMark | MapLegendMark.head | How the group's keys are drawn — pin heads, or status badges. |
| title | String? | null | The group's title — the row's first word, in the label role — or null for none. |
MapMode support
The coordinate space and viewport configuration of an `AppMapView`. Sealed: `geo` (a web-mercator tile basemap) and `image` (a fixed background drawing) are the spec's two modes.
MapPin support
A single placed pin on an AppMapView — the typed row plus its resolved presentation. The host maps each domain row to a MapPin once (parse its coordinate, state its tone, name it via label, and its own color / glyph when the record states them) and hands the list to the view; the view never touches…
| Field | Type | Default | What it does |
|---|---|---|---|
| color | KitColor? | null | The pin's CATEGORY — the head's FILL: a palette name (KitColor.named: a built-in hue or a host category declared in the theme seed) or a color parsed from data (KitColor.parse). Wins over `AppMapView.formatRules`; null with no rule color leaves the fill to the tone, else DesignTokens.pinFill… |
| glyph | KitIconRef? | null | The glyph the pin head carries — the TYPE. Null takes the holding format rule's glyph, else KitGlyph.dot, the neutral mark. |
| id * | String | — | Stable row id — the selection key. Kept separate from row so the view never assumes the opaque `T` exposes an id. |
| label | String? | null | The pin's tooltip / accessible name (the spec's `labelField`). Null renders no semantics label. |
| point * | MapPoint | — | The pin tip's position, in the coordinate space of the hosting view's MapMode — a GeoPoint under GeoMode, an ImagePoint under ImageMode. The view asserts the two agree in debug builds. |
| provisional | bool | false | Default false. True draws THE PROVISIONAL MARK (`MapView.d.ts`, 2026-10-20): a placement nobody has confirmed, hollow and dashed. Not tone `pending`, which is the RECORD's status; this is the PLACEMENT's. |
| row * | T | — | The typed domain row this pin represents; returned to the host on tap. |
| tone | AppStatusMarkTone? | null | The pin's STATUS (`MapView.d.ts` ONE CHANNEL PER JOB, #972) — `pending`, `active`, `done` or `blocked`. On a pin with a category (color, or its format rule's) it rides the STATUS BADGE; on one with none it fills the head (`pending` → DesignTokens.pinPending, `active` → the accent, `done` → ok,… |
MapPoint support
A coordinate in one of the map's two spaces. Sealed so a `switch` over it is exhaustive — the compiler catches a mode that forgets a capability.
MapTileConfig support
Provider-agnostic basemap tile source. The app supplies concrete URL templates (with any API key already substituted) so the foundation never hardcodes a provider or secret.
| Field | Type | Default | What it does |
|---|---|---|---|
| attribution * | String | — | Attribution string rendered on the map (required by MapTiler/OSM ToS). |
| satelliteUrlTemplate * | String | — | `{z}/{x}/{y}` satellite tile URL template (key already embedded). |
| streetUrlTemplate * | String | — | `{z}/{x}/{y}` street tile URL template (key already embedded). |
| userAgentPackageName | String | 'unknown' | The consuming app's package identifier, sent in the `User-Agent` of every tile request. Tile providers' usage policies (OSM's in particular) require an identifying agent, and only the app knows its own id — the foundation must never carry one. Defaults to `flutter_map`'s own placeholder so a config… |
MapViewBounds support
The visible rect an `AppMapView` reports through its mode-neutral `onBoundsChange` (`MapView.d.ts` THE VIEWPORT REPORT IS MODE-NEUTRAL) — in the coordinate space of the view's MapMode. Sealed so a host switches exhaustively: GeoViewBounds under GeoMode, ImageViewBounds under ImageMode.
Where it lives
| Class | File |
|---|---|
| AppMapView | packages/app_widgets/lib/src/kit/data/map_view.dart |
| PinPeekCard | packages/app_widgets/lib/src/kit/data/pin_peek_card.dart |
| MapTileScope | packages/app_widgets/lib/src/kit/data/map_tile_scope.dart |
| LatLongPicker | packages/app_widgets/lib/src/patterns/lat_long_picker_view.dart |
| ImagePointPicker | packages/app_widgets/lib/src/patterns/image_point_picker_view.dart |