@deck.gl/widgets 9.2.11 → 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.
- package/dist/compass-widget.js +5 -5
- package/dist/compass-widget.js.map +1 -1
- package/dist/context-menu-widget.d.ts +23 -18
- package/dist/context-menu-widget.d.ts.map +1 -1
- package/dist/context-menu-widget.js +44 -48
- package/dist/context-menu-widget.js.map +1 -1
- package/dist/dist.dev.js +2533 -420
- package/dist/fullscreen-widget.js +1 -1
- package/dist/fullscreen-widget.js.map +1 -1
- package/dist/geocoder-widget.d.ts +2 -4
- package/dist/geocoder-widget.d.ts.map +1 -1
- package/dist/geocoder-widget.js +16 -20
- package/dist/geocoder-widget.js.map +1 -1
- package/dist/gimbal-widget.d.ts +0 -3
- package/dist/gimbal-widget.d.ts.map +1 -1
- package/dist/gimbal-widget.js +4 -22
- package/dist/gimbal-widget.js.map +1 -1
- package/dist/index.cjs +921 -370
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/info-widget.d.ts +38 -17
- package/dist/info-widget.d.ts.map +1 -1
- package/dist/info-widget.js +40 -130
- package/dist/info-widget.js.map +1 -1
- package/dist/lib/components/dropdown-menu.d.ts +10 -4
- package/dist/lib/components/dropdown-menu.d.ts.map +1 -1
- package/dist/lib/components/dropdown-menu.js +12 -7
- package/dist/lib/components/dropdown-menu.js.map +1 -1
- package/dist/lib/components/icon-menu.d.ts.map +1 -1
- package/dist/lib/components/icon-menu.js +2 -3
- package/dist/lib/components/icon-menu.js.map +1 -1
- package/dist/lib/components/popover.d.ts +30 -0
- package/dist/lib/components/popover.d.ts.map +1 -0
- package/dist/lib/components/popover.js +93 -0
- package/dist/lib/components/popover.js.map +1 -0
- package/dist/lib/components/range-input.d.ts +26 -0
- package/dist/lib/components/range-input.d.ts.map +1 -0
- package/dist/lib/components/range-input.js +261 -0
- package/dist/lib/components/range-input.js.map +1 -0
- package/dist/lib/components/user-content.d.ts +8 -0
- package/dist/lib/components/user-content.d.ts.map +1 -0
- package/dist/lib/components/user-content.js +15 -0
- package/dist/lib/components/user-content.js.map +1 -0
- package/dist/popup-widget.d.ts +63 -0
- package/dist/popup-widget.d.ts.map +1 -0
- package/dist/popup-widget.js +70 -0
- package/dist/popup-widget.js.map +1 -0
- package/dist/reset-view-widget.d.ts +1 -1
- package/dist/reset-view-widget.d.ts.map +1 -1
- package/dist/reset-view-widget.js +12 -11
- package/dist/reset-view-widget.js.map +1 -1
- package/dist/scrollbar-widget.d.ts +68 -0
- package/dist/scrollbar-widget.d.ts.map +1 -0
- package/dist/scrollbar-widget.js +181 -0
- package/dist/scrollbar-widget.js.map +1 -0
- package/dist/stats-widget.d.ts +4 -0
- package/dist/stats-widget.d.ts.map +1 -1
- package/dist/stats-widget.js +3 -3
- package/dist/stats-widget.js.map +1 -1
- package/dist/stylesheet.css +244 -29
- package/dist/theme-widget.d.ts.map +1 -1
- package/dist/theme-widget.js +7 -5
- package/dist/theme-widget.js.map +1 -1
- package/dist/themes.d.ts +30 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +28 -4
- package/dist/themes.js.map +1 -1
- package/dist/timeline-widget.d.ts +25 -6
- package/dist/timeline-widget.d.ts.map +1 -1
- package/dist/timeline-widget.js +46 -18
- package/dist/timeline-widget.js.map +1 -1
- package/dist/zoom-widget.d.ts +2 -8
- package/dist/zoom-widget.d.ts.map +1 -1
- package/dist/zoom-widget.js +23 -18
- package/dist/zoom-widget.js.map +1 -1
- package/dist.min.js +2 -2
- package/package.json +4 -3
- package/src/compass-widget.tsx +5 -5
- package/src/context-menu-widget.tsx +86 -76
- package/src/fullscreen-widget.tsx +1 -1
- package/src/geocoder-widget.tsx +17 -24
- package/src/gimbal-widget.tsx +4 -26
- package/src/index.ts +11 -6
- package/src/info-widget.tsx +93 -157
- package/src/lib/components/dropdown-menu.tsx +42 -20
- package/src/lib/components/icon-menu.tsx +3 -4
- package/src/lib/components/popover.tsx +167 -0
- package/src/lib/components/range-input.tsx +432 -0
- package/src/lib/components/user-content.tsx +30 -0
- package/src/popup-widget.tsx +163 -0
- package/src/reset-view-widget.tsx +12 -11
- package/src/scrollbar-widget.tsx +288 -0
- package/src/stats-widget.tsx +10 -4
- package/src/stylesheet.css +244 -29
- package/src/theme-widget.tsx +7 -5
- package/src/themes.ts +35 -4
- package/src/timeline-widget.tsx +96 -33
- package/src/zoom-widget.tsx +25 -21
- package/dist/lib/components/grouped-icon-button.d.ts +0 -10
- package/dist/lib/components/grouped-icon-button.d.ts.map +0 -1
- package/dist/lib/components/grouped-icon-button.js +0 -7
- package/dist/lib/components/grouped-icon-button.js.map +0 -1
- package/dist/lib/components/simple-menu.d.ts +0 -16
- package/dist/lib/components/simple-menu.d.ts.map +0 -1
- package/dist/lib/components/simple-menu.js +0 -30
- package/dist/lib/components/simple-menu.js.map +0 -1
- package/src/lib/components/grouped-icon-button.tsx +0 -27
- 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.
|
|
69
|
+
this.resetViewState(initialViewState);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
}
|