@12-apps/ui 6.86.0 → 6.86.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.
Files changed (123) hide show
  1. package/dist/_internal/components/data-display/Alert/Alert.js +2 -2
  2. package/dist/_internal/components/data-display/CardKit/card-actions-context.js +3 -3
  3. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +4 -4
  4. package/dist/_internal/components/data-display/Carousel/Carousel.js +2 -2
  5. package/dist/_internal/components/data-display/DataGrid/DataGrid.js +2 -2
  6. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +20 -20
  7. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +24 -24
  8. package/dist/_internal/components/data-display/DataViews/base-list-card-geometry.js +1 -1
  9. package/dist/_internal/components/data-display/DataViews/base-list-card-layout.js +1 -1
  10. package/dist/_internal/components/data-display/DataViews/base-list-card.js +2 -2
  11. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +4 -4
  12. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +13 -13
  13. package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +2 -2
  14. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +4 -4
  15. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +3 -3
  16. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +3 -3
  17. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +2 -2
  18. package/dist/_internal/components/data-display/FleetMap/FleetBody.js +13 -13
  19. package/dist/_internal/components/data-display/FleetMap/FleetCanvas.js +12 -12
  20. package/dist/_internal/components/data-display/FleetMap/FleetMap.js +14 -14
  21. package/dist/_internal/components/data-display/Lightbox/Lightbox.js +4 -4
  22. package/dist/_internal/components/data-display/MapPreview/MapDecorations.js +2 -2
  23. package/dist/_internal/components/data-display/MapPreview/MapLayers.js +2 -2
  24. package/dist/_internal/components/data-display/MapPreview/MapMarkers.js +2 -2
  25. package/dist/_internal/components/data-display/MapPreview/MapPreview.hooks.js +2 -2
  26. package/dist/_internal/components/data-display/MapPreview/MapPreview.js +11 -11
  27. package/dist/_internal/components/data-display/MapPreview/MapTiles.js +2 -2
  28. package/dist/_internal/components/data-display/MapPreview/StaticMapView.js +4 -4
  29. package/dist/_internal/components/data-display/MapPreview/useMapPreview.js +3 -3
  30. package/dist/_internal/components/data-display/RegionMap/RegionMap.js +3 -2
  31. package/dist/_internal/components/data-display/RegionMap/RegionMap.labels.js +12 -0
  32. package/dist/_internal/components/data-display/RegionMap/RegionMap.labels.js.map +1 -0
  33. package/dist/_internal/components/data-display/RegionMap/RegionMapDrawing.js +2 -1
  34. package/dist/_internal/components/data-display/index.js +27 -26
  35. package/dist/_internal/components/feedback/NotificationStatusNotice/NotificationStatusNotice.js +3 -3
  36. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.js +4 -4
  37. package/dist/_internal/components/feedback/SequenceResolver/SequenceResolver.parts.js +3 -3
  38. package/dist/_internal/components/feedback/index.js +5 -5
  39. package/dist/_internal/components/index.js +30 -29
  40. package/dist/{chunk-T6QVM5Q5.js → chunk-4LUWZ6UE.js} +2 -2
  41. package/dist/{chunk-JKIKHWD2.js → chunk-5NT2M2JI.js} +4 -4
  42. package/dist/{chunk-I7JUAHUO.js → chunk-65KJLPF5.js} +3 -3
  43. package/dist/{chunk-C74PDNTF.js → chunk-67ADHGA2.js} +2 -2
  44. package/dist/{chunk-ZLVRCQPG.js → chunk-A5CKUJP2.js} +5 -5
  45. package/dist/{chunk-A63WU4UZ.js → chunk-AKE6KVQG.js} +70 -41
  46. package/dist/chunk-AKE6KVQG.js.map +1 -0
  47. package/dist/{chunk-XMWTVDQN.js → chunk-B32554CQ.js} +12 -12
  48. package/dist/{chunk-OA6T2ME3.js → chunk-BLZDSCIZ.js} +4 -4
  49. package/dist/{chunk-OZYUE2BI.js → chunk-CJUK32UH.js} +4 -4
  50. package/dist/{chunk-HWPQKHN2.js → chunk-COKPPU42.js} +7 -7
  51. package/dist/{chunk-U4WAAZXJ.js → chunk-DVX4HDNP.js} +2 -2
  52. package/dist/{chunk-CESGZRVQ.js → chunk-EMSMJRP3.js} +4 -4
  53. package/dist/{chunk-IFX5KP65.js → chunk-EXVCVXFX.js} +4 -4
  54. package/dist/{chunk-U3HGQHEO.js → chunk-H7CZRKN7.js} +4 -4
  55. package/dist/{chunk-5ZC7CNU4.js → chunk-I2LMLQGQ.js} +2 -2
  56. package/dist/chunk-J3AWGTJY.js +29 -0
  57. package/dist/chunk-J3AWGTJY.js.map +1 -0
  58. package/dist/{chunk-K6VEN5VN.js → chunk-K3PMMW23.js} +4 -4
  59. package/dist/{chunk-YHPSPFU5.js → chunk-LIN4A2BW.js} +4 -4
  60. package/dist/{chunk-LXMFHD7M.js → chunk-LTSVYILY.js} +4 -4
  61. package/dist/{chunk-IEAPXS57.js → chunk-MW7P2C2C.js} +2 -2
  62. package/dist/{chunk-JVTZ7FWR.js → chunk-NCI74EOS.js} +4 -4
  63. package/dist/{chunk-IMCSY2SI.js → chunk-OKRITX4S.js} +7 -7
  64. package/dist/{chunk-LRQT4MQO.js → chunk-PJNIJIE6.js} +2 -2
  65. package/dist/{chunk-2WVHZIWP.js → chunk-QSRZOXQI.js} +2 -2
  66. package/dist/{chunk-IF5UY3CT.js → chunk-QVDEGBME.js} +4 -4
  67. package/dist/{chunk-XNMU32XC.js → chunk-TMUGBHHG.js} +2 -2
  68. package/dist/{chunk-AWUFS2ZC.js → chunk-U2DRE36S.js} +4 -4
  69. package/dist/{chunk-NEYZV6V5.js → chunk-UAOE3ZPC.js} +6 -6
  70. package/dist/{chunk-VHGHF537.js → chunk-V4CWJ7Q6.js} +2 -2
  71. package/dist/{chunk-3OL2TSEJ.js → chunk-WNNA6YYE.js} +2 -2
  72. package/dist/{chunk-T6YTDEY4.js → chunk-WXZCNHBX.js} +2 -2
  73. package/dist/{chunk-5XTPEA3O.js → chunk-ZHYLOB7Q.js} +2 -2
  74. package/dist/{chunk-ARMKSYEQ.js → chunk-ZO2GZRZS.js} +2 -2
  75. package/dist/data-display/Alert.js +2 -2
  76. package/dist/data-display/CardKit.js +7 -7
  77. package/dist/data-display/Carousel.js +2 -2
  78. package/dist/data-display/DataGrid.js +2 -2
  79. package/dist/data-display/DataViews.js +31 -31
  80. package/dist/data-display/FleetMap.js +14 -14
  81. package/dist/data-display/Lightbox.js +4 -4
  82. package/dist/data-display/MapPreview.js +11 -11
  83. package/dist/data-display/RegionMap.js +3 -2
  84. package/dist/feedback/NotificationStatusNotice.js +3 -3
  85. package/dist/feedback/SequenceResolver.js +4 -4
  86. package/dist/types/components/data-display/RegionMap/RegionMap.labels.d.ts +42 -0
  87. package/dist/types/components/data-display/RegionMap/RegionMap.labels.d.ts.map +1 -0
  88. package/dist/types/components/data-display/RegionMap/RegionMapDrawing.d.ts.map +1 -1
  89. package/package.json +1 -1
  90. package/src/components/data-display/RegionMap/RegionMap.labels.ts +60 -0
  91. package/src/components/data-display/RegionMap/RegionMapDrawing.tsx +87 -45
  92. package/dist/chunk-A63WU4UZ.js.map +0 -1
  93. /package/dist/{chunk-T6QVM5Q5.js.map → chunk-4LUWZ6UE.js.map} +0 -0
  94. /package/dist/{chunk-JKIKHWD2.js.map → chunk-5NT2M2JI.js.map} +0 -0
  95. /package/dist/{chunk-I7JUAHUO.js.map → chunk-65KJLPF5.js.map} +0 -0
  96. /package/dist/{chunk-C74PDNTF.js.map → chunk-67ADHGA2.js.map} +0 -0
  97. /package/dist/{chunk-ZLVRCQPG.js.map → chunk-A5CKUJP2.js.map} +0 -0
  98. /package/dist/{chunk-XMWTVDQN.js.map → chunk-B32554CQ.js.map} +0 -0
  99. /package/dist/{chunk-OA6T2ME3.js.map → chunk-BLZDSCIZ.js.map} +0 -0
  100. /package/dist/{chunk-OZYUE2BI.js.map → chunk-CJUK32UH.js.map} +0 -0
  101. /package/dist/{chunk-HWPQKHN2.js.map → chunk-COKPPU42.js.map} +0 -0
  102. /package/dist/{chunk-U4WAAZXJ.js.map → chunk-DVX4HDNP.js.map} +0 -0
  103. /package/dist/{chunk-CESGZRVQ.js.map → chunk-EMSMJRP3.js.map} +0 -0
  104. /package/dist/{chunk-IFX5KP65.js.map → chunk-EXVCVXFX.js.map} +0 -0
  105. /package/dist/{chunk-U3HGQHEO.js.map → chunk-H7CZRKN7.js.map} +0 -0
  106. /package/dist/{chunk-5ZC7CNU4.js.map → chunk-I2LMLQGQ.js.map} +0 -0
  107. /package/dist/{chunk-K6VEN5VN.js.map → chunk-K3PMMW23.js.map} +0 -0
  108. /package/dist/{chunk-YHPSPFU5.js.map → chunk-LIN4A2BW.js.map} +0 -0
  109. /package/dist/{chunk-LXMFHD7M.js.map → chunk-LTSVYILY.js.map} +0 -0
  110. /package/dist/{chunk-IEAPXS57.js.map → chunk-MW7P2C2C.js.map} +0 -0
  111. /package/dist/{chunk-JVTZ7FWR.js.map → chunk-NCI74EOS.js.map} +0 -0
  112. /package/dist/{chunk-IMCSY2SI.js.map → chunk-OKRITX4S.js.map} +0 -0
  113. /package/dist/{chunk-LRQT4MQO.js.map → chunk-PJNIJIE6.js.map} +0 -0
  114. /package/dist/{chunk-2WVHZIWP.js.map → chunk-QSRZOXQI.js.map} +0 -0
  115. /package/dist/{chunk-IF5UY3CT.js.map → chunk-QVDEGBME.js.map} +0 -0
  116. /package/dist/{chunk-XNMU32XC.js.map → chunk-TMUGBHHG.js.map} +0 -0
  117. /package/dist/{chunk-AWUFS2ZC.js.map → chunk-U2DRE36S.js.map} +0 -0
  118. /package/dist/{chunk-NEYZV6V5.js.map → chunk-UAOE3ZPC.js.map} +0 -0
  119. /package/dist/{chunk-VHGHF537.js.map → chunk-V4CWJ7Q6.js.map} +0 -0
  120. /package/dist/{chunk-3OL2TSEJ.js.map → chunk-WNNA6YYE.js.map} +0 -0
  121. /package/dist/{chunk-T6YTDEY4.js.map → chunk-WXZCNHBX.js.map} +0 -0
  122. /package/dist/{chunk-5XTPEA3O.js.map → chunk-ZHYLOB7Q.js.map} +0 -0
  123. /package/dist/{chunk-ARMKSYEQ.js.map → chunk-ZO2GZRZS.js.map} +0 -0
@@ -0,0 +1,42 @@
1
+ /** Where a label's centre may go along one axis so the whole label stays in view. */
2
+ export declare function clampCentre(wanted: number, half: number, span: number, gap: number): number;
3
+ /** An area that may carry a label, as {@link labelOrder} ranks it. */
4
+ interface Labelable {
5
+ id: string;
6
+ name: string;
7
+ excluded?: boolean;
8
+ geometry: {
9
+ area: number;
10
+ };
11
+ }
12
+ /**
13
+ * The order labels claim their place in: highlighted first (what the person is
14
+ * looking for), then excluded, then the larger area, then by name. When two
15
+ * labels would overlap, the earlier one is drawn and the later one hidden.
16
+ */
17
+ export declare function labelOrder<T extends Labelable>(areas: readonly T[], highlighted: ReadonlySet<string>): T[];
18
+ /** A label measured in the drawing: where it wants to be (fractions), and its size in px. */
19
+ export interface LabelBox {
20
+ fractionX: number;
21
+ fractionY: number;
22
+ spanX: number;
23
+ spanY: number;
24
+ }
25
+ /** Where each label goes, in px, and whether it is drawn at all. */
26
+ export interface PlacedLabel {
27
+ x: number;
28
+ y: number;
29
+ visible: boolean;
30
+ }
31
+ /**
32
+ * Lay labels out in priority order ({@link labelOrder}): each is pulled inside
33
+ * the drawing by its own size, and one whose box would overlap a label already
34
+ * drawn is hidden — the area keeps its name in its `<title>` and in the list,
35
+ * so hiding costs nothing but the clutter two names over each other made.
36
+ */
37
+ export declare function placeLabels(labels: readonly LabelBox[], box: {
38
+ spanX: number;
39
+ spanY: number;
40
+ }, gap: number): PlacedLabel[];
41
+ export {};
42
+ //# sourceMappingURL=RegionMap.labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegionMap.labels.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/RegionMap/RegionMap.labels.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAG3F;AAED,sEAAsE;AACtE,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAK1G;AAED,6FAA6F;AAC7F,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,EAAE,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE,CAU1H"}
@@ -1 +1 @@
1
- {"version":3,"file":"RegionMapDrawing.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/RegionMap/RegionMapDrawing.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAsD,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACxG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,cAAc,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC;IAC3C,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,UAAU,EACV,cAAc,EACd,SAAS,EACT,KAAK,EACL,QAAQ,EACR,MAAM,EACN,KAAK,GACN,EAAE,qBAAqB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAmE3C"}
1
+ {"version":3,"file":"RegionMapDrawing.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/RegionMap/RegionMapDrawing.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAsD,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACxG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,cAAc,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC;IAC3C,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,UAAU,EACV,cAAc,EACd,SAAS,EACT,KAAK,EACL,QAAQ,EACR,MAAM,EACN,KAAK,GACN,EAAE,qBAAqB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA4D3C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.86.0",
3
+ "version": "6.86.1",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -0,0 +1,60 @@
1
+ // How the labels over a RegionMap drawing are ranked and laid out.
2
+
3
+ /** Where a label's centre may go along one axis so the whole label stays in view. */
4
+ export function clampCentre(wanted: number, half: number, span: number, gap: number): number {
5
+ if (span <= 2 * (half + gap)) return span / 2;
6
+ return Math.min(span - half - gap, Math.max(half + gap, wanted));
7
+ }
8
+
9
+ /** An area that may carry a label, as {@link labelOrder} ranks it. */
10
+ interface Labelable {
11
+ id: string;
12
+ name: string;
13
+ excluded?: boolean;
14
+ geometry: { area: number };
15
+ }
16
+
17
+ /**
18
+ * The order labels claim their place in: highlighted first (what the person is
19
+ * looking for), then excluded, then the larger area, then by name. When two
20
+ * labels would overlap, the earlier one is drawn and the later one hidden.
21
+ */
22
+ export function labelOrder<T extends Labelable>(areas: readonly T[], highlighted: ReadonlySet<string>): T[] {
23
+ const rank = (area: T): number => (highlighted.has(area.id) ? 0 : area.excluded ? 1 : 2);
24
+ return [...areas].sort(
25
+ (a, b) => rank(a) - rank(b) || b.geometry.area - a.geometry.area || a.name.localeCompare(b.name),
26
+ );
27
+ }
28
+
29
+ /** A label measured in the drawing: where it wants to be (fractions), and its size in px. */
30
+ export interface LabelBox {
31
+ fractionX: number;
32
+ fractionY: number;
33
+ spanX: number;
34
+ spanY: number;
35
+ }
36
+
37
+ /** Where each label goes, in px, and whether it is drawn at all. */
38
+ export interface PlacedLabel {
39
+ x: number;
40
+ y: number;
41
+ visible: boolean;
42
+ }
43
+
44
+ /**
45
+ * Lay labels out in priority order ({@link labelOrder}): each is pulled inside
46
+ * the drawing by its own size, and one whose box would overlap a label already
47
+ * drawn is hidden — the area keeps its name in its `<title>` and in the list,
48
+ * so hiding costs nothing but the clutter two names over each other made.
49
+ */
50
+ export function placeLabels(labels: readonly LabelBox[], box: { spanX: number; spanY: number }, gap: number): PlacedLabel[] {
51
+ const kept: { x0: number; y0: number; x1: number; y1: number }[] = [];
52
+ return labels.map((label) => {
53
+ const x = clampCentre(label.fractionX * box.spanX, label.spanX / 2, box.spanX, gap);
54
+ const y = clampCentre(label.fractionY * box.spanY, label.spanY / 2, box.spanY, gap);
55
+ const rect = { x0: x - label.spanX / 2, y0: y - label.spanY / 2, x1: x + label.spanX / 2, y1: y + label.spanY / 2 };
56
+ const visible = !kept.some((o) => rect.x0 < o.x1 && o.x0 < rect.x1 && rect.y0 < o.y1 && o.y0 < rect.y1);
57
+ if (visible) kept.push(rect);
58
+ return { x, y, visible };
59
+ });
60
+ }
@@ -7,6 +7,7 @@ import { rem, remPx } from '../../../tokens/relative';
7
7
 
8
8
  import { labelPosition, type AreaGeometry, type DrawnRegion, type PlaneBox } from './RegionMap.helpers';
9
9
  import type { RegionMapModel } from './RegionMap.hooks';
10
+ import { labelOrder, placeLabels } from './RegionMap.labels';
10
11
 
11
12
  export interface RegionMapDrawingProps {
12
13
  model: RegionMapModel & { view: PlaneBox };
@@ -43,7 +44,11 @@ export function RegionMapDrawing({
43
44
  const { view, drawn } = model;
44
45
  const wanted = new Set(highlightedIds);
45
46
  const highlighted = drawn.filter((d) => wanted.has(d.id));
46
- const labelled = [...drawn.filter((d) => d.excluded), ...highlighted.filter((d) => !d.excluded)];
47
+ // Highlighted, then excluded, then larger, then by name: the order labels
48
+ // claim their place in, so a collision hides the less important one.
49
+ const labelled = labelOrder(drawn.filter((d) => d.excluded || wanted.has(d.id)), wanted);
50
+ const boxRef = React.useRef<HTMLDivElement>(null);
51
+ useLabelPlacement(boxRef, labelled.map((d) => d.id).join('|'), remPx(theme, 4));
47
52
  const base = drawn.map((d) => ({ id: d.id, name: d.name, geometry: d.geometry, outside: d.outside === true }));
48
53
  return (
49
54
  <Box
@@ -60,6 +65,7 @@ export function RegionMapDrawing({
60
65
  bgcolor: 'background.paper',
61
66
  }}
62
67
  data-testid={`${testId}-canvas`}
68
+ ref={boxRef}
63
69
  >
64
70
  <svg
65
71
  viewBox={`${view.minX} ${view.minY} ${view.spanX} ${view.spanY}`}
@@ -73,19 +79,7 @@ export function RegionMapDrawing({
73
79
  <BaseAreas areas={base} testId={testId} theme={theme} />
74
80
  <ExcludedAreas drawn={drawn} hatchId={hatchId} testId={testId} theme={theme} />
75
81
  <Circles radii={model.radii} theme={theme} />
76
- {highlighted.map((d) => (
77
- <path
78
- key={`h-${d.id}`}
79
- d={d.geometry.path}
80
- fillRule="evenodd"
81
- vectorEffect="non-scaling-stroke"
82
- fill={alpha(theme.palette.primary.main, 0.3)}
83
- stroke={theme.palette.primary.main}
84
- strokeWidth={2.5}
85
- pointerEvents="none"
86
- data-testid={`${testId}-highlight-${d.id}`}
87
- />
88
- ))}
82
+ <Highlights areas={highlighted} testId={testId} theme={theme} />
89
83
  {showCenter && (
90
84
  <circle
91
85
  cx={0}
@@ -107,6 +101,35 @@ export function RegionMapDrawing({
107
101
  );
108
102
  }
109
103
 
104
+ /** The highlighted areas, drawn over everything but the centre. */
105
+ function Highlights({
106
+ areas,
107
+ testId,
108
+ theme,
109
+ }: {
110
+ areas: readonly DrawnRegion[];
111
+ testId: string;
112
+ theme: Theme;
113
+ }): React.JSX.Element {
114
+ return (
115
+ <>
116
+ {areas.map((d) => (
117
+ <path
118
+ key={`h-${d.id}`}
119
+ d={d.geometry.path}
120
+ fillRule="evenodd"
121
+ vectorEffect="non-scaling-stroke"
122
+ fill={alpha(theme.palette.primary.main, 0.3)}
123
+ stroke={theme.palette.primary.main}
124
+ strokeWidth={2.5}
125
+ pointerEvents="none"
126
+ data-testid={`${testId}-highlight-${d.id}`}
127
+ />
128
+ ))}
129
+ </>
130
+ );
131
+ }
132
+
110
133
  /** The hatch an excluded area is filled with: lines, not a colour. */
111
134
  function Hatch({ id, pitch, theme }: { id: string; pitch: number; theme: Theme }): React.JSX.Element {
112
135
  return (
@@ -225,16 +248,52 @@ function Circles({ radii, theme }: { radii: readonly number[]; theme: Theme }):
225
248
  );
226
249
  }
227
250
 
228
- /** Where a label's centre may go along one axis so the whole label stays in view. */
229
- function clampCentre(wanted: number, half: number, span: number, gap: number): number {
230
- if (span <= 2 * (half + gap)) return span / 2;
231
- return Math.min(span - half - gap, Math.max(half + gap, wanted));
251
+ /**
252
+ * Places every label the drawing carries, in their DOM order (the priority
253
+ * order), and hides one that would overlap a label already drawn
254
+ * (`placeLabels`). Measured by a ResizeObserver, never in a layout effect: its
255
+ * first call comes after the browser's own layout, so mounting a label on a
256
+ * tap forces no synchronous reflow of the drawing.
257
+ */
258
+ function useLabelPlacement(boxRef: React.RefObject<HTMLDivElement | null>, key: string, gap: number): void {
259
+ React.useEffect(() => {
260
+ const box = boxRef.current;
261
+ if (!box || typeof ResizeObserver === 'undefined') return undefined;
262
+ const labels = Array.from(box.querySelectorAll<HTMLElement>('[data-region-label]'));
263
+ const place = (): void => {
264
+ const spanX = box.clientWidth;
265
+ const spanY = box.clientHeight;
266
+ if (spanX === 0 || spanY === 0) return;
267
+ const placed = placeLabels(
268
+ labels.map((label) => ({
269
+ fractionX: Number(label.dataset.fractionX),
270
+ fractionY: Number(label.dataset.fractionY),
271
+ spanX: label.offsetWidth,
272
+ spanY: label.offsetHeight,
273
+ })),
274
+ { spanX, spanY },
275
+ gap,
276
+ );
277
+ placed.forEach(({ x, y, visible }, index) => {
278
+ const label = labels[index]!;
279
+ label.style.left = `${(x / spanX) * 100}%`;
280
+ label.style.top = `${(y / spanY) * 100}%`;
281
+ label.style.visibility = visible ? 'visible' : 'hidden';
282
+ label.dataset.hidden = visible ? 'false' : 'true';
283
+ });
284
+ };
285
+ const observer = new ResizeObserver(place);
286
+ observer.observe(box);
287
+ for (const label of labels) observer.observe(label);
288
+ return () => observer.disconnect();
289
+ }, [boxRef, key, gap]);
232
290
  }
233
291
 
234
292
  /**
235
293
  * An area's name over the drawing, at the theme's type size whatever the
236
- * width — and moved in by its own measured size, so it is never clipped at
237
- * the drawing's edge.
294
+ * width. Placed — and hidden on a collision — by {@link useLabelPlacement}.
295
+ * Decorative (`aria-hidden`): the area's name is its `<title>` and its row in
296
+ * the list.
238
297
  */
239
298
  function AreaLabel({
240
299
  area,
@@ -248,42 +307,25 @@ function AreaLabel({
248
307
  theme: Theme;
249
308
  }): React.JSX.Element {
250
309
  const at = labelPosition(area.geometry.rings, view);
251
- const ref = React.useRef<HTMLSpanElement>(null);
252
- const gap = remPx(theme, 4);
253
- // Measured by a ResizeObserver, never in a layout effect: the observer's
254
- // first call comes after the browser's own layout, so mounting a label on a
255
- // tap forces no synchronous reflow of the drawing.
256
- React.useEffect(() => {
257
- const label = ref.current;
258
- const box = label?.parentElement;
259
- if (!label || !box || typeof ResizeObserver === 'undefined') return undefined;
260
- const observer = new ResizeObserver(() => {
261
- const width = box.clientWidth;
262
- const height = box.clientHeight;
263
- if (width === 0 || height === 0) return;
264
- const x = clampCentre(at.fractionX * width, label.offsetWidth / 2, width, gap);
265
- const y = clampCentre(at.fractionY * height, label.offsetHeight / 2, height, gap);
266
- label.style.left = `${(x / width) * 100}%`;
267
- label.style.top = `${(y / height) * 100}%`;
268
- });
269
- observer.observe(box);
270
- observer.observe(label);
271
- return () => observer.disconnect();
272
- }, [at.fractionX, at.fractionY, gap]);
273
310
  const halo = `0 0 ${rem(theme, 3)} ${theme.palette.background.paper}`;
274
311
  return (
275
312
  <Typography
276
- ref={ref}
277
313
  component="span"
278
314
  aria-hidden
279
315
  data-testid={`${testId}-label-${area.id}`}
316
+ data-region-label=""
317
+ data-fraction-x={at.fractionX}
318
+ data-fraction-y={at.fractionY}
280
319
  // Position as an inline style, not `sx`: a per-label class would insert
281
320
  // a new CSS rule (and a style recalculation) on every tap.
282
- style={{ left: `${(at.fractionX * 100).toFixed(2)}%`, top: `${(at.fractionY * 100).toFixed(2)}%` }}
321
+ style={{
322
+ left: `${(at.fractionX * 100).toFixed(2)}%`,
323
+ top: `${(at.fractionY * 100).toFixed(2)}%`,
324
+ }}
283
325
  sx={{
284
326
  position: 'absolute',
285
327
  transform: 'translate(-50%, -50%)',
286
- fontSize: rem(theme, 11),
328
+ fontSize: rem(theme, 12),
287
329
  fontWeight: 700,
288
330
  lineHeight: 1.1,
289
331
  textAlign: 'center',
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/RegionMap/RegionMapDrawing.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport { alpha, type Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { rem, remPx } from '../../../tokens/relative';\n\nimport { labelPosition, type AreaGeometry, type DrawnRegion, type PlaneBox } from './RegionMap.helpers';\nimport type { RegionMapModel } from './RegionMap.hooks';\n\nexport interface RegionMapDrawingProps {\n model: RegionMapModel & { view: PlaneBox };\n showCenter: boolean;\n highlightedIds: readonly string[];\n maxHeight: string | undefined;\n label: string;\n tappable: boolean;\n testId: string;\n theme: Theme;\n}\n\n/**\n * The drawing: one `role=\"img\"`, square, with HTML labels laid over it.\n *\n * Strokes are `non-scaling-stroke` and labels are HTML at theme sizes, so the\n * picture reads the same at 320 px and 1600 px; `maxHeight` caps it on a wide\n * screen. Where it sits in a wider box is the host's call.\n *\n * The base areas are a memoised layer: a toggle changes `excluded`, not the\n * geometry, so only the hatch, highlight and label layers re-render.\n */\nexport function RegionMapDrawing({\n model,\n showCenter,\n highlightedIds,\n maxHeight,\n label,\n tappable,\n testId,\n theme,\n}: RegionMapDrawingProps): React.JSX.Element {\n const hatchId = `${React.useId().replace(/:/g, '')}-hatch`;\n const { view, drawn } = model;\n const wanted = new Set(highlightedIds);\n const highlighted = drawn.filter((d) => wanted.has(d.id));\n const labelled = [...drawn.filter((d) => d.excluded), ...highlighted.filter((d) => !d.excluded)];\n const base = drawn.map((d) => ({ id: d.id, name: d.name, geometry: d.geometry, outside: d.outside === true }));\n return (\n <Box\n sx={{\n position: 'relative',\n width: '100%',\n maxWidth: maxHeight,\n maxHeight,\n aspectRatio: '1 / 1',\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n overflow: 'hidden',\n bgcolor: 'background.paper',\n }}\n data-testid={`${testId}-canvas`}\n >\n <svg\n viewBox={`${view.minX} ${view.minY} ${view.spanX} ${view.spanY}`}\n role=\"img\"\n aria-label={label}\n onPointerDown={model.onPointerDown}\n onClick={model.onClick}\n style={{ display: 'block', width: '100%', height: '100%', cursor: tappable ? 'pointer' : 'default' }}\n >\n <Hatch id={hatchId} pitch={view.spanX / 70} theme={theme} />\n <BaseAreas areas={base} testId={testId} theme={theme} />\n <ExcludedAreas drawn={drawn} hatchId={hatchId} testId={testId} theme={theme} />\n <Circles radii={model.radii} theme={theme} />\n {highlighted.map((d) => (\n <path\n key={`h-${d.id}`}\n d={d.geometry.path}\n fillRule=\"evenodd\"\n vectorEffect=\"non-scaling-stroke\"\n fill={alpha(theme.palette.primary.main, 0.3)}\n stroke={theme.palette.primary.main}\n strokeWidth={2.5}\n pointerEvents=\"none\"\n data-testid={`${testId}-highlight-${d.id}`}\n />\n ))}\n {showCenter && (\n <circle\n cx={0}\n cy={0}\n r={view.spanX / 90}\n fill={theme.palette.primary.main}\n stroke={theme.palette.background.paper}\n strokeWidth={2}\n vectorEffect=\"non-scaling-stroke\"\n pointerEvents=\"none\"\n data-testid={`${testId}-center`}\n />\n )}\n </svg>\n {labelled.map((d) => (\n <AreaLabel key={`l-${d.id}`} area={d} view={view} testId={testId} theme={theme} />\n ))}\n </Box>\n );\n}\n\n/** The hatch an excluded area is filled with: lines, not a colour. */\nfunction Hatch({ id, pitch, theme }: { id: string; pitch: number; theme: Theme }): React.JSX.Element {\n return (\n <defs>\n <pattern id={id} width={pitch} height={pitch} patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate(45)\">\n <rect width={pitch} height={pitch} fill={alpha(theme.palette.text.primary, 0.1)} />\n <rect width={pitch / 3} height={pitch} fill={theme.palette.text.secondary} />\n </pattern>\n </defs>\n );\n}\n\ninterface BaseArea {\n id: string;\n name: string;\n geometry: AreaGeometry;\n outside: boolean;\n}\n\ninterface BaseAreasProps {\n areas: readonly BaseArea[];\n testId: string;\n theme: Theme;\n}\n\n/** Same areas, same geometry objects, same look: nothing to re-render. */\nfunction sameBase(a: BaseAreasProps, b: BaseAreasProps): boolean {\n if (a.theme !== b.theme || a.testId !== b.testId || a.areas.length !== b.areas.length) return false;\n return a.areas.every((area, i) => {\n const other = b.areas[i]!;\n return (\n area.id === other.id &&\n area.geometry === other.geometry &&\n area.outside === other.outside &&\n area.name === other.name\n );\n });\n}\n\n/** Every area's fill and border — the tap targets. Memoised: see `sameBase`. */\nconst BaseAreas = React.memo(function BaseAreas({ areas, testId, theme }: BaseAreasProps): React.JSX.Element {\n const { palette } = theme;\n return (\n <>\n {areas.map((d) => (\n <path\n key={d.id}\n d={d.geometry.path}\n fillRule=\"evenodd\"\n vectorEffect=\"non-scaling-stroke\"\n fill={d.outside ? palette.background.paper : palette.action.hover}\n stroke={palette.divider}\n strokeWidth={1}\n data-region-id={d.id}\n data-testid={`${testId}-area-${d.id}`}\n >\n <title>{d.name}</title>\n </path>\n ))}\n </>\n );\n}, sameBase);\n\n/** The hatch over every excluded area. */\nfunction ExcludedAreas({\n drawn,\n hatchId,\n testId,\n theme,\n}: {\n drawn: readonly DrawnRegion[];\n hatchId: string;\n testId: string;\n theme: Theme;\n}): React.JSX.Element {\n return (\n <>\n {drawn\n .filter((d) => d.excluded)\n .map((d) => (\n <path\n key={`x-${d.id}`}\n d={d.geometry.path}\n fillRule=\"evenodd\"\n vectorEffect=\"non-scaling-stroke\"\n fill={`url(#${hatchId})`}\n stroke={theme.palette.text.secondary}\n strokeWidth={1.5}\n pointerEvents=\"none\"\n data-testid={`${testId}-excluded-${d.id}`}\n />\n ))}\n </>\n );\n}\n\n/** The dashed circles around the centre. */\nfunction Circles({ radii, theme }: { radii: readonly number[]; theme: Theme }): React.JSX.Element {\n return (\n <>\n {radii.map((radius) => (\n <circle\n key={radius}\n cx={0}\n cy={0}\n r={radius}\n fill=\"none\"\n stroke={theme.palette.info.main}\n strokeWidth={1.75}\n strokeDasharray=\"7 5\"\n vectorEffect=\"non-scaling-stroke\"\n pointerEvents=\"none\"\n />\n ))}\n </>\n );\n}\n\n/** Where a label's centre may go along one axis so the whole label stays in view. */\nfunction clampCentre(wanted: number, half: number, span: number, gap: number): number {\n if (span <= 2 * (half + gap)) return span / 2;\n return Math.min(span - half - gap, Math.max(half + gap, wanted));\n}\n\n/**\n * An area's name over the drawing, at the theme's type size whatever the\n * width — and moved in by its own measured size, so it is never clipped at\n * the drawing's edge.\n */\nfunction AreaLabel({\n area,\n view,\n testId,\n theme,\n}: {\n area: DrawnRegion;\n view: PlaneBox;\n testId: string;\n theme: Theme;\n}): React.JSX.Element {\n const at = labelPosition(area.geometry.rings, view);\n const ref = React.useRef<HTMLSpanElement>(null);\n const gap = remPx(theme, 4);\n // Measured by a ResizeObserver, never in a layout effect: the observer's\n // first call comes after the browser's own layout, so mounting a label on a\n // tap forces no synchronous reflow of the drawing.\n React.useEffect(() => {\n const label = ref.current;\n const box = label?.parentElement;\n if (!label || !box || typeof ResizeObserver === 'undefined') return undefined;\n const observer = new ResizeObserver(() => {\n const width = box.clientWidth;\n const height = box.clientHeight;\n if (width === 0 || height === 0) return;\n const x = clampCentre(at.fractionX * width, label.offsetWidth / 2, width, gap);\n const y = clampCentre(at.fractionY * height, label.offsetHeight / 2, height, gap);\n label.style.left = `${(x / width) * 100}%`;\n label.style.top = `${(y / height) * 100}%`;\n });\n observer.observe(box);\n observer.observe(label);\n return () => observer.disconnect();\n }, [at.fractionX, at.fractionY, gap]);\n const halo = `0 0 ${rem(theme, 3)} ${theme.palette.background.paper}`;\n return (\n <Typography\n ref={ref}\n component=\"span\"\n aria-hidden\n data-testid={`${testId}-label-${area.id}`}\n // Position as an inline style, not `sx`: a per-label class would insert\n // a new CSS rule (and a style recalculation) on every tap.\n style={{ left: `${(at.fractionX * 100).toFixed(2)}%`, top: `${(at.fractionY * 100).toFixed(2)}%` }}\n sx={{\n position: 'absolute',\n transform: 'translate(-50%, -50%)',\n fontSize: rem(theme, 11),\n fontWeight: 700,\n lineHeight: 1.1,\n textAlign: 'center',\n maxWidth: '45%',\n color: 'text.primary',\n pointerEvents: 'none',\n textShadow: `${halo}, ${halo}`,\n }}\n >\n {area.name}\n </Typography>\n );\n}\n"],"mappings":"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","names":["BaseAreas"]}