vanduo-framework 1.1.8 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- /*! Vanduo v1.1.6 | Built: 2026-02-17T18:38:55.867Z | git:c40d96d | development */
1
+ /*! Vanduo v1.2.0 | Built: 2026-02-22T21:30:31.940Z | git:64c88fd | development */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -133,7 +133,7 @@ module.exports = __toCommonJS(index_exports);
133
133
  (function() {
134
134
  "use strict";
135
135
  const Vanduo2 = {
136
- version: "1.1.6",
136
+ version: "1.2.0",
137
137
  components: {},
138
138
  /**
139
139
  * Initialize framework
@@ -168,7 +168,7 @@ module.exports = __toCommonJS(index_exports);
168
168
  }
169
169
  }
170
170
  });
171
- console.log("Vanduo Framework v1.1.6 initialized");
171
+ console.log("Vanduo Framework v1.2.0 initialized");
172
172
  },
173
173
  /**
174
174
  * Register a component
@@ -183,7 +183,7 @@ module.exports = __toCommonJS(index_exports);
183
183
  * @param {string} name - Component name
184
184
  */
185
185
  reinit: function(name) {
186
- var component = this.components[name];
186
+ const component = this.components[name];
187
187
  if (component && component.init && typeof component.init === "function") {
188
188
  try {
189
189
  component.init();
@@ -197,9 +197,9 @@ module.exports = __toCommonJS(index_exports);
197
197
  * Uses lifecycle manager for memory leak prevention
198
198
  */
199
199
  destroyAll: function() {
200
- var names = Object.keys(this.components);
201
- for (var i = 0; i < names.length; i++) {
202
- var component = this.components[names[i]];
200
+ const names = Object.keys(this.components);
201
+ for (let i = 0; i < names.length; i++) {
202
+ const component = this.components[names[i]];
203
203
  if (component && component.destroyAll && typeof component.destroyAll === "function") {
204
204
  try {
205
205
  component.destroyAll();
@@ -516,7 +516,9 @@ module.exports = __toCommonJS(index_exports);
516
516
  html = this.formatHtml(html);
517
517
  html = this.escapeHtml(html);
518
518
  html = this.highlightHtml(html);
519
- pane.innerHTML = "<code>" + html + "</code>";
519
+ const codeEl = document.createElement("code");
520
+ codeEl.innerHTML = html;
521
+ pane.replaceChildren(codeEl);
520
522
  pane.dataset.extracted = "true";
521
523
  },
522
524
  /**
@@ -621,7 +623,7 @@ module.exports = __toCommonJS(index_exports);
621
623
  }
622
624
  const codeWrapper = document.createElement("div");
623
625
  codeWrapper.className = "vd-code-snippet-code";
624
- codeWrapper.innerHTML = code.outerHTML;
626
+ codeWrapper.appendChild(code.cloneNode(true));
625
627
  code.parentNode.removeChild(code);
626
628
  pane.appendChild(lineNumbers);
627
629
  pane.appendChild(codeWrapper);
@@ -1387,20 +1389,20 @@ module.exports = __toCommonJS(index_exports);
1387
1389
  // js/components/grid.js
1388
1390
  (function() {
1389
1391
  "use strict";
1390
- var supportsHas = (function() {
1392
+ const supportsHas = (function() {
1391
1393
  try {
1392
1394
  return CSS.supports("selector(:has(*))");
1393
1395
  } catch (_e) {
1394
1396
  return false;
1395
1397
  }
1396
1398
  })();
1397
- var GridLayout = {
1399
+ const GridLayout = {
1398
1400
  instances: /* @__PURE__ */ new Map(),
1399
1401
  /**
1400
1402
  * Initialize all grid layout containers
1401
1403
  */
1402
1404
  init: function() {
1403
- var containers = document.querySelectorAll("[data-layout-mode]");
1405
+ const containers = document.querySelectorAll("[data-layout-mode]");
1404
1406
  containers.forEach(function(container) {
1405
1407
  if (this.instances.has(container)) {
1406
1408
  return;
@@ -1414,8 +1416,8 @@ module.exports = __toCommonJS(index_exports);
1414
1416
  * @param {HTMLElement} container - Element with data-layout-mode
1415
1417
  */
1416
1418
  initContainer: function(container) {
1417
- var mode = container.getAttribute("data-layout-mode") || "standard";
1418
- var cleanupFunctions = [];
1419
+ const mode = container.getAttribute("data-layout-mode") || "standard";
1420
+ const cleanupFunctions = [];
1419
1421
  this.applyMode(container, mode);
1420
1422
  container.setAttribute("role", "region");
1421
1423
  container.setAttribute("aria-label", "Grid layout: " + mode + " mode");
@@ -1428,15 +1430,15 @@ module.exports = __toCommonJS(index_exports);
1428
1430
  * Initialize toggle buttons that target grid containers
1429
1431
  */
1430
1432
  initToggleButtons: function() {
1431
- var toggleButtons = document.querySelectorAll("[data-grid-toggle]");
1433
+ const toggleButtons = document.querySelectorAll("[data-grid-toggle]");
1432
1434
  toggleButtons.forEach(function(button) {
1433
1435
  if (button.getAttribute("data-grid-initialized") === "true") {
1434
1436
  return;
1435
1437
  }
1436
- var clickHandler = function(e) {
1438
+ const clickHandler = function(e) {
1437
1439
  e.preventDefault();
1438
- var targetSelector = button.getAttribute("data-grid-toggle");
1439
- var target;
1440
+ const targetSelector = button.getAttribute("data-grid-toggle");
1441
+ let target;
1440
1442
  if (targetSelector) {
1441
1443
  target = document.querySelector(targetSelector);
1442
1444
  } else {
@@ -1462,10 +1464,10 @@ module.exports = __toCommonJS(index_exports);
1462
1464
  */
1463
1465
  applyFibFallback: function(container) {
1464
1466
  if (supportsHas) return;
1465
- var rows = container.querySelectorAll(".vd-row, .row");
1467
+ const rows = container.querySelectorAll(".vd-row, .row");
1466
1468
  rows.forEach(function(row) {
1467
- var cols = row.querySelectorAll(':scope > [class*="vd-col-"], :scope > [class*="col-"]');
1468
- var count = cols.length;
1469
+ const cols = row.querySelectorAll(':scope > [class*="vd-col-"], :scope > [class*="col-"]');
1470
+ const count = cols.length;
1469
1471
  if (count === 1) {
1470
1472
  row.style.gridTemplateColumns = "1fr";
1471
1473
  } else if (count === 2) {
@@ -1484,7 +1486,7 @@ module.exports = __toCommonJS(index_exports);
1484
1486
  * @param {HTMLElement} container - Grid container
1485
1487
  */
1486
1488
  removeFibFallback: function(container) {
1487
- var rows = container.querySelectorAll(".vd-row, .row");
1489
+ const rows = container.querySelectorAll(".vd-row, .row");
1488
1490
  rows.forEach(function(row) {
1489
1491
  row.style.gridTemplateColumns = "";
1490
1492
  });
@@ -1505,11 +1507,11 @@ module.exports = __toCommonJS(index_exports);
1505
1507
  }
1506
1508
  container.setAttribute("data-layout-mode", mode);
1507
1509
  container.setAttribute("aria-label", "Grid layout: " + mode + " mode");
1508
- var toggleButtons = document.querySelectorAll("[data-grid-toggle]");
1510
+ const toggleButtons = document.querySelectorAll("[data-grid-toggle]");
1509
1511
  toggleButtons.forEach(function(btn) {
1510
- var targetSelector = btn.getAttribute("data-grid-toggle");
1512
+ const targetSelector = btn.getAttribute("data-grid-toggle");
1511
1513
  if (targetSelector && container.matches(targetSelector)) {
1512
- var isActive = mode === "fibonacci";
1514
+ const isActive = mode === "fibonacci";
1513
1515
  if (isActive) {
1514
1516
  btn.classList.add("is-active");
1515
1517
  } else {
@@ -1518,11 +1520,11 @@ module.exports = __toCommonJS(index_exports);
1518
1520
  btn.setAttribute("aria-pressed", isActive ? "true" : "false");
1519
1521
  }
1520
1522
  });
1521
- var instance = this.instances.get(container);
1523
+ const instance = this.instances.get(container);
1522
1524
  if (instance) {
1523
1525
  instance.mode = mode;
1524
1526
  }
1525
- var event;
1527
+ let event;
1526
1528
  try {
1527
1529
  event = new CustomEvent("grid:modechange", {
1528
1530
  bubbles: true,
@@ -1549,8 +1551,8 @@ module.exports = __toCommonJS(index_exports);
1549
1551
  container = document.querySelector(container);
1550
1552
  }
1551
1553
  if (!container) return;
1552
- var currentMode = container.getAttribute("data-layout-mode") || "standard";
1553
- var newMode = currentMode === "fibonacci" ? "standard" : "fibonacci";
1554
+ const currentMode = container.getAttribute("data-layout-mode") || "standard";
1555
+ const newMode = currentMode === "fibonacci" ? "standard" : "fibonacci";
1554
1556
  this.applyMode(container, newMode);
1555
1557
  },
1556
1558
  /**
@@ -1583,7 +1585,7 @@ module.exports = __toCommonJS(index_exports);
1583
1585
  * @param {HTMLElement} container - Grid container
1584
1586
  */
1585
1587
  destroy: function(container) {
1586
- var instance = this.instances.get(container);
1588
+ const instance = this.instances.get(container);
1587
1589
  if (!instance) return;
1588
1590
  instance.cleanup.forEach(function(fn) {
1589
1591
  fn();
@@ -1600,7 +1602,7 @@ module.exports = __toCommonJS(index_exports);
1600
1602
  this.instances.forEach(function(instance, container) {
1601
1603
  this.destroy(container);
1602
1604
  }.bind(this));
1603
- var toggleButtons = document.querySelectorAll('[data-grid-initialized="true"]');
1605
+ const toggleButtons = document.querySelectorAll('[data-grid-initialized="true"]');
1604
1606
  toggleButtons.forEach(function(button) {
1605
1607
  if (button._gridCleanup) {
1606
1608
  button._gridCleanup();
@@ -3697,9 +3699,9 @@ module.exports = __toCommonJS(index_exports);
3697
3699
  },
3698
3700
  // Default values
3699
3701
  DEFAULTS: {
3700
- PRIMARY_LIGHT: "amber",
3702
+ PRIMARY_LIGHT: "black",
3701
3703
  PRIMARY_DARK: "amber",
3702
- NEUTRAL: "slate",
3704
+ NEUTRAL: "neutral",
3703
3705
  RADIUS: "0.5",
3704
3706
  FONT: "ubuntu",
3705
3707
  THEME: "system"
@@ -4062,21 +4064,33 @@ module.exports = __toCommonJS(index_exports);
4062
4064
  * Generate panel HTML
4063
4065
  */
4064
4066
  getPanelHTML: function() {
4067
+ const esc = typeof escapeHtml === "function" ? escapeHtml : function(text) {
4068
+ const div = document.createElement("div");
4069
+ div.textContent = String(text ?? "");
4070
+ return div.innerHTML;
4071
+ };
4072
+ const safeColor = function(value) {
4073
+ const normalized = String(value ?? "").trim();
4074
+ if (/^(#[0-9a-fA-F]{3,8}|rgb[a]?\([^)]{1,60}\)|hsl[a]?\([^)]{1,60}\)|var\(--[a-zA-Z0-9_-]{1,40}\))$/.test(normalized)) {
4075
+ return normalized;
4076
+ }
4077
+ return "#000000";
4078
+ };
4065
4079
  let primarySwatches = "";
4066
4080
  for (const [key, value] of Object.entries(this.PRIMARY_COLORS)) {
4067
- primarySwatches += `<button class="tc-color-swatch${key === this.state.primary ? " is-active" : ""}" data-color="${key}" style="--swatch-color: ${value.color}" title="${value.name}"></button>`;
4081
+ primarySwatches += `<button class="tc-color-swatch${key === this.state.primary ? " is-active" : ""}" data-color="${esc(key)}" style="--swatch-color: ${safeColor(value.color)}" title="${esc(value.name)}"></button>`;
4068
4082
  }
4069
4083
  let neutralSwatches = "";
4070
4084
  for (const [key, value] of Object.entries(this.NEUTRAL_COLORS)) {
4071
- neutralSwatches += `<button class="tc-neutral-swatch${key === this.state.neutral ? " is-active" : ""}" data-neutral="${key}" style="--swatch-color: ${value.color}" title="${value.name}"><span>${value.name}</span></button>`;
4085
+ neutralSwatches += `<button class="tc-neutral-swatch${key === this.state.neutral ? " is-active" : ""}" data-neutral="${esc(key)}" style="--swatch-color: ${safeColor(value.color)}" title="${esc(value.name)}"><span>${esc(value.name)}</span></button>`;
4072
4086
  }
4073
4087
  let radiusButtons = "";
4074
4088
  this.RADIUS_OPTIONS.forEach((r) => {
4075
- radiusButtons += `<button class="tc-radius-btn${r === this.state.radius ? " is-active" : ""}" data-radius="${r}">${r}</button>`;
4089
+ radiusButtons += `<button class="tc-radius-btn${r === this.state.radius ? " is-active" : ""}" data-radius="${esc(r)}">${esc(r)}</button>`;
4076
4090
  });
4077
4091
  let fontOptions = "";
4078
4092
  for (const [key, value] of Object.entries(this.FONT_OPTIONS)) {
4079
- fontOptions += `<option value="${key}"${key === this.state.font ? " selected" : ""}>${value.name}</option>`;
4093
+ fontOptions += `<option value="${esc(key)}"${key === this.state.font ? " selected" : ""}>${esc(value.name)}</option>`;
4080
4094
  }
4081
4095
  const modeIcons = {
4082
4096
  "system": "ph-desktop",
@@ -4134,6 +4148,13 @@ module.exports = __toCommonJS(index_exports);
4134
4148
  /**
4135
4149
  * Bind event listeners
4136
4150
  */
4151
+ /**
4152
+ * Check whether the current primary color is one of the auto-defaults
4153
+ * (i.e. the user hasn't explicitly picked a non-default color).
4154
+ */
4155
+ isUsingDefaultPrimary: function() {
4156
+ return this.state.primary === this.DEFAULTS.PRIMARY_LIGHT || this.state.primary === this.DEFAULTS.PRIMARY_DARK;
4157
+ },
4137
4158
  bindEvents: function() {
4138
4159
  if (this.elements.trigger) {
4139
4160
  this.addListener(this.elements.trigger, "click", (e) => {
@@ -4143,6 +4164,20 @@ module.exports = __toCommonJS(index_exports);
4143
4164
  });
4144
4165
  }
4145
4166
  this.bindPanelEvents();
4167
+ if (window.matchMedia) {
4168
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
4169
+ const handler = () => {
4170
+ if (this.state.theme === "system" && this.isUsingDefaultPrimary()) {
4171
+ const newDefault = this.getDefaultPrimary("system");
4172
+ if (newDefault !== this.state.primary) {
4173
+ this.applyPrimary(newDefault);
4174
+ this.updateUI();
4175
+ }
4176
+ }
4177
+ };
4178
+ mq.addEventListener("change", handler);
4179
+ this._cleanup.push(() => mq.removeEventListener("change", handler));
4180
+ }
4146
4181
  this.addListener(document, "click", (e) => {
4147
4182
  if (this.state.isOpen && this.elements.customizer && !this.elements.customizer.contains(e.target)) {
4148
4183
  this.close();
@@ -4704,7 +4739,7 @@ module.exports = __toCommonJS(index_exports);
4704
4739
  if (typeof sanitizeHtml === "function") {
4705
4740
  return sanitizeHtml(input);
4706
4741
  }
4707
- var div = document.createElement("div");
4742
+ const div = document.createElement("div");
4708
4743
  div.textContent = input || "";
4709
4744
  return div.innerHTML;
4710
4745
  },
@@ -4943,7 +4978,7 @@ module.exports = __toCommonJS(index_exports);
4943
4978
  // js/components/doc-search.js
4944
4979
  (function() {
4945
4980
  "use strict";
4946
- var DEFAULTS = {
4981
+ const DEFAULTS = {
4947
4982
  // Behavior
4948
4983
  minQueryLength: 2,
4949
4984
  maxResults: 10,
@@ -4990,8 +5025,8 @@ module.exports = __toCommonJS(index_exports);
4990
5025
  placeholder: "Search..."
4991
5026
  };
4992
5027
  function createSearch(options) {
4993
- var config = Object.assign({}, DEFAULTS, options || {});
4994
- var state = {
5028
+ const config = Object.assign({}, DEFAULTS, options || {});
5029
+ const state = {
4995
5030
  initialized: false,
4996
5031
  index: [],
4997
5032
  results: [],
@@ -5004,6 +5039,21 @@ module.exports = __toCommonJS(index_exports);
5004
5039
  debounceTimer: null,
5005
5040
  boundHandlers: {}
5006
5041
  };
5042
+ function safeInvokeCallback(name, fn, ...args) {
5043
+ try {
5044
+ fn(...args);
5045
+ } catch (error) {
5046
+ console.warn('[Vanduo Search] Callback error in "' + name + '":', error);
5047
+ }
5048
+ }
5049
+ function setResultsHtml(html) {
5050
+ if (!state.resultsContainer) return;
5051
+ try {
5052
+ state.resultsContainer.innerHTML = html;
5053
+ } catch (error) {
5054
+ console.warn("[Vanduo Search] Failed to render results:", error);
5055
+ }
5056
+ }
5007
5057
  function init() {
5008
5058
  if (state.initialized) {
5009
5059
  return instance;
@@ -5045,20 +5095,20 @@ module.exports = __toCommonJS(index_exports);
5045
5095
  });
5046
5096
  return;
5047
5097
  }
5048
- var sections = document.querySelectorAll(config.contentSelector);
5049
- var categoryMap = buildCategoryMap();
5098
+ const sections = document.querySelectorAll(config.contentSelector);
5099
+ const categoryMap = buildCategoryMap();
5050
5100
  sections.forEach(function(section) {
5051
- var id = section.id;
5101
+ const id = section.id;
5052
5102
  if (!id) return;
5053
- var titleEl = section.querySelector(config.titleSelector);
5054
- var title = titleEl ? titleEl.textContent.replace(/v[\d.]+/g, "").trim() : id;
5055
- var category = categoryMap[id] || "Documentation";
5056
- var content = extractContent(section);
5057
- var keywords = extractKeywords(section, title);
5058
- var iconEl = titleEl ? titleEl.querySelector("i.ph") : null;
5059
- var icon = "";
5103
+ const titleEl = section.querySelector(config.titleSelector);
5104
+ const title = titleEl ? titleEl.textContent.replace(/v[\d.]+/g, "").trim() : id;
5105
+ const category = categoryMap[id] || "Documentation";
5106
+ const content = extractContent(section);
5107
+ const keywords = extractKeywords(section, title);
5108
+ const iconEl = titleEl ? titleEl.querySelector("i.ph") : null;
5109
+ let icon = "";
5060
5110
  if (iconEl && iconEl.classList) {
5061
- for (var ci = 0; ci < iconEl.classList.length; ci++) {
5111
+ for (let ci = 0; ci < iconEl.classList.length; ci++) {
5062
5112
  if (iconEl.classList[ci].indexOf("ph-") === 0) {
5063
5113
  icon = iconEl.classList[ci];
5064
5114
  break;
@@ -5078,16 +5128,16 @@ module.exports = __toCommonJS(index_exports);
5078
5128
  });
5079
5129
  }
5080
5130
  function buildCategoryMap() {
5081
- var map = {};
5082
- var currentCategory = "Documentation";
5083
- var navItems = document.querySelectorAll(config.navSelector + ", " + config.sectionSelector);
5131
+ const map = {};
5132
+ let currentCategory = "Documentation";
5133
+ const navItems = document.querySelectorAll(config.navSelector + ", " + config.sectionSelector);
5084
5134
  navItems.forEach(function(item) {
5085
5135
  if (item.classList.contains("doc-nav-section")) {
5086
5136
  currentCategory = item.textContent.trim();
5087
5137
  } else {
5088
- var href = item.getAttribute("href");
5138
+ const href = item.getAttribute("href");
5089
5139
  if (href && href.startsWith("#")) {
5090
- var id = href.substring(1);
5140
+ const id = href.substring(1);
5091
5141
  map[id] = currentCategory;
5092
5142
  }
5093
5143
  }
@@ -5095,35 +5145,35 @@ module.exports = __toCommonJS(index_exports);
5095
5145
  return map;
5096
5146
  }
5097
5147
  function extractContent(section) {
5098
- var clone = section.cloneNode(true);
5099
- var toRemove = clone.querySelectorAll(config.excludeFromContent);
5148
+ const clone = section.cloneNode(true);
5149
+ const toRemove = clone.querySelectorAll(config.excludeFromContent);
5100
5150
  toRemove.forEach(function(el) {
5101
5151
  el.remove();
5102
5152
  });
5103
- var text = clone.textContent || "";
5153
+ let text = clone.textContent || "";
5104
5154
  text = text.replace(/\s+/g, " ").trim();
5105
5155
  return text.substring(0, config.maxContentLength);
5106
5156
  }
5107
5157
  function extractKeywords(section, title) {
5108
- var keywords = [];
5158
+ const keywords = [];
5109
5159
  title.toLowerCase().split(/\s+/).forEach(function(word) {
5110
5160
  if (word.length > 2) {
5111
5161
  keywords.push(word);
5112
5162
  }
5113
5163
  });
5114
- var codeBlocks = section.querySelectorAll("code");
5164
+ const codeBlocks = section.querySelectorAll("code");
5115
5165
  codeBlocks.forEach(function(code) {
5116
- var text = code.textContent || "";
5117
- var classMatches = text.match(/\.([\w-]+)/g);
5166
+ const text = code.textContent || "";
5167
+ const classMatches = text.match(/\.([\w-]+)/g);
5118
5168
  if (classMatches) {
5119
5169
  classMatches.forEach(function(match) {
5120
5170
  keywords.push(match.substring(1).toLowerCase());
5121
5171
  });
5122
5172
  }
5123
5173
  });
5124
- var dataAttrs = section.querySelectorAll("[data-tooltip], [data-modal]");
5174
+ const dataAttrs = section.querySelectorAll("[data-tooltip], [data-modal]");
5125
5175
  dataAttrs.forEach(function(el) {
5126
- var attrs = el.getAttributeNames().filter(function(name) {
5176
+ const attrs = el.getAttributeNames().filter(function(name) {
5127
5177
  return name.startsWith("data-");
5128
5178
  });
5129
5179
  attrs.forEach(function(attr) {
@@ -5133,7 +5183,7 @@ module.exports = __toCommonJS(index_exports);
5133
5183
  return Array.from(new Set(keywords));
5134
5184
  }
5135
5185
  function extractKeywordsFromText(text) {
5136
- var words = text.toLowerCase().split(/\s+/);
5186
+ const words = text.toLowerCase().split(/\s+/);
5137
5187
  return words.filter(function(word) {
5138
5188
  return word.length > 2;
5139
5189
  });
@@ -5166,9 +5216,9 @@ module.exports = __toCommonJS(index_exports);
5166
5216
  }
5167
5217
  };
5168
5218
  state.boundHandlers.handleResultClick = function(e) {
5169
- var result = e.target.closest(".vd-doc-search-result");
5219
+ const result = e.target.closest(".vd-doc-search-result");
5170
5220
  if (result) {
5171
- var index = parseInt(result.dataset.index, 10);
5221
+ const index = parseInt(result.dataset.index, 10);
5172
5222
  select(index);
5173
5223
  }
5174
5224
  };
@@ -5192,7 +5242,7 @@ module.exports = __toCommonJS(index_exports);
5192
5242
  }
5193
5243
  }
5194
5244
  function setupAria() {
5195
- var resultsId = state.resultsContainer.id || "search-results-" + Math.random().toString(36).substr(2, 9);
5245
+ const resultsId = state.resultsContainer.id || "search-results-" + Math.random().toString(36).substr(2, 9);
5196
5246
  state.resultsContainer.id = resultsId;
5197
5247
  state.input.setAttribute("role", "combobox");
5198
5248
  state.input.setAttribute("aria-autocomplete", "list");
@@ -5202,7 +5252,7 @@ module.exports = __toCommonJS(index_exports);
5202
5252
  state.resultsContainer.setAttribute("aria-label", "Search results");
5203
5253
  }
5204
5254
  function handleInput(e) {
5205
- var query = e.target.value.trim();
5255
+ const query = e.target.value.trim();
5206
5256
  if (state.debounceTimer) {
5207
5257
  clearTimeout(state.debounceTimer);
5208
5258
  }
@@ -5217,7 +5267,7 @@ module.exports = __toCommonJS(index_exports);
5217
5267
  render();
5218
5268
  open();
5219
5269
  if (typeof config.onSearch === "function") {
5220
- config.onSearch(query, state.results);
5270
+ safeInvokeCallback("onSearch", config.onSearch, query, state.results);
5221
5271
  }
5222
5272
  }, config.debounceMs);
5223
5273
  }
@@ -5258,15 +5308,15 @@ module.exports = __toCommonJS(index_exports);
5258
5308
  }
5259
5309
  }
5260
5310
  function search(query) {
5261
- var terms = query.toLowerCase().split(/\s+/).filter(function(t) {
5311
+ const terms = query.toLowerCase().split(/\s+/).filter(function(t) {
5262
5312
  return t.length > 0;
5263
5313
  });
5264
- var scored = [];
5314
+ const scored = [];
5265
5315
  state.index.forEach(function(entry) {
5266
- var score = 0;
5267
- var titleLower = entry.title.toLowerCase();
5268
- var categoryLower = entry.category.toLowerCase();
5269
- var contentLower = entry.content.toLowerCase();
5316
+ let score = 0;
5317
+ const titleLower = entry.title.toLowerCase();
5318
+ const categoryLower = entry.category.toLowerCase();
5319
+ const contentLower = entry.content.toLowerCase();
5270
5320
  terms.forEach(function(term) {
5271
5321
  if (titleLower.includes(term)) {
5272
5322
  score += 100;
@@ -5279,7 +5329,7 @@ module.exports = __toCommonJS(index_exports);
5279
5329
  if (categoryLower.includes(term)) {
5280
5330
  score += 50;
5281
5331
  }
5282
- var keywordMatch = entry.keywords.some(function(k) {
5332
+ const keywordMatch = entry.keywords.some(function(k) {
5283
5333
  return k.includes(term);
5284
5334
  });
5285
5335
  if (keywordMatch) {
@@ -5309,19 +5359,19 @@ module.exports = __toCommonJS(index_exports);
5309
5359
  }
5310
5360
  function render() {
5311
5361
  if (state.results.length === 0) {
5312
- state.resultsContainer.innerHTML = renderEmpty();
5362
+ setResultsHtml(renderEmpty());
5313
5363
  return;
5314
5364
  }
5315
- var html = '<ul class="vd-doc-search-results-list" role="listbox">';
5365
+ let html = '<ul class="vd-doc-search-results-list" role="listbox">';
5316
5366
  state.results.forEach(function(result, index) {
5317
- var isActive = index === state.activeIndex;
5318
- var icon = result.icon || getCategoryIcon(result.categorySlug);
5319
- var excerpt = getExcerpt(result.content, state.query);
5367
+ const isActive = index === state.activeIndex;
5368
+ const icon = result.icon || getCategoryIcon(result.categorySlug);
5369
+ const excerpt = getExcerpt(result.content, state.query);
5320
5370
  html += '<li class="vd-doc-search-result' + (isActive ? " is-active" : "") + '" role="option" id="vd-doc-search-result-' + index + '" data-index="' + index + '" data-category="' + escapeHtml2(result.categorySlug) + '" aria-selected="' + isActive + '"><div class="vd-doc-search-result-icon"><i class="ph ' + escapeHtml2(icon) + '"></i></div><div class="vd-doc-search-result-content"><div class="vd-doc-search-result-title">' + highlight(result.title, state.query) + '</div><div class="vd-doc-search-result-category">' + escapeHtml2(result.category) + '</div><div class="vd-doc-search-result-excerpt">' + highlight(excerpt, state.query) + "</div></div></li>";
5321
5371
  });
5322
5372
  html += "</ul>";
5323
5373
  html += renderFooter();
5324
- state.resultsContainer.innerHTML = html;
5374
+ setResultsHtml(html);
5325
5375
  }
5326
5376
  function renderEmpty() {
5327
5377
  return '<div class="vd-doc-search-empty"><div class="vd-doc-search-empty-icon"><i class="ph ph-magnifying-glass"></i></div><div class="vd-doc-search-empty-title">' + escapeHtml2(config.emptyTitle) + '</div><div class="vd-doc-search-empty-text">' + escapeHtml2(config.emptyText) + "</div></div>";
@@ -5333,12 +5383,12 @@ module.exports = __toCommonJS(index_exports);
5333
5383
  return config.categoryIcons[categorySlug] || config.categoryIcons["default"] || "ph-file-text";
5334
5384
  }
5335
5385
  function getExcerpt(content, query) {
5336
- var terms = query.toLowerCase().split(/\s+/);
5337
- var contentLower = content.toLowerCase();
5338
- var excerptLength = 100;
5339
- var matchPos = -1;
5340
- for (var i = 0; i < terms.length; i++) {
5341
- var pos = contentLower.indexOf(terms[i]);
5386
+ const terms = query.toLowerCase().split(/\s+/);
5387
+ const contentLower = content.toLowerCase();
5388
+ const excerptLength = 100;
5389
+ let matchPos = -1;
5390
+ for (let i = 0; i < terms.length; i++) {
5391
+ const pos = contentLower.indexOf(terms[i]);
5342
5392
  if (pos !== -1 && (matchPos === -1 || pos < matchPos)) {
5343
5393
  matchPos = pos;
5344
5394
  }
@@ -5346,9 +5396,9 @@ module.exports = __toCommonJS(index_exports);
5346
5396
  if (matchPos === -1) {
5347
5397
  return content.substring(0, excerptLength) + "...";
5348
5398
  }
5349
- var start = Math.max(0, matchPos - 30);
5350
- var end = Math.min(content.length, matchPos + excerptLength);
5351
- var excerpt = content.substring(start, end);
5399
+ const start = Math.max(0, matchPos - 30);
5400
+ const end = Math.min(content.length, matchPos + excerptLength);
5401
+ let excerpt = content.substring(start, end);
5352
5402
  if (start > 0) {
5353
5403
  excerpt = "..." + excerpt;
5354
5404
  }
@@ -5359,24 +5409,24 @@ module.exports = __toCommonJS(index_exports);
5359
5409
  }
5360
5410
  function highlight(text, query) {
5361
5411
  if (!query) return escapeHtml2(text);
5362
- var terms = query.toLowerCase().split(/\s+/).filter(function(t) {
5412
+ const terms = query.toLowerCase().split(/\s+/).filter(function(t) {
5363
5413
  return t.length > 0;
5364
5414
  });
5365
- var escaped = escapeHtml2(text);
5415
+ let escaped = escapeHtml2(text);
5366
5416
  terms.forEach(function(term) {
5367
5417
  if (term.length > 50) return;
5368
- var regex = new RegExp("(" + term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + ")", "gi");
5418
+ const regex = new RegExp("(" + term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + ")", "gi");
5369
5419
  escaped = escaped.replace(regex, "<" + config.highlightTag + ">$1</" + config.highlightTag + ">");
5370
5420
  });
5371
5421
  return escaped;
5372
5422
  }
5373
5423
  function escapeHtml2(text) {
5374
- var div = document.createElement("div");
5424
+ const div = document.createElement("div");
5375
5425
  div.textContent = text;
5376
5426
  return div.innerHTML;
5377
5427
  }
5378
5428
  function navigate(direction) {
5379
- var newIndex = state.activeIndex + direction;
5429
+ let newIndex = state.activeIndex + direction;
5380
5430
  if (newIndex < 0) {
5381
5431
  newIndex = state.results.length - 1;
5382
5432
  } else if (newIndex >= state.results.length) {
@@ -5385,13 +5435,13 @@ module.exports = __toCommonJS(index_exports);
5385
5435
  setActiveIndex(newIndex);
5386
5436
  }
5387
5437
  function setActiveIndex(index) {
5388
- var prevActive = state.resultsContainer.querySelector(".vd-doc-search-result.is-active");
5438
+ const prevActive = state.resultsContainer.querySelector(".vd-doc-search-result.is-active");
5389
5439
  if (prevActive) {
5390
5440
  prevActive.classList.remove("is-active");
5391
5441
  prevActive.setAttribute("aria-selected", "false");
5392
5442
  }
5393
5443
  state.activeIndex = index;
5394
- var newActive = state.resultsContainer.querySelector('[data-index="' + index + '"]');
5444
+ const newActive = state.resultsContainer.querySelector('[data-index="' + index + '"]');
5395
5445
  if (newActive) {
5396
5446
  newActive.classList.add("is-active");
5397
5447
  newActive.setAttribute("aria-selected", "true");
@@ -5400,16 +5450,16 @@ module.exports = __toCommonJS(index_exports);
5400
5450
  }
5401
5451
  }
5402
5452
  function select(index) {
5403
- var result = state.results[index];
5453
+ const result = state.results[index];
5404
5454
  if (!result) return;
5405
5455
  close();
5406
5456
  state.input.value = "";
5407
5457
  state.query = "";
5408
5458
  if (typeof config.onSelect === "function") {
5409
- config.onSelect(result);
5459
+ safeInvokeCallback("onSelect", config.onSelect, result);
5410
5460
  return;
5411
5461
  }
5412
- var section = document.querySelector(result.url);
5462
+ const section = document.querySelector(result.url);
5413
5463
  if (section) {
5414
5464
  section.scrollIntoView({ behavior: "smooth", block: "start" });
5415
5465
  window.history.pushState(null, "", result.url);
@@ -5417,7 +5467,7 @@ module.exports = __toCommonJS(index_exports);
5417
5467
  }
5418
5468
  }
5419
5469
  function updateSidebarActive(sectionId) {
5420
- var navLinks = document.querySelectorAll(config.navSelector);
5470
+ const navLinks = document.querySelectorAll(config.navSelector);
5421
5471
  navLinks.forEach(function(link) {
5422
5472
  link.classList.remove("active");
5423
5473
  if (link.getAttribute("href") === "#" + sectionId) {
@@ -5431,7 +5481,7 @@ module.exports = __toCommonJS(index_exports);
5431
5481
  state.resultsContainer.classList.add("is-open");
5432
5482
  state.input.setAttribute("aria-expanded", "true");
5433
5483
  if (typeof config.onOpen === "function") {
5434
- config.onOpen();
5484
+ safeInvokeCallback("onOpen", config.onOpen);
5435
5485
  }
5436
5486
  }
5437
5487
  function close() {
@@ -5442,7 +5492,7 @@ module.exports = __toCommonJS(index_exports);
5442
5492
  state.input.setAttribute("aria-expanded", "false");
5443
5493
  state.input.removeAttribute("aria-activedescendant");
5444
5494
  if (typeof config.onClose === "function") {
5445
- config.onClose();
5495
+ safeInvokeCallback("onClose", config.onClose);
5446
5496
  }
5447
5497
  }
5448
5498
  function destroy() {
@@ -5456,7 +5506,7 @@ module.exports = __toCommonJS(index_exports);
5456
5506
  clearTimeout(state.debounceTimer);
5457
5507
  }
5458
5508
  if (state.resultsContainer) {
5459
- state.resultsContainer.innerHTML = "";
5509
+ setResultsHtml("");
5460
5510
  }
5461
5511
  }
5462
5512
  function rebuild() {
@@ -5471,7 +5521,7 @@ module.exports = __toCommonJS(index_exports);
5471
5521
  function getIndex() {
5472
5522
  return state.index.slice();
5473
5523
  }
5474
- var instance = {
5524
+ const instance = {
5475
5525
  init,
5476
5526
  destroy,
5477
5527
  rebuild,
@@ -5484,12 +5534,12 @@ module.exports = __toCommonJS(index_exports);
5484
5534
  };
5485
5535
  return instance;
5486
5536
  }
5487
- var Search = {
5537
+ const Search = {
5488
5538
  // Factory method — creates and auto-initializes a new independent instance.
5489
5539
  // Always returns the instance so callers retain a reference even if the
5490
5540
  // DOM container is not yet available (they can retry init() later).
5491
5541
  create: function(options) {
5492
- var instance = createSearch(options);
5542
+ const instance = createSearch(options);
5493
5543
  if (instance) {
5494
5544
  instance.init();
5495
5545
  }
@@ -5563,6 +5613,565 @@ module.exports = __toCommonJS(index_exports);
5563
5613
  window.VanduoDocSearch = Search;
5564
5614
  })();
5565
5615
 
5616
+ // js/components/draggable.js
5617
+ (function() {
5618
+ "use strict";
5619
+ const Draggable = {
5620
+ // Store initialized draggables and their cleanup functions
5621
+ instances: /* @__PURE__ */ new Map(),
5622
+ // Store current drag state
5623
+ currentDrag: null,
5624
+ // Store touch state
5625
+ touchState: null,
5626
+ // Feedback element
5627
+ feedbackElement: null,
5628
+ /**
5629
+ * Initialize draggable components
5630
+ */
5631
+ init: function() {
5632
+ const draggables = document.querySelectorAll(".vd-draggable, [data-draggable]");
5633
+ draggables.forEach((element) => {
5634
+ if (this.instances.has(element)) {
5635
+ return;
5636
+ }
5637
+ this.initDraggable(element);
5638
+ });
5639
+ const containers = document.querySelectorAll(".vd-draggable-container, .vd-draggable-container-vertical");
5640
+ containers.forEach((container) => {
5641
+ if (!this.instances.has(container)) {
5642
+ this.initContainer(container);
5643
+ }
5644
+ });
5645
+ const dropZones = document.querySelectorAll(".vd-drop-zone");
5646
+ dropZones.forEach((zone) => {
5647
+ if (!this.instances.has(zone)) {
5648
+ this.initDropZone(zone);
5649
+ }
5650
+ });
5651
+ this.createFeedbackElement();
5652
+ },
5653
+ /**
5654
+ * Initialize a single draggable element
5655
+ * @param {HTMLElement} element - Draggable element
5656
+ */
5657
+ initDraggable: function(element) {
5658
+ const cleanupFunctions = [];
5659
+ if (!element.hasAttribute("draggable")) {
5660
+ element.setAttribute("draggable", "true");
5661
+ }
5662
+ if (!element.hasAttribute("tabindex")) {
5663
+ element.setAttribute("tabindex", "0");
5664
+ }
5665
+ element.setAttribute("role", "option");
5666
+ element.setAttribute("aria-roledescription", "draggable item");
5667
+ element.setAttribute("aria-grabbed", "false");
5668
+ const dragStartHandler = (e) => {
5669
+ this.handleDragStart(e, element);
5670
+ };
5671
+ element.addEventListener("dragstart", dragStartHandler);
5672
+ cleanupFunctions.push(() => element.removeEventListener("dragstart", dragStartHandler));
5673
+ const dragHandler = (e) => {
5674
+ this.handleDrag(e, element);
5675
+ };
5676
+ element.addEventListener("drag", dragHandler);
5677
+ cleanupFunctions.push(() => element.removeEventListener("drag", dragHandler));
5678
+ const dragEndHandler = (e) => {
5679
+ this.handleDragEnd(e, element);
5680
+ };
5681
+ element.addEventListener("dragend", dragEndHandler);
5682
+ cleanupFunctions.push(() => element.removeEventListener("dragend", dragEndHandler));
5683
+ const touchStartHandler = (e) => {
5684
+ this.handleTouchStart(e, element);
5685
+ };
5686
+ element.addEventListener("touchstart", touchStartHandler);
5687
+ cleanupFunctions.push(() => element.removeEventListener("touchstart", touchStartHandler));
5688
+ const touchMoveHandler = (e) => {
5689
+ this.handleTouchMove(e, element);
5690
+ };
5691
+ element.addEventListener("touchmove", touchMoveHandler);
5692
+ cleanupFunctions.push(() => element.removeEventListener("touchmove", touchMoveHandler));
5693
+ const touchEndHandler = (e) => {
5694
+ this.handleTouchEnd(e, element);
5695
+ };
5696
+ element.addEventListener("touchend", touchEndHandler);
5697
+ cleanupFunctions.push(() => element.removeEventListener("touchend", touchEndHandler));
5698
+ const touchCancelHandler = (e) => {
5699
+ this.handleTouchEnd(e, element);
5700
+ };
5701
+ element.addEventListener("touchcancel", touchCancelHandler);
5702
+ cleanupFunctions.push(() => element.removeEventListener("touchcancel", touchCancelHandler));
5703
+ const keydownHandler = (e) => {
5704
+ this.handleKeydown(e, element);
5705
+ };
5706
+ element.addEventListener("keydown", keydownHandler);
5707
+ cleanupFunctions.push(() => element.removeEventListener("keydown", keydownHandler));
5708
+ this.instances.set(element, { cleanup: cleanupFunctions });
5709
+ },
5710
+ /**
5711
+ * Initialize a draggable container
5712
+ * @param {HTMLElement} container - Draggable container
5713
+ */
5714
+ initContainer: function(container) {
5715
+ container.setAttribute("role", "listbox");
5716
+ container.setAttribute("aria-label", container.getAttribute("aria-label") || "Draggable items");
5717
+ const items = container.querySelectorAll(".vd-draggable-item");
5718
+ items.forEach((item) => {
5719
+ if (!this.instances.has(item)) {
5720
+ this.initDraggable(item);
5721
+ }
5722
+ });
5723
+ const cleanupFunctions = [];
5724
+ const dragEnterHandler = (e) => {
5725
+ e.preventDefault();
5726
+ e.dataTransfer.dropEffect = "move";
5727
+ };
5728
+ const dragOverHandler = (e) => {
5729
+ e.preventDefault();
5730
+ e.dataTransfer.dropEffect = "move";
5731
+ if (!this.currentDrag) return;
5732
+ const draggingEl = this.currentDrag.element;
5733
+ if (!container.contains(draggingEl)) return;
5734
+ if (e.clientX === 0 && e.clientY === 0) return;
5735
+ this.handleReorder(container, draggingEl, e.clientX, e.clientY);
5736
+ };
5737
+ const dropHandler = (e) => {
5738
+ e.preventDefault();
5739
+ };
5740
+ container.addEventListener("dragenter", dragEnterHandler);
5741
+ container.addEventListener("dragover", dragOverHandler);
5742
+ container.addEventListener("drop", dropHandler);
5743
+ cleanupFunctions.push(() => {
5744
+ container.removeEventListener("dragenter", dragEnterHandler);
5745
+ container.removeEventListener("dragover", dragOverHandler);
5746
+ container.removeEventListener("drop", dropHandler);
5747
+ });
5748
+ this.instances.set(container, { cleanup: cleanupFunctions });
5749
+ },
5750
+ /**
5751
+ * Initialize a drop zone
5752
+ * @param {HTMLElement} zone - Drop zone element
5753
+ */
5754
+ initDropZone: function(zone) {
5755
+ const cleanupFunctions = [];
5756
+ zone.setAttribute("role", "region");
5757
+ zone.setAttribute("aria-dropeffect", "move");
5758
+ if (!zone.hasAttribute("aria-label")) {
5759
+ zone.setAttribute("aria-label", "Drop zone");
5760
+ }
5761
+ const dragOverHandler = (e) => {
5762
+ e.preventDefault();
5763
+ this.handleDragOver(e, zone);
5764
+ };
5765
+ zone.addEventListener("dragover", dragOverHandler);
5766
+ cleanupFunctions.push(() => zone.removeEventListener("dragover", dragOverHandler));
5767
+ const dragEnterHandler = (e) => {
5768
+ e.preventDefault();
5769
+ this.handleDragEnter(e, zone);
5770
+ };
5771
+ zone.addEventListener("dragenter", dragEnterHandler);
5772
+ cleanupFunctions.push(() => zone.removeEventListener("dragenter", dragEnterHandler));
5773
+ const dragLeaveHandler = (e) => {
5774
+ this.handleDragLeave(e, zone);
5775
+ };
5776
+ zone.addEventListener("dragleave", dragLeaveHandler);
5777
+ cleanupFunctions.push(() => zone.removeEventListener("dragleave", dragLeaveHandler));
5778
+ const dropHandler = (e) => {
5779
+ e.preventDefault();
5780
+ this.handleDrop(e, zone);
5781
+ };
5782
+ zone.addEventListener("drop", dropHandler);
5783
+ cleanupFunctions.push(() => zone.removeEventListener("drop", dropHandler));
5784
+ this.instances.set(zone, { cleanup: cleanupFunctions });
5785
+ },
5786
+ /**
5787
+ * Create feedback element for drag operations
5788
+ */
5789
+ createFeedbackElement: function() {
5790
+ if (!this.feedbackElement) {
5791
+ const existing = document.querySelector(".vd-drag-feedback");
5792
+ if (existing) {
5793
+ this.feedbackElement = existing;
5794
+ return;
5795
+ }
5796
+ this.feedbackElement = document.createElement("div");
5797
+ this.feedbackElement.className = "vd-drag-feedback hidden";
5798
+ this.feedbackElement.setAttribute("role", "presentation");
5799
+ document.body.appendChild(this.feedbackElement);
5800
+ }
5801
+ },
5802
+ /**
5803
+ * Handle drag start event
5804
+ * @param {DragEvent} e - Drag event
5805
+ * @param {HTMLElement} element - Draggable element
5806
+ */
5807
+ handleDragStart: function(e, element) {
5808
+ element.classList.add("is-dragging");
5809
+ element.setAttribute("aria-grabbed", "true");
5810
+ this.currentDrag = {
5811
+ element,
5812
+ initialPosition: { x: e.clientX, y: e.clientY },
5813
+ initialBounds: element.getBoundingClientRect(),
5814
+ data: this.getData(element)
5815
+ };
5816
+ e.dataTransfer.effectAllowed = "move";
5817
+ e.dataTransfer.setData("text/plain", this.currentDrag.data);
5818
+ element.dispatchEvent(new CustomEvent("draggable:start", {
5819
+ bubbles: true,
5820
+ detail: {
5821
+ element,
5822
+ data: this.currentDrag.data,
5823
+ position: { x: e.clientX, y: e.clientY }
5824
+ }
5825
+ }));
5826
+ },
5827
+ /**
5828
+ * Handle drag event
5829
+ * @param {DragEvent} e - Drag event
5830
+ * @param {HTMLElement} element - Draggable element
5831
+ */
5832
+ handleDrag: function(e, element) {
5833
+ if (!this.currentDrag) return;
5834
+ element.dispatchEvent(new CustomEvent("draggable:drag", {
5835
+ bubbles: true,
5836
+ detail: {
5837
+ element,
5838
+ data: this.currentDrag.data,
5839
+ position: { x: e.clientX, y: e.clientY },
5840
+ delta: {
5841
+ x: e.clientX - this.currentDrag.initialPosition.x,
5842
+ y: e.clientY - this.currentDrag.initialPosition.y
5843
+ }
5844
+ }
5845
+ }));
5846
+ },
5847
+ /**
5848
+ * Handle drag end event
5849
+ * @param {DragEvent} e - Drag event
5850
+ * @param {HTMLElement} element - Draggable element
5851
+ */
5852
+ handleDragEnd: function(e, element) {
5853
+ element.classList.remove("is-dragging");
5854
+ element.classList.add("is-dropped");
5855
+ setTimeout(() => element.classList.remove("is-dropped"), 300);
5856
+ element.setAttribute("aria-grabbed", "false");
5857
+ if (this.feedbackElement) {
5858
+ this.feedbackElement.classList.add("hidden");
5859
+ }
5860
+ const data = this.currentDrag?.data || this.getData(element);
5861
+ const initialPos = this.currentDrag?.initialPosition || { x: 0, y: 0 };
5862
+ element.dispatchEvent(new CustomEvent("draggable:end", {
5863
+ bubbles: true,
5864
+ detail: {
5865
+ element,
5866
+ data,
5867
+ position: { x: e.clientX, y: e.clientY },
5868
+ delta: {
5869
+ x: e.clientX - initialPos.x,
5870
+ y: e.clientY - initialPos.y
5871
+ }
5872
+ }
5873
+ }));
5874
+ this.currentDrag = null;
5875
+ },
5876
+ /**
5877
+ * Handle touch start event (for mobile)
5878
+ * @param {TouchEvent} e - Touch event
5879
+ * @param {HTMLElement} element - Draggable element
5880
+ */
5881
+ handleTouchStart: function(e, element) {
5882
+ const touch = e.touches[0];
5883
+ this.touchState = {
5884
+ element,
5885
+ startX: touch.clientX,
5886
+ startY: touch.clientY,
5887
+ startTime: Date.now(),
5888
+ isDragging: false
5889
+ };
5890
+ },
5891
+ /**
5892
+ * Handle touch move event (for mobile)
5893
+ * @param {TouchEvent} e - Touch event
5894
+ * @param {HTMLElement} element - Draggable element
5895
+ */
5896
+ handleTouchMove: function(e, element) {
5897
+ if (!this.touchState) return;
5898
+ const touch = e.touches[0];
5899
+ const deltaX = touch.clientX - this.touchState.startX;
5900
+ const deltaY = touch.clientY - this.touchState.startY;
5901
+ if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
5902
+ e.preventDefault();
5903
+ if (!this.touchState.isDragging) {
5904
+ this.touchState.isDragging = true;
5905
+ element.classList.add("is-dragging");
5906
+ element.setAttribute("aria-grabbed", "true");
5907
+ this.currentDrag = {
5908
+ element,
5909
+ initialPosition: { x: this.touchState.startX, y: this.touchState.startY },
5910
+ initialBounds: element.getBoundingClientRect(),
5911
+ data: this.getData(element)
5912
+ };
5913
+ element.dispatchEvent(new CustomEvent("draggable:start", {
5914
+ bubbles: true,
5915
+ detail: {
5916
+ element,
5917
+ data: this.currentDrag.data,
5918
+ position: { x: touch.clientX, y: touch.clientY }
5919
+ }
5920
+ }));
5921
+ }
5922
+ this.updateFeedback(touch.clientX, touch.clientY);
5923
+ if (this.currentDrag) {
5924
+ element.dispatchEvent(new CustomEvent("draggable:drag", {
5925
+ bubbles: true,
5926
+ detail: {
5927
+ element,
5928
+ data: this.currentDrag.data,
5929
+ position: { x: touch.clientX, y: touch.clientY },
5930
+ delta: { x: deltaX, y: deltaY }
5931
+ }
5932
+ }));
5933
+ const container = element.closest(".vd-draggable-container");
5934
+ if (container && container.contains(element)) {
5935
+ this.handleReorder(container, element, touch.clientX, touch.clientY);
5936
+ }
5937
+ }
5938
+ }
5939
+ },
5940
+ /**
5941
+ * Handle touch end event (for mobile)
5942
+ * @param {TouchEvent} e - Touch event
5943
+ * @param {HTMLElement} element - Draggable element
5944
+ */
5945
+ handleTouchEnd: function(e, element) {
5946
+ if (this.touchState && this.touchState.isDragging) {
5947
+ e.preventDefault();
5948
+ element.classList.remove("is-dragging");
5949
+ element.classList.add("is-dropped");
5950
+ element.setAttribute("aria-grabbed", "false");
5951
+ setTimeout(() => element.classList.remove("is-dropped"), 300);
5952
+ if (this.feedbackElement) {
5953
+ this.feedbackElement.classList.add("hidden");
5954
+ }
5955
+ const endTouch = e.changedTouches[0];
5956
+ const data = this.currentDrag?.data || this.getData(element);
5957
+ const startX = this.touchState?.startX || 0;
5958
+ const startY = this.touchState?.startY || 0;
5959
+ element.dispatchEvent(new CustomEvent("draggable:end", {
5960
+ bubbles: true,
5961
+ detail: {
5962
+ element,
5963
+ data,
5964
+ position: { x: endTouch.clientX, y: endTouch.clientY },
5965
+ delta: {
5966
+ x: endTouch.clientX - startX,
5967
+ y: endTouch.clientY - startY
5968
+ }
5969
+ }
5970
+ }));
5971
+ }
5972
+ this.touchState = null;
5973
+ this.currentDrag = null;
5974
+ },
5975
+ /**
5976
+ * Handle drag over event
5977
+ * @param {DragEvent} e - Drag event
5978
+ * @param {HTMLElement} _zone - Drop zone element
5979
+ */
5980
+ handleDragOver: function(e, _zone) {
5981
+ e.preventDefault();
5982
+ e.dataTransfer.dropEffect = "move";
5983
+ },
5984
+ /**
5985
+ * Handle drag enter event
5986
+ * @param {DragEvent} e - Drag event
5987
+ * @param {HTMLElement} zone - Drop zone element
5988
+ */
5989
+ handleDragEnter: function(e, zone) {
5990
+ e.preventDefault();
5991
+ zone.classList.add("is-drag-over");
5992
+ },
5993
+ /**
5994
+ * Handle drag leave event
5995
+ * @param {DragEvent} e - Drag event
5996
+ * @param {HTMLElement} zone - Drop zone element
5997
+ */
5998
+ handleDragLeave: function(e, zone) {
5999
+ zone.classList.remove("is-drag-over");
6000
+ },
6001
+ /**
6002
+ * Handle drop event
6003
+ * @param {DragEvent} e - Drag event
6004
+ * @param {HTMLElement} zone - Drop zone element
6005
+ */
6006
+ handleDrop: function(e, zone) {
6007
+ e.preventDefault();
6008
+ zone.classList.remove("is-drag-over");
6009
+ zone.dispatchEvent(new CustomEvent("draggable:drop", {
6010
+ bubbles: true,
6011
+ detail: {
6012
+ zone,
6013
+ element: this.currentDrag?.element,
6014
+ data: this.currentDrag?.data,
6015
+ position: { x: e.clientX, y: e.clientY }
6016
+ }
6017
+ }));
6018
+ },
6019
+ /**
6020
+ * Reorder elements in container based on cursor position
6021
+ * @param {HTMLElement} container
6022
+ * @param {HTMLElement} element
6023
+ * @param {number} clientX
6024
+ * @param {number} clientY
6025
+ */
6026
+ handleReorder: function(container, element, clientX, clientY) {
6027
+ const isVertical = container.classList.contains("vd-draggable-container-vertical");
6028
+ const siblings = [...container.querySelectorAll(".vd-draggable-item:not(.is-dragging), .vd-draggable:not(.is-dragging)")];
6029
+ const nextSibling = siblings.reduce((closest, child) => {
6030
+ const box = child.getBoundingClientRect();
6031
+ const offset = isVertical ? clientY - box.top - box.height / 2 : clientX - box.left - box.width / 2;
6032
+ if (offset < 0 && offset > closest.offset) {
6033
+ return { offset, element: child };
6034
+ } else {
6035
+ return closest;
6036
+ }
6037
+ }, { offset: Number.NEGATIVE_INFINITY }).element;
6038
+ if (nextSibling == null) {
6039
+ container.appendChild(element);
6040
+ } else {
6041
+ container.insertBefore(element, nextSibling);
6042
+ }
6043
+ },
6044
+ /**
6045
+ * Handle keyboard events
6046
+ * @param {KeyboardEvent} e - Keyboard event
6047
+ * @param {HTMLElement} element - Draggable element
6048
+ */
6049
+ handleKeydown: function(e, element) {
6050
+ switch (e.key) {
6051
+ case "Enter":
6052
+ case " ":
6053
+ e.preventDefault();
6054
+ element.click();
6055
+ break;
6056
+ case "Escape":
6057
+ if (element.classList.contains("is-dragging")) {
6058
+ element.classList.remove("is-dragging");
6059
+ element.setAttribute("aria-grabbed", "false");
6060
+ if (this.feedbackElement) {
6061
+ this.feedbackElement.classList.add("hidden");
6062
+ }
6063
+ this.currentDrag = null;
6064
+ }
6065
+ break;
6066
+ case "ArrowUp":
6067
+ case "ArrowLeft": {
6068
+ e.preventDefault();
6069
+ const prev = element.previousElementSibling;
6070
+ if (prev && (prev.classList.contains("vd-draggable") || prev.classList.contains("vd-draggable-item"))) {
6071
+ element.parentNode.insertBefore(element, prev);
6072
+ element.focus();
6073
+ element.dispatchEvent(new CustomEvent("draggable:reorder", {
6074
+ bubbles: true,
6075
+ detail: { element, direction: "up" }
6076
+ }));
6077
+ }
6078
+ break;
6079
+ }
6080
+ case "ArrowDown":
6081
+ case "ArrowRight": {
6082
+ e.preventDefault();
6083
+ const next = element.nextElementSibling;
6084
+ if (next && (next.classList.contains("vd-draggable") || next.classList.contains("vd-draggable-item"))) {
6085
+ element.parentNode.insertBefore(next, element);
6086
+ element.focus();
6087
+ element.dispatchEvent(new CustomEvent("draggable:reorder", {
6088
+ bubbles: true,
6089
+ detail: { element, direction: "down" }
6090
+ }));
6091
+ }
6092
+ break;
6093
+ }
6094
+ }
6095
+ },
6096
+ /**
6097
+ * Get data from draggable element
6098
+ * @param {HTMLElement} element - Draggable element
6099
+ * @returns {string} Data associated with the element
6100
+ */
6101
+ getData: function(element) {
6102
+ return element.dataset.draggable || element.textContent.trim();
6103
+ },
6104
+ /**
6105
+ * Update drag feedback element
6106
+ * @param {number} x - Current X coordinate
6107
+ * @param {number} y - Current Y coordinate
6108
+ */
6109
+ updateFeedback: function(x, y) {
6110
+ if (!this.currentDrag) return;
6111
+ this.feedbackElement.classList.remove("hidden");
6112
+ const rect = this.currentDrag.initialBounds;
6113
+ this.feedbackElement.innerHTML = "";
6114
+ const clone = this.currentDrag.element.cloneNode(true);
6115
+ this.feedbackElement.appendChild(clone);
6116
+ Object.assign(this.feedbackElement.style, {
6117
+ left: x - 20 + "px",
6118
+ top: y - 20 + "px",
6119
+ width: rect.width + "px",
6120
+ height: rect.height + "px"
6121
+ });
6122
+ },
6123
+ /**
6124
+ * Make an element draggable programmatically
6125
+ * @param {HTMLElement|string} element - Element or selector
6126
+ * @param {Object} options - Configuration options
6127
+ */
6128
+ makeDraggable: function(element, options = {}) {
6129
+ const el = typeof element === "string" ? document.querySelector(element) : element;
6130
+ if (el && !this.instances.has(el)) {
6131
+ el.classList.add("vd-draggable");
6132
+ el.setAttribute("draggable", "true");
6133
+ if (options.data) {
6134
+ el.dataset.draggable = options.data;
6135
+ }
6136
+ this.initDraggable(el);
6137
+ }
6138
+ },
6139
+ /**
6140
+ * Remove draggable functionality from an element
6141
+ * @param {HTMLElement|string} element - Element or selector
6142
+ */
6143
+ removeDraggable: function(element) {
6144
+ const el = typeof element === "string" ? document.querySelector(element) : element;
6145
+ if (el && this.instances.has(el)) {
6146
+ const instance = this.instances.get(el);
6147
+ instance.cleanup.forEach((fn) => fn());
6148
+ this.instances.delete(el);
6149
+ el.classList.remove("vd-draggable");
6150
+ el.removeAttribute("draggable");
6151
+ el.removeAttribute("data-draggable");
6152
+ }
6153
+ },
6154
+ /**
6155
+ * Destroy a draggable instance and clean up event listeners
6156
+ * @param {HTMLElement} element - Draggable element
6157
+ */
6158
+ destroy: function(element) {
6159
+ this.removeDraggable(element);
6160
+ },
6161
+ /**
6162
+ * Destroy all draggable instances
6163
+ */
6164
+ destroyAll: function() {
6165
+ const instances = Array.from(this.instances.keys());
6166
+ instances.forEach((element) => this.destroy(element));
6167
+ }
6168
+ };
6169
+ if (typeof window.Vanduo !== "undefined") {
6170
+ window.Vanduo.register("draggable", Draggable);
6171
+ }
6172
+ window.VanduoDraggable = Draggable;
6173
+ })();
6174
+
5566
6175
  // js/index.js
5567
6176
  var Vanduo = window.Vanduo;
5568
6177
  var index_default = Vanduo;