@vanduo-oss/framework 1.3.8 → 1.4.0

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 (119) hide show
  1. package/README.md +87 -41
  2. package/css/components/affix.css +1 -1
  3. package/css/components/alerts.css +40 -40
  4. package/css/components/avatar.css +33 -33
  5. package/css/components/badges.css +42 -42
  6. package/css/components/breadcrumbs.css +5 -5
  7. package/css/components/bubble.css +4 -4
  8. package/css/components/buttons.css +124 -124
  9. package/css/components/cards.css +10 -10
  10. package/css/components/chips.css +28 -28
  11. package/css/components/code-snippet.css +18 -18
  12. package/css/components/collapsible.css +28 -20
  13. package/css/components/collections.css +21 -21
  14. package/css/components/datepicker.css +13 -13
  15. package/css/components/doc-search.css +46 -53
  16. package/css/components/doc-tabs.css +10 -10
  17. package/css/components/draggable.css +34 -34
  18. package/css/components/dropdown.css +14 -14
  19. package/css/components/expanding-cards.css +1 -1
  20. package/css/components/fab.css +7 -7
  21. package/css/components/flow.css +3 -3
  22. package/css/components/footer.css +26 -26
  23. package/css/components/forms.css +95 -83
  24. package/css/components/image-box.css +13 -17
  25. package/css/components/modals.css +8 -8
  26. package/css/components/music-player.css +26 -26
  27. package/css/components/navbar.css +27 -27
  28. package/css/components/pagination.css +15 -15
  29. package/css/components/preloader.css +10 -10
  30. package/css/components/progress.css +8 -8
  31. package/css/components/rating.css +4 -4
  32. package/css/components/sidenav.css +14 -14
  33. package/css/components/skeleton.css +10 -9
  34. package/css/components/spinner.css +10 -10
  35. package/css/components/spotlight.css +7 -7
  36. package/css/components/stepper.css +13 -13
  37. package/css/components/suggest.css +10 -10
  38. package/css/components/tabs.css +22 -22
  39. package/css/components/theme-customizer.css +87 -87
  40. package/css/components/timeline.css +14 -14
  41. package/css/components/timepicker.css +7 -7
  42. package/css/components/toast.css +31 -31
  43. package/css/components/tooltips.css +11 -11
  44. package/css/components/transfer.css +12 -12
  45. package/css/components/tree.css +9 -9
  46. package/css/components/waypoint.css +3 -3
  47. package/css/core/colors.css +61 -35
  48. package/css/core/grid.css +1 -6
  49. package/css/core/helpers.css +11 -11
  50. package/css/core/tokens.css +114 -36
  51. package/css/core/typography.css +15 -13
  52. package/css/core/vd-aliases.css +100 -52
  53. package/css/effects/morph.css +5 -5
  54. package/css/utilities/media.css +2 -2
  55. package/css/utilities/table.css +34 -34
  56. package/css/utilities/transitions.css +22 -10
  57. package/css/vanduo.css +14 -34
  58. package/dist/build-info.json +3 -3
  59. package/dist/vanduo.cjs.js +935 -294
  60. package/dist/vanduo.cjs.js.map +3 -3
  61. package/dist/vanduo.cjs.min.js +7 -7
  62. package/dist/vanduo.cjs.min.js.map +3 -3
  63. package/dist/vanduo.css +7942 -7824
  64. package/dist/vanduo.css.map +1 -1
  65. package/dist/vanduo.esm.js +935 -294
  66. package/dist/vanduo.esm.js.map +3 -3
  67. package/dist/vanduo.esm.min.js +7 -7
  68. package/dist/vanduo.esm.min.js.map +3 -3
  69. package/dist/vanduo.js +935 -294
  70. package/dist/vanduo.js.map +3 -3
  71. package/dist/vanduo.min.css +2 -2
  72. package/dist/vanduo.min.css.map +1 -1
  73. package/dist/vanduo.min.js +7 -7
  74. package/dist/vanduo.min.js.map +3 -3
  75. package/js/components/affix.js +2 -2
  76. package/js/components/bubble.js +3 -3
  77. package/js/components/code-snippet.js +129 -5
  78. package/js/components/collapsible.js +2 -3
  79. package/js/components/datepicker.js +2 -2
  80. package/js/components/doc-search.js +69 -11
  81. package/js/components/draggable.js +4 -4
  82. package/js/components/dropdown.js +2 -3
  83. package/js/components/expanding-cards.js +2 -2
  84. package/js/components/flow.js +2 -2
  85. package/js/components/font-switcher.js +26 -16
  86. package/js/components/glass.js +2 -2
  87. package/js/components/grid.js +19 -8
  88. package/js/components/image-box.js +49 -10
  89. package/js/components/lazy-load.js +81 -9
  90. package/js/components/modals.js +28 -12
  91. package/js/components/morph.js +2 -2
  92. package/js/components/music-player.js +2 -2
  93. package/js/components/navbar.js +2 -2
  94. package/js/components/pagination.js +2 -3
  95. package/js/components/parallax.js +9 -10
  96. package/js/components/preloader.js +14 -5
  97. package/js/components/rating.js +2 -2
  98. package/js/components/ripple.js +2 -2
  99. package/js/components/select.js +2 -3
  100. package/js/components/sidenav.js +43 -14
  101. package/js/components/spotlight.js +2 -2
  102. package/js/components/stepper.js +2 -2
  103. package/js/components/suggest.js +9 -3
  104. package/js/components/tabs.js +2 -2
  105. package/js/components/theme-customizer.js +154 -23
  106. package/js/components/theme-switcher.js +27 -16
  107. package/js/components/timeline.js +41 -12
  108. package/js/components/timepicker.js +2 -2
  109. package/js/components/toast.js +1 -1
  110. package/js/components/tooltips.js +4 -4
  111. package/js/components/transfer.js +2 -2
  112. package/js/components/tree.js +2 -2
  113. package/js/components/validate.js +2 -2
  114. package/js/components/vd-hex.js +12 -6
  115. package/js/components/waypoint.js +2 -2
  116. package/js/utils/helpers.js +7 -4
  117. package/js/utils/lifecycle.js +158 -83
  118. package/js/vanduo.js +203 -34
  119. package/package.json +3 -4
@@ -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)) {
@@ -21,9 +21,9 @@
21
21
  DEFAULTS: {
22
22
  PRIMARY_LIGHT: 'black',
23
23
  PRIMARY_DARK: 'amber',
24
- NEUTRAL: 'neutral',
24
+ NEUTRAL: 'charcoal',
25
25
  RADIUS: '0.5',
26
- FONT: 'lato',
26
+ FONT: 'ubuntu',
27
27
  THEME: 'system'
28
28
  },
29
29
 
@@ -51,6 +51,7 @@
51
51
 
52
52
  // Neutral color definitions
53
53
  NEUTRAL_COLORS: {
54
+ 'charcoal': { name: 'Charcoal', color: '#0d1117' },
54
55
  'slate': { name: 'Slate', color: '#64748b' },
55
56
  'gray': { name: 'Gray', color: '#6b7280' },
56
57
  'zinc': { name: 'Zinc', color: '#71717a' },
@@ -85,23 +86,77 @@
85
86
 
86
87
  isInitialized: false,
87
88
  _cleanup: [],
89
+ _ownsDynamicPanel: false,
88
90
 
89
91
  // DOM references
90
92
  elements: {
91
93
  customizer: null,
92
94
  trigger: null,
95
+ activeTrigger: null,
93
96
  triggers: [],
94
97
  panel: null,
95
98
  overlay: null
96
99
  },
97
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
+
98
151
  /**
99
152
  * Initialize the Theme Customizer
100
153
  */
101
- init: function () {
154
+ init: function (root) {
155
+ const scope = this.normalizeRoot(root);
156
+
102
157
  if (this.isInitialized) {
103
- this.bindExistingElements();
104
- this.bindTriggerEvents();
158
+ this.bindExistingElements(scope);
159
+ this.bindTriggerEvents(scope);
105
160
  this.bindPanelEvents();
106
161
  this.updateUI();
107
162
  return;
@@ -112,10 +167,8 @@
112
167
 
113
168
  this.loadPreferences();
114
169
  this.applyAllPreferences();
115
- this.bindExistingElements();
116
- this.bindEvents();
117
-
118
- console.log('Vanduo Theme Customizer initialized');
170
+ this.bindExistingElements(scope);
171
+ this.bindEvents(scope);
119
172
  },
120
173
 
121
174
  addListener: function (target, event, handler, options) {
@@ -314,21 +367,33 @@
314
367
  /**
315
368
  * Bind to existing DOM elements or create them dynamically
316
369
  */
317
- bindExistingElements: function () {
318
- // First check for existing full structure
319
- this.elements.customizer = document.querySelector('.vd-theme-customizer');
320
- 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
+
321
381
  if (!this.elements.trigger && this.elements.triggers.length) {
322
382
  this.elements.trigger = this.elements.triggers[0];
323
383
  }
324
384
 
325
- 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;
326
391
  this.elements.trigger = this.elements.customizer.querySelector('.vd-theme-customizer-trigger') || this.elements.trigger;
327
392
  this.elements.panel = this.elements.customizer.querySelector('.vd-theme-customizer-panel');
328
393
  this.elements.overlay = this.elements.customizer.querySelector('.vd-theme-customizer-overlay');
329
394
  } else {
330
395
  // Look for standalone trigger buttons with data attribute
331
- if (this.elements.triggers.length) {
396
+ if (scopedTriggers.length && !this.elements.panel) {
332
397
  this.createDynamicPanel();
333
398
  }
334
399
  }
@@ -341,7 +406,7 @@
341
406
  * Create the panel dynamically when only a trigger button exists
342
407
  */
343
408
  createDynamicPanel: function () {
344
- if (!this.elements.triggers.length) {
409
+ if (!this.elements.triggers.length || (this.elements.panel && this.elements.panel.isConnected)) {
345
410
  return;
346
411
  }
347
412
  this.elements.trigger = this.elements.triggers[0];
@@ -362,6 +427,7 @@
362
427
  // Store references
363
428
  this.elements.panel = panel;
364
429
  this.elements.overlay = overlay;
430
+ this._ownsDynamicPanel = true;
365
431
  this.elements.customizer = {
366
432
  contains: (el) => panel.contains(el) || this.elements.triggers.some((trigger) => trigger.contains(el))
367
433
  };
@@ -594,8 +660,8 @@
594
660
  }
595
661
  },
596
662
 
597
- bindEvents: function () {
598
- this.bindTriggerEvents();
663
+ bindEvents: function (root) {
664
+ this.bindTriggerEvents(root);
599
665
 
600
666
  this.bindPanelEvents();
601
667
 
@@ -630,22 +696,53 @@
630
696
  });
631
697
  },
632
698
 
633
- bindTriggerEvents: function () {
634
- this.elements.triggers.forEach((trigger) => {
699
+ bindTriggerEvents: function (root) {
700
+ const triggers = root ? this.getTriggers(root) : this.elements.triggers;
701
+ triggers.forEach((trigger) => {
635
702
  if (trigger.getAttribute('data-customizer-trigger-initialized') === 'true') {
636
703
  return;
637
704
  }
638
- this.addListener(trigger, 'click', (e) => {
705
+
706
+ const onClick = (e) => {
639
707
  e.preventDefault();
640
708
  e.stopPropagation();
641
709
  this.elements.activeTrigger = trigger;
642
710
  this.elements.trigger = trigger;
643
711
  this.toggle();
644
- });
712
+ };
713
+
714
+ trigger.addEventListener('click', onClick);
715
+ trigger._themeCustomizerTriggerHandler = onClick;
645
716
  trigger.setAttribute('data-customizer-trigger-initialized', 'true');
646
717
  });
647
718
  },
648
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
+
649
746
  /**
650
747
  * Toggle panel open/close
651
748
  */
@@ -781,7 +878,21 @@
781
878
  }
782
879
  },
783
880
 
784
- 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
+
785
896
  this._cleanup.forEach(fn => fn());
786
897
  this._cleanup = [];
787
898
 
@@ -789,7 +900,27 @@
789
900
  this.elements.panel.removeAttribute('data-customizer-initialized');
790
901
  }
791
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
+
792
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;
793
924
  this.isInitialized = false;
794
925
  }
795
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);
@@ -94,7 +94,7 @@
94
94
  if (config.icon) {
95
95
  const allowSvg = config.iconAllowSvg === true;
96
96
  const safeIcon = typeof sanitizeHtml === 'function'
97
- ? sanitizeHtml(config.icon, { allowSvg })
97
+ ? sanitizeHtml(config.icon, { allowSvg, allowStyle: false })
98
98
  : escapeHtml(config.icon);
99
99
  html += `<span class="vd-toast-icon">${safeIcon}</span>`;
100
100
  } else if (config.type) {
@@ -31,8 +31,8 @@
31
31
  /**
32
32
  * Initialize tooltips
33
33
  */
34
- init: function () {
35
- const elements = document.querySelectorAll('[data-tooltip], [data-tooltip-html]');
34
+ init: function (root) {
35
+ const elements = window.Vanduo.queryAll(root, '[data-tooltip], [data-tooltip-html]');
36
36
 
37
37
  elements.forEach(element => {
38
38
  if (this.tooltips.has(element)) {
@@ -93,7 +93,7 @@
93
93
 
94
94
  if (htmlContent) {
95
95
  const allowSvg = element.hasAttribute('data-tooltip-allow-svg');
96
- tooltip.innerHTML = this.sanitizeHtml(htmlContent, { allowSvg });
96
+ tooltip.innerHTML = this.sanitizeHtml(htmlContent, { allowSvg, allowStyle: false });
97
97
  tooltip.classList.add('vd-tooltip-html');
98
98
  } else if (textContent) {
99
99
  tooltip.textContent = textContent;
@@ -253,7 +253,7 @@
253
253
  const { tooltip } = this.tooltips.get(el);
254
254
  if (isHtml) {
255
255
  const allowSvg = el.hasAttribute('data-tooltip-allow-svg');
256
- tooltip.innerHTML = this.sanitizeHtml(content, { allowSvg });
256
+ tooltip.innerHTML = this.sanitizeHtml(content, { allowSvg, allowStyle: false });
257
257
  tooltip.classList.add('vd-tooltip-html');
258
258
  } else {
259
259
  tooltip.textContent = content;
@@ -9,8 +9,8 @@
9
9
  const Transfer = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const transfers = document.querySelectorAll('[data-vd-transfer]');
12
+ init: function (root) {
13
+ const transfers = window.Vanduo.queryAll(root, '[data-vd-transfer]');
14
14
  transfers.forEach(el => {
15
15
  if (this.instances.has(el)) return;
16
16
  this.initInstance(el);
@@ -9,8 +9,8 @@
9
9
  const Tree = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const trees = document.querySelectorAll('[data-vd-tree]');
12
+ init: function (root) {
13
+ const trees = window.Vanduo.queryAll(root, '[data-vd-tree]');
14
14
  trees.forEach(el => {
15
15
  if (this.instances.has(el)) return;
16
16
  this.initInstance(el);
@@ -49,8 +49,8 @@
49
49
  match: 'Fields do not match'
50
50
  },
51
51
 
52
- init: function () {
53
- const forms = document.querySelectorAll('[data-vd-validate], .vd-validate');
52
+ init: function (root) {
53
+ const forms = window.Vanduo.queryAll(root, '[data-vd-validate], .vd-validate');
54
54
  forms.forEach(form => {
55
55
  if (this.instances.has(form)) return;
56
56
  this.initInstance(form);
@@ -90,13 +90,19 @@ export class VdHexGrid {
90
90
  const root = document.documentElement;
91
91
  const style = getComputedStyle(root);
92
92
 
93
+ const readToken = (canonical, fallback, defaultValue) => {
94
+ return style.getPropertyValue(canonical).trim()
95
+ || style.getPropertyValue(fallback).trim()
96
+ || defaultValue;
97
+ };
98
+
93
99
  return {
94
- bgPrimary: style.getPropertyValue('--bg-primary').trim() || '#ffffff',
95
- bgSecondary: style.getPropertyValue('--bg-secondary').trim() || '#f5f5f5',
96
- borderColor: style.getPropertyValue('--border-color').trim() || '#e0e0e0',
97
- colorPrimary: style.getPropertyValue('--color-primary').trim() || '#3b82f6',
98
- textColor: style.getPropertyValue('--text-primary').trim() || '#1f2937',
99
- textMuted: style.getPropertyValue('--text-muted').trim() || '#6b7280'
100
+ bgPrimary: readToken('--vd-bg-primary', '--bg-primary', '#ffffff'),
101
+ bgSecondary: readToken('--vd-bg-secondary', '--bg-secondary', '#f5f5f5'),
102
+ borderColor: readToken('--vd-border-color', '--border-color', '#e0e0e0'),
103
+ colorPrimary: readToken('--vd-color-primary', '--color-primary', '#3b82f6'),
104
+ textColor: readToken('--vd-text-primary', '--text-primary', '#1f2937'),
105
+ textMuted: readToken('--vd-text-muted', '--text-muted', '#6b7280')
100
106
  };
101
107
  }
102
108
 
@@ -9,8 +9,8 @@
9
9
  const Waypoint = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const navs = document.querySelectorAll('[data-vd-waypoint-nav], [data-vd-scrollspy-nav]');
12
+ init: function (root) {
13
+ const navs = window.Vanduo.queryAll(root, '[data-vd-waypoint-nav], [data-vd-scrollspy-nav]');
14
14
  navs.forEach(nav => {
15
15
  if (this.instances.has(nav)) return;
16
16
  this.initInstance(nav);