gesso-framework 0.4.2 → 0.5.1
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/CHANGELOG.md +120 -0
- package/README.md +2 -0
- package/dist/ChannelProtocol-ByNoHujM.d.ts +247 -0
- package/dist/{FunctionComponent-CgwLKE5d.d.ts → FunctionComponent-fYMdePtH.d.ts} +3 -247
- package/dist/agent/index.d.ts +317 -0
- package/dist/agent/index.js +184 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/app-BvlIO1G9.js +326 -0
- package/dist/app-BvlIO1G9.js.map +1 -0
- package/dist/{index-C9FAI_Kt.d.ts → index-Sff7qPGU.d.ts} +400 -269
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1013 -97
- package/dist/index.js.map +1 -1
- package/dist/jsx/jsx-runtime.d.ts +1 -1
- package/dist/{persisted-CsTPnjkc.js → persisted-pix1fS1D.js} +6 -1
- package/dist/{persisted-CsTPnjkc.js.map → persisted-pix1fS1D.js.map} +1 -1
- package/dist/remote-xxij8zXe.js +600 -0
- package/dist/remote-xxij8zXe.js.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/ui-U39HjNFA.d.ts +517 -0
- package/dist/webmcp-CJVFvAHe.js +77 -0
- package/dist/webmcp-CJVFvAHe.js.map +1 -0
- package/dist/worker/index.d.ts +4 -2
- package/dist/worker/index.js +1 -1
- package/package.json +12 -4
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-
|
|
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, scrollRange, 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, defineModifier, 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;
|
|
@@ -851,6 +854,7 @@ let OverlayLayer = class OverlayLayer extends Component {
|
|
|
851
854
|
position: "absolute",
|
|
852
855
|
anchor: entry.anchor ?? void 0,
|
|
853
856
|
anchorPoint: entry.anchor == null ? entry.point : void 0,
|
|
857
|
+
anchorRect: entry.anchor == null ? void 0 : entry.anchorRect,
|
|
854
858
|
placement: entry.placement,
|
|
855
859
|
anchorOffset: entry.offset,
|
|
856
860
|
top: entry.top,
|
|
@@ -859,7 +863,8 @@ let OverlayLayer = class OverlayLayer extends Component {
|
|
|
859
863
|
left: entry.left,
|
|
860
864
|
zIndex: entry.zIndex,
|
|
861
865
|
...centering(entry),
|
|
862
|
-
...inheritedFrom(entry.environment ?? entry.anchor ?? null)
|
|
866
|
+
...inheritedFrom(entry.environment ?? entry.anchor ?? null),
|
|
867
|
+
modifiers: followEnvironment(entry.environment ?? entry.anchor ?? null)
|
|
863
868
|
}, entry.content));
|
|
864
869
|
}
|
|
865
870
|
return elements;
|
|
@@ -902,11 +907,7 @@ function centering(entry) {
|
|
|
902
907
|
}
|
|
903
908
|
/**
|
|
904
909
|
* The scoped values an entry's content should keep, re-provided on the
|
|
905
|
-
* box that holds it.
|
|
906
|
-
*
|
|
907
|
-
* Read once, when the entry opens. An overlay that outlives a theme
|
|
908
|
-
* change re-opens; nothing here watches, because an entry's content is
|
|
909
|
-
* built once too.
|
|
910
|
+
* box that holds it, as they are when the entry opens.
|
|
910
911
|
*/
|
|
911
912
|
function inheritedFrom(node) {
|
|
912
913
|
const environment = node?.environment;
|
|
@@ -917,6 +918,28 @@ function inheritedFrom(node) {
|
|
|
917
918
|
contentColor: environment.get(UiEnvironmentKeys.contentColor)
|
|
918
919
|
};
|
|
919
920
|
}
|
|
921
|
+
/**
|
|
922
|
+
* The same values again whenever they change where the entry was
|
|
923
|
+
* declared, for as long as it is open.
|
|
924
|
+
*
|
|
925
|
+
* Reading them once let a dialog open while the page went dark (the
|
|
926
|
+
* system's setting changed, or the theme a person chose arrived from
|
|
927
|
+
* another worker a moment after they opened it) stay light over a dark
|
|
928
|
+
* page until it was closed.
|
|
929
|
+
*/
|
|
930
|
+
const followEnvironmentKind = defineModifier({
|
|
931
|
+
name: "overlayEnvironment",
|
|
932
|
+
attach(host, from) {
|
|
933
|
+
host.onEnvironment(() => {
|
|
934
|
+
host.set("theme", host.environment(UiEnvironmentKeys.theme, from));
|
|
935
|
+
host.set("textStyle", host.environment(UiEnvironmentKeys.textStyle, from));
|
|
936
|
+
host.set("contentColor", host.environment(UiEnvironmentKeys.contentColor, from));
|
|
937
|
+
}, from);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
function followEnvironment(node) {
|
|
941
|
+
return node === null ? [] : [followEnvironmentKind(node)];
|
|
942
|
+
}
|
|
920
943
|
//#endregion
|
|
921
944
|
//#region src/ComponentElement.ts
|
|
922
945
|
function isComponentElement(value) {
|
|
@@ -1143,12 +1166,12 @@ var ComponentHost = class {
|
|
|
1143
1166
|
return;
|
|
1144
1167
|
}
|
|
1145
1168
|
if (isObservable(provided)) {
|
|
1146
|
-
const subscription = provided.subscribe((value) => cell
|
|
1169
|
+
const subscription = provided.subscribe((value) => pushInput(cell, value));
|
|
1147
1170
|
this.inputSubscriptions.set(inputName, subscription);
|
|
1148
1171
|
this.subscriptions.add(subscription);
|
|
1149
1172
|
return;
|
|
1150
1173
|
}
|
|
1151
|
-
cell
|
|
1174
|
+
pushInput(cell, provided);
|
|
1152
1175
|
}
|
|
1153
1176
|
validateInputs() {
|
|
1154
1177
|
const metadata = getComponentMetadata(this.element.component);
|
|
@@ -1173,6 +1196,27 @@ var ComponentHost = class {
|
|
|
1173
1196
|
}
|
|
1174
1197
|
}
|
|
1175
1198
|
};
|
|
1199
|
+
/**
|
|
1200
|
+
* Hands an input cell a value from its parent, unless it already holds
|
|
1201
|
+
* that very value.
|
|
1202
|
+
*
|
|
1203
|
+
* A parent that re-renders hands every child its props again, and a
|
|
1204
|
+
* prop that is an Observable built in the parent's render is a new
|
|
1205
|
+
* source each time, which re-subscribes and replays the current value.
|
|
1206
|
+
* Pushed through regardless, that replay re-ran every binding derived
|
|
1207
|
+
* from the input. In the issue tracker's editor, inserting one block
|
|
1208
|
+
* re-rendered the list of 2,868, each replay rebuilt its block's text
|
|
1209
|
+
* runs as a new array, and every editable on the page was re-measured:
|
|
1210
|
+
* 12,912 nodes for one Enter. Values that cross into a component are
|
|
1211
|
+
* treated as immutable everywhere else in the framework (bindings hold
|
|
1212
|
+
* on to them, patches share structure), so the same reference is the
|
|
1213
|
+
* same value and there is nothing to tell the child.
|
|
1214
|
+
*/
|
|
1215
|
+
function pushInput(cell, value) {
|
|
1216
|
+
const current = cell.getValue();
|
|
1217
|
+
if (current !== void 0 && Object.is(current, value)) return;
|
|
1218
|
+
cell.next(value);
|
|
1219
|
+
}
|
|
1176
1220
|
//#endregion
|
|
1177
1221
|
//#region src/ComponentHostResolver.ts
|
|
1178
1222
|
/**
|
|
@@ -1704,6 +1748,7 @@ function filesDuringDrag(event) {
|
|
|
1704
1748
|
var ShellService = class {
|
|
1705
1749
|
handler = null;
|
|
1706
1750
|
scheme = internalState("light");
|
|
1751
|
+
contrastState = internalState("standard");
|
|
1707
1752
|
insets = internalState(noInsets);
|
|
1708
1753
|
isFullscreen = internalState(false);
|
|
1709
1754
|
/** Popups asked for and not yet answered, by the id sent with each. */
|
|
@@ -1712,6 +1757,9 @@ var ShellService = class {
|
|
|
1712
1757
|
/** Storage requests asked for and not yet answered, by the id sent with each. */
|
|
1713
1758
|
stores = /* @__PURE__ */ new Map();
|
|
1714
1759
|
nextStorageId = 1;
|
|
1760
|
+
/** Clipboard writes asked for and not yet answered, by the id sent with each. */
|
|
1761
|
+
copies = /* @__PURE__ */ new Map();
|
|
1762
|
+
nextCopyId = 1;
|
|
1715
1763
|
/** File requests asked for and not yet answered, by the id sent with each. */
|
|
1716
1764
|
files = /* @__PURE__ */ new Map();
|
|
1717
1765
|
nextFileId = 1;
|
|
@@ -1744,6 +1792,18 @@ var ShellService = class {
|
|
|
1744
1792
|
* read it beside a channel's view; its setter stays private here.
|
|
1745
1793
|
*/
|
|
1746
1794
|
colorScheme = this.scheme;
|
|
1795
|
+
/**
|
|
1796
|
+
* The contrast the platform is asking for: `high` when the person has
|
|
1797
|
+
* turned up contrast or turned on a contrast theme, `standard`
|
|
1798
|
+
* otherwise. Reported by the shell like `colorScheme`, and as with it,
|
|
1799
|
+
* what it means is the application's: `withContrast(theme, contrast)`
|
|
1800
|
+
* is the theme that answers it.
|
|
1801
|
+
*
|
|
1802
|
+
* const theme = combineLatest([shell.colorScheme, shell.contrast]).pipe(
|
|
1803
|
+
* map(([scheme, contrast]) => withContrast(scheme === 'dark' ? darkTheme : lightTheme, contrast))
|
|
1804
|
+
* );
|
|
1805
|
+
*/
|
|
1806
|
+
contrast = this.contrastState;
|
|
1747
1807
|
/** The current appearance, for code that needs it without subscribing. */
|
|
1748
1808
|
get currentColorScheme() {
|
|
1749
1809
|
return this.scheme.value;
|
|
@@ -1783,6 +1843,10 @@ var ShellService = class {
|
|
|
1783
1843
|
applyColorScheme(scheme) {
|
|
1784
1844
|
if (this.scheme.value !== scheme) this.scheme.value = scheme;
|
|
1785
1845
|
}
|
|
1846
|
+
/** Called by the runtime when the shell reports the contrast; not for applications. */
|
|
1847
|
+
applyContrast(contrast) {
|
|
1848
|
+
if (this.contrastState.value !== contrast) this.contrastState.value = contrast;
|
|
1849
|
+
}
|
|
1786
1850
|
/**
|
|
1787
1851
|
* Called by the runtime when the shell reports the platform's insets.
|
|
1788
1852
|
*
|
|
@@ -1793,12 +1857,44 @@ var ShellService = class {
|
|
|
1793
1857
|
applyViewportInsets(insets) {
|
|
1794
1858
|
if (!insetsEqual(this.insets.value, insets)) this.insets.value = insets;
|
|
1795
1859
|
}
|
|
1796
|
-
/**
|
|
1860
|
+
/**
|
|
1861
|
+
* Puts text on the system clipboard, and answers whether it got there.
|
|
1862
|
+
*
|
|
1863
|
+
* Most callers ignore the answer, and may: the text is on its way the
|
|
1864
|
+
* moment this returns. It is for an application that tells the person
|
|
1865
|
+
* what happened, a "Copied" toast after a menu command, say, which
|
|
1866
|
+
* would be a lie on the occasions the browser refused. It does refuse:
|
|
1867
|
+
* the clipboard wants a focused document, and some browsers a fresh
|
|
1868
|
+
* gesture, and a key pressed in a render worker reaches the window's
|
|
1869
|
+
* clipboard a message later than it reached the canvas.
|
|
1870
|
+
*
|
|
1871
|
+
* With no shell installed the answer is false, as `openPopup`'s is,
|
|
1872
|
+
* rather than a promise that never settles.
|
|
1873
|
+
*/
|
|
1797
1874
|
copyText(text) {
|
|
1798
|
-
this.handler
|
|
1875
|
+
const handler = this.handler;
|
|
1876
|
+
if (handler === void 0 || handler === null) return Promise.resolve(false);
|
|
1877
|
+
const id = this.nextCopyId++;
|
|
1878
|
+
const settled = new Promise((resolve) => {
|
|
1879
|
+
this.copies.set(id, resolve);
|
|
1880
|
+
});
|
|
1881
|
+
handler({
|
|
1799
1882
|
type: "clipboard",
|
|
1883
|
+
id,
|
|
1800
1884
|
text
|
|
1801
1885
|
});
|
|
1886
|
+
return settled;
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Called by the runtime when the shell reports whether a clipboard
|
|
1890
|
+
* write landed. Not for applications. An unknown id is ignored, on
|
|
1891
|
+
* `settlePopup`'s terms.
|
|
1892
|
+
*/
|
|
1893
|
+
settleClipboard(id, copied) {
|
|
1894
|
+
const resolve = this.copies.get(id);
|
|
1895
|
+
if (resolve === void 0) return;
|
|
1896
|
+
this.copies.delete(id);
|
|
1897
|
+
resolve(copied);
|
|
1802
1898
|
}
|
|
1803
1899
|
/** Opens a URL in the user's browser, in a new tab or window. */
|
|
1804
1900
|
openUrl(url) {
|
|
@@ -2591,7 +2687,7 @@ var EditingProxy = class {
|
|
|
2591
2687
|
* the reason the field's semantics live here rather than on a second
|
|
2592
2688
|
* element beside it.
|
|
2593
2689
|
*/
|
|
2594
|
-
describe(record) {
|
|
2690
|
+
describe(record, activeDescendant, controls) {
|
|
2595
2691
|
if (this.disposed) return;
|
|
2596
2692
|
const textarea = this.textarea;
|
|
2597
2693
|
if (record === null) {
|
|
@@ -2603,7 +2699,11 @@ var EditingProxy = class {
|
|
|
2603
2699
|
"aria-required",
|
|
2604
2700
|
"aria-invalid",
|
|
2605
2701
|
"aria-readonly",
|
|
2606
|
-
"aria-disabled"
|
|
2702
|
+
"aria-disabled",
|
|
2703
|
+
"aria-expanded",
|
|
2704
|
+
"aria-activedescendant",
|
|
2705
|
+
"aria-controls",
|
|
2706
|
+
"aria-autocomplete"
|
|
2607
2707
|
]) textarea.removeAttribute(attribute);
|
|
2608
2708
|
return;
|
|
2609
2709
|
}
|
|
@@ -2616,6 +2716,10 @@ var EditingProxy = class {
|
|
|
2616
2716
|
setOrClear(textarea, "aria-invalid", states.has("invalid") ? "true" : void 0);
|
|
2617
2717
|
setOrClear(textarea, "aria-readonly", states.has("readonly") ? "true" : void 0);
|
|
2618
2718
|
setOrClear(textarea, "aria-disabled", record.disabled === true ? "true" : void 0);
|
|
2719
|
+
setOrClear(textarea, "aria-expanded", states.has("expanded") ? "true" : states.has("collapsed") ? "false" : void 0);
|
|
2720
|
+
setOrClear(textarea, "aria-activedescendant", activeDescendant);
|
|
2721
|
+
setOrClear(textarea, "aria-controls", controls);
|
|
2722
|
+
setOrClear(textarea, "aria-autocomplete", record.autocomplete);
|
|
2619
2723
|
}
|
|
2620
2724
|
dispose() {
|
|
2621
2725
|
if (this.disposed) return;
|
|
@@ -2678,7 +2782,8 @@ var EditingProxy = class {
|
|
|
2678
2782
|
const clipboard = event.clipboardData;
|
|
2679
2783
|
event.preventDefault();
|
|
2680
2784
|
const text = clipboard?.getData("text/plain") ?? "";
|
|
2681
|
-
|
|
2785
|
+
const html = clipboard?.getData("text/html") ?? "";
|
|
2786
|
+
if (text.length > 0 || html.length > 0) this.sink.paste(text, html.length > 0 ? html : null);
|
|
2682
2787
|
};
|
|
2683
2788
|
/**
|
|
2684
2789
|
* A paste aimed at the canvas rather than at the field.
|
|
@@ -2703,6 +2808,7 @@ var EditingProxy = class {
|
|
|
2703
2808
|
if (clipboard === null || state === null) return;
|
|
2704
2809
|
event.preventDefault();
|
|
2705
2810
|
clipboard.setData("text/plain", state.text.slice(state.selectionStart, state.selectionEnd));
|
|
2811
|
+
if (state.html !== void 0) clipboard.setData("text/html", state.html);
|
|
2706
2812
|
};
|
|
2707
2813
|
const onCut = (event) => {
|
|
2708
2814
|
const state = this.state;
|
|
@@ -2757,17 +2863,15 @@ var EditingProxy = class {
|
|
|
2757
2863
|
}
|
|
2758
2864
|
};
|
|
2759
2865
|
/**
|
|
2760
|
-
* Writes text to the system clipboard from the main thread
|
|
2761
|
-
* API needs a secure context and, in
|
|
2762
|
-
* gesture; the `execCommand` fallback
|
|
2866
|
+
* Writes text to the system clipboard from the main thread, and answers
|
|
2867
|
+
* whether it got there. The async API needs a secure context and, in
|
|
2868
|
+
* some browsers, a recent user gesture; the `execCommand` fallback
|
|
2869
|
+
* covers the rest. False means both refused. It never rejects.
|
|
2763
2870
|
*/
|
|
2764
2871
|
function writeClipboard(text, doc = document) {
|
|
2765
2872
|
const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
2766
|
-
if (clipboard !== void 0 && typeof clipboard.writeText === "function")
|
|
2767
|
-
|
|
2768
|
-
return;
|
|
2769
|
-
}
|
|
2770
|
-
copyWithExecCommand(text, doc);
|
|
2873
|
+
if (clipboard !== void 0 && typeof clipboard.writeText === "function") return clipboard.writeText(text).then(() => true, () => copyWithExecCommand(text, doc));
|
|
2874
|
+
return Promise.resolve(copyWithExecCommand(text, doc));
|
|
2771
2875
|
}
|
|
2772
2876
|
function copyWithExecCommand(text, doc) {
|
|
2773
2877
|
const previous = doc.activeElement;
|
|
@@ -2778,7 +2882,9 @@ function copyWithExecCommand(text, doc) {
|
|
|
2778
2882
|
doc.body.appendChild(scratch);
|
|
2779
2883
|
scratch.select();
|
|
2780
2884
|
try {
|
|
2781
|
-
doc.execCommand("copy");
|
|
2885
|
+
return doc.execCommand("copy");
|
|
2886
|
+
} catch {
|
|
2887
|
+
return false;
|
|
2782
2888
|
} finally {
|
|
2783
2889
|
scratch.remove();
|
|
2784
2890
|
previous?.focus?.({ preventScroll: true });
|
|
@@ -2802,7 +2908,9 @@ const STATE_ATTRIBUTES = {
|
|
|
2802
2908
|
invalid: ["aria-invalid", "true"],
|
|
2803
2909
|
required: ["aria-required", "true"],
|
|
2804
2910
|
readonly: ["aria-readonly", "true"],
|
|
2805
|
-
modal: ["aria-modal", "true"]
|
|
2911
|
+
modal: ["aria-modal", "true"],
|
|
2912
|
+
multiselectable: ["aria-multiselectable", "true"],
|
|
2913
|
+
current: ["aria-current", "true"]
|
|
2806
2914
|
};
|
|
2807
2915
|
/**
|
|
2808
2916
|
* Roles whose checked state is required rather than optional.
|
|
@@ -2848,8 +2956,14 @@ const RECORD_ATTRIBUTES = [
|
|
|
2848
2956
|
"aria-invalid",
|
|
2849
2957
|
"aria-required",
|
|
2850
2958
|
"aria-readonly",
|
|
2851
|
-
"aria-modal"
|
|
2959
|
+
"aria-modal",
|
|
2960
|
+
"aria-multiselectable",
|
|
2961
|
+
"aria-current",
|
|
2962
|
+
"aria-activedescendant",
|
|
2963
|
+
"aria-controls",
|
|
2964
|
+
"aria-autocomplete"
|
|
2852
2965
|
];
|
|
2966
|
+
let mirrors = 0;
|
|
2853
2967
|
/**
|
|
2854
2968
|
* Roles whose accessible name comes from what the element contains
|
|
2855
2969
|
* rather than from an `aria-label`, plus prose, which has no role at
|
|
@@ -2915,6 +3029,12 @@ var SemanticsMirror = class {
|
|
|
2915
3029
|
applying = false;
|
|
2916
3030
|
focusedId = null;
|
|
2917
3031
|
disposed = false;
|
|
3032
|
+
/**
|
|
3033
|
+
* Prefixes every element's DOM id, which `aria-activedescendant`
|
|
3034
|
+
* refers to. Per mirror, because two apps on one page have nodes with
|
|
3035
|
+
* the same ids.
|
|
3036
|
+
*/
|
|
3037
|
+
idPrefix = `gesso-${mirrors++}-`;
|
|
2918
3038
|
constructor(canvas, sink, editing = null) {
|
|
2919
3039
|
this.canvas = canvas;
|
|
2920
3040
|
this.sink = sink;
|
|
@@ -2930,7 +3050,7 @@ var SemanticsMirror = class {
|
|
|
2930
3050
|
width: "0px",
|
|
2931
3051
|
height: "0px",
|
|
2932
3052
|
pointerEvents: "none",
|
|
2933
|
-
overflow: "
|
|
3053
|
+
overflow: "clip",
|
|
2934
3054
|
margin: "0",
|
|
2935
3055
|
padding: "0",
|
|
2936
3056
|
border: "0",
|
|
@@ -2997,11 +3117,18 @@ var SemanticsMirror = class {
|
|
|
2997
3117
|
record
|
|
2998
3118
|
});
|
|
2999
3119
|
this.ids.set(element, record.id);
|
|
3120
|
+
element.id = this.idPrefix + record.id;
|
|
3000
3121
|
} else existing.record = record;
|
|
3001
3122
|
this.describe(element, record);
|
|
3002
3123
|
this.place(element, record);
|
|
3003
3124
|
if (existing?.box !== void 0) this.position(existing);
|
|
3004
|
-
if (record.id === this.focusedId) this.
|
|
3125
|
+
if (record.id === this.focusedId) this.describeEditing(this.editing?.active === true ? record : null);
|
|
3126
|
+
}
|
|
3127
|
+
/** Hands the proxy the focused editable's record, with the DOM ids of its active descendant and what it controls. */
|
|
3128
|
+
describeEditing(record) {
|
|
3129
|
+
const active = record?.activeDescendant;
|
|
3130
|
+
const controls = record?.controls;
|
|
3131
|
+
this.editing?.describe(record, active === void 0 ? void 0 : this.idPrefix + active, controls === void 0 ? void 0 : this.idPrefix + controls);
|
|
3005
3132
|
}
|
|
3006
3133
|
createElement() {
|
|
3007
3134
|
const element = this.doc.createElement("div");
|
|
@@ -3016,7 +3143,7 @@ var SemanticsMirror = class {
|
|
|
3016
3143
|
padding: "0",
|
|
3017
3144
|
border: "0",
|
|
3018
3145
|
outline: "none",
|
|
3019
|
-
overflow: "
|
|
3146
|
+
overflow: "clip",
|
|
3020
3147
|
pointerEvents: "none",
|
|
3021
3148
|
color: "transparent"
|
|
3022
3149
|
});
|
|
@@ -3047,6 +3174,9 @@ var SemanticsMirror = class {
|
|
|
3047
3174
|
setNumber(element, "aria-setsize", record.setSize);
|
|
3048
3175
|
setNumber(element, "aria-level", record.level);
|
|
3049
3176
|
if (record.valueText !== void 0) element.setAttribute("aria-valuetext", record.valueText);
|
|
3177
|
+
if (record.activeDescendant !== void 0) element.setAttribute("aria-activedescendant", this.idPrefix + record.activeDescendant);
|
|
3178
|
+
if (record.controls !== void 0) element.setAttribute("aria-controls", this.idPrefix + record.controls);
|
|
3179
|
+
if (record.autocomplete !== void 0) element.setAttribute("aria-autocomplete", record.autocomplete);
|
|
3050
3180
|
}
|
|
3051
3181
|
/**
|
|
3052
3182
|
* Puts an element under its parent, at the index the record gives.
|
|
@@ -3146,7 +3276,7 @@ var SemanticsMirror = class {
|
|
|
3146
3276
|
this.focusedId = id;
|
|
3147
3277
|
const entry = id === null ? void 0 : this.entries.get(id);
|
|
3148
3278
|
if (this.editing !== null && this.editing.active) {
|
|
3149
|
-
this.
|
|
3279
|
+
this.describeEditing(entry?.record ?? null);
|
|
3150
3280
|
this.applying = true;
|
|
3151
3281
|
try {
|
|
3152
3282
|
this.editing.focus();
|
|
@@ -3155,7 +3285,7 @@ var SemanticsMirror = class {
|
|
|
3155
3285
|
}
|
|
3156
3286
|
return;
|
|
3157
3287
|
}
|
|
3158
|
-
this.
|
|
3288
|
+
this.describeEditing(null);
|
|
3159
3289
|
this.applying = true;
|
|
3160
3290
|
try {
|
|
3161
3291
|
if (entry !== void 0) entry.element.focus({ preventScroll: true });
|
|
@@ -3184,9 +3314,10 @@ var SemanticsMirror = class {
|
|
|
3184
3314
|
const onKeyUp = (event) => this.sink.keyUp?.(event);
|
|
3185
3315
|
const onPaste = (event) => {
|
|
3186
3316
|
const text = event.clipboardData?.getData("text/plain") ?? "";
|
|
3187
|
-
|
|
3317
|
+
const html = event.clipboardData?.getData("text/html") ?? "";
|
|
3318
|
+
if (text.length === 0 && html.length === 0) return;
|
|
3188
3319
|
event.preventDefault();
|
|
3189
|
-
this.sink.paste?.(text);
|
|
3320
|
+
this.sink.paste?.(text, html.length > 0 ? html : null);
|
|
3190
3321
|
};
|
|
3191
3322
|
this.container.addEventListener("click", onClick);
|
|
3192
3323
|
this.container.addEventListener("focusin", onFocusIn);
|
|
@@ -3402,6 +3533,49 @@ function observeColorScheme(onChange) {
|
|
|
3402
3533
|
return observeMediaQuery("(prefers-color-scheme: dark)", (dark) => onChange(dark ? "dark" : "light"));
|
|
3403
3534
|
}
|
|
3404
3535
|
//#endregion
|
|
3536
|
+
//#region src/app/contrast.ts
|
|
3537
|
+
/**
|
|
3538
|
+
* Watches the platform's contrast preference.
|
|
3539
|
+
*
|
|
3540
|
+
* `high` when the person has asked for more contrast
|
|
3541
|
+
* (`prefers-contrast: more`, macOS's Increase contrast and its
|
|
3542
|
+
* counterparts) or forced colours are on (Windows' contrast themes). A
|
|
3543
|
+
* canvas gets no forced colours from the browser, which only repaints
|
|
3544
|
+
* the DOM, so the application has to hear about it and draw its own:
|
|
3545
|
+
* `withContrast(theme, contrast)` is the theme that does.
|
|
3546
|
+
*
|
|
3547
|
+
* Shell-side, reported once at once and again on change, for the reason
|
|
3548
|
+
* `observeColorScheme` gives.
|
|
3549
|
+
*/
|
|
3550
|
+
function observeContrast(onChange) {
|
|
3551
|
+
let more = false;
|
|
3552
|
+
let forced = false;
|
|
3553
|
+
let last = null;
|
|
3554
|
+
let ready = false;
|
|
3555
|
+
const report = () => {
|
|
3556
|
+
if (!ready) return;
|
|
3557
|
+
const contrast = more || forced ? "high" : "standard";
|
|
3558
|
+
if (contrast !== last) {
|
|
3559
|
+
last = contrast;
|
|
3560
|
+
onChange(contrast);
|
|
3561
|
+
}
|
|
3562
|
+
};
|
|
3563
|
+
const stopMore = observeMediaQuery("(prefers-contrast: more)", (matches) => {
|
|
3564
|
+
more = matches;
|
|
3565
|
+
report();
|
|
3566
|
+
});
|
|
3567
|
+
const stopForced = observeMediaQuery("(forced-colors: active)", (matches) => {
|
|
3568
|
+
forced = matches;
|
|
3569
|
+
report();
|
|
3570
|
+
});
|
|
3571
|
+
ready = true;
|
|
3572
|
+
report();
|
|
3573
|
+
return () => {
|
|
3574
|
+
stopMore();
|
|
3575
|
+
stopForced();
|
|
3576
|
+
};
|
|
3577
|
+
}
|
|
3578
|
+
//#endregion
|
|
3405
3579
|
//#region src/app/reducedMotion.ts
|
|
3406
3580
|
/**
|
|
3407
3581
|
* Watches the platform's reduced-motion preference.
|
|
@@ -3807,6 +3981,15 @@ var WorkerApp = class {
|
|
|
3807
3981
|
get appLogic() {
|
|
3808
3982
|
return this.appLogicWorker === void 0 ? void 0 : portHandle(this.appLogicWorker);
|
|
3809
3983
|
}
|
|
3984
|
+
/**
|
|
3985
|
+
* Opens a port to whatever the render worker serves under `key`, or
|
|
3986
|
+
* returns undefined before `mount` has started it. The development
|
|
3987
|
+
* agent bridge asks for `gesso:agent` this way; nothing else in the
|
|
3988
|
+
* shell talks to the render worker except through its protocol.
|
|
3989
|
+
*/
|
|
3990
|
+
openRenderPort(key) {
|
|
3991
|
+
return this.renderWorker === void 0 ? void 0 : portHandle(this.renderWorker).open(key);
|
|
3992
|
+
}
|
|
3810
3993
|
mount(host) {
|
|
3811
3994
|
const element = resolveHost(host);
|
|
3812
3995
|
this.host = element;
|
|
@@ -3889,9 +4072,10 @@ var WorkerApp = class {
|
|
|
3889
4072
|
type: "compositionEnd",
|
|
3890
4073
|
text
|
|
3891
4074
|
}),
|
|
3892
|
-
paste: (text) => this.post({
|
|
4075
|
+
paste: (text, html) => this.post({
|
|
3893
4076
|
type: "paste",
|
|
3894
|
-
text
|
|
4077
|
+
text,
|
|
4078
|
+
...html === null ? {} : { html }
|
|
3895
4079
|
}),
|
|
3896
4080
|
blur: () => this.post({ type: "blur" }),
|
|
3897
4081
|
keyDown: (event) => this.forwardKeyDown(event, true),
|
|
@@ -3904,13 +4088,21 @@ var WorkerApp = class {
|
|
|
3904
4088
|
}),
|
|
3905
4089
|
keyDown: (event) => this.forwardKeyDown(event),
|
|
3906
4090
|
keyUp: (event) => this.forwardKeyUp(event),
|
|
3907
|
-
paste: (text) => this.post({
|
|
4091
|
+
paste: (text, html) => this.post({
|
|
3908
4092
|
type: "paste",
|
|
3909
|
-
text
|
|
4093
|
+
text,
|
|
4094
|
+
...html === null ? {} : { html }
|
|
3910
4095
|
})
|
|
3911
4096
|
}, this.proxy);
|
|
4097
|
+
const webmcp = this.options.webmcp;
|
|
4098
|
+
if (webmcp !== void 0 && webmcp !== false) import("./webmcp-CJVFvAHe.js").then((n) => n.a).then((agent) => agent.connectWebMcp(this, webmcp === true ? {} : webmcp)).then((disconnect) => {
|
|
4099
|
+
if (this.renderWorker === worker) this.disconnectWebMcp = disconnect;
|
|
4100
|
+
else disconnect();
|
|
4101
|
+
}, (error) => console.warn("[gesso] WebMCP could not register the channels:", error));
|
|
3912
4102
|
return () => this.dispose();
|
|
3913
4103
|
}
|
|
4104
|
+
/** Removes the WebMCP tools `webmcp` registered, if it did. */
|
|
4105
|
+
disconnectWebMcp = null;
|
|
3914
4106
|
/**
|
|
3915
4107
|
* An error the browser raised *at the worker object*, which is not
|
|
3916
4108
|
* the same thing as the worker reporting one.
|
|
@@ -4075,6 +4267,8 @@ var WorkerApp = class {
|
|
|
4075
4267
|
this.frameHandle = requestAnimationFrame(step);
|
|
4076
4268
|
}
|
|
4077
4269
|
dispose() {
|
|
4270
|
+
this.disconnectWebMcp?.();
|
|
4271
|
+
this.disconnectWebMcp = null;
|
|
4078
4272
|
this.ready = false;
|
|
4079
4273
|
this.setFrameLoop(false);
|
|
4080
4274
|
this.detachColorScheme?.();
|
|
@@ -4136,6 +4330,7 @@ var WorkerApp = class {
|
|
|
4136
4330
|
nodes: message.nodes,
|
|
4137
4331
|
measured: message.measured,
|
|
4138
4332
|
relayoutRoots: message.relayoutRoots,
|
|
4333
|
+
layoutPasses: message.layoutPasses,
|
|
4139
4334
|
at: message.at,
|
|
4140
4335
|
inputLatencyMs: message.inputLatencyMs,
|
|
4141
4336
|
phases: message.phases,
|
|
@@ -4182,7 +4377,11 @@ var WorkerApp = class {
|
|
|
4182
4377
|
return;
|
|
4183
4378
|
}
|
|
4184
4379
|
if (message.type === "clipboard") {
|
|
4185
|
-
writeClipboard(message.text)
|
|
4380
|
+
writeClipboard(message.text).then((copied) => this.post({
|
|
4381
|
+
type: "clipboardResult",
|
|
4382
|
+
id: message.id,
|
|
4383
|
+
copied
|
|
4384
|
+
}));
|
|
4186
4385
|
return;
|
|
4187
4386
|
}
|
|
4188
4387
|
if (message.type === "openUrl") {
|
|
@@ -4522,6 +4721,10 @@ var WorkerApp = class {
|
|
|
4522
4721
|
reduced
|
|
4523
4722
|
});
|
|
4524
4723
|
});
|
|
4724
|
+
const detachContrast = observeContrast((contrast) => this.post({
|
|
4725
|
+
type: "contrast",
|
|
4726
|
+
contrast
|
|
4727
|
+
}));
|
|
4525
4728
|
const onPointerMove = (event) => {
|
|
4526
4729
|
const { x, y } = toLocal(event.clientX, event.clientY);
|
|
4527
4730
|
const move = {
|
|
@@ -4606,6 +4809,21 @@ var WorkerApp = class {
|
|
|
4606
4809
|
canvas.addEventListener("wheel", onWheel, { passive: false });
|
|
4607
4810
|
canvas.addEventListener("keydown", onKeyDown);
|
|
4608
4811
|
canvas.addEventListener("keyup", onKeyUp);
|
|
4812
|
+
const aimedAtPage = (target) => target === null || target === document || target === document.body || target === document.documentElement;
|
|
4813
|
+
const onPageKeyDown = (event) => {
|
|
4814
|
+
if (aimedAtPage(event.target)) {
|
|
4815
|
+
canvas.focus({ preventScroll: true });
|
|
4816
|
+
this.forwardKeyDown(event);
|
|
4817
|
+
}
|
|
4818
|
+
};
|
|
4819
|
+
const onPageKeyUp = (event) => {
|
|
4820
|
+
if (aimedAtPage(event.target)) this.forwardKeyUp(event);
|
|
4821
|
+
};
|
|
4822
|
+
const pageKeys = this.options.pageKeys === true;
|
|
4823
|
+
if (pageKeys) {
|
|
4824
|
+
document.addEventListener("keydown", onPageKeyDown);
|
|
4825
|
+
document.addEventListener("keyup", onPageKeyUp);
|
|
4826
|
+
}
|
|
4609
4827
|
window.addEventListener("scroll", onCanvasMayHaveMoved, {
|
|
4610
4828
|
capture: true,
|
|
4611
4829
|
passive: true
|
|
@@ -4613,6 +4831,7 @@ var WorkerApp = class {
|
|
|
4613
4831
|
window.addEventListener("resize", onCanvasMayHaveMoved);
|
|
4614
4832
|
return () => {
|
|
4615
4833
|
detachReducedMotion();
|
|
4834
|
+
detachContrast();
|
|
4616
4835
|
detachFullscreen();
|
|
4617
4836
|
detachFileDrop();
|
|
4618
4837
|
canvas.removeEventListener("mousedown", onMouseDown);
|
|
@@ -4624,6 +4843,10 @@ var WorkerApp = class {
|
|
|
4624
4843
|
canvas.removeEventListener("wheel", onWheel);
|
|
4625
4844
|
canvas.removeEventListener("keydown", onKeyDown);
|
|
4626
4845
|
canvas.removeEventListener("keyup", onKeyUp);
|
|
4846
|
+
if (pageKeys) {
|
|
4847
|
+
document.removeEventListener("keydown", onPageKeyDown);
|
|
4848
|
+
document.removeEventListener("keyup", onPageKeyUp);
|
|
4849
|
+
}
|
|
4627
4850
|
window.removeEventListener("scroll", onCanvasMayHaveMoved, { capture: true });
|
|
4628
4851
|
window.removeEventListener("resize", onCanvasMayHaveMoved);
|
|
4629
4852
|
this.dropPendingMove();
|
|
@@ -5097,10 +5320,19 @@ function parseUrl(url) {
|
|
|
5097
5320
|
}
|
|
5098
5321
|
/** Joins a path and a query back into the url the history stores. */
|
|
5099
5322
|
function formatUrl(path, query = {}) {
|
|
5100
|
-
const pairs = Object.entries(query).map(([key, value]) => `${
|
|
5323
|
+
const pairs = Object.entries(query).map(([key, value]) => `${encodeQueryPart(key)}=${encodeQueryPart(value)}`);
|
|
5101
5324
|
const normalized = `/${pathSegments(path).join("/")}`;
|
|
5102
5325
|
return pairs.length === 0 ? normalized : `${normalized}?${pairs.join("&")}`;
|
|
5103
5326
|
}
|
|
5327
|
+
/**
|
|
5328
|
+
* Encodes a query key or value, leaving the characters a query may hold
|
|
5329
|
+
* as they are (`,` `:` `@` `/`), so a list of values reads as one:
|
|
5330
|
+
* `status=todo,done`, not `status=todo%2Cdone`. What would change how
|
|
5331
|
+
* the query parses (`&`, `=`, `+`, `#`) is still encoded.
|
|
5332
|
+
*/
|
|
5333
|
+
function encodeQueryPart(text) {
|
|
5334
|
+
return encodeURIComponent(text).replace(/%2C/gi, ",").replace(/%3A/gi, ":").replace(/%40/gi, "@").replace(/%2F/gi, "/");
|
|
5335
|
+
}
|
|
5104
5336
|
function splitOnce(text, separator) {
|
|
5105
5337
|
const index = text.indexOf(separator);
|
|
5106
5338
|
return index === -1 ? [text, ""] : [text.slice(0, index), text.slice(index + separator.length)];
|
|
@@ -5208,7 +5440,7 @@ var RouterService = class {
|
|
|
5208
5440
|
*/
|
|
5209
5441
|
params(route) {
|
|
5210
5442
|
const match = this.match.value;
|
|
5211
|
-
if (match === null || !match.chain
|
|
5443
|
+
if (match === null || !inChain(match.chain, route)) return null;
|
|
5212
5444
|
return match.params;
|
|
5213
5445
|
}
|
|
5214
5446
|
/** The same, as a cell, for binding a screen's title or fields. */
|
|
@@ -5300,7 +5532,7 @@ var RouterService = class {
|
|
|
5300
5532
|
isActive(route) {
|
|
5301
5533
|
return computed(() => {
|
|
5302
5534
|
const match = this.match.value;
|
|
5303
|
-
return match !== null && match.chain
|
|
5535
|
+
return match !== null && inChain(match.chain, route);
|
|
5304
5536
|
});
|
|
5305
5537
|
}
|
|
5306
5538
|
/**
|
|
@@ -5431,6 +5663,23 @@ function sameParams(a, b) {
|
|
|
5431
5663
|
const keys = Object.keys(left);
|
|
5432
5664
|
return keys.length === Object.keys(right).length && keys.every((key) => left[key] === right[key]);
|
|
5433
5665
|
}
|
|
5666
|
+
/**
|
|
5667
|
+
* Whether a route is in a matched chain: the same object, or a route
|
|
5668
|
+
* declared with the same path.
|
|
5669
|
+
*
|
|
5670
|
+
* Identity alone was the original test, and it is right for a table
|
|
5671
|
+
* loaded once. It fails silently under a dev server's hot replacement:
|
|
5672
|
+
* a route table that imports its screens, with screens that import the
|
|
5673
|
+
* table to read their params, is an import cycle, and after a hot edit
|
|
5674
|
+
* Vite can serve the two sides as `routes.tsx` and `routes.tsx?t=…`.
|
|
5675
|
+
* The table then loads twice, the router holds one copy's route objects
|
|
5676
|
+
* and the screens hold the other's, and every `params(route)` answers
|
|
5677
|
+
* null, so a team page quietly showed the default team. Matching is by
|
|
5678
|
+
* path already, so two routes with one path are one route to the url.
|
|
5679
|
+
*/
|
|
5680
|
+
function inChain(chain, route) {
|
|
5681
|
+
return chain.some((link) => link === route || link.path === route.path);
|
|
5682
|
+
}
|
|
5434
5683
|
//#endregion
|
|
5435
5684
|
//#region src/app/FindService.ts
|
|
5436
5685
|
/**
|
|
@@ -5550,14 +5799,61 @@ var EditingService = class {
|
|
|
5550
5799
|
* scroll already applied: a popup placed under the caret adds the
|
|
5551
5800
|
* node's position on screen and nothing else.
|
|
5552
5801
|
*
|
|
5802
|
+
* `offset` asks about a caret at another character than the one the
|
|
5803
|
+
* caret is at: the start of the word being completed, so its list
|
|
5804
|
+
* stays put as the word grows. With an overlay's `anchorRect` and the
|
|
5805
|
+
* field as its anchor, the list follows the field from then on.
|
|
5806
|
+
*
|
|
5553
5807
|
* Null when the node is not an editable, when it has not been laid
|
|
5554
5808
|
* out yet, or before the runtime has wired the controller — all
|
|
5555
5809
|
* three of which are "ask again next frame" rather than errors, and
|
|
5556
5810
|
* all three of which happen during the frame a field first appears.
|
|
5557
5811
|
*/
|
|
5558
|
-
caretRectOf(node) {
|
|
5812
|
+
caretRectOf(node, offset) {
|
|
5559
5813
|
if (node === null || this.controller === null) return null;
|
|
5560
|
-
return this.controller.caretRectOf(node);
|
|
5814
|
+
return this.controller.caretRectOf(node, offset);
|
|
5815
|
+
}
|
|
5816
|
+
/**
|
|
5817
|
+
* Selects from `anchor` to `focus` and focuses the field the focus is
|
|
5818
|
+
* in: within one field, or across the fields of an editing group (see
|
|
5819
|
+
* `UiEditingGroup`). What an editor does after a command over a
|
|
5820
|
+
* selection (making it bold, say) to leave it selected, since its
|
|
5821
|
+
* fields can only select their own text.
|
|
5822
|
+
*
|
|
5823
|
+
* False when the two ends aren't editables of one group, or before
|
|
5824
|
+
* the runtime has wired the controller.
|
|
5825
|
+
*/
|
|
5826
|
+
select(anchor, focus) {
|
|
5827
|
+
return this.controller?.select(anchor, focus) ?? false;
|
|
5828
|
+
}
|
|
5829
|
+
};
|
|
5830
|
+
//#endregion
|
|
5831
|
+
//#region src/app/ScrollService.ts
|
|
5832
|
+
/**
|
|
5833
|
+
* Scrolling a node into view, for a component that moves a highlight
|
|
5834
|
+
* rather than focus.
|
|
5835
|
+
*
|
|
5836
|
+
* Focus moved from the keyboard already brings its node into view. A
|
|
5837
|
+
* highlight doesn't move focus: a combobox's arrows walk its list while
|
|
5838
|
+
* the caret stays in the field, and a grid's cursor can do the same.
|
|
5839
|
+
* The list has to follow the highlight all the same, and only the
|
|
5840
|
+
* runtime knows where the scroll containers above a node are and how
|
|
5841
|
+
* far each has to move. This is the asking.
|
|
5842
|
+
*/
|
|
5843
|
+
var ScrollService = class {
|
|
5844
|
+
scroller = null;
|
|
5845
|
+
/** Installed by the runtime; without one nothing scrolls. */
|
|
5846
|
+
setScroller(scroller) {
|
|
5847
|
+
this.scroller = scroller;
|
|
5848
|
+
}
|
|
5849
|
+
/**
|
|
5850
|
+
* Scrolls every container above `node` just enough that it's inside
|
|
5851
|
+
* the container's viewport, `padding` pixels from the nearest edge.
|
|
5852
|
+
* Nothing moves when it's visible already. Before the node has been
|
|
5853
|
+
* laid out there is nowhere to scroll it to, and nothing happens.
|
|
5854
|
+
*/
|
|
5855
|
+
scrollIntoView(node, padding = 8) {
|
|
5856
|
+
this.scroller?.(node, padding);
|
|
5561
5857
|
}
|
|
5562
5858
|
};
|
|
5563
5859
|
//#endregion
|
|
@@ -5586,6 +5882,14 @@ var FocusService = class {
|
|
|
5586
5882
|
focused = internalState(null);
|
|
5587
5883
|
/** Whether focus is confined to a subtree by an open trap. */
|
|
5588
5884
|
trapped = internalState(false);
|
|
5885
|
+
/**
|
|
5886
|
+
* Whether the focus held is focus the keyboard can see: reached by Tab
|
|
5887
|
+
* or an arrow key, or by code with no pointer since, rather than by a
|
|
5888
|
+
* press. CSS's `:focus-visible`, for something other than a ring that
|
|
5889
|
+
* should answer the keyboard and not the click that happened to focus
|
|
5890
|
+
* the same control: a tooltip, say.
|
|
5891
|
+
*/
|
|
5892
|
+
focusVisible = internalState(false);
|
|
5589
5893
|
manager = null;
|
|
5590
5894
|
detach = null;
|
|
5591
5895
|
/** Actions taken before the runtime installed a manager, in order. */
|
|
@@ -5601,6 +5905,7 @@ var FocusService = class {
|
|
|
5601
5905
|
}
|
|
5602
5906
|
const focusChange = manager.onFocusChange((node) => {
|
|
5603
5907
|
this.focused.value = node;
|
|
5908
|
+
this.focusVisible.value = manager.focusVisible;
|
|
5604
5909
|
});
|
|
5605
5910
|
const scopeChange = manager.onScopeChange(() => {
|
|
5606
5911
|
this.trapped.value = manager.trapped;
|
|
@@ -5614,9 +5919,15 @@ var FocusService = class {
|
|
|
5614
5919
|
for (const action of pending) action(manager);
|
|
5615
5920
|
this.sync(manager);
|
|
5616
5921
|
}
|
|
5617
|
-
/**
|
|
5618
|
-
|
|
5619
|
-
|
|
5922
|
+
/**
|
|
5923
|
+
* Gives the node keyboard focus. Non-focusable nodes are ignored.
|
|
5924
|
+
*
|
|
5925
|
+
* The node is scrolled into view, unless `options.preventScroll` asks
|
|
5926
|
+
* for the page to stay where it is, as `element.focus({ preventScroll:
|
|
5927
|
+
* true })` does in a browser.
|
|
5928
|
+
*/
|
|
5929
|
+
focus(node, options) {
|
|
5930
|
+
this.run((manager) => manager.focus(node, "program", options));
|
|
5620
5931
|
}
|
|
5621
5932
|
/** Drops focus without moving it anywhere. */
|
|
5622
5933
|
blur() {
|
|
@@ -5656,6 +5967,7 @@ var FocusService = class {
|
|
|
5656
5967
|
}
|
|
5657
5968
|
sync(manager) {
|
|
5658
5969
|
this.focused.value = manager.focusedNode;
|
|
5970
|
+
this.focusVisible.value = manager.focusVisible;
|
|
5659
5971
|
this.trapped.value = manager.trapped;
|
|
5660
5972
|
}
|
|
5661
5973
|
};
|
|
@@ -6570,7 +6882,20 @@ var GessoRuntime = class {
|
|
|
6570
6882
|
sharedElements = new UiSharedElements();
|
|
6571
6883
|
focusNotifier = new FocusNotifier();
|
|
6572
6884
|
environmentNotifier = new EnvironmentNotifier();
|
|
6885
|
+
/**
|
|
6886
|
+
* The semantics tree: every record by id, and each record's children
|
|
6887
|
+
* in order.
|
|
6888
|
+
*
|
|
6889
|
+
* A record's own `index` is only kept current for the records that
|
|
6890
|
+
* are sent; the order is `semanticsChildren`. Renumbering every
|
|
6891
|
+
* record after an insertion cost a pass over the whole tree on every
|
|
6892
|
+
* structural change, for indices nothing would read until a record
|
|
6893
|
+
* was next sent or the tree was asked for (`semanticsTree`).
|
|
6894
|
+
*/
|
|
6573
6895
|
semantics = /* @__PURE__ */ new Map();
|
|
6896
|
+
semanticsChildren = /* @__PURE__ */ new Map();
|
|
6897
|
+
/** The tree in document order with every index current, built when asked for. */
|
|
6898
|
+
semanticsOrdered = null;
|
|
6574
6899
|
semanticsListener = null;
|
|
6575
6900
|
/**
|
|
6576
6901
|
* The box last reported for each mirrored node, so a frame that
|
|
@@ -6578,6 +6903,8 @@ var GessoRuntime = class {
|
|
|
6578
6903
|
* them. Only populated while a listener is attached.
|
|
6579
6904
|
*/
|
|
6580
6905
|
semanticsBoxes = /* @__PURE__ */ new Map();
|
|
6906
|
+
/** What each semantics walk leaves for the next; see `SemanticsMemory`. */
|
|
6907
|
+
semanticsMemory = semanticsMemory();
|
|
6581
6908
|
lastFocusedId = null;
|
|
6582
6909
|
/**
|
|
6583
6910
|
* Where the caret was when `reload` replaced the tree, put back on
|
|
@@ -6591,6 +6918,14 @@ var GessoRuntime = class {
|
|
|
6591
6918
|
focusAfterReload = null;
|
|
6592
6919
|
/** Whether `focusAfterReload` is waiting to be applied. */
|
|
6593
6920
|
restoringFocus = false;
|
|
6921
|
+
/**
|
|
6922
|
+
* Reveals asked for while the layout listeners run, kept until the
|
|
6923
|
+
* frame's boxes have settled; null outside `settleLayout`, where a
|
|
6924
|
+
* reveal is applied at once.
|
|
6925
|
+
*/
|
|
6926
|
+
deferredReveals = null;
|
|
6927
|
+
/** Whether a frame has run out of layout passes and been warned about; see `MAX_LAYOUT_PASSES`. */
|
|
6928
|
+
warnedLayoutPasses = false;
|
|
6594
6929
|
/** A frame changed semantics while nothing was listening; see `semanticsTree`. */
|
|
6595
6930
|
semanticsStale = false;
|
|
6596
6931
|
lastEditingState = null;
|
|
@@ -6678,8 +7013,8 @@ var GessoRuntime = class {
|
|
|
6678
7013
|
focus: {
|
|
6679
7014
|
isFocused: (node) => this.focusNotifier.isFocused(node),
|
|
6680
7015
|
isFocusVisible: (node) => this.focusNotifier.isFocusVisible(node),
|
|
6681
|
-
focus: (node) => {
|
|
6682
|
-
this.focusManager.focus(node);
|
|
7016
|
+
focus: (node, options) => {
|
|
7017
|
+
this.focusManager.focus(node, "program", options);
|
|
6683
7018
|
},
|
|
6684
7019
|
onFocusChange: (node, listener) => this.focusNotifier.add(node, listener)
|
|
6685
7020
|
},
|
|
@@ -6719,6 +7054,7 @@ var GessoRuntime = class {
|
|
|
6719
7054
|
if (!this.services.has(FindService)) this.services.register(FindService);
|
|
6720
7055
|
if (!this.services.has(FocusService)) this.services.register(FocusService);
|
|
6721
7056
|
if (!this.services.has(EditingService)) this.services.register(EditingService);
|
|
7057
|
+
if (!this.services.has(ScrollService)) this.services.register(ScrollService);
|
|
6722
7058
|
if (!this.services.has(FrameService)) this.services.register(FrameService);
|
|
6723
7059
|
if (!this.services.has(MediaService)) this.services.register(MediaService);
|
|
6724
7060
|
if (!this.services.has(FontService)) this.services.register(FontService);
|
|
@@ -6793,8 +7129,9 @@ var GessoRuntime = class {
|
|
|
6793
7129
|
this.input = this.createInput();
|
|
6794
7130
|
this.services.get(FocusService).setManager(this.input.focus);
|
|
6795
7131
|
this.services.get(EditingService).setController(this.input.editing);
|
|
6796
|
-
this.
|
|
6797
|
-
|
|
7132
|
+
this.services.get(ScrollService).setScroller((node, padding) => this.scrollIntoView(node, padding));
|
|
7133
|
+
this.input.focus.onFocusChange((node, source, options) => {
|
|
7134
|
+
if (node !== null && source !== "pointer" && options.preventScroll !== true) this.scrollIntoView(node);
|
|
6798
7135
|
});
|
|
6799
7136
|
if (options.width !== void 0 && options.height !== void 0) this.resize(options.width, options.height, this.pixelRatio);
|
|
6800
7137
|
}
|
|
@@ -7278,6 +7615,10 @@ var GessoRuntime = class {
|
|
|
7278
7615
|
setColorScheme(scheme) {
|
|
7279
7616
|
this.services.get(ShellService).applyColorScheme(scheme);
|
|
7280
7617
|
}
|
|
7618
|
+
/** The contrast the platform asks for, passed straight to `ShellService`; see `ShellService.contrast`. */
|
|
7619
|
+
setContrast(contrast) {
|
|
7620
|
+
this.services.get(ShellService).applyContrast(contrast);
|
|
7621
|
+
}
|
|
7281
7622
|
/**
|
|
7282
7623
|
* The platform's own insets, as the shell reports them: the safe area
|
|
7283
7624
|
* under a notch or a home indicator and the strip a soft keyboard
|
|
@@ -7342,6 +7683,13 @@ var GessoRuntime = class {
|
|
|
7342
7683
|
this.services.get(ShellService).settlePopup(id, opened);
|
|
7343
7684
|
}
|
|
7344
7685
|
/**
|
|
7686
|
+
* Reports whether a clipboard write landed, settling the promise
|
|
7687
|
+
* `ShellService.copyText` returned.
|
|
7688
|
+
*/
|
|
7689
|
+
settleClipboard(id, copied) {
|
|
7690
|
+
this.services.get(ShellService).settleClipboard(id, copied);
|
|
7691
|
+
}
|
|
7692
|
+
/**
|
|
7345
7693
|
* Reports what the shell found in `localStorage`, settling the
|
|
7346
7694
|
* promise `ShellService.requestStorage` returned.
|
|
7347
7695
|
*
|
|
@@ -7562,8 +7910,20 @@ var GessoRuntime = class {
|
|
|
7562
7910
|
* Scrolls every scroll container above `node` just enough that the
|
|
7563
7911
|
* node is inside its viewport, `padding` pixels from the nearest edge.
|
|
7564
7912
|
* Nothing moves when it is already visible.
|
|
7913
|
+
*
|
|
7914
|
+
* Asked for from a layout listener — `autoFocus` taking focus is the
|
|
7915
|
+
* usual one — it waits until the frame's layout has settled, so it
|
|
7916
|
+
* reveals where the node ends up rather than where the first pass
|
|
7917
|
+
* put it (see `settleLayout`).
|
|
7565
7918
|
*/
|
|
7566
7919
|
scrollIntoView(node, padding = 8) {
|
|
7920
|
+
if (this.deferredReveals !== null) {
|
|
7921
|
+
this.deferredReveals.push({
|
|
7922
|
+
node,
|
|
7923
|
+
padding
|
|
7924
|
+
});
|
|
7925
|
+
return;
|
|
7926
|
+
}
|
|
7567
7927
|
for (const adjustment of this.engine.revealAdjustments(node, padding)) {
|
|
7568
7928
|
this.smoothScroller.stop(adjustment.container);
|
|
7569
7929
|
adjustment.container.setProperty("scrollX", adjustment.scrollX);
|
|
@@ -7792,7 +8152,7 @@ var GessoRuntime = class {
|
|
|
7792
8152
|
editing,
|
|
7793
8153
|
selection
|
|
7794
8154
|
}),
|
|
7795
|
-
wheel: new UiWheelController(hitTester, this.dispatcher, scrollSink, () => this.appRoot ?? root),
|
|
8155
|
+
wheel: new UiWheelController(hitTester, this.dispatcher, scrollSink, () => this.appRoot ?? root, void 0, { pace: true }),
|
|
7796
8156
|
keyboard: new UiKeyboardController(this.dispatcher, focus, () => this.appRoot ?? this.layoutRoot(), {
|
|
7797
8157
|
editing,
|
|
7798
8158
|
selection,
|
|
@@ -7824,7 +8184,7 @@ var GessoRuntime = class {
|
|
|
7824
8184
|
if (listener !== null) this.semanticsTree();
|
|
7825
8185
|
if (listener === null || this.semantics.size === 0) return;
|
|
7826
8186
|
listener({
|
|
7827
|
-
patches: [...this.
|
|
8187
|
+
patches: [...this.orderedSemantics().values()].map((node) => ({
|
|
7828
8188
|
op: "add",
|
|
7829
8189
|
node
|
|
7830
8190
|
})),
|
|
@@ -7833,6 +8193,14 @@ var GessoRuntime = class {
|
|
|
7833
8193
|
});
|
|
7834
8194
|
}
|
|
7835
8195
|
/**
|
|
8196
|
+
* The id of the node that holds focus, or null. The same id the
|
|
8197
|
+
* semantics tree keys its records by, so a reader of the tree can
|
|
8198
|
+
* say which control a key press would reach.
|
|
8199
|
+
*/
|
|
8200
|
+
focusedNodeId() {
|
|
8201
|
+
return this.focusManager.focusedNode?.id ?? null;
|
|
8202
|
+
}
|
|
8203
|
+
/**
|
|
7836
8204
|
* The semantics tree as of the last frame that changed it.
|
|
7837
8205
|
*
|
|
7838
8206
|
* Rebuilt here when no mirror was listening and a frame marked it
|
|
@@ -7841,10 +8209,59 @@ var GessoRuntime = class {
|
|
|
7841
8209
|
*/
|
|
7842
8210
|
semanticsTree() {
|
|
7843
8211
|
if (this.semanticsStale) {
|
|
7844
|
-
this.
|
|
8212
|
+
this.storeSemantics(buildSemanticsTree(this.layoutRoot(), this.semanticsMemory));
|
|
7845
8213
|
this.semanticsStale = false;
|
|
7846
8214
|
}
|
|
7847
|
-
return this.
|
|
8215
|
+
return this.orderedSemantics();
|
|
8216
|
+
}
|
|
8217
|
+
/** Replaces the tree with one built in full, which is in document order. */
|
|
8218
|
+
storeSemantics(tree) {
|
|
8219
|
+
this.semantics = tree;
|
|
8220
|
+
this.semanticsOrdered = tree;
|
|
8221
|
+
this.semanticsChildren = /* @__PURE__ */ new Map();
|
|
8222
|
+
for (const record of tree.values()) {
|
|
8223
|
+
let list = this.semanticsChildren.get(record.parent);
|
|
8224
|
+
if (list === void 0) this.semanticsChildren.set(record.parent, list = []);
|
|
8225
|
+
list.push(record.id);
|
|
8226
|
+
}
|
|
8227
|
+
}
|
|
8228
|
+
/**
|
|
8229
|
+
* The tree in document order, every index current: a walk down
|
|
8230
|
+
* `semanticsChildren`, done when someone asks rather than per frame.
|
|
8231
|
+
* Records whose index had gone out of date are brought up to date in
|
|
8232
|
+
* the store as well.
|
|
8233
|
+
*/
|
|
8234
|
+
orderedSemantics() {
|
|
8235
|
+
if (this.semanticsOrdered !== null) return this.semanticsOrdered;
|
|
8236
|
+
const ordered = /* @__PURE__ */ new Map();
|
|
8237
|
+
const stack = [];
|
|
8238
|
+
const push = (parent) => {
|
|
8239
|
+
const list = this.semanticsChildren.get(parent) ?? [];
|
|
8240
|
+
const indices = semanticIndices(list);
|
|
8241
|
+
for (let i = list.length - 1; i >= 0; i--) stack.push([list[i], indices[i]]);
|
|
8242
|
+
};
|
|
8243
|
+
push(null);
|
|
8244
|
+
while (stack.length > 0) {
|
|
8245
|
+
const [id, index] = stack.pop();
|
|
8246
|
+
let record = this.semantics.get(id);
|
|
8247
|
+
if (record.index !== index) {
|
|
8248
|
+
record = {
|
|
8249
|
+
...record,
|
|
8250
|
+
index
|
|
8251
|
+
};
|
|
8252
|
+
this.semantics.set(id, record);
|
|
8253
|
+
}
|
|
8254
|
+
ordered.set(id, record);
|
|
8255
|
+
push(id);
|
|
8256
|
+
}
|
|
8257
|
+
this.semanticsOrdered = ordered;
|
|
8258
|
+
return ordered;
|
|
8259
|
+
}
|
|
8260
|
+
/** A record's index among its siblings now, which its own `index` may not say. */
|
|
8261
|
+
semanticsIndexOf(record) {
|
|
8262
|
+
const list = this.semanticsChildren.get(record.parent) ?? [];
|
|
8263
|
+
const at = list.indexOf(record.id);
|
|
8264
|
+
return at < 0 ? record.index : semanticIndices(list)[at];
|
|
7848
8265
|
}
|
|
7849
8266
|
/**
|
|
7850
8267
|
* Something an assistive technology did to a mirrored element,
|
|
@@ -7925,7 +8342,7 @@ var GessoRuntime = class {
|
|
|
7925
8342
|
const listener = this.semanticsListener;
|
|
7926
8343
|
if (rebuild) {
|
|
7927
8344
|
if (listener === null) this.semanticsStale = true;
|
|
7928
|
-
else patches = this.rescopeSemantics(frame) ?? this.rebuildSemantics();
|
|
8345
|
+
else patches = this.rescopeSemantics(frame) ?? this.restructureSemantics(frame) ?? this.rebuildSemantics();
|
|
7929
8346
|
}
|
|
7930
8347
|
if (listener === null) return;
|
|
7931
8348
|
const boxes = moved || patches.length > 0 ? this.collectSemanticsBoxes() : EMPTY_BOXES;
|
|
@@ -7944,9 +8361,10 @@ var GessoRuntime = class {
|
|
|
7944
8361
|
}
|
|
7945
8362
|
/** Walks the whole tree and diffs it against the last one. */
|
|
7946
8363
|
rebuildSemantics() {
|
|
7947
|
-
const
|
|
7948
|
-
const
|
|
7949
|
-
|
|
8364
|
+
const previous = this.orderedSemantics();
|
|
8365
|
+
const next = buildSemanticsTree(this.layoutRoot(), this.semanticsMemory);
|
|
8366
|
+
const patches = dropIndexShifts(previous, next, diffSemantics(previous, next));
|
|
8367
|
+
this.storeSemantics(next);
|
|
7950
8368
|
this.semanticsStale = false;
|
|
7951
8369
|
return patches;
|
|
7952
8370
|
}
|
|
@@ -8002,7 +8420,7 @@ var GessoRuntime = class {
|
|
|
8002
8420
|
const updates = [];
|
|
8003
8421
|
for (const owner of owners) {
|
|
8004
8422
|
const previous = this.semantics.get(owner.id);
|
|
8005
|
-
const subtree = buildSemanticsSubtree(owner, previous.parent, previous
|
|
8423
|
+
const subtree = buildSemanticsSubtree(owner, previous.parent, this.semanticsIndexOf(previous), semanticsInertAbove(owner), this.semanticsMemory);
|
|
8006
8424
|
if (subtree === null) return null;
|
|
8007
8425
|
let covered = 0;
|
|
8008
8426
|
for (const id of this.semanticIdsUnder(owner)) {
|
|
@@ -8010,13 +8428,18 @@ var GessoRuntime = class {
|
|
|
8010
8428
|
if (next === void 0) return null;
|
|
8011
8429
|
covered++;
|
|
8012
8430
|
const before = this.semantics.get(id);
|
|
8013
|
-
if (!recordsEqual(
|
|
8431
|
+
if (!recordsEqual({
|
|
8432
|
+
...before,
|
|
8433
|
+
index: next.index
|
|
8434
|
+
}, next)) updates.push(next);
|
|
8435
|
+
else if (before.index !== next.index) this.semantics.set(id, next);
|
|
8014
8436
|
}
|
|
8015
8437
|
if (covered !== subtree.size) return null;
|
|
8016
8438
|
}
|
|
8017
8439
|
const patches = [];
|
|
8018
8440
|
for (const record of updates) {
|
|
8019
8441
|
this.semantics.set(record.id, record);
|
|
8442
|
+
this.semanticsOrdered?.set(record.id, record);
|
|
8020
8443
|
patches.push({
|
|
8021
8444
|
op: "update",
|
|
8022
8445
|
node: record
|
|
@@ -8025,6 +8448,163 @@ var GessoRuntime = class {
|
|
|
8025
8448
|
return patches;
|
|
8026
8449
|
}
|
|
8027
8450
|
/**
|
|
8451
|
+
* The patches for a frame that changed the shape of the tree, without
|
|
8452
|
+
* walking all of it, or null when the whole tree has to be rebuilt.
|
|
8453
|
+
*
|
|
8454
|
+
* Any frame that added or removed a child used to rebuild the tree
|
|
8455
|
+
* from the root, describing every node again. In a 5,000-line editor
|
|
8456
|
+
* that was 11 ms of every Enter, for one new paragraph. Now the walk
|
|
8457
|
+
* starts at the nearest record above each change, as
|
|
8458
|
+
* `rescopeSemantics` does for a change of meaning, and inside it a
|
|
8459
|
+
* subtree nothing touched keeps its records: renumbered if its
|
|
8460
|
+
* siblings moved, never described again (see
|
|
8461
|
+
* `rebuildSemanticsSubtree`). What is described is the path down to
|
|
8462
|
+
* each change, whatever is new, and everything under a node whose
|
|
8463
|
+
* own meaning changed.
|
|
8464
|
+
*
|
|
8465
|
+
* The result is spliced into `this.semantics` in place, since the map
|
|
8466
|
+
* is in document order and a record's subtree is a contiguous run of
|
|
8467
|
+
* it, so adds still arrive in document order. Removals come first.
|
|
8468
|
+
*/
|
|
8469
|
+
restructureSemantics(frame) {
|
|
8470
|
+
if (this.semantics.size === 0 || this.semanticsStale) return null;
|
|
8471
|
+
const dirty = [];
|
|
8472
|
+
/** Nodes whose own meaning may have changed: everything under them is described afresh. */
|
|
8473
|
+
const meaning = /* @__PURE__ */ new Set();
|
|
8474
|
+
let structural = false;
|
|
8475
|
+
for (const [node, flags] of frame.entries()) {
|
|
8476
|
+
const children = (flags & DirtyFlags.Children) !== 0;
|
|
8477
|
+
const semantics = (flags & DirtyFlags.Semantics) !== 0;
|
|
8478
|
+
if (!children && !semantics) continue;
|
|
8479
|
+
if (!this.attached(node)) continue;
|
|
8480
|
+
structural ||= children;
|
|
8481
|
+
dirty.push(node);
|
|
8482
|
+
if (semantics) meaning.add(node);
|
|
8483
|
+
}
|
|
8484
|
+
if (!structural) return null;
|
|
8485
|
+
const owners = /* @__PURE__ */ new Set();
|
|
8486
|
+
for (const node of dirty) {
|
|
8487
|
+
const owner = this.semanticsOwnerOf(node);
|
|
8488
|
+
if (owner === null) return null;
|
|
8489
|
+
owners.add(owner);
|
|
8490
|
+
}
|
|
8491
|
+
for (const owner of owners) for (let above = owner.parent; above !== null; above = above.parent) if (owners.has(above)) {
|
|
8492
|
+
owners.delete(owner);
|
|
8493
|
+
break;
|
|
8494
|
+
}
|
|
8495
|
+
if (owners.size > SCOPED_SEMANTICS_OWNERS) return null;
|
|
8496
|
+
const path = /* @__PURE__ */ new Set();
|
|
8497
|
+
for (const node of dirty) for (let current = node; current !== null && !path.has(current); current = current.parent) {
|
|
8498
|
+
path.add(current);
|
|
8499
|
+
if (owners.has(current)) break;
|
|
8500
|
+
}
|
|
8501
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
8502
|
+
const underChangedMeaning = (node) => {
|
|
8503
|
+
const known = fresh.get(node);
|
|
8504
|
+
if (known !== void 0) return known;
|
|
8505
|
+
const answer = meaning.has(node) || !owners.has(node) && node.parent !== null && underChangedMeaning(node.parent);
|
|
8506
|
+
fresh.set(node, answer);
|
|
8507
|
+
return answer;
|
|
8508
|
+
};
|
|
8509
|
+
const unchanged = (node) => !path.has(node) && (meaning.size === 0 || !underChangedMeaning(node));
|
|
8510
|
+
const positions = /* @__PURE__ */ new Map();
|
|
8511
|
+
const reuse = {
|
|
8512
|
+
previous: this.semantics,
|
|
8513
|
+
childrenOf: (parent) => this.semanticsChildren.get(parent) ?? [],
|
|
8514
|
+
positionOf: (parent, id) => {
|
|
8515
|
+
let index = positions.get(parent);
|
|
8516
|
+
if (index === void 0) {
|
|
8517
|
+
index = new Map((this.semanticsChildren.get(parent) ?? []).map((child, i) => [child, i]));
|
|
8518
|
+
positions.set(parent, index);
|
|
8519
|
+
}
|
|
8520
|
+
return index.get(id) ?? -1;
|
|
8521
|
+
},
|
|
8522
|
+
memory: this.semanticsMemory,
|
|
8523
|
+
unchanged
|
|
8524
|
+
};
|
|
8525
|
+
const walks = [];
|
|
8526
|
+
for (const owner of owners) {
|
|
8527
|
+
const before = this.semantics.get(owner.id);
|
|
8528
|
+
if (before === void 0) return null;
|
|
8529
|
+
const walked = rewalkSemantics(owner, before.parent, this.semanticsIndexOf(before), semanticsInertAbove(owner), reuse);
|
|
8530
|
+
if (walked === null) return null;
|
|
8531
|
+
walks.push(walked);
|
|
8532
|
+
}
|
|
8533
|
+
const present = /* @__PURE__ */ new Set();
|
|
8534
|
+
for (const walked of walks) for (const list of walked.children.values()) for (const id of list) present.add(id);
|
|
8535
|
+
const removals = [];
|
|
8536
|
+
const remove = (id) => {
|
|
8537
|
+
removals.push({
|
|
8538
|
+
op: "remove",
|
|
8539
|
+
id
|
|
8540
|
+
});
|
|
8541
|
+
for (const child of this.semanticsChildren.get(id) ?? []) remove(child);
|
|
8542
|
+
this.semanticsChildren.delete(id);
|
|
8543
|
+
this.semantics.delete(id);
|
|
8544
|
+
};
|
|
8545
|
+
const formerly = /* @__PURE__ */ new Map();
|
|
8546
|
+
for (const walked of walks) for (const parent of walked.children.keys()) formerly.set(parent, this.semanticsChildren.get(parent) ?? []);
|
|
8547
|
+
for (const list of formerly.values()) for (const id of list) if (!present.has(id) && this.semantics.has(id)) remove(id);
|
|
8548
|
+
const reordered = /* @__PURE__ */ new Set();
|
|
8549
|
+
for (const walked of walks) for (const [parent, list] of walked.children) {
|
|
8550
|
+
const old = formerly.get(parent) ?? [];
|
|
8551
|
+
if (old.length === 0) continue;
|
|
8552
|
+
const was = new Map(old.map((id, i) => [id, i]));
|
|
8553
|
+
let last = -1;
|
|
8554
|
+
for (const id of list) {
|
|
8555
|
+
const at = was.get(id);
|
|
8556
|
+
if (at === void 0) continue;
|
|
8557
|
+
if (at < last) {
|
|
8558
|
+
reordered.add(parent);
|
|
8559
|
+
break;
|
|
8560
|
+
}
|
|
8561
|
+
last = at;
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8564
|
+
const changes = [];
|
|
8565
|
+
for (const walked of walks) {
|
|
8566
|
+
for (const { id, parent, index } of walked.placed) {
|
|
8567
|
+
const record = walked.records.get(id);
|
|
8568
|
+
const before = this.semantics.get(id);
|
|
8569
|
+
if (record !== void 0) {
|
|
8570
|
+
if (before === void 0) changes.push({
|
|
8571
|
+
op: "add",
|
|
8572
|
+
node: record
|
|
8573
|
+
});
|
|
8574
|
+
else if (before.parent !== record.parent || reordered.has(parent) || !recordsEqual({
|
|
8575
|
+
...before,
|
|
8576
|
+
index: record.index
|
|
8577
|
+
}, record)) changes.push({
|
|
8578
|
+
op: "update",
|
|
8579
|
+
node: record
|
|
8580
|
+
});
|
|
8581
|
+
this.semantics.set(id, record);
|
|
8582
|
+
} else if (before === void 0) return null;
|
|
8583
|
+
else if (before.parent !== parent || reordered.has(parent)) {
|
|
8584
|
+
const moved = {
|
|
8585
|
+
...before,
|
|
8586
|
+
parent,
|
|
8587
|
+
index
|
|
8588
|
+
};
|
|
8589
|
+
changes.push({
|
|
8590
|
+
op: "update",
|
|
8591
|
+
node: moved
|
|
8592
|
+
});
|
|
8593
|
+
this.semantics.set(id, moved);
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
for (const [parent, list] of walked.children) this.semanticsChildren.set(parent, list);
|
|
8597
|
+
}
|
|
8598
|
+
this.semanticsOrdered = null;
|
|
8599
|
+
return [...removals, ...changes];
|
|
8600
|
+
}
|
|
8601
|
+
/** Whether a node is still in the tree being laid out. */
|
|
8602
|
+
attached(node) {
|
|
8603
|
+
const root = this.layoutRoot();
|
|
8604
|
+
for (let current = node; current !== null; current = current.parent) if (current === root) return true;
|
|
8605
|
+
return false;
|
|
8606
|
+
}
|
|
8607
|
+
/**
|
|
8028
8608
|
* The nearest node at or above `node` that holds a semantics record,
|
|
8029
8609
|
* or null when nothing above it does.
|
|
8030
8610
|
*/
|
|
@@ -8085,22 +8665,75 @@ var GessoRuntime = class {
|
|
|
8085
8665
|
*
|
|
8086
8666
|
* Ids that have left the tree are dropped here rather than tracked,
|
|
8087
8667
|
* since a removal patch has already told the mirror about them.
|
|
8668
|
+
*
|
|
8669
|
+
* **Found by walking what is on screen, not by checking everything.**
|
|
8670
|
+
* Bounding what was sent still computed every node's box to find out
|
|
8671
|
+
* whether it was on screen: a 5,000-line document has three thousand
|
|
8672
|
+
* fields, and the sweep cost 4 to 24 ms of every keystroke. The walk
|
|
8673
|
+
* goes down from the root carrying the scroll and sticky offsets that
|
|
8674
|
+
* `visibleBox` adds up per node, and passes over any subtree whose
|
|
8675
|
+
* bounds (the hit tester's, see `subtreeBoundsFor`) are off screen.
|
|
8676
|
+
* A node it passes over is one this skipped before anyway.
|
|
8088
8677
|
*/
|
|
8089
8678
|
collectSemanticsBoxes() {
|
|
8090
8679
|
const changed = [];
|
|
8091
|
-
const focused = this.focusManager.focusedNode
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
if (node === void 0 || this.engine.recordFor(node) === void 0) continue;
|
|
8095
|
-
const box = this.engine.visibleBox(node);
|
|
8680
|
+
const focused = this.focusManager.focusedNode;
|
|
8681
|
+
let focusedSeen = false;
|
|
8682
|
+
const offer = (id, box) => {
|
|
8096
8683
|
const last = this.semanticsBoxes.get(id);
|
|
8097
|
-
if (last !== void 0 && boxesEqual(last, box))
|
|
8098
|
-
if (id !== focused && !this.onScreen(box)) continue;
|
|
8684
|
+
if (last !== void 0 && boxesEqual(last, box)) return;
|
|
8099
8685
|
this.semanticsBoxes.set(id, box);
|
|
8100
8686
|
changed.push({
|
|
8101
8687
|
id,
|
|
8102
8688
|
box
|
|
8103
8689
|
});
|
|
8690
|
+
};
|
|
8691
|
+
const root = this.root;
|
|
8692
|
+
if (root !== void 0) {
|
|
8693
|
+
this.engine.subtreeBoundsFor(root);
|
|
8694
|
+
const stack = [[
|
|
8695
|
+
root,
|
|
8696
|
+
0,
|
|
8697
|
+
0
|
|
8698
|
+
]];
|
|
8699
|
+
while (stack.length > 0) {
|
|
8700
|
+
const [node, dx, dy] = stack.pop();
|
|
8701
|
+
const rec = this.engine.recordFor(node);
|
|
8702
|
+
let childX = dx;
|
|
8703
|
+
let childY = dy;
|
|
8704
|
+
if (rec !== void 0) {
|
|
8705
|
+
const x = dx + rec.stickyOffsetX;
|
|
8706
|
+
const y = dy + rec.stickyOffsetY;
|
|
8707
|
+
if (!rec.boundsUnbounded && node !== focused && !this.onScreen({
|
|
8708
|
+
x: rec.boundsMinX + x,
|
|
8709
|
+
y: rec.boundsMinY + y,
|
|
8710
|
+
width: rec.boundsMaxX - rec.boundsMinX,
|
|
8711
|
+
height: rec.boundsMaxY - rec.boundsMinY
|
|
8712
|
+
})) continue;
|
|
8713
|
+
if (this.semantics.has(node.id)) {
|
|
8714
|
+
const box = {
|
|
8715
|
+
x: rec.x + x,
|
|
8716
|
+
y: rec.y + y,
|
|
8717
|
+
width: rec.width,
|
|
8718
|
+
height: rec.height
|
|
8719
|
+
};
|
|
8720
|
+
if (node === focused) {
|
|
8721
|
+
focusedSeen = true;
|
|
8722
|
+
offer(node.id, box);
|
|
8723
|
+
} else if (this.onScreen(box)) offer(node.id, box);
|
|
8724
|
+
}
|
|
8725
|
+
childX = x - (rec.scrollable ? rec.scrollX : 0);
|
|
8726
|
+
childY = y - (rec.scrollable ? rec.scrollY : 0);
|
|
8727
|
+
}
|
|
8728
|
+
for (let child = node.lastChild; child !== null; child = child.previousSibling) stack.push([
|
|
8729
|
+
child,
|
|
8730
|
+
childX,
|
|
8731
|
+
childY
|
|
8732
|
+
]);
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8735
|
+
if (focused !== null && focused !== void 0 && !focusedSeen && this.semantics.has(focused.id)) {
|
|
8736
|
+
if (this.engine.recordFor(focused) !== void 0) offer(focused.id, this.engine.visibleBox(focused));
|
|
8104
8737
|
}
|
|
8105
8738
|
if (this.semanticsBoxes.size > this.semantics.size) {
|
|
8106
8739
|
for (const id of this.semanticsBoxes.keys()) if (!this.semantics.has(id)) this.semanticsBoxes.delete(id);
|
|
@@ -8143,13 +8776,18 @@ var GessoRuntime = class {
|
|
|
8143
8776
|
* every pass, so writing the raw offset here is enough.
|
|
8144
8777
|
*/
|
|
8145
8778
|
createScrollSink() {
|
|
8779
|
+
const going = (node, record, axis) => {
|
|
8780
|
+
const written = node.getProperty(axis === "x" ? "scrollX" : "scrollY");
|
|
8781
|
+
const offset = typeof written === "number" ? written : axis === "x" ? record.scrollX : record.scrollY;
|
|
8782
|
+
return Math.min(Math.max(offset, 0), scrollRange(record, axis));
|
|
8783
|
+
};
|
|
8146
8784
|
return {
|
|
8147
8785
|
containerState: (node) => {
|
|
8148
8786
|
const record = this.engine.recordFor(node);
|
|
8149
8787
|
if (record === void 0) return;
|
|
8150
8788
|
return {
|
|
8151
|
-
scrollX: record
|
|
8152
|
-
scrollY: record
|
|
8789
|
+
scrollX: going(node, record, "x"),
|
|
8790
|
+
scrollY: going(node, record, "y"),
|
|
8153
8791
|
maxScrollX: scrollRange(record, "x"),
|
|
8154
8792
|
maxScrollY: scrollRange(record, "y"),
|
|
8155
8793
|
horizontal: node.getProperty("direction") === "row" || node.type === UiNodeType.Row,
|
|
@@ -8161,13 +8799,13 @@ var GessoRuntime = class {
|
|
|
8161
8799
|
const record = this.engine.recordFor(node);
|
|
8162
8800
|
if (record === void 0) return;
|
|
8163
8801
|
if (behavior === "smooth") {
|
|
8164
|
-
if (dx !== 0) this.smoothScroller.scrollBy(node, "scrollX", dx, record
|
|
8165
|
-
if (dy !== 0) this.smoothScroller.scrollBy(node, "scrollY", dy, record
|
|
8802
|
+
if (dx !== 0) this.smoothScroller.scrollBy(node, "scrollX", dx, going(node, record, "x"));
|
|
8803
|
+
if (dy !== 0) this.smoothScroller.scrollBy(node, "scrollY", dy, going(node, record, "y"));
|
|
8166
8804
|
return;
|
|
8167
8805
|
}
|
|
8168
8806
|
this.smoothScroller.stop(node);
|
|
8169
|
-
if (dx !== 0) node.setProperty("scrollX", record
|
|
8170
|
-
if (dy !== 0) node.setProperty("scrollY", record
|
|
8807
|
+
if (dx !== 0) node.setProperty("scrollX", going(node, record, "x") + dx);
|
|
8808
|
+
if (dy !== 0) node.setProperty("scrollY", going(node, record, "y") + dy);
|
|
8171
8809
|
this.graph.markDirty(node, DirtyFlags.Transform);
|
|
8172
8810
|
},
|
|
8173
8811
|
scrollContainers: () => this.engine.scrollContainers(),
|
|
@@ -8207,6 +8845,7 @@ var GessoRuntime = class {
|
|
|
8207
8845
|
for (const replica of this.replicas) replica.flush();
|
|
8208
8846
|
});
|
|
8209
8847
|
this.phaseTimings.environment = this.timePhase(() => this.graph.hasEnvironmentDirty(), () => this.graph.processEnvironmentDirty());
|
|
8848
|
+
if (this.input.wheel.advance(time)) this.scheduler.wake();
|
|
8210
8849
|
this.phaseTimings.virtualize = this.timePhase(() => this.hasVirtualWindows(), () => this.updateVirtualWindows());
|
|
8211
8850
|
}
|
|
8212
8851
|
hasVirtualWindows() {
|
|
@@ -8306,29 +8945,131 @@ var GessoRuntime = class {
|
|
|
8306
8945
|
if (rec === void 0) return 0;
|
|
8307
8946
|
return column ? rec.measuredHeight + rec.marginTop + rec.marginBottom : rec.measuredWidth + rec.marginLeft + rec.marginRight;
|
|
8308
8947
|
}
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8948
|
+
/**
|
|
8949
|
+
* Tells the layout listeners what moved, and lays out again for as
|
|
8950
|
+
* long as what they do in answer needs it, so the frame paints the
|
|
8951
|
+
* layout they settle on rather than the one they were told about.
|
|
8952
|
+
*
|
|
8953
|
+
* The browser's answer to the same problem. A `ResizeObserver`
|
|
8954
|
+
* callback runs after layout and before paint, and when it changes
|
|
8955
|
+
* layout the browser lays out again before it paints rather than
|
|
8956
|
+
* showing the stale boxes for a frame. The listeners here are
|
|
8957
|
+
* `breakpoint`, `sizeContainer` (and so `Responsive`),
|
|
8958
|
+
* `scrollPosition`, `autoFocus` and anything else on `host.onLayout`.
|
|
8959
|
+
* Before this a page whose breakpoint gave it wide padding was
|
|
8960
|
+
* painted with its narrow padding on the frame it appeared, and
|
|
8961
|
+
* jumped 16 px on the next.
|
|
8962
|
+
*
|
|
8963
|
+
* A pass after the first lays out only what the listeners dirtied,
|
|
8964
|
+
* the same incremental pass any frame runs, and then tells only the
|
|
8965
|
+
* listeners whose boxes it changed. A frame whose listeners wrote
|
|
8966
|
+
* nothing that lays out, which is almost every frame, runs no second
|
|
8967
|
+
* pass: it pays one look at the dirty set.
|
|
8968
|
+
*
|
|
8969
|
+
* **Bounded.** Listeners that keep moving each other's boxes would
|
|
8970
|
+
* otherwise never let the frame paint. ResizeObserver bounds its loop
|
|
8971
|
+
* by tree depth; this one is bounded by count, `MAX_LAYOUT_PASSES` in
|
|
8972
|
+
* all, which is easier to reason about and more than a real chain
|
|
8973
|
+
* needs (a breakpoint inside a `Responsive` inside a `Responsive` is
|
|
8974
|
+
* four passes). Past it the frame paints what it has, the rest waits
|
|
8975
|
+
* for the next frame as it always used to, and a warning says so
|
|
8976
|
+
* once, as the browser's "ResizeObserver loop completed with
|
|
8977
|
+
* undelivered notifications" does.
|
|
8978
|
+
*
|
|
8979
|
+
* **Reveals wait for the end.** `autoFocus` takes focus from a layout
|
|
8980
|
+
* listener, on its node's first layout, and focus reveals the node:
|
|
8981
|
+
* revealed from the first pass's boxes, it scrolled to where the node
|
|
8982
|
+
* was before a breakpoint moved it. A reveal asked for while the
|
|
8983
|
+
* listeners run is applied once they are quiet, from final boxes, and
|
|
8984
|
+
* its scroll laid out like any other write. So is the focus a hot
|
|
8985
|
+
* reload restores, after the listeners rather than before because
|
|
8986
|
+
* `autoFocus` is one of them and the restore has to be the last word:
|
|
8987
|
+
* every node in a subtree a reload replaced has its first layout on
|
|
8988
|
+
* this frame.
|
|
8989
|
+
*/
|
|
8990
|
+
settleLayout(frame, root, laidOut) {
|
|
8991
|
+
const stats = this.engine.stats;
|
|
8992
|
+
const settled = {
|
|
8993
|
+
frame,
|
|
8994
|
+
passes: laidOut ? 1 : 0,
|
|
8995
|
+
measured: laidOut ? stats.measured : 0,
|
|
8996
|
+
relayoutRoots: laidOut && !stats.fullLayout ? stats.relayoutRoots : 0
|
|
8997
|
+
};
|
|
8998
|
+
let fromRoot = laidOut && stats.fullLayout;
|
|
8999
|
+
const read = (node) => {
|
|
8321
9000
|
const record = this.engine.recordFor(node);
|
|
8322
9001
|
return {
|
|
8323
9002
|
box: this.engine.visibleBox(node),
|
|
8324
9003
|
scrollX: record?.scrollX ?? 0,
|
|
8325
9004
|
scrollY: record?.scrollY ?? 0
|
|
8326
9005
|
};
|
|
8327
|
-
}
|
|
9006
|
+
};
|
|
9007
|
+
this.deferredReveals = [];
|
|
9008
|
+
try {
|
|
9009
|
+
for (;;) {
|
|
9010
|
+
if (!this.layoutNotifier.isEmpty()) this.layoutNotifier.notify(read);
|
|
9011
|
+
if (!this.layoutPending()) {
|
|
9012
|
+
this.finishSettling(settled.passes > 0);
|
|
9013
|
+
if (!this.layoutPending()) break;
|
|
9014
|
+
}
|
|
9015
|
+
if (settled.passes >= MAX_LAYOUT_PASSES) {
|
|
9016
|
+
this.warnLayoutPasses();
|
|
9017
|
+
this.finishSettling(true);
|
|
9018
|
+
break;
|
|
9019
|
+
}
|
|
9020
|
+
this.phaseTimings.environment += this.timePhase(() => this.graph.hasEnvironmentDirty(), () => this.graph.processEnvironmentDirty());
|
|
9021
|
+
const more = this.scheduler.recollect(frame);
|
|
9022
|
+
settled.frame = settled.frame.merged(more);
|
|
9023
|
+
this.phaseTimings.layout += this.timePhase(() => true, () => this.engine.layoutForFrame(more, this.constraints, root));
|
|
9024
|
+
settled.passes++;
|
|
9025
|
+
settled.measured += stats.measured;
|
|
9026
|
+
settled.relayoutRoots += stats.fullLayout ? 0 : stats.relayoutRoots;
|
|
9027
|
+
fromRoot ||= stats.fullLayout;
|
|
9028
|
+
}
|
|
9029
|
+
} finally {
|
|
9030
|
+
this.deferredReveals = null;
|
|
9031
|
+
}
|
|
9032
|
+
if (fromRoot) settled.relayoutRoots = 0;
|
|
9033
|
+
return settled;
|
|
9034
|
+
}
|
|
9035
|
+
/**
|
|
9036
|
+
* Whether something written since the frame was collected has to be
|
|
9037
|
+
* laid out before the frame paints: the same flags that make a frame
|
|
9038
|
+
* lay out, or an inherited value still to be handed down.
|
|
9039
|
+
*/
|
|
9040
|
+
layoutPending() {
|
|
9041
|
+
return this.graph.hasEnvironmentDirty() || this.graph.getDirtyNodes().anyFlags(LAYOUT_DIRT);
|
|
9042
|
+
}
|
|
9043
|
+
/** Applies what waits for settled boxes: a reload's focus, then the reveals asked for. */
|
|
9044
|
+
finishSettling(laidOut) {
|
|
9045
|
+
const reveals = this.deferredReveals ?? [];
|
|
9046
|
+
this.deferredReveals = null;
|
|
8328
9047
|
if (this.restoringFocus && laidOut) {
|
|
8329
9048
|
this.restoringFocus = false;
|
|
8330
9049
|
this.restoreFocusAfterReload();
|
|
8331
9050
|
}
|
|
9051
|
+
for (const { node, padding } of reveals) if (this.engine.recordFor(node) !== void 0) this.scrollIntoView(node, padding);
|
|
9052
|
+
this.deferredReveals = [];
|
|
9053
|
+
}
|
|
9054
|
+
warnLayoutPasses() {
|
|
9055
|
+
if (this.warnedLayoutPasses) return;
|
|
9056
|
+
this.warnedLayoutPasses = true;
|
|
9057
|
+
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.`);
|
|
9058
|
+
}
|
|
9059
|
+
handleFrame(collected) {
|
|
9060
|
+
const root = this.root;
|
|
9061
|
+
if (root === void 0) return;
|
|
9062
|
+
const started = now();
|
|
9063
|
+
this.focusManager.settleScope();
|
|
9064
|
+
const firstPass = frameNeedsLayout(collected);
|
|
9065
|
+
this.phaseTimings.layout = this.timePhase(() => firstPass, () => this.engine.layoutForFrame(collected, this.constraints, root));
|
|
9066
|
+
const settled = this.settleLayout(collected, root, firstPass);
|
|
9067
|
+
const frame = settled.frame;
|
|
9068
|
+
const laidOut = settled.passes > 0;
|
|
9069
|
+
if (laidOut) {
|
|
9070
|
+
this.laidOutOnce = true;
|
|
9071
|
+
this.inspector.recordLayout(started);
|
|
9072
|
+
}
|
|
8332
9073
|
this.sendEditingState();
|
|
8333
9074
|
const rebuildSemantics = frameNeedsSemantics(frame);
|
|
8334
9075
|
this.phaseTimings.semantics = this.timePhase(() => rebuildSemantics || this.semanticsListener !== null && (laidOut || this.focusManager.focusedNode?.id !== this.lastFocusedId), () => this.updateSemantics(frame, rebuildSemantics, laidOut));
|
|
@@ -8362,8 +9103,9 @@ var GessoRuntime = class {
|
|
|
8362
9103
|
frame: frame.id,
|
|
8363
9104
|
durationMs: elapsed,
|
|
8364
9105
|
nodes: frame.size,
|
|
8365
|
-
measured:
|
|
8366
|
-
relayoutRoots:
|
|
9106
|
+
measured: settled.measured,
|
|
9107
|
+
relayoutRoots: settled.relayoutRoots,
|
|
9108
|
+
layoutPasses: settled.passes,
|
|
8367
9109
|
at: finished,
|
|
8368
9110
|
inputLatencyMs: this.inputLatency.take(epochAt(finished)),
|
|
8369
9111
|
phases: this.phaseTimings,
|
|
@@ -8589,7 +9331,7 @@ function timeOrigin() {
|
|
|
8589
9331
|
}
|
|
8590
9332
|
function editingStatesEqual(a, b) {
|
|
8591
9333
|
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;
|
|
9334
|
+
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
9335
|
}
|
|
8594
9336
|
/** A canvas for the text measurer when the draw canvas is not a 2D one. */
|
|
8595
9337
|
function createMeasureCanvas() {
|
|
@@ -8609,6 +9351,14 @@ function emptyGpuTimings() {
|
|
|
8609
9351
|
encode: 0
|
|
8610
9352
|
};
|
|
8611
9353
|
}
|
|
9354
|
+
/**
|
|
9355
|
+
* The most layout passes one frame runs before it paints; see
|
|
9356
|
+
* `settleLayout`. One for the frame and up to seven more for what its
|
|
9357
|
+
* layout listeners change.
|
|
9358
|
+
*/
|
|
9359
|
+
const MAX_LAYOUT_PASSES = 8;
|
|
9360
|
+
/** Dirt that makes a frame lay out; `frameNeedsLayout` asks the same of a collected frame. */
|
|
9361
|
+
const LAYOUT_DIRT = DirtyFlags.Layout | DirtyFlags.Children | DirtyFlags.SubtreeLayout | DirtyFlags.Transform;
|
|
8612
9362
|
function emptyPhaseTimings() {
|
|
8613
9363
|
return {
|
|
8614
9364
|
ticks: 0,
|
|
@@ -8628,7 +9378,7 @@ function emptyPhaseTimings() {
|
|
|
8628
9378
|
* spending an immeasurable amount of time deciding to do nothing.
|
|
8629
9379
|
*/
|
|
8630
9380
|
function frameNeedsLayout(frame) {
|
|
8631
|
-
return frame.anyFlags(
|
|
9381
|
+
return frame.anyFlags(LAYOUT_DIRT);
|
|
8632
9382
|
}
|
|
8633
9383
|
/**
|
|
8634
9384
|
* Semantics follow the properties that carry them and the shape of the
|
|
@@ -8648,6 +9398,18 @@ function frameNeedsLayout(frame) {
|
|
|
8648
9398
|
*/
|
|
8649
9399
|
const SCOPED_SEMANTICS_OWNERS = 32;
|
|
8650
9400
|
/**
|
|
9401
|
+
* The index each id in a parent's list of children has. A paragraph's
|
|
9402
|
+
* text runs are numbered among themselves and its other children among
|
|
9403
|
+
* themselves, as `buildSemanticsTree` numbers them.
|
|
9404
|
+
*/
|
|
9405
|
+
function semanticIndices(list) {
|
|
9406
|
+
const out = [];
|
|
9407
|
+
let runs = 0;
|
|
9408
|
+
let others = 0;
|
|
9409
|
+
for (const id of list) out.push(id.includes(TEXT_RUN_ID_SEPARATOR) ? runs++ : others++);
|
|
9410
|
+
return out;
|
|
9411
|
+
}
|
|
9412
|
+
/**
|
|
8651
9413
|
* How far outside the canvas a mirrored box is still worth reporting.
|
|
8652
9414
|
*
|
|
8653
9415
|
* One screenful of slack, so a fling that covers a lot of ground in
|
|
@@ -8689,6 +9451,14 @@ function frameNeedsSemantics(frame) {
|
|
|
8689
9451
|
*/
|
|
8690
9452
|
var GessoApp = class {
|
|
8691
9453
|
runtime;
|
|
9454
|
+
/**
|
|
9455
|
+
* The clock the runtime was given, its callback, and whether a frame
|
|
9456
|
+
* is waiting on it: what `uiHost().flush` needs to run that frame now,
|
|
9457
|
+
* since a background tab gets no animation frames to run it.
|
|
9458
|
+
*/
|
|
9459
|
+
clock;
|
|
9460
|
+
frameCallback;
|
|
9461
|
+
framePending = false;
|
|
8692
9462
|
canvas;
|
|
8693
9463
|
host;
|
|
8694
9464
|
inputEnabled;
|
|
@@ -8709,6 +9479,7 @@ var GessoApp = class {
|
|
|
8709
9479
|
detachFullscreen = null;
|
|
8710
9480
|
fullscreen = false;
|
|
8711
9481
|
detachReducedMotion = null;
|
|
9482
|
+
detachContrast = null;
|
|
8712
9483
|
/** Stops watching `prefers-color-scheme`; null while overridden. */
|
|
8713
9484
|
detachColorScheme = null;
|
|
8714
9485
|
/** Stops watching `visualViewport` for the safe area and the keyboard. */
|
|
@@ -8731,7 +9502,24 @@ var GessoApp = class {
|
|
|
8731
9502
|
routes: options.routes,
|
|
8732
9503
|
media: options.media,
|
|
8733
9504
|
fonts: options.fonts,
|
|
8734
|
-
clock:
|
|
9505
|
+
clock: (callback) => {
|
|
9506
|
+
const clock = (options.clock ?? ((onFrame) => new UiAnimationFrameClock(onFrame)))((time) => {
|
|
9507
|
+
this.framePending = false;
|
|
9508
|
+
callback(time);
|
|
9509
|
+
});
|
|
9510
|
+
this.clock = clock;
|
|
9511
|
+
this.frameCallback = callback;
|
|
9512
|
+
return {
|
|
9513
|
+
requestFrame: () => {
|
|
9514
|
+
this.framePending = true;
|
|
9515
|
+
clock.requestFrame();
|
|
9516
|
+
},
|
|
9517
|
+
cancelFrame: () => {
|
|
9518
|
+
this.framePending = false;
|
|
9519
|
+
clock.cancelFrame();
|
|
9520
|
+
}
|
|
9521
|
+
};
|
|
9522
|
+
},
|
|
8735
9523
|
dpr: devicePixelRatio()
|
|
8736
9524
|
});
|
|
8737
9525
|
const input = this.runtime.input;
|
|
@@ -8741,6 +9529,30 @@ var GessoApp = class {
|
|
|
8741
9529
|
keyboardController: input.keyboard
|
|
8742
9530
|
});
|
|
8743
9531
|
}
|
|
9532
|
+
/**
|
|
9533
|
+
* The running app, as the agent's screen tools need it: its semantics
|
|
9534
|
+
* tree and focus, the mirror's actions, keys, and a way to run a
|
|
9535
|
+
* pending frame now. See `uiSurface` in `gesso-framework/agent`.
|
|
9536
|
+
*/
|
|
9537
|
+
uiHost() {
|
|
9538
|
+
const runtime = this.runtime;
|
|
9539
|
+
return {
|
|
9540
|
+
semanticsTree: () => runtime.semanticsTree(),
|
|
9541
|
+
focusedNodeId: () => runtime.focusedNodeId(),
|
|
9542
|
+
applySemanticsAction: (action) => runtime.applySemanticsAction(action),
|
|
9543
|
+
key: (key, modifiers) => {
|
|
9544
|
+
runtime.input.keyboard.keyDown(key, modifiers);
|
|
9545
|
+
runtime.input.keyboard.keyUp(key, modifiers);
|
|
9546
|
+
},
|
|
9547
|
+
flush: () => {
|
|
9548
|
+
if (this.framePending && this.clock !== void 0 && this.frameCallback !== void 0) {
|
|
9549
|
+
this.framePending = false;
|
|
9550
|
+
this.clock.cancelFrame();
|
|
9551
|
+
this.frameCallback(performance.now());
|
|
9552
|
+
}
|
|
9553
|
+
}
|
|
9554
|
+
};
|
|
9555
|
+
}
|
|
8744
9556
|
/** The runtime services a component in this app can inject. */
|
|
8745
9557
|
get services() {
|
|
8746
9558
|
return this.runtime.services;
|
|
@@ -8877,6 +9689,8 @@ var GessoApp = class {
|
|
|
8877
9689
|
this.detachFullscreen = null;
|
|
8878
9690
|
this.detachReducedMotion?.();
|
|
8879
9691
|
this.detachReducedMotion = null;
|
|
9692
|
+
this.detachContrast?.();
|
|
9693
|
+
this.detachContrast = null;
|
|
8880
9694
|
this.detachColorScheme?.();
|
|
8881
9695
|
this.detachColorScheme = null;
|
|
8882
9696
|
this.detachViewportInsets?.();
|
|
@@ -8901,7 +9715,7 @@ var GessoApp = class {
|
|
|
8901
9715
|
compositionStart: () => editing.compositionStart(),
|
|
8902
9716
|
compositionUpdate: (text, caret) => editing.compositionUpdate(text, caret),
|
|
8903
9717
|
compositionEnd: (text) => editing.compositionEnd(text),
|
|
8904
|
-
paste: (text) => editing.paste(text),
|
|
9718
|
+
paste: (text, html) => editing.paste(text, html),
|
|
8905
9719
|
blur: () => this.runtime.input.focus.blur()
|
|
8906
9720
|
});
|
|
8907
9721
|
this.runtime.setTextInputSource("proxy");
|
|
@@ -8934,6 +9748,7 @@ var GessoApp = class {
|
|
|
8934
9748
|
this.runtime.setFullscreen(isDocumentFullscreen(canvas));
|
|
8935
9749
|
}
|
|
8936
9750
|
this.detachReducedMotion = observeReducedMotion((reduced) => this.runtime.setReducedMotion(reduced));
|
|
9751
|
+
this.detachContrast = observeContrast((contrast) => this.runtime.setContrast(contrast));
|
|
8937
9752
|
this.setColorScheme(this.colorSchemePreference);
|
|
8938
9753
|
}
|
|
8939
9754
|
/**
|
|
@@ -8949,7 +9764,7 @@ var GessoApp = class {
|
|
|
8949
9764
|
if (!this.accessibilityEnabled) return;
|
|
8950
9765
|
const mirror = new SemanticsMirror(canvas, {
|
|
8951
9766
|
action: (action) => this.runtime.applySemanticsAction(action),
|
|
8952
|
-
paste: (text) => this.runtime.input.editing.paste(text)
|
|
9767
|
+
paste: (text, html) => this.runtime.input.editing.paste(text, html)
|
|
8953
9768
|
}, this.proxy);
|
|
8954
9769
|
this.mirror = mirror;
|
|
8955
9770
|
this.runtime.onSemantics((update) => mirror.apply(update));
|
|
@@ -8994,10 +9809,11 @@ var GessoApp = class {
|
|
|
8994
9809
|
if (request.type === "popup") this.runtime.settlePopup(request.id, false);
|
|
8995
9810
|
else if (request.type === "storage") this.runtime.settleStorage(request.id, shellStorageDenied());
|
|
8996
9811
|
else if (request.type === "file") this.runtime.settleFile(request.id, shellFilesUnsupported("There is no document to reach files through."));
|
|
9812
|
+
else if (request.type === "clipboard") this.runtime.settleClipboard(request.id, false);
|
|
8997
9813
|
return;
|
|
8998
9814
|
}
|
|
8999
9815
|
if (request.type === "clipboard") {
|
|
9000
|
-
writeClipboard(request.text, this.canvas.ownerDocument);
|
|
9816
|
+
writeClipboard(request.text, this.canvas.ownerDocument).then((copied) => this.runtime.settleClipboard(request.id, copied));
|
|
9001
9817
|
return;
|
|
9002
9818
|
}
|
|
9003
9819
|
if (request.type === "fullscreen") {
|
|
@@ -9070,6 +9886,10 @@ var GessoAppBuilder = class {
|
|
|
9070
9886
|
mediaOptions;
|
|
9071
9887
|
fontDeclarations;
|
|
9072
9888
|
app;
|
|
9889
|
+
/** The mounted app's channel registry, for the workers an agent port asks. */
|
|
9890
|
+
channelHandle;
|
|
9891
|
+
webmcpChoice = false;
|
|
9892
|
+
disconnectWebMcp = null;
|
|
9073
9893
|
colorSchemePreference = "auto";
|
|
9074
9894
|
constructor(root) {
|
|
9075
9895
|
this.root = root;
|
|
@@ -9260,13 +10080,61 @@ var GessoAppBuilder = class {
|
|
|
9260
10080
|
if (this.errorListener !== void 0) app.onError(this.errorListener);
|
|
9261
10081
|
if (this.devtoolsListener !== null) app.onDevtools(this.devtoolsListener);
|
|
9262
10082
|
this.app = app;
|
|
10083
|
+
this.channelHandle = channels;
|
|
9263
10084
|
app.mount();
|
|
10085
|
+
this.connectWebMcp(app);
|
|
9264
10086
|
return () => {
|
|
10087
|
+
this.disconnectWebMcp?.();
|
|
10088
|
+
this.disconnectWebMcp = null;
|
|
9265
10089
|
this.app = void 0;
|
|
10090
|
+
this.channelHandle = void 0;
|
|
9266
10091
|
app.dispose();
|
|
9267
10092
|
channels.dispose();
|
|
9268
10093
|
};
|
|
9269
10094
|
}
|
|
10095
|
+
/**
|
|
10096
|
+
* Offer the app's channels and screen to an AI agent in the browser,
|
|
10097
|
+
* through WebMCP (default false). The single-thread form of
|
|
10098
|
+
* `createApp({ webmcp })`: registered once `mountSync` has run,
|
|
10099
|
+
* removed when the app is unmounted. Call before `mountSync`.
|
|
10100
|
+
*/
|
|
10101
|
+
useWebMcp(choice = true) {
|
|
10102
|
+
this.webmcpChoice = choice;
|
|
10103
|
+
return this;
|
|
10104
|
+
}
|
|
10105
|
+
/**
|
|
10106
|
+
* Opens a port to what this app serves under `key`, or returns
|
|
10107
|
+
* undefined before `mountSync`.
|
|
10108
|
+
*
|
|
10109
|
+
* The same question `WorkerApp.openRenderPort` answers, asked of the
|
|
10110
|
+
* page instead of a render worker, because in this configuration the
|
|
10111
|
+
* page is the thread that draws. It serves one thing, `gesso:agent`:
|
|
10112
|
+
* the channels fed here, the workers behind them, and the screen.
|
|
10113
|
+
* The agent code is loaded the first time one is opened.
|
|
10114
|
+
*/
|
|
10115
|
+
openRenderPort(key) {
|
|
10116
|
+
const app = this.app;
|
|
10117
|
+
if (app === void 0 || key !== "gesso:agent") return;
|
|
10118
|
+
const pair = new MessageChannel();
|
|
10119
|
+
const registrations = this.channelRegistrations;
|
|
10120
|
+
import("./app-BvlIO1G9.js").then((n) => n.t).then((agent) => agent.serveApplicationAgent(pair.port2, {
|
|
10121
|
+
channels: () => registrations.filter((registration) => registration.source !== void 0).map((registration) => ({
|
|
10122
|
+
token: registration.token,
|
|
10123
|
+
source: registration.source
|
|
10124
|
+
})),
|
|
10125
|
+
workers: () => this.channelHandle?.workers ?? [],
|
|
10126
|
+
ui: () => this.app === app ? app.uiHost() : void 0
|
|
10127
|
+
}));
|
|
10128
|
+
return pair.port1;
|
|
10129
|
+
}
|
|
10130
|
+
connectWebMcp(app) {
|
|
10131
|
+
const choice = this.webmcpChoice;
|
|
10132
|
+
if (choice === false) return;
|
|
10133
|
+
import("./webmcp-CJVFvAHe.js").then((n) => n.a).then((agent) => agent.connectWebMcp(this, choice === true ? {} : choice)).then((disconnect) => {
|
|
10134
|
+
if (this.app === app) this.disconnectWebMcp = disconnect;
|
|
10135
|
+
else disconnect();
|
|
10136
|
+
}, (error) => console.warn("[gesso] WebMCP could not register the channels:", error));
|
|
10137
|
+
}
|
|
9270
10138
|
};
|
|
9271
10139
|
function requireElement(selector) {
|
|
9272
10140
|
const element = document.querySelector(selector);
|
|
@@ -9818,6 +10686,11 @@ var RenderWorkerApp = class {
|
|
|
9818
10686
|
this.root = typeof root === "function" ? createComponent(root) : root;
|
|
9819
10687
|
this.host = host;
|
|
9820
10688
|
this.host.onmessage = (event) => this.receive(event.data);
|
|
10689
|
+
servePorts((key, port) => {
|
|
10690
|
+
if (key !== "gesso:agent") return false;
|
|
10691
|
+
this.serveAgent(port);
|
|
10692
|
+
return true;
|
|
10693
|
+
}, () => [AGENT_PORT], host);
|
|
9821
10694
|
this.host.addEventListener("error", (event) => {
|
|
9822
10695
|
const error = event.error;
|
|
9823
10696
|
this.reportUncaught(error ?? event.message ?? "Unknown error", error === void 0 ? locationOf(event) : void 0);
|
|
@@ -9995,6 +10868,41 @@ var RenderWorkerApp = class {
|
|
|
9995
10868
|
* console is the worker global's, and this class is what owns the
|
|
9996
10869
|
* global.
|
|
9997
10870
|
*/
|
|
10871
|
+
/**
|
|
10872
|
+
* Answers an agent port with every channel this application can
|
|
10873
|
+
* reach: the ones fed from this thread, and whatever each worker
|
|
10874
|
+
* behind them serves, asked over a port of its own. Imported rather
|
|
10875
|
+
* than loaded on demand: a worker built as an IIFE, which is Vite's
|
|
10876
|
+
* default, cannot split off a chunk, and a dynamic import here failed
|
|
10877
|
+
* every such build. Nothing runs until an agent asks.
|
|
10878
|
+
*/
|
|
10879
|
+
serveAgent(port) {
|
|
10880
|
+
serveApplicationAgent(port, {
|
|
10881
|
+
channels: () => this.channelRegistrations.filter((registration) => registration.source !== void 0).map((registration) => ({
|
|
10882
|
+
token: registration.token,
|
|
10883
|
+
source: registration.source
|
|
10884
|
+
})),
|
|
10885
|
+
workers: () => {
|
|
10886
|
+
const workers = new Set(this.channels?.workers ?? []);
|
|
10887
|
+
if (this.appLogicWorker !== void 0) workers.add(this.appLogicWorker);
|
|
10888
|
+
return workers;
|
|
10889
|
+
},
|
|
10890
|
+
ui: () => {
|
|
10891
|
+
const runtime = this.runtime;
|
|
10892
|
+
if (runtime === void 0) return;
|
|
10893
|
+
return {
|
|
10894
|
+
semanticsTree: () => runtime.semanticsTree(),
|
|
10895
|
+
focusedNodeId: () => runtime.focusedNodeId(),
|
|
10896
|
+
applySemanticsAction: (action) => runtime.applySemanticsAction(action),
|
|
10897
|
+
key: (key, modifiers) => {
|
|
10898
|
+
runtime.input.keyboard.keyDown(key, modifiers);
|
|
10899
|
+
runtime.input.keyboard.keyUp(key, modifiers);
|
|
10900
|
+
},
|
|
10901
|
+
flush: () => this.clock?.tick(performance.now())
|
|
10902
|
+
};
|
|
10903
|
+
}
|
|
10904
|
+
});
|
|
10905
|
+
}
|
|
9998
10906
|
setConsoleForwarding(enabled) {
|
|
9999
10907
|
this.restoreConsole?.();
|
|
10000
10908
|
this.restoreConsole = null;
|
|
@@ -10048,7 +10956,7 @@ var RenderWorkerApp = class {
|
|
|
10048
10956
|
runtime.input.pointer.pointerCancel(message.pointer);
|
|
10049
10957
|
break;
|
|
10050
10958
|
case "wheel":
|
|
10051
|
-
runtime.input.wheel.wheel(message.x, message.y, message.deltaX, message.deltaY, message.modifiers, message.deltaMode, message.wheelDeltaY);
|
|
10959
|
+
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
10960
|
break;
|
|
10053
10961
|
case "keyDown":
|
|
10054
10962
|
runtime.input.keyboard.keyDown(message.key, message.modifiers, message.textFollows);
|
|
@@ -10069,7 +10977,7 @@ var RenderWorkerApp = class {
|
|
|
10069
10977
|
runtime.input.editing.compositionEnd(message.text);
|
|
10070
10978
|
break;
|
|
10071
10979
|
case "paste":
|
|
10072
|
-
runtime.input.editing.paste(message.text);
|
|
10980
|
+
runtime.input.editing.paste(message.text, message.html ?? null);
|
|
10073
10981
|
break;
|
|
10074
10982
|
case "blur":
|
|
10075
10983
|
runtime.input.focus.blur();
|
|
@@ -10080,6 +10988,9 @@ var RenderWorkerApp = class {
|
|
|
10080
10988
|
case "colorScheme":
|
|
10081
10989
|
runtime.setColorScheme(message.scheme);
|
|
10082
10990
|
break;
|
|
10991
|
+
case "contrast":
|
|
10992
|
+
runtime.setContrast(message.contrast);
|
|
10993
|
+
break;
|
|
10083
10994
|
case "viewportInsets":
|
|
10084
10995
|
runtime.setViewportInsets(message.insets);
|
|
10085
10996
|
break;
|
|
@@ -10089,6 +11000,9 @@ var RenderWorkerApp = class {
|
|
|
10089
11000
|
case "popupResult":
|
|
10090
11001
|
runtime.settlePopup(message.id, message.opened);
|
|
10091
11002
|
break;
|
|
11003
|
+
case "clipboardResult":
|
|
11004
|
+
runtime.settleClipboard(message.id, message.copied);
|
|
11005
|
+
break;
|
|
10092
11006
|
case "storageResult":
|
|
10093
11007
|
runtime.settleStorage(message.id, message.result);
|
|
10094
11008
|
break;
|
|
@@ -10232,6 +11146,7 @@ var RenderWorkerApp = class {
|
|
|
10232
11146
|
this.runtime.onShellRequest((request) => {
|
|
10233
11147
|
if (request.type === "clipboard") this.host.postMessage({
|
|
10234
11148
|
type: "clipboard",
|
|
11149
|
+
id: request.id,
|
|
10235
11150
|
text: request.text
|
|
10236
11151
|
});
|
|
10237
11152
|
else if (request.type === "openUrl") this.host.postMessage({
|
|
@@ -10310,6 +11225,7 @@ var RenderWorkerApp = class {
|
|
|
10310
11225
|
nodes: metrics.nodes,
|
|
10311
11226
|
measured: metrics.measured,
|
|
10312
11227
|
relayoutRoots: metrics.relayoutRoots,
|
|
11228
|
+
layoutPasses: metrics.layoutPasses,
|
|
10313
11229
|
at: metrics.at,
|
|
10314
11230
|
inputLatencyMs: metrics.inputLatencyMs,
|
|
10315
11231
|
phases: metrics.phases,
|
|
@@ -10329,6 +11245,6 @@ function locationOf(event) {
|
|
|
10329
11245
|
return `${event.filename}:${line}:${column}`;
|
|
10330
11246
|
}
|
|
10331
11247
|
//#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 };
|
|
11248
|
+
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
11249
|
|
|
10334
11250
|
//# sourceMappingURL=index.js.map
|