@vanduo-oss/framework 1.3.2 → 1.3.3

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.
package/dist/vanduo.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! Vanduo v1.3.2 | Built: 2026-04-06T19:04:41.601Z | git:8e08b38 | development */
1
+ /*! Vanduo v1.3.3 | Built: 2026-04-10T21:45:12.664Z | git:281f4f6 | development */
2
2
  (() => {
3
3
  // js/utils/lifecycle.js
4
4
  (function() {
@@ -107,7 +107,7 @@
107
107
  // js/vanduo.js
108
108
  (function() {
109
109
  "use strict";
110
- const VANDUO_VERSION = true ? "1.3.2" : "0.0.0-dev";
110
+ const VANDUO_VERSION = true ? "1.3.3" : "0.0.0-dev";
111
111
  const Vanduo2 = {
112
112
  version: VANDUO_VERSION,
113
113
  components: {},
@@ -3806,6 +3806,7 @@
3806
3806
  loadPreferences: function() {
3807
3807
  this.state.theme = this.getStorageValue(this.STORAGE_KEYS.THEME, this.DEFAULTS.THEME);
3808
3808
  this.state.primary = this.getStorageValue(this.STORAGE_KEYS.PRIMARY, this.getDefaultPrimary(this.state.theme));
3809
+ this._normalizeDefaultPrimaryIfStaleWithStoredTheme();
3809
3810
  this.state.neutral = this.getStorageValue(this.STORAGE_KEYS.NEUTRAL, this.DEFAULTS.NEUTRAL);
3810
3811
  this.state.radius = this.getStorageValue(this.STORAGE_KEYS.RADIUS, this.DEFAULTS.RADIUS);
3811
3812
  this.state.font = this.getStorageValue(this.STORAGE_KEYS.FONT, this.DEFAULTS.FONT);
@@ -3891,12 +3892,10 @@
3891
3892
  mode = this.DEFAULTS.THEME;
3892
3893
  }
3893
3894
  this._isApplying = true;
3894
- const currentMode = this.state.theme;
3895
- const oldDefault = this.getDefaultPrimary(currentMode);
3896
- if (this.state.primary === oldDefault) {
3897
- const newDefault = this.getDefaultPrimary(mode);
3898
- if (newDefault !== this.state.primary) {
3899
- this.applyPrimary(newDefault);
3895
+ if (this.isUsingDefaultPrimary()) {
3896
+ const expected = this.getDefaultPrimary(mode);
3897
+ if (this.state.primary !== expected) {
3898
+ this.applyPrimary(expected);
3900
3899
  }
3901
3900
  }
3902
3901
  this.state.theme = mode;
@@ -4138,6 +4137,20 @@
4138
4137
  isUsingDefaultPrimary: function() {
4139
4138
  return this.state.primary === this.DEFAULTS.PRIMARY_LIGHT || this.state.primary === this.DEFAULTS.PRIMARY_DARK;
4140
4139
  },
4140
+ /**
4141
+ * When primary is still one of the auto-default palette keys (black/amber) but
4142
+ * localStorage was written under a different theme (or OS changed in system mode),
4143
+ * align in-memory state before applyAllPreferences runs — avoids amber+light / black+dark drift.
4144
+ */
4145
+ _normalizeDefaultPrimaryIfStaleWithStoredTheme: function() {
4146
+ if (!this.isUsingDefaultPrimary()) {
4147
+ return;
4148
+ }
4149
+ const expected = this.getDefaultPrimary(this.state.theme);
4150
+ if (this.state.primary !== expected) {
4151
+ this.state.primary = expected;
4152
+ }
4153
+ },
4141
4154
  bindEvents: function() {
4142
4155
  if (this.elements.trigger) {
4143
4156
  this.addListener(this.elements.trigger, "click", (e) => {
@@ -4376,6 +4389,9 @@
4376
4389
  this._onMediaChange = (_e) => {
4377
4390
  if (this.state.preference === "system") {
4378
4391
  this.applyTheme();
4392
+ if (window.ThemeCustomizer && typeof window.ThemeCustomizer.applyTheme === "function" && !window.ThemeCustomizer._isApplying) {
4393
+ window.ThemeCustomizer.applyTheme("system");
4394
+ }
4379
4395
  }
4380
4396
  };
4381
4397
  this._mediaQuery.addEventListener("change", this._onMediaChange);
@@ -5607,6 +5623,8 @@
5607
5623
  touchState: null,
5608
5624
  // Feedback element
5609
5625
  feedbackElement: null,
5626
+ // Shared selector used by init and touch reorder
5627
+ containerSelector: ".vd-draggable-container, .vd-draggable-container-vertical",
5610
5628
  /**
5611
5629
  * Initialize draggable components
5612
5630
  */
@@ -5618,7 +5636,7 @@
5618
5636
  }
5619
5637
  this.initDraggable(element);
5620
5638
  });
5621
- const containers = document.querySelectorAll(".vd-draggable-container, .vd-draggable-container-vertical");
5639
+ const containers = document.querySelectorAll(this.containerSelector);
5622
5640
  containers.forEach((container) => {
5623
5641
  if (!this.instances.has(container)) {
5624
5642
  this.initContainer(container);
@@ -5862,10 +5880,16 @@
5862
5880
  */
5863
5881
  handleTouchStart: function(e, element) {
5864
5882
  const touch = e.touches[0];
5883
+ const rect = element.getBoundingClientRect();
5865
5884
  this.touchState = {
5866
5885
  element,
5867
5886
  startX: touch.clientX,
5868
5887
  startY: touch.clientY,
5888
+ lastX: touch.clientX,
5889
+ lastY: touch.clientY,
5890
+ // Keep preview anchored to the original grab point.
5891
+ offsetX: touch.clientX - rect.left,
5892
+ offsetY: touch.clientY - rect.top,
5869
5893
  startTime: Date.now(),
5870
5894
  isDragging: false
5871
5895
  };
@@ -5878,6 +5902,8 @@
5878
5902
  handleTouchMove: function(e, element) {
5879
5903
  if (!this.touchState) return;
5880
5904
  const touch = e.touches[0];
5905
+ this.touchState.lastX = touch.clientX;
5906
+ this.touchState.lastY = touch.clientY;
5881
5907
  const deltaX = touch.clientX - this.touchState.startX;
5882
5908
  const deltaY = touch.clientY - this.touchState.startY;
5883
5909
  if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
@@ -5890,7 +5916,10 @@
5890
5916
  element,
5891
5917
  initialPosition: { x: this.touchState.startX, y: this.touchState.startY },
5892
5918
  initialBounds: element.getBoundingClientRect(),
5893
- data: this.getData(element)
5919
+ data: this.getData(element),
5920
+ // Preserve where inside the element the drag started for accurate ghost positioning.
5921
+ offsetX: this.touchState.offsetX,
5922
+ offsetY: this.touchState.offsetY
5894
5923
  };
5895
5924
  element.dispatchEvent(new CustomEvent("draggable:start", {
5896
5925
  bubbles: true,
@@ -5912,7 +5941,8 @@
5912
5941
  delta: { x: deltaX, y: deltaY }
5913
5942
  }
5914
5943
  }));
5915
- const container = element.closest(".vd-draggable-container");
5944
+ this.updateTouchDropZone(touch.clientX, touch.clientY);
5945
+ const container = element.closest(this.containerSelector);
5916
5946
  if (container && container.contains(element)) {
5917
5947
  this.handleReorder(container, element, touch.clientX, touch.clientY);
5918
5948
  }
@@ -5927,6 +5957,17 @@
5927
5957
  handleTouchEnd: function(e, element) {
5928
5958
  if (this.touchState && this.touchState.isDragging) {
5929
5959
  if (e.cancelable) e.preventDefault();
5960
+ const endTouch = e.changedTouches?.[0];
5961
+ const endPosition = {
5962
+ x: endTouch?.clientX ?? this.touchState.lastX ?? this.touchState.startX,
5963
+ y: endTouch?.clientY ?? this.touchState.lastY ?? this.touchState.startY
5964
+ };
5965
+ const dropZone = this.resolveDropZoneAtPoint(endPosition.x, endPosition.y) || this.touchState.overZone;
5966
+ if (dropZone) {
5967
+ this.dispatchDrop(dropZone, endPosition);
5968
+ } else if (this.touchState.overZone) {
5969
+ this.touchState.overZone.classList.remove("is-drag-over");
5970
+ }
5930
5971
  element.classList.remove("is-dragging");
5931
5972
  element.classList.add("is-dropped");
5932
5973
  element.setAttribute("aria-grabbed", "false");
@@ -5934,7 +5975,6 @@
5934
5975
  if (this.feedbackElement) {
5935
5976
  this.feedbackElement.classList.add("hidden");
5936
5977
  }
5937
- const endTouch = e.changedTouches[0];
5938
5978
  const data = this.currentDrag?.data || this.getData(element);
5939
5979
  const startX = this.touchState?.startX || 0;
5940
5980
  const startY = this.touchState?.startY || 0;
@@ -5943,10 +5983,10 @@
5943
5983
  detail: {
5944
5984
  element,
5945
5985
  data,
5946
- position: { x: endTouch.clientX, y: endTouch.clientY },
5986
+ position: endPosition,
5947
5987
  delta: {
5948
- x: endTouch.clientX - startX,
5949
- y: endTouch.clientY - startY
5988
+ x: endPosition.x - startX,
5989
+ y: endPosition.y - startY
5950
5990
  }
5951
5991
  }
5952
5992
  }));
@@ -5987,6 +6027,58 @@
5987
6027
  */
5988
6028
  handleDrop: function(e, zone) {
5989
6029
  e.preventDefault();
6030
+ this.dispatchDrop(zone, { x: e.clientX, y: e.clientY });
6031
+ },
6032
+ /**
6033
+ * Resolve a drop zone from viewport coordinates
6034
+ * @param {number} x
6035
+ * @param {number} y
6036
+ * @returns {HTMLElement|null}
6037
+ */
6038
+ resolveDropZoneAtPoint: function(x, y) {
6039
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
6040
+ if (typeof document.elementsFromPoint === "function") {
6041
+ const stacked = document.elementsFromPoint(x, y);
6042
+ for (const element of stacked) {
6043
+ const zone = element.closest(".vd-drop-zone");
6044
+ if (zone) return zone;
6045
+ }
6046
+ }
6047
+ const target = document.elementFromPoint(x, y);
6048
+ const targetZone = target ? target.closest(".vd-drop-zone") : null;
6049
+ if (targetZone) return targetZone;
6050
+ const zones = document.querySelectorAll(".vd-drop-zone");
6051
+ for (const zone of zones) {
6052
+ const rect = zone.getBoundingClientRect();
6053
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
6054
+ return zone;
6055
+ }
6056
+ }
6057
+ return null;
6058
+ },
6059
+ /**
6060
+ * Track and update active drop-zone hover state on touch devices
6061
+ * @param {number} x
6062
+ * @param {number} y
6063
+ */
6064
+ updateTouchDropZone: function(x, y) {
6065
+ if (!this.touchState) return;
6066
+ const nextZone = this.resolveDropZoneAtPoint(x, y);
6067
+ const prevZone = this.touchState.overZone || null;
6068
+ if (prevZone && prevZone !== nextZone) {
6069
+ prevZone.classList.remove("is-drag-over");
6070
+ }
6071
+ if (nextZone && nextZone !== prevZone) {
6072
+ nextZone.classList.add("is-drag-over");
6073
+ }
6074
+ this.touchState.overZone = nextZone || null;
6075
+ },
6076
+ /**
6077
+ * Dispatch a normalized drop event for mouse and touch flows
6078
+ * @param {HTMLElement} zone
6079
+ * @param {{x:number, y:number}} position
6080
+ */
6081
+ dispatchDrop: function(zone, position) {
5990
6082
  zone.classList.remove("is-drag-over");
5991
6083
  zone.dispatchEvent(new CustomEvent("draggable:drop", {
5992
6084
  bubbles: true,
@@ -5994,7 +6086,7 @@
5994
6086
  zone,
5995
6087
  element: this.currentDrag?.element,
5996
6088
  data: this.currentDrag?.data,
5997
- position: { x: e.clientX, y: e.clientY }
6089
+ position
5998
6090
  }
5999
6091
  }));
6000
6092
  },
@@ -6095,9 +6187,11 @@
6095
6187
  this.feedbackElement.innerHTML = "";
6096
6188
  const clone = this.currentDrag.element.cloneNode(true);
6097
6189
  this.feedbackElement.appendChild(clone);
6190
+ const offsetX = this.currentDrag.offsetX ?? 20;
6191
+ const offsetY = this.currentDrag.offsetY ?? 20;
6098
6192
  Object.assign(this.feedbackElement.style, {
6099
- left: x - 20 + "px",
6100
- top: y - 20 + "px",
6193
+ left: x - offsetX + "px",
6194
+ top: y - offsetY + "px",
6101
6195
  width: rect.width + "px",
6102
6196
  height: rect.height + "px"
6103
6197
  });