sibujs 4.12.0 → 4.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/browser.cjs +71 -18
  2. package/dist/browser.js +4 -4
  3. package/dist/build.cjs +6618 -6178
  4. package/dist/build.d.cts +554 -415
  5. package/dist/build.d.ts +554 -415
  6. package/dist/build.js +2091 -1735
  7. package/dist/cdn.dev.global.js +12 -12
  8. package/dist/cdn.full.dev.global.js +13 -13
  9. package/dist/cdn.full.global.js +12 -12
  10. package/dist/cdn.global.js +12 -12
  11. package/dist/{chunk-WFRQ6D7H.js → chunk-22OPSWMF.js} +1 -1
  12. package/dist/{chunk-ZJCHY2IB.js → chunk-2NC56RGY.js} +1 -1
  13. package/dist/{chunk-NB7TFSJE.js → chunk-2O6JJCUR.js} +4 -4
  14. package/dist/{chunk-B2Y5BJSP.js → chunk-4UTGDVSA.js} +70 -16
  15. package/dist/{chunk-NAVLCP4S.js → chunk-5RIBLY2N.js} +4 -4
  16. package/dist/{chunk-T5O2CUHV.js → chunk-5XUGJM3J.js} +1 -1
  17. package/dist/{chunk-HXT2RNH5.js → chunk-ADZHPN2B.js} +1 -1
  18. package/dist/chunk-B36ADEIR.js +21 -0
  19. package/dist/{chunk-DFZL3AX7.js → chunk-C73VBPEF.js} +9 -5
  20. package/dist/{chunk-WH4U2VUE.js → chunk-CPQKWCJ2.js} +13 -5
  21. package/dist/{chunk-LEUEKK4G.js → chunk-EU7T7EB3.js} +2 -2
  22. package/dist/{chunk-QLG5IK7F.js → chunk-G3Z2FEBR.js} +4 -4
  23. package/dist/{chunk-ZPY2UAKM.js → chunk-J4X7ICH4.js} +3 -3
  24. package/dist/{chunk-XX4BRMWF.js → chunk-K4LNUT5L.js} +1 -1
  25. package/dist/{chunk-CI4W6XAU.js → chunk-KC62VURU.js} +3 -3
  26. package/dist/chunk-N5TBYXHW.js +17 -0
  27. package/dist/{chunk-653KGQS2.js → chunk-OIKD7YDY.js} +2 -2
  28. package/dist/{chunk-LYTUTXQP.js → chunk-PBJYCE2Z.js} +46 -4
  29. package/dist/{chunk-EMB5PG7V.js → chunk-QC3NP47A.js} +1 -1
  30. package/dist/{chunk-IHA62NGJ.js → chunk-QKFVMIAV.js} +1 -1
  31. package/dist/{chunk-LXBMVGVB.js → chunk-QZ47OZ25.js} +3 -3
  32. package/dist/{chunk-ZTYCCBU3.js → chunk-VFPLWUT7.js} +44 -26
  33. package/dist/{chunk-RK7EPQPG.js → chunk-YFTBCCVH.js} +1 -1
  34. package/dist/data.cjs +71 -18
  35. package/dist/data.js +5 -5
  36. package/dist/datePicker-DxuxWpgF.d.cts +285 -0
  37. package/dist/datePicker-DxuxWpgF.d.ts +285 -0
  38. package/dist/devtools.cjs +71 -18
  39. package/dist/devtools.js +4 -4
  40. package/dist/ecosystem.cjs +82 -21
  41. package/dist/ecosystem.js +7 -7
  42. package/dist/extras.cjs +131 -23
  43. package/dist/extras.d.cts +2 -2
  44. package/dist/extras.d.ts +2 -2
  45. package/dist/extras.js +21 -20
  46. package/dist/index.cjs +115 -37
  47. package/dist/index.d.cts +59 -5
  48. package/dist/index.d.ts +59 -5
  49. package/dist/index.js +10 -10
  50. package/dist/motion.cjs +69 -16
  51. package/dist/motion.js +3 -3
  52. package/dist/patterns.cjs +71 -18
  53. package/dist/patterns.js +5 -5
  54. package/dist/performance.cjs +69 -16
  55. package/dist/performance.js +4 -4
  56. package/dist/plugins.cjs +1421 -996
  57. package/dist/plugins.d.cts +132 -3
  58. package/dist/plugins.d.ts +132 -3
  59. package/dist/plugins.js +262 -11
  60. package/dist/routeMiddleware-B1sp4P7a.d.ts +688 -0
  61. package/dist/routeMiddleware-CedQTsLK.d.cts +688 -0
  62. package/dist/ssr.cjs +3607 -3524
  63. package/dist/ssr.d.cts +20 -671
  64. package/dist/ssr.d.ts +20 -671
  65. package/dist/ssr.js +15 -7
  66. package/dist/testing.cjs +71 -18
  67. package/dist/testing.js +2 -2
  68. package/dist/ui.cjs +78 -18
  69. package/dist/ui.js +8 -7
  70. package/dist/widgets.cjs +1352 -952
  71. package/dist/widgets.d.cts +100 -268
  72. package/dist/widgets.d.ts +100 -268
  73. package/dist/widgets.js +316 -7
  74. package/package.json +9 -1
package/dist/widgets.cjs CHANGED
@@ -25,10 +25,13 @@ __export(widgets_exports, {
25
25
  contentEditable: () => contentEditable,
26
26
  datePicker: () => datePicker,
27
27
  fileUpload: () => fileUpload,
28
+ isWidgetKeyEvent: () => isWidgetKeyEvent,
28
29
  popover: () => popover,
29
30
  select: () => select,
31
+ selectMenu: () => selectMenu,
30
32
  tabs: () => tabs,
31
- tooltip: () => tooltip
33
+ tooltip: () => tooltip,
34
+ widgetKeyOwner: () => widgetKeyOwner
32
35
  });
33
36
  module.exports = __toCommonJS(widgets_exports);
34
37
 
@@ -380,6 +383,7 @@ function bindOwnerScope(fn) {
380
383
  if (scope === null) return fn;
381
384
  return (...args) => runWithOwnerScope(scope, () => fn(...args));
382
385
  }
386
+ var currentStructOwner = null;
383
387
  var subscriberEpochCounter = 0;
384
388
  function retrack(effectFn, subscriber) {
385
389
  const prev = currentSubscriber;
@@ -399,6 +403,9 @@ function retrack(effectFn, subscriber) {
399
403
  if (prevScope !== null && !sub._epoch && sub._scope === void 0) sub._scope = prevScope;
400
404
  const scope = sub._scope === void 0 ? null : sub._scope;
401
405
  if (scope !== prevScope) currentOwnerScope = scope;
406
+ const prevStructOwner = currentStructOwner;
407
+ if (prevStructOwner !== null && !sub._epoch && !sub._c && sub._owner == null) sub._owner = prevStructOwner;
408
+ if (sub._tok != null) currentStructOwner = sub._tok;
402
409
  const epoch = ++subscriberEpochCounter;
403
410
  sub._epoch = epoch;
404
411
  sub._structDirty = false;
@@ -412,6 +419,7 @@ function retrack(effectFn, subscriber) {
412
419
  } finally {
413
420
  currentSubscriber = prev;
414
421
  currentOwnerScope = prevScope;
422
+ currentStructOwner = prevStructOwner;
415
423
  if (savedDepth !== 0) {
416
424
  suspendDepth = savedDepth;
417
425
  suspendSavedSub = savedSuspendSub;
@@ -468,7 +476,25 @@ function track(effectFn, subscriber) {
468
476
  }
469
477
  return sub._dispose ?? (sub._dispose = () => cleanup(subscriber));
470
478
  }
471
- function reactiveBinding(commit, ownerNode) {
479
+ function reactiveBinding(commit, ownerNode, structural) {
480
+ return createBinding(commit, ownerNode, structural === true)._dispose;
481
+ }
482
+ function structuralBinding(commit, ownerNode) {
483
+ const sub = createBinding(commit, ownerNode, true);
484
+ return {
485
+ stop: sub._dispose,
486
+ run: (fn) => {
487
+ const prev = currentStructOwner;
488
+ currentStructOwner = sub._tok ?? null;
489
+ try {
490
+ return fn();
491
+ } finally {
492
+ currentStructOwner = prev;
493
+ }
494
+ }
495
+ };
496
+ }
497
+ function createBinding(commit, ownerNode, structural) {
472
498
  const run = () => {
473
499
  const s = subscriber;
474
500
  if (s._disposed || s._reentrant) return;
@@ -491,19 +517,25 @@ function reactiveBinding(commit, ownerNode) {
491
517
  subscriber._disposed = false;
492
518
  subscriber._errorPhase = "binding";
493
519
  subscriber._errorNode = ownerNode;
520
+ subscriber._tok = null;
521
+ subscriber._owner = null;
522
+ if (structural) subscriber._tok = { sub: subscriber };
494
523
  try {
495
524
  run();
496
525
  } catch (err) {
497
526
  subscriber._disposed = true;
498
527
  subscriber._errorNode = void 0;
528
+ if (subscriber._tok != null) subscriber._tok.sub = null;
499
529
  cleanup(subscriber);
500
530
  throw err;
501
531
  }
502
- return subscriber._dispose ?? (subscriber._dispose = () => {
532
+ subscriber._dispose = () => {
503
533
  subscriber._disposed = true;
504
534
  subscriber._errorNode = void 0;
535
+ if (subscriber._tok != null) subscriber._tok.sub = null;
505
536
  cleanup(subscriber);
506
- });
537
+ };
538
+ return subscriber;
507
539
  }
508
540
  function recordDependency(signal2) {
509
541
  if (!currentSubscriber) return;
@@ -527,6 +559,7 @@ function recordDependency(signal2) {
527
559
  }
528
560
  function cleanup(subscriber) {
529
561
  const sub = subscriber;
562
+ if (sub._owner != null) sub._owner = null;
530
563
  let node = sub.depsHead ?? null;
531
564
  sub.depsHead = null;
532
565
  sub.depsTail = null;
@@ -586,20 +619,41 @@ function drainQueue() {
586
619
  break;
587
620
  }
588
621
  const sub = pendingQueue[i++];
589
- pendingSet.delete(sub);
590
- if (quarantined.size > 0 && quarantined.has(sub)) continue;
591
- if (sub._hasComputedDep === true && !depsChanged(sub)) continue;
592
- if (tickRepeat(sub)) {
593
- cycleError(sub);
594
- quarantined.add(sub);
595
- continue;
596
- }
597
- const pushed = beginDisposerCapture(sub._cap ?? null);
598
- try {
599
- safeInvoke(sub);
600
- } finally {
601
- if (pushed) endDisposerCapture();
602
- }
622
+ if (!pendingSet.delete(sub)) continue;
623
+ if (sub._owner != null && pendingSet.size > 0) {
624
+ runPendingOwners(sub);
625
+ if (pendingSet.has(sub)) continue;
626
+ }
627
+ runQueued(sub);
628
+ }
629
+ }
630
+ function runPendingOwners(sub) {
631
+ for (; ; ) {
632
+ let outer = null;
633
+ for (let t = sub._owner ?? null; t !== null; ) {
634
+ const o = t.sub;
635
+ if (o === null) break;
636
+ if (pendingSet.has(o)) outer = o;
637
+ t = o._owner ?? null;
638
+ }
639
+ if (outer === null) return;
640
+ pendingSet.delete(outer);
641
+ runQueued(outer);
642
+ }
643
+ }
644
+ function runQueued(sub) {
645
+ if (quarantined.size > 0 && quarantined.has(sub)) return;
646
+ if (sub._hasComputedDep === true && !depsChanged(sub)) return;
647
+ if (tickRepeat(sub)) {
648
+ cycleError(sub);
649
+ quarantined.add(sub);
650
+ return;
651
+ }
652
+ const pushed = beginDisposerCapture(sub._cap ?? null);
653
+ try {
654
+ safeInvoke(sub);
655
+ } finally {
656
+ if (pushed) endDisposerCapture();
603
657
  }
604
658
  }
605
659
  function drainNotificationQueue() {
@@ -735,7 +789,7 @@ function forEachSubscriber(signal2, visit) {
735
789
  }
736
790
 
737
791
  // src/reactivity/track.ts
738
- var _runtimeVersion = true ? "4.12.0" : "dev";
792
+ var _runtimeVersion = true ? "4.13.1" : "dev";
739
793
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
740
794
  function resolveReactiveApi() {
741
795
  const g = globalThis;
@@ -770,6 +824,7 @@ function resolveReactiveApi() {
770
824
  getOwnerScope,
771
825
  runWithOwnerScope,
772
826
  bindOwnerScope,
827
+ structuralBinding,
773
828
  version: _runtimeVersion
774
829
  };
775
830
  g[REGISTRY_KEY] = local;
@@ -796,6 +851,7 @@ var forEachSubscriber2 = API.forEachSubscriber;
796
851
  var getOwnerScope2 = API.getOwnerScope ?? getOwnerScope;
797
852
  var runWithOwnerScope2 = API.runWithOwnerScope ?? runWithOwnerScope;
798
853
  var bindOwnerScope2 = API.bindOwnerScope ?? bindOwnerScope;
854
+ var structuralBinding2 = API.structuralBinding ?? ((commit, ownerNode) => ({ stop: API.reactiveBinding(commit, ownerNode), run: (fn) => fn() }));
799
855
 
800
856
  // src/core/devtoolsHook.ts
801
857
  function emitDevtools(hook, event, payload) {
@@ -1036,6 +1092,165 @@ function signal(initial, options) {
1036
1092
  return [get, set];
1037
1093
  }
1038
1094
 
1095
+ // src/reactivity/domBinding.ts
1096
+ function domBinding(commit, ownerNode) {
1097
+ if (isSSR()) return () => {
1098
+ };
1099
+ return reactiveBinding2(commit, ownerNode);
1100
+ }
1101
+
1102
+ // src/widgets/attributeSnapshot.ts
1103
+ function snapshotAttributes(el, names) {
1104
+ const previous = names.map((name) => [name, el.getAttribute(name)]);
1105
+ return () => {
1106
+ for (const [name, value] of previous) {
1107
+ if (value === null) el.removeAttribute(name);
1108
+ else el.setAttribute(name, value);
1109
+ }
1110
+ };
1111
+ }
1112
+
1113
+ // src/widgets/keyOwnership.ts
1114
+ var CLAIM = /* @__PURE__ */ Symbol.for("sibujs.widgetKey");
1115
+ function claimWidgetKey(event, widget) {
1116
+ event[CLAIM] = widget;
1117
+ }
1118
+ function isWidgetKeyEvent(event) {
1119
+ return event[CLAIM] !== void 0;
1120
+ }
1121
+ function widgetKeyOwner(event) {
1122
+ return event[CLAIM] ?? null;
1123
+ }
1124
+
1125
+ // src/widgets/Accordion.ts
1126
+ var boundAccordions = /* @__PURE__ */ new WeakMap();
1127
+ function accordion(options) {
1128
+ const { items: itemDefs, multiple = false, defaultExpanded = [] } = options;
1129
+ const knownIds = new Set(itemDefs.map((item) => item.id));
1130
+ const validDefaults = defaultExpanded.filter((id) => knownIds.has(id));
1131
+ const initialExpanded = multiple ? validDefaults : validDefaults.slice(0, 1);
1132
+ const [expandedIds, setExpandedIds] = signal(new Set(initialExpanded));
1133
+ const items = derived(
1134
+ () => itemDefs.map((item) => ({
1135
+ ...item,
1136
+ isExpanded: expandedIds().has(item.id)
1137
+ }))
1138
+ );
1139
+ function expand(id) {
1140
+ if (!itemDefs.some((item) => item.id === id)) return;
1141
+ if (multiple) {
1142
+ setExpandedIds((prev) => {
1143
+ const next = new Set(prev);
1144
+ next.add(id);
1145
+ return next;
1146
+ });
1147
+ } else {
1148
+ setExpandedIds(/* @__PURE__ */ new Set([id]));
1149
+ }
1150
+ }
1151
+ function collapse(id) {
1152
+ setExpandedIds((prev) => {
1153
+ const next = new Set(prev);
1154
+ next.delete(id);
1155
+ return next;
1156
+ });
1157
+ }
1158
+ function toggle(id) {
1159
+ if (expandedIds().has(id)) {
1160
+ collapse(id);
1161
+ } else {
1162
+ expand(id);
1163
+ }
1164
+ }
1165
+ function expandAll() {
1166
+ if (multiple) {
1167
+ setExpandedIds(new Set(itemDefs.map((item) => item.id)));
1168
+ }
1169
+ }
1170
+ function collapseAll() {
1171
+ setExpandedIds(/* @__PURE__ */ new Set());
1172
+ }
1173
+ function isExpanded(id) {
1174
+ return expandedIds().has(id);
1175
+ }
1176
+ function bind(els) {
1177
+ const idempotencyKey = els.root ?? (itemDefs.length > 0 ? els.triggers[itemDefs[0].id] : void 0);
1178
+ if (idempotencyKey) {
1179
+ const existing = boundAccordions.get(idempotencyKey);
1180
+ if (existing) return existing;
1181
+ }
1182
+ const restore = [];
1183
+ const idPrefix = createId("sibu-accordion");
1184
+ for (const item of itemDefs) {
1185
+ const trig = els.triggers[item.id];
1186
+ const panel = els.panels[item.id];
1187
+ if (!trig) continue;
1188
+ restore.push(snapshotAttributes(trig, ["id", "aria-controls", "aria-expanded"]));
1189
+ if (!trig.id) trig.id = `${idPrefix}-trigger-${idSegment(item.id)}`;
1190
+ if (panel) {
1191
+ restore.push(snapshotAttributes(panel, ["role", "id", "aria-labelledby", "hidden"]));
1192
+ panel.setAttribute("role", "region");
1193
+ if (!panel.id) panel.id = `${idPrefix}-panel-${idSegment(item.id)}`;
1194
+ panel.setAttribute("aria-labelledby", trig.id);
1195
+ trig.setAttribute("aria-controls", panel.id);
1196
+ }
1197
+ }
1198
+ const fxTeardown = domBinding(() => {
1199
+ const ids = expandedIds();
1200
+ for (const item of itemDefs) {
1201
+ const trig = els.triggers[item.id];
1202
+ const panel = els.panels[item.id];
1203
+ if (!trig) continue;
1204
+ const expanded = ids.has(item.id);
1205
+ trig.setAttribute("aria-expanded", expanded ? "true" : "false");
1206
+ if (panel) panel.hidden = !expanded;
1207
+ }
1208
+ }, idempotencyKey);
1209
+ const handlers = [];
1210
+ for (const item of itemDefs) {
1211
+ const trig = els.triggers[item.id];
1212
+ if (!trig) continue;
1213
+ const click = () => toggle(item.id);
1214
+ const key = (e) => {
1215
+ if (isWidgetKeyEvent(e)) return;
1216
+ if (e.key === "Enter" || e.key === " ") {
1217
+ e.preventDefault();
1218
+ claimWidgetKey(e, "accordion");
1219
+ toggle(item.id);
1220
+ }
1221
+ };
1222
+ trig.addEventListener("click", click);
1223
+ trig.addEventListener("keydown", key);
1224
+ handlers.push({ el: trig, click, key });
1225
+ }
1226
+ let tornDown = false;
1227
+ const teardown = () => {
1228
+ if (tornDown) return;
1229
+ tornDown = true;
1230
+ if (idempotencyKey) boundAccordions.delete(idempotencyKey);
1231
+ fxTeardown();
1232
+ for (const { el, click, key } of handlers) {
1233
+ el.removeEventListener("click", click);
1234
+ el.removeEventListener("keydown", key);
1235
+ }
1236
+ for (const r of restore) r();
1237
+ };
1238
+ if (idempotencyKey) boundAccordions.set(idempotencyKey, teardown);
1239
+ return teardown;
1240
+ }
1241
+ return {
1242
+ items,
1243
+ toggle,
1244
+ expand,
1245
+ collapse,
1246
+ expandAll,
1247
+ collapseAll,
1248
+ /** Reactive check — use inside class/nodes bindings for per-item reactivity */
1249
+ isExpanded,
1250
+ bind
1251
+ };
1252
+ }
1253
+
1039
1254
  // src/core/signals/watch.ts
1040
1255
  function watch(getter, callback) {
1041
1256
  devAssert(typeof getter === "function", "watch: first argument must be a getter function.");
@@ -1066,13 +1281,6 @@ function watch(getter, callback) {
1066
1281
  return teardown;
1067
1282
  }
1068
1283
 
1069
- // src/reactivity/domBinding.ts
1070
- function domBinding(commit, ownerNode) {
1071
- if (isSSR()) return () => {
1072
- };
1073
- return reactiveBinding2(commit, ownerNode);
1074
- }
1075
-
1076
1284
  // src/widgets/Combobox.ts
1077
1285
  var boundComboboxes = /* @__PURE__ */ new WeakMap();
1078
1286
  function combobox(options) {
@@ -1170,27 +1378,34 @@ function combobox(options) {
1170
1378
  open();
1171
1379
  };
1172
1380
  const onKey = (e) => {
1381
+ if (isWidgetKeyEvent(e)) return;
1173
1382
  if (e.key === "ArrowDown") {
1174
1383
  e.preventDefault();
1384
+ claimWidgetKey(e, "combobox");
1175
1385
  if (!isOpen()) open();
1176
1386
  highlightNext();
1177
1387
  } else if (e.key === "ArrowUp") {
1178
1388
  e.preventDefault();
1389
+ claimWidgetKey(e, "combobox");
1179
1390
  if (!isOpen()) open();
1180
1391
  highlightPrev();
1181
1392
  } else if (e.key === "Enter") {
1182
1393
  if (highlightedIndex() >= 0) {
1183
1394
  e.preventDefault();
1395
+ claimWidgetKey(e, "combobox");
1184
1396
  selectHighlighted();
1185
1397
  }
1186
1398
  } else if (e.key === "Escape") {
1187
1399
  e.preventDefault();
1400
+ claimWidgetKey(e, "combobox");
1188
1401
  close();
1189
1402
  } else if (e.key === "Home") {
1190
1403
  e.preventDefault();
1404
+ claimWidgetKey(e, "combobox");
1191
1405
  if (filteredItems().length > 0) setHighlightedIndex(0);
1192
1406
  } else if (e.key === "End") {
1193
1407
  e.preventDefault();
1408
+ claimWidgetKey(e, "combobox");
1194
1409
  const len = filteredItems().length;
1195
1410
  if (len > 0) setHighlightedIndex(len - 1);
1196
1411
  }
@@ -1261,671 +1476,375 @@ function combobox(options) {
1261
1476
  };
1262
1477
  }
1263
1478
 
1264
- // src/widgets/attributeSnapshot.ts
1265
- function snapshotAttributes(el, names) {
1266
- const previous = names.map((name) => [name, el.getAttribute(name)]);
1267
- return () => {
1268
- for (const [name, value] of previous) {
1269
- if (value === null) el.removeAttribute(name);
1270
- else el.setAttribute(name, value);
1479
+ // src/utils/sanitize.ts
1480
+ function stripHtml(html) {
1481
+ const input = String(html);
1482
+ if (typeof DOMParser !== "undefined") {
1483
+ try {
1484
+ return new DOMParser().parseFromString(input, "text/html").body.textContent ?? "";
1485
+ } catch {
1271
1486
  }
1272
- };
1487
+ }
1488
+ let prev;
1489
+ let out = input;
1490
+ do {
1491
+ prev = out;
1492
+ out = out.replace(/<[^>]*>/g, "");
1493
+ } while (out !== prev);
1494
+ return out.replace(/<[^>]*$/, "");
1273
1495
  }
1274
1496
 
1275
- // src/widgets/Tabs.ts
1276
- var boundTablists = /* @__PURE__ */ new WeakMap();
1277
- function tabs(options) {
1278
- const { tabs: tabDefs, defaultTab } = options;
1279
- const firstEnabled = tabDefs.find((t) => !t.disabled)?.id ?? "";
1280
- const initialTab = defaultTab !== void 0 && tabDefs.some((t) => t.id === defaultTab && !t.disabled) ? defaultTab : firstEnabled;
1281
- const [activeTab, setActiveTabState] = signal(initialTab);
1282
- function setActiveTab(id) {
1283
- const tab = tabDefs.find((t) => t.id === id);
1284
- if (tab && !tab.disabled) {
1285
- setActiveTabState(id);
1497
+ // src/widgets/contentEditable.ts
1498
+ function toInertText(html) {
1499
+ return stripHtml(String(html)).replace(/<(?=[a-z!/?])/gi, "&lt;");
1500
+ }
1501
+ function contentEditable(element) {
1502
+ const [content, setContentInternal] = signal("");
1503
+ const [isFocused, setFocused] = signal(false);
1504
+ function setContent(input) {
1505
+ if (typeof input === "string") {
1506
+ setContentInternal(toInertText(input));
1507
+ return;
1286
1508
  }
1287
- }
1288
- const tabs2 = derived(
1289
- () => tabDefs.map((t) => ({
1290
- ...t,
1291
- isActive: t.id === activeTab()
1292
- }))
1293
- );
1294
- function findCurrentIndex() {
1295
- return tabDefs.findIndex((t) => t.id === activeTab());
1296
- }
1297
- function nextTab() {
1298
- const currentIdx = findCurrentIndex();
1299
- const len = tabDefs.length;
1300
- if (len === 0) return;
1301
- for (let i = 1; i <= len; i++) {
1302
- const candidate = tabDefs[(currentIdx + i) % len];
1303
- if (!candidate.disabled) {
1304
- setActiveTabState(candidate.id);
1305
- return;
1306
- }
1509
+ if (typeof input.text === "string") {
1510
+ setContentInternal(input.text);
1511
+ return;
1307
1512
  }
1308
- }
1309
- function prevTab() {
1310
- const currentIdx = findCurrentIndex();
1311
- const len = tabDefs.length;
1312
- if (len === 0) return;
1313
- for (let i = 1; i <= len; i++) {
1314
- const candidate = tabDefs[(currentIdx - i + len) % len];
1315
- if (!candidate.disabled) {
1316
- setActiveTabState(candidate.id);
1317
- return;
1318
- }
1513
+ if (typeof input.html === "string") {
1514
+ const shouldSanitize = input.sanitize !== false;
1515
+ setContentInternal(shouldSanitize ? toInertText(input.html) : input.html);
1516
+ return;
1319
1517
  }
1518
+ setContentInternal("");
1320
1519
  }
1321
- function isActive(id) {
1322
- return activeTab() === id;
1323
- }
1324
- function bind(els) {
1325
- const existing = boundTablists.get(els.tablist);
1326
- if (existing) return existing;
1327
- const restore = [snapshotAttributes(els.tablist, ["role"])];
1328
- els.tablist.setAttribute("role", "tablist");
1329
- const idPrefix = createId("sibu-tabs");
1330
- for (const def of tabDefs) {
1331
- const tabEl = els.tabs[def.id];
1332
- if (!tabEl) continue;
1333
- restore.push(
1334
- snapshotAttributes(tabEl, ["role", "id", "aria-disabled", "aria-controls", "aria-selected", "tabindex"])
1335
- );
1336
- tabEl.setAttribute("role", "tab");
1337
- if (!tabEl.id) tabEl.id = `${idPrefix}-tab-${idSegment(def.id)}`;
1338
- if (def.disabled) tabEl.setAttribute("aria-disabled", "true");
1339
- else tabEl.removeAttribute("aria-disabled");
1340
- const panelEl = els.panels?.[def.id];
1341
- if (panelEl) {
1342
- restore.push(snapshotAttributes(panelEl, ["role", "id", "aria-labelledby", "hidden"]));
1343
- panelEl.setAttribute("role", "tabpanel");
1344
- if (!panelEl.id) panelEl.id = `${idPrefix}-panel-${idSegment(def.id)}`;
1345
- panelEl.setAttribute("aria-labelledby", tabEl.id);
1346
- tabEl.setAttribute("aria-controls", panelEl.id);
1347
- }
1348
- }
1349
- const fxTeardown = domBinding(() => {
1350
- const active = activeTab();
1351
- for (const def of tabDefs) {
1352
- const tabEl = els.tabs[def.id];
1353
- if (!tabEl) continue;
1354
- const isAct = def.id === active;
1355
- tabEl.setAttribute("aria-selected", isAct ? "true" : "false");
1356
- tabEl.tabIndex = isAct ? 0 : -1;
1357
- const panelEl = els.panels?.[def.id];
1358
- if (panelEl) panelEl.hidden = !isAct;
1359
- }
1360
- }, els.tablist);
1361
- const onKey = (e) => {
1362
- if (e.key === "ArrowRight" || e.key === "ArrowDown") {
1363
- e.preventDefault();
1364
- nextTab();
1365
- els.tabs[activeTab()]?.focus();
1366
- } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
1367
- e.preventDefault();
1368
- prevTab();
1369
- els.tabs[activeTab()]?.focus();
1370
- } else if (e.key === "Home") {
1371
- e.preventDefault();
1372
- const first = tabDefs.find((t) => !t.disabled);
1373
- if (first) {
1374
- setActiveTabState(first.id);
1375
- els.tabs[first.id]?.focus();
1520
+ function wrapSelection(tagName) {
1521
+ if (typeof window === "undefined") return;
1522
+ const selection = window.getSelection();
1523
+ if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return;
1524
+ const range = selection.getRangeAt(0);
1525
+ if (element && !element.contains(range.commonAncestorContainer)) return;
1526
+ const ancestor = range.commonAncestorContainer;
1527
+ const existingWrap = findWrapperWithinEditor(
1528
+ ancestor instanceof HTMLElement ? ancestor : ancestor.parentElement,
1529
+ tagName
1530
+ );
1531
+ let targetNode = null;
1532
+ if (existingWrap) {
1533
+ const parent = existingWrap.parentNode;
1534
+ if (parent) {
1535
+ const firstChild = existingWrap.firstChild;
1536
+ const lastChild = existingWrap.lastChild;
1537
+ while (existingWrap.firstChild) {
1538
+ parent.insertBefore(existingWrap.firstChild, existingWrap);
1376
1539
  }
1377
- } else if (e.key === "End") {
1378
- e.preventDefault();
1379
- for (let i = tabDefs.length - 1; i >= 0; i--) {
1380
- if (!tabDefs[i].disabled) {
1381
- setActiveTabState(tabDefs[i].id);
1382
- els.tabs[tabDefs[i].id]?.focus();
1383
- break;
1384
- }
1540
+ parent.removeChild(existingWrap);
1541
+ if (firstChild && lastChild) {
1542
+ const newRange = document.createRange();
1543
+ newRange.setStartBefore(firstChild);
1544
+ newRange.setEndAfter(lastChild);
1545
+ selection.removeAllRanges();
1546
+ selection.addRange(newRange);
1547
+ return;
1385
1548
  }
1386
1549
  }
1387
- };
1388
- els.tablist.addEventListener("keydown", onKey);
1389
- const clickHandlers = [];
1390
- for (const def of tabDefs) {
1391
- const tabEl = els.tabs[def.id];
1392
- if (!tabEl) continue;
1393
- const fn = () => setActiveTab(def.id);
1394
- tabEl.addEventListener("click", fn);
1395
- clickHandlers.push({ el: tabEl, fn });
1550
+ } else {
1551
+ const wrapper = document.createElement(tagName);
1552
+ try {
1553
+ range.surroundContents(wrapper);
1554
+ } catch {
1555
+ const fragment = range.extractContents();
1556
+ wrapper.appendChild(fragment);
1557
+ range.insertNode(wrapper);
1558
+ }
1559
+ targetNode = wrapper;
1560
+ }
1561
+ if (targetNode) {
1562
+ selection.removeAllRanges();
1563
+ const newRange = document.createRange();
1564
+ newRange.selectNodeContents(targetNode);
1565
+ selection.addRange(newRange);
1396
1566
  }
1397
- let tornDown = false;
1398
- const teardown = () => {
1399
- if (tornDown) return;
1400
- tornDown = true;
1401
- boundTablists.delete(els.tablist);
1402
- fxTeardown();
1403
- els.tablist.removeEventListener("keydown", onKey);
1404
- for (const { el, fn } of clickHandlers) el.removeEventListener("click", fn);
1405
- for (const r of restore) r();
1406
- };
1407
- boundTablists.set(els.tablist, teardown);
1408
- return teardown;
1567
+ }
1568
+ function findWrapperWithinEditor(el, tagName) {
1569
+ const upper = tagName.toUpperCase();
1570
+ while (el) {
1571
+ if (el === element || isEditingHost(el)) return null;
1572
+ if (el.tagName === upper) return el;
1573
+ el = el.parentElement;
1574
+ }
1575
+ return null;
1576
+ }
1577
+ function isEditingHost(el) {
1578
+ const attr = el.getAttribute("contenteditable");
1579
+ return attr !== null && attr.toLowerCase() !== "false";
1580
+ }
1581
+ function bold() {
1582
+ wrapSelection("strong");
1583
+ }
1584
+ function italic() {
1585
+ wrapSelection("em");
1586
+ }
1587
+ function underline() {
1588
+ wrapSelection("u");
1409
1589
  }
1410
1590
  return {
1411
- activeTab,
1412
- setActiveTab,
1413
- tabs: tabs2,
1414
- nextTab,
1415
- prevTab,
1416
- /** Reactive check — use inside class/nodes bindings for per-tab reactivity */
1417
- isActive,
1418
- bind
1591
+ content,
1592
+ setContent,
1593
+ isFocused,
1594
+ setFocused,
1595
+ bold,
1596
+ italic,
1597
+ underline
1419
1598
  };
1420
1599
  }
1421
1600
 
1422
- // src/widgets/Accordion.ts
1423
- var boundAccordions = /* @__PURE__ */ new WeakMap();
1424
- function accordion(options) {
1425
- const { items: itemDefs, multiple = false, defaultExpanded = [] } = options;
1426
- const knownIds = new Set(itemDefs.map((item) => item.id));
1427
- const validDefaults = defaultExpanded.filter((id) => knownIds.has(id));
1428
- const initialExpanded = multiple ? validDefaults : validDefaults.slice(0, 1);
1429
- const [expandedIds, setExpandedIds] = signal(new Set(initialExpanded));
1430
- const items = derived(
1431
- () => itemDefs.map((item) => ({
1432
- ...item,
1433
- isExpanded: expandedIds().has(item.id)
1434
- }))
1435
- );
1436
- function expand(id) {
1437
- if (!itemDefs.some((item) => item.id === id)) return;
1438
- if (multiple) {
1439
- setExpandedIds((prev) => {
1440
- const next = new Set(prev);
1441
- next.add(id);
1442
- return next;
1443
- });
1444
- } else {
1445
- setExpandedIds(/* @__PURE__ */ new Set([id]));
1446
- }
1601
+ // src/widgets/datePicker.ts
1602
+ var boundDatePickers = /* @__PURE__ */ new WeakMap();
1603
+ function datePicker(options) {
1604
+ const minDate = options?.minDate;
1605
+ const maxDate = options?.maxDate;
1606
+ const initialDate = options?.initialDate ?? null;
1607
+ const [selectedDate, setSelectedDate] = signal(initialDate);
1608
+ const [viewDate, setViewDate] = signal(initialDate ?? /* @__PURE__ */ new Date());
1609
+ function isDateDisabled(date) {
1610
+ const d = stripTime(date);
1611
+ if (minDate && d < stripTime(minDate)) return true;
1612
+ if (maxDate && d > stripTime(maxDate)) return true;
1613
+ return false;
1447
1614
  }
1448
- function collapse(id) {
1449
- setExpandedIds((prev) => {
1450
- const next = new Set(prev);
1451
- next.delete(id);
1452
- return next;
1453
- });
1615
+ function stripTime(d) {
1616
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate());
1454
1617
  }
1455
- function toggle(id) {
1456
- if (expandedIds().has(id)) {
1457
- collapse(id);
1458
- } else {
1459
- expand(id);
1460
- }
1618
+ function sameDay(a, b) {
1619
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1461
1620
  }
1462
- function expandAll() {
1463
- if (multiple) {
1464
- setExpandedIds(new Set(itemDefs.map((item) => item.id)));
1621
+ function select2(date) {
1622
+ if (!isDateDisabled(date)) {
1623
+ setSelectedDate(date);
1465
1624
  }
1466
1625
  }
1467
- function collapseAll() {
1468
- setExpandedIds(/* @__PURE__ */ new Set());
1626
+ function shiftMonth(prev, delta) {
1627
+ return new Date(prev.getFullYear(), prev.getMonth() + delta, 1);
1469
1628
  }
1470
- function isExpanded(id) {
1471
- return expandedIds().has(id);
1629
+ function nextMonth() {
1630
+ setViewDate((prev) => shiftMonth(prev, 1));
1472
1631
  }
1473
- function bind(els) {
1474
- const idempotencyKey = els.root ?? (itemDefs.length > 0 ? els.triggers[itemDefs[0].id] : void 0);
1475
- if (idempotencyKey) {
1476
- const existing = boundAccordions.get(idempotencyKey);
1477
- if (existing) return existing;
1478
- }
1479
- const restore = [];
1480
- const idPrefix = createId("sibu-accordion");
1481
- for (const item of itemDefs) {
1482
- const trig = els.triggers[item.id];
1483
- const panel = els.panels[item.id];
1484
- if (!trig) continue;
1485
- restore.push(snapshotAttributes(trig, ["id", "aria-controls", "aria-expanded"]));
1486
- if (!trig.id) trig.id = `${idPrefix}-trigger-${idSegment(item.id)}`;
1487
- if (panel) {
1488
- restore.push(snapshotAttributes(panel, ["role", "id", "aria-labelledby", "hidden"]));
1489
- panel.setAttribute("role", "region");
1490
- if (!panel.id) panel.id = `${idPrefix}-panel-${idSegment(item.id)}`;
1491
- panel.setAttribute("aria-labelledby", trig.id);
1492
- trig.setAttribute("aria-controls", panel.id);
1493
- }
1494
- }
1495
- const fxTeardown = domBinding(() => {
1496
- const ids = expandedIds();
1497
- for (const item of itemDefs) {
1498
- const trig = els.triggers[item.id];
1499
- const panel = els.panels[item.id];
1500
- if (!trig) continue;
1501
- const expanded = ids.has(item.id);
1502
- trig.setAttribute("aria-expanded", expanded ? "true" : "false");
1503
- if (panel) panel.hidden = !expanded;
1504
- }
1505
- }, idempotencyKey);
1506
- const handlers = [];
1507
- for (const item of itemDefs) {
1508
- const trig = els.triggers[item.id];
1509
- if (!trig) continue;
1510
- const click = () => toggle(item.id);
1511
- const key = (e) => {
1512
- if (e.key === "Enter" || e.key === " ") {
1513
- e.preventDefault();
1514
- toggle(item.id);
1515
- }
1516
- };
1517
- trig.addEventListener("click", click);
1518
- trig.addEventListener("keydown", key);
1519
- handlers.push({ el: trig, click, key });
1520
- }
1521
- let tornDown = false;
1522
- const teardown = () => {
1523
- if (tornDown) return;
1524
- tornDown = true;
1525
- if (idempotencyKey) boundAccordions.delete(idempotencyKey);
1526
- fxTeardown();
1527
- for (const { el, click, key } of handlers) {
1528
- el.removeEventListener("click", click);
1529
- el.removeEventListener("keydown", key);
1530
- }
1531
- for (const r of restore) r();
1532
- };
1533
- if (idempotencyKey) boundAccordions.set(idempotencyKey, teardown);
1534
- return teardown;
1535
- }
1536
- return {
1537
- items,
1538
- toggle,
1539
- expand,
1540
- collapse,
1541
- expandAll,
1542
- collapseAll,
1543
- /** Reactive check — use inside class/nodes bindings for per-item reactivity */
1544
- isExpanded,
1545
- bind
1546
- };
1547
- }
1548
-
1549
- // src/widgets/Popover.ts
1550
- var boundPopovers = /* @__PURE__ */ new WeakMap();
1551
- function popover() {
1552
- const [isOpen, setIsOpen] = signal(false);
1553
- function open() {
1554
- setIsOpen(true);
1555
- }
1556
- function close() {
1557
- setIsOpen(false);
1632
+ function prevMonth() {
1633
+ setViewDate((prev) => shiftMonth(prev, -1));
1558
1634
  }
1559
- function toggle() {
1560
- setIsOpen((prev) => !prev);
1635
+ function nextYear() {
1636
+ setViewDate((prev) => new Date(prev.getFullYear() + 1, prev.getMonth(), 1));
1561
1637
  }
1562
- function bind(els) {
1563
- const existing = boundPopovers.get(els.trigger);
1564
- if (existing) return existing;
1565
- const id = createId("sibu-popover");
1566
- const prevPopoverRole = els.popover.getAttribute("role");
1567
- const prevPopoverId = els.popover.id;
1568
- const prevLabelledBy = els.popover.getAttribute("aria-labelledby");
1569
- const prevTriggerHaspopup = els.trigger.getAttribute("aria-haspopup");
1570
- const prevTriggerControls = els.trigger.getAttribute("aria-controls");
1571
- els.popover.setAttribute("role", "dialog");
1572
- els.popover.id = id;
1573
- els.trigger.setAttribute("aria-haspopup", "dialog");
1574
- els.trigger.setAttribute("aria-controls", id);
1575
- let assignedLabelId = null;
1576
- if (els.labelledBy) {
1577
- if (!els.labelledBy.id) {
1578
- els.labelledBy.id = `${id}-label`;
1579
- assignedLabelId = els.labelledBy.id;
1580
- }
1581
- els.popover.setAttribute("aria-labelledby", els.labelledBy.id);
1582
- }
1583
- const fxTeardown = domBinding(() => {
1584
- const open2 = isOpen();
1585
- els.trigger.setAttribute("aria-expanded", open2 ? "true" : "false");
1586
- els.popover.hidden = !open2;
1587
- }, els.trigger);
1588
- const onTriggerClick = (e) => {
1589
- e.preventDefault();
1590
- toggle();
1591
- };
1592
- const onKey = (e) => {
1593
- if (e.key === "Escape" && isOpen()) {
1594
- e.stopPropagation();
1595
- close();
1596
- els.trigger.focus();
1597
- }
1598
- };
1599
- const onDocPointer = (e) => {
1600
- if (!isOpen()) return;
1601
- const t = e.target;
1602
- if (!t) return;
1603
- if (els.trigger.contains(t) || els.popover.contains(t)) return;
1604
- close();
1605
- };
1606
- els.trigger.addEventListener("click", onTriggerClick);
1607
- els.popover.addEventListener("keydown", onKey);
1608
- els.trigger.addEventListener("keydown", onKey);
1609
- document.addEventListener("pointerdown", onDocPointer);
1610
- let tornDown = false;
1611
- const teardown = () => {
1612
- if (tornDown) return;
1613
- tornDown = true;
1614
- if (boundPopovers.get(els.trigger) === teardown) boundPopovers.delete(els.trigger);
1615
- fxTeardown();
1616
- els.trigger.removeEventListener("click", onTriggerClick);
1617
- els.popover.removeEventListener("keydown", onKey);
1618
- els.trigger.removeEventListener("keydown", onKey);
1619
- document.removeEventListener("pointerdown", onDocPointer);
1620
- if (prevPopoverRole === null) els.popover.removeAttribute("role");
1621
- else els.popover.setAttribute("role", prevPopoverRole);
1622
- if (prevPopoverId === "") els.popover.removeAttribute("id");
1623
- else els.popover.id = prevPopoverId;
1624
- if (prevLabelledBy === null) els.popover.removeAttribute("aria-labelledby");
1625
- else els.popover.setAttribute("aria-labelledby", prevLabelledBy);
1626
- if (assignedLabelId && els.labelledBy?.id === assignedLabelId) {
1627
- els.labelledBy.removeAttribute("id");
1628
- }
1629
- if (prevTriggerHaspopup === null) els.trigger.removeAttribute("aria-haspopup");
1630
- else els.trigger.setAttribute("aria-haspopup", prevTriggerHaspopup);
1631
- if (prevTriggerControls === null) els.trigger.removeAttribute("aria-controls");
1632
- else els.trigger.setAttribute("aria-controls", prevTriggerControls);
1633
- els.trigger.removeAttribute("aria-expanded");
1634
- };
1635
- boundPopovers.set(els.trigger, teardown);
1636
- return teardown;
1638
+ function prevYear() {
1639
+ setViewDate((prev) => new Date(prev.getFullYear() - 1, prev.getMonth(), 1));
1637
1640
  }
1638
- return { isOpen, open, close, toggle, bind };
1639
- }
1640
-
1641
- // src/widgets/Select.ts
1642
- var boundSelects = /* @__PURE__ */ new WeakMap();
1643
- function select(options) {
1644
- const { items, multiple = false, itemToString, isDisabled } = options;
1645
- const isItemDisabled = isDisabled ?? (() => false);
1646
- const [selectedItems, setSelectedItems] = signal([]);
1647
- const [isOpen, setIsOpen] = signal(false);
1648
- const [highlightedIndex, setHighlightedIndex] = signal(-1);
1649
- const selectedItem = derived(() => {
1650
- const sel = selectedItems();
1651
- return sel.length > 0 ? sel[sel.length - 1] : null;
1652
- });
1653
- function select2(item) {
1654
- if (isItemDisabled(item)) return;
1655
- if (multiple) {
1656
- setSelectedItems((prev) => {
1657
- if (prev.includes(item)) return prev;
1658
- return [...prev, item];
1659
- });
1660
- } else {
1661
- batch(() => {
1662
- setSelectedItems([item]);
1663
- setIsOpen(false);
1641
+ const daysInMonth = derived(() => {
1642
+ const vd = viewDate();
1643
+ const year = vd.getFullYear();
1644
+ const month = vd.getMonth();
1645
+ const today = /* @__PURE__ */ new Date();
1646
+ const selected = selectedDate();
1647
+ const firstDay = new Date(year, month, 1);
1648
+ const startDow = firstDay.getDay();
1649
+ const lastDay = new Date(year, month + 1, 0);
1650
+ const totalDaysInMonth = lastDay.getDate();
1651
+ const days = [];
1652
+ for (let i = startDow - 1; i >= 0; i--) {
1653
+ const date = new Date(year, month, -i);
1654
+ days.push({
1655
+ date,
1656
+ isCurrentMonth: false,
1657
+ isToday: sameDay(date, today),
1658
+ isSelected: selected !== null && sameDay(date, selected),
1659
+ isDisabled: isDateDisabled(date)
1664
1660
  });
1665
1661
  }
1666
- }
1667
- function deselect(item) {
1668
- setSelectedItems((prev) => prev.filter((i) => i !== item));
1669
- }
1670
- function toggle(item) {
1671
- if (selectedItems().includes(item)) {
1672
- deselect(item);
1673
- } else {
1674
- select2(item);
1675
- }
1676
- }
1677
- function isSelected(item) {
1678
- return selectedItems().includes(item);
1679
- }
1680
- function open() {
1681
- setIsOpen(true);
1682
- }
1683
- function close() {
1684
- setIsOpen(false);
1685
- }
1686
- function nextEnabled(from, dir) {
1687
- const len = items.length;
1688
- if (len === 0) return -1;
1689
- let i = from;
1690
- for (let n = 0; n < len; n++) {
1691
- i = (i + dir + len) % len;
1692
- if (!isItemDisabled(items[i])) return i;
1693
- }
1694
- return -1;
1695
- }
1696
- function highlightNext() {
1697
- if (items.length === 0) return;
1698
- setHighlightedIndex((prev) => {
1699
- const n = nextEnabled(prev < 0 ? -1 : prev, 1);
1700
- return n === -1 ? prev : n;
1701
- });
1702
- }
1703
- function highlightPrev() {
1704
- if (items.length === 0) return;
1705
- setHighlightedIndex((prev) => {
1706
- const n = nextEnabled(prev < 0 ? items.length : prev, -1);
1707
- return n === -1 ? prev : n;
1708
- });
1709
- }
1710
- function selectHighlighted() {
1711
- const idx = highlightedIndex();
1712
- if (idx >= 0 && idx < items.length) {
1713
- select2(items[idx]);
1662
+ for (let d = 1; d <= totalDaysInMonth; d++) {
1663
+ const date = new Date(year, month, d);
1664
+ days.push({
1665
+ date,
1666
+ isCurrentMonth: true,
1667
+ isToday: sameDay(date, today),
1668
+ isSelected: selected !== null && sameDay(date, selected),
1669
+ isDisabled: isDateDisabled(date)
1670
+ });
1714
1671
  }
1715
- }
1716
- function clear() {
1717
- setSelectedItems([]);
1718
- }
1719
- function bind(els) {
1720
- const existing = boundSelects.get(els.listbox);
1721
- if (existing) return existing;
1722
- const prevId = els.listbox.id;
1723
- const prevRole = els.listbox.getAttribute("role");
1724
- const prevMultiselectable = els.listbox.getAttribute("aria-multiselectable");
1725
- const prevTabIndex = els.listbox.getAttribute("tabindex");
1726
- const listboxId = createId("sibu-select");
1727
- els.listbox.id = listboxId;
1728
- els.listbox.setAttribute("role", "listbox");
1729
- els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
1730
- if (els.listbox.tabIndex < 0) els.listbox.tabIndex = 0;
1731
- const toStr = els.itemToString ?? itemToString ?? ((it) => String(it));
1732
- const fxTeardown = domBinding(() => {
1733
- const idx = highlightedIndex();
1734
- const sel = selectedItems();
1735
- let activeId = "";
1736
- for (let i = 0; i < items.length; i++) {
1737
- const optEl = els.option(items[i], i);
1738
- if (!optEl) continue;
1739
- if (!optEl.id) optEl.id = `${listboxId}-opt-${i}`;
1740
- optEl.setAttribute("role", "option");
1741
- optEl.setAttribute("aria-selected", sel.includes(items[i]) ? "true" : "false");
1742
- const disabled = isItemDisabled(items[i]);
1743
- if (disabled) optEl.setAttribute("aria-disabled", "true");
1744
- else optEl.removeAttribute("aria-disabled");
1745
- if (i === idx && !disabled) activeId = optEl.id;
1746
- }
1747
- if (activeId) els.listbox.setAttribute("aria-activedescendant", activeId);
1748
- else els.listbox.removeAttribute("aria-activedescendant");
1749
- }, els.listbox);
1750
- let typeBuffer = "";
1751
- let typeTimer = null;
1752
- const onKey = (e) => {
1753
- if (e.key === "ArrowDown") {
1754
- e.preventDefault();
1755
- highlightNext();
1756
- } else if (e.key === "ArrowUp") {
1757
- e.preventDefault();
1758
- highlightPrev();
1759
- } else if (e.key === "Home") {
1760
- e.preventDefault();
1761
- const first = nextEnabled(-1, 1);
1762
- if (first !== -1) setHighlightedIndex(first);
1763
- } else if (e.key === "End") {
1764
- e.preventDefault();
1765
- const last = nextEnabled(items.length, -1);
1766
- if (last !== -1) setHighlightedIndex(last);
1767
- } else if (e.key === "Enter" || e.key === " ") {
1768
- if (highlightedIndex() >= 0) {
1769
- e.preventDefault();
1770
- selectHighlighted();
1771
- }
1772
- } else if (e.key.length === 1 && /\S/.test(e.key)) {
1773
- typeBuffer += e.key.toLowerCase();
1774
- if (typeTimer !== null) clearTimeout(typeTimer);
1775
- typeTimer = setTimeout(() => {
1776
- typeBuffer = "";
1777
- typeTimer = null;
1778
- }, 500);
1779
- const found = items.findIndex((it) => !isItemDisabled(it) && toStr(it).toLowerCase().startsWith(typeBuffer));
1780
- if (found !== -1) setHighlightedIndex(found);
1672
+ const remaining = days.length % 7;
1673
+ if (remaining > 0) {
1674
+ const trailingCount = 7 - remaining;
1675
+ for (let i = 1; i <= trailingCount; i++) {
1676
+ const date = new Date(year, month + 1, i);
1677
+ days.push({
1678
+ date,
1679
+ isCurrentMonth: false,
1680
+ isToday: sameDay(date, today),
1681
+ isSelected: selected !== null && sameDay(date, selected),
1682
+ isDisabled: isDateDisabled(date)
1683
+ });
1781
1684
  }
1782
- };
1783
- els.listbox.addEventListener("keydown", onKey);
1784
- let tornDown = false;
1785
- const teardown = () => {
1786
- if (tornDown) return;
1787
- tornDown = true;
1788
- if (boundSelects.get(els.listbox) === teardown) boundSelects.delete(els.listbox);
1789
- fxTeardown();
1790
- els.listbox.removeEventListener("keydown", onKey);
1791
- if (typeTimer !== null) clearTimeout(typeTimer);
1792
- if (prevId === "") els.listbox.removeAttribute("id");
1793
- else els.listbox.id = prevId;
1794
- if (prevRole === null) els.listbox.removeAttribute("role");
1795
- else els.listbox.setAttribute("role", prevRole);
1796
- if (prevMultiselectable === null) els.listbox.removeAttribute("aria-multiselectable");
1797
- else els.listbox.setAttribute("aria-multiselectable", prevMultiselectable);
1798
- if (prevTabIndex === null) els.listbox.removeAttribute("tabindex");
1799
- else els.listbox.setAttribute("tabindex", prevTabIndex);
1800
- els.listbox.removeAttribute("aria-activedescendant");
1801
- };
1802
- boundSelects.set(els.listbox, teardown);
1803
- return teardown;
1685
+ }
1686
+ return days;
1687
+ });
1688
+ function isSelected(date) {
1689
+ const sel = selectedDate();
1690
+ return sel !== null && sameDay(date, sel);
1804
1691
  }
1805
1692
  return {
1806
- selectedItems,
1807
- selectedItem,
1693
+ selectedDate,
1808
1694
  select: select2,
1809
- deselect,
1810
- toggle,
1811
- isSelected,
1812
- isOpen,
1813
- open,
1814
- close,
1815
- highlightedIndex,
1816
- highlightNext,
1817
- highlightPrev,
1818
- selectHighlighted,
1819
- clear,
1820
- bind
1821
- };
1822
- }
1823
-
1824
- // src/widgets/Tooltip.ts
1825
- var boundTriggers = /* @__PURE__ */ new WeakMap();
1826
- function tooltip(options) {
1827
- const delay = options?.delay ?? 0;
1828
- const hideDelay = options?.hideDelay ?? 100;
1829
- const [isVisible, setIsVisible] = signal(false);
1830
- const [content, setContent] = signal("");
1831
- let delayTimer = null;
1832
- let hideTimer = null;
1833
- function show() {
1834
- if (hideTimer !== null) {
1835
- clearTimeout(hideTimer);
1836
- hideTimer = null;
1837
- }
1838
- if (delay > 0) {
1839
- if (delayTimer !== null) clearTimeout(delayTimer);
1840
- delayTimer = setTimeout(() => {
1841
- setIsVisible(true);
1842
- delayTimer = null;
1843
- }, delay);
1844
- } else {
1845
- setIsVisible(true);
1846
- }
1847
- }
1848
- function hide() {
1849
- if (delayTimer !== null) {
1850
- clearTimeout(delayTimer);
1851
- delayTimer = null;
1852
- }
1853
- setIsVisible(false);
1854
- }
1855
- function scheduleHide() {
1856
- if (delayTimer !== null) {
1857
- clearTimeout(delayTimer);
1858
- delayTimer = null;
1859
- }
1860
- if (hideTimer !== null) clearTimeout(hideTimer);
1861
- hideTimer = setTimeout(() => {
1862
- hideTimer = null;
1863
- setIsVisible(false);
1864
- }, hideDelay);
1865
- }
1695
+ viewDate,
1696
+ setViewDate,
1697
+ nextMonth,
1698
+ prevMonth,
1699
+ nextYear,
1700
+ prevYear,
1701
+ daysInMonth,
1702
+ isDateDisabled,
1703
+ /** Reactive check — use inside class bindings for per-day reactivity */
1704
+ isSelected,
1705
+ bind
1706
+ };
1866
1707
  function bind(els) {
1867
- const existing = boundTriggers.get(els.trigger);
1708
+ const existing = boundDatePickers.get(els.grid);
1868
1709
  if (existing) return existing;
1869
- const id = createId("sibu-tooltip");
1870
- els.tooltip.setAttribute("role", "tooltip");
1871
- els.tooltip.id = id;
1872
- const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
1873
- els.trigger.setAttribute("aria-describedby", prevDescribedBy ? `${prevDescribedBy} ${id}` : id);
1710
+ const prevGridRole = els.grid.getAttribute("role");
1711
+ const prevGridTabIndex = els.grid.getAttribute("tabindex");
1712
+ const prevGridLabel = els.grid.getAttribute("aria-label");
1713
+ els.grid.setAttribute("role", "grid");
1714
+ if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
1715
+ let pendingFocus = false;
1716
+ const touchedCells = /* @__PURE__ */ new Map();
1717
+ const restoreAttr = (el, name, value) => {
1718
+ if (value === null) el.removeAttribute(name);
1719
+ else el.setAttribute(name, value);
1720
+ };
1721
+ const restoreCell = (cell, snap) => {
1722
+ restoreAttr(cell, "role", snap.role);
1723
+ restoreAttr(cell, "aria-selected", snap.selected);
1724
+ restoreAttr(cell, "aria-disabled", snap.disabled);
1725
+ restoreAttr(cell, "tabindex", snap.tabindex);
1726
+ };
1874
1727
  const fxTeardown = domBinding(() => {
1875
- els.tooltip.hidden = !isVisible();
1876
- }, els.trigger);
1877
- const onTriggerEnter = () => show();
1878
- const onTriggerLeave = () => scheduleHide();
1879
- const onTooltipEnter = () => {
1880
- if (hideTimer !== null) {
1881
- clearTimeout(hideTimer);
1882
- hideTimer = null;
1728
+ const sel = selectedDate();
1729
+ const view = viewDate();
1730
+ const days = daysInMonth();
1731
+ els.grid.setAttribute("aria-label", view.toLocaleDateString(void 0, { month: "long", year: "numeric" }));
1732
+ let viewCell = null;
1733
+ const displayed = /* @__PURE__ */ new Set();
1734
+ for (const d of days) {
1735
+ const cell = els.cell(d.date);
1736
+ if (!cell) continue;
1737
+ displayed.add(cell);
1738
+ if (!touchedCells.has(cell)) {
1739
+ touchedCells.set(cell, {
1740
+ role: cell.getAttribute("role"),
1741
+ selected: cell.getAttribute("aria-selected"),
1742
+ disabled: cell.getAttribute("aria-disabled"),
1743
+ tabindex: cell.getAttribute("tabindex")
1744
+ });
1745
+ }
1746
+ cell.setAttribute("role", "gridcell");
1747
+ cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
1748
+ if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
1749
+ else cell.removeAttribute("aria-disabled");
1750
+ const isView = isSameCalendarDay(view, d.date);
1751
+ cell.tabIndex = isView ? 0 : -1;
1752
+ if (isView) viewCell = cell;
1883
1753
  }
1884
- };
1885
- const onTooltipLeave = () => scheduleHide();
1886
- const onFocus = () => show();
1887
- const onBlur = () => hide();
1754
+ for (const [cell, snap] of touchedCells) {
1755
+ if (!displayed.has(cell)) {
1756
+ restoreCell(cell, snap);
1757
+ touchedCells.delete(cell);
1758
+ }
1759
+ }
1760
+ if (pendingFocus && viewCell && typeof viewCell.focus === "function") {
1761
+ pendingFocus = false;
1762
+ viewCell.focus();
1763
+ }
1764
+ }, els.grid);
1765
+ function isSameCalendarDay(a, b) {
1766
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1767
+ }
1768
+ function shiftDays(delta) {
1769
+ setViewDate((prev) => new Date(prev.getFullYear(), prev.getMonth(), prev.getDate() + delta));
1770
+ }
1888
1771
  const onKey = (e) => {
1889
- if (e.key === "Escape" && isVisible()) {
1890
- e.stopPropagation();
1891
- hide();
1772
+ if (isWidgetKeyEvent(e)) return;
1773
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Home" || e.key === "End" || e.key === "PageUp" || e.key === "PageDown") {
1774
+ pendingFocus = true;
1775
+ }
1776
+ switch (e.key) {
1777
+ case "ArrowLeft":
1778
+ e.preventDefault();
1779
+ claimWidgetKey(e, "datePicker");
1780
+ shiftDays(-1);
1781
+ break;
1782
+ case "ArrowRight":
1783
+ e.preventDefault();
1784
+ claimWidgetKey(e, "datePicker");
1785
+ shiftDays(1);
1786
+ break;
1787
+ case "ArrowUp":
1788
+ e.preventDefault();
1789
+ claimWidgetKey(e, "datePicker");
1790
+ shiftDays(-7);
1791
+ break;
1792
+ case "ArrowDown":
1793
+ e.preventDefault();
1794
+ claimWidgetKey(e, "datePicker");
1795
+ shiftDays(7);
1796
+ break;
1797
+ case "Home":
1798
+ e.preventDefault();
1799
+ claimWidgetKey(e, "datePicker");
1800
+ setViewDate((p) => new Date(p.getFullYear(), p.getMonth(), p.getDate() - p.getDay()));
1801
+ break;
1802
+ case "End": {
1803
+ e.preventDefault();
1804
+ claimWidgetKey(e, "datePicker");
1805
+ setViewDate((p) => new Date(p.getFullYear(), p.getMonth(), p.getDate() + (6 - p.getDay())));
1806
+ break;
1807
+ }
1808
+ case "PageUp":
1809
+ e.preventDefault();
1810
+ claimWidgetKey(e, "datePicker");
1811
+ if (e.shiftKey) prevYear();
1812
+ else prevMonth();
1813
+ break;
1814
+ case "PageDown":
1815
+ e.preventDefault();
1816
+ claimWidgetKey(e, "datePicker");
1817
+ if (e.shiftKey) nextYear();
1818
+ else nextMonth();
1819
+ break;
1820
+ case "Enter":
1821
+ case " ":
1822
+ e.preventDefault();
1823
+ claimWidgetKey(e, "datePicker");
1824
+ select2(viewDate());
1825
+ break;
1892
1826
  }
1893
1827
  };
1894
- els.trigger.addEventListener("pointerenter", onTriggerEnter);
1895
- els.trigger.addEventListener("pointerleave", onTriggerLeave);
1896
- els.trigger.addEventListener("focus", onFocus);
1897
- els.trigger.addEventListener("blur", onBlur);
1898
- els.trigger.addEventListener("keydown", onKey);
1899
- els.tooltip.addEventListener("pointerenter", onTooltipEnter);
1900
- els.tooltip.addEventListener("pointerleave", onTooltipLeave);
1828
+ els.grid.addEventListener("keydown", onKey);
1901
1829
  let tornDown = false;
1902
1830
  const teardown = () => {
1903
1831
  if (tornDown) return;
1904
1832
  tornDown = true;
1905
- if (boundTriggers.get(els.trigger) === teardown) boundTriggers.delete(els.trigger);
1833
+ if (boundDatePickers.get(els.grid) === teardown) boundDatePickers.delete(els.grid);
1906
1834
  fxTeardown();
1907
- els.trigger.removeEventListener("pointerenter", onTriggerEnter);
1908
- els.trigger.removeEventListener("pointerleave", onTriggerLeave);
1909
- els.trigger.removeEventListener("focus", onFocus);
1910
- els.trigger.removeEventListener("blur", onBlur);
1911
- els.trigger.removeEventListener("keydown", onKey);
1912
- els.tooltip.removeEventListener("pointerenter", onTooltipEnter);
1913
- els.tooltip.removeEventListener("pointerleave", onTooltipLeave);
1914
- const cur = els.trigger.getAttribute("aria-describedby");
1915
- if (cur) {
1916
- const remaining = cur.split(/\s+/).filter((part) => part && part !== id);
1917
- if (remaining.length > 0) els.trigger.setAttribute("aria-describedby", remaining.join(" "));
1918
- else els.trigger.removeAttribute("aria-describedby");
1919
- } else if (prevDescribedBy) {
1920
- els.trigger.setAttribute("aria-describedby", prevDescribedBy);
1921
- }
1922
- if (delayTimer !== null) clearTimeout(delayTimer);
1923
- if (hideTimer !== null) clearTimeout(hideTimer);
1835
+ els.grid.removeEventListener("keydown", onKey);
1836
+ if (prevGridRole === null) els.grid.removeAttribute("role");
1837
+ else els.grid.setAttribute("role", prevGridRole);
1838
+ if (prevGridTabIndex === null) els.grid.removeAttribute("tabindex");
1839
+ else els.grid.setAttribute("tabindex", prevGridTabIndex);
1840
+ if (prevGridLabel === null) els.grid.removeAttribute("aria-label");
1841
+ else els.grid.setAttribute("aria-label", prevGridLabel);
1842
+ for (const [cell, snap] of touchedCells) restoreCell(cell, snap);
1843
+ touchedCells.clear();
1924
1844
  };
1925
- boundTriggers.set(els.trigger, teardown);
1845
+ boundDatePickers.set(els.grid, teardown);
1926
1846
  return teardown;
1927
1847
  }
1928
- return { isVisible, show, hide, content, setContent, bind };
1929
1848
  }
1930
1849
 
1931
1850
  // src/widgets/FileUpload.ts
@@ -2073,8 +1992,10 @@ function fileUpload(options) {
2073
1992
  };
2074
1993
  const onDropClick = () => els.input.click();
2075
1994
  const onDropKey = (e) => {
1995
+ if (isWidgetKeyEvent(e)) return;
2076
1996
  if (e.key === "Enter" || e.key === " ") {
2077
1997
  e.preventDefault();
1998
+ claimWidgetKey(e, "fileUpload");
2078
1999
  els.input.click();
2079
2000
  }
2080
2001
  };
@@ -2127,365 +2048,841 @@ function fileUpload(options) {
2127
2048
  };
2128
2049
  }
2129
2050
 
2130
- // src/utils/sanitize.ts
2131
- function stripHtml(html) {
2132
- const input = String(html);
2133
- if (typeof DOMParser !== "undefined") {
2134
- try {
2135
- return new DOMParser().parseFromString(input, "text/html").body.textContent ?? "";
2136
- } catch {
2051
+ // src/widgets/Popover.ts
2052
+ var boundPopovers = /* @__PURE__ */ new WeakMap();
2053
+ function popover() {
2054
+ const [isOpen, setIsOpen] = signal(false);
2055
+ function open() {
2056
+ setIsOpen(true);
2057
+ }
2058
+ function close() {
2059
+ setIsOpen(false);
2060
+ }
2061
+ function toggle() {
2062
+ setIsOpen((prev) => !prev);
2063
+ }
2064
+ function bind(els) {
2065
+ const existing = boundPopovers.get(els.trigger);
2066
+ if (existing) return existing;
2067
+ const id = createId("sibu-popover");
2068
+ const prevPopoverRole = els.popover.getAttribute("role");
2069
+ const prevPopoverId = els.popover.id;
2070
+ const prevLabelledBy = els.popover.getAttribute("aria-labelledby");
2071
+ const prevTriggerHaspopup = els.trigger.getAttribute("aria-haspopup");
2072
+ const prevTriggerControls = els.trigger.getAttribute("aria-controls");
2073
+ els.popover.setAttribute("role", "dialog");
2074
+ els.popover.id = id;
2075
+ els.trigger.setAttribute("aria-haspopup", "dialog");
2076
+ els.trigger.setAttribute("aria-controls", id);
2077
+ let assignedLabelId = null;
2078
+ if (els.labelledBy) {
2079
+ if (!els.labelledBy.id) {
2080
+ els.labelledBy.id = `${id}-label`;
2081
+ assignedLabelId = els.labelledBy.id;
2082
+ }
2083
+ els.popover.setAttribute("aria-labelledby", els.labelledBy.id);
2137
2084
  }
2085
+ const fxTeardown = domBinding(() => {
2086
+ const open2 = isOpen();
2087
+ els.trigger.setAttribute("aria-expanded", open2 ? "true" : "false");
2088
+ els.popover.hidden = !open2;
2089
+ }, els.trigger);
2090
+ const onTriggerClick = (e) => {
2091
+ e.preventDefault();
2092
+ toggle();
2093
+ };
2094
+ const onKey = (e) => {
2095
+ if (isWidgetKeyEvent(e)) return;
2096
+ if (e.key === "Escape" && isOpen()) {
2097
+ e.stopPropagation();
2098
+ claimWidgetKey(e, "popover");
2099
+ close();
2100
+ els.trigger.focus();
2101
+ }
2102
+ };
2103
+ const onDocPointer = (e) => {
2104
+ if (!isOpen()) return;
2105
+ const t = e.target;
2106
+ if (!t) return;
2107
+ if (els.trigger.contains(t) || els.popover.contains(t)) return;
2108
+ close();
2109
+ };
2110
+ els.trigger.addEventListener("click", onTriggerClick);
2111
+ els.popover.addEventListener("keydown", onKey);
2112
+ els.trigger.addEventListener("keydown", onKey);
2113
+ document.addEventListener("pointerdown", onDocPointer);
2114
+ let tornDown = false;
2115
+ const teardown = () => {
2116
+ if (tornDown) return;
2117
+ tornDown = true;
2118
+ if (boundPopovers.get(els.trigger) === teardown) boundPopovers.delete(els.trigger);
2119
+ fxTeardown();
2120
+ els.trigger.removeEventListener("click", onTriggerClick);
2121
+ els.popover.removeEventListener("keydown", onKey);
2122
+ els.trigger.removeEventListener("keydown", onKey);
2123
+ document.removeEventListener("pointerdown", onDocPointer);
2124
+ if (prevPopoverRole === null) els.popover.removeAttribute("role");
2125
+ else els.popover.setAttribute("role", prevPopoverRole);
2126
+ if (prevPopoverId === "") els.popover.removeAttribute("id");
2127
+ else els.popover.id = prevPopoverId;
2128
+ if (prevLabelledBy === null) els.popover.removeAttribute("aria-labelledby");
2129
+ else els.popover.setAttribute("aria-labelledby", prevLabelledBy);
2130
+ if (assignedLabelId && els.labelledBy?.id === assignedLabelId) {
2131
+ els.labelledBy.removeAttribute("id");
2132
+ }
2133
+ if (prevTriggerHaspopup === null) els.trigger.removeAttribute("aria-haspopup");
2134
+ else els.trigger.setAttribute("aria-haspopup", prevTriggerHaspopup);
2135
+ if (prevTriggerControls === null) els.trigger.removeAttribute("aria-controls");
2136
+ else els.trigger.setAttribute("aria-controls", prevTriggerControls);
2137
+ els.trigger.removeAttribute("aria-expanded");
2138
+ };
2139
+ boundPopovers.set(els.trigger, teardown);
2140
+ return teardown;
2138
2141
  }
2139
- let prev;
2140
- let out = input;
2141
- do {
2142
- prev = out;
2143
- out = out.replace(/<[^>]*>/g, "");
2144
- } while (out !== prev);
2145
- return out.replace(/<[^>]*$/, "");
2142
+ return { isOpen, open, close, toggle, bind };
2146
2143
  }
2147
2144
 
2148
- // src/widgets/contentEditable.ts
2149
- function toInertText(html) {
2150
- return stripHtml(String(html)).replace(/<(?=[a-z!/?])/gi, "&lt;");
2151
- }
2152
- function contentEditable(element) {
2153
- const [content, setContentInternal] = signal("");
2154
- const [isFocused, setFocused] = signal(false);
2155
- function setContent(input) {
2156
- if (typeof input === "string") {
2157
- setContentInternal(toInertText(input));
2158
- return;
2159
- }
2160
- if (typeof input.text === "string") {
2161
- setContentInternal(input.text);
2162
- return;
2163
- }
2164
- if (typeof input.html === "string") {
2165
- const shouldSanitize = input.sanitize !== false;
2166
- setContentInternal(shouldSanitize ? toInertText(input.html) : input.html);
2167
- return;
2145
+ // src/widgets/Select.ts
2146
+ var boundSelects = /* @__PURE__ */ new WeakMap();
2147
+ function select(options) {
2148
+ const { items, multiple = false, itemToString, isDisabled } = options;
2149
+ const isItemDisabled = isDisabled ?? (() => false);
2150
+ const [selectedItems, setSelectedItems] = signal([]);
2151
+ const [isOpen, setIsOpen] = signal(false);
2152
+ const [highlightedIndex, setHighlightedIndex] = signal(-1);
2153
+ const selectedItem = derived(() => {
2154
+ const sel = selectedItems();
2155
+ return sel.length > 0 ? sel[sel.length - 1] : null;
2156
+ });
2157
+ function select2(item) {
2158
+ if (isItemDisabled(item)) return;
2159
+ if (multiple) {
2160
+ setSelectedItems((prev) => {
2161
+ if (prev.includes(item)) return prev;
2162
+ return [...prev, item];
2163
+ });
2164
+ } else {
2165
+ batch(() => {
2166
+ setSelectedItems([item]);
2167
+ setIsOpen(false);
2168
+ });
2168
2169
  }
2169
- setContentInternal("");
2170
2170
  }
2171
- function wrapSelection(tagName) {
2172
- if (typeof window === "undefined") return;
2173
- const selection = window.getSelection();
2174
- if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return;
2175
- const range = selection.getRangeAt(0);
2176
- if (element && !element.contains(range.commonAncestorContainer)) return;
2177
- const ancestor = range.commonAncestorContainer;
2178
- const existingWrap = findWrapperWithinEditor(
2179
- ancestor instanceof HTMLElement ? ancestor : ancestor.parentElement,
2180
- tagName
2181
- );
2182
- let targetNode = null;
2183
- if (existingWrap) {
2184
- const parent = existingWrap.parentNode;
2185
- if (parent) {
2186
- const firstChild = existingWrap.firstChild;
2187
- const lastChild = existingWrap.lastChild;
2188
- while (existingWrap.firstChild) {
2189
- parent.insertBefore(existingWrap.firstChild, existingWrap);
2190
- }
2191
- parent.removeChild(existingWrap);
2192
- if (firstChild && lastChild) {
2193
- const newRange = document.createRange();
2194
- newRange.setStartBefore(firstChild);
2195
- newRange.setEndAfter(lastChild);
2196
- selection.removeAllRanges();
2197
- selection.addRange(newRange);
2198
- return;
2199
- }
2200
- }
2171
+ function deselect(item) {
2172
+ setSelectedItems((prev) => prev.filter((i) => i !== item));
2173
+ }
2174
+ function toggle(item) {
2175
+ if (selectedItems().includes(item)) {
2176
+ deselect(item);
2201
2177
  } else {
2202
- const wrapper = document.createElement(tagName);
2203
- try {
2204
- range.surroundContents(wrapper);
2205
- } catch {
2206
- const fragment = range.extractContents();
2207
- wrapper.appendChild(fragment);
2208
- range.insertNode(wrapper);
2209
- }
2210
- targetNode = wrapper;
2211
- }
2212
- if (targetNode) {
2213
- selection.removeAllRanges();
2214
- const newRange = document.createRange();
2215
- newRange.selectNodeContents(targetNode);
2216
- selection.addRange(newRange);
2178
+ select2(item);
2217
2179
  }
2218
2180
  }
2219
- function findWrapperWithinEditor(el, tagName) {
2220
- const upper = tagName.toUpperCase();
2221
- while (el) {
2222
- if (el === element || isEditingHost(el)) return null;
2223
- if (el.tagName === upper) return el;
2224
- el = el.parentElement;
2181
+ function isSelected(item) {
2182
+ return selectedItems().includes(item);
2183
+ }
2184
+ function open() {
2185
+ setIsOpen(true);
2186
+ }
2187
+ function close() {
2188
+ setIsOpen(false);
2189
+ }
2190
+ function nextEnabled(from, dir) {
2191
+ const len = items.length;
2192
+ if (len === 0) return -1;
2193
+ let i = from;
2194
+ for (let n = 0; n < len; n++) {
2195
+ i = (i + dir + len) % len;
2196
+ if (!isItemDisabled(items[i])) return i;
2225
2197
  }
2226
- return null;
2198
+ return -1;
2227
2199
  }
2228
- function isEditingHost(el) {
2229
- const attr = el.getAttribute("contenteditable");
2230
- return attr !== null && attr.toLowerCase() !== "false";
2200
+ function highlightNext() {
2201
+ if (items.length === 0) return;
2202
+ setHighlightedIndex((prev) => {
2203
+ const n = nextEnabled(prev < 0 ? -1 : prev, 1);
2204
+ return n === -1 ? prev : n;
2205
+ });
2231
2206
  }
2232
- function bold() {
2233
- wrapSelection("strong");
2207
+ function highlightPrev() {
2208
+ if (items.length === 0) return;
2209
+ setHighlightedIndex((prev) => {
2210
+ const n = nextEnabled(prev < 0 ? items.length : prev, -1);
2211
+ return n === -1 ? prev : n;
2212
+ });
2234
2213
  }
2235
- function italic() {
2236
- wrapSelection("em");
2214
+ function selectHighlighted() {
2215
+ const idx = highlightedIndex();
2216
+ if (idx >= 0 && idx < items.length) {
2217
+ select2(items[idx]);
2218
+ }
2237
2219
  }
2238
- function underline() {
2239
- wrapSelection("u");
2220
+ function clear() {
2221
+ setSelectedItems([]);
2222
+ }
2223
+ function bind(els) {
2224
+ const existing = boundSelects.get(els.listbox);
2225
+ if (existing) return existing;
2226
+ const prevId = els.listbox.id;
2227
+ const prevRole = els.listbox.getAttribute("role");
2228
+ const prevMultiselectable = els.listbox.getAttribute("aria-multiselectable");
2229
+ const prevTabIndex = els.listbox.getAttribute("tabindex");
2230
+ const listboxId = createId("sibu-select");
2231
+ els.listbox.id = listboxId;
2232
+ els.listbox.setAttribute("role", "listbox");
2233
+ els.listbox.setAttribute("aria-multiselectable", multiple ? "true" : "false");
2234
+ if (els.listbox.tabIndex < 0) els.listbox.tabIndex = 0;
2235
+ const toStr = els.itemToString ?? itemToString ?? ((it) => String(it));
2236
+ const fxTeardown = domBinding(() => {
2237
+ const idx = highlightedIndex();
2238
+ const sel = selectedItems();
2239
+ let activeId = "";
2240
+ for (let i = 0; i < items.length; i++) {
2241
+ const optEl = els.option(items[i], i);
2242
+ if (!optEl) continue;
2243
+ if (!optEl.id) optEl.id = `${listboxId}-opt-${i}`;
2244
+ optEl.setAttribute("role", "option");
2245
+ optEl.setAttribute("aria-selected", sel.includes(items[i]) ? "true" : "false");
2246
+ const disabled = isItemDisabled(items[i]);
2247
+ if (disabled) optEl.setAttribute("aria-disabled", "true");
2248
+ else optEl.removeAttribute("aria-disabled");
2249
+ if (i === idx && !disabled) activeId = optEl.id;
2250
+ }
2251
+ if (activeId) els.listbox.setAttribute("aria-activedescendant", activeId);
2252
+ else els.listbox.removeAttribute("aria-activedescendant");
2253
+ }, els.listbox);
2254
+ let typeBuffer = "";
2255
+ let typeTimer = null;
2256
+ const onKey = (e) => {
2257
+ if (isWidgetKeyEvent(e)) return;
2258
+ if (e.key === "ArrowDown") {
2259
+ e.preventDefault();
2260
+ claimWidgetKey(e, "select");
2261
+ highlightNext();
2262
+ } else if (e.key === "ArrowUp") {
2263
+ e.preventDefault();
2264
+ claimWidgetKey(e, "select");
2265
+ highlightPrev();
2266
+ } else if (e.key === "Home") {
2267
+ e.preventDefault();
2268
+ claimWidgetKey(e, "select");
2269
+ const first = nextEnabled(-1, 1);
2270
+ if (first !== -1) setHighlightedIndex(first);
2271
+ } else if (e.key === "End") {
2272
+ e.preventDefault();
2273
+ claimWidgetKey(e, "select");
2274
+ const last = nextEnabled(items.length, -1);
2275
+ if (last !== -1) setHighlightedIndex(last);
2276
+ } else if (e.key === "Enter" || e.key === " ") {
2277
+ if (highlightedIndex() >= 0) {
2278
+ e.preventDefault();
2279
+ claimWidgetKey(e, "select");
2280
+ selectHighlighted();
2281
+ }
2282
+ } else if (e.key.length === 1 && /\S/.test(e.key)) {
2283
+ claimWidgetKey(e, "select");
2284
+ typeBuffer += e.key.toLowerCase();
2285
+ if (typeTimer !== null) clearTimeout(typeTimer);
2286
+ typeTimer = setTimeout(() => {
2287
+ typeBuffer = "";
2288
+ typeTimer = null;
2289
+ }, 500);
2290
+ const found = items.findIndex((it) => !isItemDisabled(it) && toStr(it).toLowerCase().startsWith(typeBuffer));
2291
+ if (found !== -1) setHighlightedIndex(found);
2292
+ }
2293
+ };
2294
+ els.listbox.addEventListener("keydown", onKey);
2295
+ let tornDown = false;
2296
+ const teardown = () => {
2297
+ if (tornDown) return;
2298
+ tornDown = true;
2299
+ if (boundSelects.get(els.listbox) === teardown) boundSelects.delete(els.listbox);
2300
+ fxTeardown();
2301
+ els.listbox.removeEventListener("keydown", onKey);
2302
+ if (typeTimer !== null) clearTimeout(typeTimer);
2303
+ if (prevId === "") els.listbox.removeAttribute("id");
2304
+ else els.listbox.id = prevId;
2305
+ if (prevRole === null) els.listbox.removeAttribute("role");
2306
+ else els.listbox.setAttribute("role", prevRole);
2307
+ if (prevMultiselectable === null) els.listbox.removeAttribute("aria-multiselectable");
2308
+ else els.listbox.setAttribute("aria-multiselectable", prevMultiselectable);
2309
+ if (prevTabIndex === null) els.listbox.removeAttribute("tabindex");
2310
+ else els.listbox.setAttribute("tabindex", prevTabIndex);
2311
+ els.listbox.removeAttribute("aria-activedescendant");
2312
+ };
2313
+ boundSelects.set(els.listbox, teardown);
2314
+ return teardown;
2240
2315
  }
2241
2316
  return {
2242
- content,
2243
- setContent,
2244
- isFocused,
2245
- setFocused,
2246
- bold,
2247
- italic,
2248
- underline
2317
+ selectedItems,
2318
+ selectedItem,
2319
+ select: select2,
2320
+ deselect,
2321
+ toggle,
2322
+ isSelected,
2323
+ isOpen,
2324
+ open,
2325
+ close,
2326
+ highlightedIndex,
2327
+ highlightNext,
2328
+ highlightPrev,
2329
+ selectHighlighted,
2330
+ clear,
2331
+ bind
2249
2332
  };
2250
2333
  }
2251
2334
 
2252
- // src/widgets/datePicker.ts
2253
- var boundDatePickers = /* @__PURE__ */ new WeakMap();
2254
- function datePicker(options) {
2255
- const minDate = options?.minDate;
2256
- const maxDate = options?.maxDate;
2257
- const initialDate = options?.initialDate ?? null;
2258
- const [selectedDate, setSelectedDate] = signal(initialDate);
2259
- const [viewDate, setViewDate] = signal(initialDate ?? /* @__PURE__ */ new Date());
2260
- function isDateDisabled(date) {
2261
- const d = stripTime(date);
2262
- if (minDate && d < stripTime(minDate)) return true;
2263
- if (maxDate && d > stripTime(maxDate)) return true;
2264
- return false;
2335
+ // src/widgets/selectMenu.ts
2336
+ var boundMenus = /* @__PURE__ */ new WeakMap();
2337
+ var OPTION_ATTRS = ["id", "role", "aria-selected", "aria-disabled"];
2338
+ function selectMenu(options) {
2339
+ const getItems = typeof options.items === "function" ? options.items : () => options.items;
2340
+ const toStr = options.itemToString ?? ((item) => String(item));
2341
+ const isDisabled = options.isDisabled ?? (() => false);
2342
+ const isEqual = options.isEqual ?? Object.is;
2343
+ const pageSize = Math.max(1, options.pageSize ?? 10);
2344
+ const [ownValue, setOwnValue] = signal(options.defaultValue ?? null);
2345
+ const readValue = options.value;
2346
+ const value = () => readValue ? readValue() ?? null : ownValue();
2347
+ const [isOpen, setIsOpen] = signal(false);
2348
+ const [active, setActiveIndex] = signal(-1);
2349
+ const enabledAt = (items, i) => i >= 0 && i < items.length && !isDisabled(items[i]);
2350
+ function enabledFrom(items, from, dir) {
2351
+ for (let i = from; i >= 0 && i < items.length; i += dir) if (!isDisabled(items[i])) return i;
2352
+ return -1;
2265
2353
  }
2266
- function stripTime(d) {
2267
- return new Date(d.getFullYear(), d.getMonth(), d.getDate());
2354
+ function selectedIndex(items) {
2355
+ const v = value();
2356
+ if (v === null) return -1;
2357
+ return items.findIndex((it) => isEqual(it, v));
2358
+ }
2359
+ function open(activeAt = "selected") {
2360
+ untracked2(() => {
2361
+ const items = getItems();
2362
+ let idx = -1;
2363
+ if (activeAt === "last") idx = enabledFrom(items, items.length - 1, -1);
2364
+ else if (activeAt === "first") idx = enabledFrom(items, 0, 1);
2365
+ else {
2366
+ const sel = selectedIndex(items);
2367
+ idx = enabledAt(items, sel) ? sel : enabledFrom(items, 0, 1);
2368
+ }
2369
+ batch(() => {
2370
+ setActiveIndex(idx);
2371
+ setIsOpen(true);
2372
+ });
2373
+ });
2268
2374
  }
2269
- function sameDay(a, b) {
2270
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2375
+ function close() {
2376
+ setIsOpen(false);
2271
2377
  }
2272
- function select2(date) {
2273
- if (!isDateDisabled(date)) {
2274
- setSelectedDate(date);
2275
- }
2378
+ function toggle() {
2379
+ if (untracked2(isOpen)) close();
2380
+ else open();
2276
2381
  }
2277
- function shiftMonth(prev, delta) {
2278
- return new Date(prev.getFullYear(), prev.getMonth() + delta, 1);
2382
+ function setActive(index) {
2383
+ if (enabledAt(untracked2(getItems), index)) setActiveIndex(index);
2279
2384
  }
2280
- function nextMonth() {
2281
- setViewDate((prev) => shiftMonth(prev, 1));
2385
+ function select2(item) {
2386
+ if (isDisabled(item)) return;
2387
+ batch(() => {
2388
+ if (!readValue) setOwnValue(() => item);
2389
+ setIsOpen(false);
2390
+ });
2391
+ options.onChange?.(item);
2282
2392
  }
2283
- function prevMonth() {
2284
- setViewDate((prev) => shiftMonth(prev, -1));
2393
+ function activeItem() {
2394
+ const items = getItems();
2395
+ const i = active();
2396
+ return i >= 0 && i < items.length ? items[i] : null;
2285
2397
  }
2286
- function nextYear() {
2287
- setViewDate((prev) => new Date(prev.getFullYear() + 1, prev.getMonth(), 1));
2398
+ function bind(els) {
2399
+ const existing = boundMenus.get(els.trigger);
2400
+ if (existing) return existing;
2401
+ const { trigger, listbox } = els;
2402
+ const restoreTrigger = snapshotAttributes(trigger, [
2403
+ "role",
2404
+ "aria-haspopup",
2405
+ "aria-expanded",
2406
+ "aria-controls",
2407
+ "aria-activedescendant",
2408
+ "tabindex"
2409
+ ]);
2410
+ const restoreListbox = snapshotAttributes(listbox, ["id", "role", "tabindex"]);
2411
+ const prevHidden = listbox.hidden;
2412
+ const optionRestores = /* @__PURE__ */ new Map();
2413
+ const generatedIds = /* @__PURE__ */ new WeakMap();
2414
+ let optionIndex = /* @__PURE__ */ new Map();
2415
+ const listboxId = listbox.id || createId("sibu-select-menu");
2416
+ listbox.id = listboxId;
2417
+ listbox.setAttribute("role", "listbox");
2418
+ listbox.tabIndex = -1;
2419
+ trigger.setAttribute("role", "combobox");
2420
+ trigger.setAttribute("aria-haspopup", "listbox");
2421
+ trigger.setAttribute("aria-controls", listboxId);
2422
+ if (trigger.tabIndex < 0) trigger.tabIndex = 0;
2423
+ const generatedIdFor = (el, taken) => {
2424
+ let id = generatedIds.get(el);
2425
+ while (id === void 0 || taken.has(id) || el.ownerDocument.getElementById(id) !== null) {
2426
+ id = createId(`${listboxId}-opt`);
2427
+ }
2428
+ generatedIds.set(el, id);
2429
+ return id;
2430
+ };
2431
+ let lastScrolled = null;
2432
+ const stopBinding = domBinding(() => {
2433
+ const items = getItems();
2434
+ const openNow = isOpen();
2435
+ const idx = active();
2436
+ const sel = value();
2437
+ trigger.setAttribute("aria-expanded", openNow ? "true" : "false");
2438
+ listbox.hidden = !openNow;
2439
+ const nextIndex = /* @__PURE__ */ new Map();
2440
+ let activeEl = null;
2441
+ const optionEls = [];
2442
+ const taken = /* @__PURE__ */ new Set();
2443
+ for (let i = 0; i < items.length; i++) {
2444
+ const el = els.option(items[i], i);
2445
+ optionEls.push(el);
2446
+ if (el?.id) taken.add(el.id);
2447
+ }
2448
+ for (let i = 0; i < items.length; i++) {
2449
+ const el = optionEls[i];
2450
+ if (!el) continue;
2451
+ if (!optionRestores.has(el)) optionRestores.set(el, snapshotAttributes(el, OPTION_ATTRS));
2452
+ nextIndex.set(el, i);
2453
+ if (!el.id) {
2454
+ el.id = generatedIdFor(el, taken);
2455
+ taken.add(el.id);
2456
+ }
2457
+ el.setAttribute("role", "option");
2458
+ el.setAttribute("aria-selected", sel !== null && isEqual(items[i], sel) ? "true" : "false");
2459
+ const disabled = isDisabled(items[i]);
2460
+ if (disabled) el.setAttribute("aria-disabled", "true");
2461
+ else el.removeAttribute("aria-disabled");
2462
+ if (i === idx && !disabled) activeEl = el;
2463
+ }
2464
+ for (const [el, restore] of optionRestores) {
2465
+ if (nextIndex.has(el)) continue;
2466
+ restore();
2467
+ optionRestores.delete(el);
2468
+ if (lastScrolled === el) lastScrolled = null;
2469
+ }
2470
+ optionIndex = nextIndex;
2471
+ if (openNow && activeEl) {
2472
+ trigger.setAttribute("aria-activedescendant", activeEl.id);
2473
+ if (activeEl !== lastScrolled) {
2474
+ activeEl.scrollIntoView?.({
2475
+ block: "nearest"
2476
+ });
2477
+ lastScrolled = activeEl;
2478
+ }
2479
+ } else {
2480
+ trigger.removeAttribute("aria-activedescendant");
2481
+ if (!openNow) lastScrolled = null;
2482
+ }
2483
+ }, trigger);
2484
+ let typeBuffer = "";
2485
+ let typeTimer = null;
2486
+ const typeahead = (char) => {
2487
+ typeBuffer += char.toLowerCase();
2488
+ if (typeTimer !== null) clearTimeout(typeTimer);
2489
+ typeTimer = setTimeout(() => {
2490
+ typeBuffer = "";
2491
+ typeTimer = null;
2492
+ }, 500);
2493
+ const items = getItems();
2494
+ const n = items.length;
2495
+ if (n === 0) return;
2496
+ const start = untracked2(active);
2497
+ const repeated = typeBuffer.length > 1 && [...typeBuffer].every((c) => c === typeBuffer[0]);
2498
+ const needle = repeated ? typeBuffer[0] : typeBuffer;
2499
+ const offset = typeBuffer.length === 1 || repeated ? 1 : 0;
2500
+ for (let k = 0; k < n; k++) {
2501
+ const i = (Math.max(start, 0) + offset + k) % n;
2502
+ if (!isDisabled(items[i]) && toStr(items[i]).toLowerCase().startsWith(needle)) {
2503
+ setActiveIndex(i);
2504
+ return;
2505
+ }
2506
+ }
2507
+ };
2508
+ const commitActive = () => {
2509
+ const item = untracked2(activeItem);
2510
+ if (item !== null && !isDisabled(item)) select2(item);
2511
+ else close();
2512
+ };
2513
+ const move = (delta) => {
2514
+ const items = getItems();
2515
+ const cur = untracked2(active);
2516
+ const dir = delta > 0 ? 1 : -1;
2517
+ const target = Math.min(items.length - 1, Math.max(0, cur + delta));
2518
+ let next = enabledFrom(items, target, dir);
2519
+ if (next === -1) next = enabledFrom(items, target, dir === 1 ? -1 : 1);
2520
+ if (next !== -1) setActiveIndex(next);
2521
+ };
2522
+ const onKey = (e) => {
2523
+ if (isWidgetKeyEvent(e) || e.ctrlKey || e.metaKey) return;
2524
+ const openNow = untracked2(isOpen);
2525
+ const key = e.key;
2526
+ let handled = true;
2527
+ if (!openNow) {
2528
+ if (key === "ArrowDown" || key === "ArrowUp" || key === "Enter" || key === " ") open();
2529
+ else if (key === "Home") open("first");
2530
+ else if (key === "End") open("last");
2531
+ else if (key.length === 1 && !e.altKey) {
2532
+ open();
2533
+ typeahead(key);
2534
+ } else handled = false;
2535
+ } else if (key === "ArrowDown" && !e.altKey) move(1);
2536
+ else if (key === "ArrowUp" && e.altKey) commitActive();
2537
+ else if (key === "ArrowUp") move(-1);
2538
+ else if (key === "Home") setActive(enabledFrom(getItems(), 0, 1));
2539
+ else if (key === "End") {
2540
+ const items = getItems();
2541
+ setActive(enabledFrom(items, items.length - 1, -1));
2542
+ } else if (key === "PageDown") move(pageSize);
2543
+ else if (key === "PageUp") move(-pageSize);
2544
+ else if (key === "Escape") close();
2545
+ else if (key === " " && typeBuffer !== "") typeahead(key);
2546
+ else if (key === "Enter" || key === " ") commitActive();
2547
+ else if (key === "Tab") {
2548
+ commitActive();
2549
+ handled = false;
2550
+ } else if (key.length === 1 && !e.altKey) typeahead(key);
2551
+ else handled = false;
2552
+ if (handled) {
2553
+ e.preventDefault();
2554
+ claimWidgetKey(e, "selectMenu");
2555
+ }
2556
+ };
2557
+ const onTriggerClick = () => toggle();
2558
+ const onListboxPointerDown = (e) => e.preventDefault();
2559
+ const optionFromEvent = (e) => {
2560
+ let node = e.target;
2561
+ while (node && node !== listbox) {
2562
+ const i = optionIndex.get(node);
2563
+ if (i !== void 0) return i;
2564
+ node = node.parentNode;
2565
+ }
2566
+ return -1;
2567
+ };
2568
+ const onListboxClick = (e) => {
2569
+ const i = optionFromEvent(e);
2570
+ if (i === -1) return;
2571
+ const item = getItems()[i];
2572
+ if (isDisabled(item)) return;
2573
+ select2(item);
2574
+ trigger.focus();
2575
+ };
2576
+ const onListboxPointerMove = (e) => {
2577
+ const i = optionFromEvent(e);
2578
+ if (i !== -1 && i !== untracked2(active)) setActive(i);
2579
+ };
2580
+ const onDocPointerDown = (e) => {
2581
+ if (!untracked2(isOpen)) return;
2582
+ const target = e.target;
2583
+ if (target && (trigger.contains(target) || listbox.contains(target))) return;
2584
+ close();
2585
+ };
2586
+ const onTriggerBlur = (e) => {
2587
+ if (!untracked2(isOpen)) return;
2588
+ const next = e.relatedTarget;
2589
+ if (next && listbox.contains(next)) return;
2590
+ close();
2591
+ };
2592
+ trigger.addEventListener("keydown", onKey);
2593
+ trigger.addEventListener("click", onTriggerClick);
2594
+ trigger.addEventListener("blur", onTriggerBlur);
2595
+ listbox.addEventListener("mousedown", onListboxPointerDown);
2596
+ listbox.addEventListener("click", onListboxClick);
2597
+ listbox.addEventListener("pointermove", onListboxPointerMove);
2598
+ const doc = trigger.ownerDocument;
2599
+ doc.addEventListener("pointerdown", onDocPointerDown, true);
2600
+ let tornDown = false;
2601
+ const teardown = () => {
2602
+ if (tornDown) return;
2603
+ tornDown = true;
2604
+ if (boundMenus.get(trigger) === teardown) boundMenus.delete(trigger);
2605
+ stopBinding();
2606
+ trigger.removeEventListener("keydown", onKey);
2607
+ trigger.removeEventListener("click", onTriggerClick);
2608
+ trigger.removeEventListener("blur", onTriggerBlur);
2609
+ listbox.removeEventListener("mousedown", onListboxPointerDown);
2610
+ listbox.removeEventListener("click", onListboxClick);
2611
+ listbox.removeEventListener("pointermove", onListboxPointerMove);
2612
+ doc.removeEventListener("pointerdown", onDocPointerDown, true);
2613
+ if (typeTimer !== null) clearTimeout(typeTimer);
2614
+ for (const restore of optionRestores.values()) restore();
2615
+ optionRestores.clear();
2616
+ optionIndex.clear();
2617
+ restoreTrigger();
2618
+ restoreListbox();
2619
+ listbox.hidden = prevHidden;
2620
+ };
2621
+ boundMenus.set(trigger, teardown);
2622
+ return teardown;
2288
2623
  }
2289
- function prevYear() {
2290
- setViewDate((prev) => new Date(prev.getFullYear() - 1, prev.getMonth(), 1));
2624
+ return { value, isOpen, open, close, toggle, active, activeItem, setActive, select: select2, bind };
2625
+ }
2626
+
2627
+ // src/widgets/Tabs.ts
2628
+ var boundTablists = /* @__PURE__ */ new WeakMap();
2629
+ function tabs(options) {
2630
+ const { tabs: tabDefs, defaultTab } = options;
2631
+ const firstEnabled = tabDefs.find((t) => !t.disabled)?.id ?? "";
2632
+ const initialTab = defaultTab !== void 0 && tabDefs.some((t) => t.id === defaultTab && !t.disabled) ? defaultTab : firstEnabled;
2633
+ const [activeTab, setActiveTabState] = signal(initialTab);
2634
+ function setActiveTab(id) {
2635
+ const tab = tabDefs.find((t) => t.id === id);
2636
+ if (tab && !tab.disabled) {
2637
+ setActiveTabState(id);
2638
+ }
2291
2639
  }
2292
- const daysInMonth = derived(() => {
2293
- const vd = viewDate();
2294
- const year = vd.getFullYear();
2295
- const month = vd.getMonth();
2296
- const today = /* @__PURE__ */ new Date();
2297
- const selected = selectedDate();
2298
- const firstDay = new Date(year, month, 1);
2299
- const startDow = firstDay.getDay();
2300
- const lastDay = new Date(year, month + 1, 0);
2301
- const totalDaysInMonth = lastDay.getDate();
2302
- const days = [];
2303
- for (let i = startDow - 1; i >= 0; i--) {
2304
- const date = new Date(year, month, -i);
2305
- days.push({
2306
- date,
2307
- isCurrentMonth: false,
2308
- isToday: sameDay(date, today),
2309
- isSelected: selected !== null && sameDay(date, selected),
2310
- isDisabled: isDateDisabled(date)
2311
- });
2640
+ const tabs2 = derived(
2641
+ () => tabDefs.map((t) => ({
2642
+ ...t,
2643
+ isActive: t.id === activeTab()
2644
+ }))
2645
+ );
2646
+ function findCurrentIndex() {
2647
+ return tabDefs.findIndex((t) => t.id === activeTab());
2648
+ }
2649
+ function nextTab() {
2650
+ const currentIdx = findCurrentIndex();
2651
+ const len = tabDefs.length;
2652
+ if (len === 0) return;
2653
+ for (let i = 1; i <= len; i++) {
2654
+ const candidate = tabDefs[(currentIdx + i) % len];
2655
+ if (!candidate.disabled) {
2656
+ setActiveTabState(candidate.id);
2657
+ return;
2658
+ }
2312
2659
  }
2313
- for (let d = 1; d <= totalDaysInMonth; d++) {
2314
- const date = new Date(year, month, d);
2315
- days.push({
2316
- date,
2317
- isCurrentMonth: true,
2318
- isToday: sameDay(date, today),
2319
- isSelected: selected !== null && sameDay(date, selected),
2320
- isDisabled: isDateDisabled(date)
2321
- });
2660
+ }
2661
+ function prevTab() {
2662
+ const currentIdx = findCurrentIndex();
2663
+ const len = tabDefs.length;
2664
+ if (len === 0) return;
2665
+ for (let i = 1; i <= len; i++) {
2666
+ const candidate = tabDefs[(currentIdx - i + len) % len];
2667
+ if (!candidate.disabled) {
2668
+ setActiveTabState(candidate.id);
2669
+ return;
2670
+ }
2322
2671
  }
2323
- const remaining = days.length % 7;
2324
- if (remaining > 0) {
2325
- const trailingCount = 7 - remaining;
2326
- for (let i = 1; i <= trailingCount; i++) {
2327
- const date = new Date(year, month + 1, i);
2328
- days.push({
2329
- date,
2330
- isCurrentMonth: false,
2331
- isToday: sameDay(date, today),
2332
- isSelected: selected !== null && sameDay(date, selected),
2333
- isDisabled: isDateDisabled(date)
2334
- });
2672
+ }
2673
+ function isActive(id) {
2674
+ return activeTab() === id;
2675
+ }
2676
+ function bind(els) {
2677
+ const existing = boundTablists.get(els.tablist);
2678
+ if (existing) return existing;
2679
+ const restore = [snapshotAttributes(els.tablist, ["role"])];
2680
+ els.tablist.setAttribute("role", "tablist");
2681
+ const idPrefix = createId("sibu-tabs");
2682
+ for (const def of tabDefs) {
2683
+ const tabEl = els.tabs[def.id];
2684
+ if (!tabEl) continue;
2685
+ restore.push(
2686
+ snapshotAttributes(tabEl, ["role", "id", "aria-disabled", "aria-controls", "aria-selected", "tabindex"])
2687
+ );
2688
+ tabEl.setAttribute("role", "tab");
2689
+ if (!tabEl.id) tabEl.id = `${idPrefix}-tab-${idSegment(def.id)}`;
2690
+ if (def.disabled) tabEl.setAttribute("aria-disabled", "true");
2691
+ else tabEl.removeAttribute("aria-disabled");
2692
+ const panelEl = els.panels?.[def.id];
2693
+ if (panelEl) {
2694
+ restore.push(snapshotAttributes(panelEl, ["role", "id", "aria-labelledby", "hidden"]));
2695
+ panelEl.setAttribute("role", "tabpanel");
2696
+ if (!panelEl.id) panelEl.id = `${idPrefix}-panel-${idSegment(def.id)}`;
2697
+ panelEl.setAttribute("aria-labelledby", tabEl.id);
2698
+ tabEl.setAttribute("aria-controls", panelEl.id);
2699
+ }
2700
+ }
2701
+ const fxTeardown = domBinding(() => {
2702
+ const active = activeTab();
2703
+ for (const def of tabDefs) {
2704
+ const tabEl = els.tabs[def.id];
2705
+ if (!tabEl) continue;
2706
+ const isAct = def.id === active;
2707
+ tabEl.setAttribute("aria-selected", isAct ? "true" : "false");
2708
+ tabEl.tabIndex = isAct ? 0 : -1;
2709
+ const panelEl = els.panels?.[def.id];
2710
+ if (panelEl) panelEl.hidden = !isAct;
2711
+ }
2712
+ }, els.tablist);
2713
+ const onKey = (e) => {
2714
+ if (isWidgetKeyEvent(e)) return;
2715
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
2716
+ e.preventDefault();
2717
+ claimWidgetKey(e, "tabs");
2718
+ nextTab();
2719
+ els.tabs[activeTab()]?.focus();
2720
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
2721
+ e.preventDefault();
2722
+ claimWidgetKey(e, "tabs");
2723
+ prevTab();
2724
+ els.tabs[activeTab()]?.focus();
2725
+ } else if (e.key === "Home") {
2726
+ e.preventDefault();
2727
+ claimWidgetKey(e, "tabs");
2728
+ const first = tabDefs.find((t) => !t.disabled);
2729
+ if (first) {
2730
+ setActiveTabState(first.id);
2731
+ els.tabs[first.id]?.focus();
2732
+ }
2733
+ } else if (e.key === "End") {
2734
+ e.preventDefault();
2735
+ claimWidgetKey(e, "tabs");
2736
+ for (let i = tabDefs.length - 1; i >= 0; i--) {
2737
+ if (!tabDefs[i].disabled) {
2738
+ setActiveTabState(tabDefs[i].id);
2739
+ els.tabs[tabDefs[i].id]?.focus();
2740
+ break;
2741
+ }
2742
+ }
2335
2743
  }
2744
+ };
2745
+ els.tablist.addEventListener("keydown", onKey);
2746
+ const clickHandlers = [];
2747
+ for (const def of tabDefs) {
2748
+ const tabEl = els.tabs[def.id];
2749
+ if (!tabEl) continue;
2750
+ const fn = () => setActiveTab(def.id);
2751
+ tabEl.addEventListener("click", fn);
2752
+ clickHandlers.push({ el: tabEl, fn });
2336
2753
  }
2337
- return days;
2338
- });
2339
- function isSelected(date) {
2340
- const sel = selectedDate();
2341
- return sel !== null && sameDay(date, sel);
2754
+ let tornDown = false;
2755
+ const teardown = () => {
2756
+ if (tornDown) return;
2757
+ tornDown = true;
2758
+ boundTablists.delete(els.tablist);
2759
+ fxTeardown();
2760
+ els.tablist.removeEventListener("keydown", onKey);
2761
+ for (const { el, fn } of clickHandlers) el.removeEventListener("click", fn);
2762
+ for (const r of restore) r();
2763
+ };
2764
+ boundTablists.set(els.tablist, teardown);
2765
+ return teardown;
2342
2766
  }
2343
2767
  return {
2344
- selectedDate,
2345
- select: select2,
2346
- viewDate,
2347
- setViewDate,
2348
- nextMonth,
2349
- prevMonth,
2350
- nextYear,
2351
- prevYear,
2352
- daysInMonth,
2353
- isDateDisabled,
2354
- /** Reactive check — use inside class bindings for per-day reactivity */
2355
- isSelected,
2768
+ activeTab,
2769
+ setActiveTab,
2770
+ tabs: tabs2,
2771
+ nextTab,
2772
+ prevTab,
2773
+ /** Reactive check — use inside class/nodes bindings for per-tab reactivity */
2774
+ isActive,
2356
2775
  bind
2357
2776
  };
2777
+ }
2778
+
2779
+ // src/widgets/Tooltip.ts
2780
+ var boundTriggers = /* @__PURE__ */ new WeakMap();
2781
+ function tooltip(options) {
2782
+ const delay = options?.delay ?? 0;
2783
+ const hideDelay = options?.hideDelay ?? 100;
2784
+ const [isVisible, setIsVisible] = signal(false);
2785
+ const [content, setContent] = signal("");
2786
+ let delayTimer = null;
2787
+ let hideTimer = null;
2788
+ function show() {
2789
+ if (hideTimer !== null) {
2790
+ clearTimeout(hideTimer);
2791
+ hideTimer = null;
2792
+ }
2793
+ if (delay > 0) {
2794
+ if (delayTimer !== null) clearTimeout(delayTimer);
2795
+ delayTimer = setTimeout(() => {
2796
+ setIsVisible(true);
2797
+ delayTimer = null;
2798
+ }, delay);
2799
+ } else {
2800
+ setIsVisible(true);
2801
+ }
2802
+ }
2803
+ function hide() {
2804
+ if (delayTimer !== null) {
2805
+ clearTimeout(delayTimer);
2806
+ delayTimer = null;
2807
+ }
2808
+ setIsVisible(false);
2809
+ }
2810
+ function scheduleHide() {
2811
+ if (delayTimer !== null) {
2812
+ clearTimeout(delayTimer);
2813
+ delayTimer = null;
2814
+ }
2815
+ if (hideTimer !== null) clearTimeout(hideTimer);
2816
+ hideTimer = setTimeout(() => {
2817
+ hideTimer = null;
2818
+ setIsVisible(false);
2819
+ }, hideDelay);
2820
+ }
2358
2821
  function bind(els) {
2359
- const existing = boundDatePickers.get(els.grid);
2822
+ const existing = boundTriggers.get(els.trigger);
2360
2823
  if (existing) return existing;
2361
- const prevGridRole = els.grid.getAttribute("role");
2362
- const prevGridTabIndex = els.grid.getAttribute("tabindex");
2363
- const prevGridLabel = els.grid.getAttribute("aria-label");
2364
- els.grid.setAttribute("role", "grid");
2365
- if (els.grid.tabIndex < 0) els.grid.tabIndex = 0;
2366
- let pendingFocus = false;
2367
- const touchedCells = /* @__PURE__ */ new Map();
2368
- const restoreAttr = (el, name, value) => {
2369
- if (value === null) el.removeAttribute(name);
2370
- else el.setAttribute(name, value);
2371
- };
2372
- const restoreCell = (cell, snap) => {
2373
- restoreAttr(cell, "role", snap.role);
2374
- restoreAttr(cell, "aria-selected", snap.selected);
2375
- restoreAttr(cell, "aria-disabled", snap.disabled);
2376
- restoreAttr(cell, "tabindex", snap.tabindex);
2377
- };
2824
+ const id = createId("sibu-tooltip");
2825
+ els.tooltip.setAttribute("role", "tooltip");
2826
+ els.tooltip.id = id;
2827
+ const prevDescribedBy = els.trigger.getAttribute("aria-describedby");
2828
+ els.trigger.setAttribute("aria-describedby", prevDescribedBy ? `${prevDescribedBy} ${id}` : id);
2378
2829
  const fxTeardown = domBinding(() => {
2379
- const sel = selectedDate();
2380
- const view = viewDate();
2381
- const days = daysInMonth();
2382
- els.grid.setAttribute("aria-label", view.toLocaleDateString(void 0, { month: "long", year: "numeric" }));
2383
- let viewCell = null;
2384
- const displayed = /* @__PURE__ */ new Set();
2385
- for (const d of days) {
2386
- const cell = els.cell(d.date);
2387
- if (!cell) continue;
2388
- displayed.add(cell);
2389
- if (!touchedCells.has(cell)) {
2390
- touchedCells.set(cell, {
2391
- role: cell.getAttribute("role"),
2392
- selected: cell.getAttribute("aria-selected"),
2393
- disabled: cell.getAttribute("aria-disabled"),
2394
- tabindex: cell.getAttribute("tabindex")
2395
- });
2396
- }
2397
- cell.setAttribute("role", "gridcell");
2398
- cell.setAttribute("aria-selected", sel && isSameCalendarDay(sel, d.date) ? "true" : "false");
2399
- if (d.isDisabled) cell.setAttribute("aria-disabled", "true");
2400
- else cell.removeAttribute("aria-disabled");
2401
- const isView = isSameCalendarDay(view, d.date);
2402
- cell.tabIndex = isView ? 0 : -1;
2403
- if (isView) viewCell = cell;
2404
- }
2405
- for (const [cell, snap] of touchedCells) {
2406
- if (!displayed.has(cell)) {
2407
- restoreCell(cell, snap);
2408
- touchedCells.delete(cell);
2409
- }
2410
- }
2411
- if (pendingFocus && viewCell && typeof viewCell.focus === "function") {
2412
- pendingFocus = false;
2413
- viewCell.focus();
2830
+ els.tooltip.hidden = !isVisible();
2831
+ }, els.trigger);
2832
+ const onTriggerEnter = () => show();
2833
+ const onTriggerLeave = () => scheduleHide();
2834
+ const onTooltipEnter = () => {
2835
+ if (hideTimer !== null) {
2836
+ clearTimeout(hideTimer);
2837
+ hideTimer = null;
2414
2838
  }
2415
- }, els.grid);
2416
- function isSameCalendarDay(a, b) {
2417
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2418
- }
2419
- function shiftDays(delta) {
2420
- setViewDate((prev) => new Date(prev.getFullYear(), prev.getMonth(), prev.getDate() + delta));
2421
- }
2839
+ };
2840
+ const onTooltipLeave = () => scheduleHide();
2841
+ const onFocus = () => show();
2842
+ const onBlur = () => hide();
2422
2843
  const onKey = (e) => {
2423
- if (e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Home" || e.key === "End" || e.key === "PageUp" || e.key === "PageDown") {
2424
- pendingFocus = true;
2425
- }
2426
- switch (e.key) {
2427
- case "ArrowLeft":
2428
- e.preventDefault();
2429
- shiftDays(-1);
2430
- break;
2431
- case "ArrowRight":
2432
- e.preventDefault();
2433
- shiftDays(1);
2434
- break;
2435
- case "ArrowUp":
2436
- e.preventDefault();
2437
- shiftDays(-7);
2438
- break;
2439
- case "ArrowDown":
2440
- e.preventDefault();
2441
- shiftDays(7);
2442
- break;
2443
- case "Home":
2444
- e.preventDefault();
2445
- setViewDate((p) => new Date(p.getFullYear(), p.getMonth(), p.getDate() - p.getDay()));
2446
- break;
2447
- case "End": {
2448
- e.preventDefault();
2449
- setViewDate((p) => new Date(p.getFullYear(), p.getMonth(), p.getDate() + (6 - p.getDay())));
2450
- break;
2451
- }
2452
- case "PageUp":
2453
- e.preventDefault();
2454
- if (e.shiftKey) prevYear();
2455
- else prevMonth();
2456
- break;
2457
- case "PageDown":
2458
- e.preventDefault();
2459
- if (e.shiftKey) nextYear();
2460
- else nextMonth();
2461
- break;
2462
- case "Enter":
2463
- case " ":
2464
- e.preventDefault();
2465
- select2(viewDate());
2466
- break;
2844
+ if (isWidgetKeyEvent(e)) return;
2845
+ if (e.key === "Escape" && isVisible()) {
2846
+ e.stopPropagation();
2847
+ claimWidgetKey(e, "tooltip");
2848
+ hide();
2467
2849
  }
2468
2850
  };
2469
- els.grid.addEventListener("keydown", onKey);
2851
+ els.trigger.addEventListener("pointerenter", onTriggerEnter);
2852
+ els.trigger.addEventListener("pointerleave", onTriggerLeave);
2853
+ els.trigger.addEventListener("focus", onFocus);
2854
+ els.trigger.addEventListener("blur", onBlur);
2855
+ els.trigger.addEventListener("keydown", onKey);
2856
+ els.tooltip.addEventListener("pointerenter", onTooltipEnter);
2857
+ els.tooltip.addEventListener("pointerleave", onTooltipLeave);
2470
2858
  let tornDown = false;
2471
2859
  const teardown = () => {
2472
2860
  if (tornDown) return;
2473
2861
  tornDown = true;
2474
- if (boundDatePickers.get(els.grid) === teardown) boundDatePickers.delete(els.grid);
2862
+ if (boundTriggers.get(els.trigger) === teardown) boundTriggers.delete(els.trigger);
2475
2863
  fxTeardown();
2476
- els.grid.removeEventListener("keydown", onKey);
2477
- if (prevGridRole === null) els.grid.removeAttribute("role");
2478
- else els.grid.setAttribute("role", prevGridRole);
2479
- if (prevGridTabIndex === null) els.grid.removeAttribute("tabindex");
2480
- else els.grid.setAttribute("tabindex", prevGridTabIndex);
2481
- if (prevGridLabel === null) els.grid.removeAttribute("aria-label");
2482
- else els.grid.setAttribute("aria-label", prevGridLabel);
2483
- for (const [cell, snap] of touchedCells) restoreCell(cell, snap);
2484
- touchedCells.clear();
2864
+ els.trigger.removeEventListener("pointerenter", onTriggerEnter);
2865
+ els.trigger.removeEventListener("pointerleave", onTriggerLeave);
2866
+ els.trigger.removeEventListener("focus", onFocus);
2867
+ els.trigger.removeEventListener("blur", onBlur);
2868
+ els.trigger.removeEventListener("keydown", onKey);
2869
+ els.tooltip.removeEventListener("pointerenter", onTooltipEnter);
2870
+ els.tooltip.removeEventListener("pointerleave", onTooltipLeave);
2871
+ const cur = els.trigger.getAttribute("aria-describedby");
2872
+ if (cur) {
2873
+ const remaining = cur.split(/\s+/).filter((part) => part && part !== id);
2874
+ if (remaining.length > 0) els.trigger.setAttribute("aria-describedby", remaining.join(" "));
2875
+ else els.trigger.removeAttribute("aria-describedby");
2876
+ } else if (prevDescribedBy) {
2877
+ els.trigger.setAttribute("aria-describedby", prevDescribedBy);
2878
+ }
2879
+ if (delayTimer !== null) clearTimeout(delayTimer);
2880
+ if (hideTimer !== null) clearTimeout(hideTimer);
2485
2881
  };
2486
- boundDatePickers.set(els.grid, teardown);
2882
+ boundTriggers.set(els.trigger, teardown);
2487
2883
  return teardown;
2488
2884
  }
2885
+ return { isVisible, show, hide, content, setContent, bind };
2489
2886
  }
2490
2887
  // Annotate the CommonJS export names for ESM import in node:
2491
2888
  0 && (module.exports = {
@@ -2494,8 +2891,11 @@ function datePicker(options) {
2494
2891
  contentEditable,
2495
2892
  datePicker,
2496
2893
  fileUpload,
2894
+ isWidgetKeyEvent,
2497
2895
  popover,
2498
2896
  select,
2897
+ selectMenu,
2499
2898
  tabs,
2500
- tooltip
2899
+ tooltip,
2900
+ widgetKeyOwner
2501
2901
  });