@tileflow/vue 0.1.0-alpha.22 → 0.1.0-alpha.24

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,8 +1,65 @@
1
1
  # @tileflow/vue
2
2
 
3
- Vue component for rendering Tileflow maps with MapLibre.
3
+ Vue components for interactive Tileflow maps, annotations, and existing-image display.
4
+ The component reads prepared manifests and styles; it does not compile `tileflow.config.ts` in the
5
+ browser.
4
6
 
5
- Install `maplibre-gl` alongside the package and import its CSS once in your app.
7
+ > Related packages and guides: [documentation index](https://raw.githubusercontent.com/tileflow/tileflow-sdk/main/llms.txt).
8
+
9
+ ## Install
10
+
11
+ Use an existing Vue 3 application. Supported peers are Vue `>=3.3 <4` and MapLibre GL JS
12
+ `>=6.4.1 <7`. The local build workflow below requires Node.js 22 or newer.
13
+
14
+ ```sh
15
+ npm install @tileflow/vue@alpha @tileflow/core@alpha @tileflow/maps@alpha "maplibre-gl@^6.4.1"
16
+ npm install --save-dev --save-exact tileflow@alpha
17
+ ```
18
+
19
+ ## Prepare a map
20
+
21
+ Create `tileflow.config.ts` at the application root:
22
+
23
+ <!-- docs:check -->
24
+
25
+ ```ts
26
+ import {defineMap} from '@tileflow/core';
27
+ import {streets} from '@tileflow/maps';
28
+
29
+ export default defineMap({
30
+ id: 'madrid',
31
+ version: 1,
32
+ extends: streets,
33
+ view: {center: [-3.7038, 40.4168], zoom: 12},
34
+ });
35
+ ```
36
+
37
+ ```sh
38
+ npx tileflow build --out public/tileflow
39
+ ```
40
+
41
+ Serve `public/` at the application's URL root. Rebuild after map or asset changes, or use the
42
+ [Vite integration](https://github.com/tileflow/tileflow-sdk/blob/main/packages/vite/README.md)
43
+ for watched development and production output. Local build and validation need no API key;
44
+ rendering may still request configured remote tiles and fonts.
45
+
46
+ ## Configure the worker
47
+
48
+ For Vite, add the following to the existing client entry before `createApp(...).mount(...)`:
49
+
50
+ ```ts
51
+ import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
52
+ import {configureTileflowMapLibre} from '@tileflow/vue';
53
+
54
+ configureTileflowMapLibre({workerUrl});
55
+ ```
56
+
57
+ Configure this once before mounting an interactive map. The worker must come from the same installed
58
+ MapLibre package as the main module. The `?worker&url` import is Vite-specific; use the
59
+ [Webpack recipe](https://github.com/tileflow/tileflow-sdk/blob/main/packages/webpack/README.md)
60
+ for Webpack. Keep MapLibre's CSS in the client application as well.
61
+
62
+ ## Render a map
6
63
 
7
64
  ```vue
8
65
  <script setup lang="ts">
@@ -16,125 +73,66 @@ import {TileflowMap} from '@tileflow/vue';
16
73
  theme="system"
17
74
  :center="[-3.7038, 40.4168]"
18
75
  :zoom="12"
19
- :map-options="{
20
- cooperativeGestures: true,
21
- maxZoom: 18,
22
- }"
76
+ :height="420"
77
+ :map-options="{cooperativeGestures: true, maxZoom: 18}"
23
78
  />
24
79
  </template>
25
80
  ```
26
81
 
27
- `mapOptions` accepts native MapLibre options except `container` and `style`, which
28
- Tileflow resolves from `source`. Direct
29
- Tileflow props such as `center`, `zoom`, and `interactive` take priority when
30
- provided. Camera resolution is the same in interactive and image modes: direct props, then
31
- `mapOptions`, then the published manifest view, then Tileflow's shared runtime defaults.
82
+ Coordinates are `[longitude, latitude]`. `source.map` is the config's portable ID, not a hosted
83
+ `map_...` identifier. The default manifest URL is exactly `/tileflow/manifest.json`; provide
84
+ `source.manifestUrl` explicitly for a subpath, reverse proxy, or external host. The component does
85
+ not infer build-tool configuration.
32
86
 
33
- Every map has exactly one discriminated `source`. Use `kind: 'tileflow'` with `map` and an optional
34
- `manifestUrl` for published delivery. `kind: 'maplibre'` is an unmanaged escape hatch for one direct
35
- style object or URL; it has no Tileflow theme identity, `system` selection, switching, or manifest
36
- traceability. Browser components do not compile `tileflow.config.ts`.
87
+ Omitting `theme` uses `defaultTheme`. `system` needs the map's explicit light/dark mapping, which
88
+ Streets supplies. Switching themes preserves the map, camera, and interactions, with rollback on
89
+ failure. Listen to `themeChange` for transitions.
37
90
 
38
- Without `manifestUrl`, the exact default is `/tileflow/manifest.json`. If a bundler, framework base
39
- path, reverse proxy, or Tileflow plugin publishes it elsewhere, set the final public URL explicitly;
40
- the component does not guess it. Manifest 404s, unknown map IDs, unresolved styles, and unresolved
41
- image URLs enter `data-tileflow-state="error"`.
91
+ `mapOptions` accepts native MapLibre options except `container` and `style`. Direct props take
92
+ priority over those options, then the published view, then shared defaults. An unmanaged
93
+ `source: {kind: 'maplibre', style: styleUrl}` loads a single style without Tileflow theme selection
94
+ or manifest traceability; do not combine it with a Tileflow theme.
42
95
 
43
- `theme` selects a published theme name; omission uses `defaultTheme`, while `"system"` requires the
44
- map's explicit light/dark mapping. Switching themes preserves the MapLibre instance, camera, and
45
- interaction state, with rollback on failure. Listen to `themeChange` for transition state.
96
+ ## Add annotations and native popup UI
46
97
 
47
- ```vue
48
- <TileflowMap
49
- :source="{
50
- kind: 'tileflow',
51
- map: 'madrid',
52
- manifestUrl: 'https://cdn.example.com/tileflow/manifest.json',
53
- }"
54
- />
55
-
56
- <TileflowMap
57
- :source="{
58
- kind: 'maplibre',
59
- style: 'https://cdn.example.com/tileflow/styles/madrid/dark.json',
60
- }"
61
- />
62
- ```
98
+ Install the contracts when importing them directly:
63
99
 
64
- `mode="image"` resolves an explicit or published image URL and renders an `<img>` without loading
65
- or evaluating MapLibre. All environments follow the same published manifest contract.
66
-
67
- ## Annotations, semantic POIs, and native Vue views
100
+ ```sh
101
+ npm install @tileflow/interactions@alpha
102
+ ```
68
103
 
69
- Use `annotations` for small, application-owned DOM markers. The portable annotation data and
70
- interaction state remain JSON-safe. Use `interactions` to bind tooltip or popup behavior to
71
- manifest-declared semantic POIs already rendered by the map style; this avoids creating one DOM
72
- marker per feature. Scoped slots render native Vue UI into hosts owned by the shared interaction
73
- runtime.
104
+ This component uses the worker and CSS setup above:
74
105
 
75
106
  ```vue
76
107
  <script setup lang="ts">
77
108
  import {ref} from 'vue';
78
- import type {
79
- TileflowAnnotation,
80
- TileflowInteractionBinding,
81
- TileflowInteractionState,
82
- } from '@tileflow/interactions';
109
+ import type {TileflowAnnotation, TileflowInteractionState} from '@tileflow/interactions';
83
110
  import {TileflowMap} from '@tileflow/vue';
84
111
 
85
- type Property = {address: string; price: number};
86
-
87
112
  const annotations = [
88
113
  {
89
- kind: 'marker',
90
114
  id: 'property-42',
91
- coordinate: [-3.7, 40.4],
115
+ kind: 'marker',
116
+ coordinate: [-3.7038, 40.4168],
92
117
  ariaLabel: 'Apartment in Madrid',
93
- data: {address: 'Calle Mayor', price: 320_000},
94
- tooltip: {content: {kind: 'field', field: 'address'}},
118
+ tooltip: {content: {kind: 'text', text: 'View apartment'}},
95
119
  popup: {content: {kind: 'view', name: 'property-card'}},
96
120
  },
97
- ] satisfies readonly TileflowAnnotation<Property>[];
121
+ ] satisfies readonly TileflowAnnotation[];
98
122
 
99
- const interactions = [
100
- {
101
- id: 'restaurant-details',
102
- target: {kind: 'semantic-feature', domain: 'poi', categories: ['food-drink']},
103
- tooltip: {content: {kind: 'field', field: 'name', fallback: 'Restaurant'}},
104
- popup: {content: {kind: 'view', name: 'poi-card'}},
105
- },
106
- ] satisfies readonly TileflowInteractionBinding[];
107
-
108
- const interactionState = ref<TileflowInteractionState>({popup: null});
123
+ const state = ref<TileflowInteractionState>({popup: null});
109
124
  </script>
110
125
 
111
126
  <template>
112
127
  <TileflowMap
113
- v-model:interaction-state="interactionState"
128
+ v-model:interaction-state="state"
114
129
  :source="{kind: 'tileflow', map: 'madrid'}"
115
130
  :annotations="annotations"
116
- :interactions="interactions"
117
- @interaction-event="(event) => console.log(event.type, event.target)"
118
- @interaction-diagnostic="(diagnostic) => console.warn(diagnostic.code)"
131
+ @interaction-diagnostic="(diagnostic) => console.error(diagnostic.code)"
119
132
  >
120
- <template #marker="{annotation}">
121
- <span class="price-marker">{{ annotation.data.price.toLocaleString() }}</span>
122
- </template>
123
-
124
- <template #tooltip="{target}">
125
- <span v-if="target.kind === 'annotation'">{{ target.annotation.data.address }}</span>
126
- <span v-else-if="target.kind === 'semantic-feature'">
127
- {{ target.feature.properties.name }}
128
- </span>
129
- </template>
130
-
131
133
  <template #popup="{target, close}">
132
134
  <article v-if="target.kind === 'annotation'">
133
- <strong>{{ target.annotation.data.address }}</strong>
134
- <button type="button" @click="close">Close</button>
135
- </article>
136
- <article v-else-if="target.kind === 'semantic-feature'">
137
- <strong>{{ target.feature.properties.name }}</strong>
135
+ <h2>{{ target.annotation.ariaLabel }}</h2>
138
136
  <button type="button" @click="close">Close</button>
139
137
  </article>
140
138
  </template>
@@ -142,58 +140,49 @@ const interactionState = ref<TileflowInteractionState>({popup: null});
142
140
  </template>
143
141
  ```
144
142
 
145
- Omit `interactionState` and use `default-interaction-state` for uncontrolled state. Do not provide
146
- both or switch ownership modes during a component instance's lifetime. `annotations` is the only
147
- application-owned marker input and uses singular `coordinate` plus required `ariaLabel`.
148
-
149
- The `marker` slot is annotation-only and preserves the annotation's generic `data` type.
150
- `tooltip` and `popup` receive the general `TileflowInteractionViewContext`; narrow
151
- `target.kind` to access either `target.annotation` or the resolved semantic feature. Semantic POI
152
- bindings require a compatible `tileflow:interaction-manifest` in the active style. Bindings are
153
- validated before reaching MapLibre, POI hit tests are coalesced with `requestAnimationFrame`, and
154
- annotation plus semantic runtimes share one controlled or uncontrolled popup state.
155
- Values in `target.categories` use Tileflow's semantic taxonomy (for example `food` or `coffee`),
156
- not raw OpenMapTiles classes.
157
-
158
- Text content is inserted as text, `field` is a bounded declarative lookup, and `view` is only a
159
- dispatch name for the corresponding application-owned scoped slot. No interaction path evaluates
160
- HTML. Passing annotations, semantic interactions, or interaction state to `mode="image"` emits an `UNSUPPORTED_MODE`
161
- diagnostic, marks capture readiness as `error`, and still does not load or evaluate MapLibre.
162
-
163
- Changing annotations, semantic bindings, slots, state listeners, or diagnostic listeners
164
- reconciles the existing runtimes; it does not recreate the map. The semantic runtime is initialized
165
- lazily, and the component and its interaction imports remain SSR-safe.
166
-
167
- Hosted maps automatically preflight a short-lived commercial session grant before eligible
168
- resources. Setting `analytics` with `enabled: false` disables the optional beacon only; it does not
169
- remove hosted authorization or override a user `mapOptions.transformRequest` callback.
170
- Use `:analytics="{surfaceId: 'store-locator'}"` when the same Map is embedded in several stable
171
- product locations. Missing or invalid Surface IDs become `default`; do not use URLs, branches,
172
- random instance IDs, or user IDs.
173
- Direct Tileflow World maps keep early `GRACE` silent, show a compact accessible owner-action pill
174
- when late `GRACE` is activated, and show a stronger banner in `MANAGED_REQUIRED`. Missing tiles and
175
- MapLibre failures do not remove that recovery path.
176
-
177
- ## Headless capture readiness
178
-
179
- Pass `capture-id` when a page contains multiple copies of the same named map:
143
+ Annotations require a stable `id`, singular `coordinate`, and non-empty `ariaLabel`; optional data
144
+ must be JSON-safe. `marker` receives annotation-only context. `tooltip` and `popup` receive the
145
+ shared interaction context; narrow `target.kind` before reading annotation or semantic feature data.
180
146
 
181
- ```vue
182
- <TileflowMap :source="{kind: 'tileflow', map: 'madrid'}" capture-id="checkout-map" />
183
- ```
147
+ Use `interactions` with `target: {kind: 'semantic-feature', domain: 'poi'}` for POIs already rendered
148
+ by the style. Categories use Tileflow's taxonomy, such as `food-drink` or `retail`, not raw source
149
+ classes. Semantic bindings require compatible metadata in the active style and avoid creating one
150
+ DOM marker per feature.
151
+
152
+ Omit controlled state and use `default-interaction-state` for uncontrolled ownership. Do not supply
153
+ both or change ownership during a component's lifetime. Annotations, bindings, slots, and callbacks
154
+ reconcile without recreating MapLibre. Text/field descriptors render as text; `view` dispatches to
155
+ an application slot and never evaluates HTML. Tooltips must remain non-interactive; use a popup for
156
+ links, buttons, or forms.
157
+
158
+ ## Display an existing image
159
+
160
+ `mode="image"` renders an explicit `imageUrl` or a published manifest image without loading MapLibre.
161
+ A local style build does not generate an image. This mode does not create or poll a static render;
162
+ use the server-side [Static Maps client](https://github.com/tileflow/tileflow-sdk/blob/main/packages/static/README.md)
163
+ for that operation and keep privileged keys on the server.
164
+
165
+ Do not pass annotations, semantic bindings, state, or interaction slots to image mode. Unsupported
166
+ interaction input reports `UNSUPPORTED_MODE` and capture readiness `error`; it does not silently
167
+ pretend the image contains live overlays.
168
+
169
+ ## Capture, authorization, and troubleshooting
184
170
 
185
- The root exposes `data-tileflow-map`, optional `data-tileflow-capture-id`, and
186
- `data-tileflow-state="loading|idle|error"`. It becomes idle only after MapLibre idle plus two
187
- animation frames (or image decode/load in image mode). A change to custom tooltip/popup/marker
188
- render-target keys independently returns interaction readiness to loading until Vue commits the
189
- Teleports and two current animation frames pass. Map and interaction errors are combined;
190
- application capture selects exactly one ready target.
171
+ Pass `capture-id` when multiple components show the same map. The root exposes `data-tileflow-map`,
172
+ resolved `data-tileflow-theme`, optional `data-tileflow-capture-id`, and
173
+ `data-tileflow-state="loading|idle|error"`. Readiness includes MapLibre or image loading plus committed
174
+ Vue interaction views. Application capture needs exactly one ready target and the application's
175
+ normal loopback server.
191
176
 
192
- ## Compatibility
177
+ For a blank/error map, check that the manifest URL returns JSON, the map and theme exist, MapLibre
178
+ CSS is imported, and the matching worker/shared assets are served. Manifest 404s and unresolved
179
+ styles/images are errors; the component does not guess another URL.
193
180
 
194
- The supported peer window is Vue 3.3-3.x and MapLibre GL JS 5-6. Compatibility smoke tests install
195
- Vue 3.3.0 with the first release of both MapLibre majors from packed Tileflow tarballs, typecheck a
196
- consumer, and render the image/SSR path while rejecting any MapLibre import. Vue 4 stays outside the
197
- peer range until that matrix passes.
181
+ Hosted maps preflight a short-lived session grant. `:analytics="{enabled: false}"` disables only the
182
+ optional beacon, not authorization. Use a stable product `surfaceId` rather than a user ID or URL.
183
+ Direct World maps can show owner-action notices when managed delivery is required. Imports and the
184
+ image path remain SSR-safe; interactive rendering needs a browser with WebGL.
198
185
 
199
- Docs: https://tileflow.dev/docs
186
+ See the [interaction guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/interactions/README.md)
187
+ and [browser runtime contract](https://github.com/tileflow/tileflow-sdk/blob/main/docs/contracts/framework-browser-runtime.md).
188
+ Use the installed package's declarations and README for a release; `main` can include newer changes.
package/dist/index.d.ts CHANGED
@@ -9,6 +9,12 @@ type TileflowMapStyleSourceProps = {
9
9
  theme?: TileflowThemeSelection;
10
10
  };
11
11
 
12
+ type TileflowMapLibreConfiguration = Readonly<{
13
+ workerUrl: string;
14
+ }>;
15
+ /** Configure the application-owned worker before mounting an interactive Tileflow map. */
16
+ declare function configureTileflowMapLibre(configuration: TileflowMapLibreConfiguration): void;
17
+
12
18
  type TileflowMapMode = 'interactive' | 'image';
13
19
  type TileflowMapOptions = Omit<MapOptions, 'container' | 'style'>;
14
20
  type TileflowMapSource = TileflowRuntimeSource;
@@ -78,4 +84,4 @@ type PublicTileflowMapComponent = PublicTileflowMapStatics & {
78
84
  };
79
85
  declare const TileflowMap: PublicTileflowMapComponent;
80
86
 
81
- export { TileflowMap, type TileflowMapAnnotationSlotContext, type TileflowMapInteractionSlotContext, type TileflowMapMode, type TileflowMapOptions, type TileflowMapProps, type TileflowMapSlots, type TileflowMapSource, TileflowMap as default };
87
+ export { TileflowMap, type TileflowMapAnnotationSlotContext, type TileflowMapInteractionSlotContext, type TileflowMapLibreConfiguration, type TileflowMapMode, type TileflowMapOptions, type TileflowMapProps, type TileflowMapSlots, type TileflowMapSource, configureTileflowMapLibre, TileflowMap as default };
package/dist/index.js CHANGED
@@ -126,8 +126,21 @@ function jsonPointer(path) {
126
126
 
127
127
  // src/maplibre.ts
128
128
  var mapLibrePromise;
129
+ var mapLibre;
130
+ var workerUrl;
131
+ function configureTileflowMapLibre(configuration) {
132
+ const nextWorkerUrl = configuration.workerUrl;
133
+ if (typeof nextWorkerUrl !== "string" || nextWorkerUrl.trim().length === 0) {
134
+ throw new TypeError("Tileflow MapLibre workerUrl must be a non-empty string.");
135
+ }
136
+ if (workerUrl !== void 0 && workerUrl !== nextWorkerUrl) {
137
+ throw new Error("Tileflow MapLibre workerUrl is already configured.");
138
+ }
139
+ workerUrl = nextWorkerUrl;
140
+ mapLibre?.setWorkerUrl(nextWorkerUrl);
141
+ }
129
142
  function loadTileflowMapLibre() {
130
- mapLibrePromise ??= import("maplibre-gl").then(resolveMapLibreModule).catch((error) => {
143
+ mapLibrePromise ??= import("maplibre-gl").then(resolveMapLibreModule).then(configureTileflowMapLibreModule).catch((error) => {
131
144
  mapLibrePromise = void 0;
132
145
  throw error;
133
146
  });
@@ -137,6 +150,11 @@ function resolveMapLibreModule(module) {
137
150
  const defaultExport = module.default;
138
151
  return defaultExport ?? module;
139
152
  }
153
+ function configureTileflowMapLibreModule(maplibregl) {
154
+ if (workerUrl !== void 0) maplibregl.setWorkerUrl(workerUrl);
155
+ mapLibre = maplibregl;
156
+ return maplibregl;
157
+ }
140
158
 
141
159
  // src/style-source.ts
142
160
  import {
@@ -1122,9 +1140,10 @@ function interactionDiagnosticKey(diagnostic) {
1122
1140
  return JSON.stringify([diagnostic.code, diagnostic.message, diagnostic.path, diagnostic.target]);
1123
1141
  }
1124
1142
  function createTileflowVuePoiMap(map) {
1143
+ const poiMap = map;
1125
1144
  return {
1126
1145
  getStyle: () => map.getStyle(),
1127
- on: (event, listener) => map.on(event, listener),
1146
+ on: (event, listener) => poiMap.on(event, listener),
1128
1147
  queryRenderedFeatures(point, options) {
1129
1148
  return map.queryRenderedFeatures(point, {
1130
1149
  layers: [...options.layers]
@@ -1140,5 +1159,6 @@ function createTileflowVuePoiMap(map) {
1140
1159
  }
1141
1160
  export {
1142
1161
  TileflowMap,
1162
+ configureTileflowMapLibre,
1143
1163
  index_default as default
1144
1164
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/vue",
3
- "version": "0.1.0-alpha.22",
3
+ "version": "0.1.0-alpha.24",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Vue component for rendering Tileflow maps with MapLibre.",
6
6
  "type": "module",
@@ -38,16 +38,16 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@tileflow/core": ">=0.1.0-alpha.24 <0.1.0-beta.0",
42
- "@tileflow/interactions": ">=0.1.0-alpha.2 <0.1.0-beta.0"
41
+ "@tileflow/core": ">=0.1.0-alpha.25 <0.1.0-beta.0",
42
+ "@tileflow/interactions": ">=0.1.0-alpha.4 <0.1.0-beta.0"
43
43
  },
44
44
  "peerDependencies": {
45
- "maplibre-gl": ">=5 <7",
45
+ "maplibre-gl": ">=6.4.1 <7",
46
46
  "vue": ">=3.3 <4"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@vue/server-renderer": "3.5.39",
50
- "maplibre-gl": "^5.24.0",
50
+ "maplibre-gl": "6.9.0",
51
51
  "tsx": "^4.23.12",
52
52
  "vue": "^3.5.26"
53
53
  },