@swiftbrowser/react 0.28.3 → 0.28.4

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";
@@ -995,35 +995,37 @@ function findStack(node) {
995
995
  return null;
996
996
  }
997
997
  const screensOf = (stack) => stack ? stack.children.filter((child) => child.kind === "navscreen") : [];
998
- function renderNode(root, node, stack) {
998
+ function renderNode(root, node, stack, elements) {
999
999
  if (node.kind === "host") return null;
1000
1000
  if (node === stack) {
1001
1001
  const first = screensOf(stack)[0];
1002
- return createElement(Fragment, { key: node.id }, first ? renderScreen(root, first, stack) : null);
1002
+ return createElement(Fragment, { key: node.id }, first ? renderScreen(root, first, stack, elements) : null);
1003
1003
  }
1004
- const children = node.children.length ? node.children.map((child) => renderNode(root, child, stack)) : void 0;
1004
+ const children = node.children.length ? node.children.map((child) => renderNode(root, child, stack, elements)) : void 0;
1005
+ const own = elements && Object.prototype.hasOwnProperty.call(elements, node.kind) ? elements[node.kind] : void 0;
1006
+ if (own) return createElement(Fragment, { key: node.id }, own(node.props, children));
1005
1007
  return createElement(Fragment, { key: node.id }, sb(node.kind, node.props, root.handlersFor(node.id), children));
1006
1008
  }
1007
- function renderSheet(root, node, stack, inherited) {
1008
- const children = node.children.map((child) => renderNode(root, child, stack));
1009
+ function renderSheet(root, node, stack, inherited, elements) {
1010
+ const children = node.children.map((child) => renderNode(root, child, stack, elements));
1009
1011
  return createElement(Fragment, { key: node.id }, sb(node.kind, node.props, root.handlersFor(node.id), replayStyles(inherited, children)));
1010
1012
  }
1011
- function renderScreen(root, screen, stack) {
1013
+ function renderScreen(root, screen, stack, elements) {
1012
1014
  const out = [screenPrefs(screen.props, "$screen")];
1013
1015
  for (const child of screen.children) {
1014
- const drawn = renderNode(root, child, stack);
1016
+ const drawn = renderNode(root, child, stack, elements);
1015
1017
  if (child.kind === "toolbar" || child.kind === "searchfield") out.push(createElement(Fragment, { key: child.id }, sb(HOIST_KIND, void 0, void 0, drawn, "navscreen")));
1016
1018
  else out.push(drawn);
1017
1019
  }
1018
1020
  return createElement(Fragment, null, ...out);
1019
1021
  }
1020
- function RemoteScreen({ root, id }) {
1022
+ function RemoteScreen({ root, id, elements }) {
1021
1023
  useSyncExternalStore(root.subscribe, root.getVersion, root.getVersion);
1022
1024
  const screen = root.node(id);
1023
1025
  if (!screen?.parent) return null;
1024
- return renderScreen(root, screen, findStack(root.root));
1026
+ return renderScreen(root, screen, findStack(root.root), elements);
1025
1027
  }
1026
- function RemoteTree({ root, initialPath }) {
1028
+ function RemoteTree({ root, initialPath, elements }) {
1027
1029
  useSyncExternalStore(root.subscribe, root.getVersion, root.getVersion);
1028
1030
  const environment = useContext(EnvironmentContext);
1029
1031
  const inherited = useInheritedStyles();
@@ -1047,7 +1049,7 @@ function RemoteTree({ root, initialPath }) {
1047
1049
  const key = keyOf2(screen);
1048
1050
  let value = values.current.get(key);
1049
1051
  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 }) };
1052
+ value = "route" in screen.props ? { view: /* @__PURE__ */ jsx(RemoteScreen, { root, id: screen.id, elements }), route: screen.props["route"] } : { view: /* @__PURE__ */ jsx(RemoteScreen, { root, id: screen.id, elements }) };
1051
1053
  values.current.set(key, value);
1052
1054
  ours.current.add(value);
1053
1055
  }
@@ -1082,13 +1084,192 @@ function RemoteTree({ root, initialPath }) {
1082
1084
  if ("colorScheme" in root.rootProps)
1083
1085
  out.push(createElement(Fragment, { key: "$scheme" }, sb(PREF_KIND, { colorScheme: root.rootProps["colorScheme"] }, void 0, void 0, "root")));
1084
1086
  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);
1087
+ const drawn = child.kind === "sheet" && inherited.length > 0 ? renderSheet(root, child, stack, inherited, elements) : renderNode(root, child, stack, elements);
1086
1088
  if (child.kind === "sheet" || child.kind === "alert") out.push(createElement(Fragment, { key: child.id }, sb(HOIST_KIND, void 0, void 0, drawn, "root")));
1087
1089
  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
1090
  else out.push(drawn);
1089
1091
  }
1090
1092
  return createElement(Fragment, null, ...out);
1091
1093
  }
1094
+ function readBinding(value, onChange) {
1095
+ if (Array.isArray(value) && value.length === 2 && typeof value[1] === "function") {
1096
+ const [current, set] = value;
1097
+ return [current, set];
1098
+ }
1099
+ return [value, onChange ?? (() => {
1100
+ })];
1101
+ }
1102
+ let active = null;
1103
+ function Draggable({ payload, children }) {
1104
+ return sb(
1105
+ "draggable",
1106
+ {},
1107
+ {
1108
+ dragstart: (event) => {
1109
+ active = { id: event.id, payload };
1110
+ },
1111
+ dragend: (event) => {
1112
+ if (active?.id === event.id) active = null;
1113
+ }
1114
+ },
1115
+ children
1116
+ );
1117
+ }
1118
+ function itemValue(item) {
1119
+ if (item.text !== void 0) {
1120
+ if (item.type === "text/uri-list") {
1121
+ try {
1122
+ return new URL(item.text.split(/\r?\n/)[0].trim());
1123
+ } catch {
1124
+ return item.text;
1125
+ }
1126
+ }
1127
+ return item.text;
1128
+ }
1129
+ const name = item.path ?? item.name ?? "file";
1130
+ if (item.file) {
1131
+ const file = item.file;
1132
+ return item.path !== void 0 && item.path !== file.name ? new File([file], item.path, { type: file.type, lastModified: file.lastModified }) : file;
1133
+ }
1134
+ if (item.url !== void 0 && item.data === void 0) {
1135
+ const fromReference = browserEnvironment.fileFromReference;
1136
+ if (fromReference) {
1137
+ return fromReference({ type: item.type, name, url: item.url, ...item.path !== void 0 ? { path: item.path } : {}, ...item.size !== void 0 ? { size: item.size } : {} });
1138
+ }
1139
+ }
1140
+ const binary = atob(item.data ?? "");
1141
+ const bytes = new Uint8Array(binary.length);
1142
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1143
+ return typeof File !== "undefined" ? new File([bytes], name, { type: item.type }) : bytes;
1144
+ }
1145
+ function DropDestination({ action, isTargeted, accepts, children }) {
1146
+ return sb(
1147
+ "dropdestination",
1148
+ { accepts: accepts ?? [] },
1149
+ {
1150
+ drop: (event) => {
1151
+ if (event.phase === "entered") {
1152
+ isTargeted?.(true);
1153
+ return;
1154
+ }
1155
+ isTargeted?.(false);
1156
+ if (event.phase !== "performed") return;
1157
+ let items;
1158
+ if (event.source !== void 0) items = active && active.id === event.source ? [active.payload] : [];
1159
+ else items = (event.items ?? []).map(itemValue);
1160
+ if (items.length > 0) action(items, { x: event.x, y: event.y });
1161
+ }
1162
+ },
1163
+ children
1164
+ );
1165
+ }
1166
+ function FileImporterPresenter(spec) {
1167
+ const [presented, setPresented] = readBinding(spec.isPresented);
1168
+ const latest = useRef(spec);
1169
+ latest.current = spec;
1170
+ const choosing = useRef(false);
1171
+ useLayoutEffect(() => {
1172
+ if (!presented || choosing.current) return;
1173
+ const { allowedContentTypes, allowsMultipleSelection } = latest.current;
1174
+ const pick = browserEnvironment.pickFiles;
1175
+ if (!pick) {
1176
+ setPresented(false);
1177
+ latest.current.onCancellation?.();
1178
+ return;
1179
+ }
1180
+ choosing.current = true;
1181
+ void pick({ accept: allowedContentTypes.join(","), multiple: allowsMultipleSelection === true }).then((files) => {
1182
+ choosing.current = false;
1183
+ setPresented(false);
1184
+ if (files.length > 0) latest.current.onCompletion(files);
1185
+ else latest.current.onCancellation?.();
1186
+ });
1187
+ }, [presented, setPresented]);
1188
+ return null;
1189
+ }
1190
+ const EXTENSIONS = {
1191
+ "text/plain": "txt",
1192
+ "text/csv": "csv",
1193
+ "text/markdown": "md",
1194
+ "text/html": "html",
1195
+ "application/json": "json",
1196
+ "application/pdf": "pdf",
1197
+ "image/png": "png",
1198
+ "image/jpeg": "jpg",
1199
+ "image/svg+xml": "svg",
1200
+ "audio/mp4": "m4a",
1201
+ "video/mp4": "mp4"
1202
+ };
1203
+ function exportFile(document2, contentType, defaultFilename, index = 0) {
1204
+ const type = document2 instanceof Blob && document2.type || contentType || "application/octet-stream";
1205
+ let name = defaultFilename ?? (document2 instanceof File ? document2.name : index === 0 ? "Untitled" : `Untitled ${index + 1}`);
1206
+ name = name.replace(/[\\/:*?"<>|]+/g, "-").trim() || "Untitled";
1207
+ const extension = EXTENSIONS[type.split(";")[0].trim()];
1208
+ if (extension && !/\.[A-Za-z0-9]{1,8}$/.test(name)) name = `${name}.${extension}`;
1209
+ if (document2 instanceof File && document2.name === name && document2.type === type) return document2;
1210
+ const bytes = typeof document2 === "string" ? new TextEncoder().encode(document2) : document2;
1211
+ return new File([bytes], name, { type });
1212
+ }
1213
+ function FileExporterPresenter(spec) {
1214
+ const [presented, setPresented] = readBinding(spec.isPresented);
1215
+ const latest = useRef(spec);
1216
+ latest.current = spec;
1217
+ const exporting = useRef(false);
1218
+ useLayoutEffect(() => {
1219
+ if (!presented || exporting.current) return;
1220
+ const { document: document2, contentType, defaultFilename } = latest.current;
1221
+ const documents = document2 === null ? [] : Array.isArray(document2) ? document2 : [document2];
1222
+ if (documents.length === 0) {
1223
+ setPresented(false);
1224
+ latest.current.onCancellation?.();
1225
+ return;
1226
+ }
1227
+ exporting.current = true;
1228
+ const files = documents.map((item, index) => exportFile(item, contentType, defaultFilename, index));
1229
+ exportFiles(files).then(
1230
+ (result) => {
1231
+ exporting.current = false;
1232
+ setPresented(false);
1233
+ if (result.outcome === "cancelled") latest.current.onCancellation?.();
1234
+ else latest.current.onCompletion?.(result);
1235
+ },
1236
+ (error) => {
1237
+ exporting.current = false;
1238
+ setPresented(false);
1239
+ latest.current.onError?.(error);
1240
+ }
1241
+ );
1242
+ }, [presented, setPresented]);
1243
+ return null;
1244
+ }
1245
+ const ListSelectionContext = createContext(null);
1246
+ function Row({ tag, onDelete, deletable, onMove, movable, index, containerIndex, children }) {
1247
+ const selection = useContext(ListSelectionContext);
1248
+ const selectable = selection !== null && tag !== void 0;
1249
+ const canDelete = onDelete !== void 0 && deletable !== false;
1250
+ const canMove = onMove !== void 0 && movable !== false;
1251
+ if (!selectable && !canDelete && !canMove) return children;
1252
+ const props = {};
1253
+ if (selectable) {
1254
+ props["selectable"] = true;
1255
+ props["selected"] = selection.isSelected(tag);
1256
+ }
1257
+ if (canDelete) props["deletable"] = true;
1258
+ if (canMove) props["movable"] = true;
1259
+ return sb(
1260
+ "listrow",
1261
+ props,
1262
+ {
1263
+ tap: () => selectable && selection.select(tag),
1264
+ delete: () => onDelete?.(),
1265
+ move: (event) => {
1266
+ const start = (containerIndex ?? index ?? 0) - (index ?? 0);
1267
+ onMove?.(Math.max(0, event.to - start));
1268
+ }
1269
+ },
1270
+ children
1271
+ );
1272
+ }
1092
1273
  const HIERARCHY = { primary: 1, secondary: 2, tertiary: 3, quaternary: 4 };
1093
1274
  function rgb(r2, g, b, a = 1) {
1094
1275
  return { r: r2, g, b, a };
@@ -1235,661 +1416,68 @@ function insets(edges, amount) {
1235
1416
  }
1236
1417
  return result;
1237
1418
  }
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;
1419
+ const isPoint = (value) => typeof value === "object" && value !== null && ("x" in value || "y" in value);
1420
+ function command(request) {
1421
+ return request.axes ? { anchor: request.anchor, serial: request.serial, axes: request.axes } : { anchor: request.anchor, serial: request.serial };
1245
1422
  }
1246
- function canMeasureText() {
1247
- return installed !== null;
1423
+ function offsetCommand(request) {
1424
+ const out = { serial: request.serial };
1425
+ if (typeof request.point.x === "number") out.x = request.point.x;
1426
+ if (typeof request.point.y === "number") out.y = request.point.y;
1427
+ return out;
1248
1428
  }
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);
1429
+ const ScrollRequestContext = createContext(null);
1430
+ let nextSerial = 1;
1431
+ function ScrollViewReader({ children }) {
1432
+ const [request, setRequest] = useState(null);
1433
+ const proxy = {
1434
+ scrollTo: (target, anchor, options) => {
1435
+ if (isPoint(target)) setRequest({ point: target, serial: nextSerial++ });
1436
+ else setRequest({ id: target, anchor: anchor === void 0 ? null : unitPoint(anchor), serial: nextSerial++, ...options?.axes ? { axes: options.axes } : {} });
1437
+ }
1438
+ };
1439
+ return /* @__PURE__ */ jsx(ScrollRequestContext.Provider, { value: request, children: children(proxy) });
1253
1440
  }
1254
- function decidePlatform(width, media = matchesMedia) {
1255
- return horizontalSizeClassFor(width) === "regular" && media("(hover: hover) and (pointer: fine)") ? "macos" : "ios";
1441
+ function ScrollTarget({ id, children }) {
1442
+ const request = useContext(ScrollRequestContext);
1443
+ const position = useContext(ScrollPositionContext);
1444
+ const on = position ? { scrollposition: () => position.report(id) } : void 0;
1445
+ if (request && "id" in request && request.id === id) return sb("styled", { style: { scrollID: true, scrollTo: command(request) } }, on, children);
1446
+ if (on) return sb("styled", { style: { scrollID: true } }, on, children);
1447
+ return styled({ scrollID: true }, children);
1256
1448
  }
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;
1840
- }
1841
- const ScrollRequestContext = createContext(null);
1842
- let nextSerial = 1;
1843
- function ScrollViewReader({ children }) {
1844
- const [request, setRequest] = useState(null);
1845
- const proxy = {
1846
- scrollTo: (target, anchor, options) => {
1847
- if (isPoint(target)) setRequest({ point: target, serial: nextSerial++ });
1848
- else setRequest({ id: target, anchor: anchor === void 0 ? null : unitPoint(anchor), serial: nextSerial++, ...options?.axes ? { axes: options.axes } : {} });
1849
- }
1850
- };
1851
- return /* @__PURE__ */ jsx(ScrollRequestContext.Provider, { value: request, children: children(proxy) });
1852
- }
1853
- function ScrollTarget({ id, children }) {
1854
- const request = useContext(ScrollRequestContext);
1855
- const position = useContext(ScrollPositionContext);
1856
- const on = position ? { scrollposition: () => position.report(id) } : void 0;
1857
- if (request && "id" in request && request.id === id) return sb("styled", { style: { scrollID: true, scrollTo: command(request) } }, on, children);
1858
- if (on) return sb("styled", { style: { scrollID: true } }, on, children);
1859
- return styled({ scrollID: true }, children);
1860
- }
1861
- const ScrollPositionContext = createContext(null);
1862
- function useScrollPosition(binding, anchor, axes) {
1863
- const bound = binding !== void 0;
1864
- const [value, set] = bound ? readBinding(binding) : [null, () => void 0];
1865
- const last = useRef(void 0);
1866
- const reported = useRef(void 0);
1867
- const request = useRef(null);
1868
- const outer = useContext(ScrollRequestContext);
1869
- const point = anchor === void 0 ? null : unitPoint(anchor);
1870
- if (value !== last.current) {
1871
- last.current = value;
1872
- if (isPoint(value)) request.current = { point: value, serial: nextSerial++ };
1873
- else if (value !== null && value !== reported.current) request.current = { id: value, anchor: point, serial: nextSerial++, axes };
1874
- reported.current = void 0;
1875
- }
1876
- const own = request.current;
1877
- const current = own && (!outer || own.serial > outer.serial) ? own : outer;
1878
- const offset = useOffsetCommand(bound ? current : outer);
1879
- if (!bound) return offset ? { props: offset, provide: (children) => children } : null;
1880
- const props = { reportsPosition: true, ...offset };
1881
- if (point) props["positionAnchor"] = point;
1882
- const context = {
1883
- report: (id) => {
1884
- reported.current = id;
1885
- set(id);
1886
- }
1887
- };
1888
- const inner = current && "point" in current ? outer && "id" in outer ? outer : null : current;
1889
- return {
1890
- props,
1891
- provide: (children) => /* @__PURE__ */ jsx(ScrollPositionContext.Provider, { value: context, children: /* @__PURE__ */ jsx(ScrollRequestContext.Provider, { value: inner, children }) })
1892
- };
1449
+ const ScrollPositionContext = createContext(null);
1450
+ function useScrollPosition(binding, anchor, axes) {
1451
+ const bound = binding !== void 0;
1452
+ const [value, set] = bound ? readBinding(binding) : [null, () => void 0];
1453
+ const last = useRef(void 0);
1454
+ const reported = useRef(void 0);
1455
+ const request = useRef(null);
1456
+ const outer = useContext(ScrollRequestContext);
1457
+ const point = anchor === void 0 ? null : unitPoint(anchor);
1458
+ if (value !== last.current) {
1459
+ last.current = value;
1460
+ if (isPoint(value)) request.current = { point: value, serial: nextSerial++ };
1461
+ else if (value !== null && value !== reported.current) request.current = { id: value, anchor: point, serial: nextSerial++, axes };
1462
+ reported.current = void 0;
1463
+ }
1464
+ const own = request.current;
1465
+ const current = own && (!outer || own.serial > outer.serial) ? own : outer;
1466
+ const offset = useOffsetCommand(bound ? current : outer);
1467
+ if (!bound) return offset ? { props: offset, provide: (children) => children } : null;
1468
+ const props = { reportsPosition: true, ...offset };
1469
+ if (point) props["positionAnchor"] = point;
1470
+ const context = {
1471
+ report: (id) => {
1472
+ reported.current = id;
1473
+ set(id);
1474
+ }
1475
+ };
1476
+ const inner = current && "point" in current ? outer && "id" in outer ? outer : null : current;
1477
+ return {
1478
+ props,
1479
+ provide: (children) => /* @__PURE__ */ jsx(ScrollPositionContext.Provider, { value: context, children: /* @__PURE__ */ jsx(ScrollRequestContext.Provider, { value: inner, children }) })
1480
+ };
1893
1481
  }
1894
1482
  function useOffsetCommand(request) {
1895
1483
  const seen = useRef(null);
@@ -2114,573 +1702,932 @@ function moveItems(items, offsets, to) {
2114
1702
  const at = to - before;
2115
1703
  return [...rest.slice(0, at), ...picked, ...rest.slice(at)];
2116
1704
  }
2117
- function ForEach(props) {
1705
+ function ForEach(props) {
1706
+ const { own, modifiers } = splitProps(props);
1707
+ const rows = own.data.map((element, index) => {
1708
+ const key = own.id ? own.id(element, index) : keyOf(element, index);
1709
+ const content = applyModifiers(own.children(element, index), modifiers);
1710
+ if (!own.onDelete && !own.onMove) return createElement(Fragment, { key }, content);
1711
+ const { onDelete, onMove } = own;
1712
+ const row = { tag: key, index };
1713
+ if (onDelete) {
1714
+ row.onDelete = () => onDelete([index]);
1715
+ row.deletable = !own.deleteDisabled?.(element, index);
1716
+ }
1717
+ if (onMove) {
1718
+ row.onMove = (to) => onMove([index], to);
1719
+ row.movable = !own.moveDisabled?.(element, index);
1720
+ }
1721
+ return createElement(Row, { key, ...row }, content);
1722
+ });
1723
+ return rows.length > LAZY_BATCH ? createElement(LazyGroup, { rows }) : rows;
1724
+ }
1725
+ function keyOf(element, index) {
1726
+ if (typeof element === "string" || typeof element === "number") return element;
1727
+ if (typeof element === "object" && element !== null && "id" in element) {
1728
+ const id = element.id;
1729
+ if (typeof id === "string" || typeof id === "number") return id;
1730
+ }
1731
+ return index;
1732
+ }
1733
+ function flattenChildren(children, options = {}) {
1734
+ const result = [];
1735
+ const visit = (node) => {
1736
+ if (node === null || node === void 0 || typeof node === "boolean") return;
1737
+ if (Array.isArray(node)) {
1738
+ node.forEach(visit);
1739
+ return;
1740
+ }
1741
+ if (!isValidElement(node)) return;
1742
+ const element = node;
1743
+ if (element.type === Fragment) {
1744
+ const inner = element.props["children"];
1745
+ if (element.key !== null && isValidElement(inner) && inner.key === null && inner.type !== Fragment) {
1746
+ visit({ ...inner, key: element.key });
1747
+ return;
1748
+ }
1749
+ visit(inner);
1750
+ return;
1751
+ }
1752
+ if (element.type === ForEach) {
1753
+ visit(ForEach(element.props));
1754
+ return;
1755
+ }
1756
+ if (element.type === LazyGroup && !options.keepLazy) {
1757
+ visit(element.props.rows);
1758
+ return;
1759
+ }
1760
+ result.push(element);
1761
+ };
1762
+ visit(children);
1763
+ return result;
1764
+ }
1765
+ function GridItem(spec = {}) {
1766
+ const dimension = (n) => n === void 0 || n === Infinity ? "infinity" : n;
1767
+ let size;
1768
+ if (spec.size && "fixed" in spec.size) size = { kind: "fixed", value: spec.size.fixed };
1769
+ else if (spec.size && "adaptive" in spec.size) {
1770
+ size = { kind: "adaptive", minimum: spec.size.adaptive.minimum, maximum: dimension(spec.size.adaptive.maximum) };
1771
+ } else {
1772
+ const flexible = spec.size && "flexible" in spec.size ? spec.size.flexible : {};
1773
+ size = { kind: "flexible", minimum: flexible.minimum ?? 10, maximum: dimension(flexible.maximum) };
1774
+ }
1775
+ return { size, spacing: spec.spacing ?? null, alignment: spec.alignment ?? null };
1776
+ }
1777
+ GridItem.flexible = (options = {}) => GridItem({ size: { flexible: options }, spacing: options.spacing, alignment: options.alignment });
1778
+ GridItem.fixed = (value, options = {}) => GridItem({ size: { fixed: value }, spacing: options.spacing, alignment: options.alignment });
1779
+ GridItem.adaptive = (options) => GridItem({ size: { adaptive: options }, spacing: options.spacing, alignment: options.alignment });
1780
+ function LazyVGrid(props) {
1781
+ const { own, modifiers } = splitProps(props);
1782
+ const grid = { columns: own.columns, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1783
+ if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
1784
+ return applyModifiers(sb("lazyvgrid", grid, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1785
+ }
1786
+ function LazyHGrid(props) {
1787
+ const { own, modifiers } = splitProps(props);
1788
+ const grid = { rows: own.rows, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1789
+ if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
1790
+ return applyModifiers(sb("lazyhgrid", grid, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1791
+ }
1792
+ function Grid(props) {
1793
+ const { own, modifiers } = splitProps(props);
1794
+ return applyModifiers(
1795
+ sb(
1796
+ "grid",
1797
+ { horizontalSpacing: own.horizontalSpacing ?? null, verticalSpacing: own.verticalSpacing ?? null, alignment: own.alignment ?? "center" },
1798
+ void 0,
1799
+ own.children
1800
+ ),
1801
+ modifiers
1802
+ );
1803
+ }
1804
+ function GridRow(props) {
1805
+ const { own, modifiers } = splitProps(props);
1806
+ return applyModifiers(sb("gridrow", { alignment: own.alignment ?? null }, void 0, own.children), modifiers);
1807
+ }
1808
+ function runStyleOf(props, inherited, nested) {
1809
+ const style = { ...inherited };
1810
+ if (props.italic) style.italic = true;
1811
+ if (props.underline) style.underline = typeof props.underline === "object" && props.underline.color ? { color: color(props.underline.color) } : {};
1812
+ if (props.strikethrough) {
1813
+ style.strikethrough = typeof props.strikethrough === "object" && props.strikethrough.color ? { color: color(props.strikethrough.color) } : {};
1814
+ }
1815
+ const kerning = props.tracking ?? props.kerning;
1816
+ if (kerning !== void 0) style.kerning = kerning;
1817
+ if (props.baselineOffset !== void 0) style.baselineOffset = props.baselineOffset;
1818
+ if (props.link) style.link = props.link;
1819
+ if (nested) {
1820
+ let runFont2 = props.font !== void 0 ? font(props.font) : void 0;
1821
+ if (props.bold) runFont2 = { ...runFont2 ?? {}, weight: "bold" };
1822
+ if (props.fontWeight) runFont2 = { ...runFont2 ?? {}, weight: props.fontWeight };
1823
+ if (props.monospacedDigit) runFont2 = { ...runFont2 ?? {}, monospacedDigit: true };
1824
+ if (runFont2) style.font = { ...style.font ?? {}, ...runFont2 };
1825
+ if (props.foregroundStyle !== void 0) {
1826
+ style.foreground = shapeStyle(Array.isArray(props.foregroundStyle) ? props.foregroundStyle[0] : props.foregroundStyle);
1827
+ }
1828
+ const background = runBackground(props.background);
1829
+ if (background) style.background = background;
1830
+ }
1831
+ return style;
1832
+ }
1833
+ function runBackground(value) {
1834
+ if (typeof value === "string") {
1835
+ const made = color(value);
1836
+ return "hierarchical" in made ? void 0 : made;
1837
+ }
1838
+ if (typeof value !== "object" || value === null || isValidElement(value) || "in" in value) return void 0;
1839
+ return "r" in value || "name" in value || "named" in value ? value : void 0;
1840
+ }
1841
+ function segmentsOf(children, style, into) {
1842
+ const visit = (node) => {
1843
+ if (node === null || node === void 0 || typeof node === "boolean") return;
1844
+ if (typeof node === "string" || typeof node === "number") {
1845
+ const last = into[into.length - 1];
1846
+ if (last && "text" in last && last.style === style) last.text += String(node);
1847
+ else into.push({ text: String(node), style });
1848
+ return;
1849
+ }
1850
+ if (Array.isArray(node)) {
1851
+ node.forEach(visit);
1852
+ return;
1853
+ }
1854
+ if (!isValidElement(node)) return;
1855
+ const element = node;
1856
+ if (element.type === Fragment) {
1857
+ visit(element.props["children"]);
1858
+ return;
1859
+ }
1860
+ if (element.type === Text) {
1861
+ const props = element.props;
1862
+ segmentsOf(props.children, runStyleOf(props, style, true), into);
1863
+ return;
1864
+ }
1865
+ if (element.type === Image$1) {
1866
+ const props = element.props;
1867
+ into.push({ image: imageProps$1(props), style });
1868
+ return;
1869
+ }
1870
+ };
1871
+ visit(children);
1872
+ }
1873
+ function isPlainStyle(style) {
1874
+ return Object.keys(style).length === 0;
1875
+ }
1876
+ function Text(props) {
1877
+ const { own, modifiers } = splitProps(props);
1878
+ const environment = useViewEnvironment();
1879
+ const style = runStyleOf(props, {}, false);
1880
+ const segments = [];
1881
+ segmentsOf(own.children, style, segments);
1882
+ const textCase = takeModifier(modifiers, "textCase") ?? environment.textCase;
1883
+ if (textCase) {
1884
+ for (const segment of segments) {
1885
+ if ("text" in segment) segment.text = textCase === "uppercase" ? segment.text.toUpperCase() : segment.text.toLowerCase();
1886
+ }
1887
+ }
1888
+ const redacted = takeModifier(modifiers, "redacted") ?? environment.redacted;
1889
+ const content = segments.map((segment) => "text" in segment ? segment.text : "").join("");
1890
+ const plain2 = segments.length <= 1 && (segments.length === 0 || "text" in segments[0] && isPlainStyle(segments[0].style));
1891
+ if (plain2) return applyModifiers(placeholder(sb("text", { text: content }), redacted), modifiers);
1892
+ const runs = segments.map((segment) => {
1893
+ const run = { ...segment.style };
1894
+ if ("text" in segment) run["text"] = segment.text;
1895
+ else run["image"] = segment.image;
1896
+ return run;
1897
+ });
1898
+ const hasLinks = segments.some((segment) => segment.style.link);
1899
+ const handlers = hasLinks ? { link: (event) => openURL(event.value) } : void 0;
1900
+ return applyModifiers(placeholder(sb("text", { text: content, runs }, handlers), redacted), modifiers);
1901
+ }
1902
+ function placeholder(node, redacted) {
1903
+ if (!redacted) return node;
1904
+ return styled({ background: { hierarchical: 3 }, backgroundCornerRadius: 4 }, styled({ foreground: { name: "clear" } }, node));
1905
+ }
1906
+ function openURL(url) {
1907
+ const native = globalThis.__sbNative;
1908
+ if (native?.openURL) {
1909
+ native.openURL(url);
1910
+ return;
1911
+ }
1912
+ if (typeof window !== "undefined") window.open(url, "_blank", "noopener");
1913
+ }
1914
+ function imageProps$1(props) {
1915
+ if (props.systemName !== void 0) return { systemName: props.systemName };
1916
+ if (props.bitmap !== void 0) {
1917
+ const { source, steps = [], version } = props.bitmap;
1918
+ const extent = bitmapExtent(source, steps);
1919
+ const finite = Number.isFinite(extent.width) && Number.isFinite(extent.height);
1920
+ const width = finite ? Math.max(0, Math.round(extent.width)) : 0;
1921
+ const height = finite ? Math.max(0, Math.round(extent.height)) : 0;
1922
+ const scale = props.scale !== void 0 && props.scale > 0 ? props.scale : 1;
1923
+ return {
1924
+ bitmap: { source: source?.key ?? null, steps, width, height, ...version !== void 0 ? { version } : {} },
1925
+ resizable: props.resizable ?? false,
1926
+ width: width / scale,
1927
+ height: height / scale,
1928
+ ...props.interpolation === "none" ? { interpolation: "none" } : {}
1929
+ };
1930
+ }
1931
+ if (props.url !== void 0) return { url: props.url, resizable: props.resizable ?? false, width: props.width ?? 0, height: props.height ?? 0 };
1932
+ return { name: props.name ?? "", resizable: props.resizable ?? false };
1933
+ }
1934
+ function Image$1(props) {
1935
+ const { modifiers } = splitProps(props);
1936
+ const environment = useViewEnvironment();
1937
+ const redacted = takeModifier(modifiers, "redacted") ?? environment.redacted;
1938
+ const mode = takeModifier(modifiers, "symbolRenderingMode") ?? environment.symbolRenderingMode;
1939
+ const own = imageProps$1(props);
1940
+ const element = props.systemName !== void 0 && mode === "palette" ? { ...own, renderingMode: "palette" } : own;
1941
+ if (props.accessibilityLabel) element["accessibilityLabel"] = props.accessibilityLabel;
1942
+ return applyModifiers(placeholder(sb("image", element), redacted), modifiers);
1943
+ }
1944
+ function Label(props) {
1945
+ const { own, modifiers } = splitProps(props);
1946
+ const environment = useViewEnvironment();
1947
+ const style = takeModifier(modifiers, "labelStyle") ?? environment.labelStyle ?? "automatic";
1948
+ 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);
1949
+ const title = typeof own.title === "string" || typeof own.title === "number" ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.title;
1950
+ if (style === "iconOnly") {
1951
+ const name = typeof own.title === "string" || typeof own.title === "number" ? String(own.title) : void 0;
1952
+ 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;
1953
+ return applyModifiers(named, modifiers);
1954
+ }
1955
+ if (style === "titleOnly") return applyModifiers(title, modifiers);
1956
+ return applyModifiers(
1957
+ /* @__PURE__ */ jsxs(HStack, { spacing: 8, children: [
1958
+ style === "titleAndIcon" ? icon : styled({ labelIcon: true }, icon),
1959
+ title
1960
+ ] }),
1961
+ modifiers
1962
+ );
1963
+ }
1964
+ function LabeledContent(props) {
2118
1965
  const { own, modifiers } = splitProps(props);
2119
- const rows = own.data.map((element, index) => {
2120
- const key = own.id ? own.id(element, index) : keyOf(element, index);
2121
- const content = applyModifiers(own.children(element, index), modifiers);
2122
- if (!own.onDelete && !own.onMove) return createElement(Fragment, { key }, content);
2123
- const { onDelete, onMove } = own;
2124
- const row = { tag: key, index };
2125
- if (onDelete) {
2126
- row.onDelete = () => onDelete([index]);
2127
- row.deletable = !own.deleteDisabled?.(element, index);
2128
- }
2129
- if (onMove) {
2130
- row.onMove = (to) => onMove([index], to);
2131
- row.movable = !own.moveDisabled?.(element, index);
2132
- }
2133
- return createElement(Row, { key, ...row }, content);
2134
- });
2135
- return rows.length > LAZY_BATCH ? createElement(LazyGroup, { rows }) : rows;
1966
+ const label = typeof own.label === "string" || typeof own.label === "number" ? /* @__PURE__ */ jsx(Text, { children: own.label }) : own.label;
1967
+ const content = own.children ?? own.value;
1968
+ const value = typeof content === "string" || typeof content === "number" ? /* @__PURE__ */ jsx(Text, { children: content }) : content;
1969
+ return applyModifiers(
1970
+ /* @__PURE__ */ jsxs(HStack, { children: [
1971
+ label,
1972
+ sb("spacer", { minLength: null }),
1973
+ styled({ foreground: { hierarchical: 2 } }, value)
1974
+ ] }),
1975
+ modifiers
1976
+ );
2136
1977
  }
2137
- function keyOf(element, index) {
2138
- if (typeof element === "string" || typeof element === "number") return element;
2139
- if (typeof element === "object" && element !== null && "id" in element) {
2140
- const id = element.id;
2141
- if (typeof id === "string" || typeof id === "number") return id;
2142
- }
2143
- return index;
1978
+ function Link(props) {
1979
+ const { own, modifiers } = splitProps(props);
1980
+ const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.children;
1981
+ return applyModifiers(sb("button", { role: null, style: "automatic" }, { tap: () => openURL(own.destination) }, label), modifiers);
2144
1982
  }
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;
1983
+ const isPushedView = (value) => typeof value === "object" && value !== null && "view" in value && isValidElement(value.view);
1984
+ function routeOf(value, depth = 0) {
1985
+ if (depth > 8) return void 0;
1986
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
1987
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
1988
+ if (Array.isArray(value)) {
1989
+ const items = value.map((item) => routeOf(item, depth + 1));
1990
+ return items.every((item) => item !== void 0) ? items : void 0;
1991
+ }
1992
+ if (typeof value === "object" && !isPushedView(value)) {
1993
+ const proto = Object.getPrototypeOf(value);
1994
+ if (proto !== Object.prototype && proto !== null) return void 0;
1995
+ const out = {};
1996
+ for (const [key, item] of Object.entries(value)) {
1997
+ if (item === void 0) continue;
1998
+ const plain2 = routeOf(item, depth + 1);
1999
+ if (plain2 === void 0) return void 0;
2000
+ out[key] = plain2;
2171
2001
  }
2172
- result.push(element);
2173
- };
2174
- visit(children);
2175
- return result;
2176
- }
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) };
2002
+ return out;
2186
2003
  }
2187
- return { size, spacing: spec.spacing ?? null, alignment: spec.alignment ?? null };
2004
+ return void 0;
2188
2005
  }
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) {
2193
- 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);
2006
+ function NavigationStack(props) {
2007
+ const enclosing = useNavigation();
2008
+ if (enclosing) return /* @__PURE__ */ jsx(FlattenedStack, { ...props });
2009
+ return /* @__PURE__ */ jsx(OwnStack, { ...props });
2197
2010
  }
2198
- function LazyHGrid(props) {
2011
+ function FlattenedStack(props) {
2199
2012
  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);
2013
+ return applyModifiers(
2014
+ /* @__PURE__ */ jsxs(Fragment$1, { children: [
2015
+ own.destination && /* @__PURE__ */ jsx(RegisterDestination, { destination: own.destination }),
2016
+ own.children
2017
+ ] }),
2018
+ modifiers
2019
+ );
2203
2020
  }
2204
- function Grid(props) {
2021
+ function OwnStack(props) {
2205
2022
  const { own, modifiers } = splitProps(props);
2023
+ const remote = useContext(HostTreeContext)?.remote ?? false;
2024
+ const [internalPath, setInternalPath] = useState([]);
2025
+ const [path, setPath] = own.path !== void 0 ? readBinding(own.path, own.onPathChange) : [internalPath, setInternalPath];
2026
+ const destination = useRef(null);
2027
+ destination.current = own.destination ?? null;
2028
+ const context = {
2029
+ push: (value) => setPath([...path, value]),
2030
+ pop: () => setPath(path.slice(0, -1)),
2031
+ path,
2032
+ setPath: (next) => setPath(next),
2033
+ registerDestination: (builder) => {
2034
+ destination.current = builder;
2035
+ }
2036
+ };
2037
+ const screens = [
2038
+ /* @__PURE__ */ jsx(Fragment, { children: sb("navscreen", { title: "", displayMode: "automatic", depth: 0 }, { tap: context.pop }, /* @__PURE__ */ jsx(ScreenDepthContext.Provider, { value: 0, children: own.children })) }, "root")
2039
+ ];
2040
+ path.forEach((value, index) => {
2041
+ const pop = () => setPath(path.slice(0, index));
2042
+ const route = remote ? routeOf(value) : void 0;
2043
+ screens.push(
2044
+ /* @__PURE__ */ jsx(Fragment, { children: sb(
2045
+ "navscreen",
2046
+ { title: "", displayMode: "automatic", depth: index + 1, ...route === void 0 ? {} : { route } },
2047
+ { tap: pop },
2048
+ /* @__PURE__ */ jsx(DismissContext.Provider, { value: pop, children: /* @__PURE__ */ jsx(ScreenDepthContext.Provider, { value: index + 1, children: /* @__PURE__ */ jsx(PathScreen, { value, destination }) }) })
2049
+ ) }, index + 1)
2050
+ );
2051
+ });
2206
2052
  return applyModifiers(
2207
- sb(
2208
- "grid",
2209
- { horizontalSpacing: own.horizontalSpacing ?? null, verticalSpacing: own.verticalSpacing ?? null, alignment: own.alignment ?? "center" },
2210
- void 0,
2211
- own.children
2212
- ),
2053
+ sb("navstack", {}, { setpath: (event) => setPath(Array.isArray(event.path) ? event.path : []) }, /* @__PURE__ */ jsx(NavigationContext.Provider, { value: context, children: screens })),
2213
2054
  modifiers
2214
2055
  );
2215
2056
  }
2216
- function GridRow(props) {
2057
+ function PathScreen({ value, destination }) {
2058
+ if (isPushedView(value)) return value.view;
2059
+ return destination.current ? destination.current(value) : null;
2060
+ }
2061
+ function NavigationLink(props) {
2217
2062
  const { own, modifiers } = splitProps(props);
2218
- return applyModifiers(sb("gridrow", { alignment: own.alignment ?? null }, void 0, own.children), modifiers);
2063
+ const navigation = useNavigation();
2064
+ const environment = useViewEnvironment();
2065
+ const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.children;
2066
+ const listSelection = useContext(ListSelectionContext);
2067
+ const sidebar = useContext(SplitSelectionContext) !== null && listSelection !== null && own.destination === void 0 && own.value !== void 0;
2068
+ const tap = () => {
2069
+ if (sidebar) {
2070
+ listSelection.select(own.value);
2071
+ return;
2072
+ }
2073
+ if (!navigation) return;
2074
+ if (own.destination !== void 0) navigation.push({ view: own.destination });
2075
+ else navigation.push(own.value);
2076
+ };
2077
+ const style = takeModifier(modifiers, "buttonStyle") ?? environment.buttonStyle;
2078
+ const linkProps = style === "plain" ? { style: "plain" } : {};
2079
+ if (sidebar) linkProps["selects"] = true;
2080
+ return applyModifiers(sb("navlink", linkProps, { tap }, label), modifiers);
2219
2081
  }
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);
2082
+ const SplitSelectionContext = createContext(null);
2083
+ function splitColumnWidthHint(width) {
2084
+ if (typeof width === "number") return { min: width, ideal: width, max: width };
2085
+ return { ...width.min !== void 0 ? { min: width.min } : {}, ideal: width.ideal, ...width.max !== void 0 ? { max: width.max } : {} };
2086
+ }
2087
+ function NavigationSplitView(props) {
2088
+ const { own, modifiers } = splitProps(props);
2089
+ const sizeClass = useHorizontalSizeClass();
2090
+ const [selected, setSelected] = useState(false);
2091
+ const reporters = useRef(/* @__PURE__ */ new Map());
2092
+ const settle = () => setSelected([...reporters.current.values()].some((entry) => entry.selected));
2093
+ const [report] = useState(() => {
2094
+ return {
2095
+ report: (key, nowSelected, clear2) => {
2096
+ reporters.current.set(key, { selected: nowSelected, clear: clear2 });
2097
+ settle();
2098
+ },
2099
+ retract: (key) => {
2100
+ if (reporters.current.delete(key)) settle();
2101
+ }
2102
+ };
2103
+ });
2104
+ const clear = () => {
2105
+ for (const entry of reporters.current.values()) {
2106
+ if (!entry.selected) continue;
2107
+ entry.clear();
2108
+ entry.selected = false;
2239
2109
  }
2240
- const background = runBackground(props.background);
2241
- if (background) style.background = background;
2110
+ settle();
2111
+ };
2112
+ const [internal, setInternal] = useState("automatic");
2113
+ const [visibility, setVisibility] = own.columnVisibility !== void 0 ? readBinding(own.columnVisibility, own.onColumnVisibilityChange) : [internal, setInternal];
2114
+ if (sizeClass === "compact") {
2115
+ return applyModifiers(/* @__PURE__ */ jsx(CompactSplit, { sidebar: own.sidebar, detail: own.detail, report, selected, clear }), modifiers);
2242
2116
  }
2243
- return style;
2117
+ const sidebarVisible = visibility !== "detailOnly";
2118
+ const toggle = () => withAnimation(() => setVisibility(sidebarVisible ? "detailOnly" : "all"));
2119
+ const detail = isValidElement(own.detail) && own.detail.type === NavigationStack ? own.detail : /* @__PURE__ */ jsx(NavigationStack, { children: own.detail });
2120
+ const sidebar = own.sidebarWidth !== void 0 ? sb("styled", { style: { splitColumnWidth: splitColumnWidthHint(own.sidebarWidth) } }, void 0, own.sidebar) : own.sidebar;
2121
+ return applyModifiers(
2122
+ sb("navsplit", { sidebarVisible }, { tap: toggle, columnwidth: (event) => own.onSidebarWidthChange?.(event.width) }, [
2123
+ /* @__PURE__ */ jsx(SplitColumn, { report, children: /* @__PURE__ */ jsx(NavigationStack, { children: sidebar }) }, "sidebar"),
2124
+ /* @__PURE__ */ jsx(SplitColumn, { report: null, children: detail }, "detail")
2125
+ ]),
2126
+ modifiers
2127
+ );
2244
2128
  }
2245
- function runBackground(value) {
2246
- if (typeof value === "string") {
2247
- const made = color(value);
2248
- return "hierarchical" in made ? void 0 : made;
2249
- }
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;
2129
+ function SplitColumn({ report, children }) {
2130
+ return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: report, children }) });
2252
2131
  }
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 });
2132
+ function CompactSplit({
2133
+ sidebar,
2134
+ detail,
2135
+ report,
2136
+ selected,
2137
+ clear
2138
+ }) {
2139
+ const [internal, setInternal] = useState([]);
2140
+ const detailStack = isValidElement(detail) && detail.type === NavigationStack ? detail.props : null;
2141
+ const [extra, setExtra] = detailStack?.path !== void 0 ? readBinding(detailStack.path, detailStack.onPathChange) : [internal, setInternal];
2142
+ const base = selected ? [{ view: detail }] : [];
2143
+ const path = [...base, ...extra];
2144
+ const setPath = (next) => {
2145
+ if (selected && next.length === 0) {
2146
+ setExtra([]);
2147
+ clear();
2280
2148
  return;
2281
2149
  }
2150
+ setExtra(next.slice(base.length));
2282
2151
  };
2283
- visit(children);
2284
- }
2285
- function isPlainStyle(style) {
2286
- return Object.keys(style).length === 0;
2152
+ 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
2153
  }
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;
2154
+ function useReportSplitSelection(split, selected, clear, active2 = true) {
2155
+ const key = useRef({}).current;
2156
+ useLayoutEffect(() => {
2157
+ if (active2) split?.report(key, selected, clear);
2158
+ else split?.retract(key);
2309
2159
  });
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);
2160
+ useLayoutEffect(() => () => split?.retract(key), [split, key]);
2161
+ return (nowSelected, nowClear) => {
2162
+ if (active2) split?.report(key, nowSelected, nowClear);
2163
+ };
2313
2164
  }
2314
- function placeholder(node, redacted) {
2315
- if (!redacted) return node;
2316
- return styled({ background: { hierarchical: 3 }, backgroundCornerRadius: 4 }, styled({ foreground: { name: "clear" } }, node));
2165
+ function OutsideSplit({ children }) {
2166
+ return /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children });
2317
2167
  }
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");
2168
+ function SheetPresenter({ isPresented, onDismiss, content, fullScreen, inherited }) {
2169
+ const around = useInheritedStyles();
2170
+ const [presented, setPresented] = readBinding(isPresented);
2171
+ if (!presented) return null;
2172
+ const dismiss = () => {
2173
+ setPresented(false);
2174
+ onDismiss?.();
2175
+ };
2176
+ const detents = fullScreen ? ["full"] : ["large"];
2177
+ return sb(
2178
+ HOIST_KIND,
2179
+ void 0,
2180
+ void 0,
2181
+ sb(
2182
+ "sheet",
2183
+ { detents },
2184
+ { tap: dismiss },
2185
+ replayStyles(
2186
+ inherited && inherited.length > 0 ? [...around, ...inherited] : around,
2187
+ /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(OutsideSplit, { children: /* @__PURE__ */ jsx(DismissContext.Provider, { value: dismiss, children: content }) }) })
2188
+ )
2189
+ ),
2190
+ "root"
2191
+ );
2325
2192
  }
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 };
2193
+ function presentationDetents(detents) {
2194
+ const encoded = detents.map(
2195
+ (detent) => typeof detent === "string" ? detent : "height" in detent ? `height:${detent.height}` : `fraction:${detent.fraction}`
2196
+ );
2197
+ return sb(PREF_KIND, { detents: encoded }, void 0, void 0, "sheet");
2345
2198
  }
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);
2199
+ function AlertPresenter({ title, isPresented, message, actions, style }) {
2200
+ const [presented, setPresented] = readBinding(isPresented);
2201
+ if (!presented) return null;
2202
+ const dismiss = () => setPresented(false);
2203
+ return sb(
2204
+ HOIST_KIND,
2205
+ void 0,
2206
+ void 0,
2207
+ sb(
2208
+ "alert",
2209
+ { title, message: message ?? null, style: style ?? "alert" },
2210
+ { tap: dismiss },
2211
+ withEnvironment({ dismissAlert: dismiss }, actions)
2212
+ ),
2213
+ "root"
2214
+ );
2355
2215
  }
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);
2216
+ function Lifecycle({ onAppear, onDisappear }) {
2217
+ useEffect(() => {
2218
+ onAppear?.();
2219
+ return () => onDisappear?.();
2220
+ }, []);
2221
+ return null;
2222
+ }
2223
+ function Inspector({ isPresented, content, columnWidth, children }) {
2224
+ const sizeClass = useHorizontalSizeClass();
2225
+ const [presented, setPresented] = readBinding(isPresented);
2226
+ if (sizeClass === "compact") {
2227
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
2228
+ children,
2229
+ /* @__PURE__ */ jsx(SheetPresenter, { isPresented: [presented, setPresented], content })
2230
+ ] });
2366
2231
  }
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
- );
2232
+ const outside = /* @__PURE__ */ jsx(OutsideSplit, { children: content });
2233
+ const panelContent = columnWidth !== void 0 ? sb("styled", { style: { inspectorColumnWidth: columnWidth } }, void 0, outside) : outside;
2234
+ return sb("inspector", { presented }, void 0, [
2235
+ /* @__PURE__ */ jsx(InspectorPart, { children }, "content"),
2236
+ /* @__PURE__ */ jsx(InspectorPart, { children: sb("inspectorpanel", {}, void 0, panelContent) }, "panel")
2237
+ ]);
2238
+ }
2239
+ function InspectorPart({ children }) {
2240
+ return children;
2241
+ }
2242
+ function Task({ id, action }) {
2243
+ const latest = useRef(action);
2244
+ latest.current = action;
2245
+ useEffect(() => {
2246
+ const controller = new AbortController();
2247
+ Promise.resolve().then(() => latest.current(controller.signal)).catch((error) => {
2248
+ if (!controller.signal.aborted) console.error(error);
2249
+ });
2250
+ return () => controller.abort();
2251
+ }, [id]);
2252
+ return null;
2253
+ }
2254
+ function OnChange({
2255
+ value,
2256
+ action,
2257
+ initial
2258
+ }) {
2259
+ const previous = useRef(null);
2260
+ const latest = useRef(action);
2261
+ latest.current = action;
2262
+ useEffect(() => {
2263
+ const last = previous.current;
2264
+ previous.current = { value };
2265
+ if (last === null) {
2266
+ if (initial) latest.current(value, value);
2267
+ return;
2268
+ }
2269
+ if (!Object.is(last.value, value)) latest.current(last.value, value);
2270
+ }, [value, initial]);
2271
+ return null;
2272
+ }
2273
+ const CoordinateSpacesContext = createContext({});
2274
+ function useCoordinateSpaces() {
2275
+ return useContext(CoordinateSpacesContext);
2375
2276
  }
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
- );
2277
+ function CoordinateSpace({ name, children }) {
2278
+ const spaces = useContext(CoordinateSpacesContext);
2279
+ const [frame2, setFrame] = useState(null);
2280
+ return /* @__PURE__ */ jsx(CoordinateSpacesContext.Provider, { value: { ...spaces, [name]: frame2 }, children: sb(
2281
+ "zstack",
2282
+ { alignment: "center", reportsSize: true, reportsFrame: true },
2283
+ {
2284
+ geometry: (event) => {
2285
+ const next = { x: event.x ?? 0, y: event.y ?? 0, width: event.width, height: event.height };
2286
+ if (!frame2 || frame2.x !== next.x || frame2.y !== next.y || frame2.width !== next.width || frame2.height !== next.height) setFrame(next);
2287
+ }
2288
+ },
2289
+ children
2290
+ ) });
2389
2291
  }
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);
2292
+ const NO_INSETS = { top: 0, leading: 0, bottom: 0, trailing: 0 };
2293
+ const NO_GEOMETRY = { size: { width: 0, height: 0 }, origin: null, safeArea: NO_INSETS, scrollView: null };
2294
+ function nextGeometry(event, reported) {
2295
+ const hasOrigin = event.x !== void 0 && event.y !== void 0;
2296
+ return {
2297
+ size: { width: event.width, height: event.height },
2298
+ origin: hasOrigin ? { x: event.x, y: event.y } : reported.origin,
2299
+ safeArea: event.safeArea ?? reported.safeArea,
2300
+ // A frame report without a scroll view means the reader is in none.
2301
+ scrollView: hasOrigin ? event.scrollView ?? null : reported.scrollView
2302
+ };
2394
2303
  }
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;
2304
+ function geometryProxy(reported, spaces, onReadFrame) {
2305
+ return {
2306
+ size: reported.size,
2307
+ frame: (space = "local") => {
2308
+ if (space === "local") return { x: 0, y: 0, ...reported.size };
2309
+ onReadFrame();
2310
+ const origin = reported.origin ?? { x: 0, y: 0 };
2311
+ if (space === "global") return { ...origin, ...reported.size };
2312
+ const named = spaces[space.named];
2313
+ return { x: origin.x - (named?.x ?? 0), y: origin.y - (named?.y ?? 0), ...reported.size };
2314
+ },
2315
+ bounds: () => {
2316
+ onReadFrame();
2317
+ const view = reported.scrollView;
2318
+ if (!view || !reported.origin) return null;
2319
+ return { x: view.x - reported.origin.x, y: view.y - reported.origin.y, width: view.width, height: view.height };
2320
+ },
2321
+ get safeAreaInsets() {
2322
+ onReadFrame();
2323
+ return reported.safeArea;
2413
2324
  }
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 });
2325
+ };
2422
2326
  }
2423
- function FlattenedStack(props) {
2327
+ function GeometryReader(props) {
2424
2328
  const { own, modifiers } = splitProps(props);
2329
+ const [reported, setReported] = useState(NO_GEOMETRY);
2330
+ const [needsFrame, setNeedsFrame] = useState(false);
2331
+ let readFrame = false;
2332
+ const proxy = geometryProxy(reported, useCoordinateSpaces(), () => {
2333
+ readFrame = true;
2334
+ });
2335
+ const content = own.children(proxy);
2336
+ useEffect(() => {
2337
+ if (readFrame && !needsFrame) setNeedsFrame(true);
2338
+ });
2425
2339
  return applyModifiers(
2426
- /* @__PURE__ */ jsxs(Fragment$1, { children: [
2427
- own.destination && /* @__PURE__ */ jsx(RegisterDestination, { destination: own.destination }),
2428
- own.children
2429
- ] }),
2340
+ sb(
2341
+ "geometry",
2342
+ needsFrame ? { reportsFrame: true } : {},
2343
+ {
2344
+ geometry: (event) => {
2345
+ const next = nextGeometry(event, reported);
2346
+ if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
2347
+ }
2348
+ },
2349
+ content
2350
+ ),
2430
2351
  modifiers
2431
2352
  );
2432
2353
  }
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;
2354
+ function GeometryChangeView({ handler, children }) {
2355
+ const [reported, setReported] = useState(NO_GEOMETRY);
2356
+ const [needsFrame, setNeedsFrame] = useState(false);
2357
+ const last = useRef(null);
2358
+ const spaces = useCoordinateSpaces();
2359
+ let readFrame = false;
2360
+ const proxy = geometryProxy(reported, spaces, () => {
2361
+ readFrame = true;
2362
+ });
2363
+ const heard = reported !== NO_GEOMETRY;
2364
+ const value = heard && typeof handler !== "function" ? handler.of(proxy) : void 0;
2365
+ useLayoutEffect(() => {
2366
+ if (readFrame && !needsFrame) setNeedsFrame(true);
2367
+ });
2368
+ useLayoutEffect(() => {
2369
+ if (!heard) return;
2370
+ if (typeof handler === "function") {
2371
+ handler(proxy);
2372
+ return;
2447
2373
  }
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
- );
2374
+ const previous = last.current;
2375
+ if (previous && JSON.stringify(previous.value) === JSON.stringify(value)) return;
2376
+ last.current = { value };
2377
+ handler.action(value, previous ? previous.value : value);
2463
2378
  });
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
2379
+ return sb(
2380
+ "zstack",
2381
+ { alignment: "center", reportsSize: true, ...needsFrame ? { reportsFrame: true } : {} },
2382
+ {
2383
+ geometry: (event) => {
2384
+ const next = nextGeometry(event, reported);
2385
+ if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
2386
+ }
2387
+ },
2388
+ children
2467
2389
  );
2468
2390
  }
2469
- function PathScreen({ value, destination }) {
2470
- if (isPushedView(value)) return value.view;
2471
- return destination.current ? destination.current(value) : null;
2391
+ const DEFAULT_VIEW_SPACING = { distance: () => 8 };
2392
+ function subviewTraits(children) {
2393
+ return flattenChildren(children).map((element) => {
2394
+ const props = element.props;
2395
+ return { priority: props.layoutPriority ?? 0, values: props.layoutValue ?? {} };
2396
+ });
2472
2397
  }
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;
2398
+ const fromDimension = (value) => value === "infinity" ? Infinity : value;
2399
+ const toDimension = (value) => Number.isFinite(value) ? Math.round(value * 100) / 100 : "infinity";
2400
+ const toSize = (size) => ({
2401
+ width: fromDimension(size.width) ?? 0,
2402
+ height: fromDimension(size.height) ?? 0
2403
+ });
2404
+ const GUIDES = ["leading", "horizontalCenter", "trailing", "top", "verticalCenter", "bottom", "firstTextBaseline", "lastTextBaseline"];
2405
+ function clamp(value, min, max) {
2406
+ return Math.min(Math.max(value, min), max);
2407
+ }
2408
+ function answerLayout(layout, record, traits = []) {
2409
+ const frames = record.children.map(() => null);
2410
+ const subviews = record.children.map((measurement, index) => {
2411
+ const min = toSize(measurement.min);
2412
+ const ideal = toSize(measurement.ideal);
2413
+ const max = toSize(measurement.max);
2414
+ const sizeThatFits = (proposal) => ({
2415
+ width: proposal.width === null ? ideal.width : clamp(proposal.width, min.width, max.width),
2416
+ height: proposal.height === null ? ideal.height : clamp(proposal.height, min.height, max.height)
2417
+ });
2418
+ return {
2419
+ min,
2420
+ ideal,
2421
+ max,
2422
+ priority: traits[index]?.priority ?? 0,
2423
+ values: traits[index]?.values ?? {},
2424
+ spacing: DEFAULT_VIEW_SPACING,
2425
+ sizeThatFits,
2426
+ place: ({ x, y, anchor, proposal }) => {
2427
+ const size2 = sizeThatFits(proposal ?? { width: null, height: null });
2428
+ const point = unitPoint(anchor ?? "topLeading");
2429
+ frames[index] = { x: x - point.x * size2.width, y: y - point.y * size2.height, ...size2 };
2430
+ }
2431
+ };
2432
+ });
2433
+ const cache = layout.makeCache ? layout.makeCache(subviews) : void 0;
2434
+ const measure = (proposal) => layout.sizeThatFits(proposal, subviews, cache);
2435
+ const size = measure(record.proposal);
2436
+ layout.placeSubviews({ x: 0, y: 0, ...size }, record.proposal, subviews, cache);
2437
+ const placed = frames.map((frame2, index) => {
2438
+ if (frame2) return frame2;
2439
+ const own = subviews[index].sizeThatFits({ width: size.width, height: size.height });
2440
+ return { x: (size.width - own.width) / 2, y: (size.height - own.height) / 2, ...own };
2441
+ });
2442
+ const guides = {};
2443
+ if (layout.explicitAlignment) {
2444
+ const bounds = { x: 0, y: 0, ...size };
2445
+ for (const guide of GUIDES) {
2446
+ const value = layout.explicitAlignment(guide, bounds, record.proposal, subviews, cache);
2447
+ if (value !== null && value !== void 0 && Number.isFinite(value)) guides[guide] = value;
2484
2448
  }
2485
- if (!navigation) return;
2486
- if (own.destination !== void 0) navigation.push({ view: own.destination });
2487
- else navigation.push(own.value);
2449
+ }
2450
+ const sizes = {
2451
+ min: measure({ width: 0, height: 0 }),
2452
+ ideal: measure({ width: null, height: null }),
2453
+ max: measure({ width: Infinity, height: Infinity })
2488
2454
  };
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 } : {} };
2455
+ const { width, height } = record.proposal;
2456
+ if (width !== null && Number.isFinite(width) && height !== null) sizes.atWidth = measure({ width, height: null });
2457
+ return { size, frames: placed, guides, sizes };
2498
2458
  }
2499
- function NavigationSplitView(props) {
2459
+ function Layout(props) {
2500
2460
  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();
2461
+ const [record, setRecord] = useState(null);
2462
+ let layoutProps = { proposal: null, size: null, frames: null };
2463
+ const guideModifiers = [];
2464
+ if (record) {
2465
+ const answer = answerLayout(own.layout, record, subviewTraits(own.children));
2466
+ for (const [guide, value] of Object.entries(answer.guides)) guideModifiers.push(["alignmentGuide", { guide, value: () => value }]);
2467
+ const encode = (size) => ({ width: toDimension(size.width), height: toDimension(size.height) });
2468
+ const round2 = (value) => Math.round(value * 100) / 100;
2469
+ layoutProps = {
2470
+ proposal: {
2471
+ width: record.proposal.width === null ? null : toDimension(record.proposal.width),
2472
+ height: record.proposal.height === null ? null : toDimension(record.proposal.height)
2510
2473
  },
2511
- retract: (key) => {
2512
- if (reporters.current.delete(key)) settle();
2474
+ size: { width: round2(answer.size.width), height: round2(answer.size.height) },
2475
+ frames: answer.frames.map((frame2) => ({ x: round2(frame2.x), y: round2(frame2.y), width: round2(frame2.width), height: round2(frame2.height) })),
2476
+ sizes: {
2477
+ min: encode(answer.sizes.min),
2478
+ ideal: encode(answer.sizes.ideal),
2479
+ max: encode(answer.sizes.max),
2480
+ ...answer.sizes.atWidth ? { atWidth: encode(answer.sizes.atWidth) } : {}
2513
2481
  }
2514
2482
  };
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
2483
  }
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
2484
  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
2485
  sb(
2620
- "alert",
2621
- { title, message: message ?? null, style: style ?? "alert" },
2622
- { tap: dismiss },
2623
- withEnvironment({ dismissAlert: dismiss }, actions)
2486
+ "layout",
2487
+ layoutProps,
2488
+ {
2489
+ layout: (event) => {
2490
+ const proposal = event.proposal;
2491
+ setRecord({
2492
+ proposal: { width: fromDimension(proposal.width), height: fromDimension(proposal.height) },
2493
+ children: event.children
2494
+ });
2495
+ }
2496
+ },
2497
+ own.children
2624
2498
  ),
2625
- "root"
2499
+ [...guideModifiers, ...modifiers]
2626
2500
  );
2627
2501
  }
2628
- function Lifecycle({ onAppear, onDisappear }) {
2629
- useEffect(() => {
2630
- onAppear?.();
2631
- return () => onDisappear?.();
2632
- }, []);
2633
- return null;
2502
+ const IDENTITY$1 = {
2503
+ offsetX: 0,
2504
+ offsetY: 0,
2505
+ scaleX: 1,
2506
+ scaleY: 1,
2507
+ rotation: 0,
2508
+ opacity: 1,
2509
+ blur: 0,
2510
+ brightness: 0,
2511
+ contrast: 1,
2512
+ saturation: 1,
2513
+ grayscale: 0,
2514
+ hueRotation: 0
2515
+ };
2516
+ class VisualEffect {
2517
+ /** @internal */
2518
+ constructor(values = IDENTITY$1) {
2519
+ this.values = values;
2520
+ }
2521
+ values;
2522
+ with(patch) {
2523
+ return new VisualEffect({ ...this.values, ...patch });
2524
+ }
2525
+ offset(offset) {
2526
+ return this.with({ offsetX: this.values.offsetX + (offset.x ?? 0), offsetY: this.values.offsetY + (offset.y ?? 0) });
2527
+ }
2528
+ scaleEffect(scale, anchor) {
2529
+ const x = typeof scale === "number" ? scale : scale.x ?? 1;
2530
+ const y = typeof scale === "number" ? scale : scale.y ?? 1;
2531
+ return this.with({ scaleX: this.values.scaleX * x, scaleY: this.values.scaleY * y, ...anchor !== void 0 ? { scaleAnchor: unitPoint(anchor) } : {} });
2532
+ }
2533
+ /** Degrees. */
2534
+ rotationEffect(degrees, anchor) {
2535
+ return this.with({ rotation: this.values.rotation + degrees, ...anchor !== void 0 ? { rotationAnchor: unitPoint(anchor) } : {} });
2536
+ }
2537
+ rotation3DEffect(spec) {
2538
+ const rotation = { angle: spec.angle, axis: spec.axis };
2539
+ if (spec.anchor !== void 0) rotation.anchor = unitPoint(spec.anchor);
2540
+ if (spec.perspective !== void 0) rotation.perspective = spec.perspective;
2541
+ return this.with({ rotation3D: rotation });
2542
+ }
2543
+ opacity(opacity2) {
2544
+ return this.with({ opacity: this.values.opacity * opacity2 });
2545
+ }
2546
+ blur(radius) {
2547
+ return this.with({ blur: this.values.blur + radius });
2548
+ }
2549
+ brightness(amount) {
2550
+ return this.with({ brightness: this.values.brightness + amount });
2551
+ }
2552
+ contrast(amount) {
2553
+ return this.with({ contrast: this.values.contrast * amount });
2554
+ }
2555
+ saturation(amount) {
2556
+ return this.with({ saturation: this.values.saturation * amount });
2557
+ }
2558
+ grayscale(amount) {
2559
+ return this.with({ grayscale: Math.min(1, this.values.grayscale + amount) });
2560
+ }
2561
+ /** Degrees. */
2562
+ hueRotation(degrees) {
2563
+ return this.with({ hueRotation: this.values.hueRotation + degrees });
2564
+ }
2634
2565
  }
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
- ] });
2566
+ const EMPTY_EFFECT = new VisualEffect();
2567
+ const round = (value) => Math.round(value * 1e3) / 1e3;
2568
+ function effectStyle(values) {
2569
+ const style = {};
2570
+ if (values.offsetX !== 0 || values.offsetY !== 0) style.offset = { x: round(values.offsetX), y: round(values.offsetY) };
2571
+ if (values.scaleX !== 1 || values.scaleY !== 1) {
2572
+ style.scale = { x: round(values.scaleX), y: round(values.scaleY) };
2573
+ if (values.scaleAnchor) style.scaleAnchor = values.scaleAnchor;
2574
+ }
2575
+ if (values.rotation !== 0) {
2576
+ style.rotation = round(values.rotation);
2577
+ if (values.rotationAnchor) style.rotationAnchor = values.rotationAnchor;
2643
2578
  }
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;
2579
+ if (values.rotation3D && values.rotation3D.angle !== 0) style.rotation3D = { ...values.rotation3D, angle: round(values.rotation3D.angle) };
2580
+ if (values.opacity !== 1) style.opacity = round(values.opacity);
2581
+ if (values.blur !== 0) style.blur = round(values.blur);
2582
+ if (values.brightness !== 0) style.brightness = round(values.brightness);
2583
+ if (values.contrast !== 1) style.contrast = round(values.contrast);
2584
+ if (values.saturation !== 1) style.saturation = round(values.saturation);
2585
+ if (values.grayscale !== 0) style.grayscale = round(values.grayscale);
2586
+ if (values.hueRotation !== 0) style.hueRotation = round(values.hueRotation);
2587
+ return style;
2653
2588
  }
2654
- function Task({ id, action }) {
2655
- const latest = useRef(action);
2656
- latest.current = action;
2589
+ function EffectBox({ render, children }) {
2590
+ const [reported, setReported] = useState(NO_GEOMETRY);
2591
+ const [needsFrame, setNeedsFrame] = useState(false);
2592
+ let readFrame = false;
2593
+ const style = render(reported, () => {
2594
+ readFrame = true;
2595
+ });
2657
2596
  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;
2597
+ if (readFrame && !needsFrame) setNeedsFrame(true);
2598
+ });
2599
+ const content = Object.keys(style).length > 0 ? sb("styled", { style }, void 0, children) : children;
2600
+ return sb(
2601
+ "zstack",
2602
+ { alignment: "center", reportsSize: true, ...needsFrame ? { reportsFrame: true } : {} },
2603
+ {
2604
+ geometry: (event) => {
2605
+ const next = nextGeometry(event, reported);
2606
+ if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
2607
+ }
2608
+ },
2609
+ content
2610
+ );
2665
2611
  }
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;
2612
+ function VisualEffectView({ effect, children }) {
2613
+ const spaces = useCoordinateSpaces();
2614
+ return /* @__PURE__ */ jsx(EffectBox, { render: (reported, markRead) => effectStyle(effect(EMPTY_EFFECT, geometryProxy(reported, spaces, markRead)).values), children });
2615
+ }
2616
+ const PHASES = {
2617
+ topLeading: { phase: "topLeading", value: -1, isIdentity: false },
2618
+ identity: { phase: "identity", value: 0, isIdentity: true },
2619
+ bottomTrailing: { phase: "bottomTrailing", value: 1, isIdentity: false }
2620
+ };
2621
+ function ScrollTransitionView({ spec, children }) {
2622
+ const phase = (which) => effectStyle(spec.transition(EMPTY_EFFECT, PHASES[which]).values);
2623
+ const transition = {
2624
+ configuration: spec.configuration ?? "interactive",
2625
+ identity: phase("identity"),
2626
+ topLeading: phase("topLeading"),
2627
+ bottomTrailing: phase("bottomTrailing")
2628
+ };
2629
+ if (spec.axis) transition.axis = spec.axis;
2630
+ return sb("styled", { style: { scrollTransition: transition } }, void 0, children);
2684
2631
  }
2685
2632
  function Hover({ action, children }) {
2686
2633
  return sb("styled", { style: { hover: true } }, { hover: (event) => action(event.value) }, children);
@@ -3644,345 +3591,404 @@ function TextInputHint({ hint, children }) {
3644
3591
  const outer = useViewEnvironment().textInputHints;
3645
3592
  return withEnvironment({ textInputHints: { ...outer, ...hint } }, children);
3646
3593
  }
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
- };
3594
+ function HostElement(props) {
3595
+ const { own, modifiers } = splitProps(props);
3596
+ return applyModifiers(sb(own.kind, own.props ? { ...own.props } : void 0, void 0, own.children), modifiers);
3658
3597
  }
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
- };
3598
+ let installed = null;
3599
+ function installTextMeasurer(measurer, remove = false) {
3600
+ if (remove) {
3601
+ if (installed === measurer) installed = null;
3602
+ return;
3603
+ }
3604
+ installed = measurer;
3681
3605
  }
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
- );
3606
+ function canMeasureText() {
3607
+ return installed !== null;
3708
3608
  }
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
- }
3609
+ function measureText(text, font$1 = "body", width) {
3610
+ if (!installed) throw new Error("measureText: nothing measures text here yet (mount the app, or run it in a SwiftBrowser Native host)");
3611
+ const constrained = typeof width === "number" && Number.isFinite(width) && width > 0 ? width : null;
3612
+ return installed(text, font(font$1), constrained);
3613
+ }
3614
+ function decidePlatform(width, media = matchesMedia) {
3615
+ return horizontalSizeClassFor(width) === "regular" && media("(hover: hover) and (pointer: fine)") ? "macos" : "ios";
3616
+ }
3617
+ function matchesMedia(query) {
3618
+ return typeof matchMedia === "function" && matchMedia(query).matches;
3619
+ }
3620
+ function mount(screen, element, options = {}) {
3621
+ let renderer = null;
3622
+ const root = createSwiftUIRoot((ops) => renderer?.applyOps(ops));
3623
+ renderer = new Renderer(screen, (event) => root.dispatch(event));
3624
+ renderer.fileDrops = "reference";
3625
+ const measurer = (text, font2, width2) => renderer.measureText(text, font2, width2);
3626
+ installTextMeasurer(measurer);
3627
+ let pageScheme = options.colorScheme ?? "light";
3628
+ let preferred = null;
3629
+ const width = screen.clientWidth || 393;
3630
+ const environment = {
3631
+ colorScheme: pageScheme,
3632
+ dynamicTypeSize: options.dynamicTypeSize ?? "large",
3633
+ horizontalSizeClass: horizontalSizeClassFor(width),
3634
+ scenePhase: typeof document !== "undefined" ? currentScenePhase() : "active",
3635
+ platform: options.platform === void 0 || options.platform === "auto" ? decidePlatform(width) : options.platform
3636
+ };
3637
+ renderer.contentHeight = options.contentHeight ?? false;
3638
+ if (options.sidebarTitleBar === false) renderer.sidebarTitleBar = false;
3639
+ if (options.assets) renderer.setAssetCatalog(options.assets);
3640
+ if (options.app) browserEnvironment.files?.setApp?.(options.app);
3641
+ renderer.setEnvironment({ colorScheme: environment.colorScheme, dynamicTypeSize: environment.dynamicTypeSize, platform: environment.platform });
3642
+ if (options.safeArea !== void 0) renderer.setSafeArea(options.safeArea);
3643
+ root.setEnvironment(environment);
3644
+ const browserHistory = (options.history ?? true) && typeof window !== "undefined" ? new BrowserHistory(renderer, screen) : null;
3645
+ browserHistory?.enable();
3646
+ const stopKeyboard = options.keyboard && typeof window !== "undefined" ? watchKeyboard((height) => renderer?.setKeyboard(height), { root: screen }) : null;
3647
+ const applyScheme = () => {
3648
+ const scheme = preferred ?? pageScheme;
3649
+ const device = screen.closest(".device");
3650
+ if (device) device.dataset["theme"] = scheme;
3651
+ renderer?.setEnvironment({ colorScheme: scheme });
3652
+ root.setEnvironment({ colorScheme: scheme });
3653
+ };
3654
+ const onPreferredScheme = (event) => {
3655
+ const scheme = event.detail.colorScheme;
3656
+ preferred = scheme;
3657
+ queueMicrotask(applyScheme);
3658
+ };
3659
+ screen.addEventListener("sb:color-scheme", onPreferredScheme);
3660
+ let sizeClass = environment.horizontalSizeClass;
3661
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
3662
+ const next = horizontalSizeClassFor(screen.clientWidth);
3663
+ if (next === sizeClass) return;
3664
+ sizeClass = next;
3665
+ root.setEnvironment({ horizontalSizeClass: next });
3666
+ }) : null;
3667
+ observer?.observe(screen);
3668
+ const stopScenePhase = typeof document !== "undefined" ? watchScenePhase((scenePhase) => root.setEnvironment({ scenePhase })) : null;
3669
+ root.render(element);
3670
+ const mounted = {
3671
+ root,
3672
+ renderer,
3673
+ setEnvironment(next) {
3674
+ if (next.colorScheme !== void 0) pageScheme = next.colorScheme;
3675
+ const scheme = preferred ?? pageScheme;
3676
+ renderer?.setEnvironment({ ...next, colorScheme: scheme });
3677
+ root.setEnvironment({ ...next, colorScheme: scheme });
3742
3678
  },
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;
3679
+ setSafeArea(insets2) {
3680
+ renderer?.setSafeArea(insets2);
3681
+ },
3682
+ viewportChanged() {
3683
+ renderer?.viewportChanged();
3684
+ },
3685
+ unmount() {
3686
+ observer?.disconnect();
3687
+ stopScenePhase?.();
3688
+ stopKeyboard?.();
3689
+ browserHistory?.dispose();
3690
+ screen.removeEventListener("sb:color-scheme", onPreferredScheme);
3691
+ installTextMeasurer(measurer, true);
3692
+ root.unmount();
3693
+ renderer?.dispose();
3803
3694
  }
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
3695
  };
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
- );
3696
+ return mounted;
3856
3697
  }
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
3698
+ const EMPTY = { canUndo: false, canRedo: false, undoActionName: "", redoActionName: "" };
3699
+ const defaultSchedule = (close) => {
3700
+ if (typeof queueMicrotask === "function") queueMicrotask(close);
3701
+ else void Promise.resolve().then(close);
3870
3702
  };
3871
- class VisualEffect {
3872
- /** @internal */
3873
- constructor(values = IDENTITY$1) {
3874
- this.values = values;
3703
+ class UndoManager {
3704
+ /** Whether registrations in one event (one task of the event loop) form one group; true, as `groupsByEvent`. */
3705
+ groupsByEvent = true;
3706
+ /** How many groups the undo stack keeps; 0 for no limit, as `levelsOfUndo`. */
3707
+ levelsOfUndo = 0;
3708
+ undoStack = [];
3709
+ redoStack = [];
3710
+ /** The group registrations go in: an explicit group, the event's group, or the inverse of what is being undone or redone. */
3711
+ open = null;
3712
+ /** How deep `beginGrouping` is; 0 when `open` is the event's group or an inverse. */
3713
+ depth = 0;
3714
+ /** Whether `open` is the event's group, which `schedule` closes. */
3715
+ byEvent = false;
3716
+ /** The last keyed group closed onto the undo stack, which a registration with its key joins. */
3717
+ coalescing = null;
3718
+ undoing = false;
3719
+ redoing = false;
3720
+ snapshot = EMPTY;
3721
+ listeners = /* @__PURE__ */ new Set();
3722
+ schedule;
3723
+ host;
3724
+ stopListening = null;
3725
+ constructor(options = {}) {
3726
+ this.schedule = options.schedule ?? defaultSchedule;
3727
+ this.host = options.host === void 0 ? browserEnvironment.undoManager ?? null : options.host;
3728
+ if (this.host) {
3729
+ this.stopListening = this.host.listen({ undo: () => this.undo(), redo: () => this.redo() });
3730
+ this.host.update(this.snapshot);
3731
+ }
3875
3732
  }
3876
- values;
3877
- with(patch) {
3878
- return new VisualEffect({ ...this.values, ...patch });
3733
+ get canUndo() {
3734
+ return this.undoStack.length > 0;
3879
3735
  }
3880
- offset(offset) {
3881
- return this.with({ offsetX: this.values.offsetX + (offset.x ?? 0), offsetY: this.values.offsetY + (offset.y ?? 0) });
3736
+ get canRedo() {
3737
+ return this.redoStack.length > 0;
3882
3738
  }
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) } : {} });
3739
+ /** The name of what `undo` takes back ('' for none): "Move" for an Edit menu's "Undo Move". */
3740
+ get undoActionName() {
3741
+ return this.undoStack[this.undoStack.length - 1]?.name ?? "";
3887
3742
  }
3888
- /** Degrees. */
3889
- rotationEffect(degrees, anchor) {
3890
- return this.with({ rotation: this.values.rotation + degrees, ...anchor !== void 0 ? { rotationAnchor: unitPoint(anchor) } : {} });
3743
+ get redoActionName() {
3744
+ return this.redoStack[this.redoStack.length - 1]?.name ?? "";
3745
+ }
3746
+ get isUndoing() {
3747
+ return this.undoing;
3748
+ }
3749
+ get isRedoing() {
3750
+ return this.redoing;
3751
+ }
3752
+ /** What the stacks can do, one object per change (for `useSyncExternalStore`). */
3753
+ get state() {
3754
+ return this.snapshot;
3755
+ }
3756
+ /**
3757
+ * Registers what undoes the change just made, under `name`. Run by `undo`,
3758
+ * the closure registers its own inverse the same way, which lands on the
3759
+ * redo stack because the manager is undoing.
3760
+ */
3761
+ registerUndo(name, undo, options = {}) {
3762
+ const action = { name, undo, target: options.target };
3763
+ if (this.undoing || this.redoing) {
3764
+ this.open.actions.push(action);
3765
+ this.changed();
3766
+ return;
3767
+ }
3768
+ this.redoStack = [];
3769
+ this.coalescing = this.coalescing && this.undoStack[this.undoStack.length - 1] === this.coalescing ? this.coalescing : null;
3770
+ if (this.open) {
3771
+ if (!this.undoStack.includes(this.open)) this.push(this.open);
3772
+ this.open.actions.push(action);
3773
+ if (!this.open.named) this.open.name = name;
3774
+ } else if (this.coalescing && options.key !== void 0 && this.coalescing.key === options.key) {
3775
+ this.coalescing.actions.push(action);
3776
+ this.coalescing.name = name;
3777
+ } else {
3778
+ const group = { name, named: false, actions: [action], key: options.key };
3779
+ this.push(group);
3780
+ this.coalescing = null;
3781
+ if (this.groupsByEvent) {
3782
+ this.open = group;
3783
+ this.byEvent = true;
3784
+ this.schedule(() => {
3785
+ if (this.open === group && this.byEvent) this.close();
3786
+ });
3787
+ } else {
3788
+ this.coalescing = group.key !== void 0 ? group : null;
3789
+ }
3790
+ }
3791
+ this.changed();
3792
+ }
3793
+ /** Takes back the top group of changes; its inverses become the top of the redo stack. */
3794
+ undo() {
3795
+ this.endOpenGroups();
3796
+ const group = this.undoStack.pop();
3797
+ if (!group) return;
3798
+ this.coalescing = null;
3799
+ this.run(group, "undo");
3800
+ }
3801
+ redo() {
3802
+ this.endOpenGroups();
3803
+ const group = this.redoStack.pop();
3804
+ if (!group) return;
3805
+ this.run(group, "redo");
3891
3806
  }
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 });
3807
+ /**
3808
+ * Starts a group: every registration until `endGrouping` undoes as one
3809
+ * (`beginUndoGrouping`). Nested calls join the outer group. The group is
3810
+ * named `name`, else after its last registration.
3811
+ */
3812
+ beginGrouping(name) {
3813
+ if (this.undoing || this.redoing) return;
3814
+ this.depth += 1;
3815
+ if (this.depth > 1) return;
3816
+ if (this.open) this.close();
3817
+ this.open = { name: name ?? "", named: name !== void 0, actions: [], key: void 0 };
3818
+ this.byEvent = false;
3819
+ this.coalescing = null;
3897
3820
  }
3898
- opacity(opacity2) {
3899
- return this.with({ opacity: this.values.opacity * opacity2 });
3821
+ /** Ends the group `beginGrouping` started; one that registered nothing is dropped. */
3822
+ endGrouping() {
3823
+ if (this.depth === 0) return;
3824
+ this.depth -= 1;
3825
+ if (this.depth === 0) this.close();
3900
3826
  }
3901
- blur(radius) {
3902
- return this.with({ blur: this.values.blur + radius });
3827
+ /** `body` as one group named `name`. */
3828
+ group(name, body) {
3829
+ this.beginGrouping(name);
3830
+ try {
3831
+ return body();
3832
+ } finally {
3833
+ this.endGrouping();
3834
+ }
3903
3835
  }
3904
- brightness(amount) {
3905
- return this.with({ brightness: this.values.brightness + amount });
3836
+ /** Forgets every registration, or those `target` made (`removeAllActions(withTarget:)`), on both stacks and in the open group. */
3837
+ removeAllActions(target) {
3838
+ const mine = (action) => target === void 0 || action.target === target;
3839
+ const strip = (groups) => groups.filter((group) => {
3840
+ group.actions = group.actions.filter((action) => !mine(action));
3841
+ return group.actions.length > 0;
3842
+ });
3843
+ this.undoStack = strip(this.undoStack);
3844
+ this.redoStack = strip(this.redoStack);
3845
+ if (this.open) this.open.actions = this.open.actions.filter((action) => !mine(action));
3846
+ if (this.coalescing && !this.undoStack.includes(this.coalescing)) this.coalescing = null;
3847
+ this.changed();
3906
3848
  }
3907
- contrast(amount) {
3908
- return this.with({ contrast: this.values.contrast * amount });
3849
+ /** Calls `listener` after each change to `state`; returns what stops it. */
3850
+ subscribe = (listener) => {
3851
+ this.listeners.add(listener);
3852
+ return () => {
3853
+ this.listeners.delete(listener);
3854
+ };
3855
+ };
3856
+ /** Stops listening to the host, for a manager made in a test. */
3857
+ dispose() {
3858
+ this.stopListening?.();
3859
+ this.stopListening = null;
3909
3860
  }
3910
- saturation(amount) {
3911
- return this.with({ saturation: this.values.saturation * amount });
3861
+ push(group) {
3862
+ this.undoStack.push(group);
3863
+ if (this.levelsOfUndo > 0 && this.undoStack.length > this.levelsOfUndo) this.undoStack.splice(0, this.undoStack.length - this.levelsOfUndo);
3912
3864
  }
3913
- grayscale(amount) {
3914
- return this.with({ grayscale: Math.min(1, this.values.grayscale + amount) });
3865
+ /** Closes `open`; a keyed group on top stays open to its key. */
3866
+ close() {
3867
+ const group = this.open;
3868
+ this.open = null;
3869
+ this.byEvent = false;
3870
+ if (group && group.key !== void 0 && this.undoStack[this.undoStack.length - 1] === group) this.coalescing = group;
3915
3871
  }
3916
- /** Degrees. */
3917
- hueRotation(degrees) {
3918
- return this.with({ hueRotation: this.values.hueRotation + degrees });
3872
+ endOpenGroups() {
3873
+ this.depth = 0;
3874
+ if (this.open) this.close();
3919
3875
  }
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;
3876
+ run(group, direction) {
3877
+ const inverse = { name: group.name, named: true, actions: [], key: void 0 };
3878
+ this.open = inverse;
3879
+ this.byEvent = false;
3880
+ if (direction === "undo") this.undoing = true;
3881
+ else this.redoing = true;
3882
+ try {
3883
+ for (let i = group.actions.length - 1; i >= 0; i -= 1) group.actions[i].undo();
3884
+ } finally {
3885
+ this.undoing = false;
3886
+ this.redoing = false;
3887
+ this.open = null;
3888
+ if (inverse.actions.length > 0) {
3889
+ if (direction === "undo") this.redoStack.push(inverse);
3890
+ else this.push(inverse);
3891
+ }
3892
+ this.changed();
3893
+ }
3929
3894
  }
3930
- if (values.rotation !== 0) {
3931
- style.rotation = round(values.rotation);
3932
- if (values.rotationAnchor) style.rotationAnchor = values.rotationAnchor;
3895
+ changed() {
3896
+ const next = { canUndo: this.canUndo, canRedo: this.canRedo, undoActionName: this.undoActionName, redoActionName: this.redoActionName };
3897
+ const last = this.snapshot;
3898
+ if (next.canUndo === last.canUndo && next.canRedo === last.canRedo && next.undoActionName === last.undoActionName && next.redoActionName === last.redoActionName) return;
3899
+ this.snapshot = next;
3900
+ this.host?.update(next);
3901
+ for (const listener of [...this.listeners]) listener();
3933
3902
  }
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
3903
  }
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
3904
+ const UndoManagerContext = createContext(null);
3905
+ let shared = null;
3906
+ function sharedUndoManager() {
3907
+ shared ??= new UndoManager();
3908
+ return shared;
3909
+ }
3910
+ function useUndoManager() {
3911
+ const manager = useContext(UndoManagerContext) ?? sharedUndoManager();
3912
+ useSyncExternalStore(
3913
+ manager.subscribe,
3914
+ () => manager.state,
3915
+ () => manager.state
3965
3916
  );
3917
+ return manager;
3966
3918
  }
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 });
3919
+ const OWN = [
3920
+ EnvironmentContext,
3921
+ ViewEnvironmentContext,
3922
+ NavigationContext,
3923
+ DismissContext,
3924
+ UndoManagerContext
3925
+ ];
3926
+ function useContextBridge(contexts = [], options = {}) {
3927
+ const own = options.environment === false ? OWN.filter((context) => context !== EnvironmentContext) : OWN;
3928
+ const all = [...own, ...contexts];
3929
+ const values = all.map((context) => useContext(context));
3930
+ return (children) => all.reduceRight((inner, context, index) => createElement(context.Provider, { value: values[index] }, inner), children);
3970
3931
  }
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);
3932
+ const WINDOW_TOP_INSET = 10;
3933
+ function insetsOf(safeArea, fit) {
3934
+ const given = typeof safeArea === "number" ? { top: safeArea } : safeArea ?? { top: fit === "fill" ? WINDOW_TOP_INSET : 0 };
3935
+ return { top: given.top ?? 0, leading: given.leading ?? 0, bottom: given.bottom ?? 0, trailing: given.trailing ?? 0 };
3936
+ }
3937
+ function SwiftUIView({
3938
+ children,
3939
+ fit = "fill",
3940
+ colorScheme = "light",
3941
+ dynamicTypeSize = "large",
3942
+ contexts,
3943
+ assets,
3944
+ safeArea,
3945
+ history = true,
3946
+ keyboard = fit === "fill",
3947
+ sidebarTitleBar = true,
3948
+ className,
3949
+ style
3950
+ }) {
3951
+ const device = useRef(null);
3952
+ const screen = useRef(null);
3953
+ const mounted = useRef(null);
3954
+ const bridge = useContextBridge(contexts, { environment: false });
3955
+ const tree = bridge(children);
3956
+ const latest = useRef(tree);
3957
+ latest.current = tree;
3958
+ useLayoutEffect(() => {
3959
+ if (!screen.current) return;
3960
+ const instance = mount(screen.current, latest.current, {
3961
+ colorScheme,
3962
+ dynamicTypeSize,
3963
+ safeArea: insetsOf(safeArea, fit),
3964
+ contentHeight: fit === "content",
3965
+ history,
3966
+ keyboard,
3967
+ ...assets ? { assets } : {}
3968
+ });
3969
+ mounted.current = instance;
3970
+ return () => {
3971
+ mounted.current = null;
3972
+ instance.unmount();
3973
+ };
3974
+ }, [fit, history, keyboard]);
3975
+ useLayoutEffect(() => {
3976
+ if (mounted.current) mounted.current.renderer.sidebarTitleBar = sidebarTitleBar;
3977
+ }, [sidebarTitleBar, fit, history, keyboard]);
3978
+ useLayoutEffect(() => {
3979
+ mounted.current?.root.render(tree);
3980
+ });
3981
+ const insets2 = insetsOf(safeArea, fit);
3982
+ const insetsKey = `${insets2.top},${insets2.leading},${insets2.bottom},${insets2.trailing}`;
3983
+ useLayoutEffect(() => {
3984
+ mounted.current?.renderer.setSafeArea(insets2);
3985
+ }, [insetsKey]);
3986
+ useLayoutEffect(() => {
3987
+ if (device.current) applyTypeSizeVariables(device.current, dynamicTypeSize, mounted.current?.renderer.platform);
3988
+ mounted.current?.setEnvironment({ colorScheme, dynamicTypeSize });
3989
+ }, [colorScheme, dynamicTypeSize]);
3990
+ const classes = ["device", "sb-island", className].filter(Boolean).join(" ");
3991
+ 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
3992
  }
3987
3993
  const r = (n) => {
3988
3994
  const v = Math.round(n * 100) / 100;
@@ -8209,6 +8215,7 @@ export {
8209
8215
  GroupBox,
8210
8216
  HStack,
8211
8217
  Host,
8218
+ HostElement,
8212
8219
  Image$1 as Image,
8213
8220
  KeyframeAnimator,
8214
8221
  LOADING_INDICATOR_DELAY_MS,