@tileflow/react 0.1.0-alpha.24 → 0.1.0-alpha.26
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 +192 -225
- package/dist/index.js +15 -35
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,86 +1,153 @@
|
|
|
1
1
|
# @tileflow/react
|
|
2
2
|
|
|
3
|
-
React components for
|
|
3
|
+
React components for interactive Tileflow maps, annotations, and static-image display.
|
|
4
|
+
Interactive maps use MapLibre GL JS; browser components read prepared assets rather than compiling
|
|
5
|
+
`tileflow.config.ts`.
|
|
4
6
|
|
|
5
|
-
|
|
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 React 18 or 19 application with matching `react-dom`. The following local build
|
|
12
|
+
workflow needs Node.js 22 or newer:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @tileflow/react@alpha @tileflow/core@alpha @tileflow/maps@alpha "maplibre-gl@^6.4.1"
|
|
16
|
+
npm install --save-dev --save-exact tileflow@alpha
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The supported peer ranges are React/React DOM 18–19 and MapLibre GL JS `>=6.4.1 <7`. The worker
|
|
20
|
+
must come from the same MapLibre installation as the main module.
|
|
21
|
+
|
|
22
|
+
## Prepare a map
|
|
23
|
+
|
|
24
|
+
Create `tileflow.config.ts` at the application root:
|
|
25
|
+
|
|
26
|
+
<!-- docs:check -->
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import {defineMap} from '@tileflow/core';
|
|
30
|
+
import {streets} from '@tileflow/maps';
|
|
31
|
+
|
|
32
|
+
export default defineMap({
|
|
33
|
+
id: 'madrid',
|
|
34
|
+
version: 1,
|
|
35
|
+
extends: streets,
|
|
36
|
+
view: {center: [-3.7038, 40.4168], zoom: 12},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Build the assets into the application's public directory:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
npx tileflow build --out public/tileflow
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This assumes the application serves `public/` at its URL root. Rebuild after config or asset
|
|
47
|
+
changes, or use the [Vite](https://github.com/tileflow/tileflow-sdk/blob/main/packages/vite/README.md),
|
|
48
|
+
[Next.js](https://github.com/tileflow/tileflow-sdk/blob/main/packages/next/README.md), or
|
|
49
|
+
[Webpack](https://github.com/tileflow/tileflow-sdk/blob/main/packages/webpack/README.md) integration
|
|
50
|
+
for watched development and production builds. Do not use both workflows to overwrite the same
|
|
51
|
+
hosted manifest.
|
|
52
|
+
|
|
53
|
+
## Configure the worker
|
|
54
|
+
|
|
55
|
+
In a Vite application's client entry, add this before mounting React:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
|
|
59
|
+
import {configureTileflowMapLibre} from '@tileflow/react';
|
|
60
|
+
|
|
61
|
+
configureTileflowMapLibre({workerUrl});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This import syntax is specific to Vite. Use the Next.js or Webpack guide linked above for that
|
|
65
|
+
bundler's worker-delivery recipe. Configure the worker once, outside component rendering. Do not
|
|
66
|
+
use a CDN worker from another MapLibre version. Next.js interactive components belong behind a
|
|
67
|
+
`'use client'` boundary.
|
|
68
|
+
|
|
69
|
+
## Render a map
|
|
70
|
+
|
|
71
|
+
Import MapLibre's CSS once in the application. After the worker and assets are ready, render:
|
|
72
|
+
|
|
73
|
+
<!-- docs:check -->
|
|
6
74
|
|
|
7
75
|
```tsx
|
|
8
|
-
import {Map} from '@tileflow/react';
|
|
9
76
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
77
|
+
import {Map} from '@tileflow/react';
|
|
10
78
|
|
|
11
79
|
export function App() {
|
|
12
80
|
return (
|
|
13
81
|
<Map
|
|
14
|
-
source={{
|
|
82
|
+
source={{map: 'madrid'}}
|
|
15
83
|
theme="system"
|
|
16
84
|
center={[-3.7038, 40.4168]}
|
|
17
85
|
zoom={12}
|
|
18
|
-
mapOptions={{
|
|
19
|
-
cooperativeGestures: true,
|
|
20
|
-
maxZoom: 18,
|
|
21
|
-
}}
|
|
86
|
+
mapOptions={{cooperativeGestures: true, maxZoom: 18}}
|
|
22
87
|
/>
|
|
23
88
|
);
|
|
24
89
|
}
|
|
25
90
|
```
|
|
26
91
|
|
|
27
|
-
|
|
92
|
+
Coordinates are `[longitude, latitude]`. `source` is one object, `{map, manifestUrl?}`.
|
|
93
|
+
`source.map` must match the config's portable map ID, not a hosted `map_...` identifier.
|
|
94
|
+
The default manifest URL is exactly `/tileflow/manifest.json`. For a subpath or external host,
|
|
95
|
+
provide `manifestUrl` in the source; the component does not infer bundler configuration or search
|
|
96
|
+
for assets.
|
|
28
97
|
|
|
29
|
-
|
|
30
|
-
|
|
98
|
+
`theme` selects a published concrete theme. Omission uses `defaultTheme`; `system` requires an
|
|
99
|
+
explicit light/dark mapping, which Streets supplies. Theme changes preserve the MapLibre instance,
|
|
100
|
+
camera, and interactions, and roll back on failure. `onThemeChange` reports transitions.
|
|
31
101
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
102
|
+
`mapOptions` accepts native MapLibre options except `container` and `style`. Direct camera props
|
|
103
|
+
take priority over `mapOptions`, then the manifest view, then shared defaults. `Map` always selects
|
|
104
|
+
a Tileflow map through its manifest; it has no renderer discriminator or direct-style mode.
|
|
105
|
+
The obsolete source fields `kind` and `style` are rejected. For a completely unmanaged map, use
|
|
106
|
+
`maplibre-gl` directly rather than a Tileflow component.
|
|
35
107
|
|
|
36
|
-
|
|
37
|
-
```
|
|
108
|
+
## Add annotations and popups
|
|
38
109
|
|
|
39
|
-
|
|
40
|
-
MapLibre-free. MapLibre GL JS 6.4.1-6.x is supported. Use the `@tileflow/next` or
|
|
41
|
-
`@tileflow/webpack` README for their worker-delivery recipes.
|
|
110
|
+
Install the contract package when importing its types directly:
|
|
42
111
|
|
|
43
|
-
|
|
112
|
+
```sh
|
|
113
|
+
npm install @tileflow/interactions@alpha
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Annotations are small, application-owned marker sets. Every annotation has a stable `id`, singular
|
|
117
|
+
`coordinate`, and non-empty `ariaLabel`. Data must be JSON-safe. Native React renderers use portals
|
|
118
|
+
into hosts managed by the interaction runtime:
|
|
44
119
|
|
|
45
|
-
|
|
46
|
-
`renderMarker`, `renderTooltip`, and `renderPopup` render native React UI through portals into the
|
|
47
|
-
MapLibre-owned marker and overlay hosts.
|
|
120
|
+
<!-- docs:check -->
|
|
48
121
|
|
|
49
122
|
```tsx
|
|
50
|
-
import {Map} from '@tileflow/react';
|
|
51
123
|
import {useState} from 'react';
|
|
52
124
|
import type {TileflowAnnotation, TileflowInteractionState} from '@tileflow/interactions';
|
|
125
|
+
import {Map} from '@tileflow/react';
|
|
53
126
|
|
|
54
|
-
type Property = {
|
|
55
|
-
price: number;
|
|
56
|
-
title: string;
|
|
57
|
-
};
|
|
127
|
+
type Property = {price: number; title: string};
|
|
58
128
|
|
|
59
129
|
const annotations = [
|
|
60
130
|
{
|
|
61
|
-
ariaLabel: 'Apartment in Madrid',
|
|
62
|
-
coordinate: [-3.7038, 40.4168],
|
|
63
|
-
data: {price: 320_000, title: 'Apartment in Madrid'},
|
|
64
131
|
id: 'property-42',
|
|
65
132
|
kind: 'marker',
|
|
66
|
-
|
|
133
|
+
coordinate: [-3.7038, 40.4168],
|
|
134
|
+
ariaLabel: 'Apartment in Madrid',
|
|
135
|
+
data: {price: 320_000, title: 'Apartment in Madrid'},
|
|
67
136
|
tooltip: {content: {kind: 'field', field: 'title'}},
|
|
68
137
|
popup: {content: {kind: 'view', name: 'property-card'}},
|
|
69
138
|
},
|
|
70
139
|
] satisfies readonly TileflowAnnotation<Property>[];
|
|
71
140
|
|
|
72
141
|
export function PropertyMap() {
|
|
73
|
-
const [
|
|
142
|
+
const [state, setState] = useState<TileflowInteractionState>({popup: null});
|
|
74
143
|
|
|
75
144
|
return (
|
|
76
145
|
<Map
|
|
77
|
-
source={{
|
|
146
|
+
source={{map: 'madrid'}}
|
|
78
147
|
annotations={annotations}
|
|
79
|
-
interactionState={
|
|
80
|
-
onInteractionStateChange={
|
|
81
|
-
onInteractionDiagnostic={(diagnostic) => console.error(diagnostic)}
|
|
82
|
-
onInteractionEvent={(event) => console.log(event.type, event.target)}
|
|
83
|
-
renderMarker={({annotation}) => <strong>EUR {annotation.data.price.toLocaleString()}</strong>}
|
|
148
|
+
interactionState={state}
|
|
149
|
+
onInteractionStateChange={setState}
|
|
150
|
+
onInteractionDiagnostic={(diagnostic) => console.error(diagnostic.code)}
|
|
84
151
|
renderPopup={({annotation, close}) => (
|
|
85
152
|
<article>
|
|
86
153
|
<h2>{annotation.data.title}</h2>
|
|
@@ -95,208 +162,108 @@ export function PropertyMap() {
|
|
|
95
162
|
}
|
|
96
163
|
```
|
|
97
164
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
host.
|
|
165
|
+
This uses the worker/CSS setup from the preceding sections. Omit controlled `interactionState` and
|
|
166
|
+
use `defaultInteractionState` for uncontrolled state; never supply both or switch ownership during
|
|
167
|
+
the component's lifetime. Updating annotations, renderers, callbacks, or state does not recreate
|
|
168
|
+
the map. Reusing an annotation ID preserves a compatible marker host.
|
|
102
169
|
|
|
103
|
-
`
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
170
|
+
Use `interactions` with `target: {kind: 'semantic-feature', domain: 'poi'}` for POIs already in the
|
|
171
|
+
compiled map instead of creating a DOM marker for every feature. Optional categories use Tileflow's
|
|
172
|
+
semantic taxonomy, such as `food-drink` or `retail`, not raw source classes. The style must contain
|
|
173
|
+
compatible interaction metadata. With semantic bindings, narrow `context.target.kind` in
|
|
174
|
+
`renderTooltip` or `renderPopup` before accessing annotation or feature data. `renderMarker` remains
|
|
175
|
+
annotation-only. See the
|
|
176
|
+
[interaction guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/interactions/README.md).
|
|
107
177
|
|
|
108
|
-
|
|
178
|
+
Text/field content uses safe text defaults. A `view` is an application renderer name, not HTML.
|
|
179
|
+
Keep tooltips non-interactive; put buttons, links, and forms in popups.
|
|
109
180
|
|
|
110
|
-
|
|
111
|
-
Tileflow style. Application code names the `poi` domain, never compiler-generated MapLibre layer
|
|
112
|
-
IDs. The runtime queries only manifest-declared layers and mounts DOM only for the active overlay.
|
|
181
|
+
## Display an image or create a render
|
|
113
182
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{
|
|
120
|
-
id: 'poi-details',
|
|
121
|
-
target: {kind: 'semantic-feature', domain: 'poi', categories: ['food-drink', 'retail']},
|
|
122
|
-
tooltip: {content: {kind: 'field', field: 'name', fallback: 'Point of interest'}},
|
|
123
|
-
popup: {content: {kind: 'view', name: 'poi-card'}},
|
|
124
|
-
},
|
|
125
|
-
] satisfies readonly TileflowInteractionBinding[];
|
|
126
|
-
|
|
127
|
-
export function PoiMap() {
|
|
128
|
-
return (
|
|
129
|
-
<Map
|
|
130
|
-
source={{kind: 'tileflow', map: 'madrid'}}
|
|
131
|
-
interactions={interactions}
|
|
132
|
-
renderPopup={(context) =>
|
|
133
|
-
context.target.kind === 'semantic-feature' ? (
|
|
134
|
-
<article>
|
|
135
|
-
<h2>{String(context.target.feature.properties.name ?? 'Point of interest')}</h2>
|
|
136
|
-
<button type="button" onClick={context.close}>
|
|
137
|
-
Close
|
|
138
|
-
</button>
|
|
139
|
-
</article>
|
|
140
|
-
) : null
|
|
141
|
-
}
|
|
142
|
-
/>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Bindings and popup state are validated before reconciliation. An invalid replacement leaves the
|
|
148
|
-
last valid runtime state active and reports structured diagnostics. `interactionState` and
|
|
149
|
-
`defaultInteractionState` are mutually exclusive. Annotation and semantic overlays share one popup
|
|
150
|
-
state, so replacing either target closes the previous popup before opening the next.
|
|
151
|
-
|
|
152
|
-
`MapProps` rejects controlled and default interaction state together. Its `mode="image"` branch
|
|
153
|
-
excludes annotations, semantic bindings, interaction state, renderers, and interaction callbacks.
|
|
154
|
-
JavaScript callers and widened inputs still receive the corresponding runtime diagnostics.
|
|
155
|
-
|
|
156
|
-
`onInteractionDiagnostic` receives each newly reported structured runtime diagnostic. Changing any
|
|
157
|
-
callback identity does not resubscribe the runtime or recreate MapLibre.
|
|
158
|
-
|
|
159
|
-
Custom portal targets move capture readiness to `loading` until React commits the target and two
|
|
160
|
-
current animation frames complete. Removing or replacing a target invalidates pending frames.
|
|
161
|
-
|
|
162
|
-
When no render prop is present, text and safe field descriptors use the shared unbranded DOM
|
|
163
|
-
defaults. A `view` descriptor is a dispatch name for an application renderer; it is not HTML or a
|
|
164
|
-
serialized component tree. Tooltip content remains non-interactive, while popup content may contain
|
|
165
|
-
normal React controls.
|
|
166
|
-
|
|
167
|
-
`annotations` is the only application-owned marker input. Its singular `coordinate`, required
|
|
168
|
-
`ariaLabel`, and optional `marker` descriptor feed the keyed runtime directly. `mode="image"`
|
|
169
|
-
cannot render interaction configuration and shows an explicit `UNSUPPORTED_MODE` diagnostic
|
|
170
|
-
without loading MapLibre.
|
|
171
|
-
|
|
172
|
-
`mapOptions` accepts native MapLibre options except `container` and `style`, which
|
|
173
|
-
Tileflow resolves from `source`. Direct
|
|
174
|
-
Tileflow props such as `center`, `zoom`, and `interactive` take priority when
|
|
175
|
-
provided. Camera resolution is the same in interactive and image modes: direct props, then
|
|
176
|
-
`mapOptions`, then the published manifest view, then Tileflow's shared runtime defaults.
|
|
177
|
-
|
|
178
|
-
Every map has exactly one discriminated `source`. Use `kind: 'tileflow'` for a map published in a
|
|
179
|
-
Tileflow manifest. `kind: 'maplibre'` is the unmanaged MapLibre escape hatch for one direct style
|
|
180
|
-
object or URL: it deliberately has no Tileflow theme identity, `system` selection, switching, or
|
|
181
|
-
manifest traceability. Browser components do not compile `tileflow.config.ts`.
|
|
182
|
-
|
|
183
|
-
Without `manifestUrl`, the exact default is `/tileflow/manifest.json`. If Vite, Webpack, Next, a
|
|
184
|
-
reverse proxy, or the Tileflow plugin publishes it anywhere else, set the final public URL
|
|
185
|
-
explicitly; the component does not attempt runtime base-path discovery. Manifest 404s, unknown map
|
|
186
|
-
IDs, unresolved styles, and unresolved image URLs enter `data-tileflow-state="error"`.
|
|
187
|
-
|
|
188
|
-
`theme` selects a published theme name. Omitting it uses the map's `defaultTheme`; `"system"` is
|
|
189
|
-
available only when the map declares explicit light and dark mappings. Changes preload fonts, diff
|
|
190
|
-
the style on the existing MapLibre instance, preserve camera and interactions, and roll back if the
|
|
191
|
-
new style fails. `onThemeChange` reports preloading, applying, ready, and error transitions.
|
|
192
|
-
|
|
193
|
-
To reuse one published map across multiple repos, point every app at the same
|
|
194
|
-
manifest:
|
|
183
|
+
`<Map mode="image">` displays an explicit `imageUrl` or derives an image URL from the selected
|
|
184
|
+
manifest map's `mapId`, `apiUrl`, concrete theme and view. It still requires a valid Tileflow source.
|
|
185
|
+
It does not create a static render, poll an operation, or load MapLibre. A local style build alone
|
|
186
|
+
does not create that image. Annotations, semantic bindings, and interaction state are not supported
|
|
187
|
+
in image mode.
|
|
195
188
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
source={{
|
|
199
|
-
kind: 'tileflow',
|
|
200
|
-
map: 'madrid',
|
|
201
|
-
manifestUrl: 'https://cdn.example.com/tileflow/manifest.json',
|
|
202
|
-
}}
|
|
203
|
-
/>
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Or deliberately leave the Tileflow theme contract and load one concrete Style JSON directly:
|
|
189
|
+
To request a new static render, use `StaticMap` from `@tileflow/react/static`. First install the
|
|
190
|
+
shared client/types used by this example:
|
|
207
191
|
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
source={{
|
|
211
|
-
kind: 'maplibre',
|
|
212
|
-
style: 'https://cdn.example.com/tileflow/styles/madrid/dark.json',
|
|
213
|
-
}}
|
|
214
|
-
/>
|
|
192
|
+
```sh
|
|
193
|
+
npm install @tileflow/static@alpha
|
|
215
194
|
```
|
|
216
195
|
|
|
217
|
-
|
|
196
|
+
The application must implement an authorized `/api/static-maps` endpoint that forwards the normalized
|
|
197
|
+
scene and idempotency key to the hosted service and returns its result contract. Keep privileged
|
|
198
|
+
Tileflow credentials on that server, not in React props or a browser bundle.
|
|
218
199
|
|
|
219
|
-
|
|
220
|
-
<Map
|
|
221
|
-
source={{kind: 'tileflow', map: 'madrid'}}
|
|
222
|
-
mode="image"
|
|
223
|
-
center={[-3.7038, 40.4168]}
|
|
224
|
-
zoom={12}
|
|
225
|
-
/>
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
`Map mode="image"` only resolves an existing image URL (explicitly or from the published map
|
|
229
|
-
manifest) and renders an `<img>`; it does not submit a render scene, create an operation, poll, or
|
|
230
|
-
apply `StaticMap` overlays. This path does not load or evaluate MapLibre. Without an explicit
|
|
231
|
-
`imageUrl`, all environments use the image URL published for the Tileflow source.
|
|
232
|
-
|
|
233
|
-
For a new, fully specified render with exact size, center/bounds camera, overlays, idempotency, and
|
|
234
|
-
asynchronous create/poll behavior, use `StaticMap` from `@tileflow/react/static` or helpers from
|
|
235
|
-
`@tileflow/static`.
|
|
200
|
+
<!-- docs:check -->
|
|
236
201
|
|
|
237
202
|
```tsx
|
|
238
|
-
import {StaticMap} from '@tileflow/react/static';
|
|
239
203
|
import {useState} from 'react';
|
|
240
|
-
import {
|
|
204
|
+
import {StaticMap} from '@tileflow/react/static';
|
|
205
|
+
import type {StaticMapAttributionEntry} from '@tileflow/static';
|
|
241
206
|
|
|
242
|
-
export function
|
|
243
|
-
const [
|
|
207
|
+
export function StaticPreview({idempotencyKey}: {idempotencyKey: string}) {
|
|
208
|
+
const [entries, setEntries] = useState<readonly StaticMapAttributionEntry[]>([]);
|
|
244
209
|
|
|
245
210
|
return (
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
211
|
+
<figure>
|
|
212
|
+
<StaticMap
|
|
213
|
+
map="madrid"
|
|
214
|
+
theme="dark"
|
|
215
|
+
alt="Map of central Madrid"
|
|
216
|
+
camera={{type: 'center', center: [-3.7038, 40.4168], zoom: 12}}
|
|
217
|
+
size={{width: 1200, height: 800}}
|
|
218
|
+
attribution={{mode: 'external'}}
|
|
219
|
+
createUrl="/api/static-maps"
|
|
220
|
+
idempotencyKey={idempotencyKey}
|
|
221
|
+
onReady={(result) => {
|
|
222
|
+
if ('attribution' in result) setEntries(result.attribution.entries);
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
<figcaption>
|
|
226
|
+
{entries.map((entry, index) => (
|
|
227
|
+
<p key={index}>
|
|
228
|
+
{entry.text}{' '}
|
|
229
|
+
{entry.links.map((link) => (
|
|
230
|
+
<a key={link.url} href={link.url}>
|
|
231
|
+
{link.label}
|
|
232
|
+
</a>
|
|
233
|
+
))}
|
|
234
|
+
</p>
|
|
235
|
+
))}
|
|
236
|
+
</figcaption>
|
|
237
|
+
</figure>
|
|
257
238
|
);
|
|
258
239
|
}
|
|
259
240
|
```
|
|
260
241
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
The root element exposes `data-tileflow-map`, optional `data-tileflow-capture-id`, and
|
|
290
|
-
`data-tileflow-state="loading|idle|error"`. Interactive mode becomes idle after MapLibre is idle
|
|
291
|
-
and two animation frames; style/data work returns it to loading. Image mode waits for decode or a
|
|
292
|
-
successful load fallback, including cached hydration. `tileflow capture` uses these markers for
|
|
293
|
-
application scenes and requires exactly one target.
|
|
294
|
-
|
|
295
|
-
## Compatibility
|
|
296
|
-
|
|
297
|
-
The supported peer window is React 18-19 and MapLibre GL JS 6.4.1-6.x. Compatibility smoke tests
|
|
298
|
-
install the exact lower bound and the selected current release from packed Tileflow tarballs,
|
|
299
|
-
typecheck a consumer, and render the image/SSR path while rejecting any MapLibre import. Future
|
|
300
|
-
majors stay outside the peer range until that matrix passes.
|
|
301
|
-
|
|
302
|
-
Docs: https://tileflow.dev/docs
|
|
242
|
+
Create the key with `createStaticMapIdempotencyKey()` once per intentional render action and retain
|
|
243
|
+
it in the owning application state across retries and remounts. Pass it into this component; do not
|
|
244
|
+
regenerate it on every render. A new scene is a new action and needs a new key.
|
|
245
|
+
|
|
246
|
+
Hosted results have `resultVersion: 1` and attribution. `onReady` also supports existing-image
|
|
247
|
+
results, which have no attribution field; narrow the union before reading it. External attribution
|
|
248
|
+
must remain visible beside the image. Omitted attribution uses embedded automatic placement.
|
|
249
|
+
See the [Static Maps guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/static/README.md)
|
|
250
|
+
for server transport, errors, overlays, and limits.
|
|
251
|
+
|
|
252
|
+
## Capture, authorization, and errors
|
|
253
|
+
|
|
254
|
+
`captureId` disambiguates repeated maps on a page. The root exposes `data-tileflow-map`, the resolved
|
|
255
|
+
`data-tileflow-theme`, optional `data-tileflow-capture-id`, and `data-tileflow-state="loading|idle|error"`.
|
|
256
|
+
Capture waits for MapLibre/image readiness and committed custom interaction views. Manifest 404s,
|
|
257
|
+
unknown maps/themes, and missing styles or images enter the error state rather than silently using
|
|
258
|
+
another map.
|
|
259
|
+
|
|
260
|
+
Hosted interactive maps acquire a short-lived session grant before eligible resource requests.
|
|
261
|
+
`analytics={{enabled: false}}` disables the optional beacon, not hosted authorization. A stable
|
|
262
|
+
`surfaceId`, such as `store-locator`, identifies a product location; do not use user IDs or URLs.
|
|
263
|
+
Existing request transforms remain application-owned, and direct World use can display an
|
|
264
|
+
owner-action notice when managed delivery is required.
|
|
265
|
+
|
|
266
|
+
The image/SSR path does not load MapLibre. Interactive rendering needs a browser with WebGL and the
|
|
267
|
+
matching worker assets. For release-specific APIs, use the installed declarations and README;
|
|
268
|
+
source `main` may be ahead of npm. Report defects in the
|
|
269
|
+
[issue tracker](https://github.com/tileflow/tileflow-sdk/issues).
|
package/dist/index.js
CHANGED
|
@@ -37,8 +37,7 @@ import {
|
|
|
37
37
|
resolveTileflowRuntimeStyle,
|
|
38
38
|
resolveTileflowRuntimeTheme,
|
|
39
39
|
resolveTileflowRuntimeView,
|
|
40
|
-
resolveTileflowStaticImageUrl
|
|
41
|
-
shouldLoadTileflowManifest
|
|
40
|
+
resolveTileflowStaticImageUrl
|
|
42
41
|
} from "@tileflow/core/runtime";
|
|
43
42
|
import {
|
|
44
43
|
createTileflowMapLibreDomRuntime,
|
|
@@ -175,9 +174,6 @@ function validateTileflowMapStyleInputs(input) {
|
|
|
175
174
|
if (input.theme !== void 0 && !validateTileflowThemeSelection(input.theme)) {
|
|
176
175
|
return { error: 'theme must be a concrete portable theme name or "system"', ok: false };
|
|
177
176
|
}
|
|
178
|
-
if (input.theme !== void 0 && input.source.kind !== "tileflow") {
|
|
179
|
-
return { error: "theme is only valid with a tileflow source", ok: false };
|
|
180
|
-
}
|
|
181
177
|
return { ok: true };
|
|
182
178
|
}
|
|
183
179
|
function assertTileflowMapStyleInputs(input) {
|
|
@@ -253,16 +249,14 @@ function Map2(props) {
|
|
|
253
249
|
} = props;
|
|
254
250
|
const generalRenderPopup = renderPopup;
|
|
255
251
|
const generalRenderTooltip = renderTooltip;
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const sourceManifestUrl = source.kind === "tileflow" ? source.manifestUrl : void 0;
|
|
259
|
-
const sourceStyle = source.kind === "maplibre" ? source.style : void 0;
|
|
252
|
+
const sourceMap = source.map;
|
|
253
|
+
const sourceManifestUrl = source.manifestUrl;
|
|
260
254
|
const runtimeSource = useMemo(
|
|
261
|
-
() =>
|
|
262
|
-
[
|
|
255
|
+
() => ({ manifestUrl: sourceManifestUrl, map: sourceMap }),
|
|
256
|
+
[sourceManifestUrl, sourceMap]
|
|
263
257
|
);
|
|
264
|
-
const mapName = runtimeSource.
|
|
265
|
-
const manifestUrl = runtimeSource.
|
|
258
|
+
const mapName = runtimeSource.map;
|
|
259
|
+
const manifestUrl = runtimeSource.manifestUrl ?? defaultTileflowManifestUrl;
|
|
266
260
|
const preparedAnnotations = useMemo(
|
|
267
261
|
() => prepareTileflowReactAnnotations(annotations),
|
|
268
262
|
[annotations]
|
|
@@ -328,22 +322,12 @@ function Map2(props) {
|
|
|
328
322
|
const resolvedCaptureId = normalizeTileflowCaptureId(captureId);
|
|
329
323
|
const resolvedMode = resolveTileflowMapMode({ mode });
|
|
330
324
|
const isImageMode = resolvedMode === "image";
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
});
|
|
334
|
-
const manifestRequestKey = shouldLoadManifest ? JSON.stringify([manifestUrl, mapName]) : "not-needed";
|
|
335
|
-
const currentManifestResolution = manifestResolution.key === manifestRequestKey ? manifestResolution : {
|
|
336
|
-
key: manifestRequestKey,
|
|
337
|
-
map: null,
|
|
338
|
-
state: shouldLoadManifest ? "loading" : "not-needed"
|
|
339
|
-
};
|
|
325
|
+
const manifestRequestKey = JSON.stringify([manifestUrl, mapName]);
|
|
326
|
+
const currentManifestResolution = manifestResolution.key === manifestRequestKey ? manifestResolution : { key: manifestRequestKey, map: null, state: "loading" };
|
|
340
327
|
const manifestMap = currentManifestResolution.state === "ready" ? currentManifestResolution.map : null;
|
|
341
328
|
const themeResolution = useMemo(() => {
|
|
342
|
-
if (!manifestMap
|
|
343
|
-
return {
|
|
344
|
-
error: false,
|
|
345
|
-
name: runtimeSource.kind === "tileflow" && theme && theme !== "system" ? theme : void 0
|
|
346
|
-
};
|
|
329
|
+
if (!manifestMap) {
|
|
330
|
+
return { error: false, name: theme && theme !== "system" ? theme : void 0 };
|
|
347
331
|
}
|
|
348
332
|
try {
|
|
349
333
|
return {
|
|
@@ -353,7 +337,7 @@ function Map2(props) {
|
|
|
353
337
|
} catch {
|
|
354
338
|
return { error: true, name: void 0 };
|
|
355
339
|
}
|
|
356
|
-
}, [manifestMap,
|
|
340
|
+
}, [manifestMap, systemColorScheme, theme]);
|
|
357
341
|
const resolvedThemeName = themeResolution.name;
|
|
358
342
|
const manifestView = resolveTileflowRuntimeView({ manifestMap });
|
|
359
343
|
const manifestCenter = useStableMapOptionValue(
|
|
@@ -458,10 +442,6 @@ function Map2(props) {
|
|
|
458
442
|
reportedDeclarativeDiagnosticKeysRef.current = nextKeys;
|
|
459
443
|
}, [declarativeInteractionDiagnostics]);
|
|
460
444
|
useEffect(() => {
|
|
461
|
-
if (!shouldLoadManifest) {
|
|
462
|
-
setManifestResolution({ key: manifestRequestKey, map: null, state: "not-needed" });
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
445
|
let cancelled = false;
|
|
466
446
|
setManifestResolution({ key: manifestRequestKey, map: null, state: "loading" });
|
|
467
447
|
loadTileflowManifest(manifestUrl).then((manifest) => {
|
|
@@ -483,7 +463,7 @@ function Map2(props) {
|
|
|
483
463
|
return () => {
|
|
484
464
|
cancelled = true;
|
|
485
465
|
};
|
|
486
|
-
}, [manifestRequestKey, manifestUrl, mapName
|
|
466
|
+
}, [manifestRequestKey, manifestUrl, mapName]);
|
|
487
467
|
const runtimeStyle = useMemo(() => {
|
|
488
468
|
if (isImageMode || themeResolution.error) {
|
|
489
469
|
return null;
|
|
@@ -496,7 +476,7 @@ function Map2(props) {
|
|
|
496
476
|
});
|
|
497
477
|
}, [isImageMode, manifestMap, runtimeSource, systemColorScheme, themeResolution]);
|
|
498
478
|
runtimeStyleRef.current = runtimeStyle;
|
|
499
|
-
const runtimeMapIdentity =
|
|
479
|
+
const runtimeMapIdentity = `${manifestUrl}\0${runtimeSource.map}`;
|
|
500
480
|
const runtimeStyleReady = runtimeStyle !== null || themeResolution.error && mapRef.current !== null;
|
|
501
481
|
const resolvedAnalytics = useMemo(
|
|
502
482
|
() => mergeTileflowAnalytics(analytics, runtimeStyle?.analytics),
|
|
@@ -544,7 +524,7 @@ function Map2(props) {
|
|
|
544
524
|
]
|
|
545
525
|
);
|
|
546
526
|
const runtimeResolutionState = (() => {
|
|
547
|
-
if (
|
|
527
|
+
if (currentManifestResolution.state !== "ready") {
|
|
548
528
|
return currentManifestResolution.state === "error" ? "error" : "loading";
|
|
549
529
|
}
|
|
550
530
|
if (themeResolution.error) return "error";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tileflow/react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.26",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "React components for rendering Tileflow maps with MapLibre.",
|
|
6
6
|
"type": "module",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@tileflow/core": ">=0.1.0-alpha.
|
|
47
|
-
"@tileflow/interactions": ">=0.1.0-alpha.
|
|
48
|
-
"@tileflow/static": ">=0.1.0-alpha.
|
|
46
|
+
"@tileflow/core": ">=0.1.0-alpha.27 <0.1.0-beta.0",
|
|
47
|
+
"@tileflow/interactions": ">=0.1.0-alpha.4 <0.1.0-beta.0",
|
|
48
|
+
"@tileflow/static": ">=0.1.0-alpha.27 <0.1.0-beta.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"maplibre-gl": ">=6.4.1 <7",
|