@deck.gl/widgets 9.3.0-alpha.6 → 9.3.0-beta.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 (55) hide show
  1. package/dist/compass-widget.d.ts +12 -0
  2. package/dist/compass-widget.d.ts.map +1 -1
  3. package/dist/compass-widget.js +9 -3
  4. package/dist/compass-widget.js.map +1 -1
  5. package/dist/dist.dev.js +285 -93
  6. package/dist/fullscreen-widget.d.ts +5 -0
  7. package/dist/fullscreen-widget.d.ts.map +1 -1
  8. package/dist/fullscreen-widget.js +18 -8
  9. package/dist/fullscreen-widget.js.map +1 -1
  10. package/dist/geocoder-widget.d.ts +13 -0
  11. package/dist/geocoder-widget.d.ts.map +1 -1
  12. package/dist/geocoder-widget.js +13 -1
  13. package/dist/geocoder-widget.js.map +1 -1
  14. package/dist/gimbal-widget.d.ts +12 -0
  15. package/dist/gimbal-widget.d.ts.map +1 -1
  16. package/dist/gimbal-widget.js +4 -1
  17. package/dist/gimbal-widget.js.map +1 -1
  18. package/dist/index.cjs +278 -90
  19. package/dist/index.cjs.map +3 -3
  20. package/dist/loading-widget.d.ts +5 -0
  21. package/dist/loading-widget.d.ts.map +1 -1
  22. package/dist/loading-widget.js +4 -1
  23. package/dist/loading-widget.js.map +1 -1
  24. package/dist/reset-view-widget.d.ts +9 -0
  25. package/dist/reset-view-widget.d.ts.map +1 -1
  26. package/dist/reset-view-widget.js +4 -1
  27. package/dist/reset-view-widget.js.map +1 -1
  28. package/dist/stats-widget.d.ts +18 -3
  29. package/dist/stats-widget.d.ts.map +1 -1
  30. package/dist/stats-widget.js +29 -12
  31. package/dist/stats-widget.js.map +1 -1
  32. package/dist/theme-widget.d.ts +20 -2
  33. package/dist/theme-widget.d.ts.map +1 -1
  34. package/dist/theme-widget.js +38 -14
  35. package/dist/theme-widget.js.map +1 -1
  36. package/dist/timeline-widget.d.ts +34 -3
  37. package/dist/timeline-widget.d.ts.map +1 -1
  38. package/dist/timeline-widget.js +98 -24
  39. package/dist/timeline-widget.js.map +1 -1
  40. package/dist/zoom-widget.d.ts +22 -1
  41. package/dist/zoom-widget.d.ts.map +1 -1
  42. package/dist/zoom-widget.js +62 -15
  43. package/dist/zoom-widget.js.map +1 -1
  44. package/dist.min.js +2 -2
  45. package/package.json +3 -3
  46. package/src/compass-widget.tsx +23 -3
  47. package/src/fullscreen-widget.tsx +24 -9
  48. package/src/geocoder-widget.tsx +23 -1
  49. package/src/gimbal-widget.tsx +17 -1
  50. package/src/loading-widget.tsx +11 -1
  51. package/src/reset-view-widget.tsx +15 -1
  52. package/src/stats-widget.tsx +43 -14
  53. package/src/theme-widget.tsx +56 -17
  54. package/src/timeline-widget.tsx +123 -24
  55. package/src/zoom-widget.tsx +108 -17
package/dist/index.cjs CHANGED
@@ -110,25 +110,60 @@ var ZoomWidget = class extends import_core.Widget {
110
110
  const ui = (0, import_jsx_runtime3.jsxs)(ButtonGroup, { orientation: this.props.orientation, children: [(0, import_jsx_runtime3.jsx)(IconButton, { onClick: () => this.handleZoomIn(), label: this.props.zoomInLabel, className: "deck-widget-zoom-in" }), (0, import_jsx_runtime3.jsx)(IconButton, { onClick: () => this.handleZoomOut(), label: this.props.zoomOutLabel, className: "deck-widget-zoom-out" })] });
111
111
  (0, import_preact.render)(ui, rootElement);
112
112
  }
113
- handleZoom(viewId, nextZoom) {
113
+ isOrthographicView(viewId) {
114
+ const deck = this.deck;
115
+ const view = (deck == null ? void 0 : deck.isInitialized) && deck.getView(viewId);
116
+ return view instanceof import_core.OrthographicView;
117
+ }
118
+ handleZoom(viewId, delta) {
119
+ var _a, _b, _c, _d;
114
120
  const viewState = this.getViewState(viewId);
115
- if (viewState) {
116
- const { minZoom, maxZoom } = viewState;
117
- if (Number.isFinite(minZoom)) {
118
- nextZoom = Math.max(minZoom, nextZoom);
119
- }
120
- if (Number.isFinite(maxZoom)) {
121
- nextZoom = Math.min(maxZoom, nextZoom);
121
+ const newViewState = {};
122
+ if (this.isOrthographicView(viewId)) {
123
+ const { zoomAxis } = this.props;
124
+ const { zoomX, minZoomX, maxZoomX, zoomY, minZoomY, maxZoomY } = normalizeOrthographicViewState(viewState);
125
+ let nextZoom;
126
+ let nextZoomY;
127
+ if (zoomAxis === "X") {
128
+ nextZoom = clamp(zoomX + delta, minZoomX, maxZoomX);
129
+ nextZoomY = zoomY;
130
+ } else if (zoomAxis === "Y") {
131
+ nextZoom = zoomX;
132
+ nextZoomY = clamp(zoomY + delta, minZoomY, maxZoomY);
133
+ } else {
134
+ const clampedDelta = clamp(delta, Math.max(minZoomX - zoomX, minZoomY - zoomY), Math.min(maxZoomX - zoomX, maxZoomY - zoomY));
135
+ nextZoom = zoomX + clampedDelta;
136
+ nextZoomY = zoomY + clampedDelta;
122
137
  }
138
+ newViewState.zoom = [nextZoom, nextZoomY];
139
+ newViewState.zoomX = nextZoom;
140
+ newViewState.zoomY = nextZoomY;
141
+ (_b = (_a = this.props).onZoom) == null ? void 0 : _b.call(_a, {
142
+ viewId,
143
+ delta,
144
+ // `zoom` will not match the new state if using 2D zoom. Deprecated behavior for backward compatibility.
145
+ zoom: zoomAxis === "Y" ? nextZoomY : nextZoom,
146
+ zoomX: nextZoom,
147
+ zoomY: nextZoomY
148
+ });
149
+ } else {
150
+ const { zoom = 0, minZoom, maxZoom } = viewState;
151
+ const nextZoom = clamp(zoom + delta, minZoom, maxZoom);
152
+ newViewState.zoom = nextZoom;
153
+ (_d = (_c = this.props).onZoom) == null ? void 0 : _d.call(_c, {
154
+ viewId,
155
+ delta,
156
+ zoom: nextZoom
157
+ });
123
158
  }
124
159
  const nextViewState = {
125
160
  ...viewState,
126
- zoom: nextZoom
161
+ ...newViewState
127
162
  };
128
163
  if (this.props.transitionDuration > 0) {
129
164
  nextViewState.transitionDuration = this.props.transitionDuration;
130
165
  nextViewState.transitionInterpolator = "latitude" in nextViewState ? new import_core.FlyToInterpolator() : new import_core.LinearInterpolator({
131
- transitionProps: ["zoom"]
166
+ transitionProps: "zoomX" in newViewState ? ["zoomX", "zoomY"] : ["zoom"]
132
167
  });
133
168
  }
134
169
  this.setViewState(viewId, nextViewState);
@@ -137,16 +172,14 @@ var ZoomWidget = class extends import_core.Widget {
137
172
  var _a;
138
173
  const viewIds = this.viewId ? [this.viewId] : ((_a = this.deck) == null ? void 0 : _a.getViews().map((v) => v.id)) ?? [];
139
174
  for (const viewId of viewIds) {
140
- const viewState = this.getViewState(viewId);
141
- this.handleZoom(viewId, viewState.zoom + 1);
175
+ this.handleZoom(viewId, 1);
142
176
  }
143
177
  }
144
178
  handleZoomOut() {
145
179
  var _a;
146
180
  const viewIds = this.viewId ? [this.viewId] : ((_a = this.deck) == null ? void 0 : _a.getViews().map((v) => v.id)) ?? [];
147
181
  for (const viewId of viewIds) {
148
- const viewState = this.getViewState(viewId);
149
- this.handleZoom(viewId, viewState.zoom - 1);
182
+ this.handleZoom(viewId, -1);
150
183
  }
151
184
  }
152
185
  };
@@ -158,8 +191,19 @@ ZoomWidget.defaultProps = {
158
191
  transitionDuration: 200,
159
192
  zoomInLabel: "Zoom In",
160
193
  zoomOutLabel: "Zoom Out",
161
- viewId: null
194
+ zoomAxis: "all",
195
+ viewId: null,
196
+ onZoom: () => {
197
+ }
162
198
  };
199
+ function clamp(zoom, minZoom, maxZoom) {
200
+ return zoom < minZoom ? minZoom : zoom > maxZoom ? maxZoom : zoom;
201
+ }
202
+ function normalizeOrthographicViewState({ zoom = 0, zoomX, zoomY, minZoom = -Infinity, maxZoom = Infinity, minZoomX = minZoom, maxZoomX = maxZoom, minZoomY = minZoom, maxZoomY = maxZoom }) {
203
+ zoomX = zoomX ?? (Array.isArray(zoom) ? zoom[0] : zoom);
204
+ zoomY = zoomY ?? (Array.isArray(zoom) ? zoom[1] : zoom);
205
+ return { zoomX, zoomY, minZoomX, minZoomY, maxZoomX, maxZoomY };
206
+ }
163
207
 
164
208
  // dist/reset-view-widget.js
165
209
  var import_jsx_runtime4 = require("preact/jsx-runtime");
@@ -186,7 +230,7 @@ var ResetViewWidget = class extends import_core2.Widget {
186
230
  this.resetViewState(initialViewState);
187
231
  }
188
232
  resetViewState(viewState) {
189
- var _a;
233
+ var _a, _b, _c;
190
234
  const viewIds = this.viewId ? [this.viewId] : ((_a = this.deck) == null ? void 0 : _a.getViews().map((v) => v.id)) ?? [];
191
235
  for (const viewId of viewIds) {
192
236
  const nextViewState = {
@@ -195,6 +239,7 @@ var ResetViewWidget = class extends import_core2.Widget {
195
239
  // transitionDuration: this.props.transitionDuration,
196
240
  // transitionInterpolator: new FlyToInterpolator()
197
241
  };
242
+ (_c = (_b = this.props).onReset) == null ? void 0 : _c.call(_b, { viewId, viewState: nextViewState });
198
243
  this.setViewState(viewId, nextViewState);
199
244
  }
200
245
  }
@@ -205,7 +250,9 @@ ResetViewWidget.defaultProps = {
205
250
  placement: "top-left",
206
251
  label: "Reset View",
207
252
  initialViewState: void 0,
208
- viewId: null
253
+ viewId: null,
254
+ onReset: () => {
255
+ }
209
256
  };
210
257
 
211
258
  // dist/gimbal-widget.js
@@ -252,9 +299,11 @@ var GimbalWidget = class extends import_core3.Widget {
252
299
  this.updateHTML();
253
300
  }
254
301
  resetOrbitView(viewport) {
302
+ var _a, _b;
255
303
  const viewId = this.viewId || (viewport == null ? void 0 : viewport.id) || "OrbitView";
256
304
  const viewState = this.getViewState(viewId);
257
305
  if ("rotationOrbit" in viewState || "rotationX" in viewState) {
306
+ (_b = (_a = this.props).onReset) == null ? void 0 : _b.call(_a, { viewId, rotationOrbit: 0, rotationX: 0 });
258
307
  const nextViewState = {
259
308
  ...viewState,
260
309
  rotationOrbit: 0,
@@ -289,7 +338,9 @@ GimbalWidget.defaultProps = {
289
338
  viewId: null,
290
339
  label: "Gimbal",
291
340
  strokeWidth: 1.5,
292
- transitionDuration: 200
341
+ transitionDuration: 200,
342
+ onReset: () => {
343
+ }
293
344
  };
294
345
  function normalizeAndClampAngle(angle) {
295
346
  let normalized = ((angle + 180) % 360 + 360) % 360 - 180;
@@ -349,13 +400,18 @@ var CompassWidget = class extends import_core4.Widget {
349
400
  return [0, 0];
350
401
  }
351
402
  handleCompassReset(viewport) {
403
+ var _a, _b;
352
404
  const viewId = this.viewId || viewport.id;
353
405
  if (viewport instanceof import_core4.WebMercatorViewport) {
354
406
  const viewState = this.getViewState(viewId);
407
+ const resetPitch = this.getRotation(viewport)[0] === 0;
408
+ const nextBearing = 0;
409
+ const nextPitch = resetPitch ? 0 : viewport.pitch;
410
+ (_b = (_a = this.props).onReset) == null ? void 0 : _b.call(_a, { viewId, bearing: nextBearing, pitch: nextPitch });
355
411
  const nextViewState = {
356
412
  ...viewState,
357
- bearing: 0,
358
- ...this.getRotation(viewport)[0] === 0 ? { pitch: 0 } : {},
413
+ bearing: nextBearing,
414
+ ...resetPitch ? { pitch: nextPitch } : {},
359
415
  transitionDuration: this.props.transitionDuration,
360
416
  transitionInterpolator: new import_core4.FlyToInterpolator()
361
417
  };
@@ -369,7 +425,9 @@ CompassWidget.defaultProps = {
369
425
  placement: "top-left",
370
426
  viewId: null,
371
427
  label: "Reset Compass",
372
- transitionDuration: 200
428
+ transitionDuration: 200,
429
+ onReset: () => {
430
+ }
373
431
  };
374
432
 
375
433
  // dist/scale-widget.js
@@ -791,9 +849,19 @@ var GeocoderWidget = class extends import_core6.Widget {
791
849
  }), (0, import_jsx_runtime9.jsx)(DropdownMenu, { menuItems, onSelect: this.handleSelect }), this.geocodeHistory.errorText && (0, import_jsx_runtime9.jsx)("div", { className: "deck-widget-geocoder-error", children: this.geocodeHistory.errorText })] }), rootElement);
792
850
  }
793
851
  flyTo(viewState) {
794
- var _a;
852
+ var _a, _b, _c;
795
853
  const viewIds = this.viewId ? [this.viewId] : ((_a = this.deck) == null ? void 0 : _a.getViews().map((v) => v.id)) ?? [];
796
854
  for (const viewId of viewIds) {
855
+ if ("longitude" in viewState && "latitude" in viewState) {
856
+ (_c = (_b = this.props).onGeocode) == null ? void 0 : _c.call(_b, {
857
+ viewId,
858
+ coordinates: {
859
+ longitude: viewState.longitude,
860
+ latitude: viewState.latitude,
861
+ zoom: viewState.zoom
862
+ }
863
+ });
864
+ }
797
865
  const currentViewState = this.getViewState(viewId);
798
866
  const nextViewState = {
799
867
  ...currentViewState,
@@ -817,7 +885,9 @@ GeocoderWidget.defaultProps = {
817
885
  geocoder: "coordinates",
818
886
  customGeocoder: CoordinatesGeocoder,
819
887
  apiKey: "",
820
- _geolocation: false
888
+ _geolocation: false,
889
+ onGeocode: () => {
890
+ }
821
891
  };
822
892
  function getGeocoder(props) {
823
893
  switch (props.geocoder) {
@@ -858,9 +928,10 @@ var FullscreenWidget = class extends import_core8.Widget {
858
928
  document.removeEventListener("fullscreenchange", this.onFullscreenChange.bind(this));
859
929
  }
860
930
  onRenderHTML(rootElement) {
931
+ const isFullscreen = this.getFullscreen();
861
932
  (0, import_preact7.render)((0, import_jsx_runtime10.jsx)(IconButton, { onClick: () => {
862
933
  this.handleClick().catch((err) => import_core8.log.error(err)());
863
- }, label: this.fullscreen ? this.props.exitLabel : this.props.enterLabel, className: this.fullscreen ? "deck-widget-fullscreen-exit" : "deck-widget-fullscreen-enter" }), rootElement);
934
+ }, label: isFullscreen ? this.props.exitLabel : this.props.enterLabel, className: isFullscreen ? "deck-widget-fullscreen-exit" : "deck-widget-fullscreen-enter" }), rootElement);
864
935
  }
865
936
  setProps(props) {
866
937
  this.placement = props.placement ?? this.placement;
@@ -871,21 +942,25 @@ var FullscreenWidget = class extends import_core8.Widget {
871
942
  var _a, _b, _c;
872
943
  return this.props.container || ((_a = this.deck) == null ? void 0 : _a.props.parent) || ((_c = (_b = this.deck) == null ? void 0 : _b.getCanvas()) == null ? void 0 : _c.parentElement);
873
944
  }
945
+ getFullscreen() {
946
+ return this.fullscreen;
947
+ }
874
948
  onFullscreenChange() {
875
- const prevFullscreen = this.fullscreen;
949
+ var _a, _b;
876
950
  const fullscreen = document.fullscreenElement === this.getContainer();
877
- if (prevFullscreen !== fullscreen) {
878
- this.fullscreen = !this.fullscreen;
951
+ if (fullscreen !== this.fullscreen) {
952
+ this.fullscreen = fullscreen;
953
+ (_b = (_a = this.props).onFullscreenChange) == null ? void 0 : _b.call(_a, fullscreen);
954
+ this.updateHTML();
879
955
  }
880
- this.updateHTML();
881
956
  }
882
957
  async handleClick() {
883
- if (this.fullscreen) {
958
+ const isFullscreen = this.getFullscreen();
959
+ if (isFullscreen) {
884
960
  await this.exitFullscreen();
885
961
  } else {
886
962
  await this.requestFullscreen();
887
963
  }
888
- this.updateHTML();
889
964
  }
890
965
  async requestFullscreen() {
891
966
  const container = this.getContainer();
@@ -903,8 +978,11 @@ var FullscreenWidget = class extends import_core8.Widget {
903
978
  }
904
979
  }
905
980
  togglePseudoFullscreen() {
906
- var _a;
981
+ var _a, _b, _c;
907
982
  (_a = this.getContainer()) == null ? void 0 : _a.classList.toggle("deck-pseudo-fullscreen");
983
+ this.fullscreen = !this.fullscreen;
984
+ (_c = (_b = this.props).onFullscreenChange) == null ? void 0 : _c.call(_b, this.fullscreen);
985
+ this.updateHTML();
908
986
  }
909
987
  };
910
988
  FullscreenWidget.defaultProps = {
@@ -914,7 +992,9 @@ FullscreenWidget.defaultProps = {
914
992
  viewId: null,
915
993
  enterLabel: "Enter Fullscreen",
916
994
  exitLabel: "Exit Fullscreen",
917
- container: void 0
995
+ container: void 0,
996
+ onFullscreenChange: () => {
997
+ }
918
998
  };
919
999
 
920
1000
  // dist/splitter-widget.js
@@ -1460,7 +1540,7 @@ var import_preact12 = require("preact");
1460
1540
  var import_jsx_runtime17 = require("preact/jsx-runtime");
1461
1541
  var import_hooks6 = require("preact/hooks");
1462
1542
  var wheelListenerOptions = { passive: false };
1463
- var clamp = (value, min, max) => {
1543
+ var clamp2 = (value, min, max) => {
1464
1544
  if (value < min) {
1465
1545
  return min;
1466
1546
  }
@@ -1502,7 +1582,7 @@ function RangeInput(props) {
1502
1582
  const range = max - min;
1503
1583
  const rangeSize = Math.max(0, value[1] - value[0]);
1504
1584
  const maxStart = Math.max(0, range - rangeSize);
1505
- const clampedStart = clamp(value[0], min, min + maxStart);
1585
+ const clampedStart = clamp2(value[0], min, min + maxStart);
1506
1586
  const { thumbLength, thumbOffset } = (0, import_hooks6.useMemo)(() => {
1507
1587
  if (trackLength <= 0 || range <= 0) {
1508
1588
  return { thumbLength: 0, thumbOffset: 0 };
@@ -1512,7 +1592,7 @@ function RangeInput(props) {
1512
1592
  }
1513
1593
  const nextThumbLength = rangeSize / range;
1514
1594
  const travel = Math.max(0, 1 - nextThumbLength);
1515
- const ratio = maxStart <= 0 ? 0 : clamp((clampedStart - min) / maxStart, 0, 1);
1595
+ const ratio = maxStart <= 0 ? 0 : clamp2((clampedStart - min) / maxStart, 0, 1);
1516
1596
  return {
1517
1597
  thumbLength: Math.max(0, Math.min(nextThumbLength, 1)),
1518
1598
  thumbOffset: travel * ratio
@@ -1522,7 +1602,7 @@ function RangeInput(props) {
1522
1602
  if (!onChange) {
1523
1603
  return;
1524
1604
  }
1525
- const clamped = clamp(nextStart, min, min + maxStart);
1605
+ const clamped = clamp2(nextStart, min, min + maxStart);
1526
1606
  onChange([clamped, clamped + rangeSize]);
1527
1607
  }, [onChange, min, maxStart, rangeSize]);
1528
1608
  const handleStepNegative = (0, import_hooks6.useCallback)((event) => {
@@ -1551,7 +1631,7 @@ function RangeInput(props) {
1551
1631
  const coordinate = vertical ? event.clientY - trackStart : event.clientX - trackStart;
1552
1632
  const span = Math.max(1, 1 - thumbLength) * trackLength;
1553
1633
  const thumbCenter = thumbLength / 2 * trackLength;
1554
- const ratio = span <= 0 ? 0 : clamp((coordinate - thumbCenter) / span, 0, 1);
1634
+ const ratio = span <= 0 ? 0 : clamp2((coordinate - thumbCenter) / span, 0, 1);
1555
1635
  emitRange(min + ratio * maxStart);
1556
1636
  }, [vertical, trackLength, thumbLength, emitRange, min, maxStart]);
1557
1637
  const handleThumbPointerDown = (event) => {
@@ -1583,7 +1663,7 @@ function RangeInput(props) {
1583
1663
  const coordinate = vertical ? event.clientY : event.clientX;
1584
1664
  const delta = coordinate - state.startCoord;
1585
1665
  const ratio = state.startRatio + delta / trackLength2;
1586
- const nextStart = clamp(ratio * (state.max - state.min), 0, state.maxStart) + state.min;
1666
+ const nextStart = clamp2(ratio * (state.max - state.min), 0, state.maxStart) + state.min;
1587
1667
  emitRange(nextStart);
1588
1668
  event.preventDefault();
1589
1669
  }, [emitRange, vertical]);
@@ -1714,7 +1794,7 @@ function RangeInput(props) {
1714
1794
  }
1715
1795
 
1716
1796
  // dist/scrollbar-widget.js
1717
- var clamp2 = (value, min, max) => {
1797
+ var clamp3 = (value, min, max) => {
1718
1798
  if (value < min) {
1719
1799
  return min;
1720
1800
  }
@@ -1822,7 +1902,7 @@ var ScrollbarWidget = class extends import_core13.Widget {
1822
1902
  }
1823
1903
  getClampedOffset() {
1824
1904
  const maxScroll = this.getMaxScroll();
1825
- return clamp2(this.scrollOffset, 0, maxScroll);
1905
+ return clamp3(this.scrollOffset, 0, maxScroll);
1826
1906
  }
1827
1907
  isVertical() {
1828
1908
  return this.props.orientation !== "horizontal";
@@ -1841,7 +1921,7 @@ var ScrollbarWidget = class extends import_core13.Widget {
1841
1921
  }
1842
1922
  emitScroll(next) {
1843
1923
  const maxScroll = this.getMaxScroll();
1844
- const target = clamp2(Math.round(next), 0, maxScroll);
1924
+ const target = clamp3(Math.round(next), 0, maxScroll);
1845
1925
  const viewport = this.viewport;
1846
1926
  if (viewport && target !== this.getClampedOffset()) {
1847
1927
  const pixel = viewport.project(viewport.position);
@@ -2017,74 +2097,124 @@ var import_jsx_runtime22 = require("preact/jsx-runtime");
2017
2097
  var import_core17 = require("@deck.gl/core");
2018
2098
  var import_preact16 = require("preact");
2019
2099
  var TimelineWidget = class extends import_core17.Widget {
2100
+ /**
2101
+ * Returns the current time value.
2102
+ * In controlled mode, returns the time prop.
2103
+ * In uncontrolled mode, returns the internal state.
2104
+ */
2105
+ getTime() {
2106
+ return this.props.time ?? this.currentTime;
2107
+ }
2108
+ /**
2109
+ * Returns the current playing state.
2110
+ * In controlled mode, returns the playing prop.
2111
+ * In uncontrolled mode, returns the internal state.
2112
+ */
2113
+ getPlaying() {
2114
+ return this.props.playing ?? this._playing;
2115
+ }
2020
2116
  constructor(props = {}) {
2021
2117
  var _a;
2022
2118
  super(props);
2023
2119
  this.id = "timeline";
2024
2120
  this.className = "deck-widget-timeline";
2025
2121
  this.placement = "fill";
2026
- this.playing = false;
2122
+ this._playing = false;
2027
2123
  this.timerId = null;
2028
2124
  this.handlePlayPause = () => {
2029
- if (this.playing) {
2030
- this.stop();
2031
- } else {
2032
- this.play();
2125
+ var _a2, _b;
2126
+ const isPlaying = this.getPlaying();
2127
+ const nextPlaying = !isPlaying;
2128
+ (_b = (_a2 = this.props).onPlayingChange) == null ? void 0 : _b.call(_a2, nextPlaying);
2129
+ if (this.props.playing === void 0) {
2130
+ if (nextPlaying) {
2131
+ this.play();
2132
+ } else {
2133
+ this.stop();
2134
+ }
2033
2135
  }
2034
2136
  };
2035
2137
  this.handleTimeChange = ([value]) => {
2036
2138
  var _a2;
2037
- this.currentTime = value;
2038
- (_a2 = this.props.timeline) == null ? void 0 : _a2.setTime(value);
2039
2139
  this.props.onTimeChange(value);
2040
- this.updateHTML();
2140
+ if (this.props.time === void 0) {
2141
+ this.currentTime = value;
2142
+ (_a2 = this.props.timeline) == null ? void 0 : _a2.setTime(value);
2143
+ this.updateHTML();
2144
+ }
2041
2145
  };
2042
2146
  this.tick = () => {
2147
+ var _a2, _b, _c;
2043
2148
  const { timeRange: [min, max], step, loop } = this.props;
2044
2149
  if (step > 0) {
2045
- let next = Math.round(this.currentTime / step) * step + step;
2150
+ const currentTime = this.getTime();
2151
+ let next = Math.round(currentTime / step) * step + step;
2046
2152
  if (next > max) {
2047
- if (this.currentTime < max) {
2153
+ if (currentTime < max) {
2048
2154
  next = max;
2049
2155
  } else if (loop) {
2050
2156
  next = min;
2051
2157
  } else {
2052
2158
  next = max;
2053
- this.playing = false;
2159
+ this._playing = false;
2160
+ (_b = (_a2 = this.props).onPlayingChange) == null ? void 0 : _b.call(_a2, false);
2054
2161
  }
2055
2162
  }
2056
- this.currentTime = next;
2057
2163
  this.props.onTimeChange(next);
2164
+ if (this.props.time === void 0) {
2165
+ this.currentTime = next;
2166
+ (_c = this.props.timeline) == null ? void 0 : _c.setTime(next);
2167
+ }
2058
2168
  this.updateHTML();
2059
2169
  }
2060
- if (this.playing) {
2170
+ if (this._playing) {
2061
2171
  this.timerId = window.setTimeout(this.tick, this.props.playInterval);
2062
2172
  } else {
2063
2173
  this.timerId = null;
2064
2174
  }
2065
2175
  };
2066
2176
  this.currentTime = this.props.initialTime ?? this.props.timeRange[0];
2067
- (_a = this.props.timeline) == null ? void 0 : _a.setTime(this.currentTime);
2177
+ const syncTime = this.props.time ?? this.currentTime;
2178
+ (_a = this.props.timeline) == null ? void 0 : _a.setTime(syncTime);
2068
2179
  this.setProps(this.props);
2069
2180
  }
2070
2181
  setProps(props) {
2182
+ var _a;
2183
+ const { playing: prevPlaying, time: prevTime } = this.props;
2071
2184
  this.viewId = props.viewId ?? this.viewId;
2072
2185
  super.setProps(props);
2186
+ if (props.time !== void 0 && props.time !== prevTime) {
2187
+ (_a = this.props.timeline) == null ? void 0 : _a.setTime(props.time);
2188
+ }
2189
+ if (props.playing !== void 0 && props.playing !== prevPlaying) {
2190
+ if (props.playing && !this._playing) {
2191
+ this._startTimer();
2192
+ } else if (!props.playing && this._playing) {
2193
+ this._stopTimer();
2194
+ }
2195
+ }
2073
2196
  }
2074
2197
  onAdd() {
2075
- this.playing = false;
2198
+ var _a, _b;
2199
+ this._playing = false;
2076
2200
  this.timerId = null;
2077
- if (this.props.autoPlay)
2078
- this.play();
2201
+ if (this.props.autoPlay) {
2202
+ if (this.props.playing !== void 0) {
2203
+ (_b = (_a = this.props).onPlayingChange) == null ? void 0 : _b.call(_a, true);
2204
+ } else {
2205
+ this.play();
2206
+ }
2207
+ }
2079
2208
  }
2080
2209
  onRemove() {
2081
2210
  this.stop();
2082
2211
  }
2083
2212
  onRenderHTML(rootElement) {
2084
2213
  const { timeRange, step, formatLabel } = this.props;
2085
- const currentTime = this.currentTime;
2214
+ const isPlaying = this.getPlaying();
2215
+ const currentTime = this.getTime();
2086
2216
  rootElement.dataset.placement = this.props.placement;
2087
- (0, import_preact16.render)((0, import_jsx_runtime22.jsxs)("div", { className: "deck-widget-button-group", children: [this.playing ? (0, import_jsx_runtime22.jsx)(IconButton, { label: "Pause", className: "deck-widget-timeline-pause", onClick: this.handlePlayPause }) : (0, import_jsx_runtime22.jsx)(IconButton, { label: "Play", className: "deck-widget-timeline-play", onClick: this.handlePlayPause }), (0, import_jsx_runtime22.jsx)(RangeInput, { min: timeRange[0], max: timeRange[1], orientation: "horizontal", step, value: [currentTime, currentTime], onChange: this.handleTimeChange, decorations: [
2217
+ (0, import_preact16.render)((0, import_jsx_runtime22.jsxs)("div", { className: "deck-widget-button-group", children: [isPlaying ? (0, import_jsx_runtime22.jsx)(IconButton, { label: "Pause", className: "deck-widget-timeline-pause", onClick: this.handlePlayPause }) : (0, import_jsx_runtime22.jsx)(IconButton, { label: "Play", className: "deck-widget-timeline-play", onClick: this.handlePlayPause }), (0, import_jsx_runtime22.jsx)(RangeInput, { min: timeRange[0], max: timeRange[1], orientation: "horizontal", step, value: [currentTime, currentTime], onChange: this.handleTimeChange, decorations: [
2088
2218
  {
2089
2219
  position: [currentTime, currentTime + step],
2090
2220
  element: (0, import_jsx_runtime22.jsx)("div", { className: "deck-widget-timeline-label deck-widget-timeline-label--current", children: formatLabel(currentTime) })
@@ -2092,22 +2222,33 @@ var TimelineWidget = class extends import_core17.Widget {
2092
2222
  ] })] }), rootElement);
2093
2223
  }
2094
2224
  play() {
2095
- this.playing = true;
2225
+ var _a;
2226
+ this._playing = true;
2096
2227
  const { timeRange: [min, max] } = this.props;
2097
- if (this.currentTime >= max) {
2228
+ if (this.props.time === void 0 && this.getTime() >= max) {
2098
2229
  this.currentTime = min;
2099
2230
  this.props.onTimeChange(min);
2231
+ (_a = this.props.timeline) == null ? void 0 : _a.setTime(min);
2100
2232
  }
2101
2233
  this.updateHTML();
2102
2234
  this.tick();
2103
2235
  }
2104
2236
  stop() {
2105
- this.playing = false;
2237
+ this._stopTimer();
2238
+ this.updateHTML();
2239
+ }
2240
+ /** Start the playback timer (used internally) */
2241
+ _startTimer() {
2242
+ this._playing = true;
2243
+ this.tick();
2244
+ }
2245
+ /** Stop the playback timer (used internally) */
2246
+ _stopTimer() {
2247
+ this._playing = false;
2106
2248
  if (this.timerId !== null) {
2107
2249
  window.clearTimeout(this.timerId);
2108
2250
  this.timerId = null;
2109
2251
  }
2110
- this.updateHTML();
2111
2252
  }
2112
2253
  };
2113
2254
  TimelineWidget.defaultProps = {
@@ -2119,11 +2260,15 @@ TimelineWidget.defaultProps = {
2119
2260
  timeRange: [0, 100],
2120
2261
  step: 1,
2121
2262
  initialTime: void 0,
2263
+ time: void 0,
2122
2264
  onTimeChange: () => {
2123
2265
  },
2124
2266
  autoPlay: false,
2125
2267
  loop: false,
2126
2268
  playInterval: 1e3,
2269
+ playing: void 0,
2270
+ onPlayingChange: () => {
2271
+ },
2127
2272
  formatLabel: String
2128
2273
  };
2129
2274
 
@@ -2313,44 +2458,64 @@ var ThemeWidget = class extends import_core20.Widget {
2313
2458
  }
2314
2459
  // eslint-disable-next-line complexity
2315
2460
  setProps(props) {
2316
- const { lightModeTheme, darkModeTheme } = this.props;
2461
+ const { lightModeTheme, darkModeTheme, themeMode: prevThemeMode } = this.props;
2317
2462
  this.placement = props.placement ?? this.placement;
2318
2463
  this.viewId = props.viewId ?? this.viewId;
2319
2464
  super.setProps(props);
2320
- switch (this.themeMode) {
2465
+ const currentMode = this.getThemeMode();
2466
+ if (props.themeMode !== void 0 && props.themeMode !== prevThemeMode) {
2467
+ this._applyTheme(props.themeMode);
2468
+ return;
2469
+ }
2470
+ switch (currentMode) {
2321
2471
  case "light":
2322
2472
  if (props.lightModeTheme && !(0, import_core19._deepEqual)(props.lightModeTheme, lightModeTheme, 1)) {
2323
- this._setThemeMode("light");
2473
+ this._applyTheme("light");
2324
2474
  }
2325
2475
  break;
2326
2476
  case "dark":
2327
2477
  if (props.darkModeTheme && !(0, import_core19._deepEqual)(props.darkModeTheme, darkModeTheme, 1)) {
2328
- this._setThemeMode("dark");
2478
+ this._applyTheme("dark");
2329
2479
  }
2330
2480
  break;
2331
2481
  default:
2332
- import_core19.log.warn(`Invalid theme mode ${this.themeMode}`)();
2482
+ import_core19.log.warn(`Invalid theme mode ${currentMode}`)();
2333
2483
  }
2334
2484
  }
2335
2485
  onRenderHTML(rootElement) {
2336
2486
  const { lightModeLabel, darkModeLabel } = this.props;
2337
- (0, import_preact18.render)((0, import_jsx_runtime24.jsx)(IconButton, { onClick: this._handleClick.bind(this), label: this.themeMode === "dark" ? darkModeLabel : lightModeLabel, className: this.themeMode === "dark" ? "deck-widget-moon" : "deck-widget-sun" }), rootElement);
2487
+ const currentMode = this.getThemeMode();
2488
+ (0, import_preact18.render)((0, import_jsx_runtime24.jsx)(IconButton, { onClick: this._handleClick.bind(this), label: currentMode === "dark" ? darkModeLabel : lightModeLabel, className: currentMode === "dark" ? "deck-widget-moon" : "deck-widget-sun" }), rootElement);
2338
2489
  }
2339
2490
  onAdd() {
2340
- this._setThemeMode(this.themeMode);
2491
+ this._applyTheme(this.getThemeMode());
2492
+ }
2493
+ /**
2494
+ * Returns the current theme mode.
2495
+ * In controlled mode, returns the themeMode prop.
2496
+ * In uncontrolled mode, returns the internal state.
2497
+ */
2498
+ getThemeMode() {
2499
+ return this.props.themeMode ?? this.themeMode;
2341
2500
  }
2342
2501
  _handleClick() {
2343
- const newThemeMode = this.themeMode === "dark" ? "light" : "dark";
2344
- this._setThemeMode(newThemeMode);
2502
+ var _a, _b;
2503
+ const currentMode = this.getThemeMode();
2504
+ const nextMode = currentMode === "dark" ? "light" : "dark";
2505
+ (_b = (_a = this.props).onThemeModeChange) == null ? void 0 : _b.call(_a, nextMode);
2506
+ if (this.props.themeMode === void 0) {
2507
+ this.themeMode = nextMode;
2508
+ this._applyTheme(nextMode);
2509
+ }
2345
2510
  }
2346
- _setThemeMode(themeMode) {
2511
+ /** Apply theme styling without changing internal state */
2512
+ _applyTheme(themeMode) {
2347
2513
  var _a;
2348
- this.themeMode = themeMode;
2349
2514
  const container = (_a = this.rootElement) == null ? void 0 : _a.closest(".deck-widget-container");
2350
2515
  if (container) {
2351
2516
  const themeStyle = themeMode === "dark" ? this.props.darkModeTheme : this.props.lightModeTheme;
2352
2517
  (0, import_core19._applyStyles)(container, themeStyle);
2353
- const label = this.themeMode === "dark" ? this.props.darkModeLabel : this.props.lightModeLabel;
2518
+ const label = themeMode === "dark" ? this.props.darkModeLabel : this.props.lightModeLabel;
2354
2519
  import_core19.log.log(1, `Switched theme to ${label}`, themeStyle)();
2355
2520
  this.updateHTML();
2356
2521
  }
@@ -2376,7 +2541,10 @@ ThemeWidget.defaultProps = {
2376
2541
  lightModeTheme: LightGlassTheme,
2377
2542
  darkModeLabel: "Dark Mode",
2378
2543
  darkModeTheme: DarkGlassTheme,
2379
- initialThemeMode: "auto"
2544
+ initialThemeMode: "auto",
2545
+ themeMode: void 0,
2546
+ onThemeModeChange: () => {
2547
+ }
2380
2548
  };
2381
2549
 
2382
2550
  // dist/loading-widget.js
@@ -2404,9 +2572,11 @@ var LoadingWidget = class extends import_core21.Widget {
2404
2572
  );
2405
2573
  }
2406
2574
  onRedraw({ layers }) {
2575
+ var _a, _b;
2407
2576
  const loading = layers.some((layer) => !layer.isLoaded);
2408
2577
  if (loading !== this.loading) {
2409
2578
  this.loading = loading;
2579
+ (_b = (_a = this.props).onLoadingChange) == null ? void 0 : _b.call(_a, loading);
2410
2580
  this.updateHTML();
2411
2581
  }
2412
2582
  }
@@ -2419,7 +2589,9 @@ LoadingWidget.defaultProps = {
2419
2589
  id: "loading",
2420
2590
  placement: "top-left",
2421
2591
  viewId: null,
2422
- label: "Loading layer data"
2592
+ label: "Loading layer data",
2593
+ onLoadingChange: () => {
2594
+ }
2423
2595
  };
2424
2596
 
2425
2597
  // dist/stats-widget.js
@@ -2444,15 +2616,28 @@ var DEFAULT_FORMATTERS = {
2444
2616
  memory: (stat) => `${stat.name}: ${formatMemory(stat.count)}`
2445
2617
  };
2446
2618
  var StatsWidget = class extends import_core22.Widget {
2619
+ /**
2620
+ * Returns the current expanded state.
2621
+ * In controlled mode, returns the expanded prop.
2622
+ * In uncontrolled mode, returns the internal state.
2623
+ */
2624
+ getExpanded() {
2625
+ return this.props.expanded ?? this._expanded;
2626
+ }
2447
2627
  constructor(props = {}) {
2448
2628
  super(props);
2449
2629
  this.className = "deck-widget-stats";
2450
2630
  this.placement = "top-left";
2451
2631
  this._counter = 0;
2452
- this.collapsed = true;
2453
- this._toggleCollapsed = () => {
2454
- this.collapsed = !this.collapsed;
2455
- this.updateHTML();
2632
+ this._expanded = false;
2633
+ this._toggleExpanded = () => {
2634
+ var _a, _b;
2635
+ const nextExpanded = !this.getExpanded();
2636
+ (_b = (_a = this.props).onExpandedChange) == null ? void 0 : _b.call(_a, nextExpanded);
2637
+ if (this.props.expanded === void 0) {
2638
+ this._expanded = nextExpanded;
2639
+ this.updateHTML();
2640
+ }
2456
2641
  };
2457
2642
  this._getFps = () => {
2458
2643
  var _a;
@@ -2460,7 +2645,7 @@ var StatsWidget = class extends import_core22.Widget {
2460
2645
  };
2461
2646
  this._formatters = { ...DEFAULT_FORMATTERS };
2462
2647
  this._resetOnUpdate = { ...this.props.resetOnUpdate };
2463
- this.collapsed = !props.defaultIsExpanded;
2648
+ this._expanded = Boolean(props.initialExpanded);
2464
2649
  this.setProps(props);
2465
2650
  }
2466
2651
  setProps(props) {
@@ -2483,9 +2668,9 @@ var StatsWidget = class extends import_core22.Widget {
2483
2668
  }
2484
2669
  }
2485
2670
  onRenderHTML(rootElement) {
2486
- const collapsed = this.collapsed;
2487
- if (collapsed) {
2488
- (0, import_preact20.render)((0, import_jsx_runtime26.jsx)(FpsIcon, { getFps: this._getFps, onClick: this._toggleCollapsed }), rootElement);
2671
+ const isExpanded = this.getExpanded();
2672
+ if (!isExpanded) {
2673
+ (0, import_preact20.render)((0, import_jsx_runtime26.jsx)(FpsIcon, { getFps: this._getFps, onClick: this._toggleExpanded }), rootElement);
2489
2674
  return;
2490
2675
  }
2491
2676
  const stats = this._getStats();
@@ -2503,10 +2688,10 @@ var StatsWidget = class extends import_core22.Widget {
2503
2688
  });
2504
2689
  });
2505
2690
  }
2506
- (0, import_preact20.render)((0, import_jsx_runtime26.jsxs)("div", { className: "deck-widget-stats-container", style: { cursor: "default" }, children: [(0, import_jsx_runtime26.jsxs)("div", { className: "deck-widget-stats-header", style: { cursor: "pointer", pointerEvents: "auto" }, onClick: this._toggleCollapsed, children: [(0, import_jsx_runtime26.jsx)("b", { children: title }), deviceLabel && (0, import_jsx_runtime26.jsx)("span", { className: "deck-widget-stats-device", children: deviceLabel }), (0, import_jsx_runtime26.jsx)("button", { className: "deck-widget-dropdown-button", children: (0, import_jsx_runtime26.jsx)("span", { className: "deck-widget-dropdown-icon open" }) })] }), (0, import_jsx_runtime26.jsx)("div", { className: "deck-widget-stats-content", children: items })] }), rootElement);
2691
+ (0, import_preact20.render)((0, import_jsx_runtime26.jsxs)("div", { className: "deck-widget-stats-container", style: { cursor: "default" }, children: [(0, import_jsx_runtime26.jsxs)("div", { className: "deck-widget-stats-header", style: { cursor: "pointer", pointerEvents: "auto" }, onClick: this._toggleExpanded, children: [(0, import_jsx_runtime26.jsx)("b", { children: title }), deviceLabel && (0, import_jsx_runtime26.jsx)("span", { className: "deck-widget-stats-device", children: deviceLabel }), (0, import_jsx_runtime26.jsx)("button", { className: "deck-widget-dropdown-button", children: (0, import_jsx_runtime26.jsx)("span", { className: "deck-widget-dropdown-icon open" }) })] }), (0, import_jsx_runtime26.jsx)("div", { className: "deck-widget-stats-content", children: items })] }), rootElement);
2507
2692
  }
2508
2693
  onRedraw() {
2509
- if (!this.collapsed) {
2694
+ if (this.getExpanded()) {
2510
2695
  const framesPerUpdate = Math.max(1, this.props.framesPerUpdate || 1);
2511
2696
  if (this._counter++ % framesPerUpdate === 0) {
2512
2697
  this.updateHTML();
@@ -2561,13 +2746,16 @@ StatsWidget.defaultProps = {
2561
2746
  type: "deck",
2562
2747
  placement: "top-left",
2563
2748
  viewId: null,
2564
- defaultIsExpanded: false,
2749
+ initialExpanded: false,
2565
2750
  stats: void 0,
2566
2751
  title: "Stats",
2567
2752
  framesPerUpdate: 1,
2568
2753
  formatters: {},
2569
2754
  resetOnUpdate: {},
2570
- id: "stats"
2755
+ id: "stats",
2756
+ expanded: void 0,
2757
+ onExpandedChange: () => {
2758
+ }
2571
2759
  };
2572
2760
  function FpsIcon({ getFps, onClick }) {
2573
2761
  const [fps, setFps] = (0, import_hooks7.useState)(getFps());