@swiftbrowser/react 0.17.7 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +243 -14
  2. package/dist/appStorage.d.ts +43 -0
  3. package/dist/appStorage.d.ts.map +1 -0
  4. package/dist/attributedText.d.ts +76 -0
  5. package/dist/attributedText.d.ts.map +1 -0
  6. package/dist/audioRecorder.d.ts +52 -0
  7. package/dist/audioRecorder.d.ts.map +1 -0
  8. package/dist/bridge.d.ts.map +1 -1
  9. package/dist/components/charts.d.ts +5 -0
  10. package/dist/components/charts.d.ts.map +1 -1
  11. package/dist/components/containers.d.ts +5 -0
  12. package/dist/components/containers.d.ts.map +1 -1
  13. package/dist/components/controls.d.ts +84 -8
  14. package/dist/components/controls.d.ts.map +1 -1
  15. package/dist/components/geometry.d.ts +18 -0
  16. package/dist/components/geometry.d.ts.map +1 -1
  17. package/dist/components/images.d.ts.map +1 -1
  18. package/dist/components/lists.d.ts.map +1 -1
  19. package/dist/components/media.d.ts +18 -10
  20. package/dist/components/media.d.ts.map +1 -1
  21. package/dist/components/stacks.d.ts +70 -4
  22. package/dist/components/stacks.d.ts.map +1 -1
  23. package/dist/environment.d.ts +2 -1
  24. package/dist/environment.d.ts.map +1 -1
  25. package/dist/fileManager.d.ts +58 -0
  26. package/dist/fileManager.d.ts.map +1 -0
  27. package/dist/host/root.d.ts +16 -2
  28. package/dist/host/root.d.ts.map +1 -1
  29. package/dist/host/tree.d.ts +9 -0
  30. package/dist/host/tree.d.ts.map +1 -1
  31. package/dist/index.d.ts +9 -3
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1200 -369
  34. package/dist/index.js.map +1 -1
  35. package/dist/measureText.d.ts +32 -0
  36. package/dist/measureText.d.ts.map +1 -0
  37. package/dist/modifiers.d.ts +81 -5
  38. package/dist/modifiers.d.ts.map +1 -1
  39. package/dist/mount.d.ts +5 -0
  40. package/dist/mount.d.ts.map +1 -1
  41. package/dist/presentation.d.ts +5 -1
  42. package/dist/presentation.d.ts.map +1 -1
  43. package/dist/scrollReader.d.ts +34 -8
  44. package/dist/scrollReader.d.ts.map +1 -1
  45. package/dist/undo.d.ts +108 -0
  46. package/dist/undo.d.ts.map +1 -0
  47. package/dist/visualEffect.d.ts +10 -14
  48. package/dist/visualEffect.d.ts.map +1 -1
  49. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
- import { createContext, useContext, createElement, useRef, useLayoutEffect, useState, Children, useEffect, isValidElement, Fragment, useId, cloneElement, useSyncExternalStore } from "react";
1
+ import { createContext, useContext, createElement, useSyncExternalStore, useRef, useLayoutEffect, useState, Children, useEffect, isValidElement, Fragment, useId, cloneElement, useCallback } from "react";
2
2
  import { DefaultEventPriority, ConcurrentRoot } from "react-reconciler/constants";
3
3
  import Reconciler from "react-reconciler";
4
4
  import { horizontalSizeClassFor } from "@swiftbrowser/web/src/protocol";
5
5
  import { BrowserHistory } from "@swiftbrowser/web/src/browserHistory";
6
6
  import { watchKeyboard } from "@swiftbrowser/web/src/deviceMode";
7
+ import { browserEnvironment, loadableURL, performHostRequest } from "@swiftbrowser/web/src/hostRequests";
7
8
  import { Renderer } from "@swiftbrowser/web/src/renderer";
8
9
  import { jsx, jsxs, Fragment as Fragment$1 } from "react/jsx-runtime";
9
10
  import { applyTypeSizeVariables } from "@swiftbrowser/web/src/typography";
10
- import { browserEnvironment, performHostRequest } from "@swiftbrowser/web/src/hostRequests";
11
+ import { fileURL } from "@swiftbrowser/web/src/files";
11
12
  import { registerHostContent, notifyHostContentChange, unregisterHostContent } from "@swiftbrowser/web/src/hostContent";
12
13
  import { createRoot } from "react-dom/client";
13
14
  import { flushSync } from "react-dom";
@@ -211,6 +212,9 @@ class HostTree {
211
212
  retired = [];
212
213
  /** `withAnimation`: stamped on the next `commit`. */
213
214
  pendingAnimation;
215
+ /** `withAnimation(_:completionCriteria:_:completion:)`: stamped on the next `commit` with its serial; the callback waits in `completions`. */
216
+ pendingCompletion;
217
+ completions = /* @__PURE__ */ new Map();
214
218
  /** The root's `colorScheme` last sent (`.preferredColorScheme`); undefined while none was. */
215
219
  committedColorScheme;
216
220
  createInstance(props) {
@@ -253,6 +257,8 @@ class HostTree {
253
257
  flush() {
254
258
  const animation = this.pendingAnimation;
255
259
  this.pendingAnimation = void 0;
260
+ const completion = this.pendingCompletion;
261
+ this.pendingCompletion = void 0;
256
262
  const effective = /* @__PURE__ */ new Map();
257
263
  const rootEffective = { props: {}, children: [], hoisted: [] };
258
264
  effective.set(this.root, rootEffective);
@@ -340,15 +346,32 @@ class HostTree {
340
346
  this.committedColorScheme = next === null ? void 0 : next;
341
347
  }
342
348
  if (ops.length === 0) return ops;
343
- ops.push(animation ? { op: "commit", animation } : { op: "commit" });
349
+ const commit = { op: "commit" };
350
+ if (animation) commit.animation = animation;
351
+ if (completion) {
352
+ commit.completion = completion.serial;
353
+ if (completion.criteria === "removed") commit.completionCriteria = "removed";
354
+ this.completions.set(completion.serial, completion.callback);
355
+ }
356
+ ops.push(commit);
344
357
  return ops;
345
358
  }
359
+ /** Whether a pass is still animating with this completion serial. */
360
+ awaitsCompletion(serial) {
361
+ return this.completions.has(serial);
362
+ }
346
363
  /** Hand a renderer event to the element's handler for its type. */
347
364
  dispatch(event) {
348
365
  if (event.type === "batch") {
349
366
  for (const each of event.events) this.dispatch(each);
350
367
  return;
351
368
  }
369
+ if (event.type === "animationcomplete") {
370
+ const callback = this.completions.get(event.serial);
371
+ this.completions.delete(event.serial);
372
+ callback?.();
373
+ return;
374
+ }
352
375
  if (!("id" in event)) return;
353
376
  const instance = this.live.get(event.id);
354
377
  const handler = instance?.on?.[event.type];
@@ -418,20 +441,207 @@ function createSwiftUIRoot(sink) {
418
441
  function injectIntoDevTools() {
419
442
  return reconciler.injectIntoDevTools() === true;
420
443
  }
421
- function withAnimation(first, second) {
444
+ let nextCompletionSerial = 1;
445
+ function withAnimation(first, second, third, fourth) {
422
446
  const animation = typeof first === "function" ? { type: "default" } : first ?? { type: "default" };
423
- const body = typeof first === "function" ? first : second;
424
- for (const tree of roots) tree.pendingAnimation = animation;
447
+ let body;
448
+ let completion;
449
+ let criteria = "logicallyComplete";
450
+ if (typeof first === "function") {
451
+ body = first;
452
+ } else if (typeof second === "function") {
453
+ body = second;
454
+ completion = third;
455
+ } else {
456
+ criteria = second?.completionCriteria ?? "logicallyComplete";
457
+ body = third;
458
+ completion = fourth;
459
+ }
460
+ const serial = completion ? nextCompletionSerial++ : 0;
461
+ for (const tree of roots) {
462
+ tree.pendingAnimation = animation;
463
+ if (completion) tree.pendingCompletion = { serial, criteria, callback: completion };
464
+ }
425
465
  try {
426
466
  return reconciler.flushSyncFromReconciler(body);
427
467
  } finally {
428
- for (const tree of roots) tree.pendingAnimation = void 0;
468
+ let committed = false;
469
+ for (const tree of roots) {
470
+ tree.pendingAnimation = void 0;
471
+ tree.pendingCompletion = void 0;
472
+ if (completion && tree.awaitsCompletion(serial)) committed = true;
473
+ }
474
+ if (completion && !committed) completion();
475
+ }
476
+ }
477
+ const HIERARCHY = { primary: 1, secondary: 2, tertiary: 3, quaternary: 4 };
478
+ function rgb(r2, g, b, a = 1) {
479
+ return { r: r2, g, b, a };
480
+ }
481
+ function hex(value) {
482
+ const digits = value.replace("#", "");
483
+ const wide = digits.length === 3 || digits.length === 4 ? [...digits].map((d) => d + d).join("") : digits;
484
+ const n = (at) => parseInt(wide.slice(at, at + 2), 16) / 255;
485
+ return { r: n(0), g: n(2), b: n(4), a: wide.length >= 8 ? n(6) : 1 };
486
+ }
487
+ function shapeStyle(value) {
488
+ if (typeof value !== "string") return value;
489
+ if (value in HIERARCHY) return { hierarchical: HIERARCHY[value] };
490
+ if (value === "white") return rgb(1, 1, 1);
491
+ if (value === "black") return rgb(0, 0, 0);
492
+ if (value.startsWith("#")) return hex(value);
493
+ return { name: value };
494
+ }
495
+ function color(value) {
496
+ return shapeStyle(value);
497
+ }
498
+ function opacity(value, a) {
499
+ const style = shapeStyle(value);
500
+ if ("r" in style) return { ...style, a: style.a * a };
501
+ if ("name" in style) return { ...style, a: (style.a ?? 1) * a };
502
+ if ("named" in style) return { ...style, opacity: style.opacity * a };
503
+ return { ...style, opacity: (style.opacity ?? 1) * a };
504
+ }
505
+ function gradient(value) {
506
+ return { colorGradient: color(value) };
507
+ }
508
+ const ColorValues = {
509
+ red: { name: "red" },
510
+ orange: { name: "orange" },
511
+ yellow: { name: "yellow" },
512
+ green: { name: "green" },
513
+ mint: { name: "mint" },
514
+ teal: { name: "teal" },
515
+ cyan: { name: "cyan" },
516
+ blue: { name: "blue" },
517
+ indigo: { name: "indigo" },
518
+ purple: { name: "purple" },
519
+ pink: { name: "pink" },
520
+ brown: { name: "brown" },
521
+ gray: { name: "gray" },
522
+ white: rgb(1, 1, 1),
523
+ black: rgb(0, 0, 0),
524
+ clear: { name: "clear" },
525
+ primary: { name: "primary" },
526
+ secondary: { name: "secondary" },
527
+ accent: { name: "accent" },
528
+ /** `Color(uiColor:)` / `Color(.name)`: a UIKit semantic color by its iOS name. */
529
+ system: (name) => ({ name }),
530
+ rgb,
531
+ hex,
532
+ opacity,
533
+ gradient
534
+ };
535
+ const UNIT_POINTS = {
536
+ center: { x: 0.5, y: 0.5 },
537
+ top: { x: 0.5, y: 0 },
538
+ bottom: { x: 0.5, y: 1 },
539
+ leading: { x: 0, y: 0.5 },
540
+ trailing: { x: 1, y: 0.5 },
541
+ topLeading: { x: 0, y: 0 },
542
+ topTrailing: { x: 1, y: 0 },
543
+ bottomLeading: { x: 0, y: 1 },
544
+ bottomTrailing: { x: 1, y: 1 }
545
+ };
546
+ function unitPoint(value) {
547
+ return typeof value === "string" ? UNIT_POINTS[value] : value;
548
+ }
549
+ function stops$1(spec) {
550
+ if (spec.stops) return spec.stops.map((stop) => ({ color: color(stop.color), location: stop.location }));
551
+ const colors = spec.colors ?? [];
552
+ const last = Math.max(colors.length - 1, 1);
553
+ return colors.map((value, index) => ({ color: color(value), location: index / last }));
554
+ }
555
+ function LinearGradient(spec) {
556
+ return { linearGradient: { stops: stops$1(spec), start: unitPoint(spec.startPoint), end: unitPoint(spec.endPoint) } };
557
+ }
558
+ function RadialGradient(spec) {
559
+ return {
560
+ radialGradient: { stops: stops$1(spec), center: unitPoint(spec.center), startRadius: spec.startRadius, endRadius: spec.endRadius }
561
+ };
562
+ }
563
+ function AngularGradient(spec) {
564
+ return {
565
+ angularGradient: {
566
+ stops: stops$1(spec),
567
+ center: unitPoint(spec.center),
568
+ startAngle: spec.startAngle ?? 0,
569
+ endAngle: spec.endAngle ?? 360
570
+ }
571
+ };
572
+ }
573
+ function font(value) {
574
+ return typeof value === "string" ? { textStyle: value } : value;
575
+ }
576
+ function system(first, second, third) {
577
+ const result = typeof first === "number" ? { size: first } : { textStyle: first };
578
+ const weight = typeof first === "number" ? second : third;
579
+ const design = typeof first === "number" ? third : second;
580
+ if (weight) result.weight = weight;
581
+ if (design) result.design = design;
582
+ return result;
583
+ }
584
+ function weighted(value, weight) {
585
+ return { ...font(value), weight };
586
+ }
587
+ const Font = { system, weighted, bold: (value) => weighted(value, "bold") };
588
+ function frame(value) {
589
+ const dimension = (n) => n === void 0 ? void 0 : n === Infinity ? "infinity" : n;
590
+ const result = { alignment: value.alignment ?? "center" };
591
+ if (value.width !== void 0) result.width = value.width;
592
+ if (value.height !== void 0) result.height = value.height;
593
+ const minWidth = dimension(value.minWidth);
594
+ const maxWidth = dimension(value.maxWidth);
595
+ const minHeight = dimension(value.minHeight);
596
+ const maxHeight = dimension(value.maxHeight);
597
+ if (minWidth !== void 0) result.minWidth = minWidth;
598
+ if (maxWidth !== void 0) result.maxWidth = maxWidth;
599
+ if (minHeight !== void 0) result.minHeight = minHeight;
600
+ if (maxHeight !== void 0) result.maxHeight = maxHeight;
601
+ return result;
602
+ }
603
+ const DEFAULT_PADDING = 16;
604
+ function padding(value) {
605
+ if (value === true) return insets(["all"], DEFAULT_PADDING);
606
+ if (typeof value === "number") return insets(["all"], value);
607
+ if (Array.isArray(value)) {
608
+ const [edges, amount] = value;
609
+ return insets(Array.isArray(edges) ? edges : [edges], amount ?? DEFAULT_PADDING);
429
610
  }
611
+ return value;
612
+ }
613
+ function insets(edges, amount) {
614
+ const result = {};
615
+ for (const edge of edges) {
616
+ if (edge === "all" || edge === "vertical" || edge === "top") result.top = amount;
617
+ if (edge === "all" || edge === "vertical" || edge === "bottom") result.bottom = amount;
618
+ if (edge === "all" || edge === "horizontal" || edge === "leading") result.leading = amount;
619
+ if (edge === "all" || edge === "horizontal" || edge === "trailing") result.trailing = amount;
620
+ }
621
+ return result;
622
+ }
623
+ let installed = null;
624
+ function installTextMeasurer(measurer, remove = false) {
625
+ if (remove) {
626
+ if (installed === measurer) installed = null;
627
+ return;
628
+ }
629
+ installed = measurer;
630
+ }
631
+ function canMeasureText() {
632
+ return installed !== null;
633
+ }
634
+ function measureText(text, font$1 = "body", width) {
635
+ if (!installed) throw new Error("measureText: nothing measures text here yet (mount the app, or run it in a SwiftBrowser Native host)");
636
+ const constrained = typeof width === "number" && Number.isFinite(width) && width > 0 ? width : null;
637
+ return installed(text, font(font$1), constrained);
430
638
  }
431
639
  function mount(screen, element, options = {}) {
432
640
  let renderer = null;
433
641
  const root = createSwiftUIRoot((ops) => renderer?.applyOps(ops));
434
642
  renderer = new Renderer(screen, (event) => root.dispatch(event));
643
+ const measurer = (text, font2, width) => renderer.measureText(text, font2, width);
644
+ installTextMeasurer(measurer);
435
645
  let pageScheme = options.colorScheme ?? "light";
436
646
  let preferred = null;
437
647
  const environment = {
@@ -441,6 +651,7 @@ function mount(screen, element, options = {}) {
441
651
  };
442
652
  renderer.contentHeight = options.contentHeight ?? false;
443
653
  if (options.assets) renderer.setAssetCatalog(options.assets);
654
+ if (options.app) browserEnvironment.files?.setApp?.(options.app);
444
655
  renderer.setEnvironment({ colorScheme: environment.colorScheme, dynamicTypeSize: environment.dynamicTypeSize });
445
656
  if (options.safeArea !== void 0) renderer.setSafeArea(options.safeArea);
446
657
  root.setEnvironment(environment);
@@ -483,6 +694,7 @@ function mount(screen, element, options = {}) {
483
694
  stopKeyboard?.();
484
695
  browserHistory?.dispose();
485
696
  screen.removeEventListener("sb:color-scheme", onPreferredScheme);
697
+ installTextMeasurer(measurer, true);
486
698
  root.unmount();
487
699
  renderer?.dispose();
488
700
  }
@@ -498,11 +710,233 @@ function RegisterDestination({ destination }) {
498
710
  navigation?.registerDestination(destination);
499
711
  return null;
500
712
  }
713
+ const EMPTY = { canUndo: false, canRedo: false, undoActionName: "", redoActionName: "" };
714
+ const defaultSchedule = (close) => {
715
+ if (typeof queueMicrotask === "function") queueMicrotask(close);
716
+ else void Promise.resolve().then(close);
717
+ };
718
+ class UndoManager {
719
+ /** Whether registrations in one event (one task of the event loop) form one group; true, as `groupsByEvent`. */
720
+ groupsByEvent = true;
721
+ /** How many groups the undo stack keeps; 0 for no limit, as `levelsOfUndo`. */
722
+ levelsOfUndo = 0;
723
+ undoStack = [];
724
+ redoStack = [];
725
+ /** The group registrations go in: an explicit group, the event's group, or the inverse of what is being undone or redone. */
726
+ open = null;
727
+ /** How deep `beginGrouping` is; 0 when `open` is the event's group or an inverse. */
728
+ depth = 0;
729
+ /** Whether `open` is the event's group, which `schedule` closes. */
730
+ byEvent = false;
731
+ /** The last keyed group closed onto the undo stack, which a registration with its key joins. */
732
+ coalescing = null;
733
+ undoing = false;
734
+ redoing = false;
735
+ snapshot = EMPTY;
736
+ listeners = /* @__PURE__ */ new Set();
737
+ schedule;
738
+ host;
739
+ stopListening = null;
740
+ constructor(options = {}) {
741
+ this.schedule = options.schedule ?? defaultSchedule;
742
+ this.host = options.host === void 0 ? browserEnvironment.undoManager ?? null : options.host;
743
+ if (this.host) {
744
+ this.stopListening = this.host.listen({ undo: () => this.undo(), redo: () => this.redo() });
745
+ this.host.update(this.snapshot);
746
+ }
747
+ }
748
+ get canUndo() {
749
+ return this.undoStack.length > 0;
750
+ }
751
+ get canRedo() {
752
+ return this.redoStack.length > 0;
753
+ }
754
+ /** The name of what `undo` takes back ('' for none): "Move" for an Edit menu's "Undo Move". */
755
+ get undoActionName() {
756
+ return this.undoStack[this.undoStack.length - 1]?.name ?? "";
757
+ }
758
+ get redoActionName() {
759
+ return this.redoStack[this.redoStack.length - 1]?.name ?? "";
760
+ }
761
+ get isUndoing() {
762
+ return this.undoing;
763
+ }
764
+ get isRedoing() {
765
+ return this.redoing;
766
+ }
767
+ /** What the stacks can do, one object per change (for `useSyncExternalStore`). */
768
+ get state() {
769
+ return this.snapshot;
770
+ }
771
+ /**
772
+ * Registers what undoes the change just made, under `name`. Run by `undo`,
773
+ * the closure registers its own inverse the same way, which lands on the
774
+ * redo stack because the manager is undoing.
775
+ */
776
+ registerUndo(name, undo, options = {}) {
777
+ const action = { name, undo, target: options.target };
778
+ if (this.undoing || this.redoing) {
779
+ this.open.actions.push(action);
780
+ this.changed();
781
+ return;
782
+ }
783
+ this.redoStack = [];
784
+ this.coalescing = this.coalescing && this.undoStack[this.undoStack.length - 1] === this.coalescing ? this.coalescing : null;
785
+ if (this.open) {
786
+ if (!this.undoStack.includes(this.open)) this.push(this.open);
787
+ this.open.actions.push(action);
788
+ if (!this.open.named) this.open.name = name;
789
+ } else if (this.coalescing && options.key !== void 0 && this.coalescing.key === options.key) {
790
+ this.coalescing.actions.push(action);
791
+ this.coalescing.name = name;
792
+ } else {
793
+ const group = { name, named: false, actions: [action], key: options.key };
794
+ this.push(group);
795
+ this.coalescing = null;
796
+ if (this.groupsByEvent) {
797
+ this.open = group;
798
+ this.byEvent = true;
799
+ this.schedule(() => {
800
+ if (this.open === group && this.byEvent) this.close();
801
+ });
802
+ } else {
803
+ this.coalescing = group.key !== void 0 ? group : null;
804
+ }
805
+ }
806
+ this.changed();
807
+ }
808
+ /** Takes back the top group of changes; its inverses become the top of the redo stack. */
809
+ undo() {
810
+ this.endOpenGroups();
811
+ const group = this.undoStack.pop();
812
+ if (!group) return;
813
+ this.coalescing = null;
814
+ this.run(group, "undo");
815
+ }
816
+ redo() {
817
+ this.endOpenGroups();
818
+ const group = this.redoStack.pop();
819
+ if (!group) return;
820
+ this.run(group, "redo");
821
+ }
822
+ /**
823
+ * Starts a group: every registration until `endGrouping` undoes as one
824
+ * (`beginUndoGrouping`). Nested calls join the outer group. The group is
825
+ * named `name`, else after its last registration.
826
+ */
827
+ beginGrouping(name) {
828
+ if (this.undoing || this.redoing) return;
829
+ this.depth += 1;
830
+ if (this.depth > 1) return;
831
+ if (this.open) this.close();
832
+ this.open = { name: name ?? "", named: name !== void 0, actions: [], key: void 0 };
833
+ this.byEvent = false;
834
+ this.coalescing = null;
835
+ }
836
+ /** Ends the group `beginGrouping` started; one that registered nothing is dropped. */
837
+ endGrouping() {
838
+ if (this.depth === 0) return;
839
+ this.depth -= 1;
840
+ if (this.depth === 0) this.close();
841
+ }
842
+ /** `body` as one group named `name`. */
843
+ group(name, body) {
844
+ this.beginGrouping(name);
845
+ try {
846
+ return body();
847
+ } finally {
848
+ this.endGrouping();
849
+ }
850
+ }
851
+ /** Forgets every registration, or those `target` made (`removeAllActions(withTarget:)`), on both stacks and in the open group. */
852
+ removeAllActions(target) {
853
+ const mine = (action) => target === void 0 || action.target === target;
854
+ const strip = (groups) => groups.filter((group) => {
855
+ group.actions = group.actions.filter((action) => !mine(action));
856
+ return group.actions.length > 0;
857
+ });
858
+ this.undoStack = strip(this.undoStack);
859
+ this.redoStack = strip(this.redoStack);
860
+ if (this.open) this.open.actions = this.open.actions.filter((action) => !mine(action));
861
+ if (this.coalescing && !this.undoStack.includes(this.coalescing)) this.coalescing = null;
862
+ this.changed();
863
+ }
864
+ /** Calls `listener` after each change to `state`; returns what stops it. */
865
+ subscribe = (listener) => {
866
+ this.listeners.add(listener);
867
+ return () => {
868
+ this.listeners.delete(listener);
869
+ };
870
+ };
871
+ /** Stops listening to the host, for a manager made in a test. */
872
+ dispose() {
873
+ this.stopListening?.();
874
+ this.stopListening = null;
875
+ }
876
+ push(group) {
877
+ this.undoStack.push(group);
878
+ if (this.levelsOfUndo > 0 && this.undoStack.length > this.levelsOfUndo) this.undoStack.splice(0, this.undoStack.length - this.levelsOfUndo);
879
+ }
880
+ /** Closes `open`; a keyed group on top stays open to its key. */
881
+ close() {
882
+ const group = this.open;
883
+ this.open = null;
884
+ this.byEvent = false;
885
+ if (group && group.key !== void 0 && this.undoStack[this.undoStack.length - 1] === group) this.coalescing = group;
886
+ }
887
+ endOpenGroups() {
888
+ this.depth = 0;
889
+ if (this.open) this.close();
890
+ }
891
+ run(group, direction) {
892
+ const inverse = { name: group.name, named: true, actions: [], key: void 0 };
893
+ this.open = inverse;
894
+ this.byEvent = false;
895
+ if (direction === "undo") this.undoing = true;
896
+ else this.redoing = true;
897
+ try {
898
+ for (let i = group.actions.length - 1; i >= 0; i -= 1) group.actions[i].undo();
899
+ } finally {
900
+ this.undoing = false;
901
+ this.redoing = false;
902
+ this.open = null;
903
+ if (inverse.actions.length > 0) {
904
+ if (direction === "undo") this.redoStack.push(inverse);
905
+ else this.push(inverse);
906
+ }
907
+ this.changed();
908
+ }
909
+ }
910
+ changed() {
911
+ const next = { canUndo: this.canUndo, canRedo: this.canRedo, undoActionName: this.undoActionName, redoActionName: this.redoActionName };
912
+ const last = this.snapshot;
913
+ if (next.canUndo === last.canUndo && next.canRedo === last.canRedo && next.undoActionName === last.undoActionName && next.redoActionName === last.redoActionName) return;
914
+ this.snapshot = next;
915
+ this.host?.update(next);
916
+ for (const listener of [...this.listeners]) listener();
917
+ }
918
+ }
919
+ const UndoManagerContext = createContext(null);
920
+ let shared = null;
921
+ function sharedUndoManager() {
922
+ shared ??= new UndoManager();
923
+ return shared;
924
+ }
925
+ function useUndoManager() {
926
+ const manager = useContext(UndoManagerContext) ?? sharedUndoManager();
927
+ useSyncExternalStore(
928
+ manager.subscribe,
929
+ () => manager.state,
930
+ () => manager.state
931
+ );
932
+ return manager;
933
+ }
501
934
  const OWN = [
502
935
  EnvironmentContext,
503
936
  ViewEnvironmentContext,
504
937
  NavigationContext,
505
- DismissContext
938
+ DismissContext,
939
+ UndoManagerContext
506
940
  ];
507
941
  function useContextBridge(contexts = [], options = {}) {
508
942
  const own = options.environment === false ? OWN.filter((context) => context !== EnvironmentContext) : OWN;
@@ -620,219 +1054,83 @@ function Draggable({ payload, children }) {
620
1054
  function itemValue(item) {
621
1055
  if (item.text !== void 0) {
622
1056
  if (item.type === "text/uri-list") {
623
- try {
624
- return new URL(item.text.split(/\r?\n/)[0].trim());
625
- } catch {
626
- return item.text;
627
- }
628
- }
629
- return item.text;
630
- }
631
- const binary = atob(item.data ?? "");
632
- const bytes = new Uint8Array(binary.length);
633
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
634
- return typeof File !== "undefined" ? new File([bytes], item.name ?? "file", { type: item.type }) : bytes;
635
- }
636
- function DropDestination({ action, isTargeted, accepts, children }) {
637
- return sb(
638
- "dropdestination",
639
- { accepts: accepts ?? [] },
640
- {
641
- drop: (event) => {
642
- if (event.phase === "entered") {
643
- isTargeted?.(true);
644
- return;
645
- }
646
- isTargeted?.(false);
647
- if (event.phase !== "performed") return;
648
- let items;
649
- if (event.source !== void 0) items = active && active.id === event.source ? [active.payload] : [];
650
- else items = (event.items ?? []).map(itemValue);
651
- if (items.length > 0) action(items, { x: event.x, y: event.y });
652
- }
653
- },
654
- children
655
- );
656
- }
657
- function FileImporterPresenter(spec) {
658
- const [presented, setPresented] = readBinding(spec.isPresented);
659
- const latest = useRef(spec);
660
- latest.current = spec;
661
- const choosing = useRef(false);
662
- useLayoutEffect(() => {
663
- if (!presented || choosing.current) return;
664
- const { allowedContentTypes, allowsMultipleSelection } = latest.current;
665
- const pick = browserEnvironment.pickFiles;
666
- if (!pick) {
667
- setPresented(false);
668
- latest.current.onCancellation?.();
669
- return;
670
- }
671
- choosing.current = true;
672
- void pick({ accept: allowedContentTypes.join(","), multiple: allowsMultipleSelection === true }).then((files) => {
673
- choosing.current = false;
674
- setPresented(false);
675
- if (files.length > 0) latest.current.onCompletion(files);
676
- else latest.current.onCancellation?.();
677
- });
678
- }, [presented, setPresented]);
679
- return null;
680
- }
681
- const HIERARCHY = { primary: 1, secondary: 2, tertiary: 3, quaternary: 4 };
682
- function rgb(r2, g, b, a = 1) {
683
- return { r: r2, g, b, a };
684
- }
685
- function hex(value) {
686
- const digits = value.replace("#", "");
687
- const wide = digits.length === 3 || digits.length === 4 ? [...digits].map((d) => d + d).join("") : digits;
688
- const n = (at) => parseInt(wide.slice(at, at + 2), 16) / 255;
689
- return { r: n(0), g: n(2), b: n(4), a: wide.length >= 8 ? n(6) : 1 };
690
- }
691
- function shapeStyle(value) {
692
- if (typeof value !== "string") return value;
693
- if (value in HIERARCHY) return { hierarchical: HIERARCHY[value] };
694
- if (value === "white") return rgb(1, 1, 1);
695
- if (value === "black") return rgb(0, 0, 0);
696
- if (value.startsWith("#")) return hex(value);
697
- return { name: value };
698
- }
699
- function color(value) {
700
- return shapeStyle(value);
701
- }
702
- function opacity(value, a) {
703
- const style = shapeStyle(value);
704
- if ("r" in style) return { ...style, a: style.a * a };
705
- if ("name" in style) return { ...style, a: (style.a ?? 1) * a };
706
- if ("named" in style) return { ...style, opacity: style.opacity * a };
707
- return { ...style, opacity: (style.opacity ?? 1) * a };
708
- }
709
- function gradient(value) {
710
- return { colorGradient: color(value) };
711
- }
712
- const ColorValues = {
713
- red: { name: "red" },
714
- orange: { name: "orange" },
715
- yellow: { name: "yellow" },
716
- green: { name: "green" },
717
- mint: { name: "mint" },
718
- teal: { name: "teal" },
719
- cyan: { name: "cyan" },
720
- blue: { name: "blue" },
721
- indigo: { name: "indigo" },
722
- purple: { name: "purple" },
723
- pink: { name: "pink" },
724
- brown: { name: "brown" },
725
- gray: { name: "gray" },
726
- white: rgb(1, 1, 1),
727
- black: rgb(0, 0, 0),
728
- clear: { name: "clear" },
729
- primary: { name: "primary" },
730
- secondary: { name: "secondary" },
731
- accent: { name: "accent" },
732
- /** `Color(uiColor:)` / `Color(.name)`: a UIKit semantic color by its iOS name. */
733
- system: (name) => ({ name }),
734
- rgb,
735
- hex,
736
- opacity,
737
- gradient
738
- };
739
- const UNIT_POINTS = {
740
- center: { x: 0.5, y: 0.5 },
741
- top: { x: 0.5, y: 0 },
742
- bottom: { x: 0.5, y: 1 },
743
- leading: { x: 0, y: 0.5 },
744
- trailing: { x: 1, y: 0.5 },
745
- topLeading: { x: 0, y: 0 },
746
- topTrailing: { x: 1, y: 0 },
747
- bottomLeading: { x: 0, y: 1 },
748
- bottomTrailing: { x: 1, y: 1 }
749
- };
750
- function unitPoint(value) {
751
- return typeof value === "string" ? UNIT_POINTS[value] : value;
752
- }
753
- function stops$1(spec) {
754
- if (spec.stops) return spec.stops.map((stop) => ({ color: color(stop.color), location: stop.location }));
755
- const colors = spec.colors ?? [];
756
- const last = Math.max(colors.length - 1, 1);
757
- return colors.map((value, index) => ({ color: color(value), location: index / last }));
758
- }
759
- function LinearGradient(spec) {
760
- return { linearGradient: { stops: stops$1(spec), start: unitPoint(spec.startPoint), end: unitPoint(spec.endPoint) } };
761
- }
762
- function RadialGradient(spec) {
763
- return {
764
- radialGradient: { stops: stops$1(spec), center: unitPoint(spec.center), startRadius: spec.startRadius, endRadius: spec.endRadius }
765
- };
766
- }
767
- function AngularGradient(spec) {
768
- return {
769
- angularGradient: {
770
- stops: stops$1(spec),
771
- center: unitPoint(spec.center),
772
- startAngle: spec.startAngle ?? 0,
773
- endAngle: spec.endAngle ?? 360
774
- }
775
- };
776
- }
777
- function font(value) {
778
- return typeof value === "string" ? { textStyle: value } : value;
779
- }
780
- function system(first, second, third) {
781
- const result = typeof first === "number" ? { size: first } : { textStyle: first };
782
- const weight = typeof first === "number" ? second : third;
783
- const design = typeof first === "number" ? third : second;
784
- if (weight) result.weight = weight;
785
- if (design) result.design = design;
786
- return result;
787
- }
788
- function weighted(value, weight) {
789
- return { ...font(value), weight };
790
- }
791
- const Font = { system, weighted, bold: (value) => weighted(value, "bold") };
792
- function frame(value) {
793
- const dimension = (n) => n === void 0 ? void 0 : n === Infinity ? "infinity" : n;
794
- const result = { alignment: value.alignment ?? "center" };
795
- if (value.width !== void 0) result.width = value.width;
796
- if (value.height !== void 0) result.height = value.height;
797
- const minWidth = dimension(value.minWidth);
798
- const maxWidth = dimension(value.maxWidth);
799
- const minHeight = dimension(value.minHeight);
800
- const maxHeight = dimension(value.maxHeight);
801
- if (minWidth !== void 0) result.minWidth = minWidth;
802
- if (maxWidth !== void 0) result.maxWidth = maxWidth;
803
- if (minHeight !== void 0) result.minHeight = minHeight;
804
- if (maxHeight !== void 0) result.maxHeight = maxHeight;
805
- return result;
806
- }
807
- const DEFAULT_PADDING = 16;
808
- function padding(value) {
809
- if (value === true) return insets(["all"], DEFAULT_PADDING);
810
- if (typeof value === "number") return insets(["all"], value);
811
- if (Array.isArray(value)) {
812
- const [edges, amount] = value;
813
- return insets(Array.isArray(edges) ? edges : [edges], amount ?? DEFAULT_PADDING);
1057
+ try {
1058
+ return new URL(item.text.split(/\r?\n/)[0].trim());
1059
+ } catch {
1060
+ return item.text;
1061
+ }
1062
+ }
1063
+ return item.text;
814
1064
  }
815
- return value;
1065
+ const binary = atob(item.data ?? "");
1066
+ const bytes = new Uint8Array(binary.length);
1067
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1068
+ return typeof File !== "undefined" ? new File([bytes], item.name ?? "file", { type: item.type }) : bytes;
816
1069
  }
817
- function insets(edges, amount) {
818
- const result = {};
819
- for (const edge of edges) {
820
- if (edge === "all" || edge === "vertical" || edge === "top") result.top = amount;
821
- if (edge === "all" || edge === "vertical" || edge === "bottom") result.bottom = amount;
822
- if (edge === "all" || edge === "horizontal" || edge === "leading") result.leading = amount;
823
- if (edge === "all" || edge === "horizontal" || edge === "trailing") result.trailing = amount;
824
- }
825
- return result;
1070
+ function DropDestination({ action, isTargeted, accepts, children }) {
1071
+ return sb(
1072
+ "dropdestination",
1073
+ { accepts: accepts ?? [] },
1074
+ {
1075
+ drop: (event) => {
1076
+ if (event.phase === "entered") {
1077
+ isTargeted?.(true);
1078
+ return;
1079
+ }
1080
+ isTargeted?.(false);
1081
+ if (event.phase !== "performed") return;
1082
+ let items;
1083
+ if (event.source !== void 0) items = active && active.id === event.source ? [active.payload] : [];
1084
+ else items = (event.items ?? []).map(itemValue);
1085
+ if (items.length > 0) action(items, { x: event.x, y: event.y });
1086
+ }
1087
+ },
1088
+ children
1089
+ );
1090
+ }
1091
+ function FileImporterPresenter(spec) {
1092
+ const [presented, setPresented] = readBinding(spec.isPresented);
1093
+ const latest = useRef(spec);
1094
+ latest.current = spec;
1095
+ const choosing = useRef(false);
1096
+ useLayoutEffect(() => {
1097
+ if (!presented || choosing.current) return;
1098
+ const { allowedContentTypes, allowsMultipleSelection } = latest.current;
1099
+ const pick = browserEnvironment.pickFiles;
1100
+ if (!pick) {
1101
+ setPresented(false);
1102
+ latest.current.onCancellation?.();
1103
+ return;
1104
+ }
1105
+ choosing.current = true;
1106
+ void pick({ accept: allowedContentTypes.join(","), multiple: allowsMultipleSelection === true }).then((files) => {
1107
+ choosing.current = false;
1108
+ setPresented(false);
1109
+ if (files.length > 0) latest.current.onCompletion(files);
1110
+ else latest.current.onCancellation?.();
1111
+ });
1112
+ }, [presented, setPresented]);
1113
+ return null;
826
1114
  }
1115
+ const isPoint = (value) => typeof value === "object" && value !== null && ("x" in value || "y" in value);
827
1116
  function command(request) {
828
1117
  return request.axes ? { anchor: request.anchor, serial: request.serial, axes: request.axes } : { anchor: request.anchor, serial: request.serial };
829
1118
  }
1119
+ function offsetCommand(request) {
1120
+ const out = { serial: request.serial };
1121
+ if (typeof request.point.x === "number") out.x = request.point.x;
1122
+ if (typeof request.point.y === "number") out.y = request.point.y;
1123
+ return out;
1124
+ }
830
1125
  const ScrollRequestContext = createContext(null);
831
1126
  let nextSerial = 1;
832
1127
  function ScrollViewReader({ children }) {
833
1128
  const [request, setRequest] = useState(null);
834
1129
  const proxy = {
835
- scrollTo: (id, anchor, options) => setRequest({ id, anchor: anchor === void 0 ? null : unitPoint(anchor), serial: nextSerial++, ...options?.axes ? { axes: options.axes } : {} })
1130
+ scrollTo: (target, anchor, options) => {
1131
+ if (isPoint(target)) setRequest({ point: target, serial: nextSerial++ });
1132
+ else setRequest({ id: target, anchor: anchor === void 0 ? null : unitPoint(anchor), serial: nextSerial++, ...options?.axes ? { axes: options.axes } : {} });
1133
+ }
836
1134
  };
837
1135
  return /* @__PURE__ */ jsx(ScrollRequestContext.Provider, { value: request, children: children(proxy) });
838
1136
  }
@@ -840,7 +1138,7 @@ function ScrollTarget({ id, children }) {
840
1138
  const request = useContext(ScrollRequestContext);
841
1139
  const position = useContext(ScrollPositionContext);
842
1140
  const on = position ? { scrollposition: () => position.report(id) } : void 0;
843
- if (request && request.id === id) return sb("styled", { style: { scrollID: true, scrollTo: command(request) } }, on, children);
1141
+ if (request && "id" in request && request.id === id) return sb("styled", { style: { scrollID: true, scrollTo: command(request) } }, on, children);
844
1142
  if (on) return sb("styled", { style: { scrollID: true } }, on, children);
845
1143
  return styled({ scrollID: true }, children);
846
1144
  }
@@ -855,25 +1153,33 @@ function useScrollPosition(binding, anchor, axes) {
855
1153
  const point = anchor === void 0 ? null : unitPoint(anchor);
856
1154
  if (value !== last.current) {
857
1155
  last.current = value;
858
- if (value !== null && value !== reported.current) request.current = { id: value, anchor: point, serial: nextSerial++, axes };
1156
+ if (isPoint(value)) request.current = { point: value, serial: nextSerial++ };
1157
+ else if (value !== null && value !== reported.current) request.current = { id: value, anchor: point, serial: nextSerial++, axes };
859
1158
  reported.current = void 0;
860
1159
  }
861
- if (!bound) return null;
862
- const props = { reportsPosition: true };
863
- if (point) props["positionAnchor"] = point;
864
1160
  const own = request.current;
865
1161
  const current = own && (!outer || own.serial > outer.serial) ? own : outer;
1162
+ const offset = useOffsetCommand(bound ? current : outer);
1163
+ if (!bound) return offset ? { props: offset, provide: (children) => children } : null;
1164
+ const props = { reportsPosition: true, ...offset };
1165
+ if (point) props["positionAnchor"] = point;
866
1166
  const context = {
867
1167
  report: (id) => {
868
1168
  reported.current = id;
869
1169
  set(id);
870
1170
  }
871
1171
  };
1172
+ const inner = current && "point" in current ? outer && "id" in outer ? outer : null : current;
872
1173
  return {
873
1174
  props,
874
- provide: (children) => /* @__PURE__ */ jsx(ScrollPositionContext.Provider, { value: context, children: /* @__PURE__ */ jsx(ScrollRequestContext.Provider, { value: current, children }) })
1175
+ provide: (children) => /* @__PURE__ */ jsx(ScrollPositionContext.Provider, { value: context, children: /* @__PURE__ */ jsx(ScrollRequestContext.Provider, { value: inner, children }) })
875
1176
  };
876
1177
  }
1178
+ function useOffsetCommand(request) {
1179
+ const seen = useRef(null);
1180
+ if (request && "point" in request && (!seen.current || request.serial > seen.current.serial)) seen.current = offsetCommand(request);
1181
+ return seen.current ? { scrollOffset: seen.current } : null;
1182
+ }
877
1183
  const ScrollSettingsContext = createContext({});
878
1184
  function edgesOf(edges) {
879
1185
  const list = Array.isArray(edges) ? edges : [edges];
@@ -966,7 +1272,7 @@ function LazyGroup({ rows, start = 0, wrap: wrap2 }) {
966
1272
  const axis = useContext(LazyContext);
967
1273
  const request = useContext(ScrollRequestContext);
968
1274
  const [count, setCount] = useState(LAZY_BATCH);
969
- const target = request ? rows.findIndex((row) => rowMatches(row, request.id)) : -1;
1275
+ const target = request && "id" in request ? rows.findIndex((row) => rowMatches(row, request.id)) : -1;
970
1276
  const needed = target >= 0 ? Math.ceil((target + 1) / LAZY_BATCH) * LAZY_BATCH : 0;
971
1277
  useEffect(() => {
972
1278
  if (needed > count) setCount(needed);
@@ -996,17 +1302,23 @@ function rowMatches(row, id) {
996
1302
  }
997
1303
  return false;
998
1304
  }
1305
+ const PinnedContext = createContext(null);
1306
+ function pinning(pinnedViews, children) {
1307
+ return pinnedViews && pinnedViews.length > 0 ? /* @__PURE__ */ jsx(PinnedContext.Provider, { value: pinnedViews, children }) : children;
1308
+ }
999
1309
  function VStack(props) {
1000
1310
  const { own, modifiers } = splitProps(props);
1001
1311
  const stack = { spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1002
1312
  if (own.scrollTargetLayout) stack["scrollTarget"] = true;
1003
- return applyModifiers(sb("vstack", stack, void 0, own.children), modifiers);
1313
+ if (own.pinnedViews && own.pinnedViews.length > 0) stack["pinned"] = own.pinnedViews;
1314
+ return applyModifiers(sb("vstack", stack, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1004
1315
  }
1005
1316
  function HStack(props) {
1006
1317
  const { own, modifiers } = splitProps(props);
1007
1318
  const stack = { spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1008
1319
  if (own.scrollTargetLayout) stack["scrollTarget"] = true;
1009
- return applyModifiers(sb("hstack", stack, void 0, own.children), modifiers);
1320
+ if (own.pinnedViews && own.pinnedViews.length > 0) stack["pinned"] = own.pinnedViews;
1321
+ return applyModifiers(sb("hstack", stack, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1010
1322
  }
1011
1323
  function ZStack(props) {
1012
1324
  const { own, modifiers } = splitProps(props);
@@ -1020,15 +1332,45 @@ function Divider(props) {
1020
1332
  const { modifiers } = splitProps(props);
1021
1333
  return applyModifiers(sb("divider", {}), modifiers);
1022
1334
  }
1335
+ function scrollGeometryOf(event) {
1336
+ return {
1337
+ contentOffset: { x: event.offset.x, y: event.offset.y },
1338
+ contentSize: { width: event.contentSize.width, height: event.contentSize.height },
1339
+ containerSize: { width: event.containerSize.width, height: event.containerSize.height },
1340
+ contentInsets: { ...event.contentInsets },
1341
+ visibleRect: { x: event.offset.x, y: event.offset.y, width: event.containerSize.width, height: event.containerSize.height }
1342
+ };
1343
+ }
1344
+ function useScrollGeometryChange(handler) {
1345
+ const last = useRef(null);
1346
+ if (!handler) return { props: {} };
1347
+ return {
1348
+ props: { reportsGeometry: true },
1349
+ handlers: {
1350
+ scrollgeometry: (event) => {
1351
+ const geometry = scrollGeometryOf(event);
1352
+ if (typeof handler === "function") return handler(geometry);
1353
+ const value = handler.of(geometry);
1354
+ const previous = last.current;
1355
+ if (previous && JSON.stringify(previous.value) === JSON.stringify(value)) return;
1356
+ last.current = { value };
1357
+ handler.action(value, previous ? previous.value : value);
1358
+ }
1359
+ }
1360
+ };
1361
+ }
1023
1362
  function ScrollView(props) {
1024
1363
  const { own, modifiers } = splitProps(props);
1025
1364
  const refresh = useRefreshable(takeModifier(modifiers, "refreshable"));
1026
1365
  const ownSettings = takeScrollSettings(modifiers);
1027
1366
  const settings = useScrollSettings(ownSettings);
1028
1367
  const axes = own.axes ?? "vertical";
1029
- const scroll = { axes, ...refresh.props };
1368
+ const geometry = useScrollGeometryChange(own.onScrollGeometryChange);
1369
+ const scroll = { axes, ...refresh.props, ...geometry.props };
1370
+ const handlers = refresh.handlers || geometry.handlers ? { ...refresh.handlers, ...geometry.handlers } : void 0;
1030
1371
  if (own.scrollTargetBehavior) scroll["snap"] = own.scrollTargetBehavior;
1031
1372
  if (own.scrollDismissesKeyboard) scroll["dismissesKeyboard"] = true;
1373
+ if (own.scrollIndicators === "hidden") scroll["showsIndicators"] = false;
1032
1374
  if (own.defaultScrollAnchor !== void 0) scroll["defaultScrollAnchor"] = unitPoint(own.defaultScrollAnchor);
1033
1375
  const edgeEffect = edgeEffectProp(settings);
1034
1376
  if (edgeEffect) scroll["edgeEffect"] = edgeEffect;
@@ -1036,7 +1378,7 @@ function ScrollView(props) {
1036
1378
  if (position) Object.assign(scroll, position.props);
1037
1379
  const content = marginedContent(own.children, settings.margins, axes === "horizontal" ? "horizontal" : "vertical");
1038
1380
  const inner = providingScrollSettings(ownSettings, content);
1039
- return applyModifiers(sb("scrollview", scroll, refresh.handlers, position ? position.provide(inner) : inner), modifiers);
1381
+ return applyModifiers(sb("scrollview", scroll, handlers, position ? position.provide(inner) : inner), modifiers);
1040
1382
  }
1041
1383
  function takeScrollSettings(modifiers) {
1042
1384
  let settings = {};
@@ -1160,17 +1502,15 @@ GridItem.fixed = (value, options = {}) => GridItem({ size: { fixed: value }, spa
1160
1502
  GridItem.adaptive = (options) => GridItem({ size: { adaptive: options }, spacing: options.spacing, alignment: options.alignment });
1161
1503
  function LazyVGrid(props) {
1162
1504
  const { own, modifiers } = splitProps(props);
1163
- return applyModifiers(
1164
- sb("lazyvgrid", { columns: own.columns, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" }, void 0, own.children),
1165
- modifiers
1166
- );
1505
+ const grid = { columns: own.columns, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1506
+ if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
1507
+ return applyModifiers(sb("lazyvgrid", grid, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1167
1508
  }
1168
1509
  function LazyHGrid(props) {
1169
1510
  const { own, modifiers } = splitProps(props);
1170
- return applyModifiers(
1171
- sb("lazyhgrid", { rows: own.rows, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" }, void 0, own.children),
1172
- modifiers
1173
- );
1511
+ const grid = { rows: own.rows, spacing: own.spacing ?? null, alignment: own.alignment ?? "center" };
1512
+ if (own.pinnedViews && own.pinnedViews.length > 0) grid["pinned"] = own.pinnedViews;
1513
+ return applyModifiers(sb("lazyhgrid", grid, void 0, pinning(own.pinnedViews, own.children)), modifiers);
1174
1514
  }
1175
1515
  function Grid(props) {
1176
1516
  const { own, modifiers } = splitProps(props);
@@ -1200,11 +1540,11 @@ function runStyleOf(props, inherited, nested) {
1200
1540
  if (props.baselineOffset !== void 0) style.baselineOffset = props.baselineOffset;
1201
1541
  if (props.link) style.link = props.link;
1202
1542
  if (nested) {
1203
- let runFont = props.font !== void 0 ? font(props.font) : void 0;
1204
- if (props.bold) runFont = { ...runFont ?? {}, weight: "bold" };
1205
- if (props.fontWeight) runFont = { ...runFont ?? {}, weight: props.fontWeight };
1206
- if (props.monospacedDigit) runFont = { ...runFont ?? {}, monospacedDigit: true };
1207
- if (runFont) style.font = { ...style.font ?? {}, ...runFont };
1543
+ let runFont2 = props.font !== void 0 ? font(props.font) : void 0;
1544
+ if (props.bold) runFont2 = { ...runFont2 ?? {}, weight: "bold" };
1545
+ if (props.fontWeight) runFont2 = { ...runFont2 ?? {}, weight: props.fontWeight };
1546
+ if (props.monospacedDigit) runFont2 = { ...runFont2 ?? {}, monospacedDigit: true };
1547
+ if (runFont2) style.font = { ...style.font ?? {}, ...runFont2 };
1208
1548
  if (props.foregroundStyle !== void 0) {
1209
1549
  style.foreground = shapeStyle(Array.isArray(props.foregroundStyle) ? props.foregroundStyle[0] : props.foregroundStyle);
1210
1550
  }
@@ -1260,8 +1600,8 @@ function Text(props) {
1260
1600
  }
1261
1601
  const redacted = takeModifier(modifiers, "redacted") ?? environment.redacted;
1262
1602
  const content = segments.map((segment) => "text" in segment ? segment.text : "").join("");
1263
- const plain = segments.length <= 1 && (segments.length === 0 || "text" in segments[0] && isPlainStyle(segments[0].style));
1264
- if (plain) return applyModifiers(placeholder(sb("text", { text: content }), redacted), modifiers);
1603
+ const plain2 = segments.length <= 1 && (segments.length === 0 || "text" in segments[0] && isPlainStyle(segments[0].style));
1604
+ if (plain2) return applyModifiers(placeholder(sb("text", { text: content }), redacted), modifiers);
1265
1605
  const runs = segments.map((segment) => {
1266
1606
  const run = { ...segment.style };
1267
1607
  if ("text" in segment) run["text"] = segment.text;
@@ -1401,7 +1741,7 @@ function NavigationLink(props) {
1401
1741
  else navigation.push(own.value);
1402
1742
  };
1403
1743
  const style = takeModifier(modifiers, "buttonStyle") ?? environment.buttonStyle;
1404
- const linkProps = style === "plain" || style === "borderless" ? { style: "plain" } : {};
1744
+ const linkProps = style === "plain" ? { style: "plain" } : {};
1405
1745
  return applyModifiers(sb("navlink", linkProps, { tap }, label), modifiers);
1406
1746
  }
1407
1747
  const SplitSelectionContext = createContext(null);
@@ -1712,6 +2052,8 @@ function toolbarPlacement(name) {
1712
2052
  return "largeTitle";
1713
2053
  case "bottomBar":
1714
2054
  return "bottom";
2055
+ case "keyboard":
2056
+ return "keyboard";
1715
2057
  case "secondaryAction":
1716
2058
  return "secondary";
1717
2059
  default:
@@ -1774,6 +2116,7 @@ const MODIFIER_KEYS = /* @__PURE__ */ new Set([
1774
2116
  "buttonStyle",
1775
2117
  "controlSize",
1776
2118
  "pickerStyle",
2119
+ "toggleStyle",
1777
2120
  "labelsHidden",
1778
2121
  "navigationTitle",
1779
2122
  "navigationBarTitleDisplayMode",
@@ -1787,11 +2130,14 @@ const MODIFIER_KEYS = /* @__PURE__ */ new Set([
1787
2130
  "confirmationDialog",
1788
2131
  "presentationDetents",
1789
2132
  "onScrollVisibilityChange",
2133
+ "onGeometryChange",
1790
2134
  "onTapGesture",
1791
2135
  "dragGesture",
1792
2136
  "magnifyGesture",
1793
2137
  "rotateGesture",
1794
2138
  "onLongPressGesture",
2139
+ "simultaneousGesture",
2140
+ "highPriorityGesture",
1795
2141
  "contextMenu",
1796
2142
  "swipeActions",
1797
2143
  "onAppear",
@@ -1814,6 +2160,7 @@ const MODIFIER_KEYS = /* @__PURE__ */ new Set([
1814
2160
  "position",
1815
2161
  "zIndex",
1816
2162
  "allowsHitTesting",
2163
+ "contentShape",
1817
2164
  "ignoresSafeArea",
1818
2165
  "rotation3DEffect",
1819
2166
  "gridCellAnchor",
@@ -1848,6 +2195,7 @@ const MODIFIER_KEYS = /* @__PURE__ */ new Set([
1848
2195
  "textCase",
1849
2196
  "coordinateSpace",
1850
2197
  "contentTransition",
2198
+ "scrollDisabled",
1851
2199
  "scrollEdgeEffectStyle",
1852
2200
  "scrollEdgeEffectHidden",
1853
2201
  "contentMargins",
@@ -1874,6 +2222,72 @@ function takeModifier(modifiers, key) {
1874
2222
  function styled(style, child) {
1875
2223
  return sb("styled", { style }, void 0, child);
1876
2224
  }
2225
+ function gesture(key, value, node, mode) {
2226
+ const base = { drag: false, longPress: false, minimumDistance: 10, minimumDuration: 0.5 };
2227
+ if (mode) base["mode"] = mode;
2228
+ switch (key) {
2229
+ case "dragGesture":
2230
+ return createElement(DragGestureElement, { spec: value, mode }, node);
2231
+ case "magnifyGesture":
2232
+ case "rotateGesture": {
2233
+ const pinch = value;
2234
+ if (key === "magnifyGesture") {
2235
+ base["magnify"] = true;
2236
+ base["minimumScaleDelta"] = pinch.minimumScaleDelta ?? 0.01;
2237
+ } else {
2238
+ base["rotate"] = true;
2239
+ base["minimumAngleDelta"] = pinch.minimumAngleDelta ?? 1;
2240
+ }
2241
+ return sb("gesture", base, {
2242
+ pinch: (event) => {
2243
+ const pinchValue = {
2244
+ magnification: event.scale,
2245
+ rotation: event.rotation,
2246
+ magnificationVelocity: event.scaleVelocity,
2247
+ rotationVelocity: event.rotationVelocity,
2248
+ startLocation: event.startLocation,
2249
+ startAnchor: event.startAnchor,
2250
+ location: event.location ?? event.startLocation
2251
+ };
2252
+ if (event.phase === "ended" || event.phase === "cancelled") pinch.onEnded?.(pinchValue);
2253
+ else pinch.onChanged?.(pinchValue);
2254
+ }
2255
+ }, node);
2256
+ }
2257
+ case "onLongPressGesture": {
2258
+ const spec = typeof value === "function" ? { action: value } : value;
2259
+ base["longPress"] = true;
2260
+ base["minimumDuration"] = spec.minimumDuration ?? 0.5;
2261
+ return sb("gesture", base, { longpress: () => spec.action() }, node);
2262
+ }
2263
+ }
2264
+ }
2265
+ function DragGestureElement({ spec, mode, children }) {
2266
+ const spaces = useCoordinateSpaces();
2267
+ const space = spec.coordinateSpace ?? "local";
2268
+ const named = typeof space === "object" ? spaces[space.named] : null;
2269
+ const props = { drag: true, longPress: false, minimumDistance: spec.minimumDistance ?? 10, minimumDuration: 0.5 };
2270
+ if (space !== "local") props["coordinateSpace"] = "global";
2271
+ if (mode) props["mode"] = mode;
2272
+ const inSpace = (point) => named ? { x: point.x - named.x, y: point.y - named.y } : point;
2273
+ return sb(
2274
+ "gesture",
2275
+ props,
2276
+ {
2277
+ drag: (event) => {
2278
+ const dragValue = {
2279
+ translation: event.translation,
2280
+ location: inSpace(event.location),
2281
+ startLocation: inSpace(event.startLocation),
2282
+ velocity: event.velocity
2283
+ };
2284
+ if (event.phase === "ended" || event.phase === "cancelled") spec.onEnded?.(dragValue);
2285
+ else spec.onChanged?.(dragValue);
2286
+ }
2287
+ },
2288
+ children
2289
+ );
2290
+ }
1877
2291
  function decorated(role, alignment, content, decoration) {
1878
2292
  return sb("decorated", { role, alignment }, void 0, [
1879
2293
  createElement(Fragment, { key: "content" }, content),
@@ -1956,6 +2370,7 @@ const STYLED_KEYS = /* @__PURE__ */ new Set([
1956
2370
  "position",
1957
2371
  "zIndex",
1958
2372
  "allowsHitTesting",
2373
+ "contentShape",
1959
2374
  "ignoresSafeArea",
1960
2375
  "rotation3DEffect",
1961
2376
  "gridCellAnchor",
@@ -1964,12 +2379,14 @@ const STYLED_KEYS = /* @__PURE__ */ new Set([
1964
2379
  "pointerStyle",
1965
2380
  "matchedGeometryEffect",
1966
2381
  "containerRelativeFrame",
1967
- "contentTransition"
2382
+ "contentTransition",
2383
+ "scrollDisabled"
1968
2384
  ]);
1969
2385
  const ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
1970
2386
  "buttonStyle",
1971
2387
  "controlSize",
1972
2388
  "pickerStyle",
2389
+ "toggleStyle",
1973
2390
  "labelsHidden",
1974
2391
  "refreshable",
1975
2392
  "submitLabel",
@@ -2095,13 +2512,15 @@ function apply(key, value, node, sides) {
2095
2512
  case "gridCellColumns":
2096
2513
  return styled({ gridCellColumns: value }, node);
2097
2514
  case "buttonStyle":
2098
- return withEnvironment({ buttonStyle: value === "borderless" ? "plain" : value }, node);
2515
+ return withEnvironment({ buttonStyle: value }, node);
2099
2516
  case "controlSize":
2100
2517
  return withEnvironment({ controlSize: value }, node);
2101
2518
  case "symbolRenderingMode":
2102
2519
  return withEnvironment({ symbolRenderingMode: value }, node);
2103
2520
  case "pickerStyle":
2104
2521
  return withEnvironment({ pickerStyle: value }, node);
2522
+ case "toggleStyle":
2523
+ return withEnvironment({ toggleStyle: value }, node);
2105
2524
  case "labelsHidden":
2106
2525
  return withEnvironment({ labelsHidden: Boolean(value) }, node);
2107
2526
  case "navigationTitle":
@@ -2141,62 +2560,31 @@ function apply(key, value, node, sides) {
2141
2560
  const spec = typeof value === "function" ? { action: value } : value;
2142
2561
  return sb("styled", { style: { scrollVisibility: { threshold: spec.threshold ?? 0.5 } } }, { scrollvisibility: (event) => spec.action(event.visible) }, node);
2143
2562
  }
2144
- case "onTapGesture":
2145
- return sb("tapgesture", {}, { tap: value }, node);
2146
- case "dragGesture": {
2147
- const drag = value;
2148
- return sb(
2149
- "gesture",
2150
- { drag: true, longPress: false, minimumDistance: drag.minimumDistance ?? 10, minimumDuration: 0.5 },
2151
- {
2152
- drag: (event) => {
2153
- const dragValue = {
2154
- translation: event.translation,
2155
- location: event.location,
2156
- startLocation: event.startLocation,
2157
- velocity: event.velocity
2158
- };
2159
- if (event.phase === "ended" || event.phase === "cancelled") drag.onEnded?.(dragValue);
2160
- else drag.onChanged?.(dragValue);
2161
- }
2162
- },
2163
- node
2164
- );
2563
+ case "onTapGesture": {
2564
+ const spec = typeof value === "function" ? { action: value } : value;
2565
+ const count = Math.floor(spec.count ?? 1);
2566
+ const props = count > 1 ? { count } : {};
2567
+ if ("perform" in spec) {
2568
+ const { perform } = spec;
2569
+ return sb("tapgesture", { ...props, location: true }, { tap: (event) => perform({ x: event.x ?? 0, y: event.y ?? 0 }) }, node);
2570
+ }
2571
+ return sb("tapgesture", props, { tap: () => spec.action() }, node);
2165
2572
  }
2573
+ case "onGeometryChange":
2574
+ return createElement(GeometryChangeView, { handler: value }, node);
2575
+ case "dragGesture":
2166
2576
  case "magnifyGesture":
2167
- case "rotateGesture": {
2168
- const pinch = value;
2169
- const props = { drag: false, longPress: false, minimumDistance: 10, minimumDuration: 0.5 };
2170
- if (key === "magnifyGesture") {
2171
- props["magnify"] = true;
2172
- props["minimumScaleDelta"] = pinch.minimumScaleDelta ?? 0.01;
2173
- } else {
2174
- props["rotate"] = true;
2175
- props["minimumAngleDelta"] = pinch.minimumAngleDelta ?? 1;
2577
+ case "rotateGesture":
2578
+ case "onLongPressGesture":
2579
+ return gesture(key, value, node);
2580
+ case "simultaneousGesture":
2581
+ case "highPriorityGesture": {
2582
+ const mode = key === "simultaneousGesture" ? "simultaneous" : "highPriority";
2583
+ let result = node;
2584
+ for (const [gestureKey, gestureValue] of Object.entries(value)) {
2585
+ if (gestureValue !== void 0) result = gesture(gestureKey, gestureValue, result, mode);
2176
2586
  }
2177
- return sb("gesture", props, {
2178
- pinch: (event) => {
2179
- const pinchValue = {
2180
- magnification: event.scale,
2181
- rotation: event.rotation,
2182
- magnificationVelocity: event.scaleVelocity,
2183
- rotationVelocity: event.rotationVelocity,
2184
- startLocation: event.startLocation,
2185
- startAnchor: event.startAnchor
2186
- };
2187
- if (event.phase === "ended" || event.phase === "cancelled") pinch.onEnded?.(pinchValue);
2188
- else pinch.onChanged?.(pinchValue);
2189
- }
2190
- }, node);
2191
- }
2192
- case "onLongPressGesture": {
2193
- const spec = typeof value === "function" ? { action: value } : value;
2194
- return sb(
2195
- "gesture",
2196
- { drag: false, longPress: true, minimumDistance: 10, minimumDuration: spec.minimumDuration ?? 0.5 },
2197
- { longpress: () => spec.action() },
2198
- node
2199
- );
2587
+ return result;
2200
2588
  }
2201
2589
  case "contextMenu":
2202
2590
  return sb("contextmenu", {}, void 0, [createElement(Fragment, { key: "content" }, node), createElement(Fragment, { key: "items" }, value)]);
@@ -2265,6 +2653,8 @@ function apply(key, value, node, sides) {
2265
2653
  return styled({ zIndex: value }, node);
2266
2654
  case "allowsHitTesting":
2267
2655
  return value === false ? styled({ hitTesting: false }, node) : node;
2656
+ case "contentShape":
2657
+ return value === "rectangle" ? styled({ contentShape: "rectangle" }, node) : node;
2268
2658
  case "ignoresSafeArea":
2269
2659
  return styled(
2270
2660
  value === true ? { ignoresSafeArea: "all", ignoresKeyboard: true } : { ignoresSafeArea: value },
@@ -2387,6 +2777,8 @@ function apply(key, value, node, sides) {
2387
2777
  const spec = typeof value === "function" ? { transition: value } : value;
2388
2778
  return createElement(ScrollTransitionView, { spec }, node);
2389
2779
  }
2780
+ case "scrollDisabled":
2781
+ return styled({ scrollDisabled: value === true }, node);
2390
2782
  case "contentTransition": {
2391
2783
  if (value === "identity") return node;
2392
2784
  if (typeof value === "object") {
@@ -2477,6 +2869,43 @@ function GeometryReader(props) {
2477
2869
  modifiers
2478
2870
  );
2479
2871
  }
2872
+ function GeometryChangeView({ handler, children }) {
2873
+ const [reported, setReported] = useState(NO_GEOMETRY);
2874
+ const [needsFrame, setNeedsFrame] = useState(false);
2875
+ const last = useRef(null);
2876
+ const spaces = useCoordinateSpaces();
2877
+ let readFrame = false;
2878
+ const proxy = geometryProxy(reported, spaces, () => {
2879
+ readFrame = true;
2880
+ });
2881
+ const heard = reported !== NO_GEOMETRY;
2882
+ const value = heard && typeof handler !== "function" ? handler.of(proxy) : void 0;
2883
+ useEffect(() => {
2884
+ if (readFrame && !needsFrame) setNeedsFrame(true);
2885
+ });
2886
+ useEffect(() => {
2887
+ if (!heard) return;
2888
+ if (typeof handler === "function") {
2889
+ handler(proxy);
2890
+ return;
2891
+ }
2892
+ const previous = last.current;
2893
+ if (previous && JSON.stringify(previous.value) === JSON.stringify(value)) return;
2894
+ last.current = { value };
2895
+ handler.action(value, previous ? previous.value : value);
2896
+ });
2897
+ return sb(
2898
+ "zstack",
2899
+ { alignment: "center", reportsSize: true, ...needsFrame ? { reportsFrame: true } : {} },
2900
+ {
2901
+ geometry: (event) => {
2902
+ const next = nextGeometry(event, reported);
2903
+ if (JSON.stringify(next) !== JSON.stringify(reported)) setReported(next);
2904
+ }
2905
+ },
2906
+ children
2907
+ );
2908
+ }
2480
2909
  const DEFAULT_VIEW_SPACING = { distance: () => 8 };
2481
2910
  function subviewTraits(children) {
2482
2911
  return flattenChildren(children).map((element) => {
@@ -2675,38 +3104,9 @@ function effectStyle(values) {
2675
3104
  if (values.hueRotation !== 0) style.hueRotation = round(values.hueRotation);
2676
3105
  return style;
2677
3106
  }
2678
- function interpolateEffects(from, to, t) {
2679
- const mix = (a, b) => a + (b - a) * t;
2680
- const result = {
2681
- offsetX: mix(from.offsetX, to.offsetX),
2682
- offsetY: mix(from.offsetY, to.offsetY),
2683
- scaleX: mix(from.scaleX, to.scaleX),
2684
- scaleY: mix(from.scaleY, to.scaleY),
2685
- rotation: mix(from.rotation, to.rotation),
2686
- opacity: mix(from.opacity, to.opacity),
2687
- blur: mix(from.blur, to.blur),
2688
- brightness: mix(from.brightness, to.brightness),
2689
- contrast: mix(from.contrast, to.contrast),
2690
- saturation: mix(from.saturation, to.saturation),
2691
- grayscale: mix(from.grayscale, to.grayscale),
2692
- hueRotation: mix(from.hueRotation, to.hueRotation)
2693
- };
2694
- const scaleAnchor = to.scaleAnchor ?? from.scaleAnchor;
2695
- if (scaleAnchor) result.scaleAnchor = scaleAnchor;
2696
- const rotationAnchor = to.rotationAnchor ?? from.rotationAnchor;
2697
- if (rotationAnchor) result.rotationAnchor = rotationAnchor;
2698
- const rotation3D = to.rotation3D ?? from.rotation3D;
2699
- if (rotation3D) result.rotation3D = { ...rotation3D, angle: mix(from.rotation3D?.angle ?? 0, to.rotation3D?.angle ?? 0) };
2700
- return result;
2701
- }
2702
- function EffectBox({
2703
- render,
2704
- alwaysFrame,
2705
- animationToken,
2706
- children
2707
- }) {
3107
+ function EffectBox({ render, children }) {
2708
3108
  const [reported, setReported] = useState(NO_GEOMETRY);
2709
- const [needsFrame, setNeedsFrame] = useState(alwaysFrame);
3109
+ const [needsFrame, setNeedsFrame] = useState(false);
2710
3110
  let readFrame = false;
2711
3111
  const style = render(reported, () => {
2712
3112
  readFrame = true;
@@ -2714,8 +3114,7 @@ function EffectBox({
2714
3114
  useEffect(() => {
2715
3115
  if (readFrame && !needsFrame) setNeedsFrame(true);
2716
3116
  });
2717
- let content = Object.keys(style).length > 0 ? sb("styled", { style }, void 0, children) : children;
2718
- if (animationToken) content = sb("styled", { style: { animation: { type: "default" }, animationToken: animationToken(reported) } }, void 0, content);
3117
+ const content = Object.keys(style).length > 0 ? sb("styled", { style }, void 0, children) : children;
2719
3118
  return sb(
2720
3119
  "zstack",
2721
3120
  { alignment: "center", reportsSize: true, ...needsFrame ? { reportsFrame: true } : {} },
@@ -2730,45 +3129,23 @@ function EffectBox({
2730
3129
  }
2731
3130
  function VisualEffectView({ effect, children }) {
2732
3131
  const spaces = useCoordinateSpaces();
2733
- return /* @__PURE__ */ jsx(EffectBox, { alwaysFrame: false, render: (reported, markRead) => effectStyle(effect(EMPTY_EFFECT, geometryProxy(reported, spaces, markRead)).values), children });
3132
+ return /* @__PURE__ */ jsx(EffectBox, { render: (reported, markRead) => effectStyle(effect(EMPTY_EFFECT, geometryProxy(reported, spaces, markRead)).values), children });
2734
3133
  }
2735
3134
  const PHASES = {
2736
3135
  topLeading: { phase: "topLeading", value: -1, isIdentity: false },
2737
3136
  identity: { phase: "identity", value: 0, isIdentity: true },
2738
3137
  bottomTrailing: { phase: "bottomTrailing", value: 1, isIdentity: false }
2739
3138
  };
2740
- function scrollProgress(reported, axis) {
2741
- const view = reported.scrollView;
2742
- const origin = reported.origin;
2743
- if (!view || !origin) return 0;
2744
- const horizontal = (axis ?? view.axis) === "horizontal";
2745
- const start = horizontal ? origin.x : origin.y;
2746
- const length = horizontal ? reported.size.width : reported.size.height;
2747
- const viewStart = horizontal ? view.x : view.y;
2748
- const viewEnd = viewStart + (horizontal ? view.width : view.height);
2749
- if (length <= 0) return 0;
2750
- if (start < viewStart) return -Math.min(1, (viewStart - start) / length);
2751
- if (start + length > viewEnd) return Math.min(1, (start + length - viewEnd) / length);
2752
- return 0;
2753
- }
2754
- function scrollTransitionValues(spec, progress) {
2755
- const identity = spec.transition(EMPTY_EFFECT, PHASES.identity).values;
2756
- const configuration = spec.configuration ?? "interactive";
2757
- if (configuration === "identity" || progress === 0) return identity;
2758
- const edge = spec.transition(EMPTY_EFFECT, progress < 0 ? PHASES.topLeading : PHASES.bottomTrailing).values;
2759
- if (configuration === "animated") return edge;
2760
- return interpolateEffects(identity, edge, Math.abs(progress));
2761
- }
2762
3139
  function ScrollTransitionView({ spec, children }) {
2763
- return /* @__PURE__ */ jsx(
2764
- EffectBox,
2765
- {
2766
- alwaysFrame: true,
2767
- render: (reported) => effectStyle(scrollTransitionValues(spec, scrollProgress(reported, spec.axis))),
2768
- ...spec.configuration === "animated" ? { animationToken: (reported) => Math.sign(scrollProgress(reported, spec.axis)) + 1 } : {},
2769
- children
2770
- }
2771
- );
3140
+ const phase = (which) => effectStyle(spec.transition(EMPTY_EFFECT, PHASES[which]).values);
3141
+ const transition = {
3142
+ configuration: spec.configuration ?? "interactive",
3143
+ identity: phase("identity"),
3144
+ topLeading: phase("topLeading"),
3145
+ bottomTrailing: phase("bottomTrailing")
3146
+ };
3147
+ if (spec.axis) transition.axis = spec.axis;
3148
+ return sb("styled", { style: { scrollTransition: transition } }, void 0, children);
2772
3149
  }
2773
3150
  const r = (n) => {
2774
3151
  const v = Math.round(n * 100) / 100;
@@ -2907,12 +3284,398 @@ class Path {
2907
3284
  return this.svg();
2908
3285
  }
2909
3286
  }
3287
+ const plain = (text) => text.map((run) => run.text).join("");
3288
+ function attributesOf(run) {
3289
+ if (!run) return {};
3290
+ const { text: _text, ...attributes } = run;
3291
+ return attributes;
3292
+ }
3293
+ function alike(a, b) {
3294
+ return !a.bold === !b.bold && !a.italic === !b.italic && !a.underline === !b.underline && !a.strikethrough === !b.strikethrough && JSON.stringify(a.foregroundColor ?? null) === JSON.stringify(b.foregroundColor ?? null) && JSON.stringify(a.backgroundColor ?? null) === JSON.stringify(b.backgroundColor ?? null) && JSON.stringify(runFont(a.font) ?? null) === JSON.stringify(runFont(b.font) ?? null);
3295
+ }
3296
+ function runFont(font2) {
3297
+ if (!font2) return void 0;
3298
+ const out = {};
3299
+ if (typeof font2.size === "number" && Number.isFinite(font2.size) && font2.size > 0) out.size = font2.size;
3300
+ if (font2.weight) out.weight = font2.weight;
3301
+ if (font2.design) out.design = font2.design;
3302
+ return Object.keys(out).length > 0 ? out : void 0;
3303
+ }
3304
+ function normalized(text) {
3305
+ const runs = [];
3306
+ for (const run of text) {
3307
+ if (!run.text) continue;
3308
+ const last = runs[runs.length - 1];
3309
+ if (last && alike(last, run)) runs[runs.length - 1] = { ...last, text: last.text + run.text };
3310
+ else runs.push(run);
3311
+ }
3312
+ return runs;
3313
+ }
3314
+ function slice(text, start, end = Infinity) {
3315
+ const runs = [];
3316
+ let at = 0;
3317
+ for (const run of text) {
3318
+ const from = Math.max(start, at);
3319
+ const to = Math.min(end, at + run.text.length);
3320
+ if (from < to) runs.push({ ...run, text: run.text.slice(from - at, to - at) });
3321
+ at += run.text.length;
3322
+ }
3323
+ return runs;
3324
+ }
3325
+ function attributesAt(text, offset) {
3326
+ let at = 0;
3327
+ for (const run of text) {
3328
+ if (offset < at + run.text.length) return attributesOf(run);
3329
+ at += run.text.length;
3330
+ }
3331
+ return attributesOf([...text].reverse().find((run) => run.text.length > 0));
3332
+ }
3333
+ function replacingCharacters(text, next) {
3334
+ const old = plain(text);
3335
+ if (old === next) return text;
3336
+ let head = 0;
3337
+ const shorter = Math.min(old.length, next.length);
3338
+ while (head < shorter && old[head] === next[head]) head++;
3339
+ let tail = 0;
3340
+ while (tail < shorter - head && old[old.length - 1 - tail] === next[next.length - 1 - tail]) tail++;
3341
+ const removedEnd = old.length - tail;
3342
+ const typed = next.slice(head, next.length - tail);
3343
+ const attributes = attributesAt(text, removedEnd > head || head === 0 ? head : head - 1);
3344
+ return normalized([...slice(text, 0, head), { ...attributes, text: typed }, ...slice(text, removedEnd)]);
3345
+ }
3346
+ function settingAttributes(text, start, end, change) {
3347
+ const inside = slice(text, start, end).map((run) => ({ ...run, ...change }));
3348
+ return normalized([...slice(text, 0, start), ...inside, ...slice(text, end)]);
3349
+ }
3350
+ function every(text, start, end, key) {
3351
+ const inside = slice(text, start, end);
3352
+ return inside.length > 0 && inside.every((run) => run[key] === true);
3353
+ }
3354
+ const AttributedText = {
3355
+ plain,
3356
+ normalized,
3357
+ slice,
3358
+ attributesAt,
3359
+ replacingCharacters,
3360
+ settingAttributes,
3361
+ every,
3362
+ /** A text of one run. */
3363
+ of: (text, attributes = {}) => normalized([{ ...attributes, text }])
3364
+ };
3365
+ const BOLD_WEIGHTS = /* @__PURE__ */ new Set(["semibold", "bold", "heavy", "black"]);
3366
+ function fromProtocolRuns(runs) {
3367
+ return normalized(
3368
+ runs.map((run) => {
3369
+ const out = { text: run.text ?? "" };
3370
+ const font2 = run.font ?? {};
3371
+ const bold = typeof font2.weight === "string" && BOLD_WEIGHTS.has(font2.weight);
3372
+ if (bold) out.bold = true;
3373
+ const own = runFont({
3374
+ ...typeof font2.size === "number" ? { size: font2.size } : {},
3375
+ ...typeof font2.weight === "string" && !bold ? { weight: font2.weight } : {},
3376
+ ...typeof font2.design === "string" ? { design: font2.design } : {}
3377
+ });
3378
+ if (own) out.font = own;
3379
+ if (run.italic) out.italic = true;
3380
+ if (run.underline) out.underline = true;
3381
+ if (run.strikethrough) out.strikethrough = true;
3382
+ if (run.foreground !== void 0) out.foregroundColor = run.foreground;
3383
+ if (run.background !== void 0) out.backgroundColor = run.background;
3384
+ return out;
3385
+ })
3386
+ );
3387
+ }
3388
+ function protocolRuns(text) {
3389
+ return text.map((run) => {
3390
+ const out = { text: run.text };
3391
+ const font2 = { ...runFont(run.font) ?? {} };
3392
+ if (run.bold && !font2.weight) font2.weight = "bold";
3393
+ if (Object.keys(font2).length > 0) out.font = font2;
3394
+ if (run.italic) out.italic = true;
3395
+ if (run.underline) out.underline = {};
3396
+ if (run.strikethrough) out.strikethrough = {};
3397
+ if (run.foregroundColor !== void 0) out.foreground = color(run.foregroundColor);
3398
+ if (run.backgroundColor !== void 0) out.background = color(run.backgroundColor);
3399
+ return out;
3400
+ });
3401
+ }
3402
+ function useAudioRecorder(options = {}) {
3403
+ const interval = options.meterInterval ?? 50;
3404
+ const host = browserEnvironment.audioRecorder;
3405
+ const [status, setStatus] = useState("idle");
3406
+ const [time, setTime] = useState(0);
3407
+ const [levels, setLevels] = useState([]);
3408
+ const [error, setError] = useState(null);
3409
+ const statusRef = useRef(status);
3410
+ statusRef.current = status;
3411
+ const fail = (reason) => setError(reason instanceof Error ? reason.message : String(reason));
3412
+ useEffect(() => {
3413
+ if (status !== "recording" || !host) return;
3414
+ let reading = false;
3415
+ let live = true;
3416
+ const timer = setInterval(() => {
3417
+ if (reading) return;
3418
+ reading = true;
3419
+ host.meter().then((meter) => {
3420
+ if (!live) return;
3421
+ setTime(meter.time);
3422
+ setLevels((previous) => [...previous, meter.level]);
3423
+ }).catch(() => void 0).finally(() => {
3424
+ reading = false;
3425
+ });
3426
+ }, interval);
3427
+ return () => {
3428
+ live = false;
3429
+ clearInterval(timer);
3430
+ };
3431
+ }, [status, host, interval]);
3432
+ useEffect(
3433
+ () => () => {
3434
+ if (statusRef.current !== "idle") void host?.cancel().catch(() => void 0);
3435
+ },
3436
+ [host]
3437
+ );
3438
+ const record = useCallback(async () => {
3439
+ if (!host) {
3440
+ setError("unsupported: this page cannot record");
3441
+ return;
3442
+ }
3443
+ if (statusRef.current !== "idle") return;
3444
+ setError(null);
3445
+ setTime(0);
3446
+ setLevels([]);
3447
+ setStatus("starting");
3448
+ try {
3449
+ await host.start();
3450
+ setStatus("recording");
3451
+ } catch (reason) {
3452
+ fail(reason);
3453
+ setStatus("idle");
3454
+ }
3455
+ }, [host]);
3456
+ const pause = useCallback(async () => {
3457
+ if (!host || statusRef.current !== "recording") return;
3458
+ await host.pause().catch(fail);
3459
+ setStatus("paused");
3460
+ }, [host]);
3461
+ const resume = useCallback(async () => {
3462
+ if (!host || statusRef.current !== "paused") return;
3463
+ await host.resume().catch(fail);
3464
+ setStatus("recording");
3465
+ }, [host]);
3466
+ const stop = useCallback(async () => {
3467
+ if (!host || statusRef.current !== "recording" && statusRef.current !== "paused") return null;
3468
+ setStatus("stopping");
3469
+ try {
3470
+ const recording = await host.stop();
3471
+ setTime(recording.duration);
3472
+ return recording;
3473
+ } catch (reason) {
3474
+ fail(reason);
3475
+ return null;
3476
+ } finally {
3477
+ setStatus("idle");
3478
+ }
3479
+ }, [host]);
3480
+ const cancel = useCallback(async () => {
3481
+ if (!host || statusRef.current === "idle") return;
3482
+ await host.cancel().catch(fail);
3483
+ setStatus("idle");
3484
+ setTime(0);
3485
+ setLevels([]);
3486
+ }, [host]);
3487
+ const discard = useCallback(async (url) => {
3488
+ await host?.discard(url).catch(fail);
3489
+ }, [host]);
3490
+ const trim = useCallback(
3491
+ async (url, keep) => {
3492
+ if (!host) throw new Error("unsupported: this page cannot cut audio");
3493
+ try {
3494
+ return await host.trim(url, keep);
3495
+ } catch (reason) {
3496
+ fail(reason);
3497
+ throw reason;
3498
+ }
3499
+ },
3500
+ [host]
3501
+ );
3502
+ return { available: host !== void 0, status, time, levels, error, record, pause, resume, stop, cancel, discard, trim };
3503
+ }
3504
+ const stores = /* @__PURE__ */ new WeakMap();
3505
+ function entryFor(store, key) {
3506
+ let entries = stores.get(store);
3507
+ if (!entries) {
3508
+ entries = /* @__PURE__ */ new Map();
3509
+ stores.set(store, entries);
3510
+ if (store === pageStorage) watchOtherTabs();
3511
+ }
3512
+ let entry = entries.get(key);
3513
+ if (!entry) {
3514
+ let raw = null;
3515
+ try {
3516
+ raw = store.getItem(key);
3517
+ } catch {
3518
+ }
3519
+ entry = { raw, value: void 0, decoded: false, decode: void 0, listeners: /* @__PURE__ */ new Set() };
3520
+ entries.set(key, entry);
3521
+ }
3522
+ return entry;
3523
+ }
3524
+ function valueOf(entry, absent) {
3525
+ if (entry.raw === null) return absent;
3526
+ if (!entry.decoded) {
3527
+ try {
3528
+ const parsed = JSON.parse(entry.raw);
3529
+ entry.value = entry.decode ? entry.decode(parsed) : parsed;
3530
+ } catch {
3531
+ entry.raw = null;
3532
+ return absent;
3533
+ }
3534
+ entry.decoded = true;
3535
+ }
3536
+ return entry.value;
3537
+ }
3538
+ function notify(entry) {
3539
+ for (const listener of [...entry.listeners]) listener();
3540
+ }
3541
+ function invalidate(entry, raw) {
3542
+ if (entry.raw === raw && entry.decoded) return;
3543
+ entry.raw = raw;
3544
+ entry.decoded = false;
3545
+ notify(entry);
3546
+ }
3547
+ function readPageStorage() {
3548
+ try {
3549
+ const storage = globalThis.localStorage;
3550
+ return storage && typeof storage.getItem === "function" ? storage : null;
3551
+ } catch {
3552
+ return null;
3553
+ }
3554
+ }
3555
+ const memory = /* @__PURE__ */ new Map();
3556
+ const memoryStorage = {
3557
+ getItem: (key) => memory.get(key) ?? null,
3558
+ setItem: (key, value) => void memory.set(key, value),
3559
+ removeItem: (key) => void memory.delete(key)
3560
+ };
3561
+ const pageStorage = readPageStorage();
3562
+ const appStorage = pageStorage ?? memoryStorage;
3563
+ let watching = false;
3564
+ function watchOtherTabs() {
3565
+ if (watching || !pageStorage) return;
3566
+ watching = true;
3567
+ const target = globalThis;
3568
+ if (typeof target.addEventListener !== "function") return;
3569
+ target.addEventListener("storage", (event) => {
3570
+ if (event.storageArea !== pageStorage) return;
3571
+ const entries = stores.get(pageStorage);
3572
+ if (!entries) return;
3573
+ if (event.key === null) {
3574
+ for (const entry of entries.values()) invalidate(entry, null);
3575
+ } else {
3576
+ const entry = entries.get(event.key);
3577
+ if (entry) invalidate(entry, event.newValue);
3578
+ }
3579
+ });
3580
+ }
3581
+ function useAppStorage(key, defaultValue, options = {}) {
3582
+ const { store = appStorage, decode, onError } = options;
3583
+ const [fallback] = useState(defaultValue);
3584
+ const entry = entryFor(store, key);
3585
+ entry.decode = decode;
3586
+ const subscribe = useCallback(
3587
+ (listener) => {
3588
+ const subscribed = entryFor(store, key);
3589
+ subscribed.listeners.add(listener);
3590
+ return () => void subscribed.listeners.delete(listener);
3591
+ },
3592
+ [store, key]
3593
+ );
3594
+ const read = useCallback(() => valueOf(entryFor(store, key), fallback), [store, key, fallback]);
3595
+ const value = useSyncExternalStore(subscribe, read, read);
3596
+ const set = useCallback(
3597
+ (next) => {
3598
+ const target = entryFor(store, key);
3599
+ const current = valueOf(target, fallback);
3600
+ const value2 = typeof next === "function" ? next(current) : next;
3601
+ const raw = value2 === void 0 ? null : JSON.stringify(value2);
3602
+ try {
3603
+ if (raw === null) store.removeItem(key);
3604
+ else store.setItem(key, raw);
3605
+ } catch (error) {
3606
+ onError?.(error);
3607
+ }
3608
+ target.raw = raw;
3609
+ target.value = value2;
3610
+ target.decoded = raw !== null;
3611
+ notify(target);
3612
+ },
3613
+ [store, key, fallback, onError]
3614
+ );
3615
+ return [value, set];
3616
+ }
3617
+ const NONE = {
3618
+ documents: "file:///data/Documents",
3619
+ applicationSupport: "file:///data/Library/Application%20Support",
3620
+ caches: "file:///data/Library/Caches",
3621
+ temporary: "file:///tmp"
3622
+ };
3623
+ async function bytesOf(contents) {
3624
+ if (typeof contents === "string") return new TextEncoder().encode(contents);
3625
+ if (contents instanceof Uint8Array) return contents;
3626
+ if (contents instanceof ArrayBuffer) return new Uint8Array(contents);
3627
+ const blob = contents;
3628
+ if (typeof blob.bytes === "function") return await blob.bytes();
3629
+ return new Uint8Array(await blob.arrayBuffer());
3630
+ }
3631
+ function createFileManager(host) {
3632
+ const required = () => {
3633
+ const files = host();
3634
+ if (!files) throw new Error("unsupported: this page keeps no files");
3635
+ return files;
3636
+ };
3637
+ const directories = () => host()?.directories ?? NONE;
3638
+ return {
3639
+ get available() {
3640
+ return host() !== void 0;
3641
+ },
3642
+ get documentsDirectory() {
3643
+ return directories().documents;
3644
+ },
3645
+ get applicationSupportDirectory() {
3646
+ return directories().applicationSupport;
3647
+ },
3648
+ get cachesDirectory() {
3649
+ return directories().caches;
3650
+ },
3651
+ get temporaryDirectory() {
3652
+ return directories().temporary;
3653
+ },
3654
+ url: (directory, path) => fileURL(directory, path),
3655
+ contents: async (url) => required().read(url),
3656
+ text: async (url) => new TextDecoder().decode(await required().read(url)),
3657
+ write: async (url, contents) => required().write(url, await bytesOf(contents)),
3658
+ contentsOfDirectory: async (url) => required().list(url),
3659
+ createDirectory: async (url) => required().createDirectory(url),
3660
+ fileExists: async (url) => await required().attributes(url) !== null,
3661
+ attributesOfItem: async (url) => {
3662
+ const attributes = await required().attributes(url);
3663
+ if (!attributes) throw new Error(`notFound: nothing at ${url}`);
3664
+ return attributes;
3665
+ },
3666
+ moveItem: async (from, to) => required().move(from, to),
3667
+ copyItem: async (from, to) => required().copy(from, to),
3668
+ removeItem: async (url) => required().remove(url),
3669
+ loadURL: async (url) => required().loadURL(url)
3670
+ };
3671
+ }
3672
+ const fileManager = createFileManager(() => browserEnvironment.files);
2910
3673
  function Button(props) {
2911
3674
  const { own, modifiers } = splitProps(props);
2912
3675
  const environment = useViewEnvironment();
2913
3676
  const ownStyle = takeModifier(modifiers, "buttonStyle");
2914
3677
  const ownSize = takeModifier(modifiers, "controlSize");
2915
- const style = ownStyle === "borderless" ? "plain" : ownStyle ?? environment.buttonStyle ?? "automatic";
3678
+ const style = ownStyle ?? environment.buttonStyle ?? "automatic";
2916
3679
  const controlSize = ownSize ?? environment.controlSize;
2917
3680
  const buttonProps = { role: own.role ?? null, style };
2918
3681
  if (controlSize !== void 0 && controlSize !== "regular") buttonProps["controlSize"] = controlSize;
@@ -2930,9 +3693,13 @@ function Button(props) {
2930
3693
  }
2931
3694
  function Toggle(props) {
2932
3695
  const { own, modifiers } = splitProps(props);
3696
+ const environment = useViewEnvironment();
3697
+ const style = takeModifier(modifiers, "toggleStyle") ?? environment.toggleStyle ?? "automatic";
2933
3698
  const [isOn, setOn] = readBinding(own.isOn, own.onChange);
2934
3699
  const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.children;
2935
- return applyModifiers(sb("toggle", { isOn }, { toggle: (event) => setOn(event.value) }, label), modifiers);
3700
+ const toggleProps = { isOn };
3701
+ if (style !== "automatic") toggleProps["style"] = style;
3702
+ return applyModifiers(sb("toggle", toggleProps, { toggle: (event) => setOn(event.value) }, label), modifiers);
2936
3703
  }
2937
3704
  function Picker(props) {
2938
3705
  const { own, modifiers } = splitProps(props);
@@ -3052,6 +3819,7 @@ function TextField(props) {
3052
3819
  secure: own.secure ?? false
3053
3820
  };
3054
3821
  if (own.onSubmit) fieldProps["submit"] = true;
3822
+ if (own.onDeleteBackward) fieldProps["deleteBackward"] = true;
3055
3823
  if (own.axis) fieldProps["axis"] = own.axis;
3056
3824
  const submitLabel = ownSubmitLabel ?? environment.submitLabel;
3057
3825
  if (submitLabel) fieldProps["submitLabel"] = submitLabel;
@@ -3059,12 +3827,13 @@ function TextField(props) {
3059
3827
  if (hints.contentType) fieldProps["contentType"] = hints.contentType;
3060
3828
  if (hints.autocapitalization) fieldProps["autocapitalization"] = hints.autocapitalization;
3061
3829
  if (hints.autocorrectionDisabled) fieldProps["autocorrectionDisabled"] = true;
3062
- const focus = own.focused !== void 0 ? readBinding(own.focused) : null;
3830
+ const focus = focusBinding(own);
3063
3831
  if (focus) fieldProps["focused"] = focus[0];
3064
3832
  return applyModifiers(
3065
3833
  sb("textfield", fieldProps, {
3066
3834
  text: (event) => setText(event.value),
3067
3835
  submit: () => own.onSubmit?.(),
3836
+ deletebackward: () => own.onDeleteBackward?.(),
3068
3837
  focus: (event) => focus?.[1](event.value)
3069
3838
  }),
3070
3839
  modifiers
@@ -3073,6 +3842,19 @@ function TextField(props) {
3073
3842
  function SecureField(props) {
3074
3843
  return /* @__PURE__ */ jsx(TextField, { ...props, secure: true });
3075
3844
  }
3845
+ function focusBinding(props) {
3846
+ if (props.focused === void 0) return null;
3847
+ if (props.focusedValue === void 0) return readBinding(props.focused);
3848
+ const value = props.focusedValue;
3849
+ const [current, set] = readBinding(props.focused);
3850
+ return [
3851
+ current === value,
3852
+ (on) => {
3853
+ if (on) set(value);
3854
+ else if (current === value) set(null);
3855
+ }
3856
+ ];
3857
+ }
3076
3858
  function ProgressView(props) {
3077
3859
  const { own, modifiers } = splitProps(props);
3078
3860
  if (own.value === void 0) {
@@ -3125,11 +3907,25 @@ function DatePicker(props) {
3125
3907
  function TextEditor(props) {
3126
3908
  const { own, modifiers } = splitProps(props);
3127
3909
  const [text, setText] = readBinding(own.text, own.onChange);
3128
- const focus = own.focused !== void 0 ? readBinding(own.focused) : null;
3129
- const editor = { text };
3910
+ const focus = focusBinding(own);
3911
+ const selection = own.selection !== void 0 ? readBinding(own.selection) : null;
3912
+ const rich = typeof text !== "string";
3913
+ const editor = { text: rich ? AttributedText.plain(text) : text };
3914
+ if (rich) editor["runs"] = protocolRuns(text);
3130
3915
  if (focus) editor["focused"] = focus[0];
3916
+ if (selection) editor["selection"] = { start: selection[0].start, end: selection[0].end };
3917
+ if (own.onDeleteBackward) editor["deleteBackward"] = true;
3131
3918
  return applyModifiers(
3132
- sb("texteditor", editor, { text: (event) => setText(event.value), focus: (event) => focus?.[1](event.value) }),
3919
+ sb("texteditor", editor, {
3920
+ text: (event) => setText(rich ? AttributedText.replacingCharacters(text, event.value) : event.value),
3921
+ deletebackward: () => own.onDeleteBackward?.(),
3922
+ // The native editor's own Bold, Italic or Underline over the text (iOS 26's edit menu): the runs it now shows.
3923
+ runs: (event) => {
3924
+ if (rich) setText(fromProtocolRuns(event.value));
3925
+ },
3926
+ focus: (event) => focus?.[1](event.value),
3927
+ selection: (event) => selection?.[1](event.value)
3928
+ }),
3133
3929
  modifiers
3134
3930
  );
3135
3931
  }
@@ -3184,8 +3980,11 @@ function Form(props) {
3184
3980
  }
3185
3981
  function Section(props) {
3186
3982
  const { own, modifiers } = splitProps(props);
3187
- const header = supplement(own.header);
3188
- const footer = supplement(own.footer);
3983
+ const pinned = useContext(PinnedContext);
3984
+ let header = supplement(own.header);
3985
+ let footer = supplement(own.footer);
3986
+ if (pinned?.includes("sectionHeaders") && header.text !== null && !header.view) header = { text: null, view: /* @__PURE__ */ jsx(Text, { children: header.text }) };
3987
+ if (pinned?.includes("sectionFooters") && footer.text !== null && !footer.view) footer = { text: null, view: /* @__PURE__ */ jsx(Text, { children: footer.text }) };
3189
3988
  const children = [];
3190
3989
  if (header.view) children.push(sb("sectionheader", {}, void 0, header.view));
3191
3990
  if (own.children !== void 0) children.push(wrapRows(own.children));
@@ -3441,7 +4240,9 @@ function AsyncImage(props) {
3441
4240
  loaded.set(own.url, "failed");
3442
4241
  if (live) setState("failed");
3443
4242
  };
3444
- image.src = own.url;
4243
+ const src = loadableURL(own.url);
4244
+ if (typeof src === "string") image.src = src;
4245
+ else void src.then((resolved) => live && (image.src = resolved));
3445
4246
  return () => {
3446
4247
  live = false;
3447
4248
  };
@@ -3720,6 +4521,11 @@ function Chart(props) {
3720
4521
  const centers = [...new Set(marks.filter((m) => m.kind === "bar").map((m) => ux(m.props.x)))].sort((a, b) => a - b);
3721
4522
  for (let i = 1; i < centers.length; i++) continuousBand = Math.min(continuousBand, centers[i] - centers[i - 1]);
3722
4523
  }
4524
+ const positions = [];
4525
+ for (const mark of marks) {
4526
+ const key = mark.kind === "bar" ? mark.props.positionBy : void 0;
4527
+ if (key !== void 0 && !positions.includes(key)) positions.push(key);
4528
+ }
3723
4529
  const encoded = [];
3724
4530
  const items = [];
3725
4531
  const annotate = (mark, rect) => {
@@ -3759,7 +4565,12 @@ function Chart(props) {
3759
4565
  switch (mark.kind) {
3760
4566
  case "bar": {
3761
4567
  const style = common(mark);
3762
- const [b0, b1] = xScale.kind === "band" ? xScale.band(p.x) : [ux(p.x) - continuousBand / 2, ux(p.x) + continuousBand / 2];
4568
+ let [b0, b1] = xScale.kind === "band" ? xScale.band(p.x) : [ux(p.x) - continuousBand / 2, ux(p.x) + continuousBand / 2];
4569
+ if (p.positionBy !== void 0 && positions.length > 1) {
4570
+ const slot = (b1 - b0) * 0.8 / positions.length;
4571
+ const start = b0 + (b1 - b0) * 0.1 + slot * positions.indexOf(p.positionBy);
4572
+ [b0, b1] = [start - slot / 16, start + slot + slot / 16];
4573
+ }
3763
4574
  const center = round2((b0 + b1) / 2);
3764
4575
  const half = round2((b1 - b0) * 0.8 / 2);
3765
4576
  const top = uy(Math.max(asNumber(p.y), p.yStart ?? 0));
@@ -4298,12 +5109,12 @@ function TimelineView({ schedule, children }) {
4298
5109
  };
4299
5110
  }
4300
5111
  if (typeof schedule === "object" && "periodic" in schedule) {
4301
- const every = Math.max(schedule.periodic.by, 0.01) * 1e3;
5112
+ const every2 = Math.max(schedule.periodic.by, 0.01) * 1e3;
4302
5113
  const from = schedule.periodic.from?.getTime() ?? Date.now();
4303
5114
  let timer;
4304
5115
  const tick = () => {
4305
5116
  const now = Date.now();
4306
- const next = from + Math.ceil((now - from + 1) / every) * every;
5117
+ const next = from + Math.ceil((now - from + 1) / every2) * every2;
4307
5118
  timer = setTimeout(() => {
4308
5119
  setDate(new Date(next));
4309
5120
  tick();
@@ -4335,7 +5146,10 @@ class AVPlayer {
4335
5146
  timeListeners = /* @__PURE__ */ new Set();
4336
5147
  time = 0;
4337
5148
  serial = 0;
5149
+ /** @internal The `report` interval for the views showing this player. */
5150
+ report;
4338
5151
  constructor(url = null, options = {}) {
5152
+ this.report = options.report;
4339
5153
  this.state = {
4340
5154
  url,
4341
5155
  rate: 0,
@@ -4537,6 +5351,7 @@ function videoElement(player, state, options) {
4537
5351
  gravity: options.gravity ?? "resizeAspect",
4538
5352
  controls: options.controls,
4539
5353
  seek: state.seek,
5354
+ ...player.report !== void 0 && player.report > 0 ? { report: player.report } : {},
4540
5355
  ...state.size ? { width: state.size.width, height: state.size.height } : {}
4541
5356
  },
4542
5357
  { video: (event) => player.handle(event) }
@@ -4692,6 +5507,7 @@ export {
4692
5507
  AngularGradient,
4693
5508
  AreaMark,
4694
5509
  AsyncImage,
5510
+ AttributedText,
4695
5511
  AudioPlayer,
4696
5512
  BarMark,
4697
5513
  Button,
@@ -4716,6 +5532,7 @@ export {
4716
5532
  ForEach,
4717
5533
  Form,
4718
5534
  Gauge,
5535
+ GeometryChangeView,
4719
5536
  GeometryReader,
4720
5537
  GraphicsContext,
4721
5538
  Grid,
@@ -4748,6 +5565,7 @@ export {
4748
5565
  Path,
4749
5566
  PhaseAnimator,
4750
5567
  Picker,
5568
+ PinnedContext,
4751
5569
  PointMark,
4752
5570
  ProgressView,
4753
5571
  RadialGradient,
@@ -4777,6 +5595,8 @@ export {
4777
5595
  ToolbarItem,
4778
5596
  ToolbarItemGroup,
4779
5597
  ToolbarSpacer,
5598
+ UndoManager,
5599
+ UndoManagerContext,
4780
5600
  UnevenRoundedRectangle,
4781
5601
  VStack,
4782
5602
  VideoPlayer,
@@ -4784,9 +5604,14 @@ export {
4784
5604
  VisualEffect,
4785
5605
  ZStack,
4786
5606
  answerLayout,
5607
+ appStorage,
5608
+ canMeasureText,
4787
5609
  color,
5610
+ createFileManager,
4788
5611
  createSwiftUIRoot,
5612
+ fileManager,
4789
5613
  flattenChildren,
5614
+ focusBinding,
4790
5615
  font,
4791
5616
  frame,
4792
5617
  geometryProxy,
@@ -4794,7 +5619,9 @@ export {
4794
5619
  hex,
4795
5620
  injectIntoDevTools,
4796
5621
  insets,
5622
+ installTextMeasurer,
4797
5623
  keyframeValues,
5624
+ measureText,
4798
5625
  mount,
4799
5626
  moveItems,
4800
5627
  nextGeometry,
@@ -4807,12 +5634,15 @@ export {
4807
5634
  providingScrollSettings,
4808
5635
  readBinding,
4809
5636
  rgb,
5637
+ scrollGeometryOf,
4810
5638
  shapeStyle,
4811
5639
  system,
4812
5640
  takeScrollSettings,
4813
5641
  timeTicks,
4814
5642
  unitPoint,
4815
5643
  useAVPlayer,
5644
+ useAppStorage,
5645
+ useAudioRecorder,
4816
5646
  useColorScheme,
4817
5647
  useContextBridge,
4818
5648
  useCoordinateSpaces,
@@ -4824,6 +5654,7 @@ export {
4824
5654
  useRedacted,
4825
5655
  useRefreshable,
4826
5656
  useReportSplitSelection,
5657
+ useUndoManager,
4827
5658
  useViewEnvironment,
4828
5659
  weighted,
4829
5660
  withAnimation