@k2b/ui 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/base.css +2 -2
- package/dist/browser/actions/Dropdown.js +6 -3
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/FilterChip.js +137 -7
- package/dist/browser/actions/FilterChip.js.map +1 -1
- package/dist/browser/actions/SegmentedControl.js +5 -9
- package/dist/browser/actions/SegmentedControl.js.map +1 -1
- package/dist/browser/actions/SpotlightSearch.js +6 -3
- package/dist/browser/actions/SpotlightSearch.js.map +1 -1
- package/dist/browser/actions/Tabs.js +5 -2
- package/dist/browser/actions/Tabs.js.map +1 -1
- package/dist/browser/actions/Toolbar.js +2 -0
- package/dist/browser/actions/Toolbar.js.map +1 -1
- package/dist/browser/actions/ViewTabs.js +294 -0
- package/dist/browser/actions/ViewTabs.js.map +1 -0
- package/dist/browser/actions/filter-messages.js +6 -2
- package/dist/browser/actions/filter-messages.js.map +1 -1
- package/dist/browser/actions/index.js +2 -1
- package/dist/browser/actions/index.js.map +1 -1
- package/dist/browser/chat/ChatComposer.js +1108 -604
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/ChatDecision.js +146 -84
- package/dist/browser/chat/ChatDecision.js.map +1 -1
- package/dist/browser/chat/ChatPrimitives.js +292 -159
- package/dist/browser/chat/ChatPrimitives.js.map +1 -1
- package/dist/browser/chat/chat-messages.js +8 -0
- package/dist/browser/chat/chat-messages.js.map +1 -1
- package/dist/browser/chat/command-search.js +26 -15
- package/dist/browser/chat/command-search.js.map +1 -1
- package/dist/browser/chat/index.js +4 -2
- package/dist/browser/chat/index.js.map +1 -1
- package/dist/browser/content/Calendar.js +143 -116
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/CalendarWeekView.js +291 -266
- package/dist/browser/content/CalendarWeekView.js.map +1 -1
- package/dist/browser/content/DataTable.js +224 -155
- package/dist/browser/content/DataTable.js.map +1 -1
- package/dist/browser/content/MapAttribution.js +27 -0
- package/dist/browser/content/MapAttribution.js.map +1 -0
- package/dist/browser/content/MapView.js +553 -0
- package/dist/browser/content/MapView.js.map +1 -0
- package/dist/browser/content/MapViewFrame.js +395 -0
- package/dist/browser/content/MapViewFrame.js.map +1 -0
- package/dist/browser/content/data-table-messages.js +4 -2
- package/dist/browser/content/data-table-messages.js.map +1 -1
- package/dist/browser/content/index.js +5 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/map-palette.js +39 -0
- package/dist/browser/content/map-palette.js.map +1 -0
- package/dist/browser/content/map-style.js +276 -0
- package/dist/browser/content/map-style.js.map +1 -0
- package/dist/browser/content/map-view-geometry.js +269 -0
- package/dist/browser/content/map-view-geometry.js.map +1 -0
- package/dist/browser/content/map-view-live.js +28 -0
- package/dist/browser/content/map-view-live.js.map +1 -0
- package/dist/browser/content/map-view-messages.js +53 -0
- package/dist/browser/content/map-view-messages.js.map +1 -0
- package/dist/browser/content/map-view-types.js +2 -0
- package/dist/browser/content/map-view-types.js.map +1 -0
- package/dist/browser/content/map-view-worker.js +10 -0
- package/dist/browser/feedback/MovedHint.js +197 -0
- package/dist/browser/feedback/MovedHint.js.map +1 -0
- package/dist/browser/feedback/dialog-core.js +85 -9
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/feedback/index.js +1 -0
- package/dist/browser/feedback/index.js.map +1 -1
- package/dist/browser/feedback/prompts.js +6 -1
- package/dist/browser/feedback/prompts.js.map +1 -1
- package/dist/browser/inputs/DatePicker.js +337 -212
- package/dist/browser/inputs/DatePicker.js.map +1 -1
- package/dist/browser/inputs/date-picker.js +59 -5
- package/dist/browser/inputs/date-picker.js.map +1 -1
- package/dist/browser/intl/messages.js +12 -0
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/intl/weekend.js +39 -0
- package/dist/browser/intl/weekend.js.map +1 -0
- package/dist/browser/layout/AppOverview.js +0 -1
- package/dist/browser/layout/AppOverview.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +701 -476
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/PageTitle.js +3 -2
- package/dist/browser/layout/PageTitle.js.map +1 -1
- package/dist/browser/layout/SettingsModal.js +2 -2
- package/dist/browser/layout/SettingsModal.js.map +1 -1
- package/dist/browser/layout/SidebarSwitcher.js +13 -12
- package/dist/browser/layout/SidebarSwitcher.js.map +1 -1
- package/dist/browser/layout/TabBar.js +1 -1
- package/dist/browser/layout/TabBar.js.map +1 -1
- package/dist/browser/layout/app-host-width.js +44 -0
- package/dist/browser/layout/app-host-width.js.map +1 -0
- package/dist/browser/layout/app-workspace-controller.js +331 -45
- package/dist/browser/layout/app-workspace-controller.js.map +1 -1
- package/dist/browser/layout/app-workspace-width.js +26 -0
- package/dist/browser/layout/app-workspace-width.js.map +1 -0
- package/dist/browser/layout/index.js +2 -1
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/surfaces/Placeholder.js +36 -13
- package/dist/browser/surfaces/Placeholder.js.map +1 -1
- package/dist/licenses/maplibre-gl.txt +116 -0
- package/dist/ssr/actions/Dropdown.js +5 -2
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/FilterChip.js +59 -7
- package/dist/ssr/actions/FilterChip.js.map +1 -1
- package/dist/ssr/actions/SegmentedControl.js +1 -2
- package/dist/ssr/actions/SegmentedControl.js.map +1 -1
- package/dist/ssr/actions/SpotlightSearch.js +2 -2
- package/dist/ssr/actions/SpotlightSearch.js.map +1 -1
- package/dist/ssr/actions/Tabs.js +2 -2
- package/dist/ssr/actions/Tabs.js.map +1 -1
- package/dist/ssr/actions/Toolbar.js +2 -0
- package/dist/ssr/actions/Toolbar.js.map +1 -1
- package/dist/ssr/actions/ViewTabs.js +211 -0
- package/dist/ssr/actions/ViewTabs.js.map +1 -0
- package/dist/ssr/actions/filter-messages.js +6 -2
- package/dist/ssr/actions/filter-messages.js.map +1 -1
- package/dist/ssr/actions/index.js +2 -1
- package/dist/ssr/actions/index.js.map +1 -1
- package/dist/ssr/chat/ChatComposer.js +546 -131
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/ChatDecision.js +44 -19
- package/dist/ssr/chat/ChatDecision.js.map +1 -1
- package/dist/ssr/chat/ChatPrimitives.js +95 -24
- package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
- package/dist/ssr/chat/chat-messages.js +8 -0
- package/dist/ssr/chat/chat-messages.js.map +1 -1
- package/dist/ssr/chat/command-search.js +26 -15
- package/dist/ssr/chat/command-search.js.map +1 -1
- package/dist/ssr/chat/index.js +4 -2
- package/dist/ssr/chat/index.js.map +1 -1
- package/dist/ssr/content/Calendar.js +109 -100
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/CalendarWeekView.js +42 -28
- package/dist/ssr/content/CalendarWeekView.js.map +1 -1
- package/dist/ssr/content/DataTable.js +78 -31
- package/dist/ssr/content/DataTable.js.map +1 -1
- package/dist/ssr/content/MapAttribution.js +19 -0
- package/dist/ssr/content/MapAttribution.js.map +1 -0
- package/dist/ssr/content/MapView.js +474 -0
- package/dist/ssr/content/MapView.js.map +1 -0
- package/dist/ssr/content/MapViewFrame.js +193 -0
- package/dist/ssr/content/MapViewFrame.js.map +1 -0
- package/dist/ssr/content/data-table-messages.js +4 -2
- package/dist/ssr/content/data-table-messages.js.map +1 -1
- package/dist/ssr/content/index.js +5 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/map-palette.js +39 -0
- package/dist/ssr/content/map-palette.js.map +1 -0
- package/dist/ssr/content/map-style.js +276 -0
- package/dist/ssr/content/map-style.js.map +1 -0
- package/dist/ssr/content/map-view-geometry.js +269 -0
- package/dist/ssr/content/map-view-geometry.js.map +1 -0
- package/dist/ssr/content/map-view-live.js +1 -0
- package/dist/ssr/content/map-view-messages.js +53 -0
- package/dist/ssr/content/map-view-messages.js.map +1 -0
- package/dist/ssr/content/map-view-types.js +2 -0
- package/dist/ssr/content/map-view-types.js.map +1 -0
- package/dist/ssr/feedback/MovedHint.js +166 -0
- package/dist/ssr/feedback/MovedHint.js.map +1 -0
- package/dist/ssr/feedback/dialog-core.js +85 -9
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/feedback/index.js +1 -0
- package/dist/ssr/feedback/index.js.map +1 -1
- package/dist/ssr/feedback/prompts.js +6 -1
- package/dist/ssr/feedback/prompts.js.map +1 -1
- package/dist/ssr/inputs/DatePicker.js +138 -69
- package/dist/ssr/inputs/DatePicker.js.map +1 -1
- package/dist/ssr/inputs/date-picker.js +59 -5
- package/dist/ssr/inputs/date-picker.js.map +1 -1
- package/dist/ssr/intl/messages.js +12 -0
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/intl/weekend.js +39 -0
- package/dist/ssr/intl/weekend.js.map +1 -0
- package/dist/ssr/layout/AppOverview.js +0 -1
- package/dist/ssr/layout/AppOverview.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +216 -44
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/PageTitle.js +3 -2
- package/dist/ssr/layout/PageTitle.js.map +1 -1
- package/dist/ssr/layout/SettingsModal.js +2 -2
- package/dist/ssr/layout/SettingsModal.js.map +1 -1
- package/dist/ssr/layout/SidebarSwitcher.js +13 -12
- package/dist/ssr/layout/SidebarSwitcher.js.map +1 -1
- package/dist/ssr/layout/TabBar.js +1 -1
- package/dist/ssr/layout/TabBar.js.map +1 -1
- package/dist/ssr/layout/app-host-width.js +44 -0
- package/dist/ssr/layout/app-host-width.js.map +1 -0
- package/dist/ssr/layout/app-workspace-controller.js +331 -45
- package/dist/ssr/layout/app-workspace-controller.js.map +1 -1
- package/dist/ssr/layout/app-workspace-width.js +26 -0
- package/dist/ssr/layout/app-workspace-width.js.map +1 -0
- package/dist/ssr/layout/index.js +2 -1
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/surfaces/Placeholder.js +24 -7
- package/dist/ssr/surfaces/Placeholder.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/Dropdown.d.ts +4 -0
- package/dist/types/actions/FilterChip.d.ts +22 -0
- package/dist/types/actions/ViewTabs.d.ts +30 -0
- package/dist/types/actions/filter-messages.d.ts +2 -0
- package/dist/types/actions/index.d.ts +4 -2
- package/dist/types/chat/ChatComposer.d.ts +52 -0
- package/dist/types/chat/ChatDecision.d.ts +18 -2
- package/dist/types/chat/ChatPrimitives.d.ts +10 -0
- package/dist/types/chat/chat-messages.d.ts +4 -0
- package/dist/types/chat/command-search.d.ts +2 -0
- package/dist/types/chat/index.d.ts +9 -8
- package/dist/types/content/DataTable.d.ts +3 -0
- package/dist/types/content/MapAttribution.d.ts +6 -0
- package/dist/types/content/MapView.d.ts +5 -0
- package/dist/types/content/MapViewFrame.d.ts +31 -0
- package/dist/types/content/data-table-messages.d.ts +1 -0
- package/dist/types/content/index.d.ts +6 -0
- package/dist/types/content/map-palette.d.ts +58 -0
- package/dist/types/content/map-style.d.ts +29 -0
- package/dist/types/content/map-view-geometry.d.ts +55 -0
- package/dist/types/content/map-view-live.d.ts +2 -0
- package/dist/types/content/map-view-messages.d.ts +21 -0
- package/dist/types/content/map-view-types.d.ts +61 -0
- package/dist/types/feedback/MovedHint.d.ts +25 -0
- package/dist/types/feedback/dialog-core.d.ts +6 -2
- package/dist/types/feedback/index.d.ts +2 -0
- package/dist/types/feedback/prompts.d.ts +2 -0
- package/dist/types/inputs/DatePicker.d.ts +7 -0
- package/dist/types/inputs/date-picker.d.ts +25 -1
- package/dist/types/intl/messages.d.ts +6 -0
- package/dist/types/intl/weekend.d.ts +12 -0
- package/dist/types/layout/AppWorkspace.d.ts +48 -8
- package/dist/types/layout/app-host-width.d.ts +6 -0
- package/dist/types/layout/app-workspace-width.d.ts +13 -0
- package/dist/types/layout/index.d.ts +3 -2
- package/dist/types/surfaces/Placeholder.d.ts +2 -1
- package/map-glyphs/IBM Plex Sans Medium/0-255.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/1024-1279.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/256-511.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/512-767.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/768-1023.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/7680-7935.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/8192-8447.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/8448-8703.pbf +0 -0
- package/map-glyphs/IBM Plex Sans Medium/8704-8959.pbf +0 -0
- package/map-glyphs/OFL.txt +93 -0
- package/package.json +11 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["content/map-view-geometry.ts"],"sourcesContent":["import type { MapViewFrameProps, MapViewLine, MapViewMarker, MapViewPoint } from \"./map-view-types\";\n\nexport const MIN_SPAN = 256 / (512 * 2 ** 16);\nexport const EARTH_CIRCUMFERENCE = 40075016.686;\n/** Room around the fitted geometry: clears the floating controls, the notice, the scale bar and the attribution. */\nexport const DEFAULT_PADDING = 64;\n/** Phones are first-class: pin separation is decided from the narrowest common screen up, so it holds at every width. */\nconst PHONE_WIDTH = 320;\n/**\n * `referenceWidth` is the fitted width when the container is wide enough; `minWidth` keeps the whole world at least as\n * tall as the map, the smallest zoom MapLibre allows, so a world-scale fit lands where MapLibre puts it.\n */\nexport type MapFrame = {\n minX: number;\n minY: number;\n spanX: number;\n spanY: number;\n aspect: number;\n referenceWidth: number;\n minWidth: number;\n /** The fitted width on a 320 px phone: pins are kept apart from this scale up, so they never cover each other there. */\n overlapWidth: number;\n};\n\nexport function decodePolyline6(encoded: string): MapViewPoint[] {\n let index = 0;\n let lat = 0;\n let lon = 0;\n const next = () => {\n let value = 0;\n let shift = 0;\n let byte: number;\n do {\n if (index >= encoded.length || shift > 50) throw new RangeError(\"Invalid polyline6\");\n byte = encoded.charCodeAt(index++) - 63;\n if (byte < 0 || byte > 63) throw new RangeError(\"Invalid polyline6\");\n value += (byte & 31) * 2 ** shift;\n shift += 5;\n } while (byte >= 32);\n return value % 2 ? -(value + 1) / 2 : value / 2;\n };\n const points: MapViewPoint[] = [];\n while (index < encoded.length) {\n lat += next();\n lon += next();\n points.push({ lat: lat / 1e6, lon: lon / 1e6 });\n }\n return points;\n}\n\nexport const linePoints = (line: MapViewLine): MapViewPoint[] => (\"points\" in line ? line.points : decodePolyline6(line.polyline6));\nexport function projectPoint(point: MapViewPoint): { x: number; y: number } {\n const lat = (Math.max(-85.051129, Math.min(85.051129, point.lat)) * Math.PI) / 180;\n return { x: (point.lon + 180) / 360, y: (1 - Math.log(Math.tan(Math.PI / 4 + lat / 2)) / Math.PI) / 2 };\n}\nexport function unprojectPoint(x: number, y: number): MapViewPoint {\n return { lon: x * 360 - 180, lat: (Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180) / Math.PI };\n}\nexport function mapFrame(\n geometry: Pick<MapViewFrameProps, \"markers\" | \"lines\" | \"areas\" | \"ownLocation\" | \"fit\">,\n height: number,\n padding = DEFAULT_PADDING,\n): MapFrame | null {\n const points = (geometry.markers ?? []).map((marker) => marker.point);\n if (geometry.fit !== \"markers\" || !points.length) {\n for (const line of geometry.lines ?? []) for (const point of linePoints(line)) points.push(point);\n for (const area of geometry.areas ?? []) for (const point of area.polygon) points.push(point);\n if (geometry.ownLocation) points.push(geometry.ownLocation.point);\n }\n if (!points.length) return null;\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n for (const point of points) {\n const { x, y } = projectPoint(point);\n minX = Math.min(minX, x);\n maxX = Math.max(maxX, x);\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n }\n const spanX = Math.max(MIN_SPAN, maxX - minX);\n const spanY = Math.max(MIN_SPAN, maxY - minY);\n minX -= (spanX - (maxX - minX)) / 2;\n minY -= (spanY - (maxY - minY)) / 2;\n const aspect = spanX / spanY;\n const minWidth = spanX * height;\n const referenceWidth = Math.max((height - 2 * padding) * aspect, minWidth);\n const overlapWidth = Math.max(minWidth, Math.min(referenceWidth, PHONE_WIDTH - 2 * padding));\n return { minX, minY, spanX, spanY, aspect, referenceWidth, minWidth, overlapWidth };\n}\nexport function fitCamera(frame: MapFrame, width: number, padding = DEFAULT_PADDING): { center: [number, number]; zoom: number } {\n const w = Math.max(Number.EPSILON, frame.minWidth, Math.min(width - 2 * padding, frame.referenceWidth));\n const center = unprojectPoint(frame.minX + frame.spanX / 2, frame.minY + frame.spanY / 2);\n return { center: [center.lon, center.lat], zoom: Math.log2(w / (frame.spanX * 512)) };\n}\nexport const isStop = (marker: MapViewMarker): boolean => typeof marker.mark === \"string\" && /^[a-z+]$/i.test(marker.mark);\nexport function sortMarkers(markers: MapViewMarker[]): MapViewMarker[] {\n const rank = (marker: MapViewMarker): number => {\n if (typeof marker.mark === \"number\") return 0;\n if (typeof marker.mark === \"string\" && /^[a-z]$/i.test(marker.mark)) return 1;\n if (marker.mark === \"+\") return 2;\n return 3;\n };\n return [...markers].sort((a, b) => {\n const group = rank(a) - rank(b);\n if (group) return group;\n if (typeof a.mark === \"number\" && typeof b.mark === \"number\") return a.mark - b.mark;\n if (rank(a) === 1) return String(a.mark).toUpperCase().localeCompare(String(b.mark).toUpperCase(), \"en\");\n return 0;\n });\n}\ntype PinOffset = { dx: number; dy: number };\ntype PlacedPin = { id: string; x: number; y: number; dx: number; dy: number };\nconst PIN_SPACING = 28;\n// An attached hit may touch its stop's halo, as in the approved mockup, but never cover the stop's mark.\nconst ATTACHED_SPACING = 20;\n\n/**\n * The smallest distance between two drawn pins at every width the map can have: the geographic gap `g` (px at the\n * phone fit) grows with the fitted width by r in [1, rMax], the offsets `o` stay in px. |g·r + o| is smallest at one r.\n */\nconst closest = (gx: number, gy: number, ox: number, oy: number, rMax: number): number => {\n const gg = gx * gx + gy * gy;\n const r = gg === 0 ? 1 : Math.min(rMax, Math.max(1, -(gx * ox + gy * oy) / gg));\n return Math.hypot(gx * r + ox, gy * r + oy);\n};\n\n/** Deterministic places around a point: the mockup's two rings first, then wider rings for dense clusters. */\nconst candidateOffsets = (count: number): PinOffset[] => {\n const angles = [-45, 45, 135, -135, 0, 90, 180, -90];\n // Ring k holds 8k places, so this many rings fit every pin of a cluster at one point with room to spare.\n const rings = Math.ceil(Math.sqrt(count / 4)) + 2;\n const result: PinOffset[] = [];\n for (let ring = 1; ring <= rings; ring++) {\n const steps = ring <= 2 ? angles : Array.from({ length: 8 * ring }, (_, index) => -45 + (360 * index) / (8 * ring));\n for (const angle of steps)\n result.push({\n dx: PIN_SPACING * ring * Math.cos((angle * Math.PI) / 180),\n dy: PIN_SPACING * ring * Math.sin((angle * Math.PI) / 180),\n });\n }\n return result;\n};\n\nconst pinPlaces = (sortedMarkers: MapViewMarker[], frame: MapFrame, zoomFactor: number) => {\n const scale = (frame.overlapWidth / frame.spanX) * zoomFactor;\n return (marker: MapViewMarker) => {\n const p = projectPoint(marker.point);\n return { x: (p.x - frame.minX) * scale, y: (p.y - frame.minY) * scale };\n };\n};\n/**\n * A drop pin's mark sits 14 px above its point (its tip is on the point). That lift is a fixed px shift, so it belongs\n * with the offsets, never with the geographic gap that grows with the map's width.\n */\nconst lift = (marker: MapViewMarker): number => (marker.mark === undefined ? 14 : 0);\n\n/**\n * Pin offsets in px, decided from the data alone: at the phone fit and checked for every wider fit up to the widest\n * one, so they hold at any width. `zoomFactor` is 2^(live zoom - fitted zoom): 1 for the server frame, the first client\n * frame and the untouched live map, so all three agree; when the person zooms, separation is decided for the new scale.\n */\nexport function pinOffsets(\n sortedMarkers: MapViewMarker[],\n frame: MapFrame,\n zoomFactor = 1,\n ownLocation?: MapViewPoint,\n): Map<string, PinOffset> {\n const position = pinPlaces(sortedMarkers, frame, zoomFactor);\n const location = ownLocation ? projectPoint(ownLocation) : undefined;\n const scale = (frame.overlapWidth / frame.spanX) * zoomFactor;\n const rMax = Math.max(1, frame.referenceWidth / frame.overlapWidth);\n const candidates = candidateOffsets(sortedMarkers.length);\n const reachOf = (offset: PinOffset) => Math.hypot(offset.dx, offset.dy);\n const furthest = reachOf(candidates[candidates.length - 1]!);\n const placed: (PlacedPin & { reach: number })[] = [];\n const offsets = new Map<string, PinOffset>();\n // Pins at one point search on from where the previous one found its place, so a dense cluster costs one pass.\n const searchFrom = new Map<string, number>();\n // Offsets here are those of the visible mark; a drop pin's own offset adds its lift back below.\n const place = (marker: MapViewMarker, preferred: PinOffset, anchorId?: string) => {\n const p = position(marker);\n // The location is fixed; route stops keep their anchors even when they coincide with it.\n const obstacle = !isStop(marker) && location;\n // Two pins whose points are further apart than their offsets plus one pin never meet at a wider fit\n // (|g·r + o| ≥ |g| − |o| for r ≥ 1), so only the pins near this one are checked, once per pin.\n const near = placed.filter((other) => Math.hypot(p.x - other.x, p.y - other.y) < furthest + other.reach + PIN_SPACING);\n // Leave 22 px around the fixed dot: 12 px pin radius + 8.5 px dot radius + 1.5 px gap.\n const clear = (offset: PinOffset, anchor?: string) =>\n (!obstacle ||\n closest(p.x - (obstacle.x - frame.minX) * scale, p.y - (obstacle.y - frame.minY) * scale, offset.dx, offset.dy, rMax) >=\n 22 - 1e-9) &&\n near.every(\n (other) =>\n closest(p.x - other.x, p.y - other.y, offset.dx - other.dx, offset.dy - other.dy, rMax) >=\n (other.id === anchor ? ATTACHED_SPACING : PIN_SPACING) - 1e-9,\n );\n let offset = preferred;\n if (!clear(preferred, anchorId)) {\n const key = `${Math.round(p.x)},${Math.round(p.y)}`;\n const first = searchFrom.get(key) ?? 0;\n const found = candidates.findIndex((candidate, index) => index >= first && clear(candidate));\n offset = candidates[found] ?? candidates[candidates.length - 1]!;\n searchFrom.set(key, found < 0 ? candidates.length : found + 1);\n }\n offsets.set(marker.id, { dx: offset.dx, dy: offset.dy + lift(marker) });\n const pin = { id: marker.id, ...p, ...offset, reach: reachOf(offset) };\n placed.push(pin);\n return pin;\n };\n // Stops come first in mark order; a later stop that would cover an earlier one (such as \"+\" beside C) moves aside.\n const stops = sortedMarkers.filter(isStop).map((stop) => place(stop, { dx: 0, dy: -lift(stop) }));\n for (const marker of sortedMarkers.filter((marker) => !isStop(marker))) {\n const p = position(marker);\n // The attachment sits top right of where the stop is drawn, wherever the hit lies around it.\n const own = { dx: 0, dy: -lift(marker) };\n const anchor = stops.find((stop) => Math.hypot(p.x + own.dx - stop.x - stop.dx, p.y + own.dy - stop.y - stop.dy) < PIN_SPACING);\n place(marker, anchor ? { dx: anchor.x + anchor.dx + 17 - p.x, dy: anchor.y + anchor.dy - 17 - p.y } : own, anchor?.id);\n }\n return offsets;\n}\n/**\n * The touch hit radius of each pin in px: up to 22 px (a 44 px target), but never into a neighbour's visible mark\n * (radius up to 14 px) at any width, so a tap on a visible pin always reaches it.\n */\nexport function pinHitRadii(\n sortedMarkers: MapViewMarker[],\n frame: MapFrame,\n offsets: Map<string, PinOffset>,\n zoomFactor = 1,\n): Map<string, number> {\n const position = pinPlaces(sortedMarkers, frame, zoomFactor);\n const rMax = Math.max(1, frame.referenceWidth / frame.overlapWidth);\n const places = sortedMarkers.map((marker) => {\n const offset = offsets.get(marker.id) ?? { dx: 0, dy: 0 };\n return { id: marker.id, ...position(marker), dx: offset.dx, dy: offset.dy - lift(marker) };\n });\n return new Map(\n places.map((place) => {\n let nearest = Infinity;\n for (const other of places)\n if (other !== place)\n nearest = Math.min(nearest, closest(place.x - other.x, place.y - other.y, place.dx - other.dx, place.dy - other.dy, rMax));\n return [place.id, Math.max(12, Math.min(22, nearest - 14))];\n }),\n );\n}\nexport const metersPerPixel = (lat: number, zoom: number): number =>\n (EARTH_CIRCUMFERENCE * Math.cos((lat * Math.PI) / 180)) / (512 * 2 ** zoom);\nexport function scaleBar(mpp: number, maxPx = 96): { meters: number; label: string } {\n const maximum = mpp * maxPx;\n const magnitude = 10 ** Math.floor(Math.log10(maximum));\n const meters = ([5, 2, 1].find((value) => value * magnitude <= maximum) ?? 1) * magnitude;\n return { meters, label: meters >= 1000 ? `${meters / 1000} km` : `${Number(meters.toPrecision(12))} m` };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function createMap() { throw new Error("MapView live rendering requires a browser"); }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { i18n } from "@k2b/stdlib";
|
|
2
|
+
import { useLocale } from "../intl/locale";
|
|
3
|
+
const mapViewMessages = i18n.define({
|
|
4
|
+
baseLocale: "en",
|
|
5
|
+
messages: {
|
|
6
|
+
en: {
|
|
7
|
+
mapLabel: "Map",
|
|
8
|
+
summary: ({
|
|
9
|
+
count
|
|
10
|
+
}) => `Map with ${count} ${count === 1 ? "place" : "places"}`,
|
|
11
|
+
pins: "Places on the map",
|
|
12
|
+
zoomIn: "Zoom in",
|
|
13
|
+
zoomOut: "Zoom out",
|
|
14
|
+
locate: "Show my location",
|
|
15
|
+
loading: "Loading map …",
|
|
16
|
+
error: "Map image did not load. The pins stay.",
|
|
17
|
+
retry: "Try again",
|
|
18
|
+
reload: "Reload page",
|
|
19
|
+
noWebgl: "This browser cannot show the map image. The pins stay.",
|
|
20
|
+
ownLocation: "Your location",
|
|
21
|
+
canvasTitle: "Map. Arrow keys move it.",
|
|
22
|
+
gestureWindows: "Use Ctrl + scroll to zoom the map",
|
|
23
|
+
gestureMac: "Use ⌘ + scroll to zoom the map",
|
|
24
|
+
gestureMobile: "Use two fingers to move the map"
|
|
25
|
+
},
|
|
26
|
+
de: {
|
|
27
|
+
mapLabel: "Karte",
|
|
28
|
+
summary: ({
|
|
29
|
+
count
|
|
30
|
+
}) => `Karte mit ${count} ${count === 1 ? "Ort" : "Orten"}`,
|
|
31
|
+
pins: "Orte auf der Karte",
|
|
32
|
+
zoomIn: "Vergrößern",
|
|
33
|
+
zoomOut: "Verkleinern",
|
|
34
|
+
locate: "Ich bin hier",
|
|
35
|
+
loading: "Karte lädt …",
|
|
36
|
+
error: "Kartenbild nicht geladen, die Pins bleiben.",
|
|
37
|
+
retry: "Erneut versuchen",
|
|
38
|
+
reload: "Seite neu laden",
|
|
39
|
+
noWebgl: "Dieser Browser kann das Kartenbild nicht zeigen, die Pins bleiben.",
|
|
40
|
+
ownLocation: "Dein Standort",
|
|
41
|
+
canvasTitle: "Karte. Pfeiltasten verschieben sie.",
|
|
42
|
+
gestureWindows: "Strg + Scrollen zoomt die Karte",
|
|
43
|
+
gestureMac: "⌘ + Scrollen zoomt die Karte",
|
|
44
|
+
gestureMobile: "Mit zwei Fingern verschieben"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
export const useMapViewMessages = () => {
|
|
49
|
+
const locale = useLocale();
|
|
50
|
+
return () => mapViewMessages.resolve([locale()]).t;
|
|
51
|
+
};
|
|
52
|
+
export const checkMapViewMessages = () => mapViewMessages.check();
|
|
53
|
+
//# sourceMappingURL=map-view-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["content/map-view-messages.ts"],"sourcesContent":["import { i18n } from \"@k2b/stdlib\";\nimport { useLocale } from \"../intl/locale\";\n\nconst mapViewMessages = i18n.define({\n baseLocale: \"en\",\n messages: {\n en: {\n mapLabel: \"Map\",\n summary: ({ count }: { count: number }) => `Map with ${count} ${count === 1 ? \"place\" : \"places\"}`,\n pins: \"Places on the map\",\n zoomIn: \"Zoom in\",\n zoomOut: \"Zoom out\",\n locate: \"Show my location\",\n loading: \"Loading map …\",\n error: \"Map image did not load. The pins stay.\",\n retry: \"Try again\",\n reload: \"Reload page\",\n noWebgl: \"This browser cannot show the map image. The pins stay.\",\n ownLocation: \"Your location\",\n canvasTitle: \"Map. Arrow keys move it.\",\n gestureWindows: \"Use Ctrl + scroll to zoom the map\",\n gestureMac: \"Use ⌘ + scroll to zoom the map\",\n gestureMobile: \"Use two fingers to move the map\",\n },\n de: {\n mapLabel: \"Karte\",\n summary: ({ count }) => `Karte mit ${count} ${count === 1 ? \"Ort\" : \"Orten\"}`,\n pins: \"Orte auf der Karte\",\n zoomIn: \"Vergrößern\",\n zoomOut: \"Verkleinern\",\n locate: \"Ich bin hier\",\n loading: \"Karte lädt …\",\n error: \"Kartenbild nicht geladen, die Pins bleiben.\",\n retry: \"Erneut versuchen\",\n reload: \"Seite neu laden\",\n noWebgl: \"Dieser Browser kann das Kartenbild nicht zeigen, die Pins bleiben.\",\n ownLocation: \"Dein Standort\",\n canvasTitle: \"Karte. Pfeiltasten verschieben sie.\",\n gestureWindows: \"Strg + Scrollen zoomt die Karte\",\n gestureMac: \"⌘ + Scrollen zoomt die Karte\",\n gestureMobile: \"Mit zwei Fingern verschieben\",\n },\n },\n});\nexport const useMapViewMessages = () => {\n const locale = useLocale();\n return () => mapViewMessages.resolve([locale()]).t;\n};\nexport const checkMapViewMessages = () => mapViewMessages.check();\n"],"mappings":"AAAA,SAAS,IAAI,QAAQ,aAAa;AAClC,SAAS,SAAS,QAAQ,gBAAgB;AAE1C,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC;EAClC,UAAU,EAAE,IAAI;EAChB,QAAQ,EAAE;IACR,EAAE,EAAE;MACF,QAAQ,EAAE,KAAK;MACf,OAAO,EAAE,CAAC;QAAE;MAAyB,CAAC,KAAK,YAAY,KAAK,IAAI,KAAK,KAAK,CAAC,GAAG,OAAO,GAAG,QAAQ,EAAE;MAClG,IAAI,EAAE,mBAAmB;MACzB,MAAM,EAAE,SAAS;MACjB,OAAO,EAAE,UAAU;MACnB,MAAM,EAAE,kBAAkB;MAC1B,OAAO,EAAE,eAAe;MACxB,KAAK,EAAE,wCAAwC;MAC/C,KAAK,EAAE,WAAW;MAClB,MAAM,EAAE,aAAa;MACrB,OAAO,EAAE,wDAAwD;MACjE,WAAW,EAAE,eAAe;MAC5B,WAAW,EAAE,0BAA0B;MACvC,cAAc,EAAE,mCAAmC;MACnD,UAAU,EAAE,gCAAgC;MAC5C,aAAa,EAAE;IACjB,CAAC;IACD,EAAE,EAAE;MACF,QAAQ,EAAE,OAAO;MACjB,OAAO,EAAE,CAAC;QAAE;MAAM,CAAC,KAAK,aAAa,KAAK,IAAI,KAAK,KAAK,CAAC,GAAG,KAAK,GAAG,OAAO,EAAE;MAC7E,IAAI,EAAE,oBAAoB;MAC1B,MAAM,EAAE,YAAY;MACpB,OAAO,EAAE,aAAa;MACtB,MAAM,EAAE,cAAc;MACtB,OAAO,EAAE,cAAc;MACvB,KAAK,EAAE,6CAA6C;MACpD,KAAK,EAAE,kBAAkB;MACzB,MAAM,EAAE,iBAAiB;MACzB,OAAO,EAAE,oEAAoE;MAC7E,WAAW,EAAE,eAAe;MAC5B,WAAW,EAAE,qCAAqC;MAClD,cAAc,EAAE,iCAAiC;MACjD,UAAU,EAAE,8BAA8B;MAC1C,aAAa,EAAE;IACjB;EACF;AACF,CAAC,CAAC;AACF,OAAO,MAAM,kBAAkB,GAAG,MAAM;EACtC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC;EAC1B,OAAO,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AACD,OAAO,MAAM,oBAAoB,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["content/map-view-types.ts"],"sourcesContent":["import type { StyleSpecification } from \"maplibre-gl\";\nimport type { JSX } from \"solid-js\";\n\nexport type MapViewPoint = { lat: number; lon: number };\nexport type MapViewTone = \"accent\" | \"second\" | \"neutral\" | \"warning\";\nexport type MapViewMarker = {\n id: string;\n point: MapViewPoint;\n /** A number, a single letter, or \"+\". Omit for a drop pin. */\n mark?: number | string;\n label: string;\n tone?: MapViewTone;\n selected?: boolean;\n};\nexport type MapViewLineTone = \"accent\" | \"muted\" | \"preview\" | \"previous\";\nexport type MapViewLine = { id: string; tone?: MapViewLineTone } & ({ polyline6: string } | { points: MapViewPoint[] });\nexport type MapViewArea = { id: string; polygon: MapViewPoint[]; tone?: MapViewTone };\nexport type MapViewOwnLocation = { point: MapViewPoint; accuracyMeters?: number };\nexport type MapViewStyle = string | StyleSpecification;\nexport type MapViewStyleSource = MapViewStyle | { light: MapViewStyle; dark: MapViewStyle };\nexport type MapViewFit = \"all\" | \"markers\";\nexport type MapViewFrameProps = {\n markers?: MapViewMarker[];\n lines?: MapViewLine[];\n areas?: MapViewArea[];\n ownLocation?: MapViewOwnLocation;\n height: number;\n padding?: number;\n fit?: MapViewFit;\n selectedId?: string;\n attribution?: JSX.Element;\n class?: string;\n};\nexport type MapViewProps = Omit<MapViewFrameProps, \"attribution\"> & {\n style: MapViewStyleSource;\n attribution: JSX.Element;\n onSelect?: (id: string) => void;\n infoTile?: (marker: MapViewMarker) => JSX.Element;\n notice?: JSX.Element;\n empty?: JSX.Element;\n onLocate?: () => void;\n};\n"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { ssr as _$ssr } from "solid-js/web";
|
|
2
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
3
|
+
import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
|
|
4
|
+
import { escape as _$escape } from "solid-js/web";
|
|
5
|
+
var _tmpl$ = ["<div class=\"", "\" popover=\"manual\" role=\"group\"", "><p class=\"k2b-moved-hint__text\"", ">", "</p><div class=\"k2b-moved-hint__actions\">", "", "</div></div>"];
|
|
6
|
+
import { createEffect, createSignal, createUniqueId, onCleanup, onMount, Show } from "solid-js";
|
|
7
|
+
import { Button, ButtonLink } from "../actions/Button";
|
|
8
|
+
import { useUiMessages } from "../intl/messages";
|
|
9
|
+
import { announce } from "./announce";
|
|
10
|
+
import { positionTooltipSurface } from "./tooltip-position";
|
|
11
|
+
export function MovedHint(props) {
|
|
12
|
+
const messages = useUiMessages();
|
|
13
|
+
const textId = `k2b-moved-hint-${createUniqueId()}`;
|
|
14
|
+
const [mounted, setMounted] = createSignal(false);
|
|
15
|
+
const [dismissed, setDismissed] = createSignal(false);
|
|
16
|
+
let surface;
|
|
17
|
+
let text;
|
|
18
|
+
let previousFocus;
|
|
19
|
+
let shown = false;
|
|
20
|
+
let announced = false;
|
|
21
|
+
const hide = () => {
|
|
22
|
+
if (!shown) return;
|
|
23
|
+
shown = false;
|
|
24
|
+
try {
|
|
25
|
+
surface?.hidePopover?.();
|
|
26
|
+
} catch {
|
|
27
|
+
// Cleanup can run after the surface disconnects.
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const dismiss = restoreFocus => {
|
|
31
|
+
if (!props.open || dismissed()) return false;
|
|
32
|
+
const focusInside = surface?.contains(document.activeElement);
|
|
33
|
+
const target = props.target();
|
|
34
|
+
const returnTo = previousFocus?.isConnected && !previousFocus.matches(":disabled") ? previousFocus : target && (target.tabIndex >= 0 || target.hasAttribute("tabindex")) && !target.matches(":disabled") ? target : undefined;
|
|
35
|
+
hide();
|
|
36
|
+
if (restoreFocus && focusInside && returnTo) {
|
|
37
|
+
returnTo.focus({
|
|
38
|
+
preventScroll: true
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
setDismissed(true);
|
|
42
|
+
props.onDismiss();
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
createEffect(() => {
|
|
46
|
+
if (!props.open) {
|
|
47
|
+
setDismissed(false);
|
|
48
|
+
previousFocus = undefined;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
onMount(() => setMounted(true));
|
|
52
|
+
createEffect(() => {
|
|
53
|
+
if (!mounted() || !props.open || dismissed()) return;
|
|
54
|
+
const anchor = props.target();
|
|
55
|
+
const placement = props.placement ?? "bottom";
|
|
56
|
+
if (!surface || !anchor) return;
|
|
57
|
+
|
|
58
|
+
// A target scrolled out of view, or clipped by its scroller, takes the hint along instead of leaving it
|
|
59
|
+
// pinned to the viewport edge over unrelated content.
|
|
60
|
+
let inView = typeof IntersectionObserver === "undefined";
|
|
61
|
+
const position = () => {
|
|
62
|
+
if (!props.open || dismissed() || !inView || !anchor.isConnected || anchor.getClientRects().length === 0) {
|
|
63
|
+
hide();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!surface || typeof surface.showPopover !== "function") return;
|
|
67
|
+
if (!shown) {
|
|
68
|
+
try {
|
|
69
|
+
surface.showPopover();
|
|
70
|
+
shown = true;
|
|
71
|
+
} catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
positionTooltipSurface(surface, anchor, placement);
|
|
76
|
+
if (!announced) {
|
|
77
|
+
announced = true;
|
|
78
|
+
announce(text?.textContent ?? "");
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const focusIn = event => {
|
|
82
|
+
if (!(event.relatedTarget instanceof Node) || !surface?.contains(event.relatedTarget)) {
|
|
83
|
+
previousFocus = event.relatedTarget instanceof HTMLElement ? event.relatedTarget : undefined;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const keydown = event => {
|
|
87
|
+
if (event.defaultPrevented || event.isComposing) return;
|
|
88
|
+
if (event.key !== "Escape" || !(event.target instanceof Node)) return;
|
|
89
|
+
if (!surface?.contains(event.target) && !anchor.contains(event.target)) return;
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
dismiss(true);
|
|
92
|
+
};
|
|
93
|
+
const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(position);
|
|
94
|
+
observer?.observe(anchor);
|
|
95
|
+
const visibility = typeof IntersectionObserver === "undefined" ? undefined : new IntersectionObserver(entries => {
|
|
96
|
+
inView = entries.at(-1)?.isIntersecting ?? inView;
|
|
97
|
+
position();
|
|
98
|
+
});
|
|
99
|
+
visibility?.observe(anchor);
|
|
100
|
+
surface.addEventListener("focusin", focusIn);
|
|
101
|
+
document.addEventListener("keydown", keydown);
|
|
102
|
+
window.addEventListener("resize", position);
|
|
103
|
+
window.addEventListener("scroll", position, {
|
|
104
|
+
capture: true,
|
|
105
|
+
passive: true
|
|
106
|
+
});
|
|
107
|
+
position();
|
|
108
|
+
onCleanup(() => {
|
|
109
|
+
observer?.disconnect();
|
|
110
|
+
visibility?.disconnect();
|
|
111
|
+
surface?.removeEventListener("focusin", focusIn);
|
|
112
|
+
document.removeEventListener("keydown", keydown);
|
|
113
|
+
window.removeEventListener("resize", position);
|
|
114
|
+
window.removeEventListener("scroll", position, true);
|
|
115
|
+
hide();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
return _$createComponent(Show, {
|
|
119
|
+
get when() {
|
|
120
|
+
return props.open;
|
|
121
|
+
},
|
|
122
|
+
get children() {
|
|
123
|
+
return _$ssr(_tmpl$, `k2b-moved-hint${props.class ? ` ${_$escape(props.class, true)}` : ""}`, _$ssrAttribute("aria-labelledby", _$escape(textId, true), false) + _$ssrAttribute("data-placement", _$escape(props.placement ?? "bottom", true), false), _$ssrAttribute("id", _$escape(textId, true), false), _$escape(props.children), _$escape(_$createComponent(Show, {
|
|
124
|
+
get when() {
|
|
125
|
+
return props.action.href !== undefined;
|
|
126
|
+
},
|
|
127
|
+
get fallback() {
|
|
128
|
+
return _$createComponent(Button, {
|
|
129
|
+
size: "sm",
|
|
130
|
+
variant: "secondary",
|
|
131
|
+
type: "button",
|
|
132
|
+
onClick: () => {
|
|
133
|
+
const action = props.action;
|
|
134
|
+
if (dismiss(false)) action.onClick?.();
|
|
135
|
+
},
|
|
136
|
+
get children() {
|
|
137
|
+
return props.action.label;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
get children() {
|
|
142
|
+
return _$createComponent(ButtonLink, {
|
|
143
|
+
size: "sm",
|
|
144
|
+
variant: "secondary",
|
|
145
|
+
get href() {
|
|
146
|
+
return props.action.href;
|
|
147
|
+
},
|
|
148
|
+
onClick: () => dismiss(false),
|
|
149
|
+
get children() {
|
|
150
|
+
return props.action.label;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
})), _$escape(_$createComponent(Button, {
|
|
155
|
+
size: "sm",
|
|
156
|
+
variant: "ghost",
|
|
157
|
+
type: "button",
|
|
158
|
+
onClick: () => dismiss(true),
|
|
159
|
+
get children() {
|
|
160
|
+
return messages().movedHintDismiss;
|
|
161
|
+
}
|
|
162
|
+
})));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=MovedHint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["feedback/MovedHint.tsx"],"sourcesContent":["import { createEffect, createSignal, createUniqueId, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { Button, ButtonLink } from \"../actions/Button\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { announce } from \"./announce\";\nimport { positionTooltipSurface, type TooltipPlacement } from \"./tooltip-position\";\n\nexport type MovedHintAction = { label: string; href: string; onClick?: never } | { label: string; onClick: () => void; href?: never };\n\nexport type MovedHintProps = {\n /** The owner decides this on the server, so a dismissed hint never renders again. */\n open: boolean;\n /** Called once per open period when the person closes the hint or uses its action. */\n onDismiss: () => void;\n /** Render the hint directly after this element to preserve keyboard order. */\n target: () => HTMLElement | undefined;\n /** Short text naming the new place. */\n children: JSX.Element;\n action: MovedHintAction;\n placement?: TooltipPlacement;\n class?: string;\n};\n\nexport function MovedHint(props: MovedHintProps): JSX.Element {\n const messages = useUiMessages();\n const textId = `k2b-moved-hint-${createUniqueId()}`;\n const [mounted, setMounted] = createSignal(false);\n const [dismissed, setDismissed] = createSignal(false);\n let surface: HTMLDivElement | undefined;\n let text: HTMLParagraphElement | undefined;\n let previousFocus: HTMLElement | undefined;\n let shown = false;\n let announced = false;\n\n const hide = () => {\n if (!shown) return;\n shown = false;\n try {\n surface?.hidePopover?.();\n } catch {\n // Cleanup can run after the surface disconnects.\n }\n };\n\n const dismiss = (restoreFocus: boolean): boolean => {\n if (!props.open || dismissed()) return false;\n const focusInside = surface?.contains(document.activeElement);\n const target = props.target();\n const returnTo =\n previousFocus?.isConnected && !previousFocus.matches(\":disabled\")\n ? previousFocus\n : target && (target.tabIndex >= 0 || target.hasAttribute(\"tabindex\")) && !target.matches(\":disabled\")\n ? target\n : undefined;\n hide();\n if (restoreFocus && focusInside && returnTo) {\n returnTo.focus({ preventScroll: true });\n }\n setDismissed(true);\n props.onDismiss();\n return true;\n };\n\n createEffect(() => {\n if (!props.open) {\n setDismissed(false);\n previousFocus = undefined;\n }\n });\n onMount(() => setMounted(true));\n\n createEffect(() => {\n if (!mounted() || !props.open || dismissed()) return;\n const anchor = props.target();\n const placement = props.placement ?? \"bottom\";\n if (!surface || !anchor) return;\n\n // A target scrolled out of view, or clipped by its scroller, takes the hint along instead of leaving it\n // pinned to the viewport edge over unrelated content.\n let inView = typeof IntersectionObserver === \"undefined\";\n const position = () => {\n if (!props.open || dismissed() || !inView || !anchor.isConnected || anchor.getClientRects().length === 0) {\n hide();\n return;\n }\n if (!surface || typeof surface.showPopover !== \"function\") return;\n if (!shown) {\n try {\n surface.showPopover();\n shown = true;\n } catch {\n return;\n }\n }\n positionTooltipSurface(surface, anchor, placement);\n if (!announced) {\n announced = true;\n announce(text?.textContent ?? \"\");\n }\n };\n const focusIn = (event: FocusEvent) => {\n if (!(event.relatedTarget instanceof Node) || !surface?.contains(event.relatedTarget)) {\n previousFocus = event.relatedTarget instanceof HTMLElement ? event.relatedTarget : undefined;\n }\n };\n const keydown = (event: KeyboardEvent) => {\n if (event.defaultPrevented || event.isComposing) return;\n if (event.key !== \"Escape\" || !(event.target instanceof Node)) return;\n if (!surface?.contains(event.target) && !anchor.contains(event.target)) return;\n event.preventDefault();\n dismiss(true);\n };\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(position);\n observer?.observe(anchor);\n const visibility =\n typeof IntersectionObserver === \"undefined\"\n ? undefined\n : new IntersectionObserver((entries) => {\n inView = entries.at(-1)?.isIntersecting ?? inView;\n position();\n });\n visibility?.observe(anchor);\n surface.addEventListener(\"focusin\", focusIn);\n document.addEventListener(\"keydown\", keydown);\n window.addEventListener(\"resize\", position);\n window.addEventListener(\"scroll\", position, { capture: true, passive: true });\n position();\n onCleanup(() => {\n observer?.disconnect();\n visibility?.disconnect();\n surface?.removeEventListener(\"focusin\", focusIn);\n document.removeEventListener(\"keydown\", keydown);\n window.removeEventListener(\"resize\", position);\n window.removeEventListener(\"scroll\", position, true);\n hide();\n });\n });\n\n return (\n <Show when={props.open}>\n <div\n ref={surface}\n class={`k2b-moved-hint${props.class ? ` ${props.class}` : \"\"}`}\n popover=\"manual\"\n role=\"group\"\n aria-labelledby={textId}\n data-placement={props.placement ?? \"bottom\"}\n >\n <p ref={text} class=\"k2b-moved-hint__text\" id={textId}>\n {props.children}\n </p>\n <div class=\"k2b-moved-hint__actions\">\n <Show\n when={props.action.href !== undefined}\n fallback={\n <Button\n size=\"sm\"\n variant=\"secondary\"\n type=\"button\"\n onClick={() => {\n const action = props.action;\n if (dismiss(false)) action.onClick?.();\n }}\n >\n {props.action.label}\n </Button>\n }\n >\n <ButtonLink size=\"sm\" variant=\"secondary\" href={props.action.href} onClick={() => dismiss(false)}>\n {props.action.label}\n </ButtonLink>\n </Show>\n <Button size=\"sm\" variant=\"ghost\" type=\"button\" onClick={() => dismiss(true)}>\n {messages().movedHintDismiss}\n </Button>\n </div>\n </div>\n </Show>\n );\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -58,6 +58,9 @@ const focusUnclaimed = dialog => {
|
|
|
58
58
|
const active = document.activeElement;
|
|
59
59
|
return !active || active === document.body || active === dialog;
|
|
60
60
|
};
|
|
61
|
+
|
|
62
|
+
/** Checked by shape: a test harness or an iframe may create the event in another realm. */
|
|
63
|
+
const isPopState = value => typeof value === "object" && value !== null && "type" in value && value.type === "popstate" && "state" in value;
|
|
61
64
|
export const createDialogCore = () => {
|
|
62
65
|
const state = {
|
|
63
66
|
stack: []
|
|
@@ -65,17 +68,26 @@ export const createDialogCore = () => {
|
|
|
65
68
|
/** Markers of this core's entries in the browser history, oldest first. */
|
|
66
69
|
let historyIds = [];
|
|
67
70
|
const issued = new Set();
|
|
71
|
+
/**
|
|
72
|
+
* Markers forgotten during island disposal. Their entries stay in `historyIds` as barriers: a synthetic Back never
|
|
73
|
+
* crosses one, because the router may have replaced that entry with another page.
|
|
74
|
+
*/
|
|
75
|
+
const forgotten = new Set();
|
|
76
|
+
const deadMarker = marker => typeof marker === "number" && forgotten.has(marker);
|
|
77
|
+
/** How often islands were disposed, so a dialog that waits to open notices that a content swap happened meanwhile. */
|
|
78
|
+
let disposals = 0;
|
|
68
79
|
const ownMarker = marker => typeof marker === "number" && issued.has(marker);
|
|
69
80
|
let seen;
|
|
70
81
|
let traversalFrom;
|
|
71
82
|
let syntheticFrom;
|
|
72
83
|
const historyHolders = new Set();
|
|
73
84
|
let historySyncScheduled = false;
|
|
85
|
+
let disposingIslands = false;
|
|
74
86
|
/** Settles when the synthetic Back that removes closed dialogs' entries has arrived. */
|
|
75
87
|
let returning;
|
|
76
88
|
let resolveReturning;
|
|
77
89
|
|
|
78
|
-
/**
|
|
90
|
+
/** Outside island disposal, closed entries leave with one Back to the newest live dialog or holder, or to the page. */
|
|
79
91
|
const syncHistory = () => {
|
|
80
92
|
if (returning) {
|
|
81
93
|
void returning.then(() => {
|
|
@@ -87,7 +99,7 @@ export const createDialogCore = () => {
|
|
|
87
99
|
const openIds = new Set([...state.stack, ...historyHolders].map(entry => entry.historyId));
|
|
88
100
|
const current = historyIds.at(-1);
|
|
89
101
|
let steps = 0;
|
|
90
|
-
while (historyIds.length > 0 && !openIds.has(historyIds.at(-1))) {
|
|
102
|
+
while (historyIds.length > 0 && !openIds.has(historyIds.at(-1)) && !deadMarker(historyIds.at(-1))) {
|
|
91
103
|
historyIds.pop();
|
|
92
104
|
steps++;
|
|
93
105
|
}
|
|
@@ -107,13 +119,26 @@ export const createDialogCore = () => {
|
|
|
107
119
|
history.go(-steps);
|
|
108
120
|
};
|
|
109
121
|
const releaseHistory = entry => {
|
|
110
|
-
if (entry.historyId === undefined
|
|
122
|
+
if (entry.historyId === undefined) return;
|
|
123
|
+
// During a content swap the router owns history. Forget the marker without moving history; its entry stays a
|
|
124
|
+
// barrier for later synthetic Backs and is left for the router to replace.
|
|
125
|
+
if (disposingIslands) {
|
|
126
|
+
issued.delete(entry.historyId);
|
|
127
|
+
forgotten.add(entry.historyId);
|
|
128
|
+
entry.historyId = undefined;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (historySyncScheduled) return;
|
|
111
132
|
historySyncScheduled = true;
|
|
112
133
|
queueMicrotask(syncHistory);
|
|
113
134
|
};
|
|
114
135
|
|
|
136
|
+
/** Traversals this core already handled: a page swap router also reports the ones it hides from listeners. */
|
|
137
|
+
const handled = new WeakSet();
|
|
115
138
|
/** Back or Forward moved to another entry: close dialogs and holders opened after the one it shows. */
|
|
116
139
|
const onPopState = event => {
|
|
140
|
+
if (handled.has(event)) return;
|
|
141
|
+
handled.add(event);
|
|
117
142
|
const resolveBack = resolveReturning;
|
|
118
143
|
// Without the Navigation API, only our synthetic Back has a known departure.
|
|
119
144
|
const from = typeof navigation !== "undefined" ? traversalFrom : resolveBack ? syntheticFrom : undefined;
|
|
@@ -126,8 +151,8 @@ export const createDialogCore = () => {
|
|
|
126
151
|
event.stopImmediatePropagation();
|
|
127
152
|
}
|
|
128
153
|
// Own entries are ordered by id. A retired entry reached by Forward stays tracked, so closing the dialogs
|
|
129
|
-
// beneath it also removes it.
|
|
130
|
-
historyIds = ownMarker(marker) ? [...historyIds.filter(id => id < marker), marker] : [];
|
|
154
|
+
// beneath it also removes it. A forgotten entry keeps the holders opened before it and stays a barrier.
|
|
155
|
+
historyIds = ownMarker(marker) || deadMarker(marker) ? [...historyIds.filter(id => id < marker), marker] : [];
|
|
131
156
|
const gone = entry => entry.historyId !== undefined && !historyIds.includes(entry.historyId);
|
|
132
157
|
while (state.stack.some(gone)) popTop(undefined);
|
|
133
158
|
for (const holder of [...historyHolders].reverse()) {
|
|
@@ -141,6 +166,36 @@ export const createDialogCore = () => {
|
|
|
141
166
|
resolveBack?.();
|
|
142
167
|
}
|
|
143
168
|
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Island disposal means the page content changes, as a document load would. Every dialog closes, top first, whichever
|
|
172
|
+
* code opened it: which island calls open() is not knowable after an await, in onMount or from a timer, and a modal
|
|
173
|
+
* left over the new content would block it.
|
|
174
|
+
*/
|
|
175
|
+
const onIslandDispose = () => {
|
|
176
|
+
if (!disposingIslands) {
|
|
177
|
+
disposingIslands = true;
|
|
178
|
+
queueMicrotask(() => {
|
|
179
|
+
disposingIslands = false;
|
|
180
|
+
});
|
|
181
|
+
// Entries released just before, whose Back has not started yet, stay as well: the router owns history now.
|
|
182
|
+
// A Back already on its way still settles through `returning`.
|
|
183
|
+
historySyncScheduled = false;
|
|
184
|
+
const live = new Set([...state.stack, ...historyHolders].map(entry => entry.historyId));
|
|
185
|
+
for (const id of historyIds) {
|
|
186
|
+
if (live.has(id) || !issued.delete(id)) continue;
|
|
187
|
+
forgotten.add(id);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
disposals++;
|
|
191
|
+
while (state.stack.length > 0) popTop(undefined);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/** `@k2b/ssr/nav` stops a traversal to another server page before later listeners and reports it here. */
|
|
195
|
+
const onHiddenPopState = event => {
|
|
196
|
+
const detail = "detail" in event ? event.detail : undefined;
|
|
197
|
+
if (isPopState(detail)) onPopState(detail);
|
|
198
|
+
};
|
|
144
199
|
const onNavigate = event => {
|
|
145
200
|
if (event.navigationType === "traverse") traversalFrom = {
|
|
146
201
|
href: location.href,
|
|
@@ -154,10 +209,23 @@ export const createDialogCore = () => {
|
|
|
154
209
|
listening?.window.removeEventListener("popstate", onPopState, {
|
|
155
210
|
capture: true
|
|
156
211
|
});
|
|
212
|
+
listening?.window.removeEventListener("ssr:nav-popstate", onHiddenPopState, {
|
|
213
|
+
capture: true
|
|
214
|
+
});
|
|
215
|
+
listening?.window.removeEventListener("ssr:island-dispose", onIslandDispose, {
|
|
216
|
+
capture: true
|
|
217
|
+
});
|
|
157
218
|
listening?.navigation?.removeEventListener("navigate", onNavigate);
|
|
158
219
|
window.addEventListener("popstate", onPopState, {
|
|
159
220
|
capture: true
|
|
160
221
|
});
|
|
222
|
+
window.addEventListener("ssr:nav-popstate", onHiddenPopState, {
|
|
223
|
+
capture: true
|
|
224
|
+
});
|
|
225
|
+
// Capture opens the disposal window before target listeners can release history holders.
|
|
226
|
+
window.addEventListener("ssr:island-dispose", onIslandDispose, {
|
|
227
|
+
capture: true
|
|
228
|
+
});
|
|
161
229
|
const current = typeof navigation !== "undefined" ? navigation : undefined;
|
|
162
230
|
current?.addEventListener("navigate", onNavigate);
|
|
163
231
|
listening = {
|
|
@@ -206,7 +274,7 @@ export const createDialogCore = () => {
|
|
|
206
274
|
};
|
|
207
275
|
|
|
208
276
|
/**
|
|
209
|
-
* Settles once released entries have left
|
|
277
|
+
* Settles once released entries have left history or were forgotten during island disposal. A caller that navigates waits
|
|
210
278
|
* for it, as a dialog's result waits: a Back still on its way would otherwise land behind the new entry.
|
|
211
279
|
*/
|
|
212
280
|
const historySettled = async () => {
|
|
@@ -386,13 +454,19 @@ export const createDialogCore = () => {
|
|
|
386
454
|
});
|
|
387
455
|
};
|
|
388
456
|
const open = (view, options = {}) => {
|
|
457
|
+
return openAfter(view, options, disposals);
|
|
458
|
+
};
|
|
459
|
+
const openAfter = (view, options, requested) => {
|
|
389
460
|
if (options.signal?.aborted) return Promise.resolve(undefined);
|
|
390
461
|
if (options.history) {
|
|
391
462
|
// Dialogs closed just now leave the history first, or this dialog's entry would be the one removed.
|
|
392
463
|
if (historySyncScheduled) syncHistory();
|
|
393
|
-
if (returning) return returning.then(() =>
|
|
464
|
+
if (returning) return returning.then(() => openAfter(view, options, requested));
|
|
394
465
|
}
|
|
466
|
+
// A dialog that waited for history while islands were disposed would open over content it was never meant for.
|
|
467
|
+
if (disposals !== requested) return Promise.resolve(undefined);
|
|
395
468
|
const dialog = ensureDialogElement();
|
|
469
|
+
listen();
|
|
396
470
|
const previousTop = state.stack[state.stack.length - 1];
|
|
397
471
|
if (previousTop) previousTop.container.style.display = "none";
|
|
398
472
|
const activeElement = document.activeElement;
|
|
@@ -427,11 +501,12 @@ export const createDialogCore = () => {
|
|
|
427
501
|
};
|
|
428
502
|
entry.resolve = value => {
|
|
429
503
|
options.signal?.removeEventListener("abort", abort);
|
|
430
|
-
if (
|
|
504
|
+
if (!options.history) {
|
|
431
505
|
resolve(value);
|
|
432
506
|
return;
|
|
433
507
|
}
|
|
434
508
|
// After every pending history sync, so a caller that navigates or opens the next dialog finds the history settled.
|
|
509
|
+
// This holds for a marker forgotten during island disposal too, while an earlier synthetic Back is on its way.
|
|
435
510
|
queueMicrotask(() => void historySettled().then(() => resolve(value)));
|
|
436
511
|
};
|
|
437
512
|
const closeTyped = result => {
|
|
@@ -522,8 +597,9 @@ export const dialogCore = publicDialogCore;
|
|
|
522
597
|
/**
|
|
523
598
|
* Internal: transient UI shares dialogs' Back handling without exposing another public dialog capability.
|
|
524
599
|
* Same-URL traversals are suppressed with the Navigation API; without it, only the synthetic Back on release is suppressed.
|
|
600
|
+
* Release during island disposal forgets the marker without traversing; the router may replace the remaining entry.
|
|
525
601
|
*/
|
|
526
602
|
export const holdDialogHistory = holdHistory;
|
|
527
|
-
/** Internal: waits until
|
|
603
|
+
/** Internal: waits until released holders have left history or were forgotten during island disposal. */
|
|
528
604
|
export const dialogHistorySettled = historySettled;
|
|
529
605
|
//# sourceMappingURL=dialog-core.js.map
|