@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
@@ -32,8 +32,8 @@
32
32
  const Affix = {
33
33
  instances: new Map(),
34
34
 
35
- init: function () {
36
- const elements = document.querySelectorAll('.vd-affix, .vd-sticky, [data-vd-affix]');
35
+ init: function (root) {
36
+ const elements = window.Vanduo.queryAll(root, '.vd-affix, .vd-sticky, [data-vd-affix]');
37
37
  elements.forEach(el => {
38
38
  if (this.instances.has(el)) return;
39
39
  this.initInstance(el);
@@ -10,8 +10,8 @@
10
10
  instances: new Map(),
11
11
  _globalCleanups: [],
12
12
 
13
- init: function () {
14
- const triggers = document.querySelectorAll('[data-vd-bubble], [data-vd-popover]');
13
+ init: function (root) {
14
+ const triggers = window.Vanduo.queryAll(root, '[data-vd-bubble], [data-vd-popover]');
15
15
  triggers.forEach(el => {
16
16
  if (this.instances.has(el)) return;
17
17
  this.initInstance(el);
@@ -82,7 +82,7 @@
82
82
  body.className = 'vd-bubble-body';
83
83
  if (htmlContent) {
84
84
  if (typeof sanitizeHtml === 'function') {
85
- body.innerHTML = sanitizeHtml(htmlContent, { allowSvg });
85
+ body.innerHTML = sanitizeHtml(htmlContent, { allowSvg, allowStyle: false });
86
86
  } else {
87
87
  body.textContent = htmlContent;
88
88
  }
@@ -12,6 +12,31 @@
12
12
  const CodeSnippet = {
13
13
  _snippetIdCounter: 0,
14
14
 
15
+ resolveRoot: function (root) {
16
+ if (root && (root.nodeType === 1 || root.nodeType === 9 || root.nodeType === 11)) {
17
+ return root;
18
+ }
19
+ return document;
20
+ },
21
+
22
+ queryWithin: function (root, selector) {
23
+ const scope = this.resolveRoot(root);
24
+ const matches = [];
25
+
26
+ if (scope instanceof Element && typeof scope.matches === 'function' && scope.matches(selector)) {
27
+ matches.push(scope);
28
+ }
29
+
30
+ if (typeof scope.querySelectorAll === 'function') {
31
+ const descendants = scope.querySelectorAll(selector);
32
+ for (let i = 0; i < descendants.length; i++) {
33
+ matches.push(descendants[i]);
34
+ }
35
+ }
36
+
37
+ return matches;
38
+ },
39
+
15
40
  getSnippetInstanceId: function (snippet) {
16
41
  if (snippet.dataset.codeSnippetId) {
17
42
  return snippet.dataset.codeSnippetId;
@@ -40,8 +65,8 @@
40
65
  /**
41
66
  * Initialize all code snippet components
42
67
  */
43
- init: function () {
44
- const snippets = document.querySelectorAll('.vd-code-snippet');
68
+ init: function (root) {
69
+ const snippets = this.queryWithin(root, '.vd-code-snippet');
45
70
 
46
71
  snippets.forEach(snippet => {
47
72
  if (!snippet.dataset.initialized) {
@@ -86,6 +111,11 @@
86
111
  this.extractHtml(pane);
87
112
  });
88
113
 
114
+ const panesToHighlight = snippet.querySelectorAll('.vd-code-snippet-pane:not([data-extract])');
115
+ panesToHighlight.forEach(pane => {
116
+ this.applyPaneHighlighting(pane);
117
+ });
118
+
89
119
  // Handle line numbers
90
120
  const lineNumberPanes = snippet.querySelectorAll('.has-line-numbers');
91
121
  lineNumberPanes.forEach(pane => {
@@ -383,6 +413,7 @@
383
413
  codeEl.innerHTML = html;
384
414
  pane.replaceChildren(codeEl);
385
415
  pane.dataset.extracted = 'true';
416
+ pane.dataset.highlighted = 'true';
386
417
  },
387
418
 
388
419
  /**
@@ -441,6 +472,94 @@
441
472
  return div.innerHTML;
442
473
  },
443
474
 
475
+ needsHighlighting: function (pane, codeEl) {
476
+ if (!codeEl) return false;
477
+ if (pane.dataset.highlighted === 'true') return false;
478
+ if (codeEl.querySelector('[class^="code-"], [class*=" code-"]')) return false;
479
+ return true;
480
+ },
481
+
482
+ getHighlightMode: function (lang) {
483
+ const normalized = String(lang || '').trim().toLowerCase();
484
+
485
+ if ([
486
+ 'html',
487
+ 'xml',
488
+ 'svg',
489
+ 'vue',
490
+ 'svelte',
491
+ 'astro'
492
+ ].includes(normalized)) {
493
+ return 'html';
494
+ }
495
+
496
+ if ([
497
+ 'css',
498
+ 'scss',
499
+ 'sass',
500
+ 'less'
501
+ ].includes(normalized)) {
502
+ return 'css';
503
+ }
504
+
505
+ if ([
506
+ 'js',
507
+ 'mjs',
508
+ 'cjs',
509
+ 'ts',
510
+ 'jsx',
511
+ 'tsx',
512
+ 'json',
513
+ 'bash',
514
+ 'sh'
515
+ ].includes(normalized)) {
516
+ return 'js';
517
+ }
518
+
519
+ return 'plain';
520
+ },
521
+
522
+ highlightCodeByLang: function (rawCode, lang) {
523
+ const escaped = this.escapeHtml(rawCode);
524
+ const mode = this.getHighlightMode(lang);
525
+
526
+ if (mode === 'html') {
527
+ return this.highlightHtml(escaped);
528
+ }
529
+
530
+ if (mode === 'css') {
531
+ return this.highlightCss(escaped);
532
+ }
533
+
534
+ if (mode === 'js') {
535
+ return this.highlightJs(escaped);
536
+ }
537
+
538
+ return escaped;
539
+ },
540
+
541
+ applyPaneHighlighting: function (pane) {
542
+ if (!pane) return;
543
+
544
+ const codeEl = pane.querySelector('code') || pane;
545
+ if (!this.needsHighlighting(pane, codeEl)) {
546
+ pane.dataset.highlighted = 'true';
547
+ return;
548
+ }
549
+
550
+ const rawCode = codeEl.textContent || '';
551
+ const highlighted = this.highlightCodeByLang(rawCode, pane.dataset.lang);
552
+ const nextCodeEl = codeEl.tagName === 'CODE' ? codeEl : document.createElement('code');
553
+
554
+ nextCodeEl.innerHTML = highlighted;
555
+
556
+ if (nextCodeEl !== codeEl) {
557
+ pane.replaceChildren(nextCodeEl);
558
+ }
559
+
560
+ pane.dataset.highlighted = 'true';
561
+ },
562
+
444
563
  /**
445
564
  * Apply syntax highlighting to HTML
446
565
  * @param {string} html - Escaped HTML string
@@ -451,9 +570,11 @@
451
570
  html = html.replace(/(&lt;\/?)([\w-]+)/g, '$1<span class="code-tag">$2</span>');
452
571
 
453
572
  // Highlight attributes
573
+ html = html.replace(/([\w-]+)(=)(["'])/g, '<span class="code-attr">$1</span>$2$3');
454
574
  html = html.replace(/([\w-]+)(=)(&quot;|&#39;)/g, '<span class="code-attr">$1</span>$2$3');
455
575
 
456
576
  // Highlight attribute values (strings)
577
+ html = html.replace(/(["'])([^"']*)(["'])/g, '$1<span class="code-string">$2</span>$3');
457
578
  html = html.replace(/(&quot;|&#39;)([^&]*)(&quot;|&#39;)/g, '$1<span class="code-string">$2</span>$3');
458
579
 
459
580
  // Highlight comments
@@ -625,9 +746,12 @@
625
746
  /**
626
747
  * Destroy all code snippet instances
627
748
  */
628
- destroyAll: function () {
629
- const snippets = document.querySelectorAll('.vd-code-snippet[data-initialized="true"]');
630
- snippets.forEach(snippet => this.destroy(snippet));
749
+ destroyAll: function (root) {
750
+ const scope = this.resolveRoot(root);
751
+ const snippets = this.queryWithin(scope, '.vd-code-snippet[data-initialized="true"]');
752
+ snippets.forEach(snippet => {
753
+ this.destroy(snippet);
754
+ });
631
755
  }
632
756
  };
633
757
 
@@ -16,8 +16,8 @@
16
16
  /**
17
17
  * Initialize collapsible components
18
18
  */
19
- init: function() {
20
- const collapsibles = document.querySelectorAll('.vd-collapsible, .accordion');
19
+ init: function(root) {
20
+ const collapsibles = window.Vanduo.queryAll(root, '.vd-collapsible, .accordion');
21
21
 
22
22
  collapsibles.forEach(container => {
23
23
  if (this.instances.has(container)) {
@@ -223,4 +223,3 @@
223
223
  window.VanduoCollapsible = Collapsible;
224
224
 
225
225
  })();
226
-
@@ -131,8 +131,8 @@
131
131
  const Datepicker = {
132
132
  instances: new Map(),
133
133
 
134
- init: function () {
135
- const inputs = document.querySelectorAll('[data-vd-datepicker]');
134
+ init: function (root) {
135
+ const inputs = window.Vanduo.queryAll(root, '[data-vd-datepicker]');
136
136
  inputs.forEach(el => {
137
137
  if (this.instances.has(el)) return;
138
138
  this.initInstance(el);
@@ -84,6 +84,26 @@
84
84
  placeholder: 'Search...'
85
85
  };
86
86
 
87
+ const ALLOWED_HIGHLIGHT_TAGS = {
88
+ mark: true,
89
+ span: true,
90
+ strong: true,
91
+ em: true
92
+ };
93
+
94
+ function isRoot(value) {
95
+ return typeof window.VanduoLifecycle !== 'undefined' && window.VanduoLifecycle.isRoot(value);
96
+ }
97
+
98
+ function normalizeRoot(root) {
99
+ return isRoot(root) ? root : document;
100
+ }
101
+
102
+ function normalizeHighlightTag(tagName) {
103
+ const normalized = typeof tagName === 'string' ? tagName.toLowerCase() : 'mark';
104
+ return ALLOWED_HIGHLIGHT_TAGS[normalized] ? normalized : 'mark';
105
+ }
106
+
87
107
  /**
88
108
  * Search Component Factory
89
109
  * Creates a new search instance with the given configuration
@@ -93,6 +113,8 @@
93
113
  */
94
114
  function createSearch(options) {
95
115
  const config = Object.assign({}, DEFAULTS, options || {});
116
+ config.root = normalizeRoot(config.root);
117
+ config.highlightTag = normalizeHighlightTag(config.highlightTag);
96
118
 
97
119
  // Instance state
98
120
  const state = {
@@ -109,6 +131,28 @@
109
131
  boundHandlers: {}
110
132
  };
111
133
 
134
+ function queryAll(selector) {
135
+ if (window.Vanduo && typeof window.Vanduo.queryAll === 'function') {
136
+ return window.Vanduo.queryAll(config.root, selector);
137
+ }
138
+
139
+ const scope = normalizeRoot(config.root);
140
+ if (scope === document) {
141
+ return Array.from(document.querySelectorAll(selector));
142
+ }
143
+
144
+ const matches = [];
145
+ if (scope instanceof Element && scope.matches(selector)) {
146
+ matches.push(scope);
147
+ }
148
+ return matches.concat(Array.from(scope.querySelectorAll(selector)));
149
+ }
150
+
151
+ function queryOne(selector) {
152
+ const matches = queryAll(selector);
153
+ return matches.length ? matches[0] : null;
154
+ }
155
+
112
156
  function safeInvokeCallback(name, fn, ...args) {
113
157
  try {
114
158
  fn(...args);
@@ -136,7 +180,7 @@
136
180
  return instance;
137
181
  }
138
182
 
139
- state.container = document.querySelector(config.containerSelector);
183
+ state.container = queryOne(config.containerSelector);
140
184
  if (!state.container) {
141
185
  state.initialized = false;
142
186
  return null;
@@ -192,7 +236,7 @@
192
236
  }
193
237
 
194
238
  // Build from DOM
195
- const sections = document.querySelectorAll(config.contentSelector);
239
+ const sections = queryAll(config.contentSelector);
196
240
  const categoryMap = buildCategoryMap();
197
241
 
198
242
  sections.forEach(function(section) {
@@ -234,7 +278,7 @@
234
278
  function buildCategoryMap() {
235
279
  const map = {};
236
280
  let currentCategory = 'Documentation';
237
- const navItems = document.querySelectorAll(config.navSelector + ', ' + config.sectionSelector);
281
+ const navItems = queryAll(config.navSelector + ', ' + config.sectionSelector);
238
282
 
239
283
  navItems.forEach(function(item) {
240
284
  if (item.classList.contains('doc-nav-section')) {
@@ -736,7 +780,7 @@
736
780
  }
737
781
 
738
782
  // Default behavior: navigate to section
739
- const section = document.querySelector(result.url);
783
+ const section = queryOne(result.url) || document.querySelector(result.url);
740
784
  if (section) {
741
785
  section.scrollIntoView({ behavior: 'smooth', block: 'start' });
742
786
  window.history.pushState(null, '', result.url);
@@ -748,7 +792,7 @@
748
792
  * Update sidebar navigation active state
749
793
  */
750
794
  function updateSidebarActive(sectionId) {
751
- const navLinks = document.querySelectorAll(config.navSelector);
795
+ const navLinks = queryAll(config.navSelector);
752
796
  navLinks.forEach(function(link) {
753
797
  link.classList.remove('active');
754
798
  if (link.getAttribute('href') === '#' + sectionId) {
@@ -822,6 +866,8 @@
822
866
  */
823
867
  function setConfig(newConfig) {
824
868
  Object.assign(config, newConfig);
869
+ config.root = normalizeRoot(config.root);
870
+ config.highlightTag = normalizeHighlightTag(config.highlightTag);
825
871
  }
826
872
 
827
873
  /**
@@ -848,7 +894,10 @@
848
894
  close: close,
849
895
  setConfig: setConfig,
850
896
  getConfig: getConfig,
851
- getIndex: getIndex
897
+ getIndex: getIndex,
898
+ getContainer: function () {
899
+ return state.container;
900
+ }
852
901
  };
853
902
 
854
903
  return instance;
@@ -878,7 +927,10 @@
878
927
  /**
879
928
  * Initialize the default search instance
880
929
  */
881
- init: function(options) {
930
+ init: function(rootOrOptions, maybeOptions) {
931
+ const root = isRoot(rootOrOptions) ? rootOrOptions : null;
932
+ const options = root ? maybeOptions : rootOrOptions;
933
+
882
934
  if (this._instance) {
883
935
  this._instance.destroy();
884
936
  }
@@ -887,22 +939,28 @@
887
939
  Object.assign(this.config, options);
888
940
  }
889
941
 
890
- this._instance = createSearch(this.config);
942
+ this._instance = createSearch(Object.assign({}, this.config, root ? { root: root } : {}));
891
943
  return this._instance ? this._instance.init() : null;
892
944
  },
893
945
 
894
946
  /**
895
947
  * Destroy the default instance
896
948
  */
897
- destroy: function() {
949
+ destroy: function(root) {
950
+ if (root && this._instance && this._instance.getContainer() && typeof window.VanduoLifecycle !== 'undefined') {
951
+ if (!window.VanduoLifecycle.isInRoot(root, this._instance.getContainer())) {
952
+ return;
953
+ }
954
+ }
955
+
898
956
  if (this._instance) {
899
957
  this._instance.destroy();
900
958
  this._instance = null;
901
959
  }
902
960
  },
903
961
 
904
- destroyAll: function() {
905
- this.destroy();
962
+ destroyAll: function(root) {
963
+ this.destroy(root);
906
964
  },
907
965
 
908
966
  /**
@@ -24,8 +24,8 @@
24
24
  /**
25
25
  * Initialize draggable components
26
26
  */
27
- init: function () {
28
- const draggables = document.querySelectorAll('.vd-draggable, [data-draggable]');
27
+ init: function (root) {
28
+ const draggables = window.Vanduo.queryAll(root, '.vd-draggable, [data-draggable]');
29
29
 
30
30
  draggables.forEach(element => {
31
31
  if (this.instances.has(element)) {
@@ -34,14 +34,14 @@
34
34
  this.initDraggable(element);
35
35
  });
36
36
 
37
- const containers = document.querySelectorAll(this.containerSelector);
37
+ const containers = window.Vanduo.queryAll(root, this.containerSelector);
38
38
  containers.forEach(container => {
39
39
  if (!this.instances.has(container)) {
40
40
  this.initContainer(container);
41
41
  }
42
42
  });
43
43
 
44
- const dropZones = document.querySelectorAll('.vd-drop-zone');
44
+ const dropZones = window.Vanduo.queryAll(root, '.vd-drop-zone');
45
45
  dropZones.forEach(zone => {
46
46
  if (!this.instances.has(zone)) {
47
47
  this.initDropZone(zone);
@@ -16,8 +16,8 @@
16
16
  /**
17
17
  * Initialize dropdown components
18
18
  */
19
- init: function() {
20
- const dropdowns = document.querySelectorAll('.vd-dropdown');
19
+ init: function(root) {
20
+ const dropdowns = window.Vanduo.queryAll(root, '.vd-dropdown');
21
21
 
22
22
  dropdowns.forEach(dropdown => {
23
23
  if (this.instances.has(dropdown)) {
@@ -367,4 +367,3 @@
367
367
  window.VanduoDropdown = Dropdown;
368
368
 
369
369
  })();
370
-
@@ -13,8 +13,8 @@
13
13
  const ExpandingCards = {
14
14
  instances: new Map(),
15
15
 
16
- init: function () {
17
- document.querySelectorAll('.vd-expanding-cards').forEach(function (el) {
16
+ init: function (root) {
17
+ window.Vanduo.queryAll(root, '.vd-expanding-cards').forEach(function (el) {
18
18
  if (el.getAttribute('data-vd-expanding-cards') === 'manual') return;
19
19
  if (ExpandingCards.instances.has(el)) return;
20
20
  ExpandingCards.initContainer(el);
@@ -9,8 +9,8 @@
9
9
  const Flow = {
10
10
  instances: new Map(),
11
11
 
12
- init: function () {
13
- const carousels = document.querySelectorAll('.vd-flow, .vd-carousel');
12
+ init: function (root) {
13
+ const carousels = window.Vanduo.queryAll(root, '.vd-flow, .vd-carousel');
14
14
  carousels.forEach(el => {
15
15
  if (this.instances.has(el)) return;
16
16
  this.initInstance(el);
@@ -40,36 +40,42 @@
40
40
  }
41
41
  },
42
42
 
43
- init: function() {
43
+ getToggles: function(root) {
44
+ if (window.Vanduo && typeof window.Vanduo.queryAll === 'function') {
45
+ return window.Vanduo.queryAll(root, '[data-toggle="font"]');
46
+ }
47
+
48
+ return Array.from(document.querySelectorAll('[data-toggle="font"]'));
49
+ },
50
+
51
+ init: function(root) {
44
52
  this.state = {
45
53
  preference: this.getPreference()
46
54
  };
47
55
  if (!this.fonts[this.state.preference]) {
48
- this.state.preference = 'lato';
56
+ this.state.preference = 'ubuntu';
49
57
  this.setStorageValue(this.STORAGE_KEY, this.state.preference);
50
58
  }
51
59
 
52
60
  if (this.isInitialized) {
53
61
  this.applyFont();
54
- this.renderUI();
55
- this.updateUI();
62
+ this.renderUI(root);
63
+ this.updateUI(root);
56
64
  return;
57
65
  }
58
66
 
59
67
  this.isInitialized = true;
60
68
 
61
69
  this.applyFont();
62
- this.renderUI();
63
-
64
- console.log('Vanduo Font Switcher initialized');
70
+ this.renderUI(root);
65
71
  },
66
72
 
67
73
  /**
68
74
  * Get saved font preference from localStorage
69
- * @returns {string} Font key or 'lato' (default)
75
+ * @returns {string} Font key or 'ubuntu' (default)
70
76
  */
71
77
  getPreference: function() {
72
- return this.getStorageValue(this.STORAGE_KEY, 'lato');
78
+ return this.getStorageValue(this.STORAGE_KEY, 'ubuntu');
73
79
  },
74
80
 
75
81
  /**
@@ -113,8 +119,8 @@
113
119
  /**
114
120
  * Initialize UI elements with data-toggle="font"
115
121
  */
116
- renderUI: function() {
117
- const toggles = document.querySelectorAll('[data-toggle="font"]');
122
+ renderUI: function(root) {
123
+ const toggles = this.getToggles(root);
118
124
 
119
125
  toggles.forEach(toggle => {
120
126
  if (toggle.getAttribute('data-font-initialized') === 'true') {
@@ -153,8 +159,8 @@
153
159
  /**
154
160
  * Update all UI elements to reflect current state
155
161
  */
156
- updateUI: function() {
157
- const toggles = document.querySelectorAll('[data-toggle="font"]');
162
+ updateUI: function(root) {
163
+ const toggles = this.getToggles(root);
158
164
 
159
165
  toggles.forEach(toggle => {
160
166
  if (toggle.tagName === 'SELECT') {
@@ -186,8 +192,10 @@
186
192
  return this.fonts[fontKey] || null;
187
193
  },
188
194
 
189
- destroyAll: function() {
190
- const toggles = document.querySelectorAll('[data-toggle="font"][data-font-initialized="true"]');
195
+ destroyAll: function(root) {
196
+ const toggles = this.getToggles(root || document).filter(function(toggle) {
197
+ return toggle.getAttribute('data-font-initialized') === 'true';
198
+ });
191
199
  toggles.forEach(toggle => {
192
200
  if (toggle._fontToggleHandler) {
193
201
  const eventName = toggle.tagName === 'SELECT' ? 'change' : 'click';
@@ -197,7 +205,9 @@
197
205
  toggle.removeAttribute('data-font-initialized');
198
206
  });
199
207
 
200
- this.isInitialized = false;
208
+ if (!root || root === document) {
209
+ this.isInitialized = false;
210
+ }
201
211
  },
202
212
 
203
213
  getStorageValue: function(key, fallback) {
@@ -18,8 +18,8 @@
18
18
  /** @type {Map<Element, IntersectionObserver>} */
19
19
  observers: new Map(),
20
20
 
21
- init: function () {
22
- document.querySelectorAll('[data-glass-scroll]').forEach(el => {
21
+ init: function (root) {
22
+ window.Vanduo.queryAll(root, '[data-glass-scroll]').forEach(el => {
23
23
  if (this.observers.has(el)) return;
24
24
  this.initElement(el);
25
25
  });
@@ -20,12 +20,13 @@
20
20
  */
21
21
  const GridLayout = {
22
22
  instances: new Map(),
23
+ __vanduoScopedDestroyAll: true,
23
24
 
24
25
  /**
25
26
  * Initialize all grid layout containers
26
27
  */
27
- init: function () {
28
- const containers = document.querySelectorAll('[data-layout-mode]');
28
+ init: function (root) {
29
+ const containers = window.Vanduo.queryAll(root, '[data-layout-mode]');
29
30
 
30
31
  containers.forEach(function (container) {
31
32
  if (this.instances.has(container)) {
@@ -34,7 +35,7 @@
34
35
  this.initContainer(container);
35
36
  }.bind(this));
36
37
 
37
- this.initToggleButtons();
38
+ this.initToggleButtons(root);
38
39
  },
39
40
 
40
41
  /**
@@ -59,8 +60,10 @@
59
60
  /**
60
61
  * Initialize toggle buttons that target grid containers
61
62
  */
62
- initToggleButtons: function () {
63
- const toggleButtons = document.querySelectorAll('[data-grid-toggle]');
63
+ initToggleButtons: function (root) {
64
+ const toggleButtons = window.Vanduo && typeof window.Vanduo.queryAll === 'function'
65
+ ? window.Vanduo.queryAll(root, '[data-grid-toggle]')
66
+ : document.querySelectorAll('[data-grid-toggle]');
64
67
 
65
68
  toggleButtons.forEach(function (button) {
66
69
  if (button.getAttribute('data-grid-initialized') === 'true') {
@@ -253,12 +256,20 @@
253
256
  /**
254
257
  * Destroy all grid layout instances and clean up toggle buttons
255
258
  */
256
- destroyAll: function () {
259
+ destroyAll: function (root) {
260
+ const scope = window.Vanduo && typeof window.Vanduo._normalizeRoot === 'function'
261
+ ? window.Vanduo._normalizeRoot(root)
262
+ : (root || document);
263
+
257
264
  this.instances.forEach(function (instance, container) {
258
- this.destroy(container);
265
+ if (scope === document || scope === container || (typeof scope.contains === 'function' && scope.contains(container))) {
266
+ this.destroy(container);
267
+ }
259
268
  }.bind(this));
260
269
 
261
- const toggleButtons = document.querySelectorAll('[data-grid-initialized="true"]');
270
+ const toggleButtons = window.Vanduo && typeof window.Vanduo.queryAll === 'function'
271
+ ? window.Vanduo.queryAll(scope, '[data-grid-toggle][data-grid-initialized="true"]')
272
+ : document.querySelectorAll('[data-grid-initialized="true"]');
262
273
  toggleButtons.forEach(function (button) {
263
274
  if (button._gridCleanup) {
264
275
  button._gridCleanup();