@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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Vanduo Framework v1.3.2
1
+ # Vanduo Framework v1.3.3
2
2
 
3
3
  <p align="center">
4
4
  <img src="vanduo-banner.svg" alt="Vanduo Framework Banner" width="100%">
@@ -38,6 +38,15 @@ A lightweight, pure HTML/CSS/JS framework with **46+ components** for designing
38
38
 
39
39
  ---
40
40
 
41
+ ## What's New in v1.3.3
42
+
43
+ v1.3.3 is a maintenance release with no breaking changes:
44
+
45
+ - **Theme default primary alignment.** `ThemeCustomizer` and `ThemeSwitcher` normalize stale `black`/`amber` primary pairs against `localStorage` and `prefers-color-scheme`, so light, dark, and system modes stay consistent with `data-primary` after reloads and OS theme changes.
46
+ - **Documentation site.** Theme customizer demo storage keys align with framework (`vanduo-radius`, `vanduo-font-preference`); changelog and CDN pins updated to v1.3.3.
47
+
48
+ ---
49
+
41
50
  ## What's New in v1.3.2
42
51
 
43
52
  v1.3.2 is a component release centered on audio playback, with no breaking changes:
@@ -89,8 +98,8 @@ The quickest way to get started — no install, no build step. Add two lines to
89
98
 
90
99
  **Pin to a specific version** for production:
91
100
  ```html
92
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.3.2/dist/vanduo.min.css">
93
- <script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.3.2/dist/vanduo.min.js"></script>
101
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.3.3/dist/vanduo.min.css">
102
+ <script src="https://cdn.jsdelivr.net/gh/vanduo-oss/framework@v1.3.3/dist/vanduo.min.js"></script>
94
103
  <script>Vanduo.init();</script>
95
104
  ```
96
105
 
@@ -153,7 +162,7 @@ This project includes an [`llms.txt`](llms.txt) file — a structured markdown s
153
162
  Use the hardened upload script to attach only approved bundle artifacts from `dist/`:
154
163
 
155
164
  ```bash
156
- pnpm run release:assets -- v1.3.2
165
+ pnpm run release:assets -- v1.3.3
157
166
  ```
158
167
 
159
168
  Notes:
@@ -187,6 +187,8 @@
187
187
  border: 1px solid var(--draggable-border-color);
188
188
  border-radius: var(--btn-border-radius);
189
189
  cursor: grab;
190
+ user-select: none;
191
+ touch-action: none;
190
192
  transition: var(--draggable-transition);
191
193
  }
192
194
 
@@ -224,7 +226,7 @@
224
226
  pointer-events: none;
225
227
  z-index: 9999;
226
228
  opacity: 0.9;
227
- transform: scale(0.8);
229
+ transform: none;
228
230
  transition: opacity 0.2s ease;
229
231
  }
230
232
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.3.2",
3
- "builtAt": "2026-04-06T19:04:41.601Z",
4
- "commit": "8e08b38",
2
+ "version": "1.3.3",
3
+ "builtAt": "2026-04-10T21:45:12.664Z",
4
+ "commit": "281f4f6",
5
5
  "mode": "development+production"
6
6
  }
@@ -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
  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.3" : "0.0.0-dev";
136
136
  const Vanduo2 = {
137
137
  version: VANDUO_VERSION,
138
138
  components: {},
@@ -3831,6 +3831,7 @@ module.exports = __toCommonJS(index_exports);
3831
3831
  loadPreferences: function() {
3832
3832
  this.state.theme = this.getStorageValue(this.STORAGE_KEYS.THEME, this.DEFAULTS.THEME);
3833
3833
  this.state.primary = this.getStorageValue(this.STORAGE_KEYS.PRIMARY, this.getDefaultPrimary(this.state.theme));
3834
+ this._normalizeDefaultPrimaryIfStaleWithStoredTheme();
3834
3835
  this.state.neutral = this.getStorageValue(this.STORAGE_KEYS.NEUTRAL, this.DEFAULTS.NEUTRAL);
3835
3836
  this.state.radius = this.getStorageValue(this.STORAGE_KEYS.RADIUS, this.DEFAULTS.RADIUS);
3836
3837
  this.state.font = this.getStorageValue(this.STORAGE_KEYS.FONT, this.DEFAULTS.FONT);
@@ -3916,12 +3917,10 @@ module.exports = __toCommonJS(index_exports);
3916
3917
  mode = this.DEFAULTS.THEME;
3917
3918
  }
3918
3919
  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);
3920
+ if (this.isUsingDefaultPrimary()) {
3921
+ const expected = this.getDefaultPrimary(mode);
3922
+ if (this.state.primary !== expected) {
3923
+ this.applyPrimary(expected);
3925
3924
  }
3926
3925
  }
3927
3926
  this.state.theme = mode;
@@ -4163,6 +4162,20 @@ module.exports = __toCommonJS(index_exports);
4163
4162
  isUsingDefaultPrimary: function() {
4164
4163
  return this.state.primary === this.DEFAULTS.PRIMARY_LIGHT || this.state.primary === this.DEFAULTS.PRIMARY_DARK;
4165
4164
  },
4165
+ /**
4166
+ * When primary is still one of the auto-default palette keys (black/amber) but
4167
+ * localStorage was written under a different theme (or OS changed in system mode),
4168
+ * align in-memory state before applyAllPreferences runs — avoids amber+light / black+dark drift.
4169
+ */
4170
+ _normalizeDefaultPrimaryIfStaleWithStoredTheme: function() {
4171
+ if (!this.isUsingDefaultPrimary()) {
4172
+ return;
4173
+ }
4174
+ const expected = this.getDefaultPrimary(this.state.theme);
4175
+ if (this.state.primary !== expected) {
4176
+ this.state.primary = expected;
4177
+ }
4178
+ },
4166
4179
  bindEvents: function() {
4167
4180
  if (this.elements.trigger) {
4168
4181
  this.addListener(this.elements.trigger, "click", (e) => {
@@ -4401,6 +4414,9 @@ module.exports = __toCommonJS(index_exports);
4401
4414
  this._onMediaChange = (_e) => {
4402
4415
  if (this.state.preference === "system") {
4403
4416
  this.applyTheme();
4417
+ if (window.ThemeCustomizer && typeof window.ThemeCustomizer.applyTheme === "function" && !window.ThemeCustomizer._isApplying) {
4418
+ window.ThemeCustomizer.applyTheme("system");
4419
+ }
4404
4420
  }
4405
4421
  };
4406
4422
  this._mediaQuery.addEventListener("change", this._onMediaChange);
@@ -5632,6 +5648,8 @@ module.exports = __toCommonJS(index_exports);
5632
5648
  touchState: null,
5633
5649
  // Feedback element
5634
5650
  feedbackElement: null,
5651
+ // Shared selector used by init and touch reorder
5652
+ containerSelector: ".vd-draggable-container, .vd-draggable-container-vertical",
5635
5653
  /**
5636
5654
  * Initialize draggable components
5637
5655
  */
@@ -5643,7 +5661,7 @@ module.exports = __toCommonJS(index_exports);
5643
5661
  }
5644
5662
  this.initDraggable(element);
5645
5663
  });
5646
- const containers = document.querySelectorAll(".vd-draggable-container, .vd-draggable-container-vertical");
5664
+ const containers = document.querySelectorAll(this.containerSelector);
5647
5665
  containers.forEach((container) => {
5648
5666
  if (!this.instances.has(container)) {
5649
5667
  this.initContainer(container);
@@ -5887,10 +5905,16 @@ module.exports = __toCommonJS(index_exports);
5887
5905
  */
5888
5906
  handleTouchStart: function(e, element) {
5889
5907
  const touch = e.touches[0];
5908
+ const rect = element.getBoundingClientRect();
5890
5909
  this.touchState = {
5891
5910
  element,
5892
5911
  startX: touch.clientX,
5893
5912
  startY: touch.clientY,
5913
+ lastX: touch.clientX,
5914
+ lastY: touch.clientY,
5915
+ // Keep preview anchored to the original grab point.
5916
+ offsetX: touch.clientX - rect.left,
5917
+ offsetY: touch.clientY - rect.top,
5894
5918
  startTime: Date.now(),
5895
5919
  isDragging: false
5896
5920
  };
@@ -5903,6 +5927,8 @@ module.exports = __toCommonJS(index_exports);
5903
5927
  handleTouchMove: function(e, element) {
5904
5928
  if (!this.touchState) return;
5905
5929
  const touch = e.touches[0];
5930
+ this.touchState.lastX = touch.clientX;
5931
+ this.touchState.lastY = touch.clientY;
5906
5932
  const deltaX = touch.clientX - this.touchState.startX;
5907
5933
  const deltaY = touch.clientY - this.touchState.startY;
5908
5934
  if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
@@ -5915,7 +5941,10 @@ module.exports = __toCommonJS(index_exports);
5915
5941
  element,
5916
5942
  initialPosition: { x: this.touchState.startX, y: this.touchState.startY },
5917
5943
  initialBounds: element.getBoundingClientRect(),
5918
- data: this.getData(element)
5944
+ data: this.getData(element),
5945
+ // Preserve where inside the element the drag started for accurate ghost positioning.
5946
+ offsetX: this.touchState.offsetX,
5947
+ offsetY: this.touchState.offsetY
5919
5948
  };
5920
5949
  element.dispatchEvent(new CustomEvent("draggable:start", {
5921
5950
  bubbles: true,
@@ -5937,7 +5966,8 @@ module.exports = __toCommonJS(index_exports);
5937
5966
  delta: { x: deltaX, y: deltaY }
5938
5967
  }
5939
5968
  }));
5940
- const container = element.closest(".vd-draggable-container");
5969
+ this.updateTouchDropZone(touch.clientX, touch.clientY);
5970
+ const container = element.closest(this.containerSelector);
5941
5971
  if (container && container.contains(element)) {
5942
5972
  this.handleReorder(container, element, touch.clientX, touch.clientY);
5943
5973
  }
@@ -5952,6 +5982,17 @@ module.exports = __toCommonJS(index_exports);
5952
5982
  handleTouchEnd: function(e, element) {
5953
5983
  if (this.touchState && this.touchState.isDragging) {
5954
5984
  if (e.cancelable) e.preventDefault();
5985
+ const endTouch = e.changedTouches?.[0];
5986
+ const endPosition = {
5987
+ x: endTouch?.clientX ?? this.touchState.lastX ?? this.touchState.startX,
5988
+ y: endTouch?.clientY ?? this.touchState.lastY ?? this.touchState.startY
5989
+ };
5990
+ const dropZone = this.resolveDropZoneAtPoint(endPosition.x, endPosition.y) || this.touchState.overZone;
5991
+ if (dropZone) {
5992
+ this.dispatchDrop(dropZone, endPosition);
5993
+ } else if (this.touchState.overZone) {
5994
+ this.touchState.overZone.classList.remove("is-drag-over");
5995
+ }
5955
5996
  element.classList.remove("is-dragging");
5956
5997
  element.classList.add("is-dropped");
5957
5998
  element.setAttribute("aria-grabbed", "false");
@@ -5959,7 +6000,6 @@ module.exports = __toCommonJS(index_exports);
5959
6000
  if (this.feedbackElement) {
5960
6001
  this.feedbackElement.classList.add("hidden");
5961
6002
  }
5962
- const endTouch = e.changedTouches[0];
5963
6003
  const data = this.currentDrag?.data || this.getData(element);
5964
6004
  const startX = this.touchState?.startX || 0;
5965
6005
  const startY = this.touchState?.startY || 0;
@@ -5968,10 +6008,10 @@ module.exports = __toCommonJS(index_exports);
5968
6008
  detail: {
5969
6009
  element,
5970
6010
  data,
5971
- position: { x: endTouch.clientX, y: endTouch.clientY },
6011
+ position: endPosition,
5972
6012
  delta: {
5973
- x: endTouch.clientX - startX,
5974
- y: endTouch.clientY - startY
6013
+ x: endPosition.x - startX,
6014
+ y: endPosition.y - startY
5975
6015
  }
5976
6016
  }
5977
6017
  }));
@@ -6012,6 +6052,58 @@ module.exports = __toCommonJS(index_exports);
6012
6052
  */
6013
6053
  handleDrop: function(e, zone) {
6014
6054
  e.preventDefault();
6055
+ this.dispatchDrop(zone, { x: e.clientX, y: e.clientY });
6056
+ },
6057
+ /**
6058
+ * Resolve a drop zone from viewport coordinates
6059
+ * @param {number} x
6060
+ * @param {number} y
6061
+ * @returns {HTMLElement|null}
6062
+ */
6063
+ resolveDropZoneAtPoint: function(x, y) {
6064
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
6065
+ if (typeof document.elementsFromPoint === "function") {
6066
+ const stacked = document.elementsFromPoint(x, y);
6067
+ for (const element of stacked) {
6068
+ const zone = element.closest(".vd-drop-zone");
6069
+ if (zone) return zone;
6070
+ }
6071
+ }
6072
+ const target = document.elementFromPoint(x, y);
6073
+ const targetZone = target ? target.closest(".vd-drop-zone") : null;
6074
+ if (targetZone) return targetZone;
6075
+ const zones = document.querySelectorAll(".vd-drop-zone");
6076
+ for (const zone of zones) {
6077
+ const rect = zone.getBoundingClientRect();
6078
+ if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
6079
+ return zone;
6080
+ }
6081
+ }
6082
+ return null;
6083
+ },
6084
+ /**
6085
+ * Track and update active drop-zone hover state on touch devices
6086
+ * @param {number} x
6087
+ * @param {number} y
6088
+ */
6089
+ updateTouchDropZone: function(x, y) {
6090
+ if (!this.touchState) return;
6091
+ const nextZone = this.resolveDropZoneAtPoint(x, y);
6092
+ const prevZone = this.touchState.overZone || null;
6093
+ if (prevZone && prevZone !== nextZone) {
6094
+ prevZone.classList.remove("is-drag-over");
6095
+ }
6096
+ if (nextZone && nextZone !== prevZone) {
6097
+ nextZone.classList.add("is-drag-over");
6098
+ }
6099
+ this.touchState.overZone = nextZone || null;
6100
+ },
6101
+ /**
6102
+ * Dispatch a normalized drop event for mouse and touch flows
6103
+ * @param {HTMLElement} zone
6104
+ * @param {{x:number, y:number}} position
6105
+ */
6106
+ dispatchDrop: function(zone, position) {
6015
6107
  zone.classList.remove("is-drag-over");
6016
6108
  zone.dispatchEvent(new CustomEvent("draggable:drop", {
6017
6109
  bubbles: true,
@@ -6019,7 +6111,7 @@ module.exports = __toCommonJS(index_exports);
6019
6111
  zone,
6020
6112
  element: this.currentDrag?.element,
6021
6113
  data: this.currentDrag?.data,
6022
- position: { x: e.clientX, y: e.clientY }
6114
+ position
6023
6115
  }
6024
6116
  }));
6025
6117
  },
@@ -6120,9 +6212,11 @@ module.exports = __toCommonJS(index_exports);
6120
6212
  this.feedbackElement.innerHTML = "";
6121
6213
  const clone = this.currentDrag.element.cloneNode(true);
6122
6214
  this.feedbackElement.appendChild(clone);
6215
+ const offsetX = this.currentDrag.offsetX ?? 20;
6216
+ const offsetY = this.currentDrag.offsetY ?? 20;
6123
6217
  Object.assign(this.feedbackElement.style, {
6124
- left: x - 20 + "px",
6125
- top: y - 20 + "px",
6218
+ left: x - offsetX + "px",
6219
+ top: y - offsetY + "px",
6126
6220
  width: rect.width + "px",
6127
6221
  height: rect.height + "px"
6128
6222
  });