@versatiles/svelte 2.1.1 → 2.1.2

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.
@@ -5,42 +5,52 @@
5
5
  import BasicMap from '../BasicMap/BasicMap.svelte';
6
6
  import { isDarkMode } from '../../utils/map_style.js';
7
7
  import { loadBBoxes } from './BBoxMap.js';
8
- import { BBoxDrawer, isSameBBox, type BBox } from './lib/bbox.js';
8
+ import { BBoxDrawer, isSameBBox, type BBox } from './lib/bbox_drawer.js';
9
9
 
10
10
  let { selectedBBox = $bindable() }: { selectedBBox?: BBox } = $props();
11
+
11
12
  const startTime = Date.now();
12
13
  let bboxDrawer: BBoxDrawer | undefined;
13
14
  let map: MaplibreMapType | undefined = $state();
14
15
  let bboxes: { key: string; value: BBox }[] | undefined = $state();
15
16
  let mapContainer: HTMLElement;
16
-
17
- $effect(() => setBBox(selectedBBox));
17
+ let disableZoomTimeout: ReturnType<typeof setTimeout> | undefined;
18
18
 
19
19
  async function onMapInit(_map: MaplibreMapType) {
20
20
  map = _map;
21
21
  mapContainer = map.getContainer();
22
22
  map.setPadding({ top: 42, right: 10, bottom: 15, left: 10 });
23
23
  bboxes = await loadBBoxes();
24
- const bbox = selectedBBox || [-180, -85, 180, 85];
25
- bboxDrawer = new BBoxDrawer(map!, bbox, isDarkMode(mapContainer) ? '#FFFFFF' : '#000000');
26
- bboxDrawer.bbox.subscribe(setBBox);
27
- if (selectedBBox) setBBox(selectedBBox);
28
- }
29
24
 
30
- function setBBox(bbox?: BBox) {
31
- if (!map || !bbox) return;
25
+ bboxDrawer = new BBoxDrawer(
26
+ map!,
27
+ selectedBBox ?? [-180, -85, 180, 85],
28
+ isDarkMode(mapContainer) ? '#FFFFFF' : '#000000'
29
+ );
32
30
 
33
- if (!selectedBBox || !isSameBBox(selectedBBox, bbox)) {
34
- selectedBBox = bbox;
31
+ if (selectedBBox) {
32
+ zoom();
35
33
  }
36
34
 
37
- if (!bboxDrawer) return;
35
+ bboxDrawer.on('dragEnd', (bbox) => {
36
+ disableZoomTemporarily();
37
+ if (!selectedBBox || !isSameBBox(bbox, selectedBBox)) {
38
+ selectedBBox = bbox;
39
+ }
40
+ });
41
+ }
42
+
43
+ $effect(() => {
44
+ if (!selectedBBox) return;
45
+ zoom();
46
+ if (bboxDrawer && selectedBBox) bboxDrawer.bbox = selectedBBox;
47
+ });
38
48
 
39
- if (!isSameBBox(bboxDrawer.getBBox(), bbox)) {
40
- bboxDrawer.setBBox(bbox);
41
- }
49
+ function zoom() {
50
+ if (!bboxDrawer || !map || !selectedBBox) return;
51
+ if (disableZoomTimeout) return;
42
52
 
43
- const transform = map.cameraForBounds(bboxDrawer.getBounds()) as CameraOptions;
53
+ const transform = map.cameraForBounds(selectedBBox) as CameraOptions;
44
54
  if (transform == null) return;
45
55
  transform.zoom = transform.zoom ?? 0 - 0.5;
46
56
  transform.bearing = 0;
@@ -67,6 +77,11 @@
67
77
  }
68
78
  return query;
69
79
  }
80
+
81
+ function disableZoomTemporarily() {
82
+ if (disableZoomTimeout) clearTimeout(disableZoomTimeout);
83
+ disableZoomTimeout = setTimeout(() => (disableZoomTimeout = undefined), 100);
84
+ }
70
85
  </script>
71
86
 
72
87
  <div class="container">
@@ -85,9 +100,11 @@
85
100
 
86
101
  <style>
87
102
  .container {
103
+ position: absolute;
88
104
  width: 100%;
89
105
  height: 100%;
90
- position: relative;
106
+ left: 0;
107
+ top: 0;
91
108
  min-height: 6em;
92
109
  }
93
110
  .input {
@@ -1,4 +1,4 @@
1
- import { type BBox } from './lib/bbox.js';
1
+ import { type BBox } from './lib/bbox_drawer.js';
2
2
  type $$ComponentProps = {
3
3
  selectedBBox?: BBox;
4
4
  };
@@ -1,6 +1,6 @@
1
1
  import type geojson from 'geojson';
2
- import { type Writable } from 'svelte/store';
3
- import maplibregl from 'maplibre-gl';
2
+ import type maplibregl from 'maplibre-gl';
3
+ import { EventHandler } from '../../../utils/event_handler.js';
4
4
  export type DragPoint = 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' | false;
5
5
  export declare const DragPointMap: Map<DragPoint, {
6
6
  cursor: string;
@@ -8,21 +8,22 @@ export declare const DragPointMap: Map<DragPoint, {
8
8
  flipV: DragPoint;
9
9
  }>;
10
10
  export type BBox = [number, number, number, number];
11
- export declare class BBoxDrawer {
11
+ export declare class BBoxDrawer extends EventHandler<{
12
+ drag: BBox;
13
+ dragEnd: BBox;
14
+ }> {
12
15
  #private;
13
16
  private sourceId;
14
17
  private dragPoint;
15
18
  private isDragging;
16
19
  private map;
17
20
  private canvas;
18
- private inverted;
19
- readonly bbox: Writable<BBox>;
20
- constructor(map: maplibregl.Map, bbox: BBox, color: string, inverted?: boolean);
21
+ private insideOut;
22
+ constructor(map: maplibregl.Map, initialBBox: BBox, color: string, insideOut?: boolean);
21
23
  private updateDragPoint;
22
24
  private getAsFeatureCollection;
23
- setBBox(bbox: geojson.BBox): void;
24
- getBBox(): BBox;
25
- getBounds(): maplibregl.LngLatBounds;
25
+ set bbox(bbox: BBox);
26
+ get bbox(): BBox;
26
27
  private redraw;
27
28
  private getAsPixel;
28
29
  private checkDragPointAt;
@@ -1,5 +1,4 @@
1
- import { writable } from 'svelte/store';
2
- import maplibregl from 'maplibre-gl';
1
+ import { EventHandler } from '../../../utils/event_handler.js';
3
2
  // prettier-ignore
4
3
  export const DragPointMap = new Map([
5
4
  ['n', { cursor: 'ns-resize', flipH: 'n', flipV: 's' }],
@@ -13,17 +12,18 @@ export const DragPointMap = new Map([
13
12
  [false, { cursor: 'default', flipH: false, flipV: false }],
14
13
  ]);
15
14
  const worldBBox = [-180, -85, 180, 85];
16
- export class BBoxDrawer {
15
+ export class BBoxDrawer extends EventHandler {
17
16
  sourceId;
18
17
  dragPoint = false;
19
18
  isDragging = false;
20
19
  map;
21
20
  canvas;
22
- inverted;
23
- bbox = writable(worldBBox);
24
- #bbox = [0, 0, 0, 0];
25
- constructor(map, bbox, color, inverted) {
26
- this.inverted = inverted ?? true;
21
+ insideOut;
22
+ #bbox;
23
+ constructor(map, initialBBox, color, insideOut) {
24
+ super();
25
+ this.#bbox = [...initialBBox];
26
+ this.insideOut = insideOut ?? true;
27
27
  this.map = map;
28
28
  this.sourceId = 'bbox_' + Math.random().toString(36).slice(2);
29
29
  map.addSource(this.sourceId, { type: 'geojson', data: this.getAsFeatureCollection() });
@@ -54,6 +54,7 @@ export class BBoxDrawer {
54
54
  this.doDrag(e.lngLat);
55
55
  this.redraw();
56
56
  e.preventDefault();
57
+ this.emit('drag', [...this.#bbox]);
57
58
  });
58
59
  map.on('mousedown', (e) => {
59
60
  if (this.isDragging)
@@ -69,8 +70,8 @@ export class BBoxDrawer {
69
70
  map.on('mouseup', () => {
70
71
  this.isDragging = false;
71
72
  this.updateDragPoint(false);
73
+ this.emit('dragEnd', [...this.#bbox]);
72
74
  });
73
- this.setBBox(bbox);
74
75
  }
75
76
  updateDragPoint(dragPoint) {
76
77
  if (this.dragPoint === dragPoint)
@@ -79,10 +80,10 @@ export class BBoxDrawer {
79
80
  this.canvas.style.cursor = this.getCursor(dragPoint);
80
81
  }
81
82
  getAsFeatureCollection() {
82
- const ring = getRing(this.getBBox());
83
+ const ring = getRing(this.#bbox);
83
84
  return {
84
85
  type: 'FeatureCollection',
85
- features: [this.inverted ? polygon(getRing(worldBBox), ring) : polygon(ring), linestring(ring)]
86
+ features: [this.insideOut ? polygon(getRing(worldBBox), ring) : polygon(ring), linestring(ring)]
86
87
  };
87
88
  function getRing(bbox) {
88
89
  const x0 = Math.min(bbox[0], bbox[2]);
@@ -99,30 +100,23 @@ export class BBoxDrawer {
99
100
  return { type: 'Feature', geometry: { type: 'LineString', coordinates }, properties: {} };
100
101
  }
101
102
  }
102
- setBBox(bbox) {
103
- const newBbox = bbox.slice(0, 4);
104
- if (this.#bbox && isSameBBox(this.#bbox, newBbox))
103
+ set bbox(bbox) {
104
+ if (isSameBBox(this.#bbox, bbox))
105
105
  return;
106
- this.#bbox = bbox.slice(0, 4);
106
+ this.#bbox = [...bbox];
107
107
  this.redraw();
108
- this.bbox.set(this.#bbox);
109
108
  }
110
- getBBox() {
111
- return this.#bbox;
112
- }
113
- getBounds() {
114
- return new maplibregl.LngLatBounds(this.getBBox());
109
+ get bbox() {
110
+ return [...this.#bbox];
115
111
  }
116
112
  redraw() {
117
113
  const source = this.map.getSource(this.sourceId);
118
- if (!source)
114
+ if (!source || source.type !== 'geojson')
119
115
  return;
120
- if (source instanceof maplibregl.GeoJSONSource) {
121
- source.setData(this.getAsFeatureCollection());
122
- }
116
+ source.setData(this.getAsFeatureCollection());
123
117
  }
124
118
  getAsPixel() {
125
- const bbox = this.getBBox();
119
+ const bbox = this.#bbox;
126
120
  const p0 = this.map.project([bbox[0], bbox[1]]);
127
121
  const p1 = this.map.project([bbox[2], bbox[3]]);
128
122
  return [Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)];
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { EventHandler } from '../lib/utils/event_handler.js';
3
+ import { EventHandler } from '../../../utils/event_handler.js';
4
4
 
5
5
  let {
6
6
  children = $bindable(),
@@ -15,7 +15,10 @@
15
15
  } = $props();
16
16
 
17
17
  let dialog: HTMLDialogElement | null = null;
18
- export const eventHandler = new EventHandler();
18
+ export const eventHandler = new EventHandler<{
19
+ open: void;
20
+ close: void;
21
+ }>();
19
22
 
20
23
  export function getNode(): HTMLDialogElement {
21
24
  return dialog!;
@@ -1,5 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
- import { EventHandler } from '../lib/utils/event_handler.js';
2
+ import { EventHandler } from '../../../utils/event_handler.js';
3
3
  type $$ComponentProps = {
4
4
  children?: Snippet;
5
5
  size?: 'big' | 'fullscreen' | 'small';
@@ -7,7 +7,10 @@ type $$ComponentProps = {
7
7
  onclose?: () => void;
8
8
  };
9
9
  declare const Dialog: import("svelte").Component<$$ComponentProps, {
10
- eventHandler: EventHandler;
10
+ eventHandler: EventHandler<{
11
+ open: void;
12
+ close: void;
13
+ }>;
11
14
  getNode: () => HTMLDialogElement;
12
15
  open: () => void;
13
16
  close: () => void;
@@ -1,13 +1,16 @@
1
1
  <script lang="ts">
2
2
  import { tick } from 'svelte';
3
3
  import Dialog from './Dialog.svelte';
4
- import { EventHandler } from '../lib/utils/event_handler.js';
4
+ import { EventHandler } from '../../../utils/event_handler.js';
5
5
 
6
6
  type Mode = 'download' | 'new' | null;
7
7
  let mode: Mode = $state(null);
8
8
  let dialog: Dialog | null = null;
9
9
  let input: HTMLInputElement | null = $state(null);
10
- let eventHandler = new EventHandler();
10
+ let eventHandler = new EventHandler<{
11
+ A: void;
12
+ B: void;
13
+ }>();
11
14
 
12
15
  async function openDialog(newMode: Mode) {
13
16
  if (!dialog) return;
package/dist/index.d.ts CHANGED
@@ -3,4 +3,4 @@ import BBoxMap from './components/BBoxMap/BBoxMap.svelte';
3
3
  import LocatorMap from './components/LocatorMap/LocatorMap.svelte';
4
4
  import MapEditor from './components/MapEditor/MapEditor.svelte';
5
5
  export { BasicMap, BBoxMap, LocatorMap, MapEditor };
6
- export { type BBox } from './components/BBoxMap/lib/bbox.js';
6
+ export { type BBox } from './components/BBoxMap/lib/bbox_drawer.js';
package/dist/index.js CHANGED
@@ -3,4 +3,4 @@ import BBoxMap from './components/BBoxMap/BBoxMap.svelte';
3
3
  import LocatorMap from './components/LocatorMap/LocatorMap.svelte';
4
4
  import MapEditor from './components/MapEditor/MapEditor.svelte';
5
5
  export { BasicMap, BBoxMap, LocatorMap, MapEditor };
6
- export {} from './components/BBoxMap/lib/bbox.js';
6
+ export {} from './components/BBoxMap/lib/bbox_drawer.js';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Callback type that accepts a payload only if the event actually carries data.
3
+ * For data‑less events (`void` or `undefined`), the callback takes no argument.
4
+ */
5
+ type EventCallback<T> = T extends void | undefined ? () => void : (data: T) => void;
6
+ export declare class EventHandler<Events extends Record<string, unknown> = {}> {
7
+ /** monotonically increasing id for every registered callback */
8
+ private index;
9
+ /**
10
+ * Per‑event registry that keeps the correct data type for every callback
11
+ * (`drag` maps to `{x: number; y: number}`, `dragEnd` to the same, etc.).
12
+ */
13
+ private events;
14
+ /** Emit an event (with data only if the event defines some) */
15
+ emit<K extends keyof Events>(name: K, ...args: Events[K] extends void | undefined ? [] : [data: Events[K]]): void;
16
+ /** Register a new listener; returns its numeric id so it can be removed later */
17
+ on<K extends keyof Events>(name: K, callback: EventCallback<Events[K]>): number;
18
+ /** Register a listener that will fire exactly once */
19
+ once<K extends keyof Events>(name: K, callback: EventCallback<Events[K]>): number;
20
+ /** Remove a single listener by id, or all listeners for an event */
21
+ off<K extends keyof Events>(name: K, id?: number): void;
22
+ /** Clear the entire registry */
23
+ clear(): void;
24
+ }
25
+ export {};
@@ -0,0 +1,50 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
2
+ export class EventHandler {
3
+ /** monotonically increasing id for every registered callback */
4
+ index = 0;
5
+ /**
6
+ * Per‑event registry that keeps the correct data type for every callback
7
+ * (`drag` maps to `{x: number; y: number}`, `dragEnd` to the same, etc.).
8
+ */
9
+ events = {};
10
+ /** Emit an event (with data only if the event defines some) */
11
+ emit(name, ...args) {
12
+ const payload = args[0];
13
+ this.events[name]?.forEach((cb) => cb(payload));
14
+ }
15
+ /** Register a new listener; returns its numeric id so it can be removed later */
16
+ on(name, callback) {
17
+ if (!callback)
18
+ throw new Error('Callback is required');
19
+ const bucket = (this.events[name] ??= new Map());
20
+ const id = ++this.index;
21
+ bucket.set(id, callback);
22
+ return id;
23
+ }
24
+ /** Register a listener that will fire exactly once */
25
+ once(name, callback) {
26
+ const id = this.on(name, ((data) => {
27
+ this.off(name, id);
28
+ callback(data);
29
+ }));
30
+ return id;
31
+ }
32
+ /** Remove a single listener by id, or all listeners for an event */
33
+ off(name, id) {
34
+ const bucket = this.events[name];
35
+ if (!bucket)
36
+ return;
37
+ if (id !== undefined) {
38
+ bucket.delete(id);
39
+ }
40
+ else {
41
+ delete this.events[name];
42
+ }
43
+ }
44
+ /** Clear the entire registry */
45
+ clear() {
46
+ for (const key in this.events) {
47
+ delete this.events[key];
48
+ }
49
+ }
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versatiles/svelte",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "build": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && vite build && npm run package",
@@ -1,10 +0,0 @@
1
- export declare class EventHandler {
2
- private index;
3
- private events;
4
- constructor();
5
- emit(event: string): void;
6
- on(name: string, callback: () => void): number;
7
- once(name: string, callback: () => void): number;
8
- off(name: string, index?: number): void;
9
- clear(): void;
10
- }
@@ -1,39 +0,0 @@
1
- export class EventHandler {
2
- index = 0;
3
- events = new Map();
4
- constructor() { }
5
- emit(event) {
6
- this.events.get(event)?.forEach((callback) => callback());
7
- }
8
- on(name, callback) {
9
- if (!callback)
10
- throw new Error('Callback is required');
11
- if (!this.events.has(name))
12
- this.events.set(name, new Map());
13
- this.index++;
14
- this.events.get(name).set(this.index, callback);
15
- return this.index;
16
- }
17
- once(name, callback) {
18
- if (!callback)
19
- throw new Error('Callback is required');
20
- const index = this.on(name, () => {
21
- this.off(name, index);
22
- callback();
23
- });
24
- return index;
25
- }
26
- off(name, index) {
27
- if (!this.events.has(name))
28
- return;
29
- if (index) {
30
- this.events.get(name)?.delete(index);
31
- }
32
- else {
33
- this.events.delete(name);
34
- }
35
- }
36
- clear() {
37
- this.events.clear();
38
- }
39
- }