@deck.gl-community/widgets 9.2.0-beta.5

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 (65) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +43 -0
  3. package/dist/_deprecate/long-press-button.d.ts +13 -0
  4. package/dist/_deprecate/long-press-button.d.ts.map +1 -0
  5. package/dist/_deprecate/long-press-button.js +32 -0
  6. package/dist/_deprecate/long-press-button.js.map +1 -0
  7. package/dist/_deprecate/view-control-widget.d.ts +78 -0
  8. package/dist/_deprecate/view-control-widget.d.ts.map +1 -0
  9. package/dist/_deprecate/view-control-widget.js +198 -0
  10. package/dist/_deprecate/view-control-widget.js.map +1 -0
  11. package/dist/index.cjs +708 -0
  12. package/dist/index.cjs.map +7 -0
  13. package/dist/index.d.ts +13 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +10 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/widgets/html-cluster-widget.d.ts +25 -0
  18. package/dist/widgets/html-cluster-widget.d.ts.map +1 -0
  19. package/dist/widgets/html-cluster-widget.js +39 -0
  20. package/dist/widgets/html-cluster-widget.js.map +1 -0
  21. package/dist/widgets/html-overlay-item.d.ts +13 -0
  22. package/dist/widgets/html-overlay-item.d.ts.map +1 -0
  23. package/dist/widgets/html-overlay-item.js +10 -0
  24. package/dist/widgets/html-overlay-item.js.map +1 -0
  25. package/dist/widgets/html-overlay-widget.d.ts +45 -0
  26. package/dist/widgets/html-overlay-widget.d.ts.map +1 -0
  27. package/dist/widgets/html-overlay-widget.js +112 -0
  28. package/dist/widgets/html-overlay-widget.js.map +1 -0
  29. package/dist/widgets/html-tooltip-widget.d.ts +30 -0
  30. package/dist/widgets/html-tooltip-widget.d.ts.map +1 -0
  31. package/dist/widgets/html-tooltip-widget.js +67 -0
  32. package/dist/widgets/html-tooltip-widget.js.map +1 -0
  33. package/dist/widgets/long-press-button.d.ts +22 -0
  34. package/dist/widgets/long-press-button.d.ts.map +1 -0
  35. package/dist/widgets/long-press-button.js +84 -0
  36. package/dist/widgets/long-press-button.js.map +1 -0
  37. package/dist/widgets/long-press-controller.d.ts +27 -0
  38. package/dist/widgets/long-press-controller.d.ts.map +1 -0
  39. package/dist/widgets/long-press-controller.js +144 -0
  40. package/dist/widgets/long-press-controller.js.map +1 -0
  41. package/dist/widgets/pan-widget.d.ts +33 -0
  42. package/dist/widgets/pan-widget.d.ts.map +1 -0
  43. package/dist/widgets/pan-widget.js +141 -0
  44. package/dist/widgets/pan-widget.js.map +1 -0
  45. package/dist/widgets/view-manager-utils.d.ts +11 -0
  46. package/dist/widgets/view-manager-utils.d.ts.map +1 -0
  47. package/dist/widgets/view-manager-utils.js +13 -0
  48. package/dist/widgets/view-manager-utils.js.map +1 -0
  49. package/dist/widgets/zoom-range-widget.d.ts +43 -0
  50. package/dist/widgets/zoom-range-widget.d.ts.map +1 -0
  51. package/dist/widgets/zoom-range-widget.js +190 -0
  52. package/dist/widgets/zoom-range-widget.js.map +1 -0
  53. package/package.json +41 -0
  54. package/src/_deprecate/long-press-button.tsx +50 -0
  55. package/src/_deprecate/view-control-widget.tsx +339 -0
  56. package/src/index.ts +18 -0
  57. package/src/widgets/html-cluster-widget.ts +84 -0
  58. package/src/widgets/html-overlay-item.tsx +32 -0
  59. package/src/widgets/html-overlay-widget.tsx +147 -0
  60. package/src/widgets/html-tooltip-widget.tsx +93 -0
  61. package/src/widgets/long-press-button.tsx +125 -0
  62. package/src/widgets/long-press-controller.ts +159 -0
  63. package/src/widgets/pan-widget.tsx +182 -0
  64. package/src/widgets/view-manager-utils.ts +24 -0
  65. package/src/widgets/zoom-range-widget.tsx +284 -0
@@ -0,0 +1,182 @@
1
+ // deck.gl-community
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {render} from 'preact';
6
+ import type {JSX} from 'preact';
7
+ import {LongPressButton} from './long-press-button';
8
+ import {cloneViewState, hasViewManager} from './view-manager-utils';
9
+ import {
10
+ Widget,
11
+ type Deck,
12
+ type Viewport,
13
+ type WidgetPlacement,
14
+ type WidgetProps
15
+ } from '@deck.gl/core';
16
+
17
+ export type PanWidgetProps = WidgetProps & {
18
+ viewId?: string | null;
19
+ placement?: WidgetPlacement;
20
+ /** Amount in screen pixels to pan by when a button is pressed. */
21
+ step?: number;
22
+ };
23
+
24
+ const WRAPPER_STYLE: Partial<CSSStyleDeclaration> = {
25
+ position: 'absolute',
26
+ display: 'flex',
27
+ flexDirection: 'column',
28
+ alignItems: 'center',
29
+ zIndex: '99',
30
+ userSelect: 'none'
31
+ };
32
+
33
+ const NAVIGATION_CONTAINER_STYLE: JSX.CSSProperties = {
34
+ position: 'relative',
35
+ background: '#f7f7f7',
36
+ borderRadius: '23px',
37
+ border: '0.5px solid #eaeaea',
38
+ boxShadow: 'inset 11px 11px 5px -7px rgba(230, 230, 230, 0.49)',
39
+ height: '46px',
40
+ width: '46px'
41
+ };
42
+
43
+ const NAVIGATION_BUTTON_STYLE: JSX.CSSProperties = {
44
+ color: '#848484',
45
+ cursor: 'pointer',
46
+ position: 'absolute',
47
+ pointerEvents: 'auto'
48
+ };
49
+
50
+ export class PanWidget extends Widget<PanWidgetProps> {
51
+ static override defaultProps = {
52
+ id: 'pan',
53
+ viewId: null,
54
+ placement: 'top-left',
55
+ step: 48,
56
+ style: {},
57
+ className: ''
58
+ } satisfies Required<WidgetProps> & Required<Pick<PanWidgetProps, 'step'>> & PanWidgetProps;
59
+
60
+ placement: WidgetPlacement = 'top-left';
61
+ className = 'deck-widget-pan';
62
+ deck?: Deck | null = null;
63
+ step: number;
64
+
65
+ constructor(props: PanWidgetProps = {}) {
66
+ super({...PanWidget.defaultProps, ...props});
67
+ this.viewId = props.viewId ?? null;
68
+ this.placement = props.placement ?? 'top-left';
69
+ this.step = props.step ?? PanWidget.defaultProps.step;
70
+ }
71
+
72
+ override setProps(props: Partial<PanWidgetProps>): void {
73
+ if (props.viewId !== undefined) {
74
+ this.viewId = props.viewId;
75
+ }
76
+ if (props.placement !== undefined) {
77
+ this.placement = props.placement;
78
+ }
79
+ if (props.step !== undefined) {
80
+ this.step = props.step;
81
+ }
82
+ super.setProps(props);
83
+ }
84
+
85
+ override onAdd({deck, viewId}: {deck: Deck; viewId: string | null}): void {
86
+ this.deck = deck;
87
+ if (this.viewId === undefined) {
88
+ this.viewId = viewId;
89
+ }
90
+ }
91
+
92
+ override onRemove(): void {
93
+ this.deck = null;
94
+ }
95
+
96
+ override onRenderHTML(rootElement: HTMLElement): void {
97
+ const style = {...WRAPPER_STYLE, ...this.props.style};
98
+ Object.assign(rootElement.style, style);
99
+
100
+ const buttons = [
101
+ {top: -2, left: 14, onClick: () => this.handlePan(0, this.step), label: '▲', key: 'up'},
102
+ {top: 12, left: 0, onClick: () => this.handlePan(this.step, 0), label: '◀', key: 'left'},
103
+ {top: 12, left: 28, onClick: () => this.handlePan(-this.step, 0), label: '▶', key: 'right'},
104
+ {top: 25, left: 14, onClick: () => this.handlePan(0, -this.step), label: '▼', key: 'down'}
105
+ ] as const;
106
+
107
+ const ui = (
108
+ <div style={NAVIGATION_CONTAINER_STYLE}>
109
+ {buttons.map((button) => {
110
+ const buttonStyle: JSX.CSSProperties = {
111
+ ...NAVIGATION_BUTTON_STYLE,
112
+ top: `${button.top}px`,
113
+ left: `${button.left}px`
114
+ };
115
+
116
+ return (
117
+ <div key={button.key} style={buttonStyle}>
118
+ <LongPressButton onClick={button.onClick}>{button.label}</LongPressButton>
119
+ </div>
120
+ );
121
+ })}
122
+ </div>
123
+ );
124
+
125
+ render(ui, rootElement);
126
+ }
127
+
128
+ private getTargetViewports(): Viewport[] {
129
+ const deck = this.deck;
130
+ if (!deck) {
131
+ return [];
132
+ }
133
+
134
+ if (this.viewId) {
135
+ if (hasViewManager(deck)) {
136
+ const viewport = deck.viewManager?.getViewport(this.viewId);
137
+ return viewport ? [viewport] : [];
138
+ }
139
+ return [];
140
+ }
141
+ return deck.getViewports();
142
+ }
143
+
144
+ private getViewState(viewport: Viewport): any {
145
+ const deck = this.deck;
146
+ const viewManager = hasViewManager(deck) ? deck.viewManager : null;
147
+ const viewId = this.viewId || viewport.id;
148
+ if (viewManager) {
149
+ try {
150
+ return {...viewManager.getViewState(viewId)};
151
+ } catch (err) {
152
+ return cloneViewState(viewManager.viewState);
153
+ }
154
+ }
155
+ return cloneViewState(viewport);
156
+ }
157
+
158
+ private handlePan(deltaX: number, deltaY: number) {
159
+ if (!this.deck) {
160
+ return;
161
+ }
162
+
163
+ const viewports = this.getTargetViewports();
164
+ for (const viewport of viewports) {
165
+ const center = viewport.unproject([viewport.width / 2, viewport.height / 2]);
166
+ if (center) {
167
+ const nextPixel: [number, number] = [
168
+ viewport.width / 2 + deltaX,
169
+ viewport.height / 2 + deltaY
170
+ ];
171
+
172
+ const viewState = this.getViewState(viewport);
173
+ const panUpdate = viewport.panByPosition(center, nextPixel);
174
+ const nextViewState = {...viewState, ...panUpdate};
175
+ const viewId = this.viewId || viewport.id || 'default-view';
176
+
177
+ // @ts-ignore Using private method until a public alternative is available
178
+ this.deck._onViewStateChange({viewId, viewState: nextViewState, interactionState: {}});
179
+ }
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,24 @@
1
+ // deck.gl-community
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {Deck, Viewport} from '@deck.gl/core';
6
+
7
+ export type DeckWithViewManager = Deck & {
8
+ viewManager?: {
9
+ getViewport: (id: string) => Viewport | null;
10
+ getViewState: (id: string) => any;
11
+ viewState?: any;
12
+ };
13
+ };
14
+
15
+ export function hasViewManager(deck: Deck | null): deck is DeckWithViewManager {
16
+ return Boolean(deck && typeof deck === 'object' && 'viewManager' in deck);
17
+ }
18
+
19
+ export function cloneViewState(value: unknown): Record<string, unknown> {
20
+ if (value && typeof value === 'object') {
21
+ return {...(value as Record<string, unknown>)};
22
+ }
23
+ return {};
24
+ }
@@ -0,0 +1,284 @@
1
+ // deck.gl-community
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {render} from 'preact';
6
+ import type {JSX} from 'preact';
7
+ import {LongPressButton} from './long-press-button';
8
+ import {cloneViewState, hasViewManager} from './view-manager-utils';
9
+ import {
10
+ Widget,
11
+ type Deck,
12
+ type Viewport,
13
+ type WidgetPlacement,
14
+ type WidgetProps
15
+ } from '@deck.gl/core';
16
+
17
+ export type ZoomRangeWidgetProps = WidgetProps & {
18
+ viewId?: string | null;
19
+ placement?: WidgetPlacement;
20
+ minZoom?: number;
21
+ maxZoom?: number;
22
+ step?: number;
23
+ };
24
+
25
+ const WRAPPER_STYLE: Partial<CSSStyleDeclaration> = {
26
+ position: 'absolute',
27
+ display: 'flex',
28
+ flexDirection: 'column',
29
+ alignItems: 'center',
30
+ background: '#f7f7f7',
31
+ border: '0.5px solid #eaeaea',
32
+ marginTop: '6px',
33
+ padding: '2px 0',
34
+ width: '18px',
35
+ userSelect: 'none',
36
+ pointerEvents: 'auto'
37
+ };
38
+
39
+ const ZOOM_BUTTON_STYLE: JSX.CSSProperties = {
40
+ cursor: 'pointer',
41
+ fontSize: '14px',
42
+ fontWeight: '500',
43
+ margin: '-4px'
44
+ };
45
+
46
+ const SLIDER_CONTAINER_STYLE: JSX.CSSProperties = {
47
+ display: 'inline-block',
48
+ height: '100px',
49
+ padding: '0',
50
+ width: '10px'
51
+ };
52
+
53
+ const VERTICAL_SLIDER_STYLE: JSX.CSSProperties = {
54
+ writingMode: 'vertical-lr',
55
+ height: '100px',
56
+ padding: '0',
57
+ margin: '0',
58
+ width: '10px'
59
+ };
60
+
61
+ export class ZoomRangeWidget extends Widget<ZoomRangeWidgetProps> {
62
+ static override defaultProps = {
63
+ id: 'zoom-range',
64
+ viewId: null,
65
+ placement: 'top-left',
66
+ minZoom: undefined,
67
+ maxZoom: undefined,
68
+ step: 0.1,
69
+ style: {},
70
+ className: ''
71
+ } satisfies Required<WidgetProps> & Required<Pick<ZoomRangeWidgetProps, 'step'>> & ZoomRangeWidgetProps;
72
+
73
+ placement: WidgetPlacement = 'top-left';
74
+ className = 'deck-widget-zoom-range';
75
+ deck?: Deck | null = null;
76
+ step: number;
77
+ currentZoom = 0;
78
+ inferredMinZoom: number | null = null;
79
+ inferredMaxZoom: number | null = null;
80
+
81
+ constructor(props: ZoomRangeWidgetProps = {}) {
82
+ super({...ZoomRangeWidget.defaultProps, ...props});
83
+ this.viewId = props.viewId ?? null;
84
+ this.placement = props.placement ?? 'top-left';
85
+ this.step = props.step ?? ZoomRangeWidget.defaultProps.step;
86
+ }
87
+
88
+ override setProps(props: Partial<ZoomRangeWidgetProps>): void {
89
+ if (props.viewId !== undefined) {
90
+ this.viewId = props.viewId;
91
+ }
92
+ if (props.placement !== undefined) {
93
+ this.placement = props.placement;
94
+ }
95
+ if (props.step !== undefined) {
96
+ this.step = props.step;
97
+ }
98
+ super.setProps(props);
99
+ }
100
+
101
+ override onAdd({deck, viewId}: {deck: Deck; viewId: string | null}): void {
102
+ this.deck = deck;
103
+ if (this.viewId === undefined) {
104
+ this.viewId = viewId;
105
+ }
106
+ }
107
+
108
+ override onRemove(): void {
109
+ this.deck = null;
110
+ }
111
+
112
+ override onRenderHTML(rootElement: HTMLElement): void {
113
+ const style = {...WRAPPER_STYLE, ...this.props.style};
114
+ Object.assign(rootElement.style, style);
115
+
116
+ const {minZoom, maxZoom} = this.getZoomBounds();
117
+ const clampedZoom = Math.max(minZoom, Math.min(maxZoom, this.currentZoom));
118
+
119
+ const stopEventPropagation = (event: Event) => {
120
+ event.stopPropagation();
121
+ if (typeof (event as any).stopImmediatePropagation === 'function') {
122
+ (event as any).stopImmediatePropagation();
123
+ }
124
+ };
125
+
126
+ const ui = (
127
+ <>
128
+ <div style={ZOOM_BUTTON_STYLE}>
129
+ <LongPressButton onClick={() => this.handleZoomDelta(this.step)}>{'+'}</LongPressButton>
130
+ </div>
131
+ <div
132
+ style={SLIDER_CONTAINER_STYLE}
133
+ onPointerDown={stopEventPropagation}
134
+ onPointerMove={stopEventPropagation}
135
+ onPointerUp={stopEventPropagation}
136
+ onMouseDown={stopEventPropagation}
137
+ onMouseMove={stopEventPropagation}
138
+ onMouseUp={stopEventPropagation}
139
+ onClick={stopEventPropagation}
140
+ onWheel={stopEventPropagation}
141
+ onTouchStart={stopEventPropagation}
142
+ onTouchMove={stopEventPropagation}
143
+ onTouchEnd={stopEventPropagation}
144
+ >
145
+ <input
146
+ type="range"
147
+ value={clampedZoom}
148
+ min={minZoom}
149
+ max={maxZoom}
150
+ step={this.step}
151
+ onInput={(event) =>
152
+ this.handleZoomTo(Number((event.target as HTMLInputElement).value))
153
+ }
154
+ onChange={(event) =>
155
+ this.handleZoomTo(Number((event.target as HTMLInputElement).value))
156
+ }
157
+ onPointerDown={stopEventPropagation}
158
+ onPointerMove={stopEventPropagation}
159
+ onPointerUp={stopEventPropagation}
160
+ onMouseDown={stopEventPropagation}
161
+ onMouseMove={stopEventPropagation}
162
+ onMouseUp={stopEventPropagation}
163
+ onClick={stopEventPropagation}
164
+ onWheel={stopEventPropagation}
165
+ onTouchStart={stopEventPropagation}
166
+ onTouchMove={stopEventPropagation}
167
+ onTouchEnd={stopEventPropagation}
168
+ /* @ts-expect-error - non-standard attribute for vertical sliders */
169
+ orient="vertical"
170
+ style={VERTICAL_SLIDER_STYLE}
171
+ />
172
+ </div>
173
+ <div style={ZOOM_BUTTON_STYLE}>
174
+ <LongPressButton onClick={() => this.handleZoomDelta(-this.step)}>{'-'}</LongPressButton>
175
+ </div>
176
+ </>
177
+ );
178
+
179
+ render(ui, rootElement);
180
+ }
181
+
182
+ override onViewportChange(viewport: Viewport): void {
183
+ const viewState = this.getViewState(viewport);
184
+ const zoom = Number(viewState?.zoom);
185
+ if (Number.isFinite(zoom)) {
186
+ this.currentZoom = zoom;
187
+ }
188
+
189
+ if (this.props.minZoom === undefined) {
190
+ const minZoom = Number(viewState?.minZoom);
191
+ if (Number.isFinite(minZoom)) {
192
+ this.inferredMinZoom = minZoom;
193
+ }
194
+ }
195
+
196
+ if (this.props.maxZoom === undefined) {
197
+ const maxZoom = Number(viewState?.maxZoom);
198
+ if (Number.isFinite(maxZoom)) {
199
+ this.inferredMaxZoom = maxZoom;
200
+ }
201
+ }
202
+
203
+ this.updateHTML();
204
+ }
205
+
206
+ private getZoomBounds(): {minZoom: number; maxZoom: number} {
207
+ const minZoom =
208
+ this.props.minZoom ?? this.inferredMinZoom ?? Number.NEGATIVE_INFINITY;
209
+ const maxZoom =
210
+ this.props.maxZoom ?? this.inferredMaxZoom ?? Number.POSITIVE_INFINITY;
211
+
212
+ if (minZoom > maxZoom) {
213
+ return {minZoom: maxZoom, maxZoom: minZoom};
214
+ }
215
+ return {
216
+ minZoom: Number.isFinite(minZoom) ? minZoom : -20,
217
+ maxZoom: Number.isFinite(maxZoom) ? maxZoom : 20
218
+ };
219
+ }
220
+
221
+ private getTargetViewports(): Viewport[] {
222
+ const deck = this.deck;
223
+ if (!deck) {
224
+ return [];
225
+ }
226
+ if (this.viewId) {
227
+ if (hasViewManager(deck)) {
228
+ const viewport = deck.viewManager?.getViewport(this.viewId);
229
+ return viewport ? [viewport] : [];
230
+ }
231
+ return [];
232
+ }
233
+ return deck.getViewports();
234
+ }
235
+
236
+ private getViewState(viewport: Viewport): any {
237
+ const deck = this.deck;
238
+ const viewManager = hasViewManager(deck) ? deck.viewManager : null;
239
+ const viewId = this.viewId || viewport.id;
240
+ if (viewManager) {
241
+ try {
242
+ return {...viewManager.getViewState(viewId)};
243
+ } catch (err) {
244
+ return cloneViewState(viewManager.viewState);
245
+ }
246
+ }
247
+ return cloneViewState(viewport);
248
+ }
249
+
250
+ private handleZoomDelta(delta: number) {
251
+ const {minZoom, maxZoom} = this.getZoomBounds();
252
+
253
+ for (const viewport of this.getTargetViewports()) {
254
+ const viewState = this.getViewState(viewport);
255
+ const baseZoom = Number(viewState.zoom);
256
+ const current = Number.isFinite(baseZoom) ? baseZoom : this.currentZoom;
257
+ const nextZoom = Math.max(minZoom, Math.min(maxZoom, current + delta));
258
+ this.updateViewState(viewport, {...viewState, zoom: nextZoom});
259
+ }
260
+ }
261
+
262
+ private handleZoomTo(zoom: number) {
263
+ const {minZoom, maxZoom} = this.getZoomBounds();
264
+ const nextZoom = Math.max(minZoom, Math.min(maxZoom, zoom));
265
+
266
+ for (const viewport of this.getTargetViewports()) {
267
+ const viewState = this.getViewState(viewport);
268
+ this.updateViewState(viewport, {...viewState, zoom: nextZoom});
269
+ }
270
+ }
271
+
272
+ private updateViewState(viewport: Viewport, viewState: any) {
273
+ if (!this.deck) {
274
+ return;
275
+ }
276
+
277
+ const viewId = this.viewId || viewport.id || 'default-view';
278
+ this.currentZoom = Number(viewState.zoom) || this.currentZoom;
279
+ this.updateHTML();
280
+
281
+ // @ts-ignore Using private method until a public alternative is available
282
+ this.deck._onViewStateChange({viewId, viewState, interactionState: {}});
283
+ }
284
+ }