@swiftbrowser/react 0.28.4 → 0.28.5

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/index.js CHANGED
@@ -989,11 +989,23 @@ function findStack(node) {
989
989
  for (const child of node.children) {
990
990
  if (child.kind === "sheet" || child.kind === "alert") continue;
991
991
  if (child.kind === "navstack") return child;
992
+ if (child.kind === "navsplit") return splitDetailStack(child);
992
993
  const found = findStack(child);
993
994
  if (found) return found;
994
995
  }
995
996
  return null;
996
997
  }
998
+ function findSplit(node) {
999
+ for (const child of node.children) {
1000
+ if (child.kind === "sheet" || child.kind === "alert" || child.kind === "navstack") continue;
1001
+ if (child.kind === "navsplit") return child;
1002
+ const found = findSplit(child);
1003
+ if (found) return found;
1004
+ }
1005
+ return null;
1006
+ }
1007
+ const splitSidebarStack = (split) => split.children[0]?.kind === "navstack" ? split.children[0] : null;
1008
+ const splitDetailStack = (split) => split.children[1]?.kind === "navstack" ? split.children[1] : null;
997
1009
  const screensOf = (stack) => stack ? stack.children.filter((child) => child.kind === "navscreen") : [];
998
1010
  function renderNode(root, node, stack, elements) {
999
1011
  if (node.kind === "host") return null;
@@ -1001,6 +1013,7 @@ function renderNode(root, node, stack, elements) {
1001
1013
  const first = screensOf(stack)[0];
1002
1014
  return createElement(Fragment, { key: node.id }, first ? renderScreen(root, first, stack, elements) : null);
1003
1015
  }
1016
+ if (node.kind === "navsplit" && stack && splitDetailStack(node) === stack) return createElement(Fragment, { key: node.id }, renderNode(root, stack, stack, elements));
1004
1017
  const children = node.children.length ? node.children.map((child) => renderNode(root, child, stack, elements)) : void 0;
1005
1018
  const own = elements && Object.prototype.hasOwnProperty.call(elements, node.kind) ? elements[node.kind] : void 0;
1006
1019
  if (own) return createElement(Fragment, { key: node.id }, own(node.props, children));
@@ -1025,6 +1038,86 @@ function RemoteScreen({ root, id, elements }) {
1025
1038
  if (!screen?.parent) return null;
1026
1039
  return renderScreen(root, screen, findStack(root.root), elements);
1027
1040
  }
1041
+ function useScreenValues(root, elements) {
1042
+ const values = useRef(/* @__PURE__ */ new Map());
1043
+ const ours = useRef(/* @__PURE__ */ new WeakSet());
1044
+ const keyOf2 = (screen) => `${screen.id}:${JSON.stringify(screen.props["route"] ?? null)}`;
1045
+ const valueFor = (screen) => {
1046
+ const key = keyOf2(screen);
1047
+ let value = values.current.get(key);
1048
+ if (!value) {
1049
+ value = "route" in screen.props ? { view: /* @__PURE__ */ jsx(RemoteScreen, { root, id: screen.id, elements }), route: screen.props["route"] } : { view: /* @__PURE__ */ jsx(RemoteScreen, { root, id: screen.id, elements }) };
1050
+ values.current.set(key, value);
1051
+ ours.current.add(value);
1052
+ }
1053
+ return value;
1054
+ };
1055
+ const isOurs = (value) => typeof value === "object" && value !== null && ours.current.has(value);
1056
+ const forget = (keys) => {
1057
+ for (const key of values.current.keys()) if (!keys.includes(key)) values.current.delete(key);
1058
+ };
1059
+ return { valueFor, keyOf: keyOf2, isOurs, forget };
1060
+ }
1061
+ function useRemoteSidebar(root, path, setPath, options = {}) {
1062
+ const shown = options.shown ?? true;
1063
+ useSyncExternalStore(root ? root.subscribe : noSubscribe, root ? root.getVersion : zero, root ? root.getVersion : zero);
1064
+ const { valueFor, keyOf: keyOf2, isOurs, forget } = useScreenValues(root ?? NO_ROOT, options.elements);
1065
+ const split = root ? findSplit(root.root) : null;
1066
+ const screens = split ? screensOf(splitSidebarStack(split)) : [];
1067
+ const dismissed = useRef(false);
1068
+ const lastKeys = useRef([]);
1069
+ const lastMine = useRef(0);
1070
+ const last = useRef({ root: null, shown: false, generation: void 0 });
1071
+ useLayoutEffect(() => {
1072
+ if (last.current.root !== root || !last.current.shown && shown || last.current.generation !== options.generation) dismissed.current = false;
1073
+ last.current = { root, shown, generation: options.generation };
1074
+ const keys = screens.map(keyOf2);
1075
+ const wanted = shown && !dismissed.current ? screens.map(valueFor) : [];
1076
+ forget(keys);
1077
+ const hosts = path.filter((value) => !isOurs(value));
1078
+ const mine = path.filter(isOurs);
1079
+ const childMoved = keys.length !== lastKeys.current.length || keys.some((key, i) => key !== lastKeys.current[i]);
1080
+ lastKeys.current = keys;
1081
+ const popped = mine.length < lastMine.current;
1082
+ lastMine.current = mine.length;
1083
+ if (root && wanted.length > 0 && !childMoved && popped && mine.length < wanted.length && mine.every((value, i) => value === wanted[i])) {
1084
+ if (mine.length === 0) {
1085
+ dismissed.current = true;
1086
+ return;
1087
+ }
1088
+ root.dispatch({ type: "tap", id: screens[mine.length].id });
1089
+ return;
1090
+ }
1091
+ if (mine.length === wanted.length && mine.every((value, i) => value === wanted[i])) return;
1092
+ lastMine.current = wanted.length;
1093
+ setPath([...hosts, ...wanted]);
1094
+ });
1095
+ }
1096
+ const noSubscribe = () => () => {
1097
+ };
1098
+ const zero = () => 0;
1099
+ const NO_ROOT = {
1100
+ applyOps() {
1101
+ },
1102
+ onEvent() {
1103
+ },
1104
+ onEnvironment() {
1105
+ },
1106
+ dispatch() {
1107
+ },
1108
+ setEnvironment() {
1109
+ },
1110
+ subscribe: noSubscribe,
1111
+ getVersion: zero,
1112
+ root: { id: 0, kind: "root", props: {}, children: [], parent: null },
1113
+ node: () => void 0,
1114
+ rootProps: {},
1115
+ handlersFor: () => ({}),
1116
+ attach() {
1117
+ },
1118
+ dispose() {
1119
+ }
1120
+ };
1028
1121
  function RemoteTree({ root, initialPath, elements }) {
1029
1122
  useSyncExternalStore(root.subscribe, root.getVersion, root.getVersion);
1030
1123
  const environment = useContext(EnvironmentContext);
@@ -1041,20 +1134,8 @@ function RemoteTree({ root, initialPath, elements }) {
1041
1134
  }, [root, environment]);
1042
1135
  const stack = navigation ? findStack(root.root) : null;
1043
1136
  const pushed = screensOf(stack).slice(1);
1044
- const values = useRef(/* @__PURE__ */ new Map());
1045
- const ours = useRef(/* @__PURE__ */ new WeakSet());
1137
+ const { valueFor, keyOf: keyOf2, isOurs, forget } = useScreenValues(root, elements);
1046
1138
  const lastPushed = useRef([]);
1047
- const keyOf2 = (screen) => `${screen.id}:${JSON.stringify(screen.props["route"] ?? null)}`;
1048
- const valueFor = (screen) => {
1049
- const key = keyOf2(screen);
1050
- let value = values.current.get(key);
1051
- if (!value) {
1052
- value = "route" in screen.props ? { view: /* @__PURE__ */ jsx(RemoteScreen, { root, id: screen.id, elements }), route: screen.props["route"] } : { view: /* @__PURE__ */ jsx(RemoteScreen, { root, id: screen.id, elements }) };
1053
- values.current.set(key, value);
1054
- ours.current.add(value);
1055
- }
1056
- return value;
1057
- };
1058
1139
  const restored = useRef(false);
1059
1140
  useLayoutEffect(() => {
1060
1141
  if (restored.current) return;
@@ -1067,10 +1148,10 @@ function RemoteTree({ root, initialPath, elements }) {
1067
1148
  if (!navigation) return;
1068
1149
  const keys = pushed.map(keyOf2);
1069
1150
  const wanted = pushed.map(valueFor);
1070
- for (const key of values.current.keys()) if (!keys.includes(key)) values.current.delete(key);
1151
+ forget(keys);
1071
1152
  const tail = navigation.path.slice(depth);
1072
1153
  let shown = 0;
1073
- while (shown < tail.length && typeof tail[shown] === "object" && tail[shown] !== null && ours.current.has(tail[shown])) shown += 1;
1154
+ while (shown < tail.length && isOurs(tail[shown])) shown += 1;
1074
1155
  const childMoved = keys.length !== lastPushed.current.length || keys.some((key, i) => key !== lastPushed.current[i]);
1075
1156
  lastPushed.current = keys;
1076
1157
  if (!childMoved && shown < wanted.length && tail.slice(0, shown).every((value, i) => value === wanted[i])) {
@@ -1544,29 +1625,49 @@ function marginedContent(children, margins, axis) {
1544
1625
  }
1545
1626
  const LAZY_BATCH = 12;
1546
1627
  const LazyContext = createContext(null);
1628
+ const LazyBatchContext = createContext(LAZY_BATCH);
1629
+ const LAZY_GRID_BATCH = 48;
1630
+ const LazyWindowContext = createContext(false);
1631
+ const LazyStackContext = createContext(false);
1547
1632
  function LazyGroup({ rows, start = 0, wrap: wrap2 }) {
1548
1633
  const axis = useContext(LazyContext);
1634
+ const batch = useContext(LazyBatchContext);
1549
1635
  const request = useContext(ScrollRequestContext);
1550
- const [count, setCount] = useState(LAZY_BATCH);
1636
+ const windowed = useContext(LazyWindowContext) && useContext(LazyStackContext) && axis !== null;
1637
+ const [count, setCount] = useState(batch);
1638
+ const [front, setFront] = useState({ from: 0, extents: [] });
1551
1639
  const target = request && "id" in request ? rows.findIndex((row) => rowMatches(row, request.id)) : -1;
1552
- const needed = target >= 0 ? Math.ceil((target + 1) / LAZY_BATCH) * LAZY_BATCH : 0;
1640
+ const needed = target >= 0 ? Math.ceil((target + 1) / batch) * batch : 0;
1553
1641
  useEffect(() => {
1554
1642
  if (needed > count) setCount(needed);
1555
- }, [needed, count]);
1643
+ if (target >= 0 && target < front.from) setFront({ from: Math.floor(target / batch) * batch, extents: front.extents.slice(0, Math.floor(target / batch)) });
1644
+ }, [needed, count, target, front, batch]);
1556
1645
  const visible = Math.max(count, needed);
1557
- const shown = axis === null ? rows : rows.slice(0, visible);
1558
- const built = shown.map((row, index) => wrap2 ? wrap2(row, start + index) : row);
1559
- if (axis === null || shown.length >= rows.length) return built;
1560
- return [
1561
- ...built,
1562
- createElement(
1563
- LazySpacer,
1564
- { key: `lazy-${visible}`, axis, remaining: rows.length - shown.length, more: () => setCount(visible + LAZY_BATCH) }
1565
- )
1566
- ];
1567
- }
1568
- function LazySpacer({ axis, remaining, more }) {
1569
- return sb("lazyspacer", { axis, remaining }, { lazy: more });
1646
+ const from = windowed ? Math.min(front.from, Math.max(0, visible - batch)) : 0;
1647
+ const shown = axis === null ? rows : rows.slice(from, visible);
1648
+ const built = shown.map((row, index) => wrap2 ? wrap2(row, start + from + index) : row);
1649
+ if (axis === null || !windowed && shown.length >= rows.length) return built;
1650
+ const ask = (event) => {
1651
+ if (event.drop && event.drop > 0) {
1652
+ const drop = Math.min(event.drop, shown.length - batch);
1653
+ if (drop > 0) setFront({ from: from + drop, extents: [...front.extents, ...(event.extents ?? []).slice(0, Math.ceil(drop / batch))] });
1654
+ } else if (event.dropEnd && event.dropEnd > 0) {
1655
+ setCount(Math.max(from + batch, visible - event.dropEnd));
1656
+ } else if (from > 0) {
1657
+ setFront({ from: Math.max(0, from - batch), extents: front.extents.slice(0, -1) });
1658
+ }
1659
+ };
1660
+ const leading = windowed ? [createElement(LazySpacer, { key: `lazy-lead-${from}`, axis, remaining: from, edge: "leading", extent: front.extents.reduce((a, b) => a + b, 0), built: shown.length, batch, ask })] : [];
1661
+ const trailing = visible < rows.length ? [createElement(LazySpacer, { key: `lazy-${visible}`, axis, remaining: rows.length - visible, ask: () => setCount(visible + batch) })] : [];
1662
+ return [...leading, ...built, ...trailing];
1663
+ }
1664
+ function LazySpacer({
1665
+ axis,
1666
+ remaining,
1667
+ ask,
1668
+ ...leading
1669
+ }) {
1670
+ return sb("lazyspacer", { axis, remaining, ...leading }, { lazy: ask });
1570
1671
  }
1571
1672
  function rowMatches(row, id) {
1572
1673
  if (row.key !== null && row.key === String(id)) return true;
@@ -1596,6 +1697,11 @@ function HStack(props) {
1596
1697
  if (own.pinnedViews && own.pinnedViews.length > 0) stack["pinned"] = own.pinnedViews;
1597
1698
  return applyModifiers(sb("hstack", stack, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1598
1699
  }
1700
+ function FlowStack(props) {
1701
+ const { own, modifiers } = splitProps(props);
1702
+ const stack = { spacing: own.spacing ?? null, lineSpacing: own.lineSpacing ?? null, alignment: own.alignment ?? "center" };
1703
+ return applyModifiers(sb("flowstack", stack, void 0, own.children), modifiers);
1704
+ }
1599
1705
  function ZStack(props) {
1600
1706
  const { own, modifiers } = splitProps(props);
1601
1707
  return applyModifiers(sb("zstack", { alignment: own.alignment ?? "center" }, void 0, own.children), modifiers);
@@ -1777,17 +1883,20 @@ function GridItem(spec = {}) {
1777
1883
  GridItem.flexible = (options = {}) => GridItem({ size: { flexible: options }, spacing: options.spacing, alignment: options.alignment });
1778
1884
  GridItem.fixed = (value, options = {}) => GridItem({ size: { fixed: value }, spacing: options.spacing, alignment: options.alignment });
1779
1885
  GridItem.adaptive = (options) => GridItem({ size: { adaptive: options }, spacing: options.spacing, alignment: options.alignment });
1886
+ function lazyCells(axis, children) {
1887
+ return createElement(LazyContext.Provider, { value: axis }, createElement(LazyStackContext.Provider, { value: false }, createElement(LazyBatchContext.Provider, { value: LAZY_GRID_BATCH }, children)));
1888
+ }
1780
1889
  function LazyVGrid(props) {
1781
1890
  const { own, modifiers } = splitProps(props);
1782
1891
  const grid = { columns: own.columns, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1783
1892
  if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
1784
- return applyModifiers(sb("lazyvgrid", grid, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1893
+ return applyModifiers(sb("lazyvgrid", grid, void 0, lazyCells("vertical", pinning(own.pinnedViews, own.children))), modifiers);
1785
1894
  }
1786
1895
  function LazyHGrid(props) {
1787
1896
  const { own, modifiers } = splitProps(props);
1788
1897
  const grid = { rows: own.rows, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1789
1898
  if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
1790
- return applyModifiers(sb("lazyhgrid", grid, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1899
+ return applyModifiers(sb("lazyhgrid", grid, void 0, lazyCells("horizontal", pinning(own.pinnedViews, own.children))), modifiers);
1791
1900
  }
1792
1901
  function Grid(props) {
1793
1902
  const { own, modifiers } = splitProps(props);
@@ -2117,10 +2226,11 @@ function NavigationSplitView(props) {
2117
2226
  const sidebarVisible = visibility !== "detailOnly";
2118
2227
  const toggle = () => withAnimation(() => setVisibility(sidebarVisible ? "detailOnly" : "all"));
2119
2228
  const detail = isValidElement(own.detail) && own.detail.type === NavigationStack ? own.detail : /* @__PURE__ */ jsx(NavigationStack, { children: own.detail });
2120
- const sidebar = own.sidebarWidth !== void 0 ? sb("styled", { style: { splitColumnWidth: splitColumnWidthHint(own.sidebarWidth) } }, void 0, own.sidebar) : own.sidebar;
2229
+ const hinted = (content) => own.sidebarWidth !== void 0 ? sb("styled", { style: { splitColumnWidth: splitColumnWidthHint(own.sidebarWidth) } }, void 0, content) : content;
2230
+ const sidebar = isValidElement(own.sidebar) && own.sidebar.type === NavigationStack ? hinted(own.sidebar) : /* @__PURE__ */ jsx(NavigationStack, { children: hinted(own.sidebar) });
2121
2231
  return applyModifiers(
2122
2232
  sb("navsplit", { sidebarVisible }, { tap: toggle, columnwidth: (event) => own.onSidebarWidthChange?.(event.width) }, [
2123
- /* @__PURE__ */ jsx(SplitColumn, { report, children: /* @__PURE__ */ jsx(NavigationStack, { children: sidebar }) }, "sidebar"),
2233
+ /* @__PURE__ */ jsx(SplitColumn, { report, children: sidebar }, "sidebar"),
2124
2234
  /* @__PURE__ */ jsx(SplitColumn, { report: null, children: detail }, "detail")
2125
2235
  ]),
2126
2236
  modifiers
@@ -3666,7 +3776,7 @@ function mount(screen, element, options = {}) {
3666
3776
  }) : null;
3667
3777
  observer?.observe(screen);
3668
3778
  const stopScenePhase = typeof document !== "undefined" ? watchScenePhase((scenePhase) => root.setEnvironment({ scenePhase })) : null;
3669
- root.render(element);
3779
+ root.render(createElement(LazyWindowContext.Provider, { value: true }, element));
3670
3780
  const mounted = {
3671
3781
  root,
3672
3782
  renderer,
@@ -5988,6 +6098,7 @@ function TextEditor(props) {
5988
6098
  if (focus) editor["focused"] = focus[0];
5989
6099
  if (selection) editor["selection"] = { start: selection[0].start, end: selection[0].end };
5990
6100
  if (own.onDeleteBackward) editor["deleteBackward"] = true;
6101
+ if (own.marks && own.marks.length > 0) editor["marks"] = own.marks.map((mark) => ({ start: mark.start, end: mark.end, color: color(mark.color ?? MARK_COLOR) }));
5991
6102
  return applyModifiers(
5992
6103
  sb("texteditor", editor, {
5993
6104
  text: (event) => setText(rich ? AttributedText.replacingCharacters(text, event.value) : event.value),
@@ -6002,6 +6113,7 @@ function TextEditor(props) {
6002
6113
  modifiers
6003
6114
  );
6004
6115
  }
6116
+ const MARK_COLOR = opacity("accent", 0.25);
6005
6117
  function buttonDisabled(modifiers) {
6006
6118
  return modifiers.some(([key, value]) => key === "disabled" && value === true);
6007
6119
  }
@@ -6060,7 +6172,9 @@ ContainerRelativeShape.shape = { kind: "containerRelative", cornerRadius: null }
6060
6172
  function Shape(props) {
6061
6173
  const { own, modifiers } = splitProps(props);
6062
6174
  const [size, setSize] = useState(null);
6063
- const affine = affinePath(own.path);
6175
+ const sized = useRef(false);
6176
+ const affine = sized.current ? null : affinePath(own.path);
6177
+ if (affine === null) sized.current = true;
6064
6178
  const shapeProps = {
6065
6179
  shape: "path",
6066
6180
  cornerRadius: 0,
@@ -6670,7 +6784,7 @@ function List(props) {
6670
6784
  const ownSettings = takeScrollSettings(modifiers);
6671
6785
  const edgeEffect = edgeEffectProp(useScrollSettings(ownSettings));
6672
6786
  if (edgeEffect) listProps["edgeEffect"] = edgeEffect;
6673
- const element = /* @__PURE__ */ jsx(LazyContext.Provider, { value: "vertical", children: sb("list", listProps, refresh.handlers, providingScrollSettings(ownSettings, wrapRows(own.children))) });
6787
+ const element = /* @__PURE__ */ jsx(LazyContext.Provider, { value: "vertical", children: /* @__PURE__ */ jsx(LazyStackContext.Provider, { value: false, children: sb("list", listProps, refresh.handlers, providingScrollSettings(ownSettings, wrapRows(own.children))) }) });
6674
6788
  return applyModifiers(
6675
6789
  selection ? /* @__PURE__ */ jsx(ListSelectionContext.Provider, { value: selection, children: element }) : element,
6676
6790
  modifiers
@@ -7316,10 +7430,10 @@ function ViewThatFits(props) {
7316
7430
  return applyModifiers(sb("viewthatfits", { axes: own.axes ?? "both" }, void 0, own.children), modifiers);
7317
7431
  }
7318
7432
  function LazyVStack(props) {
7319
- return /* @__PURE__ */ jsx(LazyContext.Provider, { value: "vertical", children: /* @__PURE__ */ jsx(VStack, { ...props }) });
7433
+ return /* @__PURE__ */ jsx(LazyContext.Provider, { value: "vertical", children: /* @__PURE__ */ jsx(LazyStackContext.Provider, { value: true, children: /* @__PURE__ */ jsx(VStack, { ...props }) }) });
7320
7434
  }
7321
7435
  function LazyHStack(props) {
7322
- return /* @__PURE__ */ jsx(LazyContext.Provider, { value: "horizontal", children: /* @__PURE__ */ jsx(HStack, { ...props }) });
7436
+ return /* @__PURE__ */ jsx(LazyContext.Provider, { value: "horizontal", children: /* @__PURE__ */ jsx(LazyStackContext.Provider, { value: true, children: /* @__PURE__ */ jsx(HStack, { ...props }) }) });
7323
7437
  }
7324
7438
  function ContentUnavailableView(props) {
7325
7439
  const { own, modifiers } = splitProps(props);
@@ -8200,6 +8314,7 @@ export {
8200
8314
  Divider,
8201
8315
  EditButton,
8202
8316
  Ellipse,
8317
+ FlowStack,
8203
8318
  Font,
8204
8319
  ForEach,
8205
8320
  Form,
@@ -8354,6 +8469,7 @@ export {
8354
8469
  usePlatform,
8355
8470
  useRedacted,
8356
8471
  useRefreshable,
8472
+ useRemoteSidebar,
8357
8473
  useReportSplitSelection,
8358
8474
  useScenePhase,
8359
8475
  useSpeechRecognition,