@vanduo-oss/framework 1.3.2 → 1.3.4

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.
@@ -1,4 +1,4 @@
1
- /*! Vanduo v1.3.2 | Built: 2026-04-06T19:04:41.601Z | git:8e08b38 | development */
1
+ /*! Vanduo v1.3.4 | Built: 2026-04-14T21:21:55.517Z | git:73e3db5 | development */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -132,7 +132,7 @@ module.exports = __toCommonJS(index_exports);
132
132
  // js/vanduo.js
133
133
  (function() {
134
134
  "use strict";
135
- const VANDUO_VERSION = true ? "1.3.2" : "0.0.0-dev";
135
+ const VANDUO_VERSION = true ? "1.3.4" : "0.0.0-dev";
136
136
  const Vanduo2 = {
137
137
  version: VANDUO_VERSION,
138
138
  components: {},
@@ -2182,6 +2182,31 @@ module.exports = __toCommonJS(index_exports);
2182
2182
  this.initNavbar(navbar);
2183
2183
  });
2184
2184
  },
2185
+ /**
2186
+ * Initialize scroll-aware glass/transparent behaviour for a navbar.
2187
+ * Adds/removes `.vd-navbar-scrolled` when the page scrolls past a threshold.
2188
+ * Threshold: `data-scroll-threshold` attribute (px) or the navbar's own height.
2189
+ * @param {HTMLElement} navbar - Navbar element
2190
+ * @returns {Function|null} Cleanup function, or null if not applicable
2191
+ */
2192
+ initScrollWatcher: function(navbar) {
2193
+ const isGlass = navbar.classList.contains("vd-navbar-glass");
2194
+ const isTransparent = navbar.classList.contains("vd-navbar-transparent");
2195
+ if (!isGlass && !isTransparent) {
2196
+ return null;
2197
+ }
2198
+ const getThreshold = () => {
2199
+ const attr = parseInt(navbar.dataset.scrollThreshold, 10);
2200
+ return isNaN(attr) ? navbar.offsetHeight || 60 : attr;
2201
+ };
2202
+ const onScroll = () => {
2203
+ const scrolled = window.scrollY > getThreshold();
2204
+ navbar.classList.toggle("vd-navbar-scrolled", scrolled);
2205
+ };
2206
+ onScroll();
2207
+ window.addEventListener("scroll", onScroll, { passive: true });
2208
+ return () => window.removeEventListener("scroll", onScroll);
2209
+ },
2185
2210
  /**
2186
2211
  * Initialize a single navbar
2187
2212
  * @param {HTMLElement} navbar - Navbar element
@@ -2190,10 +2215,17 @@ module.exports = __toCommonJS(index_exports);
2190
2215
  const toggle = navbar.querySelector(".vd-navbar-toggle, .vd-navbar-burger");
2191
2216
  const menu = navbar.querySelector(".vd-navbar-menu");
2192
2217
  const overlay = navbar.querySelector(".vd-navbar-overlay") || this.createOverlay(navbar);
2218
+ const cleanupFunctions = [];
2219
+ const scrollWatcherCleanup = this.initScrollWatcher(navbar);
2220
+ if (scrollWatcherCleanup) {
2221
+ cleanupFunctions.push(scrollWatcherCleanup);
2222
+ }
2193
2223
  if (!toggle || !menu) {
2224
+ if (cleanupFunctions.length) {
2225
+ this.instances.set(navbar, { toggle: null, menu: null, overlay: null, cleanup: cleanupFunctions });
2226
+ }
2194
2227
  return;
2195
2228
  }
2196
- const cleanupFunctions = [];
2197
2229
  const toggleClickHandler = (e) => {
2198
2230
  e.preventDefault();
2199
2231
  e.stopPropagation();
@@ -3831,6 +3863,7 @@ module.exports = __toCommonJS(index_exports);
3831
3863
  loadPreferences: function() {
3832
3864
  this.state.theme = this.getStorageValue(this.STORAGE_KEYS.THEME, this.DEFAULTS.THEME);
3833
3865
  this.state.primary = this.getStorageValue(this.STORAGE_KEYS.PRIMARY, this.getDefaultPrimary(this.state.theme));
3866
+ this._normalizeDefaultPrimaryIfStaleWithStoredTheme();
3834
3867
  this.state.neutral = this.getStorageValue(this.STORAGE_KEYS.NEUTRAL, this.DEFAULTS.NEUTRAL);
3835
3868
  this.state.radius = this.getStorageValue(this.STORAGE_KEYS.RADIUS, this.DEFAULTS.RADIUS);
3836
3869
  this.state.font = this.getStorageValue(this.STORAGE_KEYS.FONT, this.DEFAULTS.FONT);
@@ -3916,12 +3949,10 @@ module.exports = __toCommonJS(index_exports);
3916
3949
  mode = this.DEFAULTS.THEME;
3917
3950
  }
3918
3951
  this._isApplying = true;
3919
- const currentMode = this.state.theme;
3920
- const oldDefault = this.getDefaultPrimary(currentMode);
3921
- if (this.state.primary === oldDefault) {
3922
- const newDefault = this.getDefaultPrimary(mode);
3923
- if (newDefault !== this.state.primary) {
3924
- this.applyPrimary(newDefault);
3952
+ if (this.isUsingDefaultPrimary()) {
3953
+ const expected = this.getDefaultPrimary(mode);
3954
+ if (this.state.primary !== expected) {
3955
+ this.applyPrimary(expected);
3925
3956
  }
3926
3957
  }
3927
3958
  this.state.theme = mode;
@@ -4163,6 +4194,20 @@ module.exports = __toCommonJS(index_exports);
4163
4194
  isUsingDefaultPrimary: function() {
4164
4195
  return this.state.primary === this.DEFAULTS.PRIMARY_LIGHT || this.state.primary === this.DEFAULTS.PRIMARY_DARK;
4165
4196
  },
4197
+ /**
4198
+ * When primary is still one of the auto-default palette keys (black/amber) but
4199
+ * localStorage was written under a different theme (or OS changed in system mode),
4200
+ * align in-memory state before applyAllPreferences runs — avoids amber+light / black+dark drift.
4201
+ */
4202
+ _normalizeDefaultPrimaryIfStaleWithStoredTheme: function() {
4203
+ if (!this.isUsingDefaultPrimary()) {
4204
+ return;
4205
+ }
4206
+ const expected = this.getDefaultPrimary(this.state.theme);
4207
+ if (this.state.primary !== expected) {
4208
+ this.state.primary = expected;
4209
+ }
4210
+ },
4166
4211
  bindEvents: function() {
4167
4212
  if (this.elements.trigger) {
4168
4213
  this.addListener(this.elements.trigger, "click", (e) => {
@@ -4401,6 +4446,9 @@ module.exports = __toCommonJS(index_exports);
4401
4446
  this._onMediaChange = (_e) => {
4402
4447
  if (this.state.preference === "system") {
4403
4448
  this.applyTheme();
4449
+ if (window.ThemeCustomizer && typeof window.ThemeCustomizer.applyTheme === "function" && !window.ThemeCustomizer._isApplying) {
4450
+ window.ThemeCustomizer.applyTheme("system");
4451
+ }
4404
4452
  }
4405
4453
  };
4406
4454
  this._mediaQuery.addEventListener("change", this._onMediaChange);
@@ -5632,6 +5680,8 @@ module.exports = __toCommonJS(index_exports);
5632
5680
  touchState: null,
5633
5681
  // Feedback element
5634
5682
  feedbackElement: null,
5683
+ // Shared selector used by init and touch reorder
5684
+ containerSelector: ".vd-draggable-container, .vd-draggable-container-vertical",
5635
5685
  /**
5636
5686
  * Initialize draggable components
5637
5687
  */
@@ -5643,7 +5693,7 @@ module.exports = __toCommonJS(index_exports);
5643
5693
  }
5644
5694
  this.initDraggable(element);
5645
5695
  });
5646
- const containers = document.querySelectorAll(".vd-draggable-container, .vd-draggable-container-vertical");
5696
+ const containers = document.querySelectorAll(this.containerSelector);
5647
5697
  containers.forEach((container) => {
5648
5698
  if (!this.instances.has(container)) {
5649
5699
  this.initContainer(container);
@@ -5887,10 +5937,16 @@ module.exports = __toCommonJS(index_exports);
5887
5937
  */
5888
5938
  handleTouchStart: function(e, element) {
5889
5939
  const touch = e.touches[0];
5940
+ const rect = element.getBoundingClientRect();
5890
5941
  this.touchState = {
5891
5942
  element,
5892
5943
  startX: touch.clientX,
5893
5944
  startY: touch.clientY,
5945
+ lastX: touch.clientX,
5946
+ lastY: touch.clientY,
5947
+ // Keep preview anchored to the original grab point.
5948
+ offsetX: touch.clientX - rect.left,
5949
+ offsetY: touch.clientY - rect.top,
5894
5950
  startTime: Date.now(),
5895
5951
  isDragging: false
5896
5952
  };
@@ -5903,6 +5959,8 @@ module.exports = __toCommonJS(index_exports);
5903
5959
  handleTouchMove: function(e, element) {
5904
5960
  if (!this.touchState) return;
5905
5961
  const touch = e.touches[0];
5962
+ this.touchState.lastX = touch.clientX;
5963
+ this.touchState.lastY = touch.clientY;
5906
5964
  const deltaX = touch.clientX - this.touchState.startX;
5907
5965
  const deltaY = touch.clientY - this.touchState.startY;
5908
5966
  if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
@@ -5915,7 +5973,10 @@ module.exports = __toCommonJS(index_exports);
5915
5973
  element,
5916
5974
  initialPosition: { x: this.touchState.startX, y: this.touchState.startY },
5917
5975
  initialBounds: element.getBoundingClientRect(),
5918
- data: this.getData(element)
5976
+ data: this.getData(element),
5977
+ // Preserve where inside the element the drag started for accurate ghost positioning.
5978
+ offsetX: this.touchState.offsetX,
5979
+ offsetY: this.touchState.offsetY
5919
5980
  };
5920
5981
  element.dispatchEvent(new CustomEvent("draggable:start", {
5921
5982
  bubbles: true,
@@ -5937,7 +5998,8 @@ module.exports = __toCommonJS(index_exports);
5937
5998
  delta: { x: deltaX, y: deltaY }
5938
5999
  }
5939
6000
  }));
5940
- const container = element.closest(".vd-draggable-container");
6001
+ this.updateTouchDropZone(touch.clientX, touch.clientY);
6002
+ const container = element.closest(this.containerSelector);
5941
6003
  if (container && container.contains(element)) {
5942
6004
  this.handleReorder(container, element, touch.clientX, touch.clientY);
5943
6005
  }
@@ -5952,6 +6014,17 @@ module.exports = __toCommonJS(index_exports);
5952
6014
  handleTouchEnd: function(e, element) {
5953
6015
  if (this.touchState && this.touchState.isDragging) {
5954
6016
  if (e.cancelable) e.preventDefault();
6017
+ const endTouch = e.changedTouches?.[0];
6018
+ const endPosition = {
6019
+ x: endTouch?.clientX ?? this.touchState.lastX ?? this.touchState.startX,
6020
+ y: endTouch?.clientY ?? this.touchState.lastY ?? this.touchState.startY
6021
+ };
6022
+ const dropZone = this.resolveDropZoneAtPoint(endPosition.x, endPosition.y) || this.touchState.overZone;
6023
+ if (dropZone) {
6024
+ this.dispatchDrop(dropZone, endPosition);
6025
+ } else if (this.touchState.overZone) {
6026
+ this.touchState.overZone.classList.remove("is-drag-over");
6027
+ }
5955
6028
  element.classList.remove("is-dragging");
5956
6029
  element.classList.add("is-dropped");
5957
6030
  element.setAttribute("aria-grabbed", "false");
@@ -5959,7 +6032,6 @@ module.exports = __toCommonJS(index_exports);
5959
6032
  if (this.feedbackElement) {
5960
6033
  this.feedbackElement.classList.add("hidden");
5961
6034
  }
5962
- const endTouch = e.changedTouches[0];
5963
6035
  const data = this.currentDrag?.data || this.getData(element);
5964
6036
  const startX = this.touchState?.startX || 0;
5965
6037
  const startY = this.touchState?.startY || 0;
@@ -5968,10 +6040,10 @@ module.exports = __toCommonJS(index_exports);
5968
6040
  detail: {
5969
6041
  element,
5970
6042
  data,
5971
- position: { x: endTouch.clientX, y: endTouch.clientY },
6043
+ position: endPosition,
5972
6044
  delta: {
5973
- x: endTouch.clientX - startX,
5974
- y: endTouch.clientY - startY
6045
+ x: endPosition.x - startX,
6046
+ y: endPosition.y - startY
5975
6047
  }
5976
6048
  }
5977
6049
  }));
@@ -6012,6 +6084,58 @@ module.exports = __toCommonJS(index_exports);
6012
6084
  */
6013
6085
  handleDrop: function(e, zone) {
6014
6086
  e.preventDefault();
6087
+ this.dispatchDrop(zone, { x: e.clientX, y: e.clientY });
6088
+ },
6089
+ /**
6090
+ * Resolve a drop zone from viewport coordinates
6091
+ * @param {number} x
6092
+ * @param {number} y
6093
+ * @returns {HTMLElement|null}
6094
+ */
6095
+ resolveDropZoneAtPoint: function(x, y) {
6096
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
6097
+ if (typeof document.elementsFromPoint === "function") {
6098
+ const stacked = document.elementsFromPoint(x, y);
6099
+ for (const element of stacked) {
6100
+ const zone = element.closest(".vd-drop-zone");
6101
+ if (zone) return zone;
6102
+ }
6103
+ }
6104
+ const target = document.elementFromPoint(x, y);
6105
+ const targetZone = target ? target.closest(".vd-drop-zone") : null;
6106
+ if (targetZone) return targetZone;
6107
+ const zones = document.querySelectorAll(".vd-drop-zone");
6108
+ for (const zone of zones) {
6109
+ const rect = zone.getBoundingClientRect();
6110
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
6111
+ return zone;
6112
+ }
6113
+ }
6114
+ return null;
6115
+ },
6116
+ /**
6117
+ * Track and update active drop-zone hover state on touch devices
6118
+ * @param {number} x
6119
+ * @param {number} y
6120
+ */
6121
+ updateTouchDropZone: function(x, y) {
6122
+ if (!this.touchState) return;
6123
+ const nextZone = this.resolveDropZoneAtPoint(x, y);
6124
+ const prevZone = this.touchState.overZone || null;
6125
+ if (prevZone && prevZone !== nextZone) {
6126
+ prevZone.classList.remove("is-drag-over");
6127
+ }
6128
+ if (nextZone && nextZone !== prevZone) {
6129
+ nextZone.classList.add("is-drag-over");
6130
+ }
6131
+ this.touchState.overZone = nextZone || null;
6132
+ },
6133
+ /**
6134
+ * Dispatch a normalized drop event for mouse and touch flows
6135
+ * @param {HTMLElement} zone
6136
+ * @param {{x:number, y:number}} position
6137
+ */
6138
+ dispatchDrop: function(zone, position) {
6015
6139
  zone.classList.remove("is-drag-over");
6016
6140
  zone.dispatchEvent(new CustomEvent("draggable:drop", {
6017
6141
  bubbles: true,
@@ -6019,7 +6143,7 @@ module.exports = __toCommonJS(index_exports);
6019
6143
  zone,
6020
6144
  element: this.currentDrag?.element,
6021
6145
  data: this.currentDrag?.data,
6022
- position: { x: e.clientX, y: e.clientY }
6146
+ position
6023
6147
  }
6024
6148
  }));
6025
6149
  },
@@ -6120,9 +6244,11 @@ module.exports = __toCommonJS(index_exports);
6120
6244
  this.feedbackElement.innerHTML = "";
6121
6245
  const clone = this.currentDrag.element.cloneNode(true);
6122
6246
  this.feedbackElement.appendChild(clone);
6247
+ const offsetX = this.currentDrag.offsetX ?? 20;
6248
+ const offsetY = this.currentDrag.offsetY ?? 20;
6123
6249
  Object.assign(this.feedbackElement.style, {
6124
- left: x - 20 + "px",
6125
- top: y - 20 + "px",
6250
+ left: x - offsetX + "px",
6251
+ top: y - offsetY + "px",
6126
6252
  width: rect.width + "px",
6127
6253
  height: rect.height + "px"
6128
6254
  });
@@ -6410,6 +6536,67 @@ module.exports = __toCommonJS(index_exports);
6410
6536
  window.VanduoLazyLoad = VanduoLazyLoad;
6411
6537
  })();
6412
6538
 
6539
+ // js/components/glass.js
6540
+ (function() {
6541
+ "use strict";
6542
+ const GlassScroll = {
6543
+ /** @type {Map<Element, IntersectionObserver>} */
6544
+ observers: /* @__PURE__ */ new Map(),
6545
+ init: function() {
6546
+ document.querySelectorAll("[data-glass-scroll]").forEach((el) => {
6547
+ if (this.observers.has(el)) return;
6548
+ this.initElement(el);
6549
+ });
6550
+ },
6551
+ /**
6552
+ * Wire up a single scroll-activated glass element.
6553
+ * @param {HTMLElement} el
6554
+ */
6555
+ initElement: function(el) {
6556
+ const sentinelSelector = el.dataset.glassSentinel;
6557
+ let sentinel;
6558
+ if (sentinelSelector) {
6559
+ sentinel = document.querySelector(sentinelSelector);
6560
+ }
6561
+ if (!sentinel) {
6562
+ sentinel = el.previousElementSibling;
6563
+ }
6564
+ if (!sentinel) {
6565
+ el.classList.add("is-glass-active");
6566
+ return;
6567
+ }
6568
+ const observer = new IntersectionObserver(
6569
+ (entries) => {
6570
+ entries.forEach((entry) => {
6571
+ el.classList.toggle("is-glass-active", !entry.isIntersecting);
6572
+ });
6573
+ },
6574
+ { threshold: 0, rootMargin: "0px" }
6575
+ );
6576
+ observer.observe(sentinel);
6577
+ this.observers.set(el, observer);
6578
+ },
6579
+ /**
6580
+ * Disconnect and remove a single element's observer.
6581
+ * @param {HTMLElement} el
6582
+ */
6583
+ destroy: function(el) {
6584
+ const observer = this.observers.get(el);
6585
+ if (observer) {
6586
+ observer.disconnect();
6587
+ this.observers.delete(el);
6588
+ }
6589
+ },
6590
+ destroyAll: function() {
6591
+ this.observers.forEach((observer, el) => this.destroy(el));
6592
+ }
6593
+ };
6594
+ if (typeof window.Vanduo !== "undefined") {
6595
+ window.Vanduo.register("glassScroll", GlassScroll);
6596
+ }
6597
+ window.VanduoGlassScroll = GlassScroll;
6598
+ })();
6599
+
6413
6600
  // js/components/flow.js
6414
6601
  (function() {
6415
6602
  "use strict";