gesso-framework 0.4.1 → 0.5.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
@@ -1,7 +1,9 @@
1
- import { $ as structurallyEqual, A as printPropValue, B as channel, C as captureConsole, D as formatNodePath, E as formatAge, F as applyPatch, G as mutate, H as viewKeys, I as applyPatches, J as select, K as Resource, L as diffProjection, M as provide, N as findUnplainPath, O as formatNodeReport, P as requirePlainData, Q as equalityOf, R as isChannelClientMessage, S as workerHandle, T as describeStream, U as debounced, V as defineChannel, W as throttled, X as computed, Y as ComputedCell, Z as derive, _ as isHubMessage, a as undoable, at as isOutputTarget, b as portHandle, c as classifyStorageError, ct as trackRead, d as storageReadValue, et as InternalState, f as pick, g as APPLICATION_WORKER, h as serveChannels, i as OpfsStorage, it as into, j as ProvidedChannel, k as formatStream, l as storageErrorMessage, lt as withBodyOf, m as serve, n as persisted, nt as InputCell, o as UndoStack, ot as output, p as pickKeys, q as resource, r as IndexedDbStorage, rt as input, s as MemoryStorage, st as outputTargetOf, t as PersistedState, tt as internalState, u as storageReadFailure, v as isPortErrorMessage, w as isConsoleEntryMessage, x as servePorts, y as isPortHandshake, z as isChannelHostMessage } from "./persisted-CsTPnjkc.js";
1
+ import { $ as structurallyEqual, A as printPropValue, B as channel, C as captureConsole, D as formatNodePath, E as formatAge, F as applyPatch, G as mutate, H as viewKeys, I as applyPatches, J as select, K as Resource, L as diffProjection, M as provide, N as findUnplainPath, O as formatNodeReport, P as requirePlainData, Q as equalityOf, R as isChannelClientMessage, S as workerHandle, T as describeStream, U as debounced, V as defineChannel, W as throttled, X as computed, Y as ComputedCell, Z as derive, _ as isHubMessage, a as undoable, at as isOutputTarget, b as portHandle, c as classifyStorageError, ct as trackRead, d as storageReadValue, et as InternalState, f as pick, g as APPLICATION_WORKER, h as serveChannels, i as OpfsStorage, it as into, j as ProvidedChannel, k as formatStream, l as storageErrorMessage, lt as withBodyOf, m as serve, n as persisted, nt as InputCell, o as UndoStack, ot as output, p as pickKeys, q as resource, r as IndexedDbStorage, rt as input, s as MemoryStorage, st as outputTargetOf, t as PersistedState, tt as internalState, u as storageReadFailure, v as isPortErrorMessage, w as isConsoleEntryMessage, x as servePorts, y as isPortHandshake, z as isChannelHostMessage } from "./persisted-pix1fS1D.js";
2
2
  import { a as Each, i as show, n as getComponentMetadata, o as each, r as Show, t as createComponent } from "./createComponent-3n1ZSql8.js";
3
+ import { c as channelSchema, l as describeChannel, t as AGENT_PORT } from "./remote-xxij8zXe.js";
4
+ import { n as serveApplicationAgent } from "./app-BvlIO1G9.js";
3
5
  import { BehaviorSubject, EMPTY, Observable, Subject, Subscription, distinctUntilChanged, map } from "rxjs";
4
- import { AnimationDriver, Box, Canvas2DRenderer, CanvasPlatformSurface, CanvasTextMeasurer, Constraints, DefaultImageResolver, DefaultVideoResolver, DirtyFlags, EnvironmentNotifier, FocusNotifier, IconRasterizer, LayoutEngine, LayoutInspector, LayoutNotifier, MARK_PREFIX, Stack, TEXT_RUN_ID_SEPARATOR, UI_ROLES, UI_SEMANTIC_STATES, UiAnimationFrameClock, UiEditingController, UiEnvironmentKeys, UiEventType, UiFindController, UiFocusManager, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiHostFrameClock, UiInputDispatcher, UiInsetRegistry, UiKeyboardController, UiNodeType, UiPlatformAdapter, UiPointerController, UiPointerEvent, UiScheduler, UiSelectionController, UiSharedElements, UiSpring, UiTimerFrameClock, UiTouchScroller, UiVirtualSheet, UiVirtualWindow, UiWheelController, VIRTUAL_INDEX_PROP, VIRTUAL_LEAD_PROP, VIRTUAL_SHEET_PROP, VIRTUAL_WINDOW_PROP, WebGPURenderer, buildSemanticsSubtree, buildSemanticsTree, bumpFontStack, capturePointer, createCanvasSurface, createTween, createWebGPUSurface, defaultMotion, describeOverrides, diffSemantics, dragSessionFor, easings, formatExplanation, insetsEqual, isComponentLikeElement, isObservable, isWebGPUAvailable, linear, linkOf, markInstant, markNow, measure, measureSpan, motion, noInsets, observeViewportInsets, percent, performanceMarksEnabled, pointerDeviceOf, prepareInputSurface, propertyEffects, recordsEqual, registerFontStack, resolveCursor, scrollbarThumb, semanticsInertAbove, setPerformanceMarks, setPerformanceMarks as setPerformanceMarks$1, textRunOfRecordId, themeTokens, touchActionFor, wheelDeltaYOf } from "gesso-core";
6
+ import { AnimationDriver, Box, Canvas2DRenderer, CanvasPlatformSurface, CanvasTextMeasurer, Constraints, DefaultImageResolver, DefaultVideoResolver, DirtyFlags, EnvironmentNotifier, FocusNotifier, IconRasterizer, LayoutEngine, LayoutInspector, LayoutNotifier, MARK_PREFIX, Stack, TEXT_RUN_ID_SEPARATOR, UI_ROLES, UI_SEMANTIC_STATES, UiAnimationFrameClock, UiEditingController, UiEnvironmentKeys, UiEventType, UiFindController, UiFocusManager, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiHostFrameClock, UiInputDispatcher, UiInsetRegistry, UiKeyboardController, UiNodeType, UiPlatformAdapter, UiPointerController, UiPointerEvent, UiScheduler, UiSelectionController, UiSharedElements, UiSpring, UiTimerFrameClock, UiTouchScroller, UiVirtualSheet, UiVirtualWindow, UiWheelController, VIRTUAL_INDEX_PROP, VIRTUAL_LEAD_PROP, VIRTUAL_SHEET_PROP, VIRTUAL_WINDOW_PROP, WebGPURenderer, buildSemanticsSubtree, buildSemanticsTree, bumpFontStack, capturePointer, createCanvasSurface, createTween, createWebGPUSurface, defaultMotion, describeOverrides, diffSemantics, dragSessionFor, dropIndexShifts, easings, formatExplanation, insetsEqual, isComponentLikeElement, isObservable, isWebGPUAvailable, linear, linkOf, markInstant, markNow, measure, measureSpan, motion, noInsets, observeViewportInsets, percent, performanceMarksEnabled, pointerDeviceOf, prepareInputSurface, propertyEffects, recordsEqual, registerFontStack, resolveCursor, rewalkSemantics, scrollRange, scrollbarThumb, semanticsInertAbove, semanticsMemory, setPerformanceMarks, setPerformanceMarks as setPerformanceMarks$1, textRunOfRecordId, themeTokens, touchActionFor, wheelDeltaYOf } from "gesso-core";
5
7
  //#region src/Component.ts
6
8
  /**
7
9
  * Base class for all Gesso framework components.
@@ -759,6 +761,7 @@ function createChannelRegistry(registrations, onError) {
759
761
  }
760
762
  return {
761
763
  registry,
764
+ workers: handles,
762
765
  dispose: () => {
763
766
  for (const channel of local) channel.dispose();
764
767
  local.length = 0;
@@ -1143,12 +1146,12 @@ var ComponentHost = class {
1143
1146
  return;
1144
1147
  }
1145
1148
  if (isObservable(provided)) {
1146
- const subscription = provided.subscribe((value) => cell.next(value));
1149
+ const subscription = provided.subscribe((value) => pushInput(cell, value));
1147
1150
  this.inputSubscriptions.set(inputName, subscription);
1148
1151
  this.subscriptions.add(subscription);
1149
1152
  return;
1150
1153
  }
1151
- cell.next(provided);
1154
+ pushInput(cell, provided);
1152
1155
  }
1153
1156
  validateInputs() {
1154
1157
  const metadata = getComponentMetadata(this.element.component);
@@ -1173,6 +1176,27 @@ var ComponentHost = class {
1173
1176
  }
1174
1177
  }
1175
1178
  };
1179
+ /**
1180
+ * Hands an input cell a value from its parent, unless it already holds
1181
+ * that very value.
1182
+ *
1183
+ * A parent that re-renders hands every child its props again, and a
1184
+ * prop that is an Observable built in the parent's render is a new
1185
+ * source each time, which re-subscribes and replays the current value.
1186
+ * Pushed through regardless, that replay re-ran every binding derived
1187
+ * from the input. In the issue tracker's editor, inserting one block
1188
+ * re-rendered the list of 2,868, each replay rebuilt its block's text
1189
+ * runs as a new array, and every editable on the page was re-measured:
1190
+ * 12,912 nodes for one Enter. Values that cross into a component are
1191
+ * treated as immutable everywhere else in the framework (bindings hold
1192
+ * on to them, patches share structure), so the same reference is the
1193
+ * same value and there is nothing to tell the child.
1194
+ */
1195
+ function pushInput(cell, value) {
1196
+ const current = cell.getValue();
1197
+ if (current !== void 0 && Object.is(current, value)) return;
1198
+ cell.next(value);
1199
+ }
1176
1200
  //#endregion
1177
1201
  //#region src/ComponentHostResolver.ts
1178
1202
  /**
@@ -1704,6 +1728,7 @@ function filesDuringDrag(event) {
1704
1728
  var ShellService = class {
1705
1729
  handler = null;
1706
1730
  scheme = internalState("light");
1731
+ contrastState = internalState("standard");
1707
1732
  insets = internalState(noInsets);
1708
1733
  isFullscreen = internalState(false);
1709
1734
  /** Popups asked for and not yet answered, by the id sent with each. */
@@ -1712,6 +1737,9 @@ var ShellService = class {
1712
1737
  /** Storage requests asked for and not yet answered, by the id sent with each. */
1713
1738
  stores = /* @__PURE__ */ new Map();
1714
1739
  nextStorageId = 1;
1740
+ /** Clipboard writes asked for and not yet answered, by the id sent with each. */
1741
+ copies = /* @__PURE__ */ new Map();
1742
+ nextCopyId = 1;
1715
1743
  /** File requests asked for and not yet answered, by the id sent with each. */
1716
1744
  files = /* @__PURE__ */ new Map();
1717
1745
  nextFileId = 1;
@@ -1744,6 +1772,18 @@ var ShellService = class {
1744
1772
  * read it beside a channel's view; its setter stays private here.
1745
1773
  */
1746
1774
  colorScheme = this.scheme;
1775
+ /**
1776
+ * The contrast the platform is asking for: `high` when the person has
1777
+ * turned up contrast or turned on a contrast theme, `standard`
1778
+ * otherwise. Reported by the shell like `colorScheme`, and as with it,
1779
+ * what it means is the application's: `withContrast(theme, contrast)`
1780
+ * is the theme that answers it.
1781
+ *
1782
+ * const theme = combineLatest([shell.colorScheme, shell.contrast]).pipe(
1783
+ * map(([scheme, contrast]) => withContrast(scheme === 'dark' ? darkTheme : lightTheme, contrast))
1784
+ * );
1785
+ */
1786
+ contrast = this.contrastState;
1747
1787
  /** The current appearance, for code that needs it without subscribing. */
1748
1788
  get currentColorScheme() {
1749
1789
  return this.scheme.value;
@@ -1783,6 +1823,10 @@ var ShellService = class {
1783
1823
  applyColorScheme(scheme) {
1784
1824
  if (this.scheme.value !== scheme) this.scheme.value = scheme;
1785
1825
  }
1826
+ /** Called by the runtime when the shell reports the contrast; not for applications. */
1827
+ applyContrast(contrast) {
1828
+ if (this.contrastState.value !== contrast) this.contrastState.value = contrast;
1829
+ }
1786
1830
  /**
1787
1831
  * Called by the runtime when the shell reports the platform's insets.
1788
1832
  *
@@ -1793,12 +1837,44 @@ var ShellService = class {
1793
1837
  applyViewportInsets(insets) {
1794
1838
  if (!insetsEqual(this.insets.value, insets)) this.insets.value = insets;
1795
1839
  }
1796
- /** Puts text on the system clipboard. */
1840
+ /**
1841
+ * Puts text on the system clipboard, and answers whether it got there.
1842
+ *
1843
+ * Most callers ignore the answer, and may: the text is on its way the
1844
+ * moment this returns. It is for an application that tells the person
1845
+ * what happened, a "Copied" toast after a menu command, say, which
1846
+ * would be a lie on the occasions the browser refused. It does refuse:
1847
+ * the clipboard wants a focused document, and some browsers a fresh
1848
+ * gesture, and a key pressed in a render worker reaches the window's
1849
+ * clipboard a message later than it reached the canvas.
1850
+ *
1851
+ * With no shell installed the answer is false, as `openPopup`'s is,
1852
+ * rather than a promise that never settles.
1853
+ */
1797
1854
  copyText(text) {
1798
- this.handler?.({
1855
+ const handler = this.handler;
1856
+ if (handler === void 0 || handler === null) return Promise.resolve(false);
1857
+ const id = this.nextCopyId++;
1858
+ const settled = new Promise((resolve) => {
1859
+ this.copies.set(id, resolve);
1860
+ });
1861
+ handler({
1799
1862
  type: "clipboard",
1863
+ id,
1800
1864
  text
1801
1865
  });
1866
+ return settled;
1867
+ }
1868
+ /**
1869
+ * Called by the runtime when the shell reports whether a clipboard
1870
+ * write landed. Not for applications. An unknown id is ignored, on
1871
+ * `settlePopup`'s terms.
1872
+ */
1873
+ settleClipboard(id, copied) {
1874
+ const resolve = this.copies.get(id);
1875
+ if (resolve === void 0) return;
1876
+ this.copies.delete(id);
1877
+ resolve(copied);
1802
1878
  }
1803
1879
  /** Opens a URL in the user's browser, in a new tab or window. */
1804
1880
  openUrl(url) {
@@ -2591,7 +2667,7 @@ var EditingProxy = class {
2591
2667
  * the reason the field's semantics live here rather than on a second
2592
2668
  * element beside it.
2593
2669
  */
2594
- describe(record) {
2670
+ describe(record, activeDescendant) {
2595
2671
  if (this.disposed) return;
2596
2672
  const textarea = this.textarea;
2597
2673
  if (record === null) {
@@ -2603,7 +2679,9 @@ var EditingProxy = class {
2603
2679
  "aria-required",
2604
2680
  "aria-invalid",
2605
2681
  "aria-readonly",
2606
- "aria-disabled"
2682
+ "aria-disabled",
2683
+ "aria-expanded",
2684
+ "aria-activedescendant"
2607
2685
  ]) textarea.removeAttribute(attribute);
2608
2686
  return;
2609
2687
  }
@@ -2616,6 +2694,8 @@ var EditingProxy = class {
2616
2694
  setOrClear(textarea, "aria-invalid", states.has("invalid") ? "true" : void 0);
2617
2695
  setOrClear(textarea, "aria-readonly", states.has("readonly") ? "true" : void 0);
2618
2696
  setOrClear(textarea, "aria-disabled", record.disabled === true ? "true" : void 0);
2697
+ setOrClear(textarea, "aria-expanded", states.has("expanded") ? "true" : states.has("collapsed") ? "false" : void 0);
2698
+ setOrClear(textarea, "aria-activedescendant", activeDescendant);
2619
2699
  }
2620
2700
  dispose() {
2621
2701
  if (this.disposed) return;
@@ -2678,7 +2758,8 @@ var EditingProxy = class {
2678
2758
  const clipboard = event.clipboardData;
2679
2759
  event.preventDefault();
2680
2760
  const text = clipboard?.getData("text/plain") ?? "";
2681
- if (text.length > 0) this.sink.paste(text);
2761
+ const html = clipboard?.getData("text/html") ?? "";
2762
+ if (text.length > 0 || html.length > 0) this.sink.paste(text, html.length > 0 ? html : null);
2682
2763
  };
2683
2764
  /**
2684
2765
  * A paste aimed at the canvas rather than at the field.
@@ -2703,6 +2784,7 @@ var EditingProxy = class {
2703
2784
  if (clipboard === null || state === null) return;
2704
2785
  event.preventDefault();
2705
2786
  clipboard.setData("text/plain", state.text.slice(state.selectionStart, state.selectionEnd));
2787
+ if (state.html !== void 0) clipboard.setData("text/html", state.html);
2706
2788
  };
2707
2789
  const onCut = (event) => {
2708
2790
  const state = this.state;
@@ -2757,17 +2839,15 @@ var EditingProxy = class {
2757
2839
  }
2758
2840
  };
2759
2841
  /**
2760
- * Writes text to the system clipboard from the main thread. The async
2761
- * API needs a secure context and, in some browsers, a recent user
2762
- * gesture; the `execCommand` fallback covers the rest.
2842
+ * Writes text to the system clipboard from the main thread, and answers
2843
+ * whether it got there. The async API needs a secure context and, in
2844
+ * some browsers, a recent user gesture; the `execCommand` fallback
2845
+ * covers the rest. False means both refused. It never rejects.
2763
2846
  */
2764
2847
  function writeClipboard(text, doc = document) {
2765
2848
  const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
2766
- if (clipboard !== void 0 && typeof clipboard.writeText === "function") {
2767
- clipboard.writeText(text).catch(() => copyWithExecCommand(text, doc));
2768
- return;
2769
- }
2770
- copyWithExecCommand(text, doc);
2849
+ if (clipboard !== void 0 && typeof clipboard.writeText === "function") return clipboard.writeText(text).then(() => true, () => copyWithExecCommand(text, doc));
2850
+ return Promise.resolve(copyWithExecCommand(text, doc));
2771
2851
  }
2772
2852
  function copyWithExecCommand(text, doc) {
2773
2853
  const previous = doc.activeElement;
@@ -2778,7 +2858,9 @@ function copyWithExecCommand(text, doc) {
2778
2858
  doc.body.appendChild(scratch);
2779
2859
  scratch.select();
2780
2860
  try {
2781
- doc.execCommand("copy");
2861
+ return doc.execCommand("copy");
2862
+ } catch {
2863
+ return false;
2782
2864
  } finally {
2783
2865
  scratch.remove();
2784
2866
  previous?.focus?.({ preventScroll: true });
@@ -2802,7 +2884,9 @@ const STATE_ATTRIBUTES = {
2802
2884
  invalid: ["aria-invalid", "true"],
2803
2885
  required: ["aria-required", "true"],
2804
2886
  readonly: ["aria-readonly", "true"],
2805
- modal: ["aria-modal", "true"]
2887
+ modal: ["aria-modal", "true"],
2888
+ multiselectable: ["aria-multiselectable", "true"],
2889
+ current: ["aria-current", "true"]
2806
2890
  };
2807
2891
  /**
2808
2892
  * Roles whose checked state is required rather than optional.
@@ -2848,8 +2932,12 @@ const RECORD_ATTRIBUTES = [
2848
2932
  "aria-invalid",
2849
2933
  "aria-required",
2850
2934
  "aria-readonly",
2851
- "aria-modal"
2935
+ "aria-modal",
2936
+ "aria-multiselectable",
2937
+ "aria-current",
2938
+ "aria-activedescendant"
2852
2939
  ];
2940
+ let mirrors = 0;
2853
2941
  /**
2854
2942
  * Roles whose accessible name comes from what the element contains
2855
2943
  * rather than from an `aria-label`, plus prose, which has no role at
@@ -2915,6 +3003,12 @@ var SemanticsMirror = class {
2915
3003
  applying = false;
2916
3004
  focusedId = null;
2917
3005
  disposed = false;
3006
+ /**
3007
+ * Prefixes every element's DOM id, which `aria-activedescendant`
3008
+ * refers to. Per mirror, because two apps on one page have nodes with
3009
+ * the same ids.
3010
+ */
3011
+ idPrefix = `gesso-${mirrors++}-`;
2918
3012
  constructor(canvas, sink, editing = null) {
2919
3013
  this.canvas = canvas;
2920
3014
  this.sink = sink;
@@ -2930,7 +3024,7 @@ var SemanticsMirror = class {
2930
3024
  width: "0px",
2931
3025
  height: "0px",
2932
3026
  pointerEvents: "none",
2933
- overflow: "hidden",
3027
+ overflow: "clip",
2934
3028
  margin: "0",
2935
3029
  padding: "0",
2936
3030
  border: "0",
@@ -2997,11 +3091,17 @@ var SemanticsMirror = class {
2997
3091
  record
2998
3092
  });
2999
3093
  this.ids.set(element, record.id);
3094
+ element.id = this.idPrefix + record.id;
3000
3095
  } else existing.record = record;
3001
3096
  this.describe(element, record);
3002
3097
  this.place(element, record);
3003
3098
  if (existing?.box !== void 0) this.position(existing);
3004
- if (record.id === this.focusedId) this.editing?.describe(this.editing.active ? record : null);
3099
+ if (record.id === this.focusedId) this.describeEditing(this.editing?.active === true ? record : null);
3100
+ }
3101
+ /** Hands the proxy the focused editable's record, with its active descendant's DOM id. */
3102
+ describeEditing(record) {
3103
+ const active = record?.activeDescendant;
3104
+ this.editing?.describe(record, active === void 0 ? void 0 : this.idPrefix + active);
3005
3105
  }
3006
3106
  createElement() {
3007
3107
  const element = this.doc.createElement("div");
@@ -3016,7 +3116,7 @@ var SemanticsMirror = class {
3016
3116
  padding: "0",
3017
3117
  border: "0",
3018
3118
  outline: "none",
3019
- overflow: "hidden",
3119
+ overflow: "clip",
3020
3120
  pointerEvents: "none",
3021
3121
  color: "transparent"
3022
3122
  });
@@ -3047,6 +3147,7 @@ var SemanticsMirror = class {
3047
3147
  setNumber(element, "aria-setsize", record.setSize);
3048
3148
  setNumber(element, "aria-level", record.level);
3049
3149
  if (record.valueText !== void 0) element.setAttribute("aria-valuetext", record.valueText);
3150
+ if (record.activeDescendant !== void 0) element.setAttribute("aria-activedescendant", this.idPrefix + record.activeDescendant);
3050
3151
  }
3051
3152
  /**
3052
3153
  * Puts an element under its parent, at the index the record gives.
@@ -3146,7 +3247,7 @@ var SemanticsMirror = class {
3146
3247
  this.focusedId = id;
3147
3248
  const entry = id === null ? void 0 : this.entries.get(id);
3148
3249
  if (this.editing !== null && this.editing.active) {
3149
- this.editing.describe(entry?.record ?? null);
3250
+ this.describeEditing(entry?.record ?? null);
3150
3251
  this.applying = true;
3151
3252
  try {
3152
3253
  this.editing.focus();
@@ -3155,7 +3256,7 @@ var SemanticsMirror = class {
3155
3256
  }
3156
3257
  return;
3157
3258
  }
3158
- this.editing?.describe(null);
3259
+ this.describeEditing(null);
3159
3260
  this.applying = true;
3160
3261
  try {
3161
3262
  if (entry !== void 0) entry.element.focus({ preventScroll: true });
@@ -3184,9 +3285,10 @@ var SemanticsMirror = class {
3184
3285
  const onKeyUp = (event) => this.sink.keyUp?.(event);
3185
3286
  const onPaste = (event) => {
3186
3287
  const text = event.clipboardData?.getData("text/plain") ?? "";
3187
- if (text.length === 0) return;
3288
+ const html = event.clipboardData?.getData("text/html") ?? "";
3289
+ if (text.length === 0 && html.length === 0) return;
3188
3290
  event.preventDefault();
3189
- this.sink.paste?.(text);
3291
+ this.sink.paste?.(text, html.length > 0 ? html : null);
3190
3292
  };
3191
3293
  this.container.addEventListener("click", onClick);
3192
3294
  this.container.addEventListener("focusin", onFocusIn);
@@ -3402,6 +3504,49 @@ function observeColorScheme(onChange) {
3402
3504
  return observeMediaQuery("(prefers-color-scheme: dark)", (dark) => onChange(dark ? "dark" : "light"));
3403
3505
  }
3404
3506
  //#endregion
3507
+ //#region src/app/contrast.ts
3508
+ /**
3509
+ * Watches the platform's contrast preference.
3510
+ *
3511
+ * `high` when the person has asked for more contrast
3512
+ * (`prefers-contrast: more`, macOS's Increase contrast and its
3513
+ * counterparts) or forced colours are on (Windows' contrast themes). A
3514
+ * canvas gets no forced colours from the browser, which only repaints
3515
+ * the DOM, so the application has to hear about it and draw its own:
3516
+ * `withContrast(theme, contrast)` is the theme that does.
3517
+ *
3518
+ * Shell-side, reported once at once and again on change, for the reason
3519
+ * `observeColorScheme` gives.
3520
+ */
3521
+ function observeContrast(onChange) {
3522
+ let more = false;
3523
+ let forced = false;
3524
+ let last = null;
3525
+ let ready = false;
3526
+ const report = () => {
3527
+ if (!ready) return;
3528
+ const contrast = more || forced ? "high" : "standard";
3529
+ if (contrast !== last) {
3530
+ last = contrast;
3531
+ onChange(contrast);
3532
+ }
3533
+ };
3534
+ const stopMore = observeMediaQuery("(prefers-contrast: more)", (matches) => {
3535
+ more = matches;
3536
+ report();
3537
+ });
3538
+ const stopForced = observeMediaQuery("(forced-colors: active)", (matches) => {
3539
+ forced = matches;
3540
+ report();
3541
+ });
3542
+ ready = true;
3543
+ report();
3544
+ return () => {
3545
+ stopMore();
3546
+ stopForced();
3547
+ };
3548
+ }
3549
+ //#endregion
3405
3550
  //#region src/app/reducedMotion.ts
3406
3551
  /**
3407
3552
  * Watches the platform's reduced-motion preference.
@@ -3807,6 +3952,15 @@ var WorkerApp = class {
3807
3952
  get appLogic() {
3808
3953
  return this.appLogicWorker === void 0 ? void 0 : portHandle(this.appLogicWorker);
3809
3954
  }
3955
+ /**
3956
+ * Opens a port to whatever the render worker serves under `key`, or
3957
+ * returns undefined before `mount` has started it. The development
3958
+ * agent bridge asks for `gesso:agent` this way; nothing else in the
3959
+ * shell talks to the render worker except through its protocol.
3960
+ */
3961
+ openRenderPort(key) {
3962
+ return this.renderWorker === void 0 ? void 0 : portHandle(this.renderWorker).open(key);
3963
+ }
3810
3964
  mount(host) {
3811
3965
  const element = resolveHost(host);
3812
3966
  this.host = element;
@@ -3889,9 +4043,10 @@ var WorkerApp = class {
3889
4043
  type: "compositionEnd",
3890
4044
  text
3891
4045
  }),
3892
- paste: (text) => this.post({
4046
+ paste: (text, html) => this.post({
3893
4047
  type: "paste",
3894
- text
4048
+ text,
4049
+ ...html === null ? {} : { html }
3895
4050
  }),
3896
4051
  blur: () => this.post({ type: "blur" }),
3897
4052
  keyDown: (event) => this.forwardKeyDown(event, true),
@@ -3904,13 +4059,21 @@ var WorkerApp = class {
3904
4059
  }),
3905
4060
  keyDown: (event) => this.forwardKeyDown(event),
3906
4061
  keyUp: (event) => this.forwardKeyUp(event),
3907
- paste: (text) => this.post({
4062
+ paste: (text, html) => this.post({
3908
4063
  type: "paste",
3909
- text
4064
+ text,
4065
+ ...html === null ? {} : { html }
3910
4066
  })
3911
4067
  }, this.proxy);
4068
+ const webmcp = this.options.webmcp;
4069
+ if (webmcp !== void 0 && webmcp !== false) import("./webmcp-CJVFvAHe.js").then((n) => n.a).then((agent) => agent.connectWebMcp(this, webmcp === true ? {} : webmcp)).then((disconnect) => {
4070
+ if (this.renderWorker === worker) this.disconnectWebMcp = disconnect;
4071
+ else disconnect();
4072
+ }, (error) => console.warn("[gesso] WebMCP could not register the channels:", error));
3912
4073
  return () => this.dispose();
3913
4074
  }
4075
+ /** Removes the WebMCP tools `webmcp` registered, if it did. */
4076
+ disconnectWebMcp = null;
3914
4077
  /**
3915
4078
  * An error the browser raised *at the worker object*, which is not
3916
4079
  * the same thing as the worker reporting one.
@@ -4075,6 +4238,8 @@ var WorkerApp = class {
4075
4238
  this.frameHandle = requestAnimationFrame(step);
4076
4239
  }
4077
4240
  dispose() {
4241
+ this.disconnectWebMcp?.();
4242
+ this.disconnectWebMcp = null;
4078
4243
  this.ready = false;
4079
4244
  this.setFrameLoop(false);
4080
4245
  this.detachColorScheme?.();
@@ -4136,6 +4301,7 @@ var WorkerApp = class {
4136
4301
  nodes: message.nodes,
4137
4302
  measured: message.measured,
4138
4303
  relayoutRoots: message.relayoutRoots,
4304
+ layoutPasses: message.layoutPasses,
4139
4305
  at: message.at,
4140
4306
  inputLatencyMs: message.inputLatencyMs,
4141
4307
  phases: message.phases,
@@ -4182,7 +4348,11 @@ var WorkerApp = class {
4182
4348
  return;
4183
4349
  }
4184
4350
  if (message.type === "clipboard") {
4185
- writeClipboard(message.text);
4351
+ writeClipboard(message.text).then((copied) => this.post({
4352
+ type: "clipboardResult",
4353
+ id: message.id,
4354
+ copied
4355
+ }));
4186
4356
  return;
4187
4357
  }
4188
4358
  if (message.type === "openUrl") {
@@ -4522,6 +4692,10 @@ var WorkerApp = class {
4522
4692
  reduced
4523
4693
  });
4524
4694
  });
4695
+ const detachContrast = observeContrast((contrast) => this.post({
4696
+ type: "contrast",
4697
+ contrast
4698
+ }));
4525
4699
  const onPointerMove = (event) => {
4526
4700
  const { x, y } = toLocal(event.clientX, event.clientY);
4527
4701
  const move = {
@@ -4606,6 +4780,21 @@ var WorkerApp = class {
4606
4780
  canvas.addEventListener("wheel", onWheel, { passive: false });
4607
4781
  canvas.addEventListener("keydown", onKeyDown);
4608
4782
  canvas.addEventListener("keyup", onKeyUp);
4783
+ const aimedAtPage = (target) => target === null || target === document || target === document.body || target === document.documentElement;
4784
+ const onPageKeyDown = (event) => {
4785
+ if (aimedAtPage(event.target)) {
4786
+ canvas.focus({ preventScroll: true });
4787
+ this.forwardKeyDown(event);
4788
+ }
4789
+ };
4790
+ const onPageKeyUp = (event) => {
4791
+ if (aimedAtPage(event.target)) this.forwardKeyUp(event);
4792
+ };
4793
+ const pageKeys = this.options.pageKeys === true;
4794
+ if (pageKeys) {
4795
+ document.addEventListener("keydown", onPageKeyDown);
4796
+ document.addEventListener("keyup", onPageKeyUp);
4797
+ }
4609
4798
  window.addEventListener("scroll", onCanvasMayHaveMoved, {
4610
4799
  capture: true,
4611
4800
  passive: true
@@ -4613,6 +4802,7 @@ var WorkerApp = class {
4613
4802
  window.addEventListener("resize", onCanvasMayHaveMoved);
4614
4803
  return () => {
4615
4804
  detachReducedMotion();
4805
+ detachContrast();
4616
4806
  detachFullscreen();
4617
4807
  detachFileDrop();
4618
4808
  canvas.removeEventListener("mousedown", onMouseDown);
@@ -4624,6 +4814,10 @@ var WorkerApp = class {
4624
4814
  canvas.removeEventListener("wheel", onWheel);
4625
4815
  canvas.removeEventListener("keydown", onKeyDown);
4626
4816
  canvas.removeEventListener("keyup", onKeyUp);
4817
+ if (pageKeys) {
4818
+ document.removeEventListener("keydown", onPageKeyDown);
4819
+ document.removeEventListener("keyup", onPageKeyUp);
4820
+ }
4627
4821
  window.removeEventListener("scroll", onCanvasMayHaveMoved, { capture: true });
4628
4822
  window.removeEventListener("resize", onCanvasMayHaveMoved);
4629
4823
  this.dropPendingMove();
@@ -5097,10 +5291,19 @@ function parseUrl(url) {
5097
5291
  }
5098
5292
  /** Joins a path and a query back into the url the history stores. */
5099
5293
  function formatUrl(path, query = {}) {
5100
- const pairs = Object.entries(query).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
5294
+ const pairs = Object.entries(query).map(([key, value]) => `${encodeQueryPart(key)}=${encodeQueryPart(value)}`);
5101
5295
  const normalized = `/${pathSegments(path).join("/")}`;
5102
5296
  return pairs.length === 0 ? normalized : `${normalized}?${pairs.join("&")}`;
5103
5297
  }
5298
+ /**
5299
+ * Encodes a query key or value, leaving the characters a query may hold
5300
+ * as they are (`,` `:` `@` `/`), so a list of values reads as one:
5301
+ * `status=todo,done`, not `status=todo%2Cdone`. What would change how
5302
+ * the query parses (`&`, `=`, `+`, `#`) is still encoded.
5303
+ */
5304
+ function encodeQueryPart(text) {
5305
+ return encodeURIComponent(text).replace(/%2C/gi, ",").replace(/%3A/gi, ":").replace(/%40/gi, "@").replace(/%2F/gi, "/");
5306
+ }
5104
5307
  function splitOnce(text, separator) {
5105
5308
  const index = text.indexOf(separator);
5106
5309
  return index === -1 ? [text, ""] : [text.slice(0, index), text.slice(index + separator.length)];
@@ -5208,7 +5411,7 @@ var RouterService = class {
5208
5411
  */
5209
5412
  params(route) {
5210
5413
  const match = this.match.value;
5211
- if (match === null || !match.chain.includes(route)) return null;
5414
+ if (match === null || !inChain(match.chain, route)) return null;
5212
5415
  return match.params;
5213
5416
  }
5214
5417
  /** The same, as a cell, for binding a screen's title or fields. */
@@ -5300,7 +5503,7 @@ var RouterService = class {
5300
5503
  isActive(route) {
5301
5504
  return computed(() => {
5302
5505
  const match = this.match.value;
5303
- return match !== null && match.chain.includes(route);
5506
+ return match !== null && inChain(match.chain, route);
5304
5507
  });
5305
5508
  }
5306
5509
  /**
@@ -5431,6 +5634,23 @@ function sameParams(a, b) {
5431
5634
  const keys = Object.keys(left);
5432
5635
  return keys.length === Object.keys(right).length && keys.every((key) => left[key] === right[key]);
5433
5636
  }
5637
+ /**
5638
+ * Whether a route is in a matched chain: the same object, or a route
5639
+ * declared with the same path.
5640
+ *
5641
+ * Identity alone was the original test, and it is right for a table
5642
+ * loaded once. It fails silently under a dev server's hot replacement:
5643
+ * a route table that imports its screens, with screens that import the
5644
+ * table to read their params, is an import cycle, and after a hot edit
5645
+ * Vite can serve the two sides as `routes.tsx` and `routes.tsx?t=…`.
5646
+ * The table then loads twice, the router holds one copy's route objects
5647
+ * and the screens hold the other's, and every `params(route)` answers
5648
+ * null, so a team page quietly showed the default team. Matching is by
5649
+ * path already, so two routes with one path are one route to the url.
5650
+ */
5651
+ function inChain(chain, route) {
5652
+ return chain.some((link) => link === route || link.path === route.path);
5653
+ }
5434
5654
  //#endregion
5435
5655
  //#region src/app/FindService.ts
5436
5656
  /**
@@ -5559,6 +5779,48 @@ var EditingService = class {
5559
5779
  if (node === null || this.controller === null) return null;
5560
5780
  return this.controller.caretRectOf(node);
5561
5781
  }
5782
+ /**
5783
+ * Selects from `anchor` to `focus` and focuses the field the focus is
5784
+ * in: within one field, or across the fields of an editing group (see
5785
+ * `UiEditingGroup`). What an editor does after a command over a
5786
+ * selection (making it bold, say) to leave it selected, since its
5787
+ * fields can only select their own text.
5788
+ *
5789
+ * False when the two ends aren't editables of one group, or before
5790
+ * the runtime has wired the controller.
5791
+ */
5792
+ select(anchor, focus) {
5793
+ return this.controller?.select(anchor, focus) ?? false;
5794
+ }
5795
+ };
5796
+ //#endregion
5797
+ //#region src/app/ScrollService.ts
5798
+ /**
5799
+ * Scrolling a node into view, for a component that moves a highlight
5800
+ * rather than focus.
5801
+ *
5802
+ * Focus moved from the keyboard already brings its node into view. A
5803
+ * highlight doesn't move focus: a combobox's arrows walk its list while
5804
+ * the caret stays in the field, and a grid's cursor can do the same.
5805
+ * The list has to follow the highlight all the same, and only the
5806
+ * runtime knows where the scroll containers above a node are and how
5807
+ * far each has to move. This is the asking.
5808
+ */
5809
+ var ScrollService = class {
5810
+ scroller = null;
5811
+ /** Installed by the runtime; without one nothing scrolls. */
5812
+ setScroller(scroller) {
5813
+ this.scroller = scroller;
5814
+ }
5815
+ /**
5816
+ * Scrolls every container above `node` just enough that it's inside
5817
+ * the container's viewport, `padding` pixels from the nearest edge.
5818
+ * Nothing moves when it's visible already. Before the node has been
5819
+ * laid out there is nowhere to scroll it to, and nothing happens.
5820
+ */
5821
+ scrollIntoView(node, padding = 8) {
5822
+ this.scroller?.(node, padding);
5823
+ }
5562
5824
  };
5563
5825
  //#endregion
5564
5826
  //#region src/app/FocusService.ts
@@ -5586,6 +5848,14 @@ var FocusService = class {
5586
5848
  focused = internalState(null);
5587
5849
  /** Whether focus is confined to a subtree by an open trap. */
5588
5850
  trapped = internalState(false);
5851
+ /**
5852
+ * Whether the focus held is focus the keyboard can see: reached by Tab
5853
+ * or an arrow key, or by code with no pointer since, rather than by a
5854
+ * press. CSS's `:focus-visible`, for something other than a ring that
5855
+ * should answer the keyboard and not the click that happened to focus
5856
+ * the same control: a tooltip, say.
5857
+ */
5858
+ focusVisible = internalState(false);
5589
5859
  manager = null;
5590
5860
  detach = null;
5591
5861
  /** Actions taken before the runtime installed a manager, in order. */
@@ -5601,6 +5871,7 @@ var FocusService = class {
5601
5871
  }
5602
5872
  const focusChange = manager.onFocusChange((node) => {
5603
5873
  this.focused.value = node;
5874
+ this.focusVisible.value = manager.focusVisible;
5604
5875
  });
5605
5876
  const scopeChange = manager.onScopeChange(() => {
5606
5877
  this.trapped.value = manager.trapped;
@@ -5614,9 +5885,15 @@ var FocusService = class {
5614
5885
  for (const action of pending) action(manager);
5615
5886
  this.sync(manager);
5616
5887
  }
5617
- /** Gives the node keyboard focus. Non-focusable nodes are ignored. */
5618
- focus(node) {
5619
- this.run((manager) => manager.focus(node));
5888
+ /**
5889
+ * Gives the node keyboard focus. Non-focusable nodes are ignored.
5890
+ *
5891
+ * The node is scrolled into view, unless `options.preventScroll` asks
5892
+ * for the page to stay where it is, as `element.focus({ preventScroll:
5893
+ * true })` does in a browser.
5894
+ */
5895
+ focus(node, options) {
5896
+ this.run((manager) => manager.focus(node, "program", options));
5620
5897
  }
5621
5898
  /** Drops focus without moving it anywhere. */
5622
5899
  blur() {
@@ -5656,6 +5933,7 @@ var FocusService = class {
5656
5933
  }
5657
5934
  sync(manager) {
5658
5935
  this.focused.value = manager.focusedNode;
5936
+ this.focusVisible.value = manager.focusVisible;
5659
5937
  this.trapped.value = manager.trapped;
5660
5938
  }
5661
5939
  };
@@ -6570,7 +6848,20 @@ var GessoRuntime = class {
6570
6848
  sharedElements = new UiSharedElements();
6571
6849
  focusNotifier = new FocusNotifier();
6572
6850
  environmentNotifier = new EnvironmentNotifier();
6851
+ /**
6852
+ * The semantics tree: every record by id, and each record's children
6853
+ * in order.
6854
+ *
6855
+ * A record's own `index` is only kept current for the records that
6856
+ * are sent; the order is `semanticsChildren`. Renumbering every
6857
+ * record after an insertion cost a pass over the whole tree on every
6858
+ * structural change, for indices nothing would read until a record
6859
+ * was next sent or the tree was asked for (`semanticsTree`).
6860
+ */
6573
6861
  semantics = /* @__PURE__ */ new Map();
6862
+ semanticsChildren = /* @__PURE__ */ new Map();
6863
+ /** The tree in document order with every index current, built when asked for. */
6864
+ semanticsOrdered = null;
6574
6865
  semanticsListener = null;
6575
6866
  /**
6576
6867
  * The box last reported for each mirrored node, so a frame that
@@ -6578,6 +6869,8 @@ var GessoRuntime = class {
6578
6869
  * them. Only populated while a listener is attached.
6579
6870
  */
6580
6871
  semanticsBoxes = /* @__PURE__ */ new Map();
6872
+ /** What each semantics walk leaves for the next; see `SemanticsMemory`. */
6873
+ semanticsMemory = semanticsMemory();
6581
6874
  lastFocusedId = null;
6582
6875
  /**
6583
6876
  * Where the caret was when `reload` replaced the tree, put back on
@@ -6591,6 +6884,14 @@ var GessoRuntime = class {
6591
6884
  focusAfterReload = null;
6592
6885
  /** Whether `focusAfterReload` is waiting to be applied. */
6593
6886
  restoringFocus = false;
6887
+ /**
6888
+ * Reveals asked for while the layout listeners run, kept until the
6889
+ * frame's boxes have settled; null outside `settleLayout`, where a
6890
+ * reveal is applied at once.
6891
+ */
6892
+ deferredReveals = null;
6893
+ /** Whether a frame has run out of layout passes and been warned about; see `MAX_LAYOUT_PASSES`. */
6894
+ warnedLayoutPasses = false;
6594
6895
  /** A frame changed semantics while nothing was listening; see `semanticsTree`. */
6595
6896
  semanticsStale = false;
6596
6897
  lastEditingState = null;
@@ -6678,8 +6979,8 @@ var GessoRuntime = class {
6678
6979
  focus: {
6679
6980
  isFocused: (node) => this.focusNotifier.isFocused(node),
6680
6981
  isFocusVisible: (node) => this.focusNotifier.isFocusVisible(node),
6681
- focus: (node) => {
6682
- this.focusManager.focus(node);
6982
+ focus: (node, options) => {
6983
+ this.focusManager.focus(node, "program", options);
6683
6984
  },
6684
6985
  onFocusChange: (node, listener) => this.focusNotifier.add(node, listener)
6685
6986
  },
@@ -6719,6 +7020,7 @@ var GessoRuntime = class {
6719
7020
  if (!this.services.has(FindService)) this.services.register(FindService);
6720
7021
  if (!this.services.has(FocusService)) this.services.register(FocusService);
6721
7022
  if (!this.services.has(EditingService)) this.services.register(EditingService);
7023
+ if (!this.services.has(ScrollService)) this.services.register(ScrollService);
6722
7024
  if (!this.services.has(FrameService)) this.services.register(FrameService);
6723
7025
  if (!this.services.has(MediaService)) this.services.register(MediaService);
6724
7026
  if (!this.services.has(FontService)) this.services.register(FontService);
@@ -6732,7 +7034,7 @@ var GessoRuntime = class {
6732
7034
  this.smoothScroller = new SmoothScroller(this.graph, this.services.get(AnimationService), (node, axis) => {
6733
7035
  const record = this.engine.recordFor(node);
6734
7036
  if (record === void 0) return 0;
6735
- return axis === "scrollY" ? Math.max(0, record.contentHeight - record.height) : Math.max(0, record.contentWidth - record.width);
7037
+ return scrollRange(record, axis === "scrollY" ? "y" : "x");
6736
7038
  });
6737
7039
  if (!this.services.has(RouterService)) this.services.register(RouterService);
6738
7040
  const router = this.services.get(RouterService);
@@ -6793,8 +7095,9 @@ var GessoRuntime = class {
6793
7095
  this.input = this.createInput();
6794
7096
  this.services.get(FocusService).setManager(this.input.focus);
6795
7097
  this.services.get(EditingService).setController(this.input.editing);
6796
- this.input.focus.onFocusChange((node, source) => {
6797
- if (node !== null && source !== "pointer") this.scrollIntoView(node);
7098
+ this.services.get(ScrollService).setScroller((node, padding) => this.scrollIntoView(node, padding));
7099
+ this.input.focus.onFocusChange((node, source, options) => {
7100
+ if (node !== null && source !== "pointer" && options.preventScroll !== true) this.scrollIntoView(node);
6798
7101
  });
6799
7102
  if (options.width !== void 0 && options.height !== void 0) this.resize(options.width, options.height, this.pixelRatio);
6800
7103
  }
@@ -7278,6 +7581,10 @@ var GessoRuntime = class {
7278
7581
  setColorScheme(scheme) {
7279
7582
  this.services.get(ShellService).applyColorScheme(scheme);
7280
7583
  }
7584
+ /** The contrast the platform asks for, passed straight to `ShellService`; see `ShellService.contrast`. */
7585
+ setContrast(contrast) {
7586
+ this.services.get(ShellService).applyContrast(contrast);
7587
+ }
7281
7588
  /**
7282
7589
  * The platform's own insets, as the shell reports them: the safe area
7283
7590
  * under a notch or a home indicator and the strip a soft keyboard
@@ -7342,6 +7649,13 @@ var GessoRuntime = class {
7342
7649
  this.services.get(ShellService).settlePopup(id, opened);
7343
7650
  }
7344
7651
  /**
7652
+ * Reports whether a clipboard write landed, settling the promise
7653
+ * `ShellService.copyText` returned.
7654
+ */
7655
+ settleClipboard(id, copied) {
7656
+ this.services.get(ShellService).settleClipboard(id, copied);
7657
+ }
7658
+ /**
7345
7659
  * Reports what the shell found in `localStorage`, settling the
7346
7660
  * promise `ShellService.requestStorage` returned.
7347
7661
  *
@@ -7562,8 +7876,20 @@ var GessoRuntime = class {
7562
7876
  * Scrolls every scroll container above `node` just enough that the
7563
7877
  * node is inside its viewport, `padding` pixels from the nearest edge.
7564
7878
  * Nothing moves when it is already visible.
7879
+ *
7880
+ * Asked for from a layout listener — `autoFocus` taking focus is the
7881
+ * usual one — it waits until the frame's layout has settled, so it
7882
+ * reveals where the node ends up rather than where the first pass
7883
+ * put it (see `settleLayout`).
7565
7884
  */
7566
7885
  scrollIntoView(node, padding = 8) {
7886
+ if (this.deferredReveals !== null) {
7887
+ this.deferredReveals.push({
7888
+ node,
7889
+ padding
7890
+ });
7891
+ return;
7892
+ }
7567
7893
  for (const adjustment of this.engine.revealAdjustments(node, padding)) {
7568
7894
  this.smoothScroller.stop(adjustment.container);
7569
7895
  adjustment.container.setProperty("scrollX", adjustment.scrollX);
@@ -7792,7 +8118,7 @@ var GessoRuntime = class {
7792
8118
  editing,
7793
8119
  selection
7794
8120
  }),
7795
- wheel: new UiWheelController(hitTester, this.dispatcher, scrollSink, () => this.appRoot ?? root),
8121
+ wheel: new UiWheelController(hitTester, this.dispatcher, scrollSink, () => this.appRoot ?? root, void 0, { pace: true }),
7796
8122
  keyboard: new UiKeyboardController(this.dispatcher, focus, () => this.appRoot ?? this.layoutRoot(), {
7797
8123
  editing,
7798
8124
  selection,
@@ -7824,7 +8150,7 @@ var GessoRuntime = class {
7824
8150
  if (listener !== null) this.semanticsTree();
7825
8151
  if (listener === null || this.semantics.size === 0) return;
7826
8152
  listener({
7827
- patches: [...this.semantics.values()].map((node) => ({
8153
+ patches: [...this.orderedSemantics().values()].map((node) => ({
7828
8154
  op: "add",
7829
8155
  node
7830
8156
  })),
@@ -7833,6 +8159,14 @@ var GessoRuntime = class {
7833
8159
  });
7834
8160
  }
7835
8161
  /**
8162
+ * The id of the node that holds focus, or null. The same id the
8163
+ * semantics tree keys its records by, so a reader of the tree can
8164
+ * say which control a key press would reach.
8165
+ */
8166
+ focusedNodeId() {
8167
+ return this.focusManager.focusedNode?.id ?? null;
8168
+ }
8169
+ /**
7836
8170
  * The semantics tree as of the last frame that changed it.
7837
8171
  *
7838
8172
  * Rebuilt here when no mirror was listening and a frame marked it
@@ -7841,10 +8175,59 @@ var GessoRuntime = class {
7841
8175
  */
7842
8176
  semanticsTree() {
7843
8177
  if (this.semanticsStale) {
7844
- this.semantics = buildSemanticsTree(this.layoutRoot());
8178
+ this.storeSemantics(buildSemanticsTree(this.layoutRoot(), this.semanticsMemory));
7845
8179
  this.semanticsStale = false;
7846
8180
  }
7847
- return this.semantics;
8181
+ return this.orderedSemantics();
8182
+ }
8183
+ /** Replaces the tree with one built in full, which is in document order. */
8184
+ storeSemantics(tree) {
8185
+ this.semantics = tree;
8186
+ this.semanticsOrdered = tree;
8187
+ this.semanticsChildren = /* @__PURE__ */ new Map();
8188
+ for (const record of tree.values()) {
8189
+ let list = this.semanticsChildren.get(record.parent);
8190
+ if (list === void 0) this.semanticsChildren.set(record.parent, list = []);
8191
+ list.push(record.id);
8192
+ }
8193
+ }
8194
+ /**
8195
+ * The tree in document order, every index current: a walk down
8196
+ * `semanticsChildren`, done when someone asks rather than per frame.
8197
+ * Records whose index had gone out of date are brought up to date in
8198
+ * the store as well.
8199
+ */
8200
+ orderedSemantics() {
8201
+ if (this.semanticsOrdered !== null) return this.semanticsOrdered;
8202
+ const ordered = /* @__PURE__ */ new Map();
8203
+ const stack = [];
8204
+ const push = (parent) => {
8205
+ const list = this.semanticsChildren.get(parent) ?? [];
8206
+ const indices = semanticIndices(list);
8207
+ for (let i = list.length - 1; i >= 0; i--) stack.push([list[i], indices[i]]);
8208
+ };
8209
+ push(null);
8210
+ while (stack.length > 0) {
8211
+ const [id, index] = stack.pop();
8212
+ let record = this.semantics.get(id);
8213
+ if (record.index !== index) {
8214
+ record = {
8215
+ ...record,
8216
+ index
8217
+ };
8218
+ this.semantics.set(id, record);
8219
+ }
8220
+ ordered.set(id, record);
8221
+ push(id);
8222
+ }
8223
+ this.semanticsOrdered = ordered;
8224
+ return ordered;
8225
+ }
8226
+ /** A record's index among its siblings now, which its own `index` may not say. */
8227
+ semanticsIndexOf(record) {
8228
+ const list = this.semanticsChildren.get(record.parent) ?? [];
8229
+ const at = list.indexOf(record.id);
8230
+ return at < 0 ? record.index : semanticIndices(list)[at];
7848
8231
  }
7849
8232
  /**
7850
8233
  * Something an assistive technology did to a mirrored element,
@@ -7925,7 +8308,7 @@ var GessoRuntime = class {
7925
8308
  const listener = this.semanticsListener;
7926
8309
  if (rebuild) {
7927
8310
  if (listener === null) this.semanticsStale = true;
7928
- else patches = this.rescopeSemantics(frame) ?? this.rebuildSemantics();
8311
+ else patches = this.rescopeSemantics(frame) ?? this.restructureSemantics(frame) ?? this.rebuildSemantics();
7929
8312
  }
7930
8313
  if (listener === null) return;
7931
8314
  const boxes = moved || patches.length > 0 ? this.collectSemanticsBoxes() : EMPTY_BOXES;
@@ -7944,9 +8327,10 @@ var GessoRuntime = class {
7944
8327
  }
7945
8328
  /** Walks the whole tree and diffs it against the last one. */
7946
8329
  rebuildSemantics() {
7947
- const next = buildSemanticsTree(this.layoutRoot());
7948
- const patches = diffSemantics(this.semantics, next);
7949
- this.semantics = next;
8330
+ const previous = this.orderedSemantics();
8331
+ const next = buildSemanticsTree(this.layoutRoot(), this.semanticsMemory);
8332
+ const patches = dropIndexShifts(previous, next, diffSemantics(previous, next));
8333
+ this.storeSemantics(next);
7950
8334
  this.semanticsStale = false;
7951
8335
  return patches;
7952
8336
  }
@@ -8002,7 +8386,7 @@ var GessoRuntime = class {
8002
8386
  const updates = [];
8003
8387
  for (const owner of owners) {
8004
8388
  const previous = this.semantics.get(owner.id);
8005
- const subtree = buildSemanticsSubtree(owner, previous.parent, previous.index, semanticsInertAbove(owner));
8389
+ const subtree = buildSemanticsSubtree(owner, previous.parent, this.semanticsIndexOf(previous), semanticsInertAbove(owner), this.semanticsMemory);
8006
8390
  if (subtree === null) return null;
8007
8391
  let covered = 0;
8008
8392
  for (const id of this.semanticIdsUnder(owner)) {
@@ -8010,13 +8394,18 @@ var GessoRuntime = class {
8010
8394
  if (next === void 0) return null;
8011
8395
  covered++;
8012
8396
  const before = this.semantics.get(id);
8013
- if (!recordsEqual(before, next)) updates.push(next);
8397
+ if (!recordsEqual({
8398
+ ...before,
8399
+ index: next.index
8400
+ }, next)) updates.push(next);
8401
+ else if (before.index !== next.index) this.semantics.set(id, next);
8014
8402
  }
8015
8403
  if (covered !== subtree.size) return null;
8016
8404
  }
8017
8405
  const patches = [];
8018
8406
  for (const record of updates) {
8019
8407
  this.semantics.set(record.id, record);
8408
+ this.semanticsOrdered?.set(record.id, record);
8020
8409
  patches.push({
8021
8410
  op: "update",
8022
8411
  node: record
@@ -8025,6 +8414,163 @@ var GessoRuntime = class {
8025
8414
  return patches;
8026
8415
  }
8027
8416
  /**
8417
+ * The patches for a frame that changed the shape of the tree, without
8418
+ * walking all of it, or null when the whole tree has to be rebuilt.
8419
+ *
8420
+ * Any frame that added or removed a child used to rebuild the tree
8421
+ * from the root, describing every node again. In a 5,000-line editor
8422
+ * that was 11 ms of every Enter, for one new paragraph. Now the walk
8423
+ * starts at the nearest record above each change, as
8424
+ * `rescopeSemantics` does for a change of meaning, and inside it a
8425
+ * subtree nothing touched keeps its records: renumbered if its
8426
+ * siblings moved, never described again (see
8427
+ * `rebuildSemanticsSubtree`). What is described is the path down to
8428
+ * each change, whatever is new, and everything under a node whose
8429
+ * own meaning changed.
8430
+ *
8431
+ * The result is spliced into `this.semantics` in place, since the map
8432
+ * is in document order and a record's subtree is a contiguous run of
8433
+ * it, so adds still arrive in document order. Removals come first.
8434
+ */
8435
+ restructureSemantics(frame) {
8436
+ if (this.semantics.size === 0 || this.semanticsStale) return null;
8437
+ const dirty = [];
8438
+ /** Nodes whose own meaning may have changed: everything under them is described afresh. */
8439
+ const meaning = /* @__PURE__ */ new Set();
8440
+ let structural = false;
8441
+ for (const [node, flags] of frame.entries()) {
8442
+ const children = (flags & DirtyFlags.Children) !== 0;
8443
+ const semantics = (flags & DirtyFlags.Semantics) !== 0;
8444
+ if (!children && !semantics) continue;
8445
+ if (!this.attached(node)) continue;
8446
+ structural ||= children;
8447
+ dirty.push(node);
8448
+ if (semantics) meaning.add(node);
8449
+ }
8450
+ if (!structural) return null;
8451
+ const owners = /* @__PURE__ */ new Set();
8452
+ for (const node of dirty) {
8453
+ const owner = this.semanticsOwnerOf(node);
8454
+ if (owner === null) return null;
8455
+ owners.add(owner);
8456
+ }
8457
+ for (const owner of owners) for (let above = owner.parent; above !== null; above = above.parent) if (owners.has(above)) {
8458
+ owners.delete(owner);
8459
+ break;
8460
+ }
8461
+ if (owners.size > SCOPED_SEMANTICS_OWNERS) return null;
8462
+ const path = /* @__PURE__ */ new Set();
8463
+ for (const node of dirty) for (let current = node; current !== null && !path.has(current); current = current.parent) {
8464
+ path.add(current);
8465
+ if (owners.has(current)) break;
8466
+ }
8467
+ const fresh = /* @__PURE__ */ new Map();
8468
+ const underChangedMeaning = (node) => {
8469
+ const known = fresh.get(node);
8470
+ if (known !== void 0) return known;
8471
+ const answer = meaning.has(node) || !owners.has(node) && node.parent !== null && underChangedMeaning(node.parent);
8472
+ fresh.set(node, answer);
8473
+ return answer;
8474
+ };
8475
+ const unchanged = (node) => !path.has(node) && (meaning.size === 0 || !underChangedMeaning(node));
8476
+ const positions = /* @__PURE__ */ new Map();
8477
+ const reuse = {
8478
+ previous: this.semantics,
8479
+ childrenOf: (parent) => this.semanticsChildren.get(parent) ?? [],
8480
+ positionOf: (parent, id) => {
8481
+ let index = positions.get(parent);
8482
+ if (index === void 0) {
8483
+ index = new Map((this.semanticsChildren.get(parent) ?? []).map((child, i) => [child, i]));
8484
+ positions.set(parent, index);
8485
+ }
8486
+ return index.get(id) ?? -1;
8487
+ },
8488
+ memory: this.semanticsMemory,
8489
+ unchanged
8490
+ };
8491
+ const walks = [];
8492
+ for (const owner of owners) {
8493
+ const before = this.semantics.get(owner.id);
8494
+ if (before === void 0) return null;
8495
+ const walked = rewalkSemantics(owner, before.parent, this.semanticsIndexOf(before), semanticsInertAbove(owner), reuse);
8496
+ if (walked === null) return null;
8497
+ walks.push(walked);
8498
+ }
8499
+ const present = /* @__PURE__ */ new Set();
8500
+ for (const walked of walks) for (const list of walked.children.values()) for (const id of list) present.add(id);
8501
+ const removals = [];
8502
+ const remove = (id) => {
8503
+ removals.push({
8504
+ op: "remove",
8505
+ id
8506
+ });
8507
+ for (const child of this.semanticsChildren.get(id) ?? []) remove(child);
8508
+ this.semanticsChildren.delete(id);
8509
+ this.semantics.delete(id);
8510
+ };
8511
+ const formerly = /* @__PURE__ */ new Map();
8512
+ for (const walked of walks) for (const parent of walked.children.keys()) formerly.set(parent, this.semanticsChildren.get(parent) ?? []);
8513
+ for (const list of formerly.values()) for (const id of list) if (!present.has(id) && this.semantics.has(id)) remove(id);
8514
+ const reordered = /* @__PURE__ */ new Set();
8515
+ for (const walked of walks) for (const [parent, list] of walked.children) {
8516
+ const old = formerly.get(parent) ?? [];
8517
+ if (old.length === 0) continue;
8518
+ const was = new Map(old.map((id, i) => [id, i]));
8519
+ let last = -1;
8520
+ for (const id of list) {
8521
+ const at = was.get(id);
8522
+ if (at === void 0) continue;
8523
+ if (at < last) {
8524
+ reordered.add(parent);
8525
+ break;
8526
+ }
8527
+ last = at;
8528
+ }
8529
+ }
8530
+ const changes = [];
8531
+ for (const walked of walks) {
8532
+ for (const { id, parent, index } of walked.placed) {
8533
+ const record = walked.records.get(id);
8534
+ const before = this.semantics.get(id);
8535
+ if (record !== void 0) {
8536
+ if (before === void 0) changes.push({
8537
+ op: "add",
8538
+ node: record
8539
+ });
8540
+ else if (before.parent !== record.parent || reordered.has(parent) || !recordsEqual({
8541
+ ...before,
8542
+ index: record.index
8543
+ }, record)) changes.push({
8544
+ op: "update",
8545
+ node: record
8546
+ });
8547
+ this.semantics.set(id, record);
8548
+ } else if (before === void 0) return null;
8549
+ else if (before.parent !== parent || reordered.has(parent)) {
8550
+ const moved = {
8551
+ ...before,
8552
+ parent,
8553
+ index
8554
+ };
8555
+ changes.push({
8556
+ op: "update",
8557
+ node: moved
8558
+ });
8559
+ this.semantics.set(id, moved);
8560
+ }
8561
+ }
8562
+ for (const [parent, list] of walked.children) this.semanticsChildren.set(parent, list);
8563
+ }
8564
+ this.semanticsOrdered = null;
8565
+ return [...removals, ...changes];
8566
+ }
8567
+ /** Whether a node is still in the tree being laid out. */
8568
+ attached(node) {
8569
+ const root = this.layoutRoot();
8570
+ for (let current = node; current !== null; current = current.parent) if (current === root) return true;
8571
+ return false;
8572
+ }
8573
+ /**
8028
8574
  * The nearest node at or above `node` that holds a semantics record,
8029
8575
  * or null when nothing above it does.
8030
8576
  */
@@ -8085,22 +8631,75 @@ var GessoRuntime = class {
8085
8631
  *
8086
8632
  * Ids that have left the tree are dropped here rather than tracked,
8087
8633
  * since a removal patch has already told the mirror about them.
8634
+ *
8635
+ * **Found by walking what is on screen, not by checking everything.**
8636
+ * Bounding what was sent still computed every node's box to find out
8637
+ * whether it was on screen: a 5,000-line document has three thousand
8638
+ * fields, and the sweep cost 4 to 24 ms of every keystroke. The walk
8639
+ * goes down from the root carrying the scroll and sticky offsets that
8640
+ * `visibleBox` adds up per node, and passes over any subtree whose
8641
+ * bounds (the hit tester's, see `subtreeBoundsFor`) are off screen.
8642
+ * A node it passes over is one this skipped before anyway.
8088
8643
  */
8089
8644
  collectSemanticsBoxes() {
8090
8645
  const changed = [];
8091
- const focused = this.focusManager.focusedNode?.id;
8092
- for (const id of this.semantics.keys()) {
8093
- const node = this.graph.getNode(id);
8094
- if (node === void 0 || this.engine.recordFor(node) === void 0) continue;
8095
- const box = this.engine.visibleBox(node);
8646
+ const focused = this.focusManager.focusedNode;
8647
+ let focusedSeen = false;
8648
+ const offer = (id, box) => {
8096
8649
  const last = this.semanticsBoxes.get(id);
8097
- if (last !== void 0 && boxesEqual(last, box)) continue;
8098
- if (id !== focused && !this.onScreen(box)) continue;
8650
+ if (last !== void 0 && boxesEqual(last, box)) return;
8099
8651
  this.semanticsBoxes.set(id, box);
8100
8652
  changed.push({
8101
8653
  id,
8102
8654
  box
8103
8655
  });
8656
+ };
8657
+ const root = this.root;
8658
+ if (root !== void 0) {
8659
+ this.engine.subtreeBoundsFor(root);
8660
+ const stack = [[
8661
+ root,
8662
+ 0,
8663
+ 0
8664
+ ]];
8665
+ while (stack.length > 0) {
8666
+ const [node, dx, dy] = stack.pop();
8667
+ const rec = this.engine.recordFor(node);
8668
+ let childX = dx;
8669
+ let childY = dy;
8670
+ if (rec !== void 0) {
8671
+ const x = dx + rec.stickyOffsetX;
8672
+ const y = dy + rec.stickyOffsetY;
8673
+ if (!rec.boundsUnbounded && node !== focused && !this.onScreen({
8674
+ x: rec.boundsMinX + x,
8675
+ y: rec.boundsMinY + y,
8676
+ width: rec.boundsMaxX - rec.boundsMinX,
8677
+ height: rec.boundsMaxY - rec.boundsMinY
8678
+ })) continue;
8679
+ if (this.semantics.has(node.id)) {
8680
+ const box = {
8681
+ x: rec.x + x,
8682
+ y: rec.y + y,
8683
+ width: rec.width,
8684
+ height: rec.height
8685
+ };
8686
+ if (node === focused) {
8687
+ focusedSeen = true;
8688
+ offer(node.id, box);
8689
+ } else if (this.onScreen(box)) offer(node.id, box);
8690
+ }
8691
+ childX = x - (rec.scrollable ? rec.scrollX : 0);
8692
+ childY = y - (rec.scrollable ? rec.scrollY : 0);
8693
+ }
8694
+ for (let child = node.lastChild; child !== null; child = child.previousSibling) stack.push([
8695
+ child,
8696
+ childX,
8697
+ childY
8698
+ ]);
8699
+ }
8700
+ }
8701
+ if (focused !== null && focused !== void 0 && !focusedSeen && this.semantics.has(focused.id)) {
8702
+ if (this.engine.recordFor(focused) !== void 0) offer(focused.id, this.engine.visibleBox(focused));
8104
8703
  }
8105
8704
  if (this.semanticsBoxes.size > this.semantics.size) {
8106
8705
  for (const id of this.semanticsBoxes.keys()) if (!this.semantics.has(id)) this.semanticsBoxes.delete(id);
@@ -8143,15 +8742,20 @@ var GessoRuntime = class {
8143
8742
  * every pass, so writing the raw offset here is enough.
8144
8743
  */
8145
8744
  createScrollSink() {
8745
+ const going = (node, record, axis) => {
8746
+ const written = node.getProperty(axis === "x" ? "scrollX" : "scrollY");
8747
+ const offset = typeof written === "number" ? written : axis === "x" ? record.scrollX : record.scrollY;
8748
+ return Math.min(Math.max(offset, 0), scrollRange(record, axis));
8749
+ };
8146
8750
  return {
8147
8751
  containerState: (node) => {
8148
8752
  const record = this.engine.recordFor(node);
8149
8753
  if (record === void 0) return;
8150
8754
  return {
8151
- scrollX: record.scrollX,
8152
- scrollY: record.scrollY,
8153
- maxScrollX: Math.max(0, record.contentWidth - record.width),
8154
- maxScrollY: Math.max(0, record.contentHeight - record.height),
8755
+ scrollX: going(node, record, "x"),
8756
+ scrollY: going(node, record, "y"),
8757
+ maxScrollX: scrollRange(record, "x"),
8758
+ maxScrollY: scrollRange(record, "y"),
8155
8759
  horizontal: node.getProperty("direction") === "row" || node.type === UiNodeType.Row,
8156
8760
  viewportWidth: record.width,
8157
8761
  viewportHeight: record.height
@@ -8161,13 +8765,13 @@ var GessoRuntime = class {
8161
8765
  const record = this.engine.recordFor(node);
8162
8766
  if (record === void 0) return;
8163
8767
  if (behavior === "smooth") {
8164
- if (dx !== 0) this.smoothScroller.scrollBy(node, "scrollX", dx, record.scrollX);
8165
- if (dy !== 0) this.smoothScroller.scrollBy(node, "scrollY", dy, record.scrollY);
8768
+ if (dx !== 0) this.smoothScroller.scrollBy(node, "scrollX", dx, going(node, record, "x"));
8769
+ if (dy !== 0) this.smoothScroller.scrollBy(node, "scrollY", dy, going(node, record, "y"));
8166
8770
  return;
8167
8771
  }
8168
8772
  this.smoothScroller.stop(node);
8169
- if (dx !== 0) node.setProperty("scrollX", record.scrollX + dx);
8170
- if (dy !== 0) node.setProperty("scrollY", record.scrollY + dy);
8773
+ if (dx !== 0) node.setProperty("scrollX", going(node, record, "x") + dx);
8774
+ if (dy !== 0) node.setProperty("scrollY", going(node, record, "y") + dy);
8171
8775
  this.graph.markDirty(node, DirtyFlags.Transform);
8172
8776
  },
8173
8777
  scrollContainers: () => this.engine.scrollContainers(),
@@ -8207,6 +8811,7 @@ var GessoRuntime = class {
8207
8811
  for (const replica of this.replicas) replica.flush();
8208
8812
  });
8209
8813
  this.phaseTimings.environment = this.timePhase(() => this.graph.hasEnvironmentDirty(), () => this.graph.processEnvironmentDirty());
8814
+ if (this.input.wheel.advance(time)) this.scheduler.wake();
8210
8815
  this.phaseTimings.virtualize = this.timePhase(() => this.hasVirtualWindows(), () => this.updateVirtualWindows());
8211
8816
  }
8212
8817
  hasVirtualWindows() {
@@ -8306,29 +8911,131 @@ var GessoRuntime = class {
8306
8911
  if (rec === void 0) return 0;
8307
8912
  return column ? rec.measuredHeight + rec.marginTop + rec.marginBottom : rec.measuredWidth + rec.marginLeft + rec.marginRight;
8308
8913
  }
8309
- handleFrame(frame) {
8310
- const root = this.root;
8311
- if (root === void 0) return;
8312
- const started = now();
8313
- this.focusManager.settleScope();
8314
- const laidOut = frameNeedsLayout(frame);
8315
- this.phaseTimings.layout = this.timePhase(() => laidOut, () => this.engine.layoutForFrame(frame, this.constraints, root));
8316
- if (laidOut) {
8317
- this.laidOutOnce = true;
8318
- this.inspector.recordLayout(started);
8319
- }
8320
- if (!this.layoutNotifier.isEmpty()) this.layoutNotifier.notify((node) => {
8914
+ /**
8915
+ * Tells the layout listeners what moved, and lays out again for as
8916
+ * long as what they do in answer needs it, so the frame paints the
8917
+ * layout they settle on rather than the one they were told about.
8918
+ *
8919
+ * The browser's answer to the same problem. A `ResizeObserver`
8920
+ * callback runs after layout and before paint, and when it changes
8921
+ * layout the browser lays out again before it paints rather than
8922
+ * showing the stale boxes for a frame. The listeners here are
8923
+ * `breakpoint`, `sizeContainer` (and so `Responsive`),
8924
+ * `scrollPosition`, `autoFocus` and anything else on `host.onLayout`.
8925
+ * Before this a page whose breakpoint gave it wide padding was
8926
+ * painted with its narrow padding on the frame it appeared, and
8927
+ * jumped 16 px on the next.
8928
+ *
8929
+ * A pass after the first lays out only what the listeners dirtied,
8930
+ * the same incremental pass any frame runs, and then tells only the
8931
+ * listeners whose boxes it changed. A frame whose listeners wrote
8932
+ * nothing that lays out, which is almost every frame, runs no second
8933
+ * pass: it pays one look at the dirty set.
8934
+ *
8935
+ * **Bounded.** Listeners that keep moving each other's boxes would
8936
+ * otherwise never let the frame paint. ResizeObserver bounds its loop
8937
+ * by tree depth; this one is bounded by count, `MAX_LAYOUT_PASSES` in
8938
+ * all, which is easier to reason about and more than a real chain
8939
+ * needs (a breakpoint inside a `Responsive` inside a `Responsive` is
8940
+ * four passes). Past it the frame paints what it has, the rest waits
8941
+ * for the next frame as it always used to, and a warning says so
8942
+ * once, as the browser's "ResizeObserver loop completed with
8943
+ * undelivered notifications" does.
8944
+ *
8945
+ * **Reveals wait for the end.** `autoFocus` takes focus from a layout
8946
+ * listener, on its node's first layout, and focus reveals the node:
8947
+ * revealed from the first pass's boxes, it scrolled to where the node
8948
+ * was before a breakpoint moved it. A reveal asked for while the
8949
+ * listeners run is applied once they are quiet, from final boxes, and
8950
+ * its scroll laid out like any other write. So is the focus a hot
8951
+ * reload restores, after the listeners rather than before because
8952
+ * `autoFocus` is one of them and the restore has to be the last word:
8953
+ * every node in a subtree a reload replaced has its first layout on
8954
+ * this frame.
8955
+ */
8956
+ settleLayout(frame, root, laidOut) {
8957
+ const stats = this.engine.stats;
8958
+ const settled = {
8959
+ frame,
8960
+ passes: laidOut ? 1 : 0,
8961
+ measured: laidOut ? stats.measured : 0,
8962
+ relayoutRoots: laidOut && !stats.fullLayout ? stats.relayoutRoots : 0
8963
+ };
8964
+ let fromRoot = laidOut && stats.fullLayout;
8965
+ const read = (node) => {
8321
8966
  const record = this.engine.recordFor(node);
8322
8967
  return {
8323
8968
  box: this.engine.visibleBox(node),
8324
8969
  scrollX: record?.scrollX ?? 0,
8325
8970
  scrollY: record?.scrollY ?? 0
8326
8971
  };
8327
- });
8972
+ };
8973
+ this.deferredReveals = [];
8974
+ try {
8975
+ for (;;) {
8976
+ if (!this.layoutNotifier.isEmpty()) this.layoutNotifier.notify(read);
8977
+ if (!this.layoutPending()) {
8978
+ this.finishSettling(settled.passes > 0);
8979
+ if (!this.layoutPending()) break;
8980
+ }
8981
+ if (settled.passes >= MAX_LAYOUT_PASSES) {
8982
+ this.warnLayoutPasses();
8983
+ this.finishSettling(true);
8984
+ break;
8985
+ }
8986
+ this.phaseTimings.environment += this.timePhase(() => this.graph.hasEnvironmentDirty(), () => this.graph.processEnvironmentDirty());
8987
+ const more = this.scheduler.recollect(frame);
8988
+ settled.frame = settled.frame.merged(more);
8989
+ this.phaseTimings.layout += this.timePhase(() => true, () => this.engine.layoutForFrame(more, this.constraints, root));
8990
+ settled.passes++;
8991
+ settled.measured += stats.measured;
8992
+ settled.relayoutRoots += stats.fullLayout ? 0 : stats.relayoutRoots;
8993
+ fromRoot ||= stats.fullLayout;
8994
+ }
8995
+ } finally {
8996
+ this.deferredReveals = null;
8997
+ }
8998
+ if (fromRoot) settled.relayoutRoots = 0;
8999
+ return settled;
9000
+ }
9001
+ /**
9002
+ * Whether something written since the frame was collected has to be
9003
+ * laid out before the frame paints: the same flags that make a frame
9004
+ * lay out, or an inherited value still to be handed down.
9005
+ */
9006
+ layoutPending() {
9007
+ return this.graph.hasEnvironmentDirty() || this.graph.getDirtyNodes().anyFlags(LAYOUT_DIRT);
9008
+ }
9009
+ /** Applies what waits for settled boxes: a reload's focus, then the reveals asked for. */
9010
+ finishSettling(laidOut) {
9011
+ const reveals = this.deferredReveals ?? [];
9012
+ this.deferredReveals = null;
8328
9013
  if (this.restoringFocus && laidOut) {
8329
9014
  this.restoringFocus = false;
8330
9015
  this.restoreFocusAfterReload();
8331
9016
  }
9017
+ for (const { node, padding } of reveals) if (this.engine.recordFor(node) !== void 0) this.scrollIntoView(node, padding);
9018
+ this.deferredReveals = [];
9019
+ }
9020
+ warnLayoutPasses() {
9021
+ if (this.warnedLayoutPasses) return;
9022
+ this.warnedLayoutPasses = true;
9023
+ console.warn(`Gesso: a frame laid out ${MAX_LAYOUT_PASSES} times and its layout listeners were still changing it, so it was painted unsettled and the rest left for the next frame. Something on \`host.onLayout\` (a breakpoint, a sizeContainer, a Responsive, a modifier of your own) is writing a property that changes the box it is listening to, back and forth. Shown once.`);
9024
+ }
9025
+ handleFrame(collected) {
9026
+ const root = this.root;
9027
+ if (root === void 0) return;
9028
+ const started = now();
9029
+ this.focusManager.settleScope();
9030
+ const firstPass = frameNeedsLayout(collected);
9031
+ this.phaseTimings.layout = this.timePhase(() => firstPass, () => this.engine.layoutForFrame(collected, this.constraints, root));
9032
+ const settled = this.settleLayout(collected, root, firstPass);
9033
+ const frame = settled.frame;
9034
+ const laidOut = settled.passes > 0;
9035
+ if (laidOut) {
9036
+ this.laidOutOnce = true;
9037
+ this.inspector.recordLayout(started);
9038
+ }
8332
9039
  this.sendEditingState();
8333
9040
  const rebuildSemantics = frameNeedsSemantics(frame);
8334
9041
  this.phaseTimings.semantics = this.timePhase(() => rebuildSemantics || this.semanticsListener !== null && (laidOut || this.focusManager.focusedNode?.id !== this.lastFocusedId), () => this.updateSemantics(frame, rebuildSemantics, laidOut));
@@ -8362,8 +9069,9 @@ var GessoRuntime = class {
8362
9069
  frame: frame.id,
8363
9070
  durationMs: elapsed,
8364
9071
  nodes: frame.size,
8365
- measured: this.engine.stats.measured,
8366
- relayoutRoots: this.engine.stats.fullLayout ? 0 : this.engine.stats.relayoutRoots,
9072
+ measured: settled.measured,
9073
+ relayoutRoots: settled.relayoutRoots,
9074
+ layoutPasses: settled.passes,
8367
9075
  at: finished,
8368
9076
  inputLatencyMs: this.inputLatency.take(epochAt(finished)),
8369
9077
  phases: this.phaseTimings,
@@ -8589,7 +9297,7 @@ function timeOrigin() {
8589
9297
  }
8590
9298
  function editingStatesEqual(a, b) {
8591
9299
  if (a === null || b === null) return a === b;
8592
- return a.text === b.text && a.selectionStart === b.selectionStart && a.selectionEnd === b.selectionEnd && a.multiline === b.multiline && a.composing === b.composing && a.caret.x === b.caret.x && a.caret.y === b.caret.y && a.caret.width === b.caret.width && a.caret.height === b.caret.height;
9300
+ return a.text === b.text && a.selectionStart === b.selectionStart && a.selectionEnd === b.selectionEnd && a.multiline === b.multiline && a.composing === b.composing && a.html === b.html && a.caret.x === b.caret.x && a.caret.y === b.caret.y && a.caret.width === b.caret.width && a.caret.height === b.caret.height;
8593
9301
  }
8594
9302
  /** A canvas for the text measurer when the draw canvas is not a 2D one. */
8595
9303
  function createMeasureCanvas() {
@@ -8609,6 +9317,14 @@ function emptyGpuTimings() {
8609
9317
  encode: 0
8610
9318
  };
8611
9319
  }
9320
+ /**
9321
+ * The most layout passes one frame runs before it paints; see
9322
+ * `settleLayout`. One for the frame and up to seven more for what its
9323
+ * layout listeners change.
9324
+ */
9325
+ const MAX_LAYOUT_PASSES = 8;
9326
+ /** Dirt that makes a frame lay out; `frameNeedsLayout` asks the same of a collected frame. */
9327
+ const LAYOUT_DIRT = DirtyFlags.Layout | DirtyFlags.Children | DirtyFlags.SubtreeLayout | DirtyFlags.Transform;
8612
9328
  function emptyPhaseTimings() {
8613
9329
  return {
8614
9330
  ticks: 0,
@@ -8628,7 +9344,7 @@ function emptyPhaseTimings() {
8628
9344
  * spending an immeasurable amount of time deciding to do nothing.
8629
9345
  */
8630
9346
  function frameNeedsLayout(frame) {
8631
- return frame.anyFlags(DirtyFlags.Layout | DirtyFlags.Children | DirtyFlags.SubtreeLayout | DirtyFlags.Transform);
9347
+ return frame.anyFlags(LAYOUT_DIRT);
8632
9348
  }
8633
9349
  /**
8634
9350
  * Semantics follow the properties that carry them and the shape of the
@@ -8648,6 +9364,18 @@ function frameNeedsLayout(frame) {
8648
9364
  */
8649
9365
  const SCOPED_SEMANTICS_OWNERS = 32;
8650
9366
  /**
9367
+ * The index each id in a parent's list of children has. A paragraph's
9368
+ * text runs are numbered among themselves and its other children among
9369
+ * themselves, as `buildSemanticsTree` numbers them.
9370
+ */
9371
+ function semanticIndices(list) {
9372
+ const out = [];
9373
+ let runs = 0;
9374
+ let others = 0;
9375
+ for (const id of list) out.push(id.includes(TEXT_RUN_ID_SEPARATOR) ? runs++ : others++);
9376
+ return out;
9377
+ }
9378
+ /**
8651
9379
  * How far outside the canvas a mirrored box is still worth reporting.
8652
9380
  *
8653
9381
  * One screenful of slack, so a fling that covers a lot of ground in
@@ -8689,6 +9417,14 @@ function frameNeedsSemantics(frame) {
8689
9417
  */
8690
9418
  var GessoApp = class {
8691
9419
  runtime;
9420
+ /**
9421
+ * The clock the runtime was given, its callback, and whether a frame
9422
+ * is waiting on it: what `uiHost().flush` needs to run that frame now,
9423
+ * since a background tab gets no animation frames to run it.
9424
+ */
9425
+ clock;
9426
+ frameCallback;
9427
+ framePending = false;
8692
9428
  canvas;
8693
9429
  host;
8694
9430
  inputEnabled;
@@ -8709,6 +9445,7 @@ var GessoApp = class {
8709
9445
  detachFullscreen = null;
8710
9446
  fullscreen = false;
8711
9447
  detachReducedMotion = null;
9448
+ detachContrast = null;
8712
9449
  /** Stops watching `prefers-color-scheme`; null while overridden. */
8713
9450
  detachColorScheme = null;
8714
9451
  /** Stops watching `visualViewport` for the safe area and the keyboard. */
@@ -8731,7 +9468,24 @@ var GessoApp = class {
8731
9468
  routes: options.routes,
8732
9469
  media: options.media,
8733
9470
  fonts: options.fonts,
8734
- clock: options.clock ?? ((callback) => new UiAnimationFrameClock(callback)),
9471
+ clock: (callback) => {
9472
+ const clock = (options.clock ?? ((onFrame) => new UiAnimationFrameClock(onFrame)))((time) => {
9473
+ this.framePending = false;
9474
+ callback(time);
9475
+ });
9476
+ this.clock = clock;
9477
+ this.frameCallback = callback;
9478
+ return {
9479
+ requestFrame: () => {
9480
+ this.framePending = true;
9481
+ clock.requestFrame();
9482
+ },
9483
+ cancelFrame: () => {
9484
+ this.framePending = false;
9485
+ clock.cancelFrame();
9486
+ }
9487
+ };
9488
+ },
8735
9489
  dpr: devicePixelRatio()
8736
9490
  });
8737
9491
  const input = this.runtime.input;
@@ -8741,6 +9495,30 @@ var GessoApp = class {
8741
9495
  keyboardController: input.keyboard
8742
9496
  });
8743
9497
  }
9498
+ /**
9499
+ * The running app, as the agent's screen tools need it: its semantics
9500
+ * tree and focus, the mirror's actions, keys, and a way to run a
9501
+ * pending frame now. See `uiSurface` in `gesso-framework/agent`.
9502
+ */
9503
+ uiHost() {
9504
+ const runtime = this.runtime;
9505
+ return {
9506
+ semanticsTree: () => runtime.semanticsTree(),
9507
+ focusedNodeId: () => runtime.focusedNodeId(),
9508
+ applySemanticsAction: (action) => runtime.applySemanticsAction(action),
9509
+ key: (key, modifiers) => {
9510
+ runtime.input.keyboard.keyDown(key, modifiers);
9511
+ runtime.input.keyboard.keyUp(key, modifiers);
9512
+ },
9513
+ flush: () => {
9514
+ if (this.framePending && this.clock !== void 0 && this.frameCallback !== void 0) {
9515
+ this.framePending = false;
9516
+ this.clock.cancelFrame();
9517
+ this.frameCallback(performance.now());
9518
+ }
9519
+ }
9520
+ };
9521
+ }
8744
9522
  /** The runtime services a component in this app can inject. */
8745
9523
  get services() {
8746
9524
  return this.runtime.services;
@@ -8877,6 +9655,8 @@ var GessoApp = class {
8877
9655
  this.detachFullscreen = null;
8878
9656
  this.detachReducedMotion?.();
8879
9657
  this.detachReducedMotion = null;
9658
+ this.detachContrast?.();
9659
+ this.detachContrast = null;
8880
9660
  this.detachColorScheme?.();
8881
9661
  this.detachColorScheme = null;
8882
9662
  this.detachViewportInsets?.();
@@ -8901,7 +9681,7 @@ var GessoApp = class {
8901
9681
  compositionStart: () => editing.compositionStart(),
8902
9682
  compositionUpdate: (text, caret) => editing.compositionUpdate(text, caret),
8903
9683
  compositionEnd: (text) => editing.compositionEnd(text),
8904
- paste: (text) => editing.paste(text),
9684
+ paste: (text, html) => editing.paste(text, html),
8905
9685
  blur: () => this.runtime.input.focus.blur()
8906
9686
  });
8907
9687
  this.runtime.setTextInputSource("proxy");
@@ -8934,6 +9714,7 @@ var GessoApp = class {
8934
9714
  this.runtime.setFullscreen(isDocumentFullscreen(canvas));
8935
9715
  }
8936
9716
  this.detachReducedMotion = observeReducedMotion((reduced) => this.runtime.setReducedMotion(reduced));
9717
+ this.detachContrast = observeContrast((contrast) => this.runtime.setContrast(contrast));
8937
9718
  this.setColorScheme(this.colorSchemePreference);
8938
9719
  }
8939
9720
  /**
@@ -8949,7 +9730,7 @@ var GessoApp = class {
8949
9730
  if (!this.accessibilityEnabled) return;
8950
9731
  const mirror = new SemanticsMirror(canvas, {
8951
9732
  action: (action) => this.runtime.applySemanticsAction(action),
8952
- paste: (text) => this.runtime.input.editing.paste(text)
9733
+ paste: (text, html) => this.runtime.input.editing.paste(text, html)
8953
9734
  }, this.proxy);
8954
9735
  this.mirror = mirror;
8955
9736
  this.runtime.onSemantics((update) => mirror.apply(update));
@@ -8994,10 +9775,11 @@ var GessoApp = class {
8994
9775
  if (request.type === "popup") this.runtime.settlePopup(request.id, false);
8995
9776
  else if (request.type === "storage") this.runtime.settleStorage(request.id, shellStorageDenied());
8996
9777
  else if (request.type === "file") this.runtime.settleFile(request.id, shellFilesUnsupported("There is no document to reach files through."));
9778
+ else if (request.type === "clipboard") this.runtime.settleClipboard(request.id, false);
8997
9779
  return;
8998
9780
  }
8999
9781
  if (request.type === "clipboard") {
9000
- writeClipboard(request.text, this.canvas.ownerDocument);
9782
+ writeClipboard(request.text, this.canvas.ownerDocument).then((copied) => this.runtime.settleClipboard(request.id, copied));
9001
9783
  return;
9002
9784
  }
9003
9785
  if (request.type === "fullscreen") {
@@ -9070,6 +9852,10 @@ var GessoAppBuilder = class {
9070
9852
  mediaOptions;
9071
9853
  fontDeclarations;
9072
9854
  app;
9855
+ /** The mounted app's channel registry, for the workers an agent port asks. */
9856
+ channelHandle;
9857
+ webmcpChoice = false;
9858
+ disconnectWebMcp = null;
9073
9859
  colorSchemePreference = "auto";
9074
9860
  constructor(root) {
9075
9861
  this.root = root;
@@ -9260,13 +10046,61 @@ var GessoAppBuilder = class {
9260
10046
  if (this.errorListener !== void 0) app.onError(this.errorListener);
9261
10047
  if (this.devtoolsListener !== null) app.onDevtools(this.devtoolsListener);
9262
10048
  this.app = app;
10049
+ this.channelHandle = channels;
9263
10050
  app.mount();
10051
+ this.connectWebMcp(app);
9264
10052
  return () => {
10053
+ this.disconnectWebMcp?.();
10054
+ this.disconnectWebMcp = null;
9265
10055
  this.app = void 0;
10056
+ this.channelHandle = void 0;
9266
10057
  app.dispose();
9267
10058
  channels.dispose();
9268
10059
  };
9269
10060
  }
10061
+ /**
10062
+ * Offer the app's channels and screen to an AI agent in the browser,
10063
+ * through WebMCP (default false). The single-thread form of
10064
+ * `createApp({ webmcp })`: registered once `mountSync` has run,
10065
+ * removed when the app is unmounted. Call before `mountSync`.
10066
+ */
10067
+ useWebMcp(choice = true) {
10068
+ this.webmcpChoice = choice;
10069
+ return this;
10070
+ }
10071
+ /**
10072
+ * Opens a port to what this app serves under `key`, or returns
10073
+ * undefined before `mountSync`.
10074
+ *
10075
+ * The same question `WorkerApp.openRenderPort` answers, asked of the
10076
+ * page instead of a render worker, because in this configuration the
10077
+ * page is the thread that draws. It serves one thing, `gesso:agent`:
10078
+ * the channels fed here, the workers behind them, and the screen.
10079
+ * The agent code is loaded the first time one is opened.
10080
+ */
10081
+ openRenderPort(key) {
10082
+ const app = this.app;
10083
+ if (app === void 0 || key !== "gesso:agent") return;
10084
+ const pair = new MessageChannel();
10085
+ const registrations = this.channelRegistrations;
10086
+ import("./app-BvlIO1G9.js").then((n) => n.t).then((agent) => agent.serveApplicationAgent(pair.port2, {
10087
+ channels: () => registrations.filter((registration) => registration.source !== void 0).map((registration) => ({
10088
+ token: registration.token,
10089
+ source: registration.source
10090
+ })),
10091
+ workers: () => this.channelHandle?.workers ?? [],
10092
+ ui: () => this.app === app ? app.uiHost() : void 0
10093
+ }));
10094
+ return pair.port1;
10095
+ }
10096
+ connectWebMcp(app) {
10097
+ const choice = this.webmcpChoice;
10098
+ if (choice === false) return;
10099
+ import("./webmcp-CJVFvAHe.js").then((n) => n.a).then((agent) => agent.connectWebMcp(this, choice === true ? {} : choice)).then((disconnect) => {
10100
+ if (this.app === app) this.disconnectWebMcp = disconnect;
10101
+ else disconnect();
10102
+ }, (error) => console.warn("[gesso] WebMCP could not register the channels:", error));
10103
+ }
9270
10104
  };
9271
10105
  function requireElement(selector) {
9272
10106
  const element = document.querySelector(selector);
@@ -9818,6 +10652,11 @@ var RenderWorkerApp = class {
9818
10652
  this.root = typeof root === "function" ? createComponent(root) : root;
9819
10653
  this.host = host;
9820
10654
  this.host.onmessage = (event) => this.receive(event.data);
10655
+ servePorts((key, port) => {
10656
+ if (key !== "gesso:agent") return false;
10657
+ this.serveAgent(port);
10658
+ return true;
10659
+ }, () => [AGENT_PORT], host);
9821
10660
  this.host.addEventListener("error", (event) => {
9822
10661
  const error = event.error;
9823
10662
  this.reportUncaught(error ?? event.message ?? "Unknown error", error === void 0 ? locationOf(event) : void 0);
@@ -9995,6 +10834,41 @@ var RenderWorkerApp = class {
9995
10834
  * console is the worker global's, and this class is what owns the
9996
10835
  * global.
9997
10836
  */
10837
+ /**
10838
+ * Answers an agent port with every channel this application can
10839
+ * reach: the ones fed from this thread, and whatever each worker
10840
+ * behind them serves, asked over a port of its own. Imported rather
10841
+ * than loaded on demand: a worker built as an IIFE, which is Vite's
10842
+ * default, cannot split off a chunk, and a dynamic import here failed
10843
+ * every such build. Nothing runs until an agent asks.
10844
+ */
10845
+ serveAgent(port) {
10846
+ serveApplicationAgent(port, {
10847
+ channels: () => this.channelRegistrations.filter((registration) => registration.source !== void 0).map((registration) => ({
10848
+ token: registration.token,
10849
+ source: registration.source
10850
+ })),
10851
+ workers: () => {
10852
+ const workers = new Set(this.channels?.workers ?? []);
10853
+ if (this.appLogicWorker !== void 0) workers.add(this.appLogicWorker);
10854
+ return workers;
10855
+ },
10856
+ ui: () => {
10857
+ const runtime = this.runtime;
10858
+ if (runtime === void 0) return;
10859
+ return {
10860
+ semanticsTree: () => runtime.semanticsTree(),
10861
+ focusedNodeId: () => runtime.focusedNodeId(),
10862
+ applySemanticsAction: (action) => runtime.applySemanticsAction(action),
10863
+ key: (key, modifiers) => {
10864
+ runtime.input.keyboard.keyDown(key, modifiers);
10865
+ runtime.input.keyboard.keyUp(key, modifiers);
10866
+ },
10867
+ flush: () => this.clock?.tick(performance.now())
10868
+ };
10869
+ }
10870
+ });
10871
+ }
9998
10872
  setConsoleForwarding(enabled) {
9999
10873
  this.restoreConsole?.();
10000
10874
  this.restoreConsole = null;
@@ -10048,7 +10922,7 @@ var RenderWorkerApp = class {
10048
10922
  runtime.input.pointer.pointerCancel(message.pointer);
10049
10923
  break;
10050
10924
  case "wheel":
10051
- runtime.input.wheel.wheel(message.x, message.y, message.deltaX, message.deltaY, message.modifiers, message.deltaMode, message.wheelDeltaY);
10925
+ runtime.input.wheel.wheel(message.x, message.y, message.deltaX, message.deltaY, message.modifiers, message.deltaMode, message.wheelDeltaY, message.at === void 0 ? void 0 : message.at - performance.timeOrigin);
10052
10926
  break;
10053
10927
  case "keyDown":
10054
10928
  runtime.input.keyboard.keyDown(message.key, message.modifiers, message.textFollows);
@@ -10069,7 +10943,7 @@ var RenderWorkerApp = class {
10069
10943
  runtime.input.editing.compositionEnd(message.text);
10070
10944
  break;
10071
10945
  case "paste":
10072
- runtime.input.editing.paste(message.text);
10946
+ runtime.input.editing.paste(message.text, message.html ?? null);
10073
10947
  break;
10074
10948
  case "blur":
10075
10949
  runtime.input.focus.blur();
@@ -10080,6 +10954,9 @@ var RenderWorkerApp = class {
10080
10954
  case "colorScheme":
10081
10955
  runtime.setColorScheme(message.scheme);
10082
10956
  break;
10957
+ case "contrast":
10958
+ runtime.setContrast(message.contrast);
10959
+ break;
10083
10960
  case "viewportInsets":
10084
10961
  runtime.setViewportInsets(message.insets);
10085
10962
  break;
@@ -10089,6 +10966,9 @@ var RenderWorkerApp = class {
10089
10966
  case "popupResult":
10090
10967
  runtime.settlePopup(message.id, message.opened);
10091
10968
  break;
10969
+ case "clipboardResult":
10970
+ runtime.settleClipboard(message.id, message.copied);
10971
+ break;
10092
10972
  case "storageResult":
10093
10973
  runtime.settleStorage(message.id, message.result);
10094
10974
  break;
@@ -10232,6 +11112,7 @@ var RenderWorkerApp = class {
10232
11112
  this.runtime.onShellRequest((request) => {
10233
11113
  if (request.type === "clipboard") this.host.postMessage({
10234
11114
  type: "clipboard",
11115
+ id: request.id,
10235
11116
  text: request.text
10236
11117
  });
10237
11118
  else if (request.type === "openUrl") this.host.postMessage({
@@ -10310,6 +11191,7 @@ var RenderWorkerApp = class {
10310
11191
  nodes: metrics.nodes,
10311
11192
  measured: metrics.measured,
10312
11193
  relayoutRoots: metrics.relayoutRoots,
11194
+ layoutPasses: metrics.layoutPasses,
10313
11195
  at: metrics.at,
10314
11196
  inputLatencyMs: metrics.inputLatencyMs,
10315
11197
  phases: metrics.phases,
@@ -10329,6 +11211,6 @@ function locationOf(event) {
10329
11211
  return `${event.filename}:${line}:${column}`;
10330
11212
  }
10331
11213
  //#endregion
10332
- export { APPLICATION_WORKER, AnimationService, AudioService, AudioSink, BoundsCell, Channel, ChannelRegistry, ChannelReplica, Component, ComponentHost, ComponentHostResolver, ComputedCell, Define, Each, EditingProxy, EditingService, FanCell, FanOut, FindService, FocusService, FontService, FrameService, GessoApp, GessoAppBuilder, GessoRuntime, IndexedDbStorage, Inject, Input, InputCell, InternalState, MARK_PREFIX, MediaService, MemoryStorage, OpfsStorage, Output, OverlayLayer, OverlayService, PersistedState, Presence, ProvidedChannel, RenderWorkerApp, Resource, RouteState, RouterOutlet, RouterService, SemanticsMirror, ServiceRegistry, ShellService, ShellStorage, Show, TextService, ThemeTokenCell, UI_FRAME_PHASES, UI_ROLES, UI_SEMANTIC_STATES, UndoStack, WorkerApp, applyPatch, applyPatches, audioClock, bind, bounds, buildPath, channel, classifyStorageError, computed, controlled, createApp, createChannelRegistry, createComponent, createShellHistory, createSyncApp, debounced, defineChannel, derive, describeStream, diffProjection, each, fanOut, findUnplainPath, formatAge, formatNodePath, formatNodeReport, formatStream, formatUrl, input, internalState, into, isChannelClientMessage, isChannelHostMessage, isClassComponent, isComponentElement, isHubMessage, isOutputTarget, isPortErrorMessage, isPortHandshake, markInstant, markNow, measureSpan, mutate, observeColorScheme, observeMediaQuery, observeReducedMotion, output, parseUrl, performShellStorage, performanceMarksEnabled, persisted, pick, pickKeys, portHandle, printPropValue, provide, registerUndoShortcuts, renderRoot, requirePlainData, resource, route, select, serve, serveChannels, servePorts, setPerformanceMarks, shellStorageDenied, show, storageErrorMessage, storageReadFailure, storageReadValue, structurallyEqual, themeTokenCell, throttled, to, treeText, undoable, viewKeys, workerHandle, writeClipboard };
11214
+ export { APPLICATION_WORKER, AnimationService, AudioService, AudioSink, BoundsCell, Channel, ChannelRegistry, ChannelReplica, Component, ComponentHost, ComponentHostResolver, ComputedCell, Define, Each, EditingProxy, EditingService, FanCell, FanOut, FindService, FocusService, FontService, FrameService, GessoApp, GessoAppBuilder, GessoRuntime, IndexedDbStorage, Inject, Input, InputCell, InternalState, MARK_PREFIX, MediaService, MemoryStorage, OpfsStorage, Output, OverlayLayer, OverlayService, PersistedState, Presence, ProvidedChannel, RenderWorkerApp, Resource, RouteState, RouterOutlet, RouterService, ScrollService, SemanticsMirror, ServiceRegistry, ShellService, ShellStorage, Show, TextService, ThemeTokenCell, UI_FRAME_PHASES, UI_ROLES, UI_SEMANTIC_STATES, UndoStack, WorkerApp, applyPatch, applyPatches, audioClock, bind, bounds, buildPath, channel, channelSchema, classifyStorageError, computed, controlled, createApp, createChannelRegistry, createComponent, createShellHistory, createSyncApp, debounced, defineChannel, derive, describeChannel, describeStream, diffProjection, each, fanOut, findUnplainPath, formatAge, formatNodePath, formatNodeReport, formatStream, formatUrl, input, internalState, into, isChannelClientMessage, isChannelHostMessage, isClassComponent, isComponentElement, isHubMessage, isOutputTarget, isPortErrorMessage, isPortHandshake, markInstant, markNow, measureSpan, mutate, observeColorScheme, observeMediaQuery, observeReducedMotion, output, parseUrl, performShellStorage, performanceMarksEnabled, persisted, pick, pickKeys, portHandle, printPropValue, provide, registerUndoShortcuts, renderRoot, requirePlainData, resource, route, select, serve, serveChannels, servePorts, setPerformanceMarks, shellStorageDenied, show, storageErrorMessage, storageReadFailure, storageReadValue, structurallyEqual, themeTokenCell, throttled, to, treeText, undoable, viewKeys, workerHandle, writeClipboard };
10333
11215
 
10334
11216
  //# sourceMappingURL=index.js.map