@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
package/dist/vanduo.js CHANGED
@@ -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
  (() => {
3
3
  // js/utils/lifecycle.js
4
4
  (function() {
@@ -176,7 +176,7 @@
176
176
  // js/vanduo.js
177
177
  (function() {
178
178
  "use strict";
179
- const VANDUO_VERSION = true ? "1.5.1" : "0.0.0-dev";
179
+ const VANDUO_VERSION = true ? "1.7.0" : "0.0.0-dev";
180
180
  const hasOwn = Object.prototype.hasOwnProperty;
181
181
  const Vanduo2 = {
182
182
  version: VANDUO_VERSION,
@@ -4305,6 +4305,7 @@
4305
4305
  const ThemeCustomizer = {
4306
4306
  // Storage keys
4307
4307
  STORAGE_KEYS: {
4308
+ PALETTE: "vanduo-palette",
4308
4309
  PRIMARY: "vanduo-primary-color",
4309
4310
  NEUTRAL: "vanduo-neutral-color",
4310
4311
  RADIUS: "vanduo-radius",
@@ -4313,6 +4314,7 @@
4313
4314
  },
4314
4315
  // Default values
4315
4316
  DEFAULTS: {
4317
+ PALETTE: "open-color",
4316
4318
  PRIMARY_LIGHT: "black",
4317
4319
  PRIMARY_DARK: "amber",
4318
4320
  NEUTRAL: "charcoal",
@@ -4320,7 +4322,12 @@
4320
4322
  FONT: "ubuntu",
4321
4323
  THEME: "system"
4322
4324
  },
4323
- // Primary color definitions (Open Color based)
4325
+ // Palette options (the Open Color default + the optional Fibonacci palette)
4326
+ PALETTE_OPTIONS: {
4327
+ "open-color": { name: "Open Color", description: "The Open Color palette (MIT) \u2014 the Vanduo default." },
4328
+ "fibonacci": { name: "Fibonacci", description: 'Golden-angle generated palette \u2014 optional, opt in via data-palette="fibonacci".' }
4329
+ },
4330
+ // Primary color definitions (palette-aware: shown over the active palette)
4324
4331
  PRIMARY_COLORS: {
4325
4332
  "black": { name: "Black", color: "#000000" },
4326
4333
  "red": { name: "Red", color: "#fa5252" },
@@ -4364,6 +4371,7 @@
4364
4371
  THEME_MODES: ["system", "dark", "light"],
4365
4372
  // State
4366
4373
  state: {
4374
+ palette: null,
4367
4375
  primary: null,
4368
4376
  neutral: null,
4369
4377
  radius: null,
@@ -4464,6 +4472,7 @@
4464
4472
  * Load preferences from localStorage
4465
4473
  */
4466
4474
  loadPreferences: function() {
4475
+ this.state.palette = this.getStorageValue(this.STORAGE_KEYS.PALETTE, this.DEFAULTS.PALETTE);
4467
4476
  this.state.theme = this.getStorageValue(this.STORAGE_KEYS.THEME, this.DEFAULTS.THEME);
4468
4477
  this.state.primary = this.getStorageValue(this.STORAGE_KEYS.PRIMARY, this.getDefaultPrimary(this.state.theme));
4469
4478
  this._normalizeDefaultPrimaryIfStaleWithStoredTheme();
@@ -4481,12 +4490,25 @@
4481
4490
  * Apply all preferences
4482
4491
  */
4483
4492
  applyAllPreferences: function() {
4493
+ this.applyPalette(this.state.palette);
4484
4494
  this.applyPrimary(this.state.primary);
4485
4495
  this.applyNeutral(this.state.neutral);
4486
4496
  this.applyRadius(this.state.radius);
4487
4497
  this.applyFont(this.state.font);
4488
4498
  this.applyTheme(this.state.theme);
4489
4499
  },
4500
+ /**
4501
+ * Apply color palette (Fibonacci default / Open Color optional)
4502
+ */
4503
+ applyPalette: function(paletteKey) {
4504
+ if (!this.PALETTE_OPTIONS[paletteKey]) {
4505
+ paletteKey = this.DEFAULTS.PALETTE;
4506
+ }
4507
+ this.state.palette = paletteKey;
4508
+ document.documentElement.setAttribute("data-palette", paletteKey);
4509
+ this.savePreference(this.STORAGE_KEYS.PALETTE, paletteKey);
4510
+ this.dispatchEvent("palette-change", { palette: paletteKey });
4511
+ },
4490
4512
  /**
4491
4513
  * Apply primary color
4492
4514
  */
@@ -4689,6 +4711,12 @@
4689
4711
  if (!this.elements.panel) return;
4690
4712
  if (this.elements.panel.getAttribute("data-customizer-initialized") === "true") return;
4691
4713
  this.elements.panel.setAttribute("data-customizer-initialized", "true");
4714
+ this.elements.panel.querySelectorAll("[data-palette]").forEach((btn) => {
4715
+ this.addListener(btn, "click", () => {
4716
+ this.applyPalette(btn.dataset.palette);
4717
+ this.updateUI();
4718
+ });
4719
+ });
4692
4720
  this.elements.panel.querySelectorAll("[data-color]").forEach((swatch) => {
4693
4721
  this.addListener(swatch, "click", () => {
4694
4722
  this.applyPrimary(swatch.dataset.color);
@@ -4748,6 +4776,10 @@
4748
4776
  }
4749
4777
  return "#000000";
4750
4778
  };
4779
+ let paletteOptions = "";
4780
+ for (const [key, value] of Object.entries(this.PALETTE_OPTIONS)) {
4781
+ paletteOptions += `<button class="tc-palette-btn${key === this.state.palette ? " is-active" : ""}" data-palette="${esc(key)}" title="${esc(value.description)}">${esc(value.name)}</button>`;
4782
+ }
4751
4783
  let primarySwatches = "";
4752
4784
  for (const [key, value] of Object.entries(this.PRIMARY_COLORS)) {
4753
4785
  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>`;
@@ -4773,6 +4805,12 @@
4773
4805
  </div>
4774
4806
  <div class="tc-body">
4775
4807
 
4808
+ <div class="tc-section">
4809
+ <label class="tc-label">Palette</label>
4810
+ <div class="tc-palette-group">
4811
+ ${paletteOptions}
4812
+ </div>
4813
+ </div>
4776
4814
  <div class="tc-section">
4777
4815
  <label class="tc-label">Primary Color</label>
4778
4816
  <div class="tc-color-grid">
@@ -4938,6 +4976,9 @@
4938
4976
  */
4939
4977
  updateUI: function() {
4940
4978
  if (!this.elements.panel) return;
4979
+ this.elements.panel.querySelectorAll("[data-palette]").forEach((btn) => {
4980
+ btn.classList.toggle("is-active", btn.dataset.palette === this.state.palette);
4981
+ });
4941
4982
  this.elements.panel.querySelectorAll("[data-color]").forEach((swatch) => {
4942
4983
  swatch.classList.toggle("is-active", swatch.dataset.color === this.state.primary);
4943
4984
  });
@@ -4956,6 +4997,7 @@
4956
4997
  * Reset all preferences to defaults
4957
4998
  */
4958
4999
  reset: function() {
5000
+ this.applyPalette(this.DEFAULTS.PALETTE);
4959
5001
  this.applyTheme(this.DEFAULTS.THEME);
4960
5002
  this.applyPrimary(this.getDefaultPrimary(this.DEFAULTS.THEME));
4961
5003
  this.applyNeutral(this.DEFAULTS.NEUTRAL);
@@ -4974,6 +5016,7 @@
4974
5016
  * Programmatically set preferences
4975
5017
  */
4976
5018
  setPreferences: function(prefs) {
5019
+ if (prefs.palette) this.applyPalette(prefs.palette);
4977
5020
  if (prefs.primary) this.applyPrimary(prefs.primary);
4978
5021
  if (prefs.neutral) this.applyNeutral(prefs.neutral);
4979
5022
  if (prefs.radius) this.applyRadius(prefs.radius);
@@ -6431,9 +6474,8 @@
6431
6474
  return escaped;
6432
6475
  }
6433
6476
  function escapeHtml2(text) {
6434
- const div = document.createElement("div");
6435
- div.textContent = text;
6436
- return div.innerHTML;
6477
+ if (text === null || text === void 0) return "";
6478
+ return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
6437
6479
  }
6438
6480
  function navigate(direction) {
6439
6481
  let newIndex = state.activeIndex + direction;
@@ -10650,6 +10692,345 @@
10650
10692
  window.VanduoSpotlight = Spotlight;
10651
10693
  })();
10652
10694
 
10695
+ // js/components/popover.js
10696
+ (function() {
10697
+ "use strict";
10698
+ const PLACEMENTS = ["top", "bottom", "left", "right"];
10699
+ const TRIGGERS = ["click", "hover", "focus"];
10700
+ const DEFAULT_GAP = 8;
10701
+ function resolvePlacement(preferred) {
10702
+ return PLACEMENTS.indexOf(preferred) !== -1 ? preferred : "bottom";
10703
+ }
10704
+ function resolveTrigger(value) {
10705
+ if (!value) return ["click", "focus"];
10706
+ return value.split(/\s+/).filter(function(t) {
10707
+ return TRIGGERS.indexOf(t) !== -1;
10708
+ });
10709
+ }
10710
+ function findPanel(trigger) {
10711
+ const targetSelector = trigger.getAttribute("data-vd-popover-target");
10712
+ if (!targetSelector) return null;
10713
+ const doc = trigger.ownerDocument || document;
10714
+ const panel = doc.querySelector(targetSelector);
10715
+ if (!panel) return null;
10716
+ if (!panel.classList.contains("vd-popover-panel")) {
10717
+ panel.classList.add("vd-popover-panel");
10718
+ }
10719
+ return panel;
10720
+ }
10721
+ const Popover = {
10722
+ instances: /* @__PURE__ */ new Map(),
10723
+ _globalCleanups: [],
10724
+ init: function(root) {
10725
+ const scope = root || document;
10726
+ const triggers = scope.querySelectorAll ? scope.querySelectorAll(".vd-popover-trigger") : document.querySelectorAll(".vd-popover-trigger");
10727
+ Array.prototype.forEach.call(triggers, function(trigger) {
10728
+ if (Popover.instances.has(trigger)) return;
10729
+ Popover.initInstance(trigger);
10730
+ });
10731
+ if (Popover._globalCleanups.length === 0) {
10732
+ const outsideClick = function(event) {
10733
+ Popover.instances.forEach(function(inst, trigger) {
10734
+ if (inst.trigger !== "click") return;
10735
+ if (inst.panel.contains(event.target) || trigger.contains(event.target)) return;
10736
+ Popover.hide(trigger);
10737
+ });
10738
+ };
10739
+ const escHandler = function(event) {
10740
+ if (event.key !== "Escape") return;
10741
+ let lastOpen = null;
10742
+ Popover.instances.forEach(function(inst, trigger) {
10743
+ if (inst.panel.hasAttribute("hidden") === false) lastOpen = trigger;
10744
+ });
10745
+ if (lastOpen) Popover.hide(lastOpen);
10746
+ };
10747
+ document.addEventListener("click", outsideClick, true);
10748
+ document.addEventListener("keydown", escHandler);
10749
+ Popover._globalCleanups.push(function() {
10750
+ document.removeEventListener("click", outsideClick, true);
10751
+ });
10752
+ Popover._globalCleanups.push(function() {
10753
+ document.removeEventListener("keydown", escHandler);
10754
+ });
10755
+ }
10756
+ },
10757
+ initInstance: function(trigger) {
10758
+ const panel = findPanel(trigger);
10759
+ if (!panel) return;
10760
+ const cleanup = [];
10761
+ const placement = resolvePlacement(trigger.getAttribute("data-vd-popover-placement"));
10762
+ const triggers = resolveTrigger(trigger.getAttribute("data-vd-popover-trigger"));
10763
+ const allowFlip = trigger.getAttribute("data-vd-popover-flip") !== "false";
10764
+ if (!panel.id) {
10765
+ panel.id = "vd-popover-" + Math.random().toString(36).slice(2, 9);
10766
+ }
10767
+ if (!panel.hasAttribute("role")) panel.setAttribute("role", "dialog");
10768
+ if (!panel.hasAttribute("aria-modal")) panel.setAttribute("aria-modal", "false");
10769
+ trigger.setAttribute("aria-haspopup", "dialog");
10770
+ trigger.setAttribute("aria-expanded", "false");
10771
+ trigger.setAttribute("aria-controls", panel.id);
10772
+ if (triggers.indexOf("click") !== -1) {
10773
+ const clickHandler = function(event) {
10774
+ event.stopPropagation();
10775
+ const expanded = trigger.getAttribute("aria-expanded") === "true";
10776
+ if (expanded) Popover.hide(trigger);
10777
+ else {
10778
+ Popover._closeOthers(trigger);
10779
+ Popover.show(trigger);
10780
+ }
10781
+ };
10782
+ trigger.addEventListener("click", clickHandler);
10783
+ cleanup.push(function() {
10784
+ trigger.removeEventListener("click", clickHandler);
10785
+ });
10786
+ }
10787
+ if (triggers.indexOf("hover") !== -1) {
10788
+ const enterHandler = function() {
10789
+ Popover.show(trigger);
10790
+ };
10791
+ const leaveHandler = function() {
10792
+ setTimeout(function() {
10793
+ if (!panel.matches(":hover") && !trigger.matches(":hover")) Popover.hide(trigger);
10794
+ }, 80);
10795
+ };
10796
+ trigger.addEventListener("mouseenter", enterHandler);
10797
+ trigger.addEventListener("mouseleave", leaveHandler);
10798
+ panel.addEventListener("mouseenter", enterHandler);
10799
+ panel.addEventListener("mouseleave", leaveHandler);
10800
+ cleanup.push(function() {
10801
+ trigger.removeEventListener("mouseenter", enterHandler);
10802
+ });
10803
+ cleanup.push(function() {
10804
+ trigger.removeEventListener("mouseleave", leaveHandler);
10805
+ });
10806
+ cleanup.push(function() {
10807
+ panel.removeEventListener("mouseenter", enterHandler);
10808
+ });
10809
+ cleanup.push(function() {
10810
+ panel.removeEventListener("mouseleave", leaveHandler);
10811
+ });
10812
+ }
10813
+ if (triggers.indexOf("focus") !== -1) {
10814
+ const focusHandler = function() {
10815
+ Popover.show(trigger);
10816
+ };
10817
+ const blurHandler = function(event) {
10818
+ if (panel.contains(event.relatedTarget)) return;
10819
+ Popover.hide(trigger);
10820
+ };
10821
+ trigger.addEventListener("focus", focusHandler);
10822
+ trigger.addEventListener("blur", blurHandler);
10823
+ cleanup.push(function() {
10824
+ trigger.removeEventListener("focus", focusHandler);
10825
+ });
10826
+ cleanup.push(function() {
10827
+ trigger.removeEventListener("blur", blurHandler);
10828
+ });
10829
+ }
10830
+ const resizeHandler = function() {
10831
+ Popover.flipPlacement(trigger);
10832
+ };
10833
+ window.addEventListener("resize", resizeHandler);
10834
+ window.addEventListener("scroll", resizeHandler, true);
10835
+ cleanup.push(function() {
10836
+ window.removeEventListener("resize", resizeHandler);
10837
+ });
10838
+ cleanup.push(function() {
10839
+ window.removeEventListener("scroll", resizeHandler, true);
10840
+ });
10841
+ Popover.instances.set(trigger, {
10842
+ panel,
10843
+ cleanup,
10844
+ placement,
10845
+ trigger: triggers.join(" "),
10846
+ allowFlip
10847
+ });
10848
+ },
10849
+ show: function(trigger) {
10850
+ const inst = Popover.instances.get(trigger);
10851
+ if (!inst) return;
10852
+ inst.panel.hidden = false;
10853
+ requestAnimationFrame(function() {
10854
+ Popover.position(trigger, inst.panel, inst.placement);
10855
+ trigger.setAttribute("aria-expanded", "true");
10856
+ inst.panel.setAttribute("data-placement", inst.placement);
10857
+ trigger.dispatchEvent(new CustomEvent("popover:show", {
10858
+ bubbles: true,
10859
+ detail: { trigger, placement: inst.placement }
10860
+ }));
10861
+ });
10862
+ },
10863
+ hide: function(trigger) {
10864
+ const inst = Popover.instances.get(trigger);
10865
+ if (!inst) return;
10866
+ inst.panel.hidden = true;
10867
+ trigger.setAttribute("aria-expanded", "false");
10868
+ trigger.dispatchEvent(new CustomEvent("popover:hide", {
10869
+ bubbles: true,
10870
+ detail: { trigger }
10871
+ }));
10872
+ },
10873
+ _closeOthers: function(currentTrigger) {
10874
+ Popover.instances.forEach(function(_inst, trigger) {
10875
+ if (trigger !== currentTrigger) Popover.hide(trigger);
10876
+ });
10877
+ },
10878
+ position: function(trigger, panel, placement) {
10879
+ const rect = trigger.getBoundingClientRect();
10880
+ const popRect = panel.getBoundingClientRect();
10881
+ const gap = DEFAULT_GAP;
10882
+ let top;
10883
+ let left;
10884
+ const win = panel.ownerDocument.defaultView || window;
10885
+ const scrollX = win.pageXOffset || 0;
10886
+ const scrollY = win.pageYOffset || 0;
10887
+ switch (placement) {
10888
+ case "top":
10889
+ top = rect.top - popRect.height - gap + scrollY;
10890
+ left = rect.left + (rect.width - popRect.width) / 2 + scrollX;
10891
+ break;
10892
+ case "left":
10893
+ top = rect.top + (rect.height - popRect.height) / 2 + scrollY;
10894
+ left = rect.left - popRect.width - gap + scrollX;
10895
+ break;
10896
+ case "right":
10897
+ top = rect.top + (rect.height - popRect.height) / 2 + scrollY;
10898
+ left = rect.right + gap + scrollX;
10899
+ break;
10900
+ default:
10901
+ top = rect.bottom + gap + scrollY;
10902
+ left = rect.left + (rect.width - popRect.width) / 2 + scrollX;
10903
+ }
10904
+ left = Math.max(8, Math.min(left, win.innerWidth - popRect.width - 8));
10905
+ top = Math.max(8, top);
10906
+ panel.style.position = "absolute";
10907
+ panel.style.top = top + "px";
10908
+ panel.style.left = left + "px";
10909
+ },
10910
+ flipPlacement: function(trigger) {
10911
+ const inst = Popover.instances.get(trigger);
10912
+ if (!inst || !inst.allowFlip) return;
10913
+ if (inst.panel.hidden) return;
10914
+ const win = inst.panel.ownerDocument.defaultView || window;
10915
+ const rect = trigger.getBoundingClientRect();
10916
+ const popRect = inst.panel.getBoundingClientRect();
10917
+ const gap = DEFAULT_GAP;
10918
+ let flipped = null;
10919
+ const current = inst.placement;
10920
+ if (current === "top" && rect.top - popRect.height - gap < 0) flipped = "bottom";
10921
+ else if (current === "bottom" && rect.bottom + popRect.height + gap > win.innerHeight) flipped = "top";
10922
+ else if (current === "left" && rect.left - popRect.width - gap < 0) flipped = "right";
10923
+ else if (current === "right" && rect.right + popRect.width + gap > win.innerWidth) flipped = "left";
10924
+ if (flipped) {
10925
+ inst.placement = flipped;
10926
+ Popover.position(trigger, inst.panel, flipped);
10927
+ inst.panel.setAttribute("data-placement", flipped);
10928
+ }
10929
+ },
10930
+ destroy: function(trigger) {
10931
+ const inst = Popover.instances.get(trigger);
10932
+ if (!inst) return;
10933
+ inst.cleanup.forEach(function(fn) {
10934
+ fn();
10935
+ });
10936
+ Popover.hide(trigger);
10937
+ trigger.removeAttribute("aria-haspopup");
10938
+ trigger.removeAttribute("aria-expanded");
10939
+ trigger.removeAttribute("aria-controls");
10940
+ Popover.instances.delete(trigger);
10941
+ },
10942
+ destroyAll: function(root) {
10943
+ const triggers = [];
10944
+ Popover.instances.forEach(function(_inst, trigger) {
10945
+ if (!root || trigger.closest && trigger.closest(root)) triggers.push(trigger);
10946
+ });
10947
+ triggers.forEach(function(trigger) {
10948
+ Popover.destroy(trigger);
10949
+ });
10950
+ if (Popover.instances.size === 0) {
10951
+ Popover._globalCleanups.forEach(function(fn) {
10952
+ fn();
10953
+ });
10954
+ Popover._globalCleanups = [];
10955
+ }
10956
+ }
10957
+ };
10958
+ if (typeof window !== "undefined") {
10959
+ if (typeof window.Vanduo !== "undefined" && typeof window.Vanduo.register === "function") {
10960
+ window.Vanduo.register("popover", Popover);
10961
+ }
10962
+ window.VanduoPopover = Popover;
10963
+ }
10964
+ })();
10965
+
10966
+ // js/components/search.js
10967
+ (function() {
10968
+ "use strict";
10969
+ const DEFAULT_LIMIT = 10;
10970
+ const sources = /* @__PURE__ */ new Map();
10971
+ function register(source) {
10972
+ if (!source || typeof source.name !== "string" || source.name.length === 0) {
10973
+ throw new Error("VanduoSearch.register: source.name is required");
10974
+ }
10975
+ if (typeof source.fetch !== "function") {
10976
+ throw new Error("VanduoSearch.register: source.fetch must be a function");
10977
+ }
10978
+ if (sources.has(source.name)) {
10979
+ throw new Error('VanduoSearch.register: source "' + source.name + '" already registered');
10980
+ }
10981
+ sources.set(source.name, Object.freeze({
10982
+ name: source.name,
10983
+ label: source.label || source.name,
10984
+ icon: source.icon || null,
10985
+ limit: typeof source.limit === "number" ? source.limit : DEFAULT_LIMIT,
10986
+ fetch: source.fetch
10987
+ }));
10988
+ }
10989
+ function unregister(name) {
10990
+ return sources.delete(name);
10991
+ }
10992
+ function list() {
10993
+ return Object.freeze(Array.from(sources.values()));
10994
+ }
10995
+ function query(text, options) {
10996
+ options = options || {};
10997
+ const signal = options.signal;
10998
+ const limitPerSource = typeof options.limitPerSource === "number" ? options.limitPerSource : null;
10999
+ const queryText = (text || "").trim();
11000
+ const allSources = Array.from(sources.values());
11001
+ if (queryText.length === 0) {
11002
+ return Promise.resolve({
11003
+ text: queryText,
11004
+ sources: allSources.map(function(src) {
11005
+ return { name: src.name, label: src.label, results: [] };
11006
+ })
11007
+ });
11008
+ }
11009
+ const promises = allSources.map(function(src) {
11010
+ const effectiveLimit = limitPerSource != null ? limitPerSource : src.limit;
11011
+ return Promise.resolve().then(function() {
11012
+ return src.fetch(queryText, { signal, limit: effectiveLimit });
11013
+ }).then(function(results) {
11014
+ const safe = Array.isArray(results) ? results : [];
11015
+ return { name: src.name, label: src.label, results: safe };
11016
+ }).catch(function(error) {
11017
+ if (error && error.name === "AbortError") throw error;
11018
+ return { name: src.name, label: src.label, results: [], error: error.message || "fetch failed" };
11019
+ });
11020
+ });
11021
+ return Promise.all(promises).then(function(perSource) {
11022
+ return { text: queryText, sources: perSource };
11023
+ });
11024
+ }
11025
+ const Search = { register, unregister, list, query };
11026
+ if (typeof window !== "undefined") {
11027
+ if (typeof window.Vanduo !== "undefined" && typeof window.Vanduo.register === "function") {
11028
+ window.Vanduo.register("search", Search);
11029
+ }
11030
+ window.VanduoSearch = Search;
11031
+ }
11032
+ })();
11033
+
10653
11034
  // js/index.js
10654
11035
  var Vanduo = window.Vanduo;
10655
11036
  var index_default = Vanduo;