gesso-framework 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/{FunctionComponent-Czkex8fp.d.ts → FunctionComponent-BDVrANP0.d.ts} +14 -2
- package/dist/WorkerPorts-m_hLRoPj.js.map +1 -1
- package/dist/agent/index.d.ts +3 -1
- package/dist/agent/index.js +4 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/{computed-_cYcdczA.js → computed-DyW9SPpq.js} +12 -2
- package/dist/computed-DyW9SPpq.js.map +1 -0
- package/dist/{index-BENndtaH.d.ts → index-DocVauCS.d.ts} +376 -28
- package/dist/index.d.ts +4 -4
- package/dist/index.js +194 -21
- package/dist/index.js.map +1 -1
- package/dist/jsx/jsx-runtime.d.ts +1 -1
- package/dist/{persisted-8meJmsOh.js → persisted-qEBOHDnj.js} +2 -2
- package/dist/{persisted-8meJmsOh.js.map → persisted-qEBOHDnj.js.map} +1 -1
- package/dist/remote/index.d.ts +2 -2
- package/dist/{render-cydQ10Nd.js → render-W2XO444K.js} +703 -188
- package/dist/render-W2XO444K.js.map +1 -0
- package/dist/{tools-BFBwuinE.d.ts → tools-Bl8LZ7LR.d.ts} +2 -2
- package/dist/worker/index.d.ts +3 -3
- package/dist/worker/index.js +2 -2
- package/package.json +2 -2
- package/dist/computed-_cYcdczA.js.map +0 -1
- package/dist/render-cydQ10Nd.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as InternalState,
|
|
1
|
+
import { a as InternalState, c as forceInput, d as isOutputTarget, h as withBodyOf, l as input, n as computed, o as internalState, p as outputTargetOf, s as InputCell } from "./computed-DyW9SPpq.js";
|
|
2
2
|
import { f as formatNodePath, g as printPropValue, r as isPortErrorMessage, u as describeStream, v as applyPatches, w as viewKeys, x as isChannelHostMessage } from "./WorkerPorts-m_hLRoPj.js";
|
|
3
3
|
import { n as getComponentMetadata, t as createComponent } from "./createComponent-DJads88q.js";
|
|
4
4
|
import { m as designedTheme } from "./app-De3z58x7.js";
|
|
5
|
-
import { EMPTY, Subject, Subscription, map } from "rxjs";
|
|
6
|
-
import { AnimationDriver, Box, Canvas2DRenderer, CanvasTextMeasurer, Column, Constraints, DefaultImageResolver, DefaultSvgResolver, DefaultVideoResolver, DirtyFlags, EnvironmentNotifier, FocusNotifier, IconRasterizer, LayoutEngine, LayoutInspector, LayoutNotifier, Stack, TEXT_RUN_ID_SEPARATOR, UiEditingController, UiEnvironmentKeys, UiEventType, UiFindController, UiFocusManager, UiFrame, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiInputDispatcher, UiInsetRegistry, UiKeyboardController, UiManualFrameClock, UiNodeType, 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, clearOverrideProperty, createCanvasSurface, createTween, createWebGPUSurface, defaultMotion, defineModifier, describeOverrides, diffSemantics, dragSessionFor, dropIndexShifts,
|
|
5
|
+
import { EMPTY, Subject, Subscription, concat, map, of, skip } from "rxjs";
|
|
6
|
+
import { AnimationDriver, Box, Canvas2DRenderer, CanvasTextMeasurer, Column, Constraints, DefaultImageResolver, DefaultSvgResolver, DefaultVideoResolver, DirtyFlags, EnvironmentNotifier, FocusNotifier, IconRasterizer, LayoutEngine, LayoutInspector, LayoutNotifier, Stack, TEXT_RUN_ID_SEPARATOR, Text, UiEditingController, UiEnvironmentKeys, UiEventType, UiFindController, UiFocusManager, UiFrame, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiInputDispatcher, UiInsetRegistry, UiKeyboardController, UiManualFrameClock, UiNodeType, 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, clearOverrideProperty, clockOf, createCanvasSurface, createEnvironmentKey, createTween, createWebGPUSurface, defaultMotion, defineModifier, describeOverrides, diffSemantics, dragSessionFor, dropIndexShifts, durationIn, easingIn, fade, findEnvironmentKey, formatExplanation, insetsEqual, isComponentLikeElement, isObservable, isUiElement, isWebGPUAvailable, lightTheme, linear, linkOf, measure, mediaLoadsInFlight, motion, noInsets, propertyEffects, provided, recordsEqual, registerFontStack, resolveCursor, rewalkSemantics, scrollBounds, scrollRange, scrollbarThumb, scrollsFromRight, semanticsInertAbove, semanticsMemory, setPerformanceMarks, springIn, textRunOfRecordId, writeOverrideProperty } from "gesso-core";
|
|
7
7
|
//#region src/Component.ts
|
|
8
8
|
/**
|
|
9
9
|
* Base class for all Gesso framework components.
|
|
@@ -161,6 +161,33 @@ function bounds(label) {
|
|
|
161
161
|
return cell;
|
|
162
162
|
}
|
|
163
163
|
//#endregion
|
|
164
|
+
//#region src/liveInput.ts
|
|
165
|
+
/** Where the component whose body is running keeps its live inputs, while it runs. */
|
|
166
|
+
let target = null;
|
|
167
|
+
/** Runs a component's body with its live inputs going to `into`; see `liveInput`. */
|
|
168
|
+
function withLiveInputs(into, run) {
|
|
169
|
+
const previous = target;
|
|
170
|
+
target = into;
|
|
171
|
+
try {
|
|
172
|
+
return run();
|
|
173
|
+
} finally {
|
|
174
|
+
target = previous;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Says what an input of the component whose body is running holds now,
|
|
179
|
+
* as the component keeps it, and how to read it again: a switch turned
|
|
180
|
+
* on, a field typed in, a list scrolled to 600. The inspector reports it
|
|
181
|
+
* on the component's owner report (`live`), and a design tool saves a
|
|
182
|
+
* moment of a played screen from it, to start the screen there again
|
|
183
|
+
* (`StartingInputs`). `controlled` calls it for its value, so a control
|
|
184
|
+
* written by its contract reports itself; outside a component's body it
|
|
185
|
+
* does nothing.
|
|
186
|
+
*/
|
|
187
|
+
function liveInput(name, read) {
|
|
188
|
+
target?.set(name, read);
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
164
191
|
//#region src/decorators.ts
|
|
165
192
|
/**
|
|
166
193
|
* Declares a class as a Gesso component.
|
|
@@ -455,6 +482,7 @@ function isClassComponent(component) {
|
|
|
455
482
|
* declared the entry.
|
|
456
483
|
*/
|
|
457
484
|
function physicalPlacement(placement, rtl) {
|
|
485
|
+
if (placement === "over") return placement;
|
|
458
486
|
const [side, align] = placement.split("-");
|
|
459
487
|
if (side === "top" || side === "bottom") {
|
|
460
488
|
if (align === void 0 || !rtl) return placement;
|
|
@@ -498,6 +526,149 @@ var OverlayService = class {
|
|
|
498
526
|
return this.entries.value.some((entry) => entry.id === id);
|
|
499
527
|
}
|
|
500
528
|
};
|
|
529
|
+
/**
|
|
530
|
+
* The overlays of the `OverlayScope` a node is in: where an entry
|
|
531
|
+
* opened there goes instead of the runtime's layer. Null outside every
|
|
532
|
+
* scope.
|
|
533
|
+
*/
|
|
534
|
+
const OverlayScopeKey = createEnvironmentKey({
|
|
535
|
+
name: "gesso.overlayScope",
|
|
536
|
+
defaultValue: null
|
|
537
|
+
});
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/app/FocusService.ts
|
|
540
|
+
/**
|
|
541
|
+
* Keyboard focus, as a store components can inject.
|
|
542
|
+
*
|
|
543
|
+
* `UiFocusManager` lives in the runtime and a component cannot reach
|
|
544
|
+
* it — components reach the world through stores, as they do for the
|
|
545
|
+
* clipboard (`ShellService`), overlays (`OverlayService`) and find
|
|
546
|
+
* (`FindService`). Without this a component cannot autofocus a field,
|
|
547
|
+
* trap the keyboard in a dialog, or put the caret in the input that
|
|
548
|
+
* failed validation.
|
|
549
|
+
*
|
|
550
|
+
* Nodes come from a `ref` prop. A tree's refs fire before the runtime
|
|
551
|
+
* installs the manager, so an action taken during the first build is
|
|
552
|
+
* queued and replayed once there is one; that is the same problem
|
|
553
|
+
* `FindService` solves for its query field, and the reason `autoFocus`
|
|
554
|
+
* in a dialog works on the frame it mounts.
|
|
555
|
+
*
|
|
556
|
+
* It must stay on the render thread: its actions take `UiNode`s, which
|
|
557
|
+
* never cross a worker boundary.
|
|
558
|
+
*/
|
|
559
|
+
/** The nodes a trap taken inside is modal only within: each `OverlayScope`'s. */
|
|
560
|
+
const focusBounds = /* @__PURE__ */ new WeakSet();
|
|
561
|
+
/** Makes a node one, for `OverlayScope`. */
|
|
562
|
+
function boundFocus(node) {
|
|
563
|
+
focusBounds.add(node);
|
|
564
|
+
}
|
|
565
|
+
var FocusService = class {
|
|
566
|
+
/** The node holding focus, or null. A control binds its focus ring to this. */
|
|
567
|
+
focused = internalState(null);
|
|
568
|
+
/** Whether focus is confined to a subtree by an open trap. */
|
|
569
|
+
trapped = internalState(false);
|
|
570
|
+
/**
|
|
571
|
+
* Whether the focus held is focus the keyboard can see: reached by Tab
|
|
572
|
+
* or an arrow key, or by code with no pointer since, rather than by a
|
|
573
|
+
* press. CSS's `:focus-visible`, for something other than a ring that
|
|
574
|
+
* should answer the keyboard and not the click that happened to focus
|
|
575
|
+
* the same control: a tooltip, say.
|
|
576
|
+
*/
|
|
577
|
+
focusVisible = internalState(false);
|
|
578
|
+
manager = null;
|
|
579
|
+
detach = null;
|
|
580
|
+
/** Actions taken before the runtime installed a manager, in order. */
|
|
581
|
+
queued = [];
|
|
582
|
+
/** Installed by the runtime; without one every action is queued. */
|
|
583
|
+
setManager(manager) {
|
|
584
|
+
this.detach?.();
|
|
585
|
+
this.detach = null;
|
|
586
|
+
this.manager = manager;
|
|
587
|
+
if (manager === null) {
|
|
588
|
+
this.queued = [];
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const focusChange = manager.onFocusChange((node) => {
|
|
592
|
+
this.focused.value = node;
|
|
593
|
+
this.focusVisible.value = manager.focusVisible;
|
|
594
|
+
this.trapped.value = manager.trapped;
|
|
595
|
+
});
|
|
596
|
+
manager.setFocusBounds((node) => focusBounds.has(node));
|
|
597
|
+
const scopeChange = manager.onScopeChange(() => {
|
|
598
|
+
this.trapped.value = manager.trapped;
|
|
599
|
+
});
|
|
600
|
+
this.detach = () => {
|
|
601
|
+
focusChange();
|
|
602
|
+
scopeChange();
|
|
603
|
+
};
|
|
604
|
+
const pending = this.queued;
|
|
605
|
+
this.queued = [];
|
|
606
|
+
for (const action of pending) action(manager);
|
|
607
|
+
this.sync(manager);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Gives the node keyboard focus. Non-focusable nodes are ignored.
|
|
611
|
+
*
|
|
612
|
+
* The node is scrolled into view, unless `options.preventScroll` asks
|
|
613
|
+
* for the page to stay where it is, as `element.focus({ preventScroll:
|
|
614
|
+
* true })` does in a browser.
|
|
615
|
+
*/
|
|
616
|
+
focus(node, options) {
|
|
617
|
+
this.run((manager) => manager.focus(node, "program", options));
|
|
618
|
+
}
|
|
619
|
+
/** Drops focus without moving it anywhere. */
|
|
620
|
+
blur() {
|
|
621
|
+
this.run((manager) => manager.blur());
|
|
622
|
+
}
|
|
623
|
+
/** Moves focus to the next focusable node, wrapping around. */
|
|
624
|
+
focusNext() {
|
|
625
|
+
this.run((manager) => manager.focusNext());
|
|
626
|
+
}
|
|
627
|
+
/** Moves focus to the previous focusable node, wrapping around. */
|
|
628
|
+
focusPrevious() {
|
|
629
|
+
this.run((manager) => manager.focusPrevious());
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* The tab stops under `within` (the whole tree when not given), in
|
|
633
|
+
* the order Tab visits them: what a design tool numbers to show a
|
|
634
|
+
* screen's keyboard order. Empty before the runtime has a manager.
|
|
635
|
+
*/
|
|
636
|
+
tabStops(within) {
|
|
637
|
+
return this.manager?.tabStops(within) ?? [];
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Confines focus to `scope` until `releaseTrap`, moving it inside if
|
|
641
|
+
* it was elsewhere. Traps nest: a dialog opened over a dialog traps
|
|
642
|
+
* again, and each release restores its own opener.
|
|
643
|
+
*/
|
|
644
|
+
trap(scope) {
|
|
645
|
+
this.run((manager) => manager.pushScope(scope));
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Ends the innermost trap and returns focus to whatever held it when
|
|
649
|
+
* the trap was taken — the button that opened the dialog. Given the
|
|
650
|
+
* node trapped in, only that trap ends, and nothing when it ended
|
|
651
|
+
* already: a trap's subtree removed releases it, and a release after
|
|
652
|
+
* that would otherwise end the trap under it.
|
|
653
|
+
*/
|
|
654
|
+
releaseTrap(scope) {
|
|
655
|
+
this.run((manager) => manager.popScope(scope));
|
|
656
|
+
}
|
|
657
|
+
run(action) {
|
|
658
|
+
const manager = this.manager;
|
|
659
|
+
if (manager === null) {
|
|
660
|
+
this.queued.push(action);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
action(manager);
|
|
664
|
+
this.sync(manager);
|
|
665
|
+
}
|
|
666
|
+
sync(manager) {
|
|
667
|
+
this.focused.value = manager.focusedNode;
|
|
668
|
+
this.focusVisible.value = manager.focusVisible;
|
|
669
|
+
this.trapped.value = manager.trapped;
|
|
670
|
+
}
|
|
671
|
+
};
|
|
501
672
|
//#endregion
|
|
502
673
|
//#region \0@oxc-project+runtime@0.143.0/helpers/esm/decorate.js
|
|
503
674
|
function __decorate(decorators, target, key, desc) {
|
|
@@ -509,7 +680,12 @@ function __decorate(decorators, target, key, desc) {
|
|
|
509
680
|
//#endregion
|
|
510
681
|
//#region src/overlay/OverlayLayer.ts
|
|
511
682
|
let OverlayLayer = class OverlayLayer extends Component {
|
|
512
|
-
|
|
683
|
+
runtimeOverlays;
|
|
684
|
+
/** The entries to draw, when not the runtime's: an `OverlayScope`'s. */
|
|
685
|
+
service = input(null);
|
|
686
|
+
get overlays() {
|
|
687
|
+
return this.service.value ?? this.runtimeOverlays;
|
|
688
|
+
}
|
|
513
689
|
render() {
|
|
514
690
|
return Box({
|
|
515
691
|
position: "absolute",
|
|
@@ -547,6 +723,7 @@ let OverlayLayer = class OverlayLayer extends Component {
|
|
|
547
723
|
top: entry.top,
|
|
548
724
|
bottom: entry.bottom,
|
|
549
725
|
zIndex: entry.zIndex,
|
|
726
|
+
...entry.placement === "over" && entry.anchor != null ? { hitTestable: false } : {},
|
|
550
727
|
...centering(entry),
|
|
551
728
|
...physicalEdges(entry, readsRightToLeft(environment)),
|
|
552
729
|
...inheritedFrom(environment),
|
|
@@ -556,7 +733,8 @@ let OverlayLayer = class OverlayLayer extends Component {
|
|
|
556
733
|
return elements;
|
|
557
734
|
}
|
|
558
735
|
};
|
|
559
|
-
__decorate([Inject(OverlayService)], OverlayLayer.prototype, "
|
|
736
|
+
__decorate([Inject(OverlayService)], OverlayLayer.prototype, "runtimeOverlays", void 0);
|
|
737
|
+
__decorate([Input()], OverlayLayer.prototype, "service", void 0);
|
|
560
738
|
OverlayLayer = __decorate([Define("gesso-overlay-layer")], OverlayLayer);
|
|
561
739
|
/**
|
|
562
740
|
* The dim over the page, inside the backdrop rather than on it.
|
|
@@ -646,9 +824,31 @@ function inheritedFrom(node) {
|
|
|
646
824
|
theme: environment.get(UiEnvironmentKeys.theme),
|
|
647
825
|
textStyle: environment.get(UiEnvironmentKeys.textStyle),
|
|
648
826
|
color: environment.get(UiEnvironmentKeys.color),
|
|
649
|
-
contentColor: environment.get(UiEnvironmentKeys.contentColor)
|
|
827
|
+
contentColor: environment.get(UiEnvironmentKeys.contentColor),
|
|
828
|
+
provide: providedFrom(environment)
|
|
650
829
|
};
|
|
651
830
|
}
|
|
831
|
+
/** The built-in keys an entry's box carries by props of its own, or leaves to where it is drawn. */
|
|
832
|
+
const CARRIED_BY_PROPS = new Set(Object.values(UiEnvironmentKeys).map((key) => key.name).filter((name) => name !== UiEnvironmentKeys.clock.name && name !== UiEnvironmentKeys.motion.name));
|
|
833
|
+
/**
|
|
834
|
+
* The rest of what the entry was opened under, re-provided: the clock
|
|
835
|
+
* its animations run on, so a dialog opened on a settled frame is at
|
|
836
|
+
* rest there and one opened in a slowed player slows, the motion its
|
|
837
|
+
* names mean, and every key the
|
|
838
|
+
* application provides itself, so a component in a dialog reads with
|
|
839
|
+
* `ctx.environment` what the one that opened it reads.
|
|
840
|
+
*/
|
|
841
|
+
function providedFrom(environment) {
|
|
842
|
+
const out = [];
|
|
843
|
+
const seen = /* @__PURE__ */ new Set();
|
|
844
|
+
for (let at = environment; at !== null; at = at.parent) for (const name of at.providedKeys()) {
|
|
845
|
+
if (seen.has(name) || CARRIED_BY_PROPS.has(name)) continue;
|
|
846
|
+
seen.add(name);
|
|
847
|
+
const key = findEnvironmentKey(name);
|
|
848
|
+
if (key !== void 0) out.push(provided(key, environment.get(key)));
|
|
849
|
+
}
|
|
850
|
+
return out;
|
|
851
|
+
}
|
|
652
852
|
/**
|
|
653
853
|
* The same values again whenever they change where the entry was
|
|
654
854
|
* declared, for as long as it is open.
|
|
@@ -668,6 +868,7 @@ const followEnvironmentKind = defineModifier({
|
|
|
668
868
|
host.set("textStyle", textStyle);
|
|
669
869
|
host.set("color", host.environment(UiEnvironmentKeys.color, from));
|
|
670
870
|
host.set("contentColor", host.environment(UiEnvironmentKeys.contentColor, from));
|
|
871
|
+
if (from.environment !== null) host.set("provide", providedFrom(from.environment));
|
|
671
872
|
if (entry !== null) {
|
|
672
873
|
const edges = physicalEdges(entry, textStyle.textDirection === "rtl");
|
|
673
874
|
host.set("placement", edges.placement);
|
|
@@ -684,6 +885,36 @@ function followEnvironment(node, entry) {
|
|
|
684
885
|
})];
|
|
685
886
|
}
|
|
686
887
|
//#endregion
|
|
888
|
+
//#region src/startingInputsKey.ts
|
|
889
|
+
/**
|
|
890
|
+
* The inputs components under a node start with, by component name,
|
|
891
|
+
* over what their parents pass; see `StartingInputs`. Empty outside one.
|
|
892
|
+
*/
|
|
893
|
+
const StartingInputsKey = createEnvironmentKey({
|
|
894
|
+
name: "gesso.startingInputs",
|
|
895
|
+
defaultValue: {}
|
|
896
|
+
});
|
|
897
|
+
/** How many of each name have been made under each set of starting inputs, for a list's next. */
|
|
898
|
+
const made = /* @__PURE__ */ new WeakMap();
|
|
899
|
+
/** What a component named `name`, made at `anchor`, starts with: null for nothing. */
|
|
900
|
+
function startingInputsFor(anchor, name) {
|
|
901
|
+
for (let at = anchor; at !== null; at = at.parent) if (at.environment !== null) {
|
|
902
|
+
const all = at.environment.get(StartingInputsKey);
|
|
903
|
+
const given = all[name];
|
|
904
|
+
if (given === void 0) return null;
|
|
905
|
+
if (!Array.isArray(given)) return given;
|
|
906
|
+
let counts = made.get(all);
|
|
907
|
+
if (counts === void 0) {
|
|
908
|
+
counts = /* @__PURE__ */ new Map();
|
|
909
|
+
made.set(all, counts);
|
|
910
|
+
}
|
|
911
|
+
const index = counts.get(name) ?? 0;
|
|
912
|
+
counts.set(name, index + 1);
|
|
913
|
+
return given[index] ?? null;
|
|
914
|
+
}
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
//#endregion
|
|
687
918
|
//#region src/ComponentHost.ts
|
|
688
919
|
/**
|
|
689
920
|
* Owns a single component instance and its lifecycle.
|
|
@@ -710,6 +941,7 @@ function followEnvironment(node, entry) {
|
|
|
710
941
|
var ComponentHost = class {
|
|
711
942
|
services;
|
|
712
943
|
channels;
|
|
944
|
+
anchor;
|
|
713
945
|
/** The class instance; undefined for a functional component. */
|
|
714
946
|
instance;
|
|
715
947
|
element;
|
|
@@ -731,14 +963,34 @@ var ComponentHost = class {
|
|
|
731
963
|
inputSubscriptions = /* @__PURE__ */ new Map();
|
|
732
964
|
/** A functional component's cells, by prop name, as they were handed out. */
|
|
733
965
|
functionalCells = /* @__PURE__ */ new Map();
|
|
966
|
+
/**
|
|
967
|
+
* Inputs it is drawn with over its parent's, from a `StartingInputs`
|
|
968
|
+
* above it when it was made: a moment of a screen, a tour's second
|
|
969
|
+
* step. A preview goes over these, and ending one comes back to them.
|
|
970
|
+
*/
|
|
971
|
+
started;
|
|
972
|
+
/** What the parent passed for each started input at birth: a new value from it takes over. */
|
|
973
|
+
startedOver = /* @__PURE__ */ new Map();
|
|
974
|
+
/** A started input the parent passes as a stream: the start, then the stream's changes; one per input, so it is not resubscribed. */
|
|
975
|
+
startedStreams = /* @__PURE__ */ new Map();
|
|
976
|
+
/** What the component says it holds now, by input; see `liveInput`. */
|
|
977
|
+
live = /* @__PURE__ */ new Map();
|
|
978
|
+
/** Inputs a tool draws the component with over its parent's, until it ends them; see `previewInputs`. */
|
|
979
|
+
previewed = null;
|
|
980
|
+
/** A class's input values from before they were previewed, for the ones its parent does not pass. */
|
|
981
|
+
beforePreview = /* @__PURE__ */ new Map();
|
|
734
982
|
mountHooks = [];
|
|
735
983
|
unmountHooks = [];
|
|
736
984
|
/** True only while the component function runs; hooks may register then. */
|
|
737
985
|
rendering = false;
|
|
738
|
-
constructor(element, services = new ServiceRegistry(), channels = new ChannelRegistry()) {
|
|
986
|
+
constructor(element, services = new ServiceRegistry(), channels = new ChannelRegistry(), anchor = null) {
|
|
739
987
|
this.services = services;
|
|
740
988
|
this.channels = channels;
|
|
989
|
+
this.anchor = anchor;
|
|
741
990
|
this.element = element;
|
|
991
|
+
const started = startingInputsFor(anchor, element.tag);
|
|
992
|
+
this.started = started === null ? null : { ...started };
|
|
993
|
+
for (const name of Object.keys(this.started ?? {})) this.startedOver.set(name, element.props[name]);
|
|
742
994
|
if (isClassComponent(element.component)) {
|
|
743
995
|
this.instance = new element.component();
|
|
744
996
|
this.validateInputs();
|
|
@@ -793,14 +1045,68 @@ var ComponentHost = class {
|
|
|
793
1045
|
this.wireInputs();
|
|
794
1046
|
return;
|
|
795
1047
|
}
|
|
796
|
-
for (const [name, cell] of this.functionalCells) this.applyInput(name, cell,
|
|
1048
|
+
for (const [name, cell] of this.functionalCells) this.applyInput(name, cell, this.providedFor(name), true);
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Feeds the inputs `values` names from them instead of from the
|
|
1052
|
+
* parent, until a call with null hands them back to the parent: a
|
|
1053
|
+
* design tool showing one of a component's states on the live tree.
|
|
1054
|
+
* A later call replaces the earlier one; a new prop from the parent
|
|
1055
|
+
* for a previewed input waits until the preview ends.
|
|
1056
|
+
*
|
|
1057
|
+
* Each input a preview starts or ends is handed its value even when
|
|
1058
|
+
* it holds that value already, as a new value from the parent is: a
|
|
1059
|
+
* control that owns what it was started at (`controlled`) starts
|
|
1060
|
+
* again from it, rather than keeping what a person turned it to.
|
|
1061
|
+
*/
|
|
1062
|
+
previewInputs(values) {
|
|
1063
|
+
const names = /* @__PURE__ */ new Set([...Object.keys(this.previewed ?? {}), ...Object.keys(values ?? {})]);
|
|
1064
|
+
if (this.instance !== void 0) {
|
|
1065
|
+
const cells = this.instance;
|
|
1066
|
+
const inputs = new Set(getComponentMetadata(this.element.component).inputs);
|
|
1067
|
+
for (const name of Object.keys(values ?? {})) if (inputs.has(name) && !this.beforePreview.has(name)) this.beforePreview.set(name, cells[name].getValue());
|
|
1068
|
+
this.previewed = values;
|
|
1069
|
+
this.wireInputs(names);
|
|
1070
|
+
for (const [name, value] of this.beforePreview) if (values === null || !Object.hasOwn(values, name)) {
|
|
1071
|
+
this.beforePreview.delete(name);
|
|
1072
|
+
if (this.providedFor(name) === void 0) cells[name].next(value);
|
|
1073
|
+
}
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
this.previewed = values;
|
|
1077
|
+
for (const name of names) {
|
|
1078
|
+
const cell = this.functionalCells.get(name);
|
|
1079
|
+
if (cell !== void 0) this.applyInput(name, cell, this.providedFor(name), true, true);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
/** What an input is fed from: a preview's value for it, else what it was started with, else the parent's prop. */
|
|
1083
|
+
providedFor(name) {
|
|
1084
|
+
if (this.previewed !== null && Object.hasOwn(this.previewed, name)) return this.previewed[name];
|
|
1085
|
+
const parent = this.element.props[name];
|
|
1086
|
+
if (this.started !== null && Object.hasOwn(this.started, name)) {
|
|
1087
|
+
if (parent !== this.startedOver.get(name)) {
|
|
1088
|
+
delete this.started[name];
|
|
1089
|
+
this.startedStreams.delete(name);
|
|
1090
|
+
return parent;
|
|
1091
|
+
}
|
|
1092
|
+
if (isObservable(parent)) {
|
|
1093
|
+
let stream = this.startedStreams.get(name);
|
|
1094
|
+
if (stream === void 0) {
|
|
1095
|
+
stream = concat(of(this.started[name]), parent.pipe(skip(1)));
|
|
1096
|
+
this.startedStreams.set(name, stream);
|
|
1097
|
+
}
|
|
1098
|
+
return stream;
|
|
1099
|
+
}
|
|
1100
|
+
return this.started[name];
|
|
1101
|
+
}
|
|
1102
|
+
return this.element.props[name];
|
|
797
1103
|
}
|
|
798
1104
|
renderFunction() {
|
|
799
1105
|
const component = this.element.component;
|
|
800
1106
|
const context = this.createContext();
|
|
801
1107
|
this.rendering = true;
|
|
802
1108
|
try {
|
|
803
|
-
return withBodyOf(this.element.tag, () => component(this.createInputRecord(), context));
|
|
1109
|
+
return withLiveInputs(this.live, () => withBodyOf(this.element.tag, () => component(this.createInputRecord(), context)));
|
|
804
1110
|
} finally {
|
|
805
1111
|
this.rendering = false;
|
|
806
1112
|
}
|
|
@@ -820,7 +1126,7 @@ var ComponentHost = class {
|
|
|
820
1126
|
cell = new InputCell(void 0);
|
|
821
1127
|
cell.label = `${this.element.tag}.${name}`;
|
|
822
1128
|
this.functionalCells.set(name, cell);
|
|
823
|
-
this.applyInput(name, cell, this.
|
|
1129
|
+
this.applyInput(name, cell, this.providedFor(name), true);
|
|
824
1130
|
}
|
|
825
1131
|
return cell;
|
|
826
1132
|
};
|
|
@@ -863,18 +1169,25 @@ var ComponentHost = class {
|
|
|
863
1169
|
const cell = bounds(label ?? `${this.element.tag}.bounds`);
|
|
864
1170
|
this.subscriptions.add(() => cell.complete());
|
|
865
1171
|
return cell;
|
|
1172
|
+
},
|
|
1173
|
+
environment: (key) => {
|
|
1174
|
+
for (let at = this.anchor; at !== null; at = at.parent) if (at.environment !== null) return at.environment.get(key);
|
|
1175
|
+
return key.defaultValue;
|
|
866
1176
|
}
|
|
867
1177
|
};
|
|
868
1178
|
}
|
|
869
|
-
wireInputs() {
|
|
1179
|
+
wireInputs(forced = /* @__PURE__ */ new Set()) {
|
|
870
1180
|
const metadata = getComponentMetadata(this.element.component);
|
|
871
|
-
const props = this.element.props;
|
|
872
1181
|
for (const inputName of metadata.inputs) {
|
|
873
1182
|
const cell = this.instance[inputName];
|
|
874
1183
|
cell.label ??= `${metadata.tag}.${inputName}`;
|
|
875
|
-
this.applyInput(inputName, cell,
|
|
1184
|
+
this.applyInput(inputName, cell, this.providedFor(inputName), false, forced.has(inputName));
|
|
876
1185
|
}
|
|
877
1186
|
}
|
|
1187
|
+
/** What the component says each of its inputs holds now; see `liveInput`. */
|
|
1188
|
+
liveInputs() {
|
|
1189
|
+
return new Map([...this.live].map(([name, read]) => [name, read()]));
|
|
1190
|
+
}
|
|
878
1191
|
/**
|
|
879
1192
|
* What the parent most recently supplied for each input, as given:
|
|
880
1193
|
* a value, an element or a stream, whether or not the component has
|
|
@@ -894,8 +1207,8 @@ var ComponentHost = class {
|
|
|
894
1207
|
* fallback)` supplies one — so a prop the parent stops passing is
|
|
895
1208
|
* pushed through as `undefined`, which re-applies the fallback.
|
|
896
1209
|
*/
|
|
897
|
-
applyInput(inputName, cell, provided, resetWhenAbsent) {
|
|
898
|
-
if (this.inputSources.has(inputName) && this.inputSources.get(inputName) === provided) return;
|
|
1210
|
+
applyInput(inputName, cell, provided, resetWhenAbsent, force = false) {
|
|
1211
|
+
if (!force && this.inputSources.has(inputName) && this.inputSources.get(inputName) === provided) return;
|
|
899
1212
|
const previous = this.inputSubscriptions.get(inputName);
|
|
900
1213
|
if (previous !== void 0) {
|
|
901
1214
|
previous.unsubscribe();
|
|
@@ -914,12 +1227,19 @@ var ComponentHost = class {
|
|
|
914
1227
|
return;
|
|
915
1228
|
}
|
|
916
1229
|
if (isObservable(provided)) {
|
|
917
|
-
|
|
1230
|
+
let first = force;
|
|
1231
|
+
const subscription = provided.subscribe((value) => {
|
|
1232
|
+
if (first) {
|
|
1233
|
+
first = false;
|
|
1234
|
+
forceInput(cell, value);
|
|
1235
|
+
} else pushInput(cell, value);
|
|
1236
|
+
});
|
|
918
1237
|
this.inputSubscriptions.set(inputName, subscription);
|
|
919
1238
|
this.subscriptions.add(subscription);
|
|
920
1239
|
return;
|
|
921
1240
|
}
|
|
922
|
-
|
|
1241
|
+
if (force) forceInput(cell, provided);
|
|
1242
|
+
else pushInput(cell, provided);
|
|
923
1243
|
}
|
|
924
1244
|
validateInputs() {
|
|
925
1245
|
const metadata = getComponentMetadata(this.element.component);
|
|
@@ -988,7 +1308,7 @@ var ComponentHostResolver = class {
|
|
|
988
1308
|
this.services = services;
|
|
989
1309
|
this.channels = channels;
|
|
990
1310
|
}
|
|
991
|
-
resolve(element, anchorId) {
|
|
1311
|
+
resolve(element, anchorId, anchor) {
|
|
992
1312
|
let host = this.hosts.get(anchorId);
|
|
993
1313
|
if (host !== void 0 && host.component !== element.component) {
|
|
994
1314
|
host.dispose();
|
|
@@ -996,7 +1316,7 @@ var ComponentHostResolver = class {
|
|
|
996
1316
|
host = void 0;
|
|
997
1317
|
}
|
|
998
1318
|
if (host === void 0) {
|
|
999
|
-
host = new ComponentHost(element, this.services, this.channels);
|
|
1319
|
+
host = new ComponentHost(element, this.services, this.channels, anchor ?? null);
|
|
1000
1320
|
this.hosts.set(anchorId, host);
|
|
1001
1321
|
this.pendingMounts.push(host);
|
|
1002
1322
|
} else host.updateProps(element.props);
|
|
@@ -1728,6 +2048,44 @@ function shellFilesUnsupported(error = "There is no shell to reach files through
|
|
|
1728
2048
|
error
|
|
1729
2049
|
};
|
|
1730
2050
|
}
|
|
2051
|
+
//#endregion
|
|
2052
|
+
//#region src/app/FrameService.ts
|
|
2053
|
+
/**
|
|
2054
|
+
* Every frame the runtime draws, as a component can hear it.
|
|
2055
|
+
*
|
|
2056
|
+
* The shell already receives these as `onFrame`, for an FPS readout or
|
|
2057
|
+
* a profiler on the host thread. A screen that wants to show its own
|
|
2058
|
+
* frame gap or input latency, as a demo of the thread model does,
|
|
2059
|
+
* needs them on this thread, and this is where the runtime puts them.
|
|
2060
|
+
* Injected like any service: `ctx.inject(FrameService).frames`.
|
|
2061
|
+
*/
|
|
2062
|
+
var FrameService = class {
|
|
2063
|
+
subject = new Subject();
|
|
2064
|
+
flusher = null;
|
|
2065
|
+
/** Emits after each frame finishes, with what it cost and what it answered. */
|
|
2066
|
+
frames = this.subject.asObservable();
|
|
2067
|
+
/** Called by the runtime; not for components. */
|
|
2068
|
+
publish(metrics) {
|
|
2069
|
+
this.subject.next(metrics);
|
|
2070
|
+
}
|
|
2071
|
+
/** Called by the shell that drives the frames; not for components. */
|
|
2072
|
+
setFlush(flush) {
|
|
2073
|
+
this.flusher = flush;
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Draws a pending frame now, rather than when the display next asks
|
|
2077
|
+
* for one; nothing when none is pending.
|
|
2078
|
+
*
|
|
2079
|
+
* A tab in the background is sent no frames. A design editor's tool,
|
|
2080
|
+
* called by an agent while the editor's window is behind another,
|
|
2081
|
+
* changes the screen and waits for it to be drawn before reading it:
|
|
2082
|
+
* without this, it waits for a frame that never comes. The agent's
|
|
2083
|
+
* own screen tools (`uiSurface`) do the same after each action.
|
|
2084
|
+
*/
|
|
2085
|
+
flush() {
|
|
2086
|
+
this.flusher?.();
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
1731
2089
|
/** A text node's text as the tree shows it. */
|
|
1732
2090
|
function treeText(text) {
|
|
1733
2091
|
if (typeof text !== "string" || text === "") return;
|
|
@@ -2613,124 +2971,6 @@ var ScrollService = class {
|
|
|
2613
2971
|
}
|
|
2614
2972
|
};
|
|
2615
2973
|
//#endregion
|
|
2616
|
-
//#region src/app/FocusService.ts
|
|
2617
|
-
/**
|
|
2618
|
-
* Keyboard focus, as a store components can inject.
|
|
2619
|
-
*
|
|
2620
|
-
* `UiFocusManager` lives in the runtime and a component cannot reach
|
|
2621
|
-
* it — components reach the world through stores, as they do for the
|
|
2622
|
-
* clipboard (`ShellService`), overlays (`OverlayService`) and find
|
|
2623
|
-
* (`FindService`). Without this a component cannot autofocus a field,
|
|
2624
|
-
* trap the keyboard in a dialog, or put the caret in the input that
|
|
2625
|
-
* failed validation.
|
|
2626
|
-
*
|
|
2627
|
-
* Nodes come from a `ref` prop. A tree's refs fire before the runtime
|
|
2628
|
-
* installs the manager, so an action taken during the first build is
|
|
2629
|
-
* queued and replayed once there is one; that is the same problem
|
|
2630
|
-
* `FindService` solves for its query field, and the reason `autoFocus`
|
|
2631
|
-
* in a dialog works on the frame it mounts.
|
|
2632
|
-
*
|
|
2633
|
-
* It must stay on the render thread: its actions take `UiNode`s, which
|
|
2634
|
-
* never cross a worker boundary.
|
|
2635
|
-
*/
|
|
2636
|
-
var FocusService = class {
|
|
2637
|
-
/** The node holding focus, or null. A control binds its focus ring to this. */
|
|
2638
|
-
focused = internalState(null);
|
|
2639
|
-
/** Whether focus is confined to a subtree by an open trap. */
|
|
2640
|
-
trapped = internalState(false);
|
|
2641
|
-
/**
|
|
2642
|
-
* Whether the focus held is focus the keyboard can see: reached by Tab
|
|
2643
|
-
* or an arrow key, or by code with no pointer since, rather than by a
|
|
2644
|
-
* press. CSS's `:focus-visible`, for something other than a ring that
|
|
2645
|
-
* should answer the keyboard and not the click that happened to focus
|
|
2646
|
-
* the same control: a tooltip, say.
|
|
2647
|
-
*/
|
|
2648
|
-
focusVisible = internalState(false);
|
|
2649
|
-
manager = null;
|
|
2650
|
-
detach = null;
|
|
2651
|
-
/** Actions taken before the runtime installed a manager, in order. */
|
|
2652
|
-
queued = [];
|
|
2653
|
-
/** Installed by the runtime; without one every action is queued. */
|
|
2654
|
-
setManager(manager) {
|
|
2655
|
-
this.detach?.();
|
|
2656
|
-
this.detach = null;
|
|
2657
|
-
this.manager = manager;
|
|
2658
|
-
if (manager === null) {
|
|
2659
|
-
this.queued = [];
|
|
2660
|
-
return;
|
|
2661
|
-
}
|
|
2662
|
-
const focusChange = manager.onFocusChange((node) => {
|
|
2663
|
-
this.focused.value = node;
|
|
2664
|
-
this.focusVisible.value = manager.focusVisible;
|
|
2665
|
-
});
|
|
2666
|
-
const scopeChange = manager.onScopeChange(() => {
|
|
2667
|
-
this.trapped.value = manager.trapped;
|
|
2668
|
-
});
|
|
2669
|
-
this.detach = () => {
|
|
2670
|
-
focusChange();
|
|
2671
|
-
scopeChange();
|
|
2672
|
-
};
|
|
2673
|
-
const pending = this.queued;
|
|
2674
|
-
this.queued = [];
|
|
2675
|
-
for (const action of pending) action(manager);
|
|
2676
|
-
this.sync(manager);
|
|
2677
|
-
}
|
|
2678
|
-
/**
|
|
2679
|
-
* Gives the node keyboard focus. Non-focusable nodes are ignored.
|
|
2680
|
-
*
|
|
2681
|
-
* The node is scrolled into view, unless `options.preventScroll` asks
|
|
2682
|
-
* for the page to stay where it is, as `element.focus({ preventScroll:
|
|
2683
|
-
* true })` does in a browser.
|
|
2684
|
-
*/
|
|
2685
|
-
focus(node, options) {
|
|
2686
|
-
this.run((manager) => manager.focus(node, "program", options));
|
|
2687
|
-
}
|
|
2688
|
-
/** Drops focus without moving it anywhere. */
|
|
2689
|
-
blur() {
|
|
2690
|
-
this.run((manager) => manager.blur());
|
|
2691
|
-
}
|
|
2692
|
-
/** Moves focus to the next focusable node, wrapping around. */
|
|
2693
|
-
focusNext() {
|
|
2694
|
-
this.run((manager) => manager.focusNext());
|
|
2695
|
-
}
|
|
2696
|
-
/** Moves focus to the previous focusable node, wrapping around. */
|
|
2697
|
-
focusPrevious() {
|
|
2698
|
-
this.run((manager) => manager.focusPrevious());
|
|
2699
|
-
}
|
|
2700
|
-
/**
|
|
2701
|
-
* Confines focus to `scope` until `releaseTrap`, moving it inside if
|
|
2702
|
-
* it was elsewhere. Traps nest: a dialog opened over a dialog traps
|
|
2703
|
-
* again, and each release restores its own opener.
|
|
2704
|
-
*/
|
|
2705
|
-
trap(scope) {
|
|
2706
|
-
this.run((manager) => manager.pushScope(scope));
|
|
2707
|
-
}
|
|
2708
|
-
/**
|
|
2709
|
-
* Ends the innermost trap and returns focus to whatever held it when
|
|
2710
|
-
* the trap was taken — the button that opened the dialog. Given the
|
|
2711
|
-
* node trapped in, only that trap ends, and nothing when it ended
|
|
2712
|
-
* already: a trap's subtree removed releases it, and a release after
|
|
2713
|
-
* that would otherwise end the trap under it.
|
|
2714
|
-
*/
|
|
2715
|
-
releaseTrap(scope) {
|
|
2716
|
-
this.run((manager) => manager.popScope(scope));
|
|
2717
|
-
}
|
|
2718
|
-
run(action) {
|
|
2719
|
-
const manager = this.manager;
|
|
2720
|
-
if (manager === null) {
|
|
2721
|
-
this.queued.push(action);
|
|
2722
|
-
return;
|
|
2723
|
-
}
|
|
2724
|
-
action(manager);
|
|
2725
|
-
this.sync(manager);
|
|
2726
|
-
}
|
|
2727
|
-
sync(manager) {
|
|
2728
|
-
this.focused.value = manager.focusedNode;
|
|
2729
|
-
this.focusVisible.value = manager.focusVisible;
|
|
2730
|
-
this.trapped.value = manager.trapped;
|
|
2731
|
-
}
|
|
2732
|
-
};
|
|
2733
|
-
//#endregion
|
|
2734
2974
|
//#region src/app/MediaService.ts
|
|
2735
2975
|
/**
|
|
2736
2976
|
* Where an `Image` finds its bitmap, an `Icon` its raster and a
|
|
@@ -3065,6 +3305,16 @@ var AnimationService = class {
|
|
|
3065
3305
|
*/
|
|
3066
3306
|
setMotion(motion) {
|
|
3067
3307
|
this.motionVocabulary = motion;
|
|
3308
|
+
this.motionListener?.(motion);
|
|
3309
|
+
}
|
|
3310
|
+
motionListener = null;
|
|
3311
|
+
/**
|
|
3312
|
+
* Called by the runtime, which provides the vocabulary at its root so
|
|
3313
|
+
* a name in a `transition` or a `motion` state resolves to it too; a
|
|
3314
|
+
* part of the tree may provide its own over it (`UiEnvironmentKeys.motion`).
|
|
3315
|
+
*/
|
|
3316
|
+
setMotionListener(listener) {
|
|
3317
|
+
this.motionListener = listener;
|
|
3068
3318
|
}
|
|
3069
3319
|
get motion() {
|
|
3070
3320
|
return this.motionVocabulary;
|
|
@@ -3115,7 +3365,7 @@ var AnimationService = class {
|
|
|
3115
3365
|
tween.snap();
|
|
3116
3366
|
return tween.values;
|
|
3117
3367
|
}
|
|
3118
|
-
return driver.start(tween);
|
|
3368
|
+
return driver.start(tween, options.clock ?? null);
|
|
3119
3369
|
}
|
|
3120
3370
|
/**
|
|
3121
3371
|
* Pulls a number towards a value on a spring, with no duration.
|
|
@@ -3124,7 +3374,7 @@ var AnimationService = class {
|
|
|
3124
3374
|
* so a flick that changes direction bends rather than restarting.
|
|
3125
3375
|
*/
|
|
3126
3376
|
spring(cell, to, options = {}) {
|
|
3127
|
-
const base =
|
|
3377
|
+
const base = springIn(this.motionVocabulary, options.spring);
|
|
3128
3378
|
const driver = this.driver;
|
|
3129
3379
|
const previous = driver?.animationFor(cell);
|
|
3130
3380
|
const animation = new UiSpring(cell, to, {
|
|
@@ -3143,7 +3393,7 @@ var AnimationService = class {
|
|
|
3143
3393
|
animation.snap();
|
|
3144
3394
|
return animation.values;
|
|
3145
3395
|
}
|
|
3146
|
-
return driver.start(animation);
|
|
3396
|
+
return driver.start(animation, options.clock ?? null);
|
|
3147
3397
|
}
|
|
3148
3398
|
/** Stops whatever is driving a cell, leaving it where it stands. */
|
|
3149
3399
|
stop(cell) {
|
|
@@ -3162,31 +3412,10 @@ var AnimationService = class {
|
|
|
3162
3412
|
return this.driver?.animationFor(cell);
|
|
3163
3413
|
}
|
|
3164
3414
|
resolveDuration(duration) {
|
|
3165
|
-
return
|
|
3415
|
+
return durationIn(this.motionVocabulary, duration);
|
|
3166
3416
|
}
|
|
3167
3417
|
resolveEasing(easing) {
|
|
3168
|
-
|
|
3169
|
-
return this.motionVocabulary.easings[easing] ?? easings.standard;
|
|
3170
|
-
}
|
|
3171
|
-
};
|
|
3172
|
-
//#endregion
|
|
3173
|
-
//#region src/app/FrameService.ts
|
|
3174
|
-
/**
|
|
3175
|
-
* Every frame the runtime draws, as a component can hear it.
|
|
3176
|
-
*
|
|
3177
|
-
* The shell already receives these as `onFrame`, for an FPS readout or
|
|
3178
|
-
* a profiler on the host thread. A screen that wants to show its own
|
|
3179
|
-
* frame gap or input latency, as a demo of the thread model does,
|
|
3180
|
-
* needs them on this thread, and this is where the runtime puts them.
|
|
3181
|
-
* Injected like any service: `ctx.inject(FrameService).frames`.
|
|
3182
|
-
*/
|
|
3183
|
-
var FrameService = class {
|
|
3184
|
-
subject = new Subject();
|
|
3185
|
-
/** Emits after each frame finishes, with what it cost and what it answered. */
|
|
3186
|
-
frames = this.subject.asObservable();
|
|
3187
|
-
/** Called by the runtime; not for components. */
|
|
3188
|
-
publish(metrics) {
|
|
3189
|
-
this.subject.next(metrics);
|
|
3418
|
+
return easingIn(this.motionVocabulary, easing);
|
|
3190
3419
|
}
|
|
3191
3420
|
};
|
|
3192
3421
|
//#endregion
|
|
@@ -3238,6 +3467,19 @@ var InspectionService = class {
|
|
|
3238
3467
|
return this.host.box?.(node) ?? this.host.inspect(node).box;
|
|
3239
3468
|
}
|
|
3240
3469
|
/**
|
|
3470
|
+
* Where the node is seen on the screen: `box` with every transform
|
|
3471
|
+
* above it applied, so a node in a frame drawn at half its size is
|
|
3472
|
+
* half its size, where it is drawn. What an overlay laid over the
|
|
3473
|
+
* live tree is placed by (an outline on the component under the
|
|
3474
|
+
* pointer). `box` keeps the untransformed box, which is where the
|
|
3475
|
+
* node is in the layout: what an editor reading a zoomed board in
|
|
3476
|
+
* the board's own coordinates (a minimap) needs.
|
|
3477
|
+
*/
|
|
3478
|
+
screenBox(node) {
|
|
3479
|
+
if (this.host === null) throw new Error("InspectionService has nothing to measure before the runtime has built its tree.");
|
|
3480
|
+
return this.host.screenBox?.(node) ?? this.box(node);
|
|
3481
|
+
}
|
|
3482
|
+
/**
|
|
3241
3483
|
* Draws a node as though it declared `value` for `property`, until
|
|
3242
3484
|
* the preview is ended or the node leaves the tree.
|
|
3243
3485
|
*
|
|
@@ -3255,6 +3497,43 @@ var InspectionService = class {
|
|
|
3255
3497
|
endPreview(node, property) {
|
|
3256
3498
|
this.host?.endPreview?.(node, property);
|
|
3257
3499
|
}
|
|
3500
|
+
/**
|
|
3501
|
+
* Draws a component as though its parent passed `inputs`, over what
|
|
3502
|
+
* the parent did pass, until the preview is ended or the component
|
|
3503
|
+
* leaves the tree. The component is named by its anchor, as
|
|
3504
|
+
* `inspect(node).owners[].anchorId` gives it.
|
|
3505
|
+
*
|
|
3506
|
+
* What a design editor shows when a component on a played screen is
|
|
3507
|
+
* jumped to one of its states (Filled, Error, Disabled): the same
|
|
3508
|
+
* instance, still live, given that state's inputs, and the inputs it
|
|
3509
|
+
* was written with back when the preview ends. A later preview of the
|
|
3510
|
+
* same component replaces this one. The names and values are props',
|
|
3511
|
+
* as JSX would write them. An anchor no component holds is ignored.
|
|
3512
|
+
*/
|
|
3513
|
+
previewInputs(anchorId, inputs) {
|
|
3514
|
+
this.host?.previewInputs?.(anchorId, inputs);
|
|
3515
|
+
}
|
|
3516
|
+
/** Ends a preview of a component's inputs: its parent's are its own again. */
|
|
3517
|
+
endInputPreview(anchorId) {
|
|
3518
|
+
this.host?.previewInputs?.(anchorId, null);
|
|
3519
|
+
}
|
|
3520
|
+
/**
|
|
3521
|
+
* Presses a node, or replaces the text of the field it is, as a
|
|
3522
|
+
* person would: a click at the middle of where it is drawn, which
|
|
3523
|
+
* its handlers and every handler above it get, or the edit a
|
|
3524
|
+
* keyboard makes once it has focus. The path the accessibility
|
|
3525
|
+
* mirror's actions take, for any node in the tree, record or not.
|
|
3526
|
+
*
|
|
3527
|
+
* What a design editor's agent does to play a prototype in its live
|
|
3528
|
+
* frames: a row pressed, an email typed, by a node it found with
|
|
3529
|
+
* `nodesAt` or by walking the tree. False when it did not take: a
|
|
3530
|
+
* node no longer in the tree, or a field that could not have focus
|
|
3531
|
+
* because an open dialog's trap holds it, or takes no text.
|
|
3532
|
+
*/
|
|
3533
|
+
act(node, action, value) {
|
|
3534
|
+
if (this.host?.act === void 0) throw new Error("InspectionService cannot act before the runtime has built its tree.");
|
|
3535
|
+
return this.host.act(node, action, value);
|
|
3536
|
+
}
|
|
3258
3537
|
};
|
|
3259
3538
|
//#endregion
|
|
3260
3539
|
//#region src/app/InputLatency.ts
|
|
@@ -3421,7 +3700,10 @@ var SmoothScroller = class {
|
|
|
3421
3700
|
const { min, max } = this.boundsOf(node, axis);
|
|
3422
3701
|
const target = clamp((pending ?? from) + delta, min, max);
|
|
3423
3702
|
if (pending !== void 0 && target === pending) return;
|
|
3424
|
-
this.animations.spring(cell, target, {
|
|
3703
|
+
this.animations.spring(cell, target, {
|
|
3704
|
+
spring: SCROLL_SPRING,
|
|
3705
|
+
clock: clockOf(node)
|
|
3706
|
+
});
|
|
3425
3707
|
}
|
|
3426
3708
|
/**
|
|
3427
3709
|
* Shifts a running scroll by a coordinate correction.
|
|
@@ -3446,7 +3728,10 @@ var SmoothScroller = class {
|
|
|
3446
3728
|
const pending = this.destinationOf(state.cell);
|
|
3447
3729
|
if (pending === void 0) return;
|
|
3448
3730
|
state.lastWritten = state.cell.value;
|
|
3449
|
-
this.animations.spring(state.cell, pending + delta, {
|
|
3731
|
+
this.animations.spring(state.cell, pending + delta, {
|
|
3732
|
+
spring: SCROLL_SPRING,
|
|
3733
|
+
clock: clockOf(node)
|
|
3734
|
+
});
|
|
3450
3735
|
}
|
|
3451
3736
|
/** Whether this container is being animated right now. */
|
|
3452
3737
|
isScrolling(node) {
|
|
@@ -3654,6 +3939,8 @@ var GessoRuntime = class {
|
|
|
3654
3939
|
lastEarlyFrame = -Infinity;
|
|
3655
3940
|
/** When the last discrete input was routed; see `answerInput`. */
|
|
3656
3941
|
lastDiscreteInput = -Infinity;
|
|
3942
|
+
/** The wheel's steps since the last frame, added together; see `wheel`. */
|
|
3943
|
+
heldWheel = null;
|
|
3657
3944
|
/** The display time the frame being drawn is for; see `FrameMetrics.time`. */
|
|
3658
3945
|
frameTime = null;
|
|
3659
3946
|
/** When the frame's first phase began, before its changes were collected; see `FrameMetrics.durationMs`. */
|
|
@@ -3763,6 +4050,11 @@ var GessoRuntime = class {
|
|
|
3763
4050
|
* them. Only populated while a listener is attached.
|
|
3764
4051
|
*/
|
|
3765
4052
|
semanticsBoxes = /* @__PURE__ */ new Map();
|
|
4053
|
+
/** When a frame last moved something, and when the boxes were last swept; see `updateSemantics`. */
|
|
4054
|
+
semanticsMovedAt = -Infinity;
|
|
4055
|
+
semanticsSweptAt = -Infinity;
|
|
4056
|
+
/** The sweep owed once a run of moving frames stops. */
|
|
4057
|
+
semanticsSweepTimer = null;
|
|
3766
4058
|
/** What each semantics walk leaves for the next; see `SemanticsMemory`. */
|
|
3767
4059
|
semanticsMemory = semanticsMemory();
|
|
3768
4060
|
lastFocusedId = null;
|
|
@@ -3902,7 +4194,8 @@ var GessoRuntime = class {
|
|
|
3902
4194
|
width: this.width,
|
|
3903
4195
|
height: this.height
|
|
3904
4196
|
}),
|
|
3905
|
-
onLayout: (node, listener) => this.layoutNotifier.add(node, listener)
|
|
4197
|
+
onLayout: (node, listener) => this.layoutNotifier.add(node, listener),
|
|
4198
|
+
onFlowLayout: (node, listener) => this.layoutNotifier.addFlow(node, listener)
|
|
3906
4199
|
},
|
|
3907
4200
|
animations: this.animations,
|
|
3908
4201
|
sharedElements: this.sharedElements
|
|
@@ -3926,6 +4219,9 @@ var GessoRuntime = class {
|
|
|
3926
4219
|
if (options.fonts !== void 0) this.services.get(FontService).declare(options.fonts);
|
|
3927
4220
|
if (!this.services.has(AnimationService)) this.services.register(AnimationService);
|
|
3928
4221
|
this.services.get(AnimationService).setDriver(this.animations);
|
|
4222
|
+
this.services.get(AnimationService).setMotionListener((motion) => {
|
|
4223
|
+
if (this.graph.updateNodePropertyNow(this.graph.root, "provide", [provided(UiEnvironmentKeys.motion, motion)], DirtyFlags.Environment)) this.requestRepaint();
|
|
4224
|
+
});
|
|
3929
4225
|
this.services.get(AudioService).setAnimations(this.services.get(AnimationService));
|
|
3930
4226
|
this.smoothScroller = new SmoothScroller(this.graph, this.services.get(AnimationService), (node, axis) => {
|
|
3931
4227
|
const record = this.engine.recordFor(node);
|
|
@@ -4006,8 +4302,22 @@ var GessoRuntime = class {
|
|
|
4006
4302
|
height: box.height
|
|
4007
4303
|
};
|
|
4008
4304
|
},
|
|
4305
|
+
screenBox: (node) => {
|
|
4306
|
+
const box = this.engine.screenBox(node);
|
|
4307
|
+
return {
|
|
4308
|
+
x: box.x,
|
|
4309
|
+
y: box.y,
|
|
4310
|
+
width: box.width,
|
|
4311
|
+
height: box.height
|
|
4312
|
+
};
|
|
4313
|
+
},
|
|
4009
4314
|
preview: (node, property, value) => this.previewProperty(node, property, value),
|
|
4010
|
-
endPreview: (node, property) => this.endPreviews(node, property)
|
|
4315
|
+
endPreview: (node, property) => this.endPreviews(node, property),
|
|
4316
|
+
previewInputs: (anchorId, inputs) => this.resolver.hostFor(anchorId)?.previewInputs(inputs),
|
|
4317
|
+
act: (node, action, value) => this.graph.getNode(node.id) === node && this.actOn(node, value === void 0 ? { action } : {
|
|
4318
|
+
action,
|
|
4319
|
+
value
|
|
4320
|
+
})
|
|
4011
4321
|
});
|
|
4012
4322
|
this.services.get(FocusService).setManager(this.input.focus);
|
|
4013
4323
|
this.services.get(EditingService).setController(this.input.editing);
|
|
@@ -4236,6 +4546,51 @@ var GessoRuntime = class {
|
|
|
4236
4546
|
this.lastDiscreteInput = now();
|
|
4237
4547
|
this.drawEarly();
|
|
4238
4548
|
}
|
|
4549
|
+
/**
|
|
4550
|
+
* A step of the wheel, or of a trackpad's two fingers or its pinch,
|
|
4551
|
+
* held until the frame and added to the others that arrive before it.
|
|
4552
|
+
*
|
|
4553
|
+
* A trackpad sends a step on every refresh, and a handler that takes
|
|
4554
|
+
* longer than a refresh over one (a design tool's board zoomed over
|
|
4555
|
+
* hundreds of frames, each of whose names it scales) fell behind for
|
|
4556
|
+
* good: each step waited for the one before it, the frame waited for
|
|
4557
|
+
* them all, and a pinch drew nothing until the fingers stopped, then
|
|
4558
|
+
* jumped. Held, the steps that queue while the handler runs are one
|
|
4559
|
+
* step at the next frame, so the handler runs once a frame however
|
|
4560
|
+
* many arrive, and what it draws follows the fingers a frame at a
|
|
4561
|
+
* time. A step with other modifiers or in another unit is not added
|
|
4562
|
+
* to the held one but follows it, and any other input lets the held
|
|
4563
|
+
* step go first (`releaseWheel`), so nothing is answered out of order.
|
|
4564
|
+
*/
|
|
4565
|
+
wheel(...step) {
|
|
4566
|
+
const held = this.heldWheel;
|
|
4567
|
+
if (held !== null && !sameWheel(held, step)) this.releaseWheel();
|
|
4568
|
+
if (this.heldWheel === null) {
|
|
4569
|
+
this.heldWheel = step;
|
|
4570
|
+
this.scheduler.wake();
|
|
4571
|
+
return;
|
|
4572
|
+
}
|
|
4573
|
+
const [, , deltaX, deltaY, modifiers, deltaMode, wheelDeltaY] = this.heldWheel;
|
|
4574
|
+
const [x, y, moreX, moreY, , , moreWheelY, at] = step;
|
|
4575
|
+
const legacy = wheelDeltaY === void 0 || moreWheelY === void 0 ? moreWheelY : wheelDeltaY + moreWheelY;
|
|
4576
|
+
this.heldWheel = [
|
|
4577
|
+
x,
|
|
4578
|
+
y,
|
|
4579
|
+
deltaX + moreX,
|
|
4580
|
+
deltaY + moreY,
|
|
4581
|
+
modifiers,
|
|
4582
|
+
deltaMode,
|
|
4583
|
+
legacy,
|
|
4584
|
+
at
|
|
4585
|
+
];
|
|
4586
|
+
}
|
|
4587
|
+
/** Hands the held wheel step to the input now, before what follows it; see `wheel`. */
|
|
4588
|
+
releaseWheel() {
|
|
4589
|
+
const held = this.heldWheel;
|
|
4590
|
+
if (held === null) return;
|
|
4591
|
+
this.heldWheel = null;
|
|
4592
|
+
this.input.wheel.wheel(...held);
|
|
4593
|
+
}
|
|
4239
4594
|
/** Draws the pending frame in a task of its own, if the guards in `answerInput` allow. */
|
|
4240
4595
|
drawEarly() {
|
|
4241
4596
|
if (!this.started || !this.visible || this.earlyFrame !== null || !this.scheduler.framePending) return;
|
|
@@ -4436,7 +4791,8 @@ var GessoRuntime = class {
|
|
|
4436
4791
|
source: PREVIEW,
|
|
4437
4792
|
name: "preview",
|
|
4438
4793
|
order: Number.MAX_SAFE_INTEGER,
|
|
4439
|
-
value
|
|
4794
|
+
value,
|
|
4795
|
+
snap: true
|
|
4440
4796
|
}, propertyEffects(property));
|
|
4441
4797
|
}
|
|
4442
4798
|
endPreviews(node, property) {
|
|
@@ -4780,6 +5136,7 @@ var GessoRuntime = class {
|
|
|
4780
5136
|
listens: this.dispatcher.listenerTypes(node),
|
|
4781
5137
|
beneath: this.beneathAtPointer(node),
|
|
4782
5138
|
semantics: this.semanticsOf(node),
|
|
5139
|
+
...this.interactionOf(node),
|
|
4783
5140
|
explanation: formatExplanation(this.engine.explain(node)),
|
|
4784
5141
|
...this.sourceReport(node)
|
|
4785
5142
|
};
|
|
@@ -4827,6 +5184,27 @@ var GessoRuntime = class {
|
|
|
4827
5184
|
const source = this.builder.sourceOf(node);
|
|
4828
5185
|
return source === void 0 ? {} : { source };
|
|
4829
5186
|
}
|
|
5187
|
+
/** The props the node's `interactive` modifiers write while hovered and pressed, later ones over earlier. */
|
|
5188
|
+
interactionOf(node) {
|
|
5189
|
+
let hovered;
|
|
5190
|
+
let pressed;
|
|
5191
|
+
for (const modifier of this.builder.modifiersFor(node)?.modifiers ?? []) {
|
|
5192
|
+
if (modifier.kind.name !== "interactive") continue;
|
|
5193
|
+
const options = modifier.args;
|
|
5194
|
+
if (options.hovered !== void 0) hovered = {
|
|
5195
|
+
...hovered,
|
|
5196
|
+
...options.hovered
|
|
5197
|
+
};
|
|
5198
|
+
if (options.pressed !== void 0) pressed = {
|
|
5199
|
+
...pressed,
|
|
5200
|
+
...options.pressed
|
|
5201
|
+
};
|
|
5202
|
+
}
|
|
5203
|
+
return hovered === void 0 && pressed === void 0 ? {} : { interaction: {
|
|
5204
|
+
...hovered === void 0 ? {} : { hovered },
|
|
5205
|
+
...pressed === void 0 ? {} : { pressed }
|
|
5206
|
+
} };
|
|
5207
|
+
}
|
|
4830
5208
|
ownersOf(node) {
|
|
4831
5209
|
const owners = [];
|
|
4832
5210
|
for (let current = node; current !== null; current = current.parent) {
|
|
@@ -4838,11 +5216,14 @@ var GessoRuntime = class {
|
|
|
4838
5216
|
const value = ownerInput(supplied);
|
|
4839
5217
|
if (value !== void 0) inputs[name] = value;
|
|
4840
5218
|
}
|
|
5219
|
+
const live = {};
|
|
5220
|
+
for (const [name, value] of host.liveInputs()) if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") live[name] = value;
|
|
4841
5221
|
owners.push({
|
|
4842
5222
|
name: getComponentMetadata(host.component).tag,
|
|
4843
5223
|
anchorId: current.id,
|
|
4844
5224
|
...usedAt === void 0 ? {} : { usedAt },
|
|
4845
|
-
...Object.keys(inputs).length === 0 ? {} : { inputs }
|
|
5225
|
+
...Object.keys(inputs).length === 0 ? {} : { inputs },
|
|
5226
|
+
...Object.keys(live).length === 0 ? {} : { live }
|
|
4846
5227
|
});
|
|
4847
5228
|
}
|
|
4848
5229
|
}
|
|
@@ -5051,6 +5432,10 @@ var GessoRuntime = class {
|
|
|
5051
5432
|
clearTimeout(this.rendererTimer);
|
|
5052
5433
|
this.rendererTimer = null;
|
|
5053
5434
|
}
|
|
5435
|
+
if (this.semanticsSweepTimer !== null) {
|
|
5436
|
+
clearTimeout(this.semanticsSweepTimer);
|
|
5437
|
+
this.semanticsSweepTimer = null;
|
|
5438
|
+
}
|
|
5054
5439
|
this.detachViewportInsetEnvironment?.();
|
|
5055
5440
|
this.detachViewportInsetEnvironment = null;
|
|
5056
5441
|
this.viewportInsetWrite?.();
|
|
@@ -5241,7 +5626,7 @@ var GessoRuntime = class {
|
|
|
5241
5626
|
op: "add",
|
|
5242
5627
|
node
|
|
5243
5628
|
})),
|
|
5244
|
-
boxes: this.
|
|
5629
|
+
boxes: this.sweepSemanticsBoxes(),
|
|
5245
5630
|
focused: this.focusManager.focusedNode?.id ?? null
|
|
5246
5631
|
});
|
|
5247
5632
|
}
|
|
@@ -5343,18 +5728,21 @@ var GessoRuntime = class {
|
|
|
5343
5728
|
if (this.applyTextRunAction(action)) return;
|
|
5344
5729
|
const node = this.graph.getNode(action.id);
|
|
5345
5730
|
if (node === void 0 || !this.semantics.has(action.id)) return;
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5731
|
+
this.actOn(node, action);
|
|
5732
|
+
}
|
|
5733
|
+
/**
|
|
5734
|
+
* A semantics action on a node: the mirror's, or `InspectionService.act`'s
|
|
5735
|
+
* on a node with no record of its own. False when it did not take: focus
|
|
5736
|
+
* refused (an open dialog's trap holds it elsewhere), or a field that
|
|
5737
|
+
* takes no text.
|
|
5738
|
+
*/
|
|
5739
|
+
actOn(node, action) {
|
|
5740
|
+
if (action.action === "focus") return this.focusManager.focus(node);
|
|
5741
|
+
if (action.action === "setValue") return this.focusManager.focus(node) && this.input.editing.replaceText(action.value ?? "");
|
|
5355
5742
|
this.focusManager.focus(node);
|
|
5356
5743
|
const box = this.engine.screenBox(node);
|
|
5357
5744
|
this.dispatcher.dispatch(new UiPointerEvent(UiEventType.Click, box.x + box.width / 2, box.y + box.height / 2, 1), node);
|
|
5745
|
+
return true;
|
|
5358
5746
|
}
|
|
5359
5747
|
/**
|
|
5360
5748
|
* An action on an inline link, which is a run and not a node.
|
|
@@ -5398,7 +5786,7 @@ var GessoRuntime = class {
|
|
|
5398
5786
|
else patches = this.rescopeSemantics(frame) ?? this.restructureSemantics(frame) ?? this.rebuildSemantics();
|
|
5399
5787
|
}
|
|
5400
5788
|
if (listener === null) return;
|
|
5401
|
-
const boxes =
|
|
5789
|
+
const boxes = patches.length > 0 ? this.sweepSemanticsBoxes() : moved ? this.movedSemanticsBoxes() : EMPTY_BOXES;
|
|
5402
5790
|
const focused = this.focusManager.focusedNode?.id ?? null;
|
|
5403
5791
|
const focusMoved = focused !== this.lastFocusedId;
|
|
5404
5792
|
this.lastFocusedId = focused;
|
|
@@ -5695,6 +6083,70 @@ var GessoRuntime = class {
|
|
|
5695
6083
|
return ids;
|
|
5696
6084
|
}
|
|
5697
6085
|
/**
|
|
6086
|
+
* The boxes for a frame that moved something but changed no meaning:
|
|
6087
|
+
* all of them when it is the first move after a rest, and otherwise
|
|
6088
|
+
* only the focused node's, with the sweep owed for when the moving
|
|
6089
|
+
* stops (`SEMANTICS_SETTLE_MS`), or for when the boxes have been stale
|
|
6090
|
+
* too long (`SEMANTICS_MOST_STALE_MS`).
|
|
6091
|
+
*
|
|
6092
|
+
* A pan or a zoom moves every box under the camera on every frame. In
|
|
6093
|
+
* Atelier, a board of three dozen screens panned at 15%, the sweep was
|
|
6094
|
+
* 3 to 4 ms of a 4 to 6 ms frame, with frames to 13 ms, past a 120 Hz
|
|
6095
|
+
* display's 8.3; and the main thread wrote four styles on each of 545
|
|
6096
|
+
* elements per frame, which in Chromium was 45% of that thread while
|
|
6097
|
+
* it also had the wheel events to deliver. Nobody reads the rectangle
|
|
6098
|
+
* of an element that is moving under a pan: a screen reader's cursor
|
|
6099
|
+
* is on what it rests on. The focused node is the exception, since its
|
|
6100
|
+
* focus ring is drawn from its element, so it moves every frame.
|
|
6101
|
+
*/
|
|
6102
|
+
movedSemanticsBoxes() {
|
|
6103
|
+
const at = now();
|
|
6104
|
+
const resting = at - this.semanticsMovedAt > SEMANTICS_SETTLE_MS;
|
|
6105
|
+
this.semanticsMovedAt = at;
|
|
6106
|
+
if (resting || at - this.semanticsSweptAt > SEMANTICS_MOST_STALE_MS) return this.sweepSemanticsBoxes();
|
|
6107
|
+
if (this.semanticsSweepTimer === null) this.scheduleSemanticsSweep(SEMANTICS_SETTLE_MS);
|
|
6108
|
+
return this.focusedSemanticsBox();
|
|
6109
|
+
}
|
|
6110
|
+
/** Sweeps the boxes once the moving has stopped, and sends what changed. */
|
|
6111
|
+
scheduleSemanticsSweep(wait) {
|
|
6112
|
+
this.semanticsSweepTimer = setTimeout(() => {
|
|
6113
|
+
this.semanticsSweepTimer = null;
|
|
6114
|
+
const listener = this.semanticsListener;
|
|
6115
|
+
if (listener === null) return;
|
|
6116
|
+
const left = this.semanticsMovedAt + SEMANTICS_SETTLE_MS - now();
|
|
6117
|
+
if (left > 0) {
|
|
6118
|
+
this.scheduleSemanticsSweep(left);
|
|
6119
|
+
return;
|
|
6120
|
+
}
|
|
6121
|
+
const boxes = this.sweepSemanticsBoxes();
|
|
6122
|
+
if (boxes.length > 0) listener({
|
|
6123
|
+
patches: EMPTY_PATCHES,
|
|
6124
|
+
boxes
|
|
6125
|
+
});
|
|
6126
|
+
}, Math.max(1, wait));
|
|
6127
|
+
}
|
|
6128
|
+
sweepSemanticsBoxes() {
|
|
6129
|
+
this.semanticsSweptAt = now();
|
|
6130
|
+
if (this.semanticsSweepTimer !== null) {
|
|
6131
|
+
clearTimeout(this.semanticsSweepTimer);
|
|
6132
|
+
this.semanticsSweepTimer = null;
|
|
6133
|
+
}
|
|
6134
|
+
return this.collectSemanticsBoxes();
|
|
6135
|
+
}
|
|
6136
|
+
/** The focused node's box when it differs from the one last sent, as `collectSemanticsBoxes` would give it. */
|
|
6137
|
+
focusedSemanticsBox() {
|
|
6138
|
+
const focused = this.focusManager.focusedNode;
|
|
6139
|
+
if (focused === null || focused === void 0 || !this.semantics.has(focused.id) || this.engine.recordFor(focused) === void 0) return EMPTY_BOXES;
|
|
6140
|
+
const box = this.engine.screenBox(focused);
|
|
6141
|
+
const last = this.semanticsBoxes.get(focused.id);
|
|
6142
|
+
if (last !== void 0 && boxesEqual(last, box)) return EMPTY_BOXES;
|
|
6143
|
+
this.semanticsBoxes.set(focused.id, box);
|
|
6144
|
+
return [{
|
|
6145
|
+
id: focused.id,
|
|
6146
|
+
box
|
|
6147
|
+
}];
|
|
6148
|
+
}
|
|
6149
|
+
/**
|
|
5698
6150
|
* The mirrored nodes whose box differs from the one last sent, and
|
|
5699
6151
|
* which are on screen.
|
|
5700
6152
|
*
|
|
@@ -5921,6 +6373,7 @@ var GessoRuntime = class {
|
|
|
5921
6373
|
this.frameBegan = now();
|
|
5922
6374
|
this.phaseTimings = emptyPhaseTimings();
|
|
5923
6375
|
this.frameTime = time;
|
|
6376
|
+
this.releaseWheel();
|
|
5924
6377
|
this.phaseTimings.ticks = this.timePhase(() => this.visible && this.animations.isRunning, () => this.animations.advance(time));
|
|
5925
6378
|
this.scheduleAnimationTick(time);
|
|
5926
6379
|
this.phaseTimings.patches = this.timePhase(() => this.replicas.some((replica) => replica.hasPendingPatches), () => {
|
|
@@ -6092,10 +6545,18 @@ var GessoRuntime = class {
|
|
|
6092
6545
|
scrollY: record?.scrollY ?? 0
|
|
6093
6546
|
};
|
|
6094
6547
|
};
|
|
6548
|
+
const readFlow = (node) => {
|
|
6549
|
+
const record = this.engine.recordFor(node);
|
|
6550
|
+
return {
|
|
6551
|
+
box: record === void 0 ? NO_BOX : this.engine.worldBox(node),
|
|
6552
|
+
scrollX: record?.scrollX ?? 0,
|
|
6553
|
+
scrollY: record?.scrollY ?? 0
|
|
6554
|
+
};
|
|
6555
|
+
};
|
|
6095
6556
|
this.deferredReveals = [];
|
|
6096
6557
|
try {
|
|
6097
6558
|
for (;;) {
|
|
6098
|
-
if (!this.layoutNotifier.isEmpty()) this.layoutNotifier.notify(read);
|
|
6559
|
+
if (!this.layoutNotifier.isEmpty()) this.layoutNotifier.notify(read, readFlow);
|
|
6099
6560
|
if (!this.layoutPending()) {
|
|
6100
6561
|
this.finishSettling(settled.passes > 0);
|
|
6101
6562
|
if (!this.layoutPending()) break;
|
|
@@ -6522,6 +6983,14 @@ function semanticIndices(list) {
|
|
|
6522
6983
|
* one frame still hands the mirror the rows it is about to show.
|
|
6523
6984
|
*/
|
|
6524
6985
|
const SEMANTICS_VIEWPORT_MARGIN = 400;
|
|
6986
|
+
/**
|
|
6987
|
+
* How long a run of moving frames has to pause before the mirrored
|
|
6988
|
+
* boxes are swept again; see `movedSemanticsBoxes`. Longer than the gap
|
|
6989
|
+
* between a trackpad's wheel events, so a pan is one run.
|
|
6990
|
+
*/
|
|
6991
|
+
const SEMANTICS_SETTLE_MS = 120;
|
|
6992
|
+
/** The longest the mirrored boxes go unswept while something keeps moving: an endless animation still gets them. */
|
|
6993
|
+
const SEMANTICS_MOST_STALE_MS = 1e3;
|
|
6525
6994
|
const EMPTY_PATCHES = [];
|
|
6526
6995
|
const EMPTY_BOXES = [];
|
|
6527
6996
|
function boxesEqual(a, b) {
|
|
@@ -6536,6 +7005,19 @@ function boxesEqual(a, b) {
|
|
|
6536
7005
|
function frameChangedTree(frame) {
|
|
6537
7006
|
return frameNeedsSemantics(frame);
|
|
6538
7007
|
}
|
|
7008
|
+
/** The box a node with no record reports. */
|
|
7009
|
+
const NO_BOX = {
|
|
7010
|
+
x: 0,
|
|
7011
|
+
y: 0,
|
|
7012
|
+
width: 0,
|
|
7013
|
+
height: 0
|
|
7014
|
+
};
|
|
7015
|
+
/** Whether two wheel steps can be added together: the same keys held, in the same unit. */
|
|
7016
|
+
function sameWheel(a, b) {
|
|
7017
|
+
const [, , , , keysA, unitA] = a;
|
|
7018
|
+
const [, , , , keysB, unitB] = b;
|
|
7019
|
+
return unitA === unitB && (keysA?.ctrl ?? false) === (keysB?.ctrl ?? false) && (keysA?.shift ?? false) === (keysB?.shift ?? false) && (keysA?.alt ?? false) === (keysB?.alt ?? false) && (keysA?.meta ?? false) === (keysB?.meta ?? false);
|
|
7020
|
+
}
|
|
6539
7021
|
function frameNeedsSemantics(frame) {
|
|
6540
7022
|
return frame.anyFlags(DirtyFlags.Semantics | DirtyFlags.Children);
|
|
6541
7023
|
}
|
|
@@ -6752,25 +7234,58 @@ async function loadModule(module, load) {
|
|
|
6752
7234
|
}
|
|
6753
7235
|
/** The export to draw, or why none can be chosen. */
|
|
6754
7236
|
function chooseExport(exports, wanted, module) {
|
|
6755
|
-
const drawable = (value) => typeof value === "function" || isUiElement(value);
|
|
7237
|
+
const drawable = (value) => typeof value === "function" || isUiElement(value) || examplesOf(value) !== null;
|
|
6756
7238
|
if (wanted !== void 0) {
|
|
6757
|
-
|
|
7239
|
+
const [head, ...rest] = wanted.split(".");
|
|
7240
|
+
let value = exports[head];
|
|
7241
|
+
for (const key of rest) value = typeof value === "object" && value !== null ? value[key] : void 0;
|
|
7242
|
+
if (!drawable(value)) return `${module} has no component or element exported as ${wanted}. It exports ${list(exports, drawable)}.`;
|
|
7243
|
+
const examples = examplesOf(value);
|
|
6758
7244
|
return {
|
|
6759
7245
|
name: wanted,
|
|
6760
|
-
value:
|
|
7246
|
+
value: examples === null ? value : examplesSheet(examples)
|
|
6761
7247
|
};
|
|
6762
7248
|
}
|
|
7249
|
+
const asDrawn = (value) => {
|
|
7250
|
+
const examples = examplesOf(value);
|
|
7251
|
+
return examples === null ? value : examplesSheet(examples);
|
|
7252
|
+
};
|
|
6763
7253
|
if (drawable(exports.default)) return {
|
|
6764
7254
|
name: "default",
|
|
6765
|
-
value: exports.default
|
|
7255
|
+
value: asDrawn(exports.default)
|
|
6766
7256
|
};
|
|
6767
7257
|
const candidates = Object.keys(exports).filter((name) => /^[A-Z]/.test(name) && drawable(exports[name]));
|
|
6768
7258
|
if (candidates.length === 1) return {
|
|
6769
7259
|
name: candidates[0],
|
|
6770
|
-
value: exports[candidates[0]]
|
|
7260
|
+
value: asDrawn(exports[candidates[0]])
|
|
6771
7261
|
};
|
|
6772
7262
|
return candidates.length === 0 ? `${module} exports nothing to draw: no component, and no element.` : `${module} exports ${candidates.join(", ")}; say which with export.`;
|
|
6773
7263
|
}
|
|
7264
|
+
/**
|
|
7265
|
+
* An object of examples, as a component's states are kept for a design
|
|
7266
|
+
* tool (`export const Button = { Primary: <Button … />, … }`): each
|
|
7267
|
+
* element by its name, or null for anything else.
|
|
7268
|
+
*/
|
|
7269
|
+
function examplesOf(value) {
|
|
7270
|
+
if (typeof value !== "object" || value === null || isUiElement(value) || Array.isArray(value)) return null;
|
|
7271
|
+
const entries = Object.entries(value).filter((entry) => isUiElement(entry[1]));
|
|
7272
|
+
return entries.length === 0 ? null : entries;
|
|
7273
|
+
}
|
|
7274
|
+
/** Every example, each under its name, down the frame. */
|
|
7275
|
+
function examplesSheet(examples) {
|
|
7276
|
+
return Column({
|
|
7277
|
+
gap: 24,
|
|
7278
|
+
padding: 16
|
|
7279
|
+
}, ...examples.map(([name, element]) => Column({
|
|
7280
|
+
key: name,
|
|
7281
|
+
gap: 8
|
|
7282
|
+
}, Text({
|
|
7283
|
+
text: name,
|
|
7284
|
+
fontSize: 12,
|
|
7285
|
+
fontWeight: "600",
|
|
7286
|
+
color: "textMuted"
|
|
7287
|
+
}), element)));
|
|
7288
|
+
}
|
|
6774
7289
|
function list(exports, keep) {
|
|
6775
7290
|
const names = Object.keys(exports).filter((name) => keep(exports[name]));
|
|
6776
7291
|
return names.length === 0 ? "nothing that can be drawn" : names.join(", ");
|
|
@@ -6790,6 +7305,6 @@ function failure(text) {
|
|
|
6790
7305
|
};
|
|
6791
7306
|
}
|
|
6792
7307
|
//#endregion
|
|
6793
|
-
export {
|
|
7308
|
+
export { ComponentHostResolver as A, ChannelRegistry as B, ShellService as C, isDiscreteInputMessage as D, epochNow as E, FocusService as F, Input as G, Channel as H, boundFocus as I, BoundsCell as J, Output as K, OverlayScopeKey as L, StartingInputsKey as M, OverlayLayer as N, isInputMessage as O, __decorate as P, OverlayService as R, FrameService as S, epochFromEvent as T, Define as U, ChannelReplica as V, Inject as W, ServiceRegistry as X, bounds as Y, Component as Z, parsePattern as _, UI_FRAME_PHASES as a, AudioService as b, TextService as c, ScrollService as d, EditingService as f, formatUrl as g, buildPath as h, GessoRuntime as i, ComponentHost as j, modifiersFrom as k, FontService as l, RouterService as m, renderFrame as n, InspectionService as o, FindService as p, liveInput as q, isClaimedError as r, AnimationService as s, renderModule as t, MediaService as u, parseUrl as v, shellFilesUnsupported as w, treeText as x, RouteState as y, isClassComponent as z };
|
|
6794
7309
|
|
|
6795
|
-
//# sourceMappingURL=render-
|
|
7310
|
+
//# sourceMappingURL=render-W2XO444K.js.map
|