@deck.gl/widgets 9.1.12 → 9.2.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 (157) hide show
  1. package/dist/compass-widget.d.ts +11 -34
  2. package/dist/compass-widget.d.ts.map +1 -1
  3. package/dist/compass-widget.js +27 -62
  4. package/dist/compass-widget.js.map +1 -1
  5. package/dist/context-menu-widget.d.ts +37 -0
  6. package/dist/context-menu-widget.d.ts.map +1 -0
  7. package/dist/context-menu-widget.js +74 -0
  8. package/dist/context-menu-widget.js.map +1 -0
  9. package/dist/dist.dev.js +2271 -581
  10. package/dist/fps-widget.d.ts +29 -0
  11. package/dist/fps-widget.d.ts.map +1 -0
  12. package/dist/fps-widget.js +51 -0
  13. package/dist/fps-widget.js.map +1 -0
  14. package/dist/fullscreen-widget.d.ts +15 -33
  15. package/dist/fullscreen-widget.d.ts.map +1 -1
  16. package/dist/fullscreen-widget.js +20 -51
  17. package/dist/fullscreen-widget.js.map +1 -1
  18. package/dist/geocoder-widget.d.ts +51 -0
  19. package/dist/geocoder-widget.d.ts.map +1 -0
  20. package/dist/geocoder-widget.js +138 -0
  21. package/dist/geocoder-widget.js.map +1 -0
  22. package/dist/gimbal-widget.d.ts +33 -0
  23. package/dist/gimbal-widget.d.ts.map +1 -0
  24. package/dist/gimbal-widget.js +116 -0
  25. package/dist/gimbal-widget.js.map +1 -0
  26. package/dist/index.cjs +1587 -341
  27. package/dist/index.cjs.map +4 -4
  28. package/dist/index.d.ts +37 -8
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +27 -7
  31. package/dist/index.js.map +1 -1
  32. package/dist/info-widget.d.ts +37 -0
  33. package/dist/info-widget.d.ts.map +1 -0
  34. package/dist/info-widget.js +183 -0
  35. package/dist/info-widget.js.map +1 -0
  36. package/dist/lib/components/button-group.d.ts +8 -0
  37. package/dist/lib/components/button-group.d.ts.map +1 -0
  38. package/dist/lib/components/button-group.js +7 -0
  39. package/dist/lib/components/button-group.js.map +1 -0
  40. package/dist/lib/components/dropdown-menu.d.ts +8 -0
  41. package/dist/lib/components/dropdown-menu.d.ts.map +1 -0
  42. package/dist/lib/components/dropdown-menu.js +55 -0
  43. package/dist/lib/components/dropdown-menu.js.map +1 -0
  44. package/dist/lib/components/grouped-icon-button.d.ts +8 -0
  45. package/dist/lib/components/grouped-icon-button.d.ts.map +1 -0
  46. package/dist/lib/components/grouped-icon-button.js +7 -0
  47. package/dist/lib/components/grouped-icon-button.js.map +1 -0
  48. package/dist/lib/components/icon-button.d.ts +11 -0
  49. package/dist/lib/components/icon-button.d.ts.map +1 -0
  50. package/dist/lib/components/icon-button.js +7 -0
  51. package/dist/lib/components/icon-button.js.map +1 -0
  52. package/dist/lib/components/icon-menu.d.ts +16 -0
  53. package/dist/lib/components/icon-menu.d.ts.map +1 -0
  54. package/dist/lib/components/icon-menu.js +49 -0
  55. package/dist/lib/components/icon-menu.js.map +1 -0
  56. package/dist/lib/components/simple-menu.d.ts +16 -0
  57. package/dist/lib/components/simple-menu.d.ts.map +1 -0
  58. package/dist/lib/components/simple-menu.js +30 -0
  59. package/dist/lib/components/simple-menu.js.map +1 -0
  60. package/dist/lib/geocode/geocoder-history.d.ts +23 -0
  61. package/dist/lib/geocode/geocoder-history.d.ts.map +1 -0
  62. package/dist/lib/geocode/geocoder-history.js +64 -0
  63. package/dist/lib/geocode/geocoder-history.js.map +1 -0
  64. package/dist/lib/geocode/geocoder.d.ts +15 -0
  65. package/dist/lib/geocode/geocoder.d.ts.map +1 -0
  66. package/dist/lib/geocode/geocoder.js +5 -0
  67. package/dist/lib/geocode/geocoder.js.map +1 -0
  68. package/dist/lib/geocode/geocoders.d.ts +68 -0
  69. package/dist/lib/geocode/geocoders.d.ts.map +1 -0
  70. package/dist/lib/geocode/geocoders.js +182 -0
  71. package/dist/lib/geocode/geocoders.js.map +1 -0
  72. package/dist/loading-widget.d.ts +26 -0
  73. package/dist/loading-widget.d.ts.map +1 -0
  74. package/dist/loading-widget.js +41 -0
  75. package/dist/loading-widget.js.map +1 -0
  76. package/dist/reset-view-widget.d.ts +4 -4
  77. package/dist/reset-view-widget.d.ts.map +1 -1
  78. package/dist/reset-view-widget.js +7 -10
  79. package/dist/reset-view-widget.js.map +1 -1
  80. package/dist/scale-widget.d.ts +28 -0
  81. package/dist/scale-widget.d.ts.map +1 -0
  82. package/dist/scale-widget.js +111 -0
  83. package/dist/scale-widget.js.map +1 -0
  84. package/dist/screenshot-widget.d.ts +4 -4
  85. package/dist/screenshot-widget.d.ts.map +1 -1
  86. package/dist/screenshot-widget.js +7 -14
  87. package/dist/screenshot-widget.js.map +1 -1
  88. package/dist/splitter-widget.d.ts +32 -0
  89. package/dist/splitter-widget.d.ts.map +1 -0
  90. package/dist/splitter-widget.js +123 -0
  91. package/dist/splitter-widget.js.map +1 -0
  92. package/dist/stats-widget.d.ts +36 -0
  93. package/dist/stats-widget.d.ts.map +1 -0
  94. package/dist/stats-widget.js +115 -0
  95. package/dist/stats-widget.js.map +1 -0
  96. package/dist/stylesheet.css +121 -4
  97. package/dist/theme-widget.d.ts +31 -0
  98. package/dist/theme-widget.d.ts.map +1 -0
  99. package/dist/theme-widget.js +83 -0
  100. package/dist/theme-widget.js.map +1 -0
  101. package/dist/themes.d.ts +65 -36
  102. package/dist/themes.d.ts.map +1 -1
  103. package/dist/themes.js +20 -8
  104. package/dist/themes.js.map +1 -1
  105. package/dist/timeline-widget.d.ts +59 -0
  106. package/dist/timeline-widget.d.ts.map +1 -0
  107. package/dist/timeline-widget.js +83 -0
  108. package/dist/timeline-widget.js.map +1 -0
  109. package/dist/view-selector-widget.d.ts +33 -0
  110. package/dist/view-selector-widget.d.ts.map +1 -0
  111. package/dist/view-selector-widget.js +62 -0
  112. package/dist/view-selector-widget.js.map +1 -0
  113. package/dist/zoom-widget.d.ts +14 -43
  114. package/dist/zoom-widget.d.ts.map +1 -1
  115. package/dist/zoom-widget.js +14 -56
  116. package/dist/zoom-widget.js.map +1 -1
  117. package/dist.min.js +3 -1
  118. package/package.json +4 -3
  119. package/src/compass-widget.tsx +44 -106
  120. package/src/context-menu-widget.tsx +115 -0
  121. package/src/fps-widget.tsx +67 -0
  122. package/src/fullscreen-widget.tsx +33 -86
  123. package/src/geocoder-widget.tsx +209 -0
  124. package/src/gimbal-widget.tsx +188 -0
  125. package/src/index.ts +58 -8
  126. package/src/info-widget.tsx +225 -0
  127. package/src/lib/components/button-group.tsx +16 -0
  128. package/src/lib/components/dropdown-menu.tsx +98 -0
  129. package/src/lib/components/grouped-icon-button.tsx +24 -0
  130. package/src/lib/components/icon-button.tsx +30 -0
  131. package/src/lib/components/icon-menu.tsx +89 -0
  132. package/src/lib/components/simple-menu.tsx +52 -0
  133. package/src/lib/geocode/geocoder-history.ts +73 -0
  134. package/src/lib/geocode/geocoder.ts +15 -0
  135. package/src/lib/geocode/geocoders.ts +199 -0
  136. package/src/loading-widget.tsx +67 -0
  137. package/src/reset-view-widget.tsx +8 -11
  138. package/src/scale-widget.tsx +183 -0
  139. package/src/screenshot-widget.tsx +9 -11
  140. package/src/splitter-widget.tsx +179 -0
  141. package/src/stats-widget.tsx +166 -0
  142. package/src/stylesheet.css +121 -4
  143. package/src/theme-widget.tsx +120 -0
  144. package/src/themes.ts +45 -12
  145. package/src/timeline-widget.tsx +155 -0
  146. package/src/view-selector-widget.tsx +108 -0
  147. package/src/zoom-widget.tsx +39 -113
  148. package/dist/components.d.ts +0 -4
  149. package/dist/components.d.ts.map +0 -1
  150. package/dist/components.js +0 -17
  151. package/dist/components.js.map +0 -1
  152. package/dist/widget-impl.d.ts +0 -29
  153. package/dist/widget-impl.d.ts.map +0 -1
  154. package/dist/widget-impl.js +0 -60
  155. package/dist/widget-impl.js.map +0 -1
  156. package/src/components.tsx +0 -38
  157. package/src/widget-impl.ts +0 -88
@@ -35,7 +35,7 @@
35
35
  flex-direction: column;
36
36
  }
37
37
 
38
- /* Common button styles */
38
+ /* Common button background styles */
39
39
  .deck-widget-button-group button,
40
40
  .deck-widget-button button {
41
41
  width: calc(var(--button-size, 28px) - 2px);
@@ -79,7 +79,7 @@
79
79
  border-bottom-left-radius: 0;
80
80
  }
81
81
 
82
- /* Icon button styles */
82
+ /* Icon button foreground styles */
83
83
  .deck-widget button .deck-widget-icon {
84
84
  background-color: var(--button-icon-idle, #616166);
85
85
  background-position: 50%;
@@ -116,6 +116,16 @@
116
116
  );
117
117
  }
118
118
 
119
+ @keyframes spin {
120
+ to {
121
+ transform: rotate(360deg);
122
+ }
123
+ }
124
+
125
+ .deck-widget.deck-widget-loading button.deck-widget-spinner-icon .deck-widget-icon {
126
+ animation: spin 1s linear infinite;
127
+ }
128
+
119
129
  .deck-pseudo-fullscreen {
120
130
  height: 100% !important;
121
131
  left: 0 !important;
@@ -152,10 +162,117 @@
152
162
  .deck-widget.deck-widget-screenshot button.deck-widget-camera .deck-widget-icon {
153
163
  mask-image: var(
154
164
  --icon-camera,
155
- url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-260q75 0 127.5-52.5T660-440q0-75-52.5-127.5T480-620q-75 0-127.5 52.5T300-440q0 75 52.5 127.5T480-260Zm0-80q-42 0-71-29t-29-71q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29ZM160-120q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z"/></svg>')
165
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 -960 960 960" fill="black"><path d="M480-260q75 0 127.5-52.5T660-440q0-75-52.5-127.5T480-620q-75 0-127.5 52.5T300-440q0 75 52.5 127.5T480-260Zm0-80q-42 0-71-29t-29-71q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29ZM160-120q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z"/></svg>')
156
166
  );
157
167
  -webkit-mask-image: var(
158
168
  --icon-camera,
159
- url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-260q75 0 127.5-52.5T660-440q0-75-52.5-127.5T480-620q-75 0-127.5 52.5T300-440q0 75 52.5 127.5T480-260Zm0-80q-42 0-71-29t-29-71q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29ZM160-120q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z"/></svg>')
169
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 -960 960 960" fill="black"><path d="M480-260q75 0 127.5-52.5T660-440q0-75-52.5-127.5T480-620q-75 0-127.5 52.5T300-440q0 75 52.5 127.5T480-260Zm0-80q-42 0-71-29t-29-71q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29ZM160-120q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z"/></svg>')
170
+ );
171
+ }
172
+
173
+ /* Theme widget styles */
174
+ .deck-widget.deck-widget-theme button.deck-widget-sun .deck-widget-icon {
175
+ mask-image: var(
176
+ --icon-sun,
177
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="black" stroke="black"><g ><circle cx="12" cy="12" r="6" /><line x1="12" y1="1" x2="12" y2="3" /><line x1="12" y1="21" x2="12" y2="23" /><line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /><line x1="18.36" y1="18.36" x2="19.78" y2="19.78" /><line x1="1" y1="12" x2="3" y2="12" /><line x1="21" y1="12" x2="23" y2="12" /><line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /><line x1="18.36" y1="5.64" x2="19.78" y2="4.22" /></g></svg>')
160
178
  );
161
179
  }
180
+
181
+ .deck-widget.deck-widget-theme button.deck-widget-moon .deck-widget-icon {
182
+ mask-image: var(
183
+ --icon-moon,
184
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" ><circle cx="12" cy="12" r="6" fill="black" mask="url(%23moon-mask)" /><mask id="moon-mask" viewBox="0 0 24 24" ><rect x="0" y="0" width="24" height="24" fill="white" /><circle cx="24" cy="10" r="12" fill="black"/></mask></svg>')
185
+ );
186
+ -webbit-mask-image: var(
187
+ --icon-moon,
188
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><circle x="12" cy="12" r="6" /></svg>')
189
+ );
190
+ }
191
+
192
+ /* Reset view styles */
193
+ .deck-widget.deck-widget-reset-view button.deck-widget-reset-focus .deck-widget-icon {
194
+ mask-image: var(
195
+ --icon-camera,
196
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-320v-100q0-25 17.5-42.5T540-480h100v60H540v100h-60Zm60 240q-25 0-42.5-17.5T480-140v-100h60v100h100v60H540Zm280-240v-100H720v-60h100q25 0 42.5 17.5T880-420v100h-60ZM720-80v-60h100v-100h60v100q0 25-17.5 42.5T820-80H720Zm111-480h-83q-26-88-99-144t-169-56q-117 0-198.5 81.5T200-480q0 72 32.5 132t87.5 98v-110h80v240H160v-80h94q-62-50-98-122.5T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q129 0 226.5 79.5T831-560Z"/></svg>')
197
+ );
198
+ -webkit-mask-image: var(
199
+ --icon-camera,
200
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-320v-100q0-25 17.5-42.5T540-480h100v60H540v100h-60Zm60 240q-25 0-42.5-17.5T480-140v-100h60v100h100v60H540Zm280-240v-100H720v-60h100q25 0 42.5 17.5T880-420v100h-60ZM720-80v-60h100v-100h60v100q0 25-17.5 42.5T820-80H720Zm111-480h-83q-26-88-99-144t-169-56q-117 0-198.5 81.5T200-480q0 72 32.5 132t87.5 98v-110h80v240H160v-80h94q-62-50-98-122.5T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q129 0 226.5 79.5T831-560Z"/></svg>')
201
+ );
202
+ }
203
+
204
+ /* Timeline widget styles */
205
+ .deck-widget.deck-widget-timeline {
206
+ display: flex;
207
+ align-items: center;
208
+ pointer-events: 'auto';
209
+ }
210
+
211
+ .deck-widget.deck-widget-timeline .timeline-slider {
212
+ margin: 0 8px;
213
+ flex: 1;
214
+ pointer-events: 'auto';
215
+ cursor: pointer;
216
+ }
217
+
218
+ .deck-widget.deck-widget-timeline button.timeline-play-pause {
219
+ background: var(--button-background, #fff);
220
+ border: var(--button-inner-stroke, unset);
221
+ border-radius: var(--button-corner-radius, 4px);
222
+ width: var(--button-size, 28px);
223
+ height: var(--button-size, 28px);
224
+ cursor: pointer;
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: center;
228
+ padding: 0;
229
+ pointer-events: 'auto';
230
+ }
231
+
232
+ .deck-widget-spinner-icon {
233
+ mask-image: var(
234
+ --loading-icon,
235
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" ><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>')
236
+ );
237
+ -webkit-mask-image: var(
238
+ --loading-icon,
239
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" ><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>')
240
+ );
241
+ }
242
+
243
+ .deck-widget .deck-widget-scale {
244
+ background-color: var(--button-icon-idle, #616166);
245
+ background-position: 50%;
246
+ display: block;
247
+ }
248
+
249
+ .deck-widget .deck-widget-geolocate {
250
+ pointer-events: auto;
251
+ cursor: pointer;
252
+ }
253
+
254
+ /**
255
+ * Reusable styles for widgets
256
+ */
257
+ .deck-widget-common {
258
+ font-family:
259
+ 'SF Mono',
260
+ /* macOS */
261
+ 'Menlo',
262
+ /* macOS */
263
+ 'Consolas',
264
+ /* Windows */
265
+ 'DejaVu Sans Mono',
266
+ /* Linux */
267
+ 'Liberation Mono',
268
+ /* Linux */
269
+ monospace;
270
+ background-color: var(--button-background, #fff);
271
+ color: #333;
272
+ }
273
+
274
+ .deck-widget-stats-container {
275
+ font-size: 0.7rem;
276
+ /* Approximately 14px */
277
+ line-height: 1.6;
278
+ }
@@ -0,0 +1,120 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {log, _deepEqual as deepEqual, _applyStyles as applyStyles} from '@deck.gl/core';
6
+ import {Widget, WidgetProps, WidgetPlacement} from '@deck.gl/core';
7
+ import {render} from 'preact';
8
+ // import {useCallback} from 'preact/hooks';
9
+ import {IconButton} from './lib/components/icon-button';
10
+ import type {DeckWidgetTheme} from './themes';
11
+ import {LightGlassTheme, DarkGlassTheme} from './themes';
12
+
13
+ export type ThemeWidgetProps = WidgetProps & {
14
+ /** Widget positioning within the view. Default 'top-left'. */
15
+ placement?: WidgetPlacement;
16
+ /** Tooltip message when dark mode is selected. */
17
+ lightModeLabel?: string;
18
+ /** Styles for light mode theme */
19
+ lightModeTheme?: DeckWidgetTheme;
20
+ /** Tooltip message when light mode is selected. */
21
+ darkModeLabel?: string;
22
+ /** Styles for dark mode theme */
23
+ darkModeTheme?: DeckWidgetTheme;
24
+ /** Initial theme mode. 'auto' reads the browser default setting */
25
+ initialThemeMode?: 'auto' | 'light' | 'dark';
26
+ };
27
+
28
+ export class ThemeWidget extends Widget<ThemeWidgetProps> {
29
+ static defaultProps: Required<ThemeWidgetProps> = {
30
+ ...Widget.defaultProps,
31
+ id: 'theme',
32
+ placement: 'top-left',
33
+ lightModeLabel: 'Light Mode',
34
+ lightModeTheme: LightGlassTheme,
35
+ darkModeLabel: 'Dark Mode',
36
+ darkModeTheme: DarkGlassTheme,
37
+ initialThemeMode: 'auto'
38
+ };
39
+
40
+ className = 'deck-widget-theme';
41
+ placement: WidgetPlacement = 'top-left';
42
+ themeMode: 'light' | 'dark' = 'dark';
43
+
44
+ constructor(props: ThemeWidgetProps = {}) {
45
+ super(props, ThemeWidget.defaultProps);
46
+ this.placement = props.placement ?? this.placement;
47
+ this.themeMode = this._getInitialThemeMode();
48
+ }
49
+
50
+ // eslint-disable-next-line complexity
51
+ setProps(props: Partial<ThemeWidgetProps>) {
52
+ const {lightModeTheme, darkModeTheme} = this.props;
53
+ this.placement = props.placement ?? this.placement;
54
+ super.setProps(props);
55
+
56
+ // Update if current theme definition changed
57
+ switch (this.themeMode) {
58
+ case 'light':
59
+ if (props.lightModeTheme && !deepEqual(props.lightModeTheme, lightModeTheme, 1)) {
60
+ this._setThemeMode('light');
61
+ }
62
+ break;
63
+ case 'dark':
64
+ if (props.darkModeTheme && !deepEqual(props.darkModeTheme, darkModeTheme, 1)) {
65
+ this._setThemeMode('dark');
66
+ }
67
+ break;
68
+ }
69
+ }
70
+
71
+ onRenderHTML(rootElement: HTMLElement): void {
72
+ const {lightModeLabel, darkModeLabel} = this.props;
73
+ // const onClick = useCallback(this._handleClick.bind(this), [this._handleClick]);
74
+
75
+ render(
76
+ <IconButton
77
+ onClick={this._handleClick.bind(this)}
78
+ label={this.themeMode === 'dark' ? darkModeLabel : lightModeLabel}
79
+ className={this.themeMode === 'dark' ? 'deck-widget-moon' : 'deck-widget-sun'}
80
+ />,
81
+ rootElement
82
+ );
83
+ }
84
+
85
+ onAdd() {
86
+ // Note: theme styling is applied in here onAdd() once DOM element is created
87
+ this._setThemeMode(this.themeMode);
88
+ }
89
+
90
+ _handleClick() {
91
+ const newThemeMode = this.themeMode === 'dark' ? 'light' : 'dark';
92
+ this._setThemeMode(newThemeMode);
93
+ }
94
+
95
+ _setThemeMode(themeMode: 'light' | 'dark') {
96
+ this.themeMode = themeMode;
97
+ const container = this.rootElement?.closest<HTMLDivElement>('.deck-widget-container');
98
+ if (container) {
99
+ const themeStyle =
100
+ themeMode === 'dark' ? this.props.darkModeTheme : this.props.lightModeTheme;
101
+ applyStyles(container, themeStyle);
102
+
103
+ const label =
104
+ this.themeMode === 'dark' ? this.props.darkModeLabel : this.props.lightModeLabel;
105
+ log.log(1, `Switched theme to ${label}`, themeStyle);
106
+
107
+ this.updateHTML();
108
+ }
109
+ }
110
+
111
+ /** Read browser preference */
112
+ _getInitialThemeMode(): 'light' | 'dark' {
113
+ const {initialThemeMode} = this.props;
114
+ return initialThemeMode === 'auto'
115
+ ? window.matchMedia('(prefers-color-scheme: dark)').matches
116
+ ? 'dark'
117
+ : 'light'
118
+ : initialThemeMode;
119
+ }
120
+ }
package/src/themes.ts CHANGED
@@ -2,7 +2,31 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ /**
6
+ * A Deck Theme is a set of CSS variables that control CSS styling of the official widgets.
7
+ */
8
+ export type DeckWidgetTheme = {
9
+ // layout
10
+ '--widget-margin'?: string;
11
+ // button and button group
12
+ '--button-size'?: string;
13
+ '--button-corner-radius'?: string;
14
+ '--button-background'?: string;
15
+ '--button-stroke'?: string;
16
+ '--button-inner-stroke'?: string;
17
+ '--button-shadow'?: string;
18
+ '--button-backdrop-filter'?: string;
19
+ '--button-icon-idle'?: string;
20
+ '--button-icon-hover'?: string;
21
+ // inter-icon color
22
+ '--icon-compass-north-color'?: string;
23
+ '--icon-compass-south-color'?: string;
24
+ };
25
+
5
26
  export const LightTheme = {
27
+ '--widget-margin': '12px',
28
+ '--button-size': '28px',
29
+ '--button-corner-radius': '8px',
6
30
  '--button-background': '#fff',
7
31
  '--button-stroke': 'rgba(255, 255, 255, 0.3)',
8
32
  '--button-inner-stroke': 'unset',
@@ -10,11 +34,14 @@ export const LightTheme = {
10
34
  '--button-backdrop-filter': 'unset',
11
35
  '--button-icon-idle': 'rgba(97, 97, 102, 1)',
12
36
  '--button-icon-hover': 'rgba(24, 24, 26, 1)',
13
- '--icon-compass-north-color': '#F05C44',
14
- '--icon-compass-south-color': '#C2C2CC'
15
- };
37
+ '--icon-compass-north-color': 'rgb(240, 92, 68)',
38
+ '--icon-compass-south-color': 'rgb(204, 204, 204)'
39
+ } as const satisfies Required<DeckWidgetTheme>;
16
40
 
17
41
  export const DarkTheme = {
42
+ '--widget-margin': '12px',
43
+ '--button-size': '28px',
44
+ '--button-corner-radius': '8px',
18
45
  '--button-background': 'rgba(18, 18, 20, 1)',
19
46
  '--button-stroke': 'rgba(18, 18, 20, 0.30)',
20
47
  '--button-inner-stroke': 'unset',
@@ -22,11 +49,14 @@ export const DarkTheme = {
22
49
  '--button-backdrop-filter': 'unset',
23
50
  '--button-icon-idle': 'rgba(158, 157, 168, 1)',
24
51
  '--button-icon-hover': 'rgba(215, 214, 229, 1)',
25
- '--icon-compass-north-color': '#F05C44',
26
- '--icon-compass-south-color': '#C2C2CC'
27
- };
52
+ '--icon-compass-north-color': 'rgb(240, 92, 68)',
53
+ '--icon-compass-south-color': 'rgb(200, 199, 209)'
54
+ } as const satisfies Required<DeckWidgetTheme>;
28
55
 
29
56
  export const LightGlassTheme = {
57
+ '--widget-margin': '12px',
58
+ '--button-size': '28px',
59
+ '--button-corner-radius': '8px',
30
60
  '--button-background': 'rgba(255, 255, 255, 0.6)',
31
61
  '--button-stroke': 'rgba(255, 255, 255, 0.3)',
32
62
  '--button-inner-stroke': '1px solid rgba(255, 255, 255, 0.6)',
@@ -35,11 +65,14 @@ export const LightGlassTheme = {
35
65
  '--button-backdrop-filter': 'blur(4px)',
36
66
  '--button-icon-idle': 'rgba(97, 97, 102, 1)',
37
67
  '--button-icon-hover': 'rgba(24, 24, 26, 1)',
38
- '--icon-compass-north-color': '#F05C44',
39
- '--icon-compass-south-color': '#C2C2CC'
40
- };
68
+ '--icon-compass-north-color': 'rgb(240, 92, 68)',
69
+ '--icon-compass-south-color': 'rgb(204, 204, 204)'
70
+ } as const satisfies Required<DeckWidgetTheme>;
41
71
 
42
72
  export const DarkGlassTheme = {
73
+ '--widget-margin': '12px',
74
+ '--button-size': '28px',
75
+ '--button-corner-radius': '8px',
43
76
  '--button-background': 'rgba(18, 18, 20, 0.75)',
44
77
  '--button-stroke': 'rgba(18, 18, 20, 0.30)',
45
78
  '--button-inner-stroke': '1px solid rgba(18, 18, 20, 0.75)',
@@ -48,6 +81,6 @@ export const DarkGlassTheme = {
48
81
  '--button-backdrop-filter': 'blur(4px)',
49
82
  '--button-icon-idle': 'rgba(158, 157, 168, 1)',
50
83
  '--button-icon-hover': 'rgba(215, 214, 229, 1)',
51
- '--icon-compass-north-color': '#F05C44',
52
- '--icon-compass-south-color': '#C2C2CC'
53
- };
84
+ '--icon-compass-north-color': 'rgb(240, 92, 68)',
85
+ '--icon-compass-south-color': 'rgb(200, 199, 209)'
86
+ } as const satisfies Required<DeckWidgetTheme>;
@@ -0,0 +1,155 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Widget, type WidgetPlacement} from '@deck.gl/core';
6
+ import {render} from 'preact';
7
+
8
+ export type TimelineWidgetProps = {
9
+ /**
10
+ * Widget id
11
+ */
12
+ id?: string;
13
+ /**
14
+ * CSS inline style overrides.
15
+ */
16
+ style?: Partial<CSSStyleDeclaration>;
17
+ /**
18
+ * Additional CSS class.
19
+ */
20
+ className?: string;
21
+ /**
22
+ * Widget placement.
23
+ */
24
+ placement?: WidgetPlacement;
25
+ /**
26
+ * Slider timeRange [min, max].
27
+ */
28
+ timeRange?: [number, number];
29
+ /**
30
+ * Slider step.
31
+ */
32
+ step?: number;
33
+ /**
34
+ * Initial slider value.
35
+ */
36
+ initialTime?: number;
37
+ /**
38
+ * Callback when value changes.
39
+ */
40
+ onTimeChange?: (value: number) => void;
41
+ /**
42
+ * Play interval in milliseconds.
43
+ */
44
+ playInterval?: number;
45
+ };
46
+
47
+ export class TimelineWidget extends Widget<TimelineWidgetProps> {
48
+ id = 'timeline';
49
+ className = 'deck-widget-timeline';
50
+ placement: WidgetPlacement = 'bottom-left';
51
+
52
+ private playing = false;
53
+ private timerId: number | null = null;
54
+ currentTime: number;
55
+
56
+ static defaultProps: Required<TimelineWidgetProps> = {
57
+ ...Widget.defaultProps,
58
+ id: 'timeline',
59
+ placement: 'bottom-left' as const,
60
+ timeRange: [0, 100],
61
+ step: 1,
62
+ initialTime: undefined!,
63
+ onTimeChange: () => {},
64
+ playInterval: 1000
65
+ };
66
+
67
+ constructor(props: TimelineWidgetProps = {}) {
68
+ super(props, TimelineWidget.defaultProps);
69
+ this.currentTime = this.props.initialTime ?? this.props.timeRange[0];
70
+ }
71
+
72
+ setProps(props: Partial<TimelineWidgetProps>): void {
73
+ this.placement = props.placement || this.placement;
74
+ super.setProps(props);
75
+ }
76
+
77
+ onAdd(): void {
78
+ this.playing = false;
79
+ this.timerId = null;
80
+ }
81
+
82
+ onRemove(): void {
83
+ this.stop();
84
+ }
85
+
86
+ onRenderHTML(rootElement: HTMLElement): void {
87
+ render(
88
+ <div style={{display: 'flex', alignItems: 'center', pointerEvents: 'auto'}}>
89
+ <button
90
+ type="button"
91
+ className="timeline-play-pause"
92
+ title={this.playing ? 'Pause' : 'Play'}
93
+ onClick={this.handlePlayPause}
94
+ >
95
+ {this.playing ? '⏸' : '▶'}
96
+ </button>
97
+ <input
98
+ type="range"
99
+ className="timeline-slider"
100
+ min={this.props.timeRange[0]}
101
+ max={this.props.timeRange[1]}
102
+ step={this.props.step}
103
+ value={this.currentTime}
104
+ onInput={this.handleSliderChange}
105
+ />
106
+ </div>,
107
+ rootElement
108
+ );
109
+ }
110
+
111
+ private handlePlayPause = (): void => {
112
+ if (this.playing) {
113
+ this.stop();
114
+ } else {
115
+ this.start();
116
+ }
117
+ };
118
+
119
+ private handleSliderChange = (e: Event): void => {
120
+ const input = e.target as HTMLInputElement;
121
+ const val = Number(input.value);
122
+ this.currentTime = val;
123
+ this.props.onTimeChange(val);
124
+ this.updateHTML();
125
+ };
126
+
127
+ private start(): void {
128
+ this.playing = true;
129
+ this.updateHTML();
130
+ this.tick();
131
+ }
132
+
133
+ private stop(): void {
134
+ this.playing = false;
135
+ if (this.timerId !== null) {
136
+ window.clearTimeout(this.timerId);
137
+ this.timerId = null;
138
+ }
139
+ this.updateHTML();
140
+ }
141
+
142
+ private tick = (): void => {
143
+ const [min, max] = this.props.timeRange;
144
+ let next = this.currentTime + this.props.step;
145
+ if (next > max) {
146
+ next = min;
147
+ }
148
+ this.currentTime = next;
149
+ this.props.onTimeChange(next);
150
+ this.updateHTML();
151
+ if (this.playing) {
152
+ this.timerId = window.setTimeout(this.tick, this.props.playInterval);
153
+ }
154
+ };
155
+ }
@@ -0,0 +1,108 @@
1
+ // ViewSelectorWidget.tsx
2
+ // deck.gl
3
+ // SPDX-License-Identifier: MIT
4
+ // Copyright (c) vis.gl contributors
5
+
6
+ import {render, JSX} from 'preact';
7
+ import {Widget, type WidgetProps, type WidgetPlacement} from '@deck.gl/core';
8
+ import {IconMenu} from './lib/components/icon-menu';
9
+
10
+ /** The available view modes */
11
+ export type ViewMode = 'single' | 'split-horizontal' | 'split-vertical';
12
+
13
+ /** Properties for the ViewSelectorWidget */
14
+ export type ViewSelectorWidgetProps = WidgetProps & {
15
+ /** Widget positioning within the view. Default 'top-left'. */
16
+ placement?: WidgetPlacement;
17
+ /** Tooltip label */
18
+ label?: string;
19
+ /** The initial view mode. Defaults to 'single'. */
20
+ initialViewMode?: ViewMode;
21
+ /** Callback invoked when the view mode changes */
22
+ onViewModeChange?: (mode: ViewMode) => void;
23
+ };
24
+
25
+ /**
26
+ * A widget that renders a popup menu for selecting a view mode.
27
+ * It displays a button with the current view mode icon. Clicking the button
28
+ * toggles a popup that shows three icons for:
29
+ * - Single view
30
+ * - Two views, split horizontally
31
+ * - Two views, split vertically
32
+ */
33
+ export class ViewSelectorWidget extends Widget<ViewSelectorWidgetProps> {
34
+ static defaultProps: Required<ViewSelectorWidgetProps> = {
35
+ ...Widget.defaultProps,
36
+ id: 'view-selector-widget',
37
+ placement: 'top-left',
38
+ label: 'Split View',
39
+ initialViewMode: 'single',
40
+ onViewModeChange: (viewMode: string) => {
41
+ // eslint-disable-next-line no-console
42
+ console.log(viewMode);
43
+ }
44
+ };
45
+
46
+ className = 'deck-widget-view-selector';
47
+ placement: WidgetPlacement = 'top-left';
48
+ viewMode: ViewMode;
49
+
50
+ constructor(props: ViewSelectorWidgetProps = {}) {
51
+ super(props, ViewSelectorWidget.defaultProps);
52
+ this.placement = this.props.placement;
53
+ this.viewMode = this.props.initialViewMode;
54
+ }
55
+
56
+ setProps(props: Partial<ViewSelectorWidgetProps>) {
57
+ super.setProps(props);
58
+ this.placement = props.placement ?? this.placement;
59
+ }
60
+
61
+ onRenderHTML(rootElement: HTMLElement) {
62
+ render(
63
+ <IconMenu<ViewMode>
64
+ className="deck-widget-view-selector"
65
+ menuItems={MENU_ITEMS}
66
+ initialItem={this.props.initialViewMode}
67
+ onItemSelected={this.handleSelectMode}
68
+ />,
69
+ rootElement
70
+ );
71
+ }
72
+
73
+ handleSelectMode = (viewMode: ViewMode) => {
74
+ this.viewMode = viewMode;
75
+ this.updateHTML();
76
+ };
77
+ }
78
+
79
+ // Define common icon style.
80
+ const ICON_STYLE = {width: '24px', height: '24px'};
81
+
82
+ // Define inline SVG icons for each view mode.
83
+ const ICONS: Record<ViewMode, () => JSX.Element> = {
84
+ single: () => (
85
+ <svg width="24" height="24" style={ICON_STYLE}>
86
+ <rect x="4" y="4" width="16" height="16" stroke="black" fill="none" strokeWidth="2" />
87
+ </svg>
88
+ ),
89
+ 'split-horizontal': () => (
90
+ <svg width="24" height="24" style={ICON_STYLE}>
91
+ <rect x="4" y="4" width="16" height="7" stroke="black" fill="none" strokeWidth="2" />
92
+ <rect x="4" y="13" width="16" height="7" stroke="black" fill="none" strokeWidth="2" />
93
+ </svg>
94
+ ),
95
+ 'split-vertical': () => (
96
+ <svg width="24" height="24" style={ICON_STYLE}>
97
+ <rect x="4" y="4" width="7" height="16" stroke="black" fill="none" strokeWidth="2" />
98
+ <rect x="13" y="4" width="7" height="16" stroke="black" fill="none" strokeWidth="2" />
99
+ </svg>
100
+ )
101
+ };
102
+
103
+ // Define menu items for the popup menu.
104
+ const MENU_ITEMS: Array<{value: ViewMode; icon: () => JSX.Element; label: string}> = [
105
+ {value: 'single', icon: ICONS.single, label: 'Single View'},
106
+ {value: 'split-horizontal', icon: ICONS['split-horizontal'], label: 'Split Horizontal'},
107
+ {value: 'split-vertical', icon: ICONS['split-vertical'], label: 'Split Vertical'}
108
+ ];