streetui 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { C as CompiledApplication } from './compile-Cte0nf06.js';
1
+ import { C as CompiledApplication } from './compile-DqsMQVJk.js';
2
2
 
3
3
  /**
4
4
  * DOMAdapter — framework-owned abstraction over DOM operations.
@@ -1,4 +1,4 @@
1
- import { C as CompiledApplication } from './compile-Cte0nf06.cjs';
1
+ import { C as CompiledApplication } from './compile-DqsMQVJk.cjs';
2
2
 
3
3
  /**
4
4
  * DOMAdapter — framework-owned abstraction over DOM operations.
package/dist/server.cjs CHANGED
@@ -644,18 +644,25 @@ function trapFocus(dom, container) {
644
644
  dom.addEventListener(container, "keydown", onKeydown);
645
645
  return () => dom.removeEventListener(container, "keydown", onKeydown);
646
646
  }
647
+ var containmentStack = [];
647
648
  function containFocus(dom, container) {
648
649
  const body = dom.body();
649
650
  if (body === null) return () => {
650
651
  };
652
+ containmentStack.push(container);
651
653
  const onFocusIn = (event) => {
654
+ if (containmentStack[containmentStack.length - 1] !== container) return;
652
655
  const target = event.target;
653
656
  if (target !== null && !dom.contains(container, target)) {
654
657
  focusFirst(dom, container);
655
658
  }
656
659
  };
657
660
  dom.addEventListener(body, "focusin", onFocusIn);
658
- return () => dom.removeEventListener(body, "focusin", onFocusIn);
661
+ return () => {
662
+ dom.removeEventListener(body, "focusin", onFocusIn);
663
+ const index = containmentStack.lastIndexOf(container);
664
+ if (index !== -1) containmentStack.splice(index, 1);
665
+ };
659
666
  }
660
667
  function onEscape(dom, target, handler) {
661
668
  const onKeydown = (event) => {
@@ -664,6 +671,33 @@ function onEscape(dom, target, handler) {
664
671
  dom.addEventListener(target, "keydown", onKeydown);
665
672
  return () => dom.removeEventListener(target, "keydown", onKeydown);
666
673
  }
674
+ function rovingMenu(dom, container, selector = FOCUSABLE_SELECTOR) {
675
+ const onKeydown = (event) => {
676
+ const key = event.key;
677
+ const isActivate = key === "Enter" || key === " " || key === "Spacebar";
678
+ const isMove = key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End";
679
+ if (!isActivate && !isMove) return;
680
+ const items = getFocusable(dom, container, selector);
681
+ if (items.length === 0) return;
682
+ const active = dom.activeElement();
683
+ const index = active === null ? -1 : items.indexOf(active);
684
+ if (isActivate) {
685
+ if (index < 0) return;
686
+ event.preventDefault();
687
+ items[index].click?.();
688
+ return;
689
+ }
690
+ event.preventDefault();
691
+ let next;
692
+ if (key === "Home") next = 0;
693
+ else if (key === "End") next = items.length - 1;
694
+ else if (key === "ArrowDown") next = index < 0 ? 0 : (index + 1) % items.length;
695
+ else next = index <= 0 ? items.length - 1 : index - 1;
696
+ dom.focus(items[next]);
697
+ };
698
+ dom.addEventListener(container, "keydown", onKeydown);
699
+ return () => dom.removeEventListener(container, "keydown", onKeydown);
700
+ }
667
701
 
668
702
  // ../renderer/src/tag-map.ts
669
703
  var TAG_MAP = {
@@ -736,7 +770,7 @@ function patchNode(ctx, graphNode, propKey, newValue) {
736
770
  }
737
771
 
738
772
  // ../renderer/src/reconciliation.ts
739
- function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
773
+ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn, hooks) {
740
774
  const oldByKey = /* @__PURE__ */ new Map();
741
775
  for (const inst of oldInstances) {
742
776
  const key = inst.graphNode.key ?? inst.graphNode.id;
@@ -757,28 +791,33 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
757
791
  }
758
792
  newInstances.push(existing);
759
793
  } else {
760
- const inst = mountFn(newNode, parentDom);
761
- newInstances.push(inst);
794
+ const reclaimed = hooks?.takeLeaving(key);
795
+ if (reclaimed !== void 0) {
796
+ patchExistingInstance(ctx, reclaimed, newNode);
797
+ reconcileItemChildren(ctx, reclaimed, newNode, mountFn);
798
+ hooks?.onEnter(reclaimed);
799
+ newInstances.push(reclaimed);
800
+ } else {
801
+ const inst = mountFn(newNode, parentDom);
802
+ hooks?.onEnter(inst);
803
+ newInstances.push(inst);
804
+ }
762
805
  }
763
806
  }
764
807
  const removed = [];
765
808
  for (const inst of oldInstances) {
766
809
  const key = inst.graphNode.key ?? inst.graphNode.id;
767
- if (!usedKeys.has(key)) {
768
- removed.push(inst);
769
- }
770
- }
771
- for (const inst of removed) {
810
+ if (usedKeys.has(key)) continue;
811
+ if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
772
812
  const parent = ctx.dom.parentNode(inst.domNode);
773
- if (parent !== null) {
774
- ctx.dom.removeChild(parent, inst.domNode);
775
- }
813
+ if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
776
814
  inst.dispose();
815
+ removed.push(inst);
777
816
  }
778
817
  reorderDom(ctx, parentDom, newInstances);
779
818
  return { instances: newInstances, removed };
780
819
  }
781
- function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
820
+ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn, hooks) {
782
821
  const oldByKey = /* @__PURE__ */ new Map();
783
822
  for (const inst of oldInstances) {
784
823
  oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
@@ -805,21 +844,34 @@ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
805
844
  }
806
845
  newInstances.push(existing);
807
846
  } else {
808
- const freshNode = entry.build();
809
- built.push(freshNode);
810
- const inst = mountFn(freshNode, parentDom);
811
- newInstances.push(inst);
847
+ const reclaimed = hooks?.takeLeaving(entry.key);
848
+ if (reclaimed !== void 0) {
849
+ const freshNode = entry.build();
850
+ built.push(freshNode);
851
+ patchExistingInstance(ctx, reclaimed, freshNode);
852
+ reconcileItemChildren(ctx, reclaimed, freshNode, mountFn);
853
+ reclaimed.graphNode.setProp("_sig", freshNode.getProp("_sig"));
854
+ reclaimed.graphNode.setProp("_item", entry.item);
855
+ hooks?.onEnter(reclaimed);
856
+ newInstances.push(reclaimed);
857
+ } else {
858
+ const freshNode = entry.build();
859
+ built.push(freshNode);
860
+ const inst = mountFn(freshNode, parentDom);
861
+ hooks?.onEnter(inst);
862
+ newInstances.push(inst);
863
+ }
812
864
  }
813
865
  }
814
866
  const removed = [];
815
867
  for (const inst of oldInstances) {
816
868
  const key = inst.graphNode.key ?? inst.graphNode.id;
817
- if (!usedKeys.has(key)) removed.push(inst);
818
- }
819
- for (const inst of removed) {
869
+ if (usedKeys.has(key)) continue;
870
+ if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
820
871
  const parent = ctx.dom.parentNode(inst.domNode);
821
872
  if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
822
873
  inst.dispose();
874
+ removed.push(inst);
823
875
  }
824
876
  reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
825
877
  return { instances: newInstances, removed, built };
@@ -945,6 +997,176 @@ function patchExistingInstance(ctx, instance, newNode) {
945
997
  }
946
998
  }
947
999
 
1000
+ // ../renderer/src/transition.ts
1001
+ function getResolvedTransition(graph, nodeId) {
1002
+ const fn = graph.getHandler(`__transition__${nodeId}`);
1003
+ return fn === void 0 ? void 0 : fn();
1004
+ }
1005
+ function host() {
1006
+ return globalThis;
1007
+ }
1008
+ function nextFrame(cb) {
1009
+ const h = host();
1010
+ const raf = h.requestAnimationFrame;
1011
+ if (typeof raf === "function") {
1012
+ raf(() => raf(cb));
1013
+ } else {
1014
+ h.setTimeout(cb, 0);
1015
+ }
1016
+ }
1017
+ function splitClass(value) {
1018
+ if (value === null) return [];
1019
+ const out = [];
1020
+ for (const t of value.split(/\s+/)) if (t.length > 0) out.push(t);
1021
+ return out;
1022
+ }
1023
+ function addClasses(dom, el, classes) {
1024
+ if (classes.length === 0) return;
1025
+ const current = splitClass(dom.getAttribute(el, "class"));
1026
+ let changed = false;
1027
+ for (const c of classes) {
1028
+ if (!current.includes(c)) {
1029
+ current.push(c);
1030
+ changed = true;
1031
+ }
1032
+ }
1033
+ if (changed) dom.setAttribute(el, "class", current.join(" "));
1034
+ }
1035
+ function removeClasses(dom, el, classes) {
1036
+ if (classes.length === 0) return;
1037
+ const current = splitClass(dom.getAttribute(el, "class"));
1038
+ const next = current.filter((c) => !classes.includes(c));
1039
+ if (next.length !== current.length) {
1040
+ if (next.length === 0) dom.removeAttribute(el, "class");
1041
+ else dom.setAttribute(el, "class", next.join(" "));
1042
+ }
1043
+ }
1044
+ function startRun(dom, el, active, from, to, duration, onDone) {
1045
+ const h = host();
1046
+ let finished = false;
1047
+ let timer = null;
1048
+ const onEvent = (e) => {
1049
+ if (e.target !== el) return;
1050
+ finish();
1051
+ };
1052
+ const detach = () => {
1053
+ dom.removeEventListener(el, "transitionend", onEvent);
1054
+ dom.removeEventListener(el, "animationend", onEvent);
1055
+ if (timer !== null) {
1056
+ h.clearTimeout(timer);
1057
+ timer = null;
1058
+ }
1059
+ };
1060
+ const finish = () => {
1061
+ if (finished) return;
1062
+ finished = true;
1063
+ detach();
1064
+ removeClasses(dom, el, active);
1065
+ removeClasses(dom, el, to);
1066
+ removeClasses(dom, el, from);
1067
+ onDone();
1068
+ };
1069
+ addClasses(dom, el, from);
1070
+ addClasses(dom, el, active);
1071
+ dom.addEventListener(el, "transitionend", onEvent);
1072
+ dom.addEventListener(el, "animationend", onEvent);
1073
+ timer = h.setTimeout(finish, duration);
1074
+ nextFrame(() => {
1075
+ if (finished) return;
1076
+ removeClasses(dom, el, from);
1077
+ addClasses(dom, el, to);
1078
+ });
1079
+ return {
1080
+ cancel: () => {
1081
+ if (finished) return;
1082
+ finished = true;
1083
+ detach();
1084
+ removeClasses(dom, el, active);
1085
+ removeClasses(dom, el, to);
1086
+ removeClasses(dom, el, from);
1087
+ }
1088
+ };
1089
+ }
1090
+ var TransitionController = class {
1091
+ constructor(dom, graph, finalize) {
1092
+ this.dom = dom;
1093
+ this.graph = graph;
1094
+ this.finalize = finalize;
1095
+ }
1096
+ leaving = /* @__PURE__ */ new Map();
1097
+ /** True only in a real DOM environment (browser). */
1098
+ get browser() {
1099
+ return this.dom.body() !== null;
1100
+ }
1101
+ keyOf(inst) {
1102
+ return inst.graphNode.key ?? inst.graphNode.id;
1103
+ }
1104
+ resolved(node) {
1105
+ return getResolvedTransition(this.graph, node.id);
1106
+ }
1107
+ /** Run the enter animation for `inst` if it carries a transition (browser only). */
1108
+ enter(inst) {
1109
+ if (!this.browser) return;
1110
+ const rt = this.resolved(inst.graphNode);
1111
+ if (rt === void 0) return;
1112
+ const el = inst.domNode;
1113
+ if (!this.dom.isElement(el)) return;
1114
+ startRun(this.dom, el, rt.enterActive, rt.enterFrom, rt.enterTo, rt.duration, () => {
1115
+ });
1116
+ }
1117
+ /**
1118
+ * Play `appear` for any initial child that opted into it (fresh browser mount
1119
+ * only — hydration must never animate appear, §22, and this is called only on
1120
+ * the mount path).
1121
+ */
1122
+ appear(children) {
1123
+ if (!this.browser) return;
1124
+ for (const child of children) {
1125
+ const rt = this.resolved(child.graphNode);
1126
+ if (rt !== void 0 && rt.appear) this.enter(child);
1127
+ }
1128
+ }
1129
+ hooks() {
1130
+ return {
1131
+ takeLeaving: (key) => {
1132
+ const entry = this.leaving.get(key);
1133
+ if (entry === void 0) return void 0;
1134
+ entry.run.cancel();
1135
+ this.leaving.delete(key);
1136
+ return entry.inst;
1137
+ },
1138
+ beginLeave: (inst) => {
1139
+ if (!this.browser) return false;
1140
+ const rt = this.resolved(inst.graphNode);
1141
+ if (rt === void 0) return false;
1142
+ const el = inst.domNode;
1143
+ if (!this.dom.isElement(el)) return false;
1144
+ const key = this.keyOf(inst);
1145
+ const prior = this.leaving.get(key);
1146
+ if (prior !== void 0) prior.run.cancel();
1147
+ const run = startRun(
1148
+ this.dom,
1149
+ el,
1150
+ rt.leaveActive,
1151
+ rt.leaveFrom,
1152
+ rt.leaveTo,
1153
+ rt.duration,
1154
+ () => {
1155
+ const current = this.leaving.get(key);
1156
+ if (current !== void 0 && current.run === run) {
1157
+ this.leaving.delete(key);
1158
+ this.finalize(inst);
1159
+ }
1160
+ }
1161
+ );
1162
+ this.leaving.set(key, { inst, run });
1163
+ return true;
1164
+ },
1165
+ onEnter: (inst) => this.enter(inst)
1166
+ };
1167
+ }
1168
+ };
1169
+
948
1170
  // ../renderer/src/mount.ts
949
1171
  var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
950
1172
  "text",
@@ -1097,7 +1319,7 @@ function mountNode(ctx, graphNode, parentDom) {
1097
1319
  instance2.addChild(childInstance);
1098
1320
  }
1099
1321
  dom.appendChild(parentDom, el2);
1100
- wireReactiveList(ctx, graphNode, instance2, el2);
1322
+ wireReactiveList(ctx, graphNode, instance2, el2, true);
1101
1323
  return instance2;
1102
1324
  }
1103
1325
  if (graphNode.type === "portal") {
@@ -1141,6 +1363,7 @@ function mountNode(ctx, graphNode, parentDom) {
1141
1363
  instance.addChild(childInstance);
1142
1364
  }
1143
1365
  dom.appendChild(parentDom, el);
1366
+ if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
1144
1367
  return instance;
1145
1368
  }
1146
1369
  function textUpdate(dom, el, textNode) {
@@ -1205,32 +1428,42 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
1205
1428
  instance.trackCleanup(unsub);
1206
1429
  }
1207
1430
  }
1208
- function wireReactiveList(ctx, graphNode, instance, el) {
1431
+ function wireReactiveList(ctx, graphNode, instance, el, runAppear = false) {
1209
1432
  const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
1210
1433
  const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
1211
1434
  if (plan === void 0 && build === void 0) return;
1435
+ const controller = new TransitionController(ctx.dom, ctx.graph, (leaving) => {
1436
+ const parent = ctx.dom.parentNode(leaving.domNode);
1437
+ if (parent !== null) ctx.dom.removeChild(parent, leaving.domNode);
1438
+ leaving.dispose();
1439
+ forgetInstance(ctx, leaving);
1440
+ ctx.graph.detachNode(leaving.graphNode);
1441
+ });
1442
+ const hooks = controller.hooks();
1443
+ if (runAppear) controller.appear(instance.children);
1212
1444
  for (const stateRef of graphNode.stateRefs) {
1213
1445
  if (stateRef.propKey !== "items") continue;
1214
1446
  const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
1215
1447
  if (sig === void 0 || typeof sig.subscribe !== "function") continue;
1216
1448
  const unsub = sig.subscribe((value) => {
1217
1449
  if (plan !== void 0) {
1218
- reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
1450
+ reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value), hooks);
1219
1451
  } else {
1220
- reconcileReactiveList(ctx, graphNode, instance, el, build(value));
1452
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value), hooks);
1221
1453
  }
1222
1454
  });
1223
1455
  instance.trackCleanup(unsub);
1224
1456
  }
1225
1457
  }
1226
- function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
1458
+ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan, hooks) {
1227
1459
  const oldInstances = [...listInstance.children];
1228
1460
  const result = reconcileChildrenByPlan(
1229
1461
  ctx,
1230
1462
  listEl,
1231
1463
  oldInstances,
1232
1464
  plan,
1233
- (node, parent) => mountNode(ctx, node, parent)
1465
+ (node, parent) => mountNode(ctx, node, parent),
1466
+ hooks
1234
1467
  );
1235
1468
  listInstance.children.length = 0;
1236
1469
  for (const inst of result.instances) listInstance.children.push(inst);
@@ -1245,14 +1478,15 @@ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan)
1245
1478
  for (const child of [...listNode.children]) listNode.removeChild(child);
1246
1479
  for (const inst of result.instances) listNode.appendChild(inst.graphNode);
1247
1480
  }
1248
- function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
1481
+ function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes, hooks) {
1249
1482
  const oldInstances = [...listInstance.children];
1250
1483
  const result = reconcileChildren(
1251
1484
  ctx,
1252
1485
  listEl,
1253
1486
  oldInstances,
1254
1487
  newNodes,
1255
- (node, parent) => mountNode(ctx, node, parent)
1488
+ (node, parent) => mountNode(ctx, node, parent),
1489
+ hooks
1256
1490
  );
1257
1491
  listInstance.children.length = 0;
1258
1492
  for (const inst of result.instances) listInstance.children.push(inst);
@@ -1288,11 +1522,14 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
1288
1522
  const onOpenChange = (isOpen) => {
1289
1523
  if (isOpen) {
1290
1524
  if (desc.restoreFocus) saved = saveFocus(dom);
1291
- if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
1292
1525
  if (desc.modal) {
1293
1526
  active.push(trapFocus(dom, target));
1294
1527
  active.push(containFocus(dom, target));
1295
1528
  }
1529
+ if (desc.menu) {
1530
+ active.push(rovingMenu(dom, target));
1531
+ }
1532
+ if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
1296
1533
  if (desc.closeOnEscape && desc.onClose !== void 0) {
1297
1534
  active.push(onEscape(dom, target, desc.onClose));
1298
1535
  }
@@ -1309,6 +1546,11 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
1309
1546
  instance.trackCleanup(teardown);
1310
1547
  if (openSig.peek() === true) onOpenChange(true);
1311
1548
  }
1549
+ function wireComponentBehavior(ctx, graphNode, instance) {
1550
+ const fn = ctx.graph.getHandler(`__component__${graphNode.id}`);
1551
+ if (fn === void 0) return;
1552
+ for (const cleanup of fn()) instance.trackCleanup(cleanup);
1553
+ }
1312
1554
 
1313
1555
  // ../renderer/src/dehydrate.ts
1314
1556
  var STATE_MARKER_ATTR = "data-streetui-state";
@@ -1376,7 +1618,8 @@ function analyzeGraph(graph) {
1376
1618
  const isList = node.type === "reactive-list";
1377
1619
  const isConditional = node.type === "conditional";
1378
1620
  const isPortal = node.type === "portal";
1379
- const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal;
1621
+ const isComponent = node.type === "component";
1622
+ const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent;
1380
1623
  const isStaticSubtree = isStatic && allChildrenStatic;
1381
1624
  nodes.set(node.id, {
1382
1625
  isStatic,
@@ -1463,7 +1706,7 @@ function renderToString(compiled, options = {}) {
1463
1706
  }
1464
1707
 
1465
1708
  // src/version.ts
1466
- var VERSION = "1.7.0";
1709
+ var VERSION = "1.9.0";
1467
1710
  // Annotate the CommonJS export names for ESM import in node:
1468
1711
  0 && (module.exports = {
1469
1712
  STATE_MARKER_ATTR,