@swiftbrowser/react 0.28.3 → 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
@@ -4,10 +4,10 @@ import Reconciler from "react-reconciler";
4
4
  import { base64Decode, base64Encode } from "@swiftbrowser/web/src/base64";
5
5
  import { addLiveBitmap, forgetLiveBitmap } from "@swiftbrowser/web/src/bitmaps";
6
6
  import { jsx, jsxs, Fragment as Fragment$1 } from "react/jsx-runtime";
7
+ import { browserEnvironment, exportFiles, loadableURL, performHostRequest } from "@swiftbrowser/web/src/hostRequests";
7
8
  import { horizontalSizeClassFor } from "@swiftbrowser/web/src/protocol";
8
9
  import { BrowserHistory } from "@swiftbrowser/web/src/browserHistory";
9
10
  import { watchKeyboard } from "@swiftbrowser/web/src/deviceMode";
10
- import { browserEnvironment, exportFiles, loadableURL, performHostRequest } from "@swiftbrowser/web/src/hostRequests";
11
11
  import { Renderer } from "@swiftbrowser/web/src/renderer";
12
12
  import { currentScenePhase, watchScenePhase } from "@swiftbrowser/web/src/scenePhase";
13
13
  import { applyTypeSizeVariables } from "@swiftbrowser/web/src/typography";
@@ -989,41 +989,136 @@ 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
- function renderNode(root, node, stack) {
1010
+ function renderNode(root, node, stack, elements) {
999
1011
  if (node.kind === "host") return null;
1000
1012
  if (node === stack) {
1001
1013
  const first = screensOf(stack)[0];
1002
- return createElement(Fragment, { key: node.id }, first ? renderScreen(root, first, stack) : null);
1014
+ return createElement(Fragment, { key: node.id }, first ? renderScreen(root, first, stack, elements) : null);
1003
1015
  }
1004
- const children = node.children.length ? node.children.map((child) => renderNode(root, child, stack)) : void 0;
1016
+ if (node.kind === "navsplit" && stack && splitDetailStack(node) === stack) return createElement(Fragment, { key: node.id }, renderNode(root, stack, stack, elements));
1017
+ const children = node.children.length ? node.children.map((child) => renderNode(root, child, stack, elements)) : void 0;
1018
+ const own = elements && Object.prototype.hasOwnProperty.call(elements, node.kind) ? elements[node.kind] : void 0;
1019
+ if (own) return createElement(Fragment, { key: node.id }, own(node.props, children));
1005
1020
  return createElement(Fragment, { key: node.id }, sb(node.kind, node.props, root.handlersFor(node.id), children));
1006
1021
  }
1007
- function renderSheet(root, node, stack, inherited) {
1008
- const children = node.children.map((child) => renderNode(root, child, stack));
1022
+ function renderSheet(root, node, stack, inherited, elements) {
1023
+ const children = node.children.map((child) => renderNode(root, child, stack, elements));
1009
1024
  return createElement(Fragment, { key: node.id }, sb(node.kind, node.props, root.handlersFor(node.id), replayStyles(inherited, children)));
1010
1025
  }
1011
- function renderScreen(root, screen, stack) {
1026
+ function renderScreen(root, screen, stack, elements) {
1012
1027
  const out = [screenPrefs(screen.props, "$screen")];
1013
1028
  for (const child of screen.children) {
1014
- const drawn = renderNode(root, child, stack);
1029
+ const drawn = renderNode(root, child, stack, elements);
1015
1030
  if (child.kind === "toolbar" || child.kind === "searchfield") out.push(createElement(Fragment, { key: child.id }, sb(HOIST_KIND, void 0, void 0, drawn, "navscreen")));
1016
1031
  else out.push(drawn);
1017
1032
  }
1018
1033
  return createElement(Fragment, null, ...out);
1019
1034
  }
1020
- function RemoteScreen({ root, id }) {
1035
+ function RemoteScreen({ root, id, elements }) {
1021
1036
  useSyncExternalStore(root.subscribe, root.getVersion, root.getVersion);
1022
1037
  const screen = root.node(id);
1023
1038
  if (!screen?.parent) return null;
1024
- return renderScreen(root, screen, findStack(root.root));
1039
+ return renderScreen(root, screen, findStack(root.root), elements);
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
+ });
1025
1095
  }
1026
- function RemoteTree({ root, initialPath }) {
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
+ };
1121
+ function RemoteTree({ root, initialPath, elements }) {
1027
1122
  useSyncExternalStore(root.subscribe, root.getVersion, root.getVersion);
1028
1123
  const environment = useContext(EnvironmentContext);
1029
1124
  const inherited = useInheritedStyles();
@@ -1039,20 +1134,8 @@ function RemoteTree({ root, initialPath }) {
1039
1134
  }, [root, environment]);
1040
1135
  const stack = navigation ? findStack(root.root) : null;
1041
1136
  const pushed = screensOf(stack).slice(1);
1042
- const values = useRef(/* @__PURE__ */ new Map());
1043
- const ours = useRef(/* @__PURE__ */ new WeakSet());
1137
+ const { valueFor, keyOf: keyOf2, isOurs, forget } = useScreenValues(root, elements);
1044
1138
  const lastPushed = useRef([]);
1045
- const keyOf2 = (screen) => `${screen.id}:${JSON.stringify(screen.props["route"] ?? null)}`;
1046
- const valueFor = (screen) => {
1047
- const key = keyOf2(screen);
1048
- let value = values.current.get(key);
1049
- if (!value) {
1050
- value = "route" in screen.props ? { view: /* @__PURE__ */ jsx(RemoteScreen, { root, id: screen.id }), route: screen.props["route"] } : { view: /* @__PURE__ */ jsx(RemoteScreen, { root, id: screen.id }) };
1051
- values.current.set(key, value);
1052
- ours.current.add(value);
1053
- }
1054
- return value;
1055
- };
1056
1139
  const restored = useRef(false);
1057
1140
  useLayoutEffect(() => {
1058
1141
  if (restored.current) return;
@@ -1065,10 +1148,10 @@ function RemoteTree({ root, initialPath }) {
1065
1148
  if (!navigation) return;
1066
1149
  const keys = pushed.map(keyOf2);
1067
1150
  const wanted = pushed.map(valueFor);
1068
- for (const key of values.current.keys()) if (!keys.includes(key)) values.current.delete(key);
1151
+ forget(keys);
1069
1152
  const tail = navigation.path.slice(depth);
1070
1153
  let shown = 0;
1071
- 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;
1072
1155
  const childMoved = keys.length !== lastPushed.current.length || keys.some((key, i) => key !== lastPushed.current[i]);
1073
1156
  lastPushed.current = keys;
1074
1157
  if (!childMoved && shown < wanted.length && tail.slice(0, shown).every((value, i) => value === wanted[i])) {
@@ -1082,13 +1165,192 @@ function RemoteTree({ root, initialPath }) {
1082
1165
  if ("colorScheme" in root.rootProps)
1083
1166
  out.push(createElement(Fragment, { key: "$scheme" }, sb(PREF_KIND, { colorScheme: root.rootProps["colorScheme"] }, void 0, void 0, "root")));
1084
1167
  for (const child of root.root.children) {
1085
- const drawn = child.kind === "sheet" && inherited.length > 0 ? renderSheet(root, child, stack, inherited) : renderNode(root, child, stack);
1168
+ const drawn = child.kind === "sheet" && inherited.length > 0 ? renderSheet(root, child, stack, inherited, elements) : renderNode(root, child, stack, elements);
1086
1169
  if (child.kind === "sheet" || child.kind === "alert") out.push(createElement(Fragment, { key: child.id }, sb(HOIST_KIND, void 0, void 0, drawn, "root")));
1087
1170
  else if (child.kind === "toolbar" || child.kind === "searchfield") out.push(createElement(Fragment, { key: child.id }, sb(HOIST_KIND, void 0, void 0, drawn, "navscreen")));
1088
1171
  else out.push(drawn);
1089
1172
  }
1090
1173
  return createElement(Fragment, null, ...out);
1091
1174
  }
1175
+ function readBinding(value, onChange) {
1176
+ if (Array.isArray(value) && value.length === 2 && typeof value[1] === "function") {
1177
+ const [current, set] = value;
1178
+ return [current, set];
1179
+ }
1180
+ return [value, onChange ?? (() => {
1181
+ })];
1182
+ }
1183
+ let active = null;
1184
+ function Draggable({ payload, children }) {
1185
+ return sb(
1186
+ "draggable",
1187
+ {},
1188
+ {
1189
+ dragstart: (event) => {
1190
+ active = { id: event.id, payload };
1191
+ },
1192
+ dragend: (event) => {
1193
+ if (active?.id === event.id) active = null;
1194
+ }
1195
+ },
1196
+ children
1197
+ );
1198
+ }
1199
+ function itemValue(item) {
1200
+ if (item.text !== void 0) {
1201
+ if (item.type === "text/uri-list") {
1202
+ try {
1203
+ return new URL(item.text.split(/\r?\n/)[0].trim());
1204
+ } catch {
1205
+ return item.text;
1206
+ }
1207
+ }
1208
+ return item.text;
1209
+ }
1210
+ const name = item.path ?? item.name ?? "file";
1211
+ if (item.file) {
1212
+ const file = item.file;
1213
+ return item.path !== void 0 && item.path !== file.name ? new File([file], item.path, { type: file.type, lastModified: file.lastModified }) : file;
1214
+ }
1215
+ if (item.url !== void 0 && item.data === void 0) {
1216
+ const fromReference = browserEnvironment.fileFromReference;
1217
+ if (fromReference) {
1218
+ return fromReference({ type: item.type, name, url: item.url, ...item.path !== void 0 ? { path: item.path } : {}, ...item.size !== void 0 ? { size: item.size } : {} });
1219
+ }
1220
+ }
1221
+ const binary = atob(item.data ?? "");
1222
+ const bytes = new Uint8Array(binary.length);
1223
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1224
+ return typeof File !== "undefined" ? new File([bytes], name, { type: item.type }) : bytes;
1225
+ }
1226
+ function DropDestination({ action, isTargeted, accepts, children }) {
1227
+ return sb(
1228
+ "dropdestination",
1229
+ { accepts: accepts ?? [] },
1230
+ {
1231
+ drop: (event) => {
1232
+ if (event.phase === "entered") {
1233
+ isTargeted?.(true);
1234
+ return;
1235
+ }
1236
+ isTargeted?.(false);
1237
+ if (event.phase !== "performed") return;
1238
+ let items;
1239
+ if (event.source !== void 0) items = active && active.id === event.source ? [active.payload] : [];
1240
+ else items = (event.items ?? []).map(itemValue);
1241
+ if (items.length > 0) action(items, { x: event.x, y: event.y });
1242
+ }
1243
+ },
1244
+ children
1245
+ );
1246
+ }
1247
+ function FileImporterPresenter(spec) {
1248
+ const [presented, setPresented] = readBinding(spec.isPresented);
1249
+ const latest = useRef(spec);
1250
+ latest.current = spec;
1251
+ const choosing = useRef(false);
1252
+ useLayoutEffect(() => {
1253
+ if (!presented || choosing.current) return;
1254
+ const { allowedContentTypes, allowsMultipleSelection } = latest.current;
1255
+ const pick = browserEnvironment.pickFiles;
1256
+ if (!pick) {
1257
+ setPresented(false);
1258
+ latest.current.onCancellation?.();
1259
+ return;
1260
+ }
1261
+ choosing.current = true;
1262
+ void pick({ accept: allowedContentTypes.join(","), multiple: allowsMultipleSelection === true }).then((files) => {
1263
+ choosing.current = false;
1264
+ setPresented(false);
1265
+ if (files.length > 0) latest.current.onCompletion(files);
1266
+ else latest.current.onCancellation?.();
1267
+ });
1268
+ }, [presented, setPresented]);
1269
+ return null;
1270
+ }
1271
+ const EXTENSIONS = {
1272
+ "text/plain": "txt",
1273
+ "text/csv": "csv",
1274
+ "text/markdown": "md",
1275
+ "text/html": "html",
1276
+ "application/json": "json",
1277
+ "application/pdf": "pdf",
1278
+ "image/png": "png",
1279
+ "image/jpeg": "jpg",
1280
+ "image/svg+xml": "svg",
1281
+ "audio/mp4": "m4a",
1282
+ "video/mp4": "mp4"
1283
+ };
1284
+ function exportFile(document2, contentType, defaultFilename, index = 0) {
1285
+ const type = document2 instanceof Blob && document2.type || contentType || "application/octet-stream";
1286
+ let name = defaultFilename ?? (document2 instanceof File ? document2.name : index === 0 ? "Untitled" : `Untitled ${index + 1}`);
1287
+ name = name.replace(/[\\/:*?"<>|]+/g, "-").trim() || "Untitled";
1288
+ const extension = EXTENSIONS[type.split(";")[0].trim()];
1289
+ if (extension && !/\.[A-Za-z0-9]{1,8}$/.test(name)) name = `${name}.${extension}`;
1290
+ if (document2 instanceof File && document2.name === name && document2.type === type) return document2;
1291
+ const bytes = typeof document2 === "string" ? new TextEncoder().encode(document2) : document2;
1292
+ return new File([bytes], name, { type });
1293
+ }
1294
+ function FileExporterPresenter(spec) {
1295
+ const [presented, setPresented] = readBinding(spec.isPresented);
1296
+ const latest = useRef(spec);
1297
+ latest.current = spec;
1298
+ const exporting = useRef(false);
1299
+ useLayoutEffect(() => {
1300
+ if (!presented || exporting.current) return;
1301
+ const { document: document2, contentType, defaultFilename } = latest.current;
1302
+ const documents = document2 === null ? [] : Array.isArray(document2) ? document2 : [document2];
1303
+ if (documents.length === 0) {
1304
+ setPresented(false);
1305
+ latest.current.onCancellation?.();
1306
+ return;
1307
+ }
1308
+ exporting.current = true;
1309
+ const files = documents.map((item, index) => exportFile(item, contentType, defaultFilename, index));
1310
+ exportFiles(files).then(
1311
+ (result) => {
1312
+ exporting.current = false;
1313
+ setPresented(false);
1314
+ if (result.outcome === "cancelled") latest.current.onCancellation?.();
1315
+ else latest.current.onCompletion?.(result);
1316
+ },
1317
+ (error) => {
1318
+ exporting.current = false;
1319
+ setPresented(false);
1320
+ latest.current.onError?.(error);
1321
+ }
1322
+ );
1323
+ }, [presented, setPresented]);
1324
+ return null;
1325
+ }
1326
+ const ListSelectionContext = createContext(null);
1327
+ function Row({ tag, onDelete, deletable, onMove, movable, index, containerIndex, children }) {
1328
+ const selection = useContext(ListSelectionContext);
1329
+ const selectable = selection !== null && tag !== void 0;
1330
+ const canDelete = onDelete !== void 0 && deletable !== false;
1331
+ const canMove = onMove !== void 0 && movable !== false;
1332
+ if (!selectable && !canDelete && !canMove) return children;
1333
+ const props = {};
1334
+ if (selectable) {
1335
+ props["selectable"] = true;
1336
+ props["selected"] = selection.isSelected(tag);
1337
+ }
1338
+ if (canDelete) props["deletable"] = true;
1339
+ if (canMove) props["movable"] = true;
1340
+ return sb(
1341
+ "listrow",
1342
+ props,
1343
+ {
1344
+ tap: () => selectable && selection.select(tag),
1345
+ delete: () => onDelete?.(),
1346
+ move: (event) => {
1347
+ const start = (containerIndex ?? index ?? 0) - (index ?? 0);
1348
+ onMove?.(Math.max(0, event.to - start));
1349
+ }
1350
+ },
1351
+ children
1352
+ );
1353
+ }
1092
1354
  const HIERARCHY = { primary: 1, secondary: 2, tertiary: 3, quaternary: 4 };
1093
1355
  function rgb(r2, g, b, a = 1) {
1094
1356
  return { r: r2, g, b, a };
@@ -1235,608 +1497,15 @@ function insets(edges, amount) {
1235
1497
  }
1236
1498
  return result;
1237
1499
  }
1238
- let installed = null;
1239
- function installTextMeasurer(measurer, remove = false) {
1240
- if (remove) {
1241
- if (installed === measurer) installed = null;
1242
- return;
1243
- }
1244
- installed = measurer;
1245
- }
1246
- function canMeasureText() {
1247
- return installed !== null;
1248
- }
1249
- function measureText(text, font$1 = "body", width) {
1250
- if (!installed) throw new Error("measureText: nothing measures text here yet (mount the app, or run it in a SwiftBrowser Native host)");
1251
- const constrained = typeof width === "number" && Number.isFinite(width) && width > 0 ? width : null;
1252
- return installed(text, font(font$1), constrained);
1500
+ const isPoint = (value) => typeof value === "object" && value !== null && ("x" in value || "y" in value);
1501
+ function command(request) {
1502
+ return request.axes ? { anchor: request.anchor, serial: request.serial, axes: request.axes } : { anchor: request.anchor, serial: request.serial };
1253
1503
  }
1254
- function decidePlatform(width, media = matchesMedia) {
1255
- return horizontalSizeClassFor(width) === "regular" && media("(hover: hover) and (pointer: fine)") ? "macos" : "ios";
1256
- }
1257
- function matchesMedia(query) {
1258
- return typeof matchMedia === "function" && matchMedia(query).matches;
1259
- }
1260
- function mount(screen, element, options = {}) {
1261
- let renderer = null;
1262
- const root = createSwiftUIRoot((ops) => renderer?.applyOps(ops));
1263
- renderer = new Renderer(screen, (event) => root.dispatch(event));
1264
- renderer.fileDrops = "reference";
1265
- const measurer = (text, font2, width2) => renderer.measureText(text, font2, width2);
1266
- installTextMeasurer(measurer);
1267
- let pageScheme = options.colorScheme ?? "light";
1268
- let preferred = null;
1269
- const width = screen.clientWidth || 393;
1270
- const environment = {
1271
- colorScheme: pageScheme,
1272
- dynamicTypeSize: options.dynamicTypeSize ?? "large",
1273
- horizontalSizeClass: horizontalSizeClassFor(width),
1274
- scenePhase: typeof document !== "undefined" ? currentScenePhase() : "active",
1275
- platform: options.platform === void 0 || options.platform === "auto" ? decidePlatform(width) : options.platform
1276
- };
1277
- renderer.contentHeight = options.contentHeight ?? false;
1278
- if (options.sidebarTitleBar === false) renderer.sidebarTitleBar = false;
1279
- if (options.assets) renderer.setAssetCatalog(options.assets);
1280
- if (options.app) browserEnvironment.files?.setApp?.(options.app);
1281
- renderer.setEnvironment({ colorScheme: environment.colorScheme, dynamicTypeSize: environment.dynamicTypeSize, platform: environment.platform });
1282
- if (options.safeArea !== void 0) renderer.setSafeArea(options.safeArea);
1283
- root.setEnvironment(environment);
1284
- const browserHistory = (options.history ?? true) && typeof window !== "undefined" ? new BrowserHistory(renderer, screen) : null;
1285
- browserHistory?.enable();
1286
- const stopKeyboard = options.keyboard && typeof window !== "undefined" ? watchKeyboard((height) => renderer?.setKeyboard(height), { root: screen }) : null;
1287
- const applyScheme = () => {
1288
- const scheme = preferred ?? pageScheme;
1289
- const device = screen.closest(".device");
1290
- if (device) device.dataset["theme"] = scheme;
1291
- renderer?.setEnvironment({ colorScheme: scheme });
1292
- root.setEnvironment({ colorScheme: scheme });
1293
- };
1294
- const onPreferredScheme = (event) => {
1295
- const scheme = event.detail.colorScheme;
1296
- preferred = scheme;
1297
- queueMicrotask(applyScheme);
1298
- };
1299
- screen.addEventListener("sb:color-scheme", onPreferredScheme);
1300
- let sizeClass = environment.horizontalSizeClass;
1301
- const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
1302
- const next = horizontalSizeClassFor(screen.clientWidth);
1303
- if (next === sizeClass) return;
1304
- sizeClass = next;
1305
- root.setEnvironment({ horizontalSizeClass: next });
1306
- }) : null;
1307
- observer?.observe(screen);
1308
- const stopScenePhase = typeof document !== "undefined" ? watchScenePhase((scenePhase) => root.setEnvironment({ scenePhase })) : null;
1309
- root.render(element);
1310
- const mounted = {
1311
- root,
1312
- renderer,
1313
- setEnvironment(next) {
1314
- if (next.colorScheme !== void 0) pageScheme = next.colorScheme;
1315
- const scheme = preferred ?? pageScheme;
1316
- renderer?.setEnvironment({ ...next, colorScheme: scheme });
1317
- root.setEnvironment({ ...next, colorScheme: scheme });
1318
- },
1319
- setSafeArea(insets2) {
1320
- renderer?.setSafeArea(insets2);
1321
- },
1322
- viewportChanged() {
1323
- renderer?.viewportChanged();
1324
- },
1325
- unmount() {
1326
- observer?.disconnect();
1327
- stopScenePhase?.();
1328
- stopKeyboard?.();
1329
- browserHistory?.dispose();
1330
- screen.removeEventListener("sb:color-scheme", onPreferredScheme);
1331
- installTextMeasurer(measurer, true);
1332
- root.unmount();
1333
- renderer?.dispose();
1334
- }
1335
- };
1336
- return mounted;
1337
- }
1338
- const EMPTY = { canUndo: false, canRedo: false, undoActionName: "", redoActionName: "" };
1339
- const defaultSchedule = (close) => {
1340
- if (typeof queueMicrotask === "function") queueMicrotask(close);
1341
- else void Promise.resolve().then(close);
1342
- };
1343
- class UndoManager {
1344
- /** Whether registrations in one event (one task of the event loop) form one group; true, as `groupsByEvent`. */
1345
- groupsByEvent = true;
1346
- /** How many groups the undo stack keeps; 0 for no limit, as `levelsOfUndo`. */
1347
- levelsOfUndo = 0;
1348
- undoStack = [];
1349
- redoStack = [];
1350
- /** The group registrations go in: an explicit group, the event's group, or the inverse of what is being undone or redone. */
1351
- open = null;
1352
- /** How deep `beginGrouping` is; 0 when `open` is the event's group or an inverse. */
1353
- depth = 0;
1354
- /** Whether `open` is the event's group, which `schedule` closes. */
1355
- byEvent = false;
1356
- /** The last keyed group closed onto the undo stack, which a registration with its key joins. */
1357
- coalescing = null;
1358
- undoing = false;
1359
- redoing = false;
1360
- snapshot = EMPTY;
1361
- listeners = /* @__PURE__ */ new Set();
1362
- schedule;
1363
- host;
1364
- stopListening = null;
1365
- constructor(options = {}) {
1366
- this.schedule = options.schedule ?? defaultSchedule;
1367
- this.host = options.host === void 0 ? browserEnvironment.undoManager ?? null : options.host;
1368
- if (this.host) {
1369
- this.stopListening = this.host.listen({ undo: () => this.undo(), redo: () => this.redo() });
1370
- this.host.update(this.snapshot);
1371
- }
1372
- }
1373
- get canUndo() {
1374
- return this.undoStack.length > 0;
1375
- }
1376
- get canRedo() {
1377
- return this.redoStack.length > 0;
1378
- }
1379
- /** The name of what `undo` takes back ('' for none): "Move" for an Edit menu's "Undo Move". */
1380
- get undoActionName() {
1381
- return this.undoStack[this.undoStack.length - 1]?.name ?? "";
1382
- }
1383
- get redoActionName() {
1384
- return this.redoStack[this.redoStack.length - 1]?.name ?? "";
1385
- }
1386
- get isUndoing() {
1387
- return this.undoing;
1388
- }
1389
- get isRedoing() {
1390
- return this.redoing;
1391
- }
1392
- /** What the stacks can do, one object per change (for `useSyncExternalStore`). */
1393
- get state() {
1394
- return this.snapshot;
1395
- }
1396
- /**
1397
- * Registers what undoes the change just made, under `name`. Run by `undo`,
1398
- * the closure registers its own inverse the same way, which lands on the
1399
- * redo stack because the manager is undoing.
1400
- */
1401
- registerUndo(name, undo, options = {}) {
1402
- const action = { name, undo, target: options.target };
1403
- if (this.undoing || this.redoing) {
1404
- this.open.actions.push(action);
1405
- this.changed();
1406
- return;
1407
- }
1408
- this.redoStack = [];
1409
- this.coalescing = this.coalescing && this.undoStack[this.undoStack.length - 1] === this.coalescing ? this.coalescing : null;
1410
- if (this.open) {
1411
- if (!this.undoStack.includes(this.open)) this.push(this.open);
1412
- this.open.actions.push(action);
1413
- if (!this.open.named) this.open.name = name;
1414
- } else if (this.coalescing && options.key !== void 0 && this.coalescing.key === options.key) {
1415
- this.coalescing.actions.push(action);
1416
- this.coalescing.name = name;
1417
- } else {
1418
- const group = { name, named: false, actions: [action], key: options.key };
1419
- this.push(group);
1420
- this.coalescing = null;
1421
- if (this.groupsByEvent) {
1422
- this.open = group;
1423
- this.byEvent = true;
1424
- this.schedule(() => {
1425
- if (this.open === group && this.byEvent) this.close();
1426
- });
1427
- } else {
1428
- this.coalescing = group.key !== void 0 ? group : null;
1429
- }
1430
- }
1431
- this.changed();
1432
- }
1433
- /** Takes back the top group of changes; its inverses become the top of the redo stack. */
1434
- undo() {
1435
- this.endOpenGroups();
1436
- const group = this.undoStack.pop();
1437
- if (!group) return;
1438
- this.coalescing = null;
1439
- this.run(group, "undo");
1440
- }
1441
- redo() {
1442
- this.endOpenGroups();
1443
- const group = this.redoStack.pop();
1444
- if (!group) return;
1445
- this.run(group, "redo");
1446
- }
1447
- /**
1448
- * Starts a group: every registration until `endGrouping` undoes as one
1449
- * (`beginUndoGrouping`). Nested calls join the outer group. The group is
1450
- * named `name`, else after its last registration.
1451
- */
1452
- beginGrouping(name) {
1453
- if (this.undoing || this.redoing) return;
1454
- this.depth += 1;
1455
- if (this.depth > 1) return;
1456
- if (this.open) this.close();
1457
- this.open = { name: name ?? "", named: name !== void 0, actions: [], key: void 0 };
1458
- this.byEvent = false;
1459
- this.coalescing = null;
1460
- }
1461
- /** Ends the group `beginGrouping` started; one that registered nothing is dropped. */
1462
- endGrouping() {
1463
- if (this.depth === 0) return;
1464
- this.depth -= 1;
1465
- if (this.depth === 0) this.close();
1466
- }
1467
- /** `body` as one group named `name`. */
1468
- group(name, body) {
1469
- this.beginGrouping(name);
1470
- try {
1471
- return body();
1472
- } finally {
1473
- this.endGrouping();
1474
- }
1475
- }
1476
- /** Forgets every registration, or those `target` made (`removeAllActions(withTarget:)`), on both stacks and in the open group. */
1477
- removeAllActions(target) {
1478
- const mine = (action) => target === void 0 || action.target === target;
1479
- const strip = (groups) => groups.filter((group) => {
1480
- group.actions = group.actions.filter((action) => !mine(action));
1481
- return group.actions.length > 0;
1482
- });
1483
- this.undoStack = strip(this.undoStack);
1484
- this.redoStack = strip(this.redoStack);
1485
- if (this.open) this.open.actions = this.open.actions.filter((action) => !mine(action));
1486
- if (this.coalescing && !this.undoStack.includes(this.coalescing)) this.coalescing = null;
1487
- this.changed();
1488
- }
1489
- /** Calls `listener` after each change to `state`; returns what stops it. */
1490
- subscribe = (listener) => {
1491
- this.listeners.add(listener);
1492
- return () => {
1493
- this.listeners.delete(listener);
1494
- };
1495
- };
1496
- /** Stops listening to the host, for a manager made in a test. */
1497
- dispose() {
1498
- this.stopListening?.();
1499
- this.stopListening = null;
1500
- }
1501
- push(group) {
1502
- this.undoStack.push(group);
1503
- if (this.levelsOfUndo > 0 && this.undoStack.length > this.levelsOfUndo) this.undoStack.splice(0, this.undoStack.length - this.levelsOfUndo);
1504
- }
1505
- /** Closes `open`; a keyed group on top stays open to its key. */
1506
- close() {
1507
- const group = this.open;
1508
- this.open = null;
1509
- this.byEvent = false;
1510
- if (group && group.key !== void 0 && this.undoStack[this.undoStack.length - 1] === group) this.coalescing = group;
1511
- }
1512
- endOpenGroups() {
1513
- this.depth = 0;
1514
- if (this.open) this.close();
1515
- }
1516
- run(group, direction) {
1517
- const inverse = { name: group.name, named: true, actions: [], key: void 0 };
1518
- this.open = inverse;
1519
- this.byEvent = false;
1520
- if (direction === "undo") this.undoing = true;
1521
- else this.redoing = true;
1522
- try {
1523
- for (let i = group.actions.length - 1; i >= 0; i -= 1) group.actions[i].undo();
1524
- } finally {
1525
- this.undoing = false;
1526
- this.redoing = false;
1527
- this.open = null;
1528
- if (inverse.actions.length > 0) {
1529
- if (direction === "undo") this.redoStack.push(inverse);
1530
- else this.push(inverse);
1531
- }
1532
- this.changed();
1533
- }
1534
- }
1535
- changed() {
1536
- const next = { canUndo: this.canUndo, canRedo: this.canRedo, undoActionName: this.undoActionName, redoActionName: this.redoActionName };
1537
- const last = this.snapshot;
1538
- if (next.canUndo === last.canUndo && next.canRedo === last.canRedo && next.undoActionName === last.undoActionName && next.redoActionName === last.redoActionName) return;
1539
- this.snapshot = next;
1540
- this.host?.update(next);
1541
- for (const listener of [...this.listeners]) listener();
1542
- }
1543
- }
1544
- const UndoManagerContext = createContext(null);
1545
- let shared = null;
1546
- function sharedUndoManager() {
1547
- shared ??= new UndoManager();
1548
- return shared;
1549
- }
1550
- function useUndoManager() {
1551
- const manager = useContext(UndoManagerContext) ?? sharedUndoManager();
1552
- useSyncExternalStore(
1553
- manager.subscribe,
1554
- () => manager.state,
1555
- () => manager.state
1556
- );
1557
- return manager;
1558
- }
1559
- const OWN = [
1560
- EnvironmentContext,
1561
- ViewEnvironmentContext,
1562
- NavigationContext,
1563
- DismissContext,
1564
- UndoManagerContext
1565
- ];
1566
- function useContextBridge(contexts = [], options = {}) {
1567
- const own = options.environment === false ? OWN.filter((context) => context !== EnvironmentContext) : OWN;
1568
- const all = [...own, ...contexts];
1569
- const values = all.map((context) => useContext(context));
1570
- return (children) => all.reduceRight((inner, context, index) => createElement(context.Provider, { value: values[index] }, inner), children);
1571
- }
1572
- const WINDOW_TOP_INSET = 10;
1573
- function insetsOf(safeArea, fit) {
1574
- const given = typeof safeArea === "number" ? { top: safeArea } : safeArea ?? { top: fit === "fill" ? WINDOW_TOP_INSET : 0 };
1575
- return { top: given.top ?? 0, leading: given.leading ?? 0, bottom: given.bottom ?? 0, trailing: given.trailing ?? 0 };
1576
- }
1577
- function SwiftUIView({
1578
- children,
1579
- fit = "fill",
1580
- colorScheme = "light",
1581
- dynamicTypeSize = "large",
1582
- contexts,
1583
- assets,
1584
- safeArea,
1585
- history = true,
1586
- keyboard = fit === "fill",
1587
- sidebarTitleBar = true,
1588
- className,
1589
- style
1590
- }) {
1591
- const device = useRef(null);
1592
- const screen = useRef(null);
1593
- const mounted = useRef(null);
1594
- const bridge = useContextBridge(contexts, { environment: false });
1595
- const tree = bridge(children);
1596
- const latest = useRef(tree);
1597
- latest.current = tree;
1598
- useLayoutEffect(() => {
1599
- if (!screen.current) return;
1600
- const instance = mount(screen.current, latest.current, {
1601
- colorScheme,
1602
- dynamicTypeSize,
1603
- safeArea: insetsOf(safeArea, fit),
1604
- contentHeight: fit === "content",
1605
- history,
1606
- keyboard,
1607
- ...assets ? { assets } : {}
1608
- });
1609
- mounted.current = instance;
1610
- return () => {
1611
- mounted.current = null;
1612
- instance.unmount();
1613
- };
1614
- }, [fit, history, keyboard]);
1615
- useLayoutEffect(() => {
1616
- if (mounted.current) mounted.current.renderer.sidebarTitleBar = sidebarTitleBar;
1617
- }, [sidebarTitleBar, fit, history, keyboard]);
1618
- useLayoutEffect(() => {
1619
- mounted.current?.root.render(tree);
1620
- });
1621
- const insets2 = insetsOf(safeArea, fit);
1622
- const insetsKey = `${insets2.top},${insets2.leading},${insets2.bottom},${insets2.trailing}`;
1623
- useLayoutEffect(() => {
1624
- mounted.current?.renderer.setSafeArea(insets2);
1625
- }, [insetsKey]);
1626
- useLayoutEffect(() => {
1627
- if (device.current) applyTypeSizeVariables(device.current, dynamicTypeSize, mounted.current?.renderer.platform);
1628
- mounted.current?.setEnvironment({ colorScheme, dynamicTypeSize });
1629
- }, [colorScheme, dynamicTypeSize]);
1630
- const classes = ["device", "sb-island", className].filter(Boolean).join(" ");
1631
- return /* @__PURE__ */ jsx("div", { ref: device, className: classes, "data-theme": colorScheme, "data-type-size": dynamicTypeSize, "data-state": "ready", "data-sb-fit": fit, style, children: /* @__PURE__ */ jsx("div", { className: "device-screen", children: /* @__PURE__ */ jsx("div", { ref: screen, className: "screen" }) }) });
1632
- }
1633
- function readBinding(value, onChange) {
1634
- if (Array.isArray(value) && value.length === 2 && typeof value[1] === "function") {
1635
- const [current, set] = value;
1636
- return [current, set];
1637
- }
1638
- return [value, onChange ?? (() => {
1639
- })];
1640
- }
1641
- const CoordinateSpacesContext = createContext({});
1642
- function useCoordinateSpaces() {
1643
- return useContext(CoordinateSpacesContext);
1644
- }
1645
- function CoordinateSpace({ name, children }) {
1646
- const spaces = useContext(CoordinateSpacesContext);
1647
- const [frame2, setFrame] = useState(null);
1648
- return /* @__PURE__ */ jsx(CoordinateSpacesContext.Provider, { value: { ...spaces, [name]: frame2 }, children: sb(
1649
- "zstack",
1650
- { alignment: "center", reportsSize: true, reportsFrame: true },
1651
- {
1652
- geometry: (event) => {
1653
- const next = { x: event.x ?? 0, y: event.y ?? 0, width: event.width, height: event.height };
1654
- if (!frame2 || frame2.x !== next.x || frame2.y !== next.y || frame2.width !== next.width || frame2.height !== next.height) setFrame(next);
1655
- }
1656
- },
1657
- children
1658
- ) });
1659
- }
1660
- let active = null;
1661
- function Draggable({ payload, children }) {
1662
- return sb(
1663
- "draggable",
1664
- {},
1665
- {
1666
- dragstart: (event) => {
1667
- active = { id: event.id, payload };
1668
- },
1669
- dragend: (event) => {
1670
- if (active?.id === event.id) active = null;
1671
- }
1672
- },
1673
- children
1674
- );
1675
- }
1676
- function itemValue(item) {
1677
- if (item.text !== void 0) {
1678
- if (item.type === "text/uri-list") {
1679
- try {
1680
- return new URL(item.text.split(/\r?\n/)[0].trim());
1681
- } catch {
1682
- return item.text;
1683
- }
1684
- }
1685
- return item.text;
1686
- }
1687
- const name = item.path ?? item.name ?? "file";
1688
- if (item.file) {
1689
- const file = item.file;
1690
- return item.path !== void 0 && item.path !== file.name ? new File([file], item.path, { type: file.type, lastModified: file.lastModified }) : file;
1691
- }
1692
- if (item.url !== void 0 && item.data === void 0) {
1693
- const fromReference = browserEnvironment.fileFromReference;
1694
- if (fromReference) {
1695
- return fromReference({ type: item.type, name, url: item.url, ...item.path !== void 0 ? { path: item.path } : {}, ...item.size !== void 0 ? { size: item.size } : {} });
1696
- }
1697
- }
1698
- const binary = atob(item.data ?? "");
1699
- const bytes = new Uint8Array(binary.length);
1700
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1701
- return typeof File !== "undefined" ? new File([bytes], name, { type: item.type }) : bytes;
1702
- }
1703
- function DropDestination({ action, isTargeted, accepts, children }) {
1704
- return sb(
1705
- "dropdestination",
1706
- { accepts: accepts ?? [] },
1707
- {
1708
- drop: (event) => {
1709
- if (event.phase === "entered") {
1710
- isTargeted?.(true);
1711
- return;
1712
- }
1713
- isTargeted?.(false);
1714
- if (event.phase !== "performed") return;
1715
- let items;
1716
- if (event.source !== void 0) items = active && active.id === event.source ? [active.payload] : [];
1717
- else items = (event.items ?? []).map(itemValue);
1718
- if (items.length > 0) action(items, { x: event.x, y: event.y });
1719
- }
1720
- },
1721
- children
1722
- );
1723
- }
1724
- function FileImporterPresenter(spec) {
1725
- const [presented, setPresented] = readBinding(spec.isPresented);
1726
- const latest = useRef(spec);
1727
- latest.current = spec;
1728
- const choosing = useRef(false);
1729
- useLayoutEffect(() => {
1730
- if (!presented || choosing.current) return;
1731
- const { allowedContentTypes, allowsMultipleSelection } = latest.current;
1732
- const pick = browserEnvironment.pickFiles;
1733
- if (!pick) {
1734
- setPresented(false);
1735
- latest.current.onCancellation?.();
1736
- return;
1737
- }
1738
- choosing.current = true;
1739
- void pick({ accept: allowedContentTypes.join(","), multiple: allowsMultipleSelection === true }).then((files) => {
1740
- choosing.current = false;
1741
- setPresented(false);
1742
- if (files.length > 0) latest.current.onCompletion(files);
1743
- else latest.current.onCancellation?.();
1744
- });
1745
- }, [presented, setPresented]);
1746
- return null;
1747
- }
1748
- const EXTENSIONS = {
1749
- "text/plain": "txt",
1750
- "text/csv": "csv",
1751
- "text/markdown": "md",
1752
- "text/html": "html",
1753
- "application/json": "json",
1754
- "application/pdf": "pdf",
1755
- "image/png": "png",
1756
- "image/jpeg": "jpg",
1757
- "image/svg+xml": "svg",
1758
- "audio/mp4": "m4a",
1759
- "video/mp4": "mp4"
1760
- };
1761
- function exportFile(document2, contentType, defaultFilename, index = 0) {
1762
- const type = document2 instanceof Blob && document2.type || contentType || "application/octet-stream";
1763
- let name = defaultFilename ?? (document2 instanceof File ? document2.name : index === 0 ? "Untitled" : `Untitled ${index + 1}`);
1764
- name = name.replace(/[\\/:*?"<>|]+/g, "-").trim() || "Untitled";
1765
- const extension = EXTENSIONS[type.split(";")[0].trim()];
1766
- if (extension && !/\.[A-Za-z0-9]{1,8}$/.test(name)) name = `${name}.${extension}`;
1767
- if (document2 instanceof File && document2.name === name && document2.type === type) return document2;
1768
- const bytes = typeof document2 === "string" ? new TextEncoder().encode(document2) : document2;
1769
- return new File([bytes], name, { type });
1770
- }
1771
- function FileExporterPresenter(spec) {
1772
- const [presented, setPresented] = readBinding(spec.isPresented);
1773
- const latest = useRef(spec);
1774
- latest.current = spec;
1775
- const exporting = useRef(false);
1776
- useLayoutEffect(() => {
1777
- if (!presented || exporting.current) return;
1778
- const { document: document2, contentType, defaultFilename } = latest.current;
1779
- const documents = document2 === null ? [] : Array.isArray(document2) ? document2 : [document2];
1780
- if (documents.length === 0) {
1781
- setPresented(false);
1782
- latest.current.onCancellation?.();
1783
- return;
1784
- }
1785
- exporting.current = true;
1786
- const files = documents.map((item, index) => exportFile(item, contentType, defaultFilename, index));
1787
- exportFiles(files).then(
1788
- (result) => {
1789
- exporting.current = false;
1790
- setPresented(false);
1791
- if (result.outcome === "cancelled") latest.current.onCancellation?.();
1792
- else latest.current.onCompletion?.(result);
1793
- },
1794
- (error) => {
1795
- exporting.current = false;
1796
- setPresented(false);
1797
- latest.current.onError?.(error);
1798
- }
1799
- );
1800
- }, [presented, setPresented]);
1801
- return null;
1802
- }
1803
- const ListSelectionContext = createContext(null);
1804
- function Row({ tag, onDelete, deletable, onMove, movable, index, containerIndex, children }) {
1805
- const selection = useContext(ListSelectionContext);
1806
- const selectable = selection !== null && tag !== void 0;
1807
- const canDelete = onDelete !== void 0 && deletable !== false;
1808
- const canMove = onMove !== void 0 && movable !== false;
1809
- if (!selectable && !canDelete && !canMove) return children;
1810
- const props = {};
1811
- if (selectable) {
1812
- props["selectable"] = true;
1813
- props["selected"] = selection.isSelected(tag);
1814
- }
1815
- if (canDelete) props["deletable"] = true;
1816
- if (canMove) props["movable"] = true;
1817
- return sb(
1818
- "listrow",
1819
- props,
1820
- {
1821
- tap: () => selectable && selection.select(tag),
1822
- delete: () => onDelete?.(),
1823
- move: (event) => {
1824
- const start = (containerIndex ?? index ?? 0) - (index ?? 0);
1825
- onMove?.(Math.max(0, event.to - start));
1826
- }
1827
- },
1828
- children
1829
- );
1830
- }
1831
- const isPoint = (value) => typeof value === "object" && value !== null && ("x" in value || "y" in value);
1832
- function command(request) {
1833
- return request.axes ? { anchor: request.anchor, serial: request.serial, axes: request.axes } : { anchor: request.anchor, serial: request.serial };
1834
- }
1835
- function offsetCommand(request) {
1836
- const out = { serial: request.serial };
1837
- if (typeof request.point.x === "number") out.x = request.point.x;
1838
- if (typeof request.point.y === "number") out.y = request.point.y;
1839
- return out;
1504
+ function offsetCommand(request) {
1505
+ const out = { serial: request.serial };
1506
+ if (typeof request.point.x === "number") out.x = request.point.x;
1507
+ if (typeof request.point.y === "number") out.y = request.point.y;
1508
+ return out;
1840
1509
  }
1841
1510
  const ScrollRequestContext = createContext(null);
1842
1511
  let nextSerial = 1;
@@ -1956,29 +1625,49 @@ function marginedContent(children, margins, axis) {
1956
1625
  }
1957
1626
  const LAZY_BATCH = 12;
1958
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);
1959
1632
  function LazyGroup({ rows, start = 0, wrap: wrap2 }) {
1960
1633
  const axis = useContext(LazyContext);
1634
+ const batch = useContext(LazyBatchContext);
1961
1635
  const request = useContext(ScrollRequestContext);
1962
- 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: [] });
1963
1639
  const target = request && "id" in request ? rows.findIndex((row) => rowMatches(row, request.id)) : -1;
1964
- 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;
1965
1641
  useEffect(() => {
1966
1642
  if (needed > count) setCount(needed);
1967
- }, [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]);
1968
1645
  const visible = Math.max(count, needed);
1969
- const shown = axis === null ? rows : rows.slice(0, visible);
1970
- const built = shown.map((row, index) => wrap2 ? wrap2(row, start + index) : row);
1971
- if (axis === null || shown.length >= rows.length) return built;
1972
- return [
1973
- ...built,
1974
- createElement(
1975
- LazySpacer,
1976
- { key: `lazy-${visible}`, axis, remaining: rows.length - shown.length, more: () => setCount(visible + LAZY_BATCH) }
1977
- )
1978
- ];
1979
- }
1980
- function LazySpacer({ axis, remaining, more }) {
1981
- 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 });
1982
1671
  }
1983
1672
  function rowMatches(row, id) {
1984
1673
  if (row.key !== null && row.key === String(id)) return true;
@@ -2008,6 +1697,11 @@ function HStack(props) {
2008
1697
  if (own.pinnedViews && own.pinnedViews.length > 0) stack["pinned"] = own.pinnedViews;
2009
1698
  return applyModifiers(sb("hstack", stack, void 0, pinning(own.pinnedViews, own.children)), modifiers);
2010
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
+ }
2011
1705
  function ZStack(props) {
2012
1706
  const { own, modifiers } = splitProps(props);
2013
1707
  return applyModifiers(sb("zstack", { alignment: own.alignment ?? "center" }, void 0, own.children), modifiers);
@@ -2140,547 +1834,910 @@ function keyOf(element, index) {
2140
1834
  const id = element.id;
2141
1835
  if (typeof id === "string" || typeof id === "number") return id;
2142
1836
  }
2143
- return index;
1837
+ return index;
1838
+ }
1839
+ function flattenChildren(children, options = {}) {
1840
+ const result = [];
1841
+ const visit = (node) => {
1842
+ if (node === null || node === void 0 || typeof node === "boolean") return;
1843
+ if (Array.isArray(node)) {
1844
+ node.forEach(visit);
1845
+ return;
1846
+ }
1847
+ if (!isValidElement(node)) return;
1848
+ const element = node;
1849
+ if (element.type === Fragment) {
1850
+ const inner = element.props["children"];
1851
+ if (element.key !== null && isValidElement(inner) && inner.key === null && inner.type !== Fragment) {
1852
+ visit({ ...inner, key: element.key });
1853
+ return;
1854
+ }
1855
+ visit(inner);
1856
+ return;
1857
+ }
1858
+ if (element.type === ForEach) {
1859
+ visit(ForEach(element.props));
1860
+ return;
1861
+ }
1862
+ if (element.type === LazyGroup && !options.keepLazy) {
1863
+ visit(element.props.rows);
1864
+ return;
1865
+ }
1866
+ result.push(element);
1867
+ };
1868
+ visit(children);
1869
+ return result;
1870
+ }
1871
+ function GridItem(spec = {}) {
1872
+ const dimension = (n) => n === void 0 || n === Infinity ? "infinity" : n;
1873
+ let size;
1874
+ if (spec.size && "fixed" in spec.size) size = { kind: "fixed", value: spec.size.fixed };
1875
+ else if (spec.size && "adaptive" in spec.size) {
1876
+ size = { kind: "adaptive", minimum: spec.size.adaptive.minimum, maximum: dimension(spec.size.adaptive.maximum) };
1877
+ } else {
1878
+ const flexible = spec.size && "flexible" in spec.size ? spec.size.flexible : {};
1879
+ size = { kind: "flexible", minimum: flexible.minimum ?? 10, maximum: dimension(flexible.maximum) };
1880
+ }
1881
+ return { size, spacing: spec.spacing ?? null, alignment: spec.alignment ?? null };
1882
+ }
1883
+ GridItem.flexible = (options = {}) => GridItem({ size: { flexible: options }, spacing: options.spacing, alignment: options.alignment });
1884
+ GridItem.fixed = (value, options = {}) => GridItem({ size: { fixed: value }, spacing: options.spacing, alignment: options.alignment });
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
+ }
1889
+ function LazyVGrid(props) {
1890
+ const { own, modifiers } = splitProps(props);
1891
+ const grid = { columns: own.columns, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1892
+ if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
1893
+ return applyModifiers(sb("lazyvgrid", grid, void 0, lazyCells("vertical", pinning(own.pinnedViews, own.children))), modifiers);
1894
+ }
1895
+ function LazyHGrid(props) {
1896
+ const { own, modifiers } = splitProps(props);
1897
+ const grid = { rows: own.rows, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1898
+ if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
1899
+ return applyModifiers(sb("lazyhgrid", grid, void 0, lazyCells("horizontal", pinning(own.pinnedViews, own.children))), modifiers);
1900
+ }
1901
+ function Grid(props) {
1902
+ const { own, modifiers } = splitProps(props);
1903
+ return applyModifiers(
1904
+ sb(
1905
+ "grid",
1906
+ { horizontalSpacing: own.horizontalSpacing ?? null, verticalSpacing: own.verticalSpacing ?? null, alignment: own.alignment ?? "center" },
1907
+ void 0,
1908
+ own.children
1909
+ ),
1910
+ modifiers
1911
+ );
1912
+ }
1913
+ function GridRow(props) {
1914
+ const { own, modifiers } = splitProps(props);
1915
+ return applyModifiers(sb("gridrow", { alignment: own.alignment ?? null }, void 0, own.children), modifiers);
1916
+ }
1917
+ function runStyleOf(props, inherited, nested) {
1918
+ const style = { ...inherited };
1919
+ if (props.italic) style.italic = true;
1920
+ if (props.underline) style.underline = typeof props.underline === "object" && props.underline.color ? { color: color(props.underline.color) } : {};
1921
+ if (props.strikethrough) {
1922
+ style.strikethrough = typeof props.strikethrough === "object" && props.strikethrough.color ? { color: color(props.strikethrough.color) } : {};
1923
+ }
1924
+ const kerning = props.tracking ?? props.kerning;
1925
+ if (kerning !== void 0) style.kerning = kerning;
1926
+ if (props.baselineOffset !== void 0) style.baselineOffset = props.baselineOffset;
1927
+ if (props.link) style.link = props.link;
1928
+ if (nested) {
1929
+ let runFont2 = props.font !== void 0 ? font(props.font) : void 0;
1930
+ if (props.bold) runFont2 = { ...runFont2 ?? {}, weight: "bold" };
1931
+ if (props.fontWeight) runFont2 = { ...runFont2 ?? {}, weight: props.fontWeight };
1932
+ if (props.monospacedDigit) runFont2 = { ...runFont2 ?? {}, monospacedDigit: true };
1933
+ if (runFont2) style.font = { ...style.font ?? {}, ...runFont2 };
1934
+ if (props.foregroundStyle !== void 0) {
1935
+ style.foreground = shapeStyle(Array.isArray(props.foregroundStyle) ? props.foregroundStyle[0] : props.foregroundStyle);
1936
+ }
1937
+ const background = runBackground(props.background);
1938
+ if (background) style.background = background;
1939
+ }
1940
+ return style;
1941
+ }
1942
+ function runBackground(value) {
1943
+ if (typeof value === "string") {
1944
+ const made = color(value);
1945
+ return "hierarchical" in made ? void 0 : made;
1946
+ }
1947
+ if (typeof value !== "object" || value === null || isValidElement(value) || "in" in value) return void 0;
1948
+ return "r" in value || "name" in value || "named" in value ? value : void 0;
1949
+ }
1950
+ function segmentsOf(children, style, into) {
1951
+ const visit = (node) => {
1952
+ if (node === null || node === void 0 || typeof node === "boolean") return;
1953
+ if (typeof node === "string" || typeof node === "number") {
1954
+ const last = into[into.length - 1];
1955
+ if (last && "text" in last && last.style === style) last.text += String(node);
1956
+ else into.push({ text: String(node), style });
1957
+ return;
1958
+ }
1959
+ if (Array.isArray(node)) {
1960
+ node.forEach(visit);
1961
+ return;
1962
+ }
1963
+ if (!isValidElement(node)) return;
1964
+ const element = node;
1965
+ if (element.type === Fragment) {
1966
+ visit(element.props["children"]);
1967
+ return;
1968
+ }
1969
+ if (element.type === Text) {
1970
+ const props = element.props;
1971
+ segmentsOf(props.children, runStyleOf(props, style, true), into);
1972
+ return;
1973
+ }
1974
+ if (element.type === Image$1) {
1975
+ const props = element.props;
1976
+ into.push({ image: imageProps$1(props), style });
1977
+ return;
1978
+ }
1979
+ };
1980
+ visit(children);
1981
+ }
1982
+ function isPlainStyle(style) {
1983
+ return Object.keys(style).length === 0;
1984
+ }
1985
+ function Text(props) {
1986
+ const { own, modifiers } = splitProps(props);
1987
+ const environment = useViewEnvironment();
1988
+ const style = runStyleOf(props, {}, false);
1989
+ const segments = [];
1990
+ segmentsOf(own.children, style, segments);
1991
+ const textCase = takeModifier(modifiers, "textCase") ?? environment.textCase;
1992
+ if (textCase) {
1993
+ for (const segment of segments) {
1994
+ if ("text" in segment) segment.text = textCase === "uppercase" ? segment.text.toUpperCase() : segment.text.toLowerCase();
1995
+ }
1996
+ }
1997
+ const redacted = takeModifier(modifiers, "redacted") ?? environment.redacted;
1998
+ const content = segments.map((segment) => "text" in segment ? segment.text : "").join("");
1999
+ const plain2 = segments.length <= 1 && (segments.length === 0 || "text" in segments[0] && isPlainStyle(segments[0].style));
2000
+ if (plain2) return applyModifiers(placeholder(sb("text", { text: content }), redacted), modifiers);
2001
+ const runs = segments.map((segment) => {
2002
+ const run = { ...segment.style };
2003
+ if ("text" in segment) run["text"] = segment.text;
2004
+ else run["image"] = segment.image;
2005
+ return run;
2006
+ });
2007
+ const hasLinks = segments.some((segment) => segment.style.link);
2008
+ const handlers = hasLinks ? { link: (event) => openURL(event.value) } : void 0;
2009
+ return applyModifiers(placeholder(sb("text", { text: content, runs }, handlers), redacted), modifiers);
2010
+ }
2011
+ function placeholder(node, redacted) {
2012
+ if (!redacted) return node;
2013
+ return styled({ background: { hierarchical: 3 }, backgroundCornerRadius: 4 }, styled({ foreground: { name: "clear" } }, node));
2014
+ }
2015
+ function openURL(url) {
2016
+ const native = globalThis.__sbNative;
2017
+ if (native?.openURL) {
2018
+ native.openURL(url);
2019
+ return;
2020
+ }
2021
+ if (typeof window !== "undefined") window.open(url, "_blank", "noopener");
2022
+ }
2023
+ function imageProps$1(props) {
2024
+ if (props.systemName !== void 0) return { systemName: props.systemName };
2025
+ if (props.bitmap !== void 0) {
2026
+ const { source, steps = [], version } = props.bitmap;
2027
+ const extent = bitmapExtent(source, steps);
2028
+ const finite = Number.isFinite(extent.width) && Number.isFinite(extent.height);
2029
+ const width = finite ? Math.max(0, Math.round(extent.width)) : 0;
2030
+ const height = finite ? Math.max(0, Math.round(extent.height)) : 0;
2031
+ const scale = props.scale !== void 0 && props.scale > 0 ? props.scale : 1;
2032
+ return {
2033
+ bitmap: { source: source?.key ?? null, steps, width, height, ...version !== void 0 ? { version } : {} },
2034
+ resizable: props.resizable ?? false,
2035
+ width: width / scale,
2036
+ height: height / scale,
2037
+ ...props.interpolation === "none" ? { interpolation: "none" } : {}
2038
+ };
2039
+ }
2040
+ if (props.url !== void 0) return { url: props.url, resizable: props.resizable ?? false, width: props.width ?? 0, height: props.height ?? 0 };
2041
+ return { name: props.name ?? "", resizable: props.resizable ?? false };
2144
2042
  }
2145
- function flattenChildren(children, options = {}) {
2146
- const result = [];
2147
- const visit = (node) => {
2148
- if (node === null || node === void 0 || typeof node === "boolean") return;
2149
- if (Array.isArray(node)) {
2150
- node.forEach(visit);
2151
- return;
2152
- }
2153
- if (!isValidElement(node)) return;
2154
- const element = node;
2155
- if (element.type === Fragment) {
2156
- const inner = element.props["children"];
2157
- if (element.key !== null && isValidElement(inner) && inner.key === null && inner.type !== Fragment) {
2158
- visit({ ...inner, key: element.key });
2159
- return;
2160
- }
2161
- visit(inner);
2162
- return;
2163
- }
2164
- if (element.type === ForEach) {
2165
- visit(ForEach(element.props));
2166
- return;
2167
- }
2168
- if (element.type === LazyGroup && !options.keepLazy) {
2169
- visit(element.props.rows);
2170
- return;
2171
- }
2172
- result.push(element);
2173
- };
2174
- visit(children);
2175
- return result;
2043
+ function Image$1(props) {
2044
+ const { modifiers } = splitProps(props);
2045
+ const environment = useViewEnvironment();
2046
+ const redacted = takeModifier(modifiers, "redacted") ?? environment.redacted;
2047
+ const mode = takeModifier(modifiers, "symbolRenderingMode") ?? environment.symbolRenderingMode;
2048
+ const own = imageProps$1(props);
2049
+ const element = props.systemName !== void 0 && mode === "palette" ? { ...own, renderingMode: "palette" } : own;
2050
+ if (props.accessibilityLabel) element["accessibilityLabel"] = props.accessibilityLabel;
2051
+ return applyModifiers(placeholder(sb("image", element), redacted), modifiers);
2176
2052
  }
2177
- function GridItem(spec = {}) {
2178
- const dimension = (n) => n === void 0 || n === Infinity ? "infinity" : n;
2179
- let size;
2180
- if (spec.size && "fixed" in spec.size) size = { kind: "fixed", value: spec.size.fixed };
2181
- else if (spec.size && "adaptive" in spec.size) {
2182
- size = { kind: "adaptive", minimum: spec.size.adaptive.minimum, maximum: dimension(spec.size.adaptive.maximum) };
2183
- } else {
2184
- const flexible = spec.size && "flexible" in spec.size ? spec.size.flexible : {};
2185
- size = { kind: "flexible", minimum: flexible.minimum ?? 10, maximum: dimension(flexible.maximum) };
2053
+ function Label(props) {
2054
+ const { own, modifiers } = splitProps(props);
2055
+ const environment = useViewEnvironment();
2056
+ const style = takeModifier(modifiers, "labelStyle") ?? environment.labelStyle ?? "automatic";
2057
+ const icon = own.icon ?? (own.systemImage !== void 0 ? /* @__PURE__ */ jsx(Image$1, { systemName: own.systemImage }) : own.image !== void 0 ? /* @__PURE__ */ jsx(Image$1, { name: own.image }) : null);
2058
+ const title = typeof own.title === "string" || typeof own.title === "number" ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.title;
2059
+ if (style === "iconOnly") {
2060
+ const name = typeof own.title === "string" || typeof own.title === "number" ? String(own.title) : void 0;
2061
+ const named = own.icon === void 0 && name !== void 0 ? own.systemImage !== void 0 ? /* @__PURE__ */ jsx(Image$1, { systemName: own.systemImage, accessibilityLabel: name }) : own.image !== void 0 ? /* @__PURE__ */ jsx(Image$1, { name: own.image, accessibilityLabel: name }) : icon : icon;
2062
+ return applyModifiers(named, modifiers);
2186
2063
  }
2187
- return { size, spacing: spec.spacing ?? null, alignment: spec.alignment ?? null };
2064
+ if (style === "titleOnly") return applyModifiers(title, modifiers);
2065
+ return applyModifiers(
2066
+ /* @__PURE__ */ jsxs(HStack, { spacing: 8, children: [
2067
+ style === "titleAndIcon" ? icon : styled({ labelIcon: true }, icon),
2068
+ title
2069
+ ] }),
2070
+ modifiers
2071
+ );
2188
2072
  }
2189
- GridItem.flexible = (options = {}) => GridItem({ size: { flexible: options }, spacing: options.spacing, alignment: options.alignment });
2190
- GridItem.fixed = (value, options = {}) => GridItem({ size: { fixed: value }, spacing: options.spacing, alignment: options.alignment });
2191
- GridItem.adaptive = (options) => GridItem({ size: { adaptive: options }, spacing: options.spacing, alignment: options.alignment });
2192
- function LazyVGrid(props) {
2073
+ function LabeledContent(props) {
2193
2074
  const { own, modifiers } = splitProps(props);
2194
- const grid = { columns: own.columns, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
2195
- if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
2196
- return applyModifiers(sb("lazyvgrid", grid, void 0, pinning(own.pinnedViews, own.children)), modifiers);
2075
+ const label = typeof own.label === "string" || typeof own.label === "number" ? /* @__PURE__ */ jsx(Text, { children: own.label }) : own.label;
2076
+ const content = own.children ?? own.value;
2077
+ const value = typeof content === "string" || typeof content === "number" ? /* @__PURE__ */ jsx(Text, { children: content }) : content;
2078
+ return applyModifiers(
2079
+ /* @__PURE__ */ jsxs(HStack, { children: [
2080
+ label,
2081
+ sb("spacer", { minLength: null }),
2082
+ styled({ foreground: { hierarchical: 2 } }, value)
2083
+ ] }),
2084
+ modifiers
2085
+ );
2197
2086
  }
2198
- function LazyHGrid(props) {
2087
+ function Link(props) {
2199
2088
  const { own, modifiers } = splitProps(props);
2200
- const grid = { rows: own.rows, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
2201
- if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
2202
- return applyModifiers(sb("lazyhgrid", grid, void 0, pinning(own.pinnedViews, own.children)), modifiers);
2089
+ const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.children;
2090
+ return applyModifiers(sb("button", { role: null, style: "automatic" }, { tap: () => openURL(own.destination) }, label), modifiers);
2203
2091
  }
2204
- function Grid(props) {
2092
+ const isPushedView = (value) => typeof value === "object" && value !== null && "view" in value && isValidElement(value.view);
2093
+ function routeOf(value, depth = 0) {
2094
+ if (depth > 8) return void 0;
2095
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
2096
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
2097
+ if (Array.isArray(value)) {
2098
+ const items = value.map((item) => routeOf(item, depth + 1));
2099
+ return items.every((item) => item !== void 0) ? items : void 0;
2100
+ }
2101
+ if (typeof value === "object" && !isPushedView(value)) {
2102
+ const proto = Object.getPrototypeOf(value);
2103
+ if (proto !== Object.prototype && proto !== null) return void 0;
2104
+ const out = {};
2105
+ for (const [key, item] of Object.entries(value)) {
2106
+ if (item === void 0) continue;
2107
+ const plain2 = routeOf(item, depth + 1);
2108
+ if (plain2 === void 0) return void 0;
2109
+ out[key] = plain2;
2110
+ }
2111
+ return out;
2112
+ }
2113
+ return void 0;
2114
+ }
2115
+ function NavigationStack(props) {
2116
+ const enclosing = useNavigation();
2117
+ if (enclosing) return /* @__PURE__ */ jsx(FlattenedStack, { ...props });
2118
+ return /* @__PURE__ */ jsx(OwnStack, { ...props });
2119
+ }
2120
+ function FlattenedStack(props) {
2205
2121
  const { own, modifiers } = splitProps(props);
2206
2122
  return applyModifiers(
2207
- sb(
2208
- "grid",
2209
- { horizontalSpacing: own.horizontalSpacing ?? null, verticalSpacing: own.verticalSpacing ?? null, alignment: own.alignment ?? "center" },
2210
- void 0,
2123
+ /* @__PURE__ */ jsxs(Fragment$1, { children: [
2124
+ own.destination && /* @__PURE__ */ jsx(RegisterDestination, { destination: own.destination }),
2211
2125
  own.children
2212
- ),
2126
+ ] }),
2213
2127
  modifiers
2214
2128
  );
2215
2129
  }
2216
- function GridRow(props) {
2130
+ function OwnStack(props) {
2217
2131
  const { own, modifiers } = splitProps(props);
2218
- return applyModifiers(sb("gridrow", { alignment: own.alignment ?? null }, void 0, own.children), modifiers);
2132
+ const remote = useContext(HostTreeContext)?.remote ?? false;
2133
+ const [internalPath, setInternalPath] = useState([]);
2134
+ const [path, setPath] = own.path !== void 0 ? readBinding(own.path, own.onPathChange) : [internalPath, setInternalPath];
2135
+ const destination = useRef(null);
2136
+ destination.current = own.destination ?? null;
2137
+ const context = {
2138
+ push: (value) => setPath([...path, value]),
2139
+ pop: () => setPath(path.slice(0, -1)),
2140
+ path,
2141
+ setPath: (next) => setPath(next),
2142
+ registerDestination: (builder) => {
2143
+ destination.current = builder;
2144
+ }
2145
+ };
2146
+ const screens = [
2147
+ /* @__PURE__ */ jsx(Fragment, { children: sb("navscreen", { title: "", displayMode: "automatic", depth: 0 }, { tap: context.pop }, /* @__PURE__ */ jsx(ScreenDepthContext.Provider, { value: 0, children: own.children })) }, "root")
2148
+ ];
2149
+ path.forEach((value, index) => {
2150
+ const pop = () => setPath(path.slice(0, index));
2151
+ const route = remote ? routeOf(value) : void 0;
2152
+ screens.push(
2153
+ /* @__PURE__ */ jsx(Fragment, { children: sb(
2154
+ "navscreen",
2155
+ { title: "", displayMode: "automatic", depth: index + 1, ...route === void 0 ? {} : { route } },
2156
+ { tap: pop },
2157
+ /* @__PURE__ */ jsx(DismissContext.Provider, { value: pop, children: /* @__PURE__ */ jsx(ScreenDepthContext.Provider, { value: index + 1, children: /* @__PURE__ */ jsx(PathScreen, { value, destination }) }) })
2158
+ ) }, index + 1)
2159
+ );
2160
+ });
2161
+ return applyModifiers(
2162
+ sb("navstack", {}, { setpath: (event) => setPath(Array.isArray(event.path) ? event.path : []) }, /* @__PURE__ */ jsx(NavigationContext.Provider, { value: context, children: screens })),
2163
+ modifiers
2164
+ );
2219
2165
  }
2220
- function runStyleOf(props, inherited, nested) {
2221
- const style = { ...inherited };
2222
- if (props.italic) style.italic = true;
2223
- if (props.underline) style.underline = typeof props.underline === "object" && props.underline.color ? { color: color(props.underline.color) } : {};
2224
- if (props.strikethrough) {
2225
- style.strikethrough = typeof props.strikethrough === "object" && props.strikethrough.color ? { color: color(props.strikethrough.color) } : {};
2226
- }
2227
- const kerning = props.tracking ?? props.kerning;
2228
- if (kerning !== void 0) style.kerning = kerning;
2229
- if (props.baselineOffset !== void 0) style.baselineOffset = props.baselineOffset;
2230
- if (props.link) style.link = props.link;
2231
- if (nested) {
2232
- let runFont2 = props.font !== void 0 ? font(props.font) : void 0;
2233
- if (props.bold) runFont2 = { ...runFont2 ?? {}, weight: "bold" };
2234
- if (props.fontWeight) runFont2 = { ...runFont2 ?? {}, weight: props.fontWeight };
2235
- if (props.monospacedDigit) runFont2 = { ...runFont2 ?? {}, monospacedDigit: true };
2236
- if (runFont2) style.font = { ...style.font ?? {}, ...runFont2 };
2237
- if (props.foregroundStyle !== void 0) {
2238
- style.foreground = shapeStyle(Array.isArray(props.foregroundStyle) ? props.foregroundStyle[0] : props.foregroundStyle);
2166
+ function PathScreen({ value, destination }) {
2167
+ if (isPushedView(value)) return value.view;
2168
+ return destination.current ? destination.current(value) : null;
2169
+ }
2170
+ function NavigationLink(props) {
2171
+ const { own, modifiers } = splitProps(props);
2172
+ const navigation = useNavigation();
2173
+ const environment = useViewEnvironment();
2174
+ const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.children;
2175
+ const listSelection = useContext(ListSelectionContext);
2176
+ const sidebar = useContext(SplitSelectionContext) !== null && listSelection !== null && own.destination === void 0 && own.value !== void 0;
2177
+ const tap = () => {
2178
+ if (sidebar) {
2179
+ listSelection.select(own.value);
2180
+ return;
2239
2181
  }
2240
- const background = runBackground(props.background);
2241
- if (background) style.background = background;
2242
- }
2243
- return style;
2182
+ if (!navigation) return;
2183
+ if (own.destination !== void 0) navigation.push({ view: own.destination });
2184
+ else navigation.push(own.value);
2185
+ };
2186
+ const style = takeModifier(modifiers, "buttonStyle") ?? environment.buttonStyle;
2187
+ const linkProps = style === "plain" ? { style: "plain" } : {};
2188
+ if (sidebar) linkProps["selects"] = true;
2189
+ return applyModifiers(sb("navlink", linkProps, { tap }, label), modifiers);
2244
2190
  }
2245
- function runBackground(value) {
2246
- if (typeof value === "string") {
2247
- const made = color(value);
2248
- return "hierarchical" in made ? void 0 : made;
2191
+ const SplitSelectionContext = createContext(null);
2192
+ function splitColumnWidthHint(width) {
2193
+ if (typeof width === "number") return { min: width, ideal: width, max: width };
2194
+ return { ...width.min !== void 0 ? { min: width.min } : {}, ideal: width.ideal, ...width.max !== void 0 ? { max: width.max } : {} };
2195
+ }
2196
+ function NavigationSplitView(props) {
2197
+ const { own, modifiers } = splitProps(props);
2198
+ const sizeClass = useHorizontalSizeClass();
2199
+ const [selected, setSelected] = useState(false);
2200
+ const reporters = useRef(/* @__PURE__ */ new Map());
2201
+ const settle = () => setSelected([...reporters.current.values()].some((entry) => entry.selected));
2202
+ const [report] = useState(() => {
2203
+ return {
2204
+ report: (key, nowSelected, clear2) => {
2205
+ reporters.current.set(key, { selected: nowSelected, clear: clear2 });
2206
+ settle();
2207
+ },
2208
+ retract: (key) => {
2209
+ if (reporters.current.delete(key)) settle();
2210
+ }
2211
+ };
2212
+ });
2213
+ const clear = () => {
2214
+ for (const entry of reporters.current.values()) {
2215
+ if (!entry.selected) continue;
2216
+ entry.clear();
2217
+ entry.selected = false;
2218
+ }
2219
+ settle();
2220
+ };
2221
+ const [internal, setInternal] = useState("automatic");
2222
+ const [visibility, setVisibility] = own.columnVisibility !== void 0 ? readBinding(own.columnVisibility, own.onColumnVisibilityChange) : [internal, setInternal];
2223
+ if (sizeClass === "compact") {
2224
+ return applyModifiers(/* @__PURE__ */ jsx(CompactSplit, { sidebar: own.sidebar, detail: own.detail, report, selected, clear }), modifiers);
2249
2225
  }
2250
- if (typeof value !== "object" || value === null || isValidElement(value) || "in" in value) return void 0;
2251
- return "r" in value || "name" in value || "named" in value ? value : void 0;
2226
+ const sidebarVisible = visibility !== "detailOnly";
2227
+ const toggle = () => withAnimation(() => setVisibility(sidebarVisible ? "detailOnly" : "all"));
2228
+ const detail = isValidElement(own.detail) && own.detail.type === NavigationStack ? own.detail : /* @__PURE__ */ jsx(NavigationStack, { children: own.detail });
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) });
2231
+ return applyModifiers(
2232
+ sb("navsplit", { sidebarVisible }, { tap: toggle, columnwidth: (event) => own.onSidebarWidthChange?.(event.width) }, [
2233
+ /* @__PURE__ */ jsx(SplitColumn, { report, children: sidebar }, "sidebar"),
2234
+ /* @__PURE__ */ jsx(SplitColumn, { report: null, children: detail }, "detail")
2235
+ ]),
2236
+ modifiers
2237
+ );
2252
2238
  }
2253
- function segmentsOf(children, style, into) {
2254
- const visit = (node) => {
2255
- if (node === null || node === void 0 || typeof node === "boolean") return;
2256
- if (typeof node === "string" || typeof node === "number") {
2257
- const last = into[into.length - 1];
2258
- if (last && "text" in last && last.style === style) last.text += String(node);
2259
- else into.push({ text: String(node), style });
2260
- return;
2261
- }
2262
- if (Array.isArray(node)) {
2263
- node.forEach(visit);
2264
- return;
2265
- }
2266
- if (!isValidElement(node)) return;
2267
- const element = node;
2268
- if (element.type === Fragment) {
2269
- visit(element.props["children"]);
2270
- return;
2271
- }
2272
- if (element.type === Text) {
2273
- const props = element.props;
2274
- segmentsOf(props.children, runStyleOf(props, style, true), into);
2275
- return;
2276
- }
2277
- if (element.type === Image$1) {
2278
- const props = element.props;
2279
- into.push({ image: imageProps$1(props), style });
2239
+ function SplitColumn({ report, children }) {
2240
+ return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: report, children }) });
2241
+ }
2242
+ function CompactSplit({
2243
+ sidebar,
2244
+ detail,
2245
+ report,
2246
+ selected,
2247
+ clear
2248
+ }) {
2249
+ const [internal, setInternal] = useState([]);
2250
+ const detailStack = isValidElement(detail) && detail.type === NavigationStack ? detail.props : null;
2251
+ const [extra, setExtra] = detailStack?.path !== void 0 ? readBinding(detailStack.path, detailStack.onPathChange) : [internal, setInternal];
2252
+ const base = selected ? [{ view: detail }] : [];
2253
+ const path = [...base, ...extra];
2254
+ const setPath = (next) => {
2255
+ if (selected && next.length === 0) {
2256
+ setExtra([]);
2257
+ clear();
2280
2258
  return;
2281
2259
  }
2260
+ setExtra(next.slice(base.length));
2282
2261
  };
2283
- visit(children);
2284
- }
2285
- function isPlainStyle(style) {
2286
- return Object.keys(style).length === 0;
2262
+ return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children: /* @__PURE__ */ jsx(NavigationStack, { path: [path, setPath], children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: report, children: sidebar }) }) }) });
2287
2263
  }
2288
- function Text(props) {
2289
- const { own, modifiers } = splitProps(props);
2290
- const environment = useViewEnvironment();
2291
- const style = runStyleOf(props, {}, false);
2292
- const segments = [];
2293
- segmentsOf(own.children, style, segments);
2294
- const textCase = takeModifier(modifiers, "textCase") ?? environment.textCase;
2295
- if (textCase) {
2296
- for (const segment of segments) {
2297
- if ("text" in segment) segment.text = textCase === "uppercase" ? segment.text.toUpperCase() : segment.text.toLowerCase();
2298
- }
2299
- }
2300
- const redacted = takeModifier(modifiers, "redacted") ?? environment.redacted;
2301
- const content = segments.map((segment) => "text" in segment ? segment.text : "").join("");
2302
- const plain2 = segments.length <= 1 && (segments.length === 0 || "text" in segments[0] && isPlainStyle(segments[0].style));
2303
- if (plain2) return applyModifiers(placeholder(sb("text", { text: content }), redacted), modifiers);
2304
- const runs = segments.map((segment) => {
2305
- const run = { ...segment.style };
2306
- if ("text" in segment) run["text"] = segment.text;
2307
- else run["image"] = segment.image;
2308
- return run;
2264
+ function useReportSplitSelection(split, selected, clear, active2 = true) {
2265
+ const key = useRef({}).current;
2266
+ useLayoutEffect(() => {
2267
+ if (active2) split?.report(key, selected, clear);
2268
+ else split?.retract(key);
2309
2269
  });
2310
- const hasLinks = segments.some((segment) => segment.style.link);
2311
- const handlers = hasLinks ? { link: (event) => openURL(event.value) } : void 0;
2312
- return applyModifiers(placeholder(sb("text", { text: content, runs }, handlers), redacted), modifiers);
2270
+ useLayoutEffect(() => () => split?.retract(key), [split, key]);
2271
+ return (nowSelected, nowClear) => {
2272
+ if (active2) split?.report(key, nowSelected, nowClear);
2273
+ };
2313
2274
  }
2314
- function placeholder(node, redacted) {
2315
- if (!redacted) return node;
2316
- return styled({ background: { hierarchical: 3 }, backgroundCornerRadius: 4 }, styled({ foreground: { name: "clear" } }, node));
2275
+ function OutsideSplit({ children }) {
2276
+ return /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children });
2317
2277
  }
2318
- function openURL(url) {
2319
- const native = globalThis.__sbNative;
2320
- if (native?.openURL) {
2321
- native.openURL(url);
2322
- return;
2323
- }
2324
- if (typeof window !== "undefined") window.open(url, "_blank", "noopener");
2278
+ function SheetPresenter({ isPresented, onDismiss, content, fullScreen, inherited }) {
2279
+ const around = useInheritedStyles();
2280
+ const [presented, setPresented] = readBinding(isPresented);
2281
+ if (!presented) return null;
2282
+ const dismiss = () => {
2283
+ setPresented(false);
2284
+ onDismiss?.();
2285
+ };
2286
+ const detents = fullScreen ? ["full"] : ["large"];
2287
+ return sb(
2288
+ HOIST_KIND,
2289
+ void 0,
2290
+ void 0,
2291
+ sb(
2292
+ "sheet",
2293
+ { detents },
2294
+ { tap: dismiss },
2295
+ replayStyles(
2296
+ inherited && inherited.length > 0 ? [...around, ...inherited] : around,
2297
+ /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(OutsideSplit, { children: /* @__PURE__ */ jsx(DismissContext.Provider, { value: dismiss, children: content }) }) })
2298
+ )
2299
+ ),
2300
+ "root"
2301
+ );
2325
2302
  }
2326
- function imageProps$1(props) {
2327
- if (props.systemName !== void 0) return { systemName: props.systemName };
2328
- if (props.bitmap !== void 0) {
2329
- const { source, steps = [], version } = props.bitmap;
2330
- const extent = bitmapExtent(source, steps);
2331
- const finite = Number.isFinite(extent.width) && Number.isFinite(extent.height);
2332
- const width = finite ? Math.max(0, Math.round(extent.width)) : 0;
2333
- const height = finite ? Math.max(0, Math.round(extent.height)) : 0;
2334
- const scale = props.scale !== void 0 && props.scale > 0 ? props.scale : 1;
2335
- return {
2336
- bitmap: { source: source?.key ?? null, steps, width, height, ...version !== void 0 ? { version } : {} },
2337
- resizable: props.resizable ?? false,
2338
- width: width / scale,
2339
- height: height / scale,
2340
- ...props.interpolation === "none" ? { interpolation: "none" } : {}
2341
- };
2342
- }
2343
- if (props.url !== void 0) return { url: props.url, resizable: props.resizable ?? false, width: props.width ?? 0, height: props.height ?? 0 };
2344
- return { name: props.name ?? "", resizable: props.resizable ?? false };
2303
+ function presentationDetents(detents) {
2304
+ const encoded = detents.map(
2305
+ (detent) => typeof detent === "string" ? detent : "height" in detent ? `height:${detent.height}` : `fraction:${detent.fraction}`
2306
+ );
2307
+ return sb(PREF_KIND, { detents: encoded }, void 0, void 0, "sheet");
2345
2308
  }
2346
- function Image$1(props) {
2347
- const { modifiers } = splitProps(props);
2348
- const environment = useViewEnvironment();
2349
- const redacted = takeModifier(modifiers, "redacted") ?? environment.redacted;
2350
- const mode = takeModifier(modifiers, "symbolRenderingMode") ?? environment.symbolRenderingMode;
2351
- const own = imageProps$1(props);
2352
- const element = props.systemName !== void 0 && mode === "palette" ? { ...own, renderingMode: "palette" } : own;
2353
- if (props.accessibilityLabel) element["accessibilityLabel"] = props.accessibilityLabel;
2354
- return applyModifiers(placeholder(sb("image", element), redacted), modifiers);
2309
+ function AlertPresenter({ title, isPresented, message, actions, style }) {
2310
+ const [presented, setPresented] = readBinding(isPresented);
2311
+ if (!presented) return null;
2312
+ const dismiss = () => setPresented(false);
2313
+ return sb(
2314
+ HOIST_KIND,
2315
+ void 0,
2316
+ void 0,
2317
+ sb(
2318
+ "alert",
2319
+ { title, message: message ?? null, style: style ?? "alert" },
2320
+ { tap: dismiss },
2321
+ withEnvironment({ dismissAlert: dismiss }, actions)
2322
+ ),
2323
+ "root"
2324
+ );
2355
2325
  }
2356
- function Label(props) {
2357
- const { own, modifiers } = splitProps(props);
2358
- const environment = useViewEnvironment();
2359
- const style = takeModifier(modifiers, "labelStyle") ?? environment.labelStyle ?? "automatic";
2360
- const icon = own.icon ?? (own.systemImage !== void 0 ? /* @__PURE__ */ jsx(Image$1, { systemName: own.systemImage }) : own.image !== void 0 ? /* @__PURE__ */ jsx(Image$1, { name: own.image }) : null);
2361
- const title = typeof own.title === "string" || typeof own.title === "number" ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.title;
2362
- if (style === "iconOnly") {
2363
- const name = typeof own.title === "string" || typeof own.title === "number" ? String(own.title) : void 0;
2364
- const named = own.icon === void 0 && name !== void 0 ? own.systemImage !== void 0 ? /* @__PURE__ */ jsx(Image$1, { systemName: own.systemImage, accessibilityLabel: name }) : own.image !== void 0 ? /* @__PURE__ */ jsx(Image$1, { name: own.image, accessibilityLabel: name }) : icon : icon;
2365
- return applyModifiers(named, modifiers);
2326
+ function Lifecycle({ onAppear, onDisappear }) {
2327
+ useEffect(() => {
2328
+ onAppear?.();
2329
+ return () => onDisappear?.();
2330
+ }, []);
2331
+ return null;
2332
+ }
2333
+ function Inspector({ isPresented, content, columnWidth, children }) {
2334
+ const sizeClass = useHorizontalSizeClass();
2335
+ const [presented, setPresented] = readBinding(isPresented);
2336
+ if (sizeClass === "compact") {
2337
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
2338
+ children,
2339
+ /* @__PURE__ */ jsx(SheetPresenter, { isPresented: [presented, setPresented], content })
2340
+ ] });
2366
2341
  }
2367
- if (style === "titleOnly") return applyModifiers(title, modifiers);
2368
- return applyModifiers(
2369
- /* @__PURE__ */ jsxs(HStack, { spacing: 8, children: [
2370
- style === "titleAndIcon" ? icon : styled({ labelIcon: true }, icon),
2371
- title
2372
- ] }),
2373
- modifiers
2374
- );
2342
+ const outside = /* @__PURE__ */ jsx(OutsideSplit, { children: content });
2343
+ const panelContent = columnWidth !== void 0 ? sb("styled", { style: { inspectorColumnWidth: columnWidth } }, void 0, outside) : outside;
2344
+ return sb("inspector", { presented }, void 0, [
2345
+ /* @__PURE__ */ jsx(InspectorPart, { children }, "content"),
2346
+ /* @__PURE__ */ jsx(InspectorPart, { children: sb("inspectorpanel", {}, void 0, panelContent) }, "panel")
2347
+ ]);
2348
+ }
2349
+ function InspectorPart({ children }) {
2350
+ return children;
2351
+ }
2352
+ function Task({ id, action }) {
2353
+ const latest = useRef(action);
2354
+ latest.current = action;
2355
+ useEffect(() => {
2356
+ const controller = new AbortController();
2357
+ Promise.resolve().then(() => latest.current(controller.signal)).catch((error) => {
2358
+ if (!controller.signal.aborted) console.error(error);
2359
+ });
2360
+ return () => controller.abort();
2361
+ }, [id]);
2362
+ return null;
2363
+ }
2364
+ function OnChange({
2365
+ value,
2366
+ action,
2367
+ initial
2368
+ }) {
2369
+ const previous = useRef(null);
2370
+ const latest = useRef(action);
2371
+ latest.current = action;
2372
+ useEffect(() => {
2373
+ const last = previous.current;
2374
+ previous.current = { value };
2375
+ if (last === null) {
2376
+ if (initial) latest.current(value, value);
2377
+ return;
2378
+ }
2379
+ if (!Object.is(last.value, value)) latest.current(last.value, value);
2380
+ }, [value, initial]);
2381
+ return null;
2382
+ }
2383
+ const CoordinateSpacesContext = createContext({});
2384
+ function useCoordinateSpaces() {
2385
+ return useContext(CoordinateSpacesContext);
2375
2386
  }
2376
- function LabeledContent(props) {
2377
- const { own, modifiers } = splitProps(props);
2378
- const label = typeof own.label === "string" || typeof own.label === "number" ? /* @__PURE__ */ jsx(Text, { children: own.label }) : own.label;
2379
- const content = own.children ?? own.value;
2380
- const value = typeof content === "string" || typeof content === "number" ? /* @__PURE__ */ jsx(Text, { children: content }) : content;
2381
- return applyModifiers(
2382
- /* @__PURE__ */ jsxs(HStack, { children: [
2383
- label,
2384
- sb("spacer", { minLength: null }),
2385
- styled({ foreground: { hierarchical: 2 } }, value)
2386
- ] }),
2387
- modifiers
2388
- );
2387
+ function CoordinateSpace({ name, children }) {
2388
+ const spaces = useContext(CoordinateSpacesContext);
2389
+ const [frame2, setFrame] = useState(null);
2390
+ return /* @__PURE__ */ jsx(CoordinateSpacesContext.Provider, { value: { ...spaces, [name]: frame2 }, children: sb(
2391
+ "zstack",
2392
+ { alignment: "center", reportsSize: true, reportsFrame: true },
2393
+ {
2394
+ geometry: (event) => {
2395
+ const next = { x: event.x ?? 0, y: event.y ?? 0, width: event.width, height: event.height };
2396
+ if (!frame2 || frame2.x !== next.x || frame2.y !== next.y || frame2.width !== next.width || frame2.height !== next.height) setFrame(next);
2397
+ }
2398
+ },
2399
+ children
2400
+ ) });
2389
2401
  }
2390
- function Link(props) {
2391
- const { own, modifiers } = splitProps(props);
2392
- const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.children;
2393
- return applyModifiers(sb("button", { role: null, style: "automatic" }, { tap: () => openURL(own.destination) }, label), modifiers);
2402
+ const NO_INSETS = { top: 0, leading: 0, bottom: 0, trailing: 0 };
2403
+ const NO_GEOMETRY = { size: { width: 0, height: 0 }, origin: null, safeArea: NO_INSETS, scrollView: null };
2404
+ function nextGeometry(event, reported) {
2405
+ const hasOrigin = event.x !== void 0 && event.y !== void 0;
2406
+ return {
2407
+ size: { width: event.width, height: event.height },
2408
+ origin: hasOrigin ? { x: event.x, y: event.y } : reported.origin,
2409
+ safeArea: event.safeArea ?? reported.safeArea,
2410
+ // A frame report without a scroll view means the reader is in none.
2411
+ scrollView: hasOrigin ? event.scrollView ?? null : reported.scrollView
2412
+ };
2394
2413
  }
2395
- const isPushedView = (value) => typeof value === "object" && value !== null && "view" in value && isValidElement(value.view);
2396
- function routeOf(value, depth = 0) {
2397
- if (depth > 8) return void 0;
2398
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
2399
- if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
2400
- if (Array.isArray(value)) {
2401
- const items = value.map((item) => routeOf(item, depth + 1));
2402
- return items.every((item) => item !== void 0) ? items : void 0;
2403
- }
2404
- if (typeof value === "object" && !isPushedView(value)) {
2405
- const proto = Object.getPrototypeOf(value);
2406
- if (proto !== Object.prototype && proto !== null) return void 0;
2407
- const out = {};
2408
- for (const [key, item] of Object.entries(value)) {
2409
- if (item === void 0) continue;
2410
- const plain2 = routeOf(item, depth + 1);
2411
- if (plain2 === void 0) return void 0;
2412
- out[key] = plain2;
2414
+ function geometryProxy(reported, spaces, onReadFrame) {
2415
+ return {
2416
+ size: reported.size,
2417
+ frame: (space = "local") => {
2418
+ if (space === "local") return { x: 0, y: 0, ...reported.size };
2419
+ onReadFrame();
2420
+ const origin = reported.origin ?? { x: 0, y: 0 };
2421
+ if (space === "global") return { ...origin, ...reported.size };
2422
+ const named = spaces[space.named];
2423
+ return { x: origin.x - (named?.x ?? 0), y: origin.y - (named?.y ?? 0), ...reported.size };
2424
+ },
2425
+ bounds: () => {
2426
+ onReadFrame();
2427
+ const view = reported.scrollView;
2428
+ if (!view || !reported.origin) return null;
2429
+ return { x: view.x - reported.origin.x, y: view.y - reported.origin.y, width: view.width, height: view.height };
2430
+ },
2431
+ get safeAreaInsets() {
2432
+ onReadFrame();
2433
+ return reported.safeArea;
2413
2434
  }
2414
- return out;
2415
- }
2416
- return void 0;
2417
- }
2418
- function NavigationStack(props) {
2419
- const enclosing = useNavigation();
2420
- if (enclosing) return /* @__PURE__ */ jsx(FlattenedStack, { ...props });
2421
- return /* @__PURE__ */ jsx(OwnStack, { ...props });
2435
+ };
2422
2436
  }
2423
- function FlattenedStack(props) {
2437
+ function GeometryReader(props) {
2424
2438
  const { own, modifiers } = splitProps(props);
2439
+ const [reported, setReported] = useState(NO_GEOMETRY);
2440
+ const [needsFrame, setNeedsFrame] = useState(false);
2441
+ let readFrame = false;
2442
+ const proxy = geometryProxy(reported, useCoordinateSpaces(), () => {
2443
+ readFrame = true;
2444
+ });
2445
+ const content = own.children(proxy);
2446
+ useEffect(() => {
2447
+ if (readFrame && !needsFrame) setNeedsFrame(true);
2448
+ });
2425
2449
  return applyModifiers(
2426
- /* @__PURE__ */ jsxs(Fragment$1, { children: [
2427
- own.destination && /* @__PURE__ */ jsx(RegisterDestination, { destination: own.destination }),
2428
- own.children
2429
- ] }),
2450
+ sb(
2451
+ "geometry",
2452
+ needsFrame ? { reportsFrame: true } : {},
2453
+ {
2454
+ geometry: (event) => {
2455
+ const next = nextGeometry(event, reported);
2456
+ if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
2457
+ }
2458
+ },
2459
+ content
2460
+ ),
2430
2461
  modifiers
2431
2462
  );
2432
2463
  }
2433
- function OwnStack(props) {
2434
- const { own, modifiers } = splitProps(props);
2435
- const remote = useContext(HostTreeContext)?.remote ?? false;
2436
- const [internalPath, setInternalPath] = useState([]);
2437
- const [path, setPath] = own.path !== void 0 ? readBinding(own.path, own.onPathChange) : [internalPath, setInternalPath];
2438
- const destination = useRef(null);
2439
- destination.current = own.destination ?? null;
2440
- const context = {
2441
- push: (value) => setPath([...path, value]),
2442
- pop: () => setPath(path.slice(0, -1)),
2443
- path,
2444
- setPath: (next) => setPath(next),
2445
- registerDestination: (builder) => {
2446
- destination.current = builder;
2464
+ function GeometryChangeView({ handler, children }) {
2465
+ const [reported, setReported] = useState(NO_GEOMETRY);
2466
+ const [needsFrame, setNeedsFrame] = useState(false);
2467
+ const last = useRef(null);
2468
+ const spaces = useCoordinateSpaces();
2469
+ let readFrame = false;
2470
+ const proxy = geometryProxy(reported, spaces, () => {
2471
+ readFrame = true;
2472
+ });
2473
+ const heard = reported !== NO_GEOMETRY;
2474
+ const value = heard && typeof handler !== "function" ? handler.of(proxy) : void 0;
2475
+ useLayoutEffect(() => {
2476
+ if (readFrame && !needsFrame) setNeedsFrame(true);
2477
+ });
2478
+ useLayoutEffect(() => {
2479
+ if (!heard) return;
2480
+ if (typeof handler === "function") {
2481
+ handler(proxy);
2482
+ return;
2447
2483
  }
2448
- };
2449
- const screens = [
2450
- /* @__PURE__ */ jsx(Fragment, { children: sb("navscreen", { title: "", displayMode: "automatic", depth: 0 }, { tap: context.pop }, /* @__PURE__ */ jsx(ScreenDepthContext.Provider, { value: 0, children: own.children })) }, "root")
2451
- ];
2452
- path.forEach((value, index) => {
2453
- const pop = () => setPath(path.slice(0, index));
2454
- const route = remote ? routeOf(value) : void 0;
2455
- screens.push(
2456
- /* @__PURE__ */ jsx(Fragment, { children: sb(
2457
- "navscreen",
2458
- { title: "", displayMode: "automatic", depth: index + 1, ...route === void 0 ? {} : { route } },
2459
- { tap: pop },
2460
- /* @__PURE__ */ jsx(DismissContext.Provider, { value: pop, children: /* @__PURE__ */ jsx(ScreenDepthContext.Provider, { value: index + 1, children: /* @__PURE__ */ jsx(PathScreen, { value, destination }) }) })
2461
- ) }, index + 1)
2462
- );
2484
+ const previous = last.current;
2485
+ if (previous && JSON.stringify(previous.value) === JSON.stringify(value)) return;
2486
+ last.current = { value };
2487
+ handler.action(value, previous ? previous.value : value);
2463
2488
  });
2464
- return applyModifiers(
2465
- sb("navstack", {}, { setpath: (event) => setPath(Array.isArray(event.path) ? event.path : []) }, /* @__PURE__ */ jsx(NavigationContext.Provider, { value: context, children: screens })),
2466
- modifiers
2489
+ return sb(
2490
+ "zstack",
2491
+ { alignment: "center", reportsSize: true, ...needsFrame ? { reportsFrame: true } : {} },
2492
+ {
2493
+ geometry: (event) => {
2494
+ const next = nextGeometry(event, reported);
2495
+ if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
2496
+ }
2497
+ },
2498
+ children
2467
2499
  );
2468
2500
  }
2469
- function PathScreen({ value, destination }) {
2470
- if (isPushedView(value)) return value.view;
2471
- return destination.current ? destination.current(value) : null;
2501
+ const DEFAULT_VIEW_SPACING = { distance: () => 8 };
2502
+ function subviewTraits(children) {
2503
+ return flattenChildren(children).map((element) => {
2504
+ const props = element.props;
2505
+ return { priority: props.layoutPriority ?? 0, values: props.layoutValue ?? {} };
2506
+ });
2472
2507
  }
2473
- function NavigationLink(props) {
2474
- const { own, modifiers } = splitProps(props);
2475
- const navigation = useNavigation();
2476
- const environment = useViewEnvironment();
2477
- const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.children;
2478
- const listSelection = useContext(ListSelectionContext);
2479
- const sidebar = useContext(SplitSelectionContext) !== null && listSelection !== null && own.destination === void 0 && own.value !== void 0;
2480
- const tap = () => {
2481
- if (sidebar) {
2482
- listSelection.select(own.value);
2483
- return;
2484
- }
2485
- if (!navigation) return;
2486
- if (own.destination !== void 0) navigation.push({ view: own.destination });
2487
- else navigation.push(own.value);
2508
+ const fromDimension = (value) => value === "infinity" ? Infinity : value;
2509
+ const toDimension = (value) => Number.isFinite(value) ? Math.round(value * 100) / 100 : "infinity";
2510
+ const toSize = (size) => ({
2511
+ width: fromDimension(size.width) ?? 0,
2512
+ height: fromDimension(size.height) ?? 0
2513
+ });
2514
+ const GUIDES = ["leading", "horizontalCenter", "trailing", "top", "verticalCenter", "bottom", "firstTextBaseline", "lastTextBaseline"];
2515
+ function clamp(value, min, max) {
2516
+ return Math.min(Math.max(value, min), max);
2517
+ }
2518
+ function answerLayout(layout, record, traits = []) {
2519
+ const frames = record.children.map(() => null);
2520
+ const subviews = record.children.map((measurement, index) => {
2521
+ const min = toSize(measurement.min);
2522
+ const ideal = toSize(measurement.ideal);
2523
+ const max = toSize(measurement.max);
2524
+ const sizeThatFits = (proposal) => ({
2525
+ width: proposal.width === null ? ideal.width : clamp(proposal.width, min.width, max.width),
2526
+ height: proposal.height === null ? ideal.height : clamp(proposal.height, min.height, max.height)
2527
+ });
2528
+ return {
2529
+ min,
2530
+ ideal,
2531
+ max,
2532
+ priority: traits[index]?.priority ?? 0,
2533
+ values: traits[index]?.values ?? {},
2534
+ spacing: DEFAULT_VIEW_SPACING,
2535
+ sizeThatFits,
2536
+ place: ({ x, y, anchor, proposal }) => {
2537
+ const size2 = sizeThatFits(proposal ?? { width: null, height: null });
2538
+ const point = unitPoint(anchor ?? "topLeading");
2539
+ frames[index] = { x: x - point.x * size2.width, y: y - point.y * size2.height, ...size2 };
2540
+ }
2541
+ };
2542
+ });
2543
+ const cache = layout.makeCache ? layout.makeCache(subviews) : void 0;
2544
+ const measure = (proposal) => layout.sizeThatFits(proposal, subviews, cache);
2545
+ const size = measure(record.proposal);
2546
+ layout.placeSubviews({ x: 0, y: 0, ...size }, record.proposal, subviews, cache);
2547
+ const placed = frames.map((frame2, index) => {
2548
+ if (frame2) return frame2;
2549
+ const own = subviews[index].sizeThatFits({ width: size.width, height: size.height });
2550
+ return { x: (size.width - own.width) / 2, y: (size.height - own.height) / 2, ...own };
2551
+ });
2552
+ const guides = {};
2553
+ if (layout.explicitAlignment) {
2554
+ const bounds = { x: 0, y: 0, ...size };
2555
+ for (const guide of GUIDES) {
2556
+ const value = layout.explicitAlignment(guide, bounds, record.proposal, subviews, cache);
2557
+ if (value !== null && value !== void 0 && Number.isFinite(value)) guides[guide] = value;
2558
+ }
2559
+ }
2560
+ const sizes = {
2561
+ min: measure({ width: 0, height: 0 }),
2562
+ ideal: measure({ width: null, height: null }),
2563
+ max: measure({ width: Infinity, height: Infinity })
2488
2564
  };
2489
- const style = takeModifier(modifiers, "buttonStyle") ?? environment.buttonStyle;
2490
- const linkProps = style === "plain" ? { style: "plain" } : {};
2491
- if (sidebar) linkProps["selects"] = true;
2492
- return applyModifiers(sb("navlink", linkProps, { tap }, label), modifiers);
2493
- }
2494
- const SplitSelectionContext = createContext(null);
2495
- function splitColumnWidthHint(width) {
2496
- if (typeof width === "number") return { min: width, ideal: width, max: width };
2497
- return { ...width.min !== void 0 ? { min: width.min } : {}, ideal: width.ideal, ...width.max !== void 0 ? { max: width.max } : {} };
2565
+ const { width, height } = record.proposal;
2566
+ if (width !== null && Number.isFinite(width) && height !== null) sizes.atWidth = measure({ width, height: null });
2567
+ return { size, frames: placed, guides, sizes };
2498
2568
  }
2499
- function NavigationSplitView(props) {
2569
+ function Layout(props) {
2500
2570
  const { own, modifiers } = splitProps(props);
2501
- const sizeClass = useHorizontalSizeClass();
2502
- const [selected, setSelected] = useState(false);
2503
- const reporters = useRef(/* @__PURE__ */ new Map());
2504
- const settle = () => setSelected([...reporters.current.values()].some((entry) => entry.selected));
2505
- const [report] = useState(() => {
2506
- return {
2507
- report: (key, nowSelected, clear2) => {
2508
- reporters.current.set(key, { selected: nowSelected, clear: clear2 });
2509
- settle();
2571
+ const [record, setRecord] = useState(null);
2572
+ let layoutProps = { proposal: null, size: null, frames: null };
2573
+ const guideModifiers = [];
2574
+ if (record) {
2575
+ const answer = answerLayout(own.layout, record, subviewTraits(own.children));
2576
+ for (const [guide, value] of Object.entries(answer.guides)) guideModifiers.push(["alignmentGuide", { guide, value: () => value }]);
2577
+ const encode = (size) => ({ width: toDimension(size.width), height: toDimension(size.height) });
2578
+ const round2 = (value) => Math.round(value * 100) / 100;
2579
+ layoutProps = {
2580
+ proposal: {
2581
+ width: record.proposal.width === null ? null : toDimension(record.proposal.width),
2582
+ height: record.proposal.height === null ? null : toDimension(record.proposal.height)
2510
2583
  },
2511
- retract: (key) => {
2512
- if (reporters.current.delete(key)) settle();
2584
+ size: { width: round2(answer.size.width), height: round2(answer.size.height) },
2585
+ frames: answer.frames.map((frame2) => ({ x: round2(frame2.x), y: round2(frame2.y), width: round2(frame2.width), height: round2(frame2.height) })),
2586
+ sizes: {
2587
+ min: encode(answer.sizes.min),
2588
+ ideal: encode(answer.sizes.ideal),
2589
+ max: encode(answer.sizes.max),
2590
+ ...answer.sizes.atWidth ? { atWidth: encode(answer.sizes.atWidth) } : {}
2513
2591
  }
2514
2592
  };
2515
- });
2516
- const clear = () => {
2517
- for (const entry of reporters.current.values()) {
2518
- if (!entry.selected) continue;
2519
- entry.clear();
2520
- entry.selected = false;
2521
- }
2522
- settle();
2523
- };
2524
- const [internal, setInternal] = useState("automatic");
2525
- const [visibility, setVisibility] = own.columnVisibility !== void 0 ? readBinding(own.columnVisibility, own.onColumnVisibilityChange) : [internal, setInternal];
2526
- if (sizeClass === "compact") {
2527
- return applyModifiers(/* @__PURE__ */ jsx(CompactSplit, { sidebar: own.sidebar, detail: own.detail, report, selected, clear }), modifiers);
2528
2593
  }
2529
- const sidebarVisible = visibility !== "detailOnly";
2530
- const toggle = () => withAnimation(() => setVisibility(sidebarVisible ? "detailOnly" : "all"));
2531
- const detail = isValidElement(own.detail) && own.detail.type === NavigationStack ? own.detail : /* @__PURE__ */ jsx(NavigationStack, { children: own.detail });
2532
- const sidebar = own.sidebarWidth !== void 0 ? sb("styled", { style: { splitColumnWidth: splitColumnWidthHint(own.sidebarWidth) } }, void 0, own.sidebar) : own.sidebar;
2533
2594
  return applyModifiers(
2534
- sb("navsplit", { sidebarVisible }, { tap: toggle, columnwidth: (event) => own.onSidebarWidthChange?.(event.width) }, [
2535
- /* @__PURE__ */ jsx(SplitColumn, { report, children: /* @__PURE__ */ jsx(NavigationStack, { children: sidebar }) }, "sidebar"),
2536
- /* @__PURE__ */ jsx(SplitColumn, { report: null, children: detail }, "detail")
2537
- ]),
2538
- modifiers
2539
- );
2540
- }
2541
- function SplitColumn({ report, children }) {
2542
- return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: report, children }) });
2543
- }
2544
- function CompactSplit({
2545
- sidebar,
2546
- detail,
2547
- report,
2548
- selected,
2549
- clear
2550
- }) {
2551
- const [internal, setInternal] = useState([]);
2552
- const detailStack = isValidElement(detail) && detail.type === NavigationStack ? detail.props : null;
2553
- const [extra, setExtra] = detailStack?.path !== void 0 ? readBinding(detailStack.path, detailStack.onPathChange) : [internal, setInternal];
2554
- const base = selected ? [{ view: detail }] : [];
2555
- const path = [...base, ...extra];
2556
- const setPath = (next) => {
2557
- if (selected && next.length === 0) {
2558
- setExtra([]);
2559
- clear();
2560
- return;
2561
- }
2562
- setExtra(next.slice(base.length));
2563
- };
2564
- return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children: /* @__PURE__ */ jsx(NavigationStack, { path: [path, setPath], children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: report, children: sidebar }) }) }) });
2565
- }
2566
- function useReportSplitSelection(split, selected, clear, active2 = true) {
2567
- const key = useRef({}).current;
2568
- useLayoutEffect(() => {
2569
- if (active2) split?.report(key, selected, clear);
2570
- else split?.retract(key);
2571
- });
2572
- useLayoutEffect(() => () => split?.retract(key), [split, key]);
2573
- return (nowSelected, nowClear) => {
2574
- if (active2) split?.report(key, nowSelected, nowClear);
2575
- };
2576
- }
2577
- function OutsideSplit({ children }) {
2578
- return /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children });
2579
- }
2580
- function SheetPresenter({ isPresented, onDismiss, content, fullScreen, inherited }) {
2581
- const around = useInheritedStyles();
2582
- const [presented, setPresented] = readBinding(isPresented);
2583
- if (!presented) return null;
2584
- const dismiss = () => {
2585
- setPresented(false);
2586
- onDismiss?.();
2587
- };
2588
- const detents = fullScreen ? ["full"] : ["large"];
2589
- return sb(
2590
- HOIST_KIND,
2591
- void 0,
2592
- void 0,
2593
- sb(
2594
- "sheet",
2595
- { detents },
2596
- { tap: dismiss },
2597
- replayStyles(
2598
- inherited && inherited.length > 0 ? [...around, ...inherited] : around,
2599
- /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(OutsideSplit, { children: /* @__PURE__ */ jsx(DismissContext.Provider, { value: dismiss, children: content }) }) })
2600
- )
2601
- ),
2602
- "root"
2603
- );
2604
- }
2605
- function presentationDetents(detents) {
2606
- const encoded = detents.map(
2607
- (detent) => typeof detent === "string" ? detent : "height" in detent ? `height:${detent.height}` : `fraction:${detent.fraction}`
2608
- );
2609
- return sb(PREF_KIND, { detents: encoded }, void 0, void 0, "sheet");
2610
- }
2611
- function AlertPresenter({ title, isPresented, message, actions, style }) {
2612
- const [presented, setPresented] = readBinding(isPresented);
2613
- if (!presented) return null;
2614
- const dismiss = () => setPresented(false);
2615
- return sb(
2616
- HOIST_KIND,
2617
- void 0,
2618
- void 0,
2619
2595
  sb(
2620
- "alert",
2621
- { title, message: message ?? null, style: style ?? "alert" },
2622
- { tap: dismiss },
2623
- withEnvironment({ dismissAlert: dismiss }, actions)
2596
+ "layout",
2597
+ layoutProps,
2598
+ {
2599
+ layout: (event) => {
2600
+ const proposal = event.proposal;
2601
+ setRecord({
2602
+ proposal: { width: fromDimension(proposal.width), height: fromDimension(proposal.height) },
2603
+ children: event.children
2604
+ });
2605
+ }
2606
+ },
2607
+ own.children
2624
2608
  ),
2625
- "root"
2609
+ [...guideModifiers, ...modifiers]
2626
2610
  );
2627
2611
  }
2628
- function Lifecycle({ onAppear, onDisappear }) {
2629
- useEffect(() => {
2630
- onAppear?.();
2631
- return () => onDisappear?.();
2632
- }, []);
2633
- return null;
2612
+ const IDENTITY$1 = {
2613
+ offsetX: 0,
2614
+ offsetY: 0,
2615
+ scaleX: 1,
2616
+ scaleY: 1,
2617
+ rotation: 0,
2618
+ opacity: 1,
2619
+ blur: 0,
2620
+ brightness: 0,
2621
+ contrast: 1,
2622
+ saturation: 1,
2623
+ grayscale: 0,
2624
+ hueRotation: 0
2625
+ };
2626
+ class VisualEffect {
2627
+ /** @internal */
2628
+ constructor(values = IDENTITY$1) {
2629
+ this.values = values;
2630
+ }
2631
+ values;
2632
+ with(patch) {
2633
+ return new VisualEffect({ ...this.values, ...patch });
2634
+ }
2635
+ offset(offset) {
2636
+ return this.with({ offsetX: this.values.offsetX + (offset.x ?? 0), offsetY: this.values.offsetY + (offset.y ?? 0) });
2637
+ }
2638
+ scaleEffect(scale, anchor) {
2639
+ const x = typeof scale === "number" ? scale : scale.x ?? 1;
2640
+ const y = typeof scale === "number" ? scale : scale.y ?? 1;
2641
+ return this.with({ scaleX: this.values.scaleX * x, scaleY: this.values.scaleY * y, ...anchor !== void 0 ? { scaleAnchor: unitPoint(anchor) } : {} });
2642
+ }
2643
+ /** Degrees. */
2644
+ rotationEffect(degrees, anchor) {
2645
+ return this.with({ rotation: this.values.rotation + degrees, ...anchor !== void 0 ? { rotationAnchor: unitPoint(anchor) } : {} });
2646
+ }
2647
+ rotation3DEffect(spec) {
2648
+ const rotation = { angle: spec.angle, axis: spec.axis };
2649
+ if (spec.anchor !== void 0) rotation.anchor = unitPoint(spec.anchor);
2650
+ if (spec.perspective !== void 0) rotation.perspective = spec.perspective;
2651
+ return this.with({ rotation3D: rotation });
2652
+ }
2653
+ opacity(opacity2) {
2654
+ return this.with({ opacity: this.values.opacity * opacity2 });
2655
+ }
2656
+ blur(radius) {
2657
+ return this.with({ blur: this.values.blur + radius });
2658
+ }
2659
+ brightness(amount) {
2660
+ return this.with({ brightness: this.values.brightness + amount });
2661
+ }
2662
+ contrast(amount) {
2663
+ return this.with({ contrast: this.values.contrast * amount });
2664
+ }
2665
+ saturation(amount) {
2666
+ return this.with({ saturation: this.values.saturation * amount });
2667
+ }
2668
+ grayscale(amount) {
2669
+ return this.with({ grayscale: Math.min(1, this.values.grayscale + amount) });
2670
+ }
2671
+ /** Degrees. */
2672
+ hueRotation(degrees) {
2673
+ return this.with({ hueRotation: this.values.hueRotation + degrees });
2674
+ }
2634
2675
  }
2635
- function Inspector({ isPresented, content, columnWidth, children }) {
2636
- const sizeClass = useHorizontalSizeClass();
2637
- const [presented, setPresented] = readBinding(isPresented);
2638
- if (sizeClass === "compact") {
2639
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
2640
- children,
2641
- /* @__PURE__ */ jsx(SheetPresenter, { isPresented: [presented, setPresented], content })
2642
- ] });
2676
+ const EMPTY_EFFECT = new VisualEffect();
2677
+ const round = (value) => Math.round(value * 1e3) / 1e3;
2678
+ function effectStyle(values) {
2679
+ const style = {};
2680
+ if (values.offsetX !== 0 || values.offsetY !== 0) style.offset = { x: round(values.offsetX), y: round(values.offsetY) };
2681
+ if (values.scaleX !== 1 || values.scaleY !== 1) {
2682
+ style.scale = { x: round(values.scaleX), y: round(values.scaleY) };
2683
+ if (values.scaleAnchor) style.scaleAnchor = values.scaleAnchor;
2684
+ }
2685
+ if (values.rotation !== 0) {
2686
+ style.rotation = round(values.rotation);
2687
+ if (values.rotationAnchor) style.rotationAnchor = values.rotationAnchor;
2643
2688
  }
2644
- const outside = /* @__PURE__ */ jsx(OutsideSplit, { children: content });
2645
- const panelContent = columnWidth !== void 0 ? sb("styled", { style: { inspectorColumnWidth: columnWidth } }, void 0, outside) : outside;
2646
- return sb("inspector", { presented }, void 0, [
2647
- /* @__PURE__ */ jsx(InspectorPart, { children }, "content"),
2648
- /* @__PURE__ */ jsx(InspectorPart, { children: sb("inspectorpanel", {}, void 0, panelContent) }, "panel")
2649
- ]);
2650
- }
2651
- function InspectorPart({ children }) {
2652
- return children;
2689
+ if (values.rotation3D && values.rotation3D.angle !== 0) style.rotation3D = { ...values.rotation3D, angle: round(values.rotation3D.angle) };
2690
+ if (values.opacity !== 1) style.opacity = round(values.opacity);
2691
+ if (values.blur !== 0) style.blur = round(values.blur);
2692
+ if (values.brightness !== 0) style.brightness = round(values.brightness);
2693
+ if (values.contrast !== 1) style.contrast = round(values.contrast);
2694
+ if (values.saturation !== 1) style.saturation = round(values.saturation);
2695
+ if (values.grayscale !== 0) style.grayscale = round(values.grayscale);
2696
+ if (values.hueRotation !== 0) style.hueRotation = round(values.hueRotation);
2697
+ return style;
2653
2698
  }
2654
- function Task({ id, action }) {
2655
- const latest = useRef(action);
2656
- latest.current = action;
2699
+ function EffectBox({ render, children }) {
2700
+ const [reported, setReported] = useState(NO_GEOMETRY);
2701
+ const [needsFrame, setNeedsFrame] = useState(false);
2702
+ let readFrame = false;
2703
+ const style = render(reported, () => {
2704
+ readFrame = true;
2705
+ });
2657
2706
  useEffect(() => {
2658
- const controller = new AbortController();
2659
- Promise.resolve().then(() => latest.current(controller.signal)).catch((error) => {
2660
- if (!controller.signal.aborted) console.error(error);
2661
- });
2662
- return () => controller.abort();
2663
- }, [id]);
2664
- return null;
2707
+ if (readFrame && !needsFrame) setNeedsFrame(true);
2708
+ });
2709
+ const content = Object.keys(style).length > 0 ? sb("styled", { style }, void 0, children) : children;
2710
+ return sb(
2711
+ "zstack",
2712
+ { alignment: "center", reportsSize: true, ...needsFrame ? { reportsFrame: true } : {} },
2713
+ {
2714
+ geometry: (event) => {
2715
+ const next = nextGeometry(event, reported);
2716
+ if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
2717
+ }
2718
+ },
2719
+ content
2720
+ );
2665
2721
  }
2666
- function OnChange({
2667
- value,
2668
- action,
2669
- initial
2670
- }) {
2671
- const previous = useRef(null);
2672
- const latest = useRef(action);
2673
- latest.current = action;
2674
- useEffect(() => {
2675
- const last = previous.current;
2676
- previous.current = { value };
2677
- if (last === null) {
2678
- if (initial) latest.current(value, value);
2679
- return;
2680
- }
2681
- if (!Object.is(last.value, value)) latest.current(last.value, value);
2682
- }, [value, initial]);
2683
- return null;
2722
+ function VisualEffectView({ effect, children }) {
2723
+ const spaces = useCoordinateSpaces();
2724
+ return /* @__PURE__ */ jsx(EffectBox, { render: (reported, markRead) => effectStyle(effect(EMPTY_EFFECT, geometryProxy(reported, spaces, markRead)).values), children });
2725
+ }
2726
+ const PHASES = {
2727
+ topLeading: { phase: "topLeading", value: -1, isIdentity: false },
2728
+ identity: { phase: "identity", value: 0, isIdentity: true },
2729
+ bottomTrailing: { phase: "bottomTrailing", value: 1, isIdentity: false }
2730
+ };
2731
+ function ScrollTransitionView({ spec, children }) {
2732
+ const phase = (which) => effectStyle(spec.transition(EMPTY_EFFECT, PHASES[which]).values);
2733
+ const transition = {
2734
+ configuration: spec.configuration ?? "interactive",
2735
+ identity: phase("identity"),
2736
+ topLeading: phase("topLeading"),
2737
+ bottomTrailing: phase("bottomTrailing")
2738
+ };
2739
+ if (spec.axis) transition.axis = spec.axis;
2740
+ return sb("styled", { style: { scrollTransition: transition } }, void 0, children);
2684
2741
  }
2685
2742
  function Hover({ action, children }) {
2686
2743
  return sb("styled", { style: { hover: true } }, { hover: (event) => action(event.value) }, children);
@@ -3644,345 +3701,404 @@ function TextInputHint({ hint, children }) {
3644
3701
  const outer = useViewEnvironment().textInputHints;
3645
3702
  return withEnvironment({ textInputHints: { ...outer, ...hint } }, children);
3646
3703
  }
3647
- const NO_INSETS = { top: 0, leading: 0, bottom: 0, trailing: 0 };
3648
- const NO_GEOMETRY = { size: { width: 0, height: 0 }, origin: null, safeArea: NO_INSETS, scrollView: null };
3649
- function nextGeometry(event, reported) {
3650
- const hasOrigin = event.x !== void 0 && event.y !== void 0;
3651
- return {
3652
- size: { width: event.width, height: event.height },
3653
- origin: hasOrigin ? { x: event.x, y: event.y } : reported.origin,
3654
- safeArea: event.safeArea ?? reported.safeArea,
3655
- // A frame report without a scroll view means the reader is in none.
3656
- scrollView: hasOrigin ? event.scrollView ?? null : reported.scrollView
3657
- };
3704
+ function HostElement(props) {
3705
+ const { own, modifiers } = splitProps(props);
3706
+ return applyModifiers(sb(own.kind, own.props ? { ...own.props } : void 0, void 0, own.children), modifiers);
3658
3707
  }
3659
- function geometryProxy(reported, spaces, onReadFrame) {
3660
- return {
3661
- size: reported.size,
3662
- frame: (space = "local") => {
3663
- if (space === "local") return { x: 0, y: 0, ...reported.size };
3664
- onReadFrame();
3665
- const origin = reported.origin ?? { x: 0, y: 0 };
3666
- if (space === "global") return { ...origin, ...reported.size };
3667
- const named = spaces[space.named];
3668
- return { x: origin.x - (named?.x ?? 0), y: origin.y - (named?.y ?? 0), ...reported.size };
3669
- },
3670
- bounds: () => {
3671
- onReadFrame();
3672
- const view = reported.scrollView;
3673
- if (!view || !reported.origin) return null;
3674
- return { x: view.x - reported.origin.x, y: view.y - reported.origin.y, width: view.width, height: view.height };
3675
- },
3676
- get safeAreaInsets() {
3677
- onReadFrame();
3678
- return reported.safeArea;
3679
- }
3680
- };
3708
+ let installed = null;
3709
+ function installTextMeasurer(measurer, remove = false) {
3710
+ if (remove) {
3711
+ if (installed === measurer) installed = null;
3712
+ return;
3713
+ }
3714
+ installed = measurer;
3681
3715
  }
3682
- function GeometryReader(props) {
3683
- const { own, modifiers } = splitProps(props);
3684
- const [reported, setReported] = useState(NO_GEOMETRY);
3685
- const [needsFrame, setNeedsFrame] = useState(false);
3686
- let readFrame = false;
3687
- const proxy = geometryProxy(reported, useCoordinateSpaces(), () => {
3688
- readFrame = true;
3689
- });
3690
- const content = own.children(proxy);
3691
- useEffect(() => {
3692
- if (readFrame && !needsFrame) setNeedsFrame(true);
3693
- });
3694
- return applyModifiers(
3695
- sb(
3696
- "geometry",
3697
- needsFrame ? { reportsFrame: true } : {},
3698
- {
3699
- geometry: (event) => {
3700
- const next = nextGeometry(event, reported);
3701
- if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
3702
- }
3703
- },
3704
- content
3705
- ),
3706
- modifiers
3707
- );
3716
+ function canMeasureText() {
3717
+ return installed !== null;
3708
3718
  }
3709
- function GeometryChangeView({ handler, children }) {
3710
- const [reported, setReported] = useState(NO_GEOMETRY);
3711
- const [needsFrame, setNeedsFrame] = useState(false);
3712
- const last = useRef(null);
3713
- const spaces = useCoordinateSpaces();
3714
- let readFrame = false;
3715
- const proxy = geometryProxy(reported, spaces, () => {
3716
- readFrame = true;
3717
- });
3718
- const heard = reported !== NO_GEOMETRY;
3719
- const value = heard && typeof handler !== "function" ? handler.of(proxy) : void 0;
3720
- useLayoutEffect(() => {
3721
- if (readFrame && !needsFrame) setNeedsFrame(true);
3722
- });
3723
- useLayoutEffect(() => {
3724
- if (!heard) return;
3725
- if (typeof handler === "function") {
3726
- handler(proxy);
3727
- return;
3728
- }
3729
- const previous = last.current;
3730
- if (previous && JSON.stringify(previous.value) === JSON.stringify(value)) return;
3731
- last.current = { value };
3732
- handler.action(value, previous ? previous.value : value);
3733
- });
3734
- return sb(
3735
- "zstack",
3736
- { alignment: "center", reportsSize: true, ...needsFrame ? { reportsFrame: true } : {} },
3737
- {
3738
- geometry: (event) => {
3739
- const next = nextGeometry(event, reported);
3740
- if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
3741
- }
3719
+ function measureText(text, font$1 = "body", width) {
3720
+ if (!installed) throw new Error("measureText: nothing measures text here yet (mount the app, or run it in a SwiftBrowser Native host)");
3721
+ const constrained = typeof width === "number" && Number.isFinite(width) && width > 0 ? width : null;
3722
+ return installed(text, font(font$1), constrained);
3723
+ }
3724
+ function decidePlatform(width, media = matchesMedia) {
3725
+ return horizontalSizeClassFor(width) === "regular" && media("(hover: hover) and (pointer: fine)") ? "macos" : "ios";
3726
+ }
3727
+ function matchesMedia(query) {
3728
+ return typeof matchMedia === "function" && matchMedia(query).matches;
3729
+ }
3730
+ function mount(screen, element, options = {}) {
3731
+ let renderer = null;
3732
+ const root = createSwiftUIRoot((ops) => renderer?.applyOps(ops));
3733
+ renderer = new Renderer(screen, (event) => root.dispatch(event));
3734
+ renderer.fileDrops = "reference";
3735
+ const measurer = (text, font2, width2) => renderer.measureText(text, font2, width2);
3736
+ installTextMeasurer(measurer);
3737
+ let pageScheme = options.colorScheme ?? "light";
3738
+ let preferred = null;
3739
+ const width = screen.clientWidth || 393;
3740
+ const environment = {
3741
+ colorScheme: pageScheme,
3742
+ dynamicTypeSize: options.dynamicTypeSize ?? "large",
3743
+ horizontalSizeClass: horizontalSizeClassFor(width),
3744
+ scenePhase: typeof document !== "undefined" ? currentScenePhase() : "active",
3745
+ platform: options.platform === void 0 || options.platform === "auto" ? decidePlatform(width) : options.platform
3746
+ };
3747
+ renderer.contentHeight = options.contentHeight ?? false;
3748
+ if (options.sidebarTitleBar === false) renderer.sidebarTitleBar = false;
3749
+ if (options.assets) renderer.setAssetCatalog(options.assets);
3750
+ if (options.app) browserEnvironment.files?.setApp?.(options.app);
3751
+ renderer.setEnvironment({ colorScheme: environment.colorScheme, dynamicTypeSize: environment.dynamicTypeSize, platform: environment.platform });
3752
+ if (options.safeArea !== void 0) renderer.setSafeArea(options.safeArea);
3753
+ root.setEnvironment(environment);
3754
+ const browserHistory = (options.history ?? true) && typeof window !== "undefined" ? new BrowserHistory(renderer, screen) : null;
3755
+ browserHistory?.enable();
3756
+ const stopKeyboard = options.keyboard && typeof window !== "undefined" ? watchKeyboard((height) => renderer?.setKeyboard(height), { root: screen }) : null;
3757
+ const applyScheme = () => {
3758
+ const scheme = preferred ?? pageScheme;
3759
+ const device = screen.closest(".device");
3760
+ if (device) device.dataset["theme"] = scheme;
3761
+ renderer?.setEnvironment({ colorScheme: scheme });
3762
+ root.setEnvironment({ colorScheme: scheme });
3763
+ };
3764
+ const onPreferredScheme = (event) => {
3765
+ const scheme = event.detail.colorScheme;
3766
+ preferred = scheme;
3767
+ queueMicrotask(applyScheme);
3768
+ };
3769
+ screen.addEventListener("sb:color-scheme", onPreferredScheme);
3770
+ let sizeClass = environment.horizontalSizeClass;
3771
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
3772
+ const next = horizontalSizeClassFor(screen.clientWidth);
3773
+ if (next === sizeClass) return;
3774
+ sizeClass = next;
3775
+ root.setEnvironment({ horizontalSizeClass: next });
3776
+ }) : null;
3777
+ observer?.observe(screen);
3778
+ const stopScenePhase = typeof document !== "undefined" ? watchScenePhase((scenePhase) => root.setEnvironment({ scenePhase })) : null;
3779
+ root.render(createElement(LazyWindowContext.Provider, { value: true }, element));
3780
+ const mounted = {
3781
+ root,
3782
+ renderer,
3783
+ setEnvironment(next) {
3784
+ if (next.colorScheme !== void 0) pageScheme = next.colorScheme;
3785
+ const scheme = preferred ?? pageScheme;
3786
+ renderer?.setEnvironment({ ...next, colorScheme: scheme });
3787
+ root.setEnvironment({ ...next, colorScheme: scheme });
3742
3788
  },
3743
- children
3744
- );
3745
- }
3746
- const DEFAULT_VIEW_SPACING = { distance: () => 8 };
3747
- function subviewTraits(children) {
3748
- return flattenChildren(children).map((element) => {
3749
- const props = element.props;
3750
- return { priority: props.layoutPriority ?? 0, values: props.layoutValue ?? {} };
3751
- });
3752
- }
3753
- const fromDimension = (value) => value === "infinity" ? Infinity : value;
3754
- const toDimension = (value) => Number.isFinite(value) ? Math.round(value * 100) / 100 : "infinity";
3755
- const toSize = (size) => ({
3756
- width: fromDimension(size.width) ?? 0,
3757
- height: fromDimension(size.height) ?? 0
3758
- });
3759
- const GUIDES = ["leading", "horizontalCenter", "trailing", "top", "verticalCenter", "bottom", "firstTextBaseline", "lastTextBaseline"];
3760
- function clamp(value, min, max) {
3761
- return Math.min(Math.max(value, min), max);
3762
- }
3763
- function answerLayout(layout, record, traits = []) {
3764
- const frames = record.children.map(() => null);
3765
- const subviews = record.children.map((measurement, index) => {
3766
- const min = toSize(measurement.min);
3767
- const ideal = toSize(measurement.ideal);
3768
- const max = toSize(measurement.max);
3769
- const sizeThatFits = (proposal) => ({
3770
- width: proposal.width === null ? ideal.width : clamp(proposal.width, min.width, max.width),
3771
- height: proposal.height === null ? ideal.height : clamp(proposal.height, min.height, max.height)
3772
- });
3773
- return {
3774
- min,
3775
- ideal,
3776
- max,
3777
- priority: traits[index]?.priority ?? 0,
3778
- values: traits[index]?.values ?? {},
3779
- spacing: DEFAULT_VIEW_SPACING,
3780
- sizeThatFits,
3781
- place: ({ x, y, anchor, proposal }) => {
3782
- const size2 = sizeThatFits(proposal ?? { width: null, height: null });
3783
- const point = unitPoint(anchor ?? "topLeading");
3784
- frames[index] = { x: x - point.x * size2.width, y: y - point.y * size2.height, ...size2 };
3785
- }
3786
- };
3787
- });
3788
- const cache = layout.makeCache ? layout.makeCache(subviews) : void 0;
3789
- const measure = (proposal) => layout.sizeThatFits(proposal, subviews, cache);
3790
- const size = measure(record.proposal);
3791
- layout.placeSubviews({ x: 0, y: 0, ...size }, record.proposal, subviews, cache);
3792
- const placed = frames.map((frame2, index) => {
3793
- if (frame2) return frame2;
3794
- const own = subviews[index].sizeThatFits({ width: size.width, height: size.height });
3795
- return { x: (size.width - own.width) / 2, y: (size.height - own.height) / 2, ...own };
3796
- });
3797
- const guides = {};
3798
- if (layout.explicitAlignment) {
3799
- const bounds = { x: 0, y: 0, ...size };
3800
- for (const guide of GUIDES) {
3801
- const value = layout.explicitAlignment(guide, bounds, record.proposal, subviews, cache);
3802
- if (value !== null && value !== void 0 && Number.isFinite(value)) guides[guide] = value;
3789
+ setSafeArea(insets2) {
3790
+ renderer?.setSafeArea(insets2);
3791
+ },
3792
+ viewportChanged() {
3793
+ renderer?.viewportChanged();
3794
+ },
3795
+ unmount() {
3796
+ observer?.disconnect();
3797
+ stopScenePhase?.();
3798
+ stopKeyboard?.();
3799
+ browserHistory?.dispose();
3800
+ screen.removeEventListener("sb:color-scheme", onPreferredScheme);
3801
+ installTextMeasurer(measurer, true);
3802
+ root.unmount();
3803
+ renderer?.dispose();
3803
3804
  }
3804
- }
3805
- const sizes = {
3806
- min: measure({ width: 0, height: 0 }),
3807
- ideal: measure({ width: null, height: null }),
3808
- max: measure({ width: Infinity, height: Infinity })
3809
3805
  };
3810
- const { width, height } = record.proposal;
3811
- if (width !== null && Number.isFinite(width) && height !== null) sizes.atWidth = measure({ width, height: null });
3812
- return { size, frames: placed, guides, sizes };
3813
- }
3814
- function Layout(props) {
3815
- const { own, modifiers } = splitProps(props);
3816
- const [record, setRecord] = useState(null);
3817
- let layoutProps = { proposal: null, size: null, frames: null };
3818
- const guideModifiers = [];
3819
- if (record) {
3820
- const answer = answerLayout(own.layout, record, subviewTraits(own.children));
3821
- for (const [guide, value] of Object.entries(answer.guides)) guideModifiers.push(["alignmentGuide", { guide, value: () => value }]);
3822
- const encode = (size) => ({ width: toDimension(size.width), height: toDimension(size.height) });
3823
- const round2 = (value) => Math.round(value * 100) / 100;
3824
- layoutProps = {
3825
- proposal: {
3826
- width: record.proposal.width === null ? null : toDimension(record.proposal.width),
3827
- height: record.proposal.height === null ? null : toDimension(record.proposal.height)
3828
- },
3829
- size: { width: round2(answer.size.width), height: round2(answer.size.height) },
3830
- frames: answer.frames.map((frame2) => ({ x: round2(frame2.x), y: round2(frame2.y), width: round2(frame2.width), height: round2(frame2.height) })),
3831
- sizes: {
3832
- min: encode(answer.sizes.min),
3833
- ideal: encode(answer.sizes.ideal),
3834
- max: encode(answer.sizes.max),
3835
- ...answer.sizes.atWidth ? { atWidth: encode(answer.sizes.atWidth) } : {}
3836
- }
3837
- };
3838
- }
3839
- return applyModifiers(
3840
- sb(
3841
- "layout",
3842
- layoutProps,
3843
- {
3844
- layout: (event) => {
3845
- const proposal = event.proposal;
3846
- setRecord({
3847
- proposal: { width: fromDimension(proposal.width), height: fromDimension(proposal.height) },
3848
- children: event.children
3849
- });
3850
- }
3851
- },
3852
- own.children
3853
- ),
3854
- [...guideModifiers, ...modifiers]
3855
- );
3806
+ return mounted;
3856
3807
  }
3857
- const IDENTITY$1 = {
3858
- offsetX: 0,
3859
- offsetY: 0,
3860
- scaleX: 1,
3861
- scaleY: 1,
3862
- rotation: 0,
3863
- opacity: 1,
3864
- blur: 0,
3865
- brightness: 0,
3866
- contrast: 1,
3867
- saturation: 1,
3868
- grayscale: 0,
3869
- hueRotation: 0
3808
+ const EMPTY = { canUndo: false, canRedo: false, undoActionName: "", redoActionName: "" };
3809
+ const defaultSchedule = (close) => {
3810
+ if (typeof queueMicrotask === "function") queueMicrotask(close);
3811
+ else void Promise.resolve().then(close);
3870
3812
  };
3871
- class VisualEffect {
3872
- /** @internal */
3873
- constructor(values = IDENTITY$1) {
3874
- this.values = values;
3813
+ class UndoManager {
3814
+ /** Whether registrations in one event (one task of the event loop) form one group; true, as `groupsByEvent`. */
3815
+ groupsByEvent = true;
3816
+ /** How many groups the undo stack keeps; 0 for no limit, as `levelsOfUndo`. */
3817
+ levelsOfUndo = 0;
3818
+ undoStack = [];
3819
+ redoStack = [];
3820
+ /** The group registrations go in: an explicit group, the event's group, or the inverse of what is being undone or redone. */
3821
+ open = null;
3822
+ /** How deep `beginGrouping` is; 0 when `open` is the event's group or an inverse. */
3823
+ depth = 0;
3824
+ /** Whether `open` is the event's group, which `schedule` closes. */
3825
+ byEvent = false;
3826
+ /** The last keyed group closed onto the undo stack, which a registration with its key joins. */
3827
+ coalescing = null;
3828
+ undoing = false;
3829
+ redoing = false;
3830
+ snapshot = EMPTY;
3831
+ listeners = /* @__PURE__ */ new Set();
3832
+ schedule;
3833
+ host;
3834
+ stopListening = null;
3835
+ constructor(options = {}) {
3836
+ this.schedule = options.schedule ?? defaultSchedule;
3837
+ this.host = options.host === void 0 ? browserEnvironment.undoManager ?? null : options.host;
3838
+ if (this.host) {
3839
+ this.stopListening = this.host.listen({ undo: () => this.undo(), redo: () => this.redo() });
3840
+ this.host.update(this.snapshot);
3841
+ }
3875
3842
  }
3876
- values;
3877
- with(patch) {
3878
- return new VisualEffect({ ...this.values, ...patch });
3843
+ get canUndo() {
3844
+ return this.undoStack.length > 0;
3879
3845
  }
3880
- offset(offset) {
3881
- return this.with({ offsetX: this.values.offsetX + (offset.x ?? 0), offsetY: this.values.offsetY + (offset.y ?? 0) });
3846
+ get canRedo() {
3847
+ return this.redoStack.length > 0;
3882
3848
  }
3883
- scaleEffect(scale, anchor) {
3884
- const x = typeof scale === "number" ? scale : scale.x ?? 1;
3885
- const y = typeof scale === "number" ? scale : scale.y ?? 1;
3886
- return this.with({ scaleX: this.values.scaleX * x, scaleY: this.values.scaleY * y, ...anchor !== void 0 ? { scaleAnchor: unitPoint(anchor) } : {} });
3849
+ /** The name of what `undo` takes back ('' for none): "Move" for an Edit menu's "Undo Move". */
3850
+ get undoActionName() {
3851
+ return this.undoStack[this.undoStack.length - 1]?.name ?? "";
3887
3852
  }
3888
- /** Degrees. */
3889
- rotationEffect(degrees, anchor) {
3890
- return this.with({ rotation: this.values.rotation + degrees, ...anchor !== void 0 ? { rotationAnchor: unitPoint(anchor) } : {} });
3853
+ get redoActionName() {
3854
+ return this.redoStack[this.redoStack.length - 1]?.name ?? "";
3855
+ }
3856
+ get isUndoing() {
3857
+ return this.undoing;
3858
+ }
3859
+ get isRedoing() {
3860
+ return this.redoing;
3861
+ }
3862
+ /** What the stacks can do, one object per change (for `useSyncExternalStore`). */
3863
+ get state() {
3864
+ return this.snapshot;
3865
+ }
3866
+ /**
3867
+ * Registers what undoes the change just made, under `name`. Run by `undo`,
3868
+ * the closure registers its own inverse the same way, which lands on the
3869
+ * redo stack because the manager is undoing.
3870
+ */
3871
+ registerUndo(name, undo, options = {}) {
3872
+ const action = { name, undo, target: options.target };
3873
+ if (this.undoing || this.redoing) {
3874
+ this.open.actions.push(action);
3875
+ this.changed();
3876
+ return;
3877
+ }
3878
+ this.redoStack = [];
3879
+ this.coalescing = this.coalescing && this.undoStack[this.undoStack.length - 1] === this.coalescing ? this.coalescing : null;
3880
+ if (this.open) {
3881
+ if (!this.undoStack.includes(this.open)) this.push(this.open);
3882
+ this.open.actions.push(action);
3883
+ if (!this.open.named) this.open.name = name;
3884
+ } else if (this.coalescing && options.key !== void 0 && this.coalescing.key === options.key) {
3885
+ this.coalescing.actions.push(action);
3886
+ this.coalescing.name = name;
3887
+ } else {
3888
+ const group = { name, named: false, actions: [action], key: options.key };
3889
+ this.push(group);
3890
+ this.coalescing = null;
3891
+ if (this.groupsByEvent) {
3892
+ this.open = group;
3893
+ this.byEvent = true;
3894
+ this.schedule(() => {
3895
+ if (this.open === group && this.byEvent) this.close();
3896
+ });
3897
+ } else {
3898
+ this.coalescing = group.key !== void 0 ? group : null;
3899
+ }
3900
+ }
3901
+ this.changed();
3902
+ }
3903
+ /** Takes back the top group of changes; its inverses become the top of the redo stack. */
3904
+ undo() {
3905
+ this.endOpenGroups();
3906
+ const group = this.undoStack.pop();
3907
+ if (!group) return;
3908
+ this.coalescing = null;
3909
+ this.run(group, "undo");
3910
+ }
3911
+ redo() {
3912
+ this.endOpenGroups();
3913
+ const group = this.redoStack.pop();
3914
+ if (!group) return;
3915
+ this.run(group, "redo");
3891
3916
  }
3892
- rotation3DEffect(spec) {
3893
- const rotation = { angle: spec.angle, axis: spec.axis };
3894
- if (spec.anchor !== void 0) rotation.anchor = unitPoint(spec.anchor);
3895
- if (spec.perspective !== void 0) rotation.perspective = spec.perspective;
3896
- return this.with({ rotation3D: rotation });
3917
+ /**
3918
+ * Starts a group: every registration until `endGrouping` undoes as one
3919
+ * (`beginUndoGrouping`). Nested calls join the outer group. The group is
3920
+ * named `name`, else after its last registration.
3921
+ */
3922
+ beginGrouping(name) {
3923
+ if (this.undoing || this.redoing) return;
3924
+ this.depth += 1;
3925
+ if (this.depth > 1) return;
3926
+ if (this.open) this.close();
3927
+ this.open = { name: name ?? "", named: name !== void 0, actions: [], key: void 0 };
3928
+ this.byEvent = false;
3929
+ this.coalescing = null;
3897
3930
  }
3898
- opacity(opacity2) {
3899
- return this.with({ opacity: this.values.opacity * opacity2 });
3931
+ /** Ends the group `beginGrouping` started; one that registered nothing is dropped. */
3932
+ endGrouping() {
3933
+ if (this.depth === 0) return;
3934
+ this.depth -= 1;
3935
+ if (this.depth === 0) this.close();
3900
3936
  }
3901
- blur(radius) {
3902
- return this.with({ blur: this.values.blur + radius });
3937
+ /** `body` as one group named `name`. */
3938
+ group(name, body) {
3939
+ this.beginGrouping(name);
3940
+ try {
3941
+ return body();
3942
+ } finally {
3943
+ this.endGrouping();
3944
+ }
3903
3945
  }
3904
- brightness(amount) {
3905
- return this.with({ brightness: this.values.brightness + amount });
3946
+ /** Forgets every registration, or those `target` made (`removeAllActions(withTarget:)`), on both stacks and in the open group. */
3947
+ removeAllActions(target) {
3948
+ const mine = (action) => target === void 0 || action.target === target;
3949
+ const strip = (groups) => groups.filter((group) => {
3950
+ group.actions = group.actions.filter((action) => !mine(action));
3951
+ return group.actions.length > 0;
3952
+ });
3953
+ this.undoStack = strip(this.undoStack);
3954
+ this.redoStack = strip(this.redoStack);
3955
+ if (this.open) this.open.actions = this.open.actions.filter((action) => !mine(action));
3956
+ if (this.coalescing && !this.undoStack.includes(this.coalescing)) this.coalescing = null;
3957
+ this.changed();
3906
3958
  }
3907
- contrast(amount) {
3908
- return this.with({ contrast: this.values.contrast * amount });
3959
+ /** Calls `listener` after each change to `state`; returns what stops it. */
3960
+ subscribe = (listener) => {
3961
+ this.listeners.add(listener);
3962
+ return () => {
3963
+ this.listeners.delete(listener);
3964
+ };
3965
+ };
3966
+ /** Stops listening to the host, for a manager made in a test. */
3967
+ dispose() {
3968
+ this.stopListening?.();
3969
+ this.stopListening = null;
3909
3970
  }
3910
- saturation(amount) {
3911
- return this.with({ saturation: this.values.saturation * amount });
3971
+ push(group) {
3972
+ this.undoStack.push(group);
3973
+ if (this.levelsOfUndo > 0 && this.undoStack.length > this.levelsOfUndo) this.undoStack.splice(0, this.undoStack.length - this.levelsOfUndo);
3912
3974
  }
3913
- grayscale(amount) {
3914
- return this.with({ grayscale: Math.min(1, this.values.grayscale + amount) });
3975
+ /** Closes `open`; a keyed group on top stays open to its key. */
3976
+ close() {
3977
+ const group = this.open;
3978
+ this.open = null;
3979
+ this.byEvent = false;
3980
+ if (group && group.key !== void 0 && this.undoStack[this.undoStack.length - 1] === group) this.coalescing = group;
3915
3981
  }
3916
- /** Degrees. */
3917
- hueRotation(degrees) {
3918
- return this.with({ hueRotation: this.values.hueRotation + degrees });
3982
+ endOpenGroups() {
3983
+ this.depth = 0;
3984
+ if (this.open) this.close();
3919
3985
  }
3920
- }
3921
- const EMPTY_EFFECT = new VisualEffect();
3922
- const round = (value) => Math.round(value * 1e3) / 1e3;
3923
- function effectStyle(values) {
3924
- const style = {};
3925
- if (values.offsetX !== 0 || values.offsetY !== 0) style.offset = { x: round(values.offsetX), y: round(values.offsetY) };
3926
- if (values.scaleX !== 1 || values.scaleY !== 1) {
3927
- style.scale = { x: round(values.scaleX), y: round(values.scaleY) };
3928
- if (values.scaleAnchor) style.scaleAnchor = values.scaleAnchor;
3986
+ run(group, direction) {
3987
+ const inverse = { name: group.name, named: true, actions: [], key: void 0 };
3988
+ this.open = inverse;
3989
+ this.byEvent = false;
3990
+ if (direction === "undo") this.undoing = true;
3991
+ else this.redoing = true;
3992
+ try {
3993
+ for (let i = group.actions.length - 1; i >= 0; i -= 1) group.actions[i].undo();
3994
+ } finally {
3995
+ this.undoing = false;
3996
+ this.redoing = false;
3997
+ this.open = null;
3998
+ if (inverse.actions.length > 0) {
3999
+ if (direction === "undo") this.redoStack.push(inverse);
4000
+ else this.push(inverse);
4001
+ }
4002
+ this.changed();
4003
+ }
3929
4004
  }
3930
- if (values.rotation !== 0) {
3931
- style.rotation = round(values.rotation);
3932
- if (values.rotationAnchor) style.rotationAnchor = values.rotationAnchor;
4005
+ changed() {
4006
+ const next = { canUndo: this.canUndo, canRedo: this.canRedo, undoActionName: this.undoActionName, redoActionName: this.redoActionName };
4007
+ const last = this.snapshot;
4008
+ if (next.canUndo === last.canUndo && next.canRedo === last.canRedo && next.undoActionName === last.undoActionName && next.redoActionName === last.redoActionName) return;
4009
+ this.snapshot = next;
4010
+ this.host?.update(next);
4011
+ for (const listener of [...this.listeners]) listener();
3933
4012
  }
3934
- if (values.rotation3D && values.rotation3D.angle !== 0) style.rotation3D = { ...values.rotation3D, angle: round(values.rotation3D.angle) };
3935
- if (values.opacity !== 1) style.opacity = round(values.opacity);
3936
- if (values.blur !== 0) style.blur = round(values.blur);
3937
- if (values.brightness !== 0) style.brightness = round(values.brightness);
3938
- if (values.contrast !== 1) style.contrast = round(values.contrast);
3939
- if (values.saturation !== 1) style.saturation = round(values.saturation);
3940
- if (values.grayscale !== 0) style.grayscale = round(values.grayscale);
3941
- if (values.hueRotation !== 0) style.hueRotation = round(values.hueRotation);
3942
- return style;
3943
4013
  }
3944
- function EffectBox({ render, children }) {
3945
- const [reported, setReported] = useState(NO_GEOMETRY);
3946
- const [needsFrame, setNeedsFrame] = useState(false);
3947
- let readFrame = false;
3948
- const style = render(reported, () => {
3949
- readFrame = true;
3950
- });
3951
- useEffect(() => {
3952
- if (readFrame && !needsFrame) setNeedsFrame(true);
3953
- });
3954
- const content = Object.keys(style).length > 0 ? sb("styled", { style }, void 0, children) : children;
3955
- return sb(
3956
- "zstack",
3957
- { alignment: "center", reportsSize: true, ...needsFrame ? { reportsFrame: true } : {} },
3958
- {
3959
- geometry: (event) => {
3960
- const next = nextGeometry(event, reported);
3961
- if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
3962
- }
3963
- },
3964
- content
4014
+ const UndoManagerContext = createContext(null);
4015
+ let shared = null;
4016
+ function sharedUndoManager() {
4017
+ shared ??= new UndoManager();
4018
+ return shared;
4019
+ }
4020
+ function useUndoManager() {
4021
+ const manager = useContext(UndoManagerContext) ?? sharedUndoManager();
4022
+ useSyncExternalStore(
4023
+ manager.subscribe,
4024
+ () => manager.state,
4025
+ () => manager.state
3965
4026
  );
4027
+ return manager;
3966
4028
  }
3967
- function VisualEffectView({ effect, children }) {
3968
- const spaces = useCoordinateSpaces();
3969
- return /* @__PURE__ */ jsx(EffectBox, { render: (reported, markRead) => effectStyle(effect(EMPTY_EFFECT, geometryProxy(reported, spaces, markRead)).values), children });
4029
+ const OWN = [
4030
+ EnvironmentContext,
4031
+ ViewEnvironmentContext,
4032
+ NavigationContext,
4033
+ DismissContext,
4034
+ UndoManagerContext
4035
+ ];
4036
+ function useContextBridge(contexts = [], options = {}) {
4037
+ const own = options.environment === false ? OWN.filter((context) => context !== EnvironmentContext) : OWN;
4038
+ const all = [...own, ...contexts];
4039
+ const values = all.map((context) => useContext(context));
4040
+ return (children) => all.reduceRight((inner, context, index) => createElement(context.Provider, { value: values[index] }, inner), children);
3970
4041
  }
3971
- const PHASES = {
3972
- topLeading: { phase: "topLeading", value: -1, isIdentity: false },
3973
- identity: { phase: "identity", value: 0, isIdentity: true },
3974
- bottomTrailing: { phase: "bottomTrailing", value: 1, isIdentity: false }
3975
- };
3976
- function ScrollTransitionView({ spec, children }) {
3977
- const phase = (which) => effectStyle(spec.transition(EMPTY_EFFECT, PHASES[which]).values);
3978
- const transition = {
3979
- configuration: spec.configuration ?? "interactive",
3980
- identity: phase("identity"),
3981
- topLeading: phase("topLeading"),
3982
- bottomTrailing: phase("bottomTrailing")
3983
- };
3984
- if (spec.axis) transition.axis = spec.axis;
3985
- return sb("styled", { style: { scrollTransition: transition } }, void 0, children);
4042
+ const WINDOW_TOP_INSET = 10;
4043
+ function insetsOf(safeArea, fit) {
4044
+ const given = typeof safeArea === "number" ? { top: safeArea } : safeArea ?? { top: fit === "fill" ? WINDOW_TOP_INSET : 0 };
4045
+ return { top: given.top ?? 0, leading: given.leading ?? 0, bottom: given.bottom ?? 0, trailing: given.trailing ?? 0 };
4046
+ }
4047
+ function SwiftUIView({
4048
+ children,
4049
+ fit = "fill",
4050
+ colorScheme = "light",
4051
+ dynamicTypeSize = "large",
4052
+ contexts,
4053
+ assets,
4054
+ safeArea,
4055
+ history = true,
4056
+ keyboard = fit === "fill",
4057
+ sidebarTitleBar = true,
4058
+ className,
4059
+ style
4060
+ }) {
4061
+ const device = useRef(null);
4062
+ const screen = useRef(null);
4063
+ const mounted = useRef(null);
4064
+ const bridge = useContextBridge(contexts, { environment: false });
4065
+ const tree = bridge(children);
4066
+ const latest = useRef(tree);
4067
+ latest.current = tree;
4068
+ useLayoutEffect(() => {
4069
+ if (!screen.current) return;
4070
+ const instance = mount(screen.current, latest.current, {
4071
+ colorScheme,
4072
+ dynamicTypeSize,
4073
+ safeArea: insetsOf(safeArea, fit),
4074
+ contentHeight: fit === "content",
4075
+ history,
4076
+ keyboard,
4077
+ ...assets ? { assets } : {}
4078
+ });
4079
+ mounted.current = instance;
4080
+ return () => {
4081
+ mounted.current = null;
4082
+ instance.unmount();
4083
+ };
4084
+ }, [fit, history, keyboard]);
4085
+ useLayoutEffect(() => {
4086
+ if (mounted.current) mounted.current.renderer.sidebarTitleBar = sidebarTitleBar;
4087
+ }, [sidebarTitleBar, fit, history, keyboard]);
4088
+ useLayoutEffect(() => {
4089
+ mounted.current?.root.render(tree);
4090
+ });
4091
+ const insets2 = insetsOf(safeArea, fit);
4092
+ const insetsKey = `${insets2.top},${insets2.leading},${insets2.bottom},${insets2.trailing}`;
4093
+ useLayoutEffect(() => {
4094
+ mounted.current?.renderer.setSafeArea(insets2);
4095
+ }, [insetsKey]);
4096
+ useLayoutEffect(() => {
4097
+ if (device.current) applyTypeSizeVariables(device.current, dynamicTypeSize, mounted.current?.renderer.platform);
4098
+ mounted.current?.setEnvironment({ colorScheme, dynamicTypeSize });
4099
+ }, [colorScheme, dynamicTypeSize]);
4100
+ const classes = ["device", "sb-island", className].filter(Boolean).join(" ");
4101
+ return /* @__PURE__ */ jsx("div", { ref: device, className: classes, "data-theme": colorScheme, "data-type-size": dynamicTypeSize, "data-state": "ready", "data-sb-fit": fit, style, children: /* @__PURE__ */ jsx("div", { className: "device-screen", children: /* @__PURE__ */ jsx("div", { ref: screen, className: "screen" }) }) });
3986
4102
  }
3987
4103
  const r = (n) => {
3988
4104
  const v = Math.round(n * 100) / 100;
@@ -5982,6 +6098,7 @@ function TextEditor(props) {
5982
6098
  if (focus) editor["focused"] = focus[0];
5983
6099
  if (selection) editor["selection"] = { start: selection[0].start, end: selection[0].end };
5984
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) }));
5985
6102
  return applyModifiers(
5986
6103
  sb("texteditor", editor, {
5987
6104
  text: (event) => setText(rich ? AttributedText.replacingCharacters(text, event.value) : event.value),
@@ -5996,6 +6113,7 @@ function TextEditor(props) {
5996
6113
  modifiers
5997
6114
  );
5998
6115
  }
6116
+ const MARK_COLOR = opacity("accent", 0.25);
5999
6117
  function buttonDisabled(modifiers) {
6000
6118
  return modifiers.some(([key, value]) => key === "disabled" && value === true);
6001
6119
  }
@@ -6054,7 +6172,9 @@ ContainerRelativeShape.shape = { kind: "containerRelative", cornerRadius: null }
6054
6172
  function Shape(props) {
6055
6173
  const { own, modifiers } = splitProps(props);
6056
6174
  const [size, setSize] = useState(null);
6057
- 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;
6058
6178
  const shapeProps = {
6059
6179
  shape: "path",
6060
6180
  cornerRadius: 0,
@@ -6664,7 +6784,7 @@ function List(props) {
6664
6784
  const ownSettings = takeScrollSettings(modifiers);
6665
6785
  const edgeEffect = edgeEffectProp(useScrollSettings(ownSettings));
6666
6786
  if (edgeEffect) listProps["edgeEffect"] = edgeEffect;
6667
- 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))) }) });
6668
6788
  return applyModifiers(
6669
6789
  selection ? /* @__PURE__ */ jsx(ListSelectionContext.Provider, { value: selection, children: element }) : element,
6670
6790
  modifiers
@@ -7310,10 +7430,10 @@ function ViewThatFits(props) {
7310
7430
  return applyModifiers(sb("viewthatfits", { axes: own.axes ?? "both" }, void 0, own.children), modifiers);
7311
7431
  }
7312
7432
  function LazyVStack(props) {
7313
- 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 }) }) });
7314
7434
  }
7315
7435
  function LazyHStack(props) {
7316
- 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 }) }) });
7317
7437
  }
7318
7438
  function ContentUnavailableView(props) {
7319
7439
  const { own, modifiers } = splitProps(props);
@@ -8194,6 +8314,7 @@ export {
8194
8314
  Divider,
8195
8315
  EditButton,
8196
8316
  Ellipse,
8317
+ FlowStack,
8197
8318
  Font,
8198
8319
  ForEach,
8199
8320
  Form,
@@ -8209,6 +8330,7 @@ export {
8209
8330
  GroupBox,
8210
8331
  HStack,
8211
8332
  Host,
8333
+ HostElement,
8212
8334
  Image$1 as Image,
8213
8335
  KeyframeAnimator,
8214
8336
  LOADING_INDICATOR_DELAY_MS,
@@ -8347,6 +8469,7 @@ export {
8347
8469
  usePlatform,
8348
8470
  useRedacted,
8349
8471
  useRefreshable,
8472
+ useRemoteSidebar,
8350
8473
  useReportSplitSelection,
8351
8474
  useScenePhase,
8352
8475
  useSpeechRecognition,