@12-apps/ui 6.23.0 → 6.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NATIVE.md +2 -1
- package/dist/_internal/components/data-display/Avatar/Avatar.js +3 -3
- package/dist/_internal/components/data-display/Avatar/Avatar.view.js +2 -2
- package/dist/_internal/components/data-display/Badge/Badge.js +2 -2
- package/dist/_internal/components/data-display/Chart/Chart.js +3 -3
- package/dist/_internal/components/data-display/Chart/SpecChart.js +6 -6
- package/dist/_internal/components/data-display/Chart/chart-renderers.js +2 -2
- package/dist/_internal/components/data-display/Chart/chart-spec.js +2 -2
- package/dist/_internal/components/data-display/Chip/Chip.js +3 -3
- package/dist/_internal/components/data-display/Chip/Chip.styles.js +2 -2
- package/dist/_internal/components/data-display/DataGrid/DataGrid.js +6 -4
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +24 -24
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +30 -30
- package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +4 -4
- package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +3 -3
- package/dist/_internal/components/data-display/DataViews/base-list-card.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +21 -21
- package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-range-pill.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +6 -6
- package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +5 -5
- package/dist/_internal/components/data-display/DataViews/list-card-rails.js +2 -2
- package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
- package/dist/_internal/components/data-display/FleetMap/FleetBody.js +4 -3
- package/dist/_internal/components/data-display/FleetMap/FleetMap.base.js +1 -0
- package/dist/_internal/components/data-display/FleetMap/FleetMap.base.js.map +1 -0
- package/dist/_internal/components/data-display/FleetMap/FleetMap.helpers.js +1 -1
- package/dist/_internal/components/data-display/FleetMap/FleetMap.hooks.js +2 -2
- package/dist/_internal/components/data-display/FleetMap/FleetMap.js +6 -5
- package/dist/_internal/components/data-display/FleetMap/FleetMap.metrics.js +20 -0
- package/dist/_internal/components/data-display/FleetMap/FleetMap.metrics.js.map +1 -0
- package/dist/_internal/components/data-display/FleetMap/FleetRoster.js +3 -2
- package/dist/_internal/components/data-display/index.js +26 -25
- package/dist/_internal/components/index.js +26 -25
- package/dist/_internal/platform/aria.js.map +1 -1
- package/dist/_internal/user-avatar/user-avatar.js +4 -4
- package/dist/charts.js +10 -10
- package/dist/{chunk-WGRS4PGY.js → chunk-2C47DEVU.js} +4 -4
- package/dist/{chunk-KHWCX4LI.js → chunk-6LGWJFQK.js} +2 -2
- package/dist/{chunk-XSF6AXEU.js → chunk-6RZDDUSM.js} +2 -2
- package/dist/{chunk-3FLVIVOK.js → chunk-BHIGHSXA.js} +2 -2
- package/dist/{chunk-VNNG46TS.js → chunk-BPLFM2GL.js} +6 -6
- package/dist/chunk-BTQZ3EX3.js +45 -0
- package/dist/chunk-BTQZ3EX3.js.map +1 -0
- package/dist/{chunk-LYSSO6WC.js → chunk-CZPU5TW3.js} +4 -4
- package/dist/{chunk-BZTFPSWT.js → chunk-DFLTUI7W.js} +7 -7
- package/dist/{chunk-URXT5TOY.js → chunk-E46PG577.js} +4 -4
- package/dist/{chunk-X5MPHXG5.js → chunk-EZ5Z5UFI.js} +9 -7
- package/dist/{chunk-X5MPHXG5.js.map → chunk-EZ5Z5UFI.js.map} +1 -1
- package/dist/{chunk-DRHIIUDO.js → chunk-FPWB5ILF.js} +5 -5
- package/dist/{chunk-R6SBP5M5.js → chunk-GOWBYHYW.js} +4 -4
- package/dist/{chunk-MQNKRIGC.js → chunk-HMV4QMXS.js} +25 -11
- package/dist/chunk-HMV4QMXS.js.map +1 -0
- package/dist/{chunk-N2TUX4A5.js → chunk-I3E2QHTH.js} +3 -3
- package/dist/{chunk-4XZXLB4I.js → chunk-JQ45NDBS.js} +3 -3
- package/dist/{chunk-I7HYGIEJ.js → chunk-JUHUKZGU.js} +2 -2
- package/dist/{chunk-TWQYDKCA.js → chunk-JZXWCEH5.js} +12 -9
- package/dist/chunk-JZXWCEH5.js.map +1 -0
- package/dist/{chunk-WQA4LIIK.js → chunk-KT6HWAEN.js} +4 -4
- package/dist/{chunk-2UGJIKVA.js → chunk-MZQTHSLN.js} +3 -3
- package/dist/{chunk-4SQFO4GW.js → chunk-NA7MIEG6.js} +7 -7
- package/dist/{chunk-MYGNIFZC.js → chunk-O4VCZTK4.js} +2 -2
- package/dist/{chunk-LS4FMSHY.js → chunk-OAYX4XN7.js} +4 -4
- package/dist/{chunk-Y5UCOBQN.js → chunk-OGGHD64H.js} +4 -4
- package/dist/{chunk-CBTY36TU.js → chunk-OH3YGJT5.js} +2 -2
- package/dist/{chunk-VON4BFK4.js → chunk-PN5CQKA2.js} +2 -2
- package/dist/{chunk-CF3FAAPC.js → chunk-RP5FK3SO.js} +2 -2
- package/dist/{chunk-L4U23SBX.js → chunk-TSYWI3NP.js} +13 -13
- package/dist/{chunk-YQ6TNHAC.js → chunk-TTLI6PF7.js} +5 -5
- package/dist/{chunk-OIKQO73C.js → chunk-TX5V42AY.js} +5 -5
- package/dist/{chunk-AJGYHDA5.js → chunk-U2YJCNRU.js} +4 -4
- package/dist/{chunk-W7Y6RA6W.js → chunk-WR5JFDQ5.js} +4 -4
- package/dist/{chunk-ACFGEZ4Q.js → chunk-XO753WHD.js} +3 -3
- package/dist/{chunk-5V4UPOWK.js → chunk-XPMGFAJ3.js} +1 -1
- package/dist/chunk-XPMGFAJ3.js.map +1 -0
- package/dist/{chunk-AEGXHZQT.js → chunk-YW5T6KZ5.js} +7 -7
- package/dist/{chunk-F7VBGS4C.js → chunk-ZCPSND5Y.js} +2 -2
- package/dist/chunk-ZCPSND5Y.js.map +1 -0
- package/dist/{chunk-4LRFGB3K.js → chunk-ZRPGVHTR.js} +2 -2
- package/dist/data-display/Avatar.js +3 -3
- package/dist/data-display/Badge.js +2 -2
- package/dist/data-display/CardKit.js +10 -10
- package/dist/data-display/Chart.js +10 -10
- package/dist/data-display/Chip.js +3 -3
- package/dist/data-display/DataGrid.js +2 -2
- package/dist/data-display/DataViews.js +48 -48
- package/dist/data-display/FleetMap.js +6 -5
- package/dist/native/chunks/{chunk-T6BJPP3E.js → chunk-DN73KYIZ.js} +1 -1
- package/dist/native/chunks/chunk-DN73KYIZ.js.map +1 -0
- package/dist/native/chunks/chunk-GW7TQ7HO.js +215 -0
- package/dist/native/chunks/chunk-GW7TQ7HO.js.map +1 -0
- package/dist/native/chunks/chunk-MJVSQXOD.js +9 -0
- package/dist/native/chunks/chunk-MJVSQXOD.js.map +1 -0
- package/dist/native/chunks/chunk-ZAQ3AZJM.js +292 -0
- package/dist/native/chunks/chunk-ZAQ3AZJM.js.map +1 -0
- package/dist/native/data-display/Alert.js +4 -4
- package/dist/native/data-display/Avatar.js +3 -3
- package/dist/native/data-display/Badge.js +4 -4
- package/dist/native/data-display/Chip.js +3 -5
- package/dist/native/data-display/Chip.js.map +1 -1
- package/dist/native/data-display/EmptyState.js +9 -286
- package/dist/native/data-display/EmptyState.js.map +1 -1
- package/dist/native/data-display/ErrorState.js +6 -6
- package/dist/native/data-display/FleetMap.js +498 -0
- package/dist/native/data-display/FleetMap.js.map +1 -0
- package/dist/native/data-display/LoadingState.js +3 -3
- package/dist/native/feedback/Dialog.js +3 -3
- package/dist/native/form/Checkbox.js +1 -1
- package/dist/native/form/Input.js +1 -1
- package/dist/native/form/Select.js +1 -1
- package/dist/native/form/Switch.js +1 -1
- package/dist/native/layout/Card.js +3 -3
- package/dist/native/layout/Skeleton.js +6 -209
- package/dist/native/layout/Skeleton.js.map +1 -1
- package/dist/types/components/data-display/DataGrid/DataGrid.d.ts +11 -0
- package/dist/types/components/data-display/DataGrid/DataGrid.d.ts.map +1 -1
- package/dist/types/components/data-display/FleetMap/FleetMap.base.d.ts +141 -0
- package/dist/types/components/data-display/FleetMap/FleetMap.base.d.ts.map +1 -0
- package/dist/types/components/data-display/FleetMap/FleetMap.d.ts.map +1 -1
- package/dist/types/components/data-display/FleetMap/FleetMap.helpers.d.ts +1 -1
- package/dist/types/components/data-display/FleetMap/FleetMap.helpers.d.ts.map +1 -1
- package/dist/types/components/data-display/FleetMap/FleetMap.hooks.d.ts +20 -3
- package/dist/types/components/data-display/FleetMap/FleetMap.hooks.d.ts.map +1 -1
- package/dist/types/components/data-display/FleetMap/FleetMap.metrics.d.ts +76 -0
- package/dist/types/components/data-display/FleetMap/FleetMap.metrics.d.ts.map +1 -0
- package/dist/types/components/data-display/FleetMap/FleetMap.types.d.ts +13 -113
- package/dist/types/components/data-display/FleetMap/FleetMap.types.d.ts.map +1 -1
- package/dist/types/components/data-display/FleetMap/FleetRoster.d.ts.map +1 -1
- package/dist/types/platform/aria.d.ts +13 -0
- package/dist/types/platform/aria.d.ts.map +1 -1
- package/dist/types-native/components/data-display/FleetMap/FleetMap.base.d.ts +141 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.base.d.ts.map +1 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.helpers.d.ts +45 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.helpers.d.ts.map +1 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.hooks.d.ts +71 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.hooks.d.ts.map +1 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.metrics.d.ts +76 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.metrics.d.ts.map +1 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.native.d.ts +30 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.native.d.ts.map +1 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.types.native.d.ts +36 -0
- package/dist/types-native/components/data-display/FleetMap/FleetMap.types.native.d.ts.map +1 -0
- package/dist/types-native/components/data-display/FleetMap/FleetRoster.native.d.ts +17 -0
- package/dist/types-native/components/data-display/FleetMap/FleetRoster.native.d.ts.map +1 -0
- package/dist/types-native/components/data-display/FleetMap/index.native.d.ts +4 -0
- package/dist/types-native/components/data-display/FleetMap/index.native.d.ts.map +1 -0
- package/dist/types-native/platform/aria.d.ts +13 -0
- package/dist/types-native/platform/aria.d.ts.map +1 -1
- package/dist/user-avatar.js +4 -4
- package/package.json +5 -1
- package/src/components/data-display/DataGrid/DataGrid.tsx +5 -1
- package/src/components/data-display/FleetMap/FleetMap.base.ts +144 -0
- package/src/components/data-display/FleetMap/FleetMap.helpers.ts +1 -1
- package/src/components/data-display/FleetMap/FleetMap.hooks.ts +23 -4
- package/src/components/data-display/FleetMap/FleetMap.md +15 -8
- package/src/components/data-display/FleetMap/FleetMap.metrics.ts +81 -0
- package/src/components/data-display/FleetMap/FleetMap.native.tsx +185 -0
- package/src/components/data-display/FleetMap/FleetMap.tsx +8 -14
- package/src/components/data-display/FleetMap/FleetMap.types.native.ts +44 -0
- package/src/components/data-display/FleetMap/FleetMap.types.ts +19 -114
- package/src/components/data-display/FleetMap/FleetRoster.native.tsx +382 -0
- package/src/components/data-display/FleetMap/FleetRoster.tsx +17 -11
- package/src/components/data-display/FleetMap/NATIVE-NOTES.md +14 -0
- package/src/components/data-display/FleetMap/index.native.ts +10 -0
- package/src/components/data-display/FleetMap/tests.md +6 -1
- package/src/platform/aria.ts +13 -0
- package/dist/chunk-5V4UPOWK.js.map +0 -1
- package/dist/chunk-F7VBGS4C.js.map +0 -1
- package/dist/chunk-MQNKRIGC.js.map +0 -1
- package/dist/chunk-TWQYDKCA.js.map +0 -1
- package/dist/native/chunks/chunk-T6BJPP3E.js.map +0 -1
- /package/dist/{chunk-WGRS4PGY.js.map → chunk-2C47DEVU.js.map} +0 -0
- /package/dist/{chunk-KHWCX4LI.js.map → chunk-6LGWJFQK.js.map} +0 -0
- /package/dist/{chunk-XSF6AXEU.js.map → chunk-6RZDDUSM.js.map} +0 -0
- /package/dist/{chunk-3FLVIVOK.js.map → chunk-BHIGHSXA.js.map} +0 -0
- /package/dist/{chunk-VNNG46TS.js.map → chunk-BPLFM2GL.js.map} +0 -0
- /package/dist/{chunk-LYSSO6WC.js.map → chunk-CZPU5TW3.js.map} +0 -0
- /package/dist/{chunk-BZTFPSWT.js.map → chunk-DFLTUI7W.js.map} +0 -0
- /package/dist/{chunk-URXT5TOY.js.map → chunk-E46PG577.js.map} +0 -0
- /package/dist/{chunk-DRHIIUDO.js.map → chunk-FPWB5ILF.js.map} +0 -0
- /package/dist/{chunk-R6SBP5M5.js.map → chunk-GOWBYHYW.js.map} +0 -0
- /package/dist/{chunk-N2TUX4A5.js.map → chunk-I3E2QHTH.js.map} +0 -0
- /package/dist/{chunk-4XZXLB4I.js.map → chunk-JQ45NDBS.js.map} +0 -0
- /package/dist/{chunk-I7HYGIEJ.js.map → chunk-JUHUKZGU.js.map} +0 -0
- /package/dist/{chunk-WQA4LIIK.js.map → chunk-KT6HWAEN.js.map} +0 -0
- /package/dist/{chunk-2UGJIKVA.js.map → chunk-MZQTHSLN.js.map} +0 -0
- /package/dist/{chunk-4SQFO4GW.js.map → chunk-NA7MIEG6.js.map} +0 -0
- /package/dist/{chunk-MYGNIFZC.js.map → chunk-O4VCZTK4.js.map} +0 -0
- /package/dist/{chunk-LS4FMSHY.js.map → chunk-OAYX4XN7.js.map} +0 -0
- /package/dist/{chunk-Y5UCOBQN.js.map → chunk-OGGHD64H.js.map} +0 -0
- /package/dist/{chunk-CBTY36TU.js.map → chunk-OH3YGJT5.js.map} +0 -0
- /package/dist/{chunk-VON4BFK4.js.map → chunk-PN5CQKA2.js.map} +0 -0
- /package/dist/{chunk-CF3FAAPC.js.map → chunk-RP5FK3SO.js.map} +0 -0
- /package/dist/{chunk-L4U23SBX.js.map → chunk-TSYWI3NP.js.map} +0 -0
- /package/dist/{chunk-YQ6TNHAC.js.map → chunk-TTLI6PF7.js.map} +0 -0
- /package/dist/{chunk-OIKQO73C.js.map → chunk-TX5V42AY.js.map} +0 -0
- /package/dist/{chunk-AJGYHDA5.js.map → chunk-U2YJCNRU.js.map} +0 -0
- /package/dist/{chunk-W7Y6RA6W.js.map → chunk-WR5JFDQ5.js.map} +0 -0
- /package/dist/{chunk-ACFGEZ4Q.js.map → chunk-XO753WHD.js.map} +0 -0
- /package/dist/{chunk-AEGXHZQT.js.map → chunk-YW5T6KZ5.js.map} +0 -0
- /package/dist/{chunk-4LRFGB3K.js.map → chunk-ZRPGVHTR.js.map} +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { FleetMap, default } from './FleetMap.native';
|
|
2
|
+
export { FleetRoster } from './FleetRoster.native';
|
|
3
|
+
export type {
|
|
4
|
+
FleetFreshness,
|
|
5
|
+
FleetMapBaseProps,
|
|
6
|
+
FleetMapCopy,
|
|
7
|
+
FleetMapCopyBase,
|
|
8
|
+
FleetMapProps,
|
|
9
|
+
FleetUnit,
|
|
10
|
+
} from './FleetMap.types.native';
|
|
@@ -76,4 +76,9 @@ measure the fixture rather than the component.
|
|
|
76
76
|
- [x] Lint clean
|
|
77
77
|
- [x] TypeCheck clean
|
|
78
78
|
- [x] Stories working
|
|
79
|
-
- [x] Unit tests passing
|
|
79
|
+
- [x] Unit tests passing — web 35 (16 helpers + 8 hook + 9 guards + 2 theme),
|
|
80
|
+
native 14 (`FleetMap.native.test.tsx`, run by the `Native` lane rather
|
|
81
|
+
than by `pnpm test`, so each runs once)
|
|
82
|
+
- [x] React Native build shipped — `@12-apps/ui/data-display/FleetMap` carries a
|
|
83
|
+
`react-native` export condition; the rendering gaps, the map's absence
|
|
84
|
+
among them, are in `NATIVE-NOTES.md` and counted by `pnpm native:ledger`
|
package/src/platform/aria.ts
CHANGED
|
@@ -27,6 +27,19 @@ export interface WebAria {
|
|
|
27
27
|
* it expands is a listbox — which is what the web's own trigger carries.
|
|
28
28
|
*/
|
|
29
29
|
'aria-haspopup'?: 'listbox' | 'menu' | 'dialog' | 'true';
|
|
30
|
+
/**
|
|
31
|
+
* Which option a composite widget's single tab stop is currently on — the
|
|
32
|
+
* listbox pattern, where the LIST holds focus and the selection moves inside
|
|
33
|
+
* it. React Native has no equivalent because it has no roving focus to
|
|
34
|
+
* describe: a device selects by touching the row.
|
|
35
|
+
*
|
|
36
|
+
* Note the interaction with `aria-atomic` documented on {@link webAria}:
|
|
37
|
+
* react-native-web reads THIS value when it means the atomic flag, so a
|
|
38
|
+
* component that sets both gets the descendant id emitted as `aria-atomic`.
|
|
39
|
+
* Nothing here sets both, and a component that needs to should pass its own
|
|
40
|
+
* atomic flag directly rather than through this helper.
|
|
41
|
+
*/
|
|
42
|
+
'aria-activedescendant'?: string;
|
|
30
43
|
}
|
|
31
44
|
|
|
32
45
|
/**
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/FleetMap/FleetMap.helpers.ts"],"sourcesContent":["import type { FleetFreshness, FleetUnit } from './FleetMap.types';\n\n/**\n * How current a fix is, on the two thresholds the caller supplied.\n *\n * Inclusive at both boundaries in the direction that reads better on a screen:\n * a unit exactly at the lagging threshold is already lagging. A boundary has to\n * fall somewhere, and the conservative side is the one that does not tell a\n * dispatcher somebody is live when they are on the edge of not being.\n */\nexport function freshnessOf(\n staleSeconds: number,\n laggingAfterSeconds: number,\n staleAfterSeconds: number,\n): FleetFreshness {\n if (staleSeconds >= staleAfterSeconds) return 'stale';\n if (staleSeconds >= laggingAfterSeconds) return 'lagging';\n return 'live';\n}\n\n/** A latitude/longitude pair, as the map wants its centre. */\nexport interface LatLng {\n lat: number;\n lng: number;\n}\n\n/**\n * Where the map should point.\n *\n * The selected unit when there is one — a dispatcher who clicked a name is\n * asking to look at them. Otherwise the fleet's own centroid, which keeps\n * everybody in frame without the caller having to compute a bounding box.\n *\n * `null` for an empty fleet rather than a coordinate: (0, 0) is a real place in\n * the Atlantic, and a map that quietly sails there is worse than one that says\n * it has nothing to show.\n */\nexport function mapCentre(\n units: readonly FleetUnit[],\n selectedId?: string | null,\n): LatLng | null {\n if (units.length === 0) return null;\n const selected = selectedId ? units.find((unit) => unit.id === selectedId) : undefined;\n if (selected) return { lat: selected.latitude, lng: selected.longitude };\n const sum = units.reduce(\n (acc, unit) => ({ lat: acc.lat + unit.latitude, lng: acc.lng + unit.longitude }),\n { lat: 0, lng: 0 },\n );\n return { lat: sum.lat / units.length, lng: sum.lng / units.length };\n}\n\n/**\n * The roster's order: freshest first, then alphabetically.\n *\n * Not the caller's order, deliberately. The one question this panel answers is\n * *who is moving right now*, and a list sorted by anything else buries the\n * answer under whoever happens to sort first. Ties break on the label so the\n * list does not reshuffle between polls when two units share a staleness.\n */\nexport function rosterOrder(units: readonly FleetUnit[]): FleetUnit[] {\n return [...units].sort(\n (a, b) => a.staleSeconds - b.staleSeconds || a.label.localeCompare(b.label),\n );\n}\n\n/**\n * Move the selection by one row, wrapping at both ends.\n *\n * Wrapping rather than stopping: this is a short list a dispatcher arrows\n * through repeatedly, and a selection that sticks at the last row makes the\n * keyboard path feel broken next to the mouse one.\n */\nexport function nextSelection(\n units: readonly FleetUnit[],\n currentId: string | null | undefined,\n step: 1 | -1,\n): string | null {\n if (units.length === 0) return null;\n const index = units.findIndex((unit) => unit.id === currentId);\n // Nothing selected: Down opens at the top and Up opens at the bottom, which\n // is what every listbox in the house does.\n if (index === -1) return (step === 1 ? units[0] : units[units.length - 1])?.id ?? null;\n const next = (index + step + units.length) % units.length;\n return units[next]?.id ?? null;\n}\n"],"mappings":";AAUO,SAAS,YACd,cACA,qBACA,mBACgB;AAChB,MAAI,gBAAgB,kBAAmB,QAAO;AAC9C,MAAI,gBAAgB,oBAAqB,QAAO;AAChD,SAAO;AACT;AAmBO,SAAS,UACd,OACA,YACe;AACf,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,WAAW,aAAa,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,UAAU,IAAI;AAC7E,MAAI,SAAU,QAAO,EAAE,KAAK,SAAS,UAAU,KAAK,SAAS,UAAU;AACvE,QAAM,MAAM,MAAM;AAAA,IAChB,CAAC,KAAK,UAAU,EAAE,KAAK,IAAI,MAAM,KAAK,UAAU,KAAK,IAAI,MAAM,KAAK,UAAU;AAAA,IAC9E,EAAE,KAAK,GAAG,KAAK,EAAE;AAAA,EACnB;AACA,SAAO,EAAE,KAAK,IAAI,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,MAAM,OAAO;AACpE;AAUO,SAAS,YAAY,OAA0C;AACpE,SAAO,CAAC,GAAG,KAAK,EAAE;AAAA,IAChB,CAAC,GAAG,MAAM,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,EAAE,KAAK;AAAA,EAC5E;AACF;AASO,SAAS,cACd,OACA,WACA,MACe;AACf,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,QAAQ,MAAM,UAAU,CAAC,SAAS,KAAK,OAAO,SAAS;AAG7D,MAAI,UAAU,GAAI,SAAQ,SAAS,IAAI,MAAM,CAAC,IAAI,MAAM,MAAM,SAAS,CAAC,IAAI,MAAM;AAClF,QAAM,QAAQ,QAAQ,OAAO,MAAM,UAAU,MAAM;AACnD,SAAO,MAAM,IAAI,GAAG,MAAM;AAC5B;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/FleetMap/FleetMap.hooks.ts"],"sourcesContent":["import React from 'react';\n\nimport { mapCentre, nextSelection, rosterOrder, type LatLng } from './FleetMap.helpers';\nimport type { FleetUnit } from './FleetMap.types';\n\n/**\n * Everything the board derives from its props, in one hook.\n *\n * Split out of the component so the component is layout and nothing else.\n *\n * The memos are honest about what they buy, which is less than it looks. `units`\n * arrives as a fresh array on every poll, so `ordered` and `markers` are\n * recomputed on every poll whatever these do — a new array is a new identity.\n * What they buy is stability across the renders a poll does NOT cause: a parent\n * re-render, a selection change (which re-runs `centre` but not `ordered`), a\n * theme flip. Sorting a fleet is cheap either way; this is tidiness, not a\n * measured win, and it should not be read as one.\n *\n * `centre` is the exception, and there it is load-bearing rather than tidy —\n * see {@link useFleetMap}.\n *\n * ## Selection works with, without, or half-way through a handler\n *\n * `selectedId` and `onSelect` are both optional, so all four combinations\n * arrive. Left purely controlled, the two useful ones broke: with neither prop\n * the roster was a listbox that answered nothing, and arrowing it called\n * `preventDefault` before a no-op `select`, so the keys did not move a\n * selection AND did not scroll the page either.\n *\n * So the hook keeps its own selection and defers to `selectedId` only when the\n * caller passes one — the ordinary controlled/uncontrolled split, the same one\n * an `<input>` makes. `onSelect` still fires either way, so a consumer can\n * observe the selection without owning it.\n */\n\n/** One pin, in the shape `MapPreview` takes. */\nexport interface FleetMarker {\n position: LatLng;\n title: string;\n onClick: () => void;\n}\n\nexport interface FleetMapState {\n /** The units, freshest first — see {@link rosterOrder}. */\n ordered: FleetUnit[];\n /**\n * The selection in force — the caller's when they own it, this hook's when\n * they do not. Everything downstream reads THIS and never the raw prop.\n */\n active: string | null;\n /** Where the map points, or `null` when there is nothing to show. */\n centre: LatLng | null;\n select: (id: string) => void;\n markers: FleetMarker[];\n onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n}\n\n/** Which way an arrow moves the selection, or `0` when it is not ours. */\nfunction stepFor(event: React.KeyboardEvent<HTMLDivElement>): 1 | -1 | 0 {\n // A modified arrow is the PAGE's shortcut, not the roster's: Cmd+Down is\n // \"scroll to the end of the document\" on macOS, and Shift+Arrow extends a\n // selection. Capturing them would take a browser gesture away.\n if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return 0;\n if (event.key === 'ArrowDown') return 1;\n if (event.key === 'ArrowUp') return -1;\n return 0;\n}\n\n/**\n * Who owns the selection, decided ONCE on the first render.\n *\n * Exactly as React decides it for an `<input>`, and for the same reason.\n * `selectedId` is typed `string | null | undefined`, so `useState<string>()` —\n * the natural shape for a caller — hands us `undefined` on the first render and\n * again on every reset. Re-deriving the mode from the prop each time would make\n * the component change its own semantics mid-life: it starts uncontrolled,\n * silently becomes controlled the moment the caller's `onSelect` writes a value\n * back, and then silently un-becomes it when they clear — resurrecting whatever\n * was selected before the hand-off.\n *\n * Latched, `undefined` is unambiguous within each mode: to a controlled board it\n * is \"nobody is selected\", so clearing the caller's state clears the roster; to\n * an uncontrolled one it is simply the prop staying absent. A caller who wants\n * control passes `string | null` from the first render.\n */\nfunction useSelection(\n selectedId: string | null | undefined,\n onSelect: ((id: string) => void) | undefined,\n): { active: string | null; select: (id: string) => void; controlled: boolean } {\n const controlledRef = React.useRef(selectedId !== undefined);\n const controlled = controlledRef.current;\n const [own, setOwn] = React.useState<string | null>(null);\n\n const select = React.useCallback(\n (id: string) => {\n if (!controlled) setOwn(id);\n onSelect?.(id);\n },\n [controlled, onSelect],\n );\n\n return { active: controlled ? (selectedId ?? null) : own, select, controlled };\n}\n\nexport function useFleetMap(\n units: readonly FleetUnit[],\n selectedId: string | null | undefined,\n onSelect: ((id: string) => void) | undefined,\n): FleetMapState {\n const ordered = React.useMemo(() => rosterOrder(units), [units]);\n const { active, select, controlled } = useSelection(selectedId, onSelect);\n\n // Memoised on the COORDINATES and not the object, because `mapCentre` builds\n // a fresh literal every call and `MapPreview` re-centres from an effect keyed\n // on `center`'s identity. A poll that changes nothing would otherwise hand it\n // a new object, and the map would jump back to the centroid — throwing away a\n // pan the dispatcher had just made, on a timer, for as long as they watch it.\n //\n // ROUNDED, because keying on the raw numbers was not enough. `mapCentre`\n // sums over `ordered`, and floating-point addition is not associative — so\n // re-sorting the SAME coordinates can change the centroid's last bit.\n // `rosterOrder` sorts on `staleSeconds`, which moves on every poll, so rank\n // swaps are the ordinary case for a fleet that is standing still: measured\n // over 20k random three-rider fleets, a single adjacent swap changed the\n // centroid 17% of the time. Six decimals is ~11cm, far finer than a map\n // centre resolves and far coarser than the noise.\n const raw = mapCentre(ordered, active);\n const lat = raw === null ? null : Math.round(raw.lat * 1e6) / 1e6;\n const lng = raw === null ? null : Math.round(raw.lng * 1e6) / 1e6;\n const centre = React.useMemo(\n () => (lat === null || lng === null ? null : { lat, lng }),\n [lat, lng],\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n const step = stepFor(event);\n if (step === 0) return;\n // Nothing can move a CONTROLLED selection without a handler, so the keys\n // belong to the page. Swallowing them there left a keyboard user unable to\n // move the selection and unable to scroll past the roster.\n if (controlled && !onSelect) return;\n const next = nextSelection(ordered, active, step);\n // `preventDefault` only once a move is certain, so the page still scrolls\n // with the arrow keys when the list is empty and there is nothing to move.\n if (next === null) return;\n event.preventDefault();\n select(next);\n },\n [ordered, active, select, controlled, onSelect],\n );\n\n const markers = React.useMemo(\n () =>\n ordered.map((unit) => ({\n position: { lat: unit.latitude, lng: unit.longitude },\n title: unit.label,\n onClick: () => select(unit.id),\n })),\n [ordered, select],\n );\n\n return { ordered, active, centre, select, markers, onKeyDown };\n}\n"],"mappings":";;;;;;;AAAA,OAAO,WAAW;AA0DlB,SAAS,QAAQ,OAAwD;AAIvE,MAAI,MAAM,UAAU,MAAM,WAAW,MAAM,WAAW,MAAM,SAAU,QAAO;AAC7E,MAAI,MAAM,QAAQ,YAAa,QAAO;AACtC,MAAI,MAAM,QAAQ,UAAW,QAAO;AACpC,SAAO;AACT;AAmBA,SAAS,aACP,YACA,UAC8E;AAC9E,QAAM,gBAAgB,MAAM,OAAO,eAAe,MAAS;AAC3D,QAAM,aAAa,cAAc;AACjC,QAAM,CAAC,KAAK,MAAM,IAAI,MAAM,SAAwB,IAAI;AAExD,QAAM,SAAS,MAAM;AAAA,IACnB,CAAC,OAAe;AACd,UAAI,CAAC,WAAY,QAAO,EAAE;AAC1B,iBAAW,EAAE;AAAA,IACf;AAAA,IACA,CAAC,YAAY,QAAQ;AAAA,EACvB;AAEA,SAAO,EAAE,QAAQ,aAAc,cAAc,OAAQ,KAAK,QAAQ,WAAW;AAC/E;AAEO,SAAS,YACd,OACA,YACA,UACe;AACf,QAAM,UAAU,MAAM,QAAQ,MAAM,YAAY,KAAK,GAAG,CAAC,KAAK,CAAC;AAC/D,QAAM,EAAE,QAAQ,QAAQ,WAAW,IAAI,aAAa,YAAY,QAAQ;AAgBxE,QAAM,MAAM,UAAU,SAAS,MAAM;AACrC,QAAM,MAAM,QAAQ,OAAO,OAAO,KAAK,MAAM,IAAI,MAAM,GAAG,IAAI;AAC9D,QAAM,MAAM,QAAQ,OAAO,OAAO,KAAK,MAAM,IAAI,MAAM,GAAG,IAAI;AAC9D,QAAM,SAAS,MAAM;AAAA,IACnB,MAAO,QAAQ,QAAQ,QAAQ,OAAO,OAAO,EAAE,KAAK,IAAI;AAAA,IACxD,CAAC,KAAK,GAAG;AAAA,EACX;AAEA,QAAM,YAAY,MAAM;AAAA,IACtB,CAAC,UAA+C;AAC9C,YAAM,OAAO,QAAQ,KAAK;AAC1B,UAAI,SAAS,EAAG;AAIhB,UAAI,cAAc,CAAC,SAAU;AAC7B,YAAM,OAAO,cAAc,SAAS,QAAQ,IAAI;AAGhD,UAAI,SAAS,KAAM;AACnB,YAAM,eAAe;AACrB,aAAO,IAAI;AAAA,IACb;AAAA,IACA,CAAC,SAAS,QAAQ,QAAQ,YAAY,QAAQ;AAAA,EAChD;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB,MACE,QAAQ,IAAI,CAAC,UAAU;AAAA,MACrB,UAAU,EAAE,KAAK,KAAK,UAAU,KAAK,KAAK,UAAU;AAAA,MACpD,OAAO,KAAK;AAAA,MACZ,SAAS,MAAM,OAAO,KAAK,EAAE;AAAA,IAC/B,EAAE;AAAA,IACJ,CAAC,SAAS,MAAM;AAAA,EAClB;AAEA,SAAO,EAAE,SAAS,QAAQ,QAAQ,QAAQ,SAAS,UAAU;AAC/D;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/FleetMap/FleetRoster.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport Stack from '@mui/material/Stack/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport { useTheme, type SxProps, type Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { Skeleton } from '../../layout/Skeleton/Skeleton';\n\nimport { freshnessOf } from './FleetMap.helpers';\nimport type { FleetFreshness, FleetMapCopy, FleetUnit } from './FleetMap.types';\n\n/**\n * The roster beside the map — and the ACCESSIBLE half of this component.\n *\n * A map is a picture of positions and a screen reader cannot read a picture, so\n * the panel's information is carried by this list. That is not a consolation\n * prize: the list answers the questions a dispatcher actually asks — who is\n * reporting, how recently, and what are they carrying — and it answers them in\n * an order the map cannot express.\n *\n * The map beside it is a NAMED LANDMARK rather than an `aria-hidden` one, so a\n * reader can skip it in one gesture — see `FleetCanvas`, which carries the\n * reason it must not be hidden.\n */\n\n/** Each freshness draws from the semantic palette, never a hardcoded hex. */\nfunction freshnessColor(freshness: FleetFreshness, theme: Theme): string {\n if (freshness === 'live') return theme.palette.success.main;\n if (freshness === 'lagging') return theme.palette.warning.main;\n return theme.palette.text.disabled;\n}\n\n/**\n * A row's two lines of text — the name, and the one-line summary under it.\n *\n * Split out of `FleetRow` to keep that function under the size gate, and the\n * seam is the natural one: everything here is READ, nothing is interactive.\n */\nfunction RowText({\n unit,\n copy,\n freshness,\n testId,\n}: Pick<RowProps, 'unit' | 'copy' | 'freshness' | 'testId'>): React.JSX.Element {\n const theme = useTheme();\n\n // Dropped rather than rendered empty: a phone that reports no accuracy radius\n // would otherwise print a trailing separator with nothing after it.\n const meta = [\n copy.freshness[freshness],\n copy.lastSeen(unit.staleSeconds),\n unit.accuracyM == null ? null : copy.accuracy(unit.accuracyM),\n ]\n .filter((part): part is string => Boolean(part))\n .join(' · ');\n\n return (\n <Stack sx={{ minWidth: 0, flexGrow: 1 }}>\n <Typography\n variant=\"body2\"\n component=\"p\"\n data-testid={`${testId}-label`}\n sx={{ fontWeight: theme.typography.fontWeightMedium, overflowWrap: 'anywhere' }}\n >\n {unit.label}\n </Typography>\n <Typography\n variant=\"caption\"\n component=\"p\"\n data-testid={`${testId}-meta`}\n sx={{ color: theme.palette.text.secondary }}\n >\n {meta}\n </Typography>\n </Stack>\n );\n}\n\n/**\n * Keep the selected row visible INSIDE the roster.\n *\n * The roster caps its own height, so a dispatcher arrowing through a fleet of\n * thirty otherwise walks the selection below the fold with nothing moving —\n * `aria-activedescendant` follows, and a SIGHTED keyboard user sees no feedback\n * at all.\n *\n * Watches the row's POSITION as well as its selectedness, because the roster\n * re-sorts on every poll: a rider who stays selected while their staleness\n * moves them down the list would otherwise slide out of view with the effect\n * never re-running.\n *\n * Arithmetic rather than `scrollIntoView({ block: 'nearest' })` because that\n * walks every scrollable ancestor, the document included, and `nearest` only\n * spares an ancestor the row is already visible in — which is exactly false\n * when the panel is off screen. Re-running on every re-sort, it would yank the\n * whole page back to the map on a timer, and drag it there on mount for any\n * board rendered below the fold with a selection already set.\n */\nfunction useKeptInView(\n selected: boolean,\n position: number,\n): React.RefObject<HTMLDivElement | null> {\n const row = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n const box = row.current;\n const list = box?.parentElement;\n if (!selected || !box || !list) return;\n const top = box.offsetTop - list.offsetTop;\n if (top < list.scrollTop) {\n list.scrollTop = top;\n } else if (top + box.offsetHeight > list.scrollTop + list.clientHeight) {\n list.scrollTop = top + box.offsetHeight - list.clientHeight;\n }\n }, [selected, position]);\n\n return row;\n}\n\n/**\n * A row's own box, selected or not.\n *\n * Out of line because the hover rule needs the explanation more than the JSX\n * has room for: hover must never WEAKEN the selection. Written flat as\n * `'&:hover': { backgroundColor: action.hover }`, the pseudo-class wins on\n * specificity and repaints the selected row with the hover tint — measured at\n * `rgba(0,0,0,0.08)` idle against `rgba(0,0,0,0.04)` hovered, so pointing at\n * the selected row visually DESELECTED it and made it indistinguishable from\n * any other row under the cursor.\n */\nfunction rowSx(theme: Theme, selected: boolean): SxProps<Theme> {\n const resting = selected ? theme.palette.action.selected : 'transparent';\n return {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1.5),\n padding: theme.spacing(1, 1.5),\n // `1`, not `theme.shape.borderRadius / 4`. MUI's `sx` reads a NUMBER here as\n // a multiple of `theme.shape.borderRadius`, so dividing the theme value out\n // and letting `sx` multiply it back in is quadratic: measured 4px on the\n // default theme — which is why this looked right — and 36px on a host that\n // sets its own radius to 12. In a package whose whole premise is being\n // re-themed, only the multiple is portable.\n borderRadius: 1,\n cursor: 'pointer',\n backgroundColor: resting,\n '&:hover': {\n backgroundColor: selected ? theme.palette.action.selected : theme.palette.action.hover,\n },\n };\n}\n\ninterface RowProps {\n unit: FleetUnit;\n copy: FleetMapCopy;\n freshness: FleetFreshness;\n selected: boolean;\n onSelect: (id: string) => void;\n /** The row's DOM id, which `aria-activedescendant` points at. */\n optionId: string;\n /** Where the row sits in the re-sorted roster — see the scroll effect. */\n position: number;\n testId: string;\n}\n\nfunction FleetRow({\n unit,\n copy,\n freshness,\n selected,\n onSelect,\n optionId,\n position,\n testId,\n}: RowProps): React.JSX.Element {\n const theme = useTheme();\n const color = freshnessColor(freshness, theme);\n const row = useKeptInView(selected, position);\n\n return (\n <Box\n ref={row}\n role=\"option\"\n aria-selected={selected}\n id={optionId}\n data-testid={testId}\n data-freshness={freshness}\n onClick={() => onSelect(unit.id)}\n sx={rowSx(theme, selected)}\n >\n {/* The dot repeats what the freshness word beside it already says, so it\n is hidden rather than read twice — and colour is never the only\n carrier of the state. */}\n <Box\n aria-hidden=\"true\"\n data-testid={`${testId}-dot`}\n sx={{\n width: 10,\n height: 10,\n borderRadius: '50%',\n flexShrink: 0,\n backgroundColor: color,\n }}\n />\n <RowText unit={unit} copy={copy} freshness={freshness} testId={testId} />\n {unit.badge && (\n <Typography\n variant=\"caption\"\n component=\"span\"\n data-testid={`${testId}-badge`}\n sx={{ color: theme.palette.text.secondary, flexShrink: 0 }}\n >\n {unit.badge}\n </Typography>\n )}\n </Box>\n );\n}\n\n/**\n * What stands in for the roster before any unit has landed.\n *\n * `aria-hidden`, because three grey bars are not information — the panel's\n * `aria-busy` and its optional announcement carry that. The radius is the same\n * MULTIPLE a real row uses rather than a raw pixel count: `Skeleton` forwards\n * the number into `sx`, where it scales with `theme.shape.borderRadius`, so a\n * literal `4` drew 16px against the rows' 4px on the default theme and 48px\n * against 12px on a re-themed host.\n */\nfunction FirstLoad({ testId }: { testId: string }): React.JSX.Element {\n return (\n <Stack spacing={1} data-testid={`${testId}-skeleton`} aria-hidden=\"true\">\n {[0, 1, 2].map((row) => (\n <Skeleton key={row} variant=\"rectangular\" height={44} borderRadius={1} />\n ))}\n </Stack>\n );\n}\n\nexport interface FleetRosterProps {\n units: readonly FleetUnit[];\n copy: FleetMapCopy;\n selectedId: string | null | undefined;\n onSelect: (id: string) => void;\n laggingAfterSeconds: number;\n staleAfterSeconds: number;\n loading: boolean;\n testId: string;\n onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n}\n\nexport function FleetRoster({\n units,\n copy,\n selectedId,\n onSelect,\n laggingAfterSeconds,\n staleAfterSeconds,\n loading,\n testId,\n onKeyDown,\n}: FleetRosterProps): React.JSX.Element {\n const theme = useTheme();\n // Generated, not derived from `dataTestId`: two boards on one page filtered\n // from the same fleet would otherwise emit duplicate DOM ids and an ambiguous\n // `aria-activedescendant`. Same reason the heading uses one.\n const rowIdPrefix = React.useId();\n const optionId = (id: string): string => `${rowIdPrefix}-${id}`;\n\n // The selection is CONTROLLED, so it can name a unit that has since dropped\n // out of the freshness window — a courier ending their shift mid-poll. The\n // attribute must then be absent rather than pointing at an element that no\n // longer renders, which announces nothing and fails an axe audit. `mapCentre`\n // and `nextSelection` both handle the same case; this is the third place.\n const active = units.some((unit) => unit.id === selectedId) ? selectedId : null;\n\n // Only when there is nothing to show yet. Unmounting a populated listbox on\n // every poll destroys the focus inside it, so a dispatcher arrowing through\n // the roster is thrown back to the top of the page each time the data\n // refreshes — and the rows they were reading vanish and return.\n if (loading && units.length === 0) return <FirstLoad testId={testId} />;\n\n return (\n <Box\n role=\"listbox\"\n // A single tab stop with arrow keys inside, which is the listbox pattern:\n // a dispatcher tabbing past a fleet of thirty should not have to press it\n // thirty times to reach the map.\n tabIndex={0}\n aria-label={copy.rosterLabel}\n aria-activedescendant={active ? optionId(active) : undefined}\n data-testid={`${testId}-roster`}\n onKeyDown={onKeyDown}\n sx={{\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(0.5),\n overflowY: 'auto',\n minWidth: 0,\n '&:focus-visible': {\n outline: `2px solid ${theme.palette.primary.main}`,\n outlineOffset: 2,\n },\n }}\n >\n {units.map((unit, index) => (\n <FleetRow\n key={unit.id}\n unit={unit}\n copy={copy}\n freshness={freshnessOf(unit.staleSeconds, laggingAfterSeconds, staleAfterSeconds)}\n selected={unit.id === active}\n onSelect={onSelect}\n optionId={optionId(unit.id)}\n position={index}\n testId={`${testId}-${unit.id}`}\n />\n ))}\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/FleetMap/FleetMap.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport { useTheme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { SR_ONLY_SX } from '../../form/Label/Label.styles';\nimport { EmptyState } from '../EmptyState/EmptyState';\n\nimport { FleetBody } from './FleetBody';\nimport { useFleetMap } from './FleetMap.hooks';\nimport type { FleetMapProps } from './FleetMap.types';\n\n/**\n * How long a fix stays `live`, and then `lagging`, when the caller says nothing.\n *\n * Ninety seconds and five minutes: sized for a phone reporting every twenty\n * seconds, which is the densest cadence a battery tolerates all day. They are\n * DEFAULTS and not a rule — a fleet on five-minute trackers passes its own, and\n * the props exist so it can.\n */\nconst DEFAULT_LAGGING_AFTER_SECONDS = 90;\nconst DEFAULT_STALE_AFTER_SECONDS = 300;\n\n/** The map's own default height. Overridable, and never a hardcoded viewport. */\nconst DEFAULT_HEIGHT = '420px';\n\n/**\n * The reload announcement, and nothing else.\n *\n * `aria-busy` marks the region stale for assistive technology but UTTERS\n * nothing, and the skeletons beside it are `aria-hidden` — so a screen-reader\n * user got silence while the roster reloaded. This is the utterance.\n *\n * Optional by design: the sentence is in the reader's language, which only the\n * caller knows, and inventing an English one in a component whose whole premise\n * is that every word arrives through `copy` would be the bug it exists to\n * avoid. No `copy.loading`, no region.\n *\n * ## The node outlives the text, and that is the whole trick\n *\n * A live region has to be in the DOM BEFORE its content changes; a screen\n * reader watches an existing region for mutations rather than announcing one\n * that appears already populated. Mounting region-and-text together in a single\n * commit is the classic silent case — it satisfies a DOM assertion while\n * announcing nothing. So the region mounts as soon as the caller offers the\n * copy and merely EMPTIES when idle, which is what `CepField`, `UploadButton`\n * and `DateRangePickerFields` all do.\n */\nfunction FleetStatus({\n announcement,\n loading,\n testId,\n}: {\n announcement: string | undefined;\n loading: boolean;\n testId: string;\n}): React.JSX.Element | null {\n // No copy, no region at all — the caller has not asked for an announcement.\n if (announcement === undefined) return null;\n return (\n <Box role=\"status\" aria-live=\"polite\" data-testid={`${testId}-status`} sx={SR_ONLY_SX}>\n {loading ? announcement : ''}\n </Box>\n );\n}\n\n/**\n * FleetMap — where a set of tracked units is right now, as a map beside the\n * roster that reads it.\n *\n * Product-free by construction: it draws labelled dots with a freshness, and\n * every word — the heading, the three freshness states, how a duration and a\n * distance are written — arrives through {@link FleetMapProps.copy}. Couriers,\n * service vans and field engineers are all the same picture.\n *\n * ## Three decisions worth knowing before changing it\n *\n * **The roster is the accessible representation, not a sidebar.** A map is a\n * picture and a screen reader cannot read one, so the list carries the\n * information: everything a sighted user gets from a pin — who, how recently,\n * what they are carrying — is on the row. The map is a NAMED region rather than\n * an `aria-hidden` one, because its controls are focusable and hiding a\n * focusable subtree is the `aria-hidden-focus` violation.\n *\n * **Nothing here formats.** `lastSeen` and `accuracy` are functions on the copy\n * object, the same line `StatCard` holds for its `value`: a duration and a\n * distance are locale rules, and a component that wrote \"2 min ago\" would have\n * made English the only language it could ever render.\n *\n * **An empty fleet is an empty STATE, not an empty map.** A map with no pins is\n * indistinguishable from a map that failed to load, and the difference is the\n * whole question a dispatcher is asking.\n */\nexport const FleetMap: React.FC<FleetMapProps> = React.memo(\n ({\n units,\n copy,\n // NO default. `undefined` is what tells the hook the caller does not own\n // the selection; defaulting it to `null` here made every render look\n // controlled and the uncontrolled path unreachable.\n selectedId,\n onSelect,\n laggingAfterSeconds = DEFAULT_LAGGING_AFTER_SECONDS,\n staleAfterSeconds = DEFAULT_STALE_AFTER_SECONDS,\n height = DEFAULT_HEIGHT,\n loading = false,\n className,\n dataTestId,\n }) => {\n const theme = useTheme();\n const testId = dataTestId || 'fleet-map';\n const headingId = React.useId();\n const { ordered, active, centre, select, markers, onKeyDown } = useFleetMap(\n units,\n selectedId,\n onSelect,\n );\n\n return (\n <Box\n role=\"region\"\n aria-labelledby={headingId}\n className={className}\n data-testid={testId}\n sx={{ display: 'flex', flexDirection: 'column', gap: theme.spacing(1.5), minWidth: 0 }}\n >\n <Typography\n id={headingId}\n variant=\"subtitle1\"\n component=\"h2\"\n data-testid={`${testId}-title`}\n sx={{ fontWeight: theme.typography.fontWeightMedium }}\n >\n {copy.title}\n </Typography>\n\n <FleetStatus announcement={copy.loading} loading={loading} testId={testId} />\n\n {!loading && ordered.length === 0 ? (\n <EmptyState\n variant=\"minimal\"\n title={copy.emptyTitle}\n description={copy.emptyDescription}\n dataTestId={`${testId}-empty`}\n />\n ) : (\n <FleetBody\n ordered={ordered}\n centre={centre}\n markers={markers}\n select={select}\n onKeyDown={onKeyDown}\n copy={copy}\n selectedId={active}\n laggingAfterSeconds={laggingAfterSeconds}\n staleAfterSeconds={staleAfterSeconds}\n height={height}\n loading={loading}\n testId={testId}\n />\n )}\n </Box>\n );\n },\n);\n\nFleetMap.displayName = 'FleetMap';\n\nexport default FleetMap;\n"],"mappings":";;;;;;;;;;;;;;AAAA,OAAO,SAAS;AAChB,OAAO,gBAAgB;AACvB,SAAS,gBAAgB;AACzB,OAAO,WAAW;AAyDd,cA2DE,YA3DF;AAxCJ,IAAM,gCAAgC;AACtC,IAAM,8BAA8B;AAGpC,IAAM,iBAAiB;AAwBvB,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,GAI6B;AAE3B,MAAI,iBAAiB,OAAW,QAAO;AACvC,SACE,oBAAC,OAAI,MAAK,UAAS,aAAU,UAAS,eAAa,GAAG,MAAM,WAAW,IAAI,YACxE,oBAAU,eAAe,IAC5B;AAEJ;AA6BO,IAAM,WAAoC,MAAM;AAAA,EACrD,CAAC;AAAA,IACC;AAAA,IACA;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,oBAAoB;AAAA,IACpB,SAAS;AAAA,IACT,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF,MAAM;AACJ,UAAM,QAAQ,SAAS;AACvB,UAAM,SAAS,cAAc;AAC7B,UAAM,YAAY,MAAM,MAAM;AAC9B,UAAM,EAAE,SAAS,QAAQ,QAAQ,QAAQ,SAAS,UAAU,IAAI;AAAA,MAC9D;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,mBAAiB;AAAA,QACjB;AAAA,QACA,eAAa;AAAA,QACb,IAAI,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,MAAM,QAAQ,GAAG,GAAG,UAAU,EAAE;AAAA,QAErF;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI;AAAA,cACJ,SAAQ;AAAA,cACR,WAAU;AAAA,cACV,eAAa,GAAG,MAAM;AAAA,cACtB,IAAI,EAAE,YAAY,MAAM,WAAW,iBAAiB;AAAA,cAEnD,eAAK;AAAA;AAAA,UACR;AAAA,UAEA,oBAAC,eAAY,cAAc,KAAK,SAAS,SAAkB,QAAgB;AAAA,UAE1E,CAAC,WAAW,QAAQ,WAAW,IAC9B;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,OAAO,KAAK;AAAA,cACZ,aAAa,KAAK;AAAA,cAClB,YAAY,GAAG,MAAM;AAAA;AAAA,UACvB,IAEA;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,YAAY;AAAA,cACZ;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA;AAAA,UACF;AAAA;AAAA;AAAA,IAEJ;AAAA,EAEJ;AACF;AAEA,SAAS,cAAc;AAEvB,IAAO,mBAAQ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/platform/aria.ts"],"sourcesContent":["/**\n * WEB ATTRIBUTES REACT NATIVE DOES NOT DECLARE.\n *\n * React Native's `ViewProps` (0.83) type a dozen `aria-*` props and stop:\n * `aria-level`, `aria-atomic` and `aria-describedby` are not among them.\n * react-native-web forwards all three to the DOM — and `aria-level` also\n * picks the heading tag, so a `role=\"heading\"` at level 3 renders an `<h3>`\n * exactly as the MUI half's `component=\"h3\"` does — while a device ignores\n * them. A native component spreads them through here rather than widening\n * the element's props type or casting at every call site.\n */\nexport interface WebAria {\n 'aria-level'?: 1 | 2 | 3 | 4 | 5 | 6;\n 'aria-atomic'?: 'true' | 'false';\n 'aria-describedby'?: string;\n /**\n * A field's validity and whether it must be filled. React Native's\n * `accessibilityState` has neither — its five flags are disabled, selected,\n * checked, busy and expanded — so these are web-only, and a device reads the\n * error text instead. react-native-web forwards both.\n */\n 'aria-invalid'?: boolean;\n 'aria-required'?: boolean;\n /**\n * What a control pops open. React Native has `aria-expanded` and no\n * `aria-haspopup`, so a `combobox` needs this one from here to say the thing\n * it expands is a listbox — which is what the web's own trigger carries.\n */\n 'aria-haspopup'?: 'listbox' | 'menu' | 'dialog' | 'true';\n}\n\n/**\n * `attrs` as something JSX can spread onto a react-native element without\n * that element's props type having to know them.\n *\n * `aria-atomic` travels under react-native-web's own older name. Its\n * `createDOMProps` (0.20.0) reads `aria-activedescendant` where it means\n * `aria-atomic` — `_ariaAtomic = ariaAtomic != null ? ariaActiveDescendant :\n * accessibilityAtomic` — so the modern spelling never reaches the DOM and\n * the legacy one, still accepted and still emitted as `aria-atomic`, does.\n * Neither name exists on a device, so nothing is lost there.\n */\nexport function webAria(attrs: WebAria): object {\n const { 'aria-atomic': atomic, ...rest } = attrs;\n return atomic === undefined ? rest : { ...rest, accessibilityAtomic: atomic };\n}\n\n/**\n * `disabled` for a react-native-web form control.\n *\n * React Native's `TextInput` has `editable` and no `disabled`, because a\n * device has no disabled state to speak of — an uneditable field simply does\n * not take the keyboard. The DOM does: only a real `disabled` attribute takes\n * an `<input>` out of the tab order, and it is what `toBeDisabled()` reads. So\n * a native field spreads this ALONGSIDE `editable={false}` and\n * `aria-disabled`: react-native-web puts the attribute on the input, and a\n * device ignores a prop it does not know.\n */\nexport function webDisabled(disabled: boolean): object {\n return disabled ? { disabled: true } : {};\n}\n\n/**\n * `onClick` for a react-native-web element whose React Native twin has no\n * click of its own.\n *\n * react-native-web's `TextInput` forwards `onClick` to the DOM input and drops\n * `onPressIn`; a device does the opposite. A field that must answer a press on\n * both renderers spreads this ALONGSIDE `onPressIn`, and exactly one of the two\n * fires on each side — never both.\n */\nexport function webClick<E>(handler: ((event: E) => void) | undefined): object {\n return handler === undefined ? {} : { onClick: handler };\n}\n\n/**\n * A `role` React Native's own union does not carry.\n *\n * `Role` (0.83) stops at `list` and `menu`: `listbox`, the role a dropdown's\n * option list owes a screen reader, is not in it. react-native-web forwards\n * whatever string it is given, and a device ignores a role it does not know —\n * the same trade `webAria` makes for the attributes below it.\n */\nexport function webRole(role: string): object {\n return { role };\n}\n\n/** The half of a DOM keyboard event a component needs, without the DOM lib. */\nexport interface WebKeyEvent {\n key: string;\n}\n\n/**\n * `onKeyDown` for a react-native-web element.\n *\n * React Native has no key events on a `View` — a phone has no keyboard to\n * press Escape on — so nothing here is typed by react-native and nothing here\n * fires on a device. react-native-web forwards it to the DOM, which is what\n * lets a native dropdown still answer Escape and the arrow keys in a browser.\n */\nexport function webKeyDown(handler: (event: WebKeyEvent) => void): object {\n return { onKeyDown: handler };\n}\n"],"mappings":";AA0CO,SAAS,QAAQ,OAAwB;AAC9C,QAAM,EAAE,eAAe,QAAQ,GAAG,KAAK,IAAI;AAC3C,SAAO,WAAW,SAAY,OAAO,EAAE,GAAG,MAAM,qBAAqB,OAAO;AAC9E;AAaO,SAAS,YAAY,UAA2B;AACrD,SAAO,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;AAC1C;AAWO,SAAS,SAAY,SAAmD;AAC7E,SAAO,YAAY,SAAY,CAAC,IAAI,EAAE,SAAS,QAAQ;AACzD;AAUO,SAAS,QAAQ,MAAsB;AAC5C,SAAO,EAAE,KAAK;AAChB;AAeO,SAAS,WAAW,SAA+C;AACxE,SAAO,EAAE,WAAW,QAAQ;AAC9B;","names":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|