@tileflow/svelte 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,13 +1,75 @@
1
1
  # @tileflow/svelte
2
2
 
3
- Svelte component for rendering Tileflow maps with MapLibre.
3
+ Svelte components for interactive Tileflow maps, annotations, and existing-image display.
4
+ The component consumes prepared assets; it does not compile executable map configuration 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 Svelte 5 application. Supported peers are Svelte `>=5 <6` 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/svelte@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
+ For a Vite application serving `public/` at the URL root:
38
+
39
+ ```sh
40
+ npx tileflow build --out public/tileflow
41
+ ```
42
+
43
+ SvelteKit serves static files from `static/` by default; for that setup, build to `static/tileflow`
44
+ instead. Set the component's manifest URL to the final public URL when a base path changes it.
45
+ Rebuild after config/asset changes or use the
46
+ [Vite integration](https://github.com/tileflow/tileflow-sdk/blob/main/packages/vite/README.md)
47
+ for watched development and production output. Local preparation needs no API key; rendering can
48
+ still fetch configured remote resources.
49
+
50
+ ## Configure the worker
51
+
52
+ In a Vite client entry, add the following before mounting the application:
53
+
54
+ ```ts
55
+ import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
56
+ import {configureTileflowMapLibre} from '@tileflow/svelte';
57
+
58
+ configureTileflowMapLibre({workerUrl});
59
+ ```
60
+
61
+ Configure once before an interactive map mounts. In SvelteKit, place client-only setup in the
62
+ application's client initialization path rather than running browser setup in a server load
63
+ function. The `?worker&url` syntax is Vite-specific. Webpack users need the
64
+ [Webpack worker recipe](https://github.com/tileflow/tileflow-sdk/blob/main/packages/webpack/README.md).
65
+ The worker and shared module must match the application's installed MapLibre version.
66
+
67
+ ## Render a map
6
68
 
7
69
  ```svelte
8
70
  <script lang="ts">
9
- import {TileflowMap} from '@tileflow/svelte';
10
71
  import 'maplibre-gl/dist/maplibre-gl.css';
72
+ import {TileflowMap} from '@tileflow/svelte';
11
73
  </script>
12
74
 
13
75
  <TileflowMap
@@ -15,180 +77,120 @@ Install `maplibre-gl` alongside the package and import its CSS once in your app.
15
77
  theme="system"
16
78
  center={[-3.7038, 40.4168]}
17
79
  zoom={12}
18
- mapOptions={{
19
- cooperativeGestures: true,
20
- maxZoom: 18,
21
- }}
80
+ mapOptions={{cooperativeGestures: true, maxZoom: 18}}
22
81
  />
23
82
  ```
24
83
 
25
- `mapOptions` accepts native MapLibre options except `container` and `style`, which
26
- Tileflow resolves from `source`. Direct
27
- Tileflow props such as `center`, `zoom`, and `interactive` take priority when
28
- provided. Camera resolution is the same in interactive and image modes: direct props, then
29
- `mapOptions`, then the published manifest view, then Tileflow's shared runtime defaults.
84
+ Coordinates are `[longitude, latitude]`. `source.map` matches the config's portable ID, not a hosted
85
+ `map_...` identifier. The default manifest URL is exactly `/tileflow/manifest.json`. Set
86
+ `source.manifestUrl` for a subpath or external host; the browser does not infer bundler configuration.
30
87
 
31
- Every map has exactly one discriminated `source`. Use `kind: 'tileflow'` with `map` and an optional
32
- `manifestUrl` for published delivery. `kind: 'maplibre'` is an unmanaged escape hatch for one direct
33
- style object or URL; it has no Tileflow theme identity, `system` selection, switching, or manifest
34
- traceability. Browser components do not compile `tileflow.config.ts`.
88
+ Omitting `theme` uses `defaultTheme`. `system` needs an explicit light/dark mapping, which Streets
89
+ supplies. Theme changes keep the MapLibre instance, camera, and interactions, and roll back on
90
+ failure. `onThemeChange` reports transitions.
35
91
 
36
- Without `manifestUrl`, the exact default is `/tileflow/manifest.json`. If a bundler, framework base
37
- path, reverse proxy, or Tileflow plugin publishes it elsewhere, set the final public URL explicitly;
38
- the component does not guess it. Manifest 404s, unknown map IDs, unresolved styles, and unresolved
39
- image URLs enter `data-tileflow-state="error"`.
92
+ `mapOptions` accepts native MapLibre options except `container` and `style`. Direct props win over
93
+ those options, then the manifest view, then shared defaults. An unmanaged source with
94
+ `{kind: 'maplibre', style: styleUrl}` loads one style without Tileflow themes or manifest identity;
95
+ do not pass a Tileflow theme with it.
40
96
 
41
- `theme` selects a published theme name. Omission uses `defaultTheme`; `"system"` requires the map's
42
- explicit light/dark mapping. Switching preserves the MapLibre instance, camera, and interactions,
43
- and rolls back a failed style. `onThemeChange` receives transition state.
97
+ ## Add annotations and native popup UI
44
98
 
45
- ```svelte
46
- <TileflowMap
47
- source={{
48
- kind: 'tileflow',
49
- map: 'madrid',
50
- manifestUrl: 'https://cdn.example.com/tileflow/manifest.json',
51
- }}
52
- />
99
+ Install the contract types used below:
53
100
 
54
- <TileflowMap
55
- source={{
56
- kind: 'maplibre',
57
- style: 'https://cdn.example.com/tileflow/styles/madrid/dark.json',
58
- }}
59
- />
101
+ ```sh
102
+ npm install @tileflow/interactions@alpha
60
103
  ```
61
104
 
62
- `mode="image"` resolves an explicit or published image URL and renders an `<img>` without loading
63
- or evaluating MapLibre. All environments follow the same published manifest contract.
64
-
65
- Hosted maps automatically preflight a short-lived commercial session grant before eligible
66
- resources. Setting `analytics={{enabled: false}}` disables the optional beacon only; it does not
67
- remove hosted authorization or override a user `mapOptions.transformRequest` callback.
68
- Use `analytics={{surfaceId: 'store-locator'}}` when the same Map is embedded in several stable
69
- product locations. Missing or invalid Surface IDs become `default`; do not use URLs, branches,
70
- random instance IDs, or user IDs.
71
- Direct Tileflow World maps keep early `GRACE` silent, show a compact accessible owner-action pill
72
- when late `GRACE` is activated, and show a stronger banner in `MANAGED_REQUIRED`. Missing tiles and
73
- MapLibre failures do not remove that recovery path.
74
-
75
- ## Annotations and overlays
76
-
77
- Use `annotations` for keyed markers with optional tooltips and popups. Tileflow supplies accessible,
78
- text-only defaults; Svelte snippets can replace any surface and are mounted into the DOM containers
79
- owned by the shared MapLibre interaction runtime.
105
+ This Svelte 5 component uses the worker and CSS setup above:
80
106
 
81
107
  ```svelte
82
108
  <script lang="ts">
83
- import {TileflowMap} from '@tileflow/svelte';
84
109
  import type {
85
110
  TileflowAnnotation,
86
- TileflowInteractionBinding,
87
111
  TileflowInteractionState,
112
+ TileflowInteractionViewContext,
88
113
  } from '@tileflow/interactions';
114
+ import {TileflowMap} from '@tileflow/svelte';
89
115
 
90
- const annotations: TileflowAnnotation[] = [
116
+ const annotations = [
91
117
  {
92
- ariaLabel: 'Madrid',
93
- coordinate: [-3.7038, 40.4168],
94
118
  id: 'madrid',
95
119
  kind: 'marker',
96
- marker: {content: {kind: 'text', text: 'Madrid'}},
120
+ coordinate: [-3.7038, 40.4168],
121
+ ariaLabel: 'Madrid',
122
+ tooltip: {content: {kind: 'text', text: 'View Madrid'}},
97
123
  popup: {content: {kind: 'view', name: 'city-card'}},
98
- tooltip: {content: {kind: 'text', text: 'Open Madrid'}},
99
124
  },
100
- ];
125
+ ] satisfies readonly TileflowAnnotation[];
101
126
 
102
- const interactions: TileflowInteractionBinding[] = [
103
- {
104
- id: 'poi-details',
105
- popup: {content: {kind: 'view', name: 'poi-card'}},
106
- target: {categories: ['food-drink'], domain: 'poi', kind: 'semantic-feature'},
107
- tooltip: {content: {field: 'name', fallback: 'Restaurant', kind: 'field'}},
108
- },
109
- ];
110
-
111
- let interactionState: TileflowInteractionState = {popup: null};
127
+ let state = $state<TileflowInteractionState>({popup: null});
112
128
  </script>
113
129
 
114
- {#snippet marker(context)}
115
- <span>{context.annotation.ariaLabel}</span>
116
- {/snippet}
117
-
118
- {#snippet tooltip(context)}
130
+ {#snippet popup(context: TileflowInteractionViewContext)}
119
131
  {#if context.target.kind === 'annotation'}
120
- <span>{context.target.annotation.ariaLabel}</span>
121
- {:else}
122
- <span>Point of interest</span>
123
- {/if}
124
- {/snippet}
125
-
126
- {#snippet popup(context)}
127
- <article>
128
- {#if context.target.kind === 'annotation'}
132
+ <article>
129
133
  <h2>{context.target.annotation.ariaLabel}</h2>
130
- {:else if context.target.kind === 'semantic-feature'}
131
- <h2>POI details</h2>
132
- {/if}
133
- <button type="button" onclick={context.close}>Close</button>
134
- </article>
134
+ <button type="button" onclick={context.close}>Close</button>
135
+ </article>
136
+ {/if}
135
137
  {/snippet}
136
138
 
137
139
  <TileflowMap
138
140
  source={{kind: 'tileflow', map: 'madrid'}}
139
141
  {annotations}
140
- {interactions}
141
- {interactionState}
142
- onInteractionStateChange={(next) => (interactionState = next)}
143
- onInteractionEvent={(event) => console.log(event.type)}
144
- onInteractionDiagnostic={(diagnostic) => console.error(diagnostic)}
145
- {marker}
146
- {tooltip}
142
+ interactionState={state}
143
+ onInteractionStateChange={(next) => (state = next)}
144
+ onInteractionDiagnostic={(diagnostic) => console.error(diagnostic.code)}
147
145
  {popup}
148
146
  />
149
147
  ```
150
148
 
151
- `annotations` is the only application-owned marker input. Controlled `interactionState` and
152
- uncontrolled `defaultInteractionState` are mutually exclusive. `interactions` binds tooltips and
153
- popups to semantic POIs exposed by the compiled style metadata; applications select the stable
154
- `poi` domain and optional categories rather than physical style-layer IDs.
149
+ Annotations need a stable `id`, singular `coordinate`, and non-empty `ariaLabel`; optional data must
150
+ be JSON-safe. `marker` receives annotation-only context. `tooltip` and `popup` receive the shared
151
+ interaction context; narrow `target.kind` before reading annotation or semantic feature data.
155
152
 
156
- Snippet callbacks are read at the time an interaction occurs, and adding or removing a snippet
157
- switches between custom and default rendering without recreating the map or its keyed markers.
158
- `marker` receives the annotation-only context. `tooltip` and `popup` receive the general interaction
159
- context and therefore handle both annotation and semantic-feature targets. Each context includes
160
- resolved content, the target, optional view name, and `close()`.
153
+ Use `interactions` with `target: {kind: 'semantic-feature', domain: 'poi'}` for POIs already rendered
154
+ by the style. Optional categories use Tileflow's taxonomy, such as `food-drink` or `retail`, not raw
155
+ source classes. Semantic bindings require compatible style metadata and avoid one DOM marker per
156
+ feature. Annotation and semantic overlays share one popup state.
161
157
 
162
- Both runtimes are controlled by one interaction-state coordinator. Replacing an annotation popup
163
- with a POI popup (or the reverse) closes the previous owner before opening the next one, so event
164
- order remains `popup:close` followed by `popup:open`.
158
+ Controlled `interactionState` and uncontrolled `defaultInteractionState` are mutually exclusive.
159
+ Do not change ownership during the component's lifetime. Updating annotations, bindings, state, or
160
+ snippets reconciles existing runtimes rather than recreating MapLibre.
165
161
 
166
- Annotations and semantic interactions are interactive-only. The TypeScript API excludes
167
- annotations, interactions, interaction state, snippets, and interaction callbacks from the
168
- `mode="image"` branch. Untyped JavaScript that supplies them still receives an
169
- `UNSUPPORTED_MODE` diagnostic and capture readiness `error`, rather than pretending the static image
170
- contains live overlays.
162
+ Text/field descriptors render as text. A `view` names an application snippet; it is not serialized
163
+ HTML. Keep tooltips non-interactive and put buttons, links, and forms in popups.
171
164
 
172
- ## Headless capture readiness
165
+ ## Display an existing image
173
166
 
174
- Use `captureId` to disambiguate repeated named maps:
167
+ `mode="image"` displays an explicit `imageUrl` or a published manifest image without loading
168
+ MapLibre. A local style build does not generate that image. This mode does not submit or poll a
169
+ Static Maps operation; use the server-side
170
+ [Static Maps client](https://github.com/tileflow/tileflow-sdk/blob/main/packages/static/README.md)
171
+ for new renders, keeping privileged credentials off the browser.
175
172
 
176
- ```svelte
177
- <TileflowMap source={{kind: 'tileflow', map: 'madrid'}} captureId="checkout-map" />
178
- ```
173
+ Annotations, semantic bindings, interaction state, and interaction snippets are interactive-only.
174
+ Untyped callers that pass them to image mode receive `UNSUPPORTED_MODE` and capture readiness
175
+ `error`, not simulated live overlays.
176
+
177
+ ## Capture, authorization, and troubleshooting
179
178
 
180
- The root exposes `data-tileflow-map`, optional `data-tileflow-capture-id`, and
181
- `data-tileflow-state="loading|idle|error"`. It becomes idle only after MapLibre idle plus two
182
- animation frames and, for custom interaction snippets, the Svelte DOM commit plus two current
183
- animation frames. It returns to loading when either surface changes and marks map or interaction
184
- errors. In image mode, readiness follows image decode/load when no interaction configuration is
185
- present. Application capture selects exactly one ready target.
179
+ Use `captureId` to disambiguate repeated maps. The root exposes `data-tileflow-map`, resolved
180
+ `data-tileflow-theme`, optional `data-tileflow-capture-id`, and
181
+ `data-tileflow-state="loading|idle|error"`. Readiness includes MapLibre/image loading and committed
182
+ Svelte interaction views. Application capture needs exactly one ready target and the application's
183
+ normal loopback server.
186
184
 
187
- ## Compatibility
185
+ For an error or blank map, check the manifest URL, map/theme IDs, imported CSS, and matching
186
+ worker/shared assets. The component does not guess a new manifest location after a 404.
188
187
 
189
- The supported peer window is Svelte 5.x and MapLibre GL JS 5-6. Compatibility smoke tests install
190
- Svelte 5.0.0 with the first release of both MapLibre majors from packed Tileflow tarballs, typecheck
191
- the public declarations, and compile real Svelte consumers. Future Svelte majors stay outside the
192
- peer range until that matrix passes.
188
+ Hosted maps acquire a short-lived session grant before eligible resource requests.
189
+ `analytics={{enabled: false}}` disables the optional beacon, not authorization. Use a stable product
190
+ `surfaceId`, not a URL or user identifier. Direct World maps can show owner-action notices when
191
+ managed delivery is required. Imports and the image path remain SSR-safe; interactive rendering
192
+ requires a browser with WebGL.
193
193
 
194
- Docs: https://tileflow.dev/docs
194
+ See the [interaction guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/interactions/README.md)
195
+ and [browser runtime contract](https://github.com/tileflow/tileflow-sdk/blob/main/docs/contracts/framework-browser-runtime.md).
196
+ Use the installed declarations and README for release-specific behavior; `main` may be newer than npm.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/svelte",
3
- "version": "0.1.0-alpha.22",
3
+ "version": "0.1.0-alpha.24",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Svelte component for rendering Tileflow maps with MapLibre.",
6
6
  "type": "module",
@@ -44,15 +44,15 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@tileflow/core": ">=0.1.0-alpha.24 <0.1.0-beta.0",
48
- "@tileflow/interactions": ">=0.1.0-alpha.2 <0.1.0-beta.0"
47
+ "@tileflow/core": ">=0.1.0-alpha.25 <0.1.0-beta.0",
48
+ "@tileflow/interactions": ">=0.1.0-alpha.4 <0.1.0-beta.0"
49
49
  },
50
50
  "peerDependencies": {
51
- "maplibre-gl": ">=5 <7",
51
+ "maplibre-gl": ">=6.4.1 <7",
52
52
  "svelte": ">=5 <6"
53
53
  },
54
54
  "devDependencies": {
55
- "maplibre-gl": "^5.24.0",
55
+ "maplibre-gl": "6.9.0",
56
56
  "svelte": "^5.43.17",
57
57
  "svelte-check": "^4.3.4"
58
58
  },
@@ -1202,9 +1202,11 @@
1202
1202
  }
1203
1203
 
1204
1204
  function createTileflowMapLibrePoiMap(map: MapLibreMap): TileflowMapLibrePoiMap {
1205
+ const poiMap = map as unknown as TileflowMapLibrePoiMap;
1206
+
1205
1207
  return {
1206
1208
  getStyle: () => map.getStyle(),
1207
- on: (event, listener) => map.on(event, listener),
1209
+ on: (event, listener) => poiMap.on(event, listener),
1208
1210
  queryRenderedFeatures(point, options) {
1209
1211
  return map
1210
1212
  .queryRenderedFeatures(point as PointLike, {layers: [...options.layers]})
package/src/index.d.ts CHANGED
@@ -19,6 +19,10 @@ import type {
19
19
  export type TileflowMapMode = 'interactive' | 'image';
20
20
  export type TileflowMapOptions = Omit<MapLibreMapOptions, 'container' | 'style'>;
21
21
  export type TileflowMapSource = TileflowRuntimeSource;
22
+ export type TileflowMapLibreConfiguration = Readonly<{workerUrl: string}>;
23
+
24
+ /** Configure the application-owned worker before mounting an interactive Tileflow map. */
25
+ export function configureTileflowMapLibre(configuration: TileflowMapLibreConfiguration): void;
22
26
 
23
27
  export type TileflowMapAnnotationSnippet<
24
28
  TAnnotation extends TileflowAnnotation = TileflowAnnotation,
package/src/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export {default, default as TileflowMap} from './TileflowMap.svelte';
2
+ export {configureTileflowMapLibre} from './maplibre.js';
package/src/maplibre.js CHANGED
@@ -1,14 +1,39 @@
1
1
  /** @typedef {typeof import('maplibre-gl')} TileflowMapLibre */
2
+ /** @typedef {{workerUrl: string}} TileflowMapLibreConfiguration */
2
3
 
3
4
  /** @type {Promise<TileflowMapLibre> | undefined} */
4
5
  let mapLibrePromise;
6
+ /** @type {TileflowMapLibre | undefined} */
7
+ let mapLibre;
8
+ /** @type {string | undefined} */
9
+ let workerUrl;
10
+
11
+ /** @param {TileflowMapLibreConfiguration} configuration */
12
+ export function configureTileflowMapLibre(configuration) {
13
+ const nextWorkerUrl = configuration.workerUrl;
14
+
15
+ if (typeof nextWorkerUrl !== 'string' || nextWorkerUrl.trim().length === 0) {
16
+ throw new TypeError('Tileflow MapLibre workerUrl must be a non-empty string.');
17
+ }
18
+
19
+ if (workerUrl !== undefined && workerUrl !== nextWorkerUrl) {
20
+ throw new Error('Tileflow MapLibre workerUrl is already configured.');
21
+ }
22
+
23
+ workerUrl = nextWorkerUrl;
24
+ mapLibre?.setWorkerUrl(nextWorkerUrl);
25
+ }
5
26
 
6
27
  /** Load the interactive renderer only after a browser map requests it. */
7
28
  export function loadTileflowMapLibre() {
8
- mapLibrePromise ??= import('maplibre-gl').then(resolveMapLibreModule).catch((error) => {
9
- mapLibrePromise = undefined;
10
- throw error;
11
- });
29
+ mapLibrePromise ??= import('maplibre-gl')
30
+ .then(resolveMapLibreModule)
31
+ .then(configureTileflowMapLibreModule)
32
+ .catch((error) => {
33
+ mapLibrePromise = undefined;
34
+ throw error;
35
+ });
36
+
12
37
  return mapLibrePromise;
13
38
  }
14
39
 
@@ -16,3 +41,10 @@ export function loadTileflowMapLibre() {
16
41
  function resolveMapLibreModule(module) {
17
42
  return /** @type {TileflowMapLibre & {default?: TileflowMapLibre}} */ (module).default ?? module;
18
43
  }
44
+
45
+ /** @param {TileflowMapLibre} maplibregl */
46
+ function configureTileflowMapLibreModule(maplibregl) {
47
+ if (workerUrl !== undefined) maplibregl.setWorkerUrl(workerUrl);
48
+ mapLibre = maplibregl;
49
+ return maplibregl;
50
+ }