streetui 1.8.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.
package/dist/server.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- export { R as RenderToStringOptions, S as STATE_MARKER_ATTR, a as ServerDOMAdapter, r as readState, b as renderToString, s as serializeState } from './server-CnLEnlYU.cjs';
2
- export { V as VERSION } from './compile-CJJQYi71.cjs';
1
+ export { R as RenderToStringOptions, S as STATE_MARKER_ATTR, a as ServerDOMAdapter, r as readState, b as renderToString, s as serializeState } from './server-DnwqBTfE.cjs';
2
+ export { V as VERSION } from './compile-DqsMQVJk.cjs';
package/dist/server.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { R as RenderToStringOptions, S as STATE_MARKER_ATTR, a as ServerDOMAdapter, r as readState, b as renderToString, s as serializeState } from './server-DNlg7ou2.js';
2
- export { V as VERSION } from './compile-CJJQYi71.js';
1
+ export { R as RenderToStringOptions, S as STATE_MARKER_ATTR, a as ServerDOMAdapter, r as readState, b as renderToString, s as serializeState } from './server-BYnmb_hR.js';
2
+ export { V as VERSION } from './compile-DqsMQVJk.js';
package/dist/server.js CHANGED
@@ -613,18 +613,25 @@ function trapFocus(dom, container) {
613
613
  dom.addEventListener(container, "keydown", onKeydown);
614
614
  return () => dom.removeEventListener(container, "keydown", onKeydown);
615
615
  }
616
+ var containmentStack = [];
616
617
  function containFocus(dom, container) {
617
618
  const body = dom.body();
618
619
  if (body === null) return () => {
619
620
  };
621
+ containmentStack.push(container);
620
622
  const onFocusIn = (event) => {
623
+ if (containmentStack[containmentStack.length - 1] !== container) return;
621
624
  const target = event.target;
622
625
  if (target !== null && !dom.contains(container, target)) {
623
626
  focusFirst(dom, container);
624
627
  }
625
628
  };
626
629
  dom.addEventListener(body, "focusin", onFocusIn);
627
- return () => dom.removeEventListener(body, "focusin", onFocusIn);
630
+ return () => {
631
+ dom.removeEventListener(body, "focusin", onFocusIn);
632
+ const index = containmentStack.lastIndexOf(container);
633
+ if (index !== -1) containmentStack.splice(index, 1);
634
+ };
628
635
  }
629
636
  function onEscape(dom, target, handler) {
630
637
  const onKeydown = (event) => {
@@ -633,6 +640,33 @@ function onEscape(dom, target, handler) {
633
640
  dom.addEventListener(target, "keydown", onKeydown);
634
641
  return () => dom.removeEventListener(target, "keydown", onKeydown);
635
642
  }
643
+ function rovingMenu(dom, container, selector = FOCUSABLE_SELECTOR) {
644
+ const onKeydown = (event) => {
645
+ const key = event.key;
646
+ const isActivate = key === "Enter" || key === " " || key === "Spacebar";
647
+ const isMove = key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End";
648
+ if (!isActivate && !isMove) return;
649
+ const items = getFocusable(dom, container, selector);
650
+ if (items.length === 0) return;
651
+ const active = dom.activeElement();
652
+ const index = active === null ? -1 : items.indexOf(active);
653
+ if (isActivate) {
654
+ if (index < 0) return;
655
+ event.preventDefault();
656
+ items[index].click?.();
657
+ return;
658
+ }
659
+ event.preventDefault();
660
+ let next;
661
+ if (key === "Home") next = 0;
662
+ else if (key === "End") next = items.length - 1;
663
+ else if (key === "ArrowDown") next = index < 0 ? 0 : (index + 1) % items.length;
664
+ else next = index <= 0 ? items.length - 1 : index - 1;
665
+ dom.focus(items[next]);
666
+ };
667
+ dom.addEventListener(container, "keydown", onKeydown);
668
+ return () => dom.removeEventListener(container, "keydown", onKeydown);
669
+ }
636
670
 
637
671
  // ../renderer/src/tag-map.ts
638
672
  var TAG_MAP = {
@@ -705,7 +739,7 @@ function patchNode(ctx, graphNode, propKey, newValue) {
705
739
  }
706
740
 
707
741
  // ../renderer/src/reconciliation.ts
708
- function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
742
+ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn, hooks) {
709
743
  const oldByKey = /* @__PURE__ */ new Map();
710
744
  for (const inst of oldInstances) {
711
745
  const key = inst.graphNode.key ?? inst.graphNode.id;
@@ -726,28 +760,33 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
726
760
  }
727
761
  newInstances.push(existing);
728
762
  } else {
729
- const inst = mountFn(newNode, parentDom);
730
- newInstances.push(inst);
763
+ const reclaimed = hooks?.takeLeaving(key);
764
+ if (reclaimed !== void 0) {
765
+ patchExistingInstance(ctx, reclaimed, newNode);
766
+ reconcileItemChildren(ctx, reclaimed, newNode, mountFn);
767
+ hooks?.onEnter(reclaimed);
768
+ newInstances.push(reclaimed);
769
+ } else {
770
+ const inst = mountFn(newNode, parentDom);
771
+ hooks?.onEnter(inst);
772
+ newInstances.push(inst);
773
+ }
731
774
  }
732
775
  }
733
776
  const removed = [];
734
777
  for (const inst of oldInstances) {
735
778
  const key = inst.graphNode.key ?? inst.graphNode.id;
736
- if (!usedKeys.has(key)) {
737
- removed.push(inst);
738
- }
739
- }
740
- for (const inst of removed) {
779
+ if (usedKeys.has(key)) continue;
780
+ if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
741
781
  const parent = ctx.dom.parentNode(inst.domNode);
742
- if (parent !== null) {
743
- ctx.dom.removeChild(parent, inst.domNode);
744
- }
782
+ if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
745
783
  inst.dispose();
784
+ removed.push(inst);
746
785
  }
747
786
  reorderDom(ctx, parentDom, newInstances);
748
787
  return { instances: newInstances, removed };
749
788
  }
750
- function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
789
+ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn, hooks) {
751
790
  const oldByKey = /* @__PURE__ */ new Map();
752
791
  for (const inst of oldInstances) {
753
792
  oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
@@ -774,21 +813,34 @@ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
774
813
  }
775
814
  newInstances.push(existing);
776
815
  } else {
777
- const freshNode = entry.build();
778
- built.push(freshNode);
779
- const inst = mountFn(freshNode, parentDom);
780
- newInstances.push(inst);
816
+ const reclaimed = hooks?.takeLeaving(entry.key);
817
+ if (reclaimed !== void 0) {
818
+ const freshNode = entry.build();
819
+ built.push(freshNode);
820
+ patchExistingInstance(ctx, reclaimed, freshNode);
821
+ reconcileItemChildren(ctx, reclaimed, freshNode, mountFn);
822
+ reclaimed.graphNode.setProp("_sig", freshNode.getProp("_sig"));
823
+ reclaimed.graphNode.setProp("_item", entry.item);
824
+ hooks?.onEnter(reclaimed);
825
+ newInstances.push(reclaimed);
826
+ } else {
827
+ const freshNode = entry.build();
828
+ built.push(freshNode);
829
+ const inst = mountFn(freshNode, parentDom);
830
+ hooks?.onEnter(inst);
831
+ newInstances.push(inst);
832
+ }
781
833
  }
782
834
  }
783
835
  const removed = [];
784
836
  for (const inst of oldInstances) {
785
837
  const key = inst.graphNode.key ?? inst.graphNode.id;
786
- if (!usedKeys.has(key)) removed.push(inst);
787
- }
788
- for (const inst of removed) {
838
+ if (usedKeys.has(key)) continue;
839
+ if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
789
840
  const parent = ctx.dom.parentNode(inst.domNode);
790
841
  if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
791
842
  inst.dispose();
843
+ removed.push(inst);
792
844
  }
793
845
  reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
794
846
  return { instances: newInstances, removed, built };
@@ -914,6 +966,176 @@ function patchExistingInstance(ctx, instance, newNode) {
914
966
  }
915
967
  }
916
968
 
969
+ // ../renderer/src/transition.ts
970
+ function getResolvedTransition(graph, nodeId) {
971
+ const fn = graph.getHandler(`__transition__${nodeId}`);
972
+ return fn === void 0 ? void 0 : fn();
973
+ }
974
+ function host() {
975
+ return globalThis;
976
+ }
977
+ function nextFrame(cb) {
978
+ const h = host();
979
+ const raf = h.requestAnimationFrame;
980
+ if (typeof raf === "function") {
981
+ raf(() => raf(cb));
982
+ } else {
983
+ h.setTimeout(cb, 0);
984
+ }
985
+ }
986
+ function splitClass(value) {
987
+ if (value === null) return [];
988
+ const out = [];
989
+ for (const t of value.split(/\s+/)) if (t.length > 0) out.push(t);
990
+ return out;
991
+ }
992
+ function addClasses(dom, el, classes) {
993
+ if (classes.length === 0) return;
994
+ const current = splitClass(dom.getAttribute(el, "class"));
995
+ let changed = false;
996
+ for (const c of classes) {
997
+ if (!current.includes(c)) {
998
+ current.push(c);
999
+ changed = true;
1000
+ }
1001
+ }
1002
+ if (changed) dom.setAttribute(el, "class", current.join(" "));
1003
+ }
1004
+ function removeClasses(dom, el, classes) {
1005
+ if (classes.length === 0) return;
1006
+ const current = splitClass(dom.getAttribute(el, "class"));
1007
+ const next = current.filter((c) => !classes.includes(c));
1008
+ if (next.length !== current.length) {
1009
+ if (next.length === 0) dom.removeAttribute(el, "class");
1010
+ else dom.setAttribute(el, "class", next.join(" "));
1011
+ }
1012
+ }
1013
+ function startRun(dom, el, active, from, to, duration, onDone) {
1014
+ const h = host();
1015
+ let finished = false;
1016
+ let timer = null;
1017
+ const onEvent = (e) => {
1018
+ if (e.target !== el) return;
1019
+ finish();
1020
+ };
1021
+ const detach = () => {
1022
+ dom.removeEventListener(el, "transitionend", onEvent);
1023
+ dom.removeEventListener(el, "animationend", onEvent);
1024
+ if (timer !== null) {
1025
+ h.clearTimeout(timer);
1026
+ timer = null;
1027
+ }
1028
+ };
1029
+ const finish = () => {
1030
+ if (finished) return;
1031
+ finished = true;
1032
+ detach();
1033
+ removeClasses(dom, el, active);
1034
+ removeClasses(dom, el, to);
1035
+ removeClasses(dom, el, from);
1036
+ onDone();
1037
+ };
1038
+ addClasses(dom, el, from);
1039
+ addClasses(dom, el, active);
1040
+ dom.addEventListener(el, "transitionend", onEvent);
1041
+ dom.addEventListener(el, "animationend", onEvent);
1042
+ timer = h.setTimeout(finish, duration);
1043
+ nextFrame(() => {
1044
+ if (finished) return;
1045
+ removeClasses(dom, el, from);
1046
+ addClasses(dom, el, to);
1047
+ });
1048
+ return {
1049
+ cancel: () => {
1050
+ if (finished) return;
1051
+ finished = true;
1052
+ detach();
1053
+ removeClasses(dom, el, active);
1054
+ removeClasses(dom, el, to);
1055
+ removeClasses(dom, el, from);
1056
+ }
1057
+ };
1058
+ }
1059
+ var TransitionController = class {
1060
+ constructor(dom, graph, finalize) {
1061
+ this.dom = dom;
1062
+ this.graph = graph;
1063
+ this.finalize = finalize;
1064
+ }
1065
+ leaving = /* @__PURE__ */ new Map();
1066
+ /** True only in a real DOM environment (browser). */
1067
+ get browser() {
1068
+ return this.dom.body() !== null;
1069
+ }
1070
+ keyOf(inst) {
1071
+ return inst.graphNode.key ?? inst.graphNode.id;
1072
+ }
1073
+ resolved(node) {
1074
+ return getResolvedTransition(this.graph, node.id);
1075
+ }
1076
+ /** Run the enter animation for `inst` if it carries a transition (browser only). */
1077
+ enter(inst) {
1078
+ if (!this.browser) return;
1079
+ const rt = this.resolved(inst.graphNode);
1080
+ if (rt === void 0) return;
1081
+ const el = inst.domNode;
1082
+ if (!this.dom.isElement(el)) return;
1083
+ startRun(this.dom, el, rt.enterActive, rt.enterFrom, rt.enterTo, rt.duration, () => {
1084
+ });
1085
+ }
1086
+ /**
1087
+ * Play `appear` for any initial child that opted into it (fresh browser mount
1088
+ * only — hydration must never animate appear, §22, and this is called only on
1089
+ * the mount path).
1090
+ */
1091
+ appear(children) {
1092
+ if (!this.browser) return;
1093
+ for (const child of children) {
1094
+ const rt = this.resolved(child.graphNode);
1095
+ if (rt !== void 0 && rt.appear) this.enter(child);
1096
+ }
1097
+ }
1098
+ hooks() {
1099
+ return {
1100
+ takeLeaving: (key) => {
1101
+ const entry = this.leaving.get(key);
1102
+ if (entry === void 0) return void 0;
1103
+ entry.run.cancel();
1104
+ this.leaving.delete(key);
1105
+ return entry.inst;
1106
+ },
1107
+ beginLeave: (inst) => {
1108
+ if (!this.browser) return false;
1109
+ const rt = this.resolved(inst.graphNode);
1110
+ if (rt === void 0) return false;
1111
+ const el = inst.domNode;
1112
+ if (!this.dom.isElement(el)) return false;
1113
+ const key = this.keyOf(inst);
1114
+ const prior = this.leaving.get(key);
1115
+ if (prior !== void 0) prior.run.cancel();
1116
+ const run = startRun(
1117
+ this.dom,
1118
+ el,
1119
+ rt.leaveActive,
1120
+ rt.leaveFrom,
1121
+ rt.leaveTo,
1122
+ rt.duration,
1123
+ () => {
1124
+ const current = this.leaving.get(key);
1125
+ if (current !== void 0 && current.run === run) {
1126
+ this.leaving.delete(key);
1127
+ this.finalize(inst);
1128
+ }
1129
+ }
1130
+ );
1131
+ this.leaving.set(key, { inst, run });
1132
+ return true;
1133
+ },
1134
+ onEnter: (inst) => this.enter(inst)
1135
+ };
1136
+ }
1137
+ };
1138
+
917
1139
  // ../renderer/src/mount.ts
918
1140
  var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
919
1141
  "text",
@@ -1066,7 +1288,7 @@ function mountNode(ctx, graphNode, parentDom) {
1066
1288
  instance2.addChild(childInstance);
1067
1289
  }
1068
1290
  dom.appendChild(parentDom, el2);
1069
- wireReactiveList(ctx, graphNode, instance2, el2);
1291
+ wireReactiveList(ctx, graphNode, instance2, el2, true);
1070
1292
  return instance2;
1071
1293
  }
1072
1294
  if (graphNode.type === "portal") {
@@ -1175,32 +1397,42 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
1175
1397
  instance.trackCleanup(unsub);
1176
1398
  }
1177
1399
  }
1178
- function wireReactiveList(ctx, graphNode, instance, el) {
1400
+ function wireReactiveList(ctx, graphNode, instance, el, runAppear = false) {
1179
1401
  const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
1180
1402
  const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
1181
1403
  if (plan === void 0 && build === void 0) return;
1404
+ const controller = new TransitionController(ctx.dom, ctx.graph, (leaving) => {
1405
+ const parent = ctx.dom.parentNode(leaving.domNode);
1406
+ if (parent !== null) ctx.dom.removeChild(parent, leaving.domNode);
1407
+ leaving.dispose();
1408
+ forgetInstance(ctx, leaving);
1409
+ ctx.graph.detachNode(leaving.graphNode);
1410
+ });
1411
+ const hooks = controller.hooks();
1412
+ if (runAppear) controller.appear(instance.children);
1182
1413
  for (const stateRef of graphNode.stateRefs) {
1183
1414
  if (stateRef.propKey !== "items") continue;
1184
1415
  const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
1185
1416
  if (sig === void 0 || typeof sig.subscribe !== "function") continue;
1186
1417
  const unsub = sig.subscribe((value) => {
1187
1418
  if (plan !== void 0) {
1188
- reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
1419
+ reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value), hooks);
1189
1420
  } else {
1190
- reconcileReactiveList(ctx, graphNode, instance, el, build(value));
1421
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value), hooks);
1191
1422
  }
1192
1423
  });
1193
1424
  instance.trackCleanup(unsub);
1194
1425
  }
1195
1426
  }
1196
- function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
1427
+ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan, hooks) {
1197
1428
  const oldInstances = [...listInstance.children];
1198
1429
  const result = reconcileChildrenByPlan(
1199
1430
  ctx,
1200
1431
  listEl,
1201
1432
  oldInstances,
1202
1433
  plan,
1203
- (node, parent) => mountNode(ctx, node, parent)
1434
+ (node, parent) => mountNode(ctx, node, parent),
1435
+ hooks
1204
1436
  );
1205
1437
  listInstance.children.length = 0;
1206
1438
  for (const inst of result.instances) listInstance.children.push(inst);
@@ -1215,14 +1447,15 @@ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan)
1215
1447
  for (const child of [...listNode.children]) listNode.removeChild(child);
1216
1448
  for (const inst of result.instances) listNode.appendChild(inst.graphNode);
1217
1449
  }
1218
- function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
1450
+ function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes, hooks) {
1219
1451
  const oldInstances = [...listInstance.children];
1220
1452
  const result = reconcileChildren(
1221
1453
  ctx,
1222
1454
  listEl,
1223
1455
  oldInstances,
1224
1456
  newNodes,
1225
- (node, parent) => mountNode(ctx, node, parent)
1457
+ (node, parent) => mountNode(ctx, node, parent),
1458
+ hooks
1226
1459
  );
1227
1460
  listInstance.children.length = 0;
1228
1461
  for (const inst of result.instances) listInstance.children.push(inst);
@@ -1258,11 +1491,14 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
1258
1491
  const onOpenChange = (isOpen) => {
1259
1492
  if (isOpen) {
1260
1493
  if (desc.restoreFocus) saved = saveFocus(dom);
1261
- if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
1262
1494
  if (desc.modal) {
1263
1495
  active.push(trapFocus(dom, target));
1264
1496
  active.push(containFocus(dom, target));
1265
1497
  }
1498
+ if (desc.menu) {
1499
+ active.push(rovingMenu(dom, target));
1500
+ }
1501
+ if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
1266
1502
  if (desc.closeOnEscape && desc.onClose !== void 0) {
1267
1503
  active.push(onEscape(dom, target, desc.onClose));
1268
1504
  }
@@ -1439,7 +1675,7 @@ function renderToString(compiled, options = {}) {
1439
1675
  }
1440
1676
 
1441
1677
  // src/version.ts
1442
- var VERSION = "1.8.0";
1678
+ var VERSION = "1.9.0";
1443
1679
  export {
1444
1680
  STATE_MARKER_ATTR,
1445
1681
  ServerDOMAdapter,