@swiftbrowser/react 0.18.3 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,7 +14,9 @@ import { fileURL } from "@swiftbrowser/web/src/files";
14
14
  import { registerHostContent, notifyHostContentChange, unregisterHostContent } from "@swiftbrowser/web/src/hostContent";
15
15
  import { createRoot } from "react-dom/client";
16
16
  import { flushSync } from "react-dom";
17
+ import { ViewRegistry } from "@swiftbrowser/web/src/viewRegistry";
17
18
  import { chooseColor } from "@swiftbrowser/web/src/colorPicker";
19
+ import { base64Encode } from "@swiftbrowser/web/src/base64";
18
20
  import { timingFor } from "@swiftbrowser/web/src/animation";
19
21
  const ViewEnvironmentContext = createContext({});
20
22
  function useViewEnvironment() {
@@ -646,6 +648,7 @@ function mount(screen, element, options = {}) {
646
648
  let renderer = null;
647
649
  const root = createSwiftUIRoot((ops) => renderer?.applyOps(ops));
648
650
  renderer = new Renderer(screen, (event) => root.dispatch(event));
651
+ renderer.fileDrops = "reference";
649
652
  const measurer = (text, font2, width) => renderer.measureText(text, font2, width);
650
653
  installTextMeasurer(measurer);
651
654
  let pageScheme = options.colorScheme ?? "light";
@@ -1071,10 +1074,21 @@ function itemValue(item) {
1071
1074
  }
1072
1075
  return item.text;
1073
1076
  }
1077
+ const name = item.path ?? item.name ?? "file";
1078
+ if (item.file) {
1079
+ const file = item.file;
1080
+ return item.path !== void 0 && item.path !== file.name ? new File([file], item.path, { type: file.type, lastModified: file.lastModified }) : file;
1081
+ }
1082
+ if (item.url !== void 0 && item.data === void 0) {
1083
+ const fromReference = browserEnvironment.fileFromReference;
1084
+ if (fromReference) {
1085
+ return fromReference({ type: item.type, name, url: item.url, ...item.path !== void 0 ? { path: item.path } : {}, ...item.size !== void 0 ? { size: item.size } : {} });
1086
+ }
1087
+ }
1074
1088
  const binary = atob(item.data ?? "");
1075
1089
  const bytes = new Uint8Array(binary.length);
1076
1090
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1077
- return typeof File !== "undefined" ? new File([bytes], item.name ?? "file", { type: item.type }) : bytes;
1091
+ return typeof File !== "undefined" ? new File([bytes], name, { type: item.type }) : bytes;
1078
1092
  }
1079
1093
  function DropDestination({ action, isTargeted, accepts, children }) {
1080
1094
  return sb(
@@ -1176,6 +1190,34 @@ function FileExporterPresenter(spec) {
1176
1190
  }, [presented, setPresented]);
1177
1191
  return null;
1178
1192
  }
1193
+ const ListSelectionContext = createContext(null);
1194
+ function Row({ tag, onDelete, deletable, onMove, movable, index, containerIndex, children }) {
1195
+ const selection = useContext(ListSelectionContext);
1196
+ const selectable = selection !== null && tag !== void 0;
1197
+ const canDelete = onDelete !== void 0 && deletable !== false;
1198
+ const canMove = onMove !== void 0 && movable !== false;
1199
+ if (!selectable && !canDelete && !canMove) return children;
1200
+ const props = {};
1201
+ if (selectable) {
1202
+ props["selectable"] = true;
1203
+ props["selected"] = selection.isSelected(tag);
1204
+ }
1205
+ if (canDelete) props["deletable"] = true;
1206
+ if (canMove) props["movable"] = true;
1207
+ return sb(
1208
+ "listrow",
1209
+ props,
1210
+ {
1211
+ tap: () => selectable && selection.select(tag),
1212
+ delete: () => onDelete?.(),
1213
+ move: (event) => {
1214
+ const start = (containerIndex ?? index ?? 0) - (index ?? 0);
1215
+ onMove?.(Math.max(0, event.to - start));
1216
+ }
1217
+ },
1218
+ children
1219
+ );
1220
+ }
1179
1221
  const isPoint = (value) => typeof value === "object" && value !== null && ("x" in value || "y" in value);
1180
1222
  function command(request) {
1181
1223
  return request.axes ? { anchor: request.anchor, serial: request.serial, axes: request.axes } : { anchor: request.anchor, serial: request.serial };
@@ -1302,34 +1344,6 @@ function marginedContent(children, margins, axis) {
1302
1344
  const content = list.length === 1 ? list[0] : sb(axis === "horizontal" ? "hstack" : "vstack", { spacing: null, alignment: "center" }, void 0, children);
1303
1345
  return sb("styled", { style: { padding: margins } }, void 0, content);
1304
1346
  }
1305
- const ListSelectionContext = createContext(null);
1306
- function Row({ tag, onDelete, deletable, onMove, movable, index, containerIndex, children }) {
1307
- const selection = useContext(ListSelectionContext);
1308
- const selectable = selection !== null && tag !== void 0;
1309
- const canDelete = onDelete !== void 0 && deletable !== false;
1310
- const canMove = onMove !== void 0 && movable !== false;
1311
- if (!selectable && !canDelete && !canMove) return children;
1312
- const props = {};
1313
- if (selectable) {
1314
- props["selectable"] = true;
1315
- props["selected"] = selection.isSelected(tag);
1316
- }
1317
- if (canDelete) props["deletable"] = true;
1318
- if (canMove) props["movable"] = true;
1319
- return sb(
1320
- "listrow",
1321
- props,
1322
- {
1323
- tap: () => selectable && selection.select(tag),
1324
- delete: () => onDelete?.(),
1325
- move: (event) => {
1326
- const start = (containerIndex ?? index ?? 0) - (index ?? 0);
1327
- onMove?.(Math.max(0, event.to - start));
1328
- }
1329
- },
1330
- children
1331
- );
1332
- }
1333
1347
  const LAZY_BATCH = 12;
1334
1348
  const LazyContext = createContext(null);
1335
1349
  function LazyGroup({ rows, start = 0, wrap: wrap2 }) {
@@ -1433,7 +1447,8 @@ function ScrollView(props) {
1433
1447
  const scroll = { axes, ...refresh.props, ...geometry.props };
1434
1448
  const handlers = refresh.handlers || geometry.handlers ? { ...refresh.handlers, ...geometry.handlers } : void 0;
1435
1449
  if (own.scrollTargetBehavior) scroll["snap"] = own.scrollTargetBehavior;
1436
- if (own.scrollDismissesKeyboard) scroll["dismissesKeyboard"] = true;
1450
+ if (own.scrollDismissesKeyboard === true || own.scrollDismissesKeyboard === "immediately") scroll["dismissesKeyboard"] = true;
1451
+ else if (own.scrollDismissesKeyboard === "interactively") scroll["dismissesKeyboard"] = "interactively";
1437
1452
  if (own.scrollIndicators === "hidden") scroll["showsIndicators"] = false;
1438
1453
  if (own.defaultScrollAnchor !== void 0) scroll["defaultScrollAnchor"] = unitPoint(own.defaultScrollAnchor);
1439
1454
  const edgeEffect = edgeEffectProp(settings);
@@ -1799,13 +1814,20 @@ function NavigationLink(props) {
1799
1814
  const navigation = useNavigation();
1800
1815
  const environment = useViewEnvironment();
1801
1816
  const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.children;
1817
+ const listSelection = useContext(ListSelectionContext);
1818
+ const sidebar = useContext(SplitSelectionContext) !== null && listSelection !== null && own.destination === void 0 && own.value !== void 0;
1802
1819
  const tap = () => {
1820
+ if (sidebar) {
1821
+ listSelection.select(own.value);
1822
+ return;
1823
+ }
1803
1824
  if (!navigation) return;
1804
1825
  if (own.destination !== void 0) navigation.push({ view: own.destination });
1805
1826
  else navigation.push(own.value);
1806
1827
  };
1807
1828
  const style = takeModifier(modifiers, "buttonStyle") ?? environment.buttonStyle;
1808
1829
  const linkProps = style === "plain" ? { style: "plain" } : {};
1830
+ if (sidebar) linkProps["selects"] = true;
1809
1831
  return applyModifiers(sb("navlink", linkProps, { tap }, label), modifiers);
1810
1832
  }
1811
1833
  const SplitSelectionContext = createContext(null);
@@ -1814,8 +1836,8 @@ function NavigationSplitView(props) {
1814
1836
  const sizeClass = useHorizontalSizeClass();
1815
1837
  const [selected, setSelected] = useState(false);
1816
1838
  const reporters = useRef(/* @__PURE__ */ new Map());
1839
+ const settle = () => setSelected([...reporters.current.values()].some((entry) => entry.selected));
1817
1840
  const [report] = useState(() => {
1818
- const settle = () => setSelected([...reporters.current.values()].some((entry) => entry.selected));
1819
1841
  return {
1820
1842
  report: (key, nowSelected, clear2) => {
1821
1843
  reporters.current.set(key, { selected: nowSelected, clear: clear2 });
@@ -1827,15 +1849,17 @@ function NavigationSplitView(props) {
1827
1849
  };
1828
1850
  });
1829
1851
  const clear = () => {
1830
- for (const entry of reporters.current.values()) if (entry.selected) entry.clear();
1852
+ for (const entry of reporters.current.values()) {
1853
+ if (!entry.selected) continue;
1854
+ entry.clear();
1855
+ entry.selected = false;
1856
+ }
1857
+ settle();
1831
1858
  };
1832
1859
  const [internal, setInternal] = useState("automatic");
1833
1860
  const [visibility, setVisibility] = own.columnVisibility !== void 0 ? readBinding(own.columnVisibility, own.onColumnVisibilityChange) : [internal, setInternal];
1834
1861
  if (sizeClass === "compact") {
1835
- return applyModifiers(
1836
- /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: report, children: /* @__PURE__ */ jsx(CompactSplit, { sidebar: own.sidebar, detail: own.detail, selected, clear }) }),
1837
- modifiers
1838
- );
1862
+ return applyModifiers(/* @__PURE__ */ jsx(CompactSplit, { sidebar: own.sidebar, detail: own.detail, report, selected, clear }), modifiers);
1839
1863
  }
1840
1864
  const sidebarVisible = visibility !== "detailOnly";
1841
1865
  const toggle = () => withAnimation(() => setVisibility(sidebarVisible ? "detailOnly" : "all"));
@@ -1851,11 +1875,17 @@ function NavigationSplitView(props) {
1851
1875
  function SplitColumn({ report, children }) {
1852
1876
  return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: report, children }) });
1853
1877
  }
1854
- function CompactSplit({ sidebar, detail, selected, clear }) {
1878
+ function CompactSplit({
1879
+ sidebar,
1880
+ detail,
1881
+ report,
1882
+ selected,
1883
+ clear
1884
+ }) {
1855
1885
  const [internal, setInternal] = useState([]);
1856
1886
  const detailStack = isValidElement(detail) && detail.type === NavigationStack ? detail.props : null;
1857
1887
  const [extra, setExtra] = detailStack?.path !== void 0 ? readBinding(detailStack.path, detailStack.onPathChange) : [internal, setInternal];
1858
- const base = selected ? [{ view: /* @__PURE__ */ jsx(DetailScope, { children: detail }) }] : [];
1888
+ const base = selected ? [{ view: detail }] : [];
1859
1889
  const path = [...base, ...extra];
1860
1890
  const setPath = (next) => {
1861
1891
  if (selected && next.length === 0) {
@@ -1865,10 +1895,7 @@ function CompactSplit({ sidebar, detail, selected, clear }) {
1865
1895
  }
1866
1896
  setExtra(next.slice(base.length));
1867
1897
  };
1868
- return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(NavigationStack, { path: [path, setPath], children: sidebar }) });
1869
- }
1870
- function DetailScope({ children }) {
1871
- return /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children });
1898
+ return /* @__PURE__ */ jsx(NavigationContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children: /* @__PURE__ */ jsx(NavigationStack, { path: [path, setPath], children: /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: report, children: sidebar }) }) }) });
1872
1899
  }
1873
1900
  function useReportSplitSelection(split, selected, clear, active2 = true) {
1874
1901
  const key = useRef({}).current;
@@ -1877,6 +1904,9 @@ function useReportSplitSelection(split, selected, clear, active2 = true) {
1877
1904
  else split?.retract(key);
1878
1905
  });
1879
1906
  useLayoutEffect(() => () => split?.retract(key), [split, key]);
1907
+ return (nowSelected, nowClear) => {
1908
+ if (active2) split?.report(key, nowSelected, nowClear);
1909
+ };
1880
1910
  }
1881
1911
  function OutsideSplit({ children }) {
1882
1912
  return /* @__PURE__ */ jsx(SplitSelectionContext.Provider, { value: null, children });
@@ -2156,6 +2186,7 @@ const MODIFIER_KEYS = /* @__PURE__ */ new Set([
2156
2186
  "cornerRadius",
2157
2187
  "clipShape",
2158
2188
  "clipped",
2189
+ "compositingGroup",
2159
2190
  "opacity",
2160
2191
  "disabled",
2161
2192
  "hidden",
@@ -2185,6 +2216,8 @@ const MODIFIER_KEYS = /* @__PURE__ */ new Set([
2185
2216
  "navigationTitle",
2186
2217
  "navigationBarTitleDisplayMode",
2187
2218
  "toolbarRole",
2219
+ "toolbarVisibility",
2220
+ "statusBarHidden",
2188
2221
  "toolbar",
2189
2222
  "searchable",
2190
2223
  "navigationDestination",
@@ -2259,6 +2292,7 @@ const MODIFIER_KEYS = /* @__PURE__ */ new Set([
2259
2292
  "onKeyPress",
2260
2293
  "sensoryFeedback",
2261
2294
  "textCase",
2295
+ "textSelection",
2262
2296
  "coordinateSpace",
2263
2297
  "contentTransition",
2264
2298
  "scrollDisabled",
@@ -2401,6 +2435,7 @@ const STYLED_KEYS = /* @__PURE__ */ new Set([
2401
2435
  "cornerRadius",
2402
2436
  "clipShape",
2403
2437
  "clipped",
2438
+ "compositingGroup",
2404
2439
  "opacity",
2405
2440
  "disabled",
2406
2441
  "hidden",
@@ -2446,7 +2481,8 @@ const STYLED_KEYS = /* @__PURE__ */ new Set([
2446
2481
  "matchedGeometryEffect",
2447
2482
  "containerRelativeFrame",
2448
2483
  "contentTransition",
2449
- "scrollDisabled"
2484
+ "scrollDisabled",
2485
+ "textSelection"
2450
2486
  ]);
2451
2487
  const ENVIRONMENT_KEYS = /* @__PURE__ */ new Set([
2452
2488
  "buttonStyle",
@@ -2513,6 +2549,8 @@ function apply(key, value, node, sides) {
2513
2549
  }
2514
2550
  case "clipped":
2515
2551
  return value ? styled({ clipped: true }, node) : node;
2552
+ case "compositingGroup":
2553
+ return value ? styled({ compositingGroup: true }, node) : node;
2516
2554
  case "opacity":
2517
2555
  return styled({ opacity: value }, node);
2518
2556
  case "disabled":
@@ -2598,6 +2636,17 @@ function apply(key, value, node, sides) {
2598
2636
  case "toolbarRole":
2599
2637
  sides.push(sb(PREF_KIND, { toolbarRole: value }, void 0, void 0, "navscreen"));
2600
2638
  return node;
2639
+ case "toolbarVisibility": {
2640
+ const bars = value;
2641
+ const props = {};
2642
+ if (bars.navigationBar && bars.navigationBar !== "automatic") props["navigationBarHidden"] = bars.navigationBar === "hidden";
2643
+ if (bars.bottomBar && bars.bottomBar !== "automatic") props["bottomBarHidden"] = bars.bottomBar === "hidden";
2644
+ sides.push(sb(PREF_KIND, props, void 0, void 0, "navscreen"));
2645
+ return node;
2646
+ }
2647
+ case "statusBarHidden":
2648
+ sides.push(sb(PREF_KIND, { statusBarHidden: Boolean(value) }, void 0, void 0, "navscreen"));
2649
+ return node;
2601
2650
  case "toolbar":
2602
2651
  sides.push(sb(HOIST_KIND, void 0, void 0, value, "navscreen"));
2603
2652
  return node;
@@ -2631,8 +2680,8 @@ function apply(key, value, node, sides) {
2631
2680
  const count = Math.floor(spec.count ?? 1);
2632
2681
  const props = count > 1 ? { count } : {};
2633
2682
  if ("perform" in spec) {
2634
- const { perform } = spec;
2635
- return sb("tapgesture", { ...props, location: true }, { tap: (event) => perform({ x: event.x ?? 0, y: event.y ?? 0 }) }, node);
2683
+ const { perform: perform2 } = spec;
2684
+ return sb("tapgesture", { ...props, location: true }, { tap: (event) => perform2({ x: event.x ?? 0, y: event.y ?? 0 }) }, node);
2636
2685
  }
2637
2686
  return sb("tapgesture", props, { tap: () => spec.action() }, node);
2638
2687
  }
@@ -2852,6 +2901,8 @@ function apply(key, value, node, sides) {
2852
2901
  }
2853
2902
  case "scrollDisabled":
2854
2903
  return styled({ scrollDisabled: value === true }, node);
2904
+ case "textSelection":
2905
+ return styled({ textSelection: value === "enabled" }, node);
2855
2906
  case "contentTransition": {
2856
2907
  if (value === "identity") return node;
2857
2908
  if (typeof value === "object") {
@@ -3574,6 +3625,848 @@ function useAudioRecorder(options = {}) {
3574
3625
  );
3575
3626
  return { available: host !== void 0, status, time, levels, error, record, pause, resume, stop, cancel, discard, trim };
3576
3627
  }
3628
+ let nextId$1 = 1;
3629
+ const newId = () => nextId$1++;
3630
+ const hostNow$1 = () => browserEnvironment.audioEngine;
3631
+ function unsupported() {
3632
+ return new Error("unsupported: this page or app has no audio engine");
3633
+ }
3634
+ function perform$1(command2) {
3635
+ const host = hostNow$1();
3636
+ if (!host) return Promise.reject(unsupported());
3637
+ return host.perform(command2);
3638
+ }
3639
+ class AVAudioTime {
3640
+ spec;
3641
+ constructor(sampleTime, atRate) {
3642
+ this.spec = { sampleTime: Math.round(sampleTime), sampleRate: atRate };
3643
+ }
3644
+ static secondsFromNow(seconds) {
3645
+ const time = Object.create(AVAudioTime.prototype);
3646
+ time.spec = { delay: Math.max(0, seconds) };
3647
+ return time;
3648
+ }
3649
+ get isSampleTimeValid() {
3650
+ return "sampleTime" in this.spec;
3651
+ }
3652
+ get sampleTime() {
3653
+ return "sampleTime" in this.spec ? this.spec.sampleTime : 0;
3654
+ }
3655
+ get sampleRate() {
3656
+ return "sampleTime" in this.spec ? this.spec.sampleRate : 0;
3657
+ }
3658
+ /** The time in seconds: a sample time's place on its timeline, or the delay. */
3659
+ get seconds() {
3660
+ return "sampleTime" in this.spec ? this.spec.sampleTime / this.spec.sampleRate : this.spec.delay;
3661
+ }
3662
+ }
3663
+ const released = typeof FinalizationRegistry !== "undefined" ? new FinalizationRegistry((id) => void perform$1({ action: "release", id }).catch(() => void 0)) : null;
3664
+ class AVAudioFile {
3665
+ constructor(id, url, length, processingFormat) {
3666
+ this.id = id;
3667
+ this.url = url;
3668
+ this.length = length;
3669
+ this.processingFormat = processingFormat;
3670
+ released?.register(this, id, this);
3671
+ }
3672
+ id;
3673
+ url;
3674
+ length;
3675
+ processingFormat;
3676
+ /** Where the next `read` starts, in frames. */
3677
+ framePosition = 0;
3678
+ /** Opens a file for reading; rejects with `notFound: …` or `decode: …`. */
3679
+ static async open(url) {
3680
+ const id = newId();
3681
+ const info = await perform$1({ action: "openFile", file: id, url });
3682
+ return new AVAudioFile(id, url, info.length, { sampleRate: info.sampleRate, channelCount: info.channelCount });
3683
+ }
3684
+ /** Seconds. */
3685
+ get duration() {
3686
+ return this.length / this.processingFormat.sampleRate;
3687
+ }
3688
+ /** Reads frames from `framePosition` into `buffer` (up to its capacity, or `frameCount`) and moves past them. */
3689
+ async read(into, frameCount) {
3690
+ const frames = Math.max(0, Math.min(frameCount ?? into.frameCapacity, into.frameCapacity, this.length - this.framePosition));
3691
+ const info = await perform$1({ action: "readFile", file: this.id, buffer: into.id, start: this.framePosition, frames });
3692
+ into.frameLength = info.frameLength;
3693
+ into.format = { sampleRate: info.sampleRate, channelCount: info.channelCount };
3694
+ this.framePosition += info.frameLength;
3695
+ }
3696
+ /** Lets the host free the decoded audio (what a scheduled segment still plays keeps playing). */
3697
+ close() {
3698
+ released?.unregister(this);
3699
+ void perform$1({ action: "release", id: this.id }).catch(() => void 0);
3700
+ }
3701
+ }
3702
+ class AVAudioPCMBuffer {
3703
+ constructor(format, frameCapacity) {
3704
+ this.format = format;
3705
+ this.frameCapacity = frameCapacity;
3706
+ released?.register(this, this.id, this);
3707
+ }
3708
+ format;
3709
+ frameCapacity;
3710
+ id = newId();
3711
+ frameLength = 0;
3712
+ /** Seconds of audio it holds. */
3713
+ get duration() {
3714
+ return this.frameLength / this.format.sampleRate;
3715
+ }
3716
+ /** Fills the buffer with the app's samples, one array per channel (up to its capacity). */
3717
+ async write(channels) {
3718
+ const frames = Math.min(this.frameCapacity, ...channels.map((data) => data.length));
3719
+ await perform$1({
3720
+ action: "writeBuffer",
3721
+ buffer: this.id,
3722
+ sampleRate: this.format.sampleRate,
3723
+ channels: channels.map((data) => data.length === frames ? data : data.subarray(0, frames))
3724
+ });
3725
+ this.frameLength = frames;
3726
+ this.format = { sampleRate: this.format.sampleRate, channelCount: channels.length };
3727
+ }
3728
+ /** Lets the host free the audio. */
3729
+ close() {
3730
+ released?.unregister(this);
3731
+ void perform$1({ action: "release", id: this.id }).catch(() => void 0);
3732
+ }
3733
+ }
3734
+ class AVAudioNode {
3735
+ id = newId();
3736
+ /** The engine it is attached to, or null. */
3737
+ engine = null;
3738
+ values = {};
3739
+ get(key, fallback) {
3740
+ return this.values[key] ?? fallback;
3741
+ }
3742
+ set(key, value) {
3743
+ this.values[key] = value;
3744
+ this.engine?.fire({ action: "set", node: this.id, values: { [key]: value } });
3745
+ }
3746
+ /** The properties to attach it with. */
3747
+ attachValues() {
3748
+ return { ...this.values };
3749
+ }
3750
+ /** How many EQ bands it has (only an EQ has any). */
3751
+ get bandCount() {
3752
+ return void 0;
3753
+ }
3754
+ /** What its engine must be for a call to reach it. */
3755
+ attached() {
3756
+ if (!this.engine) throw new Error("notAttached: attach the node to an engine first");
3757
+ return this.engine;
3758
+ }
3759
+ /** Its output's level now (0 silent … 1 at 0 dB, from -50 dB), as a tap on it would measure; 0 when not attached. */
3760
+ async meter() {
3761
+ if (!this.engine) return 0;
3762
+ const result = await this.engine.send({ action: "meter", node: this.id });
3763
+ return typeof result?.level === "number" ? result.level : 0;
3764
+ }
3765
+ }
3766
+ class MixingNode extends AVAudioNode {
3767
+ get volume() {
3768
+ return this.get("volume", 1);
3769
+ }
3770
+ set volume(value) {
3771
+ this.set("volume", value);
3772
+ }
3773
+ get pan() {
3774
+ return this.get("pan", 0);
3775
+ }
3776
+ set pan(value) {
3777
+ this.set("pan", value);
3778
+ }
3779
+ }
3780
+ class AVAudioMixerNode extends MixingNode {
3781
+ kind = "mixer";
3782
+ get outputVolume() {
3783
+ return this.get("outputVolume", 1);
3784
+ }
3785
+ set outputVolume(value) {
3786
+ this.set("outputVolume", value);
3787
+ }
3788
+ }
3789
+ class AVAudioOutputNode extends AVAudioNode {
3790
+ kind = null;
3791
+ }
3792
+ class AVAudioInputNode extends MixingNode {
3793
+ kind = null;
3794
+ }
3795
+ class AVAudioPlayerNode extends MixingNode {
3796
+ kind = "player";
3797
+ playing = false;
3798
+ get isPlaying() {
3799
+ return this.playing && this.engine?.isRunning === true;
3800
+ }
3801
+ scheduleFile(file, at = null, completionHandler) {
3802
+ return this.schedule({ action: "scheduleFile", node: this.id, file: file.id, ...at ? { at: at.spec } : {} }, completionHandler);
3803
+ }
3804
+ scheduleSegment(file, startingFrame, frameCount, at = null, completionHandler) {
3805
+ return this.schedule(
3806
+ {
3807
+ action: "scheduleFile",
3808
+ node: this.id,
3809
+ file: file.id,
3810
+ start: Math.max(0, Math.floor(startingFrame)),
3811
+ frames: Math.max(0, Math.floor(frameCount)),
3812
+ ...at ? { at: at.spec } : {}
3813
+ },
3814
+ completionHandler
3815
+ );
3816
+ }
3817
+ scheduleBuffer(buffer, at = null, options = {}, completionHandler) {
3818
+ const command2 = {
3819
+ action: "scheduleBuffer",
3820
+ node: this.id,
3821
+ buffer: buffer.id,
3822
+ ...at ? { at: at.spec } : {}
3823
+ };
3824
+ if (options.loops) command2.loops = true;
3825
+ if (options.interruptsAtLoop) command2.interruptsAtLoop = true;
3826
+ else if (options.interrupts) command2.interrupts = true;
3827
+ return this.schedule(command2, completionHandler);
3828
+ }
3829
+ schedule(command2, completionHandler) {
3830
+ const engine = this.attached();
3831
+ return engine.send(command2).then(() => completionHandler?.());
3832
+ }
3833
+ /** Starts (or resumes) the timeline, now or at `at`; the engine must be running. */
3834
+ play(at = null) {
3835
+ const engine = this.attached();
3836
+ this.playing = true;
3837
+ engine.fire({ action: "play", node: this.id, ...at ? { at: at.spec } : {} });
3838
+ }
3839
+ /** Holds the timeline where it is; `play()` goes on from there. */
3840
+ pause() {
3841
+ this.playing = false;
3842
+ this.engine?.fire({ action: "pausePlayer", node: this.id });
3843
+ }
3844
+ /** Drops everything scheduled (each completion called) and returns the timeline to 0. */
3845
+ stop() {
3846
+ this.playing = false;
3847
+ this.engine?.fire({ action: "stopPlayer", node: this.id });
3848
+ }
3849
+ /** Where the timeline is now (`playerTime(forNodeTime: lastRenderTime)`), or null before it plays. */
3850
+ async playerTime() {
3851
+ const time = await this.attached().send({ action: "playerTime", node: this.id });
3852
+ return time && typeof time.sampleTime === "number" ? new AVAudioTime(time.sampleTime, time.sampleRate) : null;
3853
+ }
3854
+ /** Called by the engine when it stops: the player stops with it. */
3855
+ engineStopped() {
3856
+ this.playing = false;
3857
+ }
3858
+ }
3859
+ const NOTE_CLASSES = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
3860
+ function noteInName(url) {
3861
+ const name = decodeURIComponent(url.split(/[/\\]/).pop() ?? "").replace(/\.[^.]*$/, "");
3862
+ const named = [...name.matchAll(/(?:^|[^A-Za-z])([A-Ga-g])([#♯]|b|♭)?(-?\d)(?!\d)/g)].pop();
3863
+ if (named) {
3864
+ const pitchClass = NOTE_CLASSES[named[1].toLowerCase()];
3865
+ const accidental = named[2] === "#" || named[2] === "♯" ? 1 : named[2] ? -1 : 0;
3866
+ const note = (Number(named[3]) + 1) * 12 + pitchClass + accidental;
3867
+ return note >= 0 && note <= 127 ? note : null;
3868
+ }
3869
+ const numbered = [...name.matchAll(/(?:^|\D)(\d{1,3})(?!\d)/g)].pop();
3870
+ if (numbered) {
3871
+ const note = Number(numbered[1]);
3872
+ return note <= 127 ? note : null;
3873
+ }
3874
+ return null;
3875
+ }
3876
+ function samplerZones(samples) {
3877
+ const sorted = [...samples].sort((a, b) => a.root - b.root).filter((sample, i, all) => i === 0 || sample.root !== all[i - 1].root);
3878
+ return sorted.map((sample, i) => {
3879
+ const below = sorted[i - 1];
3880
+ const above = sorted[i + 1];
3881
+ return {
3882
+ file: sample.file,
3883
+ root: sample.root,
3884
+ low: below ? Math.floor((below.root + sample.root) / 2) + 1 : 0,
3885
+ high: above ? Math.floor((sample.root + above.root) / 2) : 127
3886
+ };
3887
+ });
3888
+ }
3889
+ class AVAudioUnitSampler extends MixingNode {
3890
+ kind = "sampler";
3891
+ files = [];
3892
+ /** Decibels, -90 … 12. */
3893
+ get overallGain() {
3894
+ return this.get("overallGain", 0);
3895
+ }
3896
+ set overallGain(value) {
3897
+ this.set("overallGain", value);
3898
+ }
3899
+ /** Cents, -2400 … 2400. */
3900
+ get globalTuning() {
3901
+ return this.get("globalTuning", 0);
3902
+ }
3903
+ set globalTuning(value) {
3904
+ this.set("globalTuning", value);
3905
+ }
3906
+ /** -100 left … 100 right. */
3907
+ get stereoPan() {
3908
+ return this.get("stereoPan", 0);
3909
+ }
3910
+ set stereoPan(value) {
3911
+ this.set("stereoPan", value);
3912
+ }
3913
+ async loadAudioFiles(urls, options = {}) {
3914
+ const engine = this.attached();
3915
+ const roots2 = urls.map((url, i) => options.rootNotes?.[i] ?? noteInName(url) ?? 60);
3916
+ const files = await Promise.all(urls.map((url) => AVAudioFile.open(url)));
3917
+ const zones = samplerZones(files.map((file, i) => ({ file: file.id, root: roots2[i] })));
3918
+ await engine.send({ action: "loadZones", node: this.id, zones, attack: options.attack ?? 2e-3, release: options.release ?? 0.15 });
3919
+ const previous = this.files;
3920
+ this.files = files;
3921
+ for (const file of previous) file.close();
3922
+ }
3923
+ /** Loads an `.aupreset`, a DLS or a SoundFont's first instrument (natively; a page rejects with `unsupported: …`). */
3924
+ async loadInstrument(url) {
3925
+ await this.attached().send({ action: "loadInstrument", node: this.id, url });
3926
+ }
3927
+ /** Loads one instrument of a SoundFont or DLS bank by program and bank (natively; a page rejects with `unsupported: …`). */
3928
+ async loadSoundBankInstrument(url, program, bankMSB = 121, bankLSB = 0) {
3929
+ await this.attached().send({ action: "loadInstrument", node: this.id, url, program, bankMSB, bankLSB });
3930
+ }
3931
+ startNote(note, withVelocity, onChannel = 0) {
3932
+ this.attached().fire({ action: "startNote", node: this.id, note: Math.round(note), velocity: Math.round(withVelocity), channel: onChannel });
3933
+ }
3934
+ stopNote(note, onChannel = 0) {
3935
+ this.attached().fire({ action: "stopNote", node: this.id, note: Math.round(note), channel: onChannel });
3936
+ }
3937
+ /** A MIDI controller: 64 the sustain pedal, 7 volume, 11 expression, 10 pan, 123 all notes off. */
3938
+ sendController(controller, withValue, onChannel = 0) {
3939
+ this.attached().fire({ action: "controller", node: this.id, controller, value: Math.round(withValue), channel: onChannel });
3940
+ }
3941
+ /** The pitch wheel: 0 … 16383, 8192 centered (±2 semitones). */
3942
+ sendPitchBend(pitchbend, onChannel = 0) {
3943
+ this.attached().fire({ action: "pitchBend", node: this.id, value: Math.round(pitchbend), channel: onChannel });
3944
+ }
3945
+ }
3946
+ class AVAudioUnitEffect extends AVAudioNode {
3947
+ get bypass() {
3948
+ return this.get("bypass", false);
3949
+ }
3950
+ set bypass(value) {
3951
+ this.set("bypass", value);
3952
+ }
3953
+ }
3954
+ class AVAudioUnitReverb extends AVAudioUnitEffect {
3955
+ kind = "reverb";
3956
+ constructor() {
3957
+ super();
3958
+ this.values["preset"] = "mediumHall";
3959
+ this.values["wetDryMix"] = 100;
3960
+ }
3961
+ get wetDryMix() {
3962
+ return this.get("wetDryMix", 100);
3963
+ }
3964
+ set wetDryMix(value) {
3965
+ this.set("wetDryMix", value);
3966
+ }
3967
+ loadFactoryPreset(preset) {
3968
+ this.values["preset"] = preset;
3969
+ this.engine?.fire({ action: "preset", node: this.id, preset });
3970
+ }
3971
+ }
3972
+ class AVAudioUnitDelay extends AVAudioUnitEffect {
3973
+ kind = "delay";
3974
+ constructor() {
3975
+ super();
3976
+ Object.assign(this.values, { delayTime: 1, feedback: 50, lowPassCutoff: 15e3, wetDryMix: 100 });
3977
+ }
3978
+ get delayTime() {
3979
+ return this.get("delayTime", 1);
3980
+ }
3981
+ set delayTime(value) {
3982
+ this.set("delayTime", value);
3983
+ }
3984
+ get feedback() {
3985
+ return this.get("feedback", 50);
3986
+ }
3987
+ set feedback(value) {
3988
+ this.set("feedback", value);
3989
+ }
3990
+ get lowPassCutoff() {
3991
+ return this.get("lowPassCutoff", 15e3);
3992
+ }
3993
+ set lowPassCutoff(value) {
3994
+ this.set("lowPassCutoff", value);
3995
+ }
3996
+ get wetDryMix() {
3997
+ return this.get("wetDryMix", 100);
3998
+ }
3999
+ set wetDryMix(value) {
4000
+ this.set("wetDryMix", value);
4001
+ }
4002
+ }
4003
+ class AVAudioUnitDistortion extends AVAudioUnitEffect {
4004
+ kind = "distortion";
4005
+ constructor() {
4006
+ super();
4007
+ Object.assign(this.values, { preGain: -6, wetDryMix: 50 });
4008
+ }
4009
+ get preGain() {
4010
+ return this.get("preGain", -6);
4011
+ }
4012
+ set preGain(value) {
4013
+ this.set("preGain", value);
4014
+ }
4015
+ get wetDryMix() {
4016
+ return this.get("wetDryMix", 50);
4017
+ }
4018
+ set wetDryMix(value) {
4019
+ this.set("wetDryMix", value);
4020
+ }
4021
+ loadFactoryPreset(preset) {
4022
+ this.values["preset"] = preset;
4023
+ this.engine?.fire({ action: "preset", node: this.id, preset });
4024
+ }
4025
+ }
4026
+ class AVAudioUnitEQFilterParameters {
4027
+ constructor(eq, index) {
4028
+ this.eq = eq;
4029
+ this.index = index;
4030
+ }
4031
+ eq;
4032
+ index;
4033
+ key(field) {
4034
+ return `bands.${this.index}.${field}`;
4035
+ }
4036
+ get filterType() {
4037
+ return this.eq.band(this.key("filterType"), "parametric");
4038
+ }
4039
+ set filterType(value) {
4040
+ this.eq.setBand(this.key("filterType"), value);
4041
+ }
4042
+ /** Hz. */
4043
+ get frequency() {
4044
+ return this.eq.band(this.key("frequency"), 1e3);
4045
+ }
4046
+ set frequency(value) {
4047
+ this.eq.setBand(this.key("frequency"), value);
4048
+ }
4049
+ /** Octaves (0.05 … 5). */
4050
+ get bandwidth() {
4051
+ return this.eq.band(this.key("bandwidth"), 0.5);
4052
+ }
4053
+ set bandwidth(value) {
4054
+ this.eq.setBand(this.key("bandwidth"), value);
4055
+ }
4056
+ /** Decibels (-96 … 24). */
4057
+ get gain() {
4058
+ return this.eq.band(this.key("gain"), 0);
4059
+ }
4060
+ set gain(value) {
4061
+ this.eq.setBand(this.key("gain"), value);
4062
+ }
4063
+ get bypass() {
4064
+ return this.eq.band(this.key("bypass"), true);
4065
+ }
4066
+ set bypass(value) {
4067
+ this.eq.setBand(this.key("bypass"), value);
4068
+ }
4069
+ }
4070
+ class AVAudioUnitEQ extends AVAudioUnitEffect {
4071
+ kind = "eq";
4072
+ bands;
4073
+ constructor(numberOfBands = 1) {
4074
+ super();
4075
+ const count = Math.max(1, Math.min(32, Math.floor(numberOfBands)));
4076
+ this.bands = Array.from({ length: count }, (_, i) => new AVAudioUnitEQFilterParameters(this, i));
4077
+ for (let i = 0; i < count; i += 1) {
4078
+ Object.assign(this.values, {
4079
+ [`bands.${i}.filterType`]: "parametric",
4080
+ [`bands.${i}.frequency`]: 1e3,
4081
+ [`bands.${i}.bandwidth`]: 0.5,
4082
+ [`bands.${i}.gain`]: 0,
4083
+ [`bands.${i}.bypass`]: true
4084
+ });
4085
+ }
4086
+ }
4087
+ get bandCount() {
4088
+ return this.bands.length;
4089
+ }
4090
+ get globalGain() {
4091
+ return this.get("globalGain", 0);
4092
+ }
4093
+ set globalGain(value) {
4094
+ this.set("globalGain", value);
4095
+ }
4096
+ /** A band's field (for `AVAudioUnitEQFilterParameters`). */
4097
+ band(key, fallback) {
4098
+ return this.values[key] ?? fallback;
4099
+ }
4100
+ setBand(key, value) {
4101
+ this.set(key, value);
4102
+ }
4103
+ }
4104
+ class AVAudioUnitTimePitch extends AVAudioUnitEffect {
4105
+ kind = "timePitch";
4106
+ get pitch() {
4107
+ return this.get("pitch", 0);
4108
+ }
4109
+ set pitch(value) {
4110
+ this.set("pitch", value);
4111
+ }
4112
+ get rate() {
4113
+ return this.get("rate", 1);
4114
+ }
4115
+ set rate(value) {
4116
+ this.set("rate", value);
4117
+ }
4118
+ get overlap() {
4119
+ return this.get("overlap", 8);
4120
+ }
4121
+ set overlap(value) {
4122
+ this.set("overlap", value);
4123
+ }
4124
+ }
4125
+ const engines = typeof FinalizationRegistry !== "undefined" ? new FinalizationRegistry((id) => void perform$1({ action: "destroy", engine: id }).catch(() => void 0)) : null;
4126
+ class AVAudioEngine {
4127
+ id = newId();
4128
+ mainMixerNode = new AVAudioMixerNode();
4129
+ outputNode = new AVAudioOutputNode();
4130
+ inputNode = new AVAudioInputNode();
4131
+ attachedNodes = /* @__PURE__ */ new Set();
4132
+ /** Where failures of calls that answer nothing go (a console warning by default). */
4133
+ onError = (error) => console.warn(`AVAudioEngine: ${error.message}`);
4134
+ running = false;
4135
+ disposed = false;
4136
+ /** Whether this page or app has an audio engine. */
4137
+ static get isAvailable() {
4138
+ return hostNow$1() !== void 0;
4139
+ }
4140
+ constructor() {
4141
+ for (const node of [this.mainMixerNode, this.outputNode, this.inputNode]) {
4142
+ node.engine = this;
4143
+ this.attachedNodes.add(node);
4144
+ }
4145
+ this.fire({ action: "create", engine: this.id, mixer: this.mainMixerNode.id, output: this.outputNode.id, input: this.inputNode.id });
4146
+ engines?.register(this, this.id, this);
4147
+ }
4148
+ get isRunning() {
4149
+ return this.running;
4150
+ }
4151
+ /** Sends a command and answers its result. */
4152
+ send(command2) {
4153
+ if (this.disposed) return Promise.reject(new Error("disposed: the engine was disposed"));
4154
+ return perform$1(command2);
4155
+ }
4156
+ /** Sends a command that answers nothing; a failure goes to `onError`. */
4157
+ fire(command2) {
4158
+ this.send(command2).catch((error) => this.onError(error instanceof Error ? error : new Error(String(error))));
4159
+ }
4160
+ attach(node) {
4161
+ if (node.engine === this) return;
4162
+ if (node.engine) throw new Error("attached: the node is attached to another engine");
4163
+ if (!node.kind) throw new Error("unsupported: an engine makes its own mixer, output and input");
4164
+ node.engine = this;
4165
+ this.attachedNodes.add(node);
4166
+ const bands = node.bandCount;
4167
+ this.fire({ action: "attach", engine: this.id, node: node.id, kind: node.kind, values: node.attachValues(), ...bands !== void 0 ? { bands } : {} });
4168
+ }
4169
+ detach(node) {
4170
+ if (node.engine !== this || !node.kind) return;
4171
+ this.fire({ action: "detach", engine: this.id, node: node.id });
4172
+ if (node instanceof AVAudioPlayerNode) node.engineStopped();
4173
+ node.engine = null;
4174
+ this.attachedNodes.delete(node);
4175
+ }
4176
+ /**
4177
+ * Connects `from`'s output to `to` (an array: to each of them), replacing
4178
+ * where it went before; a node that is not a mixer takes one input, so
4179
+ * what was connected into it is let go. `format` is accepted for Swift's
4180
+ * shape: the host picks one that fits both ends.
4181
+ */
4182
+ connect(from, to, format = null) {
4183
+ const destinations = Array.isArray(to) ? to : [to];
4184
+ for (const node of [from, ...destinations]) {
4185
+ if (node.engine !== this) throw new Error("notAttached: attach both nodes to the engine before connecting them");
4186
+ }
4187
+ this.fire({ action: "connect", engine: this.id, from: from.id, to: destinations.map((node) => node.id) });
4188
+ }
4189
+ disconnectNodeOutput(node) {
4190
+ this.fire({ action: "disconnectOutput", engine: this.id, node: node.id });
4191
+ }
4192
+ disconnectNodeInput(node) {
4193
+ this.fire({ action: "disconnectInput", engine: this.id, node: node.id });
4194
+ }
4195
+ prepare() {
4196
+ this.fire({ action: "prepare", engine: this.id });
4197
+ }
4198
+ /** Starts rendering; with the input connected, asks for the microphone (rejecting `permission: …` when refused). */
4199
+ async start() {
4200
+ await this.send({ action: "start", engine: this.id });
4201
+ this.running = true;
4202
+ }
4203
+ /** Pauses rendering; players hold their place and play on at `start()`. */
4204
+ pause() {
4205
+ this.running = false;
4206
+ this.fire({ action: "pause", engine: this.id });
4207
+ }
4208
+ /** Stops rendering; players stop and drop what they had scheduled. */
4209
+ stop() {
4210
+ this.running = false;
4211
+ for (const node of this.attachedNodes) if (node instanceof AVAudioPlayerNode) node.engineStopped();
4212
+ this.fire({ action: "stop", engine: this.id });
4213
+ }
4214
+ /** Silences every node (players stop, notes end) without stopping the engine. */
4215
+ reset() {
4216
+ for (const node of this.attachedNodes) if (node instanceof AVAudioPlayerNode) node.engineStopped();
4217
+ this.fire({ action: "reset", engine: this.id });
4218
+ }
4219
+ /** Stops the engine and frees it on the host; it cannot be used after. */
4220
+ dispose() {
4221
+ if (this.disposed) return;
4222
+ engines?.unregister(this);
4223
+ void perform$1({ action: "destroy", engine: this.id }).catch(() => void 0);
4224
+ this.disposed = true;
4225
+ this.running = false;
4226
+ for (const node of this.attachedNodes) node.engine = null;
4227
+ this.attachedNodes.clear();
4228
+ }
4229
+ }
4230
+ function useAudioEngine(build) {
4231
+ const [engine] = useState(() => {
4232
+ if (!AVAudioEngine.isAvailable) return null;
4233
+ const made = new AVAudioEngine();
4234
+ build?.(made);
4235
+ return made;
4236
+ });
4237
+ useEffect(() => () => engine?.dispose(), [engine]);
4238
+ return engine;
4239
+ }
4240
+ let nextId = 1;
4241
+ const hostNow = () => browserEnvironment.languageModel;
4242
+ function perform(command2) {
4243
+ const host = hostNow();
4244
+ if (!host) return Promise.reject(new GenerationError("assetsUnavailable", "This page or app has no language model."));
4245
+ return host.perform(command2).catch((error) => {
4246
+ throw GenerationError.from(error);
4247
+ });
4248
+ }
4249
+ const KINDS = {
4250
+ unavailable: "assetsUnavailable",
4251
+ context: "exceededContextWindowSize",
4252
+ guardrail: "guardrailViolation",
4253
+ language: "unsupportedLanguageOrLocale",
4254
+ concurrent: "concurrentRequests",
4255
+ rateLimited: "rateLimited",
4256
+ refusal: "refusal"
4257
+ };
4258
+ class GenerationError extends Error {
4259
+ constructor(kind, message) {
4260
+ super(message);
4261
+ this.kind = kind;
4262
+ this.name = "GenerationError";
4263
+ }
4264
+ kind;
4265
+ /** The error a host's tagged message (`context: …`) stands for. */
4266
+ static from(error) {
4267
+ if (error instanceof GenerationError) return error;
4268
+ const message = error instanceof Error ? error.message : String(error);
4269
+ const match = /^(\w+): ?([\s\S]*)$/.exec(message);
4270
+ const kind = match ? KINDS[match[1]] : void 0;
4271
+ return kind ? new GenerationError(kind, match[2]) : new GenerationError("unknown", message);
4272
+ }
4273
+ }
4274
+ class SystemLanguageModel {
4275
+ static default = new SystemLanguageModel();
4276
+ constructor() {
4277
+ }
4278
+ /** Views showing the availability, asked again when a download starts or the model goes. */
4279
+ listeners = /* @__PURE__ */ new Set();
4280
+ /** Whether the model can answer now, or why not. */
4281
+ async availability() {
4282
+ if (!hostNow()) return { status: "unavailable", reason: "deviceNotEligible" };
4283
+ try {
4284
+ return await perform({ action: "availability" });
4285
+ } catch {
4286
+ return { status: "unavailable", reason: "deviceNotEligible" };
4287
+ }
4288
+ }
4289
+ async isAvailable() {
4290
+ return (await this.availability()).status === "available";
4291
+ }
4292
+ /**
4293
+ * Starts fetching a model the app fetches itself (availability's
4294
+ * `download`), which the viewer asked for: show its size first. Not part
4295
+ * of FoundationModels, whose model the system manages.
4296
+ */
4297
+ async download() {
4298
+ await perform({ action: "download" });
4299
+ this.changed();
4300
+ }
4301
+ /**
4302
+ * Makes the model with this `id` (one of availability's `models`) the one
4303
+ * that answers, from the next session on; remembered across launches.
4304
+ */
4305
+ async selectModel(id) {
4306
+ await perform({ action: "select", id });
4307
+ this.changed();
4308
+ }
4309
+ /** Stops such a download, or removes the model from the device to free its space. */
4310
+ async deleteDownload() {
4311
+ await perform({ action: "deleteDownload" });
4312
+ this.changed();
4313
+ }
4314
+ /** @internal `useSystemLanguageModel` hears downloads start and models go. */
4315
+ listen(listener) {
4316
+ this.listeners.add(listener);
4317
+ return () => this.listeners.delete(listener);
4318
+ }
4319
+ changed() {
4320
+ for (const listener of this.listeners) listener();
4321
+ }
4322
+ }
4323
+ function useSystemLanguageModel(model = SystemLanguageModel.default) {
4324
+ const [availability, setAvailability] = useState(null);
4325
+ useEffect(() => {
4326
+ let live = true;
4327
+ let timer;
4328
+ const check = () => {
4329
+ if (timer !== void 0) clearTimeout(timer);
4330
+ timer = void 0;
4331
+ void model.availability().then((next) => {
4332
+ if (!live) return;
4333
+ setAvailability((current) => JSON.stringify(current) === JSON.stringify(next) ? current : next);
4334
+ if (timer !== void 0) return;
4335
+ if (next.download?.progress !== void 0) timer = setTimeout(check, 1e3);
4336
+ else if (next.status === "unavailable" && next.reason === "modelNotReady" && !next.download) timer = setTimeout(check, 5e3);
4337
+ });
4338
+ };
4339
+ check();
4340
+ const stop = model.listen(check);
4341
+ return () => {
4342
+ live = false;
4343
+ stop();
4344
+ if (timer !== void 0) clearTimeout(timer);
4345
+ };
4346
+ }, [model]);
4347
+ return availability;
4348
+ }
4349
+ class ResponseStream {
4350
+ /** @internal Made by `LanguageModelSession.streamResponse`. */
4351
+ constructor(session, opened, prompt, options, ended) {
4352
+ this.ended = ended;
4353
+ this.started = opened.then(
4354
+ () => perform({ action: "stream", session: session.id, stream: this.id, prompt, ...options ? { options } : {} })
4355
+ );
4356
+ this.started.catch(() => this.end());
4357
+ }
4358
+ ended;
4359
+ id = nextId++;
4360
+ started;
4361
+ finished = false;
4362
+ last = { content: "" };
4363
+ async *[Symbol.asyncIterator]() {
4364
+ try {
4365
+ await this.started;
4366
+ while (!this.finished) {
4367
+ const { content, done } = await perform({ action: "next", stream: this.id });
4368
+ if (done) this.end();
4369
+ if (content !== this.last.content) {
4370
+ this.last = { content };
4371
+ yield this.last;
4372
+ }
4373
+ }
4374
+ } catch (error) {
4375
+ this.end();
4376
+ throw error;
4377
+ } finally {
4378
+ this.cancel();
4379
+ }
4380
+ }
4381
+ /** The whole response, once it has ended. */
4382
+ async collect() {
4383
+ for await (const snapshot of this) this.last = snapshot;
4384
+ return this.last;
4385
+ }
4386
+ /** Stops the response; the iteration ends with what came so far. */
4387
+ cancel() {
4388
+ if (this.finished) return;
4389
+ this.end();
4390
+ void this.started.then(() => perform({ action: "cancel", stream: this.id })).catch(() => {
4391
+ });
4392
+ }
4393
+ end() {
4394
+ if (this.finished) return;
4395
+ this.finished = true;
4396
+ this.ended();
4397
+ }
4398
+ }
4399
+ class LanguageModelSession {
4400
+ id = nextId++;
4401
+ instructions;
4402
+ tools;
4403
+ opened;
4404
+ responses = 0;
4405
+ closed = false;
4406
+ constructor(options = {}) {
4407
+ this.instructions = options.instructions;
4408
+ this.tools = options.tools ?? [];
4409
+ const tools = this.tools.map(({ name, description, parameters }) => ({ name, description, parameters }));
4410
+ this.opened = perform({
4411
+ action: "open",
4412
+ session: this.id,
4413
+ ...options.instructions ? { instructions: options.instructions } : {},
4414
+ ...tools.length > 0 ? { tools } : {}
4415
+ });
4416
+ this.opened.catch(() => {
4417
+ });
4418
+ if (tools.length > 0) void this.opened.then(() => this.serveTools()).catch(() => {
4419
+ });
4420
+ }
4421
+ /** Runs the model's tool calls as they come, until the session closes. */
4422
+ async serveTools() {
4423
+ while (!this.closed) {
4424
+ const request = await perform({ action: "toolCall", session: this.id });
4425
+ if (!request || request.closed || request.call === void 0) return;
4426
+ const tool = this.tools.find((candidate) => candidate.name === request.name);
4427
+ let answer;
4428
+ try {
4429
+ if (!tool) throw new Error(`no tool named ${String(request.name)}`);
4430
+ const output = await tool.call(request.arguments ?? {});
4431
+ answer = { output: typeof output === "string" ? output : JSON.stringify(output ?? null) };
4432
+ } catch (error) {
4433
+ answer = { error: error instanceof Error ? error.message : String(error) };
4434
+ }
4435
+ await perform({ action: "toolResult", session: this.id, call: request.call, ...answer });
4436
+ }
4437
+ }
4438
+ /** Whether a response is under way. */
4439
+ get isResponding() {
4440
+ return this.responses > 0;
4441
+ }
4442
+ /** Loads the model ahead of the first prompt, so it answers sooner. */
4443
+ prewarm() {
4444
+ void this.opened.then(() => perform({ action: "prewarm", session: this.id })).catch(() => {
4445
+ });
4446
+ }
4447
+ async respond(to, options) {
4448
+ this.responses += 1;
4449
+ try {
4450
+ await this.opened;
4451
+ return await perform({ action: "respond", session: this.id, prompt: to, ...options ? { options } : {} });
4452
+ } finally {
4453
+ this.responses -= 1;
4454
+ }
4455
+ }
4456
+ streamResponse(to, options) {
4457
+ this.responses += 1;
4458
+ return new ResponseStream(this, this.opened, to, options, () => {
4459
+ this.responses -= 1;
4460
+ });
4461
+ }
4462
+ /** Ends the session: the host lets its conversation go. */
4463
+ close() {
4464
+ if (this.closed) return;
4465
+ this.closed = true;
4466
+ void this.opened.then(() => perform({ action: "close", session: this.id })).catch(() => {
4467
+ });
4468
+ }
4469
+ }
3577
4470
  function wakeLockAPI() {
3578
4471
  const nav = globalThis.navigator;
3579
4472
  return nav?.wakeLock ?? null;
@@ -4321,13 +5214,11 @@ const NONE = {
4321
5214
  caches: "file:///data/Library/Caches",
4322
5215
  temporary: "file:///tmp"
4323
5216
  };
4324
- async function bytesOf(contents) {
5217
+ function hostData(contents) {
4325
5218
  if (typeof contents === "string") return new TextEncoder().encode(contents);
4326
5219
  if (contents instanceof Uint8Array) return contents;
4327
5220
  if (contents instanceof ArrayBuffer) return new Uint8Array(contents);
4328
- const blob = contents;
4329
- if (typeof blob.bytes === "function") return await blob.bytes();
4330
- return new Uint8Array(await blob.arrayBuffer());
5221
+ return contents;
4331
5222
  }
4332
5223
  function createFileManager(host) {
4333
5224
  const required = () => {
@@ -4353,9 +5244,16 @@ function createFileManager(host) {
4353
5244
  return directories().temporary;
4354
5245
  },
4355
5246
  url: (directory, path) => fileURL(directory, path),
4356
- contents: async (url) => required().read(url),
5247
+ contents: async (url, range) => {
5248
+ const files = required();
5249
+ if (!range) return files.read(url);
5250
+ if (files.readRange) return files.readRange(url, range.start, range.end);
5251
+ const all = await files.read(url);
5252
+ const start = Math.min(Math.max(0, range.start), all.byteLength);
5253
+ return all.subarray(start, Math.min(Math.max(start, range.end), all.byteLength));
5254
+ },
4357
5255
  text: async (url) => new TextDecoder().decode(await required().read(url)),
4358
- write: async (url, contents) => required().write(url, await bytesOf(contents)),
5256
+ write: async (url, contents) => required().write(url, hostData(contents)),
4359
5257
  contentsOfDirectory: async (url) => required().list(url),
4360
5258
  createDirectory: async (url) => required().createDirectory(url),
4361
5259
  fileExists: async (url) => await required().attributes(url) !== null,
@@ -4462,6 +5360,8 @@ function List(props) {
4462
5360
  if (editing) listProps["editing"] = true;
4463
5361
  let selection = null;
4464
5362
  const split = useContext(SplitSelectionContext);
5363
+ let reportNow = () => {
5364
+ };
4465
5365
  let selected = false;
4466
5366
  let clearSelection = NO_CLEAR;
4467
5367
  if (own.selection !== void 0) {
@@ -4478,13 +5378,16 @@ function List(props) {
4478
5378
  if (next.has(tag)) next.delete(tag);
4479
5379
  else next.add(tag);
4480
5380
  setValue(next);
5381
+ reportNow(next.size > 0, () => setValue(/* @__PURE__ */ new Set()));
4481
5382
  } else {
4482
- setValue(editing && Object.is(value, tag) ? null : tag);
5383
+ const next = editing && Object.is(value, tag) ? null : tag;
5384
+ setValue(next);
5385
+ reportNow(next !== null, () => setValue(null));
4483
5386
  }
4484
5387
  }
4485
5388
  };
4486
5389
  }
4487
- useReportSplitSelection(split, selected, clearSelection, own.selection !== void 0);
5390
+ reportNow = useReportSplitSelection(split, selected, clearSelection, own.selection !== void 0);
4488
5391
  const refresh = useRefreshable(takeModifier(modifiers, "refreshable"));
4489
5392
  Object.assign(listProps, refresh.props);
4490
5393
  const ownSettings = takeScrollSettings(modifiers);
@@ -4502,15 +5405,26 @@ function Form(props) {
4502
5405
  function Section(props) {
4503
5406
  const { own, modifiers } = splitProps(props);
4504
5407
  const pinned = useContext(PinnedContext);
5408
+ const [expanded, setExpanded] = own.isExpanded !== void 0 ? readBinding(own.isExpanded, own.onExpandedChange) : [true, () => {
5409
+ }];
5410
+ const collapsible = own.isExpanded !== void 0;
4505
5411
  let header = supplement(own.header);
4506
5412
  let footer = supplement(own.footer);
4507
5413
  if (pinned?.includes("sectionHeaders") && header.text !== null && !header.view) header = { text: null, view: /* @__PURE__ */ jsx(Text, { children: header.text }) };
4508
5414
  if (pinned?.includes("sectionFooters") && footer.text !== null && !footer.view) footer = { text: null, view: /* @__PURE__ */ jsx(Text, { children: footer.text }) };
4509
5415
  const children = [];
4510
- if (header.view) children.push(sb("sectionheader", {}, void 0, header.view));
4511
- if (own.children !== void 0) children.push(wrapRows(own.children));
4512
- if (footer.view) children.push(sb("sectionfooter", {}, void 0, footer.view));
4513
- return applyModifiers(sb("section", { header: header.text, footer: footer.text }, void 0, children), modifiers);
5416
+ if (header.view) children.push(/* @__PURE__ */ jsx(Fragment, { children: sb("sectionheader", {}, void 0, header.view) }, "header"));
5417
+ if (own.children !== void 0) children.push(/* @__PURE__ */ jsx(Fragment, { children: wrapRows(own.children) }, "rows"));
5418
+ if (footer.view) children.push(/* @__PURE__ */ jsx(Fragment, { children: sb("sectionfooter", {}, void 0, footer.view) }, "footer"));
5419
+ return applyModifiers(
5420
+ sb(
5421
+ "section",
5422
+ collapsible ? { header: header.text, footer: footer.text, collapsible: true, expanded } : { header: header.text, footer: footer.text },
5423
+ collapsible ? { tap: () => setExpanded(!expanded) } : void 0,
5424
+ children
5425
+ ),
5426
+ modifiers
5427
+ );
4514
5428
  }
4515
5429
  function wrapRows(children) {
4516
5430
  let offset = 0;
@@ -4618,6 +5532,11 @@ function Host(props) {
4618
5532
  );
4619
5533
  return applyModifiers(sb("host", { content: key, sizing }), modifiers);
4620
5534
  }
5535
+ function RegisteredView(all) {
5536
+ const { own, modifiers } = splitProps(all);
5537
+ const on = own.onEvent ? { registered: (event) => own.onEvent(event.value) } : void 0;
5538
+ return applyModifiers(sb("registered", { name: own.name, props: own.props ?? {} }, on, own.children), modifiers);
5539
+ }
4621
5540
  function Menu(props) {
4622
5541
  const { own, modifiers } = splitProps(props);
4623
5542
  const label = own.title !== void 0 ? /* @__PURE__ */ jsx(Text, { children: own.title }) : own.label;
@@ -5182,22 +6101,10 @@ function DisclosureGroup(props) {
5182
6101
  const label = typeof own.label === "string" ? /* @__PURE__ */ jsx(Text, { children: own.label }) : own.label;
5183
6102
  const toggle = () => withAnimation({ type: "easeInOut", duration: 0.25 }, () => setExpanded(!expanded));
5184
6103
  return applyModifiers(
5185
- /* @__PURE__ */ jsxs(VStack, { alignment: "leading", spacing: 0, children: [
5186
- /* @__PURE__ */ jsx(Button, { action: toggle, buttonStyle: "plain", children: /* @__PURE__ */ jsxs(HStack, { children: [
5187
- styled({ foreground: { hierarchical: 1 } }, label),
5188
- /* @__PURE__ */ jsx(Spacer, {}),
5189
- /* @__PURE__ */ jsx(
5190
- Image$1,
5191
- {
5192
- systemName: "chevron.right",
5193
- font: { textStyle: "footnote", weight: "semibold" },
5194
- foregroundStyle: "tint",
5195
- rotationEffect: expanded ? 90 : 0
5196
- }
5197
- )
5198
- ] }) }),
5199
- expanded && /* @__PURE__ */ jsx(VStack, { alignment: "leading", spacing: 8, padding: ["top", 8], transition: { kind: "opacity" }, children: own.children })
5200
- ] }),
6104
+ sb("disclosuregroup", { expanded }, { tap: toggle }, [
6105
+ /* @__PURE__ */ jsx(Fragment, { children: sb("zstack", { alignment: "center" }, void 0, label) }, "label"),
6106
+ /* @__PURE__ */ jsx(Fragment, { children: own.children }, "content")
6107
+ ]),
5201
6108
  modifiers
5202
6109
  );
5203
6110
  }
@@ -5293,16 +6200,46 @@ function ColorPicker(props) {
5293
6200
  modifiers
5294
6201
  );
5295
6202
  }
6203
+ const isFile = (item) => typeof item === "object" && !(item instanceof URL) && "data" in item;
6204
+ const MIME_BY_EXTENSION = {
6205
+ txt: "text/plain",
6206
+ md: "text/markdown",
6207
+ csv: "text/csv",
6208
+ json: "application/json",
6209
+ html: "text/html",
6210
+ pdf: "application/pdf",
6211
+ png: "image/png",
6212
+ jpg: "image/jpeg",
6213
+ jpeg: "image/jpeg",
6214
+ gif: "image/gif",
6215
+ svg: "image/svg+xml",
6216
+ m4a: "audio/mp4",
6217
+ mp3: "audio/mpeg",
6218
+ mp4: "video/mp4",
6219
+ mov: "video/quicktime",
6220
+ zip: "application/zip"
6221
+ };
6222
+ async function sharedFile(file) {
6223
+ const bytes = typeof file.data === "string" ? new TextEncoder().encode(file.data) : file.data instanceof Uint8Array ? file.data : file.data instanceof ArrayBuffer ? new Uint8Array(file.data) : new Uint8Array(await file.data.arrayBuffer());
6224
+ const extension = file.name.includes(".") ? file.name.slice(file.name.lastIndexOf(".") + 1).toLowerCase() : "";
6225
+ return { name: file.name, type: file.type ?? MIME_BY_EXTENSION[extension] ?? "application/octet-stream", data: base64Encode(bytes) };
6226
+ }
6227
+ async function shareParamsOf(props) {
6228
+ const item = props.item;
6229
+ const isURL = item instanceof URL || typeof item === "string" && /^[a-z][a-z0-9+.-]*:\/\//i.test(item);
6230
+ const params = {};
6231
+ if (props.subject) params.title = props.subject;
6232
+ if (isURL) params.url = String(item);
6233
+ const text = [props.message, typeof item === "string" && !isURL ? item : null].filter(Boolean).join("\n");
6234
+ if (text) params.text = text;
6235
+ const files = [...isFile(item) ? [item] : [], ...props.files ?? []];
6236
+ if (files.length) params.files = await Promise.all(files.map(sharedFile));
6237
+ return params;
6238
+ }
5296
6239
  function ShareLink(props) {
5297
6240
  const { own, modifiers } = splitProps(props);
5298
6241
  const share = () => {
5299
- const isURL = own.item instanceof URL || /^[a-z][a-z0-9+.-]*:\/\//i.test(own.item);
5300
- const params = {};
5301
- if (own.subject) params["title"] = own.subject;
5302
- if (isURL) params["url"] = String(own.item);
5303
- const text = [own.message, isURL ? null : String(own.item)].filter(Boolean).join("\n");
5304
- if (text) params["text"] = text;
5305
- performHostRequest({ op: "request", id: 0, kind: "share", params }, browserEnvironment).catch((error) => console.error(error));
6242
+ shareParamsOf(own).then((params) => performHostRequest({ op: "request", id: 0, kind: "share", params }, browserEnvironment)).catch((error) => console.error(error));
5306
6243
  };
5307
6244
  return applyModifiers(
5308
6245
  /* @__PURE__ */ jsx(Button, { action: share, children: own.children ?? /* @__PURE__ */ jsx(Label, { title: own.title ?? "Share", systemImage: "square.and.arrow.up" }) }),
@@ -5923,6 +6860,23 @@ function KeyframeAnimator({
5923
6860
  return children(value);
5924
6861
  }
5925
6862
  export {
6863
+ AVAudioEngine,
6864
+ AVAudioFile,
6865
+ AVAudioInputNode,
6866
+ AVAudioMixerNode,
6867
+ AVAudioNode,
6868
+ AVAudioOutputNode,
6869
+ AVAudioPCMBuffer,
6870
+ AVAudioPlayerNode,
6871
+ AVAudioTime,
6872
+ AVAudioUnitDelay,
6873
+ AVAudioUnitDistortion,
6874
+ AVAudioUnitEQ,
6875
+ AVAudioUnitEQFilterParameters,
6876
+ AVAudioUnitEffect,
6877
+ AVAudioUnitReverb,
6878
+ AVAudioUnitSampler,
6879
+ AVAudioUnitTimePitch,
5926
6880
  AVPlayer,
5927
6881
  AVQueuePlayer,
5928
6882
  AngularGradient,
@@ -5953,6 +6907,7 @@ export {
5953
6907
  ForEach,
5954
6908
  Form,
5955
6909
  Gauge,
6910
+ GenerationError,
5956
6911
  GeometryChangeView,
5957
6912
  GeometryReader,
5958
6913
  GraphicsContext,
@@ -5968,6 +6923,7 @@ export {
5968
6923
  LOADING_INDICATOR_DELAY_MS,
5969
6924
  Label,
5970
6925
  LabeledContent,
6926
+ LanguageModelSession,
5971
6927
  Layout,
5972
6928
  LazyHGrid,
5973
6929
  LazyHStack,
@@ -5991,6 +6947,8 @@ export {
5991
6947
  ProgressView,
5992
6948
  RadialGradient,
5993
6949
  Rectangle,
6950
+ RegisteredView,
6951
+ ResponseStream,
5994
6952
  RoundedRectangle,
5995
6953
  Row,
5996
6954
  RuleMark,
@@ -6006,6 +6964,7 @@ export {
6006
6964
  SplitSelectionContext,
6007
6965
  Stepper,
6008
6966
  SwiftUIView,
6967
+ SystemLanguageModel,
6009
6968
  Tab,
6010
6969
  TabView,
6011
6970
  Table,
@@ -6022,6 +6981,7 @@ export {
6022
6981
  UnevenRoundedRectangle,
6023
6982
  VStack,
6024
6983
  VideoPlayer,
6984
+ ViewRegistry,
6025
6985
  ViewThatFits,
6026
6986
  VisualEffect,
6027
6987
  ZStack,
@@ -6052,6 +7012,7 @@ export {
6052
7012
  moveItems,
6053
7013
  nextGeometry,
6054
7014
  niceStep,
7015
+ noteInName,
6055
7016
  opacity,
6056
7017
  openURL,
6057
7018
  padding,
@@ -6060,8 +7021,10 @@ export {
6060
7021
  providingScrollSettings,
6061
7022
  readBinding,
6062
7023
  rgb,
7024
+ samplerZones,
6063
7025
  scrollGeometryOf,
6064
7026
  shapeStyle,
7027
+ shareParamsOf,
6065
7028
  signInWithApple,
6066
7029
  system,
6067
7030
  takeScrollSettings,
@@ -6069,6 +7032,7 @@ export {
6069
7032
  unitPoint,
6070
7033
  useAVPlayer,
6071
7034
  useAppStorage,
7035
+ useAudioEngine,
6072
7036
  useAudioRecorder,
6073
7037
  useColorScheme,
6074
7038
  useContextBridge,
@@ -6086,6 +7050,7 @@ export {
6086
7050
  useScenePhase,
6087
7051
  useSpeechRecognition,
6088
7052
  useSpeechSynthesis,
7053
+ useSystemLanguageModel,
6089
7054
  useUndoManager,
6090
7055
  useViewEnvironment,
6091
7056
  useWakeLock,