1(function polyfill() { 2 const relList = document.createElement("link").relList; 3 if (relList && relList.supports && relList.supports("modulepreload")) return; 4 for (const link of document.querySelectorAll("link[rel=\"modulepreload\"]")) processPreload(link); 5 new MutationObserver((mutations) => { 6 for (const mutation of mutations) { 7 if (mutation.type !== "childList") continue; 8 for (const node of mutation.addedNodes) if (node.tagName === "LINK" && node.rel === "modulepreload") processPreload(node); 9 } 10 }).observe(document, { 11 childList: true, 12 subtree: true 13 }); 14 function getFetchOpts(link) { 15 const fetchOpts = {}; 16 if (link.integrity) fetchOpts.integrity = link.integrity; 17 if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy; 18 if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include"; 19 else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit"; 20 else fetchOpts.credentials = "same-origin"; 21 return fetchOpts; 22 } 23 function processPreload(link) { 24 if (link.ep) return; 25 link.ep = true; 26 const fetchOpts = getFetchOpts(link); 27 fetch(link.href, fetchOpts); 28 } 29})(); 30function kebabToCamel(str) { 31 return str.replaceAll(/-([a-z])/g, (_, letter) => letter.toUpperCase()); 32} 33function isNil(value) { 34 return value === null || value === void 0; 35} 36function isSignal(value) { 37 return typeof value === "object" && value !== null && "get" in value && "subscribe" in value && typeof value.get === "function" && typeof value.subscribe === "function"; 38} 39function findScopedSignal(scope, path) { 40 const trimmed = path.trim(); 41 if (!trimmed) return; 42 const parts = trimmed.split("."); 43 let current = scope; 44 for (const part of parts) { 45 if (isNil(current) || typeof current !== "object") return; 46 const record = current; 47 if (Object.hasOwn(record, part)) { 48 current = record[part]; 49 continue; 50 } 51 const camelCandidate = kebabToCamel(part); 52 if (Object.hasOwn(record, camelCandidate)) { 53 current = record[camelCandidate]; 54 continue; 55 } 56 const lowerPart = part.toLowerCase(); 57 const matchedKey = Object.keys(record).find((key) => key.toLowerCase() === lowerPart); 58 if (!matchedKey) return; 59 current = record[matchedKey]; 60 } 61 if (isSignal(current)) return current; 62} 63function getComputedAttributes(el$1) { 64 const computed$1 = /* @__PURE__ */ new Map(); 65 for (const attr of el$1.attributes) if (attr.name.startsWith("data-volt-computed:")) { 66 const name = attr.name.slice(19); 67 const camelName = kebabToCamel(name); 68 computed$1.set(camelName, attr.value); 69 } 70 return computed$1; 71} 72function sleep(ms) { 73 return new Promise((resolve) => setTimeout(resolve, ms)); 74} 75function extractDeps(expr, scope) { 76 const deps = []; 77 const seen = /* @__PURE__ */ new Set(); 78 const storeCalls = expr.matchAll(/\$store\.(get|set|has)\s*\(\s*['"]([^'"]+)['"]\s*(?:,|\))/g); 79 for (const match of storeCalls) { 80 const key = match[2]; 81 const storeKey = `$store.${key}`; 82 if (seen.has(storeKey)) continue; 83 seen.add(storeKey); 84 const store$1 = scope.$store; 85 if (store$1 && typeof store$1 === "object" && "_signals" in store$1) { 86 const signal$1 = store$1._signals.get(key); 87 if (signal$1 && !deps.includes(signal$1)) deps.push(signal$1); 88 } 89 } 90 const matches = expr.matchAll(/\b([a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*)*)\b/g); 91 for (const match of matches) { 92 const path = match[1]; 93 if ([ 94 "true", 95 "false", 96 "null", 97 "undefined" 98 ].includes(path)) continue; 99 if (seen.has(path)) continue; 100 seen.add(path); 101 const signal$1 = findScopedSignal(scope, path); 102 if (signal$1) { 103 deps.push(signal$1); 104 continue; 105 } 106 const topLevel = path.split(".")[0]; 107 const value = scope[topLevel]; 108 if (isSignal(value) && !deps.includes(value)) deps.push(value); 109 } 110 return deps; 111} 112function updateAndRegister(ctx, update, expr) { 113 update(); 114 const deps = extractDeps(expr, ctx.scope); 115 for (const dep of deps) { 116 const unsubscribe = dep.subscribe(update);
117 ctx.cleanups.push(unsubscribe); 118 } 119} 120var transitionRegistry = /* @__PURE__ */ new Map(); 121var builtinPresets = { 122 fade: { 123 enter: { 124 from: { opacity: 0 }, 125 to: { opacity: 1 }, 126 duration: 300, 127 easing: "ease" 128 }, 129 leave: { 130 from: { opacity: 1 }, 131 to: { opacity: 0 }, 132 duration: 300, 133 easing: "ease" 134 } 135 }, 136 "slide-up": { 137 enter: { 138 from: { 139 opacity: 0, 140 transform: "translateY(20px)" 141 }, 142 to: { 143 opacity: 1, 144 transform: "translateY(0)" 145 }, 146 duration: 300, 147 easing: "ease-out" 148 }, 149 leave: { 150 from: { 151 opacity: 1, 152 transform: "translateY(0)" 153 }, 154 to: { 155 opacity: 0, 156 transform: "translateY(-20px)" 157 }, 158 duration: 300, 159 easing: "ease-in" 160 } 161 }, 162 "slide-down": { 163 enter: { 164 from: { 165 opacity: 0, 166 transform: "translateY(-20px)" 167 }, 168 to: { 169 opacity: 1, 170 transform: "translateY(0)" 171 }, 172 duration: 300, 173 easing: "ease-out" 174 }, 175 leave: { 176 from: { 177 opacity: 1, 178 transform: "translateY(0)" 179 }, 180 to: { 181 opacity: 0, 182 transform: "translateY(20px)" 183 }, 184 duration: 300, 185 easing: "ease-in" 186 } 187 }, 188 "slide-left": { 189 enter: { 190 from: { 191 opacity: 0, 192 transform: "translateX(20px)" 193 }, 194 to: { 195 opacity: 1, 196 transform: "translateX(0)" 197 }, 198 duration: 300, 199 easing: "ease-out" 200 }, 201 leave: { 202 from: { 203 opacity: 1, 204 transform: "translateX(0)" 205 }, 206 to: { 207 opacity: 0, 208 transform: "translateX(-20px)" 209 }, 210 duration: 300, 211 easing: "ease-in" 212 } 213 }, 214 "slide-right": { 215 enter: { 216 from: { 217 opacity: 0, 218 transform: "translateX(-20px)" 219 }, 220 to: { 221 opacity: 1, 222 transform: "translateX(0)" 223 }, 224 duration: 300, 225 easing: "ease-out" 226 }, 227 leave: { 228 from: { 229 opacity: 1, 230 transform: "translateX(0)" 231 }, 232 to: { 233 opacity: 0, 234 transform: "translateX(20px)" 235 }, 236 duration: 300, 237 easing: "ease-in" 238 } 239 }, 240 scale: {
241 enter: { 242 from: { 243 opacity: 0, 244 transform: "scale(0.95)" 245 }, 246 to: { 247 opacity: 1, 248 transform: "scale(1)" 249 }, 250 duration: 300, 251 easing: "ease-out" 252 }, 253 leave: { 254 from: { 255 opacity: 1, 256 transform: "scale(1)" 257 }, 258 to: { 259 opacity: 0, 260 transform: "scale(0.95)" 261 }, 262 duration: 300, 263 easing: "ease-in" 264 } 265 }, 266 blur: { 267 enter: { 268 from: { 269 opacity: 0, 270 filter: "blur(10px)" 271 }, 272 to: { 273 opacity: 1, 274 filter: "blur(0)" 275 }, 276 duration: 300, 277 easing: "ease" 278 }, 279 leave: { 280 from: { 281 opacity: 1, 282 filter: "blur(0)" 283 }, 284 to: { 285 opacity: 0, 286 filter: "blur(10px)" 287 }, 288 duration: 300, 289 easing: "ease" 290 } 291 } 292}; 293function initBuiltinPresets() { 294 for (const [name, preset] of Object.entries(builtinPresets)) transitionRegistry.set(name, preset); 295} 296initBuiltinPresets(); 297function getTransition(name) { 298 return transitionRegistry.get(name); 299} 300function parseTransitionValue(value) { 301 const parts = value.split("."); 302 const presetName = parts[0]?.trim(); 303 if (!presetName) return; 304 const preset = getTransition(presetName); 305 if (!preset) { 306 console.error(`[Volt] Unknown transition preset: "${presetName}"`); 307 return; 308 } 309 const result = { preset }; 310 if (parts.length > 1) { 311 const duration = Number.parseInt(parts[1], 10); 312 if (!Number.isNaN(duration)) result.duration = duration; 313 } 314 if (parts.length > 2) { 315 const delay = Number.parseInt(parts[2], 10); 316 if (!Number.isNaN(delay)) result.delay = delay; 317 } 318 return result; 319} 320const easings = { 321 linear: "linear", 322 ease: "ease", 323 "ease-in": "ease-in", 324 "ease-out": "ease-out", 325 "ease-in-out": "ease-in-out", 326 "ease-in-sine": "cubic-bezier(0.12, 0, 0.39, 0)", 327 "ease-out-sine": "cubic-bezier(0.61, 1, 0.88, 1)", 328 "ease-in-out-sine": "cubic-bezier(0.37, 0, 0.63, 1)", 329 "ease-in-quad": "cubic-bezier(0.11, 0, 0.5, 0)", 330 "ease-out-quad": "cubic-bezier(0.5, 1, 0.89, 1)", 331 "ease-in-out-quad": "cubic-bezier(0.45, 0, 0.55, 1)", 332 "ease-in-cubic": "cubic-bezier(0.32, 0, 0.67, 0)", 333 "ease-out-cubic": "cubic-bezier(0.33, 1, 0.68, 1)", 334 "ease-in-out-cubic": "cubic-bezier(0.65, 0, 0.35, 1)", 335 "ease-in-quart": "cubic-bezier(0.5, 0, 0.75, 0)", 336 "ease-out-quart": "cubic-bezier(0.25, 1, 0.5, 1)", 337 "ease-in-out-quart": "cubic-bezier(0.76, 0, 0.24, 1)", 338 "ease-in-quint": "cubic-bezier(0.64, 0, 0.78, 0)", 339 "ease-out-quint": "cubic-bezier(0.22, 1, 0.36, 1)", 340 "ease-in-out-quint": "cubic-bezier(0.83, 0, 0.17, 1)", 341 "ease-in-expo": "cubic-bezier(0.7, 0, 0.84, 0)", 342 "ease-out-expo": "cubic-bezier(0.16, 1, 0.3, 1)", 343 "ease-in-out-expo": "cubic-bezier(0.87, 0, 0.13, 1)", 344 "ease-in-circ": "cubic-bezier(0.55, 0, 1, 0.45)", 345 "ease-out-circ": "cubic-bezier(0, 0.55, 0.45, 1)", 346 "ease-in-out-circ": "cubic-bezier(0.85, 0, 0.15, 1)", 347 "ease-in-back": "cubic-bezier(0.36, 0, 0.66, -0.56)", 348 "ease-out-back": "cubic-bezier(0.34, 1.56, 0.64, 1)", 349 "ease-in-out-back": "cubic-bezier(0.68, -0.6, 0.32, 1.6)" 350}; 351function getEasing(name) { 352 return easings[name] ?? name; 353} 354function prefersReducedMotion() { 355 if (globalThis.window === void 0 || !globalThis.matchMedia) return false; 356 return globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches; 357} 358function applyOverrides(phase, duration, delay) { 359 return { 360 ...phase, 361 ...duration !== void 0 && { duration }, 362 ...delay !== void 0 && { delay } 363 }; 364} 365function supportsViewTransitions() { 366 return typeof document !== "undefined" && "startViewTransition" in document; 367} 368async function startViewTransition(cb, opts = {}) { 369 const { respectReducedMotion = true, forceFallback = false } = opts; 370 if (respectReducedMotion && prefersReducedMotion()) { 371 await cb(); 372 return; 373 } 374 if (!forceFallback && supportsViewTransitions()) { 375 const namedElements = applyViewTransitionNames(opts.name, opts.elements); 376 try { 377 await document.startViewTransition(cb).finished; 378 } finally { 379 removeViewTransitionNames(namedElements); 380 } 381 } else await cb(); 382} 383function applyViewTransitionNames(baseName, elements) { 384 const originalNames = /* @__PURE__ */ new Map(); 385 if (!baseName || !elements || elements.length === 0) return originalNames; 386 for (const [index, element] of elements.entries()) { 387 const originalValue = element.style.viewTransitionName; 388 originalNames.set(element, originalValue); 389 const transitionName = elements.length === 1 ? baseName : `${baseName}-${index}`; 390 element.style.viewTransitionName = transitionName; 391 } 392 return originalNames; 393} 394function removeViewTransitionNames(namedElements) { 395 for (const [element, originalValue] of namedElements) if (originalValue) element.style.viewTransitionName = originalValue; 396 else element.style.viewTransitionName = ""; 397} 398function withViewTransition(cb, respectReducedMotion = true) { 399 if (respectReducedMotion && prefersReducedMotion()) { 400 cb(); 401 return; 402 } 403 if (supportsViewTransitions()) document.startViewTransition(cb); 404 else cb(); 405} 406function applyStyles(element, styles) { 407 for (const [property, value] of Object.entries(styles)) { 408 const cssProperty = property.replaceAll(/[A-Z]/g, (m) => `-${m.toLowerCase()}`); 409 element.style.setProperty(cssProperty, String(value)); 410 } 411} 412function applyClasses(el$1, classes) { 413 for (const cls of classes) el$1.classList.add(cls); 414} 415function rmClasses(el$1, classes) { 416 for (const cls of classes) el$1.classList.remove(cls); 417} 418async function execEnter(element, phase, useViewTransitions) { 419 const duration = phase.duration ?? 300;
420 const delay = phase.delay ?? 0; 421 const easing = getEasing(phase.easing ?? "ease"); 422 if (prefersReducedMotion()) { 423 if (phase.to) applyStyles(element, phase.to); 424 if (phase.classes) applyClasses(element, phase.classes); 425 return; 426 } 427 if (phase.from) applyStyles(element, phase.from); 428 if (phase.classes) applyClasses(element, phase.classes); 429 element.offsetHeight; 430 element.style.transition = `all ${duration}ms ${easing} ${delay}ms`; 431 if (delay > 0) await sleep(delay); 432 const transitionPromise = new Promise((resolve) => { 433 const handleTransitionEnd = (event) => { 434 if (event.target === element) { 435 element.removeEventListener("transitionend", handleTransitionEnd); 436 resolve(); 437 } 438 }; 439 element.addEventListener("transitionend", handleTransitionEnd); 440 setTimeout(() => { 441 element.removeEventListener("transitionend", handleTransitionEnd); 442 resolve(); 443 }, duration + delay + 50); 444 }); 445 if (useViewTransitions) withViewTransition(() => { 446 if (phase.to) applyStyles(element, phase.to); 447 }, false); 448 else if (phase.to) applyStyles(element, phase.to); 449 await transitionPromise; 450 element.style.transition = ""; 451 if (phase.classes) rmClasses(element, phase.classes); 452} 453async function execLeave(element, phase, useViewTransitions) { 454 const duration = phase.duration ?? 300; 455 const delay = phase.delay ?? 0; 456 const easing = getEasing(phase.easing ?? "ease"); 457 if (prefersReducedMotion()) { 458 if (phase.to) applyStyles(element, phase.to); 459 if (phase.classes) applyClasses(element, phase.classes); 460 return; 461 } 462 if (phase.from) applyStyles(element, phase.from); 463 if (phase.classes) applyClasses(element, phase.classes); 464 element.offsetHeight; 465 element.style.transition = `all ${duration}ms ${easing} ${delay}ms`; 466 if (delay > 0) await sleep(delay); 467 const transitionPromise = new Promise((resolve) => { 468 const handleTransitionEnd = (event) => { 469 if (event.target === element) { 470 element.removeEventListener("transitionend", handleTransitionEnd); 471 resolve(); 472 } 473 }; 474 element.addEventListener("transitionend", handleTransitionEnd); 475 setTimeout(() => { 476 element.removeEventListener("transitionend", handleTransitionEnd); 477 resolve(); 478 }, duration + delay + 50); 479 }); 480 if (useViewTransitions) withViewTransition(() => { 481 if (phase.to) applyStyles(element, phase.to); 482 }, false); 483 else if (phase.to) applyStyles(element, phase.to); 484 await transitionPromise; 485 element.style.transition = ""; 486 if (phase.classes) rmClasses(element, phase.classes); 487} 488function parseSurgeValue(value) { 489 const parts = value.split(":"); 490 if (parts.length === 2) { 491 const [signalPath, presetValue] = parts; 492 const parsed$1 = parseTransitionValue(presetValue.trim()); 493 if (!parsed$1) return; 494 return { 495 enterPreset: parsed$1.preset.enter, 496 leavePreset: parsed$1.preset.leave, 497 signalPath: signalPath.trim(), 498 useViewTransitions: true 499 }; 500 } 501 const parsed = parseTransitionValue(value.trim()); 502 if (!parsed) return; 503 return { 504 enterPreset: parsed.preset.enter, 505 leavePreset: parsed.preset.leave, 506 useViewTransitions: true 507 }; 508} 509function ensureInlineSurgeState(element) { 510 if (!element._vxSurgeConf) { 511 const attr = element.dataset.voltSurge; 512 if (attr) { 513 const parsed = parseSurgeValue(attr); 514 if (parsed) element._vxSurgeConf = parsed; 515 } 516 } 517 if (!element._vxSurgeEnter) { 518 const enterAttr = element.dataset["voltSurge:enter"]; 519 if (enterAttr) { 520 const enterPhase = parsePhaseValue(enterAttr, "enter"); 521 if (enterPhase) element._vxSurgeEnter = enterPhase; 522 } 523 } 524 if (!element._vxSurgeLeave) { 525 const leaveAttr = element.dataset["voltSurge:leave"]; 526 if (leaveAttr) { 527 const leavePhase = parsePhaseValue(leaveAttr, "leave"); 528 if (leavePhase) element._vxSurgeLeave = leavePhase; 529 } 530 } 531} 532function parsePhaseValue(value, phase) { 533 const parsed = parseTransitionValue(value.trim()); 534 if (!parsed) return; 535 const presetPhase = phase === "enter" ? parsed.preset.enter : parsed.preset.leave; 536 return applyOverrides(presetPhase, parsed.duration, parsed.delay); 537} 538function surgePlugin(ctx, value) { 539 const el$1 = ctx.element; 540 if (value.includes(":")) { 541 const [phase, presetValue] = value.split(":", 2); 542 if (phase === "enter") { 543 const enterPhase = parsePhaseValue(presetValue, "enter"); 544 if (!enterPhase) { 545 console.error(`[Volt] Invalid surge enter value: "${value}"`); 546 return; 547 } 548 el$1._vxSurgeEnter = enterPhase; 549 return; 550 } 551 if (phase === "leave") { 552 const leavePhase = parsePhaseValue(presetValue, "leave"); 553 if (!leavePhase) { 554 console.error(`[Volt] Invalid surge leave value: "${value}"`); 555 return; 556 } 557 el$1._vxSurgeLeave = leavePhase; 558 return; 559 } 560 } 561 const config = parseSurgeValue(value); 562 if (!config) { 563 console.error(`[Volt] Invalid surge value: "${value}"`); 564 return; 565 } 566 if (!config.signalPath) { 567 el$1._vxSurgeConf = config; 568 return; 569 } 570 const signal$1 = ctx.findSignal(config.signalPath); 571 if (!signal$1) { 572 console.error(`[Volt] Signal "${config.signalPath}" not found for surge binding`); 573 return; 574 } 575 let isVisible = Boolean(signal$1.get()); 576 let isTransitioning = false;
577 if (!isVisible) el$1.style.display = "none"; 578 const handleTransition = async (shouldShow) => { 579 if (isTransitioning || shouldShow === isVisible) return; 580 isTransitioning = true; 581 if (shouldShow && config.enterPreset) { 582 el$1.style.display = ""; 583 await execEnter(el$1, config.enterPreset, config.useViewTransitions); 584 isVisible = true; 585 } else if (!shouldShow && config.leavePreset) { 586 await execLeave(el$1, config.leavePreset, config.useViewTransitions); 587 el$1.style.display = "none"; 588 isVisible = false; 589 } 590 isTransitioning = false; 591 }; 592 const unsubscribe = signal$1.subscribe((value$1) => { 593 handleTransition(Boolean(value$1)); 594 }); 595 ctx.addCleanup(unsubscribe); 596} 597async function executeSurgeEnter(element) { 598 const surgeEl = element; 599 ensureInlineSurgeState(surgeEl); 600 const config = surgeEl._vxSurgeConf; 601 const enterPhase = surgeEl._vxSurgeEnter ?? config?.enterPreset; 602 if (!enterPhase) return; 603 const useViewTransitions = config?.useViewTransitions ?? true; 604 await execEnter(element, enterPhase, useViewTransitions); 605} 606async function executeSurgeLeave(element) { 607 const surgeEl = element; 608 ensureInlineSurgeState(surgeEl); 609 const config = surgeEl._vxSurgeConf; 610 const leavePhase = surgeEl._vxSurgeLeave ?? config?.leavePreset; 611 if (!leavePhase) return; 612 const useViewTransitions = config?.useViewTransitions ?? true; 613 await execLeave(element, leavePhase, useViewTransitions); 614} 615function hasSurge(element) { 616 const surgeEl = element; 617 ensureInlineSurgeState(surgeEl); 618 return Boolean(surgeEl._vxSurgeConf || surgeEl._vxSurgeEnter || surgeEl._vxSurgeLeave); 619} 620const BOOLEAN_ATTRS = [ 621 "disabled", 622 "checked", 623 "selected", 624 "readonly", 625 "required", 626 "multiple", 627 "autofocus", 628 "autoplay", 629 "controls", 630 "loop", 631 "muted" 632]; 633const DANGEROUS_PROPERTIES = [ 634 "__proto__", 635 "prototype", 636 "constructor" 637]; 638const DANGEROUS_GLOBALS = [ 639 "window", 640 "self", 641 "global", 642 "globalThis", 643 "process", 644 "require", 645 "import", 646 "module", 647 "exports" 648]; 649const SAFE_GLOBALS = [ 650 "Array", 651 "Object", 652 "String", 653 "Number", 654 "Boolean", 655 "Date", 656 "Math", 657 "JSON", 658 "RegExp", 659 "Map", 660 "Set", 661 "Promise" 662]; 663function walkDOM(root) { 664 const elements = []; 665 function walk(element) { 666 if (hasVoltAttr(element)) { 667 elements.push(element); 668 if (Object.hasOwn(element.dataset, "voltFor") || Object.hasOwn(element.dataset, "voltIf")) return; 669 } 670 for (const child of element.children) walk(child); 671 } 672 walk(root); 673 return elements; 674} 675function hasVoltAttr(el$1) { 676 return [...el$1.attributes].some((attribute) => attribute.name.startsWith("data-volt-")); 677} 678function getVoltAttrs(el$1) { 679 const attrs = /* @__PURE__ */ new Map(); 680 for (const attr of el$1.attributes) if (attr.name.startsWith("data-volt-")) { 681 const name = attr.name.slice(10); 682 if (name === "state" || name.startsWith("computed:")) continue; 683 attrs.set(name, attr.value); 684 } 685 return attrs; 686} 687function setText(el$1, value) { 688 el$1.textContent = String(value ?? ""); 689} 690function setHTML(el$1, value) { 691 el$1.innerHTML = value; 692} 693function toggleClass(el$1, cls, add) { 694 el$1.classList.toggle(cls, add); 695} 696function isWrappedSignal(value) { 697 return value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.subscribe === "function"; 698} 699function unwrapIfSignal(value) { 700 if (isWrappedSignal(value)) return value.get(); 701 return value; 702} 703function parseClassBinding(value) { 704 const classes = /* @__PURE__ */ new Map(); 705 switch (typeof value) { 706 case "string": 707 for (const cls of value.split(/\s+/).filter(Boolean)) classes.set(cls, true); 708 break; 709 case "object": 710 if (value !== null) for (const [key, value_] of Object.entries(value)) { 711 const unwrapped = unwrapIfSignal(value_); 712 classes.set(key, Boolean(unwrapped)); 713 } 714 break; 715 case "boolean": 716 case "number": 717 classes.set(String(value), true); 718 break; 719 } 720 return classes; 721} 722var VoltError = class VoltError extends Error { 723 source; 724 level; 725 element; 726 directive; 727 expression; 728 cause; 729 timestamp; 730 context; 731 _stopped = false; 732 constructor(cause, context) { 733 const message = VoltError.buildMessage(cause, context); 734 super(message); 735 this.name = "VoltError"; 736 this.cause = cause; 737 this.source = context.source; 738 this.level = context.level ?? "error"; 739 this.element = context.element; 740 this.directive = context.directive; 741 this.expression = context.expression; 742 this.context = context; 743 this.timestamp = Date.now(); 744 if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor); 745 } 746 stopPropagation() { 747 this._stopped = true; 748 } 749 get stopped() { 750 return this._stopped; 751 } 752 static buildMessage(cause, context) { 753 const parts = []; 754 const level = context.level ?? "error"; 755 parts.push(`[${level.toUpperCase()}] [${context.source}
755] ${cause.message}`); 756 if (context.directive) parts.push(`Directive: ${context.directive}`); 757 if (context.expression) { 758 const truncated = context.expression.length > 100 ? `${context.expression.slice(0, 100)}...` : context.expression; 759 parts.push(`Expression: ${truncated}`); 760 } 761 if (context.pluginName) parts.push(`Plugin: ${context.pluginName}`); 762 if (context.httpMethod && context.httpUrl) { 763 parts.push(`HTTP: ${context.httpMethod} ${context.httpUrl}`); 764 if (context.httpStatus) parts.push(`Status: ${context.httpStatus}`); 765 } 766 if (context.hookName) parts.push(`Hook: ${context.hookName}`); 767 if (context.element) { 768 const tag = context.element.tagName.toLowerCase(); 769 const id = context.element.id ? `#${context.element.id}` : ""; 770 const cls = context.element.className ? `.${context.element.className.split(" ").join(".")}` : ""; 771 parts.push(`Element: <${tag}${id}${cls}>`); 772 } 773 return parts.join(" | "); 774 } 775 toJSON() { 776 return { 777 name: this.name, 778 message: this.message, 779 source: this.source, 780 level: this.level, 781 directive: this.directive, 782 expression: this.expression, 783 timestamp: this.timestamp, 784 context: this.context, 785 cause: { 786 name: this.cause.name, 787 message: this.cause.message, 788 stack: this.cause.stack 789 }, 790 stack: this.stack 791 }; 792 } 793}; 794var EvaluatorError = class extends VoltError { 795 constructor(cause, context) { 796 super(cause, { 797 ...context, 798 source: "evaluator" 799 }); 800 this.name = "EvaluatorError"; 801 } 802}; 803var BindingError = class extends VoltError { 804 constructor(cause, context) { 805 super(cause, { 806 ...context, 807 source: "binding" 808 }); 809 this.name = "BindingError"; 810 } 811}; 812var EffectError = class extends VoltError { 813 constructor(cause, context) { 814 super(cause, { 815 ...context, 816 source: "effect" 817 }); 818 this.name = "EffectError"; 819 } 820}; 821var HttpError = class extends VoltError { 822 constructor(cause, context) { 823 super(cause, { 824 ...context, 825 source: "http" 826 }); 827 this.name = "HttpError"; 828 } 829}; 830var PluginError = class extends VoltError { 831 constructor(cause, context) { 832 super(cause, { 833 ...context, 834 source: "plugin" 835 }); 836 this.name = "PluginError"; 837 } 838}; 839var LifecycleError = class extends VoltError { 840 constructor(cause, context) { 841 super(cause, { 842 ...context, 843 source: "lifecycle" 844 }); 845 this.name = "LifecycleError"; 846 } 847}; 848var ChargeError = class extends VoltError { 849 constructor(cause, context) { 850 super(cause, { 851 ...context, 852 source: "charge" 853 }); 854 this.name = "ChargeError"; 855 } 856}; 857var UserError = class extends VoltError { 858 constructor(cause, context) { 859 super(cause, { 860 ...context, 861 source: "user" 862 }); 863 this.name = "UserError"; 864 } 865}; 866var errorHandlers = []; 867function report(error, context) { 868 const errorObj = error instanceof Error ? error : new Error(String(error)); 869 const voltError = createErrorBySource(errorObj, context); 870 if (errorHandlers.length === 0) { 871 const logFn = voltError.level === "warn" ? console.warn : console.error; 872 logFn(voltError.message); 873 logFn("Caused by:", voltError.cause); 874 if (voltError.element) logFn("Element:", voltError.element); 875 if (voltError.level === "fatal") throw voltError; 876 return; 877 } 878 for (const handler of errorHandlers) try { 879 handler(voltError); 880 if (voltError.stopped) break; 881 } catch (handlerError) { 882 console.error("Error in error handler:", handlerError); 883 } 884 if (voltError.level === "fatal") throw voltError; 885} 886function createErrorBySource(cause, context) { 887 switch (context.source) { 888 case "evaluator": return new EvaluatorError(cause, context); 889 case "binding": return new BindingError(cause, context); 890 case "effect": return new EffectError(cause, context); 891 case "http": return new HttpError(cause, context); 892 case "plugin": return new PluginError(cause, context); 893 case "lifecycle": return new LifecycleError(cause, context); 894 case "charge": return new ChargeError(cause, context); 895 case "user": return new UserError(cause, context); 896 default: return new VoltError(cause, context); 897 } 898} 899var EvaluationError = class extends Error { 900 expr; 901 cause; 902 constructor(expression, cause) {
903 const message = cause instanceof Error ? cause.message : String(cause); 904 super(`Error evaluating "${expression}": ${message}`); 905 this.name = "EvaluationError"; 906 this.expr = expression; 907 this.cause = cause; 908 } 909}; 910var dangerousProps = new Set(DANGEROUS_PROPERTIES); 911var dangerousGlobals = new Set(DANGEROUS_GLOBALS); 912var safeGlobals = new Set(SAFE_GLOBALS); 913var defaultWrapOptions = { unwrapSignals: false }; 914var readWrapOptions = { unwrapSignals: true }; 915function isDangerousProperty(key) { 916 if (typeof key !== "string" && typeof key !== "symbol") return false; 917 return dangerousProps.has(String(key)); 918} 919function hasSetMethod(dep) { 920 return typeof dep === "object" && dep !== null && "set" in dep && typeof dep.set === "function"; 921} 922function wrapSignal(signal$1, options$1) { 923 const hasSet = hasSetMethod(signal$1); 924 const wrapper = { 925 get: signal$1.get, 926 subscribe: signal$1.subscribe, 927 valueOf: () => signal$1.get(), 928 toString: () => String(signal$1.get()), 929 [Symbol.toPrimitive]: (_hint) => signal$1.get() 930 }; 931 if (hasSet) wrapper.set = signal$1.set; 932 return new Proxy(wrapper, { 933 get(target, prop) { 934 if (isDangerousProperty(prop)) return; 935 if (prop === "get" || prop === "subscribe") return target[prop]; 936 if (prop === "set" && hasSet) return target[prop]; 937 if (prop === "valueOf" || prop === "toString" || prop === Symbol.toPrimitive) return target[prop]; 938 if (prop === Symbol.iterator) { 939 const unwrapped$1 = signal$1.get(); 940 if (unwrapped$1 && typeof unwrapped$1 === "object" && Symbol.iterator in unwrapped$1) return unwrapped$1[Symbol.iterator].bind(unwrapped$1); 941 return; 942 } 943 const unwrapped = signal$1.get(); 944 if (unwrapped && (typeof unwrapped === "object" || typeof unwrapped === "function")) return wrapValue(unwrapped, options$1)[prop]; 945 if (unwrapped !== null && unwrapped !== void 0) { 946 const boxed = new Object(unwrapped); 947 const value = Reflect.get(boxed, prop, boxed); 948 if (typeof value === "function") return value.bind(unwrapped); 949 return wrapValue(value, options$1); 950 } 951 }, 952 has(_target, prop) { 953 if (isDangerousProperty(prop)) return false; 954 if (prop === "get" || prop === "subscribe") return true; 955 if (prop === "set" && hasSet) return true; 956 if (prop === Symbol.iterator) { 957 const unwrapped$1 = signal$1.get(); 958 return unwrapped$1 !== null && unwrapped$1 !== void 0 && typeof unwrapped$1 === "object" && Symbol.iterator in unwrapped$1; 959 } 960 const unwrapped = signal$1.get(); 961 if (unwrapped && (typeof unwrapped === "object" || typeof unwrapped === "function")) return prop in unwrapped; 962 if (unwrapped !== null && unwrapped !== void 0) { 963 const boxed = new Object(unwrapped); 964 return Reflect.has(boxed, prop); 965 } 966 return false; 967 } 968 }); 969} 970function wrapValue(value, options$1 = defaultWrapOptions) { 971 if (value === null || value === void 0) return value; 972 if (isSignal(value)) { 973 if (options$1.unwrapSignals) return wrapValue(value.get(), options$1); 974 return wrapSignal(value, options$1); 975 } 976 if (typeof value !== "object" && typeof value !== "function") return value; 977 return new Proxy(value, { 978 get(target, prop) { 979 if (isDangerousProperty(prop)) return; 980 const result = target[prop]; 981 if (typeof result === "function") return result.bind(target); 982 return wrapValue(result, options$1); 983 }, 984 set(target, prop, newValue) { 985 if (isDangerousProperty(prop)) return true; 986 target[prop] = newValue; 987 return true; 988 }, 989 has(target, prop) { 990 if (isDangerousProperty(prop)) return false; 991 return prop in target; 992 } 993 }); 994} 995function createScopeProxy(scope, options$1 = defaultWrapOptions) { 996 const base = Object.create(null); 997 return new Proxy(base, { 998 get(_target, prop) { 999 const propStr = String(prop); 1000 if (dangerousGlobals.has(propStr)) return; 1001 if (isDangerousProperty(prop)) return; 1002 if (propStr in scope) { 1003 const value = scope[propStr]; 1004 return wrapValue(value, options$1); 1005 } 1006 if (safeGlobals.has(propStr)) return wrapValue(globalThis[propStr], options$1); 1007 }, 1008 set(_target, prop, value) { 1009 if (isDangerousProperty(prop)) return true; 1010 const propStr = String(prop); 1011 if (propStr in scope) { 1012 const existing = scope[propStr]; 1013 if (isSignal(existing) && hasSetMethod(existing)) { 1014 existing.set(value); 1015 return true; 1016 } 1017 } 1018 scope[propStr] = value; 1019 return true; 1020 }, 1021 has(_target, prop) { 1022 if (prop === "$unwrap") return false;
1023 return true; 1024 }, 1025 ownKeys(_target) { 1026 return Object.keys(scope).filter((key) => !isDangerousProperty(key)); 1027 }, 1028 getOwnPropertyDescriptor(_target, prop) { 1029 if (isDangerousProperty(prop)) return; 1030 const propStr = String(prop); 1031 if (propStr in scope) return { 1032 configurable: true, 1033 enumerable: true, 1034 writable: true, 1035 value: scope[propStr] 1036 }; 1037 } 1038 }); 1039} 1040var exprCache = /* @__PURE__ */ new Map(); 1041function isIdentifierStart(char) { 1042 if (char.length === 0) return false; 1043 const code$1 = char.charCodeAt(0); 1044 return code$1 >= 65 && code$1 <= 90 || code$1 >= 97 && code$1 <= 122 || char === "_" || char === "$"; 1045} 1046function isIdentifierPart(char) { 1047 if (char.length === 0) return false; 1048 const code$1 = char.charCodeAt(0); 1049 return code$1 >= 65 && code$1 <= 90 || code$1 >= 97 && code$1 <= 122 || code$1 >= 48 && code$1 <= 57 || char === "_" || char === "$"; 1050} 1051function isWhitespace(char) { 1052 return char === " " || char === "\n" || char === "\r" || char === " "; 1053} 1054function transformExpr(expr) { 1055 let result = ""; 1056 let index = 0; 1057 while (index < expr.length) { 1058 const char = expr[index]; 1059 if (char === "!") { 1060 if ((expr[index + 1] ?? "") === "=") { 1061 result += "!"; 1062 index += 1; 1063 continue; 1064 } 1065 let cursor = index + 1; 1066 while (cursor < expr.length && isWhitespace(expr[cursor])) cursor += 1; 1067 const identStart = expr[cursor] ?? ""; 1068 if (!isIdentifierStart(identStart)) { 1069 result += "!"; 1070 index += 1; 1071 continue; 1072 } 1073 let end = cursor + 1; 1074 while (end < expr.length && isIdentifierPart(expr.charAt(end))) end += 1; 1075 while (end < expr.length && expr[end] === ".") { 1076 const afterDot = expr[end + 1] ?? ""; 1077 if (!isIdentifierStart(afterDot)) break; 1078 end += 2; 1079 while (end < expr.length && isIdentifierPart(expr.charAt(end))) end += 1; 1080 } 1081 if ((expr[end] ?? "") === "(") { 1082 result += "!"; 1083 index += 1; 1084 continue; 1085 } 1086 const identifier = expr.slice(cursor, end); 1087 result += "!$unwrap(" + identifier + ")"; 1088 index = end; 1089 continue; 1090 } 1091 if (char === ":" && index > 0) { 1092 result += char; 1093 index += 1; 1094 while (index < expr.length && isWhitespace(expr[index])) { 1095 result += expr[index]; 1096 index += 1; 1097 } 1098 if (index < expr.length && isIdentifierStart(expr[index])) { 1099 const identStart = index; 1100 let identEnd = identStart + 1; 1101 while (identEnd < expr.length && isIdentifierPart(expr[identEnd])) identEnd += 1; 1102 let lookahead = identEnd; 1103 while (lookahead < expr.length && isWhitespace(expr[lookahead])) lookahead += 1; 1104 const afterIdent = expr[lookahead] ?? ""; 1105 if (afterIdent === "," || afterIdent === "}" || lookahead >= expr.length || afterIdent === ")") { 1106 const identifier = expr.slice(identStart, identEnd); 1107 result += "$unwrap(" + identifier + ")"; 1108 index = identEnd; 1109 continue; 1110 } 1111 } 1112 continue; 1113 } 1114 result += char; 1115 index += 1; 1116 } 1117 return result; 1118} 1119function unwrapMaybeSignal(value) { 1120 if (isSignal(value)) return value.get(); 1121 return value; 1122} 1123function compileExpr(expr, isStmt = false) { 1124 const cacheKey = `${isStmt ? "stmt" : "expr"}:${expr}`; 1125 let fn = exprCache.get(cacheKey); 1126 if (fn) return fn; 1127 try { 1128 const transformed = transformExpr(expr); 1129 if (isStmt) fn = new Function("$scope", "$unwrap", `with($scope){${transformed}}`); 1130 else fn = new Function("$scope", "$unwrap", `with($scope){return(${transformed})}`); 1131 exprCache.set(cacheKey, fn); 1132 return fn; 1133 } catch (error) { 1134 throw new EvaluationError(expr, error); 1135 } 1136} 1137function unwrapSignal(value) { 1138 if (isSignal(value)) return value.get(); 1139 if (value && typeof value === "object" && typeof value.get === "function" && typeof value.subscribe === "function") return value.get(); 1140 return value; 1141} 1142function evaluate(expr, scope, opts) { 1143 try { 1144 const fn = compileExpr(expr, false); 1145 const wrapOptions = opts?.unwrapSignals === false ? defaultWrapOptions : readWrapOptions; 1146 const proxiedScope = createScopeProxy(scope, wrapOptions); 1147 const result = fn(proxiedScope, unwrapMaybeSignal); 1148 return unwrapSignal(result); 1149 } catch (error) { 1150 if (error instanceof EvaluationError) throw error; 1151 if (error instanceof ReferenceError) return; 1152 throw new EvaluationError(expr, error); 1153 } 1154} 1155var lifecycleHooks = new Map([
1156 ["beforeMount", /* @__PURE__ */ new Set()], 1157 ["afterMount", /* @__PURE__ */ new Set()], 1158 ["beforeUnmount", /* @__PURE__ */ new Set()], 1159 ["afterUnmount", /* @__PURE__ */ new Set()] 1160]); 1161var elementLifecycleStates = /* @__PURE__ */ new WeakMap(); 1162function execGlobalHooks(hookName, root, scope) { 1163 const hooks = lifecycleHooks.get(hookName); 1164 if (!hooks || hooks.size === 0) return; 1165 for (const callback of hooks) try { 1166 if (hookName === "beforeMount" || hookName === "afterMount") { 1167 if (scope !== void 0) callback(root, scope); 1168 } else callback(root); 1169 } catch (error) { 1170 report(error, { 1171 source: "lifecycle", 1172 element: root, 1173 hookName 1174 }); 1175 } 1176} 1177function getElementLifecycleState(element) { 1178 let state = elementLifecycleStates.get(element); 1179 if (!state) { 1180 state = { 1181 isMounted: false, 1182 bindings: /* @__PURE__ */ new Set(), 1183 onMount: /* @__PURE__ */ new Set(), 1184 onUnmount: /* @__PURE__ */ new Set() 1185 }; 1186 elementLifecycleStates.set(element, state); 1187 } 1188 return state; 1189} 1190function notifyElementMounted(el$1) { 1191 const state = getElementLifecycleState(el$1); 1192 if (state.isMounted) return; 1193 state.isMounted = true; 1194 for (const callback of state.onMount) try { 1195 callback(); 1196 } catch (error) { 1197 report(error, { 1198 source: "lifecycle", 1199 element: el$1, 1200 hookName: "onMount" 1201 }); 1202 } 1203} 1204function notifyElementUnmounted(el$1) { 1205 const state = getElementLifecycleState(el$1); 1206 if (!state.isMounted) return; 1207 state.isMounted = false; 1208 for (const callback of state.onUnmount) try { 1209 callback(); 1210 } catch (error) { 1211 report(error, { 1212 source: "lifecycle", 1213 element: el$1, 1214 hookName: "onUnmount" 1215 }); 1216 } 1217 elementLifecycleStates.delete(el$1); 1218} 1219function notifyBindingCreated(element, name) { 1220 getElementLifecycleState(element).bindings.add(name); 1221} 1222function parseModifiers(attrName) { 1223 const parts = attrName.split("-"); 1224 if (parts.length === 1) return { 1225 baseName: attrName, 1226 modifiers: [] 1227 }; 1228 const baseName = parts[0]; 1229 const modifiers = []; 1230 const KNOWN_MODIFIERS = new Set([ 1231 "prevent", 1232 "stop", 1233 "self", 1234 "window", 1235 "document", 1236 "once", 1237 "debounce", 1238 "throttle", 1239 "passive", 1240 "number", 1241 "trim", 1242 "lazy", 1243 "replace", 1244 "prefetch", 1245 "notransition" 1246 ]); 1247 let i = 1; 1248 while (i < parts.length) { 1249 const part = parts[i]; 1250 const numMatch = /^([a-zA-Z]+)(\d+)$/.exec(part); 1251 if (numMatch && KNOWN_MODIFIERS.has(numMatch[1])) { 1252 modifiers.push({ 1253 name: numMatch[1], 1254 value: Number(numMatch[2]) 1255 }); 1256 i++; 1257 } else if (KNOWN_MODIFIERS.has(part)) { 1258 if (i + 1 < parts.length) { 1259 const numValue = Number(parts[i + 1]); 1260 if (!Number.isNaN(numValue)) { 1261 modifiers.push({ 1262 name: part, 1263 value: numValue 1264 }); 1265 i += 2; 1266 continue; 1267 } 1268 } 1269 modifiers.push({ name: part }); 1270 i++; 1271 } else break; 1272 } 1273 if (modifiers.length === 0) return { 1274 baseName: attrName, 1275 modifiers: [] 1276 }; 1277 return { 1278 baseName, 1279 modifiers 1280 }; 1281} 1282function hasModifier(modifiers, name) { 1283 return modifiers.some((m) => m.name === name); 1284} 1285function getModifierValue(modifiers, name, defaultValue) { 1286 return modifiers.find((m) => m.name === name)?.value ?? defaultValue; 1287} 1288function debounce(fn, wait) { 1289 let timeoutId; 1290 const debounced = function(...args) { 1291 if (timeoutId !== void 0) clearTimeout(timeoutId); 1292 timeoutId = setTimeout(() => { 1293 timeoutId = void 0; 1294 fn.apply(this, args); 1295 }, wait); 1296 }; 1297 debounced.cancel = () => { 1298 if (timeoutId !== void 0) { 1299 clearTimeout(timeoutId); 1300 timeoutId = void 0; 1301 } 1302 }; 1303 return debounced; 1304} 1305function throttle(fn, wait) { 1306 let timeoutId; 1307 let lastExecutionTime = 0; 1308 let pendingArgs; 1309 let pendingThis; 1310 const throttled = function(...args) { 1311 const now = Date.now(); 1312 const timeSinceLastExecution = now - lastExecutionTime; 1313 pendingArgs = args; 1314 pendingThis = this; 1315 if (timeSinceLastExecution >= wait) { 1316 lastExecutionTime = now; 1317 fn.apply(this, args); 1318 pendingArgs = void 0; 1319 pendingThis = void 0; 1320 } else if (timeoutId === void 0) { 1321 const remainingTime = wait - timeSinceLastExecution; 1322 timeoutId = setTimeout(() => { 1323 timeoutId = void 0; 1324 lastExecutionTime = Date.now(); 1325 if (pendingArgs !== void 0) {
1326 fn.apply(pendingThis, pendingArgs); 1327 pendingArgs = void 0; 1328 pendingThis = void 0; 1329 } 1330 }, remainingTime); 1331 } 1332 }; 1333 throttled.cancel = () => { 1334 if (timeoutId !== void 0) { 1335 clearTimeout(timeoutId); 1336 timeoutId = void 0; 1337 } 1338 pendingArgs = void 0; 1339 pendingThis = void 0; 1340 }; 1341 return throttled; 1342} 1343var pluginRegistry = /* @__PURE__ */ new Map(); 1344function registerPlugin(name, handler) { 1345 if (pluginRegistry.has(name)) console.warn(`Plugin "${name}" is already registered. Overwriting.`); 1346 pluginRegistry.set(name, handler); 1347} 1348function getPlugin(name) { 1349 return pluginRegistry.get(name); 1350} 1351var scopeMetadataMap = /* @__PURE__ */ new WeakMap(); 1352function createScopeMetadata(scope, origin, parent) { 1353 const metadata = { 1354 origin, 1355 pins: /* @__PURE__ */ new Map(), 1356 uidCounter: 0, 1357 parent 1358 }; 1359 scopeMetadataMap.set(scope, metadata); 1360 return metadata; 1361} 1362function registerPin(scope, name, element) { 1363 const metadata = scopeMetadataMap.get(scope); 1364 if (metadata) metadata.pins.set(name, element); 1365} 1366function getPin(scope, name) { 1367 return scopeMetadataMap.get(scope)?.pins.get(name); 1368} 1369function incrementUidCounter(scope) { 1370 const metadata = scopeMetadataMap.get(scope); 1371 if (!metadata) return 0; 1372 return ++metadata.uidCounter; 1373} 1374function createPulse() { 1375 return (cb) => { 1376 queueMicrotask(cb); 1377 }; 1378} 1379function createUid(scope) { 1380 return (prefix) => { 1381 const counter = incrementUidCounter(scope); 1382 return prefix ? `volt-${prefix}-${counter}` : `volt-${counter}`; 1383 }; 1384} 1385function createArc(element) { 1386 return (eventName, detail) => { 1387 const event = new CustomEvent(eventName, { 1388 detail, 1389 bubbles: true, 1390 composed: true, 1391 cancelable: true 1392 }); 1393 element.dispatchEvent(event); 1394 }; 1395} 1396function createProbe(scope) { 1397 return (expr, cb) => { 1398 const unsubs = []; 1399 let isDisposed = false; 1400 const runProbe = () => { 1401 if (isDisposed) return; 1402 try { 1403 const value = evaluate(expr, scope); 1404 cb(value); 1405 } catch (error) { 1406 console.error("Error in $probe expression:", error); 1407 } 1408 }; 1409 try { 1410 const deps = extractDeps(expr, scope); 1411 for (const dep of deps) { 1412 const unsubscribe = dep.subscribe(runProbe); 1413 unsubs.push(unsubscribe); 1414 } 1415 } catch (error) { 1416 console.error("Error extracting dependencies for $probe:", error); 1417 } 1418 runProbe(); 1419 return () => { 1420 isDisposed = true; 1421 for (const unsub of unsubs) try { 1422 unsub(); 1423 } catch (error) { 1424 console.error("Error unsubscribing $probe:", error); 1425 } 1426 }; 1427 }; 1428} 1429var trackingStack = []; 1430function getActiveContext() { 1431 return trackingStack.at(-1); 1432} 1433function startTracking(source) { 1434 const context = { 1435 deps: /* @__PURE__ */ new Set(), 1436 source 1437 }; 1438 trackingStack.push(context); 1439 return context; 1440} 1441function stopTracking() { 1442 const context = trackingStack.pop(); 1443 if (!context) { 1444 console.warn("stopTracking called without matching startTracking"); 1445 return []; 1446 } 1447 return [...context.deps]; 1448} 1449function recordDep(dep) { 1450 const context = getActiveContext(); 1451 if (!context) return; 1452 if (context.source === dep) throw new Error("Circular dependency detected: a signal cannot depend on itself"); 1453 context.deps.add(dep); 1454} 1455function signal(initialValue) { 1456 let value = initialValue; 1457 const subscribers = /* @__PURE__ */ new Set(); 1458 const notify = () => { 1459 const snapshot = [...subscribers]; 1460 for (const callback of snapshot) try { 1461 callback(value); 1462 } catch (error) { 1463 report(error, { source: "effect" }); 1464 } 1465 }; 1466 const sig = { 1467 get() { 1468 recordDep(sig); 1469 return value; 1470 }, 1471 set(newValue) { 1472 if (value === newValue) return; 1473 value = newValue; 1474 notify(); 1475 }, 1476 subscribe(callback) { 1477 subscribers.add(callback); 1478 return () => { 1479 subscribers.delete(callback); 1480 }; 1481 } 1482 }; 1483 return sig; 1484} 1485function computed(compute) { 1486 let value; 1487 let isInitialized = false; 1488 let isRecomputing = false; 1489 const subs = /* @__PURE__ */ new Set(); 1490 const unsubscribers = []; 1491 const notify = () => { 1492 const snapshot = [...subs]; 1493 for (const cb of snapshot) try { 1494 cb(value); 1495 } catch (error) { 1496 report(error, { source: "effect" }); 1497 } 1498 }; 1499 const recompute = () => { 1500 if (isRecomputing) throw new Error("Circular dependency detected in computed signal"); 1501 isRecomputing = true; 1502 let shouldNotify = false;
1503 try { 1504 for (const unsub of unsubscribers) unsub(); 1505 unsubscribers.length = 0; 1506 startTracking(comp); 1507 try { 1508 const newValue = compute(); 1509 if (!isInitialized || value !== newValue) { 1510 value = newValue; 1511 isInitialized = true; 1512 shouldNotify = subs.size > 0; 1513 } 1514 } catch (error) { 1515 report(error, { source: "effect" }); 1516 throw error; 1517 } finally { 1518 const deps = stopTracking(); 1519 for (const dep of deps) { 1520 const unsub = dep.subscribe(recompute); 1521 unsubscribers.push(unsub); 1522 } 1523 } 1524 } finally { 1525 isRecomputing = false; 1526 } 1527 if (shouldNotify) notify(); 1528 }; 1529 const comp = { 1530 get() { 1531 if (!isInitialized) recompute(); 1532 recordDep(comp); 1533 return value; 1534 }, 1535 subscribe(callback) { 1536 if (!isInitialized) recompute(); 1537 subs.add(callback); 1538 return () => { 1539 subs.delete(callback); 1540 }; 1541 } 1542 }; 1543 return comp; 1544} 1545var storeSignals = /* @__PURE__ */ new Map(); 1546var store = { 1547 _signals: storeSignals, 1548 get(key) { 1549 const sig = storeSignals.get(key); 1550 return sig ? sig.get() : void 0; 1551 }, 1552 set(key, value) { 1553 const sig = storeSignals.get(key); 1554 if (sig) sig.set(value); 1555 else storeSignals.set(key, signal(value)); 1556 }, 1557 has(key) { 1558 return storeSignals.has(key); 1559 } 1560}; 1561function registerStore(state) { 1562 for (const [key, value] of Object.entries(state)) if (value && typeof value === "object" && "get" in value && "set" in value && "subscribe" in value) { 1563 storeSignals.set(key, value); 1564 Object.defineProperty(store, key, { 1565 get: () => value, 1566 enumerable: true, 1567 configurable: true 1568 }); 1569 } else { 1570 const sig = signal(value); 1571 storeSignals.set(key, sig); 1572 Object.defineProperty(store, key, { 1573 get: () => sig, 1574 enumerable: true, 1575 configurable: true 1576 }); 1577 } 1578} 1579function getStore() { 1580 return store; 1581} 1582var directiveRegistry = /* @__PURE__ */ new Map(); 1583function registerDirective(name, handler) { 1584 directiveRegistry.set(name, handler); 1585} 1586function scheduleTransitionTask(cb) { 1587 let executed = false; 1588 const wrapped = () => { 1589 if (executed) return; 1590 executed = true; 1591 cb(); 1592 }; 1593 if (typeof requestAnimationFrame === "function") requestAnimationFrame(wrapped); 1594 setTimeout(wrapped, 16); 1595} 1596function mount(root, scope) { 1597 injectSpecialVars(scope, root); 1598 execGlobalHooks("beforeMount", root, scope); 1599 const elements = walkDOM(root).filter((element) => { 1600 let current = element; 1601 while (current) { 1602 if (Object.hasOwn(current.dataset, "voltSkip")) return false; 1603 if (current === root) break; 1604 current = current.parentElement; 1605 } 1606 return true; 1607 }); 1608 const allCleanups = []; 1609 const mountedElements = []; 1610 for (const el$1 of elements) { 1611 if (Object.hasOwn(el$1.dataset, "voltCloak")) delete el$1.dataset.voltCloak; 1612 const attributes = getVoltAttrs(el$1); 1613 const context = { 1614 element: el$1, 1615 scope, 1616 cleanups: [] 1617 }; 1618 if (attributes.has("for")) { 1619 const forExpression = attributes.get("for"); 1620 bindFor(context, forExpression); 1621 notifyBindingCreated(el$1, "for"); 1622 } else if (attributes.has("if")) { 1623 const ifExpression = attributes.get("if"); 1624 bindIf(context, ifExpression); 1625 notifyBindingCreated(el$1, "if"); 1626 } else for (const [name, value] of attributes) { 1627 bindAttribute(context, name, value); 1628 notifyBindingCreated(el$1, name); 1629 } 1630 notifyElementMounted(el$1); 1631 mountedElements.push(el$1); 1632 allCleanups.push(...context.cleanups); 1633 } 1634 execGlobalHooks("afterMount", root, scope); 1635 return () => { 1636 execGlobalHooks("beforeUnmount", root); 1637 for (const element of mountedElements) notifyElementUnmounted(element); 1638 for (const cleanup of allCleanups) try { 1639 cleanup(); 1640 } catch (error) { 1641 report(error, { 1642 source: "binding", 1643 element: root 1644 }); 1645 } 1646 execGlobalHooks("afterUnmount", root); 1647 }; 1648} 1649function execPlugin(plugin, ctx, val, base) { 1650 const pluginCtx = createPluginCtx(ctx); 1651 try { 1652 plugin(pluginCtx, val); 1653 } catch (error) { 1654 report(error, { 1655 source: "plugin", 1656 element: ctx.element, 1657 directive: `data-volt-${base}`, 1658 pluginName: base 1659 }); 1660 } 1661} 1662function bindAttribute(ctx, name, value) { 1663 if (name.startsWith("on-")) { 1664 const eventSpec = name.slice(3); 1665 const { baseName: eventName, modifiers: modifiers$1 } = parseModifiers(eventSpec); 1666 bindEvent(ctx, eventName, value, modifiers$1); 1667 return; 1668 } 1669 if (name.startsWith("bind:") || name.startsWith("bind-")) { 1670 const attrSpec = name.slice(5); 1671 const { baseName: attrName, modifiers: modifiers$1 } = parseModifiers(attrSpec); 1672 bindAttr(ctx, attrName, value, modifiers$1); 1673 return; 1674 } 1675 if (name.includes(":")) { 1676 const colonIndex = name.indexOf(":"); 1677 const pluginName = name.slice(0, colonIndex); 1678 const suffix = name.slice(colonIndex + 1); 1679 const plugin = getPlugin(pluginName); 1680 if (plugin) { 1681 const combinedVal = `${suffix}:${value}`; 1682 execPlugin(plugin, ctx, combinedVal, pluginName); 1683 return; 1684 } 1685 } 1686 const { baseName, modifiers } = parseModifiers(name); 1687 switch (baseName) { 1688 case "text": 1689 bindNode("text")(ctx, value); 1690 break; 1691 case "html": 1692 bindNode("html")(ctx, value); 1693 break; 1694 case "class": 1695 bindClass(ctx, value); 1696 break; 1697 case "show": 1698 bindShow(ctx, value); 1699 break; 1700 case "style": 1701 bindStyle(ctx, value); 1702 break; 1703 case "model": 1704 bindModel(ctx, value, modifiers); 1705 break; 1706 case "pin": 1707 bindPin(ctx, value); 1708 break; 1709 case "init": 1710 bindInit(ctx, value); 1711 break; 1712 case "for": 1713 bindFor(ctx, value); 1714 break; 1715 case "else": break; 1716 default: { 1717 const directiveHandler = directiveRegistry.get(baseName); 1718 if (directiveHandler) { 1719 directiveHandler(ctx, value, modifiers); 1720 return; 1721 } 1722 const plugin = getPlugin(baseName); 1723 if (plugin) execPlugin(plugin, ctx, value, baseName); 1724 else console.warn(`Unknown binding: data-volt-${baseName}`); 1725 } 1726 } 1727} 1728function bindNode(kind) { 1729 return function(ctx, expr) { 1730 const update = () => { 1731 const value = evaluate(expr, ctx.scope); 1732 if (kind === "text") setText(ctx.element, value); 1733 else setHTML(ctx.element, String(value ?? "")); 1734 }; 1735 updateAndRegister(ctx, update, expr); 1736 }; 1737} 1738function bindClass(ctx, expr) { 1739 let prevClasses = /* @__PURE__ */ new Map(); 1740 const update = () => { 1741 const value = evaluate(expr, ctx.scope);
1742 const classes = parseClassBinding(value); 1743 for (const [className] of prevClasses) if (!classes.has(className)) toggleClass(ctx.element, className, false); 1744 for (const [className, shouldAdd] of classes) toggleClass(ctx.element, className, shouldAdd); 1745 prevClasses = classes; 1746 }; 1747 updateAndRegister(ctx, update, expr); 1748} 1749function bindShow(ctx, expr) { 1750 const el$1 = ctx.element; 1751 const originalInlineDisplay = el$1.style.display; 1752 if (!hasSurge(el$1)) { 1753 const update$1 = () => { 1754 const value = evaluate(expr, ctx.scope); 1755 if (Boolean(value)) el$1.style.display = originalInlineDisplay; 1756 else el$1.style.display = "none"; 1757 }; 1758 updateAndRegister(ctx, update$1, expr); 1759 return; 1760 } 1761 let isVisible = el$1.style.display !== "none"; 1762 let isTransitioning = false; 1763 const update = () => { 1764 const value = evaluate(expr, ctx.scope); 1765 const shouldShow = Boolean(value); 1766 if (shouldShow === isVisible || isTransitioning) return; 1767 isTransitioning = true; 1768 scheduleTransitionTask(() => { 1769 (async () => { 1770 try { 1771 if (shouldShow) { 1772 el$1.style.display = originalInlineDisplay; 1773 await executeSurgeEnter(el$1); 1774 isVisible = true; 1775 } else { 1776 await executeSurgeLeave(el$1); 1777 el$1.style.display = "none"; 1778 isVisible = false; 1779 } 1780 } finally { 1781 isTransitioning = false; 1782 } 1783 })(); 1784 }); 1785 }; 1786 updateAndRegister(ctx, update, expr); 1787} 1788function bindStyle(ctx, expr) { 1789 const element = ctx.element; 1790 const update = () => { 1791 const value = evaluate(expr, ctx.scope); 1792 if (typeof value === "object" && value !== null) for (const [key, val] of Object.entries(value)) { 1793 const cssKey = key.replaceAll(/[A-Z]/g, (m) => `-${m.toLowerCase()}`); 1794 if (isNil(val)) element.style.removeProperty(cssKey); 1795 else try { 1796 element.style.setProperty(cssKey, String(val)); 1797 } catch (error) { 1798 report(error, { 1799 source: "binding", 1800 element, 1801 directive: "data-volt-style", 1802 expression: expr 1803 }); 1804 } 1805 } 1806 else if (typeof value === "string") element.style.cssText = value; 1807 }; 1808 updateAndRegister(ctx, update, expr); 1809} 1810function extractStatements(expr) { 1811 const statements = []; 1812 let current = ""; 1813 let depth = 0; 1814 let inString = null; 1815 for (const [i, char] of [...expr].entries()) { 1816 const prev = i > 0 ? expr[i - 1] : ""; 1817 if ((char === "\"" || char === "'") && prev !== "\\") { 1818 if (inString === char) inString = null; 1819 else if (inString === null) inString = char; 1820 } 1821 if (inString === null) { 1822 if (char === "(" || char === "{" || char === "[") depth++; 1823 else if (char === ")" || char === "}" || char === "]") depth--; 1824 } 1825 if (char === ";" && depth === 0 && inString === null) { 1826 if (current.trim()) statements.push(current.trim()); 1827 current = ""; 1828 } else current += char; 1829 } 1830 if (current.trim()) statements.push(current.trim()); 1831 return statements; 1832} 1833function bindEvent(ctx, eventName, expr, modifiers = []) { 1834 const executeHandler = (event) => { 1835 const eventScope = { 1836 ...ctx.scope, 1837 $el: ctx.element, 1838 $event: event 1839 }; 1840 try { 1841 const statements = extractStatements(expr); 1842 let result; 1843 for (const stmt of statements) result = evaluate(stmt, eventScope, { unwrapSignals: false }); 1844 if (typeof result === "function") result(event); 1845 } catch (error) { 1846 report(error, { 1847 source: "binding", 1848 element: ctx.element, 1849 directive: `data-volt-on-${eventName}`, 1850 expression: expr 1851 }); 1852 } 1853 }; 1854 let wrappedExecute = executeHandler; 1855 if (hasModifier(modifiers, "debounce")) { 1856 const wait = getModifierValue(modifiers, "debounce", 300); 1857 const debouncedExecute = debounce(executeHandler, wait); 1858 wrappedExecute = debouncedExecute; 1859 ctx.cleanups.push(() => debouncedExecute.cancel()); 1860 } else if (hasModifier(modifiers, "throttle")) { 1861 const wait = getModifierValue(modifiers, "throttle", 300); 1862 const throttledExecute = throttle(executeHandler, wait); 1863 wrappedExecute = throttledExecute; 1864 ctx.cleanups.push(() => throttledExecute.cancel()); 1865 } 1866 const handler = (event) => { 1867 if (hasModifier(modifiers, "self") && event.target !== ctx.element) return; 1868 if (hasModifier(modifiers, "prevent")) event.preventDefault(); 1869 if (hasModifier(modifiers, "stop")) event.stopPropagation(); 1870 wrappedExecute(event); 1871 }; 1872 const target = hasModifier(modifiers, "window") ? globalThis : hasModifier(modifiers, "document") ? document : ctx.element; 1873 const options$1 = {}; 1874 if (hasModifier(modifiers, "once")) options$1.once = true; 1875 if (hasModifier(modifiers, "passive")) options$1.passive = true; 1876 target.addEventListener(eventName, handler, options$1); 1877 ctx.cleanups.push(() => { 1878 target.removeEventListener(eventName, handler, options$1); 1879 }); 1880} 1881function getNestedProperty(obj, path) { 1882 let current = obj; 1883 for (const key of path) { 1884 if (current == null || typeof current !== "object") return;
1885 current = current[key]; 1886 } 1887 return current; 1888} 1889function setNestedProperty(obj, path, value) { 1890 if (path.length === 0) return value; 1891 if (obj == null || typeof obj !== "object") return obj; 1892 const clone = Array.isArray(obj) ? [...obj] : { ...obj }; 1893 const [head, ...tail] = path; 1894 if (tail.length === 0) clone[head] = value; 1895 else clone[head] = setNestedProperty(clone[head], tail, value); 1896 return clone; 1897} 1898function findModelSignal(scope, path) { 1899 const signal$1 = findScopedSignal(scope, path); 1900 if (signal$1) return { 1901 signal: signal$1, 1902 propertyPath: [] 1903 }; 1904 const parts = path.split("."); 1905 for (let i = parts.length - 1; i > 0; i--) { 1906 const prefix = parts.slice(0, i).join("."); 1907 const testSignal = findScopedSignal(scope, prefix); 1908 if (testSignal) { 1909 const propertyPath = parts.slice(i); 1910 return { 1911 signal: testSignal, 1912 propertyPath 1913 }; 1914 } 1915 } 1916 return null; 1917} 1918function bindModel(context, signalPath, modifiers = []) { 1919 const result = findModelSignal(context.scope, signalPath); 1920 if (!result) { 1921 report(/* @__PURE__ */ new Error(`Signal "${signalPath}" not found`), { 1922 source: "binding", 1923 element: context.element, 1924 directive: "data-volt-model", 1925 expression: signalPath 1926 }); 1927 return; 1928 } 1929 const { signal: signal$1, propertyPath } = result; 1930 const element = context.element; 1931 const type = element instanceof HTMLInputElement ? element.type : null; 1932 const getValue = () => { 1933 const signalValue = signal$1.get(); 1934 return propertyPath.length > 0 ? getNestedProperty(signalValue, propertyPath) : signalValue; 1935 }; 1936 const initialValue = getValue(); 1937 setElementValue(element, initialValue, type); 1938 const unsubscribe = signal$1.subscribe(() => { 1939 const value = getValue(); 1940 setElementValue(element, value, type); 1941 });
1942 context.cleanups.push(unsubscribe); 1943 const isLazy = hasModifier(modifiers, "lazy"); 1944 const isNumber = hasModifier(modifiers, "number"); 1945 const isTrim = hasModifier(modifiers, "trim"); 1946 const eventName = isLazy ? "change" : type === "checkbox" || type === "radio" ? "change" : "input"; 1947 const baseHandler = () => { 1948 let value = getElementValue(element, type); 1949 if (typeof value === "string") { 1950 if (isTrim) value = value.trim(); 1951 if (isNumber) value = value === "" ? NaN : Number(value); 1952 } 1953 if (propertyPath.length > 0) { 1954 const currentObj = signal$1.get(); 1955 const updatedObj = setNestedProperty(currentObj, propertyPath, value); 1956 signal$1.set(updatedObj); 1957 } else signal$1.set(value); 1958 }; 1959 let handler = baseHandler; 1960 if (hasModifier(modifiers, "debounce")) { 1961 const wait = getModifierValue(modifiers, "debounce", 300); 1962 const debouncedHandler = debounce(baseHandler, wait); 1963 handler = debouncedHandler; 1964 context.cleanups.push(() => debouncedHandler.cancel()); 1965 } 1966 element.addEventListener(eventName, handler); 1967 context.cleanups.push(() => { 1968 element.removeEventListener(eventName, handler); 1969 }); 1970} 1971function setElementValue(el$1, value, type) { 1972 if (el$1 instanceof HTMLInputElement) switch (type) { 1973 case "checkbox": 1974 el$1.checked = Boolean(value); 1975 break; 1976 case "radio": 1977 el$1.checked = el$1.value === String(value); 1978 break; 1979 case "number": 1980 el$1.value = String(value ?? ""); 1981 break; 1982 default: el$1.value = String(value ?? ""); 1983 } 1984 else if (el$1 instanceof HTMLSelectElement) el$1.value = String(value ?? ""); 1985 else if (el$1 instanceof HTMLTextAreaElement) el$1.value = String(value ?? ""); 1986} 1987function getElementValue(el$1, type) { 1988 if (el$1 instanceof HTMLInputElement) { 1989 if (type === "checkbox") return el$1.checked; 1990 if (type === "number") return el$1.valueAsNumber; 1991 return el$1.value; 1992 } 1993 if (el$1 instanceof HTMLSelectElement) return el$1.value; 1994 if (el$1 instanceof HTMLTextAreaElement) return el$1.value; 1995 return ""; 1996} 1997function bindAttr(ctx, attrName, expr, modifiers = []) { 1998 const isNumber = hasModifier(modifiers, "number"); 1999 const isTrim = hasModifier(modifiers, "trim"); 2000 const update = () => { 2001 let value = evaluate(expr, ctx.scope); 2002 if (typeof value === "string") { 2003 if (isTrim) value = value.trim(); 2004 if (isNumber) value = value === "" ? NaN : Number(value); 2005 } 2006 if (new Set(BOOLEAN_ATTRS).has(attrName)) if (value) ctx.element.setAttribute(attrName, ""); 2007 else ctx.element.removeAttribute(attrName); 2008 else if (isNil(value) || value === false) ctx.element.removeAttribute(attrName); 2009 else ctx.element.setAttribute(attrName, String(value)); 2010 }; 2011 updateAndRegister(ctx, update, expr); 2012} 2013function bindInit(ctx, expr) { 2014 try { 2015 const statements = extractStatements(expr); 2016 for (const stmt of statements) evaluate(stmt, ctx.scope, { unwrapSignals: false }); 2017 } catch (error) { 2018 report(error, { 2019 source: "binding", 2020 element: ctx.element, 2021 directive: "data-volt-init", 2022 expression: expr 2023 }); 2024 } 2025} 2026function bindPin(ctx, name) { 2027 registerPin(ctx.scope, name, ctx.element); 2028} 2029function bindFor(ctx, expr) { 2030 const parsed = parseForExpr(expr); 2031 if (!parsed) { 2032 report(/* @__PURE__ */ new Error(`Invalid data-volt-for expression: "${expr}"`), { 2033 source: "binding", 2034 element: ctx.element, 2035 directive: "data-volt-for", 2036 expression: expr 2037 }); 2038 return; 2039 } 2040 const { itemName, indexName, arrayPath } = parsed; 2041 const templ = ctx.element; 2042 if (!templ.parentElement) { 2043 report(/* @__PURE__ */ new Error("data-volt-for element must have a parent"), { 2044 source: "binding", 2045 element: ctx.element, 2046 directive: "data-volt-for", 2047 expression: expr 2048 }); 2049 return; 2050 } 2051 const placeholder = document.createComment(`for: ${expr}`); 2052 templ.before(placeholder); 2053 templ.remove(); 2054 const renderedElements = []; 2055 const renderedCleanups = []; 2056 const render = () => { 2057 for (const cleanup of renderedCleanups) cleanup(); 2058 renderedCleanups.length = 0; 2059 for (const element of renderedElements) element.remove(); 2060 renderedElements.length = 0; 2061 const arrayValue = evaluate(arrayPath, ctx.scope); 2062 if (!Array.isArray(arrayValue)) return; 2063 for (const [index, item] of arrayValue.entries()) { 2064 const clone = templ.cloneNode(true); 2065 delete clone.dataset.voltFor; 2066 const itemScope = { 2067 ...ctx.scope, 2068 [itemName]: item 2069 }; 2070 if (indexName) itemScope[indexName] = index; 2071 const cleanup = mount(clone, itemScope); 2072 renderedCleanups.push(cleanup); 2073 renderedElements.push(clone); 2074 placeholder.before(clone); 2075 } 2076 }; 2077 updateAndRegister(ctx, render, expr); 2078 ctx.cleanups.push(() => { 2079 for (const cleanup of renderedCleanups) cleanup(); 2080 }); 2081} 2082function bindIf(ctx, expr) { 2083 const ifTempl = ctx.element; 2084 if (!ifTempl.parentElement) { 2085 report(/* @__PURE__ */ new Error("data-volt-if element must have a parent"), { 2086 source: "binding", 2087 element: ctx.element, 2088 directive: "data-volt-if", 2089 expression: expr 2090 }); 2091 return; 2092 } 2093 let elseTempl; 2094 let nextSibling = ifTempl.nextElementSibling; 2095 while (nextSibling && nextSibling.nodeType !== 1) nextSibling = nextSibling.nextElementSibling; 2096 if (nextSibling && Object.hasOwn(nextSibling.dataset, "voltElse")) { 2097 elseTempl = nextSibling; 2098 elseTempl.remove(); 2099 } 2100 const placeholder = document.createComment(`if: ${expr}`); 2101 ifTempl.before(placeholder); 2102 ifTempl.remove(); 2103 const ifHasSurge = hasSurge(ifTempl); 2104 const elseHasSurge = elseTempl ? hasSurge(elseTempl) : false;
2105 const anySurge = ifHasSurge || elseHasSurge; 2106 let currentElement; 2107 let currentCleanup; 2108 let currentBranch; 2109 let isTransitioning = false; 2110 let pendingRender = false; 2111 const render = () => { 2112 const condition = evaluate(expr, ctx.scope); 2113 const targetBranch = Boolean(condition) ? "if" : elseTempl ? "else" : void 0; 2114 if (targetBranch === currentBranch || isTransitioning) { 2115 if (isTransitioning) pendingRender = true; 2116 return; 2117 } 2118 if (!anySurge) { 2119 if (currentCleanup) { 2120 currentCleanup(); 2121 currentCleanup = void 0; 2122 } 2123 if (currentElement) { 2124 currentElement.remove(); 2125 currentElement = void 0; 2126 } 2127 if (targetBranch === "if") { 2128 currentElement = ifTempl.cloneNode(true); 2129 delete currentElement.dataset.voltIf; 2130 currentCleanup = mount(currentElement, ctx.scope); 2131 placeholder.before(currentElement); 2132 currentBranch = "if"; 2133 } else if (targetBranch === "else" && elseTempl) { 2134 currentElement = elseTempl.cloneNode(true); 2135 delete currentElement.dataset.voltElse; 2136 currentCleanup = mount(currentElement, ctx.scope); 2137 placeholder.before(currentElement); 2138 currentBranch = "else"; 2139 } else currentBranch = void 0; 2140 return; 2141 } 2142 isTransitioning = true; 2143 (async () => { 2144 try { 2145 if (currentElement) { 2146 const currentEl = currentElement; 2147 if (currentBranch === "if" ? ifHasSurge : elseHasSurge) await executeSurgeLeave(currentEl); 2148 if (currentCleanup) { 2149 currentCleanup(); 2150 currentCleanup = void 0; 2151 } 2152 currentElement.remove(); 2153 currentElement = void 0; 2154 } 2155 if (targetBranch === "if") { 2156 currentElement = ifTempl.cloneNode(true); 2157 delete currentElement.dataset.voltIf; 2158 placeholder.before(currentElement); 2159 if (ifHasSurge) await executeSurgeEnter(currentElement); 2160 currentCleanup = mount(currentElement, ctx.scope); 2161 currentBranch = "if"; 2162 } else if (targetBranch === "else" && elseTempl) { 2163 currentElement = elseTempl.cloneNode(true); 2164 delete currentElement.dataset.voltElse; 2165 placeholder.before(currentElement); 2166 if (elseHasSurge) await executeSurgeEnter(currentElement); 2167 currentCleanup = mount(currentElement, ctx.scope); 2168 currentBranch = "else"; 2169 } else currentBranch = void 0; 2170 } finally { 2171 isTransitioning = false; 2172 if (pendingRender) { 2173 pendingRender = false;
2174 render(); 2175 } 2176 } 2177 })(); 2178 }; 2179 updateAndRegister(ctx, render, expr); 2180 ctx.cleanups.push(() => { 2181 if (currentCleanup) currentCleanup(); 2182 }); 2183} 2184function parseForExpr(expr) { 2185 const trimmed = expr.trim(); 2186 const withIndex = /^\((\w+)\s*,\s*(\w+)\)\s+in\s+(.+)$/.exec(trimmed); 2187 if (withIndex) return { 2188 itemName: withIndex[1], 2189 indexName: withIndex[2], 2190 arrayPath: withIndex[3].trim() 2191 }; 2192 const simple = /^(\w+)\s+in\s+(.+)$/.exec(trimmed); 2193 if (simple) return { 2194 itemName: simple[1], 2195 indexName: void 0, 2196 arrayPath: simple[2].trim() 2197 }; 2198} 2199function createPluginCtx(ctx) { 2200 const mountCallbacks = []; 2201 const unmountCallbacks = []; 2202 const beforeBindingCallbacks = []; 2203 const afterBindingCallbacks = []; 2204 const lifecycle = { 2205 onMount: (cb) => { 2206 mountCallbacks.push(cb); 2207 try { 2208 cb(); 2209 } catch (error) { 2210 report(error, { 2211 source: "plugin", 2212 element: ctx.element, 2213 hookName: "onMount" 2214 }); 2215 } 2216 }, 2217 onUnmount: (cb) => { 2218 unmountCallbacks.push(cb); 2219 }, 2220 beforeBinding: (cb) => { 2221 beforeBindingCallbacks.push(cb); 2222 try { 2223 cb(); 2224 } catch (error) { 2225 report(error, { 2226 source: "plugin", 2227 element: ctx.element, 2228 hookName: "beforeBinding" 2229 }); 2230 } 2231 }, 2232 afterBinding: (cb) => { 2233 afterBindingCallbacks.push(cb); 2234 queueMicrotask(() => { 2235 try { 2236 cb(); 2237 } catch (error) { 2238 report(error, { 2239 source: "plugin", 2240 element: ctx.element, 2241 hookName: "afterBinding" 2242 }); 2243 } 2244 }); 2245 } 2246 }; 2247 ctx.cleanups.push(() => { 2248 for (const cb of unmountCallbacks) try { 2249 cb(); 2250 } catch (error) { 2251 report(error, { 2252 source: "plugin", 2253 element: ctx.element, 2254 hookName: "onUnmount" 2255 }); 2256 } 2257 }); 2258 return { 2259 element: ctx.element, 2260 scope: ctx.scope, 2261 addCleanup: (fn) => { 2262 ctx.cleanups.push(fn); 2263 }, 2264 findSignal: (path) => findScopedSignal(ctx.scope, path), 2265 evaluate: (expr, options$1) => evaluate(expr, ctx.scope, options$1), 2266 lifecycle 2267 }; 2268} 2269function injectSpecialVars(scope, root) { 2270 createScopeMetadata(scope, root); 2271 scope.$store = getStore(); 2272 scope.$pulse = createPulse(); 2273 scope.$origin = root; 2274 scope.$scope = scope; 2275 scope.$pins = new Proxy({}, { 2276 get(_target, prop) { 2277 if (typeof prop === "string") return getPin(scope, prop); 2278 }, 2279 has(_target, prop) { 2280 if (typeof prop === "string") return getPin(scope, prop) !== void 0; 2281 return false; 2282 } 2283 }); 2284 scope.$uid = createUid(scope); 2285 scope.$arc = createArc(root); 2286 scope.$probe = createProbe(scope); 2287} 2288function charge(rootSelector = "[data-volt]") { 2289 parseDeclarativeStore(); 2290 const elements = document.querySelectorAll(rootSelector); 2291 const chargedRoots = []; 2292 for (const element of elements) try { 2293 const scope = createScopeFromElement(element); 2294 const cleanup = mount(element, scope); 2295 chargedRoots.push({ 2296 element, 2297 scope, 2298 cleanup 2299 }); 2300 } catch (error) { 2301 report(error, { 2302 source: "charge", 2303 element 2304 }); 2305 } 2306 return { 2307 roots: chargedRoots, 2308 cleanup: () => { 2309 for (const root of chargedRoots) try { 2310 root.cleanup(); 2311 } catch (error) { 2312 report(error, { 2313 source: "charge", 2314 element: root.element 2315 }); 2316 } 2317 } 2318 }; 2319} 2320function createScopeFromElement(el$1) { 2321 const scope = {}; 2322 const stateAttr = el$1.dataset.voltState; 2323 if (stateAttr) try { 2324 const stateData = JSON.parse(stateAttr); 2325 if (typeof stateData !== "object" || isNil(stateData) || Array.isArray(stateData)) report(/* @__PURE__ */ new Error(`data-volt-state must be a JSON object, got ${typeof stateData}`), { 2326 source: "charge", 2327 level: "fatal", 2328 element: el$1, 2329 directive: "data-volt-state", 2330 expression: stateAttr 2331 }); 2332 else for (const [key, value] of Object.entries(stateData)) scope[key] = signal(value); 2333 } catch (error) { 2334 report(error, { 2335 source: "charge", 2336 level: "fatal", 2337 element: el$1, 2338 directive: "data-volt-state", 2339 expression: stateAttr 2340 }); 2341 } 2342 const computedAttrs = getComputedAttributes(el$1); 2343 for (const [name, expression] of computedAttrs) try { 2344 scope[name] = computed(() => evaluate(expression, scope)); 2345 } catch (error) { 2346 report(error, { 2347 source: "charge", 2348 element: el$1, 2349 directive: `data-volt-computed:${name}`, 2350 expression 2351 }); 2352 } 2353 return scope; 2354} 2355function parseDeclarativeStore() { 2356 const scripts = document.querySelectorAll("script[data-volt-store][type=\"application/json\"]"); 2357 for (const script of scripts) try { 2358 const content = script.textContent?.trim(); 2359 if (!content) continue; 2360 const data = JSON.parse(content); 2361 if (typeof data !== "object" || isNil(data) || Array.isArray(data)) { 2362 report(/* @__PURE__ */ new Error(`data-volt-store script must contain a JSON object, got: ${typeof data}`), {
2363 source: "charge", 2364 level: "fatal", 2365 element: script, 2366 directive: "data-volt-store" 2367 }); 2368 continue; 2369 } 2370 registerStore(data); 2371 } catch (error) { 2372 report(error, { 2373 source: "charge", 2374 level: "fatal", 2375 element: script, 2376 directive: "data-volt-store" 2377 }); 2378 } 2379} 2380var scrollPositions = /* @__PURE__ */ new Map(); 2381var focusSelectors = /* @__PURE__ */ new Map(); 2382var currentRouterMode = "history"; 2383function setRouterMode(mode) { 2384 currentRouterMode = mode; 2385} 2386function getLocationKey() { 2387 if (currentRouterMode === "hash") return globalThis.location.hash.slice(1) || "/"; 2388 return `${globalThis.location.pathname}${globalThis.location.search}`; 2389} 2390function bindNavigate(ctx, value, modifiers = []) { 2391 const element = ctx.element; 2392 if (element instanceof HTMLAnchorElement) handleLinkNavigation(ctx, value, modifiers); 2393 else if (element instanceof HTMLFormElement) handleFormNavigation(ctx, value, modifiers); 2394 else console.warn("data-volt-navigate only works on <a> and <form> elements"); 2395} 2396function handleLinkNavigation(ctx, value, modifiers) { 2397 const link = ctx.element; 2398 const targetUrl = value || link.getAttribute("href"); 2399 if (!targetUrl) { 2400 console.warn("data-volt-navigate: no URL specified and no href found"); 2401 return; 2402 } 2403 if (hasModifier(modifiers, "prefetch")) { 2404 const viewportPrefetch = hasModifier(modifiers, "viewport"); 2405 setupPrefetch(link, targetUrl, { viewport: viewportPrefetch }); 2406 } 2407 const clickHandler = async (event) => { 2408 if (event.ctrlKey || event.metaKey || event.shiftKey || event.button !== 0) return; 2409 if (isExternalLink(targetUrl)) return; 2410 event.preventDefault(); 2411 const useReplace = hasModifier(modifiers, "replace"); 2412 const useTransition = !hasModifier(modifiers, "notransition"); 2413 await navigateTo(targetUrl, { 2414 replace: useReplace, 2415 transition: useTransition, 2416 transitionName: "page-transition" 2417 }); 2418 }; 2419 link.addEventListener("click", clickHandler); 2420 ctx.cleanups.push(() => link.removeEventListener("click", clickHandler)); 2421} 2422function handleFormNavigation(ctx, value, modifiers) { 2423 const form$1 = ctx.element; 2424 const targetUrl = value || form$1.getAttribute("action") || globalThis.location.pathname; 2425 const submitHandler = async (event) => { 2426 event.preventDefault(); 2427 const formData = new FormData(form$1); 2428 const method = form$1.method.toLowerCase(); 2429 const useReplace = hasModifier(modifiers, "replace"); 2430 const useTransition = !hasModifier(modifiers, "notransition"); 2431 if (method === "get") { 2432 const params = new URLSearchParams(formData); 2433 const url = `${targetUrl}?${params.toString()}`; 2434 await navigateTo(url, { 2435 replace: useReplace, 2436 transition: useTransition, 2437 transitionName: "page-transition" 2438 }); 2439 } else console.warn("data-volt-navigate: POST/PUT/PATCH forms should use data-volt-post/put/patch"); 2440 }; 2441 form$1.addEventListener("submit", submitHandler); 2442 ctx.cleanups.push(() => form$1.removeEventListener("submit", submitHandler)); 2443} 2444async function navigateTo(url, options$1 = {}) { 2445 const { replace = false, transition = true, transitionName = "page-transition" } = options$1; 2446 const currentKey = getLocationKey(); 2447 scrollPositions.set(currentKey, { 2448 x: window.scrollX, 2449 y: window.scrollY 2450 }); 2451 const activeElement = document.activeElement; 2452 const focusSelector = activeElement && activeElement !== document.body ? getElementSelector(activeElement) : void 0; 2453 if (focusSelector) focusSelectors.set(currentKey, focusSelector); 2454 const state = { 2455 scrollPosition: { 2456 x: window.scrollX, 2457 y: window.scrollY 2458 }, 2459 focusSelector, 2460 timestamp: Date.now() 2461 }; 2462 const performNavigation = async () => { 2463 if (currentRouterMode === "hash") { 2464 const hash = url.startsWith("#") ? url : `#${url}`; 2465 if (replace) globalThis.history.replaceState(state, "", hash); 2466 else globalThis.location.hash = hash; 2467 } else if (replace) globalThis.history.replaceState(state, "", url); 2468 else globalThis.history.pushState(state, "", url); 2469 globalThis.dispatchEvent(new CustomEvent("volt:navigate", { 2470 detail: { 2471 url, 2472 replace 2473 }, 2474 bubbles: true, 2475 cancelable: false 2476 })); 2477 window.scrollTo(0, 0); 2478 resetFocusAfterNavigation(); 2479 }; 2480 if (transition && typeof transitionName === "string") await startViewTransition(performNavigation, { name: transitionName }); 2481 else await performNavigation(); 2482} 2483function getElementSelector(element) {
2484 if (element.id) return `#${element.id}`; 2485 if (element.hasAttribute("name")) { 2486 const name = element.getAttribute("name"); 2487 return `${element.tagName.toLowerCase()}[name="${name}"]`; 2488 } 2489 for (const attr of element.attributes) if (attr.name.startsWith("data-volt-")) return `[${attr.name}="${attr.value}"]`; 2490 if (element.hasAttribute("aria-label")) return `[aria-label="${element.getAttribute("aria-label")}"]`; 2491 const parent = element.parentElement; 2492 if (!parent) return void 0; 2493 const index = [...parent.children].indexOf(element); 2494 return `${element.tagName.toLowerCase()}:nth-child(${index + 1})`; 2495} 2496function resetFocusAfterNavigation() { 2497 const main = document.querySelector("main, [role='main'], #main-content"); 2498 if (main instanceof HTMLElement && main.tabIndex < 0) main.tabIndex = -1; 2499 if (main instanceof HTMLElement) { 2500 main.focus({ preventScroll: true }); 2501 return; 2502 } 2503 const firstHeading = document.querySelector("h1"); 2504 if (firstHeading instanceof HTMLElement) { 2505 if (firstHeading.tabIndex < 0) firstHeading.tabIndex = -1; 2506 firstHeading.focus({ preventScroll: true }); 2507 return; 2508 } 2509 document.body.focus({ preventScroll: true }); 2510} 2511function restoreFocus(selector) { 2512 try { 2513 const element = document.querySelector(selector); 2514 if (element instanceof HTMLElement) { 2515 element.focus({ preventScroll: true }); 2516 return true; 2517 } 2518 } catch (error) { 2519 console.warn(`Could not restore focus to selector: ${selector}`, error); 2520 } 2521 return false; 2522} 2523function isExternalLink(url) { 2524 try { 2525 return new URL(url, globalThis.location.origin).origin !== globalThis.location.origin; 2526 } catch { 2527 return false; 2528 } 2529} 2530function setupPrefetch(element, url, opts = {}) { 2531 const { viewport = false } = opts; 2532 let prefetched = false; 2533 const prefetch = () => { 2534 if (prefetched) return; 2535 prefetched = true; 2536 fetch(url, { 2537 method: "GET", 2538 priority: "low", 2539 credentials: "same-origin" 2540 }).catch(() => { 2541 const link = document.createElement("link"); 2542 link.rel = "prefetch"; 2543 link.href = url; 2544 document.head.append(link); 2545 }); 2546 }; 2547 if (viewport) { 2548 const observer = new IntersectionObserver((entries) => { 2549 for (const entry of entries) if (entry.isIntersecting) { 2550 prefetch(); 2551 observer.disconnect(); 2552 } 2553 }, { rootMargin: "50px" }); 2554 observer.observe(element); 2555 } else { 2556 element.addEventListener("mouseenter", prefetch, { 2557 once: true, 2558 passive: true 2559 }); 2560 element.addEventListener("focus", prefetch, { 2561 once: true, 2562 passive: true 2563 }); 2564 } 2565} 2566function initNavigationListener() { 2567 const handleNavigation = (state = null) => { 2568 const key = getLocationKey(); 2569 const savedPosition = scrollPositions.get(key); 2570 const savedFocus = focusSelectors.get(key); 2571 if (savedPosition) window.scrollTo(savedPosition.x, savedPosition.y); 2572 else if (state?.scrollPosition) window.scrollTo(state.scrollPosition.x, state.scrollPosition.y); 2573 if (savedFocus) restoreFocus(savedFocus); 2574 else if (state?.focusSelector) restoreFocus(state.focusSelector); 2575 else resetFocusAfterNavigation(); 2576 globalThis.dispatchEvent(new CustomEvent("volt:popstate", { 2577 detail: { state }, 2578 bubbles: true, 2579 cancelable: false 2580 })); 2581 }; 2582 const handlePopState = (event) => { 2583 const state = event.state; 2584 handleNavigation(state); 2585 }; 2586 const handleHashChange = () => { 2587 if (currentRouterMode === "hash") handleNavigation(); 2588 }; 2589 globalThis.addEventListener("popstate", handlePopState); 2590 globalThis.addEventListener("hashchange", handleHashChange); 2591 return () => { 2592 globalThis.removeEventListener("popstate", handlePopState); 2593 globalThis.removeEventListener("hashchange", handleHashChange); 2594 }; 2595} 2596registerDirective("navigate", bindNavigate); 2597var storageAdapterRegistry = /* @__PURE__ */ new Map(); 2598var localStorageAdapter = { 2599 get(key) { 2600 const value = localStorage.getItem(key); 2601 if (isNil(value)) return void 0; 2602 try { 2603 return JSON.parse(value); 2604 } catch { 2605 return value; 2606 } 2607 }, 2608 set(key, value) { 2609 localStorage.setItem(key, JSON.stringify(value)); 2610 }, 2611 remove(key) { 2612 localStorage.removeItem(key); 2613 } 2614}; 2615var sessionStorageAdapter = { 2616 get(key) { 2617 const value = sessionStorage.getItem(key); 2618 if (isNil(value)) return void 0; 2619 try { 2620 return JSON.parse(value); 2621 } catch { 2622 return value; 2623 } 2624 }, 2625 set(key, value) { 2626 sessionStorage.setItem(key, JSON.stringify(value)); 2627 }, 2628 remove(key) { 2629 sessionStorage.removeItem(key); 2630 } 2631}; 2632var idbAdapter = { 2633 async get(key) { 2634 const db = await openDB(); 2635 return new Promise((resolve, reject) => { 2636 const request$1 = db.transaction(["voltStore"], "readonly").objectStore("voltStore").get(key); 2637 request$1.onsuccess = () => { 2638 resolve(request$1.result?.value); 2639 }; 2640 request$1.onerror = () => { 2641 reject(request$1.error); 2642 }; 2643 }); 2644 }, 2645 async set(key, value) { 2646 const db = await openDB(); 2647 return new Promise((resolve, reject) => { 2648 const request$1 = db.transaction(["voltStore"], "readwrite").objectStore("voltStore").put({ 2649 key, 2650 value 2651 }); 2652 request$1.onsuccess = () => { 2653 resolve(); 2654 }; 2655 request$1.onerror = () => { 2656 reject(request$1.error); 2657 }; 2658 }); 2659 }, 2660 async remove(key) { 2661 const db = await openDB(); 2662 return new Promise((resolve, reject) => { 2663 const request$1 = db.transaction(["voltStore"], "readwrite").objectStore("voltStore").delete(key); 2664 request$1.onsuccess = () => { 2665 resolve(); 2666 }; 2667 request$1.onerror = () => { 2668 reject(request$1.error); 2669 }; 2670 }); 2671 } 2672}; 2673var dbPromise; 2674function openDB() { 2675 if (dbPromise) return dbPromise; 2676 dbPromise = new Promise((resolve, reject) => { 2677 const request$1 = indexedDB.open("voltDB", 1);
2678 request$1.onupgradeneeded = () => { 2679 const db = request$1.result; 2680 if (!db.objectStoreNames.contains("voltStore")) db.createObjectStore("voltStore", { keyPath: "key" }); 2681 }; 2682 request$1.onsuccess = () => { 2683 resolve(request$1.result); 2684 }; 2685 request$1.onerror = () => { 2686 reject(request$1.error); 2687 }; 2688 }); 2689 return dbPromise; 2690} 2691function getStorageAdapter(type) { 2692 switch (type) { 2693 case "local": return localStorageAdapter; 2694 case "session": return sessionStorageAdapter; 2695 case "indexeddb": return idbAdapter; 2696 default: return storageAdapterRegistry.get(type); 2697 } 2698} 2699function resolveCanonicalPath$1(scope, rawPath) { 2700 const trimmed = rawPath.trim(); 2701 if (!trimmed) return trimmed; 2702 const parts = trimmed.split("."); 2703 const resolved = []; 2704 let current = scope; 2705 for (const part of parts) { 2706 if (isNil(current) || typeof current !== "object") { 2707 resolved.push(part); 2708 current = void 0; 2709 continue; 2710 } 2711 const record = current; 2712 if (Object.hasOwn(record, part)) { 2713 resolved.push(part); 2714 current = record[part]; 2715 continue; 2716 } 2717 const camelCandidate = kebabToCamel(part); 2718 if (Object.hasOwn(record, camelCandidate)) { 2719 resolved.push(camelCandidate); 2720 current = record[camelCandidate]; 2721 continue; 2722 } 2723 const lower = part.toLowerCase(); 2724 const matchedKey = Object.keys(record).find((key) => key.toLowerCase() === lower); 2725 if (matchedKey) { 2726 resolved.push(matchedKey); 2727 current = record[matchedKey]; 2728 continue; 2729 } 2730 resolved.push(part); 2731 current = void 0; 2732 } 2733 return resolved.join("."); 2734} 2735function resolveSignal$1(ctx, rawPath) { 2736 const trimmed = rawPath.trim(); 2737 if (!trimmed) return; 2738 const canonicalPath = resolveCanonicalPath$1(ctx.scope, trimmed); 2739 const candidatePaths = new Set([canonicalPath, trimmed]); 2740 for (const candidate of candidatePaths) { 2741 const found = ctx.findSignal(candidate); 2742 if (found) return { 2743 path: candidate, 2744 signal: found 2745 }; 2746 } 2747} 2748function normalizeStorageType(type) { 2749 const original = type.trim(); 2750 switch (original.toLowerCase().replaceAll(/[\s_-]/g, "")) { 2751 case "local": 2752 case "localstorage": return { 2753 key: "local", 2754 original 2755 }; 2756 case "session": 2757 case "sessionstorage": return { 2758 key: "session", 2759 original 2760 }; 2761 case "indexeddb": 2762 case "indexed-db": return { 2763 key: "indexeddb", 2764 original 2765 }; 2766 default: return { 2767 key: original, 2768 original 2769 }; 2770 } 2771} 2772function persistPlugin(ctx, value) { 2773 const parts = value.split(":"); 2774 if (parts.length !== 2) { 2775 console.error(`Invalid persist binding: "${value}". Expected format: "signalPath:storageType"`); 2776 return; 2777 } 2778 const [signalPath, storageType] = parts; 2779 const resolvedSignal = resolveSignal$1(ctx, signalPath); 2780 if (!resolvedSignal) { 2781 console.error(`Signal "${signalPath.trim()}" not found in scope for persist binding`); 2782 return; 2783 } 2784 const { key: adapterKey, original } = normalizeStorageType(storageType); 2785 const adapter = getStorageAdapter(adapterKey) ?? (adapterKey === original ? void 0 : getStorageAdapter(original)); 2786 if (!adapter) { 2787 console.error(`Unknown storage type: "${storageType.trim()}"`); 2788 return; 2789 } 2790 const storageKey = `volt:${resolvedSignal.path}`; 2791 try { 2792 const result = adapter.get(storageKey); 2793 if (result instanceof Promise) result.then((storedValue) => { 2794 if (storedValue !== void 0) resolvedSignal.signal.set(storedValue); 2795 }).catch((error) => { 2796 console.error(`Failed to load persisted value for "${signalPath.trim()}":`, error); 2797 }); 2798 else if (result !== void 0) resolvedSignal.signal.set(result); 2799 } catch (error) { 2800 console.error(`Failed to load persisted value for "${signalPath.trim()}":`, error); 2801 } 2802 const unsubscribe = resolvedSignal.signal.subscribe((newValue) => { 2803 try { 2804 const result = adapter.set(storageKey, newValue); 2805 if (result instanceof Promise) result.catch((error) => { 2806 console.error(`Failed to persist value for "${signalPath.trim()}":`, error); 2807 }); 2808 } catch (error) { 2809 console.error(`Failed to persist value for "${signalPath.trim()}":`, error); 2810 } 2811 }); 2812 ctx.addCleanup(unsubscribe); 2813} 2814function scrollPlugin(ctx, value) { 2815 if (value === "history") { 2816 handleScrollHistory(ctx); 2817 return; 2818 } 2819 const parts = value.split(":"); 2820 if (parts.length !== 2) { 2821 console.error(`Invalid scroll binding: "${value}". Expected format: "mode:signalPath" or "history"`); 2822 return; 2823 } 2824 const [mode, signalPath] = parts.map((p$1) => p$1.trim()); 2825 switch (mode) { 2826 case "restore": 2827 handleScrollRestore(ctx, signalPath); 2828 break; 2829 case "scrollTo": 2830 handleScrollTo(ctx, signalPath); 2831 break; 2832 case "spy": 2833 handleScrollSpy(ctx, signalPath); 2834 break; 2835 case "smooth": 2836 handleSmoothScroll(ctx, signalPath); 2837 break; 2838 default: console.error(`Unknown scroll mode: "${mode}"`); 2839 } 2840} 2841function handleScrollRestore(ctx, signalPath) { 2842 const signal$1 = ctx.findSignal(signalPath); 2843 if (!signal$1) { 2844 console.error(`Signal "${signalPath}" not found for scroll restore`); 2845 return; 2846 } 2847 const element = ctx.element; 2848 const savedPosition = signal$1.get(); 2849 if (typeof savedPosition === "number") element.scrollTop = savedPosition; 2850 const savePosition = () => { 2851 signal$1.set(element.scrollTop); 2852 }; 2853 element.addEventListener("scroll", savePosition, { passive: true }); 2854 ctx.addCleanup(() => { 2855 element.removeEventListener("scroll", savePosition); 2856 }); 2857} 2858function handleScrollTo(ctx, signalPath) { 2859 const signal$1 = ctx.findSignal(signalPath); 2860 if (!signal$1) { 2861 console.error(`Signal "${signalPath}" not found for scrollTo`); 2862 return; 2863 } 2864 const element = ctx.element;
2865 const elementId = element.id; 2866 const checkAndScroll = (target) => { 2867 if (target === elementId || target === `#${elementId}`) element.scrollIntoView({ 2868 behavior: "smooth", 2869 block: "start" 2870 }); 2871 }; 2872 checkAndScroll(signal$1.get()); 2873 const unsubscribe = signal$1.subscribe(checkAndScroll); 2874 ctx.addCleanup(unsubscribe); 2875} 2876function handleScrollSpy(ctx, signalPath) { 2877 const signal$1 = ctx.findSignal(signalPath); 2878 if (!signal$1) { 2879 console.error(`Signal "${signalPath}" not found for scroll spy`); 2880 return; 2881 } 2882 const element = ctx.element; 2883 const observer = new IntersectionObserver((entries) => { 2884 for (const entry of entries) if (entry.target === element) signal$1.set(entry.isIntersecting); 2885 }, { threshold: .1 }); 2886 observer.observe(element); 2887 ctx.addCleanup(() => { 2888 observer.disconnect(); 2889 }); 2890} 2891function handleSmoothScroll(ctx, signalPath) { 2892 const signal$1 = ctx.findSignal(signalPath); 2893 if (!signal$1) { 2894 console.error(`Signal "${signalPath}" not found for smooth scroll`); 2895 return; 2896 } 2897 const element = ctx.element; 2898 const applyBehavior = (value) => { 2899 if (value === true || value === "smooth") element.style.scrollBehavior = "smooth"; 2900 else if (value === false || value === "auto") element.style.scrollBehavior = "auto"; 2901 }; 2902 applyBehavior(signal$1.get()); 2903 const unsubscribe = signal$1.subscribe(applyBehavior); 2904 ctx.addCleanup(() => { 2905 unsubscribe(); 2906 element.style.scrollBehavior = ""; 2907 }); 2908} 2909function handleScrollHistory(ctx) { 2910 const element = ctx.element; 2911 const scrollPositions$1 = /* @__PURE__ */ new Map(); 2912 const handleNavigate = () => { 2913 const key = `${globalThis.location.pathname}${globalThis.location.search}`; 2914 scrollPositions$1.set(key, element.scrollTop); 2915 }; 2916 const handlePopstate = () => { 2917 const key = `${globalThis.location.pathname}${globalThis.location.search}`; 2918 const savedPosition = scrollPositions$1.get(key); 2919 if (savedPosition !== void 0) requestAnimationFrame(() => { 2920 element.scrollTop = savedPosition; 2921 }); 2922 }; 2923 globalThis.addEventListener("volt:navigate", handleNavigate); 2924 globalThis.addEventListener("volt:popstate", handlePopstate); 2925 ctx.addCleanup(() => { 2926 globalThis.removeEventListener("volt:navigate", handleNavigate); 2927 globalThis.removeEventListener("volt:popstate", handlePopstate); 2928 }); 2929} 2930var animationRegistry = /* @__PURE__ */ new Map(); 2931var keyframeRegistry = /* @__PURE__ */ new Map(); 2932var keyframeSheet; 2933var keyframeCounter = 0; 2934var builtinAnimations = { 2935 bounce: { 2936 keyframes: [ 2937 { 2938 offset: 0, 2939 transform: "translateY(0)" 2940 }, 2941 { 2942 offset: .25, 2943 transform: "translateY(-20px)" 2944 }, 2945 { 2946 offset: .5, 2947 transform: "translateY(0)" 2948 }, 2949 { 2950 offset: .75, 2951 transform: "translateY(-10px)" 2952 }, 2953 { 2954 offset: 1, 2955 transform: "translateY(0)" 2956 } 2957 ], 2958 duration: 100, 2959 iterations: 1, 2960 timing: "ease" 2961 }, 2962 shake: { 2963 keyframes: [ 2964 { 2965 offset: 0, 2966 transform: "translateX(0)" 2967 }, 2968 { 2969 offset: .1, 2970 transform: "translateX(-10px)" 2971 }, 2972 { 2973 offset: .2, 2974 transform: "translateX(10px)" 2975 }, 2976 { 2977 offset: .3, 2978 transform: "translateX(-10px)" 2979 }, 2980 { 2981 offset: .4, 2982 transform: "translateX(10px)" 2983 }, 2984 { 2985 offset: .5, 2986 transform: "translateX(-10px)" 2987 }, 2988 { 2989 offset: .6, 2990 transform: "translateX(10px)" 2991 }, 2992 { 2993 offset: .7, 2994 transform: "translateX(-10px)" 2995 }, 2996 { 2997 offset: .8, 2998 transform: "translateX(10px)" 2999 }, 3000 { 3001 offset: .9, 3002 transform: "translateX(-10px)" 3003 }, 3004 { 3005 offset: 1, 3006 transform: "translateX(0)" 3007 } 3008 ], 3009 duration: 500, 3010 iterations: 1, 3011 timing: "ease" 3012 }, 3013 pulse: { 3014 keyframes: [ 3015 { 3016 offset: 0, 3017 transform: "scale(1)", 3018 opacity: "1" 3019 }, 3020 { 3021 offset: .5, 3022 transform: "scale(1.05)", 3023 opacity: "0.9" 3024 }, 3025 { 3026 offset: 1, 3027 transform: "scale(1)", 3028 opacity: "1" 3029 } 3030 ], 3031 duration: 1e3, 3032 iterations: Number.POSITIVE_INFINITY, 3033 timing: "ease-in-out" 3034 }, 3035 spin: { 3036 keyframes: [{ 3037 offset: 0, 3038 transform: "rotate(0deg)" 3039 }, { 3040 offset: 1, 3041 transform: "rotate(360deg)" 3042 }], 3043 duration: 1e3, 3044 iterations: Number.POSITIVE_INFINITY, 3045 timing: "linear" 3046 }, 3047 flash: { 3048 keyframes: [ 3049 { 3050 offset: 0, 3051 opacity: "1" 3052 }, 3053 { 3054 offset: .25, 3055 opacity: "0" 3056 }, 3057 { 3058 offset: .5, 3059 opacity: "1" 3060 }, 3061 { 3062 offset: .75, 3063 opacity: "0" 3064 }, 3065 { 3066 offset: 1, 3067 opacity: "1" 3068 } 3069 ], 3070 duration: 1e3, 3071 iterations: 1, 3072 timing: "linear" 3073 } 3074}; 3075function initBuiltinAnimations() { 3076 for (const [name, preset] of Object.entries(builtinAnimations)) animationRegistry.set(name, preset); 3077} 3078initBuiltinAnimations(); 3079function getAnimation(name) { 3080 return animationRegistry.get(name); 3081} 3082function parseShiftValue(value) { 3083 const colonIndex = value.indexOf(":"); 3084 if (colonIndex !== -1) { 3085 const signalPath = value.slice(0, colonIndex).trim(); 3086 const animationPart = value.slice(colonIndex + 1).trim(); 3087 const parsed = parseAnimationValue(animationPart); 3088 if (!parsed) return; 3089 return { 3090 ...parsed, 3091 signalPath 3092 }; 3093 } 3094 return parseAnimationValue(value); 3095} 3096function parseAnimationValue(value) { 3097 const parts = value.split("."); 3098 const animationName = parts[0]?.trim(); 3099 if (!animationName) return; 3100 const result = { animationName }; 3101 if (parts.length > 1) { 3102 const duration = Number.parseInt(parts[1], 10); 3103 if (!Number.isNaN(duration)) result.duration = duration; 3104 } 3105 if (parts.length > 2) { 3106 const iterations = Number.parseInt(parts[2], 10); 3107 if (!Number.isNaN(iterations)) result.iterations = iterations; 3108 } 3109 return result; 3110} 3111function stopAnimation(el$1) { 3112 el$1.style.animation = ""; 3113 el$1.style.animationName = ""; 3114 el$1.style.animationDuration = ""; 3115 el$1.style.animationTimingFunction = ""; 3116 el$1.style.animationIterationCount = ""; 3117 el$1.style.animationFillMode = ""; 3118 restoreOriginalDisplay(el$1); 3119} 3120function applyAnimation(el$1, preset, duration, iterations) { 3121 if (prefersReducedMotion()) return; 3122 const effectiveDuration = duration ?? preset.duration; 3123 const effectiveIterations = iterations ?? preset.iterations; 3124 const animationName = getOrCreateKeyframes(preset); 3125 if (!animationName) return; 3126 ensureInlineBlockForTransforms(el$1, effectiveIterations === Number.POSITIVE_INFINITY); 3127 resetCssAnimation(el$1); 3128 el$1.style.animationName = animationName; 3129 el$1.style.animationDuration = `${effectiveDuration}ms`; 3130 el$1.style.animationTimingFunction = preset.timing; 3131 el$1.style.animationIterationCount = effectiveIterations === Number.POSITIVE_INFINITY ? "infinite" : String(effectiveIterations); 3132 el$1.style.animationFillMode = "forwards"; 3133 const runs = Number.parseInt(el$1.dataset.voltShiftRuns ?? "0", 10) + 1; 3134 el$1.dataset.voltShiftRuns = String(runs); 3135 if (effectiveIterations !== Number.POSITIVE_INFINITY) { 3136 const totalDuration = effectiveDuration * effectiveIterations; 3137 setTimeout(() => { 3138 if (el$1.style.animationName === animationName) stopAnimation(el$1); 3139 }, totalDuration); 3140 } 3141} 3142function resetCssAnimation(el$1) { 3143 if (!el$1.style.animationName) return; 3144 el$1.style.animation = "none"; 3145 el$1.offsetWidth; 3146 el$1.style.animation = ""; 3147 el$1.style.animationName = ""; 3148} 3149function ensureKeyframeSheet() { 3150 if (keyframeSheet) return keyframeSheet; 3151 if (typeof document === "undefined" || !document.head) return; 3152 const styleEl = document.createElement("style"); 3153 styleEl.dataset.voltShift = "true"; 3154 document.head.append(styleEl); 3155 keyframeSheet = styleEl.sheet ?? void 0; 3156 return keyframeSheet; 3157} 3158function toCssProperty(property) { 3159 return property.replaceAll(/[A-Z]/g, (match) => `-${match.toLowerCase()}`); 3160} 3161function getOrCreateKeyframes(preset) {
3162 const key = JSON.stringify(preset.keyframes) + preset.timing; 3163 if (keyframeRegistry.has(key)) return keyframeRegistry.get(key); 3164 const sheet = ensureKeyframeSheet(); 3165 if (!sheet) return; 3166 const animationName = `volt-shift-${keyframeCounter += 1}`; 3167 keyframeRegistry.set(key, animationName); 3168 const frames = preset.keyframes.map((frame, index) => { 3169 const offset = frame.offset ?? (preset.keyframes.length > 1 ? index / (preset.keyframes.length - 1) : 0); 3170 const percent = Math.round(offset * 1e4) / 100; 3171 const declarations = Object.entries(frame).filter(([prop]) => prop !== "offset").map(([prop, value]) => `${toCssProperty(prop)}: ${value};`).join(" "); 3172 return `${percent}% { ${declarations} }`; 3173 }).join(" "); 3174 sheet.insertRule(`@keyframes ${animationName} { ${frames} }`, sheet.cssRules.length); 3175 return animationName; 3176} 3177function ensureInlineBlockForTransforms(el$1, isInf) { 3178 if (el$1.dataset.voltShiftDisplayManaged) return; 3179 if (typeof getComputedStyle !== "function") return; 3180 if (!el$1.isConnected) return; 3181 el$1.offsetHeight; 3182 if (getComputedStyle(el$1).display !== "inline") return; 3183 el$1.dataset.voltShiftDisplayManaged = isInf ? "infinite" : "managed"; 3184 el$1.dataset.voltShiftOriginalDisplay = el$1.style.display ?? ""; 3185 if (!el$1.dataset.voltShiftOriginalTransformOrigin) el$1.dataset.voltShiftOriginalTransformOrigin = el$1.style.transformOrigin ?? ""; 3186 el$1.style.display = "inline-block"; 3187 if (!el$1.style.transformOrigin) el$1.style.transformOrigin = "center center"; 3188} 3189function restoreOriginalDisplay(element) { 3190 const state = element.dataset.voltShiftDisplayManaged; 3191 if (!state || state === "infinite") return; 3192 const original = element.dataset.voltShiftOriginalDisplay ?? ""; 3193 element.style.display = original; 3194 const originalOrigin = element.dataset.voltShiftOriginalTransformOrigin ?? ""; 3195 element.style.transformOrigin = originalOrigin; 3196 delete element.dataset.voltShiftDisplayManaged; 3197 delete element.dataset.voltShiftOriginalDisplay; 3198 delete element.dataset.voltShiftOriginalTransformOrigin; 3199} 3200function shiftPlugin(ctx, value) { 3201 const el$1 = ctx.element; 3202 const parsed = parseShiftValue(value); 3203 if (!parsed) { 3204 console.error(`[Volt] Invalid shift value: "${value}"`); 3205 return; 3206 } 3207 const preset = getAnimation(parsed.animationName); 3208 if (!preset) { 3209 console.error(`[Volt] Unknown animation preset: "${parsed.animationName}"`); 3210 return; 3211 } 3212 if (parsed.signalPath) { 3213 const signal$1 = ctx.findSignal(parsed.signalPath); 3214 if (!signal$1) { 3215 console.error(`[Volt] Signal "${parsed.signalPath}" not found for shift binding`); 3216 return; 3217 } 3218 const isInfinite = (parsed.iterations ?? preset.iterations) === Number.POSITIVE_INFINITY; 3219 let previousValue = signal$1.get(); 3220 const unsubscribe = signal$1.subscribe((value$1) => { 3221 if (value$1 !== previousValue) { 3222 if (value$1) applyAnimation(el$1, preset, parsed.duration, parsed.iterations); 3223 else if (isInfinite && el$1.style.animationName) stopAnimation(el$1); 3224 } 3225 previousValue = value$1; 3226 }); 3227 ctx.addCleanup(unsubscribe); 3228 if (signal$1.get()) ctx.lifecycle.onMount(() => { 3229 requestAnimationFrame(() => { 3230 applyAnimation(el$1, preset, parsed.duration, parsed.iterations); 3231 }); 3232 }); 3233 } else ctx.lifecycle.onMount(() => { 3234 requestAnimationFrame(() => { 3235 applyAnimation(el$1, preset, parsed.duration, parsed.iterations); 3236 }); 3237 }); 3238} 3239function normalizeMode(mode) { 3240 switch (mode.trim().toLowerCase().replaceAll(/[\s_-]/g, "")) { 3241 case "read": return "read"; 3242 case "sync": 3243 case "bidirectional": return "sync"; 3244 case "query": 3245 case "search": return "sync"; 3246 case "hash": return "hash"; 3247 case "history": 3248 case "route": return "history"; 3249 default: return; 3250 } 3251} 3252function resolveCanonicalPath(scope, rawPath) { 3253 const trimmed = rawPath.trim(); 3254 if (!trimmed) return trimmed; 3255 const parts = trimmed.split("."); 3256 const resolved = []; 3257 let current = scope; 3258 for (const part of parts) { 3259 if (isNil(current) || typeof current !== "object") { 3260 resolved.push(part); 3261 current = void 0; 3262 continue; 3263 } 3264 const record = current; 3265 if (Object.hasOwn(record, part)) { 3266 resolved.push(part); 3267 current = record[part]; 3268 continue; 3269 } 3270 const camelCandidate = kebabToCamel(part);
3271 if (Object.hasOwn(record, camelCandidate)) { 3272 resolved.push(camelCandidate); 3273 current = record[camelCandidate]; 3274 continue; 3275 } 3276 const lower = part.toLowerCase(); 3277 const matchedKey = Object.keys(record).find((key) => key.toLowerCase() === lower); 3278 if (matchedKey) { 3279 resolved.push(matchedKey); 3280 current = record[matchedKey]; 3281 continue; 3282 } 3283 resolved.push(part); 3284 current = void 0; 3285 } 3286 return resolved.join("."); 3287} 3288function resolveSignal(ctx, rawPath) { 3289 const trimmed = rawPath.trim(); 3290 if (!trimmed) return; 3291 const canonicalPath = resolveCanonicalPath(ctx.scope, trimmed); 3292 const candidatePaths = new Set([canonicalPath, trimmed]); 3293 for (const candidate of candidatePaths) { 3294 const found = ctx.findSignal(candidate); 3295 if (found) return { 3296 path: candidate, 3297 signal: found 3298 }; 3299 } 3300} 3301function urlPlugin(ctx, value) { 3302 const parts = value.split(":").map((part) => part.trim()).filter((part) => part.length > 0); 3303 if (parts.length < 2) { 3304 console.error(`Invalid url binding: "${value}". Expected format: "mode:signalPath[:basePath]" or "signalPath:mode[:basePath]"`); 3305 return; 3306 } 3307 const firstMode = normalizeMode(parts[0]); 3308 const secondMode = normalizeMode(parts[1] ?? ""); 3309 let mode; 3310 let signalPath; 3311 let basePath; 3312 if (firstMode) { 3313 mode = firstMode; 3314 signalPath = parts[1] ?? ""; 3315 basePath = parts.slice(2).join(":") || void 0; 3316 } else if (secondMode) { 3317 mode = secondMode; 3318 signalPath = parts[0]; 3319 basePath = parts.slice(2).join(":") || void 0; 3320 } else { 3321 console.error(`Unknown url mode in binding "${value}"`); 3322 return; 3323 } 3324 if (!signalPath) { 3325 console.error(`Signal path missing for url binding "${value}"`); 3326 return; 3327 } 3328 const resolvedSignal = resolveSignal(ctx, signalPath); 3329 if (!resolvedSignal) { 3330 console.error(`Signal "${signalPath}" not found for url binding`); 3331 return; 3332 } 3333 switch (mode) { 3334 case "read": 3335 handleReadURL(resolvedSignal); 3336 break; 3337 case "sync": 3338 handleSyncURL(ctx, resolvedSignal); 3339 break; 3340 case "hash": 3341 handleHashRouting(ctx, resolvedSignal); 3342 break; 3343 case "history": 3344 handleHistoryRouting(ctx, resolvedSignal, basePath); 3345 break; 3346 } 3347} 3348function handleReadURL(resolved) { 3349 const paramValue = new URLSearchParams(globalThis.location.search).get(resolved.path); 3350 if (paramValue !== null) resolved.signal.set(deserializeValue(paramValue)); 3351} 3352function handleSyncURL(ctx, resolved) { 3353 const paramValue = new URLSearchParams(globalThis.location.search).get(resolved.path); 3354 if (paramValue !== null) resolved.signal.set(deserializeValue(paramValue)); 3355 let isUpdatingFromUrl = false; 3356 let updateTimeout; 3357 const updateUrl = (value) => { 3358 if (isUpdatingFromUrl) return; 3359 if (updateTimeout) clearTimeout(updateTimeout); 3360 updateTimeout = setTimeout(() => { 3361 const params = new URLSearchParams(globalThis.location.search); 3362 const serialized = serializeValue(value); 3363 if (isNil(serialized) || serialized === "") params.delete(resolved.path); 3364 else params.set(resolved.path, serialized); 3365 const newSearch = params.toString(); 3366 const newUrl = newSearch ? `?${newSearch}` : globalThis.location.pathname; 3367 globalThis.history.pushState({}, "", newUrl); 3368 }, 100); 3369 }; 3370 const handlePopState = () => { 3371 isUpdatingFromUrl = true; 3372 const paramValue$1 = new URLSearchParams(globalThis.location.search).get(resolved.path); 3373 if (isNil(paramValue$1)) resolved.signal.set(""); 3374 else resolved.signal.set(deserializeValue(paramValue$1)); 3375 isUpdatingFromUrl = false; 3376 }; 3377 const unsubscribe = resolved.signal.subscribe(updateUrl); 3378 globalThis.addEventListener("popstate", handlePopState); 3379 ctx.addCleanup(() => { 3380 unsubscribe(); 3381 globalThis.removeEventListener("popstate", handlePopState); 3382 if (updateTimeout) clearTimeout(updateTimeout); 3383 }); 3384} 3385function handleHashRouting(ctx, resolved) { 3386 const currentHash = globalThis.location.hash.slice(1); 3387 if (currentHash) resolved.signal.set(currentHash); 3388 let isUpdatingFromHash = false; 3389 const updateHash = (value) => { 3390 if (isUpdatingFromHash) return; 3391 const hashValue = String(value ?? ""); 3392 const newHash = hashValue ? `#${hashValue}` : ""; 3393 if (globalThis.location.hash !== newHash) globalThis.history.pushState({}, "", newHash || globalThis.location.pathname); 3394 }; 3395 const handleHashChange = () => { 3396 isUpdatingFromHash = true; 3397 const currentHash$1 = globalThis.location.hash.slice(1); 3398 resolved.signal.set(currentHash$1); 3399 isUpdatingFromHash = false;
3400 }; 3401 const unsubscribe = resolved.signal.subscribe(updateHash); 3402 globalThis.addEventListener("hashchange", handleHashChange); 3403 ctx.addCleanup(() => { 3404 unsubscribe(); 3405 globalThis.removeEventListener("hashchange", handleHashChange); 3406 }); 3407} 3408function serializeValue(value) { 3409 if (isNil(value)) return ""; 3410 if (typeof value === "string") return value; 3411 if (typeof value === "number" || typeof value === "boolean") return String(value); 3412 return JSON.stringify(value); 3413} 3414function deserializeValue(value) { 3415 if (value === "true") return true; 3416 if (value === "false") return false; 3417 if (value === "null") return null; 3418 if (value === "undefined") return void 0; 3419 const numberValue = Number(value); 3420 if (!Number.isNaN(numberValue) && value !== "") return numberValue; 3421 try { 3422 return JSON.parse(value); 3423 } catch { 3424 return value; 3425 } 3426} 3427function normalizeRoute(path) { 3428 if (!path) return "/"; 3429 return path.startsWith("/") ? path : `/${path}`; 3430} 3431function handleHistoryRouting(ctx, resolved, basePath) { 3432 const base = basePath?.trim() ?? ""; 3433 const extractRoute = () => { 3434 const fullPath = globalThis.location.pathname + globalThis.location.search; 3435 if (base && fullPath.startsWith(base)) { 3436 const stripped = fullPath.slice(base.length) || "/"; 3437 return normalizeRoute(stripped); 3438 } 3439 return normalizeRoute(fullPath); 3440 }; 3441 const currentRoute = extractRoute(); 3442 resolved.signal.set(currentRoute); 3443 let isUpdatingFromHistory = false; 3444 const updateUrl = (value) => { 3445 if (isUpdatingFromHistory) return; 3446 const route = normalizeRoute(String(value ?? "/")); 3447 const fullPath = base ? `${base}${route}` : route; 3448 if (globalThis.location.pathname + globalThis.location.search !== fullPath) { 3449 globalThis.history.pushState({}, "", fullPath); 3450 globalThis.dispatchEvent(new CustomEvent("volt:navigate", { 3451 detail: { 3452 url: fullPath, 3453 route 3454 }, 3455 bubbles: true, 3456 cancelable: false 3457 })); 3458 } 3459 }; 3460 const handlePopState = () => { 3461 isUpdatingFromHistory = true; 3462 const route = extractRoute(); 3463 resolved.signal.set(route); 3464 globalThis.dispatchEvent(new CustomEvent("volt:popstate", { 3465 detail: { route }, 3466 bubbles: true, 3467 cancelable: false 3468 })); 3469 isUpdatingFromHistory = false; 3470 }; 3471 const handleNavigate = () => { 3472 isUpdatingFromHistory = true; 3473 resolved.signal.set(extractRoute()); 3474 isUpdatingFromHistory = false; 3475 }; 3476 const unsubscribe = resolved.signal.subscribe(updateUrl); 3477 globalThis.addEventListener("popstate", handlePopState); 3478 globalThis.addEventListener("volt:navigate", handleNavigate); 3479 ctx.addCleanup(() => { 3480 unsubscribe(); 3481 globalThis.removeEventListener("popstate", handlePopState); 3482 globalThis.removeEventListener("volt:navigate", handleNavigate); 3483 }); 3484} 3485var indicatorStrategies = /* @__PURE__ */ new WeakMap(); 3486async function request(conf) { 3487 const { method, url, headers = {}, body } = conf; 3488 try { 3489 const response = await fetch(url, { 3490 method, 3491 headers: { ...headers }, 3492 body 3493 }); 3494 const contentType = response.headers.get("content-type") || ""; 3495 const isHTML = contentType.includes("text/html"); 3496 const isJSON = contentType.includes("application/json"); 3497 let html; 3498 let json; 3499 if (isHTML) html = await response.text(); 3500 else if (isJSON) json = await response.json(); 3501 else html = await response.text(); 3502 return { 3503 status: response.status, 3504 statusText: response.statusText, 3505 headers: response.headers, 3506 html, 3507 json, 3508 ok: response.ok 3509 }; 3510 } catch (error) { 3511 throw new Error(`HTTP request failed: ${error instanceof Error ? error.message : String(error)}`); 3512 } 3513} 3514function captureState(root) { 3515 const state = { 3516 focusPath: null, 3517 scrollPositions: /* @__PURE__ */ new Map(), 3518 inputValues: /* @__PURE__ */ new Map() 3519 }; 3520 const activeEl = document.activeElement; 3521 if (activeEl && root.contains(activeEl)) state.focusPath = getElementPath(activeEl, root); 3522 const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); 3523 let currentNode = walker.currentNode; 3524 while (currentNode) { 3525 const el$1 = currentNode; 3526 const path = getElementPath(el$1, root); 3527 if (el$1.scrollTop > 0 || el$1.scrollLeft > 0) state.scrollPositions.set(path, { 3528 top: el$1.scrollTop, 3529 left: el$1.scrollLeft 3530 }); 3531 if (el$1 instanceof HTMLInputElement) if (el$1.type === "checkbox" || el$1.type === "radio") state.inputValues.set(path, el$1.checked); 3532 else state.inputValues.set(path, el$1.value); 3533 else if (el$1 instanceof HTMLTextAreaElement || el$1 instanceof HTMLSelectElement) state.inputValues.set(path, el$1.value); 3534 currentNode = walker.nextNode(); 3535 } 3536 return state; 3537} 3538function getElementPath(el$1, root) { 3539 const path = []; 3540 let current = el$1; 3541 while (current && current !== root) { 3542 const parent = current.parentElement; 3543 if (!parent) break; 3544 const index = [...parent.children].indexOf(current); 3545 if (index === -1) break; 3546 path.unshift(index); 3547 current = parent; 3548 } 3549 return path; 3550} 3551function getElementByPath(path, root) { 3552 let current = root; 3553 for (const index of path) { 3554 const children = [...current.children]; 3555 if (index >= children.length) return null; 3556 current = children[index]; 3557 } 3558 return current; 3559} 3560function restoreState(root, state) { 3561 if (state.focusPath) { 3562 const element = getElementByPath(state.focusPath, root); 3563 if (element instanceof HTMLElement) element.focus(); 3564 } 3565 for (const [path, position] of state.scrollPositions) { 3566 const element = getElementByPath(path, root); 3567 if (element) { 3568 element.scrollTop = position.top; 3569 element.scrollLeft = position.left; 3570 } 3571 } 3572 for (const [path, value] of state.inputValues) { 3573 const element = getElementByPath(path, root); 3574 if (element instanceof HTMLInputElement) if (element.type === "checkbox" || element.type === "radio") element.checked = value; 3575 else element.value = value; 3576 else if (element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) element.value = value; 3577 } 3578} 3579function swap(target, content, strategy = "innerHTML") { 3580 const state = strategy === "innerHTML" || strategy === "outerHTML" ? captureState(target) : null; 3581 switch (strategy) { 3582 case "innerHTML": 3583 target.innerHTML = content; 3584 if (state) restoreState(target, state); 3585 break; 3586 case "outerHTML": { 3587 const parent = target.parentElement; 3588 const nextSibling = target.nextElementSibling; 3589 target.outerHTML = content; 3590 if (state && parent) { 3591 const newElement = nextSibling ? nextSibling.previousElementSibling : parent.lastElementChild; 3592 if (newElement) restoreState(newElement, state); 3593 } 3594 break; 3595 } 3596 case "beforebegin": 3597 target.insertAdjacentHTML("beforebegin", content); 3598 break; 3599 case "afterbegin": 3600 target.insertAdjacentHTML("afterbegin", content); 3601 break; 3602 case "beforeend": 3603 target.insertAdjacentHTML("beforeend", content); 3604 break; 3605 case "afterend": 3606 target.insertAdjacentHTML("afterend", content); 3607 break; 3608 case "delete": 3609 target.remove(); 3610 break; 3611 case "none": break; 3612 default: report(/* @__PURE__ */ new Error(`Unknown swap strategy: ${strategy}`), { 3613 source: "http", 3614 level: "warn", 3615 element: target 3616 }); 3617 } 3618} 3619function serializeForm(form$1) { 3620 return new FormData(form$1); 3621} 3622function parseHttpConfig(el$1, scope) { 3623 const dataset = el$1.dataset;
3624 const trigger = dataset.voltTrigger || getDefaultTrigger(el$1); 3625 let target = el$1; 3626 if (dataset.voltTarget) { 3627 const trimmed = dataset.voltTarget.trim(); 3628 if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) target = trimmed.slice(1, -1); 3629 else { 3630 const targetValue = evaluate(dataset.voltTarget, scope); 3631 if (typeof targetValue === "string") target = targetValue; 3632 else if (targetValue instanceof Element) target = targetValue; 3633 } 3634 } 3635 const swap$1 = dataset.voltSwap || "innerHTML"; 3636 let headers = {}; 3637 if (dataset.voltHeaders) try { 3638 const headersValue = evaluate(dataset.voltHeaders, scope); 3639 if (typeof headersValue === "object" && headersValue !== null) headers = headersValue; 3640 } catch (error) { 3641 report(error, { 3642 source: "http", 3643 element: el$1, 3644 directive: "data-volt-headers", 3645 expression: dataset.voltHeaders 3646 }); 3647 } 3648 let retry; 3649 if (dataset.voltRetry) { 3650 const maxAttempts = Number.parseInt(dataset.voltRetry, 10); 3651 const initialDelay = dataset.voltRetryDelay ? Number.parseInt(dataset.voltRetryDelay, 10) : 1e3; 3652 if (!Number.isNaN(maxAttempts) && maxAttempts > 0) retry = { 3653 maxAttempts, 3654 initialDelay 3655 }; 3656 } 3657 const indicator = dataset.voltIndicator; 3658 return { 3659 trigger, 3660 target, 3661 swap: swap$1, 3662 headers, 3663 retry, 3664 indicator 3665 }; 3666} 3667function getDefaultTrigger(el$1) { 3668 if (el$1 instanceof HTMLFormElement) return "submit"; 3669 return "click"; 3670} 3671function setLoadingState(el$1, indicator) { 3672 el$1.dataset.voltLoading = "true"; 3673 if (indicator) showIndicator(indicator); 3674 el$1.dispatchEvent(new CustomEvent("volt:loading", { 3675 detail: { element: el$1 }, 3676 bubbles: true, 3677 cancelable: false 3678 })); 3679} 3680function setErrorState(el$1, msg, indicator) { 3681 el$1.dataset.voltError = msg; 3682 if (indicator) hideIndicator(indicator); 3683 el$1.dispatchEvent(new CustomEvent("volt:error", { 3684 detail: { 3685 element: el$1, 3686 message: msg 3687 }, 3688 bubbles: true, 3689 cancelable: false 3690 })); 3691} 3692function clearStates(el$1, indicator) { 3693 delete el$1.dataset.voltLoading; 3694 delete el$1.dataset.voltError; 3695 delete el$1.dataset.voltRetryAttempt; 3696 if (indicator) hideIndicator(indicator); 3697 el$1.dispatchEvent(new CustomEvent("volt:success", { 3698 detail: { element: el$1 }, 3699 bubbles: true, 3700 cancelable: false 3701 })); 3702} 3703function detectIndicatorStrategy(el$1) { 3704 if (indicatorStrategies.has(el$1)) return indicatorStrategies.get(el$1); 3705 const htmlElement = el$1; 3706 const inlineDisplay = htmlElement.style.display; 3707 const computedDisplay = globalThis.getComputedStyle(htmlElement).display; 3708 if (inlineDisplay === "none" || computedDisplay === "none") { 3709 indicatorStrategies.set(el$1, "display"); 3710 return "display"; 3711 } 3712 if ([...el$1.classList].some((cls) => cls.toLowerCase().includes("hidden"))) { 3713 indicatorStrategies.set(el$1, "class"); 3714 return "class"; 3715 } 3716 indicatorStrategies.set(el$1, "class"); 3717 return "class"; 3718} 3719function showIndicatorElement(el$1) { 3720 const strategy = detectIndicatorStrategy(el$1); 3721 const htmlElement = el$1; 3722 if (strategy === "display") htmlElement.style.display = ""; 3723 else { 3724 const hiddenClass = [...el$1.classList].find((cls) => cls.toLowerCase().includes("hidden")) || "hidden"; 3725 el$1.classList.remove(hiddenClass); 3726 } 3727} 3728function hideIndicatorElement(el$1) { 3729 const strategy = detectIndicatorStrategy(el$1); 3730 const htmlElement = el$1; 3731 if (strategy === "display") htmlElement.style.display = "none"; 3732 else { 3733 const hiddenClass = [...el$1.classList].find((cls) => cls.toLowerCase().includes("hidden")) || "hidden"; 3734 el$1.classList.add(hiddenClass); 3735 } 3736} 3737function showIndicator(selector) { 3738 const indicators = document.querySelectorAll(selector); 3739 for (const indicator of indicators) showIndicatorElement(indicator); 3740} 3741function hideIndicator(selector) { 3742 const indicators = document.querySelectorAll(selector); 3743 for (const indicator of indicators) hideIndicatorElement(indicator); 3744} 3745function resolveTarget(targetConf, defaultEl) { 3746 if (targetConf instanceof Element) return targetConf; 3747 if (targetConf === "this" || targetConf === "") return defaultEl; 3748 const target = document.querySelector(targetConf); 3749 if (!target) { 3750 report(/* @__PURE__ */ new Error(`Target element not found: ${targetConf}`), { 3751 source: "http", 3752 level: "warn", 3753 element: defaultEl, 3754 directive: "data-volt-target" 3755 }); 3756 return; 3757 } 3758 return target; 3759} 3760function classifyError(error) { 3761 if (error instanceof Error && error.message.includes("HTTP")) { 3762 const match = error.message.match(/HTTP (\d+):/); 3763 if (match) { 3764 const status = Number.parseInt(match[1], 10); 3765 if (status >= 500 && status < 600) return "server"; 3766 if (status >= 400 && status < 500) return "client"; 3767 } 3768 } 3769 if (error instanceof Error && (error.message.includes("fetch") || error.message.includes("network"))) return "network"; 3770 return "other"; 3771} 3772function shouldRetry(error) { 3773 const errorType = classifyError(error); 3774 return errorType === "network" || errorType === "server"; 3775} 3776function calculateRetryDelay(error, attempt, initialDelay) { 3777 const errorType = classifyError(error); 3778 if (errorType === "network") return 0; 3779 if (errorType === "server") return initialDelay * 2 ** attempt; 3780 return 0; 3781} 3782async function performRequest(el$1, method, url, conf, body) { 3783 const target = resolveTarget(conf.target, el$1); 3784 if (!target) return; 3785 setLoadingState(target, conf.indicator); 3786 let lastError; 3787 const maxAttempts = conf.retry ? conf.retry.maxAttempts + 1 : 1; 3788 const initialDelay = conf.retry?.initialDelay ?? 1e3; 3789 for (let attempt = 0; attempt < maxAttempts; attempt++) try { 3790 if (attempt > 0) { 3791 target.dataset.voltRetryAttempt = String(attempt); 3792 target.dataset.voltLoading = "retrying"; 3793 target.dispatchEvent(new CustomEvent("volt:retry", { 3794 detail: { 3795 element: target, 3796 attempt 3797 }, 3798 bubbles: true, 3799 cancelable: false 3800 })); 3801 } 3802 const response = await request({ 3803 method, 3804 url, 3805 headers: conf.headers, 3806 body 3807 }); 3808 if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); 3809 clearStates(target, conf.indicator); 3810 if (response.html !== void 0) swap(target, response.html, conf.swap); 3811 else if (response.json !== void 0) console.warn("JSON responses are not yet integrated
3811with signal updates. HTML response expected."); 3812 return; 3813 } catch (error) { 3814 lastError = error; 3815 const isLastAttempt = attempt === maxAttempts - 1; 3816 const canRetry = conf.retry && shouldRetry(error); 3817 if (isLastAttempt || !canRetry) break; 3818 const delay = calculateRetryDelay(error, attempt, initialDelay); 3819 if (delay > 0) await sleep(delay); 3820 } 3821 const errorMessage = lastError instanceof Error ? lastError.message : String(lastError); 3822 setErrorState(target, errorMessage, conf.indicator); 3823 report(lastError, { 3824 source: "http", 3825 element: el$1, 3826 httpMethod: method, 3827 httpUrl: url 3828 }); 3829} 3830function bindGet(ctx, url) { 3831 bindHttpMethod(ctx, "GET", url); 3832} 3833function bindPost(ctx, url) { 3834 bindHttpMethod(ctx, "POST", url); 3835} 3836function bindPut(ctx, url) { 3837 bindHttpMethod(ctx, "PUT", url); 3838} 3839function bindPatch(ctx, url) { 3840 bindHttpMethod(ctx, "PATCH", url); 3841} 3842function bindDelete(ctx, url) { 3843 bindHttpMethod(ctx, "DELETE", url); 3844} 3845function bindHttpMethod(ctx, method, url) { 3846 const config = parseHttpConfig(ctx.element, ctx.scope); 3847 const urlValue = evaluate(url, ctx.scope); 3848 const resolvedUrl = String(urlValue); 3849 const handler = async (event) => { 3850 if (config.trigger === "submit" || ctx.element instanceof HTMLFormElement) event.preventDefault(); 3851 let body; 3852 if (method !== "GET" && method !== "DELETE" && ctx.element instanceof HTMLFormElement) body = serializeForm(ctx.element); 3853 await performRequest(ctx.element, method, resolvedUrl, config, body); 3854 }; 3855 ctx.element.addEventListener(config.trigger, handler); 3856 const cleanup = () => { 3857 ctx.element.removeEventListener(config.trigger, handler); 3858 }; 3859 if ("addCleanup" in ctx) ctx.addCleanup(cleanup); 3860 else ctx.cleanups.push(cleanup); 3861} 3862registerDirective("get", bindGet); 3863registerDirective("post", bindPost); 3864registerDirective("put", bindPut); 3865registerDirective("patch", bindPatch); 3866registerDirective("delete", bindDelete); 3867const el = (tag, attrs, ...children) => { 3868 const element = document.createElement(tag); 3869 if (typeof attrs === "string") element.className = attrs; 3870 else if (attrs) for (const [key, value] of Object.entries(attrs)) { 3871 if (isNil(value) || value === false) continue; 3872 if (value === true) element.setAttribute(key, ""); 3873 else element.setAttribute(key, String(value)); 3874 } 3875 for (const child of children) if (typeof child === "string") element.append(document.createTextNode(child)); 3876 else element.append(child); 3877 return element; 3878}; 3879const repeat = (createFn, items, attrs) => { 3880 return items.map((item) => createFn(attrs, item)); 3881}; 3882function kv(pairs, dtAttrs, ddAttrs) { 3883 const elements = []; 3884 for (const [term, definition] of pairs) elements.push(dt(dtAttrs, term), dd(ddAttrs, definition)); 3885 return elements; 3886} 3887function options(items, attrs) { 3888 return items.map(([value, label$1]) => { 3889 const optionAttrs = typeof attrs === "string" ? { 3890 class: attrs, 3891 value 3892 } : { 3893 ...attrs, 3894 value 3895 }; 3896 return option(optionAttrs, label$1); 3897 }); 3898} 3899function labelFor(labelText, inputAttrs, labelAttrs) { 3900 const labelElement = label(typeof labelAttrs === "string" ? labelAttrs : { 3901 ...labelAttrs, 3902 for: inputAttrs.id 3903 }, labelText); 3904 const inputElement = input(inputAttrs); 3905 return [labelElement, inputElement]; 3906} 3907function labelWith(labelText, inputAttrs, labelAttrs) { 3908 const inputElement = input(inputAttrs); 3909 if (typeof labelText === "string") return label(labelAttrs, inputElement, " ", labelText); 3910 return label(labelAttrs, inputElement, " ", ...labelText); 3911} 3912function buttons(items, sharedAttrs) { 3913 return items.map((item) => { 3914 if (Array.isArray(item)) { 3915 const [label$1, onClick$1] = item; 3916 const attrs$1 = { 3917 ...typeof sharedAttrs === "object" && sharedAttrs !== null ? sharedAttrs : {}, 3918 "data-volt-on-click": onClick$1 3919 }; 3920 return button(attrs$1, label$1); 3921 } 3922 const { label: buttonLabel, onClick,...restAttrs } = item; 3923 const attrs = { 3924 ...typeof sharedAttrs === "object" && sharedAttrs !== null ? sharedAttrs : {}, 3925 ...restAttrs, 3926 "data-volt-on-click": onClick 3927 }; 3928 return button(attrs, buttonLabel); 3929 }); 3930} 3931const h1 = (attrs, ...children) => el("h1", attrs, ...children); 3932const h2 = (attrs, ...children) => el("h2", attrs, ...children); 3933const h3 = (attrs, ...children) => el("h3", attrs, ...children); 3934const h4 = (attrs, ...children) => el("h4", attrs, ...children); 3935const h5 = (attrs, ...children) => el("h5", attrs, ...children); 3936const h6 = (attrs, ...children) => el("h6", attrs, ...children); 3937const p = (attrs, ...children) => el("p", attrs, ...children); 3938const div = (attrs, ...children) => el("div", attrs, ...children); 3939const span = (attrs, ...children) => el("span", attrs, ...children); 3940const small = (attrs, ...children) => el("small", attrs, ...children); 3941const article = (attrs, ...children) => el("article", attrs, ...children); 3942const section = (attrs, ...children) => el("section", attrs, ...children); 3943const header = (attrs, ...children) => el("header", attrs, ...children); 3944const footer = (attrs, ...children) => el("footer", attrs, ...children); 3945const nav = (attrs, ...children) => el("nav", attrs, ...children); 3946const ul = (attrs, ...children) => el("ul", attrs, ...children); 3947const ol = (attrs, ...children) => el("ol", attrs, ...children); 3948const li = (attrs, ...children) => el("li", attrs, ...children); 3949const dl = (attrs, ...children) => el("dl", attrs, ...children); 3950const dt = (attrs, ...children) => el("dt", attrs, ...children); 3951const dd = (attrs, ...children) => el("dd", attrs, ...children); 3952const a = (attrs, ...children) => el("a", attrs, ...children); 3953const button = (attrs, ...children) => el("button", attrs, ...children); 3954const input = (attrs) => el("input", attrs); 3955const textarea = (attrs, ...children) => el("textarea", attrs, ...children); 3956const select = (attrs, ...children) => el("select", attrs, ...children); 3957const option = (attrs, ...children) => el("option", attrs, ...children); 3958const label = (attrs, ...children) => el("label", attrs, ...children); 3959const form = (attrs, ...children) => el("form", attrs, ...children); 3960const fieldset = (attrs, ...children) => el("fieldset", attrs, ...children); 3961const legend = (attrs, ...children) => el("legend", attrs, ...children); 3962const table = (attrs, ...children) => el("table", attrs, ...children); 3963const thead = (attrs, ...children) => el("thead", attrs, ...children); 3964const tbody = (attrs, ...children) => el("tbody", attrs, ...children); 3965const tr = (attrs, ...children) => el("tr", attrs, ...children); 3966const th = (attrs, ...children) => el("th", attrs, ...children); 3967const td = (attrs, ...children) => el("td", attrs, ...children); 3968const blockquote = (attrs, ...children) => el("blockquote", attrs, ...children); 3969const cite = (attrs, ...children) => el("cite", attrs, ...children); 3970const code = (attrs, ...children) => el("code", attrs, ...children); 3971const pre = (attrs, ...children) => el("pre", attrs, ...children); 3972const dialog = (attrs, ...children) => el("dialog", attrs, ...children); 3973const details = (attrs, ...children) => el("details", attrs, ...children); 3974const summary = (attrs, ...children) => el("summary", attrs, ...children); 3975const strong = (attrs, ...children) => el("strong", attrs, ...children); 3976const em = (attrs, ...children) => el("em", attrs, ...children); 3977const del = (attrs, ...children) => el("del", attrs, ...children); 3978const abbr = (attrs, ...children) => el("abbr", attrs, ...children); 3979function createAnimationsSection() { 3980 return article({ id: "animations" }, h2(null, "Animation Demos"), section(null, h3(null, "Surge Plugin: Enter/Leave Transitions"), p(null, "The surge plugin provides smooth transitions when elements appear or disappear.", small
3980(null, "Toggle elements to see fade, slide, scale, and blur transitions. All integrate automatically with data-volt-if and data-volt-show bindings.")), details(null, summary(null, "Fade"), button({ "data-volt-on-click": "showFade.set(!showFade)" }, "Toggle Fade"), blockquote({ 3981 "data-volt-if": "showFade", 3982 "data-volt-surge": "fade" 3983 }, "Fades in and out smoothly")), details(null, summary(null, "Slide Down"), button({ "data-volt-on-click": "showSlideDown.set(!showSlideDown)" }, "Toggle Slide"), blockquote({ 3984 "data-volt-if": "showSlideDown", 3985 "data-volt-surge": "slide-down" 3986 }, "Slides down from above")), details(null, summary(null, "Scale"), button({ "data-volt-on-click": "showScale.set(!showScale)" }, "Toggle Scale"), blockquote({ 3987 "data-volt-if": "showScale", 3988 "data-volt-surge": "scale" 3989 }, "Scales up smoothly")), details(null, summary(null, "Blur"), button({ "data-volt-on-click": "showBlur.set(!showBlur)" }, "Toggle Blur"), blockquote({ 3990 "data-volt-if": "showBlur", 3991 "data-volt-surge": "blur" 3992 }, "Blur effect transition"))), section(null, h3(null, "Custom Timing"), p(null, "Override transition duration and delay using dot notation.", small(null, "Syntax: data-volt-surge=\"preset.duration.delay\" (times in milliseconds)")), details(null, summary(null, "Slow Fade (1000ms)"), button({ "data-volt-on-click": "showSlowFade.set(!showSlowFade)" }, "Toggle"), blockquote({ 3993 "data-volt-if": "showSlowFade", 3994 "data-volt-surge": "fade.1000" 3995 }, "Very slow fade")), details(null, summary(null, "Delayed Slide (500ms + 200ms delay)"), button({ "data-volt-on-click": "showDelayedSlide.set(!showDelayedSlide)" }, "Toggle"), blockquote({ 3996 "data-volt-if": "showDelayedSlide", 3997 "data-volt-surge": "slide-down.500.200" 3998 }, "Slides with delay"))), section(null, h3(null, "Different Enter/Leave Transitions"), p(null, "Specify different transitions for entering and leaving.", small(null, "Use data-volt-surge:enter and data-volt-surge:leave for granular control")), button({ "data-volt-on-click": "showGranular.set(!showGranular)" }, "Toggle Mixed Transition"), blockquote({ 3999 "data-volt-if": "showGranular", 4000 "data-volt-surge:enter": "slide-down.400", 4001 "data-volt-surge:leave": "fade.200" 4002 }, "Slides in, fades out")), section(null, h3(null, "Shift Plugin: Keyframe Animations"), p(null, "The shift plugin applies CSS keyframe animations for attention effects.", small(null, "Click buttons to trigger animations. Some run continuously, others on demand.")), div({ style: "display: flex; gap: 0.5rem; flex-wrap: wrap;" }, button({ 4003 "data-volt-on-click": "triggerBounce.set(triggerBounce + 1)", 4004 "data-volt-shift": "triggerBounce:bounce" 4005 }, "Bounce"), button({ 4006 "data-volt-on-click": "triggerShake.set(triggerShake + 1)", 4007 "data-volt-shift": "triggerShake:shake" 4008 }, "Shake"), button({ "data-volt-shift": "pulse" }, "Pulse (Continuous)"), button({ 4009 "data-volt-on-click": "triggerFlash.set(triggerFlash + 1)", 4010 "data-volt-shift": "triggerFlash:flash" 4011 }, "Flash")), p(null, button({ "data-volt-on-click": "spinningGear.set(!spinningGear)" }, "Toggle Spin"), " Spinning gear: ", span({ 4012 "data-volt-shift": "spinningGear:spin", 4013 style: "font-size: 2rem;" 4014 }, "âï¸"))), section(null, h3(null, "Custom Animation Settings"), p(null, "Override animation duration and iteration count.", small(null, "Syntax: data-volt-shift=\"animation.duration.iterations\"")), div({ style: "display: flex; gap: 0.5rem; flex-wrap: wrap;" }, button({ 4015 "data-volt-on-click": "triggerTripleBounce.set(triggerTripleBounce + 1)", 4016 "data-volt-shift": "triggerTripleBounce:bounce.800.3" 4017 }, "Triple Bounce (800ms each)"), button({ 4018 "data-volt-on-click": "triggerLongShake.set(triggerLongShake + 1)", 4019 "data-volt-shift": "triggerLongShake:shake.1000.2" 4020 }, "Long Shake (1000ms, 2x)"))), section(null, h3(null, "Combined Effects"), p(null, "Surge and shift can be combined for complex animation choreography.", small(null, "Toggle to see content that fades in, then bounces on mount")), button({ "data-volt-on-click": "showCombined.set(!showCombined)" }, "Toggle Combined Animation"), p(null, strong(null, "Animated sidenote:"), " This paragraph keeps the flow of the article while the sidenote animates into view.", " ", small
4020({ 4021 "data-volt-if": "showCombined", 4022 "data-volt-surge": "fade.400", 4023 "data-volt-shift": "bounce" 4024 }, "This margin note fades into place and bounces to grab your attention.")))); 4025} 4026function createCssSection() { 4027 return article({ id: "css" }, h2(null, "VoltX.css"), section(null, h3(null, "Headings & Hierarchy"), p(null, "Volt CSS provides a harmonious type scale based on a 1.25 ratio (major third).", small(null, "The modular scale creates visual hierarchy without requiring any CSS classes. Font sizes range from 0.889rem to 2.566rem."), " All headings automatically receive appropriate sizing, spacing, and weight."), h4(null, "Level 4 Heading"), p(null, "Demonstrates the fourth level of hierarchy."), h5(null, "Level 5 Heading"), p(null, "Even smaller, but still distinct and readable."), h6(null, "Level 6 Heading"), p(null, "The smallest heading level in the hierarchy.")), section(null, h3(null, "Tufte-Style Sidenotes"), p(null, "One of the signature features of Volt CSS is Tufte-style sidenotes.", small(null, "Edward Tufte is renowned for his work on information design and data visualization. His books feature extensive use of margin notes that provide context without interrupting the main narrative flow."), " These appear in the margin on desktop and inline on mobile devices."), p(null, "Sidenotes are created using the semantic ", code(null, "<small>"), " element.", small(null, "The <small> element represents side comments and fine print, making it semantically appropriate for sidenotes. No custom attributes needed!"), " This keeps markup clean and portable while maintaining semantic meaning."), p(null, "The responsive behavior ensures readability across all devices.", small(null, "On narrow screens, sidenotes appear inline with subtle styling. On wider screens (â¥1200px), they float into the right margin."), " Try resizing your browser to see the effect.")), section(null, h3(null, "Lists"), p(null, "Both ordered and unordered lists are styled with appropriate spacing:"), h4(null, "Unordered List"), ul(null, ...repeat(li, [ 4028 "Reactive signals for state management", 4029 "Computed values derived from signals", 4030 "Effect system for side effects", 4031 "Declarative data binding via attributes" 4032 ])), h4(null, "Ordered List"), ol(null, ...repeat(li, [ 4033 "Define your signals and computed values", 4034 "Write semantic HTML markup", 4035 "Add data-volt-* attributes for reactivity", 4036 "Mount the scope and watch the magic happen" 4037 ])), h4(null, "Description List"), dl(null, ...kv([ 4038 ["Signal", "A reactive primitive that holds a value and notifies subscribers of changes."], 4039 ["Computed", "A derived value that automatically updates when its dependencies change."], 4040 ["Effect", "A side effect that runs when its reactive dependencies change."] 4041 ]))), section(null, h3(null, "Blockquotes & Citations"), blockquote(null, p(null, "Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."), cite(null, "Antoine de Saint-Exupéry")), blockquote(null, p(null, "The best programs are written so that computing machines can perform them quickly and so that human beings can understand them clearly."), cite(null, "Donald Knuth"))), section(null, h3(null, "Code & Preformatted Text"), p(null, "Inline code uses ", code(null, "monospace font"), " for clarity."), p(null, "Code blocks preserve formatting and provide syntax-appropriate styling:"), pre(null, code(null, `import { signal, computed, mount } from 'volt'; 4042 4043const count = signal(0); 4044const doubled = computed(() => count.get() * 2); 4045 4046mount(document.querySelector('#app'), { 4047 count, 4048 doubled, 4049 increment: () => count.set(count.get() + 1) 4050});`))), section(null, h3(null, "Tables"), p(null, "Tables receive zebra striping and responsive styling automatically.", small(null, "Tables use alternating row colors for improved scannability. On mobile, they remain scrollable horizontally if needed.")), table(null, thead(null, tr(null, ...repeat(th, [ 4051 "Feature", 4052 "Volt.js", 4053 "Framework X", 4054 "Framework Y" 4055 ]))), tbody(null, tr(null, ...repeat(td, [ 4056 "Bundle Size", 4057 "< 15KB gzipped", 4058 "~40KB", 4059 "~30KB" 4060 ])), tr(null, td(null, "Virtual DOM"), td(null, "No"), td(null, "Yes"), td(null, "Yes")), tr(null, ...repeat(td, [ 4061 "Reactive System", 4062 "Signals", 4063 "Proxy-based", 4064 "Observable" 4065 ])), tr(null, ...repeat(td, [ 4066 "Learning Curve", 4067 "Gentle", 4068 "Moderate", 4069 "Steep" 4070 ]))))), section(null, h3(null, "Tooltips"), p(null, "VoltX CSS includes pure-CSS tooltips with zero JavaScript. Try hovering over these examples:"), p(null, abbr({ 4071 "data-vx-tooltip": "Tooltips appear on top by default", 4072 "data-placement": "top" 4073 }, "Top"), " · ", abbr({ 4074 "data-vx-tooltip": "Tooltips can appear on the right", 4075 "data-placement": "right" 4076 }, "Right"), " · ", abbr({ 4077 "data-vx-tooltip": "Tooltips can appear on the bottom", 4078 "data-placement": "bottom" 4079 }, "Bottom"), " · ", abbr({ 4080 "data-vx-tooltip": "Tooltips can appear on the left", 4081 "data-placement": "left" 4082 }, "Left")), p(null, small(null, "Tooltips use the data-vx-tooltip attribute and are styled with pure CSS. They automatically hide on mobile devices for better touch interaction.")), h4(null, "Implementation"), p(null, "Add the ", code(null, "data-vx-tooltip"), " attribute with your tooltip text, and optionally specify ", code(null, "data-placement"), " (top, right, bottom, left) for positioning."), pre(null, code(null, `<abbr data-vx-tooltip="Helpful tooltip text" 4083 data-placement="top"> 4084 Hover me 4085</abbr>`))), section(null, h3(null, "Accordions"), p(null, "Native HTML ", code(null, "<details>"), " and ", code(null, "<summary>"), " elements provide semantic accordion functionality.", small
4085(null, "The details element is fully accessible by default, with keyboard navigation built in. VoltX CSS styles it beautifully without requiring any JavaScript.")), details(null, summary(null, "What is VoltX.js?"), p(null, "VoltX.js is a lightweight reactive framework for building declarative user interfaces. It uses signals for reactivity and provides HTML-driven behavior through data-volt-* attributes.")), details(null, summary(null, "How does reactivity work?"), p(null, "VoltX uses a signal-based reactive system. When a signal changes, all computed values and effects that depend on it automatically update. The DOM bindings subscribe to these signals and update the UI accordingly.")), details(null, summary(null, "What about styling?"), p(null, "VoltX CSS is a classless CSS framework inspired by Pico CSS and Tufte CSS. It provides beautiful, semantic styling without requiring any CSS classes. Just write semantic HTML and it looks great!"))), section(null, h3(null, "Dialogs"), p(null, "Native ", code(null, "<dialog>"), " elements provide semantic modal functionality with built-in accessibility.", small(null, "The dialog element includes keyboard handling (ESC to close), focus trapping, and backdrop support. Modern browsers support it natively."), " VoltX CSS styles dialogs elegantly with proper backdrop blur and animations."), p(null, "Dialogs require JavaScript to open and close (using ", code(null, "showModal()"), " and ", code(null, "close()"), " methods). ", "See the ", a({ href: "/interactivity" }, "Interactivity section"), " for a working dialog demo."), h4(null, "Structure"), p(null, "Dialogs should follow this semantic structure for proper styling:"), pre(null, code(null, `<dialog id="my-dialog"> 4086 <article> 4087 <header> 4088 <h3>Dialog Title</h3> 4089 <button aria-label="Close">Ã</button> 4090 </header> 4091 <!-- Dialog content --> 4092 <footer> 4093 <button>Cancel</button> 4094 <button>Confirm</button> 4095 </footer> 4096 </article> 4097</dialog>`))), section(null, h3(null, "Additional Features"), p(null, "VoltX CSS includes comprehensive styling for all semantic HTML elements:"), ul(null, ...repeat(li, [ 4098 "Typography with modular scale and Tufte-style sidenotes", 4099 "Form elements with consistent, accessible styling", 4100 "Tables with zebra striping and responsive behavior", 4101 "Code blocks with syntax-appropriate styling", 4102 "Blockquotes and citations", 4103 "Responsive images and media", 4104 "And much more, all without CSS classes!" 4105 ])), p(null, "Explore the other sections of this demo to see these features in action."))); 4106} 4107function createFormsSection() { 4108 return article({ id: "forms" }, h2(null, "Forms & Two-Way Binding"), section(null, h3(null, "Complete Form Example"), p(null, "The ", code(null, "data-volt-model"), " attribute provides two-way data binding.", small(null, "Changes in the input automatically update the signal, and changes to the signal automatically update the input. No manual event handlers needed!")), form({ "data-volt-on-submit": "$helpers.handleFormSubmit($event)" }, fieldset(null, legend(null, "User Information"), ...labelFor("Name", { 4109 type: "text", 4110 id: "name", 4111 "data-volt-model": "formData.name", 4112 placeholder: "John Doe", 4113 required: true 4114 }), ...labelFor("Email", { 4115 type: "email", 4116 id: "email", 4117 "data-volt-model": "formData.email", 4118 placeholder: "[email protected]", 4119 required: true 4120 }), label({ for: "bio" }, "Bio"), textarea({ 4121 id: "bio", 4122 "data-volt-model": "formData.bio", 4123 placeholder: "Tell us about yourself...", 4124 rows: "4" 4125 }), label({ for: "country" }, "Country"), select({ 4126 id: "country", 4127 "data-volt-model": "formData.country" 4128 }, ...options([ 4129 ["us", "United States"], 4130 ["uk", "United Kingdom"], 4131 ["ca", "Canada"], 4132 ["au", "Australia"], 4133 ["other", "Other"] 4134 ])), labelWith("Subscribe to newsletter", { 4135 type: "checkbox", 4136 "data-volt-model": "formData.newsletter" 4137 }), fieldset(null, legend(null, "Plan"), labelWith("Free", { 4138 type: "radio", 4139 name: "plan", 4140 value: "free", 4141 "data-volt-model": "formData.plan" 4142 }), labelWith("Pro", { 4143 type: "radio", 4144 name: "plan", 4145 value: "pro", 4146 "data-volt-model": "formData.plan" 4147 }), labelWith("Enterprise", { 4148 type: "radio", 4149 name: "plan", 4150 value: "enterprise", 4151 "data-volt-model": "formData.plan" 4152 })), button({ type: "submit" }, "Submit Form"), button({ type: "reset" }, "Clear"))), details(null, summary(null, "Current Form Data (Live)"), pre(null, code({ "data-volt-text": "JSON.stringify(formData, null, 2)" }, "Loading..."))))); 4153} 4154function createHomeSection() { 4155 return article({ id: "home" }, section(null, h2(null, "VoltX.js: Declarative Reactivity for the Modern Web"), p(null, "VoltX.js is a lightweight reactive framework that brings signal-based reactivity to HTML.", small
4155(null, "Build rich, interactive applications using declarative markup without writing JavaScript. No virtual DOM, no build step, no dependenciesâjust pure reactive primitives and HTML attributes."))), section(null, h3(null, "Why VoltX.js?"), dl(null, ...kv([ 4156 ["< 15KB Gzipped", "Smaller than most icon libraries. Ships only what you need with zero dependencies."], 4157 ["Declarative-First", "Build entire applications using only HTML attributes. JavaScript is optional for complex logic."], 4158 ["Signal-Based Reactivity", "Fine-grained reactivity that updates only what changed. No virtual DOM diffing."], 4159 ["Zero Build Step", "Import from npm or CDN and start building. No webpack, no rollup, no configuration."], 4160 ["Progressive Enhancement", "Works with server-rendered HTML. Add reactivity incrementally where needed."], 4161 ["Standards-Based", "Built on standard DOM APIs. No proprietary JSX or template syntax."] 4162 ]))), section(null, h3(null, "Quick Start"), p(null, "Get started with VoltX.js in seconds:"), pre(null, code(null, `<!-- Include VoltX.js from CDN --> 4163<script type="module"> 4164 import { charge } from 'https://esm.sh/voltx.js'; 4165 charge(); 4166<\/script> 4167 4168<!-- Declare reactive state in HTML --> 4169<div data-volt data-volt-state='{"count": 0}' 4170 data-volt-computed:doubled="count * 2"> 4171 <p>Count: <span data-volt-text="count"></span></p> 4172 <p>Doubled: <span data-volt-text="doubled"></span></p> 4173 <button data-volt-on-click="count.set(count + 1)"> 4174 Increment 4175 </button> 4176</div>`)), p(null, "That's it! No build step, no configuration, no JavaScript beyond the import. ", strong(null, "The state, reactivity, and interactions are all declared in HTML."))), section(null, h3(null, "Explore the Features"), p(null, "Navigate through the demo to see VoltX.js in action:"), ul(null, li(null, strong(null, "Typography & CSS"), " - Explore Volt CSS, our classless CSS framework with Tufte-style sidenotes"), li(null, strong(null, "Interactivity"), " - See dialogs, buttons, and event handling with declarative bindings"), li(null, strong(null, "Forms"), " - Two-way data binding with all standard form elements"), li(null, strong(null, "Reactivity"), " - Conditional rendering, list rendering, and computed values"), li(null, strong(null, "Plugins"), " - Persistence, scroll management, URL synchronization, and more"), li(null, strong(null, "Animations"), " - Built-in transitions and keyframe animations with zero configuration")), p(null, "Use the navigation above to explore each section.")), section(null, h3(null, "Learn More"), p(null, a({ href: "https://github.com/stormlightlabs/volt" }, "GitHub Repository"), " | ", a({ href: "https://stormlightlabs.github.io/volt" }, "Documentation"), " | ", a({ href: "https://www.npmjs.com/package/voltx.js" }, "npm Package"), " | ", a({ href: "https://jsr.io/@voltx/core" }, "JSR Package")))); 4177} 4178function createInteractivitySection() { 4179 return article({ id: "interactivity" }, h2(null, "Dialogs & Interactivity"), section(null, h3(null, "Native Dialog Element"), p(null, "The HTML ", code(null, "<dialog>"), " element provides semantic modal functionality.", small(null, "Modern browsers support the dialog element natively, providing built-in accessibility features and keyboard handling (ESC to close, focus trapping, etc.)."), " VoltX CSS styles it elegantly, and VoltX.js handles the interaction."), button({ "data-volt-on-click": "$helpers.openDialog('demo-dialog'); dialogMessage.set(''); dialogInput.set('')" }, "Open Dialog"), p({ 4180 "data-volt-if": "dialogMessage", 4181 "data-volt-text": "dialogMessage" 4182 }), dialog({ id: "demo-dialog" }, article(null, header(null, h3(null, "Dialog Demo"), button({ 4183 "data-volt-on-click": "$helpers.closeDialog('demo-dialog')", 4184 "aria-label": "Close" 4185 }, "Ã")), form({ 4186 id: "demo-dialog-form", 4187 "data-volt-on-submit": "$event.preventDefault(); dialogMessage.set('You entered: ' + dialogInput); setTimeout(() => $helpers.closeDialog('demo-dialog'), 2000)" 4188 }, ...labelFor("Enter something:", { 4189 type: "text", 4190 id: "dialog-input", 4191 "data-volt-model": "dialogInput", 4192 placeholder: "Type here...", 4193 required: true 4194 })), footer(null, button({ 4195 type: "button", 4196 "data-volt-on-click": "$helpers.closeDialog('demo-dialog')" 4197 }, "Cancel"), button({ 4198 type: "submit", 4199 form: "demo-dialog-form" 4200 }, "Submit"))))), section(null, h3(null, "Button Interactions"), p(null, "Count: ", strong({ "data-volt-text": "count" }, "0"), " | Doubled: ", strong({ "data-volt-text": "doubled" }, "0")), div({ style: "display: flex; gap: 0.5rem; flex-wrap: wrap;" }, ...buttons([ 4201 ["Increment", "count.set(count + 1)"], 4202 ["Decrement", "count.set(count - 1)"], 4203 ["Reset", "count.set(0)"], 4204 ["Update Header", "message.set('Count is now ' + count)"] 4205 ])))); 4206} 4207function createPluginsSection() { 4208 return article({ id: "plugins" }, h2(null, "Plugin Demos"), section(null, h3(null, "Persist Plugin"), p(null, "The persist plugin syncs signals with localStorage, sessionStorage, or IndexedDB.", small
4208(null, "Try incrementing the counter, then refresh the page. The value persists! This uses localStorage by default.")), p(null, "Persisted Count: ", strong({ "data-volt-text": "persistedCount" }, "0")), div({ "data-volt-persist:persistedCount": "localStorage" }, button({ "data-volt-on-click": "persistedCount.set(persistedCount + 1)" }, "Increment Persisted"), " ", button({ "data-volt-on-click": "persistedCount.set(0)" }, "Reset Persisted"))), section(null, h3(null, "Scroll Plugin"), p(null, "The scroll plugin provides scroll tracking and smooth scrolling utilities.", small(null, "Current scroll position is tracked in real-time. The scroll position updates as you scroll the page, and you can jump to sections smoothly.")), p(null, "Current Scroll Position: ", strong({ "data-volt-text": "Math.round(scrollPosition)" }, "0"), "px"), div({ style: "display: flex; gap: 0.5rem; flex-wrap: wrap;" }, button({ "data-volt-on-click": "$helpers.scrollToTop()" }, "Scroll to Top"), button({ "data-volt-on-click": "$helpers.scrollToSection('typography')" }, "Go to Typography"), button({ "data-volt-on-click": "$helpers.scrollToSection('forms')" }, "Go to Forms"), button({ "data-volt-on-click": "$helpers.scrollToSection('plugins')" }, "Go to Plugins"))), section(null, h3(null, "URL Plugin"), p(null, "The URL plugin syncs signals with URL query parameters or hash.", small(null, "Try typing in the input below. Notice how the URL updates automatically! Bookmark the URL and return later - the state is preserved.")), div({ 4209 "data-volt-url:urlParam": "query", 4210 "style": "max-width: var(--content-width);" 4211 }, ...labelFor("URL Parameter (synced with ?urlParam=...)", { 4212 type: "text", 4213 id: "url-input", 4214 "data-volt-model": "urlParam", 4215 placeholder: "Type to update URL..." 4216 }), p(null, "Current value: ", strong({ "data-volt-text": "urlParam || '(empty)'" }, "Loading..."))))); 4217} 4218function createReactivitySection() { 4219 const style = el("style", null, `.active { 4220 color: var(--color-accent); 4221 font-weight: bold; 4222} 4223.highlight { 4224 background: var(--color-mark); 4225 padding: 0.25rem 0.5rem; 4226 border-radius: var(--radius-sm); 4227}`); 4228 return article({ id: "reactivity" }, h2(null, "Reactivity Features"), section(null, h3(null, "Conditional Rendering"), p(null, "Use ", code(null, "data-volt-if"), " and ", code(null, "data-volt-else"), " for conditional display:"), button({ "data-volt-on-click": "showAdvanced.set(!showAdvanced)" }, span({ "data-volt-text": "showAdvanced ? 'Hide' : 'Show'" }, "Show"), " Advanced Options"), div({ "data-volt-if": "showAdvanced" }, h4(null, "Advanced Configuration"), p(null, "These options are only visible when advanced mode is enabled."), labelWith("Enable debug mode", { type: "checkbox" }), labelWith("Use experimental features", { type: "checkbox" })), div({ "data-volt-else": true }, p(null, em(null, "Advanced options are hidden. Click the button above to reveal them.")))), section(null, h3(null, "List Rendering"), p(null, "Use ", code(null, "data-volt-for"), " to render dynamic lists.", small(null, `The syntax is "item in items" where items is a signal or expression. Each item gets its own scope with access to the item data and parent scope.`)), div(null, input({ 4229 type: "text", 4230 "data-volt-model": "newTodoText", 4231 placeholder: "New todo...", 4232 "data-volt-on-keydown": "$event.key === 'Enter' && newTodoText.trim() ? (todos.set([...todos, {id: todoIdCounter, text: newTodoText.trim(), done: false}]), todoIdCounter.set(todoIdCounter + 1), newTodoText.set('')) : null" 4233 }), button({ "data-volt-on-click": "newTodoText.trim() ? (todos.set([...todos, {id: todoIdCounter, text: newTodoText.trim(), done: false}]), todoIdCounter.set(todoIdCounter + 1), newTodoText.set('')) : null" }, "Add Todo")), h4(null, "Active Todos (", span({ "data-volt-text": "activeTodos.length" }, "0"), ")"), ul(null, li({ "data-volt-for": "todo in activeTodos" }, input({ 4234 type: "checkbox", 4235 "data-volt-on-change": "todos.set(todos.map(t => t.id === todo.id ? {...t, done: !t.done} : t))" 4236 }), " ", span({ "data-volt-text": "todo.text" }, "Todo item"), " ", button({ "data-volt-on-click": "todos.set(todos.filter(t => t.id !== todo.id))" }, "Remove"))), h4(null, "Completed Todos (", span({ "data-volt-text": "completedTodos.length" }, "0"), ")"), ul(null, li({ "data-volt-for": "todo in completedTodos" }, input({ 4237 type: "checkbox", 4238 checked: true, 4239 "data-volt-on-change": "todos.set(todos.map(t => t.id === todo.id ? {...t, done: !t.done} : t))" 4240 }), " ", del({ "data-volt-text": "todo.text" }, "Todo item"), " ", button({ "data-volt-on-click": "todos.set(todos.filter(t => t.id !== todo.id))" }, "Remove")))), section(null, h3(null, "Class Bindings"), p(null, "Toggle CSS classes reactively using ", co
4240de(null, "data-volt-class"), ":"), p({ "data-volt-class": "{ active: isActive, highlight: isHighlighted }" }, "This paragraph has dynamic classes. Try the buttons below!"), button({ "data-volt-on-click": "isActive.set(!isActive)" }, "Toggle Active (currently: ", span({ "data-volt-text": "isActive ? 'ON' : 'OFF'" }, "OFF"), ")"), " ", button({ "data-volt-on-click": "isHighlighted.set(!isHighlighted)" }, "Toggle Highlight (currently: ", span({ "data-volt-text": "isHighlighted ? 'ON' : 'OFF'" }, "OFF"), ")"), style)); 4241} 4242setRouterMode("hash"); 4243registerPlugin("persist", persistPlugin); 4244registerPlugin("scroll", scrollPlugin); 4245registerPlugin("url", urlPlugin); 4246registerPlugin("surge", surgePlugin); 4247registerPlugin("shift", shiftPlugin); 4248var helpers = { 4249 openDialog(id) { 4250 const dialog$1 = document.querySelector(`#${id}`); 4251 if (dialog$1) dialog$1.showModal(); 4252 }, 4253 closeDialog(id) { 4254 const dialog$1 = document.querySelector(`#${id}`); 4255 if (dialog$1) dialog$1.close(); 4256 }, 4257 scrollToTop() { 4258 window.scrollTo({ 4259 top: 0, 4260 behavior: "smooth" 4261 }); 4262 }, 4263 scrollToSection(id) { 4264 const element = document.querySelector(`#${id}`); 4265 if (element) element.scrollIntoView({ behavior: "smooth" }); 4266 }, 4267 handleFormSubmit(event) { 4268 event.preventDefault(); 4269 const form$1 = event.target; 4270 const formData = new FormData(form$1); 4271 const data = Object.fromEntries(formData.entries()); 4272 console.log("Form submitted:", data); 4273 alert("Form submitted! Check console for data."); 4274 } 4275}; 4276var buildNav = () => nav(null, a({ 4277 "data-volt-navigate": "", 4278 href: "/" 4279}, "Home"), " | ", a({ 4280 "data-volt-navigate": "", 4281 href: "/css" 4282}, "CSS"), " | ", a({ 4283 "data-volt-navigate": "", 4284 href: "/interactivity" 4285}, "Interactivity"), " | ", a({ 4286 "data-volt-navigate": "", 4287 href: "/forms" 4288}, "Forms"), " | ", a({ 4289 "data-volt-navigate": "", 4290 href: "/reactivity" 4291}, "Reactivity"), " | ", a({ 4292 "data-volt-navigate": "", 4293 href: "/plugins" 4294}, "Plugins"), " | ", a({ 4295 "data-volt-navigate": "", 4296 href: "/animations" 4297}, "Animations")); 4298function getCurrentPageFromPath() { 4299 if (globalThis.location.hash) { 4300 const hash = globalThis.location.hash.slice(1); 4301 if (hash === "/" || hash === "") return "home"; 4302 return hash.slice(1); 4303 } 4304 const path = globalThis.location.pathname; 4305 if (path === "/" || path === "") return "home"; 4306 return path.slice(1); 4307} 4308function buildDemoStructure() { 4309 const initialState = { 4310 currentPage: getCurrentPageFromPath(), 4311 message: "Welcome to the VoltX.js Demo", 4312 count: 0, 4313 formData: { 4314 name: "", 4315 email: "", 4316 bio: "", 4317 country: "us", 4318 newsletter: false, 4319 plan: "free" 4320 }, 4321 todos: [ 4322 { 4323 id: 1, 4324 text: "Learn VoltX.js", 4325 done: false 4326 }, 4327 { 4328 id: 2, 4329 text: "Build an app", 4330 done: false 4331 }, 4332 { 4333 id: 3, 4334 text: "Ship to production", 4335 done: false 4336 } 4337 ], 4338 newTodoText: "", 4339 todoIdCounter: 4, 4340 showAdvanced: false, 4341 isActive: true, 4342 isHighlighted: false, 4343 dialogMessage: "", 4344 dialogInput: "", 4345 persistedCount: 0, 4346 scrollPosition: 0, 4347 urlParam: "", 4348 showFade: false, 4349 showSlideDown: false, 4350 showScale: false, 4351 showBlur: false, 4352 showSlowFade: false, 4353 showDelayedSlide: false, 4354 showGranular: false, 4355 showCombined: false, 4356 triggerBounce: 0, 4357 triggerShake: 0, 4358 triggerFlash: 0, 4359 triggerTripleBounce: 0, 4360 triggerLongShake: 0, 4361 spinningGear: true 4362 }; 4363 return div({ 4364 "data-volt": "", 4365 "data-volt-state": JSON.stringify(initialState), 4366 "data-volt-computed:doubled": "count * 2", 4367 "data-volt-computed:active-todos": "todos.filter(t => !t.done)", 4368 "data-volt-computed:completed-todos": "todos.filter(t => t.done)" 4369 }, header(null, h1({ "data-volt-text": "message" }, "Loading..."), p(null, "A comprehensive demo showcasing VoltX.js reactive framework and Volt CSS classless styling.", small(null, "This demo demonstrates both the framework's reactive capabilities and the elegant, semantic styling of Volt CSS. No CSS classes needed!"), buildNav())), el("main", null, div({ "data-volt-if": "currentPage === 'home'" }, createHomeSection()), div({ "data-volt-if": "currentPage === 'css'" }, createCssSection()), div({ "data-volt-if": "currentPage === 'interactivity'" }, createInteractivitySection()), div({ "data-volt-if": "currentPage === 'forms'" }, createFormsSection()), div({ "data-volt-if": "currentPage === 'reactivity'" }, createReactivitySection()), div({ "data-volt-if": "currentPage === 'plugins'" }, createPluginsSection()), div({ "data-volt-if": "currentPage === 'animations'" }, createAnimationsSection())), footer(null, p(null, "Built with ", a({ href: "https://github.com/stormlightlabs/volt" }, "VoltX.js"), " - A lightweight, reactive hypermedia framework"), p(null, "This demo showcases both VoltX's reactive features and VoltX.css' classless styling."))); 4370} 4371function setupDemo() { 4372 const app = document.querySelector("#app"); 4373 if (!app) { 4374 console.error("App container not found"); 4375 return; 4376 } 4377 const demoStructure = buildDemoStructure(); 4378 app.append(demoStructure); 4379 const chargeResult = charge(); 4380 const cleanupNav = initNavigationListener(); 4381 const rootScope = chargeResult.roots[0]?.scope; 4382 if (!rootScope) { 4383 console.error("Failed to get root scope from charge result"); 4384 return; 4385 } 4386 rootScope.$helpers = helpers; 4387 const handleNavigate = (event) => { 4388 const url = event.detail?.url || ""; 4389 let page = "home"; 4390 if (url.startsWith("#")) { 4391 const hash = url.slice(1); 4392 page = hash === "/" || hash === "" ? "home" : hash.slice(1); 4393 } else if (url) page = url === "/" || url === "" ? "home" : url.slice(1); 4394 else page = getCurrentPageFromPath(); 4395 const currentPageSignal = rootScope.currentPage; 4396 if (currentPageSignal && isSignal(currentPageSignal)) currentPageSignal.set(page); 4397 window.scrollTo({ 4398 top: 0, 4399 behavior: "instant" 4400 }); 4401 }; 4402 const handlePopstate = () => { 4403 const page = getCurrentPageFromPath(); 4404 const currentPageSignal = rootScope.currentPage; 4405 if (currentPageSignal && isSignal(currentPageSignal)) currentPageSignal.set(page); 4406 }; 4407 const handleScroll = () => { 4408 const scrollPositionSignal = rootScope.scrollPosition; 4409 if (scrollPositionSignal && isSignal(scrollPositionSignal)) scrollPositionSignal.set(window.scrollY); 4410 }; 4411 globalThis.addEventListener("volt:navigate", handleNavigate); 4412 globalThis.addEventListener("volt:popstate", handlePopstate); 4413 window.addEventListener("scroll", handleScroll); 4414 return () => { 4415 chargeResult.cleanup(); 4416 cleanupNav(); 4417 globalThis.removeEventListener("volt:navigate", handleNavigate); 4418 globalThis.removeEventListener("volt:popstate", handlePopstate); 4419 window.removeEventListener("scroll", handleScroll); 4420 }; 4421} 4422setupDemo();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.