efficient-shell 4.127.0 → 4.132.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.
Files changed (128) hide show
  1. package/README.md +938 -5
  2. package/dist/{Browser-F5TKMP4K.js → Browser-XPVJ2K7B.js} +13 -10
  3. package/dist/Browser-XPVJ2K7B.js.map +1 -0
  4. package/dist/{Calculator-T3XG7S73.js → Calculator-ZXJPXG7F.js} +12 -9
  5. package/dist/Calculator-ZXJPXG7F.js.map +1 -0
  6. package/dist/ConfirmDialog-MTC7QMUJ.js +4 -0
  7. package/dist/{ConfirmDialog-LJXMB45A.js.map → ConfirmDialog-MTC7QMUJ.js.map} +1 -1
  8. package/dist/{CurrencyConverter-CASQMKLO.js → CurrencyConverter-WSJ2BBSX.js} +12 -9
  9. package/dist/CurrencyConverter-WSJ2BBSX.js.map +1 -0
  10. package/dist/{Documents-YX67YTZS.js → Documents-ONGSXUJI.js} +14 -11
  11. package/dist/Documents-ONGSXUJI.js.map +1 -0
  12. package/dist/{Files-QB63GO6J.js → Files-GQZMFWXQ.js} +17 -14
  13. package/dist/Files-GQZMFWXQ.js.map +1 -0
  14. package/dist/{Notepad-3SUD5XUE.js → Notepad-HM73XAM4.js} +15 -12
  15. package/dist/Notepad-HM73XAM4.js.map +1 -0
  16. package/dist/PdfViewer-M2YGZIYX.js +5 -0
  17. package/dist/{PdfViewer-LSP3YNFC.js.map → PdfViewer-M2YGZIYX.js.map} +1 -1
  18. package/dist/{PomodoroTimer-4EHD2QC4.js → PomodoroTimer-DG3LI5CX.js} +14 -11
  19. package/dist/PomodoroTimer-DG3LI5CX.js.map +1 -0
  20. package/dist/{Preview-4ZHR4SKP.js → Preview-TUSV4UMO.js} +16 -13
  21. package/dist/Preview-TUSV4UMO.js.map +1 -0
  22. package/dist/{Sidebar-J76PV4QF.js → Sidebar-BPU7RCZP.js} +5 -4
  23. package/dist/Sidebar-BPU7RCZP.js.map +1 -0
  24. package/dist/{Spreadsheet-G3SC7XJ5.js → Spreadsheet-3WTXCHYQ.js} +14 -11
  25. package/dist/Spreadsheet-3WTXCHYQ.js.map +1 -0
  26. package/dist/{Stock-TLT4VN2R.js → Stock-YMPOMUAW.js} +12 -9
  27. package/dist/Stock-YMPOMUAW.js.map +1 -0
  28. package/dist/{Weather-FRKX3DAK.js → Weather-NZO3CWLH.js} +12 -9
  29. package/dist/Weather-NZO3CWLH.js.map +1 -0
  30. package/dist/{WorldClock-GKCITO3M.js → WorldClock-HUC6D3AD.js} +12 -9
  31. package/dist/WorldClock-HUC6D3AD.js.map +1 -0
  32. package/dist/apps/index.js +26 -22
  33. package/dist/apps/index.js.map +1 -1
  34. package/dist/brand.css +1 -1
  35. package/dist/chunk-7IN7YZAL.js +547 -0
  36. package/dist/chunk-7IN7YZAL.js.map +1 -0
  37. package/dist/{chunk-YNGPTOL6.js → chunk-7MA5XBWD.js} +3 -3
  38. package/dist/{chunk-YNGPTOL6.js.map → chunk-7MA5XBWD.js.map} +1 -1
  39. package/dist/{chunk-WK7FV7VV.js → chunk-BJ34QOR3.js} +827 -625
  40. package/dist/chunk-BJ34QOR3.js.map +1 -0
  41. package/dist/{chunk-HNH7FOYB.js → chunk-CGXMWHQW.js} +7 -6
  42. package/dist/chunk-CGXMWHQW.js.map +1 -0
  43. package/dist/{chunk-XUCIYMN7.js → chunk-CJYO4RVV.js} +4 -4
  44. package/dist/{chunk-XUCIYMN7.js.map → chunk-CJYO4RVV.js.map} +1 -1
  45. package/dist/{chunk-N3CG5HG5.js → chunk-CLGNTGC5.js} +12 -11
  46. package/dist/chunk-CLGNTGC5.js.map +1 -0
  47. package/dist/chunk-D276EMXS.js +57 -0
  48. package/dist/chunk-D276EMXS.js.map +1 -0
  49. package/dist/{chunk-7KIOEKHS.js → chunk-E3TWIN7Z.js} +21 -135
  50. package/dist/chunk-E3TWIN7Z.js.map +1 -0
  51. package/dist/{chunk-2VM2FRZJ.js → chunk-EENVXIVU.js} +47 -67
  52. package/dist/chunk-EENVXIVU.js.map +1 -0
  53. package/dist/{chunk-YG45DAUT.js → chunk-FONEQRBH.js} +17 -11
  54. package/dist/chunk-FONEQRBH.js.map +1 -0
  55. package/dist/chunk-IPXN646P.js +14 -0
  56. package/dist/chunk-IPXN646P.js.map +1 -0
  57. package/dist/chunk-IRWSRJ4K.js +48 -0
  58. package/dist/chunk-IRWSRJ4K.js.map +1 -0
  59. package/dist/chunk-JMKYYPOF.js +7 -0
  60. package/dist/{chunk-CJZXR7MG.js.map → chunk-JMKYYPOF.js.map} +1 -1
  61. package/dist/{chunk-JFQGPY7W.js → chunk-KWEX4ZAF.js} +20 -17
  62. package/dist/chunk-KWEX4ZAF.js.map +1 -0
  63. package/dist/{chunk-OKEFO6TI.js → chunk-LQTVTOSY.js} +10 -10
  64. package/dist/chunk-LQTVTOSY.js.map +1 -0
  65. package/dist/{chunk-G4KA3SAX.js → chunk-MQPX6ERJ.js} +4 -4
  66. package/dist/{chunk-G4KA3SAX.js.map → chunk-MQPX6ERJ.js.map} +1 -1
  67. package/dist/{chunk-FQB24B42.js → chunk-SEWF4O3H.js} +3 -3
  68. package/dist/{chunk-FQB24B42.js.map → chunk-SEWF4O3H.js.map} +1 -1
  69. package/dist/{chunk-AXWC3332.js → chunk-TFXJRHJV.js} +3 -3
  70. package/dist/{chunk-AXWC3332.js.map → chunk-TFXJRHJV.js.map} +1 -1
  71. package/dist/{chunk-G3HQLQYZ.js → chunk-TQURTLK2.js} +59 -34
  72. package/dist/chunk-TQURTLK2.js.map +1 -0
  73. package/dist/{chunk-6XQHH7D2.js → chunk-TRCWINHF.js} +5 -5
  74. package/dist/{chunk-6XQHH7D2.js.map → chunk-TRCWINHF.js.map} +1 -1
  75. package/dist/{chunk-NPJCMM6V.js → chunk-VWZIGZFF.js} +4 -4
  76. package/dist/{chunk-NPJCMM6V.js.map → chunk-VWZIGZFF.js.map} +1 -1
  77. package/dist/{chunk-HYZ5QMVO.js → chunk-XH3ELPQZ.js} +22 -19
  78. package/dist/chunk-XH3ELPQZ.js.map +1 -0
  79. package/dist/{chunk-PPUZJFCI.js → chunk-ZKHD4J73.js} +8 -7
  80. package/dist/chunk-ZKHD4J73.js.map +1 -0
  81. package/dist/file-intake/index.js +3 -1
  82. package/dist/file-intake/index.js.map +1 -1
  83. package/dist/{index-dQia6NpB.d.ts → index-COwksV6G.d.ts} +699 -199
  84. package/dist/index.d.ts +2 -2
  85. package/dist/index.js +37 -32
  86. package/dist/index.js.map +1 -1
  87. package/dist/map/index.js +5 -3
  88. package/dist/map/index.js.map +1 -1
  89. package/dist/markdown/index.js +4 -3
  90. package/dist/markdown/index.js.map +1 -1
  91. package/dist/page-tabs/index.js +12 -8
  92. package/dist/page-tabs/index.js.map +1 -1
  93. package/dist/shell.css +17 -17
  94. package/dist/themes.css +15 -0
  95. package/dist/ui/index.d.ts +1 -1
  96. package/dist/ui/index.js +17 -13
  97. package/dist/ui/index.js.map +1 -1
  98. package/dist/ui.css +691 -283
  99. package/package.json +1 -1
  100. package/dist/Browser-F5TKMP4K.js.map +0 -1
  101. package/dist/Calculator-T3XG7S73.js.map +0 -1
  102. package/dist/ConfirmDialog-LJXMB45A.js +0 -3
  103. package/dist/CurrencyConverter-CASQMKLO.js.map +0 -1
  104. package/dist/Documents-YX67YTZS.js.map +0 -1
  105. package/dist/Files-QB63GO6J.js.map +0 -1
  106. package/dist/Notepad-3SUD5XUE.js.map +0 -1
  107. package/dist/PdfViewer-LSP3YNFC.js +0 -4
  108. package/dist/PomodoroTimer-4EHD2QC4.js.map +0 -1
  109. package/dist/Preview-4ZHR4SKP.js.map +0 -1
  110. package/dist/Sidebar-J76PV4QF.js.map +0 -1
  111. package/dist/Spreadsheet-G3SC7XJ5.js.map +0 -1
  112. package/dist/Stock-TLT4VN2R.js.map +0 -1
  113. package/dist/Weather-FRKX3DAK.js.map +0 -1
  114. package/dist/WorldClock-GKCITO3M.js.map +0 -1
  115. package/dist/chunk-2VM2FRZJ.js.map +0 -1
  116. package/dist/chunk-3CUSBZWG.js +0 -59
  117. package/dist/chunk-3CUSBZWG.js.map +0 -1
  118. package/dist/chunk-7KIOEKHS.js.map +0 -1
  119. package/dist/chunk-CJZXR7MG.js +0 -7
  120. package/dist/chunk-G3HQLQYZ.js.map +0 -1
  121. package/dist/chunk-HNH7FOYB.js.map +0 -1
  122. package/dist/chunk-HYZ5QMVO.js.map +0 -1
  123. package/dist/chunk-JFQGPY7W.js.map +0 -1
  124. package/dist/chunk-N3CG5HG5.js.map +0 -1
  125. package/dist/chunk-OKEFO6TI.js.map +0 -1
  126. package/dist/chunk-PPUZJFCI.js.map +0 -1
  127. package/dist/chunk-WK7FV7VV.js.map +0 -1
  128. package/dist/chunk-YG45DAUT.js.map +0 -1
package/dist/map/index.js CHANGED
@@ -1,4 +1,6 @@
1
- import { Button_default } from '../chunk-OKEFO6TI.js';
1
+ "use client";
2
+ import { Button_default } from '../chunk-LQTVTOSY.js';
3
+ import { rootIsDark, ROOT_THEME_ATTRIBUTES } from '../chunk-IPXN646P.js';
2
4
  import { useRef, useState, useEffect, useCallback, useMemo, useSyncExternalStore } from 'react';
3
5
  import { jsxs, jsx } from 'react/jsx-runtime';
4
6
  import { useQuery } from '@tanstack/react-query';
@@ -38,10 +40,10 @@ function loadMapLibre() {
38
40
  }
39
41
  function subscribe(onChange) {
40
42
  const watcher = new MutationObserver(onChange);
41
- watcher.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
43
+ watcher.observe(document.documentElement, { attributes: true, attributeFilter: ROOT_THEME_ATTRIBUTES });
42
44
  return () => watcher.disconnect();
43
45
  }
44
- var isDark = () => document.documentElement.getAttribute("data-theme") === "dark";
46
+ var isDark = rootIsDark;
45
47
  function useDarkTheme() {
46
48
  return useSyncExternalStore(subscribe, isDark, () => false);
47
49
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/map/attribution.ts","../../src/map/maplibre.ts","../../src/map/useDarkTheme.ts","../../src/map/EntityMap.tsx","../../src/map/points.ts","../../src/map/MapOverlays.tsx","../../src/map/useMapPoints.ts","../../src/map/LocationsMap.tsx"],"names":["jsx","jsxs","useRef","useEffect","useState"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Keep the map's credit line (\"OpenFreeMap © OpenMapTiles Data from\n * OpenStreetMap\") folded behind its (i) button until someone clicks it —\n * expanded, it runs across the bottom of a compact map.\n *\n * MapLibre's compact attribution control unfolds ITSELF once: the first time\n * the attribution text arrives with the style's sources it adds\n * `maplibregl-compact` together with `maplibregl-compact-show`, which can be\n * well after the map's `load` (folding on `load` or `idle` lost that race). So\n * this watches the control and removes `maplibregl-compact-show` the first\n * time it appears — the same class MapLibre itself drops when the map is\n * dragged — then stops watching. From then on the (i) toggles the text as\n * usual: MapLibre only unfolds automatically while the control is not yet\n * compact, and after this it always is.\n *\n * Returns the cleanup that disconnects the watcher if the map unmounts first.\n */\nexport const COMPACT_SHOW = 'maplibregl-compact-show';\n\nexport function foldAttributionOnce(control: Element | null): () => void {\n if (!control) return () => {};\n const fold = () => {\n if (!control.classList.contains(COMPACT_SHOW)) return false;\n control.classList.remove(COMPACT_SHOW);\n return true;\n };\n if (fold()) return () => {};\n const watcher = new MutationObserver(() => {\n if (fold()) watcher.disconnect();\n });\n watcher.observe(control, { attributes: true, attributeFilter: ['class'] });\n return () => watcher.disconnect();\n}\n","/**\n * MapLibre, fetched only when a map is on screen: `EntityMap` calls\n * `loadMapLibre()` as it mounts, so its ~1 MB (and its stylesheet) never rides\n * in a page that shows no map. `maplibre-gl` is an OPTIONAL peer of this\n * package, installed by the portals that draw maps, and it must stay behind\n * this dynamic import — `scripts/verify-dist.mjs` fails a build that reaches\n * it statically.\n *\n * The worker. MapLibre 6 starts a MODULE worker from a URL it is given, and\n * that worker (`maplibre-gl-worker.mjs`) imports `./maplibre-gl-shared.mjs`\n * beside it — so the host serves both files, unhashed, next to each other,\n * under the installed version:\n *\n * /maplibre-gl/<version>/maplibre-gl-worker.mjs\n * /maplibre-gl/<version>/maplibre-gl-shared.mjs\n *\n * The versioned path means an upgrade can never pair a cached old worker with\n * a new main thread. In the Next.js portals that is a static route handler,\n * `app/maplibre-gl/[version]/[file]/route.ts`, copying the two files verbatim\n * from `node_modules/maplibre-gl/dist` at export time.\n */\ntype MapLibre = typeof import('maplibre-gl');\n\nexport const MAPLIBRE_WORKER_FILE = 'maplibre-gl-worker.mjs';\n\n/** Where the host serves the worker for a given MapLibre version. */\nexport const mapLibreWorkerUrl = (version: string) => `/maplibre-gl/${version}/${MAPLIBRE_WORKER_FILE}`;\n\nlet loading: Promise<MapLibre> | null = null;\n\n/** MapLibre, loaded once per page; a failed load is retried by the next map. */\nexport function loadMapLibre(): Promise<MapLibre> {\n loading ??= Promise.all([import('maplibre-gl'), import('maplibre-gl/dist/maplibre-gl.css')])\n .then(([maplibre]) => {\n maplibre.setWorkerUrl(mapLibreWorkerUrl(maplibre.getVersion()));\n return maplibre;\n })\n .catch((error: unknown) => {\n loading = null;\n throw error;\n });\n return loading;\n}\n","import { useSyncExternalStore } from 'react';\n\n/**\n * True while the page is in the dark theme; re-renders when it changes. The\n * map canvas cannot follow a stylesheet — it swaps its tile style — so it has\n * to be told.\n *\n * Reads the `data-theme` attribute on `<html>` rather than calling `useTheme`:\n * every host stamps that attribute (the desktop shell through `useTheme`, the\n * routed portals through their own theme toggle), while `useTheme` is the\n * desktop's and owns side effects (it removes the attribute on unmount).\n */\nfunction subscribe(onChange: () => void): () => void {\n const watcher = new MutationObserver(onChange);\n watcher.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });\n return () => watcher.disconnect();\n}\n\nconst isDark = () => document.documentElement.getAttribute('data-theme') === 'dark';\n\nexport function useDarkTheme(): boolean {\n return useSyncExternalStore(subscribe, isDark, () => false);\n}\n","import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport type { GeoJSONSource, Map as MapLibreMap, Marker } from 'maplibre-gl';\nimport { foldAttributionOnce } from './attribution';\nimport { loadMapLibre } from './maplibre';\nimport { useDarkTheme } from './useDarkTheme';\n\n/**\n * Records on a map. One pin per placed record: a button carrying a glyph (or\n * an icon) and, for a screen reader, the record's name in words, never the\n * colour alone. The one map every EFFICIENT portal draws — the admin portal's\n * Customer / Supplier maps and Trip Planner, the dealer portal's Address Book.\n *\n * Ported from Foyerboard's leads map, which draws its map the way\n * whoson.today does: MapLibre over OpenFreeMap's vector tiles — free,\n * keyless, OpenStreetMap data (the same data the backend geocodes against), a\n * light and a dark style. The tiles load straight from tiles.openfreemap.org,\n * so a host with a Content-Security-Policy must allow that origin.\n *\n * MapLibre is fetched only once the map mounts (`loadMapLibre`, which also\n * says how the host serves its worker), and the map is made once per mount;\n * pins, the selection, the theme and the fitted view are separate effects so\n * a re-render never rebuilds the map.\n *\n * It also draws ROUTES — the Trip Planner's days, each a line under the pins\n * (solid along the streets, dashed where it is only a straight-line estimate),\n * again after a theme change, which replaces the style and every layer on it.\n */\n\nexport interface MapPin {\n key: string;\n label: string;\n lat: number;\n lng: number;\n /** One or two characters drawn on the pin (when it has no `icon`). */\n glyph: string;\n /** An icon drawn instead of the glyph: SVG path data, 24 × 24, stroked —\n * the admin Trip Planner's stage icons. */\n icon?: readonly string[];\n /** A small tag on the pin's corner — a visit's place in the day's order. */\n badge?: string;\n /** Tone classes for the badge; white by default. */\n badgeTone?: string;\n /** Tone classes (background + text) — see `pinTone`. */\n tone: string;\n /** The pin marks an area, not the address: drawn dashed and faded. */\n approximate?: boolean;\n /** Shown for context only (another day's visits on the Trip Planner): drawn faint. */\n faded?: boolean;\n}\n\n/** A line on the map: a day's road, or its straight-line estimate. */\nexport interface MapRoute {\n key: string;\n /** [lng, lat] pairs, in order. */\n coordinates: [number, number][];\n /** Straight lines between the points, not the road: drawn dashed. */\n estimate: boolean;\n /** Context only (another day): drawn faint. */\n faded?: boolean;\n}\n\nconst ROUTE_SOURCE = 'routes';\n// The GeoJSON MapLibre's source takes, from its own types (no direct @types/geojson dependency).\ntype RouteData = Exclude<Parameters<GeoJSONSource['setData']>[0], string>;\n\n/**\n * The accent the theme gives a blue control, read off a probe element — the\n * shell's themes remap `text-blue-600` to their accent, and the map canvas\n * cannot read a class, only a colour.\n */\nfunction accentColor(): string {\n const probe = document.createElement('span');\n probe.className = 'text-blue-600';\n probe.style.display = 'none';\n document.body.appendChild(probe);\n const color = getComputedStyle(probe).color || '#2563eb';\n probe.remove();\n return toRgb(color);\n}\n\n/**\n * Any CSS colour the browser knows, as `rgb()` — what MapLibre can parse.\n * Tailwind v4's default palette computes to `oklch(…)`, which MapLibre\n * silently fails to read, drawing the route in nothing at all (only the\n * themes that remap blue to an `rgb` accent showed a line).\n */\nfunction toRgb(css: string): string {\n const ctx = document.createElement('canvas').getContext('2d');\n if (!ctx) return '#2563eb';\n ctx.fillStyle = '#2563eb';\n ctx.fillStyle = css;\n ctx.fillRect(0, 0, 1, 1);\n const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;\n return `rgb(${r}, ${g}, ${b})`;\n}\n\nconst STYLES = {\n light: 'https://tiles.openfreemap.org/styles/positron',\n dark: 'https://tiles.openfreemap.org/styles/dark',\n} as const;\n\n/** Where the view opens before it has fitted the pins: the whole world. */\nconst WORLD: { center: [number, number]; zoom: number } = { center: [30, 20], zoom: 1 };\n\ntype MapLibre = typeof import('maplibre-gl');\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nfunction iconElement(paths: readonly string[], selected: boolean): SVGSVGElement {\n const svg = document.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '2');\n svg.setAttribute('aria-hidden', 'true');\n svg.setAttribute('class', selected ? 'h-4 w-4' : 'h-3.5 w-3.5');\n for (const d of paths) {\n const path = document.createElementNS(SVG_NS, 'path');\n path.setAttribute('stroke-linecap', 'round');\n path.setAttribute('stroke-linejoin', 'round');\n path.setAttribute('d', d);\n svg.appendChild(path);\n }\n return svg;\n}\n\nfunction pinElement(pin: MapPin, selected: boolean): HTMLButtonElement {\n const el = document.createElement('button');\n el.type = 'button';\n el.title = pin.approximate ? `${pin.label} (approximate)` : pin.label;\n el.setAttribute('aria-label', pin.approximate ? `${pin.label}, approximate location` : pin.label);\n el.dataset.pin = pin.key;\n el.className = [\n 'relative grid place-items-center rounded-full border-2 border-white font-semibold leading-none shadow-md cursor-pointer',\n pin.tone,\n selected ? 'h-8 w-8 text-xs ring-2 ring-gray-900 ring-offset-1' : 'h-6 w-6 text-[10px]',\n pin.approximate ? 'border-dashed opacity-80' : '',\n pin.faded && !selected ? 'opacity-40' : '',\n ].join(' ');\n if (pin.icon?.length) el.appendChild(iconElement(pin.icon, selected));\n else el.textContent = pin.glyph;\n if (pin.badge) {\n const badge = document.createElement('span');\n badge.setAttribute('aria-hidden', 'true');\n badge.className = [\n 'absolute -right-2 -top-2 grid h-4 min-w-4 place-items-center rounded-full px-0.5 text-[9px] font-semibold leading-none shadow',\n pin.badgeTone ?? 'bg-white text-gray-900',\n ].join(' ');\n badge.textContent = pin.badge;\n el.appendChild(badge);\n }\n return el;\n}\n\nexport default function EntityMap({\n pins,\n selectedKey,\n focus,\n onSelect,\n ariaLabel,\n className = '',\n children,\n routes,\n fitTo,\n attribution,\n}: {\n pins: MapPin[];\n selectedKey: string | null;\n /** Bump `n` to fly to a pin (the list's \"show on map\"). */\n focus: { key: string; n: number } | null;\n onSelect: (key: string) => void;\n ariaLabel: string;\n className?: string;\n /** Drawn over the map: the selected record's card, the legend. */\n children?: ReactNode;\n /** Lines under the pins — the Trip Planner's day routes. */\n routes?: MapRoute[];\n /** Fit the view to these pins only (the day being planned); all pins when unset. */\n fitTo?: string[] | null;\n /** Extra credit line for the attribution (HTML), e.g. the router's. Read once, at mount. */\n attribution?: string;\n}) {\n const box = useRef<HTMLDivElement>(null);\n const map = useRef<MapLibreMap | null>(null);\n const lib = useRef<MapLibre | null>(null);\n const markers = useRef<Marker[]>([]);\n const fitted = useRef('');\n const [ready, setReady] = useState(false);\n const [failed, setFailed] = useState(false);\n const theme = useDarkTheme() ? 'dark' : 'light';\n // What the map's own listeners (made once) call: the latest render's props.\n const latest = useRef({ onSelect, pins, selectedKey });\n useEffect(() => { latest.current = { onSelect, pins, selectedKey }; });\n\n /** Pan (never zoom) to the chosen pin when it is out of view — a panel\n * opening beside the map narrows it, and can cover the very pin chosen.\n * Reads refs only, so the resize observer made at mount can call it. */\n const revealSelected = useCallback(() => {\n const m = map.current;\n const { pins: shown, selectedKey: chosen } = latest.current;\n const pin = chosen ? shown.find((p) => p.key === chosen) : undefined;\n if (!m || !pin || m.getBounds().contains([pin.lng, pin.lat])) return;\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n m.easeTo({ center: [pin.lng, pin.lat], duration: reduced ? 0 : 400 });\n }, []);\n\n // The map itself, once per mount.\n useEffect(() => {\n let cancelled = false;\n let created: MapLibreMap | null = null;\n let observer: ResizeObserver | null = null;\n let unfoldWatch: () => void = () => {};\n void (async () => {\n try {\n const maplibre = await loadMapLibre();\n if (cancelled || !box.current) return;\n lib.current = maplibre;\n created = new maplibre.Map({\n container: box.current,\n style: STYLES[theme],\n center: WORLD.center,\n zoom: WORLD.zoom,\n attributionControl: attribution\n ? { compact: true, customAttribution: attribution }\n : { compact: true },\n dragRotate: false,\n pitchWithRotate: false,\n touchPitch: false,\n maxPitch: 0,\n });\n created.touchZoomRotate.disableRotation();\n created.keyboard.disableRotation();\n created.addControl(new maplibre.NavigationControl({ showCompass: false }), 'top-right');\n // Tiles that do not come (offline, the tile host down) leave a blank\n // map; the pins still stand on it and the list has everything.\n created.on('error', (event) => console.warn('[map]', event.error?.message));\n // The credit line starts folded behind its (i) — see attribution.ts.\n unfoldWatch = foldAttributionOnce(box.current.querySelector('.maplibregl-ctrl-attrib'));\n // A shell window resizes without a window resize event, which is all\n // MapLibre listens for — without this the canvas keeps its first size.\n const current = created;\n observer = new ResizeObserver(() => { current.resize(); revealSelected(); });\n observer.observe(box.current);\n map.current = created;\n setReady(true);\n } catch (error) {\n console.error('[map] could not start', error);\n if (!cancelled) setFailed(true);\n }\n })();\n return () => {\n cancelled = true;\n observer?.disconnect();\n unfoldWatch();\n markers.current.forEach((m) => m.remove());\n markers.current = [];\n created?.remove();\n map.current = null;\n };\n // The theme is applied by the effect below; the map is made once.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // The theme's tiles.\n const shownTheme = useRef(theme);\n useEffect(() => {\n if (!ready || !map.current || shownTheme.current === theme) return;\n shownTheme.current = theme;\n map.current.setStyle(STYLES[theme]);\n }, [ready, theme]);\n\n // The pins — the selected one last, so it sits on top.\n useEffect(() => {\n const m = map.current;\n const maplibre = lib.current;\n if (!ready || !m || !maplibre) return;\n markers.current.forEach((marker) => marker.remove());\n markers.current = [];\n const ordered = [...pins].sort((a, b) => Number(a.key === selectedKey) - Number(b.key === selectedKey));\n for (const pin of ordered) {\n const el = pinElement(pin, pin.key === selectedKey);\n el.addEventListener('click', (event) => {\n event.stopPropagation();\n latest.current.onSelect(pin.key);\n });\n markers.current.push(new maplibre.Marker({ element: el, anchor: 'center' }).setLngLat([pin.lng, pin.lat]).addTo(m));\n }\n }, [ready, pins, selectedKey]);\n\n // Keep a newly chosen pin in view.\n useEffect(() => {\n if (!ready || !selectedKey) return;\n const frame = requestAnimationFrame(revealSelected);\n return () => cancelAnimationFrame(frame);\n }, [ready, selectedKey, revealSelected]);\n\n // Fit the pins when WHICH pins are shown changes — not on every redraw, so\n // a poll that adds one located address doesn't yank the view the user panned.\n useEffect(() => {\n const m = map.current;\n const maplibre = lib.current;\n const target = fitTo ? pins.filter((p) => fitTo.includes(p.key)) : pins;\n if (!ready || !m || !maplibre || target.length === 0) return;\n const key = target.map((p) => `${p.key}@${p.lat},${p.lng}`).sort().join('|');\n if (fitted.current === key) return;\n const first = fitted.current === '';\n fitted.current = key;\n if (target.length === 1) {\n m.jumpTo({ center: [target[0].lng, target[0].lat], zoom: 13 });\n return;\n }\n const bounds = new maplibre.LngLatBounds([target[0].lng, target[0].lat], [target[0].lng, target[0].lat]);\n for (const p of target) bounds.extend([p.lng, p.lat]);\n m.fitBounds(bounds, { padding: 48, maxZoom: 14, animate: !first });\n }, [ready, pins, fitTo]);\n\n // The routes, as lines under the pins. A theme change replaces the style —\n // and with it every source and layer — so they are drawn again on each\n // `style.load`, not only when the routes change.\n useEffect(() => {\n const m = map.current;\n if (!ready || !m) return;\n const data: RouteData = {\n type: 'FeatureCollection',\n features: (routes ?? []).filter((r) => r.coordinates.length >= 2).map((r) => ({\n type: 'Feature',\n properties: { key: r.key, estimate: r.estimate, faded: !!r.faded },\n geometry: { type: 'LineString', coordinates: r.coordinates },\n })),\n };\n // Idempotent: each piece (the source, each layer) is made if missing and\n // updated if there, so a draw cut short by a style still loading never\n // leaves a source without its layers.\n const draw = () => {\n const color = accentColor();\n const opacity = ['case', ['get', 'faded'], 0.25, 0.85] as unknown as number;\n const source = m.getSource<GeoJSONSource>(ROUTE_SOURCE);\n if (source) source.setData(data);\n else m.addSource(ROUTE_SOURCE, { type: 'geojson', data });\n if (m.getLayer('routes-road')) m.setPaintProperty('routes-road', 'line-color', color);\n else m.addLayer({\n id: 'routes-road', type: 'line', source: ROUTE_SOURCE,\n filter: ['!', ['get', 'estimate']],\n layout: { 'line-join': 'round', 'line-cap': 'round' },\n paint: { 'line-color': color, 'line-width': 4, 'line-opacity': opacity },\n });\n if (m.getLayer('routes-estimate')) m.setPaintProperty('routes-estimate', 'line-color', color);\n else m.addLayer({\n id: 'routes-estimate', type: 'line', source: ROUTE_SOURCE,\n filter: ['get', 'estimate'],\n layout: { 'line-join': 'round', 'line-cap': 'round' },\n paint: { 'line-color': color, 'line-width': 3, 'line-dasharray': [1.5, 1.5], 'line-opacity': opacity },\n });\n };\n // Not gated on `isStyleLoaded()`: that is false whenever TILES are still\n // loading (after any pan or zoom), which skipped the redraw and left a\n // reordered day showing its old line. Before the style itself is in, the\n // map refuses sources and layers — `style.load` draws then.\n const tryDraw = () => {\n try {\n draw();\n } catch (error) {\n console.debug('[map] routes wait for the style', error);\n }\n };\n tryDraw();\n m.on('style.load', tryDraw);\n // A style that finished loading between this effect's first try and its\n // listener would never fire `style.load` again; `idle` catches it.\n m.once('idle', tryDraw);\n return () => {\n m.off('style.load', tryDraw);\n m.off('idle', tryDraw);\n };\n }, [ready, routes, theme]);\n\n // Fly to a pin the list asked to show.\n useEffect(() => {\n const m = map.current;\n if (!ready || !m || !focus) return;\n const pin = pins.find((p) => p.key === focus.key);\n if (!pin) return;\n const to = { center: [pin.lng, pin.lat] as [number, number], zoom: Math.max(m.getZoom(), 13) };\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) m.jumpTo(to);\n else m.flyTo({ ...to, duration: 600 });\n // `focus.n` is what asks; the pins changing under it does not.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ready, focus?.n]);\n\n return (\n <div className={`relative min-h-72 overflow-hidden rounded-lg border border-gray-200 bg-gray-100 ${className}`}>\n {/* Out of flow in a box of its own: MapLibre makes its container `position: relative`. */}\n <div className=\"absolute inset-0\">\n <div ref={box} className=\"h-full w-full\" role=\"region\" aria-label={ariaLabel} />\n </div>\n {failed && (\n <p className=\"absolute inset-0 grid place-items-center p-6 text-center text-sm text-gray-500\">\n The map could not be drawn here. The list has everything it shows.\n </p>\n )}\n {children}\n </div>\n );\n}\n","/**\n * Map points — the wire shape every EFFICIENT map endpoint answers with, and\n * the pure logic over it: what a point says about itself, how its pin looks,\n * and the counts over a list of them. Kept free of React and MapLibre so it is\n * testable without WebGL.\n *\n * The shape is efficient-backend's `logistics.serializers.MapPointsSerializer`\n * (the `address-geocoding` capability): the admin portal's Customer / Supplier\n * maps and Trip Planner, and the dealer portal's Address Book. Every record\n * comes back, pinned or not — a point off the map has null `lat` / `lng` and a\n * `placement` saying why, so a list beside the map can explain it instead of\n * silently dropping the row. The backend never makes a request wait on the\n * geocoder: an address it has not seen is `pending`, and the caller asks again\n * while `pending > 0` (`mapPointsRefetchInterval`).\n *\n * Moved here from the admin portal (`src/components/map/mapPoints.ts`) so the\n * dealer portal draws the same map rather than a second one.\n */\n\n/**\n * located — the geocoder matched the street\n * approximate — only the suburb / postcode / city matched; the pin marks the area\n * pending — still being looked up (poll again)\n * not_found — the geocoder found nothing for this address\n * failed — the map service could not answer; retried later\n * no_address — too little to look up: no city or postcode (and none written into the street)\n */\nexport type MapPlacement = 'located' | 'approximate' | 'pending' | 'not_found' | 'failed' | 'no_address';\n\n/**\n * One record on (or off) a map. `kind` is the record type the endpoint pinned\n * (`customer`, `delivery_location`, `supplier`, `warehouse`, the Trip Planner's\n * `office` / `trip_place` / `trip_stop` …); a portal narrows it to the kinds it\n * reads with the type parameter.\n */\nexport interface MapPoint<K extends string = string> {\n kind: K;\n id: string;\n label: string;\n /** CID / MID where the record has one. */\n code: string;\n /** What the pin is coloured by: the customer type, the supplier type, or the kind. */\n category: string;\n /** The address as one line. */\n address: string;\n placement: MapPlacement;\n lat: number | null;\n lng: number | null;\n /** What the geocoder matched, when placed. */\n matched: string;\n}\n\nexport interface MapPoints<P extends MapPoint = MapPoint> {\n points: P[];\n /** Points still being looked up — poll again while this is above zero. */\n pending: number;\n}\n\n/** How often to ask again while addresses are still being located. */\nexport const PENDING_POLL_MS = 3000;\n\n/** A react-query `refetchInterval` for a map endpoint: every few seconds while\n * the backend is still locating addresses, not at all once every point is settled. */\nexport const mapPointsRefetchInterval = (data: Pick<MapPoints, 'pending'> | undefined): number | false =>\n (data?.pending ? PENDING_POLL_MS : false);\n\n/** A point's identity across kinds — a customer and one of its delivery\n * locations are different pins even if two ids ever collided. */\nexport const pointKey = (p: Pick<MapPoint, 'kind' | 'id'>) => `${p.kind}:${p.id}`;\n\n/** On the map = placed by the geocoder AND carrying coordinates. */\nexport const isOnMap = <P extends MapPoint>(p: P): p is P & { lat: number; lng: number } =>\n (p.placement === 'located' || p.placement === 'approximate') && p.lat != null && p.lng != null;\n\n/** Why a point is not (exactly) where its address is — null when it is. */\nexport function placementNote(placement: MapPlacement): string | null {\n switch (placement) {\n case 'located': return null;\n case 'approximate': return 'Approximate: only the suburb or postcode matched';\n case 'pending': return 'Locating…';\n case 'not_found': return 'Not on the map: address not found';\n case 'failed': return 'Not on the map: the map service could not be reached';\n case 'no_address': return 'Not on the map: no city or postcode';\n }\n}\n\n/** A pin colour and the legend's word for it. */\nexport interface MapCategory {\n label: string;\n /** Tone classes (background + text) for the pin and its legend swatch. */\n tone: string;\n}\n\n/**\n * Pin colours by category. Customers follow the admin Customers list's Type\n * badge families (importer, dealer purple, retail green); a record's\n * secondary locations (delivery locations, warehouses) are amber.\n *\n * Importer and supplier are SKY, not blue: this package's themes remap\n * `bg-blue-600` to the theme's accent (themes.css), so in the green theme\n * importer pins came out the same green as retail. None of the other families\n * here is remapped — a categorical colour must not follow the accent the way\n * a button does.\n *\n * A portal that names a category differently passes its own map with the same\n * tones (the dealer's own head office is its \"Billing address\", not \"Dealer\").\n */\nexport const MAP_CATEGORIES: Readonly<Record<string, MapCategory>> = {\n importer: { label: 'Importer', tone: 'bg-sky-600 text-white' },\n dealer: { label: 'Dealer', tone: 'bg-purple-600 text-white' },\n retail: { label: 'Retail', tone: 'bg-green-600 text-white' },\n supplier: { label: 'Supplier', tone: 'bg-sky-600 text-white' },\n delivery_location: { label: 'Delivery location', tone: 'bg-amber-600 text-white' },\n warehouse: { label: 'Warehouse', tone: 'bg-amber-600 text-white' },\n};\nconst DEFAULT_TONE = 'bg-gray-700 text-white';\n/** The legend's order. */\nconst CATEGORY_ORDER = ['importer', 'dealer', 'retail', 'supplier', 'delivery_location', 'warehouse'];\n\n/** Suppliers colour by the kind rather than their free-text type (Factory,\n * Shipping Forwarder…), which has no fixed palette to map onto. */\nexport function pinCategory(p: Pick<MapPoint, 'kind' | 'category'>): string {\n if (p.kind === 'supplier') return 'supplier';\n if (p.kind === 'customer') return p.category || 'importer';\n return p.kind;\n}\n\nexport const categoryTone = (category: string, categories: Readonly<Record<string, MapCategory>> = MAP_CATEGORIES): string =>\n categories[category]?.tone ?? DEFAULT_TONE;\n\nexport const categoryLabel = (category: string, categories: Readonly<Record<string, MapCategory>> = MAP_CATEGORIES): string =>\n categories[category]?.label ?? category;\n\nexport const pinTone = (p: Pick<MapPoint, 'kind' | 'category'>, categories?: Readonly<Record<string, MapCategory>>): string =>\n categoryTone(pinCategory(p), categories);\n\n/**\n * The character on a pin. A profile map numbers its extra locations (1, 2, 3 —\n * the same numbers the list shows) and marks the head office with its\n * category's initial; a list-wide map shows each record's category initial.\n */\nexport function pinGlyph(p: MapPoint, number: number | null, categories: Readonly<Record<string, MapCategory>> = MAP_CATEGORIES): string {\n if (number != null) return String(number);\n return (categories[pinCategory(p)]?.label ?? '').charAt(0).toUpperCase();\n}\n\n/** The kinds a profile map numbers: a record's secondary locations. */\nexport const SECONDARY_KINDS: readonly string[] = ['delivery_location', 'warehouse'];\n\n/**\n * The numbers a profile map gives its secondary locations, in list order; the\n * record itself gets none. An unplaced location keeps its number, so the list\n * and the pins never renumber as addresses resolve.\n */\nexport function locationNumbers(points: MapPoint[], kinds: readonly string[] = SECONDARY_KINDS): Map<string, number> {\n const numbers = new Map<string, number>();\n let n = 0;\n for (const p of points) {\n if (kinds.includes(p.kind)) numbers.set(pointKey(p), ++n);\n }\n return numbers;\n}\n\nexport interface MapSummary {\n onMap: number;\n approximate: number;\n pending: number;\n off: number;\n}\n\nexport function summarize(points: MapPoint[]): MapSummary {\n const summary: MapSummary = { onMap: 0, approximate: 0, pending: 0, off: 0 };\n for (const p of points) {\n if (isOnMap(p)) {\n summary.onMap += 1;\n if (p.placement === 'approximate') summary.approximate += 1;\n } else if (p.placement === 'pending') summary.pending += 1;\n else summary.off += 1;\n }\n return summary;\n}\n\n/** \"12 on the map · 2 approximate · 3 not on the map\" — the list header. */\nexport function summaryText(s: MapSummary): string {\n const parts = [`${s.onMap} on the map`];\n if (s.approximate) parts.push(`${s.approximate} approximate`);\n if (s.pending) parts.push(`${s.pending} locating`);\n if (s.off) parts.push(`${s.off} not on the map`);\n return parts.join(' · ');\n}\n\n/** The categories present among the placed points, in a stable order — the\n * legend. Categories outside the palette follow, in the order first seen. */\nexport function legendCategories(points: MapPoint[]): string[] {\n const present = [...new Set(points.filter(isOnMap).map(pinCategory))];\n const known = CATEGORY_ORDER.filter((c) => present.includes(c));\n return [...known, ...present.filter((c) => !CATEGORY_ORDER.includes(c))];\n}\n\n/**\n * The records a compact map can't pin, each with its reason, for the notes\n * drawn over the map (a map with no list beside it). Pending points are left\n * out — they are counted as \"Locating…\" instead.\n */\nexport function offMapNotes(points: MapPoint[], numbers?: ReadonlyMap<string, number>): { key: string; text: string }[] {\n return points\n .filter((p) => !isOnMap(p) && p.placement !== 'pending')\n .map((p) => {\n const n = numbers?.get(pointKey(p));\n const note = placementNote(p.placement)?.replace(/^Not on the map: /, '') ?? '';\n return { key: pointKey(p), text: `${n != null ? `${n}. ` : ''}${p.label}: ${note}` };\n });\n}\n","import Button from '../forms/Button';\nimport { MAP_CATEGORIES, categoryLabel, categoryTone, pinCategory, placementNote, type MapCategory, type MapPoint } from './points';\n\n/**\n * What sits over an `EntityMap` — the legend and the chosen record's card.\n * Both take the portal's category map (`MAP_CATEGORIES` unless it names a\n * category its own way), so the legend's words and the card's match the pins.\n */\n\n/** Which colour means what; drawn only when the map shows more than one.\n * `floating` pins it to the map's top-left corner; off, the caller places it\n * (a compact map stacks its notes under it). */\nexport function MapLegend({ categories, palette = MAP_CATEGORIES, floating = true }: {\n categories: string[];\n palette?: Readonly<Record<string, MapCategory>>;\n floating?: boolean;\n}) {\n if (categories.length < 2) return null;\n return (\n <div className={`${floating ? 'absolute left-3 top-3 z-10 ' : ''}flex flex-wrap gap-x-3 gap-y-1 rounded-lg border border-gray-200 bg-white px-2.5 py-1.5 text-xs text-gray-600 shadow-sm`}>\n {categories.map((c) => (\n <span key={c} className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className={`h-2.5 w-2.5 rounded-full ${categoryTone(c, palette)}`} />\n {categoryLabel(c, palette)}\n </span>\n ))}\n </div>\n );\n}\n\n/** The chosen record's card, over the map's bottom edge. */\nexport function SelectedCard({ point, number, onOpen, onClose, palette = MAP_CATEGORIES }: {\n point: MapPoint;\n number: number | null;\n /** Open the record's own page or window; null when it has none. */\n onOpen: (() => void) | null;\n onClose: () => void;\n palette?: Readonly<Record<string, MapCategory>>;\n}) {\n const note = placementNote(point.placement);\n const category = pinCategory(point);\n return (\n <section aria-label={`${point.label}, chosen on the map`}\n className=\"absolute bottom-3 left-3 right-3 z-10 max-w-md space-y-1.5 rounded-lg border border-gray-200 bg-white p-3 shadow-md\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-gray-900 [overflow-wrap:anywhere]\">\n {number != null && <span className=\"mr-1 text-gray-400\">{number}.</span>}\n {point.label}\n {point.code && <span className=\"ml-1.5 text-xs font-normal text-gray-400\">{point.code}</span>}\n </h3>\n <p className=\"text-xs text-gray-500\">{palette[category] ? categoryLabel(category, palette) : point.category}</p>\n </div>\n <button type=\"button\" onClick={onClose} aria-label=\"Close\" className=\"rounded p-0.5 text-gray-400 hover:text-gray-600\">\n <svg aria-hidden className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" /></svg>\n </button>\n </div>\n <p className=\"text-sm text-gray-700 [overflow-wrap:anywhere]\">{point.address || '—'}</p>\n {note && <p className=\"text-xs text-gray-500\">{note}</p>}\n {point.matched && point.placement === 'approximate' && (\n <p className=\"text-xs text-gray-400 [overflow-wrap:anywhere]\">Placed at: {point.matched}</p>\n )}\n {onOpen && (\n <div className=\"flex justify-end\">\n <Button variant=\"secondary\" size=\"sm\" onClick={onOpen}>Open</Button>\n </div>\n )}\n </section>\n );\n}\n","import { useQuery, type QueryKey } from '@tanstack/react-query';\nimport { mapPointsRefetchInterval, type MapPoints } from './points';\n\n/**\n * A map endpoint's points, asked again every few seconds while the backend is\n * still locating addresses (`pending > 0`) and not at all once every point is\n * settled. The backend never makes the request wait on the geocoder, so a\n * first visit shows the list at once and the pins arrive as they are found.\n */\nexport function useMapPoints<T extends MapPoints>(\n queryKey: QueryKey,\n queryFn: () => Promise<T>,\n { staleTime }: { staleTime?: number } = {},\n) {\n return useQuery({\n queryKey,\n queryFn,\n staleTime,\n refetchInterval: (query) => mapPointsRefetchInterval(query.state.data),\n });\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { QueryKey } from '@tanstack/react-query';\nimport EntityMap, { type MapPin } from './EntityMap';\nimport { MapLegend, SelectedCard } from './MapOverlays';\nimport {\n MAP_CATEGORIES, isOnMap, legendCategories, locationNumbers, offMapNotes, pinGlyph, pinTone, pointKey, summarize,\n summaryText, type MapCategory, type MapPoint, type MapPoints,\n} from './points';\nimport { useMapPoints } from './useMapPoints';\n\n/** How many off-map records the notes box names before it says \"+N more\". */\nconst NOTES_SHOWN = 3;\n\n/**\n * A record's Locations map — compact, with no list beside it, because the\n * page it sits on already lists the locations. The admin portal puts it on the\n * customer / supplier Details tab beside the score (a customer's head office\n * and delivery locations, a supplier's address and warehouses); the dealer\n * portal puts it on the Address Book (the billing address and the delivery\n * addresses). What a list would say lives over the map: the legend,\n * \"Locating…\" while addresses are looked up, and a note for each record that\n * has no pin and why. Clicking a pin shows its card.\n *\n * `refreshSignal` changes when the records it pins change elsewhere on the\n * page (a delivery location added or edited, the address edited), and the\n * points are asked again; `staleTime: 0` does the same whenever it remounts.\n *\n * Moved from the admin portal's `ProfileLocationsMap`.\n */\nexport default function LocationsMap({\n queryKey,\n queryFn,\n refreshSignal,\n onOpen,\n ariaLabel,\n title = 'Locations',\n palette = MAP_CATEGORIES,\n numbers: givenNumbers,\n className = '',\n mapClassName = 'min-h-[240px] flex-1',\n}: {\n queryKey: QueryKey;\n queryFn: () => Promise<MapPoints>;\n refreshSignal?: unknown;\n /** Open a point's own page or window; null for points that have none. */\n onOpen?: (point: MapPoint) => ((() => void) | null);\n ariaLabel: string;\n /** The heading over the map. */\n title?: string;\n /** How the portal names and colours each category (the legend, the card, the head-office initial). */\n palette?: Readonly<Record<string, MapCategory>>;\n /** The number each pin shows, by `pointKey` — the numbers the page's own list\n * shows. Unset: the secondary locations numbered in the endpoint's order. */\n numbers?: ReadonlyMap<string, number>;\n className?: string;\n /** Sizing for the map box (it grows to fill the section by default). */\n mapClassName?: string;\n}) {\n const { data, isError, refetch } = useMapPoints(queryKey, queryFn, { staleTime: 0 });\n const mounted = useRef(false);\n useEffect(() => {\n if (!mounted.current) {\n mounted.current = true;\n return;\n }\n void refetch();\n // Only a change of the signal asks again; `refetch` is stable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [refreshSignal]);\n\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n const points = useMemo(() => data?.points ?? [], [data]);\n const numbers = useMemo(() => givenNumbers ?? locationNumbers(points), [givenNumbers, points]);\n const pins: MapPin[] = useMemo(() => points.filter(isOnMap).map((p) => ({\n key: pointKey(p),\n label: p.label,\n lat: p.lat,\n lng: p.lng,\n glyph: pinGlyph(p, numbers.get(pointKey(p)) ?? null, palette),\n tone: pinTone(p, palette),\n approximate: p.placement === 'approximate',\n })), [points, numbers, palette]);\n const notes = useMemo(() => offMapNotes(points, numbers), [points, numbers]);\n const legend = useMemo(() => legendCategories(points), [points]);\n const pending = data?.pending ?? 0;\n const selected = points.find((p) => pointKey(p) === selectedKey) ?? null;\n const open = selected && onOpen ? onOpen(selected) : null;\n\n return (\n <section aria-label={title} className={`flex min-w-0 flex-col ${className}`}>\n {/* The score beside it heads its card with the period picker (30px tall);\n the same row height keeps the two boxes' tops level. */}\n <div className=\"flex min-h-[30px] items-center justify-between mb-2\">\n <h3 className=\"text-sm font-semibold text-gray-900\">{title}</h3>\n {data && <span className=\"text-xs text-gray-500\">{summaryText(summarize(points))}</span>}\n </div>\n <EntityMap pins={pins} selectedKey={selectedKey} focus={null} onSelect={setSelectedKey}\n ariaLabel={ariaLabel} className={mapClassName}>\n {/* Top-left, under the legend: the bottom edge belongs to the map's\n OpenStreetMap credit and the chosen record's card. */}\n <div className=\"absolute left-3 top-3 z-10 flex max-w-[70%] flex-col items-start gap-1.5\">\n <MapLegend categories={legend} palette={palette} floating={false} />\n {isError && (\n <div className=\"flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-2.5 py-1.5 text-xs text-gray-600 shadow-sm\">\n Couldn’t load the locations.\n <button type=\"button\" onClick={() => void refetch()} className=\"font-medium text-blue-600 hover:text-blue-800 hover:underline\">\n Try again\n </button>\n </div>\n )}\n {!isError && (pending > 0 || notes.length > 0) && (\n <div className=\"max-w-full space-y-0.5 rounded-lg border border-gray-200 bg-white px-2.5 py-1.5 text-xs shadow-sm\" aria-live=\"polite\">\n {pending > 0 && (\n <p className=\"flex items-center gap-1.5 text-gray-500\">\n <svg className=\"h-3 w-3 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\"><circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" /><path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" /></svg>\n Locating {pending} address{pending === 1 ? '' : 'es'}…\n </p>\n )}\n {notes.slice(0, NOTES_SHOWN).map((n) => (\n <p key={n.key} className=\"truncate text-amber-700\" title={n.text}>{n.text}</p>\n ))}\n {notes.length > NOTES_SHOWN && (\n <p className=\"text-gray-500\">+{notes.length - NOTES_SHOWN} more not on the map</p>\n )}\n </div>\n )}\n </div>\n {selected && (\n <SelectedCard point={selected} number={numbers.get(pointKey(selected)) ?? null} onOpen={open}\n onClose={() => setSelectedKey(null)} palette={palette} />\n )}\n </EntityMap>\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../src/map/attribution.ts","../../src/map/maplibre.ts","../../src/map/useDarkTheme.ts","../../src/map/EntityMap.tsx","../../src/map/points.ts","../../src/map/MapOverlays.tsx","../../src/map/useMapPoints.ts","../../src/map/LocationsMap.tsx"],"names":["jsx","jsxs","useRef","useEffect","useState"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Keep the map's credit line (\"OpenFreeMap © OpenMapTiles Data from\n * OpenStreetMap\") folded behind its (i) button until someone clicks it —\n * expanded, it runs across the bottom of a compact map.\n *\n * MapLibre's compact attribution control unfolds ITSELF once: the first time\n * the attribution text arrives with the style's sources it adds\n * `maplibregl-compact` together with `maplibregl-compact-show`, which can be\n * well after the map's `load` (folding on `load` or `idle` lost that race). So\n * this watches the control and removes `maplibregl-compact-show` the first\n * time it appears — the same class MapLibre itself drops when the map is\n * dragged — then stops watching. From then on the (i) toggles the text as\n * usual: MapLibre only unfolds automatically while the control is not yet\n * compact, and after this it always is.\n *\n * Returns the cleanup that disconnects the watcher if the map unmounts first.\n */\nexport const COMPACT_SHOW = 'maplibregl-compact-show';\n\nexport function foldAttributionOnce(control: Element | null): () => void {\n if (!control) return () => {};\n const fold = () => {\n if (!control.classList.contains(COMPACT_SHOW)) return false;\n control.classList.remove(COMPACT_SHOW);\n return true;\n };\n if (fold()) return () => {};\n const watcher = new MutationObserver(() => {\n if (fold()) watcher.disconnect();\n });\n watcher.observe(control, { attributes: true, attributeFilter: ['class'] });\n return () => watcher.disconnect();\n}\n","/**\n * MapLibre, fetched only when a map is on screen: `EntityMap` calls\n * `loadMapLibre()` as it mounts, so its ~1 MB (and its stylesheet) never rides\n * in a page that shows no map. `maplibre-gl` is an OPTIONAL peer of this\n * package, installed by the portals that draw maps, and it must stay behind\n * this dynamic import — `scripts/verify-dist.mjs` fails a build that reaches\n * it statically.\n *\n * The worker. MapLibre 6 starts a MODULE worker from a URL it is given, and\n * that worker (`maplibre-gl-worker.mjs`) imports `./maplibre-gl-shared.mjs`\n * beside it — so the host serves both files, unhashed, next to each other,\n * under the installed version:\n *\n * /maplibre-gl/<version>/maplibre-gl-worker.mjs\n * /maplibre-gl/<version>/maplibre-gl-shared.mjs\n *\n * The versioned path means an upgrade can never pair a cached old worker with\n * a new main thread. In the Next.js portals that is a static route handler,\n * `app/maplibre-gl/[version]/[file]/route.ts`, copying the two files verbatim\n * from `node_modules/maplibre-gl/dist` at export time.\n */\ntype MapLibre = typeof import('maplibre-gl');\n\nexport const MAPLIBRE_WORKER_FILE = 'maplibre-gl-worker.mjs';\n\n/** Where the host serves the worker for a given MapLibre version. */\nexport const mapLibreWorkerUrl = (version: string) => `/maplibre-gl/${version}/${MAPLIBRE_WORKER_FILE}`;\n\nlet loading: Promise<MapLibre> | null = null;\n\n/** MapLibre, loaded once per page; a failed load is retried by the next map. */\nexport function loadMapLibre(): Promise<MapLibre> {\n loading ??= Promise.all([import('maplibre-gl'), import('maplibre-gl/dist/maplibre-gl.css')])\n .then(([maplibre]) => {\n maplibre.setWorkerUrl(mapLibreWorkerUrl(maplibre.getVersion()));\n return maplibre;\n })\n .catch((error: unknown) => {\n loading = null;\n throw error;\n });\n return loading;\n}\n","import { useSyncExternalStore } from 'react';\nimport { rootIsDark, ROOT_THEME_ATTRIBUTES } from '../utils/rootTheme';\n\n/**\n * True while the page is in the dark theme; re-renders when it changes. The\n * map canvas cannot follow a stylesheet — it swaps its tile style — so it has\n * to be told.\n *\n * Reads `<html>` rather than calling `useTheme`: every host stamps the theme\n * there (the desktop shell through `useTheme`, the routed portals through their\n * own theme toggle, a next-themes app as a `dark` class), while `useTheme` is\n * the desktop's and owns side effects (it removes the attribute on unmount).\n * `rootIsDark` accepts either marker, exactly as ui.css does.\n */\nfunction subscribe(onChange: () => void): () => void {\n const watcher = new MutationObserver(onChange);\n watcher.observe(document.documentElement, { attributes: true, attributeFilter: ROOT_THEME_ATTRIBUTES });\n return () => watcher.disconnect();\n}\n\nconst isDark = rootIsDark;\n\nexport function useDarkTheme(): boolean {\n return useSyncExternalStore(subscribe, isDark, () => false);\n}\n","import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport type { GeoJSONSource, Map as MapLibreMap, Marker } from 'maplibre-gl';\nimport { foldAttributionOnce } from './attribution';\nimport { loadMapLibre } from './maplibre';\nimport { useDarkTheme } from './useDarkTheme';\n\n/**\n * Records on a map. One pin per placed record: a button carrying a glyph (or\n * an icon) and, for a screen reader, the record's name in words, never the\n * colour alone. The one map every EFFICIENT portal draws — the admin portal's\n * Customer / Supplier maps and Trip Planner, the dealer portal's Address Book.\n *\n * Ported from Foyerboard's leads map, which draws its map the way\n * whoson.today does: MapLibre over OpenFreeMap's vector tiles — free,\n * keyless, OpenStreetMap data (the same data the backend geocodes against), a\n * light and a dark style. The tiles load straight from tiles.openfreemap.org,\n * so a host with a Content-Security-Policy must allow that origin.\n *\n * MapLibre is fetched only once the map mounts (`loadMapLibre`, which also\n * says how the host serves its worker), and the map is made once per mount;\n * pins, the selection, the theme and the fitted view are separate effects so\n * a re-render never rebuilds the map.\n *\n * It also draws ROUTES — the Trip Planner's days, each a line under the pins\n * (solid along the streets, dashed where it is only a straight-line estimate),\n * again after a theme change, which replaces the style and every layer on it.\n */\n\nexport interface MapPin {\n key: string;\n label: string;\n lat: number;\n lng: number;\n /** One or two characters drawn on the pin (when it has no `icon`). */\n glyph: string;\n /** An icon drawn instead of the glyph: SVG path data, 24 × 24, stroked —\n * the admin Trip Planner's stage icons. */\n icon?: readonly string[];\n /** A small tag on the pin's corner — a visit's place in the day's order. */\n badge?: string;\n /** Tone classes for the badge; white by default. */\n badgeTone?: string;\n /** Tone classes (background + text) — see `pinTone`. */\n tone: string;\n /** The pin marks an area, not the address: drawn dashed and faded. */\n approximate?: boolean;\n /** Shown for context only (another day's visits on the Trip Planner): drawn faint. */\n faded?: boolean;\n}\n\n/** A line on the map: a day's road, or its straight-line estimate. */\nexport interface MapRoute {\n key: string;\n /** [lng, lat] pairs, in order. */\n coordinates: [number, number][];\n /** Straight lines between the points, not the road: drawn dashed. */\n estimate: boolean;\n /** Context only (another day): drawn faint. */\n faded?: boolean;\n}\n\nconst ROUTE_SOURCE = 'routes';\n// The GeoJSON MapLibre's source takes, from its own types (no direct @types/geojson dependency).\ntype RouteData = Exclude<Parameters<GeoJSONSource['setData']>[0], string>;\n\n/**\n * The accent the theme gives a blue control, read off a probe element — the\n * shell's themes remap `text-blue-600` to their accent, and the map canvas\n * cannot read a class, only a colour.\n */\nfunction accentColor(): string {\n const probe = document.createElement('span');\n probe.className = 'text-blue-600';\n probe.style.display = 'none';\n document.body.appendChild(probe);\n const color = getComputedStyle(probe).color || '#2563eb';\n probe.remove();\n return toRgb(color);\n}\n\n/**\n * Any CSS colour the browser knows, as `rgb()` — what MapLibre can parse.\n * Tailwind v4's default palette computes to `oklch(…)`, which MapLibre\n * silently fails to read, drawing the route in nothing at all (only the\n * themes that remap blue to an `rgb` accent showed a line).\n */\nfunction toRgb(css: string): string {\n const ctx = document.createElement('canvas').getContext('2d');\n if (!ctx) return '#2563eb';\n ctx.fillStyle = '#2563eb';\n ctx.fillStyle = css;\n ctx.fillRect(0, 0, 1, 1);\n const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;\n return `rgb(${r}, ${g}, ${b})`;\n}\n\nconst STYLES = {\n light: 'https://tiles.openfreemap.org/styles/positron',\n dark: 'https://tiles.openfreemap.org/styles/dark',\n} as const;\n\n/** Where the view opens before it has fitted the pins: the whole world. */\nconst WORLD: { center: [number, number]; zoom: number } = { center: [30, 20], zoom: 1 };\n\ntype MapLibre = typeof import('maplibre-gl');\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nfunction iconElement(paths: readonly string[], selected: boolean): SVGSVGElement {\n const svg = document.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '2');\n svg.setAttribute('aria-hidden', 'true');\n svg.setAttribute('class', selected ? 'h-4 w-4' : 'h-3.5 w-3.5');\n for (const d of paths) {\n const path = document.createElementNS(SVG_NS, 'path');\n path.setAttribute('stroke-linecap', 'round');\n path.setAttribute('stroke-linejoin', 'round');\n path.setAttribute('d', d);\n svg.appendChild(path);\n }\n return svg;\n}\n\nfunction pinElement(pin: MapPin, selected: boolean): HTMLButtonElement {\n const el = document.createElement('button');\n el.type = 'button';\n el.title = pin.approximate ? `${pin.label} (approximate)` : pin.label;\n el.setAttribute('aria-label', pin.approximate ? `${pin.label}, approximate location` : pin.label);\n el.dataset.pin = pin.key;\n el.className = [\n 'relative grid place-items-center rounded-full border-2 border-white font-semibold leading-none shadow-md cursor-pointer',\n pin.tone,\n selected ? 'h-8 w-8 text-xs ring-2 ring-gray-900 ring-offset-1' : 'h-6 w-6 text-[10px]',\n pin.approximate ? 'border-dashed opacity-80' : '',\n pin.faded && !selected ? 'opacity-40' : '',\n ].join(' ');\n if (pin.icon?.length) el.appendChild(iconElement(pin.icon, selected));\n else el.textContent = pin.glyph;\n if (pin.badge) {\n const badge = document.createElement('span');\n badge.setAttribute('aria-hidden', 'true');\n badge.className = [\n 'absolute -right-2 -top-2 grid h-4 min-w-4 place-items-center rounded-full px-0.5 text-[9px] font-semibold leading-none shadow',\n pin.badgeTone ?? 'bg-white text-gray-900',\n ].join(' ');\n badge.textContent = pin.badge;\n el.appendChild(badge);\n }\n return el;\n}\n\nexport default function EntityMap({\n pins,\n selectedKey,\n focus,\n onSelect,\n ariaLabel,\n className = '',\n children,\n routes,\n fitTo,\n attribution,\n}: {\n pins: MapPin[];\n selectedKey: string | null;\n /** Bump `n` to fly to a pin (the list's \"show on map\"). */\n focus: { key: string; n: number } | null;\n onSelect: (key: string) => void;\n ariaLabel: string;\n className?: string;\n /** Drawn over the map: the selected record's card, the legend. */\n children?: ReactNode;\n /** Lines under the pins — the Trip Planner's day routes. */\n routes?: MapRoute[];\n /** Fit the view to these pins only (the day being planned); all pins when unset. */\n fitTo?: string[] | null;\n /** Extra credit line for the attribution (HTML), e.g. the router's. Read once, at mount. */\n attribution?: string;\n}) {\n const box = useRef<HTMLDivElement>(null);\n const map = useRef<MapLibreMap | null>(null);\n const lib = useRef<MapLibre | null>(null);\n const markers = useRef<Marker[]>([]);\n const fitted = useRef('');\n const [ready, setReady] = useState(false);\n const [failed, setFailed] = useState(false);\n const theme = useDarkTheme() ? 'dark' : 'light';\n // What the map's own listeners (made once) call: the latest render's props.\n const latest = useRef({ onSelect, pins, selectedKey });\n useEffect(() => { latest.current = { onSelect, pins, selectedKey }; });\n\n /** Pan (never zoom) to the chosen pin when it is out of view — a panel\n * opening beside the map narrows it, and can cover the very pin chosen.\n * Reads refs only, so the resize observer made at mount can call it. */\n const revealSelected = useCallback(() => {\n const m = map.current;\n const { pins: shown, selectedKey: chosen } = latest.current;\n const pin = chosen ? shown.find((p) => p.key === chosen) : undefined;\n if (!m || !pin || m.getBounds().contains([pin.lng, pin.lat])) return;\n const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n m.easeTo({ center: [pin.lng, pin.lat], duration: reduced ? 0 : 400 });\n }, []);\n\n // The map itself, once per mount.\n useEffect(() => {\n let cancelled = false;\n let created: MapLibreMap | null = null;\n let observer: ResizeObserver | null = null;\n let unfoldWatch: () => void = () => {};\n void (async () => {\n try {\n const maplibre = await loadMapLibre();\n if (cancelled || !box.current) return;\n lib.current = maplibre;\n created = new maplibre.Map({\n container: box.current,\n style: STYLES[theme],\n center: WORLD.center,\n zoom: WORLD.zoom,\n attributionControl: attribution\n ? { compact: true, customAttribution: attribution }\n : { compact: true },\n dragRotate: false,\n pitchWithRotate: false,\n touchPitch: false,\n maxPitch: 0,\n });\n created.touchZoomRotate.disableRotation();\n created.keyboard.disableRotation();\n created.addControl(new maplibre.NavigationControl({ showCompass: false }), 'top-right');\n // Tiles that do not come (offline, the tile host down) leave a blank\n // map; the pins still stand on it and the list has everything.\n created.on('error', (event) => console.warn('[map]', event.error?.message));\n // The credit line starts folded behind its (i) — see attribution.ts.\n unfoldWatch = foldAttributionOnce(box.current.querySelector('.maplibregl-ctrl-attrib'));\n // A shell window resizes without a window resize event, which is all\n // MapLibre listens for — without this the canvas keeps its first size.\n const current = created;\n observer = new ResizeObserver(() => { current.resize(); revealSelected(); });\n observer.observe(box.current);\n map.current = created;\n setReady(true);\n } catch (error) {\n console.error('[map] could not start', error);\n if (!cancelled) setFailed(true);\n }\n })();\n return () => {\n cancelled = true;\n observer?.disconnect();\n unfoldWatch();\n markers.current.forEach((m) => m.remove());\n markers.current = [];\n created?.remove();\n map.current = null;\n };\n // The theme is applied by the effect below; the map is made once.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // The theme's tiles.\n const shownTheme = useRef(theme);\n useEffect(() => {\n if (!ready || !map.current || shownTheme.current === theme) return;\n shownTheme.current = theme;\n map.current.setStyle(STYLES[theme]);\n }, [ready, theme]);\n\n // The pins — the selected one last, so it sits on top.\n useEffect(() => {\n const m = map.current;\n const maplibre = lib.current;\n if (!ready || !m || !maplibre) return;\n markers.current.forEach((marker) => marker.remove());\n markers.current = [];\n const ordered = [...pins].sort((a, b) => Number(a.key === selectedKey) - Number(b.key === selectedKey));\n for (const pin of ordered) {\n const el = pinElement(pin, pin.key === selectedKey);\n el.addEventListener('click', (event) => {\n event.stopPropagation();\n latest.current.onSelect(pin.key);\n });\n markers.current.push(new maplibre.Marker({ element: el, anchor: 'center' }).setLngLat([pin.lng, pin.lat]).addTo(m));\n }\n }, [ready, pins, selectedKey]);\n\n // Keep a newly chosen pin in view.\n useEffect(() => {\n if (!ready || !selectedKey) return;\n const frame = requestAnimationFrame(revealSelected);\n return () => cancelAnimationFrame(frame);\n }, [ready, selectedKey, revealSelected]);\n\n // Fit the pins when WHICH pins are shown changes — not on every redraw, so\n // a poll that adds one located address doesn't yank the view the user panned.\n useEffect(() => {\n const m = map.current;\n const maplibre = lib.current;\n const target = fitTo ? pins.filter((p) => fitTo.includes(p.key)) : pins;\n if (!ready || !m || !maplibre || target.length === 0) return;\n const key = target.map((p) => `${p.key}@${p.lat},${p.lng}`).sort().join('|');\n if (fitted.current === key) return;\n const first = fitted.current === '';\n fitted.current = key;\n if (target.length === 1) {\n m.jumpTo({ center: [target[0].lng, target[0].lat], zoom: 13 });\n return;\n }\n const bounds = new maplibre.LngLatBounds([target[0].lng, target[0].lat], [target[0].lng, target[0].lat]);\n for (const p of target) bounds.extend([p.lng, p.lat]);\n m.fitBounds(bounds, { padding: 48, maxZoom: 14, animate: !first });\n }, [ready, pins, fitTo]);\n\n // The routes, as lines under the pins. A theme change replaces the style —\n // and with it every source and layer — so they are drawn again on each\n // `style.load`, not only when the routes change.\n useEffect(() => {\n const m = map.current;\n if (!ready || !m) return;\n const data: RouteData = {\n type: 'FeatureCollection',\n features: (routes ?? []).filter((r) => r.coordinates.length >= 2).map((r) => ({\n type: 'Feature',\n properties: { key: r.key, estimate: r.estimate, faded: !!r.faded },\n geometry: { type: 'LineString', coordinates: r.coordinates },\n })),\n };\n // Idempotent: each piece (the source, each layer) is made if missing and\n // updated if there, so a draw cut short by a style still loading never\n // leaves a source without its layers.\n const draw = () => {\n const color = accentColor();\n const opacity = ['case', ['get', 'faded'], 0.25, 0.85] as unknown as number;\n const source = m.getSource<GeoJSONSource>(ROUTE_SOURCE);\n if (source) source.setData(data);\n else m.addSource(ROUTE_SOURCE, { type: 'geojson', data });\n if (m.getLayer('routes-road')) m.setPaintProperty('routes-road', 'line-color', color);\n else m.addLayer({\n id: 'routes-road', type: 'line', source: ROUTE_SOURCE,\n filter: ['!', ['get', 'estimate']],\n layout: { 'line-join': 'round', 'line-cap': 'round' },\n paint: { 'line-color': color, 'line-width': 4, 'line-opacity': opacity },\n });\n if (m.getLayer('routes-estimate')) m.setPaintProperty('routes-estimate', 'line-color', color);\n else m.addLayer({\n id: 'routes-estimate', type: 'line', source: ROUTE_SOURCE,\n filter: ['get', 'estimate'],\n layout: { 'line-join': 'round', 'line-cap': 'round' },\n paint: { 'line-color': color, 'line-width': 3, 'line-dasharray': [1.5, 1.5], 'line-opacity': opacity },\n });\n };\n // Not gated on `isStyleLoaded()`: that is false whenever TILES are still\n // loading (after any pan or zoom), which skipped the redraw and left a\n // reordered day showing its old line. Before the style itself is in, the\n // map refuses sources and layers — `style.load` draws then.\n const tryDraw = () => {\n try {\n draw();\n } catch (error) {\n console.debug('[map] routes wait for the style', error);\n }\n };\n tryDraw();\n m.on('style.load', tryDraw);\n // A style that finished loading between this effect's first try and its\n // listener would never fire `style.load` again; `idle` catches it.\n m.once('idle', tryDraw);\n return () => {\n m.off('style.load', tryDraw);\n m.off('idle', tryDraw);\n };\n }, [ready, routes, theme]);\n\n // Fly to a pin the list asked to show.\n useEffect(() => {\n const m = map.current;\n if (!ready || !m || !focus) return;\n const pin = pins.find((p) => p.key === focus.key);\n if (!pin) return;\n const to = { center: [pin.lng, pin.lat] as [number, number], zoom: Math.max(m.getZoom(), 13) };\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) m.jumpTo(to);\n else m.flyTo({ ...to, duration: 600 });\n // `focus.n` is what asks; the pins changing under it does not.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ready, focus?.n]);\n\n return (\n <div className={`relative min-h-72 overflow-hidden rounded-lg border border-gray-200 bg-gray-100 ${className}`}>\n {/* Out of flow in a box of its own: MapLibre makes its container `position: relative`. */}\n <div className=\"absolute inset-0\">\n <div ref={box} className=\"h-full w-full\" role=\"region\" aria-label={ariaLabel} />\n </div>\n {failed && (\n <p className=\"absolute inset-0 grid place-items-center p-6 text-center text-sm text-gray-500\">\n The map could not be drawn here. The list has everything it shows.\n </p>\n )}\n {children}\n </div>\n );\n}\n","/**\n * Map points — the wire shape every EFFICIENT map endpoint answers with, and\n * the pure logic over it: what a point says about itself, how its pin looks,\n * and the counts over a list of them. Kept free of React and MapLibre so it is\n * testable without WebGL.\n *\n * The shape is efficient-backend's `logistics.serializers.MapPointsSerializer`\n * (the `address-geocoding` capability): the admin portal's Customer / Supplier\n * maps and Trip Planner, and the dealer portal's Address Book. Every record\n * comes back, pinned or not — a point off the map has null `lat` / `lng` and a\n * `placement` saying why, so a list beside the map can explain it instead of\n * silently dropping the row. The backend never makes a request wait on the\n * geocoder: an address it has not seen is `pending`, and the caller asks again\n * while `pending > 0` (`mapPointsRefetchInterval`).\n *\n * Moved here from the admin portal (`src/components/map/mapPoints.ts`) so the\n * dealer portal draws the same map rather than a second one.\n */\n\n/**\n * located — the geocoder matched the street\n * approximate — only the suburb / postcode / city matched; the pin marks the area\n * pending — still being looked up (poll again)\n * not_found — the geocoder found nothing for this address\n * failed — the map service could not answer; retried later\n * no_address — too little to look up: no city or postcode (and none written into the street)\n */\nexport type MapPlacement = 'located' | 'approximate' | 'pending' | 'not_found' | 'failed' | 'no_address';\n\n/**\n * One record on (or off) a map. `kind` is the record type the endpoint pinned\n * (`customer`, `delivery_location`, `supplier`, `warehouse`, the Trip Planner's\n * `office` / `trip_place` / `trip_stop` …); a portal narrows it to the kinds it\n * reads with the type parameter.\n */\nexport interface MapPoint<K extends string = string> {\n kind: K;\n id: string;\n label: string;\n /** CID / MID where the record has one. */\n code: string;\n /** What the pin is coloured by: the customer type, the supplier type, or the kind. */\n category: string;\n /** The address as one line. */\n address: string;\n placement: MapPlacement;\n lat: number | null;\n lng: number | null;\n /** What the geocoder matched, when placed. */\n matched: string;\n}\n\nexport interface MapPoints<P extends MapPoint = MapPoint> {\n points: P[];\n /** Points still being looked up — poll again while this is above zero. */\n pending: number;\n}\n\n/** How often to ask again while addresses are still being located. */\nexport const PENDING_POLL_MS = 3000;\n\n/** A react-query `refetchInterval` for a map endpoint: every few seconds while\n * the backend is still locating addresses, not at all once every point is settled. */\nexport const mapPointsRefetchInterval = (data: Pick<MapPoints, 'pending'> | undefined): number | false =>\n (data?.pending ? PENDING_POLL_MS : false);\n\n/** A point's identity across kinds — a customer and one of its delivery\n * locations are different pins even if two ids ever collided. */\nexport const pointKey = (p: Pick<MapPoint, 'kind' | 'id'>) => `${p.kind}:${p.id}`;\n\n/** On the map = placed by the geocoder AND carrying coordinates. */\nexport const isOnMap = <P extends MapPoint>(p: P): p is P & { lat: number; lng: number } =>\n (p.placement === 'located' || p.placement === 'approximate') && p.lat != null && p.lng != null;\n\n/** Why a point is not (exactly) where its address is — null when it is. */\nexport function placementNote(placement: MapPlacement): string | null {\n switch (placement) {\n case 'located': return null;\n case 'approximate': return 'Approximate: only the suburb or postcode matched';\n case 'pending': return 'Locating…';\n case 'not_found': return 'Not on the map: address not found';\n case 'failed': return 'Not on the map: the map service could not be reached';\n case 'no_address': return 'Not on the map: no city or postcode';\n }\n}\n\n/** A pin colour and the legend's word for it. */\nexport interface MapCategory {\n label: string;\n /** Tone classes (background + text) for the pin and its legend swatch. */\n tone: string;\n}\n\n/**\n * Pin colours by category. Customers follow the admin Customers list's Type\n * badge families (importer, dealer purple, retail green); a record's\n * secondary locations (delivery locations, warehouses) are amber.\n *\n * Importer and supplier are SKY, not blue: this package's themes remap\n * `bg-blue-600` to the theme's accent (themes.css), so in the green theme\n * importer pins came out the same green as retail. None of the other families\n * here is remapped — a categorical colour must not follow the accent the way\n * a button does.\n *\n * A portal that names a category differently passes its own map with the same\n * tones (the dealer's own head office is its \"Billing address\", not \"Dealer\").\n */\nexport const MAP_CATEGORIES: Readonly<Record<string, MapCategory>> = {\n importer: { label: 'Importer', tone: 'bg-sky-600 text-white' },\n dealer: { label: 'Dealer', tone: 'bg-purple-600 text-white' },\n retail: { label: 'Retail', tone: 'bg-green-600 text-white' },\n supplier: { label: 'Supplier', tone: 'bg-sky-600 text-white' },\n delivery_location: { label: 'Delivery location', tone: 'bg-amber-600 text-white' },\n warehouse: { label: 'Warehouse', tone: 'bg-amber-600 text-white' },\n};\nconst DEFAULT_TONE = 'bg-gray-700 text-white';\n/** The legend's order. */\nconst CATEGORY_ORDER = ['importer', 'dealer', 'retail', 'supplier', 'delivery_location', 'warehouse'];\n\n/** Suppliers colour by the kind rather than their free-text type (Factory,\n * Shipping Forwarder…), which has no fixed palette to map onto. */\nexport function pinCategory(p: Pick<MapPoint, 'kind' | 'category'>): string {\n if (p.kind === 'supplier') return 'supplier';\n if (p.kind === 'customer') return p.category || 'importer';\n return p.kind;\n}\n\nexport const categoryTone = (category: string, categories: Readonly<Record<string, MapCategory>> = MAP_CATEGORIES): string =>\n categories[category]?.tone ?? DEFAULT_TONE;\n\nexport const categoryLabel = (category: string, categories: Readonly<Record<string, MapCategory>> = MAP_CATEGORIES): string =>\n categories[category]?.label ?? category;\n\nexport const pinTone = (p: Pick<MapPoint, 'kind' | 'category'>, categories?: Readonly<Record<string, MapCategory>>): string =>\n categoryTone(pinCategory(p), categories);\n\n/**\n * The character on a pin. A profile map numbers its extra locations (1, 2, 3 —\n * the same numbers the list shows) and marks the head office with its\n * category's initial; a list-wide map shows each record's category initial.\n */\nexport function pinGlyph(p: MapPoint, number: number | null, categories: Readonly<Record<string, MapCategory>> = MAP_CATEGORIES): string {\n if (number != null) return String(number);\n return (categories[pinCategory(p)]?.label ?? '').charAt(0).toUpperCase();\n}\n\n/** The kinds a profile map numbers: a record's secondary locations. */\nexport const SECONDARY_KINDS: readonly string[] = ['delivery_location', 'warehouse'];\n\n/**\n * The numbers a profile map gives its secondary locations, in list order; the\n * record itself gets none. An unplaced location keeps its number, so the list\n * and the pins never renumber as addresses resolve.\n */\nexport function locationNumbers(points: MapPoint[], kinds: readonly string[] = SECONDARY_KINDS): Map<string, number> {\n const numbers = new Map<string, number>();\n let n = 0;\n for (const p of points) {\n if (kinds.includes(p.kind)) numbers.set(pointKey(p), ++n);\n }\n return numbers;\n}\n\nexport interface MapSummary {\n onMap: number;\n approximate: number;\n pending: number;\n off: number;\n}\n\nexport function summarize(points: MapPoint[]): MapSummary {\n const summary: MapSummary = { onMap: 0, approximate: 0, pending: 0, off: 0 };\n for (const p of points) {\n if (isOnMap(p)) {\n summary.onMap += 1;\n if (p.placement === 'approximate') summary.approximate += 1;\n } else if (p.placement === 'pending') summary.pending += 1;\n else summary.off += 1;\n }\n return summary;\n}\n\n/** \"12 on the map · 2 approximate · 3 not on the map\" — the list header. */\nexport function summaryText(s: MapSummary): string {\n const parts = [`${s.onMap} on the map`];\n if (s.approximate) parts.push(`${s.approximate} approximate`);\n if (s.pending) parts.push(`${s.pending} locating`);\n if (s.off) parts.push(`${s.off} not on the map`);\n return parts.join(' · ');\n}\n\n/** The categories present among the placed points, in a stable order — the\n * legend. Categories outside the palette follow, in the order first seen. */\nexport function legendCategories(points: MapPoint[]): string[] {\n const present = [...new Set(points.filter(isOnMap).map(pinCategory))];\n const known = CATEGORY_ORDER.filter((c) => present.includes(c));\n return [...known, ...present.filter((c) => !CATEGORY_ORDER.includes(c))];\n}\n\n/**\n * The records a compact map can't pin, each with its reason, for the notes\n * drawn over the map (a map with no list beside it). Pending points are left\n * out — they are counted as \"Locating…\" instead.\n */\nexport function offMapNotes(points: MapPoint[], numbers?: ReadonlyMap<string, number>): { key: string; text: string }[] {\n return points\n .filter((p) => !isOnMap(p) && p.placement !== 'pending')\n .map((p) => {\n const n = numbers?.get(pointKey(p));\n const note = placementNote(p.placement)?.replace(/^Not on the map: /, '') ?? '';\n return { key: pointKey(p), text: `${n != null ? `${n}. ` : ''}${p.label}: ${note}` };\n });\n}\n","import Button from '../forms/Button';\nimport { MAP_CATEGORIES, categoryLabel, categoryTone, pinCategory, placementNote, type MapCategory, type MapPoint } from './points';\n\n/**\n * What sits over an `EntityMap` — the legend and the chosen record's card.\n * Both take the portal's category map (`MAP_CATEGORIES` unless it names a\n * category its own way), so the legend's words and the card's match the pins.\n */\n\n/** Which colour means what; drawn only when the map shows more than one.\n * `floating` pins it to the map's top-left corner; off, the caller places it\n * (a compact map stacks its notes under it). */\nexport function MapLegend({ categories, palette = MAP_CATEGORIES, floating = true }: {\n categories: string[];\n palette?: Readonly<Record<string, MapCategory>>;\n floating?: boolean;\n}) {\n if (categories.length < 2) return null;\n return (\n <div className={`${floating ? 'absolute left-3 top-3 z-10 ' : ''}flex flex-wrap gap-x-3 gap-y-1 rounded-lg border border-gray-200 bg-white px-2.5 py-1.5 text-xs text-gray-600 shadow-sm`}>\n {categories.map((c) => (\n <span key={c} className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className={`h-2.5 w-2.5 rounded-full ${categoryTone(c, palette)}`} />\n {categoryLabel(c, palette)}\n </span>\n ))}\n </div>\n );\n}\n\n/** The chosen record's card, over the map's bottom edge. */\nexport function SelectedCard({ point, number, onOpen, onClose, palette = MAP_CATEGORIES }: {\n point: MapPoint;\n number: number | null;\n /** Open the record's own page or window; null when it has none. */\n onOpen: (() => void) | null;\n onClose: () => void;\n palette?: Readonly<Record<string, MapCategory>>;\n}) {\n const note = placementNote(point.placement);\n const category = pinCategory(point);\n return (\n <section aria-label={`${point.label}, chosen on the map`}\n className=\"absolute bottom-3 left-3 right-3 z-10 max-w-md space-y-1.5 rounded-lg border border-gray-200 bg-white p-3 shadow-md\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-gray-900 [overflow-wrap:anywhere]\">\n {number != null && <span className=\"mr-1 text-gray-400\">{number}.</span>}\n {point.label}\n {point.code && <span className=\"ml-1.5 text-xs font-normal text-gray-400\">{point.code}</span>}\n </h3>\n <p className=\"text-xs text-gray-500\">{palette[category] ? categoryLabel(category, palette) : point.category}</p>\n </div>\n <button type=\"button\" onClick={onClose} aria-label=\"Close\" className=\"rounded p-0.5 text-gray-400 hover:text-gray-600\">\n <svg aria-hidden className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" /></svg>\n </button>\n </div>\n <p className=\"text-sm text-gray-700 [overflow-wrap:anywhere]\">{point.address || '—'}</p>\n {note && <p className=\"text-xs text-gray-500\">{note}</p>}\n {point.matched && point.placement === 'approximate' && (\n <p className=\"text-xs text-gray-400 [overflow-wrap:anywhere]\">Placed at: {point.matched}</p>\n )}\n {onOpen && (\n <div className=\"flex justify-end\">\n <Button variant=\"secondary\" size=\"sm\" onClick={onOpen}>Open</Button>\n </div>\n )}\n </section>\n );\n}\n","import { useQuery, type QueryKey } from '@tanstack/react-query';\nimport { mapPointsRefetchInterval, type MapPoints } from './points';\n\n/**\n * A map endpoint's points, asked again every few seconds while the backend is\n * still locating addresses (`pending > 0`) and not at all once every point is\n * settled. The backend never makes the request wait on the geocoder, so a\n * first visit shows the list at once and the pins arrive as they are found.\n */\nexport function useMapPoints<T extends MapPoints>(\n queryKey: QueryKey,\n queryFn: () => Promise<T>,\n { staleTime }: { staleTime?: number } = {},\n) {\n return useQuery({\n queryKey,\n queryFn,\n staleTime,\n refetchInterval: (query) => mapPointsRefetchInterval(query.state.data),\n });\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { QueryKey } from '@tanstack/react-query';\nimport EntityMap, { type MapPin } from './EntityMap';\nimport { MapLegend, SelectedCard } from './MapOverlays';\nimport {\n MAP_CATEGORIES, isOnMap, legendCategories, locationNumbers, offMapNotes, pinGlyph, pinTone, pointKey, summarize,\n summaryText, type MapCategory, type MapPoint, type MapPoints,\n} from './points';\nimport { useMapPoints } from './useMapPoints';\n\n/** How many off-map records the notes box names before it says \"+N more\". */\nconst NOTES_SHOWN = 3;\n\n/**\n * A record's Locations map — compact, with no list beside it, because the\n * page it sits on already lists the locations. The admin portal puts it on the\n * customer / supplier Details tab beside the score (a customer's head office\n * and delivery locations, a supplier's address and warehouses); the dealer\n * portal puts it on the Address Book (the billing address and the delivery\n * addresses). What a list would say lives over the map: the legend,\n * \"Locating…\" while addresses are looked up, and a note for each record that\n * has no pin and why. Clicking a pin shows its card.\n *\n * `refreshSignal` changes when the records it pins change elsewhere on the\n * page (a delivery location added or edited, the address edited), and the\n * points are asked again; `staleTime: 0` does the same whenever it remounts.\n *\n * Moved from the admin portal's `ProfileLocationsMap`.\n */\nexport default function LocationsMap({\n queryKey,\n queryFn,\n refreshSignal,\n onOpen,\n ariaLabel,\n title = 'Locations',\n palette = MAP_CATEGORIES,\n numbers: givenNumbers,\n className = '',\n mapClassName = 'min-h-[240px] flex-1',\n}: {\n queryKey: QueryKey;\n queryFn: () => Promise<MapPoints>;\n refreshSignal?: unknown;\n /** Open a point's own page or window; null for points that have none. */\n onOpen?: (point: MapPoint) => ((() => void) | null);\n ariaLabel: string;\n /** The heading over the map. */\n title?: string;\n /** How the portal names and colours each category (the legend, the card, the head-office initial). */\n palette?: Readonly<Record<string, MapCategory>>;\n /** The number each pin shows, by `pointKey` — the numbers the page's own list\n * shows. Unset: the secondary locations numbered in the endpoint's order. */\n numbers?: ReadonlyMap<string, number>;\n className?: string;\n /** Sizing for the map box (it grows to fill the section by default). */\n mapClassName?: string;\n}) {\n const { data, isError, refetch } = useMapPoints(queryKey, queryFn, { staleTime: 0 });\n const mounted = useRef(false);\n useEffect(() => {\n if (!mounted.current) {\n mounted.current = true;\n return;\n }\n void refetch();\n // Only a change of the signal asks again; `refetch` is stable.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [refreshSignal]);\n\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n const points = useMemo(() => data?.points ?? [], [data]);\n const numbers = useMemo(() => givenNumbers ?? locationNumbers(points), [givenNumbers, points]);\n const pins: MapPin[] = useMemo(() => points.filter(isOnMap).map((p) => ({\n key: pointKey(p),\n label: p.label,\n lat: p.lat,\n lng: p.lng,\n glyph: pinGlyph(p, numbers.get(pointKey(p)) ?? null, palette),\n tone: pinTone(p, palette),\n approximate: p.placement === 'approximate',\n })), [points, numbers, palette]);\n const notes = useMemo(() => offMapNotes(points, numbers), [points, numbers]);\n const legend = useMemo(() => legendCategories(points), [points]);\n const pending = data?.pending ?? 0;\n const selected = points.find((p) => pointKey(p) === selectedKey) ?? null;\n const open = selected && onOpen ? onOpen(selected) : null;\n\n return (\n <section aria-label={title} className={`flex min-w-0 flex-col ${className}`}>\n {/* The score beside it heads its card with the period picker (30px tall);\n the same row height keeps the two boxes' tops level. */}\n <div className=\"flex min-h-[30px] items-center justify-between mb-2\">\n <h3 className=\"text-sm font-semibold text-gray-900\">{title}</h3>\n {data && <span className=\"text-xs text-gray-500\">{summaryText(summarize(points))}</span>}\n </div>\n <EntityMap pins={pins} selectedKey={selectedKey} focus={null} onSelect={setSelectedKey}\n ariaLabel={ariaLabel} className={mapClassName}>\n {/* Top-left, under the legend: the bottom edge belongs to the map's\n OpenStreetMap credit and the chosen record's card. */}\n <div className=\"absolute left-3 top-3 z-10 flex max-w-[70%] flex-col items-start gap-1.5\">\n <MapLegend categories={legend} palette={palette} floating={false} />\n {isError && (\n <div className=\"flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-2.5 py-1.5 text-xs text-gray-600 shadow-sm\">\n Couldn’t load the locations.\n <button type=\"button\" onClick={() => void refetch()} className=\"font-medium text-blue-600 hover:text-blue-800 hover:underline\">\n Try again\n </button>\n </div>\n )}\n {!isError && (pending > 0 || notes.length > 0) && (\n <div className=\"max-w-full space-y-0.5 rounded-lg border border-gray-200 bg-white px-2.5 py-1.5 text-xs shadow-sm\" aria-live=\"polite\">\n {pending > 0 && (\n <p className=\"flex items-center gap-1.5 text-gray-500\">\n <svg className=\"h-3 w-3 animate-spin\" fill=\"none\" viewBox=\"0 0 24 24\"><circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" /><path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" /></svg>\n Locating {pending} address{pending === 1 ? '' : 'es'}…\n </p>\n )}\n {notes.slice(0, NOTES_SHOWN).map((n) => (\n <p key={n.key} className=\"truncate text-amber-700\" title={n.text}>{n.text}</p>\n ))}\n {notes.length > NOTES_SHOWN && (\n <p className=\"text-gray-500\">+{notes.length - NOTES_SHOWN} more not on the map</p>\n )}\n </div>\n )}\n </div>\n {selected && (\n <SelectedCard point={selected} number={numbers.get(pointKey(selected)) ?? null} onOpen={open}\n onClose={() => setSelectedKey(null)} palette={palette} />\n )}\n </EntityMap>\n </section>\n );\n}\n"]}
@@ -1,10 +1,11 @@
1
- import { memo, useRef, useState, useLayoutEffect, useMemo } from 'react';
1
+ "use client";
2
+ import { useBrowserLayoutEffect } from '../chunk-IRWSRJ4K.js';
3
+ import { memo, useRef, useState, useMemo } from 'react';
2
4
  import ReactMarkdown from 'react-markdown';
3
5
  import remarkBreaks from 'remark-breaks';
4
6
  import remarkGfm from 'remark-gfm';
5
7
  import { jsx, jsxs } from 'react/jsx-runtime';
6
8
 
7
- // src/markdown/index.tsx
8
9
  function remarkDialect() {
9
10
  const data = this.data();
10
11
  (data.micromarkExtensions ??= []).push({
@@ -138,7 +139,7 @@ function Markdown({
138
139
  }) {
139
140
  const ref = useRef(null);
140
141
  const [overflowing, setOverflowing] = useState(false);
141
- useLayoutEffect(() => {
142
+ useBrowserLayoutEffect(() => {
142
143
  const el = ref.current;
143
144
  if (!clamp || !el) {
144
145
  setOverflowing(false);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/markdown/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { memo, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkBreaks from 'remark-breaks';\nimport remarkGfm from 'remark-gfm';\n\n/**\n * The full Markdown renderer — CommonMark + GFM, behind its own subpath.\n *\n * ## Why this is not in the UI kit\n *\n * `react-os-shell/ui` promises to import nothing but React, and two checks\n * hold that line: `tests/uiEntryIsPeerFree.test.ts` walks the source graph and\n * `scripts/verify-dist.mjs` walks the built one (only the second catches a peer\n * arriving through a shared chunk). That promise is what lets the till, which\n * has no `@heroicons/react` installed at all, take the kit.\n *\n * The kit's `Markdown` was written under that constraint — a regex renderer,\n * because a regex was all it could afford. It is still there, still correct for\n * what it covers, and still what `HelpCenter` falls back to. But a regex cannot\n * nest, so it cannot grow tables inside list items, task lists, footnotes, or\n * anything else with structure, and the day arrived when a body needed more.\n *\n * So: a second entry, with a real parser, whose peers are OPTIONAL. A consumer\n * that never imports `react-os-shell/markdown` never installs react-markdown\n * and never pays a byte for it — the same bargain `react-os-shell/markup`\n * already strikes from the other direction. The package's `dependencies` stay\n * empty, which is the promise underneath all of the others.\n *\n * ## What is deliberately NOT here\n *\n * - **Syntax highlighting.** Shiki and Prism each cost more than this whole\n * package. Code blocks are monospace with their own horizontal scroll, and\n * the info string is carried through as `data-language` so a host that wants\n * highlighting can add it without a fork.\n * - **`rehype-raw`.** Adding it would re-open raw HTML, and raw HTML in a body\n * a customer typed is an XSS surface. The dialect below closes it instead.\n * - **A sanitiser.** Not needed, and worth stating so nobody adds one out of\n * caution: no raw HTML is parsed, and react-markdown's default `urlTransform`\n * neutralises `javascript:` URLs. There is no HTML string anywhere in this\n * pipeline to sanitise — the output is React elements throughout.\n * - **MDX.** The input is a string a person or a service wrote, not a module.\n */\n\n// ── Dialect ────────────────────────────────────────────────────────────────\n\n/**\n * Four constructs disabled, each because a body that predates markdown would\n * otherwise render surprisingly. Every one of these was found in stored copy,\n * not imagined:\n *\n * - `codeIndented` — a pasted, quoted email is indented four spaces and would\n * become a code block.\n * - `htmlFlow` / `htmlText` — `<John>` and `<script>` must render as the text\n * they are. react-markdown DROPS raw-HTML nodes silently, so without this a\n * name in angle brackets simply disappears from the message.\n * - `setextUnderline` — `Title` followed by `----` is a horizontal rule under a\n * line of text far more often than it is a heading.\n *\n * Fenced ``` blocks and `<https://…>` autolinks are untouched.\n */\nfunction remarkDialect(this: unknown) {\n const data = (this as { data: () => { micromarkExtensions?: unknown[] } }).data();\n (data.micromarkExtensions ??= []).push({\n disable: { null: ['codeIndented', 'htmlFlow', 'htmlText', 'setextUnderline'] },\n });\n}\n\n/**\n * The plugin lists, exported for a host assembling its own pipeline.\n *\n * `note` carries `remark-breaks`; `article` does not, and the difference is\n * about who wrote the text. A note is typed into a box, where pressing Return\n * means a line break. An article is authored markdown whose source is hard\n * wrapped at ~78 columns, where a single newline is a soft wrap and honouring\n * it would shred every paragraph.\n */\nexport const MARKDOWN_PLUGINS = {\n note: [remarkGfm, remarkBreaks, remarkDialect],\n article: [remarkGfm, remarkDialect],\n} as const;\n\n// ── Shared element renderers ───────────────────────────────────────────────\n\n/**\n * Colours here are utility classes that `ui.css` REMAPS under\n * `[data-theme=\"dark\"]` — this package's dark mode is not a Tailwind variant.\n * Two consequences worth carrying in your head when editing this file:\n *\n * 1. A class with no remap stays light-on-light. `tests/markdown.test.tsx`\n * fails the build rather than let one through.\n * 2. `bg-gray-200`, never `bg-gray-100`, for anything drawn ON a panel:\n * `bg-gray-100` remaps to `--surface`, and so does `bg-white`, so a chip\n * using it is the exact colour of the surface behind it. It was invisible\n * in dark mode in the kit renderer for a year.\n */\nconst linkRenderer: Components['a'] = ({ node: _n, ...props }) => (\n <a {...props} target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 hover:underline break-all\" />\n);\n\nconst codeRenderer: Components['code'] = ({ node: _n, ...props }) => (\n <code className=\"rounded bg-gray-200 px-1 py-0.5 font-mono text-[0.85em] text-gray-800\" {...props} />\n);\n\n/**\n * `pre` un-chips the `code` inside it: one `code` renderer serves both inline\n * spans and block content, so the block case has to undo the chip's own\n * background, padding and size.\n */\nconst preRenderer = (extra: string): Components['pre'] =>\n ({ node: _n, ...props }) => (\n <pre\n className={\n `${extra} overflow-x-auto rounded-lg border border-gray-200 bg-gray-50 font-mono text-xs ` +\n '[&>code]:bg-transparent [&>code]:p-0 [&>code]:[font-size:inherit]'\n }\n {...props}\n />\n );\n\nconst tableRenderers: Pick<Components, 'th' | 'td'> = {\n th: ({ node: _n, ...props }) => (\n <th className=\"border border-gray-200 bg-gray-50 px-2 py-1 text-left font-semibold text-gray-700\" {...props} />\n ),\n td: ({ node: _n, ...props }) => <td className=\"border border-gray-200 px-2 py-1 align-top\" {...props} />,\n};\n\n// ── variant: note ──────────────────────────────────────────────────────────\n\n/**\n * Timeline cards, message bodies, bug-report descriptions — anything a person\n * or a service TYPED, shown inside a card that has other things in it.\n *\n * Headings cap one step above body text. An author writing `# heading` in a\n * note means emphasis, not a document outline, and a 30px line inside a\n * timeline card reads as a rendering fault.\n */\nconst NOTE_COMPONENTS: Components = {\n a: linkRenderer,\n h1: ({ node: _n, ...props }) => <h1 className=\"text-base font-semibold text-gray-900\" {...props} />,\n h2: ({ node: _n, ...props }) => <h2 className=\"text-sm font-semibold text-gray-900\" {...props} />,\n h3: ({ node: _n, ...props }) => <h3 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h4: ({ node: _n, ...props }) => <h4 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h5: ({ node: _n, ...props }) => <h5 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h6: ({ node: _n, ...props }) => <h6 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n ul: ({ node: _n, ...props }) => <ul className=\"list-disc pl-5\" {...props} />,\n ol: ({ node: _n, ...props }) => <ol className=\"list-decimal pl-5\" {...props} />,\n code: codeRenderer,\n pre: preRenderer('p-2'),\n blockquote: ({ node: _n, ...props }) => (\n <blockquote className=\"border-l-2 border-gray-300 pl-2.5 text-gray-600\" {...props} />\n ),\n hr: ({ node: _n, ...props }) => <hr className=\"border-gray-200\" {...props} />,\n table: ({ node: _n, ...props }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"text-xs border-collapse\" {...props} />\n </div>\n ),\n ...tableRenderers,\n img: ({ node: _n, alt, ...props }) => (\n <img {...props} alt={alt ?? ''} loading=\"lazy\" className=\"max-w-full max-h-64 rounded-lg border border-gray-200\" />\n ),\n};\n\n/** The note variant's set, with `resolveImageSrc` wired the same way the\n * article's is — a resolver that silently applied to one variant and not the\n * other was a broken image with no signal. */\nfunction noteComponents(resolveImageSrc?: (src: string) => string): Components {\n if (!resolveImageSrc) return NOTE_COMPONENTS;\n return {\n ...NOTE_COMPONENTS,\n img: ({ node: _n, src, alt, ...props }) => {\n const raw = typeof src === 'string' ? src : undefined;\n return (\n <img\n {...props}\n src={raw ? resolveImageSrc(raw) : raw}\n alt={alt ?? ''}\n loading=\"lazy\"\n className=\"max-w-full max-h-64 rounded-lg border border-gray-200\"\n />\n );\n },\n };\n}\n\n// ── variant: article ───────────────────────────────────────────────────────\n\n/**\n * A screenshot that has not been uploaded yet shows a labelled box rather than\n * a broken-image icon: most seeded article screenshots do not exist, and a\n * broken icon per step reads as a bug where the box reads as intent.\n *\n * Spans, not divs — react-markdown puts a lone image inside a `<p>`.\n */\nfunction ArticleImage({ src, alt }: { src?: string; alt?: string }) {\n const [failed, setFailed] = useState(false);\n const label = (alt ?? '').replace(/^screenshot:\\s*/i, '');\n if (!src || failed) {\n return (\n <span className=\"my-3 flex items-start gap-2 rounded-lg border border-dashed border-gray-300 bg-gray-50 px-3 py-2.5\">\n <svg\n className=\"mt-0.5 h-4 w-4 shrink-0 text-gray-400\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M3 6a1.5 1.5 0 0 1 1.5-1.5h1.1l.4-.9a1 1 0 0 1 .9-.6h6.2a1 1 0 0 1 .9.6l.4.9h1.1A1.5 1.5 0 0 1 18 6v8.5A1.5 1.5 0 0 1 16.5 16h-13A1.5 1.5 0 0 1 2 14.5V6Zm7.5 1.5a3.25 3.25 0 1 0 0 6.5 3.25 3.25 0 0 0 0-6.5Z\" />\n </svg>\n <span className=\"text-xs text-gray-500\">{label || 'Screenshot'}</span>\n </span>\n );\n }\n return (\n <img\n src={src}\n alt={alt ?? ''}\n loading=\"lazy\"\n onError={() => setFailed(true)}\n className=\"my-3 block max-w-full rounded-lg border border-gray-200\"\n />\n );\n}\n\n/**\n * Help articles and other authored documentation: full heading scale, airier\n * blocks, `>` styled as a callout rather than an email-quote bar, and a capped\n * measure so a wide window does not produce 200-character lines.\n */\nfunction articleComponents(resolveImageSrc?: (src: string) => string): Components {\n return {\n a: linkRenderer,\n h1: ({ node: _n, ...props }) => (\n <h1 className=\"mt-6 mb-3 text-xl font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h2: ({ node: _n, ...props }) => (\n <h2 className=\"mt-6 mb-2.5 border-b border-gray-200 pb-1.5 text-lg font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h3: ({ node: _n, ...props }) => (\n <h3 className=\"mt-5 mb-2 text-base font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h4: ({ node: _n, ...props }) => (\n <h4 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h5: ({ node: _n, ...props }) => (\n <h5 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-700 first:mt-0\" {...props} />\n ),\n h6: ({ node: _n, ...props }) => (\n <h6 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-700 first:mt-0\" {...props} />\n ),\n p: ({ node: _n, ...props }) => <p className=\"my-2.5 leading-relaxed\" {...props} />,\n ul: ({ node: _n, ...props }) => <ul className=\"my-2.5 list-disc space-y-1 pl-5\" {...props} />,\n ol: ({ node: _n, ...props }) => <ol className=\"my-2.5 list-decimal space-y-1 pl-5\" {...props} />,\n // A numbered walkthrough has multi-paragraph steps (text, then a\n // screenshot); paragraphs inside an item stay tighter than the gap between\n // items, or the numbering stops reading as a sequence.\n li: ({ node: _n, ...props }) => <li className=\"leading-relaxed [&>p]:my-1\" {...props} />,\n code: codeRenderer,\n pre: preRenderer('my-3 p-3'),\n blockquote: ({ node: _n, ...props }) => (\n <blockquote\n className=\"my-3 rounded-r-lg border-l-4 border-blue-400/60 bg-blue-50 px-3 py-2 text-gray-700 [&>p]:my-1\"\n {...props}\n />\n ),\n hr: ({ node: _n, ...props }) => <hr className=\"my-4 border-gray-200\" {...props} />,\n table: ({ node: _n, ...props }) => (\n <div className=\"my-3 overflow-x-auto\">\n <table className=\"border-collapse text-sm\" {...props} />\n </div>\n ),\n ...tableRenderers,\n img: ({ node: _n, src, alt }) => {\n const raw = typeof src === 'string' ? src : undefined;\n return <ArticleImage src={raw && resolveImageSrc ? resolveImageSrc(raw) : raw} alt={alt} />;\n },\n };\n}\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nexport type MarkdownVariant = 'note' | 'article';\n\nexport interface MarkdownProps {\n /** The markdown source. */\n children: string;\n /**\n * `note` (default) — typed copy inside a card: capped headings, single\n * newlines honoured as line breaks, tight blocks.\n * `article` — authored documentation: full heading scale, soft wraps, callout\n * blockquotes, screenshot placeholders, a capped measure.\n */\n variant?: MarkdownVariant;\n /**\n * Collapse to ~6 text lines with a fade. The \"Show more\" control belongs to\n * the caller; this only clamps. The fade appears only when the clamped\n * content actually OVERFLOWS, measured after layout — a note that is long in\n * characters but short on screen would otherwise get a fade under nothing.\n *\n * Needs `react-os-shell/ui.css` for `.rosmd-clamp` / `.rosmd-fade`.\n */\n clamp?: boolean;\n /**\n * Rewrite a relative image `src` before it is fetched. Article bodies\n * reference screenshots by a repo-relative path that only the host knows how\n * to serve.\n */\n resolveImageSrc?: (src: string) => string;\n /** Override individual element renderers. Merged over the variant's set. */\n components?: Partial<Components>;\n className?: string;\n}\n\n/**\n * Render markdown as React elements.\n *\n * No HTML string is produced at any point, and no raw HTML is parsed, so there\n * is nothing here to sanitise — see the note at the top of this file before\n * reaching for DOMPurify.\n */\nfunction Markdown({\n children,\n variant = 'note',\n clamp = false,\n resolveImageSrc,\n components,\n className = '',\n}: MarkdownProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [overflowing, setOverflowing] = useState(false);\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!clamp || !el) {\n setOverflowing(false);\n return;\n }\n const check = () => setOverflowing(el.scrollHeight > el.clientHeight + 1);\n check();\n // A ResizeObserver rather than a one-off measure: the same note reflows\n // when its window is resized, and a fade that was correct at mount is\n // wrong the moment it is.\n const ro = new ResizeObserver(check);\n ro.observe(el);\n return () => ro.disconnect();\n }, [clamp, children]);\n\n const resolved = useMemo<Components>(\n () => ({\n ...(variant === 'article' ? articleComponents(resolveImageSrc) : noteComponents(resolveImageSrc)),\n ...components,\n }),\n [variant, resolveImageSrc, components],\n );\n\n // `className` wins over the variant's own ink and type size.\n //\n // Both are ordinary utilities on the same element, so the one that wins is\n // decided by the order Tailwind emits them, not by the order they appear in\n // the attribute — a host asking for `text-green-900` on a green panel would\n // be gambling. The alternative is an important utility at every call site,\n // and the two syntaxes for that (v3's `!text-green-900`, v4's\n // `text-green-900!`) are each silently INERT in the other version, which is\n // a bad thing to ask a caller to get right for a colour.\n //\n // So: a host that names one simply gets it.\n const named = ` ${className} `;\n const ink = /\\stext-(?:[a-z]+-\\d{2,3}(?:\\/\\d+)?|black|white|inherit)\\s/.test(named)\n ? ''\n : 'text-gray-800';\n const size = /\\stext-(?:xs|sm|base|lg|[2-9]?xl|\\[[^\\]]+\\])\\s/.test(named) ? '' : 'text-sm';\n\n return (\n <div\n ref={ref}\n className={[\n variant === 'article' ? 'max-w-3xl break-words' : 'break-words space-y-1.5',\n size,\n ink,\n clamp ? 'rosmd-clamp' : '',\n clamp && overflowing ? 'rosmd-fade' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <ReactMarkdown remarkPlugins={MARKDOWN_PLUGINS[variant] as never} components={resolved}>\n {children}\n </ReactMarkdown>\n </div>\n );\n}\n\nconst MemoMarkdown = memo(Markdown);\n\nexport { MemoMarkdown as Markdown };\nexport default MemoMarkdown;\n\nexport type { Components as MarkdownComponents } from 'react-markdown';\n\n/** Re-exported so a host can render a placeholder in its own layout. */\nexport type MarkdownNode = ReactNode;\n"]}
1
+ {"version":3,"sources":["../../src/markdown/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { memo, useMemo, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkBreaks from 'remark-breaks';\nimport remarkGfm from 'remark-gfm';\n\nimport { useBrowserLayoutEffect } from '../shell/hydration';\n\n/**\n * The full Markdown renderer — CommonMark + GFM, behind its own subpath.\n *\n * ## Why this is not in the UI kit\n *\n * `react-os-shell/ui` promises to import nothing but React, and two checks\n * hold that line: `tests/uiEntryIsPeerFree.test.ts` walks the source graph and\n * `scripts/verify-dist.mjs` walks the built one (only the second catches a peer\n * arriving through a shared chunk). That promise is what lets the till, which\n * has no `@heroicons/react` installed at all, take the kit.\n *\n * The kit's `Markdown` was written under that constraint — a regex renderer,\n * because a regex was all it could afford. It is still there, still correct for\n * what it covers, and still what `HelpCenter` falls back to. But a regex cannot\n * nest, so it cannot grow tables inside list items, task lists, footnotes, or\n * anything else with structure, and the day arrived when a body needed more.\n *\n * So: a second entry, with a real parser, whose peers are OPTIONAL. A consumer\n * that never imports `react-os-shell/markdown` never installs react-markdown\n * and never pays a byte for it — the same bargain `react-os-shell/markup`\n * already strikes from the other direction. The package's `dependencies` stay\n * empty, which is the promise underneath all of the others.\n *\n * ## What is deliberately NOT here\n *\n * - **Syntax highlighting.** Shiki and Prism each cost more than this whole\n * package. Code blocks are monospace with their own horizontal scroll, and\n * the info string is carried through as `data-language` so a host that wants\n * highlighting can add it without a fork.\n * - **`rehype-raw`.** Adding it would re-open raw HTML, and raw HTML in a body\n * a customer typed is an XSS surface. The dialect below closes it instead.\n * - **A sanitiser.** Not needed, and worth stating so nobody adds one out of\n * caution: no raw HTML is parsed, and react-markdown's default `urlTransform`\n * neutralises `javascript:` URLs. There is no HTML string anywhere in this\n * pipeline to sanitise — the output is React elements throughout.\n * - **MDX.** The input is a string a person or a service wrote, not a module.\n */\n\n// ── Dialect ────────────────────────────────────────────────────────────────\n\n/**\n * Four constructs disabled, each because a body that predates markdown would\n * otherwise render surprisingly. Every one of these was found in stored copy,\n * not imagined:\n *\n * - `codeIndented` — a pasted, quoted email is indented four spaces and would\n * become a code block.\n * - `htmlFlow` / `htmlText` — `<John>` and `<script>` must render as the text\n * they are. react-markdown DROPS raw-HTML nodes silently, so without this a\n * name in angle brackets simply disappears from the message.\n * - `setextUnderline` — `Title` followed by `----` is a horizontal rule under a\n * line of text far more often than it is a heading.\n *\n * Fenced ``` blocks and `<https://…>` autolinks are untouched.\n */\nfunction remarkDialect(this: unknown) {\n const data = (this as { data: () => { micromarkExtensions?: unknown[] } }).data();\n (data.micromarkExtensions ??= []).push({\n disable: { null: ['codeIndented', 'htmlFlow', 'htmlText', 'setextUnderline'] },\n });\n}\n\n/**\n * The plugin lists, exported for a host assembling its own pipeline.\n *\n * `note` carries `remark-breaks`; `article` does not, and the difference is\n * about who wrote the text. A note is typed into a box, where pressing Return\n * means a line break. An article is authored markdown whose source is hard\n * wrapped at ~78 columns, where a single newline is a soft wrap and honouring\n * it would shred every paragraph.\n */\nexport const MARKDOWN_PLUGINS = {\n note: [remarkGfm, remarkBreaks, remarkDialect],\n article: [remarkGfm, remarkDialect],\n} as const;\n\n// ── Shared element renderers ───────────────────────────────────────────────\n\n/**\n * Colours here are utility classes that `ui.css` REMAPS under\n * `[data-theme=\"dark\"]` — this package's dark mode is not a Tailwind variant.\n * Two consequences worth carrying in your head when editing this file:\n *\n * 1. A class with no remap stays light-on-light. `tests/markdown.test.tsx`\n * fails the build rather than let one through.\n * 2. `bg-gray-200`, never `bg-gray-100`, for anything drawn ON a panel:\n * `bg-gray-100` remaps to `--surface`, and so does `bg-white`, so a chip\n * using it is the exact colour of the surface behind it. It was invisible\n * in dark mode in the kit renderer for a year.\n */\nconst linkRenderer: Components['a'] = ({ node: _n, ...props }) => (\n <a {...props} target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 hover:underline break-all\" />\n);\n\nconst codeRenderer: Components['code'] = ({ node: _n, ...props }) => (\n <code className=\"rounded bg-gray-200 px-1 py-0.5 font-mono text-[0.85em] text-gray-800\" {...props} />\n);\n\n/**\n * `pre` un-chips the `code` inside it: one `code` renderer serves both inline\n * spans and block content, so the block case has to undo the chip's own\n * background, padding and size.\n */\nconst preRenderer = (extra: string): Components['pre'] =>\n ({ node: _n, ...props }) => (\n <pre\n className={\n `${extra} overflow-x-auto rounded-lg border border-gray-200 bg-gray-50 font-mono text-xs ` +\n '[&>code]:bg-transparent [&>code]:p-0 [&>code]:[font-size:inherit]'\n }\n {...props}\n />\n );\n\nconst tableRenderers: Pick<Components, 'th' | 'td'> = {\n th: ({ node: _n, ...props }) => (\n <th className=\"border border-gray-200 bg-gray-50 px-2 py-1 text-left font-semibold text-gray-700\" {...props} />\n ),\n td: ({ node: _n, ...props }) => <td className=\"border border-gray-200 px-2 py-1 align-top\" {...props} />,\n};\n\n// ── variant: note ──────────────────────────────────────────────────────────\n\n/**\n * Timeline cards, message bodies, bug-report descriptions — anything a person\n * or a service TYPED, shown inside a card that has other things in it.\n *\n * Headings cap one step above body text. An author writing `# heading` in a\n * note means emphasis, not a document outline, and a 30px line inside a\n * timeline card reads as a rendering fault.\n */\nconst NOTE_COMPONENTS: Components = {\n a: linkRenderer,\n h1: ({ node: _n, ...props }) => <h1 className=\"text-base font-semibold text-gray-900\" {...props} />,\n h2: ({ node: _n, ...props }) => <h2 className=\"text-sm font-semibold text-gray-900\" {...props} />,\n h3: ({ node: _n, ...props }) => <h3 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h4: ({ node: _n, ...props }) => <h4 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h5: ({ node: _n, ...props }) => <h5 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n h6: ({ node: _n, ...props }) => <h6 className=\"text-sm font-semibold text-gray-700\" {...props} />,\n ul: ({ node: _n, ...props }) => <ul className=\"list-disc pl-5\" {...props} />,\n ol: ({ node: _n, ...props }) => <ol className=\"list-decimal pl-5\" {...props} />,\n code: codeRenderer,\n pre: preRenderer('p-2'),\n blockquote: ({ node: _n, ...props }) => (\n <blockquote className=\"border-l-2 border-gray-300 pl-2.5 text-gray-600\" {...props} />\n ),\n hr: ({ node: _n, ...props }) => <hr className=\"border-gray-200\" {...props} />,\n table: ({ node: _n, ...props }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"text-xs border-collapse\" {...props} />\n </div>\n ),\n ...tableRenderers,\n img: ({ node: _n, alt, ...props }) => (\n <img {...props} alt={alt ?? ''} loading=\"lazy\" className=\"max-w-full max-h-64 rounded-lg border border-gray-200\" />\n ),\n};\n\n/** The note variant's set, with `resolveImageSrc` wired the same way the\n * article's is — a resolver that silently applied to one variant and not the\n * other was a broken image with no signal. */\nfunction noteComponents(resolveImageSrc?: (src: string) => string): Components {\n if (!resolveImageSrc) return NOTE_COMPONENTS;\n return {\n ...NOTE_COMPONENTS,\n img: ({ node: _n, src, alt, ...props }) => {\n const raw = typeof src === 'string' ? src : undefined;\n return (\n <img\n {...props}\n src={raw ? resolveImageSrc(raw) : raw}\n alt={alt ?? ''}\n loading=\"lazy\"\n className=\"max-w-full max-h-64 rounded-lg border border-gray-200\"\n />\n );\n },\n };\n}\n\n// ── variant: article ───────────────────────────────────────────────────────\n\n/**\n * A screenshot that has not been uploaded yet shows a labelled box rather than\n * a broken-image icon: most seeded article screenshots do not exist, and a\n * broken icon per step reads as a bug where the box reads as intent.\n *\n * Spans, not divs — react-markdown puts a lone image inside a `<p>`.\n */\nfunction ArticleImage({ src, alt }: { src?: string; alt?: string }) {\n const [failed, setFailed] = useState(false);\n const label = (alt ?? '').replace(/^screenshot:\\s*/i, '');\n if (!src || failed) {\n return (\n <span className=\"my-3 flex items-start gap-2 rounded-lg border border-dashed border-gray-300 bg-gray-50 px-3 py-2.5\">\n <svg\n className=\"mt-0.5 h-4 w-4 shrink-0 text-gray-400\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M3 6a1.5 1.5 0 0 1 1.5-1.5h1.1l.4-.9a1 1 0 0 1 .9-.6h6.2a1 1 0 0 1 .9.6l.4.9h1.1A1.5 1.5 0 0 1 18 6v8.5A1.5 1.5 0 0 1 16.5 16h-13A1.5 1.5 0 0 1 2 14.5V6Zm7.5 1.5a3.25 3.25 0 1 0 0 6.5 3.25 3.25 0 0 0 0-6.5Z\" />\n </svg>\n <span className=\"text-xs text-gray-500\">{label || 'Screenshot'}</span>\n </span>\n );\n }\n return (\n <img\n src={src}\n alt={alt ?? ''}\n loading=\"lazy\"\n onError={() => setFailed(true)}\n className=\"my-3 block max-w-full rounded-lg border border-gray-200\"\n />\n );\n}\n\n/**\n * Help articles and other authored documentation: full heading scale, airier\n * blocks, `>` styled as a callout rather than an email-quote bar, and a capped\n * measure so a wide window does not produce 200-character lines.\n */\nfunction articleComponents(resolveImageSrc?: (src: string) => string): Components {\n return {\n a: linkRenderer,\n h1: ({ node: _n, ...props }) => (\n <h1 className=\"mt-6 mb-3 text-xl font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h2: ({ node: _n, ...props }) => (\n <h2 className=\"mt-6 mb-2.5 border-b border-gray-200 pb-1.5 text-lg font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h3: ({ node: _n, ...props }) => (\n <h3 className=\"mt-5 mb-2 text-base font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h4: ({ node: _n, ...props }) => (\n <h4 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-900 first:mt-0\" {...props} />\n ),\n h5: ({ node: _n, ...props }) => (\n <h5 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-700 first:mt-0\" {...props} />\n ),\n h6: ({ node: _n, ...props }) => (\n <h6 className=\"mt-4 mb-1.5 text-sm font-semibold text-gray-700 first:mt-0\" {...props} />\n ),\n p: ({ node: _n, ...props }) => <p className=\"my-2.5 leading-relaxed\" {...props} />,\n ul: ({ node: _n, ...props }) => <ul className=\"my-2.5 list-disc space-y-1 pl-5\" {...props} />,\n ol: ({ node: _n, ...props }) => <ol className=\"my-2.5 list-decimal space-y-1 pl-5\" {...props} />,\n // A numbered walkthrough has multi-paragraph steps (text, then a\n // screenshot); paragraphs inside an item stay tighter than the gap between\n // items, or the numbering stops reading as a sequence.\n li: ({ node: _n, ...props }) => <li className=\"leading-relaxed [&>p]:my-1\" {...props} />,\n code: codeRenderer,\n pre: preRenderer('my-3 p-3'),\n blockquote: ({ node: _n, ...props }) => (\n <blockquote\n className=\"my-3 rounded-r-lg border-l-4 border-blue-400/60 bg-blue-50 px-3 py-2 text-gray-700 [&>p]:my-1\"\n {...props}\n />\n ),\n hr: ({ node: _n, ...props }) => <hr className=\"my-4 border-gray-200\" {...props} />,\n table: ({ node: _n, ...props }) => (\n <div className=\"my-3 overflow-x-auto\">\n <table className=\"border-collapse text-sm\" {...props} />\n </div>\n ),\n ...tableRenderers,\n img: ({ node: _n, src, alt }) => {\n const raw = typeof src === 'string' ? src : undefined;\n return <ArticleImage src={raw && resolveImageSrc ? resolveImageSrc(raw) : raw} alt={alt} />;\n },\n };\n}\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nexport type MarkdownVariant = 'note' | 'article';\n\nexport interface MarkdownProps {\n /** The markdown source. */\n children: string;\n /**\n * `note` (default) — typed copy inside a card: capped headings, single\n * newlines honoured as line breaks, tight blocks.\n * `article` — authored documentation: full heading scale, soft wraps, callout\n * blockquotes, screenshot placeholders, a capped measure.\n */\n variant?: MarkdownVariant;\n /**\n * Collapse to ~6 text lines with a fade. The \"Show more\" control belongs to\n * the caller; this only clamps. The fade appears only when the clamped\n * content actually OVERFLOWS, measured after layout — a note that is long in\n * characters but short on screen would otherwise get a fade under nothing.\n *\n * Needs `react-os-shell/ui.css` for `.rosmd-clamp` / `.rosmd-fade`.\n */\n clamp?: boolean;\n /**\n * Rewrite a relative image `src` before it is fetched. Article bodies\n * reference screenshots by a repo-relative path that only the host knows how\n * to serve.\n */\n resolveImageSrc?: (src: string) => string;\n /** Override individual element renderers. Merged over the variant's set. */\n components?: Partial<Components>;\n className?: string;\n}\n\n/**\n * Render markdown as React elements.\n *\n * No HTML string is produced at any point, and no raw HTML is parsed, so there\n * is nothing here to sanitise — see the note at the top of this file before\n * reaching for DOMPurify.\n */\nfunction Markdown({\n children,\n variant = 'note',\n clamp = false,\n resolveImageSrc,\n components,\n className = '',\n}: MarkdownProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [overflowing, setOverflowing] = useState(false);\n\n useBrowserLayoutEffect(() => {\n const el = ref.current;\n if (!clamp || !el) {\n setOverflowing(false);\n return;\n }\n const check = () => setOverflowing(el.scrollHeight > el.clientHeight + 1);\n check();\n // A ResizeObserver rather than a one-off measure: the same note reflows\n // when its window is resized, and a fade that was correct at mount is\n // wrong the moment it is.\n const ro = new ResizeObserver(check);\n ro.observe(el);\n return () => ro.disconnect();\n }, [clamp, children]);\n\n const resolved = useMemo<Components>(\n () => ({\n ...(variant === 'article' ? articleComponents(resolveImageSrc) : noteComponents(resolveImageSrc)),\n ...components,\n }),\n [variant, resolveImageSrc, components],\n );\n\n // `className` wins over the variant's own ink and type size.\n //\n // Both are ordinary utilities on the same element, so the one that wins is\n // decided by the order Tailwind emits them, not by the order they appear in\n // the attribute — a host asking for `text-green-900` on a green panel would\n // be gambling. The alternative is an important utility at every call site,\n // and the two syntaxes for that (v3's `!text-green-900`, v4's\n // `text-green-900!`) are each silently INERT in the other version, which is\n // a bad thing to ask a caller to get right for a colour.\n //\n // So: a host that names one simply gets it.\n const named = ` ${className} `;\n const ink = /\\stext-(?:[a-z]+-\\d{2,3}(?:\\/\\d+)?|black|white|inherit)\\s/.test(named)\n ? ''\n : 'text-gray-800';\n const size = /\\stext-(?:xs|sm|base|lg|[2-9]?xl|\\[[^\\]]+\\])\\s/.test(named) ? '' : 'text-sm';\n\n return (\n <div\n ref={ref}\n className={[\n variant === 'article' ? 'max-w-3xl break-words' : 'break-words space-y-1.5',\n size,\n ink,\n clamp ? 'rosmd-clamp' : '',\n clamp && overflowing ? 'rosmd-fade' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n <ReactMarkdown remarkPlugins={MARKDOWN_PLUGINS[variant] as never} components={resolved}>\n {children}\n </ReactMarkdown>\n </div>\n );\n}\n\nconst MemoMarkdown = memo(Markdown);\n\nexport { MemoMarkdown as Markdown };\nexport default MemoMarkdown;\n\nexport type { Components as MarkdownComponents } from 'react-markdown';\n\n/** Re-exported so a host can render a placeholder in its own layout. */\nexport type MarkdownNode = ReactNode;\n"]}
@@ -1,8 +1,12 @@
1
- import { useShellStrings, PopupMenu, PopupMenuItem, PopupMenuDivider } from '../chunk-7KIOEKHS.js';
2
- import { confirm } from '../chunk-HYZ5QMVO.js';
3
- import '../chunk-3CUSBZWG.js';
1
+ "use client";
2
+ import { PopupMenu, PopupMenuItem, PopupMenuDivider } from '../chunk-E3TWIN7Z.js';
3
+ import { confirm } from '../chunk-XH3ELPQZ.js';
4
+ import '../chunk-D276EMXS.js';
5
+ import { useShellStrings } from '../chunk-7IN7YZAL.js';
6
+ import { useBrowserLayoutEffect } from '../chunk-IRWSRJ4K.js';
7
+ import '../chunk-IPXN646P.js';
4
8
  import * as ReactModule from 'react';
5
- import { createContext, memo, useRef, useLayoutEffect, useEffect, useMemo, useId, useCallback, useReducer, useContext, useState, Fragment as Fragment$1 } from 'react';
9
+ import { createContext, memo, useRef, useEffect, useMemo, useId, useCallback, useReducer, useContext, useState, Fragment as Fragment$1 } from 'react';
6
10
  import { useLocation, useNavigate } from 'react-router-dom';
7
11
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
8
12
 
@@ -220,7 +224,7 @@ function PageTabsProvider({
220
224
  const locations = useRef(/* @__PURE__ */ new Map());
221
225
  const current = enabled ? entryFor(location) : null;
222
226
  const currentKey = current?.key ?? null;
223
- useLayoutEffect(() => {
227
+ useBrowserLayoutEffect(() => {
224
228
  if (!enabled) return;
225
229
  const entry = entryFor(location);
226
230
  if (entry) {
@@ -269,7 +273,7 @@ function PageTabsProvider({
269
273
  if (confirmDiscard) return confirmDiscard(tabs);
270
274
  return confirm({
271
275
  title: strings.discardTitle,
272
- message: tabs.length === 1 ? strings.discardOne : `${tabs.length} ${strings.discardMany}`,
276
+ message: tabs.length === 1 ? strings.discardOne : strings.discardCount?.(tabs.length) ?? `${tabs.length} ${strings.discardMany}`,
273
277
  confirmLabel: strings.discardConfirm,
274
278
  cancelLabel: strings.keepEditing,
275
279
  variant: "warning"
@@ -376,7 +380,7 @@ var TabFrame = memo(function TabFrame2({ tabKey, active, location, render, class
376
380
  const ref = useRef(null);
377
381
  const activeRef = useRef(active);
378
382
  const scrolls = useRef(/* @__PURE__ */ new Map());
379
- useLayoutEffect(() => {
383
+ useBrowserLayoutEffect(() => {
380
384
  const wasActive = activeRef.current;
381
385
  activeRef.current = active;
382
386
  if (!active || wasActive) return;
@@ -594,7 +598,7 @@ function PageTabBar({ className, activeClassName, end }) {
594
598
  {
595
599
  type: "button",
596
600
  tabIndex: -1,
597
- "aria-label": `${strings.close} ${tab.title}`,
601
+ "aria-label": strings.closeTab?.(tab.title) ?? `${strings.close} ${tab.title}`,
598
602
  onClick: () => void close(tab.key),
599
603
  className: `size-5 items-center justify-center rounded text-gray-400 hover:bg-gray-200 hover:text-gray-700 ${dirty ? "hidden group-hover:flex" : active ? "flex" : "flex opacity-0 group-hover:opacity-100 focus-visible:opacity-100"}`,
600
604
  children: /* @__PURE__ */ jsx(CloseIcon, {})