@tileflow/core 0.1.0-alpha.3 → 0.1.0-alpha.31

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.
Files changed (38) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +273 -26
  3. package/THIRD_PARTY_NOTICES.md +327 -0
  4. package/dist/browser.d.ts +962 -0
  5. package/dist/browser.js +6237 -0
  6. package/dist/build.d.ts +574 -0
  7. package/dist/build.js +25423 -0
  8. package/dist/capture-scene-D6soWztE.d.ts +161 -0
  9. package/dist/capture.d.ts +9 -0
  10. package/dist/capture.js +276 -0
  11. package/dist/config-reference.json +32261 -0
  12. package/dist/icon-composition-BiOTwrvD.d.ts +1331 -0
  13. package/dist/index.d.ts +1114 -162
  14. package/dist/index.js +18714 -3479
  15. package/dist/manifest-types-CTT2eLbp.d.ts +790 -0
  16. package/dist/manifest.d.ts +9 -0
  17. package/dist/manifest.js +368 -0
  18. package/dist/native-profile.d.ts +188 -0
  19. package/dist/native-profile.js +9316 -0
  20. package/dist/native.d.ts +355 -0
  21. package/dist/native.js +5552 -0
  22. package/dist/runtime.d.ts +1 -0
  23. package/dist/runtime.js +1317 -0
  24. package/docs/authoring-model.md +241 -0
  25. package/docs/capture-scenes.md +36 -0
  26. package/docs/compiled-style-performance.md +263 -0
  27. package/docs/data-is-separate-from-design.md +345 -0
  28. package/docs/globe-atmosphere.md +83 -0
  29. package/docs/hosted-session-authorization.md +108 -0
  30. package/docs/map-and-terrain-examples.md +149 -0
  31. package/docs/map-emphasis.md +89 -0
  32. package/docs/native-artifact-profile.md +145 -0
  33. package/docs/native-resource-urls.md +367 -0
  34. package/docs/public-api-and-browser-subpath.md +93 -0
  35. package/docs/shared-visual-primitives.md +81 -0
  36. package/docs/theme-transitions-and-blends.md +221 -0
  37. package/docs/themes-and-module-styles.md +83 -0
  38. package/package.json +58 -8
@@ -0,0 +1,221 @@
1
+ # Theme transitions and blends
2
+
3
+ Start with the [@tileflow/core guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/README.md) for installation and a complete first example.
4
+
5
+ A Tileflow map can change theme with a cross-fade, or show any point between two or more of its
6
+ themes. Use a transition when people switch between appearances, such as light and dark. Use a
7
+ blend when the application controls the appearance continuously, such as a slider or a day that
8
+ passes from night to day.
9
+
10
+ Both work on the one MapLibre map the application already has. They create no second map, add no
11
+ map views, and request the same tiles as a map that does not change theme. The React, Vue, and
12
+ Svelte maps offer them with MapLibre GL JS, and the React Native map with MapLibre Native on iOS
13
+ and Android; see [React Native](#react-native) for what differs there.
14
+
15
+ ## Cross-fade a theme change
16
+
17
+ Pass `themeTransition` to the React, Vue, or Svelte map. This React example assumes a map named
18
+ `city` with `light` and `dark` themes in the published manifest:
19
+
20
+ ```tsx
21
+ import {Map} from '@tileflow/react';
22
+ import {useState} from 'react';
23
+
24
+ export function CityMap() {
25
+ const [theme, setTheme] = useState<'dark' | 'light'>('light');
26
+ return (
27
+ <>
28
+ <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Switch theme</button>
29
+ <Map source={{map: 'city'}} theme={theme} themeTransition={{duration: 450}} />
30
+ </>
31
+ );
32
+ }
33
+ ```
34
+
35
+ When the theme changes, a snapshot of the current frame covers the map. The snapshot follows the
36
+ camera while people pan, zoom, rotate, or tilt, and fades out once the new theme is drawn. The map
37
+ stays interactive throughout. `onThemeChange` reports `preloading`, `applying`, and finally `ready`
38
+ after the fade. A `theme="system"` change, entering a blend, and leaving one use the same
39
+ transition.
40
+
41
+ `duration` is in milliseconds, from 0 to 5000. It defaults to 0, which changes the theme at once.
42
+ When the browser asks for reduced motion, every change happens at once.
43
+
44
+ ## Blend themes
45
+
46
+ Pass `themeBlend` with the themes to blend, in order, and a `position` between them. Position 0
47
+ shows the first theme, 1 the second, and 1.25 a quarter of the way from the second to the third:
48
+
49
+ ```tsx
50
+ import {Map} from '@tileflow/react';
51
+ import {useState} from 'react';
52
+
53
+ export function DayMap() {
54
+ const [position, setPosition] = useState(0);
55
+ return (
56
+ <>
57
+ <input
58
+ aria-label="Time of day"
59
+ max={2}
60
+ min={0}
61
+ onChange={(event) => setPosition(Number(event.target.value))}
62
+ step={0.01}
63
+ type="range"
64
+ value={position}
65
+ />
66
+ <Map source={{map: 'city'}} themeBlend={{position, themes: ['night', 'dusk', 'day']}} />
67
+ </>
68
+ );
69
+ }
70
+ ```
71
+
72
+ The first blend of a set of themes loads each theme's style and fonts, prepares one style that
73
+ holds every theme, and replaces the map's style once, with `themeTransition` if set. Until then the
74
+ map shows the theme nearest to the position. After that, changing `position` updates the map at
75
+ once without loading anything, so it can follow a slider, a clock, or an animation frame by frame.
76
+ Changing `themes` prepares a new blend. Removing `themeBlend` returns to `theme`.
77
+
78
+ While a blend is shown, `data-tileflow-theme` names the theme nearest to the position.
79
+ `onThemeChange` reports the preparation of a blend; moving its position reports nothing.
80
+
81
+ ### What follows the position
82
+
83
+ - Colours and numbers of the ground, such as land, water, roads, buildings, and their widths and
84
+ opacities, follow the position continuously. Colours mix in the OKLab colour space, so halfway
85
+ between two themes looks halfway to the eye.
86
+ - Patterns, the sky, and light follow the position too. Pattern artwork is mixed in its pixels.
87
+ - Labels, icons, and other symbols change when the nearest theme changes, with a short cross-fade.
88
+ Light lettering on a dark plate and dark lettering on a light plate would meet in the same grey
89
+ halfway, so symbols do not follow the position.
90
+ - A value that depends on feature state, such as a caption that changes colour when highlighted,
91
+ changes with the nearest theme. MapLibre lays its source out again for that change; a cross-fade
92
+ covers it.
93
+
94
+ ### Which themes can be blended
95
+
96
+ Blend themes of the same map. Every theme of a map exposes the same token keys, so its compiled
97
+ styles share sources, layers, filters, and expressions, and differ only in colours, numbers, and
98
+ image names. Up to eight themes can be blended. A blend fails, and the map keeps its current
99
+ appearance, when the styles differ in structure, sources, sprite, or glyphs.
100
+
101
+ Icon and pattern artwork is mixed in place of the first theme's image when every theme's version
102
+ has the same size. Otherwise that image changes with the nearest theme instead.
103
+
104
+ ### Cost
105
+
106
+ The prepared style holds every theme. A value that depends on feature data, such as a land-use
107
+ colour chosen by class, becomes one layer per class while the blend is shown, so the prepared
108
+ style has more layers than one theme. Physical layer IDs are diagnostic, not stable; semantic
109
+ interactions are not affected.
110
+
111
+ Entering a blend replaces the map's style once. MapLibre then takes about as long as it takes to
112
+ load the map: for a detailed city map, around half a second on a desktop computer and longer on a
113
+ phone, while the transition snapshot, if any, covers the map. Start a blend before people need it
114
+ to move, for example when the map loads.
115
+
116
+ Each position change after that sets the map's themed values directly, without expression parsing.
117
+ For a detailed city map this takes a few milliseconds on a mid-range phone. Positions closer than a
118
+ four-hundredth of a theme are not applied, so a slowly moving position leaves the map at rest
119
+ between changes.
120
+
121
+ ## React Native
122
+
123
+ The `Map` of `@tileflow/react-native` accepts the same `themeTransition` and `themeBlend` props:
124
+
125
+ ```tsx
126
+ import {Map} from '@tileflow/react-native';
127
+
128
+ export function DayMap({position}: {position: number}) {
129
+ return (
130
+ <Map
131
+ source={{map: 'city', manifestUrl: 'https://maps.example.com/tileflow/manifest.json'}}
132
+ themeTransition={{duration: 450}}
133
+ themeBlend={{position, themes: ['night', 'dusk', 'day']}}
134
+ style={{flex: 1}}
135
+ />
136
+ );
137
+ }
138
+ ```
139
+
140
+ A transition covers the drawn map with a snapshot of its current frame, below markers and map
141
+ controls. The snapshot follows the camera and fades out once the new style is drawn.
142
+ `onReadinessChange` and `onThemeChange` report `ready` after the fade. The device's reduce-motion
143
+ setting (iOS) or removed animations (Android) change at once.
144
+
145
+ A blend loads every blended theme's style once and replaces the map's style once. Position changes
146
+ then apply in batches on the main thread, at most one at a time, and later positions replace any
147
+ that are still waiting. MapLibre Native's style transition smooths values between batches.
148
+
149
+ What follows the position differs from the browser in a few ways:
150
+
151
+ - Ground colours and numbers, pattern artwork, and light follow the position. Native maps have no
152
+ sky.
153
+ - Labels, icons, and switched values change at the nearest theme under a short cross-fade of the
154
+ whole map. Icon artwork changes by image name instead of mixing pixels, so stretchable images
155
+ keep their stretch and content areas.
156
+ - A value that depends on feature data and cannot be split by class, such as a road casing chosen
157
+ by access and zoom together, is drawn once per theme. Only the nearest theme's copy is shown.
158
+ The map has a few more layers while the blend is shown.
159
+
160
+ If the application's native build predates these props, for example after an over-the-air
161
+ JavaScript update, themes change at once and a blend shows its nearest theme.
162
+
163
+ ## Use the core controller
164
+
165
+ Framework adapters use `createTileflowThemeController` from `@tileflow/core/browser`. A direct
166
+ MapLibre integration can use it too. Resolve runtime styles from the published manifest with
167
+ `resolveTileflowRuntimeStyle` and `resolveTileflowRuntimeThemeBlend` from `@tileflow/core/runtime`:
168
+
169
+ ```ts
170
+ import type {Map as MapLibreMap} from 'maplibre-gl';
171
+ import {createTileflowThemeController} from '@tileflow/core/browser';
172
+ import {
173
+ resolveTileflowRuntimeStyle,
174
+ resolveTileflowRuntimeThemeBlend,
175
+ } from '@tileflow/core/runtime';
176
+ import type {TileflowRuntimeManifestMap} from '@tileflow/core/runtime';
177
+
178
+ export async function moveThroughTheDay(map: MapLibreMap, manifestMap: TileflowRuntimeManifestMap) {
179
+ const source = {map: 'city'};
180
+ const styleOf = (theme: string) => {
181
+ const style = resolveTileflowRuntimeStyle({manifestMap, source, theme});
182
+ if (!style) throw new Error(`The manifest has no ${theme} theme for ${source.map}.`);
183
+ return style;
184
+ };
185
+ // `initial` is the runtime style the map was created with.
186
+ const controller = createTileflowThemeController({initial: styleOf('day'), map});
187
+
188
+ await controller.setTheme(styleOf('night'), {transition: {duration: 450}});
189
+
190
+ const themes = resolveTileflowRuntimeThemeBlend({
191
+ blend: {position: 0, themes: ['night', 'dusk', 'day']},
192
+ manifestMap,
193
+ source,
194
+ });
195
+ await controller.setBlend({position: 0.5, themes});
196
+ // The same themes: the position applies at once.
197
+ await controller.setBlend({position: 0.6, themes});
198
+ return controller.getBlend(); // {position: 0.6, themes: ['night', 'dusk', 'day']}
199
+ }
200
+ ```
201
+
202
+ `setTheme` and `setBlend` accept `{transition: {duration}}`;
203
+ `createTileflowThemeController({transition})` sets a default. Requests are serialized and the
204
+ latest wins. A failed change restores the previous appearance and reports `failed` with its error.
205
+
206
+ The controller loads blended themes' style documents with the same request policy it uses for
207
+ style font metadata: same-origin credentials, no redirects, and a bounded size. Pass `loadStyle`
208
+ when styles need another loader.
209
+
210
+ The controller also shows [map emphasis](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/map-emphasis.md),
211
+ which stays through theme changes and follows every blend position.
212
+
213
+ ## Fast sweeps on slower devices
214
+
215
+ Each position change makes MapLibre evaluate the paint of every layer whose value changed. A clock
216
+ that moves slowly changes few values per frame, and positions closer than a four-hundredth of a
217
+ theme are skipped. A sweep that crosses a whole theme in a few seconds while the camera also moves
218
+ changes most ground layers on every frame. Measured in Chromium on Apple silicon at 1440 × 900 for
219
+ a detailed city map, such a sweep kept 76 frames a second; with the processor slowed four times, as
220
+ on a mid-range phone, it fell to about 11, against 28 for the same camera move without the sweep.
221
+ On slower devices, prefer short sweeps or move the position less often.
@@ -0,0 +1,83 @@
1
+ # Themes and module styles
2
+
3
+ Start with the [@tileflow/core guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/README.md) for installation and a complete first example.
4
+
5
+ A map declares complete named appearances in `themes`, chooses one deterministic
6
+ `defaultTheme`, and may map the browser's light/dark preference through `systemThemes`. `system` is
7
+ never a compiled theme name: it is a runtime request that resolves immediately to one concrete
8
+ name. Disk builds write `styles/<map>/<theme>.json` beneath their output root. Public and
9
+ content-addressed Style URLs belong to the runtime manifest and may include a deployment prefix;
10
+ capture scenes and receipts store the concrete name rather than `system`.
11
+
12
+ Themes are flat, JSON-safe documents with `colorScheme`, identity, typography, lighting, and four
13
+ typed token catalogs: `color`, `font`, `image`, and `number`. Every theme on one map must expose the
14
+ same token keys, so changing a theme can never change map structure or silently lose a semantic
15
+ role. `defineTheme(base, definition)` is an authoring convenience that returns a fully materialized
16
+ document; it does not leave an inheritance edge in the resolved config.
17
+
18
+ The key in the `themes` record is its concrete runtime selector and output-path segment. The
19
+ theme's own `id` and `version` are its editorial identity and build provenance, so they need not
20
+ equal that key. Concrete keys may be names such as `day` and `night`; every `systemThemes.light` or
21
+ `systemThemes.dark` value must name an existing theme whose `colorScheme` matches that branch.
22
+
23
+ ```ts
24
+ import {color, defineMap, defineTheme, fixed, roads, token} from '@tileflow/core';
25
+ import {streets, streetsThemes} from '@tileflow/maps';
26
+
27
+ const dark = defineTheme(streetsThemes.dark, {
28
+ id: 'company-dark',
29
+ version: 1,
30
+ colorScheme: 'dark',
31
+ tokens: {
32
+ color: {
33
+ 'surface.land': '#101722',
34
+ 'labels.primary': color.mix('#eef3fb', token.color('surface.background'), {amount: 0.08}),
35
+ },
36
+ },
37
+ });
38
+
39
+ export default defineMap({
40
+ id: 'company',
41
+ version: 1,
42
+ extends: streets,
43
+ themes: {light: streetsThemes.light, dark},
44
+ defaultTheme: 'light',
45
+ systemThemes: {light: 'light', dark: 'dark'},
46
+ modules: {
47
+ roads: roads({
48
+ classes: {
49
+ primary: {
50
+ surface: {
51
+ fill: {
52
+ color: fixed('#ff2d78', {reason: 'Regulatory primary-road ink is invariant'}),
53
+ },
54
+ },
55
+ },
56
+ secondary: {
57
+ surface: {
58
+ fill: {
59
+ color: token.color('roads.secondary'),
60
+ },
61
+ },
62
+ },
63
+ },
64
+ }),
65
+ },
66
+ });
67
+ ```
68
+
69
+ Semantic modules own structure and behavior; visual fields accept typed token references.
70
+ Use a visual literal only inside a theme. If a module value intentionally must not vary, wrap it in
71
+ `fixed(value, {reason})`. `tileflow inspect --json` reports stable `THEME_IMPLICIT_FIXED`
72
+ diagnostics: implicit color, font, and image literals block Style compilation, while direct visual
73
+ number literals are warnings so an agent can document their invariance deliberately. Structural
74
+ numbers such as zoom bounds and ranks are not visual-theme diagnostics. `color.alpha()` and
75
+ deterministic OKLCH `color.mix()` keep derived palette logic inspectable. Unknown refs, cycles,
76
+ cross-category refs, token-schema drift, and unresolved visual nodes fail before Style JSON is
77
+ emitted.
78
+
79
+ There is no physical-layer override for the Tileflow basemap or separate official-map authoring
80
+ surface. Basemap behavior belongs to a typed control or owner-local render stack. Every
81
+ official and application map therefore follows the same schema, inheritance rules, diagnostics,
82
+ and lowering path. `createStyle()` validates the final planned Style JSON with the MapLibre style
83
+ spec and never returns an invalid style.
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "@tileflow/core",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.31",
4
+ "license": "Apache-2.0",
4
5
  "description": "Typed Tileflow config and MapLibre style compiler.",
5
6
  "type": "module",
7
+ "sideEffects": false,
6
8
  "homepage": "https://tileflow.dev",
7
9
  "repository": {
8
10
  "type": "git",
9
- "url": "git+https://github.com/tileflow/tileflow.git",
11
+ "url": "git+https://github.com/tileflow/tileflow-sdk.git",
10
12
  "directory": "packages/core"
11
13
  },
12
14
  "bugs": {
13
- "url": "https://github.com/tileflow/tileflow/issues"
15
+ "url": "https://github.com/tileflow/tileflow-sdk/issues"
14
16
  },
15
17
  "keywords": [
16
18
  "tileflow",
@@ -22,25 +24,73 @@
22
24
  "main": "./dist/index.js",
23
25
  "types": "./dist/index.d.ts",
24
26
  "files": [
25
- "dist"
27
+ "dist",
28
+ "docs",
29
+ "LICENSE",
30
+ "THIRD_PARTY_NOTICES.md"
26
31
  ],
27
32
  "exports": {
28
33
  ".": {
29
34
  "types": "./dist/index.d.ts",
30
35
  "import": "./dist/index.js",
31
36
  "default": "./dist/index.js"
32
- }
37
+ },
38
+ "./browser": {
39
+ "types": "./dist/browser.d.ts",
40
+ "import": "./dist/browser.js",
41
+ "default": "./dist/browser.js"
42
+ },
43
+ "./build": {
44
+ "types": "./dist/build.d.ts",
45
+ "import": "./dist/build.js",
46
+ "default": "./dist/build.js"
47
+ },
48
+ "./capture": {
49
+ "types": "./dist/capture.d.ts",
50
+ "import": "./dist/capture.js",
51
+ "default": "./dist/capture.js"
52
+ },
53
+ "./config-reference.json": "./dist/config-reference.json",
54
+ "./manifest": {
55
+ "types": "./dist/manifest.d.ts",
56
+ "import": "./dist/manifest.js",
57
+ "default": "./dist/manifest.js"
58
+ },
59
+ "./native": {
60
+ "types": "./dist/native.d.ts",
61
+ "import": "./dist/native.js",
62
+ "default": "./dist/native.js"
63
+ },
64
+ "./native-profile": {
65
+ "types": "./dist/native-profile.d.ts",
66
+ "import": "./dist/native-profile.js",
67
+ "default": "./dist/native-profile.js"
68
+ },
69
+ "./runtime": {
70
+ "types": "./dist/runtime.d.ts",
71
+ "import": "./dist/runtime.js",
72
+ "default": "./dist/runtime.js"
73
+ },
74
+ "./package.json": "./package.json"
33
75
  },
34
76
  "publishConfig": {
35
77
  "access": "public"
36
78
  },
37
79
  "dependencies": {
80
+ "@maplibre/maplibre-gl-style-spec": "24.8.5",
38
81
  "zod": "^4.4.3"
39
82
  },
83
+ "devDependencies": {
84
+ "maplibre-contour": "0.1.0",
85
+ "pmtiles": "4.4.0",
86
+ "whatwg-url": "15.1.0"
87
+ },
40
88
  "scripts": {
41
- "build": "tsup src/index.ts --format esm --dts --clean",
42
- "verify": "tsx scripts/verify.ts",
43
- "typecheck": "tsc -p tsconfig.json --noEmit",
89
+ "build": "tsup --config tsup.build.config.ts && tsup --config tsup.native-profile.config.ts && tsup src/browser.ts --format esm --dts --no-splitting --platform browser && tsup --config tsup.native.config.ts && cp ../../docs/modules-api-reference.json dist/config-reference.json",
90
+ "reference:check": "tsx scripts/generate-config-reference.ts --check",
91
+ "reference:generate": "tsx scripts/generate-config-reference.ts --write",
92
+ "verify": "pnpm run build && tsx --test test/*.test.ts",
93
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.contract.json",
44
94
  "lint": "tsc -p tsconfig.json --noEmit"
45
95
  }
46
96
  }