@deck.gl/widgets 9.2.10 → 9.3.0-alpha.1

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 (111) hide show
  1. package/dist/compass-widget.js +5 -5
  2. package/dist/compass-widget.js.map +1 -1
  3. package/dist/context-menu-widget.d.ts +23 -18
  4. package/dist/context-menu-widget.d.ts.map +1 -1
  5. package/dist/context-menu-widget.js +44 -48
  6. package/dist/context-menu-widget.js.map +1 -1
  7. package/dist/dist.dev.js +2533 -420
  8. package/dist/fullscreen-widget.js +1 -1
  9. package/dist/fullscreen-widget.js.map +1 -1
  10. package/dist/geocoder-widget.d.ts +2 -4
  11. package/dist/geocoder-widget.d.ts.map +1 -1
  12. package/dist/geocoder-widget.js +16 -20
  13. package/dist/geocoder-widget.js.map +1 -1
  14. package/dist/gimbal-widget.d.ts +0 -3
  15. package/dist/gimbal-widget.d.ts.map +1 -1
  16. package/dist/gimbal-widget.js +4 -22
  17. package/dist/gimbal-widget.js.map +1 -1
  18. package/dist/index.cjs +921 -370
  19. package/dist/index.cjs.map +4 -4
  20. package/dist/index.d.ts +5 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +4 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/info-widget.d.ts +38 -17
  25. package/dist/info-widget.d.ts.map +1 -1
  26. package/dist/info-widget.js +40 -130
  27. package/dist/info-widget.js.map +1 -1
  28. package/dist/lib/components/dropdown-menu.d.ts +10 -4
  29. package/dist/lib/components/dropdown-menu.d.ts.map +1 -1
  30. package/dist/lib/components/dropdown-menu.js +12 -7
  31. package/dist/lib/components/dropdown-menu.js.map +1 -1
  32. package/dist/lib/components/icon-menu.d.ts.map +1 -1
  33. package/dist/lib/components/icon-menu.js +2 -3
  34. package/dist/lib/components/icon-menu.js.map +1 -1
  35. package/dist/lib/components/popover.d.ts +30 -0
  36. package/dist/lib/components/popover.d.ts.map +1 -0
  37. package/dist/lib/components/popover.js +93 -0
  38. package/dist/lib/components/popover.js.map +1 -0
  39. package/dist/lib/components/range-input.d.ts +26 -0
  40. package/dist/lib/components/range-input.d.ts.map +1 -0
  41. package/dist/lib/components/range-input.js +261 -0
  42. package/dist/lib/components/range-input.js.map +1 -0
  43. package/dist/lib/components/user-content.d.ts +8 -0
  44. package/dist/lib/components/user-content.d.ts.map +1 -0
  45. package/dist/lib/components/user-content.js +15 -0
  46. package/dist/lib/components/user-content.js.map +1 -0
  47. package/dist/popup-widget.d.ts +63 -0
  48. package/dist/popup-widget.d.ts.map +1 -0
  49. package/dist/popup-widget.js +70 -0
  50. package/dist/popup-widget.js.map +1 -0
  51. package/dist/reset-view-widget.d.ts +1 -1
  52. package/dist/reset-view-widget.d.ts.map +1 -1
  53. package/dist/reset-view-widget.js +12 -11
  54. package/dist/reset-view-widget.js.map +1 -1
  55. package/dist/scrollbar-widget.d.ts +68 -0
  56. package/dist/scrollbar-widget.d.ts.map +1 -0
  57. package/dist/scrollbar-widget.js +181 -0
  58. package/dist/scrollbar-widget.js.map +1 -0
  59. package/dist/stats-widget.d.ts +4 -0
  60. package/dist/stats-widget.d.ts.map +1 -1
  61. package/dist/stats-widget.js +3 -3
  62. package/dist/stats-widget.js.map +1 -1
  63. package/dist/stylesheet.css +244 -29
  64. package/dist/theme-widget.d.ts.map +1 -1
  65. package/dist/theme-widget.js +7 -5
  66. package/dist/theme-widget.js.map +1 -1
  67. package/dist/themes.d.ts +30 -0
  68. package/dist/themes.d.ts.map +1 -1
  69. package/dist/themes.js +28 -4
  70. package/dist/themes.js.map +1 -1
  71. package/dist/timeline-widget.d.ts +25 -6
  72. package/dist/timeline-widget.d.ts.map +1 -1
  73. package/dist/timeline-widget.js +46 -18
  74. package/dist/timeline-widget.js.map +1 -1
  75. package/dist/zoom-widget.d.ts +2 -8
  76. package/dist/zoom-widget.d.ts.map +1 -1
  77. package/dist/zoom-widget.js +23 -18
  78. package/dist/zoom-widget.js.map +1 -1
  79. package/dist.min.js +2 -2
  80. package/package.json +4 -3
  81. package/src/compass-widget.tsx +5 -5
  82. package/src/context-menu-widget.tsx +86 -76
  83. package/src/fullscreen-widget.tsx +1 -1
  84. package/src/geocoder-widget.tsx +17 -24
  85. package/src/gimbal-widget.tsx +4 -26
  86. package/src/index.ts +11 -6
  87. package/src/info-widget.tsx +93 -157
  88. package/src/lib/components/dropdown-menu.tsx +42 -20
  89. package/src/lib/components/icon-menu.tsx +3 -4
  90. package/src/lib/components/popover.tsx +167 -0
  91. package/src/lib/components/range-input.tsx +432 -0
  92. package/src/lib/components/user-content.tsx +30 -0
  93. package/src/popup-widget.tsx +163 -0
  94. package/src/reset-view-widget.tsx +12 -11
  95. package/src/scrollbar-widget.tsx +288 -0
  96. package/src/stats-widget.tsx +10 -4
  97. package/src/stylesheet.css +244 -29
  98. package/src/theme-widget.tsx +7 -5
  99. package/src/themes.ts +35 -4
  100. package/src/timeline-widget.tsx +96 -33
  101. package/src/zoom-widget.tsx +25 -21
  102. package/dist/lib/components/grouped-icon-button.d.ts +0 -10
  103. package/dist/lib/components/grouped-icon-button.d.ts.map +0 -1
  104. package/dist/lib/components/grouped-icon-button.js +0 -7
  105. package/dist/lib/components/grouped-icon-button.js.map +0 -1
  106. package/dist/lib/components/simple-menu.d.ts +0 -16
  107. package/dist/lib/components/simple-menu.d.ts.map +0 -1
  108. package/dist/lib/components/simple-menu.js +0 -30
  109. package/dist/lib/components/simple-menu.js.map +0 -1
  110. package/src/lib/components/grouped-icon-button.tsx +0 -27
  111. package/src/lib/components/simple-menu.tsx +0 -52
@@ -0,0 +1,432 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+ import type {JSX} from 'preact';
5
+ import {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'preact/hooks';
6
+
7
+ export type RangeInputDecoration = {
8
+ position: [start: number, end: number];
9
+ element: JSX.Element;
10
+ };
11
+
12
+ export type RangeInputProps = {
13
+ className?: string;
14
+ min: number;
15
+ max: number;
16
+ step: number;
17
+ value: [start: number, end: number];
18
+ orientation: 'horizontal' | 'vertical';
19
+ pageSize?: number;
20
+ /** Show step buttons at ends */
21
+ stepButtons?: boolean;
22
+ startButtonAriaLabel?: string;
23
+ endButtonAriaLabel?: string;
24
+ /** Target element for wheel and keyboard events.
25
+ * If not supplied, falls back to the root element of the input.
26
+ */
27
+ eventTarget?: HTMLElement | null;
28
+ decorations?: RangeInputDecoration[];
29
+ onChange?: (nextValue: [start: number, end: number]) => void;
30
+ };
31
+
32
+ type DragState = {
33
+ pointerId: number;
34
+ startCoord: number;
35
+ startRatio: number;
36
+ min: number;
37
+ max: number;
38
+ maxStart: number;
39
+ };
40
+
41
+ const wheelListenerOptions: AddEventListenerOptions = {passive: false};
42
+
43
+ const clamp = (value: number, min: number, max: number) => {
44
+ if (value < min) {
45
+ return min;
46
+ }
47
+ if (value > max) {
48
+ return max;
49
+ }
50
+ return value;
51
+ };
52
+
53
+ const getTrackDimension = (track: HTMLDivElement | null, vertical: boolean) => {
54
+ if (!track || !track.firstElementChild) {
55
+ return [0, 0];
56
+ }
57
+ const rect = track.firstElementChild.getBoundingClientRect();
58
+ if (vertical) {
59
+ return [rect.top, rect.height];
60
+ }
61
+ return vertical ? [rect.top, rect.height] : [rect.left, rect.width];
62
+ };
63
+
64
+ const getEffectiveStep = (step: number, range: number) => {
65
+ if (typeof step === 'number' && !Number.isNaN(step) && step > 0) {
66
+ return step;
67
+ }
68
+ return Math.max(1, range / 10 || 1);
69
+ };
70
+
71
+ const getEffectivePage = (pageSize: number | undefined, rangeSize: number) => {
72
+ if (typeof pageSize === 'number' && !Number.isNaN(pageSize) && pageSize > 0) {
73
+ return pageSize;
74
+ }
75
+ return Math.max(1, rangeSize || 1);
76
+ };
77
+
78
+ export function RangeInput(props: RangeInputProps) {
79
+ const {
80
+ className = '',
81
+ min,
82
+ max,
83
+ step,
84
+ value,
85
+ orientation,
86
+ pageSize,
87
+ stepButtons = false,
88
+ startButtonAriaLabel,
89
+ endButtonAriaLabel,
90
+ eventTarget,
91
+ decorations = [],
92
+ onChange
93
+ } = props;
94
+
95
+ const vertical = orientation !== 'horizontal';
96
+ const rootRef = useRef<HTMLDivElement>(null);
97
+ const trackRef = useRef<HTMLDivElement>(null);
98
+ const thumbRef = useRef<HTMLDivElement>(null);
99
+ const dragStateRef = useRef<DragState | null>(null);
100
+ const [trackLength, setTrackLength] = useState(0);
101
+
102
+ const range = max - min;
103
+ const rangeSize = Math.max(0, value[1] - value[0]);
104
+ const maxStart = Math.max(0, range - rangeSize);
105
+ const clampedStart = clamp(value[0], min, min + maxStart);
106
+
107
+ const {thumbLength, thumbOffset} = useMemo(() => {
108
+ if (trackLength <= 0 || range <= 0) {
109
+ return {thumbLength: 0, thumbOffset: 0};
110
+ }
111
+
112
+ if (range <= rangeSize) {
113
+ return {thumbLength: 1, thumbOffset: 0};
114
+ }
115
+
116
+ const nextThumbLength = rangeSize / range;
117
+ const travel = Math.max(0, 1 - nextThumbLength);
118
+ const ratio = maxStart <= 0 ? 0 : clamp((clampedStart - min) / maxStart, 0, 1);
119
+
120
+ return {
121
+ thumbLength: Math.max(0, Math.min(nextThumbLength, 1)),
122
+ thumbOffset: travel * ratio
123
+ };
124
+ }, [trackLength, range, rangeSize, maxStart, clampedStart, min]);
125
+
126
+ const emitRange = useCallback(
127
+ (nextStart: number) => {
128
+ if (!onChange) {
129
+ return;
130
+ }
131
+ const clamped = clamp(nextStart, min, min + maxStart);
132
+ onChange([clamped, clamped + rangeSize]);
133
+ },
134
+ [onChange, min, maxStart, rangeSize]
135
+ );
136
+
137
+ const handleStepNegative = useCallback(
138
+ (event: MouseEvent) => {
139
+ event.stopPropagation();
140
+ emitRange(clampedStart - getEffectiveStep(step, range));
141
+ },
142
+ [emitRange, clampedStart, step, range]
143
+ );
144
+
145
+ const handleStepPositive = useCallback(
146
+ (event: MouseEvent) => {
147
+ event.stopPropagation();
148
+ emitRange(clampedStart + getEffectiveStep(step, range));
149
+ },
150
+ [emitRange, clampedStart, step, range]
151
+ );
152
+
153
+ const handleTrackClick = useCallback(
154
+ (event: MouseEvent) => {
155
+ if (event.button !== 0) {
156
+ return;
157
+ }
158
+ const target = event.target as HTMLElement | null;
159
+ if (target?.dataset.scrollbarThumb === 'true') {
160
+ return;
161
+ }
162
+
163
+ const track = trackRef.current;
164
+ if (!track) {
165
+ return;
166
+ }
167
+
168
+ event.preventDefault();
169
+ event.stopPropagation();
170
+
171
+ const [trackStart] = getTrackDimension(track, vertical);
172
+ const coordinate = vertical ? event.clientY - trackStart : event.clientX - trackStart;
173
+ const span = Math.max(1, 1 - thumbLength) * trackLength;
174
+ const thumbCenter = (thumbLength / 2) * trackLength;
175
+ const ratio = span <= 0 ? 0 : clamp((coordinate - thumbCenter) / span, 0, 1);
176
+ emitRange(min + ratio * maxStart);
177
+ },
178
+ [vertical, trackLength, thumbLength, emitRange, min, maxStart]
179
+ );
180
+
181
+ const handleThumbPointerDown = (event: PointerEvent) => {
182
+ if (event.button !== 0) {
183
+ return;
184
+ }
185
+ const track = trackRef.current;
186
+ if (!track) {
187
+ return;
188
+ }
189
+
190
+ dragStateRef.current = {
191
+ pointerId: event.pointerId,
192
+ startCoord: vertical ? event.clientY : event.clientX,
193
+ startRatio: thumbOffset,
194
+ min,
195
+ max,
196
+ maxStart
197
+ };
198
+
199
+ (event.currentTarget as HTMLDivElement).setPointerCapture(event.pointerId);
200
+ event.preventDefault();
201
+ event.stopPropagation();
202
+ };
203
+
204
+ const handleThumbPointerMove = useCallback(
205
+ (event: PointerEvent) => {
206
+ const state = dragStateRef.current;
207
+ if (!state || state.pointerId !== event.pointerId) {
208
+ return;
209
+ }
210
+
211
+ const [trackStart, trackLength] = getTrackDimension(trackRef.current, vertical);
212
+ const coordinate = vertical ? event.clientY : event.clientX;
213
+ const delta = coordinate - state.startCoord;
214
+
215
+ const ratio = state.startRatio + delta / trackLength;
216
+
217
+ const nextStart = clamp(ratio * (state.max - state.min), 0, state.maxStart) + state.min;
218
+ emitRange(nextStart);
219
+ event.preventDefault();
220
+ },
221
+ [emitRange, vertical]
222
+ );
223
+
224
+ const handleThumbPointerUp = useCallback((event: PointerEvent) => {
225
+ const state = dragStateRef.current;
226
+ if (state && state.pointerId === event.pointerId) {
227
+ dragStateRef.current = null;
228
+ thumbRef.current?.releasePointerCapture(event.pointerId);
229
+ event.preventDefault();
230
+ }
231
+ }, []);
232
+
233
+ const handleKeyDown = useCallback(
234
+ (event: KeyboardEvent) => {
235
+ switch (event.key) {
236
+ case 'ArrowUp':
237
+ case 'ArrowLeft':
238
+ if ((vertical && event.key === 'ArrowUp') || (!vertical && event.key === 'ArrowLeft')) {
239
+ emitRange(clampedStart - getEffectiveStep(step, range));
240
+ event.preventDefault();
241
+ }
242
+ break;
243
+ case 'ArrowDown':
244
+ case 'ArrowRight':
245
+ if (
246
+ (vertical && event.key === 'ArrowDown') ||
247
+ (!vertical && event.key === 'ArrowRight')
248
+ ) {
249
+ emitRange(clampedStart + getEffectiveStep(step, range));
250
+ event.preventDefault();
251
+ }
252
+ break;
253
+ case 'PageUp':
254
+ emitRange(clampedStart - getEffectivePage(pageSize, rangeSize));
255
+ event.preventDefault();
256
+ break;
257
+ case 'PageDown':
258
+ emitRange(clampedStart + getEffectivePage(pageSize, rangeSize));
259
+ event.preventDefault();
260
+ break;
261
+ case 'Home':
262
+ emitRange(min);
263
+ event.preventDefault();
264
+ break;
265
+ case 'End':
266
+ emitRange(min + maxStart);
267
+ event.preventDefault();
268
+ break;
269
+ default:
270
+ break;
271
+ }
272
+ },
273
+ [vertical, emitRange, clampedStart, step, range, pageSize, rangeSize, min, maxStart]
274
+ );
275
+
276
+ const handleWheel = useCallback(
277
+ (event: WheelEvent) => {
278
+ event.preventDefault();
279
+ event.stopPropagation();
280
+
281
+ if (maxStart === 0) {
282
+ return;
283
+ }
284
+
285
+ let delta = vertical ? event.deltaY : event.deltaX;
286
+ if (!vertical && delta === 0) {
287
+ delta = event.deltaY;
288
+ }
289
+
290
+ if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
291
+ delta *= getEffectiveStep(step, range);
292
+ } else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
293
+ delta *= getEffectivePage(pageSize, rangeSize);
294
+ }
295
+
296
+ if (delta === 0) {
297
+ return;
298
+ }
299
+
300
+ emitRange(clampedStart + delta);
301
+ },
302
+ [emitRange, clampedStart, maxStart, vertical, step, range, pageSize, rangeSize]
303
+ );
304
+
305
+ useLayoutEffect(() => {
306
+ setTrackLength(getTrackDimension(trackRef.current, vertical)[1]);
307
+ }, [vertical]);
308
+
309
+ useLayoutEffect(() => {
310
+ const track = trackRef.current;
311
+ if (!track) {
312
+ return undefined;
313
+ }
314
+
315
+ const update = () => {
316
+ setTrackLength(getTrackDimension(track, vertical)[1]);
317
+ };
318
+
319
+ update();
320
+
321
+ if (typeof ResizeObserver !== 'undefined') {
322
+ const observer = new ResizeObserver(update);
323
+ observer.observe(track);
324
+ return () => observer.disconnect();
325
+ }
326
+
327
+ if (typeof window !== 'undefined') {
328
+ window.addEventListener('resize', update);
329
+ return () => window.removeEventListener('resize', update);
330
+ }
331
+
332
+ return undefined;
333
+ }, [vertical]);
334
+
335
+ useEffect(() => {
336
+ const eventRoot = eventTarget ?? rootRef.current;
337
+ if (!eventRoot) return undefined;
338
+
339
+ eventRoot.addEventListener('keydown', handleKeyDown);
340
+ eventRoot.addEventListener('wheel', handleWheel, wheelListenerOptions);
341
+ return () => {
342
+ eventRoot.removeEventListener('keydown', handleKeyDown);
343
+ eventRoot.removeEventListener('wheel', handleWheel, wheelListenerOptions);
344
+ };
345
+ }, [eventTarget, handleKeyDown, handleWheel]);
346
+
347
+ const decorationElements = useMemo(() => {
348
+ if (!decorations.length || range <= 0) {
349
+ return [];
350
+ }
351
+
352
+ return decorations.map((decoration, index) => {
353
+ const [start, end] = decoration.position;
354
+ const startRatio = (start - min) / range;
355
+ const endRatio = (end - min) / range;
356
+ const offsetPct = Math.round(startRatio * 1000) / 10;
357
+ const sizePct = Math.max(0, Math.round((endRatio - startRatio) * 1000) / 10);
358
+ const style = vertical
359
+ ? {
360
+ left: '0',
361
+ width: '100%',
362
+ top: `${offsetPct}%`,
363
+ height: `${sizePct}%`
364
+ }
365
+ : {
366
+ top: '0',
367
+ height: '100%',
368
+ left: `${offsetPct}%`,
369
+ width: `${sizePct}%`
370
+ };
371
+
372
+ return (
373
+ <div key={`decoration-${index}`} className="deck-widget-range__decoration" style={style}>
374
+ {decoration.element}
375
+ </div>
376
+ );
377
+ });
378
+ }, [decorations, range, min, vertical]);
379
+
380
+ return (
381
+ <div
382
+ ref={rootRef}
383
+ tabIndex={0}
384
+ role="scrollbar"
385
+ aria-valuemin={min}
386
+ aria-valuemax={min + maxStart}
387
+ aria-valuenow={clampedStart}
388
+ aria-orientation={orientation}
389
+ className={`${className} deck-widget-range deck-widget-range--${orientation} ${maxStart === 0 ? 'deck-widget-range--disabled' : ''}`}
390
+ >
391
+ {stepButtons && (
392
+ <button
393
+ type="button"
394
+ className="deck-widget-range__button deck-widget-range__button--start"
395
+ aria-label={startButtonAriaLabel}
396
+ disabled={clampedStart <= min}
397
+ onClick={handleStepNegative}
398
+ >
399
+ <span className="deck-widget-icon" />
400
+ </button>
401
+ )}
402
+ <div className="deck-widget-range__track" ref={trackRef} onClick={handleTrackClick}>
403
+ <div className="deck-widget-range__decorations">{decorationElements}</div>
404
+ <div
405
+ className="deck-widget-range__thumb"
406
+ data-scrollbar-thumb="true"
407
+ ref={thumbRef}
408
+ style={
409
+ vertical
410
+ ? {height: `${thumbLength * 100}%`, top: `${thumbOffset * 100}%`}
411
+ : {width: `${thumbLength * 100}%`, left: `${thumbOffset * 100}%`}
412
+ }
413
+ onPointerDown={handleThumbPointerDown}
414
+ onPointerMove={handleThumbPointerMove}
415
+ onPointerUp={handleThumbPointerUp}
416
+ onPointerCancel={handleThumbPointerUp}
417
+ />
418
+ </div>
419
+ {stepButtons && (
420
+ <button
421
+ type="button"
422
+ className="deck-widget-range__button deck-widget-range__button--end"
423
+ aria-label={endButtonAriaLabel}
424
+ disabled={clampedStart >= min + maxStart}
425
+ onClick={handleStepPositive}
426
+ >
427
+ <span className="deck-widget-icon" />
428
+ </button>
429
+ )}
430
+ </div>
431
+ );
432
+ }
@@ -0,0 +1,30 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+ import type {JSX, ComponentChildren} from 'preact';
5
+ import {useEffect, useRef} from 'preact/hooks';
6
+
7
+ export type UserContentProps = JSX.HTMLAttributes<HTMLDivElement> & {
8
+ text?: string;
9
+ html?: string;
10
+ element?: HTMLElement | null;
11
+ };
12
+
13
+ export const UserContent = ({text, html, element, ...props}: UserContentProps) => {
14
+ const containerRef = useRef<HTMLDivElement>(null);
15
+
16
+ useEffect(() => {
17
+ if (containerRef.current && element) {
18
+ containerRef.current.append(element);
19
+ }
20
+ return () => {
21
+ element?.remove();
22
+ };
23
+ }, [element]);
24
+
25
+ return (
26
+ <div ref={containerRef} {...props} dangerouslySetInnerHTML={html ? {__html: html} : undefined}>
27
+ {text}
28
+ </div>
29
+ );
30
+ };
@@ -0,0 +1,163 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Widget} from '@deck.gl/core';
6
+ import type {Deck, Viewport, WidgetProps} from '@deck.gl/core';
7
+ import {render, JSX} from 'preact';
8
+ import {Popover, type PopoverProps} from './lib/components/popover';
9
+ import {UserContent} from './lib/components/user-content';
10
+ import {IconButton} from './lib/components/icon-button';
11
+
12
+ export type PopupContent = {
13
+ text?: string;
14
+ html?: string;
15
+ element?: HTMLElement | null;
16
+ };
17
+
18
+ export type PopupWidgetProps = WidgetProps & {
19
+ /** View to attach to and interact with. Required when using multiple views */
20
+ viewId?: string | null;
21
+ /** Content to display at the anchor. Opens the popup if clicked. */
22
+ marker?: PopupContent | null;
23
+ /** Content to display in the popup */
24
+ content: string | PopupContent;
25
+ /** Whether the pop up is open by default
26
+ * @default true
27
+ */
28
+ defaultIsOpen?: boolean;
29
+ /** Anchor of the popup in world coordinates, e.g. [longitude, latitude]. */
30
+ position: number[];
31
+ /** Position popup relative to the anchor.
32
+ * @default 'right'
33
+ */
34
+ placement?: PopoverProps['placement'];
35
+ /** Pixel offset
36
+ * @default 10
37
+ */
38
+ offset?: PopoverProps['offset'];
39
+ /**
40
+ * Show an arrow pointing at the anchor. Optionally accepts a pixel size.
41
+ * @default 10
42
+ */
43
+ arrow?: PopoverProps['arrow'];
44
+ /** Whether to show a close button in the popup
45
+ * @default true
46
+ */
47
+ closeButton?: boolean;
48
+ /** Close the popup if clicked outside
49
+ * @default false
50
+ */
51
+ closeOnClickOutside?: boolean;
52
+ /** Callback when popup is opened/closed */
53
+ onOpenChange?: (isOpen: boolean) => void;
54
+ };
55
+
56
+ export class PopupWidget extends Widget<PopupWidgetProps> {
57
+ static defaultProps: Required<PopupWidgetProps> = {
58
+ ...Widget.defaultProps,
59
+ id: 'info',
60
+ viewId: null,
61
+ position: [0, 0],
62
+ marker: null,
63
+ defaultIsOpen: true,
64
+ content: '',
65
+ placement: 'right',
66
+ offset: 10,
67
+ arrow: 10,
68
+ closeButton: true,
69
+ closeOnClickOutside: false,
70
+ onOpenChange: () => {}
71
+ };
72
+
73
+ className = 'deck-widget-popup';
74
+ placement = 'fill' as const;
75
+ viewport?: Viewport;
76
+ isOpen: boolean;
77
+
78
+ constructor(props: PopupWidgetProps) {
79
+ super(props);
80
+ this.setProps(this.props);
81
+ this.isOpen = this.props.defaultIsOpen;
82
+ }
83
+
84
+ setProps(props: Partial<PopupWidgetProps>) {
85
+ this.viewId = props.viewId ?? this.viewId;
86
+ super.setProps(props);
87
+ }
88
+
89
+ onAdd({deck}: {deck: Deck<any>; viewId: string | null}) {
90
+ this.deck = deck;
91
+ }
92
+
93
+ onViewportChange(viewport) {
94
+ this.viewport = viewport;
95
+ this.updateHTML();
96
+ }
97
+
98
+ onClick() {
99
+ if (this.props.closeOnClickOutside) {
100
+ this._setIsOpen(false);
101
+ }
102
+ }
103
+
104
+ protected _setIsOpen(isOpen: boolean) {
105
+ if (this.isOpen === isOpen) return;
106
+ this.isOpen = isOpen;
107
+ this.props.onOpenChange?.(isOpen);
108
+ this.updateHTML();
109
+ }
110
+
111
+ onRenderHTML(rootElement: HTMLElement): void {
112
+ if (!this.viewport) {
113
+ render(null, rootElement);
114
+ return;
115
+ }
116
+ const {marker, content, style} = this.props;
117
+ // Project the clicked geographic coordinate to canvas (x, y)
118
+ const [x, y] = this.viewport.project(this.props.position);
119
+
120
+ // Render the popup container with a content box and a placeholder for the arrow.
121
+ // The container is positioned absolutely (initially at 0,0) and will be repositioned after measuring.
122
+ const ui = (
123
+ <div>
124
+ {marker && (
125
+ <div
126
+ style={{position: 'absolute', left: x, top: y, pointerEvents: 'all', cursor: 'pointer'}}
127
+ >
128
+ <UserContent {...marker} onClick={() => this._setIsOpen(true)} />
129
+ </div>
130
+ )}
131
+ {this.isOpen && (
132
+ <Popover
133
+ x={x}
134
+ y={y}
135
+ placement={this.props.placement}
136
+ arrow={this.props.arrow}
137
+ arrowColor="var(--menu-background, #fff)"
138
+ offset={this.props.offset}
139
+ >
140
+ <div
141
+ className={`deck-widget-popup-content ${this.props.className}`}
142
+ style={style as JSX.CSSProperties}
143
+ >
144
+ {this.props.closeButton && (
145
+ <div
146
+ className="deck-widget-popup-controls"
147
+ style={{width: '100%', display: 'flex', justifyContent: 'end'}}
148
+ >
149
+ <IconButton
150
+ className="deck-widget-popup-close-button"
151
+ onClick={() => this._setIsOpen(false)}
152
+ />
153
+ </div>
154
+ )}
155
+ <UserContent {...(typeof content === 'string' ? {text: content} : content)} />
156
+ </div>
157
+ </Popover>
158
+ )}
159
+ </div>
160
+ );
161
+ render(ui, rootElement);
162
+ }
163
+ }
@@ -66,18 +66,19 @@ export class ResetViewWidget<ViewsT extends ViewOrViews = null> extends Widget<
66
66
 
67
67
  handleClick() {
68
68
  const initialViewState = this.props.initialViewState || this.deck?.props.initialViewState;
69
- this.setViewState(initialViewState);
69
+ this.resetViewState(initialViewState);
70
70
  }
71
71
 
72
- setViewState(viewState?: ViewStateMap<ViewsT>) {
73
- const viewId = (this.props.viewId || 'default-view') as unknown as string;
74
- const nextViewState = {
75
- ...(viewId !== 'default-view' ? viewState?.[viewId] : viewState)
76
- // only works for geospatial?
77
- // transitionDuration: this.props.transitionDuration,
78
- // transitionInterpolator: new FlyToInterpolator()
79
- };
80
- // @ts-ignore Using private method temporary until there's a public one
81
- this.deck._onViewStateChange({viewId, viewState: nextViewState, interactionState: {}});
72
+ resetViewState(viewState?: ViewStateMap<ViewsT>) {
73
+ const viewIds = this.viewId ? [this.viewId] : (this.deck?.getViews().map(v => v.id) ?? []);
74
+ for (const viewId of viewIds) {
75
+ const nextViewState = {
76
+ ...(viewState?.[viewId] ?? viewState)
77
+ // only works for geospatial?
78
+ // transitionDuration: this.props.transitionDuration,
79
+ // transitionInterpolator: new FlyToInterpolator()
80
+ };
81
+ this.setViewState(viewId, nextViewState);
82
+ }
82
83
  }
83
84
  }