@humanforest/nuxt-layer 0.1.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/LICENSE +67 -0
- package/README.md +208 -0
- package/app.config.ts +10 -0
- package/assets/icons/forest/bike-asterisk.svg +21 -0
- package/assets/icons/forest/bike-down.svg +19 -0
- package/assets/icons/forest/bike-off.svg +20 -0
- package/assets/icons/forest/bike-up.svg +19 -0
- package/assets/icons/forest/bike-x.svg +19 -0
- package/assets/icons/forest/bike.svg +18 -0
- package/assets/icons/forest/mark-fill.svg +6 -0
- package/assets/icons/forest/mark.svg +6 -0
- package/assets/icons/forest/parking.svg +3 -0
- package/assets/icons/forest/star-fill.svg +4 -0
- package/assets/icons/forest/star.svg +4 -0
- package/assets/icons/forest/traffic-light-caution.svg +8 -0
- package/assets/icons/forest/traffic-light-go.svg +8 -0
- package/assets/icons/forest/traffic-light-lit.svg +8 -0
- package/assets/icons/forest/traffic-light-stop.svg +8 -0
- package/assets/icons/forest/traffic-light.svg +8 -0
- package/nuxt.config.ts +81 -0
- package/package.json +35 -0
- package/packages/charts/src/FBoroughShape.vue +52 -0
- package/packages/charts/src/FCalendarGrid.vue +332 -0
- package/packages/charts/src/FCellLegend.vue +54 -0
- package/packages/charts/src/FChartFrame.vue +201 -0
- package/packages/charts/src/FChartFrameSingle.vue +91 -0
- package/packages/charts/src/FChartLegend.vue +26 -0
- package/packages/charts/src/FDistributionBar.vue +224 -0
- package/packages/charts/src/FDottedMap.vue +335 -0
- package/packages/charts/src/FLondonMap.vue +399 -0
- package/packages/charts/src/FSparkline.vue +198 -0
- package/packages/charts/src/FStatusTrack.vue +151 -0
- package/packages/charts/src/cellSize.ts +46 -0
- package/packages/charts/src/distribution.ts +99 -0
- package/packages/charts/src/engine.ts +15 -0
- package/packages/charts/src/forestTooltip.ts +110 -0
- package/packages/charts/src/fromCategories.ts +43 -0
- package/packages/charts/src/index.ts +87 -0
- package/packages/charts/src/londonAreas.ts +75 -0
- package/packages/charts/src/londonGrid.ts +67 -0
- package/packages/charts/src/motionDuration.ts +49 -0
- package/packages/charts/src/presets.ts +143 -0
- package/packages/charts/src/resolveVar.ts +21 -0
- package/packages/charts/src/snapTooltip.ts +154 -0
- package/packages/charts/src/tooltipStandIn.ts +58 -0
- package/packages/charts/src/useCellScale.ts +155 -0
- package/packages/charts/src/useChartPalette.ts +61 -0
- package/packages/charts/src/useChartRepaintKey.ts +22 -0
- package/packages/charts/src/useThemeVersion.ts +17 -0
- package/packages/frames/src/FBrowserFrame.vue +53 -0
- package/packages/frames/src/FDeviceFrame.vue +242 -0
- package/packages/frames/src/FFitFrame.vue +83 -0
- package/packages/frames/src/art/iphone-11.svg +124 -0
- package/packages/frames/src/art/iphone-12.svg +601 -0
- package/packages/frames/src/art/iphone-13-pro.svg +820 -0
- package/packages/frames/src/art/iphone-13.svg +849 -0
- package/packages/frames/src/art/iphone-14.svg +681 -0
- package/packages/frames/src/art/iphone-15-pro.svg +633 -0
- package/packages/frames/src/art/iphone-15.svg +713 -0
- package/packages/frames/src/art/iphone-16-plus.svg +685 -0
- package/packages/frames/src/art/iphone-16-pro-max.svg +611 -0
- package/packages/frames/src/art/iphone-16-pro.svg +631 -0
- package/packages/frames/src/art/iphone-16.svg +713 -0
- package/packages/frames/src/art/iphone-17-pro-max.svg +355 -0
- package/packages/frames/src/art/iphone-17.svg +305 -0
- package/packages/frames/src/art/iphone-air.svg +389 -0
- package/packages/frames/src/art/iphone-se-3.svg +473 -0
- package/packages/frames/src/art/iphone-x.svg +652 -0
- package/packages/frames/src/art/pixel-10-pro-xl.svg +32 -0
- package/packages/frames/src/art/pixel-10-pro.svg +32 -0
- package/packages/frames/src/art/pixel-10.svg +32 -0
- package/packages/frames/src/art/pixel-8-pro.svg +208 -0
- package/packages/frames/src/art/pixel-8.svg +208 -0
- package/packages/frames/src/art/pixel-8a.svg +8 -0
- package/packages/frames/src/art/pixel-9-pro-xl.svg +32 -0
- package/packages/frames/src/art/pixel-9-pro.svg +34 -0
- package/packages/frames/src/art/pixel-9a.svg +25 -0
- package/packages/frames/src/art.ts +29 -0
- package/packages/frames/src/devices.ts +337 -0
- package/packages/frames/src/index.ts +15 -0
- package/packages/tokens/geo/london-boroughs.json +1 -0
- package/packages/tokens/geo/london-dots-coarse.json +1 -0
- package/packages/tokens/geo/london-dots-fine.json +1 -0
- package/packages/tokens/geo/london-dots-medium.json +1 -0
- package/packages/tokens/geo/london-dots-ultra.json +1 -0
- package/packages/tokens/geo/london-wards.json +1 -0
- package/packages/tokens/glyphs/glyphs.json +1 -0
- package/packages/tokens/logos/f-rot.svg +4 -0
- package/packages/tokens/logos/f.svg +4 -0
- package/packages/tokens/logos/favicon-c2w.svg +9 -0
- package/packages/tokens/logos/favicon-console.svg +9 -0
- package/packages/tokens/logos/favicon-ds.svg +9 -0
- package/packages/tokens/logos/favicon-fleet.svg +9 -0
- package/packages/tokens/logos/favicon-flex.svg +9 -0
- package/packages/tokens/logos/favicon-plus.svg +9 -0
- package/packages/tokens/logos/favicon-radar.svg +9 -0
- package/packages/tokens/logos/favicon-tower.svg +9 -0
- package/packages/tokens/logos/lockup-horizontal.svg +17 -0
- package/packages/tokens/logos/lockup-vertical.svg +17 -0
- package/packages/tokens/logos/mark-mono.svg +4 -0
- package/packages/tokens/logos/mark.svg +7 -0
- package/packages/tokens/logos/wordmark.svg +9 -0
- package/packages/tokens/src/colourEngine.ts +604 -0
- package/packages/tokens/src/glyphs.ts +127 -0
- package/packages/tokens/src/logo.ts +50 -0
- package/packages/tokens/src/subbrands.ts +44 -0
- package/packages/tokens/src/sublogo.ts +80 -0
- package/packages/ui/icons/forest/map/forest-place-count.svg +21 -0
- package/packages/ui/icons/forest/map/forest-place-marker.svg +17 -0
- package/packages/ui/icons/forest/map/forest-vehicle-marker.svg +73 -0
- package/packages/ui/icons/forest/map/manifest.json +373 -0
- package/packages/ui/icons/forest/map/place-count-manifest.json +83 -0
- package/packages/ui/icons/forest/map/place-manifest.json +74 -0
- package/packages/ui/src/address/address.ts +125 -0
- package/packages/ui/src/address/autofill.ts +78 -0
- package/packages/ui/src/address/geocode.ts +55 -0
- package/packages/ui/src/address/index.ts +31 -0
- package/packages/ui/src/address/validate.ts +91 -0
- package/packages/ui/src/canvas/FCanvasCard.vue +97 -0
- package/packages/ui/src/canvas/FCanvasControl.vue +121 -0
- package/packages/ui/src/canvas/FCanvasControlGroup.vue +100 -0
- package/packages/ui/src/canvas/FCanvasFlyout.vue +216 -0
- package/packages/ui/src/canvas/FCanvasHud.vue +402 -0
- package/packages/ui/src/canvas/canvasSurface.ts +39 -0
- package/packages/ui/src/canvas/index.ts +17 -0
- package/packages/ui/src/contexts/ForestContext.vue +53 -0
- package/packages/ui/src/contexts/contexts.ts +153 -0
- package/packages/ui/src/contexts/index.ts +23 -0
- package/packages/ui/src/contexts/marketing-theme.ts +214 -0
- package/packages/ui/src/contexts/mobile-theme.ts +402 -0
- package/packages/ui/src/filter/FFilterBar.vue +72 -0
- package/packages/ui/src/filter/FFilterChip.vue +362 -0
- package/packages/ui/src/filter/FFilterClauses.vue +104 -0
- package/packages/ui/src/filter/FFilterTrigger.vue +183 -0
- package/packages/ui/src/filter/filter.theme.ts +27 -0
- package/packages/ui/src/filter/filterClause.ts +265 -0
- package/packages/ui/src/filter/filterPredicate.ts +134 -0
- package/packages/ui/src/filter/filterSchema.ts +82 -0
- package/packages/ui/src/filter/index.ts +36 -0
- package/packages/ui/src/forest-preset.ts +68 -0
- package/packages/ui/src/icons.ts +79 -0
- package/packages/ui/src/index.ts +17 -0
- package/packages/ui/src/kpi/FKpi.vue +253 -0
- package/packages/ui/src/kpi/FKpiGroup.vue +161 -0
- package/packages/ui/src/kpi/index.ts +20 -0
- package/packages/ui/src/kpi/kpi.theme.ts +168 -0
- package/packages/ui/src/kpi/kpiDelta.ts +82 -0
- package/packages/ui/src/kpi/kpiFormat.ts +72 -0
- package/packages/ui/src/kpi/kpiGroupState.ts +63 -0
- package/packages/ui/src/logo/FLogo.vue +52 -0
- package/packages/ui/src/logo/FSubLogo.vue +214 -0
- package/packages/ui/src/logo/index.ts +8 -0
- package/packages/ui/src/map/FMap.vue +355 -0
- package/packages/ui/src/map/FMapCompass.vue +154 -0
- package/packages/ui/src/map/FMapCoordinates.vue +134 -0
- package/packages/ui/src/map/FMapDrawTools.vue +116 -0
- package/packages/ui/src/map/FMapFullscreen.vue +92 -0
- package/packages/ui/src/map/FMapGeolocate.vue +208 -0
- package/packages/ui/src/map/FMapLayers.vue +75 -0
- package/packages/ui/src/map/FMapLegend.vue +45 -0
- package/packages/ui/src/map/FMapPopup.vue +129 -0
- package/packages/ui/src/map/FMapRamp.vue +69 -0
- package/packages/ui/src/map/FMapRegion.vue +77 -0
- package/packages/ui/src/map/FMapScale.vue +107 -0
- package/packages/ui/src/map/FMapSearch.vue +362 -0
- package/packages/ui/src/map/FMapTilt.vue +87 -0
- package/packages/ui/src/map/FMapTools.vue +61 -0
- package/packages/ui/src/map/FMapZoom.vue +81 -0
- package/packages/ui/src/map/FMarker.vue +218 -0
- package/packages/ui/src/map/FTrackScrubber.vue +94 -0
- package/packages/ui/src/map/FVehicleLegend.vue +72 -0
- package/packages/ui/src/map/FVehicleMark.vue +166 -0
- package/packages/ui/src/map/bin.ts +196 -0
- package/packages/ui/src/map/coordinates.ts +83 -0
- package/packages/ui/src/map/density.ts +401 -0
- package/packages/ui/src/map/draw.ts +356 -0
- package/packages/ui/src/map/ease.ts +67 -0
- package/packages/ui/src/map/fullscreen.ts +59 -0
- package/packages/ui/src/map/heat.ts +160 -0
- package/packages/ui/src/map/index.ts +351 -0
- package/packages/ui/src/map/liveMarkers.ts +60 -0
- package/packages/ui/src/map/map.css +116 -0
- package/packages/ui/src/map/mapColor.ts +31 -0
- package/packages/ui/src/map/mapContext.ts +12 -0
- package/packages/ui/src/map/mapLayers.ts +55 -0
- package/packages/ui/src/map/mapLegend.ts +70 -0
- package/packages/ui/src/map/mapSearch.theme.ts +79 -0
- package/packages/ui/src/map/mapSearch.ts +279 -0
- package/packages/ui/src/map/mapUrl.ts +117 -0
- package/packages/ui/src/map/markerAsset.ts +3 -0
- package/packages/ui/src/map/padding.ts +39 -0
- package/packages/ui/src/map/place.ts +192 -0
- package/packages/ui/src/map/ramp.ts +95 -0
- package/packages/ui/src/map/regionSelect.ts +163 -0
- package/packages/ui/src/map/route.ts +167 -0
- package/packages/ui/src/map/scale.ts +329 -0
- package/packages/ui/src/map/scaleBar.ts +67 -0
- package/packages/ui/src/map/selectionModel.ts +50 -0
- package/packages/ui/src/map/sources/coordinates.ts +58 -0
- package/packages/ui/src/map/sources/mapboxPlaces.ts +71 -0
- package/packages/ui/src/map/style.ts +14 -0
- package/packages/ui/src/map/track.ts +279 -0
- package/packages/ui/src/map/useFeatureSelect.ts +230 -0
- package/packages/ui/src/map/useLiveMarkers.ts +84 -0
- package/packages/ui/src/map/useMap.ts +150 -0
- package/packages/ui/src/map/useMapDraw.ts +776 -0
- package/packages/ui/src/map/useMapOverlay.ts +421 -0
- package/packages/ui/src/map/useMapTableSelection.ts +101 -0
- package/packages/ui/src/map/useMapUrlState.ts +155 -0
- package/packages/ui/src/map/useMarkerSource.ts +163 -0
- package/packages/ui/src/map/useRegionSelect.ts +309 -0
- package/packages/ui/src/map/useTokenMode.ts +61 -0
- package/packages/ui/src/map/useTrackPlayback.ts +131 -0
- package/packages/ui/src/map/useVehicleFilter.ts +51 -0
- package/packages/ui/src/map/useVisibleFeatures.ts +124 -0
- package/packages/ui/src/map/vehicle.ts +807 -0
- package/packages/ui/src/map/vehicleFilter.ts +284 -0
- package/packages/ui/src/map/vehicleLegend.ts +155 -0
- package/packages/ui/src/map/visibleFeatures.ts +192 -0
- package/packages/ui/src/map/zone.ts +291 -0
- package/packages/ui/src/shell/FAppNavbar.vue +68 -0
- package/packages/ui/src/shell/FAppShell.vue +163 -0
- package/packages/ui/src/shell/FAppSidebar.vue +215 -0
- package/packages/ui/src/shell/FPageToolbar.vue +126 -0
- package/packages/ui/src/shell/FScrollShadow.vue +43 -0
- package/packages/ui/src/shell/FSidePanel.vue +258 -0
- package/packages/ui/src/shell/FToolbarActions.vue +32 -0
- package/packages/ui/src/shell/index.ts +33 -0
- package/packages/ui/src/shell/nav.ts +37 -0
- package/packages/ui/src/shell/pageToolbar.ts +69 -0
- package/packages/ui/src/shell/shell.ts +74 -0
- package/packages/ui/src/shell/sidePanel.ts +115 -0
- package/packages/ui/src/themes/accordion.ts +36 -0
- package/packages/ui/src/themes/alert.ts +16 -0
- package/packages/ui/src/themes/auth-form.ts +19 -0
- package/packages/ui/src/themes/avatar-group.ts +9 -0
- package/packages/ui/src/themes/avatar.ts +18 -0
- package/packages/ui/src/themes/badge.ts +51 -0
- package/packages/ui/src/themes/banner.ts +21 -0
- package/packages/ui/src/themes/breadcrumb.ts +10 -0
- package/packages/ui/src/themes/button.mechanics.ts +191 -0
- package/packages/ui/src/themes/button.ts +136 -0
- package/packages/ui/src/themes/calendar.ts +29 -0
- package/packages/ui/src/themes/card.ts +67 -0
- package/packages/ui/src/themes/carousel.ts +41 -0
- package/packages/ui/src/themes/checkbox-group.ts +20 -0
- package/packages/ui/src/themes/checkbox.ts +47 -0
- package/packages/ui/src/themes/chip.ts +50 -0
- package/packages/ui/src/themes/collapsible.ts +26 -0
- package/packages/ui/src/themes/color-picker.ts +13 -0
- package/packages/ui/src/themes/command-palette.ts +37 -0
- package/packages/ui/src/themes/container.ts +14 -0
- package/packages/ui/src/themes/context-menu.ts +38 -0
- package/packages/ui/src/themes/dashboard-group.ts +11 -0
- package/packages/ui/src/themes/dashboard-navbar.ts +10 -0
- package/packages/ui/src/themes/dashboard-panel.ts +18 -0
- package/packages/ui/src/themes/dashboard-resize-handle.ts +35 -0
- package/packages/ui/src/themes/dashboard-sidebar-collapse.ts +7 -0
- package/packages/ui/src/themes/dashboard-sidebar-toggle.ts +7 -0
- package/packages/ui/src/themes/dashboard-sidebar.ts +58 -0
- package/packages/ui/src/themes/dashboard-toolbar.ts +15 -0
- package/packages/ui/src/themes/drawer.ts +45 -0
- package/packages/ui/src/themes/dropdown-menu.ts +38 -0
- package/packages/ui/src/themes/empty.ts +43 -0
- package/packages/ui/src/themes/field-group.ts +52 -0
- package/packages/ui/src/themes/file-upload.ts +38 -0
- package/packages/ui/src/themes/footer-columns.ts +13 -0
- package/packages/ui/src/themes/footer.ts +9 -0
- package/packages/ui/src/themes/form-field.ts +25 -0
- package/packages/ui/src/themes/form.ts +22 -0
- package/packages/ui/src/themes/header.ts +12 -0
- package/packages/ui/src/themes/icon.ts +61 -0
- package/packages/ui/src/themes/index.ts +99 -0
- package/packages/ui/src/themes/input-date.ts +73 -0
- package/packages/ui/src/themes/input-menu.ts +66 -0
- package/packages/ui/src/themes/input-number.ts +58 -0
- package/packages/ui/src/themes/input-rating.ts +25 -0
- package/packages/ui/src/themes/input-tags.ts +63 -0
- package/packages/ui/src/themes/input-time.ts +70 -0
- package/packages/ui/src/themes/input.ts +148 -0
- package/packages/ui/src/themes/kbd.ts +27 -0
- package/packages/ui/src/themes/link.ts +27 -0
- package/packages/ui/src/themes/listbox.ts +48 -0
- package/packages/ui/src/themes/main.ts +6 -0
- package/packages/ui/src/themes/marquee.ts +45 -0
- package/packages/ui/src/themes/modal.ts +46 -0
- package/packages/ui/src/themes/navigation-menu.ts +80 -0
- package/packages/ui/src/themes/page-anchors.ts +7 -0
- package/packages/ui/src/themes/page-aside.ts +7 -0
- package/packages/ui/src/themes/page-card.ts +22 -0
- package/packages/ui/src/themes/page-cta.ts +18 -0
- package/packages/ui/src/themes/page-feature.ts +13 -0
- package/packages/ui/src/themes/page-header.ts +16 -0
- package/packages/ui/src/themes/page-hero.ts +15 -0
- package/packages/ui/src/themes/page-links.ts +12 -0
- package/packages/ui/src/themes/page-section.ts +14 -0
- package/packages/ui/src/themes/page.ts +7 -0
- package/packages/ui/src/themes/pagination.ts +17 -0
- package/packages/ui/src/themes/pin-input.ts +73 -0
- package/packages/ui/src/themes/popover.ts +30 -0
- package/packages/ui/src/themes/progress.ts +29 -0
- package/packages/ui/src/themes/prose.ts +45 -0
- package/packages/ui/src/themes/radio-group.ts +39 -0
- package/packages/ui/src/themes/scroll-area.ts +23 -0
- package/packages/ui/src/themes/select-menu.ts +71 -0
- package/packages/ui/src/themes/select.ts +72 -0
- package/packages/ui/src/themes/separator.ts +28 -0
- package/packages/ui/src/themes/sidebar.ts +127 -0
- package/packages/ui/src/themes/skeleton.ts +15 -0
- package/packages/ui/src/themes/slideover.ts +87 -0
- package/packages/ui/src/themes/slider.ts +48 -0
- package/packages/ui/src/themes/stepper.ts +41 -0
- package/packages/ui/src/themes/switch.ts +63 -0
- package/packages/ui/src/themes/table.ts +34 -0
- package/packages/ui/src/themes/tabs.ts +74 -0
- package/packages/ui/src/themes/textarea.ts +77 -0
- package/packages/ui/src/themes/timeline.ts +25 -0
- package/packages/ui/src/themes/toast.ts +29 -0
- package/packages/ui/src/themes/toaster.ts +66 -0
- package/packages/ui/src/themes/tooltip.ts +31 -0
- package/packages/ui/src/themes/tree.ts +41 -0
- package/packages/ui/src/themes/user.ts +28 -0
- package/styles.cdn.gen.css +2047 -0
- package/theme.gen.json +1 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// Forest's map subsystem — one entry, like @humanforest/charts.
|
|
2
|
+
//
|
|
3
|
+
// The split it commits to: DOM-world things are COMPONENTS (the artefact is DOM, which is what Vue is
|
|
4
|
+
// for — tokens, focus, aria, slots, keyed reconciliation), and GL-world things are COMPOSABLES over
|
|
5
|
+
// style-spec objects (the artefact is GPU state, and mapbox's spec is already declarative).
|
|
6
|
+
//
|
|
7
|
+
// <FMap> the host: instance, lazy WebGL init, token guard, chrome on tokens
|
|
8
|
+
// <FMarker> a marker, declared — pin or pill, tokened, focusable
|
|
9
|
+
// <FVehicleMark> ONE vehicle, painted from the uploaded asset. Works with no map at all
|
|
10
|
+
// useMarkerSource() many markers from one clustered source — only the visible ones get an element
|
|
11
|
+
// useLiveMarkers() glide those between fixes. DOM tier only: the GL fleet snaps, on purpose
|
|
12
|
+
// densityLayers() how MUCH, as counted cells you can point at and read off a legend
|
|
13
|
+
// heatLayers() the same question as a FIELD — no cell, no unit, no buckets; ramp is a prop
|
|
14
|
+
// balanceLayers() a SIGNED gap around zero — surplus against deficit, magnitude kept
|
|
15
|
+
// <FMapRamp> the key to a quantity: ordered buckets, a stated unit, a stated cell size
|
|
16
|
+
// bin.ts the H3 seam — cell sizes, the zoom→resolution ladder, joins and robust domains
|
|
17
|
+
// <FMapPopup> a popup whose content is a Vue slot
|
|
18
|
+
// <FMapSearch> one field over several sources — places, vehicles, bays — grouped and keyboard-driven
|
|
19
|
+
// <FMapScale> the scale bar, measured off the canvas so latitude and pitch are accounted for
|
|
20
|
+
// <FMapCoordinates> where the map is, in numbers — at the precision the camera has earned
|
|
21
|
+
// <FMapFullscreen> fills the screen with the HUD, chrome and all, rather than with a bare canvas
|
|
22
|
+
// <FMapRegion> the lasso, box or circle a region gesture is drawing — paint only, no listeners
|
|
23
|
+
// <FMapTools> the tool switch: one control at rest, opening in place to the four
|
|
24
|
+
// <FMapDrawTools> the same switch for drawing, plus finish and cancel while a ring is in flight
|
|
25
|
+
// useMapDraw() author a boundary — the one seam here that WRITES geometry rather than reading it
|
|
26
|
+
// <FMapLegend> the key to what is painted — swatches drawn the way their layers are drawn
|
|
27
|
+
// <FMapLayers> the same rows, made operable. The model holds what is HIDDEN, never the map
|
|
28
|
+
// <FVehicleLegend> the key to a vehicle mark — a section per axis, each varying one channel
|
|
29
|
+
// useRegionSelect() drag a region and feed useFeatureSelect's set: replace, shift-add, alt-subtract
|
|
30
|
+
// useMapOverlay() sources, layers and basemap config as data, with paint order and tokens owned
|
|
31
|
+
// scale.ts the fleet seam: crossover zoom, mass-tier ramps, symbol defaults
|
|
32
|
+
// useFeatureSelect() hit-testing at 3px and selection-as-a-set on a GL layer
|
|
33
|
+
// vehicle.ts the three axes (state / connectivity / battery) and how a marker encodes them
|
|
34
|
+
// useTokenMode() ticks when the mode LANDS on <html> — the fix for resolved-token staleness
|
|
35
|
+
// useMapReady() the enclosing map, once its style has loaded
|
|
36
|
+
// useMapCamera() camera moves that respect prefers-reduced-motion
|
|
37
|
+
// token() marks a paint value as token-derived
|
|
38
|
+
// MAP_SLOT which Standard slot a layer belongs in — marks go ABOVE the 3D buildings
|
|
39
|
+
// MARK_CLAIMS_SPACE symbol layout that makes a colliding basemap LABEL yield to the mark
|
|
40
|
+
// resolveMapColor() the low-level token → sRGB step, for driving the raw instance
|
|
41
|
+
//
|
|
42
|
+
// The raw instance stays first-class: `@ready`, the default slot's `map`, and `defineExpose({ map })`
|
|
43
|
+
// are the contract, not a failure mode. Anything unwrapped must be reachable without ceremony.
|
|
44
|
+
export { default as FMap } from './FMap.vue';
|
|
45
|
+
export { FOREST_MAP_STYLE } from './style';
|
|
46
|
+
export { default as FMarker } from './FMarker.vue';
|
|
47
|
+
// ★ NOT a map component — it needs no <FMap>. The vehicle mark, painted from the uploaded asset, for a list
|
|
48
|
+
// row or a detail header as much as for FMarker's default slot. See the file's own header.
|
|
49
|
+
export { default as FVehicleMark } from './FVehicleMark.vue';
|
|
50
|
+
export { default as FMapPopup } from './FMapPopup.vue';
|
|
51
|
+
// Map-aware canvas controls. They render FCanvasControl, so they sit in a HUD rail like any other
|
|
52
|
+
// chrome — which Mapbox's own controls cannot, mounting into its corner containers instead. Each takes
|
|
53
|
+
// the map as a PROP: FMap sits in the HUD's default slot and the control in a rail, so they are
|
|
54
|
+
// siblings and no injection chain runs between them.
|
|
55
|
+
export { default as FMapZoom } from './FMapZoom.vue';
|
|
56
|
+
export { default as FMapCompass } from './FMapCompass.vue';
|
|
57
|
+
export { default as FMapGeolocate } from './FMapGeolocate.vue';
|
|
58
|
+
export { default as FMapTilt } from './FMapTilt.vue';
|
|
59
|
+
export { default as FMapFullscreen } from './FMapFullscreen.vue';
|
|
60
|
+
export { default as FMapSearch } from './FMapSearch.vue';
|
|
61
|
+
export { default as FMapScale } from './FMapScale.vue';
|
|
62
|
+
export { default as FMapCoordinates } from './FMapCoordinates.vue';
|
|
63
|
+
export { default as FMapRegion } from './FMapRegion.vue';
|
|
64
|
+
export { default as FMapTools } from './FMapTools.vue';
|
|
65
|
+
export { default as FMapDrawTools } from './FMapDrawTools.vue';
|
|
66
|
+
// Not a control — it takes no map and operates nothing. It reads as chrome because it sits in the HUD,
|
|
67
|
+
// but it is a list of meanings, and works anywhere a reader needs one.
|
|
68
|
+
export { default as FMapLegend } from './FMapLegend.vue';
|
|
69
|
+
export type { MapLegendItem } from './mapLegend';
|
|
70
|
+
// The same rows with a control on each — the legend, made operable. Takes no map: the model feeds
|
|
71
|
+
// `visible:` in the surface's own OverlaySpec, which useMapOverlay applies and would otherwise revert.
|
|
72
|
+
export { default as FMapLayers } from './FMapLayers.vue';
|
|
73
|
+
export { legendSwatchStyle } from './mapLegend';
|
|
74
|
+
export { sectionState, toggleKey, toggleSection } from './mapLayers';
|
|
75
|
+
export type { MapLayerSection, SectionState } from './mapLayers';
|
|
76
|
+
// The vehicle mark's own key. Not an FMapLegend variant: its swatch is a rendered <FVehicleMark>, which
|
|
77
|
+
// a colour-and-dash item cannot describe, and its rows are grouped by AXIS rather than listed.
|
|
78
|
+
export { default as FVehicleLegend } from './FVehicleLegend.vue';
|
|
79
|
+
export { vehicleLegendSections } from './vehicleLegend';
|
|
80
|
+
export type { VehicleLegendAxis, VehicleLegendRow, VehicleLegendSection } from './vehicleLegend';
|
|
81
|
+
// Drawing — the seam that WRITES. Everything else in this module reads a payload someone else
|
|
82
|
+
// authored; this one hands a ring back. The arithmetic is separable from the map on purpose (node runs
|
|
83
|
+
// the suite), so the guards and the ring operations are importable on their own.
|
|
84
|
+
export { useMapDraw } from './useMapDraw';
|
|
85
|
+
export type { MapDrawOptions } from './useMapDraw';
|
|
86
|
+
export {
|
|
87
|
+
CIRCLE_SEGMENTS,
|
|
88
|
+
MIN_DRAW_PX,
|
|
89
|
+
SNAP_PX,
|
|
90
|
+
boxCorners,
|
|
91
|
+
closeRing,
|
|
92
|
+
drawFeatureToGeoJSON,
|
|
93
|
+
geodesicRing,
|
|
94
|
+
// metresBetween stays inside draw.ts. It is a haversine, which is the kind of helper every geometry
|
|
95
|
+
// module grows its own copy of, and a bare name that generic on the map's public surface is a
|
|
96
|
+
// collision waiting for the next one.
|
|
97
|
+
midpoints,
|
|
98
|
+
ringProblem,
|
|
99
|
+
segmentsCross,
|
|
100
|
+
selfIntersects,
|
|
101
|
+
} from './draw';
|
|
102
|
+
export type { DrawFeature, DrawProblem, DrawRing, DrawTool } from './draw';
|
|
103
|
+
export { useRegionSelect } from './useRegionSelect';
|
|
104
|
+
export type { RegionSelectOptions, RegionSelectTarget } from './useRegionSelect';
|
|
105
|
+
export {
|
|
106
|
+
} from './regionSelect';
|
|
107
|
+
export type { RegionMode, RegionShape, RegionTool, ScreenBox, ScreenPoint } from './regionSelect';
|
|
108
|
+
export { coordinatePlaces, formatCoordinates, parseCoordinates } from './coordinates';
|
|
109
|
+
export type { LngLat, ParseCoordinatesOptions } from './coordinates';
|
|
110
|
+
export { fullscreenApi, isFullscreen } from './fullscreen';
|
|
111
|
+
export type { FullscreenApi } from './fullscreen';
|
|
112
|
+
export { roundedDistance, scaleReading } from './scaleBar';
|
|
113
|
+
export type { ScaleReading, ScaleUnit } from './scaleBar';
|
|
114
|
+
// The time axis. One list of fixes, four readings of it: a route is the track painted, playback is it
|
|
115
|
+
// read at a time, a trail is its tail, and a live vehicle is a track of length two growing at the head.
|
|
116
|
+
export {
|
|
117
|
+
MAX_GAP_MS,
|
|
118
|
+
TRAIL_WINDOW_MS,
|
|
119
|
+
// `advance`, `fitSpeed`, `elapsedLabel` and MAX_FRAME_MS are the clock's own arithmetic, kept in
|
|
120
|
+
// track.ts so a node test can reach them. useTrackPlayback and FTrackScrubber are the public way in.
|
|
121
|
+
// metresBetween and bearingBetween stay inside track.ts, for the reason draw.ts keeps its own
|
|
122
|
+
// haversine private: names that generic on a shared public surface collide with the next module
|
|
123
|
+
// that needs one. `trackDistance` is the public answer for distance, and a bearing arrives on the
|
|
124
|
+
// TrackPoint that needed it.
|
|
125
|
+
positionAt,
|
|
126
|
+
progressAt,
|
|
127
|
+
trackBounds,
|
|
128
|
+
trackDistance,
|
|
129
|
+
trackLine,
|
|
130
|
+
trackSegments,
|
|
131
|
+
trackSlice,
|
|
132
|
+
trackSpan,
|
|
133
|
+
trackTail,
|
|
134
|
+
} from './track';
|
|
135
|
+
export type { Fix, Track, TrackPoint, TrackSpan } from './track';
|
|
136
|
+
export {
|
|
137
|
+
ROUTE_CASING_WIDTH,
|
|
138
|
+
ROUTE_WIDTH,
|
|
139
|
+
TRAIL_WIDTH,
|
|
140
|
+
routeLayers,
|
|
141
|
+
routeSource,
|
|
142
|
+
trailLayers,
|
|
143
|
+
// The played portion is its OWN geometry, sliced from the track. `line-progress` indexes distance
|
|
144
|
+
// and a playhead is a fraction of time, so a gradient stop trails the mark by every second the
|
|
145
|
+
// vehicle spent halted or out of signal.
|
|
146
|
+
travelledLayers,
|
|
147
|
+
} from './route';
|
|
148
|
+
export type { RouteLayerOptions, TrailLayerOptions, TravelledLayerOptions } from './route';
|
|
149
|
+
export { useTrackPlayback } from './useTrackPlayback';
|
|
150
|
+
export type { TrackPlayback, TrackPlaybackOptions } from './useTrackPlayback';
|
|
151
|
+
// The live tier: the viewport's DOM markers glide between fixes. The GL fleet keeps snapping on
|
|
152
|
+
// purpose — a full re-upload is 222 ms per tick at 30k, which a 2–5 s cadence absorbs and a frame
|
|
153
|
+
// does not.
|
|
154
|
+
export { useLiveMarkers } from './useLiveMarkers';
|
|
155
|
+
export type { LiveMarkerOptions } from './useLiveMarkers';
|
|
156
|
+
// `liveMarkers.ts` stays module-internal — `advanceLeg`, `legAt` and the two tween helpers are the
|
|
157
|
+
// composable's own mechanics, and the tests import them from the file rather than from here. Names
|
|
158
|
+
// that generic on a shared surface are a collision waiting for the next module that needs one.
|
|
159
|
+
// Not a control: it takes no map and operates nothing. It scrubs a track, and works anywhere one is
|
|
160
|
+
// being read.
|
|
161
|
+
export { default as FTrackScrubber } from './FTrackScrubber.vue';
|
|
162
|
+
export type { MapSearchResult, MapSearchSource } from './mapSearch';
|
|
163
|
+
|
|
164
|
+
export { FOREST_MAP, FOREST_MARKER } from './mapContext';
|
|
165
|
+
export { resolveMapColor } from './mapColor';
|
|
166
|
+
// useLazyHost stays internal: it exists so FMarker and FMapPopup can create the element mapbox owns
|
|
167
|
+
// without touching `document` during setup. It is not a seam a surface should reach for — the public
|
|
168
|
+
// way to attach to a map is useMapReady, or the injection keys above for a bespoke child.
|
|
169
|
+
export { useMapCamera, useMapReady, useMapScaleFactor, readerTextScale } from './useMap';
|
|
170
|
+
export { MAP_SLOT, MARK_CLAIMS_SPACE, token, useMapOverlay } from './useMapOverlay';
|
|
171
|
+
export type { OverlayLayer, OverlaySpec } from './useMapOverlay';
|
|
172
|
+
export {
|
|
173
|
+
DISSOLVE_START_ZOOM,
|
|
174
|
+
FLEET_SYMBOL_LAYOUT,
|
|
175
|
+
MARKER_CROSSOVER_ZOOM,
|
|
176
|
+
MARKER_MIN_ICON_SIZE,
|
|
177
|
+
MARKER_PX,
|
|
178
|
+
MARKER_SIZE_SCALE_RANGE,
|
|
179
|
+
fleetCircleOpacity,
|
|
180
|
+
fleetCircleRadius,
|
|
181
|
+
fleetCircleStroke,
|
|
182
|
+
fleetSymbolOpacity,
|
|
183
|
+
markerIconSize,
|
|
184
|
+
} from './scale';
|
|
185
|
+
export {
|
|
186
|
+
AVAILABLE_BATTERY,
|
|
187
|
+
AVAILABLE_STATES,
|
|
188
|
+
BATTERY_RING,
|
|
189
|
+
BATTERY_RING_BAND,
|
|
190
|
+
IN_USE_GRADIENT,
|
|
191
|
+
LOCKED_MARK,
|
|
192
|
+
RENTAL_TAKEN,
|
|
193
|
+
RENTAL_PAUSED,
|
|
194
|
+
batteryRingToken,
|
|
195
|
+
STATE_GLYPH,
|
|
196
|
+
STATE_LABEL,
|
|
197
|
+
stateLabel,
|
|
198
|
+
markerImageExpression,
|
|
199
|
+
markerGlyphCrossFade,
|
|
200
|
+
OFFLINE_DOT,
|
|
201
|
+
markerParams,
|
|
202
|
+
paintMarkerSvg,
|
|
203
|
+
BADGE_SHAPES,
|
|
204
|
+
BADGE_PARAMS,
|
|
205
|
+
BADGE_COLOUR,
|
|
206
|
+
BADGE_COLOURS,
|
|
207
|
+
BADGE_SAFE,
|
|
208
|
+
BADGE_EDGE,
|
|
209
|
+
MARKER_CANVAS,
|
|
210
|
+
STATE_GROUP_COLOUR,
|
|
211
|
+
SOC_THRESHOLDS,
|
|
212
|
+
STATE_GROUP,
|
|
213
|
+
groupMatchInput,
|
|
214
|
+
labelOf,
|
|
215
|
+
isAvailable,
|
|
216
|
+
unavailableReason,
|
|
217
|
+
unavailableReasons,
|
|
218
|
+
} from './vehicle';
|
|
219
|
+
export type {
|
|
220
|
+
BadgeShape,
|
|
221
|
+
BadgeColour,
|
|
222
|
+
BatteryLevel,
|
|
223
|
+
BatteryRingBand,
|
|
224
|
+
MarkerBadge,
|
|
225
|
+
RentalState,
|
|
226
|
+
StateGroup,
|
|
227
|
+
VehicleFacts,
|
|
228
|
+
VehicleState,
|
|
229
|
+
} from './vehicle';
|
|
230
|
+
// Places — the fixed things an operation works from. A place is not a vehicle: different shape,
|
|
231
|
+
// different vocabulary, no battery and no connectivity. Zones are paint, not marks — see zone.ts.
|
|
232
|
+
export {
|
|
233
|
+
PLACE_STATE_COLOUR,
|
|
234
|
+
placeState,
|
|
235
|
+
placeColour,
|
|
236
|
+
WAREHOUSE_COLOUR,
|
|
237
|
+
COUNT_GLYPH,
|
|
238
|
+
PLACE_COUNT_LAYOUT,
|
|
239
|
+
PLACE_GLYPH,
|
|
240
|
+
placeImageExpression,
|
|
241
|
+
placeParams,
|
|
242
|
+
} from './place';
|
|
243
|
+
export type { Place, PlaceColours, PlaceKind, PlaceState } from './place';
|
|
244
|
+
// Zones — a rule with a boundary. Paint, never a mark, and AUDIENCE decides which ones exist at all.
|
|
245
|
+
export {
|
|
246
|
+
ZONE_AUDIENCE,
|
|
247
|
+
ZONE_COLOUR,
|
|
248
|
+
ZONE_DASH,
|
|
249
|
+
ZONE_FILL_OPACITY,
|
|
250
|
+
ZONE_LABEL,
|
|
251
|
+
ZONE_RANK,
|
|
252
|
+
ZONE_RULE,
|
|
253
|
+
ZONE_RULE_TEXT,
|
|
254
|
+
zoneFeatures,
|
|
255
|
+
zoneKinds,
|
|
256
|
+
zoneLayers,
|
|
257
|
+
zoneLegendItems,
|
|
258
|
+
zoneVisibleTo,
|
|
259
|
+
} from './zone';
|
|
260
|
+
export type { ZoneAudience, ZoneKind, ZoneLayerOptions, ZoneRule } from './zone';
|
|
261
|
+
export { onTokenModeChange, useTokenMode } from './useTokenMode';
|
|
262
|
+
export { useFeatureSelect } from './useFeatureSelect';
|
|
263
|
+
export type { FeatureSelectOptions, PickedFeature } from './useFeatureSelect';
|
|
264
|
+
export { useMarkerSource } from './useMarkerSource';
|
|
265
|
+
export type { MarkerSourceItem, MarkerSourceOptions } from './useMarkerSource';
|
|
266
|
+
// Density — how MUCH, as ground paint. `density.ts` is the paint doctrine (heat for the glance, bins
|
|
267
|
+
// for the decision); `bin.ts` is the H3 seam, which takes cell GEOMETRY in and therefore needs no h3
|
|
268
|
+
// dependency. <FMapRamp> is the ordered legend a categorical one cannot draw.
|
|
269
|
+
export { default as FMapRamp } from './FMapRamp.vue';
|
|
270
|
+
export {
|
|
271
|
+
DENSITY_ALPHA,
|
|
272
|
+
DENSITY_STEPS,
|
|
273
|
+
BALANCE_STEPS,
|
|
274
|
+
balanceLayers,
|
|
275
|
+
balanceLegend,
|
|
276
|
+
balanceStops,
|
|
277
|
+
densityLayers,
|
|
278
|
+
densityLegend,
|
|
279
|
+
densityStops,
|
|
280
|
+
stepColour,
|
|
281
|
+
stepEdges,
|
|
282
|
+
} from './density';
|
|
283
|
+
export type { BalanceOptions, DensityOptions, LegendOptions, RampLegend, RampStop } from './density';
|
|
284
|
+
// The kernel field, and it is a separate module on purpose: it shares nothing with bins but a basemap.
|
|
285
|
+
// No cell, no id, no resolution ladder, no H3 — raw events and a radius somebody chose. Its ramp and
|
|
286
|
+
// its ramp is a PROP where the bin ramp is fixed — but it has NO bucket count at all, because a
|
|
287
|
+
// field is continuous and nobody matches a pixel to a swatch.
|
|
288
|
+
export { HEAT_FLOOR, HEAT_RAMPS, heatLayers, heatRadiusByZoom } from './heat';
|
|
289
|
+
// One place samples the engine's sequential ramps and reverses them after dark — both mechanisms build
|
|
290
|
+
// their colours through it, so neither can ship a ramp that means the opposite thing at night.
|
|
291
|
+
export {
|
|
292
|
+
BALANCE_CENTRE_TOKEN,
|
|
293
|
+
DIVERGING_RAMPS,
|
|
294
|
+
RAMP_LENGTH,
|
|
295
|
+
SEQUENTIAL_RAMPS,
|
|
296
|
+
divergingStops,
|
|
297
|
+
prefersInvertedRamp,
|
|
298
|
+
rampStops,
|
|
299
|
+
} from './ramp';
|
|
300
|
+
export type { DivergingRamp, SequentialRamp } from './ramp';
|
|
301
|
+
export type { HeatOptions, HeatRamp } from './heat';
|
|
302
|
+
export {
|
|
303
|
+
CELL_PX_BAND,
|
|
304
|
+
H3_EDGE_M,
|
|
305
|
+
H3_RES_RANGE,
|
|
306
|
+
cellFitsBand,
|
|
307
|
+
cellWidthPx,
|
|
308
|
+
h3ResForZoom,
|
|
309
|
+
joinCellValues,
|
|
310
|
+
metresPerPixel,
|
|
311
|
+
robustDomain,
|
|
312
|
+
symmetricDomain,
|
|
313
|
+
} from './bin';
|
|
314
|
+
export type { CellCollection, Domain, JoinOptions } from './bin';
|
|
315
|
+
// ── State ───────────────────────────────────────────────────────────────────────────────────────
|
|
316
|
+
//
|
|
317
|
+
// A console is not one view of a map. It is a map, a list, a set of filters and a link somebody
|
|
318
|
+
// pastes into Slack, and these four keep those in step. Each is a pure core plus a thin wrapper: the
|
|
319
|
+
// core takes plain values so the node suite can exercise every edge, the wrapper owns the events.
|
|
320
|
+
//
|
|
321
|
+
// useMapUrlState where the reader was looking, in the address bar — replaceState, never push
|
|
322
|
+
// useVisibleFeatures what is on screen right now, deduped across tile seams and capped
|
|
323
|
+
// useMapTableSelection the map's selection and a table's, as one thing
|
|
324
|
+
// useVehicleFilter one filter, rendered as a GL expression, a predicate and a query string
|
|
325
|
+
export { useMapUrlState } from './useMapUrlState';
|
|
326
|
+
export type { MapUrlAdapter, MapUrlStateOptions } from './useMapUrlState';
|
|
327
|
+
export { MAP_URL_PRECISION, decodeCamera, encodeCamera, patchQuery, readQuery, sameCamera } from './mapUrl';
|
|
328
|
+
export type { MapCamera } from './mapUrl';
|
|
329
|
+
export { useVisibleFeatures } from './useVisibleFeatures';
|
|
330
|
+
export type { VisibleFeaturesOptions } from './useVisibleFeatures';
|
|
331
|
+
export { VISIBLE_LIMIT, boxContains, collectVisible, featureAnchor, featureInBox, featureKey } from './visibleFeatures';
|
|
332
|
+
export type { CollectOptions, ViewBox, VisibleFeature } from './visibleFeatures';
|
|
333
|
+
export { useMapTableSelection } from './useMapTableSelection';
|
|
334
|
+
export type { FeatureSelectionApi, MapTableSelectionOptions } from './useMapTableSelection';
|
|
335
|
+
export { rowKey, sameSelection, selectedKeys, toRowSelection } from './selectionModel';
|
|
336
|
+
export type { RowSelection } from './selectionModel';
|
|
337
|
+
export { useVehicleFilter } from './useVehicleFilter';
|
|
338
|
+
// VEHICLE_FILTER_AXES and VEHICLE_FILTER_LIST_AXES stay inside the module: nothing needs to iterate
|
|
339
|
+
// the axes, and the list type — which `toggle` puts in a caller's signature — is exported on its own.
|
|
340
|
+
export {
|
|
341
|
+
activeAxisCount,
|
|
342
|
+
availableExpression,
|
|
343
|
+
decodeVehicleFilter,
|
|
344
|
+
encodeVehicleFilter,
|
|
345
|
+
filterStates,
|
|
346
|
+
isEmptyVehicleFilter,
|
|
347
|
+
toggleVehicleFilter,
|
|
348
|
+
vehicleFilterExpression,
|
|
349
|
+
vehicleFilterMatch,
|
|
350
|
+
} from './vehicleFilter';
|
|
351
|
+
export type { VehicleFilter, VehicleFilterListAxis, VehicleFilterProps } from './vehicleFilter';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The arithmetic behind a marker that glides between fixes. Pure, so a node test can hold it.
|
|
2
|
+
//
|
|
3
|
+
// A leg is one hop: where the marker was when the fix landed, where the fix says it is, and when the
|
|
4
|
+
// hop started. Everything the composable does is bookkeeping over a map of these.
|
|
5
|
+
|
|
6
|
+
export interface Leg {
|
|
7
|
+
from: [number, number];
|
|
8
|
+
to: [number, number];
|
|
9
|
+
/** the clock reading the hop began at */
|
|
10
|
+
startedAt: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Linear between two positions. At a few seconds per fix the great-circle correction is sub-pixel. */
|
|
14
|
+
export function tweenPosition(from: [number, number], to: [number, number], t: number): [number, number] {
|
|
15
|
+
return [from[0] + (to[0] - from[0]) * t, from[1] + (to[1] - from[1]) * t];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How far through the expected interval `now` is, 0–1.
|
|
20
|
+
*
|
|
21
|
+
* ★ Clamped at 1. A fix arriving late is normal, and running past the target would extrapolate a
|
|
22
|
+
* position nothing measured — a vehicle confidently riding into the river.
|
|
23
|
+
*/
|
|
24
|
+
export function tweenFraction(now: number, startedAt: number, ms: number): number {
|
|
25
|
+
if (ms <= 0) return 1;
|
|
26
|
+
return Math.min(1, Math.max(0, (now - startedAt) / ms));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Where a leg has reached by `now`. */
|
|
30
|
+
export function legAt(leg: Leg, now: number, ms: number): [number, number] {
|
|
31
|
+
return tweenPosition(leg.from, leg.to, tweenFraction(now, leg.startedAt, ms));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The leg a key should be on, given the position its latest fix reports.
|
|
36
|
+
*
|
|
37
|
+
* Three cases, and the first two are the ones that go wrong if they are not stated:
|
|
38
|
+
*
|
|
39
|
+
* ★ A KEY SEEN FOR THE FIRST TIME APPEARS WHERE IT IS. Its leg starts and ends at the same point, so
|
|
40
|
+
* nothing animates. Otherwise a marker entering the viewport flies in from wherever the previous
|
|
41
|
+
* occupant of that slot sat — a vehicle that was always parked there would slide across the screen
|
|
42
|
+
* the moment a pan brought it into view.
|
|
43
|
+
*
|
|
44
|
+
* ★ AN UNCHANGED TARGET KEEPS THE SAME LEG OBJECT. Returning a fresh leg would reset `startedAt`
|
|
45
|
+
* every evaluation, the fraction would never leave zero, and the marker would crawl instead of
|
|
46
|
+
* arriving.
|
|
47
|
+
*
|
|
48
|
+
* ★ A NEW FIX MID-GLIDE BENDS FROM WHERE THE MARKER IS. Starting the new leg from the old leg's
|
|
49
|
+
* `from` would snap the marker backwards up the road each time a fix landed.
|
|
50
|
+
*/
|
|
51
|
+
export function advanceLeg(
|
|
52
|
+
leg: Leg | undefined,
|
|
53
|
+
target: [number, number],
|
|
54
|
+
now: number,
|
|
55
|
+
ms: number,
|
|
56
|
+
): Leg {
|
|
57
|
+
if (!leg) return { from: target, to: target, startedAt: now };
|
|
58
|
+
if (leg.to[0] === target[0] && leg.to[1] === target[1]) return leg;
|
|
59
|
+
return { from: legAt(leg, now, ms), to: target, startedAt: now };
|
|
60
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/* Mapbox chrome on Forest tokens. mapbox-gl.css hardcodes #fff surfaces, #333 text and 3–4px radii
|
|
2
|
+
on everything it creates at runtime — popups, controls, attribution, scale. Left alone those are
|
|
3
|
+
white boxes in dark mode (and Forest text classes inside a popup go near-white on near-white), and
|
|
4
|
+
their corners ignore --ui-radius. Every rule here re-points one of those literals at a token.
|
|
5
|
+
Imported by FMap after mapbox-gl.css, so equal-specificity rules land last and win.
|
|
6
|
+
|
|
7
|
+
Radius follows the contract: chrome is a control/panel surface, so 12px — calc(--ui-radius × 1.5).
|
|
8
|
+
Control glyphs are background-image data URIs with baked-in dark strokes, so dark mode inverts
|
|
9
|
+
them rather than recolouring (hue-rotate keeps the coloured ones, e.g. active geolocate, on hue). */
|
|
10
|
+
|
|
11
|
+
/* ── Popup ───────────────────────────────────────────────────────────────── */
|
|
12
|
+
.mapboxgl-popup-content {
|
|
13
|
+
background: var(--ui-bg);
|
|
14
|
+
color: var(--ui-text);
|
|
15
|
+
border-radius: calc(var(--ui-radius) * 1.5);
|
|
16
|
+
box-shadow: 0 4px 12px -2px rgb(0 0 0 / 0.12), 0 0 0 1px var(--ui-border);
|
|
17
|
+
padding: 0.625rem 0.75rem;
|
|
18
|
+
font-family: var(--font-sans);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* The tip is a CSS triangle: the visible edge is whichever border side faces the anchor. */
|
|
22
|
+
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip,
|
|
23
|
+
.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip,
|
|
24
|
+
.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
|
|
25
|
+
border-bottom-color: var(--ui-bg);
|
|
26
|
+
}
|
|
27
|
+
.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip,
|
|
28
|
+
.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip,
|
|
29
|
+
.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
|
|
30
|
+
border-top-color: var(--ui-bg);
|
|
31
|
+
}
|
|
32
|
+
.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
|
|
33
|
+
border-right-color: var(--ui-bg);
|
|
34
|
+
}
|
|
35
|
+
.mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
|
|
36
|
+
border-left-color: var(--ui-bg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.mapboxgl-popup-close-button {
|
|
40
|
+
color: var(--ui-text-dimmed);
|
|
41
|
+
border-radius: calc(var(--ui-radius) * 1.5);
|
|
42
|
+
width: 1.5rem;
|
|
43
|
+
height: 1.5rem;
|
|
44
|
+
font-size: 1rem;
|
|
45
|
+
line-height: 1;
|
|
46
|
+
}
|
|
47
|
+
.mapboxgl-popup-close-button:hover {
|
|
48
|
+
background: var(--ui-bg-elevated);
|
|
49
|
+
color: var(--ui-text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ── Controls ────────────────────────────────────────────────────────────── */
|
|
53
|
+
/* Selectors mirror mapbox's own specificity — `:not(:empty)`, the compound `.mapboxgl-ctrl.…`, the
|
|
54
|
+
button corner rules. A plainer selector silently loses the cascade, and !important is not the fix. */
|
|
55
|
+
.mapboxgl-ctrl-group,
|
|
56
|
+
.mapboxgl-ctrl-group:not(:empty) {
|
|
57
|
+
background: var(--ui-bg);
|
|
58
|
+
border-radius: calc(var(--ui-radius) * 1.5);
|
|
59
|
+
box-shadow: 0 2px 8px -2px rgb(0 0 0 / 0.12), 0 0 0 1px var(--ui-border);
|
|
60
|
+
}
|
|
61
|
+
/* Buttons carry their own 4px corners, which would peek out of a 12px group. */
|
|
62
|
+
.mapboxgl-ctrl-group button:first-child {
|
|
63
|
+
border-radius: calc(var(--ui-radius) * 1.5) calc(var(--ui-radius) * 1.5) 0 0;
|
|
64
|
+
}
|
|
65
|
+
.mapboxgl-ctrl-group button:last-child {
|
|
66
|
+
border-radius: 0 0 calc(var(--ui-radius) * 1.5) calc(var(--ui-radius) * 1.5);
|
|
67
|
+
}
|
|
68
|
+
.mapboxgl-ctrl-group button + button {
|
|
69
|
+
border-top-color: var(--ui-border);
|
|
70
|
+
}
|
|
71
|
+
.mapboxgl-ctrl-group button:hover {
|
|
72
|
+
background: var(--ui-bg-elevated);
|
|
73
|
+
}
|
|
74
|
+
/* Mapbox glows blue on plain :focus, i.e. also on a mouse press. Forest shows a ring on keyboard
|
|
75
|
+
focus only. */
|
|
76
|
+
.mapboxgl-ctrl-group button:focus {
|
|
77
|
+
box-shadow: none;
|
|
78
|
+
}
|
|
79
|
+
.mapboxgl-ctrl-group button:focus-visible {
|
|
80
|
+
outline: 2px solid var(--ui-primary);
|
|
81
|
+
outline-offset: -2px;
|
|
82
|
+
}
|
|
83
|
+
.dark .mapboxgl-ctrl-group button .mapboxgl-ctrl-icon {
|
|
84
|
+
filter: invert(1) hue-rotate(180deg);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ── Attribution + scale ─────────────────────────────────────────────────── */
|
|
88
|
+
/* Attribution stays legible in both modes — it is a Mapbox terms requirement, not decoration. */
|
|
89
|
+
.mapboxgl-ctrl.mapboxgl-ctrl-attrib {
|
|
90
|
+
background-color: color-mix(in oklab, var(--ui-bg) 80%, transparent);
|
|
91
|
+
font-family: var(--font-sans);
|
|
92
|
+
}
|
|
93
|
+
/* Collapsed pill (small maps): opaque, and on the radius contract. */
|
|
94
|
+
.mapboxgl-ctrl-attrib.mapboxgl-compact {
|
|
95
|
+
background-color: var(--ui-bg);
|
|
96
|
+
border-radius: calc(var(--ui-radius) * 1.5);
|
|
97
|
+
}
|
|
98
|
+
.mapboxgl-ctrl-attrib,
|
|
99
|
+
.mapboxgl-ctrl-attrib a {
|
|
100
|
+
color: var(--ui-text-muted);
|
|
101
|
+
}
|
|
102
|
+
.dark .mapboxgl-ctrl-attrib-button {
|
|
103
|
+
filter: invert(1) hue-rotate(180deg);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.mapboxgl-ctrl-scale {
|
|
107
|
+
background-color: color-mix(in oklab, var(--ui-bg) 75%, transparent);
|
|
108
|
+
border-color: var(--ui-border-accented);
|
|
109
|
+
color: var(--ui-text-muted);
|
|
110
|
+
font-family: var(--font-sans);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ── Cooperative-gesture scrim ───────────────────────────────────────────── */
|
|
114
|
+
.mapboxgl-cooperative-gesture-screen {
|
|
115
|
+
font-family: var(--font-sans);
|
|
116
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The token → GL colour seam, in one place.
|
|
2
|
+
//
|
|
3
|
+
// GL paint properties and basemap config take sRGB strings. Forest tokens are wide-gamut, so
|
|
4
|
+
// `getComputedStyle` hands back `color(display-p3 …)` — which mapbox's style parser rejects outright.
|
|
5
|
+
// A 1×1 canvas parses that value and reads it back gamut-mapped to sRGB bytes.
|
|
6
|
+
//
|
|
7
|
+
// DOM markers are exempt: they take CSS vars directly. This is only for GL layers and basemap config.
|
|
8
|
+
// Prefer `token()` + `useMapOverlay`, which calls this and re-resolves on a colour-mode flip; reach
|
|
9
|
+
// for it directly only when driving the raw map instance.
|
|
10
|
+
let probe: HTMLCanvasElement | null = null;
|
|
11
|
+
|
|
12
|
+
export function resolveMapColor(expr: string): string {
|
|
13
|
+
const el = document.createElement('div');
|
|
14
|
+
el.style.color = expr;
|
|
15
|
+
el.style.display = 'none';
|
|
16
|
+
document.body.appendChild(el);
|
|
17
|
+
const computed = getComputedStyle(el).color;
|
|
18
|
+
el.remove();
|
|
19
|
+
|
|
20
|
+
if (!probe) {
|
|
21
|
+
probe = document.createElement('canvas');
|
|
22
|
+
probe.width = probe.height = 1;
|
|
23
|
+
}
|
|
24
|
+
const ctx = probe.getContext('2d');
|
|
25
|
+
if (!ctx) return computed;
|
|
26
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
27
|
+
ctx.fillStyle = computed;
|
|
28
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
29
|
+
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
|
|
30
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Injection seam between the map primitives. <FMap> provides the map instance; <FMarker> provides
|
|
2
|
+
// the marker it created. Anything nested finds its parent without prop-drilling, so a page can write
|
|
3
|
+
// <FMap><FMarker><FMapPopup>…</FMapPopup></FMarker></FMap> and nothing has to thread refs through.
|
|
4
|
+
//
|
|
5
|
+
// Both are shallowRefs holding a mapbox object, and both start null: FMap creates the map lazily
|
|
6
|
+
// (WebGL only once the frame nears the viewport), so children must watch rather than read once.
|
|
7
|
+
// It is also what makes the DOM-side components testable — inject a fake map and assert on real DOM.
|
|
8
|
+
import type { InjectionKey, ShallowRef } from 'vue';
|
|
9
|
+
import type { Map, Marker } from 'mapbox-gl';
|
|
10
|
+
|
|
11
|
+
export const FOREST_MAP: InjectionKey<ShallowRef<Map | null>> = Symbol('forest-map');
|
|
12
|
+
export const FOREST_MARKER: InjectionKey<ShallowRef<Marker | null>> = Symbol('forest-marker');
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// What a layers switch is made of, and the arithmetic of turning things off.
|
|
2
|
+
//
|
|
3
|
+
// ★ THE MODEL HOLDS WHAT IS HIDDEN, NOT WHAT IS SHOWN. A map's resting state is everything the surface
|
|
4
|
+
// authored, painted. With a hidden-set `[]` says exactly that, and a surface that never seeds the model
|
|
5
|
+
// gets the map it wrote. With a visible-set the same oversight paints nothing at all — and a blank map
|
|
6
|
+
// reads as a broken overlay rather than as a missing default, which is the harder bug to find.
|
|
7
|
+
//
|
|
8
|
+
// ★ NOTHING HERE TOUCHES THE MAP. The model feeds `visible:` in the surface's OverlaySpec and
|
|
9
|
+
// useMapOverlay applies it. A switch calling setLayoutProperty directly would work until the next
|
|
10
|
+
// apply — a spec change, a colour-mode flip, a new style — and then be reverted while the control
|
|
11
|
+
// carried on reporting the state it had asked for.
|
|
12
|
+
import type { MapLegendItem } from './mapLegend';
|
|
13
|
+
|
|
14
|
+
/** A group of rows. */
|
|
15
|
+
export interface MapLayerSection {
|
|
16
|
+
/**
|
|
17
|
+
* What the group is called.
|
|
18
|
+
*
|
|
19
|
+
* ★ A LABELLED SECTION GETS AN ALL/NONE HEADER; an unlabelled one does not. There is nothing to name
|
|
20
|
+
* the toggle after, and a control for "everything" is the map itself. A flat list is one section
|
|
21
|
+
* without a label, which is why there is no second shape for it.
|
|
22
|
+
*/
|
|
23
|
+
label?: string;
|
|
24
|
+
items: MapLegendItem[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SectionState = 'all' | 'none' | 'some';
|
|
28
|
+
|
|
29
|
+
/** `some` renders indeterminate. An empty section is `none` — nothing is shown, so nothing is all. */
|
|
30
|
+
export function sectionState(items: MapLegendItem[], hidden: readonly string[]): SectionState {
|
|
31
|
+
if (!items.length) return 'none';
|
|
32
|
+
const off = items.filter((i) => hidden.includes(i.key)).length;
|
|
33
|
+
if (off === 0) return 'all';
|
|
34
|
+
if (off === items.length) return 'none';
|
|
35
|
+
return 'some';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The header's press.
|
|
40
|
+
*
|
|
41
|
+
* ★ PARTIAL REVEALS RATHER THAN HIDES. It is the direction every tri-state checkbox a reader has met
|
|
42
|
+
* takes, and the non-destructive one: pressing a header should never be the thing that empties a map.
|
|
43
|
+
*/
|
|
44
|
+
export function toggleSection(items: MapLegendItem[], hidden: readonly string[]): string[] {
|
|
45
|
+
const keys = items.map((i) => i.key);
|
|
46
|
+
if (sectionState(items, hidden) === 'all') {
|
|
47
|
+
return [...hidden, ...keys.filter((k) => !hidden.includes(k))];
|
|
48
|
+
}
|
|
49
|
+
return hidden.filter((k) => !keys.includes(k));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One row. Returns a new array — a model mutated in place does not reach the parent. */
|
|
53
|
+
export function toggleKey(key: string, hidden: readonly string[]): string[] {
|
|
54
|
+
return hidden.includes(key) ? hidden.filter((k) => k !== key) : [...hidden, key];
|
|
55
|
+
}
|