@vanduo-oss/framework 1.5.1 → 1.7.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 (58) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +25 -154
  3. package/SKILL.md +58 -0
  4. package/css/components/accordion.css +80 -0
  5. package/css/components/alerts.css +65 -0
  6. package/css/components/avatar.css +2 -1
  7. package/css/components/badges.css +2 -1
  8. package/css/components/buttons.css +44 -0
  9. package/css/components/cards.css +26 -0
  10. package/css/components/code-snippet.css +23 -0
  11. package/css/components/collections.css +21 -1
  12. package/css/components/flow.css +20 -0
  13. package/css/components/forms.css +12 -1
  14. package/css/components/modals.css +69 -0
  15. package/css/components/navbar.css +11 -0
  16. package/css/components/popover.css +97 -0
  17. package/css/components/preloader.css +31 -0
  18. package/css/components/progress.css +46 -0
  19. package/css/components/separator.css +50 -0
  20. package/css/components/skeleton.css +42 -0
  21. package/css/components/slider.css +92 -0
  22. package/css/components/tabs.css +46 -0
  23. package/css/components/theme-customizer.css +53 -0
  24. package/css/core/colors-fib-base.css +524 -0
  25. package/css/core/colors-palette.css +786 -0
  26. package/css/core/colors.css +54 -46
  27. package/css/core/helpers.css +17 -0
  28. package/css/core/tokens.css +19 -19
  29. package/css/utilities/table.css +9 -0
  30. package/css/vanduo.css +11 -1
  31. package/dist/build-info.json +3 -3
  32. package/dist/vanduo-core.css +2147 -93
  33. package/dist/vanduo-core.css.map +1 -1
  34. package/dist/vanduo-core.min.css +2 -2
  35. package/dist/vanduo-core.min.css.map +1 -1
  36. package/dist/vanduo.cjs.js +387 -6
  37. package/dist/vanduo.cjs.js.map +3 -3
  38. package/dist/vanduo.cjs.min.js +16 -10
  39. package/dist/vanduo.cjs.min.js.map +4 -4
  40. package/dist/vanduo.css +2147 -93
  41. package/dist/vanduo.css.map +1 -1
  42. package/dist/vanduo.esm.js +387 -6
  43. package/dist/vanduo.esm.js.map +3 -3
  44. package/dist/vanduo.esm.min.js +16 -10
  45. package/dist/vanduo.esm.min.js.map +4 -4
  46. package/dist/vanduo.js +387 -6
  47. package/dist/vanduo.js.map +3 -3
  48. package/dist/vanduo.min.css +2 -2
  49. package/dist/vanduo.min.css.map +1 -1
  50. package/dist/vanduo.min.js +16 -10
  51. package/dist/vanduo.min.js.map +4 -4
  52. package/js/components/doc-search.js +9 -4
  53. package/js/components/popover.js +295 -0
  54. package/js/components/search.js +108 -0
  55. package/js/components/theme-customizer.js +54 -1
  56. package/js/index.js +2 -0
  57. package/js/utils/helpers.js +12 -4
  58. package/package.json +8 -4
@@ -1,4 +1,4 @@
1
- /*! Vanduo v1.5.1 | Built: 2026-06-20T15:00:18.257Z | git:baf8178 | development */
1
+ /*! Vanduo v1.7.0 | Built: 2026-06-29T06:28:14.462Z | git:c12c685 | development */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -201,7 +201,7 @@ module.exports = __toCommonJS(index_exports);
201
201
  // js/vanduo.js
202
202
  (function() {
203
203
  "use strict";
204
- const VANDUO_VERSION = true ? "1.5.1" : "0.0.0-dev";
204
+ const VANDUO_VERSION = true ? "1.7.0" : "0.0.0-dev";
205
205
  const hasOwn = Object.prototype.hasOwnProperty;
206
206
  const Vanduo2 = {
207
207
  version: VANDUO_VERSION,
@@ -4330,6 +4330,7 @@ module.exports = __toCommonJS(index_exports);
4330
4330
  const ThemeCustomizer = {
4331
4331
  // Storage keys
4332
4332
  STORAGE_KEYS: {
4333
+ PALETTE: "vanduo-palette",
4333
4334
  PRIMARY: "vanduo-primary-color",
4334
4335
  NEUTRAL: "vanduo-neutral-color",
4335
4336
  RADIUS: "vanduo-radius",
@@ -4338,6 +4339,7 @@ module.exports = __toCommonJS(index_exports);
4338
4339
  },
4339
4340
  // Default values
4340
4341
  DEFAULTS: {
4342
+ PALETTE: "open-color",
4341
4343
  PRIMARY_LIGHT: "black",
4342
4344
  PRIMARY_DARK: "amber",
4343
4345
  NEUTRAL: "charcoal",
@@ -4345,7 +4347,12 @@ module.exports = __toCommonJS(index_exports);
4345
4347
  FONT: "ubuntu",
4346
4348
  THEME: "system"
4347
4349
  },
4348
- // Primary color definitions (Open Color based)
4350
+ // Palette options (the Open Color default + the optional Fibonacci palette)
4351
+ PALETTE_OPTIONS: {
4352
+ "open-color": { name: "Open Color", description: "The Open Color palette (MIT) \u2014 the Vanduo default." },
4353
+ "fibonacci": { name: "Fibonacci", description: 'Golden-angle generated palette \u2014 optional, opt in via data-palette="fibonacci".' }
4354
+ },
4355
+ // Primary color definitions (palette-aware: shown over the active palette)
4349
4356
  PRIMARY_COLORS: {
4350
4357
  "black": { name: "Black", color: "#000000" },
4351
4358
  "red": { name: "Red", color: "#fa5252" },
@@ -4389,6 +4396,7 @@ module.exports = __toCommonJS(index_exports);
4389
4396
  THEME_MODES: ["system", "dark", "light"],
4390
4397
  // State
4391
4398
  state: {
4399
+ palette: null,
4392
4400
  primary: null,
4393
4401
  neutral: null,
4394
4402
  radius: null,
@@ -4489,6 +4497,7 @@ module.exports = __toCommonJS(index_exports);
4489
4497
  * Load preferences from localStorage
4490
4498
  */
4491
4499
  loadPreferences: function() {
4500
+ this.state.palette = this.getStorageValue(this.STORAGE_KEYS.PALETTE, this.DEFAULTS.PALETTE);
4492
4501
  this.state.theme = this.getStorageValue(this.STORAGE_KEYS.THEME, this.DEFAULTS.THEME);
4493
4502
  this.state.primary = this.getStorageValue(this.STORAGE_KEYS.PRIMARY, this.getDefaultPrimary(this.state.theme));
4494
4503
  this._normalizeDefaultPrimaryIfStaleWithStoredTheme();
@@ -4506,12 +4515,25 @@ module.exports = __toCommonJS(index_exports);
4506
4515
  * Apply all preferences
4507
4516
  */
4508
4517
  applyAllPreferences: function() {
4518
+ this.applyPalette(this.state.palette);
4509
4519
  this.applyPrimary(this.state.primary);
4510
4520
  this.applyNeutral(this.state.neutral);
4511
4521
  this.applyRadius(this.state.radius);
4512
4522
  this.applyFont(this.state.font);
4513
4523
  this.applyTheme(this.state.theme);
4514
4524
  },
4525
+ /**
4526
+ * Apply color palette (Fibonacci default / Open Color optional)
4527
+ */
4528
+ applyPalette: function(paletteKey) {
4529
+ if (!this.PALETTE_OPTIONS[paletteKey]) {
4530
+ paletteKey = this.DEFAULTS.PALETTE;
4531
+ }
4532
+ this.state.palette = paletteKey;
4533
+ document.documentElement.setAttribute("data-palette", paletteKey);
4534
+ this.savePreference(this.STORAGE_KEYS.PALETTE, paletteKey);
4535
+ this.dispatchEvent("palette-change", { palette: paletteKey });
4536
+ },
4515
4537
  /**
4516
4538
  * Apply primary color
4517
4539
  */
@@ -4714,6 +4736,12 @@ module.exports = __toCommonJS(index_exports);
4714
4736
  if (!this.elements.panel) return;
4715
4737
  if (this.elements.panel.getAttribute("data-customizer-initialized") === "true") return;
4716
4738
  this.elements.panel.setAttribute("data-customizer-initialized", "true");
4739
+ this.elements.panel.querySelectorAll("[data-palette]").forEach((btn) => {
4740
+ this.addListener(btn, "click", () => {
4741
+ this.applyPalette(btn.dataset.palette);
4742
+ this.updateUI();
4743
+ });
4744
+ });
4717
4745
  this.elements.panel.querySelectorAll("[data-color]").forEach((swatch) => {
4718
4746
  this.addListener(swatch, "click", () => {
4719
4747
  this.applyPrimary(swatch.dataset.color);
@@ -4773,6 +4801,10 @@ module.exports = __toCommonJS(index_exports);
4773
4801
  }
4774
4802
  return "#000000";
4775
4803
  };
4804
+ let paletteOptions = "";
4805
+ for (const [key, value] of Object.entries(this.PALETTE_OPTIONS)) {
4806
+ paletteOptions += `<button class="tc-palette-btn${key === this.state.palette ? " is-active" : ""}" data-palette="${esc(key)}" title="${esc(value.description)}">${esc(value.name)}</button>`;
4807
+ }
4776
4808
  let primarySwatches = "";
4777
4809
  for (const [key, value] of Object.entries(this.PRIMARY_COLORS)) {
4778
4810
  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>`;
@@ -4798,6 +4830,12 @@ module.exports = __toCommonJS(index_exports);
4798
4830
  </div>
4799
4831
  <div class="tc-body">
4800
4832
 
4833
+ <div class="tc-section">
4834
+ <label class="tc-label">Palette</label>
4835
+ <div class="tc-palette-group">
4836
+ ${paletteOptions}
4837
+ </div>
4838
+ </div>
4801
4839
  <div class="tc-section">
4802
4840
  <label class="tc-label">Primary Color</label>
4803
4841
  <div class="tc-color-grid">
@@ -4963,6 +5001,9 @@ module.exports = __toCommonJS(index_exports);
4963
5001
  */
4964
5002
  updateUI: function() {
4965
5003
  if (!this.elements.panel) return;
5004
+ this.elements.panel.querySelectorAll("[data-palette]").forEach((btn) => {
5005
+ btn.classList.toggle("is-active", btn.dataset.palette === this.state.palette);
5006
+ });
4966
5007
  this.elements.panel.querySelectorAll("[data-color]").forEach((swatch) => {
4967
5008
  swatch.classList.toggle("is-active", swatch.dataset.color === this.state.primary);
4968
5009
  });
@@ -4981,6 +5022,7 @@ module.exports = __toCommonJS(index_exports);
4981
5022
  * Reset all preferences to defaults
4982
5023
  */
4983
5024
  reset: function() {
5025
+ this.applyPalette(this.DEFAULTS.PALETTE);
4984
5026
  this.applyTheme(this.DEFAULTS.THEME);
4985
5027
  this.applyPrimary(this.getDefaultPrimary(this.DEFAULTS.THEME));
4986
5028
  this.applyNeutral(this.DEFAULTS.NEUTRAL);
@@ -4999,6 +5041,7 @@ module.exports = __toCommonJS(index_exports);
4999
5041
  * Programmatically set preferences
5000
5042
  */
5001
5043
  setPreferences: function(prefs) {
5044
+ if (prefs.palette) this.applyPalette(prefs.palette);
5002
5045
  if (prefs.primary) this.applyPrimary(prefs.primary);
5003
5046
  if (prefs.neutral) this.applyNeutral(prefs.neutral);
5004
5047
  if (prefs.radius) this.applyRadius(prefs.radius);
@@ -6456,9 +6499,8 @@ module.exports = __toCommonJS(index_exports);
6456
6499
  return escaped;
6457
6500
  }
6458
6501
  function escapeHtml2(text) {
6459
- const div = document.createElement("div");
6460
- div.textContent = text;
6461
- return div.innerHTML;
6502
+ if (text === null || text === void 0) return "";
6503
+ return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
6462
6504
  }
6463
6505
  function navigate(direction) {
6464
6506
  let newIndex = state.activeIndex + direction;
@@ -10675,6 +10717,345 @@ module.exports = __toCommonJS(index_exports);
10675
10717
  window.VanduoSpotlight = Spotlight;
10676
10718
  })();
10677
10719
 
10720
+ // js/components/popover.js
10721
+ (function() {
10722
+ "use strict";
10723
+ const PLACEMENTS = ["top", "bottom", "left", "right"];
10724
+ const TRIGGERS = ["click", "hover", "focus"];
10725
+ const DEFAULT_GAP = 8;
10726
+ function resolvePlacement(preferred) {
10727
+ return PLACEMENTS.indexOf(preferred) !== -1 ? preferred : "bottom";
10728
+ }
10729
+ function resolveTrigger(value) {
10730
+ if (!value) return ["click", "focus"];
10731
+ return value.split(/\s+/).filter(function(t) {
10732
+ return TRIGGERS.indexOf(t) !== -1;
10733
+ });
10734
+ }
10735
+ function findPanel(trigger) {
10736
+ const targetSelector = trigger.getAttribute("data-vd-popover-target");
10737
+ if (!targetSelector) return null;
10738
+ const doc = trigger.ownerDocument || document;
10739
+ const panel = doc.querySelector(targetSelector);
10740
+ if (!panel) return null;
10741
+ if (!panel.classList.contains("vd-popover-panel")) {
10742
+ panel.classList.add("vd-popover-panel");
10743
+ }
10744
+ return panel;
10745
+ }
10746
+ const Popover = {
10747
+ instances: /* @__PURE__ */ new Map(),
10748
+ _globalCleanups: [],
10749
+ init: function(root) {
10750
+ const scope = root || document;
10751
+ const triggers = scope.querySelectorAll ? scope.querySelectorAll(".vd-popover-trigger") : document.querySelectorAll(".vd-popover-trigger");
10752
+ Array.prototype.forEach.call(triggers, function(trigger) {
10753
+ if (Popover.instances.has(trigger)) return;
10754
+ Popover.initInstance(trigger);
10755
+ });
10756
+ if (Popover._globalCleanups.length === 0) {
10757
+ const outsideClick = function(event) {
10758
+ Popover.instances.forEach(function(inst, trigger) {
10759
+ if (inst.trigger !== "click") return;
10760
+ if (inst.panel.contains(event.target) || trigger.contains(event.target)) return;
10761
+ Popover.hide(trigger);
10762
+ });
10763
+ };
10764
+ const escHandler = function(event) {
10765
+ if (event.key !== "Escape") return;
10766
+ let lastOpen = null;
10767
+ Popover.instances.forEach(function(inst, trigger) {
10768
+ if (inst.panel.hasAttribute("hidden") === false) lastOpen = trigger;
10769
+ });
10770
+ if (lastOpen) Popover.hide(lastOpen);
10771
+ };
10772
+ document.addEventListener("click", outsideClick, true);
10773
+ document.addEventListener("keydown", escHandler);
10774
+ Popover._globalCleanups.push(function() {
10775
+ document.removeEventListener("click", outsideClick, true);
10776
+ });
10777
+ Popover._globalCleanups.push(function() {
10778
+ document.removeEventListener("keydown", escHandler);
10779
+ });
10780
+ }
10781
+ },
10782
+ initInstance: function(trigger) {
10783
+ const panel = findPanel(trigger);
10784
+ if (!panel) return;
10785
+ const cleanup = [];
10786
+ const placement = resolvePlacement(trigger.getAttribute("data-vd-popover-placement"));
10787
+ const triggers = resolveTrigger(trigger.getAttribute("data-vd-popover-trigger"));
10788
+ const allowFlip = trigger.getAttribute("data-vd-popover-flip") !== "false";
10789
+ if (!panel.id) {
10790
+ panel.id = "vd-popover-" + Math.random().toString(36).slice(2, 9);
10791
+ }
10792
+ if (!panel.hasAttribute("role")) panel.setAttribute("role", "dialog");
10793
+ if (!panel.hasAttribute("aria-modal")) panel.setAttribute("aria-modal", "false");
10794
+ trigger.setAttribute("aria-haspopup", "dialog");
10795
+ trigger.setAttribute("aria-expanded", "false");
10796
+ trigger.setAttribute("aria-controls", panel.id);
10797
+ if (triggers.indexOf("click") !== -1) {
10798
+ const clickHandler = function(event) {
10799
+ event.stopPropagation();
10800
+ const expanded = trigger.getAttribute("aria-expanded") === "true";
10801
+ if (expanded) Popover.hide(trigger);
10802
+ else {
10803
+ Popover._closeOthers(trigger);
10804
+ Popover.show(trigger);
10805
+ }
10806
+ };
10807
+ trigger.addEventListener("click", clickHandler);
10808
+ cleanup.push(function() {
10809
+ trigger.removeEventListener("click", clickHandler);
10810
+ });
10811
+ }
10812
+ if (triggers.indexOf("hover") !== -1) {
10813
+ const enterHandler = function() {
10814
+ Popover.show(trigger);
10815
+ };
10816
+ const leaveHandler = function() {
10817
+ setTimeout(function() {
10818
+ if (!panel.matches(":hover") && !trigger.matches(":hover")) Popover.hide(trigger);
10819
+ }, 80);
10820
+ };
10821
+ trigger.addEventListener("mouseenter", enterHandler);
10822
+ trigger.addEventListener("mouseleave", leaveHandler);
10823
+ panel.addEventListener("mouseenter", enterHandler);
10824
+ panel.addEventListener("mouseleave", leaveHandler);
10825
+ cleanup.push(function() {
10826
+ trigger.removeEventListener("mouseenter", enterHandler);
10827
+ });
10828
+ cleanup.push(function() {
10829
+ trigger.removeEventListener("mouseleave", leaveHandler);
10830
+ });
10831
+ cleanup.push(function() {
10832
+ panel.removeEventListener("mouseenter", enterHandler);
10833
+ });
10834
+ cleanup.push(function() {
10835
+ panel.removeEventListener("mouseleave", leaveHandler);
10836
+ });
10837
+ }
10838
+ if (triggers.indexOf("focus") !== -1) {
10839
+ const focusHandler = function() {
10840
+ Popover.show(trigger);
10841
+ };
10842
+ const blurHandler = function(event) {
10843
+ if (panel.contains(event.relatedTarget)) return;
10844
+ Popover.hide(trigger);
10845
+ };
10846
+ trigger.addEventListener("focus", focusHandler);
10847
+ trigger.addEventListener("blur", blurHandler);
10848
+ cleanup.push(function() {
10849
+ trigger.removeEventListener("focus", focusHandler);
10850
+ });
10851
+ cleanup.push(function() {
10852
+ trigger.removeEventListener("blur", blurHandler);
10853
+ });
10854
+ }
10855
+ const resizeHandler = function() {
10856
+ Popover.flipPlacement(trigger);
10857
+ };
10858
+ window.addEventListener("resize", resizeHandler);
10859
+ window.addEventListener("scroll", resizeHandler, true);
10860
+ cleanup.push(function() {
10861
+ window.removeEventListener("resize", resizeHandler);
10862
+ });
10863
+ cleanup.push(function() {
10864
+ window.removeEventListener("scroll", resizeHandler, true);
10865
+ });
10866
+ Popover.instances.set(trigger, {
10867
+ panel,
10868
+ cleanup,
10869
+ placement,
10870
+ trigger: triggers.join(" "),
10871
+ allowFlip
10872
+ });
10873
+ },
10874
+ show: function(trigger) {
10875
+ const inst = Popover.instances.get(trigger);
10876
+ if (!inst) return;
10877
+ inst.panel.hidden = false;
10878
+ requestAnimationFrame(function() {
10879
+ Popover.position(trigger, inst.panel, inst.placement);
10880
+ trigger.setAttribute("aria-expanded", "true");
10881
+ inst.panel.setAttribute("data-placement", inst.placement);
10882
+ trigger.dispatchEvent(new CustomEvent("popover:show", {
10883
+ bubbles: true,
10884
+ detail: { trigger, placement: inst.placement }
10885
+ }));
10886
+ });
10887
+ },
10888
+ hide: function(trigger) {
10889
+ const inst = Popover.instances.get(trigger);
10890
+ if (!inst) return;
10891
+ inst.panel.hidden = true;
10892
+ trigger.setAttribute("aria-expanded", "false");
10893
+ trigger.dispatchEvent(new CustomEvent("popover:hide", {
10894
+ bubbles: true,
10895
+ detail: { trigger }
10896
+ }));
10897
+ },
10898
+ _closeOthers: function(currentTrigger) {
10899
+ Popover.instances.forEach(function(_inst, trigger) {
10900
+ if (trigger !== currentTrigger) Popover.hide(trigger);
10901
+ });
10902
+ },
10903
+ position: function(trigger, panel, placement) {
10904
+ const rect = trigger.getBoundingClientRect();
10905
+ const popRect = panel.getBoundingClientRect();
10906
+ const gap = DEFAULT_GAP;
10907
+ let top;
10908
+ let left;
10909
+ const win = panel.ownerDocument.defaultView || window;
10910
+ const scrollX = win.pageXOffset || 0;
10911
+ const scrollY = win.pageYOffset || 0;
10912
+ switch (placement) {
10913
+ case "top":
10914
+ top = rect.top - popRect.height - gap + scrollY;
10915
+ left = rect.left + (rect.width - popRect.width) / 2 + scrollX;
10916
+ break;
10917
+ case "left":
10918
+ top = rect.top + (rect.height - popRect.height) / 2 + scrollY;
10919
+ left = rect.left - popRect.width - gap + scrollX;
10920
+ break;
10921
+ case "right":
10922
+ top = rect.top + (rect.height - popRect.height) / 2 + scrollY;
10923
+ left = rect.right + gap + scrollX;
10924
+ break;
10925
+ default:
10926
+ top = rect.bottom + gap + scrollY;
10927
+ left = rect.left + (rect.width - popRect.width) / 2 + scrollX;
10928
+ }
10929
+ left = Math.max(8, Math.min(left, win.innerWidth - popRect.width - 8));
10930
+ top = Math.max(8, top);
10931
+ panel.style.position = "absolute";
10932
+ panel.style.top = top + "px";
10933
+ panel.style.left = left + "px";
10934
+ },
10935
+ flipPlacement: function(trigger) {
10936
+ const inst = Popover.instances.get(trigger);
10937
+ if (!inst || !inst.allowFlip) return;
10938
+ if (inst.panel.hidden) return;
10939
+ const win = inst.panel.ownerDocument.defaultView || window;
10940
+ const rect = trigger.getBoundingClientRect();
10941
+ const popRect = inst.panel.getBoundingClientRect();
10942
+ const gap = DEFAULT_GAP;
10943
+ let flipped = null;
10944
+ const current = inst.placement;
10945
+ if (current === "top" && rect.top - popRect.height - gap < 0) flipped = "bottom";
10946
+ else if (current === "bottom" && rect.bottom + popRect.height + gap > win.innerHeight) flipped = "top";
10947
+ else if (current === "left" && rect.left - popRect.width - gap < 0) flipped = "right";
10948
+ else if (current === "right" && rect.right + popRect.width + gap > win.innerWidth) flipped = "left";
10949
+ if (flipped) {
10950
+ inst.placement = flipped;
10951
+ Popover.position(trigger, inst.panel, flipped);
10952
+ inst.panel.setAttribute("data-placement", flipped);
10953
+ }
10954
+ },
10955
+ destroy: function(trigger) {
10956
+ const inst = Popover.instances.get(trigger);
10957
+ if (!inst) return;
10958
+ inst.cleanup.forEach(function(fn) {
10959
+ fn();
10960
+ });
10961
+ Popover.hide(trigger);
10962
+ trigger.removeAttribute("aria-haspopup");
10963
+ trigger.removeAttribute("aria-expanded");
10964
+ trigger.removeAttribute("aria-controls");
10965
+ Popover.instances.delete(trigger);
10966
+ },
10967
+ destroyAll: function(root) {
10968
+ const triggers = [];
10969
+ Popover.instances.forEach(function(_inst, trigger) {
10970
+ if (!root || trigger.closest && trigger.closest(root)) triggers.push(trigger);
10971
+ });
10972
+ triggers.forEach(function(trigger) {
10973
+ Popover.destroy(trigger);
10974
+ });
10975
+ if (Popover.instances.size === 0) {
10976
+ Popover._globalCleanups.forEach(function(fn) {
10977
+ fn();
10978
+ });
10979
+ Popover._globalCleanups = [];
10980
+ }
10981
+ }
10982
+ };
10983
+ if (typeof window !== "undefined") {
10984
+ if (typeof window.Vanduo !== "undefined" && typeof window.Vanduo.register === "function") {
10985
+ window.Vanduo.register("popover", Popover);
10986
+ }
10987
+ window.VanduoPopover = Popover;
10988
+ }
10989
+ })();
10990
+
10991
+ // js/components/search.js
10992
+ (function() {
10993
+ "use strict";
10994
+ const DEFAULT_LIMIT = 10;
10995
+ const sources = /* @__PURE__ */ new Map();
10996
+ function register(source) {
10997
+ if (!source || typeof source.name !== "string" || source.name.length === 0) {
10998
+ throw new Error("VanduoSearch.register: source.name is required");
10999
+ }
11000
+ if (typeof source.fetch !== "function") {
11001
+ throw new Error("VanduoSearch.register: source.fetch must be a function");
11002
+ }
11003
+ if (sources.has(source.name)) {
11004
+ throw new Error('VanduoSearch.register: source "' + source.name + '" already registered');
11005
+ }
11006
+ sources.set(source.name, Object.freeze({
11007
+ name: source.name,
11008
+ label: source.label || source.name,
11009
+ icon: source.icon || null,
11010
+ limit: typeof source.limit === "number" ? source.limit : DEFAULT_LIMIT,
11011
+ fetch: source.fetch
11012
+ }));
11013
+ }
11014
+ function unregister(name) {
11015
+ return sources.delete(name);
11016
+ }
11017
+ function list() {
11018
+ return Object.freeze(Array.from(sources.values()));
11019
+ }
11020
+ function query(text, options) {
11021
+ options = options || {};
11022
+ const signal = options.signal;
11023
+ const limitPerSource = typeof options.limitPerSource === "number" ? options.limitPerSource : null;
11024
+ const queryText = (text || "").trim();
11025
+ const allSources = Array.from(sources.values());
11026
+ if (queryText.length === 0) {
11027
+ return Promise.resolve({
11028
+ text: queryText,
11029
+ sources: allSources.map(function(src) {
11030
+ return { name: src.name, label: src.label, results: [] };
11031
+ })
11032
+ });
11033
+ }
11034
+ const promises = allSources.map(function(src) {
11035
+ const effectiveLimit = limitPerSource != null ? limitPerSource : src.limit;
11036
+ return Promise.resolve().then(function() {
11037
+ return src.fetch(queryText, { signal, limit: effectiveLimit });
11038
+ }).then(function(results) {
11039
+ const safe = Array.isArray(results) ? results : [];
11040
+ return { name: src.name, label: src.label, results: safe };
11041
+ }).catch(function(error) {
11042
+ if (error && error.name === "AbortError") throw error;
11043
+ return { name: src.name, label: src.label, results: [], error: error.message || "fetch failed" };
11044
+ });
11045
+ });
11046
+ return Promise.all(promises).then(function(perSource) {
11047
+ return { text: queryText, sources: perSource };
11048
+ });
11049
+ }
11050
+ const Search = { register, unregister, list, query };
11051
+ if (typeof window !== "undefined") {
11052
+ if (typeof window.Vanduo !== "undefined" && typeof window.Vanduo.register === "function") {
11053
+ window.Vanduo.register("search", Search);
11054
+ }
11055
+ window.VanduoSearch = Search;
11056
+ }
11057
+ })();
11058
+
10678
11059
  // js/index.js
10679
11060
  var Vanduo = window.Vanduo;
10680
11061
  var index_default = Vanduo;