@yewdiel21/motion-kit 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +93 -83
  2. package/dist/auto/index.d.ts +22 -3
  3. package/dist/auto/index.js +41 -3
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-BFC27F2G.js → chunk-IKQ5FODN.js} +2 -2
  6. package/dist/{chunk-JQMJQSXD.js → chunk-TFNRIZHU.js} +1614 -11
  7. package/dist/chunk-TFNRIZHU.js.map +1 -0
  8. package/dist/{chunk-XDWBVFHQ.js → chunk-XAKC2QY7.js} +1202 -3
  9. package/dist/chunk-XAKC2QY7.js.map +1 -0
  10. package/dist/css/components/calendar.css +159 -0
  11. package/dist/css/components/hover-card.css +14 -0
  12. package/dist/css/components/input-otp.css +10 -0
  13. package/dist/css/components/pagination.css +11 -0
  14. package/dist/css/components/quantity.css +16 -0
  15. package/dist/css/components/slot-picker.css +126 -0
  16. package/dist/css/components/studio-dock.css +35 -0
  17. package/dist/css/components/trailer.css +37 -0
  18. package/dist/css/controls.css +65 -0
  19. package/dist/elements/index.d.ts +72 -4
  20. package/dist/elements/index.js +170 -4
  21. package/dist/elements/index.js.map +1 -1
  22. package/dist/index.css +1017 -0
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +45 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/react/index.d.ts +105 -6
  27. package/dist/react/index.js +350 -3
  28. package/dist/react/index.js.map +1 -1
  29. package/dist/{reduced-B98f8LPs.d.ts → reduced-BmBzKH94.d.ts} +199 -2
  30. package/dist/{reveal-C0hCDcB_.d.ts → reveal-jsX-uwUz.d.ts} +211 -1
  31. package/dist/{store-pHiUvxP8.d.ts → store-DYb3tTsZ.d.ts} +1 -1
  32. package/package.json +1 -1
  33. package/registry.json +178 -104
  34. package/skills/motion-kit/SKILL.md +209 -189
  35. package/src/auto/auto-init.ts +38 -0
  36. package/src/components/calendar/README.md +82 -0
  37. package/src/components/calendar/calendar.css +158 -0
  38. package/src/components/calendar/element.ts +27 -0
  39. package/src/components/calendar/engine.ts +412 -0
  40. package/src/components/calendar/react.tsx +69 -0
  41. package/src/components/hover-card/README.md +16 -0
  42. package/src/components/hover-card/element.ts +8 -0
  43. package/src/components/hover-card/engine.ts +69 -0
  44. package/src/components/hover-card/hover-card.css +13 -0
  45. package/src/components/hover-card/react.tsx +19 -0
  46. package/src/components/input-otp/README.md +18 -0
  47. package/src/components/input-otp/element.ts +14 -0
  48. package/src/components/input-otp/engine.ts +106 -0
  49. package/src/components/input-otp/input-otp.css +9 -0
  50. package/src/components/input-otp/react.tsx +21 -0
  51. package/src/components/pagination/README.md +17 -0
  52. package/src/components/pagination/element.ts +14 -0
  53. package/src/components/pagination/engine.ts +102 -0
  54. package/src/components/pagination/pagination.css +10 -0
  55. package/src/components/pagination/react.tsx +22 -0
  56. package/src/components/quantity/README.md +20 -0
  57. package/src/components/quantity/element.ts +14 -0
  58. package/src/components/quantity/engine.ts +85 -0
  59. package/src/components/quantity/quantity.css +15 -0
  60. package/src/components/quantity/react.tsx +21 -0
  61. package/src/components/slot-picker/README.md +68 -0
  62. package/src/components/slot-picker/element.ts +27 -0
  63. package/src/components/slot-picker/engine.ts +412 -0
  64. package/src/components/slot-picker/react.tsx +76 -0
  65. package/src/components/slot-picker/slot-picker.css +125 -0
  66. package/src/components/studio-dock/README.md +44 -0
  67. package/src/components/studio-dock/element.ts +11 -0
  68. package/src/components/studio-dock/engine.ts +212 -0
  69. package/src/components/studio-dock/react.tsx +23 -0
  70. package/src/components/studio-dock/studio-dock.css +34 -0
  71. package/src/components/trailer/README.md +28 -0
  72. package/src/components/trailer/element.ts +13 -0
  73. package/src/components/trailer/engine.ts +189 -0
  74. package/src/components/trailer/react.tsx +16 -0
  75. package/src/components/trailer/trailer.css +36 -0
  76. package/src/core/dates.ts +98 -0
  77. package/src/css/controls/README.md +14 -0
  78. package/src/css/controls/status.css +63 -0
  79. package/src/elements/index.ts +24 -0
  80. package/src/index.ts +25 -0
  81. package/src/react/index.ts +21 -0
  82. package/src/react/use-action-button.ts +6 -0
  83. package/src/react/use-editorial-reveal.ts +7 -0
  84. package/src/react/use-film-hero.ts +6 -0
  85. package/src/react/use-font-cycle.ts +7 -0
  86. package/src/react/use-image-zoom.ts +5 -0
  87. package/src/react/use-joined-cta.ts +5 -0
  88. package/src/react/use-local-clock.ts +5 -0
  89. package/src/react/use-scroll-dock.ts +5 -0
  90. package/src/react/use-sticker.ts +5 -0
  91. package/src/react/use-studio-dock.ts +8 -0
  92. package/src/react/use-thumbnail-nav.ts +7 -0
  93. package/src/react/use-trailer.ts +6 -0
  94. package/src/react/use-weight-hover.ts +5 -0
  95. package/src/recipes/action-button/README.md +31 -0
  96. package/src/recipes/action-button/action-button.css +113 -0
  97. package/src/recipes/action-button/create-action-button.ts +56 -0
  98. package/src/recipes/editorial-reveal/README.md +35 -0
  99. package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
  100. package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
  101. package/src/recipes/film-hero/README.md +23 -0
  102. package/src/recipes/film-hero/create-film-hero.ts +65 -0
  103. package/src/recipes/film-hero/film-hero.css +29 -0
  104. package/src/recipes/font-cycle/README.md +11 -0
  105. package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
  106. package/src/recipes/font-cycle/font-cycle.css +36 -0
  107. package/src/recipes/image-zoom/README.md +16 -0
  108. package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
  109. package/src/recipes/image-zoom/image-zoom.css +7 -0
  110. package/src/recipes/joined-cta/README.md +13 -0
  111. package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
  112. package/src/recipes/joined-cta/joined-cta.css +17 -0
  113. package/src/recipes/local-clock/README.md +31 -0
  114. package/src/recipes/local-clock/create-local-clock.ts +61 -0
  115. package/src/recipes/scroll-dock/README.md +46 -0
  116. package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
  117. package/src/recipes/scroll-dock/scroll-dock.css +70 -0
  118. package/src/recipes/sticker/README.md +59 -0
  119. package/src/recipes/sticker/create-sticker.ts +490 -0
  120. package/src/recipes/sticker/sticker.css +112 -0
  121. package/src/recipes/thumbnail-nav/README.md +16 -0
  122. package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
  123. package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
  124. package/src/recipes/weight-hover/README.md +37 -0
  125. package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
  126. package/src/recipes/weight-hover/weight-hover.css +7 -0
  127. package/dist/chunk-JQMJQSXD.js.map +0 -1
  128. package/dist/chunk-XDWBVFHQ.js.map +0 -1
  129. /package/dist/{chunk-BFC27F2G.js.map → chunk-IKQ5FODN.js.map} +0 -0
@@ -22,11 +22,11 @@ function springPath(ratio, velocity = 0, steps = 72) {
22
22
  const stiffness = 5.6;
23
23
  const frequency = stiffness / damping * Math.sqrt(1 - damping * damping);
24
24
  const phase = (velocity - stiffness) / frequency;
25
- const at = (time) => 1 + Math.exp(-stiffness * time) * (-Math.cos(frequency * time) + phase * Math.sin(frequency * time));
25
+ const at = (time2) => 1 + Math.exp(-stiffness * time2) * (-Math.cos(frequency * time2) + phase * Math.sin(frequency * time2));
26
26
  let path = "M0,0";
27
27
  for (let step = 1; step <= steps; step += 1) {
28
- const time = step / steps;
29
- path += ` L${time.toFixed(4)},${(step === steps ? 1 : at(time)).toFixed(4)}`;
28
+ const time2 = step / steps;
29
+ path += ` L${time2.toFixed(4)},${(step === steps ? 1 : at(time2)).toFixed(4)}`;
30
30
  }
31
31
  return path;
32
32
  }
@@ -132,9 +132,9 @@ function stepChannel(channel, seconds) {
132
132
  const config = channel.config;
133
133
  if (!channel.active || !config) return;
134
134
  if (config.type === "spring") {
135
- const time = Math.min(0.064, Math.max(0, seconds));
136
- const steps = Math.max(1, Math.ceil(time / (1 / 60) - 1e-9));
137
- const step = time / steps;
135
+ const time2 = Math.min(0.064, Math.max(0, seconds));
136
+ const steps = Math.max(1, Math.ceil(time2 / (1 / 60) - 1e-9));
137
+ const step = time2 / steps;
138
138
  for (let index = 0; index < steps; index += 1) {
139
139
  const force = -config.stiffness * (channel.current - channel.target) - config.damping * channel.velocity;
140
140
  channel.velocity += force / (config.mass ?? 1) * step;
@@ -591,9 +591,9 @@ function revealCounter(el, to, { from, duration = TIMING.revealCounter, format =
591
591
  let began = null;
592
592
  let cancel = () => {
593
593
  };
594
- const step = (time) => {
595
- began ??= time;
596
- const progress = Math.min(1, (time - began) / (duration * 1e3));
594
+ const step = (time2) => {
595
+ began ??= time2;
596
+ const progress = Math.min(1, (time2 - began) / (duration * 1e3));
597
597
  write(start + (to - start) * (1 - (1 - progress) ** 3));
598
598
  if (progress < 1) cancel = requestFrame(step);
599
599
  else frames.delete(el);
@@ -2291,6 +2291,1599 @@ function createMenu(dialog, options = {}) {
2291
2291
  };
2292
2292
  }
2293
2293
 
2294
+ // src/components/quantity/engine.ts
2295
+ function createQuantity(root, options = {}) {
2296
+ const input = root?.querySelector("[data-mk-quantity-input]");
2297
+ if (!root || !input) return inert({ setValue() {
2298
+ }, value: 0 });
2299
+ const scope = createScope();
2300
+ const min = Number.isFinite(options.min) ? options.min : 1;
2301
+ const max = Number.isFinite(options.max) ? Math.max(min, options.max) : Math.max(min, 999);
2302
+ const step = Number.isFinite(options.step) && options.step > 0 ? options.step : 1;
2303
+ const steps = Math.floor((max - min) / step + 1e-9);
2304
+ const upper = Math.min(max, Number((min + steps * step).toFixed(10)));
2305
+ const normalize = (n) => Math.max(min, Math.min(upper, Number((min + Math.round((n - min) / step) * step).toFixed(10))));
2306
+ const original = input.value;
2307
+ const disabled = options.disabled ?? input.disabled;
2308
+ const decrease = root.querySelector("[data-mk-quantity-decrease]");
2309
+ const increase = root.querySelector("[data-mk-quantity-increase]");
2310
+ scope.attr(input, "min", String(min));
2311
+ scope.attr(input, "max", String(max));
2312
+ scope.attr(input, "step", String(step));
2313
+ scope.attr(input, "disabled", disabled ? "" : null);
2314
+ scope.attr(root, "data-mk-ready", "");
2315
+ const initial = input.value.trim() ? Number(input.value) : NaN;
2316
+ let value = normalize(Number.isFinite(options.value) ? options.value : Number.isFinite(initial) ? initial : min);
2317
+ let pendingReset = null;
2318
+ const typed = () => input.value.trim() && Number.isFinite(input.valueAsNumber) ? input.valueAsNumber : NaN;
2319
+ const render = (syncInput = true) => {
2320
+ if (pendingReset) {
2321
+ if (pendingReset.eventPhase !== 0) return;
2322
+ if (!pendingReset.defaultPrevented) value = normalize(Number.isFinite(typed()) ? typed() : min);
2323
+ pendingReset = null;
2324
+ }
2325
+ if (syncInput) input.value = String(value);
2326
+ if (decrease) scope.attr(decrease, "disabled", disabled || value <= min ? "" : null);
2327
+ if (increase) scope.attr(increase, "disabled", disabled || value >= upper ? "" : null);
2328
+ };
2329
+ const commit = (next, notify, syncInput) => {
2330
+ if (scope.disposed || !Number.isFinite(next)) return;
2331
+ if (pendingReset?.eventPhase === 0) pendingReset = null;
2332
+ const previous = value;
2333
+ value = normalize(next);
2334
+ render(syncInput);
2335
+ if (notify && value !== previous) {
2336
+ options.onChange?.(value);
2337
+ root.dispatchEvent(new CustomEvent("mk:change", { bubbles: true, detail: { value, previous } }));
2338
+ }
2339
+ };
2340
+ const setValue = (next, notify = true) => commit(next, notify, true);
2341
+ if (decrease) scope.on(decrease, "click", () => {
2342
+ if (!decrease.disabled) setValue(value - step);
2343
+ });
2344
+ if (increase) scope.on(increase, "click", () => {
2345
+ if (!increase.disabled) setValue(value + step);
2346
+ });
2347
+ scope.on(input, "input", () => {
2348
+ const n = typed();
2349
+ if (Number.isFinite(n) && normalize(n) === n) commit(n, true, false);
2350
+ });
2351
+ scope.on(input, "change", () => {
2352
+ if (Number.isFinite(typed())) setValue(typed());
2353
+ else render();
2354
+ });
2355
+ scope.on(input, "blur", () => render());
2356
+ if (input.form) scope.on(input.form, "reset", (event) => {
2357
+ render();
2358
+ pendingReset = event;
2359
+ scope.timeout(() => {
2360
+ if (pendingReset === event) render();
2361
+ }, 0);
2362
+ });
2363
+ scope.add(() => {
2364
+ input.value = original;
2365
+ });
2366
+ render();
2367
+ return { setValue, get value() {
2368
+ return value;
2369
+ }, destroy: () => scope.dispose() };
2370
+ }
2371
+
2372
+ // src/components/pagination/engine.ts
2373
+ var positive = (n, fallback) => Number.isFinite(n) ? Math.max(1, Math.min(Number.MAX_SAFE_INTEGER - 32, Math.floor(n))) : fallback;
2374
+ function createPagination(root, options = {}) {
2375
+ const pages = root?.querySelector("[data-mk-pagination-pages]");
2376
+ if (!root || !pages) return inert({ setPage() {
2377
+ }, setTotalPages() {
2378
+ }, page: 1, totalPages: 1 });
2379
+ const scope = createScope();
2380
+ const original = [...pages.childNodes];
2381
+ const previous = root.querySelector("[data-mk-pagination-previous]");
2382
+ const next = root.querySelector("[data-mk-pagination-next]");
2383
+ let total = positive(options.totalPages, 10);
2384
+ let page = Math.min(total, positive(options.page, 1));
2385
+ const siblings = Math.min(10, Math.max(0, Number.isFinite(options.siblings) ? Math.floor(options.siblings) : 1));
2386
+ scope.attr(root, "data-mk-ready", "");
2387
+ const render = () => {
2388
+ const hadFocus = pages.contains(document.activeElement);
2389
+ const selected = /* @__PURE__ */ new Set([1, total]);
2390
+ for (let n = Math.max(1, page - siblings); n <= Math.min(total, page + siblings); n++) selected.add(n);
2391
+ const fragment = document.createDocumentFragment();
2392
+ let last = 0;
2393
+ for (const n of [...selected].sort((a, b) => a - b)) {
2394
+ if (last && n - last > 1) {
2395
+ const gap = document.createElement("span");
2396
+ gap.className = "mk-pagination__gap";
2397
+ gap.textContent = "\u2026";
2398
+ gap.setAttribute("aria-hidden", "true");
2399
+ fragment.append(gap);
2400
+ }
2401
+ const button = document.createElement("button");
2402
+ button.type = "button";
2403
+ button.dataset.mkPage = String(n);
2404
+ button.textContent = String(n);
2405
+ button.setAttribute("aria-label", options.pageLabel?.(n) ?? `P\xE1gina ${n}`);
2406
+ if (n === page) button.setAttribute("aria-current", "page");
2407
+ fragment.append(button);
2408
+ last = n;
2409
+ }
2410
+ pages.replaceChildren(fragment);
2411
+ if (hadFocus) pages.querySelector("[aria-current]")?.focus();
2412
+ if (previous) scope.attr(previous, "disabled", page === 1 ? "" : null);
2413
+ if (next) scope.attr(next, "disabled", page === total ? "" : null);
2414
+ };
2415
+ const setPage = (value, notify = true) => {
2416
+ if (scope.disposed || !Number.isFinite(value)) return;
2417
+ const candidate = Math.max(1, Math.min(total, Math.round(value)));
2418
+ if (candidate === page) return;
2419
+ const focused = pages.contains(document.activeElement);
2420
+ page = candidate;
2421
+ render();
2422
+ if (focused) pages.querySelector("[aria-current]")?.focus();
2423
+ if (notify) {
2424
+ options.onChange?.(page);
2425
+ root.dispatchEvent(new CustomEvent("mk:change", { bubbles: true, detail: { page, totalPages: total } }));
2426
+ }
2427
+ };
2428
+ scope.on(root, "click", (event) => {
2429
+ const target = event.target.closest("button");
2430
+ if (!target || target.disabled) return;
2431
+ if (target === previous) setPage(page - 1);
2432
+ else if (target === next) setPage(page + 1);
2433
+ else if (target.dataset.mkPage) setPage(Number(target.dataset.mkPage));
2434
+ });
2435
+ scope.on(root, "keydown", (event) => {
2436
+ if (!(event.target instanceof HTMLButtonElement)) return;
2437
+ const to = event.key === "ArrowRight" ? page + 1 : event.key === "ArrowLeft" ? page - 1 : event.key === "Home" ? 1 : event.key === "End" ? total : null;
2438
+ if (to === null) return;
2439
+ event.preventDefault();
2440
+ setPage(to);
2441
+ pages.querySelector("[aria-current]")?.focus();
2442
+ });
2443
+ scope.add(() => pages.replaceChildren(...original));
2444
+ render();
2445
+ return {
2446
+ setPage,
2447
+ setTotalPages(value) {
2448
+ if (scope.disposed || !Number.isFinite(value)) return;
2449
+ total = positive(value, 1);
2450
+ if (page > total) setPage(total);
2451
+ else render();
2452
+ },
2453
+ get page() {
2454
+ return page;
2455
+ },
2456
+ get totalPages() {
2457
+ return total;
2458
+ },
2459
+ destroy: () => scope.dispose()
2460
+ };
2461
+ }
2462
+
2463
+ // src/components/hover-card/engine.ts
2464
+ var sequence = 0;
2465
+ function createHoverCard(root, options = {}) {
2466
+ const trigger = root?.querySelector("[data-mk-hover-trigger]");
2467
+ const panel = root?.querySelector("[data-mk-hover-panel]");
2468
+ if (!root || !trigger || !panel) return inert({ open() {
2469
+ }, close() {
2470
+ }, isOpen: false });
2471
+ const scope = createScope();
2472
+ const openDelay = Number.isFinite(options.openDelay) ? Math.max(0, options.openDelay) : 120;
2473
+ const closeDelay = Number.isFinite(options.closeDelay) ? Math.max(0, options.closeDelay) : 140;
2474
+ let opened = false;
2475
+ let timer;
2476
+ let pointerInside = false;
2477
+ let id = panel.id;
2478
+ if (!id) {
2479
+ do {
2480
+ id = `mk-hover-${++sequence}`;
2481
+ } while (document.getElementById(id));
2482
+ }
2483
+ scope.attr(panel, "id", id);
2484
+ scope.attr(trigger, "aria-controls", id);
2485
+ scope.attr(root, "data-mk-placement", options.placement ?? "top");
2486
+ scope.attr(root, "data-mk-ready", "");
2487
+ const cancel = () => {
2488
+ clearTimeout(timer);
2489
+ timer = void 0;
2490
+ };
2491
+ const set = (value) => {
2492
+ cancel();
2493
+ if (scope.disposed) return;
2494
+ const changed = value !== opened;
2495
+ opened = value;
2496
+ scope.attr(trigger, "aria-expanded", String(value));
2497
+ scope.attr(panel, "hidden", value ? null : "");
2498
+ scope.attr(root, "data-mk-open", value ? "" : null);
2499
+ if (changed) {
2500
+ options.onOpenChange?.(value);
2501
+ root.dispatchEvent(new CustomEvent("mk:open-change", { bubbles: true, detail: { open: value } }));
2502
+ }
2503
+ };
2504
+ const schedule = (value, delay) => {
2505
+ cancel();
2506
+ timer = setTimeout(() => set(value), delay);
2507
+ };
2508
+ scope.on(root, "pointerenter", () => {
2509
+ pointerInside = true;
2510
+ schedule(true, openDelay);
2511
+ });
2512
+ scope.on(root, "pointerleave", () => {
2513
+ pointerInside = false;
2514
+ if (!root.contains(document.activeElement)) schedule(false, closeDelay);
2515
+ });
2516
+ scope.on(root, "focusin", () => set(true));
2517
+ scope.on(root, "focusout", (event) => {
2518
+ if (!pointerInside && !root.contains(event.relatedTarget)) schedule(false, closeDelay);
2519
+ });
2520
+ scope.on(trigger, "click", () => set(true));
2521
+ scope.on(document, "pointerdown", (event) => {
2522
+ if (!root.contains(event.target)) set(false);
2523
+ });
2524
+ scope.on(root, "keydown", (event) => {
2525
+ if (event.key !== "Escape" || !opened) return;
2526
+ event.preventDefault();
2527
+ trigger.focus();
2528
+ set(false);
2529
+ });
2530
+ scope.add(cancel);
2531
+ set(false);
2532
+ return { open: () => set(true), close: () => set(false), get isOpen() {
2533
+ return opened;
2534
+ }, destroy: () => scope.dispose() };
2535
+ }
2536
+
2537
+ // src/components/input-otp/engine.ts
2538
+ function createInputOtp(root, options = {}) {
2539
+ const input = root?.querySelector("[data-mk-otp-input]");
2540
+ const slots = root?.querySelector("[data-mk-otp-slots]");
2541
+ if (!root || !input || !slots) return inert({ setValue() {
2542
+ }, focus() {
2543
+ }, value: "" });
2544
+ const scope = createScope();
2545
+ const length = Number.isFinite(options.length) ? Math.min(16, Math.max(1, Math.round(options.length))) : 6;
2546
+ const clean = (text) => text.replace(/[^0-9]/g, "").slice(0, length);
2547
+ const original = input.value;
2548
+ const originalNodes = [...slots.childNodes];
2549
+ let value = clean(options.value ?? input.value);
2550
+ let pendingReset = null;
2551
+ const cells = Array.from({ length }, () => {
2552
+ const cell = document.createElement("span");
2553
+ cell.className = "mk-input-otp__slot";
2554
+ return cell;
2555
+ });
2556
+ slots.replaceChildren(...cells);
2557
+ scope.attr(slots, "aria-hidden", "true");
2558
+ scope.attr(input, "type", "text");
2559
+ scope.attr(input, "inputmode", "numeric");
2560
+ scope.attr(input, "autocomplete", "one-time-code");
2561
+ scope.attr(input, "maxlength", String(length));
2562
+ scope.attr(input, "pattern", `[0-9]{${length}}`);
2563
+ scope.attr(root, "data-mk-ready", "");
2564
+ const render = () => {
2565
+ if (pendingReset) {
2566
+ if (pendingReset.eventPhase !== 0) return;
2567
+ if (!pendingReset.defaultPrevented) value = clean(input.value);
2568
+ pendingReset = null;
2569
+ }
2570
+ input.value = value;
2571
+ const active = document.activeElement === input ? Math.min(length - 1, input.selectionStart ?? value.length) : -1;
2572
+ cells.forEach((cell, i) => {
2573
+ const text = value[i] ? options.mask ? "\u2022" : value[i] : "";
2574
+ if (cell.textContent !== text) cell.textContent = text;
2575
+ cell.toggleAttribute("data-mk-active", i === active);
2576
+ cell.toggleAttribute("data-mk-filled", i < value.length);
2577
+ });
2578
+ scope.attr(root, "data-mk-complete", value.length === length ? "" : null);
2579
+ };
2580
+ const setValue = (next, notify = true) => {
2581
+ if (scope.disposed) return;
2582
+ if (pendingReset?.eventPhase === 0) pendingReset = null;
2583
+ const previous = value;
2584
+ value = clean(String(next));
2585
+ render();
2586
+ if (notify && value !== previous) {
2587
+ options.onChange?.(value);
2588
+ root.dispatchEvent(new CustomEvent("mk:change", { bubbles: true, detail: { value, complete: value.length === length } }));
2589
+ if (value.length === length) {
2590
+ options.onComplete?.(value);
2591
+ root.dispatchEvent(new CustomEvent("mk:complete", { bubbles: true, detail: { value } }));
2592
+ }
2593
+ }
2594
+ };
2595
+ scope.on(input, "input", (event) => {
2596
+ if (event.isComposing) return;
2597
+ const caret = clean(input.value.slice(0, input.selectionStart ?? input.value.length)).length;
2598
+ setValue(input.value);
2599
+ input.setSelectionRange(caret, caret);
2600
+ render();
2601
+ });
2602
+ scope.on(input, "compositionend", () => setValue(input.value));
2603
+ scope.on(input, "paste", (event) => {
2604
+ const text = event.clipboardData?.getData("text");
2605
+ if (text === void 0) return;
2606
+ event.preventDefault();
2607
+ const start = input.selectionStart ?? value.length;
2608
+ const end = input.selectionEnd ?? start;
2609
+ const inserted = clean(text);
2610
+ setValue(value.slice(0, start) + inserted + value.slice(end));
2611
+ const caret = Math.min(length, start + inserted.length);
2612
+ input.setSelectionRange(caret, caret);
2613
+ render();
2614
+ });
2615
+ scope.on(input, "focus", render);
2616
+ scope.on(input, "blur", render);
2617
+ scope.on(input, "keyup", render);
2618
+ scope.on(input, "click", render);
2619
+ scope.on(document, "selectionchange", () => {
2620
+ if (document.activeElement === input) render();
2621
+ });
2622
+ if (input.form) scope.on(input.form, "reset", (event) => {
2623
+ render();
2624
+ pendingReset = event;
2625
+ scope.timeout(() => {
2626
+ if (pendingReset === event) render();
2627
+ }, 0);
2628
+ });
2629
+ scope.add(() => {
2630
+ slots.replaceChildren(...originalNodes);
2631
+ input.value = original;
2632
+ });
2633
+ render();
2634
+ return { setValue, focus: () => {
2635
+ if (!scope.disposed) input.focus();
2636
+ }, get value() {
2637
+ return value;
2638
+ }, destroy: () => scope.dispose() };
2639
+ }
2640
+
2641
+ // src/components/studio-dock/engine.ts
2642
+ var nextId = 0;
2643
+ var PANEL_OFFSET = 76;
2644
+ var VIEWPORT_GAP = 12;
2645
+ var MIN_SPACE = 320;
2646
+ var FOCUSABLE = 'a[href],button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),video[controls],[tabindex]:not([tabindex="-1"])';
2647
+ function createStudioDock(root, options = {}) {
2648
+ const toggleButton = root?.querySelector("[data-mk-studio-toggle]");
2649
+ const panel = root?.querySelector("[data-mk-studio-panel]");
2650
+ if (!root || !toggleButton || !panel) return inert({ open() {
2651
+ }, close() {
2652
+ }, toggle() {
2653
+ }, selectPage() {
2654
+ }, state: { open: false, view: "menu", page: 0 } });
2655
+ registerMotion();
2656
+ const scope = createScope();
2657
+ const context = gsap.context(() => {
2658
+ }, root);
2659
+ scope.add(() => context.revert());
2660
+ const views = [...panel.querySelectorAll("[data-mk-studio-view]")];
2661
+ const pages = [...panel.querySelectorAll("[data-mk-studio-page]")];
2662
+ const pageButtons = [...root.querySelectorAll("[data-mk-studio-page-to]")];
2663
+ const slide = root.querySelector("[data-mk-studio-slide]");
2664
+ const tracked = [root, panel, ...slide ? [slide] : []];
2665
+ tracked.forEach((el) => {
2666
+ const style = el.getAttribute("style");
2667
+ scope.add(() => style === null ? el.removeAttribute("style") : el.setAttribute("style", style));
2668
+ });
2669
+ scope.attr(panel, "id", panel.id || `mk-studio-panel-${++nextId}`);
2670
+ scope.attr(panel, "role", panel.getAttribute("role") || "dialog");
2671
+ scope.attr(panel, "aria-label", panel.getAttribute("aria-label") || "Studio navigation");
2672
+ scope.attr(panel, "tabindex", "-1");
2673
+ scope.attr(toggleButton, "aria-controls", panel.id);
2674
+ scope.attr(toggleButton, "aria-expanded", "false");
2675
+ scope.attr(panel, "hidden", "");
2676
+ scope.attr(panel, "aria-hidden", "true");
2677
+ scope.attr(root, "data-mk-ready", "");
2678
+ let state = { open: false, view: "menu", page: 0 };
2679
+ let previousFocus = null;
2680
+ let animation;
2681
+ let slideStart = null;
2682
+ let slideDistance = 0;
2683
+ let slideMoved = false;
2684
+ let suppressSlideClick = false;
2685
+ const place2 = (decide = false) => {
2686
+ const view = root.ownerDocument.defaultView;
2687
+ if (!state.open || !view) return;
2688
+ const rect = root.getBoundingClientRect();
2689
+ const offset = parseFloat(view.getComputedStyle(root).getPropertyValue("--mk-studio-dock-offset")) || PANEL_OFFSET;
2690
+ const above = rect.bottom - offset - VIEWPORT_GAP;
2691
+ const below = view.innerHeight - rect.top - offset - VIEWPORT_GAP;
2692
+ if (decide) scope.attr(root, "data-mk-studio-below", above < Math.min(panel.scrollHeight || MIN_SPACE, MIN_SPACE) && below > above ? "" : null);
2693
+ const flip = root.hasAttribute("data-mk-studio-below");
2694
+ root.style.setProperty("--mk-studio-dock-space", `${Math.max(MIN_SPACE / 2, Math.floor(flip ? below : above))}px`);
2695
+ };
2696
+ const hidden = () => root.hasAttribute("data-mk-studio-below") ? { clipPath: "inset(0% 0% 100% 0% round 18px)", y: -16, opacity: 0 } : { clipPath: "inset(100% 0% 0% 0% round 18px)", y: 16, opacity: 0 };
2697
+ const notify = () => {
2698
+ const value = { ...state };
2699
+ options.onChange?.(value);
2700
+ root.dispatchEvent(new CustomEvent("mk:studio-dock", { bubbles: true, detail: value }));
2701
+ };
2702
+ const isTabbable = (el) => el.tabIndex >= 0 && !el.closest("[hidden], [inert]") && el.getAttribute("aria-disabled") !== "true";
2703
+ const visibleFocus = () => [...panel.querySelectorAll(FOCUSABLE)].filter(isTabbable);
2704
+ const focusView = () => {
2705
+ if (options.focusOnOpen !== false) (visibleFocus()[0] ?? panel).focus({ preventScroll: true });
2706
+ };
2707
+ const setView = (view) => {
2708
+ state.view = views.length && !views.some((el) => el.dataset.mkStudioView === view) ? "menu" : view;
2709
+ views.forEach((el) => scope.attr(el, "hidden", el.dataset.mkStudioView === state.view ? null : ""));
2710
+ scope.attr(root, "data-mk-studio-active", state.view);
2711
+ panel.querySelectorAll("video").forEach((video) => {
2712
+ if (state.view !== "reel") video.pause();
2713
+ });
2714
+ };
2715
+ const selectPage = (index, announce = true) => {
2716
+ if (scope.disposed || !pages.length || !Number.isFinite(index)) return;
2717
+ state.page = (Math.round(index) % pages.length + pages.length) % pages.length;
2718
+ pages.forEach((el, i) => scope.attr(el, "hidden", i === state.page ? null : ""));
2719
+ pageButtons.forEach((el) => scope.attr(el, "aria-current", Number(el.dataset.mkStudioPageTo) === state.page ? "true" : null));
2720
+ if (announce) notify();
2721
+ };
2722
+ const open = (view = "menu") => {
2723
+ if (scope.disposed) return;
2724
+ const wasOpen = state.open;
2725
+ if (wasOpen && state.view === view) return;
2726
+ if (!wasOpen) {
2727
+ const active = root.ownerDocument.activeElement;
2728
+ previousFocus = active instanceof HTMLElement && active !== root.ownerDocument.body && !panel.contains(active) ? active : toggleButton;
2729
+ }
2730
+ animation?.kill();
2731
+ state.open = true;
2732
+ scope.attr(panel, "hidden", null);
2733
+ scope.attr(panel, "inert", null);
2734
+ scope.attr(panel, "aria-hidden", "false");
2735
+ scope.attr(root, "data-mk-studio-open", "");
2736
+ scope.attr(toggleButton, "aria-expanded", "true");
2737
+ setView(view);
2738
+ place2(!wasOpen);
2739
+ if (!reduced()) context.add(() => {
2740
+ animation = gsap.fromTo(panel, hidden(), { clipPath: "inset(0% 0% 0% 0% round 18px)", y: 0, opacity: 1, duration: Math.max(0, options.duration ?? 1.3), ease: options.ease ?? "back.out(0.52)", overwrite: true });
2741
+ });
2742
+ else context.add(() => {
2743
+ gsap.set(panel, { clipPath: "inset(0% round 18px)", y: 0, opacity: 1 });
2744
+ });
2745
+ focusView();
2746
+ notify();
2747
+ };
2748
+ const close = (restoreFocus = true) => {
2749
+ if (scope.disposed || !state.open) return;
2750
+ animation?.kill();
2751
+ state.open = false;
2752
+ scope.attr(toggleButton, "aria-expanded", "false");
2753
+ scope.attr(root, "data-mk-studio-open", null);
2754
+ scope.attr(panel, "inert", "");
2755
+ scope.attr(panel, "aria-hidden", "true");
2756
+ if (reduced()) scope.attr(panel, "hidden", "");
2757
+ else context.add(() => {
2758
+ animation = gsap.to(panel, { ...hidden(), duration: Math.max(0, options.duration ?? 1.3), ease: options.ease ?? "back.out(0.52)", overwrite: true, onComplete: () => {
2759
+ if (!scope.disposed && !state.open) scope.attr(panel, "hidden", "");
2760
+ } });
2761
+ });
2762
+ panel.querySelectorAll("video").forEach((video) => video.pause());
2763
+ if (restoreFocus) (previousFocus?.isConnected ? previousFocus : toggleButton).focus({ preventScroll: true });
2764
+ notify();
2765
+ };
2766
+ scope.on(root, "click", (event) => {
2767
+ const target = event.target instanceof Element ? event.target : null;
2768
+ if (!target) return;
2769
+ if (target.closest("[data-mk-studio-toggle]") === toggleButton) state.open ? close() : open();
2770
+ else if (target.closest("[data-mk-studio-close]")) close();
2771
+ else if (target.closest("[data-mk-studio-slide]")) {
2772
+ if (suppressSlideClick) {
2773
+ event.preventDefault();
2774
+ suppressSlideClick = false;
2775
+ } else open("contact");
2776
+ } else {
2777
+ const view = target.closest("[data-mk-studio-to]")?.dataset.mkStudioTo;
2778
+ if (view === "menu" || view === "contact" || view === "reel") open(view);
2779
+ const page = target.closest("[data-mk-studio-page-to]");
2780
+ if (page) selectPage(Number(page.dataset.mkStudioPageTo));
2781
+ }
2782
+ });
2783
+ if (root.ownerDocument.defaultView) scope.on(root.ownerDocument.defaultView, "resize", () => place2());
2784
+ scope.on(root.ownerDocument, "scroll", () => place2(), { passive: true, capture: true });
2785
+ scope.on(root.ownerDocument, "pointerdown", (event) => {
2786
+ if (state.open && event.target instanceof Node && !root.contains(event.target)) close(false);
2787
+ });
2788
+ scope.on(root.ownerDocument, "keydown", (event) => {
2789
+ if (!state.open || event.defaultPrevented) return;
2790
+ if (event.key === "Escape") {
2791
+ event.preventDefault();
2792
+ close();
2793
+ return;
2794
+ }
2795
+ if (!(event.target instanceof Node) || !root.contains(event.target)) return;
2796
+ if ((event.key === "ArrowLeft" || event.key === "ArrowRight") && event.target.closest("[data-mk-studio-pages]")) {
2797
+ event.preventDefault();
2798
+ selectPage(state.page + (event.key === "ArrowRight" ? 1 : -1));
2799
+ pageButtons.find((el) => Number(el.dataset.mkStudioPageTo) === state.page)?.focus();
2800
+ }
2801
+ if (event.key === "Tab" && options.trapFocus !== false) {
2802
+ const elements = [...root.querySelectorAll(FOCUSABLE)].filter((el) => (el === toggleButton || panel.contains(el)) && isTabbable(el));
2803
+ if (!elements.length) {
2804
+ event.preventDefault();
2805
+ panel.focus();
2806
+ return;
2807
+ }
2808
+ const first = elements[0], last = elements.at(-1);
2809
+ if (event.shiftKey && (root.ownerDocument.activeElement === first || root.ownerDocument.activeElement === panel)) {
2810
+ event.preventDefault();
2811
+ last.focus();
2812
+ } else if (!event.shiftKey && root.ownerDocument.activeElement === last) {
2813
+ event.preventDefault();
2814
+ first.focus();
2815
+ }
2816
+ }
2817
+ });
2818
+ if (slide) {
2819
+ scope.on(slide, "pointerdown", (event) => {
2820
+ if (event.button !== 0) return;
2821
+ slideStart = event.clientX;
2822
+ slideDistance = 0;
2823
+ slideMoved = false;
2824
+ });
2825
+ scope.on(root.ownerDocument, "pointermove", (event) => {
2826
+ if (slideStart === null) return;
2827
+ const width = Math.max(0, (slide.parentElement?.clientWidth ?? 0) - slide.offsetWidth);
2828
+ slideMoved ||= Math.abs(event.clientX - slideStart) > 5;
2829
+ slideDistance = Math.max(0, Math.min(width, event.clientX - slideStart));
2830
+ if (!reduced()) slide.style.setProperty("--mk-studio-drag", `${slideDistance}px`);
2831
+ });
2832
+ const release = (cancel = false) => {
2833
+ if (slideStart === null) return;
2834
+ const width = Math.max(0, (slide.parentElement?.clientWidth ?? 0) - slide.offsetWidth);
2835
+ suppressSlideClick = slideMoved;
2836
+ slideStart = null;
2837
+ slide.style.removeProperty("--mk-studio-drag");
2838
+ if (!cancel && width > 0 && slideDistance / width >= 0.7) open("contact");
2839
+ scope.timeout(() => {
2840
+ suppressSlideClick = false;
2841
+ }, 150);
2842
+ };
2843
+ scope.on(root.ownerDocument, "pointerup", () => release());
2844
+ scope.on(root.ownerDocument, "pointercancel", () => release(true));
2845
+ }
2846
+ const clocks = [...root.querySelectorAll("[data-mk-studio-timezone]")];
2847
+ const clockOriginals = clocks.map((el) => ({ el, text: el.textContent, style: el.getAttribute("style") }));
2848
+ const tick = () => clocks.forEach((el) => {
2849
+ try {
2850
+ el.textContent = new Intl.DateTimeFormat(void 0, { hour: "2-digit", minute: "2-digit", timeZone: el.dataset.mkStudioTimezone }).format(/* @__PURE__ */ new Date());
2851
+ } catch {
2852
+ }
2853
+ });
2854
+ if (clocks.length) {
2855
+ tick();
2856
+ const timer = setInterval(tick, 6e4);
2857
+ scope.add(() => clearInterval(timer));
2858
+ }
2859
+ scope.add(() => clockOriginals.forEach(({ el, text, style }) => {
2860
+ el.textContent = text;
2861
+ if (style === null) el.removeAttribute("style");
2862
+ else el.setAttribute("style", style);
2863
+ }));
2864
+ setView("menu");
2865
+ selectPage(0, false);
2866
+ return { open, close, toggle: () => state.open ? close() : open(), selectPage, get state() {
2867
+ return { ...state };
2868
+ }, destroy() {
2869
+ if (scope.disposed) return;
2870
+ const focused = root.contains(root.ownerDocument.activeElement);
2871
+ animation?.kill();
2872
+ panel.querySelectorAll("video").forEach((video) => video.pause());
2873
+ scope.dispose();
2874
+ if (focused) toggleButton.focus({ preventScroll: true });
2875
+ } };
2876
+ }
2877
+
2878
+ // src/components/trailer/engine.ts
2879
+ var time = (value) => {
2880
+ const seconds = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
2881
+ return `${Math.floor(seconds / 60).toString().padStart(2, "0")}:${(seconds % 60).toString().padStart(2, "0")}`;
2882
+ };
2883
+ function createTrailer(root, options = {}) {
2884
+ const video = root?.querySelector("video");
2885
+ if (!root || !video || root.tagName !== "DIALOG") return inert({ open() {
2886
+ }, close() {
2887
+ }, toggle() {
2888
+ }, play: async () => false, pause() {
2889
+ }, seek() {
2890
+ }, setVolume() {
2891
+ }, isOpen: false });
2892
+ const scope = createScope();
2893
+ const get = (name) => root.querySelector(`[data-mk-trailer-${name}]`);
2894
+ const playButton = get("play");
2895
+ const muteButton = get("mute");
2896
+ const fullButton = get("fullscreen");
2897
+ const fullTarget = get("stage") ?? video;
2898
+ const captionsButton = get("captions");
2899
+ const seekInput = get("seek");
2900
+ const volumeInput = get("volume");
2901
+ const output = get("time");
2902
+ const status = get("status");
2903
+ const originalText = [output, status].filter((el) => !!el).map((el) => [el, el.textContent]);
2904
+ scope.add(() => originalText.forEach(([el, text]) => {
2905
+ el.textContent = text;
2906
+ }));
2907
+ scope.attr(root, "data-mk-trailer-ready", "");
2908
+ scope.attr(root, "data-mk-trailer-state", "paused");
2909
+ const controls = video.controls;
2910
+ if (playButton && muteButton && seekInput && (!video.querySelector("track") || captionsButton)) video.controls = false;
2911
+ scope.add(() => {
2912
+ video.controls = controls;
2913
+ });
2914
+ for (const input of [seekInput, volumeInput]) {
2915
+ if (!input) continue;
2916
+ const initialValue = input.value;
2917
+ scope.add(() => {
2918
+ input.value = initialValue;
2919
+ });
2920
+ scope.attr(input, "min", "0");
2921
+ scope.attr(input, "max", "1");
2922
+ scope.attr(input, "step", input === volumeInput ? ".01" : ".1");
2923
+ scope.attr(input, "aria-label", input.getAttribute("aria-label") ?? (input === volumeInput ? "Volumen" : "Posici\xF3n del video"));
2924
+ }
2925
+ if (status) scope.attr(status, "role", "status");
2926
+ const tell = (message) => {
2927
+ if (!scope.disposed && status) status.textContent = message;
2928
+ };
2929
+ const sync = () => {
2930
+ if (scope.disposed) return;
2931
+ const duration = Number.isFinite(video.duration) ? Math.max(0, video.duration) : 0;
2932
+ scope.attr(root, "data-mk-trailer-state", video.ended ? "ended" : video.paused ? "paused" : "playing");
2933
+ if (playButton) {
2934
+ scope.attr(playButton, "aria-label", video.paused || video.ended ? "Reproducir" : "Pausar");
2935
+ scope.attr(playButton, "aria-pressed", String(!video.paused && !video.ended));
2936
+ }
2937
+ if (muteButton) {
2938
+ scope.attr(muteButton, "aria-label", video.muted ? "Activar sonido" : "Silenciar");
2939
+ scope.attr(muteButton, "aria-pressed", String(video.muted));
2940
+ }
2941
+ if (seekInput) {
2942
+ scope.attr(seekInput, "max", String(duration || 1));
2943
+ scope.attr(seekInput, "disabled", duration ? null : "");
2944
+ scope.attr(seekInput, "aria-valuetext", `${time(video.currentTime)} de ${time(duration)}`);
2945
+ seekInput.value = String(Number.isFinite(video.currentTime) ? video.currentTime : 0);
2946
+ }
2947
+ if (volumeInput) volumeInput.value = String(video.muted ? 0 : video.volume);
2948
+ if (captionsButton) {
2949
+ scope.attr(captionsButton, "disabled", video.textTracks.length ? null : "");
2950
+ scope.attr(captionsButton, "aria-pressed", String(video.textTracks[0]?.mode === "showing"));
2951
+ }
2952
+ if (output) output.textContent = `${time(video.currentTime)} / ${time(duration)}`;
2953
+ };
2954
+ let playRequest = 0;
2955
+ let wantsPlayback = false;
2956
+ const pause = () => {
2957
+ if (!scope.disposed) {
2958
+ wantsPlayback = false;
2959
+ ++playRequest;
2960
+ video.pause();
2961
+ sync();
2962
+ }
2963
+ };
2964
+ const play = async () => {
2965
+ if (scope.disposed) return false;
2966
+ wantsPlayback = true;
2967
+ const request = ++playRequest;
2968
+ try {
2969
+ await video.play();
2970
+ if (scope.disposed || request !== playRequest) {
2971
+ if (scope.disposed || !wantsPlayback) video.pause();
2972
+ return false;
2973
+ }
2974
+ tell("");
2975
+ sync();
2976
+ return true;
2977
+ } catch {
2978
+ if (scope.disposed || request !== playRequest) return false;
2979
+ wantsPlayback = false;
2980
+ tell("No se pudo reproducir el video. Revisa la fuente y vuelve a intentarlo.");
2981
+ sync();
2982
+ return false;
2983
+ }
2984
+ };
2985
+ const seek = (seconds) => {
2986
+ if (scope.disposed || !Number.isFinite(seconds) || !Number.isFinite(video.duration) || video.duration <= 0) return;
2987
+ video.currentTime = Math.max(0, Math.min(video.duration, seconds));
2988
+ sync();
2989
+ };
2990
+ const setVolume = (volume) => {
2991
+ if (scope.disposed || !Number.isFinite(volume)) return;
2992
+ video.volume = Math.max(0, Math.min(1, volume));
2993
+ video.muted = volume <= 0;
2994
+ sync();
2995
+ };
2996
+ const exitFullscreen = () => {
2997
+ if (document.fullscreenElement !== fullTarget) return;
2998
+ void document.exitFullscreen?.().catch(() => {
2999
+ });
3000
+ };
3001
+ const dialog = createDialog(root, {
3002
+ triggers: options.triggers,
3003
+ closeOnBackdrop: options.closeOnBackdrop ?? true,
3004
+ drag: false,
3005
+ autoResize: false,
3006
+ animateContent: false,
3007
+ onOpen: () => {
3008
+ sync();
3009
+ if (options.autoplay) void play();
3010
+ options.onOpen?.();
3011
+ },
3012
+ onClose: () => {
3013
+ ++playRequest;
3014
+ pause();
3015
+ exitFullscreen();
3016
+ options.onClose?.();
3017
+ }
3018
+ });
3019
+ if (playButton) scope.on(playButton, "click", () => video.paused || video.ended ? void play() : pause());
3020
+ if (muteButton) scope.on(muteButton, "click", () => {
3021
+ video.muted = !video.muted;
3022
+ sync();
3023
+ });
3024
+ if (seekInput) scope.on(seekInput, "input", () => seek(Number(seekInput.value)));
3025
+ if (volumeInput) scope.on(volumeInput, "input", () => setVolume(Number(volumeInput.value)));
3026
+ if (captionsButton) scope.on(captionsButton, "click", () => {
3027
+ const track = video.textTracks[0];
3028
+ if (track) track.mode = track.mode === "showing" ? "hidden" : "showing";
3029
+ sync();
3030
+ });
3031
+ const fullscreen = async () => {
3032
+ if (scope.disposed) return;
3033
+ try {
3034
+ if (document.fullscreenElement === fullTarget) await document.exitFullscreen?.();
3035
+ else if (fullTarget.requestFullscreen) {
3036
+ await fullTarget.requestFullscreen();
3037
+ if (scope.disposed || !dialog.isOpen) exitFullscreen();
3038
+ } else tell("La pantalla completa no est\xE1 disponible en este navegador.");
3039
+ } catch {
3040
+ tell("No se pudo abrir la pantalla completa.");
3041
+ }
3042
+ };
3043
+ if (fullButton) {
3044
+ scope.attr(fullButton, "aria-label", "Pantalla completa");
3045
+ scope.on(fullButton, "click", () => {
3046
+ void fullscreen();
3047
+ });
3048
+ scope.on(document, "fullscreenchange", () => {
3049
+ scope.attr(fullButton, "aria-label", document.fullscreenElement === fullTarget ? "Salir de pantalla completa" : "Pantalla completa");
3050
+ });
3051
+ }
3052
+ for (const event of ["loadedmetadata", "durationchange", "timeupdate", "play", "pause", "ended", "volumechange"]) scope.on(video, event, sync);
3053
+ scope.on(video, "error", () => {
3054
+ tell("El video no est\xE1 disponible.");
3055
+ scope.attr(root, "data-mk-trailer-state", "error");
3056
+ });
3057
+ scope.on(root, "keydown", (event) => {
3058
+ if (!dialog.isOpen || event.target instanceof Element && event.target.closest("button, input, select, textarea, a, [contenteditable]")) return;
3059
+ if (event.key === " " || event.key.toLowerCase() === "k") {
3060
+ event.preventDefault();
3061
+ video.paused ? void play() : pause();
3062
+ } else if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
3063
+ event.preventDefault();
3064
+ seek(video.currentTime + (event.key === "ArrowRight" ? 5 : -5));
3065
+ } else if (event.key.toLowerCase() === "m") {
3066
+ video.muted = !video.muted;
3067
+ sync();
3068
+ }
3069
+ });
3070
+ sync();
3071
+ return {
3072
+ open: (origin) => {
3073
+ if (!scope.disposed) dialog.open(origin);
3074
+ },
3075
+ close: () => {
3076
+ if (!scope.disposed) dialog.close();
3077
+ },
3078
+ toggle: (origin) => {
3079
+ if (!scope.disposed) dialog.toggle(origin);
3080
+ },
3081
+ play,
3082
+ pause,
3083
+ seek,
3084
+ setVolume,
3085
+ get isOpen() {
3086
+ return dialog.isOpen;
3087
+ },
3088
+ destroy() {
3089
+ if (scope.disposed) return;
3090
+ wantsPlayback = false;
3091
+ ++playRequest;
3092
+ video.pause();
3093
+ exitFullscreen();
3094
+ dialog.destroy();
3095
+ scope.dispose();
3096
+ }
3097
+ };
3098
+ }
3099
+
3100
+ // src/core/dates.ts
3101
+ function toDate(input, fallback = /* @__PURE__ */ new Date()) {
3102
+ if (input instanceof Date) return Number.isNaN(input.getTime()) ? startOfDay(fallback) : startOfDay(input);
3103
+ if (typeof input === "string") {
3104
+ const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(input.trim());
3105
+ if (day) return new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3]));
3106
+ }
3107
+ if (typeof input === "string" || typeof input === "number") {
3108
+ const parsed = new Date(input);
3109
+ if (!Number.isNaN(parsed.getTime())) return startOfDay(parsed);
3110
+ }
3111
+ return startOfDay(fallback);
3112
+ }
3113
+ function toDateTime(input) {
3114
+ if (input instanceof Date) return Number.isNaN(input.getTime()) ? null : input;
3115
+ if (typeof input === "string" && /^\d{4}-\d{2}-\d{2}$/.test(input.trim())) return toDate(input);
3116
+ const parsed = new Date(input);
3117
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
3118
+ }
3119
+ var startOfDay = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate());
3120
+ var addDays = (date, days) => new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
3121
+ function addMonths(date, months) {
3122
+ const target = new Date(date.getFullYear(), date.getMonth() + months, 1);
3123
+ const last = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
3124
+ return new Date(target.getFullYear(), target.getMonth(), Math.min(date.getDate(), last));
3125
+ }
3126
+ var startOfMonth = (date) => new Date(date.getFullYear(), date.getMonth(), 1);
3127
+ function startOfWeek(date, firstDay = 0) {
3128
+ const shift = (date.getDay() - firstDay + 7) % 7;
3129
+ return addDays(startOfDay(date), -shift);
3130
+ }
3131
+ var sameDay = (a, b) => !!a && !!b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
3132
+ var sameMonth = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
3133
+ var pad = (n) => String(n).padStart(2, "0");
3134
+ var dayKey = (date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
3135
+ function monthWeeks(date, firstDay = 0) {
3136
+ const first = startOfMonth(date);
3137
+ const last = new Date(date.getFullYear(), date.getMonth() + 1, 0);
3138
+ const weeks = [];
3139
+ for (let start = startOfWeek(first, firstDay); start <= last; start = addDays(start, 7)) {
3140
+ weeks.push(Array.from({ length: 7 }, (_, i) => addDays(start, i)));
3141
+ }
3142
+ return weeks;
3143
+ }
3144
+ var weekDays = (date, firstDay = 0) => Array.from({ length: 7 }, (_, i) => addDays(startOfWeek(date, firstDay), i));
3145
+ function weekdayNames(locale, firstDay = 0, width = "short") {
3146
+ const format = safeFormat(locale, { weekday: width });
3147
+ return Array.from({ length: 7 }, (_, i) => format.format(new Date(2024, 8, 1 + (firstDay + i) % 7)));
3148
+ }
3149
+ function safeFormat(locale, options) {
3150
+ try {
3151
+ return new Intl.DateTimeFormat(locale, options);
3152
+ } catch {
3153
+ return new Intl.DateTimeFormat(void 0, options);
3154
+ }
3155
+ }
3156
+ var capitalize = (text) => text.charAt(0).toLocaleUpperCase() + text.slice(1);
3157
+ function fromJson(value) {
3158
+ if (typeof value !== "string") return value;
3159
+ try {
3160
+ return JSON.parse(value);
3161
+ } catch {
3162
+ return void 0;
3163
+ }
3164
+ }
3165
+ var clampFirstDay = (n) => Number.isInteger(n) && n >= 0 && n <= 6 ? n : 0;
3166
+
3167
+ // src/components/calendar/engine.ts
3168
+ var SLIDE = 30;
3169
+ var SLIDE_OUT = 0.12;
3170
+ var SLIDE_GAP = 0.1;
3171
+ var SLIDE_DURATION = 0.25;
3172
+ var CELL_STAGGER = 5e-3;
3173
+ var EVENT_STAGGER = 0.02;
3174
+ function createCalendar(root, options = {}) {
3175
+ const grid = root?.querySelector("[data-mk-calendar-grid]");
3176
+ if (!root || !grid) {
3177
+ const now = toDate(null);
3178
+ return inert({
3179
+ next() {
3180
+ },
3181
+ prev() {
3182
+ },
3183
+ goToday() {
3184
+ },
3185
+ setDate() {
3186
+ },
3187
+ setView() {
3188
+ },
3189
+ setEvents() {
3190
+ },
3191
+ select() {
3192
+ },
3193
+ date: now,
3194
+ view: "month",
3195
+ selected: null,
3196
+ range: { start: now, end: now, view: "month" }
3197
+ });
3198
+ }
3199
+ registerMotion();
3200
+ const scope = createScope();
3201
+ const ctx = gsap.context(() => {
3202
+ });
3203
+ scope.add(() => ctx.revert());
3204
+ const locale = options.locale ?? "es";
3205
+ const firstDay = clampFirstDay(options.firstDay);
3206
+ const maxEvents = Number.isFinite(options.maxEvents) ? Math.max(0, Math.floor(options.maxEvents)) : 3;
3207
+ const labels = {
3208
+ more: options.labels?.more ?? ((n) => `+${n} m\xE1s`),
3209
+ events: options.labels?.events ?? ((n) => n === 1 ? "1 evento" : `${n} eventos`),
3210
+ status: { pending: "pendiente", done: "completado", cancelled: "cancelado", ...options.labels?.status }
3211
+ };
3212
+ let date = toDate(options.date);
3213
+ let view = options.view === "week" ? "week" : "month";
3214
+ let selected = options.selected ? toDate(options.selected) : null;
3215
+ let focusDay = selected ?? date;
3216
+ let events = [];
3217
+ let byDay = /* @__PURE__ */ new Map();
3218
+ let first = true;
3219
+ const today = () => toDate(options.today ?? null);
3220
+ const title = root.querySelector("[data-mk-calendar-title]");
3221
+ const prevButton = root.querySelector("[data-mk-calendar-prev]");
3222
+ const nextButton = root.querySelector("[data-mk-calendar-next]");
3223
+ const todayButton = root.querySelector("[data-mk-calendar-today]");
3224
+ const viewButtons = [...root.querySelectorAll("[data-mk-calendar-view]")];
3225
+ const originalGrid = [...grid.childNodes];
3226
+ const originalTitle = title ? [...title.childNodes] : [];
3227
+ scope.add(() => {
3228
+ grid.replaceChildren(...originalGrid);
3229
+ title?.replaceChildren(...originalTitle);
3230
+ });
3231
+ scope.attr(root, "data-mk-ready", "");
3232
+ scope.attr(grid, "role", "grid");
3233
+ if (title) {
3234
+ if (!title.id) scope.attr(title, "id", `mk-calendar-${Math.random().toString(36).slice(2, 8)}`);
3235
+ scope.attr(grid, "aria-labelledby", title.id);
3236
+ }
3237
+ const monthTitle = safeFormat(locale, { month: "long", year: "numeric" });
3238
+ const rangeTitle = safeFormat(locale, { day: "numeric", month: "short", year: "numeric" });
3239
+ const fullDay = safeFormat(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" });
3240
+ const timeFormat = safeFormat(locale, { hour: "numeric", minute: "2-digit" });
3241
+ const index = (list2) => {
3242
+ events = Array.isArray(list2) ? list2.filter((e) => e && toDateTime(e.start)) : [];
3243
+ byDay = /* @__PURE__ */ new Map();
3244
+ const sorted = [...events].sort((a, b) => toDateTime(a.start).getTime() - toDateTime(b.start).getTime());
3245
+ for (const event of sorted) {
3246
+ const start = toDate(toDateTime(event.start));
3247
+ const endTime = event.end ? toDateTime(event.end) : null;
3248
+ const end = endTime && endTime > start ? toDate(endTime) : start;
3249
+ for (let day = start, n = 0; day <= end && n < 62; day = addDays(day, 1), n++) {
3250
+ const key = dayKey(day);
3251
+ byDay.set(key, [...byDay.get(key) ?? [], event]);
3252
+ }
3253
+ }
3254
+ };
3255
+ index(fromJson(options.events) ?? []);
3256
+ const days = () => view === "week" ? [weekDays(date, firstDay)] : monthWeeks(date, firstDay);
3257
+ const range = () => {
3258
+ const weeks = days();
3259
+ return { start: weeks[0][0], end: weeks[weeks.length - 1][6], view };
3260
+ };
3261
+ const inRange = (day) => {
3262
+ const r = range();
3263
+ return day >= r.start && day <= r.end;
3264
+ };
3265
+ const header = () => {
3266
+ const row = document.createElement("div");
3267
+ row.className = "mk-calendar__head";
3268
+ row.setAttribute("role", "row");
3269
+ const long = weekdayNames(locale, firstDay, "short");
3270
+ const narrow = weekdayNames(locale, firstDay, "narrow");
3271
+ long.forEach((name, i) => {
3272
+ const cell = document.createElement("div");
3273
+ cell.className = "mk-calendar__weekday";
3274
+ cell.setAttribute("role", "columnheader");
3275
+ cell.innerHTML = '<span class="mk-calendar__weekday-long"></span><span class="mk-calendar__weekday-narrow" aria-hidden="true"></span>';
3276
+ cell.firstElementChild.textContent = name;
3277
+ cell.lastElementChild.textContent = narrow[i];
3278
+ row.append(cell);
3279
+ });
3280
+ return row;
3281
+ };
3282
+ const chip = (event) => {
3283
+ const button = document.createElement("button");
3284
+ button.type = "button";
3285
+ button.className = "mk-calendar__event";
3286
+ button.dataset.mkEvent = String(event.id);
3287
+ const status = event.status ?? "pending";
3288
+ button.dataset.state = status;
3289
+ if (event.color) button.style.setProperty("--mk-calendar-event-color", event.color);
3290
+ const start = toDateTime(event.start);
3291
+ const time2 = event.allDay ? "" : timeFormat.format(start);
3292
+ button.innerHTML = '<span class="mk-calendar__status" aria-hidden="true"></span><span class="mk-calendar__event-title"></span><span class="mk-calendar__event-time"></span>';
3293
+ button.children[1].textContent = event.title;
3294
+ button.children[2].textContent = time2;
3295
+ button.setAttribute("aria-label", [event.title, time2, labels.status[status]].filter(Boolean).join(", "));
3296
+ return button;
3297
+ };
3298
+ const body = () => {
3299
+ const wrap = document.createElement("div");
3300
+ wrap.className = "mk-calendar__body";
3301
+ wrap.dataset.view = view;
3302
+ wrap.setAttribute("role", "rowgroup");
3303
+ const now = today();
3304
+ const focusKey = dayKey(focusDay);
3305
+ for (const week of days()) {
3306
+ const row = document.createElement("div");
3307
+ row.className = "mk-calendar__week";
3308
+ row.setAttribute("role", "row");
3309
+ for (const day of week) {
3310
+ const key = dayKey(day);
3311
+ const cell = document.createElement("div");
3312
+ cell.className = "mk-calendar__day";
3313
+ cell.setAttribute("role", "gridcell");
3314
+ cell.dataset.mkDate = key;
3315
+ if (view === "month" && !sameMonth(day, date)) cell.dataset.outside = "";
3316
+ if (sameDay(day, now)) cell.dataset.today = "";
3317
+ const isSelected = sameDay(day, selected);
3318
+ cell.setAttribute("aria-selected", String(isSelected));
3319
+ const list2 = byDay.get(key) ?? [];
3320
+ const number = document.createElement("button");
3321
+ number.type = "button";
3322
+ number.className = "mk-calendar__number";
3323
+ number.dataset.mkDay = key;
3324
+ number.textContent = view === "week" ? `${day.getDate()}` : String(day.getDate());
3325
+ number.tabIndex = key === focusKey ? 0 : -1;
3326
+ number.setAttribute("aria-label", `${capitalize(fullDay.format(day))}${list2.length ? `, ${labels.events(list2.length)}` : ""}`);
3327
+ if (sameDay(day, now)) number.setAttribute("aria-current", "date");
3328
+ cell.append(number);
3329
+ if (list2.length) {
3330
+ const box = document.createElement("div");
3331
+ box.className = "mk-calendar__events";
3332
+ const shown2 = view === "week" ? list2 : list2.slice(0, maxEvents);
3333
+ for (const event of shown2) box.append(chip(event));
3334
+ if (shown2.length < list2.length) {
3335
+ const more = document.createElement("button");
3336
+ more.type = "button";
3337
+ more.className = "mk-calendar__more";
3338
+ more.dataset.mkMore = key;
3339
+ more.textContent = labels.more(list2.length - shown2.length);
3340
+ box.append(more);
3341
+ }
3342
+ cell.append(box);
3343
+ }
3344
+ row.append(cell);
3345
+ }
3346
+ wrap.append(row);
3347
+ }
3348
+ return wrap;
3349
+ };
3350
+ let current2 = null;
3351
+ const leaving = /* @__PURE__ */ new Set();
3352
+ scope.add(() => leaving.forEach((el) => el.remove()));
3353
+ const syncChrome = () => {
3354
+ if (title) {
3355
+ const r = range();
3356
+ let text = capitalize(monthTitle.format(date));
3357
+ if (view === "week") {
3358
+ const format = rangeTitle;
3359
+ text = format.formatRange ? format.formatRange(r.start, r.end) : `${rangeTitle.format(r.start)} \u2013 ${rangeTitle.format(r.end)}`;
3360
+ }
3361
+ title.textContent = text;
3362
+ }
3363
+ for (const button of viewButtons) {
3364
+ scope.attr(button, "aria-pressed", String(button.dataset.mkCalendarView === view));
3365
+ }
3366
+ };
3367
+ const render = (direction) => {
3368
+ const next = body();
3369
+ const motion = !reduced();
3370
+ if (!current2) {
3371
+ grid.replaceChildren(header(), next);
3372
+ } else if (motion && direction !== 0) {
3373
+ const old = current2;
3374
+ old.style.top = `${old.offsetTop}px`;
3375
+ old.classList.add("mk-calendar__body--leaving");
3376
+ old.setAttribute("aria-hidden", "true");
3377
+ old.querySelectorAll("button").forEach((b) => b.tabIndex = -1);
3378
+ leaving.add(old);
3379
+ grid.append(next);
3380
+ ctx.add(() => {
3381
+ gsap.to(old, { x: -SLIDE * direction, opacity: 0, duration: SLIDE_OUT, ease: "power1.in", onComplete: () => {
3382
+ old.remove();
3383
+ leaving.delete(old);
3384
+ } });
3385
+ gsap.fromTo(next, { x: SLIDE * direction, opacity: 0 }, { x: 0, opacity: 1, duration: SLIDE_DURATION, delay: SLIDE_GAP, ease: "power2.out", clearProps: "transform,opacity" });
3386
+ });
3387
+ } else {
3388
+ current2.replaceWith(next);
3389
+ }
3390
+ current2 = next;
3391
+ syncChrome();
3392
+ if (motion) {
3393
+ ctx.add(() => {
3394
+ if (first) flowIn(next, { target: ".mk-calendar__day", stagger: CELL_STAGGER });
3395
+ flowIn(next, { target: ".mk-calendar__event, .mk-calendar__more", stagger: EVENT_STAGGER, delay: first ? 0.1 : direction ? SLIDE_GAP + SLIDE_DURATION * 0.5 : 0 });
3396
+ });
3397
+ }
3398
+ first = false;
3399
+ };
3400
+ const focusButton = () => grid.querySelector(`.mk-calendar__body:not(.mk-calendar__body--leaving) [data-mk-day="${dayKey(focusDay)}"]`);
3401
+ const navigate = (to, focus = false) => {
3402
+ if (scope.disposed) return;
3403
+ const from = date;
3404
+ const moved = view === "week" ? !sameDay(startOfWeek(to, firstDay), startOfWeek(from, firstDay)) : !sameMonth(to, from);
3405
+ date = to;
3406
+ focusDay = to;
3407
+ if (moved) {
3408
+ render(to > from ? 1 : -1);
3409
+ const r = range();
3410
+ options.onNavigate?.(r);
3411
+ root.dispatchEvent(new CustomEvent("mk:navigate", { bubbles: true, detail: r }));
3412
+ } else {
3413
+ grid.querySelectorAll("[data-mk-day]").forEach((b) => b.tabIndex = b.dataset.mkDay === dayKey(to) ? 0 : -1);
3414
+ }
3415
+ if (focus) focusButton()?.focus();
3416
+ };
3417
+ const select = (value, notify = true) => {
3418
+ if (scope.disposed) return;
3419
+ selected = value === null ? null : toDate(value);
3420
+ grid.querySelectorAll(".mk-calendar__day").forEach((cell) => cell.setAttribute("aria-selected", String(!!selected && cell.dataset.mkDate === dayKey(selected))));
3421
+ if (selected && notify) {
3422
+ options.onSelect?.(selected);
3423
+ root.dispatchEvent(new CustomEvent("mk:select", { bubbles: true, detail: { date: selected, key: dayKey(selected) } }));
3424
+ }
3425
+ };
3426
+ scope.on(root, "click", (e) => {
3427
+ const target = e.target.closest("button");
3428
+ if (!target || !root.contains(target) || target.disabled) return;
3429
+ if (target === prevButton) navigate(view === "week" ? addDays(date, -7) : addMonths(date, -1));
3430
+ else if (target === nextButton) navigate(view === "week" ? addDays(date, 7) : addMonths(date, 1));
3431
+ else if (target === todayButton) navigate(today(), true);
3432
+ else if (target.dataset.mkCalendarView) setView(target.dataset.mkCalendarView);
3433
+ else if (target.dataset.mkEvent !== void 0) {
3434
+ const event = events.find((ev) => String(ev.id) === target.dataset.mkEvent);
3435
+ if (event) {
3436
+ options.onEventClick?.(event);
3437
+ root.dispatchEvent(new CustomEvent("mk:event", { bubbles: true, detail: { event } }));
3438
+ }
3439
+ } else if (target.dataset.mkDay || target.dataset.mkMore) {
3440
+ const day = toDate(target.dataset.mkDay ?? target.dataset.mkMore);
3441
+ focusDay = day;
3442
+ grid.querySelectorAll("[data-mk-day]").forEach((b) => b.tabIndex = b.dataset.mkDay === dayKey(day) ? 0 : -1);
3443
+ select(day);
3444
+ }
3445
+ });
3446
+ scope.on(grid, "keydown", (e) => {
3447
+ const event = e;
3448
+ const target = event.target;
3449
+ if (!target.dataset?.mkDay) return;
3450
+ const day = toDate(target.dataset.mkDay);
3451
+ const map = {
3452
+ ArrowLeft: () => addDays(day, -1),
3453
+ ArrowRight: () => addDays(day, 1),
3454
+ ArrowUp: () => addDays(day, -7),
3455
+ ArrowDown: () => addDays(day, 7),
3456
+ Home: () => startOfWeek(day, firstDay),
3457
+ End: () => addDays(startOfWeek(day, firstDay), 6),
3458
+ PageUp: () => addMonths(day, event.shiftKey ? -12 : -1),
3459
+ PageDown: () => addMonths(day, event.shiftKey ? 12 : 1)
3460
+ };
3461
+ const to = map[event.key]?.();
3462
+ if (!to) return;
3463
+ event.preventDefault();
3464
+ if (!event.key.startsWith("Page") && inRange(to)) {
3465
+ focusDay = to;
3466
+ grid.querySelectorAll("[data-mk-day]").forEach((b) => b.tabIndex = b.dataset.mkDay === dayKey(to) ? 0 : -1);
3467
+ focusButton()?.focus();
3468
+ } else navigate(to, true);
3469
+ });
3470
+ const setView = (next) => {
3471
+ if (scope.disposed || next !== "month" && next !== "week" || next === view) return;
3472
+ view = next;
3473
+ date = focusDay;
3474
+ render(0);
3475
+ const r = range();
3476
+ options.onNavigate?.(r);
3477
+ root.dispatchEvent(new CustomEvent("mk:navigate", { bubbles: true, detail: r }));
3478
+ };
3479
+ render(0);
3480
+ return {
3481
+ next: () => navigate(view === "week" ? addDays(date, 7) : addMonths(date, 1)),
3482
+ prev: () => navigate(view === "week" ? addDays(date, -7) : addMonths(date, -1)),
3483
+ goToday: () => navigate(today()),
3484
+ setDate: (value) => navigate(toDate(value, date)),
3485
+ setView,
3486
+ setEvents(list2) {
3487
+ if (scope.disposed) return;
3488
+ const hadFocus = grid.contains(document.activeElement);
3489
+ index(fromJson(list2) ?? []);
3490
+ const fresh = body();
3491
+ current2?.replaceWith(fresh);
3492
+ current2 = fresh;
3493
+ if (hadFocus) focusButton()?.focus();
3494
+ },
3495
+ select,
3496
+ get date() {
3497
+ return date;
3498
+ },
3499
+ get view() {
3500
+ return view;
3501
+ },
3502
+ get selected() {
3503
+ return selected;
3504
+ },
3505
+ get range() {
3506
+ return range();
3507
+ },
3508
+ destroy: () => scope.dispose()
3509
+ };
3510
+ }
3511
+
3512
+ // src/components/slot-picker/engine.ts
3513
+ var SLIDE2 = 30;
3514
+ var SLIDE_DURATION2 = 0.25;
3515
+ var slotEaseReady = false;
3516
+ function createSlotPicker(root, options = {}) {
3517
+ const daysHost = root?.querySelector("[data-mk-slot-days]");
3518
+ const timesHost = root?.querySelector("[data-mk-slot-times]");
3519
+ if (!root || !daysHost || !timesHost) {
3520
+ return inert({ selectDate() {
3521
+ }, selectTime() {
3522
+ }, setSlots() {
3523
+ }, date: null, time: null, value: null });
3524
+ }
3525
+ registerMotion();
3526
+ if (!slotEaseReady && typeof window !== "undefined") {
3527
+ CustomEase.create("mk-slot-in", ".38,.49,0,1.1");
3528
+ slotEaseReady = true;
3529
+ }
3530
+ const scope = createScope();
3531
+ const ctx = gsap.context(() => {
3532
+ });
3533
+ scope.add(() => ctx.revert());
3534
+ const locale = options.locale ?? "es";
3535
+ const firstDay = clampFirstDay(options.firstDay);
3536
+ const labels = {
3537
+ placeholder: options.labels?.placeholder ?? "Elige un d\xEDa para ver los horarios.",
3538
+ empty: options.labels?.empty ?? "No hay horarios este d\xEDa.",
3539
+ loading: options.labels?.loading ?? "Cargando horarios\u2026",
3540
+ heading: options.labels?.heading ?? ((d) => capitalize(safeFormat(locale, { weekday: "long", day: "numeric", month: "long" }).format(d)))
3541
+ };
3542
+ const min = options.min !== void 0 ? toDate(options.min) : null;
3543
+ const max = options.max !== void 0 ? toDate(options.max) : null;
3544
+ const closedList = fromJson(options.closedDays);
3545
+ const closed = new Set((Array.isArray(closedList) ? closedList : []).filter((n) => Number.isInteger(n)));
3546
+ let slots = fromJson(options.slots) ?? [];
3547
+ const initial = (() => {
3548
+ const v = options.value;
3549
+ if (typeof v === "string") {
3550
+ const match = /^(\d{4}-\d{2}-\d{2})(?:T(\d{1,2}:\d{2}))?/.exec(v);
3551
+ return match ? { date: toDate(match[1]), time: match[2] ?? null } : null;
3552
+ }
3553
+ return v ? { date: toDate(v.date), time: v.time ?? null } : null;
3554
+ })();
3555
+ let month = initial?.date ?? toDate(options.date);
3556
+ let date = initial?.date ?? (options.date !== void 0 ? toDate(options.date) : null);
3557
+ let time2 = initial?.time ?? null;
3558
+ let focusDay = date ?? month;
3559
+ let times = [];
3560
+ let request = 0;
3561
+ const title = root.querySelector("[data-mk-slot-title]");
3562
+ const heading = root.querySelector("[data-mk-slot-heading]");
3563
+ const prev = root.querySelector("[data-mk-slot-prev]");
3564
+ const next = root.querySelector("[data-mk-slot-next]");
3565
+ const output = root.querySelector("[data-mk-slot-value]");
3566
+ const originals = [daysHost, timesHost, title, heading].filter(Boolean).map((el) => [el, [...el.childNodes]]);
3567
+ scope.add(() => originals.forEach(([el, nodes]) => el.replaceChildren(...nodes)));
3568
+ if (output) {
3569
+ const before = output.value;
3570
+ scope.add(() => {
3571
+ output.value = before;
3572
+ });
3573
+ }
3574
+ scope.attr(root, "data-mk-ready", "");
3575
+ scope.attr(daysHost, "role", "grid");
3576
+ scope.attr(timesHost, "role", "radiogroup");
3577
+ if (heading) {
3578
+ if (!heading.id) scope.attr(heading, "id", `mk-slot-${Math.random().toString(36).slice(2, 8)}`);
3579
+ scope.attr(timesHost, "aria-labelledby", heading.id);
3580
+ }
3581
+ if (title) {
3582
+ if (!title.id) scope.attr(title, "id", `mk-slot-month-${Math.random().toString(36).slice(2, 8)}`);
3583
+ scope.attr(daysHost, "aria-labelledby", title.id);
3584
+ }
3585
+ const monthTitle = safeFormat(locale, { month: "long", year: "numeric" });
3586
+ const fullDay = safeFormat(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" });
3587
+ const disabled = (d) => min && d < min || max && d > max || closed.has(d.getDay()) || !!options.isDisabled?.(d);
3588
+ let daysBody = null;
3589
+ const leaving = /* @__PURE__ */ new Set();
3590
+ scope.add(() => leaving.forEach((el) => el.remove()));
3591
+ const buildDays = () => {
3592
+ const wrap = document.createElement("div");
3593
+ wrap.className = "mk-slot-picker__days";
3594
+ wrap.setAttribute("role", "rowgroup");
3595
+ const head = document.createElement("div");
3596
+ head.className = "mk-slot-picker__weekdays";
3597
+ head.setAttribute("role", "row");
3598
+ for (const name of weekdayNames(locale, firstDay, "short")) {
3599
+ const cell = document.createElement("span");
3600
+ cell.setAttribute("role", "columnheader");
3601
+ cell.textContent = name.slice(0, 2);
3602
+ head.append(cell);
3603
+ }
3604
+ wrap.append(head);
3605
+ const focusKey = dayKey(focusDay);
3606
+ for (const week of monthWeeks(month, firstDay)) {
3607
+ const row = document.createElement("div");
3608
+ row.className = "mk-slot-picker__week";
3609
+ row.setAttribute("role", "row");
3610
+ for (const d of week) {
3611
+ const cell = document.createElement("span");
3612
+ cell.setAttribute("role", "gridcell");
3613
+ if (!sameMonth(d, month)) {
3614
+ row.append(cell);
3615
+ continue;
3616
+ }
3617
+ const button = document.createElement("button");
3618
+ button.type = "button";
3619
+ button.className = "mk-slot-picker__day";
3620
+ button.dataset.mkSlotDay = dayKey(d);
3621
+ button.textContent = String(d.getDate());
3622
+ button.tabIndex = dayKey(d) === focusKey ? 0 : -1;
3623
+ button.setAttribute("aria-label", capitalize(fullDay.format(d)));
3624
+ if (disabled(d)) button.disabled = true;
3625
+ if (sameDay(d, /* @__PURE__ */ new Date())) button.setAttribute("aria-current", "date");
3626
+ cell.setAttribute("aria-selected", String(sameDay(d, date)));
3627
+ cell.append(button);
3628
+ row.append(cell);
3629
+ }
3630
+ wrap.append(row);
3631
+ }
3632
+ return wrap;
3633
+ };
3634
+ const renderDays = (direction) => {
3635
+ const fresh = buildDays();
3636
+ if (title) title.textContent = capitalize(monthTitle.format(month));
3637
+ if (prev) scope.attr(prev, "disabled", min && new Date(month.getFullYear(), month.getMonth(), 0) < min ? "" : null);
3638
+ if (next) scope.attr(next, "disabled", max && new Date(month.getFullYear(), month.getMonth() + 1, 1) > max ? "" : null);
3639
+ if (daysBody && direction && !reduced()) {
3640
+ const old = daysBody;
3641
+ old.style.top = `${old.offsetTop}px`;
3642
+ old.classList.add("mk-slot-picker__leaving");
3643
+ old.setAttribute("aria-hidden", "true");
3644
+ leaving.add(old);
3645
+ daysHost.append(fresh);
3646
+ ctx.add(() => {
3647
+ gsap.to(old, { x: -SLIDE2 * direction, opacity: 0, duration: SLIDE_DURATION2, ease: "power1.out", onComplete: () => {
3648
+ old.remove();
3649
+ leaving.delete(old);
3650
+ } });
3651
+ gsap.fromTo(fresh, { x: SLIDE2 * direction, opacity: 0 }, { x: 0, opacity: 1, duration: SLIDE_DURATION2, ease: "power1.out", clearProps: "transform,opacity" });
3652
+ });
3653
+ } else if (daysBody) daysBody.replaceWith(fresh);
3654
+ else daysHost.replaceChildren(fresh);
3655
+ daysBody = fresh;
3656
+ };
3657
+ const dayButton = (d) => daysHost.querySelector(`.mk-slot-picker__days:not(.mk-slot-picker__leaving) [data-mk-slot-day="${dayKey(d)}"]`);
3658
+ const moveFocus = (to, focus) => {
3659
+ focusDay = to;
3660
+ if (!sameMonth(to, month)) {
3661
+ const direction = to > month ? 1 : -1;
3662
+ month = to;
3663
+ renderDays(direction);
3664
+ } else daysHost.querySelectorAll("[data-mk-slot-day]").forEach((b) => b.tabIndex = b.dataset.mkSlotDay === dayKey(to) ? 0 : -1);
3665
+ if (focus) dayButton(to)?.focus();
3666
+ };
3667
+ let timesBody = null;
3668
+ let firstList = true;
3669
+ const buildTimes = (state) => {
3670
+ const wrap = document.createElement("div");
3671
+ wrap.className = "mk-slot-picker__list";
3672
+ if (state !== "ready" || !times.length) {
3673
+ const note = document.createElement("p");
3674
+ note.className = "mk-slot-picker__note";
3675
+ note.textContent = state === "placeholder" ? labels.placeholder : state === "loading" ? labels.loading : labels.empty;
3676
+ wrap.append(note);
3677
+ return wrap;
3678
+ }
3679
+ const focusTime = time2 && times.includes(time2) ? time2 : times[0];
3680
+ for (const t of times) {
3681
+ const button = document.createElement("button");
3682
+ button.type = "button";
3683
+ button.className = "mk-slot-picker__time";
3684
+ button.setAttribute("role", "radio");
3685
+ button.dataset.mkSlotTime = t;
3686
+ button.textContent = t;
3687
+ button.setAttribute("aria-checked", String(t === time2));
3688
+ button.tabIndex = t === focusTime ? 0 : -1;
3689
+ wrap.append(button);
3690
+ }
3691
+ return wrap;
3692
+ };
3693
+ const renderTimes = (state, direction) => {
3694
+ const fresh = buildTimes(state);
3695
+ scope.attr(timesHost, "aria-busy", state === "loading" ? "true" : null);
3696
+ const motion = !reduced();
3697
+ if (timesBody && direction && motion) {
3698
+ const old = timesBody;
3699
+ old.style.top = `${old.offsetTop}px`;
3700
+ old.classList.add("mk-slot-picker__leaving");
3701
+ old.setAttribute("aria-hidden", "true");
3702
+ old.querySelectorAll("button").forEach((b) => b.tabIndex = -1);
3703
+ leaving.add(old);
3704
+ timesHost.append(fresh);
3705
+ ctx.add(() => {
3706
+ gsap.to(old, { x: -SLIDE2 * direction, opacity: 0, duration: SLIDE_DURATION2, ease: "power1.out", onComplete: () => {
3707
+ old.remove();
3708
+ leaving.delete(old);
3709
+ } });
3710
+ gsap.fromTo(fresh, { x: SLIDE2 * direction, opacity: 0 }, { x: 0, opacity: 1, duration: SLIDE_DURATION2, ease: "power1.out", clearProps: "transform,opacity" });
3711
+ });
3712
+ } else if (timesBody) timesBody.replaceWith(fresh);
3713
+ else timesHost.replaceChildren(fresh);
3714
+ timesBody = fresh;
3715
+ if (state === "ready" && times.length && motion && firstList) {
3716
+ firstList = false;
3717
+ ctx.add(() => flowIn(fresh, { target: ".mk-slot-picker__time", style: 2, startScale: 0.7, blur: 32, duration: 0.3, stagger: 0.02, ease: "mk-slot-in" }));
3718
+ }
3719
+ };
3720
+ const resolve = (d) => {
3721
+ const source = slots;
3722
+ if (Array.isArray(source)) return source;
3723
+ if (typeof source === "function") return source(d);
3724
+ return source?.[dayKey(d)] ?? [];
3725
+ };
3726
+ const clean = (list2) => Array.isArray(list2) ? [...new Set(list2.filter((t) => typeof t === "string" && /^\d{1,2}:\d{2}$/.test(t)))] : [];
3727
+ const load = (direction) => {
3728
+ if (!date) {
3729
+ times = [];
3730
+ renderTimes("placeholder", 0);
3731
+ return;
3732
+ }
3733
+ const mine = ++request;
3734
+ let result;
3735
+ try {
3736
+ result = resolve(date);
3737
+ } catch {
3738
+ result = [];
3739
+ }
3740
+ if (result && typeof result.then === "function") {
3741
+ renderTimes("loading", direction);
3742
+ result.then(
3743
+ (list2) => {
3744
+ if (mine === request && !scope.disposed) {
3745
+ times = clean(list2);
3746
+ renderTimes("ready", 0);
3747
+ }
3748
+ },
3749
+ () => {
3750
+ if (mine === request && !scope.disposed) {
3751
+ times = [];
3752
+ renderTimes("ready", 0);
3753
+ }
3754
+ }
3755
+ );
3756
+ } else {
3757
+ times = clean(result);
3758
+ if (time2 && !times.includes(time2)) time2 = null;
3759
+ renderTimes("ready", direction);
3760
+ }
3761
+ };
3762
+ const syncHeading = () => {
3763
+ if (!heading) return;
3764
+ if (date) {
3765
+ heading.textContent = labels.heading(date);
3766
+ if (!heading.hasAttribute("data-mk-shown")) scope.attr(heading, "data-mk-shown", "");
3767
+ }
3768
+ };
3769
+ const emit = () => {
3770
+ const key = date ? dayKey(date) : "";
3771
+ const value = date && time2 ? `${key}T${time2}` : null;
3772
+ if (output) {
3773
+ output.value = value ?? "";
3774
+ output.dispatchEvent(new Event("input", { bubbles: true }));
3775
+ output.dispatchEvent(new Event("change", { bubbles: true }));
3776
+ }
3777
+ if (date && time2 && value) {
3778
+ const detail = { date, time: time2, key, value };
3779
+ options.onChange?.(detail);
3780
+ root.dispatchEvent(new CustomEvent("mk:change", { bubbles: true, detail }));
3781
+ }
3782
+ };
3783
+ const selectDate = (input, notify = true) => {
3784
+ if (scope.disposed) return;
3785
+ const d = toDate(input);
3786
+ if (disabled(d)) return;
3787
+ const direction = date ? Math.sign(d.getTime() - date.getTime()) : 0;
3788
+ if (sameDay(d, date)) return;
3789
+ date = d;
3790
+ time2 = null;
3791
+ focusDay = d;
3792
+ if (!sameMonth(d, month)) {
3793
+ const dir = d > month ? 1 : -1;
3794
+ month = d;
3795
+ renderDays(dir);
3796
+ }
3797
+ daysHost.querySelectorAll('[role="gridcell"]').forEach((cell) => {
3798
+ const b = cell.querySelector("[data-mk-slot-day]");
3799
+ if (b) {
3800
+ cell.setAttribute("aria-selected", String(b.dataset.mkSlotDay === dayKey(d)));
3801
+ b.tabIndex = b.dataset.mkSlotDay === dayKey(d) ? 0 : -1;
3802
+ }
3803
+ });
3804
+ syncHeading();
3805
+ load(direction);
3806
+ if (notify) {
3807
+ options.onDateChange?.(d);
3808
+ emit();
3809
+ }
3810
+ };
3811
+ const selectTime = (t, notify = true) => {
3812
+ if (scope.disposed || !date) return;
3813
+ if (t !== null && !times.includes(t)) return;
3814
+ time2 = t;
3815
+ timesHost.querySelectorAll("[data-mk-slot-time]").forEach((b) => {
3816
+ b.setAttribute("aria-checked", String(b.dataset.mkSlotTime === t));
3817
+ b.tabIndex = (t ? b.dataset.mkSlotTime === t : b === timesHost.querySelector("[data-mk-slot-time]")) ? 0 : -1;
3818
+ });
3819
+ if (notify) emit();
3820
+ };
3821
+ scope.on(root, "click", (e) => {
3822
+ const target = e.target.closest("button");
3823
+ if (!target || target.disabled || !root.contains(target)) return;
3824
+ if (target === prev) moveFocus(addMonths(month, -1), false);
3825
+ else if (target === next) moveFocus(addMonths(month, 1), false);
3826
+ else if (target.dataset.mkSlotDay) selectDate(target.dataset.mkSlotDay);
3827
+ else if (target.dataset.mkSlotTime) selectTime(target.dataset.mkSlotTime);
3828
+ });
3829
+ scope.on(daysHost, "keydown", (e) => {
3830
+ const event = e;
3831
+ const key = event.target.dataset?.mkSlotDay;
3832
+ if (!key) return;
3833
+ const d = toDate(key);
3834
+ const to = {
3835
+ ArrowLeft: () => addDays(d, -1),
3836
+ ArrowRight: () => addDays(d, 1),
3837
+ ArrowUp: () => addDays(d, -7),
3838
+ ArrowDown: () => addDays(d, 7),
3839
+ Home: () => startOfWeek(d, firstDay),
3840
+ End: () => addDays(startOfWeek(d, firstDay), 6),
3841
+ PageUp: () => addMonths(d, -1),
3842
+ PageDown: () => addMonths(d, 1)
3843
+ }[event.key]?.();
3844
+ if (!to) return;
3845
+ event.preventDefault();
3846
+ moveFocus(to, true);
3847
+ });
3848
+ scope.on(timesHost, "keydown", (e) => {
3849
+ const event = e;
3850
+ const current2 = event.target.dataset?.mkSlotTime;
3851
+ if (!current2) return;
3852
+ const i = times.indexOf(current2);
3853
+ const to = { ArrowRight: i + 1, ArrowDown: i + 1, ArrowLeft: i - 1, ArrowUp: i - 1, Home: 0, End: times.length - 1 }[event.key];
3854
+ if (to === void 0) return;
3855
+ event.preventDefault();
3856
+ const t = times[(to + times.length) % times.length];
3857
+ selectTime(t);
3858
+ timesHost.querySelector(`[data-mk-slot-time="${t}"]`)?.focus();
3859
+ });
3860
+ renderDays(0);
3861
+ if (date) {
3862
+ syncHeading();
3863
+ load(0);
3864
+ if (time2 && !times.includes(time2)) time2 = null;
3865
+ } else renderTimes("placeholder", 0);
3866
+ return {
3867
+ selectDate: (d) => selectDate(d),
3868
+ selectTime: (t) => selectTime(t),
3869
+ setSlots(source) {
3870
+ if (scope.disposed) return;
3871
+ slots = fromJson(source) ?? [];
3872
+ load(0);
3873
+ },
3874
+ get date() {
3875
+ return date;
3876
+ },
3877
+ get time() {
3878
+ return time2;
3879
+ },
3880
+ get value() {
3881
+ return date && time2 ? `${dayKey(date)}T${time2}` : null;
3882
+ },
3883
+ destroy: () => scope.dispose()
3884
+ };
3885
+ }
3886
+
2294
3887
  export {
2295
3888
  PATHS,
2296
3889
  springPath,
@@ -2369,6 +3962,16 @@ export {
2369
3962
  pushUnderlay,
2370
3963
  releaseUnderlay,
2371
3964
  createDialog,
2372
- createMenu
3965
+ createMenu,
3966
+ createQuantity,
3967
+ createPagination,
3968
+ createHoverCard,
3969
+ createInputOtp,
3970
+ createStudioDock,
3971
+ createTrailer,
3972
+ toDate,
3973
+ dayKey,
3974
+ createCalendar,
3975
+ createSlotPicker
2373
3976
  };
2374
- //# sourceMappingURL=chunk-JQMJQSXD.js.map
3977
+ //# sourceMappingURL=chunk-TFNRIZHU.js.map