@vanduo-oss/framework 1.3.9 → 1.4.1

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 (124) hide show
  1. package/README.md +89 -42
  2. package/css/components/affix.css +12 -12
  3. package/css/components/alerts.css +70 -70
  4. package/css/components/avatar.css +78 -78
  5. package/css/components/badges.css +67 -67
  6. package/css/components/breadcrumbs.css +32 -32
  7. package/css/components/bubble.css +40 -40
  8. package/css/components/button-group.css +2 -2
  9. package/css/components/buttons.css +157 -157
  10. package/css/components/cards.css +79 -79
  11. package/css/components/chips.css +50 -50
  12. package/css/components/code-snippet.css +132 -132
  13. package/css/components/collapsible.css +67 -67
  14. package/css/components/collections.css +68 -68
  15. package/css/components/datepicker.css +54 -54
  16. package/css/components/doc-search.css +96 -103
  17. package/css/components/doc-tabs.css +11 -11
  18. package/css/components/draggable.css +77 -77
  19. package/css/components/dropdown.css +50 -50
  20. package/css/components/expanding-cards.css +1 -1
  21. package/css/components/fab.css +61 -61
  22. package/css/components/flow.css +55 -55
  23. package/css/components/footer.css +62 -62
  24. package/css/components/forms.css +437 -425
  25. package/css/components/image-box.css +50 -54
  26. package/css/components/modals.css +51 -51
  27. package/css/components/music-player.css +150 -150
  28. package/css/components/navbar.css +80 -80
  29. package/css/components/pagination.css +51 -51
  30. package/css/components/preloader.css +19 -19
  31. package/css/components/progress.css +20 -20
  32. package/css/components/rating.css +19 -19
  33. package/css/components/ripple.css +10 -10
  34. package/css/components/sidenav.css +72 -72
  35. package/css/components/skeleton.css +17 -16
  36. package/css/components/spinner.css +33 -33
  37. package/css/components/spotlight.css +33 -33
  38. package/css/components/stepper.css +39 -39
  39. package/css/components/suggest.css +37 -37
  40. package/css/components/tabs.css +60 -60
  41. package/css/components/theme-customizer.css +154 -154
  42. package/css/components/timeline.css +50 -50
  43. package/css/components/timepicker.css +29 -29
  44. package/css/components/toast.css +53 -53
  45. package/css/components/tooltips.css +78 -78
  46. package/css/components/transfer.css +37 -37
  47. package/css/components/tree.css +28 -28
  48. package/css/components/waypoint.css +12 -12
  49. package/css/core/colors.css +640 -640
  50. package/css/core/grid.css +127 -132
  51. package/css/core/helpers.css +349 -349
  52. package/css/core/tokens.css +133 -67
  53. package/css/core/typography.css +105 -103
  54. package/css/effects/morph.css +21 -21
  55. package/css/effects/parallax.css +6 -6
  56. package/css/utilities/color-utilities.css +273 -273
  57. package/css/utilities/media.css +4 -4
  58. package/css/utilities/shadow.css +75 -75
  59. package/css/utilities/table.css +64 -64
  60. package/css/utilities/transitions.css +53 -41
  61. package/css/vanduo.css +14 -35
  62. package/dist/build-info.json +3 -3
  63. package/dist/vanduo.cjs.js +947 -307
  64. package/dist/vanduo.cjs.js.map +3 -3
  65. package/dist/vanduo.cjs.min.js +7 -7
  66. package/dist/vanduo.cjs.min.js.map +3 -3
  67. package/dist/vanduo.css +9650 -9656
  68. package/dist/vanduo.css.map +1 -1
  69. package/dist/vanduo.esm.js +947 -307
  70. package/dist/vanduo.esm.js.map +3 -3
  71. package/dist/vanduo.esm.min.js +7 -7
  72. package/dist/vanduo.esm.min.js.map +3 -3
  73. package/dist/vanduo.js +947 -307
  74. package/dist/vanduo.js.map +3 -3
  75. package/dist/vanduo.min.css +2 -2
  76. package/dist/vanduo.min.css.map +1 -1
  77. package/dist/vanduo.min.js +7 -7
  78. package/dist/vanduo.min.js.map +3 -3
  79. package/js/components/affix.js +4 -4
  80. package/js/components/bubble.js +3 -3
  81. package/js/components/code-snippet.js +129 -5
  82. package/js/components/collapsible.js +2 -3
  83. package/js/components/datepicker.js +2 -2
  84. package/js/components/doc-search.js +69 -11
  85. package/js/components/draggable.js +4 -4
  86. package/js/components/dropdown.js +2 -3
  87. package/js/components/expanding-cards.js +2 -2
  88. package/js/components/flow.js +2 -2
  89. package/js/components/font-switcher.js +23 -13
  90. package/js/components/glass.js +2 -2
  91. package/js/components/grid.js +19 -8
  92. package/js/components/image-box.js +51 -12
  93. package/js/components/lazy-load.js +81 -9
  94. package/js/components/modals.js +28 -12
  95. package/js/components/morph.js +3 -3
  96. package/js/components/music-player.js +13 -13
  97. package/js/components/navbar.js +3 -3
  98. package/js/components/pagination.js +2 -3
  99. package/js/components/parallax.js +9 -10
  100. package/js/components/preloader.js +15 -6
  101. package/js/components/rating.js +2 -2
  102. package/js/components/ripple.js +2 -2
  103. package/js/components/select.js +2 -3
  104. package/js/components/sidenav.js +43 -14
  105. package/js/components/spotlight.js +2 -2
  106. package/js/components/stepper.js +2 -2
  107. package/js/components/suggest.js +9 -3
  108. package/js/components/tabs.js +2 -2
  109. package/js/components/theme-customizer.js +155 -25
  110. package/js/components/theme-switcher.js +27 -16
  111. package/js/components/timeline.js +41 -12
  112. package/js/components/timepicker.js +2 -2
  113. package/js/components/toast.js +1 -1
  114. package/js/components/tooltips.js +4 -4
  115. package/js/components/transfer.js +2 -2
  116. package/js/components/tree.js +2 -2
  117. package/js/components/validate.js +2 -2
  118. package/js/components/vd-hex.js +10 -6
  119. package/js/components/waypoint.js +2 -2
  120. package/js/utils/helpers.js +7 -4
  121. package/js/utils/lifecycle.js +158 -83
  122. package/js/vanduo.js +203 -34
  123. package/package.json +2 -1
  124. package/css/core/vd-aliases.css +0 -60
@@ -13,9 +13,11 @@
13
13
  sidenavs: new Map(),
14
14
  breakpoint: 992, // Desktop breakpoint
15
15
  restoreDelayMs: 450,
16
+ __vanduoScopedDestroyAll: true,
16
17
 
17
18
  // Global cleanup functions (toggles, resize)
18
19
  _globalCleanups: [],
20
+ _resizeCleanup: null,
19
21
 
20
22
  isFixedVariant: function(sidenav) {
21
23
  return sidenav.classList.contains('vd-sidenav-fixed') || sidenav.classList.contains('sidenav-fixed');
@@ -140,8 +142,8 @@
140
142
  /**
141
143
  * Initialize sidenav components
142
144
  */
143
- init: function() {
144
- const sidenavs = document.querySelectorAll('.vd-sidenav, .vd-offcanvas');
145
+ init: function(root) {
146
+ const sidenavs = window.Vanduo.queryAll(root, '.vd-sidenav, .vd-offcanvas');
145
147
 
146
148
  sidenavs.forEach(sidenav => {
147
149
  if (this.sidenavs.has(sidenav)) {
@@ -151,7 +153,9 @@
151
153
  });
152
154
 
153
155
  // Handle toggle buttons
154
- const toggles = document.querySelectorAll('[data-sidenav-toggle]');
156
+ const toggles = window.Vanduo && typeof window.Vanduo.queryAll === 'function'
157
+ ? window.Vanduo.queryAll(root, '[data-sidenav-toggle]')
158
+ : document.querySelectorAll('[data-sidenav-toggle]');
155
159
  toggles.forEach(toggle => {
156
160
  if (toggle.dataset.sidenavToggleInitialized) return;
157
161
  toggle.dataset.sidenavToggleInitialized = 'true';
@@ -165,16 +169,18 @@
165
169
  }
166
170
  };
167
171
  toggle.addEventListener('click', toggleClickHandler);
168
- this._globalCleanups.push(() => toggle.removeEventListener('click', toggleClickHandler));
172
+ toggle._sidenavToggleCleanup = () => toggle.removeEventListener('click', toggleClickHandler);
169
173
  });
170
174
 
171
175
  // Handle responsive behavior
172
176
  this.handleResize();
173
- const resizeHandler = () => {
174
- this.handleResize();
175
- };
176
- window.addEventListener('resize', resizeHandler);
177
- this._globalCleanups.push(() => window.removeEventListener('resize', resizeHandler));
177
+ if (!this._resizeCleanup) {
178
+ const resizeHandler = () => {
179
+ this.handleResize();
180
+ };
181
+ window.addEventListener('resize', resizeHandler);
182
+ this._resizeCleanup = () => window.removeEventListener('resize', resizeHandler);
183
+ }
178
184
  },
179
185
 
180
186
  /**
@@ -406,12 +412,36 @@
406
412
  /**
407
413
  * Destroy all sidenav instances
408
414
  */
409
- destroyAll: function() {
415
+ destroyAll: function(root) {
416
+ const scope = window.Vanduo && typeof window.Vanduo._normalizeRoot === 'function'
417
+ ? window.Vanduo._normalizeRoot(root)
418
+ : (root || document);
419
+
410
420
  this.sidenavs.forEach((data, sidenav) => {
411
- this.destroy(sidenav);
421
+ if (scope === document || scope === sidenav || (typeof scope.contains === 'function' && scope.contains(sidenav))) {
422
+ this.destroy(sidenav);
423
+ }
412
424
  });
413
- this._globalCleanups.forEach(fn => fn());
414
- this._globalCleanups = [];
425
+
426
+ const toggles = window.Vanduo && typeof window.Vanduo.queryAll === 'function'
427
+ ? window.Vanduo.queryAll(scope, '[data-sidenav-toggle][data-sidenav-toggle-initialized]')
428
+ : document.querySelectorAll('[data-sidenav-toggle][data-sidenav-toggle-initialized]');
429
+ toggles.forEach(toggle => {
430
+ if (toggle._sidenavToggleCleanup) {
431
+ toggle._sidenavToggleCleanup();
432
+ delete toggle._sidenavToggleCleanup;
433
+ }
434
+ delete toggle.dataset.sidenavToggleInitialized;
435
+ });
436
+
437
+ if (scope === document) {
438
+ if (this._resizeCleanup) {
439
+ this._resizeCleanup();
440
+ this._resizeCleanup = null;
441
+ }
442
+ this._globalCleanups.forEach(fn => fn());
443
+ this._globalCleanups = [];
444
+ }
415
445
  }
416
446
  };
417
447
 
@@ -424,4 +454,3 @@
424
454
  window.VanduoSidenav = Sidenav;
425
455
 
426
456
  })();
427
-
@@ -15,8 +15,8 @@
15
15
  _boundTriggers: new WeakMap(),
16
16
  _triggerElement: null,
17
17
 
18
- init: function () {
19
- const triggers = document.querySelectorAll('[data-vd-spotlight]');
18
+ init: function (root) {
19
+ const triggers = window.Vanduo.queryAll(root, '[data-vd-spotlight]');
20
20
 
21
21
  triggers.forEach(trigger => {
22
22
  if (this._boundTriggers.has(trigger)) return;
@@ -9,8 +9,8 @@
9
9
  const Stepper = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const steppers = document.querySelectorAll('.vd-stepper');
12
+ init: function (root) {
13
+ const steppers = window.Vanduo.queryAll(root, '.vd-stepper');
14
14
  steppers.forEach(el => {
15
15
  if (this.instances.has(el)) return;
16
16
  this.initInstance(el);
@@ -36,8 +36,8 @@
36
36
  const Suggest = {
37
37
  instances: new Map(),
38
38
 
39
- init: function () {
40
- const inputs = document.querySelectorAll('[data-vd-suggest], [data-vd-autocomplete]');
39
+ init: function (root) {
40
+ const inputs = window.Vanduo.queryAll(root, '[data-vd-suggest], [data-vd-autocomplete]');
41
41
  inputs.forEach(el => {
42
42
  if (this.instances.has(el)) return;
43
43
  this.initInstance(el);
@@ -220,16 +220,22 @@
220
220
  const blurHandler = () => {
221
221
  setTimeout(close, 200);
222
222
  };
223
+ const focusHandler = () => {
224
+ if (input.value.length >= minChars) {
225
+ doSearch(input.value);
226
+ }
227
+ };
223
228
 
224
229
  input.addEventListener('input', inputHandler);
225
230
  input.addEventListener('keydown', keyHandler);
226
231
  input.addEventListener('blur', blurHandler);
227
- input.addEventListener('focus', () => { if (input.value.length >= minChars) doSearch(input.value); });
232
+ input.addEventListener('focus', focusHandler);
228
233
 
229
234
  cleanup.push(
230
235
  () => input.removeEventListener('input', inputHandler),
231
236
  () => input.removeEventListener('keydown', keyHandler),
232
237
  () => input.removeEventListener('blur', blurHandler),
238
+ () => input.removeEventListener('focus', focusHandler),
233
239
  () => clearTimeout(debounceTimer),
234
240
  () => { if (list.parentNode) list.parentNode.removeChild(list); }
235
241
  );
@@ -16,8 +16,8 @@
16
16
  /**
17
17
  * Initialize all tab components
18
18
  */
19
- init: function() {
20
- const tabContainers = document.querySelectorAll('.vd-tabs, [data-tabs]');
19
+ init: function(root) {
20
+ const tabContainers = window.Vanduo.queryAll(root, '.vd-tabs, [data-tabs]');
21
21
 
22
22
  tabContainers.forEach(container => {
23
23
  if (this.instances.has(container)) {
@@ -86,23 +86,77 @@
86
86
 
87
87
  isInitialized: false,
88
88
  _cleanup: [],
89
+ _ownsDynamicPanel: false,
89
90
 
90
91
  // DOM references
91
92
  elements: {
92
93
  customizer: null,
93
94
  trigger: null,
95
+ activeTrigger: null,
94
96
  triggers: [],
95
97
  panel: null,
96
98
  overlay: null
97
99
  },
98
100
 
101
+ isRoot: function (root) {
102
+ return !!root && (root === document || root.nodeType === 1 || root.nodeType === 9 || root.nodeType === 11);
103
+ },
104
+
105
+ normalizeRoot: function (root) {
106
+ return this.isRoot(root) ? root : document;
107
+ },
108
+
109
+ queryAll: function (root, selector) {
110
+ const scope = this.normalizeRoot(root);
111
+ if (typeof window.VanduoLifecycle !== 'undefined' && typeof window.VanduoLifecycle.queryAll === 'function') {
112
+ return window.VanduoLifecycle.queryAll(scope, selector);
113
+ }
114
+
115
+ const matches = [];
116
+ if (scope instanceof Element && typeof scope.matches === 'function' && scope.matches(selector)) {
117
+ matches.push(scope);
118
+ }
119
+ if (typeof scope.querySelectorAll === 'function') {
120
+ const descendants = scope.querySelectorAll(selector);
121
+ for (let i = 0; i < descendants.length; i++) {
122
+ matches.push(descendants[i]);
123
+ }
124
+ }
125
+ return matches;
126
+ },
127
+
128
+ queryOne: function (root, selector) {
129
+ const matches = this.queryAll(root, selector);
130
+ return matches.length ? matches[0] : null;
131
+ },
132
+
133
+ getTriggers: function (root) {
134
+ return this.queryAll(root, '[data-theme-customizer-trigger]');
135
+ },
136
+
137
+ pruneTriggers: function () {
138
+ this.elements.triggers = this.elements.triggers.filter(function (trigger) {
139
+ return trigger && trigger.isConnected;
140
+ });
141
+
142
+ if (this.elements.trigger && !this.elements.trigger.isConnected) {
143
+ this.elements.trigger = null;
144
+ }
145
+
146
+ if (this.elements.activeTrigger && !this.elements.activeTrigger.isConnected) {
147
+ this.elements.activeTrigger = null;
148
+ }
149
+ },
150
+
99
151
  /**
100
152
  * Initialize the Theme Customizer
101
153
  */
102
- init: function () {
154
+ init: function (root) {
155
+ const scope = this.normalizeRoot(root);
156
+
103
157
  if (this.isInitialized) {
104
- this.bindExistingElements();
105
- this.bindTriggerEvents();
158
+ this.bindExistingElements(scope);
159
+ this.bindTriggerEvents(scope);
106
160
  this.bindPanelEvents();
107
161
  this.updateUI();
108
162
  return;
@@ -113,10 +167,8 @@
113
167
 
114
168
  this.loadPreferences();
115
169
  this.applyAllPreferences();
116
- this.bindExistingElements();
117
- this.bindEvents();
118
-
119
- console.log('Vanduo Theme Customizer initialized');
170
+ this.bindExistingElements(scope);
171
+ this.bindEvents(scope);
120
172
  },
121
173
 
122
174
  addListener: function (target, event, handler, options) {
@@ -208,7 +260,7 @@
208
260
 
209
261
  this.state.radius = radius;
210
262
  document.documentElement.setAttribute('data-radius', radius);
211
- document.documentElement.style.setProperty('--radius-scale', radius);
263
+ document.documentElement.style.setProperty('--vd-radius-scale', radius);
212
264
  this.savePreference(this.STORAGE_KEYS.RADIUS, radius);
213
265
 
214
266
  this.dispatchEvent('radius-change', { radius: radius });
@@ -315,21 +367,33 @@
315
367
  /**
316
368
  * Bind to existing DOM elements or create them dynamically
317
369
  */
318
- bindExistingElements: function () {
319
- // First check for existing full structure
320
- this.elements.customizer = document.querySelector('.vd-theme-customizer');
321
- this.elements.triggers = Array.from(document.querySelectorAll('[data-theme-customizer-trigger]'));
370
+ bindExistingElements: function (root) {
371
+ const scope = this.normalizeRoot(root);
372
+ this.pruneTriggers();
373
+
374
+ const scopedTriggers = this.getTriggers(scope);
375
+ scopedTriggers.forEach((trigger) => {
376
+ if (!this.elements.triggers.includes(trigger)) {
377
+ this.elements.triggers.push(trigger);
378
+ }
379
+ });
380
+
322
381
  if (!this.elements.trigger && this.elements.triggers.length) {
323
382
  this.elements.trigger = this.elements.triggers[0];
324
383
  }
325
384
 
326
- if (this.elements.customizer) {
385
+ const existingCustomizer = this.queryOne(scope, '.vd-theme-customizer')
386
+ || (this.elements.customizer && typeof this.elements.customizer.contains === 'function' ? this.elements.customizer : null)
387
+ || document.querySelector('.vd-theme-customizer');
388
+
389
+ if (existingCustomizer instanceof Element) {
390
+ this.elements.customizer = existingCustomizer;
327
391
  this.elements.trigger = this.elements.customizer.querySelector('.vd-theme-customizer-trigger') || this.elements.trigger;
328
392
  this.elements.panel = this.elements.customizer.querySelector('.vd-theme-customizer-panel');
329
393
  this.elements.overlay = this.elements.customizer.querySelector('.vd-theme-customizer-overlay');
330
394
  } else {
331
395
  // Look for standalone trigger buttons with data attribute
332
- if (this.elements.triggers.length) {
396
+ if (scopedTriggers.length && !this.elements.panel) {
333
397
  this.createDynamicPanel();
334
398
  }
335
399
  }
@@ -342,7 +406,7 @@
342
406
  * Create the panel dynamically when only a trigger button exists
343
407
  */
344
408
  createDynamicPanel: function () {
345
- if (!this.elements.triggers.length) {
409
+ if (!this.elements.triggers.length || (this.elements.panel && this.elements.panel.isConnected)) {
346
410
  return;
347
411
  }
348
412
  this.elements.trigger = this.elements.triggers[0];
@@ -363,6 +427,7 @@
363
427
  // Store references
364
428
  this.elements.panel = panel;
365
429
  this.elements.overlay = overlay;
430
+ this._ownsDynamicPanel = true;
366
431
  this.elements.customizer = {
367
432
  contains: (el) => panel.contains(el) || this.elements.triggers.some((trigger) => trigger.contains(el))
368
433
  };
@@ -396,7 +461,7 @@
396
461
  } else {
397
462
  // Desktop: position directly below the trigger button, aligned to its right edge
398
463
  const triggerRect = anchorTrigger.getBoundingClientRect();
399
- const panelWidth = 320; // --customizer-width
464
+ const panelWidth = 320; // --vd-customizer-width
400
465
  const panelTop = triggerRect.bottom + 8;
401
466
 
402
467
  // Calculate right position: align panel's right edge with trigger's right edge
@@ -507,13 +572,13 @@
507
572
  // Generate primary color swatches
508
573
  let primarySwatches = '';
509
574
  for (const [key, value] of Object.entries(this.PRIMARY_COLORS)) {
510
- primarySwatches += `<button class="tc-color-swatch${key === this.state.primary ? ' is-active' : ''}" data-color="${esc(key)}" style="--swatch-color: ${safeColor(value.color)}" title="${esc(value.name)}"></button>`;
575
+ primarySwatches += `<button class="tc-color-swatch${key === this.state.primary ? ' is-active' : ''}" data-color="${esc(key)}" style="--vd-swatch-color: ${safeColor(value.color)}" title="${esc(value.name)}"></button>`;
511
576
  }
512
577
 
513
578
  // Generate neutral color swatches
514
579
  let neutralSwatches = '';
515
580
  for (const [key, value] of Object.entries(this.NEUTRAL_COLORS)) {
516
- neutralSwatches += `<button class="tc-neutral-swatch${key === this.state.neutral ? ' is-active' : ''}" data-neutral="${esc(key)}" style="--swatch-color: ${safeColor(value.color)}" title="${esc(value.name)}"><span>${esc(value.name)}</span></button>`;
581
+ neutralSwatches += `<button class="tc-neutral-swatch${key === this.state.neutral ? ' is-active' : ''}" data-neutral="${esc(key)}" style="--vd-swatch-color: ${safeColor(value.color)}" title="${esc(value.name)}"><span>${esc(value.name)}</span></button>`;
517
582
  }
518
583
 
519
584
  // Generate radius buttons
@@ -595,8 +660,8 @@
595
660
  }
596
661
  },
597
662
 
598
- bindEvents: function () {
599
- this.bindTriggerEvents();
663
+ bindEvents: function (root) {
664
+ this.bindTriggerEvents(root);
600
665
 
601
666
  this.bindPanelEvents();
602
667
 
@@ -631,22 +696,53 @@
631
696
  });
632
697
  },
633
698
 
634
- bindTriggerEvents: function () {
635
- this.elements.triggers.forEach((trigger) => {
699
+ bindTriggerEvents: function (root) {
700
+ const triggers = root ? this.getTriggers(root) : this.elements.triggers;
701
+ triggers.forEach((trigger) => {
636
702
  if (trigger.getAttribute('data-customizer-trigger-initialized') === 'true') {
637
703
  return;
638
704
  }
639
- this.addListener(trigger, 'click', (e) => {
705
+
706
+ const onClick = (e) => {
640
707
  e.preventDefault();
641
708
  e.stopPropagation();
642
709
  this.elements.activeTrigger = trigger;
643
710
  this.elements.trigger = trigger;
644
711
  this.toggle();
645
- });
712
+ };
713
+
714
+ trigger.addEventListener('click', onClick);
715
+ trigger._themeCustomizerTriggerHandler = onClick;
646
716
  trigger.setAttribute('data-customizer-trigger-initialized', 'true');
647
717
  });
648
718
  },
649
719
 
720
+ cleanupTrigger: function (trigger) {
721
+ if (!trigger || trigger.getAttribute('data-customizer-trigger-initialized') !== 'true') {
722
+ return;
723
+ }
724
+
725
+ if (trigger._themeCustomizerTriggerHandler) {
726
+ trigger.removeEventListener('click', trigger._themeCustomizerTriggerHandler);
727
+ delete trigger._themeCustomizerTriggerHandler;
728
+ }
729
+
730
+ trigger.setAttribute('aria-expanded', 'false');
731
+ trigger.removeAttribute('data-customizer-trigger-initialized');
732
+
733
+ if (this.elements.activeTrigger === trigger) {
734
+ this.elements.activeTrigger = null;
735
+ }
736
+
737
+ this.elements.triggers = this.elements.triggers.filter(function (candidate) {
738
+ return candidate !== trigger;
739
+ });
740
+
741
+ if (this.elements.trigger === trigger) {
742
+ this.elements.trigger = this.elements.triggers[0] || null;
743
+ }
744
+ },
745
+
650
746
  /**
651
747
  * Toggle panel open/close
652
748
  */
@@ -782,7 +878,21 @@
782
878
  }
783
879
  },
784
880
 
785
- destroyAll: function () {
881
+ destroyAll: function (root) {
882
+ const scope = this.normalizeRoot(root);
883
+
884
+ if (scope !== document) {
885
+ this.getTriggers(scope).forEach((trigger) => {
886
+ this.cleanupTrigger(trigger);
887
+ });
888
+ this.pruneTriggers();
889
+
890
+ if (!this.elements.triggers.length) {
891
+ this.destroyAll(document);
892
+ }
893
+ return;
894
+ }
895
+
786
896
  this._cleanup.forEach(fn => fn());
787
897
  this._cleanup = [];
788
898
 
@@ -790,7 +900,27 @@
790
900
  this.elements.panel.removeAttribute('data-customizer-initialized');
791
901
  }
792
902
 
903
+ this.elements.triggers.slice().forEach((trigger) => {
904
+ this.cleanupTrigger(trigger);
905
+ });
906
+
907
+ if (this._ownsDynamicPanel) {
908
+ if (this.elements.panel && this.elements.panel.parentNode) {
909
+ this.elements.panel.parentNode.removeChild(this.elements.panel);
910
+ }
911
+ if (this.elements.overlay && this.elements.overlay.parentNode) {
912
+ this.elements.overlay.parentNode.removeChild(this.elements.overlay);
913
+ }
914
+ this._ownsDynamicPanel = false;
915
+ }
916
+
793
917
  this.close();
918
+ this.elements.customizer = null;
919
+ this.elements.trigger = null;
920
+ this.elements.activeTrigger = null;
921
+ this.elements.triggers = [];
922
+ this.elements.panel = null;
923
+ this.elements.overlay = null;
794
924
  this.isInitialized = false;
795
925
  }
796
926
  };
@@ -11,7 +11,15 @@
11
11
  _mediaQuery: null,
12
12
  _onMediaChange: null,
13
13
 
14
- init: function () {
14
+ getToggles: function (root) {
15
+ if (window.Vanduo && typeof window.Vanduo.queryAll === 'function') {
16
+ return window.Vanduo.queryAll(root, '[data-toggle="theme"]');
17
+ }
18
+
19
+ return Array.from(document.querySelectorAll('[data-toggle="theme"]'));
20
+ },
21
+
22
+ init: function (root) {
15
23
  this.STORAGE_KEY = 'vanduo-theme-preference';
16
24
  this.state = {
17
25
  preference: this.getPreference() // 'light', 'dark', or 'system'
@@ -19,8 +27,8 @@
19
27
 
20
28
  if (this.isInitialized) {
21
29
  this.applyTheme();
22
- this.renderUI();
23
- this.updateUI();
30
+ this.renderUI(root);
31
+ this.updateUI(root);
24
32
  return;
25
33
  }
26
34
 
@@ -28,9 +36,7 @@
28
36
 
29
37
  this.applyTheme();
30
38
  this.listenForSystemChanges();
31
- this.renderUI();
32
-
33
- console.log('Vanduo Theme Switcher initialized');
39
+ this.renderUI(root);
34
40
  },
35
41
 
36
42
  getPreference: function () {
@@ -113,9 +119,9 @@
113
119
  },
114
120
 
115
121
  // Helper to facilitate UI creation if needed, though often UI is in HTML
116
- renderUI: function () {
122
+ renderUI: function (root) {
117
123
  // Look for any uninitialized theme toggles
118
- const toggles = document.querySelectorAll('[data-toggle="theme"]');
124
+ const toggles = this.getToggles(root);
119
125
  toggles.forEach(toggle => {
120
126
  if (toggle.getAttribute('data-theme-initialized') === 'true') {
121
127
  if (toggle.tagName === 'SELECT') {
@@ -147,8 +153,8 @@
147
153
  });
148
154
  },
149
155
 
150
- updateUI: function () {
151
- const toggles = document.querySelectorAll('[data-toggle="theme"]');
156
+ updateUI: function (root) {
157
+ const toggles = this.getToggles(root);
152
158
  toggles.forEach(toggle => {
153
159
  if (toggle.tagName === 'SELECT') {
154
160
  toggle.value = this.state.preference;
@@ -166,8 +172,11 @@
166
172
  });
167
173
  },
168
174
 
169
- destroyAll: function () {
170
- const toggles = document.querySelectorAll('[data-toggle="theme"][data-theme-initialized="true"]');
175
+ destroyAll: function (root) {
176
+ const scope = root || document;
177
+ const toggles = this.getToggles(scope).filter(function (toggle) {
178
+ return toggle.getAttribute('data-theme-initialized') === 'true';
179
+ });
171
180
  toggles.forEach(toggle => {
172
181
  if (toggle._themeToggleHandler) {
173
182
  const eventName = toggle.tagName === 'SELECT' ? 'change' : 'click';
@@ -177,13 +186,15 @@
177
186
  toggle.removeAttribute('data-theme-initialized');
178
187
  });
179
188
 
180
- if (this._mediaQuery && this._onMediaChange) {
189
+ if (scope === document && this._mediaQuery && this._onMediaChange) {
181
190
  this._mediaQuery.removeEventListener('change', this._onMediaChange);
182
191
  }
183
192
 
184
- this._mediaQuery = null;
185
- this._onMediaChange = null;
186
- this.isInitialized = false;
193
+ if (scope === document) {
194
+ this._mediaQuery = null;
195
+ this._onMediaChange = null;
196
+ this.isInitialized = false;
197
+ }
187
198
  }
188
199
  };
189
200
 
@@ -37,6 +37,8 @@
37
37
  const pauseBtn = scope.querySelector('[data-vd-timeline-pause]');
38
38
 
39
39
  let playTimer = null;
40
+ let isPlaying = false;
41
+ let playToken = 0;
40
42
 
41
43
  function updateNavButtons() {
42
44
  const k = countRevealedPrefix(items);
@@ -52,10 +54,10 @@
52
54
  nextBtn.setAttribute('aria-disabled', atEnd ? 'true' : 'false');
53
55
  }
54
56
  if (playBtn) {
55
- playBtn.setAttribute('aria-pressed', playTimer ? 'true' : 'false');
57
+ playBtn.setAttribute('aria-pressed', isPlaying ? 'true' : 'false');
56
58
  }
57
59
  if (pauseBtn) {
58
- pauseBtn.disabled = !playTimer;
60
+ pauseBtn.disabled = !isPlaying;
59
61
  }
60
62
  }
61
63
 
@@ -75,21 +77,43 @@
75
77
  updateNavButtons();
76
78
  }
77
79
 
78
- function play() {
79
- if (playTimer) return;
80
- playTimer = setInterval(function () {
80
+ function scheduleNext() {
81
+ const token = ++playToken;
82
+ playTimer = setTimeout(function () {
83
+ playTimer = null;
84
+
85
+ if (!isPlaying || token !== playToken) {
86
+ return;
87
+ }
88
+
81
89
  if (countRevealedPrefix(items) >= items.length) {
82
90
  pause();
83
91
  return;
84
92
  }
93
+
85
94
  stepNext();
95
+
96
+ if (countRevealedPrefix(items) >= items.length) {
97
+ pause();
98
+ return;
99
+ }
100
+
101
+ scheduleNext();
86
102
  }, PLAY_INTERVAL_MS);
103
+ }
104
+
105
+ function play() {
106
+ if (isPlaying) return;
107
+ isPlaying = true;
108
+ scheduleNext();
87
109
  updateNavButtons();
88
110
  }
89
111
 
90
112
  function pause() {
113
+ isPlaying = false;
114
+ playToken++;
91
115
  if (playTimer) {
92
- clearInterval(playTimer);
116
+ clearTimeout(playTimer);
93
117
  playTimer = null;
94
118
  }
95
119
  updateNavButtons();
@@ -129,16 +153,16 @@
129
153
  const Timeline = {
130
154
  instances: new Map(),
131
155
 
132
- init: function () {
133
- document.querySelectorAll('.vd-timeline.vd-timeline-animated').forEach(function (el) {
156
+ init: function (root) {
157
+ window.Vanduo.queryAll(root, '.vd-timeline.vd-timeline-animated').forEach(function (el) {
134
158
  if (Timeline.instances.has(el)) return;
135
159
  Timeline.initInstance(el);
136
160
  });
137
161
  },
138
162
 
139
- reinit: function () {
140
- Timeline.destroyAll();
141
- Timeline.init();
163
+ reinit: function (root) {
164
+ Timeline.destroyAll(root);
165
+ Timeline.init(root);
142
166
  },
143
167
 
144
168
  initInstance: function (container) {
@@ -211,8 +235,13 @@
211
235
  this.instances.delete(container);
212
236
  },
213
237
 
214
- destroyAll: function () {
238
+ destroyAll: function (root) {
239
+ const scope = window.Vanduo && typeof window.Vanduo._normalizeRoot === 'function'
240
+ ? window.Vanduo._normalizeRoot(root)
241
+ : document;
242
+
215
243
  this.instances.forEach(function (_, el) {
244
+ if (scope !== document && scope !== el && (!scope.contains || !scope.contains(el))) return;
216
245
  Timeline.destroy(el);
217
246
  });
218
247
  }
@@ -9,8 +9,8 @@
9
9
  const Timepicker = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const inputs = document.querySelectorAll('[data-vd-timepicker]');
12
+ init: function (root) {
13
+ const inputs = window.Vanduo.queryAll(root, '[data-vd-timepicker]');
14
14
  inputs.forEach(el => {
15
15
  if (this.instances.has(el)) return;
16
16
  this.initInstance(el);