@fusion.dev/helix-map 0.112.0 → 0.114.0

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.
@@ -33,9 +33,13 @@ export interface MapDriver {
33
33
  stop(): void;
34
34
  };
35
35
  /**
36
- * Calls `done` once the renderer has drawn its view with its tiles, and
37
- * returns what stops the waiting. What takes a snapshot drawn on the server
38
- * away; without it, the snapshot stays under the renderer's drawing.
36
+ * Calls `done` once the renderer has drawn its view whole, with every tile
37
+ * it needs and the ground where there is terrain, and returns what stops the
38
+ * waiting. Leave the frame's own shapes out, since a route that animates is
39
+ * redrawn every frame and would never count as drawn. What takes a snapshot
40
+ * drawn on the server away and sends the frame's `helix-map-ready`; without
41
+ * it, the snapshot stays under the renderer's drawing and the frame is never
42
+ * `:state(ready)`.
39
43
  */
40
44
  ready?(done: () => void): () => void;
41
45
  /**
@@ -726,23 +726,23 @@ export const connectMap = (frame, driver, options = {}) => {
726
726
  snapshot.style.display = 'none';
727
727
  };
728
728
  const snapshots = [...frame.querySelectorAll(':scope > [data-helix-map-snapshot]')];
729
- const stopWaiting = snapshots.length > 0
730
- ? driver.ready?.(() => {
731
- const motion = motionOf(frame, 'step');
732
- for (const snapshot of snapshots) {
733
- if (!motion || typeof snapshot.animate !== 'function') {
734
- away(snapshot);
735
- continue;
736
- }
737
- snapshot
738
- .animate([{ opacity: 1 }, { opacity: 0 }], {
739
- duration: motion.duration,
740
- easing: `linear(${Array.from({ length: 13 }, (_, step) => motion.easing(step / 12).toFixed(3)).join(', ')})`,
741
- })
742
- .finished.then(() => away(snapshot), () => away(snapshot));
729
+ const stopWaiting = driver.ready?.(() => {
730
+ place();
731
+ frame.drawn?.();
732
+ const motion = motionOf(frame, 'step');
733
+ for (const snapshot of snapshots) {
734
+ if (!motion || typeof snapshot.animate !== 'function') {
735
+ away(snapshot);
736
+ continue;
743
737
  }
744
- })
745
- : undefined;
738
+ snapshot
739
+ .animate([{ opacity: 1 }, { opacity: 0 }], {
740
+ duration: motion.duration,
741
+ easing: `linear(${Array.from({ length: 13 }, (_, step) => motion.easing(step / 12).toFixed(3)).join(', ')})`,
742
+ })
743
+ .finished.then(() => away(snapshot), () => away(snapshot));
744
+ }
745
+ });
746
746
  let live = true;
747
747
  let placing = false;
748
748
  let drawing = false;
@@ -876,6 +876,7 @@ export const connectMap = (frame, driver, options = {}) => {
876
876
  stopViewing?.();
877
877
  clearTimeout(looking);
878
878
  stopWaiting?.();
879
+ frame.drawn?.(false);
879
880
  stopTheming?.();
880
881
  styled.disconnect();
881
882
  children.disconnect();
@@ -1,4 +1,4 @@
1
- export type { MapLibreStyle, MapStyleOptions } from '../style/index.js';
2
- export { mapLibreStyle } from '../style/index.js';
1
+ export type { MapLandmark, MapLibreStyle, MapStyleOptions } from '../style/index.js';
2
+ export { MAP_LANDMARKS, mapLibreStyle } from '../style/index.js';
3
3
  export type { FollowedTheme, MapLibreMap } from './maplibre-driver.js';
4
4
  export { followTheme, mapLibreDriver } from './maplibre-driver.js';
@@ -1,2 +1,2 @@
1
- export { mapLibreStyle } from '../style/index.js';
1
+ export { MAP_LANDMARKS, mapLibreStyle } from '../style/index.js';
2
2
  export { followTheme, mapLibreDriver } from './maplibre-driver.js';
@@ -101,6 +101,11 @@ export interface MapLibreMap {
101
101
  loaded?(): boolean;
102
102
  /** Whether the style has loaded: `true`, and anything else is not yet. MapLibre 6 types it loosely. */
103
103
  isStyleLoaded?(): unknown;
104
+ /**
105
+ * Whether one source has every tile the view needs, once the view has asked
106
+ * for them; a source no shown layer draws from has nothing to wait for.
107
+ */
108
+ isSourceLoaded?(id: string): boolean;
104
109
  /** The canvas, which MapLibre makes focusable. */
105
110
  getCanvas(): HTMLElement;
106
111
  /** The middle of the view, which a map-type picture is drawn around. */
@@ -1056,30 +1056,68 @@ export const mapLibreDriver = (map) => ({
1056
1056
  return { x, y };
1057
1057
  },
1058
1058
  ready: (done) => {
1059
- if (map.loaded?.() && map.areTilesLoaded?.()) {
1060
- done();
1061
- return () => undefined;
1059
+ if (!map.isSourceLoaded) {
1060
+ if (map.loaded?.() && map.areTilesLoaded?.()) {
1061
+ done();
1062
+ return () => undefined;
1063
+ }
1064
+ let finished = false;
1065
+ const finish = () => {
1066
+ if (!finished) {
1067
+ finished = true;
1068
+ map.off('load', finish);
1069
+ map.off('render', drawn);
1070
+ done();
1071
+ }
1072
+ };
1073
+ const drawn = () => {
1074
+ if (map.isStyleLoaded?.() === true && map.areTilesLoaded?.() === true) {
1075
+ finish();
1076
+ }
1077
+ };
1078
+ map.on('load', finish);
1079
+ map.on('render', drawn);
1080
+ return () => {
1081
+ map.off('load', finish);
1082
+ map.off('render', drawn);
1083
+ };
1062
1084
  }
1063
- let finished = false;
1064
- const finish = () => {
1065
- if (finished) {
1066
- return;
1085
+ let tiled;
1086
+ const read = () => {
1087
+ const sources = map.getStyle()?.sources;
1088
+ tiled = sources
1089
+ ? Object.entries(sources)
1090
+ .filter(([, source]) => source.type !== 'geojson')
1091
+ .map(([id]) => id)
1092
+ : undefined;
1093
+ };
1094
+ const whole = () => {
1095
+ if (tiled === undefined) {
1096
+ read();
1067
1097
  }
1068
- finished = true;
1069
- stop();
1098
+ return tiled?.every((id) => map.isSourceLoaded?.(id) === true) === true;
1099
+ };
1100
+ if (whole()) {
1070
1101
  done();
1102
+ return () => undefined;
1103
+ }
1104
+ const restyled = () => {
1105
+ tiled = undefined;
1071
1106
  };
1072
- const drawn = () => {
1073
- if (map.isStyleLoaded?.() === true && map.areTilesLoaded?.() === true) {
1074
- finish();
1107
+ const heard = () => {
1108
+ if (whole()) {
1109
+ stop();
1110
+ done();
1075
1111
  }
1076
1112
  };
1077
1113
  const stop = () => {
1078
- map.off('load', finish);
1079
- map.off('render', drawn);
1114
+ map.off('styledata', restyled);
1115
+ map.off('render', heard);
1116
+ map.off('idle', heard);
1080
1117
  };
1081
- map.on('load', finish);
1082
- map.on('render', drawn);
1118
+ map.on('styledata', restyled);
1119
+ map.on('render', heard);
1120
+ map.on('idle', heard);
1083
1121
  return stop;
1084
1122
  },
1085
1123
  rotate: ({ by, to }, motion) => {
@@ -1,4 +1,4 @@
1
1
  export type { MapColors } from './map-colors.js';
2
2
  export { colorsNow, colorsReady, fontReady, mapColorsFrom, mapFontFrom, PATIENCE, settledColor, } from './map-colors.js';
3
- export type { MapImage, MapLibreStyle, MapStyleOptions } from './map-style.js';
4
- export { mapLibreStyle } from './map-style.js';
3
+ export type { MapImage, MapLandmark, MapLibreStyle, MapStyleOptions } from './map-style.js';
4
+ export { MAP_LANDMARKS, mapLibreStyle } from './map-style.js';
@@ -1,2 +1,2 @@
1
1
  export { colorsNow, colorsReady, fontReady, mapColorsFrom, mapFontFrom, PATIENCE, settledColor, } from './map-colors.js';
2
- export { mapLibreStyle } from './map-style.js';
2
+ export { MAP_LANDMARKS, mapLibreStyle } from './map-style.js';
@@ -23,6 +23,18 @@ export interface MapStyleOptions {
23
23
  * every zoom, which MapLibre 4 also needs, since it has no globe.
24
24
  */
25
25
  globe?: boolean | undefined;
26
+ /**
27
+ * Which landmarks the map draws, each kind an icon in a hue of its own.
28
+ * `true`, the default, draws every kind in `MAP_LANDMARKS`; `false` draws
29
+ * none, for a map whose own markers are the places a reader is looking for;
30
+ * a list draws those kinds and no others, such as `['nature', 'transit']`.
31
+ *
32
+ * **An option of the style, so it holds through a theme change.**
33
+ * `followTheme` builds the style again from the same options when the mode
34
+ * changes, where a page hiding the layers itself had them back on every
35
+ * rebuild and had to learn their ids from this file to do it.
36
+ */
37
+ landmarks?: boolean | readonly MapLandmark[] | undefined;
26
38
  /**
27
39
  * The language labels are written in, where the tiles carry it, falling back
28
40
  * to the Latin name and then the local one. `en` by default.
@@ -78,6 +90,13 @@ export type MapImage = {
78
90
  ink: string;
79
91
  kind: 'sign';
80
92
  };
93
+ /**
94
+ * The kinds of landmark the style draws, each an icon in a hue of its own, for
95
+ * `MapStyleOptions.landmarks`.
96
+ */
97
+ export declare const MAP_LANDMARKS: readonly ['culture', 'food', 'health', 'learning', 'lodging', 'nature', 'shopping', 'sport', 'transit'];
98
+ /** One kind of landmark, such as `nature` for parks and gardens. */
99
+ export type MapLandmark = (typeof MAP_LANDMARKS)[number];
81
100
  /**
82
101
  * A MapLibre style in the page's theme.
83
102
  *
@@ -4,6 +4,17 @@ const OPENFREEMAP = {
4
4
  glyphs: 'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf',
5
5
  tiles: 'https://tiles.openfreemap.org/planet',
6
6
  };
7
+ export const MAP_LANDMARKS = [
8
+ 'culture',
9
+ 'food',
10
+ 'health',
11
+ 'learning',
12
+ 'lodging',
13
+ 'nature',
14
+ 'shopping',
15
+ 'sport',
16
+ 'transit',
17
+ ];
7
18
  const LANDMARKS = [
8
19
  {
9
20
  classes: ['park', 'garden', 'playground', 'campsite', 'golf', 'zoo'],
@@ -151,7 +162,10 @@ export const mapLibreStyle = (colors, options = {}) => {
151
162
  const images = {};
152
163
  const landmarkImage = ['match', ['get', 'class']];
153
164
  const landmarkColor = ['match', ['get', 'class']];
154
- for (const { classes, group, hue, icon } of LANDMARKS) {
165
+ const kept = LANDMARKS.filter(({ group }) => options.landmarks === undefined || options.landmarks === true
166
+ ? true
167
+ : options.landmarks !== false && options.landmarks.includes(group));
168
+ for (const { classes, group, hue, icon } of kept) {
155
169
  const color = cssColor(hued(hue));
156
170
  const image = {
157
171
  color,
@@ -166,7 +180,7 @@ export const mapLibreStyle = (colors, options = {}) => {
166
180
  }
167
181
  landmarkImage.push('');
168
182
  landmarkColor.push(legible(land, 0.6));
169
- const classesOf = (everyday) => LANDMARKS.filter((one) => (one.everyday === true) === everyday).flatMap(({ classes }) => classes);
183
+ const classesOf = (everyday) => kept.filter((one) => (one.everyday === true) === everyday).flatMap(({ classes }) => classes);
170
184
  const signFont = face ? `"${face}", sans-serif` : 'sans-serif';
171
185
  const signs = `helix-sign-${stamp(signFont)}-`;
172
186
  images[`${signs}interstate`] = {
@@ -375,7 +389,9 @@ export const mapLibreStyle = (colors, options = {}) => {
375
389
  'source-layer': 'transportation_name',
376
390
  type: 'symbol',
377
391
  },
378
- ...[false, true].map((everyday) => ({
392
+ ...[false, true]
393
+ .filter((everyday) => classesOf(everyday).length > 0)
394
+ .map((everyday) => ({
379
395
  filter: ['match', ['get', 'class'], classesOf(everyday), true, false],
380
396
  id: everyday ? 'label-landmark-everyday' : 'label-landmark',
381
397
  layout: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-map",
3
3
  "description": "Connects the Helix Map frame to a map renderer: MapLibre, Leaflet, or a driver of your own.",
4
- "version": "0.112.0",
4
+ "version": "0.114.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -43,14 +43,14 @@
43
43
  },
44
44
  "bugs": "https://github.com/project-fusion/fusion/issues",
45
45
  "dependencies": {
46
- "@fusion.dev/helix-core": "0.112.0",
47
- "@fusion.dev/helix-icons": "0.112.0",
46
+ "@fusion.dev/helix-core": "0.114.0",
47
+ "@fusion.dev/helix-icons": "0.114.0",
48
48
  "@mapbox/vector-tile": "^3.0.0",
49
49
  "pbf": "^5.1.2"
50
50
  },
51
51
  "devDependencies": {
52
- "@fusion.dev/helix-headless": "0.112.0",
53
- "@fusion.dev/helix-testing": "0.112.0",
52
+ "@fusion.dev/helix-headless": "0.114.0",
53
+ "@fusion.dev/helix-testing": "0.114.0",
54
54
  "@maplibre/maplibre-gl-style-spec": "^26.4.4",
55
55
  "@types/bun": "^1.3.0",
56
56
  "@types/leaflet": "^1.9.20",