@deck.gl/widgets 9.2.11 → 9.3.0-alpha.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.
Files changed (152) 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 +27 -20
  4. package/dist/context-menu-widget.d.ts.map +1 -1
  5. package/dist/context-menu-widget.js +45 -48
  6. package/dist/context-menu-widget.js.map +1 -1
  7. package/dist/dist.dev.js +3114 -869
  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/icon-widget.d.ts +28 -0
  19. package/dist/icon-widget.d.ts.map +1 -0
  20. package/dist/icon-widget.js +35 -0
  21. package/dist/icon-widget.js.map +1 -0
  22. package/dist/index.cjs +1271 -561
  23. package/dist/index.cjs.map +4 -4
  24. package/dist/index.d.ts +16 -12
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +11 -10
  27. package/dist/index.js.map +1 -1
  28. package/dist/info-widget.d.ts +39 -17
  29. package/dist/info-widget.d.ts.map +1 -1
  30. package/dist/info-widget.js +46 -130
  31. package/dist/info-widget.js.map +1 -1
  32. package/dist/lib/components/dropdown-menu.d.ts +12 -5
  33. package/dist/lib/components/dropdown-menu.d.ts.map +1 -1
  34. package/dist/lib/components/dropdown-menu.js +20 -11
  35. package/dist/lib/components/dropdown-menu.js.map +1 -1
  36. package/dist/lib/components/icon-button.d.ts +3 -0
  37. package/dist/lib/components/icon-button.d.ts.map +1 -1
  38. package/dist/lib/components/icon-button.js +10 -2
  39. package/dist/lib/components/icon-button.js.map +1 -1
  40. package/dist/lib/components/popover.d.ts +30 -0
  41. package/dist/lib/components/popover.d.ts.map +1 -0
  42. package/dist/lib/components/popover.js +93 -0
  43. package/dist/lib/components/popover.js.map +1 -0
  44. package/dist/lib/components/range-input.d.ts +26 -0
  45. package/dist/lib/components/range-input.d.ts.map +1 -0
  46. package/dist/lib/components/range-input.js +261 -0
  47. package/dist/lib/components/range-input.js.map +1 -0
  48. package/dist/lib/components/user-content.d.ts +8 -0
  49. package/dist/lib/components/user-content.d.ts.map +1 -0
  50. package/dist/lib/components/user-content.js +15 -0
  51. package/dist/lib/components/user-content.js.map +1 -0
  52. package/dist/lib/data-url.d.ts +5 -0
  53. package/dist/lib/data-url.d.ts.map +1 -0
  54. package/dist/lib/data-url.js +7 -0
  55. package/dist/lib/data-url.js.map +1 -0
  56. package/dist/popup-widget.d.ts +64 -0
  57. package/dist/popup-widget.d.ts.map +1 -0
  58. package/dist/popup-widget.js +76 -0
  59. package/dist/popup-widget.js.map +1 -0
  60. package/dist/reset-view-widget.d.ts +1 -1
  61. package/dist/reset-view-widget.d.ts.map +1 -1
  62. package/dist/reset-view-widget.js +12 -11
  63. package/dist/reset-view-widget.js.map +1 -1
  64. package/dist/scrollbar-widget.d.ts +68 -0
  65. package/dist/scrollbar-widget.d.ts.map +1 -0
  66. package/dist/scrollbar-widget.js +181 -0
  67. package/dist/scrollbar-widget.js.map +1 -0
  68. package/dist/selector-widget.d.ts +44 -0
  69. package/dist/selector-widget.d.ts.map +1 -0
  70. package/dist/selector-widget.js +64 -0
  71. package/dist/selector-widget.js.map +1 -0
  72. package/dist/splitter-widget.d.ts +52 -11
  73. package/dist/splitter-widget.d.ts.map +1 -1
  74. package/dist/splitter-widget.js +177 -76
  75. package/dist/splitter-widget.js.map +1 -1
  76. package/dist/stats-widget.d.ts +6 -1
  77. package/dist/stats-widget.d.ts.map +1 -1
  78. package/dist/stats-widget.js +39 -10
  79. package/dist/stats-widget.js.map +1 -1
  80. package/dist/stylesheet.css +291 -39
  81. package/dist/theme-widget.d.ts.map +1 -1
  82. package/dist/theme-widget.js +7 -5
  83. package/dist/theme-widget.js.map +1 -1
  84. package/dist/themes.d.ts +30 -0
  85. package/dist/themes.d.ts.map +1 -1
  86. package/dist/themes.js +28 -4
  87. package/dist/themes.js.map +1 -1
  88. package/dist/timeline-widget.d.ts +25 -6
  89. package/dist/timeline-widget.d.ts.map +1 -1
  90. package/dist/timeline-widget.js +51 -18
  91. package/dist/timeline-widget.js.map +1 -1
  92. package/dist/toggle-widget.d.ts +38 -0
  93. package/dist/toggle-widget.d.ts.map +1 -0
  94. package/dist/toggle-widget.js +47 -0
  95. package/dist/toggle-widget.js.map +1 -0
  96. package/dist/zoom-widget.d.ts +2 -8
  97. package/dist/zoom-widget.d.ts.map +1 -1
  98. package/dist/zoom-widget.js +23 -18
  99. package/dist/zoom-widget.js.map +1 -1
  100. package/dist.min.js +2 -2
  101. package/package.json +4 -3
  102. package/src/compass-widget.tsx +5 -5
  103. package/src/context-menu-widget.tsx +88 -78
  104. package/src/fullscreen-widget.tsx +1 -1
  105. package/src/geocoder-widget.tsx +17 -24
  106. package/src/gimbal-widget.tsx +4 -26
  107. package/src/icon-widget.tsx +69 -0
  108. package/src/index.ts +22 -15
  109. package/src/info-widget.tsx +100 -157
  110. package/src/lib/components/dropdown-menu.tsx +58 -29
  111. package/src/lib/components/icon-button.tsx +15 -3
  112. package/src/lib/components/popover.tsx +167 -0
  113. package/src/lib/components/range-input.tsx +432 -0
  114. package/src/lib/components/user-content.tsx +30 -0
  115. package/src/lib/data-url.ts +5 -0
  116. package/src/popup-widget.tsx +170 -0
  117. package/src/reset-view-widget.tsx +12 -11
  118. package/src/scrollbar-widget.tsx +288 -0
  119. package/src/selector-widget.tsx +111 -0
  120. package/src/splitter-widget.tsx +281 -96
  121. package/src/stats-widget.tsx +59 -12
  122. package/src/stylesheet.css +291 -39
  123. package/src/theme-widget.tsx +7 -5
  124. package/src/themes.ts +35 -4
  125. package/src/timeline-widget.tsx +103 -33
  126. package/src/toggle-widget.tsx +101 -0
  127. package/src/zoom-widget.tsx +25 -21
  128. package/dist/fps-widget.d.ts +0 -28
  129. package/dist/fps-widget.d.ts.map +0 -1
  130. package/dist/fps-widget.js +0 -51
  131. package/dist/fps-widget.js.map +0 -1
  132. package/dist/lib/components/grouped-icon-button.d.ts +0 -10
  133. package/dist/lib/components/grouped-icon-button.d.ts.map +0 -1
  134. package/dist/lib/components/grouped-icon-button.js +0 -7
  135. package/dist/lib/components/grouped-icon-button.js.map +0 -1
  136. package/dist/lib/components/icon-menu.d.ts +0 -16
  137. package/dist/lib/components/icon-menu.d.ts.map +0 -1
  138. package/dist/lib/components/icon-menu.js +0 -34
  139. package/dist/lib/components/icon-menu.js.map +0 -1
  140. package/dist/lib/components/simple-menu.d.ts +0 -16
  141. package/dist/lib/components/simple-menu.d.ts.map +0 -1
  142. package/dist/lib/components/simple-menu.js +0 -30
  143. package/dist/lib/components/simple-menu.js.map +0 -1
  144. package/dist/view-selector-widget.d.ts +0 -35
  145. package/dist/view-selector-widget.d.ts.map +0 -1
  146. package/dist/view-selector-widget.js +0 -63
  147. package/dist/view-selector-widget.js.map +0 -1
  148. package/src/fps-widget.tsx +0 -77
  149. package/src/lib/components/grouped-icon-button.tsx +0 -27
  150. package/src/lib/components/icon-menu.tsx +0 -71
  151. package/src/lib/components/simple-menu.tsx +0 -52
  152. package/src/view-selector-widget.tsx +0 -151
@@ -0,0 +1,288 @@
1
+ import {Widget} from '@deck.gl/core';
2
+ import type {Position, Viewport, WidgetPlacement, WidgetProps} from '@deck.gl/core';
3
+ import {render} from 'preact';
4
+ import {RangeInput, type RangeInputDecoration} from './lib/components/range-input';
5
+
6
+ export type ScrollbarOrientation = 'vertical' | 'horizontal';
7
+
8
+ export type ContentBounds = [min: Position, max: Position];
9
+
10
+ export type ScrollbarDecoration = {
11
+ contentBounds: ContentBounds;
12
+ color: string;
13
+ title?: string;
14
+ /** Callback when the decoration is clicked. Return `true` to mark the event as handled, and prevent the default behavior. */
15
+ onClick?: (evt: MouseEvent) => boolean;
16
+ };
17
+
18
+ const clamp = (value: number, min: number, max: number) => {
19
+ if (value < min) {
20
+ return min;
21
+ }
22
+ if (value > max) {
23
+ return max;
24
+ }
25
+ return value;
26
+ };
27
+
28
+ export type ScrollbarWidgetProps = WidgetProps & {
29
+ /** The full extent of the scrollable content, in world coordinates.
30
+ * The widget relies on this value to calculate the position and size of the slider button and track.
31
+ * If not supplied, the scrollbar will always be hidden.
32
+ */
33
+ contentBounds?: ContentBounds | null;
34
+ placement?: WidgetPlacement;
35
+ viewId?: string | null;
36
+ /** Direction of the scrollbar. `'horizontal'` scrolls the camera along the X axis, and `'vertical'` scrolls the camera along the Y axis.
37
+ * @default 'vertical'
38
+ */
39
+ orientation?: ScrollbarOrientation;
40
+ /* Pixels scrolled when clicked on the step buttons.
41
+ * @default 1/10 of the viewport size
42
+ */
43
+ stepSize?: number | null;
44
+ /* Pixels scrolled when clicked on the track.
45
+ * @default 100% of the viewport size
46
+ */
47
+ pageSize?: number | null;
48
+ /** Label of the step button at the start.
49
+ * @default 'Scroll left' | 'Scroll up'
50
+ */
51
+ startButtonAriaLabel?: string;
52
+ /** Label of the end button at the start.
53
+ * @default 'Scroll right' | 'Scroll down'
54
+ */
55
+ endButtonAriaLabel?: string;
56
+ /** If `true`, mouse wheel events over the canvas will be intercepted by this scrollbar.
57
+ * Useful when simulating the native scrollbar's behavior.
58
+ * @default false
59
+ */
60
+ captureWheel?: boolean;
61
+ /** Custom markers to overlay on the track. */
62
+ decorations?: ScrollbarDecoration[];
63
+ };
64
+
65
+ type ScrollbarWidgetRequiredProps = Required<ScrollbarWidgetProps>;
66
+
67
+ /** A scrollbar widget to be used with OrthographicView */
68
+ export class ScrollbarWidget extends Widget<ScrollbarWidgetProps> {
69
+ static override defaultProps: ScrollbarWidgetRequiredProps = {
70
+ ...Widget.defaultProps,
71
+ contentBounds: null,
72
+ placement: 'top-right',
73
+ viewId: null,
74
+ orientation: 'vertical',
75
+ stepSize: null,
76
+ pageSize: null,
77
+ startButtonAriaLabel: '',
78
+ endButtonAriaLabel: '',
79
+ captureWheel: false,
80
+ decorations: []
81
+ };
82
+
83
+ override className = 'deck-widget-scrollbar';
84
+
85
+ placement: WidgetPlacement = 'fill';
86
+
87
+ private viewport?: Viewport;
88
+ private contentSize = 0;
89
+ private viewportSize = 0;
90
+ private scrollOffset = 0;
91
+
92
+ constructor(props: ScrollbarWidgetProps) {
93
+ const resolved = {
94
+ ...ScrollbarWidget.defaultProps,
95
+ ...props
96
+ } as ScrollbarWidgetRequiredProps;
97
+ super(resolved);
98
+ this.viewId = resolved.viewId ?? null;
99
+ }
100
+
101
+ override onViewportChange(viewport: Viewport) {
102
+ this.viewport = viewport;
103
+ this.onRenderHTML();
104
+ }
105
+
106
+ override onRenderHTML(): void {
107
+ const element = this.rootElement;
108
+ if (!element) {
109
+ return;
110
+ }
111
+
112
+ element.dataset.placement = this.props.placement;
113
+ const viewport = this.viewport;
114
+ this.updateViewport(viewport);
115
+
116
+ const clampedOffset = this.getClampedOffset();
117
+ const wheelTarget = this.getWheelEventTarget(this.props.captureWheel ? 'global' : 'local');
118
+ const decorations = this.getDecorations(viewport);
119
+ const isVertical = this.isVertical();
120
+ const startLabel =
121
+ this.props.startButtonAriaLabel ?? (isVertical ? 'Scroll up' : 'Scroll left');
122
+ const endLabel = this.props.endButtonAriaLabel ?? (isVertical ? 'Scroll down' : 'Scroll right');
123
+
124
+ const ui = (
125
+ <RangeInput
126
+ min={0}
127
+ max={Math.max(0, this.contentSize)}
128
+ step={this.getEffectiveStep()}
129
+ pageSize={this.getEffectivePage()}
130
+ value={[clampedOffset, clampedOffset + this.viewportSize]}
131
+ orientation={this.props.orientation}
132
+ stepButtons
133
+ startButtonAriaLabel={startLabel}
134
+ endButtonAriaLabel={endLabel}
135
+ eventTarget={wheelTarget}
136
+ decorations={decorations}
137
+ onChange={this.handleRangeChange}
138
+ />
139
+ );
140
+
141
+ render(ui, element);
142
+ }
143
+
144
+ override onRemove(): void {
145
+ if (this.rootElement) {
146
+ render(null, this.rootElement);
147
+ }
148
+ super.onRemove();
149
+ }
150
+
151
+ private updateViewport(viewport?: Viewport): void {
152
+ if (!viewport) {
153
+ this.contentSize = 0;
154
+ this.scrollOffset = 0;
155
+ this.viewportSize = 0;
156
+ return;
157
+ }
158
+
159
+ const {contentBounds} = this.props;
160
+ const isVertical = this.isVertical();
161
+ const projectedBounds = contentBounds
162
+ ? projectBounds(contentBounds, viewport, isVertical)
163
+ : ([0, 0] as [number, number]);
164
+
165
+ this.contentSize = projectedBounds[1] - projectedBounds[0];
166
+ this.scrollOffset = -projectedBounds[0];
167
+ this.viewportSize = isVertical ? viewport.height : viewport.width;
168
+ }
169
+
170
+ private getDecorations(viewport?: Viewport): RangeInputDecoration[] {
171
+ const {decorations = [], contentBounds} = this.props;
172
+ if (!viewport || !contentBounds || decorations.length === 0) {
173
+ return [];
174
+ }
175
+
176
+ const isVertical = this.isVertical();
177
+ const [contentStart] = projectBounds(contentBounds, viewport, isVertical);
178
+
179
+ return decorations.map(decoration => {
180
+ const [start, end] = projectBounds(decoration.contentBounds, viewport, isVertical);
181
+ const onClick = decoration.onClick
182
+ ? (e: MouseEvent) => {
183
+ const handled = decoration.onClick?.(e);
184
+ if (handled) {
185
+ e.stopPropagation();
186
+ e.preventDefault();
187
+ }
188
+ }
189
+ : undefined;
190
+ return {
191
+ position: [start - contentStart, end - contentStart],
192
+ element: (
193
+ <div
194
+ style={{
195
+ pointerEvents: onClick ? 'all' : 'none',
196
+ width: '100%',
197
+ height: '100%',
198
+ backgroundColor: decoration.color
199
+ }}
200
+ title={decoration.title}
201
+ onClick={onClick}
202
+ />
203
+ )
204
+ };
205
+ });
206
+ }
207
+
208
+ private getWheelEventTarget(mode: 'global' | 'local' | null) {
209
+ if (mode === null) return null;
210
+ if (mode === 'local') return this.rootElement;
211
+
212
+ return this.deck?.props.parent || this.deck?.getCanvas()?.parentElement || this.rootElement;
213
+ }
214
+
215
+ private getMaxScroll(): number {
216
+ return Math.max(0, this.contentSize - this.viewportSize);
217
+ }
218
+
219
+ private getClampedOffset(): number {
220
+ const maxScroll = this.getMaxScroll();
221
+ return clamp(this.scrollOffset, 0, maxScroll);
222
+ }
223
+
224
+ private isVertical(): boolean {
225
+ return this.props.orientation !== 'horizontal';
226
+ }
227
+
228
+ private getEffectiveStep(): number {
229
+ if (typeof this.props.stepSize === 'number' && !Number.isNaN(this.props.stepSize)) {
230
+ return this.props.stepSize;
231
+ }
232
+ return Math.max(1, this.viewportSize / 10 || 1);
233
+ }
234
+
235
+ private getEffectivePage(): number {
236
+ if (typeof this.props.pageSize === 'number' && !Number.isNaN(this.props.pageSize)) {
237
+ return this.props.pageSize;
238
+ }
239
+ return this.viewportSize;
240
+ }
241
+
242
+ private emitScroll(next: number): void {
243
+ const maxScroll = this.getMaxScroll();
244
+ const target = clamp(Math.round(next), 0, maxScroll);
245
+ const viewport = this.viewport;
246
+ if (viewport && target !== this.getClampedOffset()) {
247
+ const pixel = viewport.project(viewport.position) as [number, number];
248
+ if (this.isVertical()) {
249
+ pixel[1] -= target - this.scrollOffset;
250
+ } else {
251
+ pixel[0] -= target - this.scrollOffset;
252
+ }
253
+ const {target: newTarget} = viewport.panByPosition(viewport.position, pixel);
254
+
255
+ // @ts-expect-error Using private method temporary until there's a public one
256
+ this.deck._onViewStateChange({
257
+ viewId: viewport.id,
258
+ viewState: {
259
+ ...this.getViewState(viewport.id),
260
+ target: newTarget
261
+ },
262
+ interactionState: {}
263
+ });
264
+ }
265
+ }
266
+
267
+ private handleRangeChange = (nextValue: [number, number]) => {
268
+ this.emitScroll(nextValue[0]);
269
+ };
270
+ }
271
+
272
+ function projectBounds(
273
+ bounds: ContentBounds,
274
+ viewport: Viewport,
275
+ isVertical: boolean
276
+ ): [min: number, max: number] {
277
+ return bounds
278
+ .map(([x, y]) => viewport.project([x, y, 0]))
279
+ .reduce(
280
+ (range: [number, number], [x, y]) => {
281
+ const value = isVertical ? y : x;
282
+ range[0] = Math.min(range[0], value);
283
+ range[1] = Math.max(range[1], value);
284
+ return range;
285
+ },
286
+ [Infinity, -Infinity]
287
+ );
288
+ }
@@ -0,0 +1,111 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {render} from 'preact';
6
+ import {Widget, type WidgetProps, type WidgetPlacement} from '@deck.gl/core';
7
+ import {SimpleMenu, type MenuItem} from './lib/components/dropdown-menu';
8
+ import {Popover, type PopoverProps} from './lib/components/popover';
9
+ import {IconButton} from './lib/components/icon-button';
10
+
11
+ export type SelectorWidgetOption<ValueT = string> = {
12
+ value: ValueT;
13
+ icon: string;
14
+ label?: string;
15
+ };
16
+
17
+ /** Properties for the ViewSelectorWidget */
18
+ export type SelectorWidgetProps<ValueT = string> = WidgetProps & {
19
+ /** Widget positioning within the view. Default 'top-left'. */
20
+ placement?: WidgetPlacement;
21
+ /** View to attach to and interact with. Required when using multiple views. */
22
+ viewId?: string | null;
23
+ options: SelectorWidgetOption<ValueT>[];
24
+ /** The initial value. Default to the first option. */
25
+ initialValue?: ValueT;
26
+ /** Callback invoked when the value changes */
27
+ onChange?: (value: ValueT) => void;
28
+ };
29
+
30
+ /**
31
+ * A widget that renders a popup menu for selecting a view mode.
32
+ * It displays a button with the current view mode icon. Clicking the button
33
+ * toggles a popup that shows three icons for:
34
+ * - Single view
35
+ * - Two views, split horizontally
36
+ * - Two views, split vertically
37
+ */
38
+ export class SelectorWidget<ValueT = string> extends Widget<SelectorWidgetProps<ValueT>> {
39
+ static defaultProps: Required<SelectorWidgetProps> = {
40
+ ...Widget.defaultProps,
41
+ id: 'view-selector',
42
+ placement: 'top-left',
43
+ viewId: null,
44
+ initialValue: '',
45
+ options: [],
46
+ onChange: () => {}
47
+ };
48
+
49
+ className = 'deck-widget-selector';
50
+ placement: WidgetPlacement = 'top-left';
51
+ value: ValueT;
52
+ isOpen: {x: number; y: number; placement: PopoverProps['placement']} | false = false;
53
+
54
+ constructor(props: SelectorWidgetProps<ValueT>) {
55
+ super(props);
56
+ this.value = this.props.initialValue;
57
+ this.setProps(this.props);
58
+ }
59
+
60
+ setProps(props: Partial<SelectorWidgetProps<ValueT>>) {
61
+ this.placement = props.placement ?? this.placement;
62
+ this.viewId = props.viewId ?? this.viewId;
63
+ super.setProps(props);
64
+ }
65
+
66
+ onRenderHTML(rootElement: HTMLElement) {
67
+ const selectedOption =
68
+ this.props.options.find(opt => opt.value === this.value) ?? this.props.options[0];
69
+
70
+ render(
71
+ <div>
72
+ <IconButton
73
+ icon={selectedOption.icon}
74
+ label={selectedOption.label}
75
+ onClick={this._toggleMenu}
76
+ />
77
+ {this.isOpen && (
78
+ <Popover {...this.isOpen}>
79
+ <SimpleMenu
80
+ isOpen
81
+ style={{pointerEvents: 'auto', position: 'static'}}
82
+ menuItems={this.props.options as MenuItem[]}
83
+ onSelect={this._handleSelectMode}
84
+ onClose={this._toggleMenu}
85
+ />
86
+ </Popover>
87
+ )}
88
+ </div>,
89
+ rootElement
90
+ );
91
+ }
92
+
93
+ private _toggleMenu = () => {
94
+ if (this.isOpen) {
95
+ this.isOpen = false;
96
+ } else if (this.rootElement) {
97
+ this.isOpen = {
98
+ x: this.rootElement.offsetLeft,
99
+ y: this.rootElement.offsetTop,
100
+ placement: this.props.placement.includes('right') ? 'left-start' : 'right-start'
101
+ };
102
+ }
103
+ this.updateHTML();
104
+ };
105
+
106
+ private _handleSelectMode = (value: string) => {
107
+ this.value = value as ValueT;
108
+ this.props.onChange(value as ValueT);
109
+ this.updateHTML();
110
+ };
111
+ }