@12-apps/ui 6.98.1 → 6.99.0
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/dist/_internal/components/data-display/Alert/Alert.content.js +2 -2
- package/dist/_internal/components/data-display/Alert/Alert.js +3 -3
- package/dist/_internal/components/data-display/CardKit/card-actions-context.js +4 -4
- package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +5 -5
- package/dist/_internal/components/data-display/Carousel/Carousel.js +2 -2
- package/dist/_internal/components/data-display/Chart/SpecChart.js +3 -3
- package/dist/_internal/components/data-display/DataGrid/DataGrid.js +1 -1
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +17 -17
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +22 -22
- package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +1 -1
- package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +1 -1
- package/dist/_internal/components/data-display/DataViews/base-list-card-slots.js +1 -1
- package/dist/_internal/components/data-display/DataViews/base-list-card.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +14 -14
- 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 +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +4 -4
- package/dist/_internal/components/data-display/DataViews/list-card-rails.js +1 -1
- package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
- package/dist/_internal/components/data-display/DayRuler/DayRuler.footer.js +2 -2
- package/dist/_internal/components/data-display/DayRuler/DayRuler.js +7 -7
- package/dist/_internal/components/data-display/DayRuler/DayRuler.keyboard.js +2 -2
- package/dist/_internal/components/data-display/DayRuler/DayRuler.parts.js +3 -3
- package/dist/_internal/components/data-display/DayRuler/useRulerDrag.js +2 -2
- package/dist/_internal/components/data-display/FleetMap/FleetBody.js +6 -6
- package/dist/_internal/components/data-display/FleetMap/FleetCanvas.js +5 -5
- package/dist/_internal/components/data-display/FleetMap/FleetMap.js +7 -7
- package/dist/_internal/components/data-display/HoverCard/HoverCard.js +2 -2
- package/dist/_internal/components/data-display/Lightbox/Lightbox.js +3 -3
- package/dist/_internal/components/data-display/MapPreview/MapLayers.js +2 -2
- package/dist/_internal/components/data-display/MapPreview/MapPreview.js +4 -4
- package/dist/_internal/components/data-display/Progress/Progress.js +2 -2
- package/dist/_internal/components/data-display/RegionMap/RegionMap.fixtures.js +1 -1
- package/dist/_internal/components/data-display/RegionMap/RegionMap.helpers.js +3 -1
- package/dist/_internal/components/data-display/RegionMap/RegionMap.hooks.js +3 -3
- package/dist/_internal/components/data-display/RegionMap/RegionMap.js +6 -5
- package/dist/_internal/components/data-display/RegionMap/RegionMap.view.js +2 -2
- package/dist/_internal/components/data-display/RegionMap/RegionMapDrawing.js +3 -2
- package/dist/_internal/components/data-display/RegionMap/RegionMapPin.js +26 -0
- package/dist/_internal/components/data-display/index.js +51 -45
- package/dist/_internal/components/feedback/NotificationStatusNotice/NotificationStatusNotice.js +4 -4
- package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.frame.js +4 -4
- package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.header.js +3 -3
- package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.js +10 -10
- package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.overview.js +5 -5
- package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.parts.js +4 -4
- package/dist/_internal/components/feedback/index.js +11 -11
- package/dist/_internal/components/index.js +57 -51
- package/dist/charts.js +11 -11
- package/dist/{chunk-WYNGH2WL.js → chunk-255WTBZ5.js} +8 -8
- package/dist/{chunk-CGLMWS7K.js → chunk-37MJZLQU.js} +2 -2
- package/dist/{chunk-PNAKRHRT.js → chunk-3I7KE2N6.js} +2 -2
- package/dist/{chunk-L4FWEI2P.js → chunk-3V3Z4EML.js} +5 -5
- package/dist/{chunk-E4MJA52O.js → chunk-3V5V44UP.js} +16 -18
- package/dist/chunk-3V5V44UP.js.map +1 -0
- package/dist/{chunk-6RYSSNOO.js → chunk-54DOLCJ3.js} +2 -2
- package/dist/{chunk-74IH3ZGD.js → chunk-643YFWNK.js} +2 -2
- package/dist/{chunk-FRQGZIYU.js → chunk-6WAP7BJS.js} +2 -2
- package/dist/{chunk-3CXRHRGE.js → chunk-AYDVNXGR.js} +7 -3
- package/dist/chunk-AYDVNXGR.js.map +1 -0
- package/dist/{chunk-JO6D7YGN.js → chunk-CWRQGA2T.js} +2 -2
- package/dist/chunk-DRYBA76G.js +293 -0
- package/dist/chunk-DRYBA76G.js.map +1 -0
- package/dist/{chunk-ZW7Q3IRL.js → chunk-FYJL432J.js} +4 -4
- package/dist/{chunk-G6TKWNF2.js → chunk-GZITNT7O.js} +11 -11
- package/dist/{chunk-RFMQBIE6.js → chunk-HBHBRRSW.js} +4 -4
- package/dist/{chunk-U4Z6GZDW.js → chunk-HN36UVOE.js} +4 -4
- package/dist/{chunk-WK577LQL.js → chunk-J2IQ6FJ5.js} +4 -4
- package/dist/{chunk-HCTHY66U.js → chunk-KAGXV3ID.js} +4 -4
- package/dist/{chunk-FWAY2SAI.js → chunk-KAZWBDIH.js} +2 -2
- package/dist/{chunk-DKPELKGL.js → chunk-KV7LTOWC.js} +5 -5
- package/dist/{chunk-UC7DLGMP.js → chunk-KVP5A3YK.js} +2 -2
- package/dist/{chunk-EGBQR5GH.js → chunk-LOLYBFYY.js} +5 -5
- package/dist/{chunk-SOXO2VN3.js → chunk-MEMVZW64.js} +3 -3
- package/dist/{chunk-COOSJKZL.js → chunk-MNJZPJ5S.js} +2 -1
- package/dist/chunk-MNJZPJ5S.js.map +1 -0
- package/dist/{chunk-G2NVBCF7.js → chunk-NWUMGE6T.js} +3 -3
- package/dist/{chunk-RKAX37F4.js → chunk-Q5SCBQLY.js} +7 -7
- package/dist/{chunk-3R5MCSYO.js → chunk-QLKUWDMQ.js} +4 -4
- package/dist/{chunk-MPVH2GM5.js → chunk-RAQD5QCM.js} +4 -4
- package/dist/{chunk-KXDLLS62.js → chunk-REHGWN5M.js} +4 -4
- package/dist/{chunk-3N2HWNKT.js → chunk-UIJLDAV7.js} +2 -2
- package/dist/{chunk-GDWU7WF4.js → chunk-UN5WU2OX.js} +4 -4
- package/dist/{chunk-A27J4CIZ.js → chunk-UTIYB6VE.js} +11 -11
- package/dist/{chunk-5V7IZBRY.js → chunk-VRSW7PIO.js} +5 -5
- package/dist/{chunk-AESKKFA5.js → chunk-WDEVKND3.js} +3 -3
- package/dist/chunk-WGKLBVDZ.js +1 -0
- package/dist/chunk-WGKLBVDZ.js.map +1 -0
- package/dist/{chunk-67GTAPK6.js → chunk-XFOKLWJZ.js} +2 -2
- package/dist/{chunk-OV5NM4FY.js → chunk-XQAFSC66.js} +2 -2
- package/dist/{chunk-6FGP7RMV.js → chunk-XR474YYX.js} +2 -2
- package/dist/{chunk-4N4M237A.js → chunk-YQ5GQ32T.js} +2 -2
- package/dist/{chunk-YLZXIR6B.js → chunk-Z5V5E3JW.js} +4 -4
- package/dist/data-display/Alert.js +3 -3
- package/dist/data-display/CardKit.js +5 -5
- package/dist/data-display/Carousel.js +2 -2
- package/dist/data-display/Chart.js +11 -11
- package/dist/data-display/DataGrid.js +1 -1
- package/dist/data-display/DataViews.js +29 -29
- package/dist/data-display/DayRuler.js +7 -7
- package/dist/data-display/FleetMap.js +7 -7
- package/dist/data-display/HoverCard.js +2 -2
- package/dist/data-display/Lightbox.js +3 -3
- package/dist/data-display/MapPreview.js +4 -4
- package/dist/data-display/Progress.js +2 -2
- package/dist/data-display/RegionMap.js +14 -8
- package/dist/feedback/NotificationStatusNotice.js +4 -4
- package/dist/feedback/SequenceResolver.js +10 -10
- package/dist/types/components/data-display/RegionMap/RegionMap.d.ts +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.d.ts.map +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.helpers.d.ts +9 -0
- package/dist/types/components/data-display/RegionMap/RegionMap.helpers.d.ts.map +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.types.d.ts +45 -1
- package/dist/types/components/data-display/RegionMap/RegionMap.types.d.ts.map +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMapDrawing.d.ts +4 -1
- package/dist/types/components/data-display/RegionMap/RegionMapDrawing.d.ts.map +1 -1
- package/dist/types/components/data-display/RegionMap/RegionMapPin.d.ts +102 -0
- package/dist/types/components/data-display/RegionMap/RegionMapPin.d.ts.map +1 -0
- package/dist/types/components/data-display/RegionMap/index.d.ts +2 -1
- package/dist/types/components/data-display/RegionMap/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/data-display/RegionMap/RegionMap.helpers.ts +1 -1
- package/src/components/data-display/RegionMap/RegionMap.md +19 -2
- package/src/components/data-display/RegionMap/RegionMap.tsx +4 -0
- package/src/components/data-display/RegionMap/RegionMap.types.ts +49 -1
- package/src/components/data-display/RegionMap/RegionMapDrawing.tsx +11 -13
- package/src/components/data-display/RegionMap/RegionMapPin.tsx +388 -0
- package/src/components/data-display/RegionMap/index.ts +5 -0
- package/dist/chunk-3CXRHRGE.js.map +0 -1
- package/dist/chunk-COOSJKZL.js.map +0 -1
- package/dist/chunk-DY2I6LMX.js +0 -1
- package/dist/chunk-E4MJA52O.js.map +0 -1
- /package/dist/{chunk-DY2I6LMX.js.map → _internal/components/data-display/RegionMap/RegionMapPin.js.map} +0 -0
- /package/dist/{chunk-WYNGH2WL.js.map → chunk-255WTBZ5.js.map} +0 -0
- /package/dist/{chunk-CGLMWS7K.js.map → chunk-37MJZLQU.js.map} +0 -0
- /package/dist/{chunk-PNAKRHRT.js.map → chunk-3I7KE2N6.js.map} +0 -0
- /package/dist/{chunk-L4FWEI2P.js.map → chunk-3V3Z4EML.js.map} +0 -0
- /package/dist/{chunk-6RYSSNOO.js.map → chunk-54DOLCJ3.js.map} +0 -0
- /package/dist/{chunk-74IH3ZGD.js.map → chunk-643YFWNK.js.map} +0 -0
- /package/dist/{chunk-FRQGZIYU.js.map → chunk-6WAP7BJS.js.map} +0 -0
- /package/dist/{chunk-JO6D7YGN.js.map → chunk-CWRQGA2T.js.map} +0 -0
- /package/dist/{chunk-ZW7Q3IRL.js.map → chunk-FYJL432J.js.map} +0 -0
- /package/dist/{chunk-G6TKWNF2.js.map → chunk-GZITNT7O.js.map} +0 -0
- /package/dist/{chunk-RFMQBIE6.js.map → chunk-HBHBRRSW.js.map} +0 -0
- /package/dist/{chunk-U4Z6GZDW.js.map → chunk-HN36UVOE.js.map} +0 -0
- /package/dist/{chunk-WK577LQL.js.map → chunk-J2IQ6FJ5.js.map} +0 -0
- /package/dist/{chunk-HCTHY66U.js.map → chunk-KAGXV3ID.js.map} +0 -0
- /package/dist/{chunk-FWAY2SAI.js.map → chunk-KAZWBDIH.js.map} +0 -0
- /package/dist/{chunk-DKPELKGL.js.map → chunk-KV7LTOWC.js.map} +0 -0
- /package/dist/{chunk-UC7DLGMP.js.map → chunk-KVP5A3YK.js.map} +0 -0
- /package/dist/{chunk-EGBQR5GH.js.map → chunk-LOLYBFYY.js.map} +0 -0
- /package/dist/{chunk-SOXO2VN3.js.map → chunk-MEMVZW64.js.map} +0 -0
- /package/dist/{chunk-G2NVBCF7.js.map → chunk-NWUMGE6T.js.map} +0 -0
- /package/dist/{chunk-RKAX37F4.js.map → chunk-Q5SCBQLY.js.map} +0 -0
- /package/dist/{chunk-3R5MCSYO.js.map → chunk-QLKUWDMQ.js.map} +0 -0
- /package/dist/{chunk-MPVH2GM5.js.map → chunk-RAQD5QCM.js.map} +0 -0
- /package/dist/{chunk-KXDLLS62.js.map → chunk-REHGWN5M.js.map} +0 -0
- /package/dist/{chunk-3N2HWNKT.js.map → chunk-UIJLDAV7.js.map} +0 -0
- /package/dist/{chunk-GDWU7WF4.js.map → chunk-UN5WU2OX.js.map} +0 -0
- /package/dist/{chunk-A27J4CIZ.js.map → chunk-UTIYB6VE.js.map} +0 -0
- /package/dist/{chunk-5V7IZBRY.js.map → chunk-VRSW7PIO.js.map} +0 -0
- /package/dist/{chunk-AESKKFA5.js.map → chunk-WDEVKND3.js.map} +0 -0
- /package/dist/{chunk-67GTAPK6.js.map → chunk-XFOKLWJZ.js.map} +0 -0
- /package/dist/{chunk-OV5NM4FY.js.map → chunk-XQAFSC66.js.map} +0 -0
- /package/dist/{chunk-6FGP7RMV.js.map → chunk-XR474YYX.js.map} +0 -0
- /package/dist/{chunk-4N4M237A.js.map → chunk-YQ5GQ32T.js.map} +0 -0
- /package/dist/{chunk-YLZXIR6B.js.map → chunk-Z5V5E3JW.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegionMapPin.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/RegionMap/RegionMapPin.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { RegionMap } from './RegionMap';
|
|
2
2
|
export { decodePolyline, hitRegion, projectionAt } from './RegionMap.helpers';
|
|
3
|
+
export { latLngAt, scaleMetres } from './RegionMapPin';
|
|
3
4
|
export { boundsOfRings, regionMapView } from './RegionMap.view';
|
|
4
5
|
export type { AreaGeometry, PlaneBox, PlanePoint, PlaneRegion, Projection } from './RegionMap.helpers';
|
|
5
|
-
export type { RegionMapBounds, RegionMapCircle, RegionMapCopy, RegionMapLayer, RegionMapListMode, RegionMapListSort, RegionMapPoint, RegionMapProps, RegionMapReach, RegionMapRegion, RegionMapRing, } from './RegionMap.types';
|
|
6
|
+
export type { RegionMapBounds, RegionMapCircle, RegionMapCenter, RegionMapCopy, RegionMapDot, RegionMapGhost, RegionMapLayer, RegionMapListMode, RegionMapListSort, RegionMapPin, RegionMapPoint, RegionMapProps, RegionMapReach, RegionMapRegion, RegionMapRing, } from './RegionMap.types';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/RegionMap/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAChE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACvG,YAAY,EACV,eAAe,EACf,eAAe,EACf,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/RegionMap/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAChE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACvG,YAAY,EACV,eAAe,EACf,eAAe,EACf,eAAe,EACf,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,mBAAmB,CAAC"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ import type { RegionMapPoint, RegionMapRegion, RegionMapRing } from './RegionMap
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/** Metres per degree of latitude on the mean sphere. */
|
|
12
|
-
const METRES_PER_DEGREE = 111_195;
|
|
12
|
+
export const METRES_PER_DEGREE = 111_195;
|
|
13
13
|
|
|
14
14
|
/** A projected point, metres east (`x`) and south (`y`) of the centre. */
|
|
15
15
|
export interface PlanePoint {
|
|
@@ -102,7 +102,10 @@ drawing means copying its view math and losing its frame. So:
|
|
|
102
102
|
|---|---|---|---|
|
|
103
103
|
| `regions` | `readonly RegionMapRegion[]` | — | the areas; drawn when any part is in view |
|
|
104
104
|
| `center` | `{ latitude, longitude } \| null` | — | centre, dot and circles; `null` centres on the areas, no dot |
|
|
105
|
-
| `circles` | `readonly { radiusMetres }[]` | `[]` | dashed circles; the view fits the largest |
|
|
105
|
+
| `circles` | `readonly { radiusMetres }[]` | `[]` | dashed circles; the view fits the largest (solid, around the pin, when there is a `pin`) |
|
|
106
|
+
| `pin` | `{ at, label, ariaLabel, onMove?, onDrop, stepMetres?, disabled? }` | — | the centre as a pin the viewer drags or nudges with the arrow keys; the view stays on `center` |
|
|
107
|
+
| `ghost` | `{ at, radiusMetres }` | — | a dashed ring and dot where the circles stood, a dashed line to the pin |
|
|
108
|
+
| `dots` | `readonly { id, at, filled }[]` | — | small points, filled or hollow, not tappable |
|
|
106
109
|
| `reach` | `{ radiusMetres } \| { bounds: { south, west, north, east } }` | — | an extent the view must also fit (margin on top) |
|
|
107
110
|
| `overlay` | `(layer: { view, projection }) => ReactNode` | — | a host layer inside the SVG, under the circles, tap-transparent |
|
|
108
111
|
| `onToggle` | `(id: string) => void` | — | a tap or a list press; omit for read-only |
|
|
@@ -132,7 +135,21 @@ drawing means copying its view math and losing its frame. So:
|
|
|
132
135
|
|
|
133
136
|
`mapLabel`, `legendExcluded`, `legendCircle(radiusMetres)`, `legendCenter`,
|
|
134
137
|
`listLabel`, `showList(count)` (the count of LISTED areas), `hideList`, and
|
|
135
|
-
`rowState: { excluded, outside }` — a row's state in words, for a screen reader
|
|
138
|
+
`rowState: { excluded, outside }` — a row's state in words, for a screen reader;
|
|
139
|
+
optional `scale(metres)` — a scale bar's length ("1 km"), shown only when given.
|
|
140
|
+
|
|
141
|
+
### The movable centre (`pin`)
|
|
142
|
+
|
|
143
|
+
A store's point placed 200 m off by a geocoder is corrected by dragging it
|
|
144
|
+
where the circles are measured from. So the pin moves and the VIEW does not:
|
|
145
|
+
the areas never slide under a dragging finger. The pin is HTML over the
|
|
146
|
+
drawing (a 44×48 button, `touch-action: none`), carrying `data-region-pin` so
|
|
147
|
+
a host that zooms the drawing's layers can move it with them. A drag is
|
|
148
|
+
captured, clamped inside the view, and kept from the host's own listeners on
|
|
149
|
+
the drawing; each arrow key moves it `stepMetres` (50). Moves are reported
|
|
150
|
+
(`onMove`, `onDrop`), never applied: the host passes the new `at` back.
|
|
151
|
+
`latLngAt(center, planePoint)` is the projection's inverse for a host that
|
|
152
|
+
needs it.
|
|
136
153
|
|
|
137
154
|
## Helpers
|
|
138
155
|
|
|
@@ -39,6 +39,9 @@ export function RegionMap({
|
|
|
39
39
|
regions,
|
|
40
40
|
center,
|
|
41
41
|
circles,
|
|
42
|
+
pin,
|
|
43
|
+
ghost,
|
|
44
|
+
dots,
|
|
42
45
|
reach,
|
|
43
46
|
overlay,
|
|
44
47
|
onToggle,
|
|
@@ -62,6 +65,7 @@ export function RegionMap({
|
|
|
62
65
|
model={{ ...model, view }}
|
|
63
66
|
overlay={overlay}
|
|
64
67
|
showCenter={center !== null}
|
|
68
|
+
moving={center === null ? undefined : { center, pin, ghost, dots, copy }}
|
|
65
69
|
highlightedIds={highlightedIds}
|
|
66
70
|
maxHeight={maxHeight}
|
|
67
71
|
label={copy.mapLabel}
|
|
@@ -49,6 +49,46 @@ export interface RegionMapRegion {
|
|
|
49
49
|
detail?: string;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** A latitude/longitude pair, by name. */
|
|
53
|
+
export interface RegionMapCenter {
|
|
54
|
+
latitude: number;
|
|
55
|
+
longitude: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The centre as a pin the viewer moves (a store's door). The VIEW stays on
|
|
60
|
+
* `center`; the pin, and the circles with it, are drawn at `at`. Moves are
|
|
61
|
+
* reported, never applied: the host passes the new `at` back.
|
|
62
|
+
*/
|
|
63
|
+
export interface RegionMapPin {
|
|
64
|
+
at: RegionMapCenter;
|
|
65
|
+
/** Printed beside the pin, e.g. "Store". */
|
|
66
|
+
label: string;
|
|
67
|
+
/** The pin's accessible name, e.g. "Store pin. Drag it, or use the arrow keys, to move it". */
|
|
68
|
+
ariaLabel: string;
|
|
69
|
+
/** Every point while a pointer drags it. */
|
|
70
|
+
onMove?: (at: RegionMapCenter) => void;
|
|
71
|
+
/** Where it came to rest: the pointer let go, or an arrow key moved it. */
|
|
72
|
+
onDrop: (at: RegionMapCenter) => void;
|
|
73
|
+
/** Metres one arrow key moves it. Default 50. */
|
|
74
|
+
stepMetres?: number;
|
|
75
|
+
/** Held in place (a save in flight). */
|
|
76
|
+
disabled?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Where the pin's outermost circle stood: a dashed ring of `radiusMetres` and a dashed dot at `at`. */
|
|
80
|
+
export interface RegionMapGhost {
|
|
81
|
+
at: RegionMapCenter;
|
|
82
|
+
radiusMetres: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A small point on the map (an address), filled or hollow. Not tappable. */
|
|
86
|
+
export interface RegionMapDot {
|
|
87
|
+
id: string;
|
|
88
|
+
at: RegionMapCenter;
|
|
89
|
+
filled: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
52
92
|
/** A circle drawn around the centre, e.g. a delivery ring. */
|
|
53
93
|
export interface RegionMapCircle {
|
|
54
94
|
radiusMetres: number;
|
|
@@ -99,6 +139,8 @@ export interface RegionMapCopy {
|
|
|
99
139
|
* `outside` one (faded). Neither is shown; the row's look carries it.
|
|
100
140
|
*/
|
|
101
141
|
rowState: { excluded: string; outside: string };
|
|
142
|
+
/** A scale bar's length, e.g. "1 km". Omit for no scale bar. */
|
|
143
|
+
scale?: (metres: number) => string;
|
|
102
144
|
}
|
|
103
145
|
|
|
104
146
|
/** The order of the list: as given (e.g. nearest first), or by name. */
|
|
@@ -120,8 +162,14 @@ export interface RegionMapProps {
|
|
|
120
162
|
* `null` with no regions renders NOTHING rather than a map of `0, 0`.
|
|
121
163
|
*/
|
|
122
164
|
center: { latitude: number; longitude: number } | null;
|
|
123
|
-
/** Circles around the centre; the view fits the largest. */
|
|
165
|
+
/** Circles around the centre (around `pin.at` when there is a pin); the view fits the largest. */
|
|
124
166
|
circles?: readonly RegionMapCircle[];
|
|
167
|
+
/** The centre as a draggable pin; it replaces the centre's dot, and the circles follow it. */
|
|
168
|
+
pin?: RegionMapPin;
|
|
169
|
+
/** A dashed ring and dot where the circles stood (drawn with a `pin`). */
|
|
170
|
+
ghost?: RegionMapGhost;
|
|
171
|
+
/** Small points, filled or hollow, over the areas and under the pin. */
|
|
172
|
+
dots?: readonly RegionMapDot[];
|
|
125
173
|
/**
|
|
126
174
|
* An extent the view must ALSO fit, before its margin — something the host
|
|
127
175
|
* draws past the largest circle. With circles the view stays centred on the
|
|
@@ -9,12 +9,16 @@ import { labelPosition, type AreaGeometry, type DrawnRegion, type PlaneBox } fro
|
|
|
9
9
|
import type { RegionMapModel } from './RegionMap.hooks';
|
|
10
10
|
import { labelOrder, placeLabels } from './RegionMap.labels';
|
|
11
11
|
import type { RegionMapLayer } from './RegionMap.types';
|
|
12
|
+
import type { Moving } from './RegionMapPin';
|
|
13
|
+
import { PinOverlay, PinShapes } from './RegionMapPin';
|
|
12
14
|
|
|
13
15
|
export interface RegionMapDrawingProps {
|
|
14
16
|
model: RegionMapModel & { view: PlaneBox };
|
|
15
17
|
/** The host's layer, drawn inside the SVG under the circles (`RegionMapProps.overlay`). */
|
|
16
18
|
overlay?: (layer: RegionMapLayer) => React.ReactNode;
|
|
17
19
|
showCenter: boolean;
|
|
20
|
+
/** The movable centre and what is drawn with it (`RegionMapProps.pin`, `ghost`, `dots`). */
|
|
21
|
+
moving?: Moving;
|
|
18
22
|
highlightedIds: readonly string[];
|
|
19
23
|
maxHeight: string | undefined;
|
|
20
24
|
label: string;
|
|
@@ -33,17 +37,8 @@ export interface RegionMapDrawingProps {
|
|
|
33
37
|
* The base areas are a memoised layer: a toggle changes `excluded`, not the
|
|
34
38
|
* geometry, so only the hatch, highlight and label layers re-render.
|
|
35
39
|
*/
|
|
36
|
-
export function RegionMapDrawing({
|
|
37
|
-
model,
|
|
38
|
-
overlay,
|
|
39
|
-
showCenter,
|
|
40
|
-
highlightedIds,
|
|
41
|
-
maxHeight,
|
|
42
|
-
label,
|
|
43
|
-
tappable,
|
|
44
|
-
testId,
|
|
45
|
-
theme,
|
|
46
|
-
}: RegionMapDrawingProps): React.JSX.Element {
|
|
40
|
+
export function RegionMapDrawing(props: RegionMapDrawingProps): React.JSX.Element {
|
|
41
|
+
const { model, overlay, showCenter, moving, highlightedIds, maxHeight, label, tappable, testId, theme } = props;
|
|
47
42
|
const hatchId = `${React.useId().replace(/:/g, '')}-hatch`;
|
|
48
43
|
const { view, drawn } = model;
|
|
49
44
|
const wanted = new Set(highlightedIds);
|
|
@@ -52,6 +47,7 @@ export function RegionMapDrawing({
|
|
|
52
47
|
// claim their place in, so a collision hides the less important one.
|
|
53
48
|
const labelled = labelOrder(drawn.filter((d) => d.excluded || wanted.has(d.id)), wanted);
|
|
54
49
|
const boxRef = React.useRef<HTMLDivElement>(null);
|
|
50
|
+
const svgRef = React.useRef<SVGSVGElement>(null);
|
|
55
51
|
useLabelPlacement(boxRef, labelled.map((d) => d.id).join('|'), remPx(theme, 4));
|
|
56
52
|
const base = drawn.map((d) => ({ id: d.id, name: d.name, geometry: d.geometry, outside: d.outside === true }));
|
|
57
53
|
return (
|
|
@@ -72,6 +68,7 @@ export function RegionMapDrawing({
|
|
|
72
68
|
ref={boxRef}
|
|
73
69
|
>
|
|
74
70
|
<svg
|
|
71
|
+
ref={svgRef}
|
|
75
72
|
viewBox={`${view.minX} ${view.minY} ${view.spanX} ${view.spanY}`}
|
|
76
73
|
role="img"
|
|
77
74
|
aria-label={label}
|
|
@@ -89,9 +86,9 @@ export function RegionMapDrawing({
|
|
|
89
86
|
{overlay({ view, projection: model.projection })}
|
|
90
87
|
</g>
|
|
91
88
|
)}
|
|
92
|
-
<Circles radii={model.radii} theme={theme} />
|
|
89
|
+
<PinShapes model={model} moving={moving} testId={testId} theme={theme} fallback={<Circles radii={model.radii} theme={theme} />} />
|
|
93
90
|
<Highlights areas={highlighted} testId={testId} theme={theme} />
|
|
94
|
-
{showCenter && (
|
|
91
|
+
{showCenter && !moving?.pin && (
|
|
95
92
|
<circle
|
|
96
93
|
cx={0}
|
|
97
94
|
cy={0}
|
|
@@ -108,6 +105,7 @@ export function RegionMapDrawing({
|
|
|
108
105
|
{labelled.map((d) => (
|
|
109
106
|
<AreaLabel key={`l-${d.id}`} area={d} view={view} testId={testId} theme={theme} />
|
|
110
107
|
))}
|
|
108
|
+
<PinOverlay model={model} moving={moving} svgRef={svgRef} testId={testId} theme={theme} />
|
|
111
109
|
</Box>
|
|
112
110
|
);
|
|
113
111
|
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import Box from '@mui/material/Box/index.js';
|
|
2
|
+
import { alpha, type Theme } from '@mui/material/styles/index.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
import { rem } from '../../../tokens/relative';
|
|
6
|
+
|
|
7
|
+
import { METRES_PER_DEGREE, type PlaneBox, type PlanePoint, type Projection } from './RegionMap.helpers';
|
|
8
|
+
import type { RegionMapModel } from './RegionMap.hooks';
|
|
9
|
+
import type { RegionMapCenter, RegionMapCopy, RegionMapDot, RegionMapGhost, RegionMapPin } from './RegionMap.types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The movable centre of a `RegionMap` (`pin`, `ghost`, `dots`): a pin the
|
|
13
|
+
* viewer drags with a mouse or one finger, or focuses and nudges with the
|
|
14
|
+
* arrow keys, while the VIEW stays where `center` put it — so the areas never
|
|
15
|
+
* slide under a dragging finger. The circles follow the pin; a dashed ring
|
|
16
|
+
* and dot stay at `ghost`; `dots` are small points (filled or hollow).
|
|
17
|
+
*
|
|
18
|
+
* The pin is HTML over the drawing, at its point by percent like the labels,
|
|
19
|
+
* carrying `data-region-pin` so a host that zooms the drawing's layers can
|
|
20
|
+
* move it with them. Its moves are reported, never applied: the host passes
|
|
21
|
+
* the new point back.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** The movable centre and what is drawn with it, as the drawing receives it. */
|
|
25
|
+
export interface Moving {
|
|
26
|
+
center: RegionMapCenter;
|
|
27
|
+
pin?: RegionMapPin;
|
|
28
|
+
ghost?: RegionMapGhost;
|
|
29
|
+
dots?: readonly RegionMapDot[];
|
|
30
|
+
copy: RegionMapCopy;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type DrawnModel = Pick<RegionMapModel, 'projection' | 'radii'> & { view: PlaneBox };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* What the movable centre draws inside the SVG: the circles around the pin
|
|
37
|
+
* (else `fallback`, the centre's own circles), the ghost, and the dots.
|
|
38
|
+
*/
|
|
39
|
+
export function PinShapes({ model, moving, testId, theme, fallback }: { model: DrawnModel; moving: Moving | undefined; testId: string; theme: Theme; fallback: React.ReactNode }): React.JSX.Element {
|
|
40
|
+
const { projection, view } = model;
|
|
41
|
+
if (!moving || !projection) return <>{fallback}</>;
|
|
42
|
+
const pinAt = moving.pin ? projection.project([moving.pin.at.latitude, moving.pin.at.longitude]) : null;
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
{pinAt ? <PinCircles at={pinAt} radii={model.radii} theme={theme} /> : fallback}
|
|
46
|
+
{pinAt && moving.ghost ? <GhostShapes ghost={moving.ghost} pinAt={pinAt} projection={projection} view={view} testId={testId} theme={theme} /> : null}
|
|
47
|
+
{moving.dots ? <Dots dots={moving.dots} projection={projection} view={view} testId={testId} theme={theme} /> : null}
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** What the movable centre draws over the SVG, in HTML: the pin and the scale bar. */
|
|
53
|
+
export function PinOverlay({
|
|
54
|
+
model,
|
|
55
|
+
moving,
|
|
56
|
+
svgRef,
|
|
57
|
+
testId,
|
|
58
|
+
theme,
|
|
59
|
+
}: {
|
|
60
|
+
model: DrawnModel;
|
|
61
|
+
moving: Moving | undefined;
|
|
62
|
+
svgRef: React.RefObject<SVGSVGElement | null>;
|
|
63
|
+
testId: string;
|
|
64
|
+
theme: Theme;
|
|
65
|
+
}): React.JSX.Element | null {
|
|
66
|
+
if (!moving || !model.projection) return null;
|
|
67
|
+
return (
|
|
68
|
+
<>
|
|
69
|
+
{moving.pin ? <PinMarker pin={moving.pin} center={moving.center} projection={model.projection} view={model.view} svgRef={svgRef} testId={testId} theme={theme} /> : null}
|
|
70
|
+
<ScaleBar view={model.view} copy={moving.copy} svgRef={svgRef} testId={testId} theme={theme} />
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The flat projection's inverse around `center` — it is linear. */
|
|
76
|
+
export function latLngAt(center: RegionMapCenter, point: PlanePoint): RegionMapCenter {
|
|
77
|
+
const kx = METRES_PER_DEGREE * Math.cos((center.latitude * Math.PI) / 180);
|
|
78
|
+
return { latitude: center.latitude - point.y / METRES_PER_DEGREE, longitude: center.longitude + point.x / kx };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A point of the plane as fractions of the view (0..1 across, 0..1 down). */
|
|
82
|
+
function fractionOf(view: PlaneBox, point: PlanePoint): { x: number; y: number } {
|
|
83
|
+
return { x: (point.x - view.minX) / view.spanX, y: (point.y - view.minY) / view.spanY };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Keep a fraction inside the view, a hair in from its edge (the mock's clamp). */
|
|
87
|
+
const clampFraction = (value: number): number => Math.min(0.995, Math.max(0.005, value));
|
|
88
|
+
|
|
89
|
+
/** A plane point kept inside the view by the same clamp a drag gets. */
|
|
90
|
+
function clampToView(view: PlaneBox, point: PlanePoint): PlanePoint {
|
|
91
|
+
const at = fractionOf(view, point);
|
|
92
|
+
return { x: view.minX + clampFraction(at.x) * view.spanX, y: view.minY + clampFraction(at.y) * view.spanY };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Arrow keys, as metres east and north per press. */
|
|
96
|
+
const ARROWS: Record<string, [number, number]> = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, 1], ArrowDown: [0, -1] };
|
|
97
|
+
|
|
98
|
+
/** One arrow key: 50 m unless the host says otherwise. */
|
|
99
|
+
const DEFAULT_STEP_METRES = 50;
|
|
100
|
+
|
|
101
|
+
interface PinLayerProps {
|
|
102
|
+
pin: RegionMapPin;
|
|
103
|
+
center: RegionMapCenter;
|
|
104
|
+
projection: Projection;
|
|
105
|
+
view: PlaneBox;
|
|
106
|
+
/** The drawing's SVG, whose on-screen box maps a pointer to the plane (zoomed or not). */
|
|
107
|
+
svgRef: React.RefObject<SVGSVGElement | null>;
|
|
108
|
+
testId: string;
|
|
109
|
+
theme: Theme;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The draggable pin, as HTML over the drawing. */
|
|
113
|
+
export function PinMarker({ pin, center, projection, view, svgRef, testId, theme }: PinLayerProps): React.JSX.Element {
|
|
114
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
115
|
+
const live = React.useRef({ pin, center, view });
|
|
116
|
+
live.current = { pin, center, view };
|
|
117
|
+
usePinDrag(ref, svgRef, live);
|
|
118
|
+
const at = fractionOf(view, projection.project([pin.at.latitude, pin.at.longitude]));
|
|
119
|
+
const onKeyDown = (event: React.KeyboardEvent): void => {
|
|
120
|
+
const arrow = ARROWS[event.key];
|
|
121
|
+
if (!arrow || pin.disabled) return;
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
const step = pin.stepMetres ?? DEFAULT_STEP_METRES;
|
|
124
|
+
const plane = projection.project([pin.at.latitude, pin.at.longitude]);
|
|
125
|
+
const next = latLngAt(center, clampToView(view, { x: plane.x + arrow[0] * step, y: plane.y - arrow[1] * step }));
|
|
126
|
+
pin.onMove?.(next);
|
|
127
|
+
pin.onDrop(next);
|
|
128
|
+
};
|
|
129
|
+
return (
|
|
130
|
+
<Box
|
|
131
|
+
ref={ref}
|
|
132
|
+
role="button"
|
|
133
|
+
tabIndex={pin.disabled ? -1 : 0}
|
|
134
|
+
aria-label={pin.ariaLabel}
|
|
135
|
+
aria-disabled={pin.disabled || undefined}
|
|
136
|
+
onKeyDown={onKeyDown}
|
|
137
|
+
data-testid={`${testId}-pin`}
|
|
138
|
+
data-region-pin=""
|
|
139
|
+
style={{ left: `${(at.x * 100).toFixed(3)}%`, top: `${(at.y * 100).toFixed(3)}%` }}
|
|
140
|
+
sx={{
|
|
141
|
+
position: 'absolute',
|
|
142
|
+
transform: 'translate(-50%, -100%)',
|
|
143
|
+
width: rem(theme, 44),
|
|
144
|
+
height: rem(theme, 48),
|
|
145
|
+
display: 'flex',
|
|
146
|
+
alignItems: 'flex-end',
|
|
147
|
+
justifyContent: 'center',
|
|
148
|
+
cursor: pin.disabled ? 'default' : 'grab',
|
|
149
|
+
touchAction: 'none',
|
|
150
|
+
zIndex: 2,
|
|
151
|
+
borderRadius: 1,
|
|
152
|
+
'&:focus-visible': { outline: `${rem(theme, 2)} solid`, outlineColor: 'primary.main', outlineOffset: rem(theme, 2) },
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<Box component="svg" viewBox="0 0 32 42" aria-hidden="true" sx={{ width: rem(theme, 32), height: rem(theme, 42) }}>
|
|
156
|
+
<path
|
|
157
|
+
d="M16 41C16 41 3 25 3 15.5A13 13 0 1 1 29 15.5C29 25 16 41 16 41Z"
|
|
158
|
+
fill={theme.palette.primary.main}
|
|
159
|
+
stroke={theme.palette.background.paper}
|
|
160
|
+
strokeWidth={2}
|
|
161
|
+
/>
|
|
162
|
+
<circle cx="16" cy="15.5" r="5" fill={theme.palette.background.paper} />
|
|
163
|
+
</Box>
|
|
164
|
+
<Box
|
|
165
|
+
component="span"
|
|
166
|
+
aria-hidden
|
|
167
|
+
sx={{
|
|
168
|
+
position: 'absolute',
|
|
169
|
+
left: '88%',
|
|
170
|
+
top: 0,
|
|
171
|
+
px: 0.75,
|
|
172
|
+
borderRadius: 0.5,
|
|
173
|
+
border: 1,
|
|
174
|
+
borderColor: 'divider',
|
|
175
|
+
bgcolor: 'background.paper',
|
|
176
|
+
color: 'text.primary',
|
|
177
|
+
fontSize: rem(theme, 12),
|
|
178
|
+
fontWeight: 600,
|
|
179
|
+
whiteSpace: 'nowrap',
|
|
180
|
+
pointerEvents: 'none',
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
{pin.label}
|
|
184
|
+
</Box>
|
|
185
|
+
</Box>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The pointer drag: native listeners on the pin, so a host's own listeners on
|
|
191
|
+
* the drawing (a pan, a tap) never see the press; captured, so a finger that
|
|
192
|
+
* leaves the pin keeps dragging it; clamped inside the view.
|
|
193
|
+
*/
|
|
194
|
+
function usePinDrag(
|
|
195
|
+
ref: React.RefObject<HTMLDivElement | null>,
|
|
196
|
+
svgRef: React.RefObject<SVGSVGElement | null>,
|
|
197
|
+
live: React.RefObject<{ pin: RegionMapPin; center: RegionMapCenter; view: PlaneBox }>,
|
|
198
|
+
): void {
|
|
199
|
+
React.useEffect(() => {
|
|
200
|
+
const element = ref.current;
|
|
201
|
+
if (!element) return undefined;
|
|
202
|
+
let dragging: number | null = null;
|
|
203
|
+
// Where the last move put the pin: a cancelled gesture (a browser may
|
|
204
|
+
// report 0,0 for it) rests there rather than jumping to the corner.
|
|
205
|
+
let last: RegionMapCenter | null = null;
|
|
206
|
+
const pointAt = (event: PointerEvent): RegionMapCenter | null => {
|
|
207
|
+
const box = svgRef.current?.getBoundingClientRect();
|
|
208
|
+
if (!box || box.width === 0 || box.height === 0) return null;
|
|
209
|
+
const { view, center } = live.current;
|
|
210
|
+
if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) return null;
|
|
211
|
+
const fx = clampFraction((event.clientX - box.left) / box.width);
|
|
212
|
+
const fy = clampFraction((event.clientY - box.top) / box.height);
|
|
213
|
+
return latLngAt(center, { x: view.minX + fx * view.spanX, y: view.minY + fy * view.spanY });
|
|
214
|
+
};
|
|
215
|
+
const down = (event: PointerEvent): void => {
|
|
216
|
+
event.stopPropagation();
|
|
217
|
+
if (live.current.pin.disabled) return;
|
|
218
|
+
event.preventDefault();
|
|
219
|
+
dragging = event.pointerId;
|
|
220
|
+
last = null;
|
|
221
|
+
element.setPointerCapture?.(event.pointerId);
|
|
222
|
+
};
|
|
223
|
+
const move = (event: PointerEvent): void => {
|
|
224
|
+
if (dragging !== event.pointerId) return;
|
|
225
|
+
event.stopPropagation();
|
|
226
|
+
const at = pointAt(event);
|
|
227
|
+
if (!at) return;
|
|
228
|
+
last = at;
|
|
229
|
+
live.current.pin.onMove?.(at);
|
|
230
|
+
};
|
|
231
|
+
const up = (event: PointerEvent): void => {
|
|
232
|
+
if (dragging !== event.pointerId) return;
|
|
233
|
+
event.stopPropagation();
|
|
234
|
+
dragging = null;
|
|
235
|
+
const at = event.type === 'pointercancel' ? last : (pointAt(event) ?? last);
|
|
236
|
+
if (at) live.current.pin.onDrop(at);
|
|
237
|
+
};
|
|
238
|
+
// A click that ends a drag is the drag's, not a tap on the area under it.
|
|
239
|
+
const click = (event: MouseEvent): void => event.stopPropagation();
|
|
240
|
+
element.addEventListener('pointerdown', down);
|
|
241
|
+
element.addEventListener('pointermove', move);
|
|
242
|
+
element.addEventListener('pointerup', up);
|
|
243
|
+
element.addEventListener('pointercancel', up);
|
|
244
|
+
element.addEventListener('click', click);
|
|
245
|
+
return () => {
|
|
246
|
+
element.removeEventListener('pointerdown', down);
|
|
247
|
+
element.removeEventListener('pointermove', move);
|
|
248
|
+
element.removeEventListener('pointerup', up);
|
|
249
|
+
element.removeEventListener('pointercancel', up);
|
|
250
|
+
element.removeEventListener('click', click);
|
|
251
|
+
};
|
|
252
|
+
}, [ref, svgRef, live]);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The circles around the pin: solid in the primary colour, faintly filled. */
|
|
256
|
+
export function PinCircles({ at, radii, theme }: { at: PlanePoint; radii: readonly number[]; theme: Theme }): React.JSX.Element {
|
|
257
|
+
return (
|
|
258
|
+
<>
|
|
259
|
+
{radii.map((radius) => (
|
|
260
|
+
<circle
|
|
261
|
+
key={radius}
|
|
262
|
+
cx={at.x}
|
|
263
|
+
cy={at.y}
|
|
264
|
+
r={radius}
|
|
265
|
+
fill={alpha(theme.palette.primary.main, 0.06)}
|
|
266
|
+
stroke={theme.palette.primary.main}
|
|
267
|
+
strokeWidth={2.25}
|
|
268
|
+
vectorEffect="non-scaling-stroke"
|
|
269
|
+
pointerEvents="none"
|
|
270
|
+
/>
|
|
271
|
+
))}
|
|
272
|
+
</>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Where the circles stood: a dashed ring and dot at `ghost`, and a dashed line from it to the pin. */
|
|
277
|
+
export function GhostShapes({
|
|
278
|
+
ghost,
|
|
279
|
+
pinAt,
|
|
280
|
+
projection,
|
|
281
|
+
view,
|
|
282
|
+
testId,
|
|
283
|
+
theme,
|
|
284
|
+
}: {
|
|
285
|
+
ghost: RegionMapGhost;
|
|
286
|
+
pinAt: PlanePoint;
|
|
287
|
+
projection: Projection;
|
|
288
|
+
view: PlaneBox;
|
|
289
|
+
testId: string;
|
|
290
|
+
theme: Theme;
|
|
291
|
+
}): React.JSX.Element {
|
|
292
|
+
const at = projection.project([ghost.at.latitude, ghost.at.longitude]);
|
|
293
|
+
const stroke = theme.palette.text.secondary;
|
|
294
|
+
const common = { fill: 'none', stroke, vectorEffect: 'non-scaling-stroke', pointerEvents: 'none' } as const;
|
|
295
|
+
return (
|
|
296
|
+
<g data-testid={`${testId}-ghost`}>
|
|
297
|
+
<circle cx={at.x} cy={at.y} r={ghost.radiusMetres} strokeWidth={1.5} strokeDasharray="6 5" {...common} />
|
|
298
|
+
<line x1={at.x} y1={at.y} x2={pinAt.x} y2={pinAt.y} strokeWidth={1.5} strokeDasharray="4 4" {...common} />
|
|
299
|
+
<circle cx={at.x} cy={at.y} r={view.spanX / 110} strokeWidth={2} strokeDasharray="3 2" {...common} fill={alpha(theme.palette.background.paper, 0.7)} />
|
|
300
|
+
</g>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The dots: filled with the ink, or hollow (paper with an ink ring). */
|
|
305
|
+
export function Dots({ dots, projection, view, testId, theme }: { dots: readonly RegionMapDot[]; projection: Projection; view: PlaneBox; testId: string; theme: Theme }): React.JSX.Element {
|
|
306
|
+
return (
|
|
307
|
+
<g data-testid={`${testId}-dots`}>
|
|
308
|
+
{dots.map((dot) => {
|
|
309
|
+
const at = projection.project([dot.at.latitude, dot.at.longitude]);
|
|
310
|
+
return (
|
|
311
|
+
<circle
|
|
312
|
+
key={dot.id}
|
|
313
|
+
cx={at.x}
|
|
314
|
+
cy={at.y}
|
|
315
|
+
r={view.spanX / 150}
|
|
316
|
+
fill={dot.filled ? theme.palette.text.primary : theme.palette.background.paper}
|
|
317
|
+
stroke={theme.palette.text.primary}
|
|
318
|
+
strokeWidth={2}
|
|
319
|
+
vectorEffect="non-scaling-stroke"
|
|
320
|
+
pointerEvents="none"
|
|
321
|
+
data-filled={dot.filled ? 'true' : 'false'}
|
|
322
|
+
/>
|
|
323
|
+
);
|
|
324
|
+
})}
|
|
325
|
+
</g>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Nice lengths a scale bar may take, in metres. */
|
|
330
|
+
const SCALE_STEPS = [50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000];
|
|
331
|
+
|
|
332
|
+
/** The longest nice length that fits in a quarter of the drawing's width. */
|
|
333
|
+
export function scaleMetres(view: PlaneBox): number {
|
|
334
|
+
const quarter = view.spanX / 4;
|
|
335
|
+
return SCALE_STEPS.filter((step) => step <= quarter).at(-1) ?? SCALE_STEPS[0]!;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* A scale bar, bottom-left, when the host gives `copy.scale`. It reads what is
|
|
340
|
+
* on screen: a host that zooms the drawing's layers (a CSS scale on the SVG)
|
|
341
|
+
* shows fewer metres per pixel, so after every render the bar compares the
|
|
342
|
+
* SVG's drawn width with its laid-out width — both off the SVG itself — and
|
|
343
|
+
* spans the drawing's own width, so its share of it is a share of the view.
|
|
344
|
+
*/
|
|
345
|
+
export function ScaleBar({
|
|
346
|
+
view,
|
|
347
|
+
copy,
|
|
348
|
+
svgRef,
|
|
349
|
+
testId,
|
|
350
|
+
theme,
|
|
351
|
+
}: {
|
|
352
|
+
view: PlaneBox;
|
|
353
|
+
copy: RegionMapCopy;
|
|
354
|
+
svgRef: React.RefObject<SVGSVGElement | null>;
|
|
355
|
+
testId: string;
|
|
356
|
+
theme: Theme;
|
|
357
|
+
}): React.JSX.Element | null {
|
|
358
|
+
// What the drawing shows on screen: its laid-out width, and its width as
|
|
359
|
+
// drawn (a host's CSS scale on the SVG), both read off the SVG itself.
|
|
360
|
+
const [screen, setScreen] = React.useState({ laidOut: 0, drawn: 0 });
|
|
361
|
+
const wanted = copy.scale !== undefined;
|
|
362
|
+
React.useLayoutEffect(() => {
|
|
363
|
+
if (!wanted) return;
|
|
364
|
+
const svg = svgRef.current;
|
|
365
|
+
const next = { laidOut: svg?.clientWidth ?? 0, drawn: svg?.getBoundingClientRect().width ?? 0 };
|
|
366
|
+
if (next.laidOut !== screen.laidOut || Math.abs(next.drawn - screen.drawn) > 0.5) setScreen(next);
|
|
367
|
+
});
|
|
368
|
+
if (!copy.scale) return null;
|
|
369
|
+
const zoom = screen.laidOut > 0 && screen.drawn > 0 ? screen.drawn / screen.laidOut : 1;
|
|
370
|
+
const metres = scaleMetres({ ...view, spanX: view.spanX / zoom });
|
|
371
|
+
// The bar's share of the drawing's width: metres on screen over the metres the width shows.
|
|
372
|
+
const share = (metres * zoom) / view.spanX;
|
|
373
|
+
return (
|
|
374
|
+
<Box
|
|
375
|
+
data-testid={`${testId}-scale`}
|
|
376
|
+
aria-hidden
|
|
377
|
+
sx={{ position: 'absolute', left: 0, right: 0, bottom: rem(theme, 10), pointerEvents: 'none', zIndex: 1 }}
|
|
378
|
+
>
|
|
379
|
+
<Box component="span" sx={{ display: 'block', ml: rem(theme, 10), fontSize: rem(theme, 11), color: 'text.secondary' }}>
|
|
380
|
+
{copy.scale(metres)}
|
|
381
|
+
</Box>
|
|
382
|
+
<Box
|
|
383
|
+
component="span"
|
|
384
|
+
sx={{ display: 'block', height: rem(theme, 4), ml: rem(theme, 10), width: `${(share * 100).toFixed(2)}%`, borderStyle: 'solid', borderWidth: `0 ${rem(theme, 1.5)} ${rem(theme, 1.5)}`, borderColor: 'text.secondary' }}
|
|
385
|
+
/>
|
|
386
|
+
</Box>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
export { RegionMap } from './RegionMap';
|
|
2
2
|
export { decodePolyline, hitRegion, projectionAt } from './RegionMap.helpers';
|
|
3
|
+
export { latLngAt, scaleMetres } from './RegionMapPin';
|
|
3
4
|
export { boundsOfRings, regionMapView } from './RegionMap.view';
|
|
4
5
|
export type { AreaGeometry, PlaneBox, PlanePoint, PlaneRegion, Projection } from './RegionMap.helpers';
|
|
5
6
|
export type {
|
|
6
7
|
RegionMapBounds,
|
|
7
8
|
RegionMapCircle,
|
|
9
|
+
RegionMapCenter,
|
|
8
10
|
RegionMapCopy,
|
|
11
|
+
RegionMapDot,
|
|
12
|
+
RegionMapGhost,
|
|
9
13
|
RegionMapLayer,
|
|
10
14
|
RegionMapListMode,
|
|
11
15
|
RegionMapListSort,
|
|
16
|
+
RegionMapPin,
|
|
12
17
|
RegionMapPoint,
|
|
13
18
|
RegionMapProps,
|
|
14
19
|
RegionMapReach,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/RegionMap/RegionMap.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport { useTheme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { useRegionMap } from './RegionMap.hooks';\nimport type { RegionMapProps } from './RegionMap.types';\nimport { RegionMapDrawing } from './RegionMapDrawing';\nimport { RegionMapLegend } from './RegionMapLegend';\nimport { RegionMapList } from './RegionMapList';\n\n/** One empty list, so the default does not change identity per render. */\nconst NO_IDS: readonly string[] = [];\n\n/**\n * A hand-drawn map of named areas around a centre; a tap toggles an area.\n *\n * Drawn as inline SVG over a plain background — no tile server, no API key,\n * nothing fetched — because the question it answers is \"which of THESE areas\",\n * not \"what is the street called\". Four choices shape it:\n *\n * - **The list is the accessible way in.** The drawing is one `role=\"img\"`\n * with a name; its areas are not tab stops (dozens of them, many too small to\n * tap on a phone). Every area is also a toggle button in the list beneath,\n * with `aria-pressed`, which is what a keyboard and a screen reader use —\n * and what reaches a sliver no finger can.\n * - **Excluded is hatched AND named, never only coloured,** and never in the\n * error colour: leaving an area out is a choice, not a failure.\n * - **The hit test is ours, not only the browser's.** A mouse click on a path\n * is that path. A finger's tap takes the SMALLEST area within a few pixels'\n * worth of metres (`hitRegion`), so a sliver beside a big area can be hit\n * from the map; one too thin even for that is still on the list.\n * - **The host can draw inside it.** `reach` widens the view past the largest\n * circle and `overlay` draws a layer in the SVG at the map's own projection,\n * tap-transparent; `regionMapView` gives the same view to HTML laid over it.\n * - **A toggle is cheap.** Geometry is projected once per `rings` identity,\n * and the base layer is memoised, so a tap re-renders the hatch and labels.\n */\nexport function RegionMap({\n regions,\n center,\n circles,\n reach,\n overlay,\n onToggle,\n highlightedIds = NO_IDS,\n copy,\n listMode = 'collapsed',\n listSort = 'given',\n maxHeight,\n className,\n dataTestId = 'region-map',\n}: RegionMapProps): React.JSX.Element | null {\n const theme = useTheme();\n const model = useRegionMap({ regions, center, circles, reach, onToggle });\n const { view } = model;\n if (view === null && regions.length === 0) return null;\n return (\n <Box className={className} data-testid={dataTestId}>\n {view && (\n <>\n <RegionMapDrawing\n model={{ ...model, view }}\n overlay={overlay}\n showCenter={center !== null}\n highlightedIds={highlightedIds}\n maxHeight={maxHeight}\n label={copy.mapLabel}\n tappable={onToggle !== undefined}\n testId={dataTestId}\n theme={theme}\n />\n <RegionMapLegend\n copy={copy}\n outermostMetres={model.outermostMetres}\n showCenter={center !== null}\n theme={theme}\n testId={dataTestId}\n />\n </>\n )}\n {listMode !== 'none' && (\n <RegionMapList\n regions={regions}\n copy={copy}\n mode={listMode}\n sort={listSort}\n onToggle={onToggle}\n testId={dataTestId}\n theme={theme}\n />\n )}\n </Box>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,OAAO,SAAS;AAChB,SAAS,gBAAgB;AA0DjB,mBACE,KADF;AAhDR,IAAM,SAA4B,CAAC;AA0B5B,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,aAAa;AACf,GAA6C;AAC3C,QAAM,QAAQ,SAAS;AACvB,QAAM,QAAQ,aAAa,EAAE,SAAS,QAAQ,SAAS,OAAO,SAAS,CAAC;AACxE,QAAM,EAAE,KAAK,IAAI;AACjB,MAAI,SAAS,QAAQ,QAAQ,WAAW,EAAG,QAAO;AAClD,SACE,qBAAC,OAAI,WAAsB,eAAa,YACrC;AAAA,YACC,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,GAAG,OAAO,KAAK;AAAA,UACxB;AAAA,UACA,YAAY,WAAW;AAAA,UACvB;AAAA,UACA;AAAA,UACA,OAAO,KAAK;AAAA,UACZ,UAAU,aAAa;AAAA,UACvB,QAAQ;AAAA,UACR;AAAA;AAAA,MACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,iBAAiB,MAAM;AAAA,UACvB,YAAY,WAAW;AAAA,UACvB;AAAA,UACA,QAAQ;AAAA;AAAA,MACV;AAAA,OACF;AAAA,IAED,aAAa,UACZ;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,QAAQ;AAAA,QACR;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;","names":[]}
|