@phila/layerboard 3.0.0-beta.2 → 3.0.0-beta.21

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 CHANGED
@@ -1,66 +1,213 @@
1
- # vue3-layerboard
1
+ # @phila/layerboard
2
2
 
3
- A Vue 3 + MapLibre GL JS framework for building interactive map applications with layer management, measurement tools, and ArcGIS integration.
3
+ A Vue 3 component framework for building interactive map applications powered by ArcGIS Online WebMaps and MapLibre GL JS. Provide a WebMap ID and get a full mapping app with layer management, popups, legends, search, and more.
4
4
 
5
- ## Features
5
+ ## Live Examples
6
6
 
7
- - **MapLibre GL JS Integration** - Modern, performant vector tile mapping
8
- - **Layer Management** - Dynamic layer loading from ArcGIS Online web maps
9
- - **Measurement Tools** - Interactive area and distance measurement
10
- - **Geolocation** - User location tracking and display
11
- - **Legend & Layer Controls** - Configurable layer visibility and legends
12
- - **Popup System** - Feature identification and attribute display
7
+ - [OpenMaps](https://openmaps.phila.gov) flat layer list (all layers searchable)
8
+ - [StreetSmartPHL](https://streetsmartphl.phila.gov) topic-based layout (layers grouped in accordions)
13
9
 
14
10
  ## Installation
15
11
 
16
12
  ```sh
17
- npm install @phila/layerboard
13
+ pnpm add @phila/layerboard
18
14
  ```
19
15
 
20
- ## Peer Dependencies
16
+ ### Peer Dependencies
21
17
 
22
- This package requires the following peer dependencies:
18
+ ```sh
19
+ pnpm add vue pinia maplibre-gl @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome
20
+ ```
23
21
 
24
- - `vue` ^3.5.0
25
- - `pinia` ^3.0.0
26
- - `maplibre-gl` ^5.0.0
27
- - `@fortawesome/fontawesome-svg-core` ^7.0.0
28
- - `@fortawesome/free-solid-svg-icons` ^7.0.0
29
- - `@fortawesome/vue-fontawesome` ^3.0.0
22
+ ## Quick Start
30
23
 
31
- ## Development
24
+ ```vue
25
+ <template>
26
+ <Layerboard
27
+ title="My Map App"
28
+ web-map-id="1596df70df0349e293ceec46a06ccc50"
29
+ />
30
+ </template>
31
+
32
+ <script setup>
33
+ import { Layerboard } from "@phila/layerboard";
34
+ import "@phila/layerboard/dist/layerboard.css";
35
+ </script>
36
+ ```
32
37
 
33
- ### Recommended IDE Setup
38
+ This renders a full-screen map app with a sidebar listing all layers from the WebMap, complete with legends, opacity sliders, search, popups, and mobile responsiveness.
34
39
 
35
- [VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
40
+ ## Layout Modes
36
41
 
37
- ### Recommended Browser Setup
42
+ ### Flat Mode (default)
38
43
 
39
- - Chromium-based browsers (Chrome, Edge, Brave, etc.):
40
- - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
41
- - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
42
- - Firefox:
43
- - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
44
- - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
44
+ All layers in a searchable list. Set `show-default-sidebar` to `true` (default) and the built-in `LayerPanel` renders in the sidebar.
45
45
 
46
- ### Type Support for `.vue` Imports in TS
46
+ ### Topics Mode
47
47
 
48
- TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
48
+ Group layers into collapsible accordions using the sidebar slot:
49
49
 
50
- ### Project Setup
50
+ ```vue
51
+ <Layerboard
52
+ title="StreetSmartPHL"
53
+ :web-map-id="webMapId"
54
+ :show-default-sidebar="false"
55
+ >
56
+ <template #sidebar="{ layers, visibleLayers, toggleLayer, setOpacity }">
57
+ <TopicAccordion title="Paving" :expanded="true">
58
+ <LayerCheckboxSet
59
+ :layers="pavingLayers"
60
+ :visible-layer-ids="visibleLayers"
61
+ @toggle-layer="toggleLayer"
62
+ @set-opacity="setOpacity"
63
+ />
64
+ </TopicAccordion>
65
+ </template>
66
+ </Layerboard>
67
+ ```
51
68
 
52
- ```sh
53
- npm install
69
+ ## Props
70
+
71
+ | Prop | Type | Default | Description |
72
+ |------|------|---------|-------------|
73
+ | `title` | `string` | *required* | App title in header |
74
+ | `webMapId` | `string` | *required* | ArcGIS Online WebMap ID |
75
+ | `subtitle` | `string` | — | Subtitle in header |
76
+ | `themeColor` | `string` | `"#0f4d90"` | Header/footer background color |
77
+ | `showDefaultSidebar` | `boolean` | `true` | Show built-in LayerPanel (false for custom sidebar) |
78
+ | `sidebarWidth` | `string` | `"30%"` | Sidebar width (CSS units) |
79
+ | `sidebarLabel` | `string` | `"Layers"` | Mobile toggle label for sidebar view |
80
+ | `mapLabel` | `string` | `"Map"` | Mobile toggle label for map view |
81
+ | `fetchMetadata` | `boolean` | `false` | Fetch layer metadata from Carto |
82
+ | `tiledLayers` | `TiledLayerConfig[]` | `[]` | ESRI MapServer tiled layers |
83
+ | `dataSources` | `DataSourceConfig[]` | `[]` | External API data sources |
84
+ | `layerStyleOverrides` | `Record<string, LayerStyleOverride>` | `{}` | Override paint/legend per layer |
85
+ | `popupOverrides` | `Record<string, PopupOverride>` | `{}` | Override popup behavior per layer |
86
+ | `initialZoom` | `number` | — | Initial map zoom level |
87
+ | `initialCenter` | `[number, number]` | — | Initial map center `[lng, lat]` |
88
+ | `cyclomediaConfig` | `CyclomediaConfig` | — | Cyclomedia street-level imagery config |
89
+ | `pictometryCredentials` | `PictometryCredentials` | — | Pictometry oblique imagery credentials |
90
+
91
+ ### Control Positions
92
+
93
+ All default to sensible positions. Each accepts `"top-left" | "top-right" | "bottom-left" | "bottom-right"`.
94
+
95
+ | Prop | Default |
96
+ |------|---------|
97
+ | `basemapControlPosition` | `"top-right"` |
98
+ | `navigationControlPosition` | `"bottom-right"` |
99
+ | `geolocationControlPosition` | `"bottom-right"` |
100
+ | `searchControlPosition` | `"top-left"` |
101
+ | `drawControlPosition` | `"bottom-left"` (or `null` to remove) |
102
+ | `cyclomediaButtonPosition` | `"top-right"` |
103
+ | `pictometryButtonPosition` | `"top-right"` |
104
+
105
+ ## Events
106
+
107
+ | Event | Payload | Description |
108
+ |-------|---------|-------------|
109
+ | `configs-loaded` | `LayerConfig[]` | Layer configs loaded from WebMap |
110
+ | `load-error` | `string` | Error message on load failure |
111
+ | `zoom` | `number` | Zoom level changed |
112
+
113
+ ## Slots
114
+
115
+ | Slot | Scope | Description |
116
+ |------|-------|-------------|
117
+ | `header` | — | Replace default header |
118
+ | `sidebar` | layer state + methods (see below) | Replace default LayerPanel |
119
+ | `footer` | `{ openModal, closeModal, isModalOpen }` | Custom footer content |
120
+ | `modal` | `{ closeModal }` | Modal content |
121
+
122
+ ### Sidebar Slot Scope
123
+
124
+ The sidebar slot exposes the full layer state for building custom UIs:
125
+
126
+ ```typescript
127
+ {
128
+ layers: Array<{ config: LayerConfig; component: string }>
129
+ visibleLayers: Set<string>
130
+ layerOpacities: Record<string, number>
131
+ loadingLayers: Set<string>
132
+ layerErrors: Record<string, string>
133
+ currentZoom: number
134
+ toggleLayer: (id: string) => void
135
+ setLayerVisible: (id: string, visible: boolean) => void
136
+ setLayersVisible: (ids: string[], visible: boolean) => void
137
+ setOpacity: (id: string, opacity: number) => void
138
+ // Tiled layers
139
+ tiledLayers: TiledLayerConfig[]
140
+ visibleTiledLayers: Set<string>
141
+ tiledLayerOpacities: Record<string, number>
142
+ toggleTiledLayer: (id: string) => void
143
+ setTiledLayerVisible: (id: string, visible: boolean) => void
144
+ setTiledLayerOpacity: (id: string, opacity: number) => void
145
+ // Data sources
146
+ dataSourcesState: Record<string, DataSourceState>
147
+ dataSourcesLoading: boolean
148
+ getDataSource: (id: string) => unknown | null
149
+ refetchDataSource: (id: string) => Promise<void>
150
+ }
151
+ ```
152
+
153
+ ## Components
154
+
155
+ All components are exported for building custom layouts:
156
+
157
+ ```typescript
158
+ import {
159
+ Layerboard, // Main framework component
160
+ LayerPanel, // Flat layer list with search/legends/opacity
161
+ MapPanel, // MapLibre map with layer rendering
162
+ TopicAccordion, // Collapsible accordion for topic grouping
163
+ LayerCheckboxSet, // Checkbox controls for layer toggling
164
+ LayerRadioButtonSet, // Radio buttons for mutually exclusive layers
165
+ } from "@phila/layerboard";
166
+ ```
167
+
168
+ ## Types
169
+
170
+ All types are exported:
171
+
172
+ ```typescript
173
+ import type {
174
+ LayerConfig,
175
+ LayerDisplayOptions,
176
+ LayerStyleOverride,
177
+ LegendItem,
178
+ PopupConfig,
179
+ PopupField,
180
+ PopupOverride,
181
+ TiledLayerConfig,
182
+ DataSourceConfig,
183
+ DataSourceState,
184
+ LayerboardConfig,
185
+ TopicConfig,
186
+ FeatureFlags,
187
+ CyclomediaConfig, // re-exported from @phila/phila-ui-map-core
188
+ PictometryCredentials, // re-exported from @phila/phila-ui-map-core
189
+ } from "@phila/layerboard";
54
190
  ```
55
191
 
56
- ### Compile and Hot-Reload for Development
192
+ ## How It Works
193
+
194
+ 1. You provide an ArcGIS Online **WebMap ID**
195
+ 2. Layerboard fetches the WebMap JSON at runtime
196
+ 3. Esri renderers, symbols, scales, and popup configs are transformed into MapLibre-compatible layer configs
197
+ 4. Layers render on a MapLibre GL map — feature data is fetched from ArcGIS FeatureServer endpoints with spatial filtering and pagination
198
+ 5. Server-side geometry simplification (`maxAllowableOffset`) scales with zoom level for polygon layers
199
+
200
+ ## Development
57
201
 
58
202
  ```sh
59
- npm run dev
203
+ pnpm install
204
+ pnpm build # type-check + vite build
60
205
  ```
61
206
 
62
- ### Type-Check, Compile and Minify for Production
207
+ ### Publishing
63
208
 
64
209
  ```sh
65
- npm run build
210
+ pnpm version prerelease # bump beta version
211
+ git push origin main
212
+ git tag v<version> && git push origin v<version> # triggers publish workflow
66
213
  ```
@@ -0,0 +1,36 @@
1
+ import { LayerConfig } from '../types/layer';
2
+ type __VLS_Props = {
3
+ /** Array of layer configurations to display */
4
+ layers: LayerConfig[];
5
+ /** Set of currently visible layer IDs */
6
+ visibleLayerIds: Set<string>;
7
+ /** Map of layer IDs to opacity values (0-1) */
8
+ layerOpacities?: Record<string, number>;
9
+ /** Set of layer IDs currently loading */
10
+ loadingLayerIds?: Set<string>;
11
+ /** Map of layer IDs to error messages */
12
+ layerErrors?: Record<string, string>;
13
+ /** Current map zoom level (for zoom-based availability) */
14
+ currentZoom?: number;
15
+ /** Whether to show opacity sliders (can be overridden per-layer) */
16
+ showOpacity?: boolean;
17
+ /** Whether to show legends (can be overridden per-layer) */
18
+ showLegend?: boolean;
19
+ /** Accessible label for the group */
20
+ groupLabel?: string;
21
+ };
22
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
23
+ toggleLayer: (layerId: string) => any;
24
+ setOpacity: (layerId: string, opacity: number) => any;
25
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
26
+ onToggleLayer?: ((layerId: string) => any) | undefined;
27
+ onSetOpacity?: ((layerId: string, opacity: number) => any) | undefined;
28
+ }>, {
29
+ layerOpacities: Record<string, number>;
30
+ layerErrors: Record<string, string>;
31
+ currentZoom: number;
32
+ showOpacity: boolean;
33
+ showLegend: boolean;
34
+ loadingLayerIds: Set<string>;
35
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLFieldSetElement>;
36
+ export default _default;
@@ -0,0 +1,63 @@
1
+ import { LayerConfig } from '../types/layer';
2
+ type __VLS_Props = {
3
+ /** Array of layer configurations with component type */
4
+ layerList: Array<{
5
+ config: LayerConfig;
6
+ component: string;
7
+ }>;
8
+ /** Set of currently visible layer IDs */
9
+ visibleLayers: Set<string>;
10
+ /** Map of layer IDs to opacity values (0-1) */
11
+ layerOpacities: Record<string, number>;
12
+ /** Set of layer IDs currently loading */
13
+ loadingLayers: Set<string>;
14
+ /** Map of layer IDs to error messages */
15
+ layerErrors: Record<string, string>;
16
+ /** Current map zoom level */
17
+ currentZoom: number;
18
+ /** Current search query */
19
+ searchQuery: string;
20
+ /** Map of layer URLs to metadata page URLs */
21
+ layerMetadata: Record<string, string>;
22
+ /** Display mode: 'flat' for simple list, 'topics' for accordion grouping */
23
+ mode?: "flat" | "topics";
24
+ /** Whether to show the search box */
25
+ showSearch?: boolean;
26
+ /** Whether to show opacity sliders */
27
+ showOpacity?: boolean;
28
+ /** Whether to show legends */
29
+ showLegend?: boolean;
30
+ /** Placeholder text for search input */
31
+ searchPlaceholder?: string;
32
+ };
33
+ declare function __VLS_template(): {
34
+ attrs: Partial<{}>;
35
+ slots: {
36
+ topics?(_: {}): any;
37
+ };
38
+ refs: {};
39
+ rootEl: HTMLDivElement;
40
+ };
41
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
42
+ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
43
+ toggleLayer: (layerId: string) => any;
44
+ setOpacity: (layerId: string, opacity: number) => any;
45
+ updateSearch: (query: string) => any;
46
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
47
+ onToggleLayer?: ((layerId: string) => any) | undefined;
48
+ onSetOpacity?: ((layerId: string, opacity: number) => any) | undefined;
49
+ onUpdateSearch?: ((query: string) => any) | undefined;
50
+ }>, {
51
+ mode: "flat" | "topics";
52
+ showSearch: boolean;
53
+ showOpacity: boolean;
54
+ showLegend: boolean;
55
+ searchPlaceholder: string;
56
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
57
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
58
+ export default _default;
59
+ type __VLS_WithTemplateSlots<T, S> = T & {
60
+ new (): {
61
+ $slots: S;
62
+ };
63
+ };
@@ -0,0 +1,39 @@
1
+ import { LayerConfig } from '../types/layer';
2
+ type __VLS_Props = {
3
+ /** Array of layer configurations to display */
4
+ layers: LayerConfig[];
5
+ /** Set of currently visible layer IDs */
6
+ visibleLayerIds: Set<string>;
7
+ /** Map of layer IDs to opacity values (0-1) */
8
+ layerOpacities?: Record<string, number>;
9
+ /** Set of layer IDs currently loading */
10
+ loadingLayerIds?: Set<string>;
11
+ /** Map of layer IDs to error messages */
12
+ layerErrors?: Record<string, string>;
13
+ /** Current map zoom level (for zoom-based availability) */
14
+ currentZoom?: number;
15
+ /** Whether to show opacity sliders (can be overridden per-layer) */
16
+ showOpacity?: boolean;
17
+ /** Whether to show legends (can be overridden per-layer) */
18
+ showLegend?: boolean;
19
+ /** Unique name for the radio button group */
20
+ groupName?: string;
21
+ /** Accessible label for the group */
22
+ groupLabel?: string;
23
+ };
24
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
25
+ setOpacity: (layerId: string, opacity: number) => any;
26
+ selectLayer: (layerId: string, previousLayerIds: string[]) => any;
27
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
28
+ onSetOpacity?: ((layerId: string, opacity: number) => any) | undefined;
29
+ onSelectLayer?: ((layerId: string, previousLayerIds: string[]) => any) | undefined;
30
+ }>, {
31
+ layerOpacities: Record<string, number>;
32
+ layerErrors: Record<string, string>;
33
+ currentZoom: number;
34
+ showOpacity: boolean;
35
+ showLegend: boolean;
36
+ loadingLayerIds: Set<string>;
37
+ groupName: string;
38
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
39
+ export default _default;