gesso-core 0.2.1 → 0.3.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.
@@ -161,6 +161,32 @@ let UiEventType = /* @__PURE__ */ function(UiEventType) {
161
161
  UiEventType["BeforeInput"] = "beforeinput";
162
162
  /** An editable's text changed. */
163
163
  UiEventType["Input"] = "input";
164
+ /**
165
+ * An editable's selection moved, whether or not its text changed.
166
+ *
167
+ * Separate from `Input` because the caret moves without the text on
168
+ * every arrow key, every click into the text and every
169
+ * select-all — and something has to be able to follow it. A formula
170
+ * editor that colours the bracket beside the caret has no other way
171
+ * to know it moved.
172
+ *
173
+ * Fires *after* `Input` when a change did both, so a listener that
174
+ * reads the text sees the new one. It is skipped entirely when the
175
+ * selection did not actually move, so holding a key that is already
176
+ * at the end of the text is silent.
177
+ */
178
+ UiEventType["SelectionChange"] = "selectionchange";
179
+ /**
180
+ * Text arrived from the system clipboard with nothing editable
181
+ * focused.
182
+ *
183
+ * A paste with a caret in it belongs to the text and never reaches
184
+ * here. This is the other case: a surface that owns a selection of
185
+ * its own — a grid with a rectangle of cells picked out — and is the
186
+ * only thing that knows what a block of text means to it.
187
+ * `preventDefault()` says it was taken.
188
+ */
189
+ UiEventType["Paste"] = "paste";
164
190
  UiEventType["Click"] = "click";
165
191
  UiEventType["LongPress"] = "longpress";
166
192
  UiEventType["DragStart"] = "dragstart";
@@ -406,6 +432,21 @@ var UiBeforeInputEvent = class extends UiInputEvent {
406
432
  * An editable node's text changed: the new value and the selection
407
433
  * after the change. Bubbles, so a form can watch all its fields.
408
434
  */
435
+ /**
436
+ * Text from the system clipboard, offered to whatever holds focus.
437
+ *
438
+ * Carries the text rather than a clipboard handle, because a render
439
+ * worker has neither a clipboard nor a window: the shell reads it and
440
+ * sends it across, which is the same path `ShellService.copyText`
441
+ * takes in the other direction.
442
+ */
443
+ var UiPasteEvent = class extends UiInputEvent {
444
+ text;
445
+ constructor(text) {
446
+ super("paste");
447
+ this.text = text;
448
+ }
449
+ };
409
450
  var UiTextChangeEvent = class extends UiInputEvent {
410
451
  value;
411
452
  selectionStart;
@@ -418,6 +459,36 @@ var UiTextChangeEvent = class extends UiInputEvent {
418
459
  }
419
460
  };
420
461
  /**
462
+ * An editable's selection moved.
463
+ *
464
+ * Carries the text as well as the offsets, because every use of this
465
+ * is "what is under the caret" and the answer needs both — and
466
+ * reading the text back off the model afterwards is a second source
467
+ * of truth that can disagree by a frame.
468
+ *
469
+ * `start` and `end` are ordered; `anchor` and `focus` are where the
470
+ * selection was begun and where the caret now is, which is the pair
471
+ * that says which way it was dragged.
472
+ */
473
+ var UiSelectionChangeEvent = class extends UiInputEvent {
474
+ value;
475
+ start;
476
+ end;
477
+ anchor;
478
+ focus;
479
+ constructor(value, start, end, anchor, focus) {
480
+ super("selectionchange");
481
+ this.value = value;
482
+ this.start = start;
483
+ this.end = end;
484
+ this.anchor = anchor;
485
+ this.focus = focus;
486
+ }
487
+ get collapsed() {
488
+ return this.start === this.end;
489
+ }
490
+ };
491
+ /**
421
492
  * Wheel scroll input event.
422
493
  *
423
494
  * `deltaX`/`deltaY` are the scroll deltas in canvas pixels produced
@@ -1615,6 +1686,27 @@ function textContentOf(node) {
1615
1686
  function resolvedSpansOf(node) {
1616
1687
  return spannedTextOf(node)?.spans ?? EMPTY_SPANS;
1617
1688
  }
1689
+ /**
1690
+ * The runs an *editable* node styles its own text with.
1691
+ *
1692
+ * An editable's paragraph is the user's, held by its model rather
1693
+ * than by a property — so unlike a `Text`, its runs do not supply the
1694
+ * text, they only describe it. `spans` on an editable is presentation
1695
+ * over a string somebody is typing.
1696
+ *
1697
+ * Which is why this checks. Every offset the editor works in — the
1698
+ * caret, the selection, the hit test that turns a click into a text
1699
+ * position — is an offset into the model's string. Runs describing a
1700
+ * *different* string would put the glyphs somewhere the caret does
1701
+ * not agree with, and the field would look subtly, unfixably wrong.
1702
+ * When they disagree the text is drawn unstyled, which is a frame of
1703
+ * plain colour rather than a frame of nonsense, and corrects itself
1704
+ * as soon as the application catches up.
1705
+ */
1706
+ function editableSpansOf(node, text) {
1707
+ const spanned = spannedTextOf(node);
1708
+ return spanned !== void 0 && spanned.text === text ? spanned.spans : EMPTY_SPANS;
1709
+ }
1618
1710
  /** The span covering an offset, or undefined where no run does. */
1619
1711
  function spanAtOffset(spans, offset) {
1620
1712
  for (const span of spans) if (offset >= span.start && offset < span.end) return span;
@@ -4223,6 +4315,12 @@ const UiProperties = {
4223
4315
  inherited: false,
4224
4316
  affects: 64
4225
4317
  }),
4318
+ tabStop: defineProperty({
4319
+ name: "tabStop",
4320
+ defaultValue: void 0,
4321
+ inherited: false,
4322
+ affects: 64
4323
+ }),
4226
4324
  disabled: defineProperty({
4227
4325
  name: "disabled",
4228
4326
  defaultValue: void 0,
@@ -4451,6 +4549,13 @@ const UiProperties = {
4451
4549
  inherited: false,
4452
4550
  affects: 64
4453
4551
  }),
4552
+ /** Set by LazySheet on the scroll container: its UiVirtualSheet. */
4553
+ virtualSheet: defineProperty({
4554
+ name: "virtualSheet",
4555
+ defaultValue: void 0,
4556
+ inherited: false,
4557
+ affects: 64
4558
+ }),
4454
4559
  theme: defineProperty({
4455
4560
  name: "theme",
4456
4561
  defaultValue: void 0,
@@ -5547,6 +5652,21 @@ var EditableTextModel = class {
5547
5652
  blinkOrigin = 0;
5548
5653
  /** The last `value` the host synchronised in; see `UiEditable`. */
5549
5654
  syncedValue = void 0;
5655
+ /**
5656
+ * The selection the last `selectionchange` reported.
5657
+ *
5658
+ * Kept on the model rather than worked out at each call site,
5659
+ * because the caret is moved from a dozen places — arrows, clicks,
5660
+ * select-all, an edit that replaces a range — and every one of them
5661
+ * would otherwise have to remember to compare. Storing what was
5662
+ * last said makes the comparison the notifier's job and the answer
5663
+ * the same wherever the move came from.
5664
+ *
5665
+ * `NaN` to begin with, so the first report always fires: a fresh
5666
+ * model at offset zero has genuinely not told anyone yet.
5667
+ */
5668
+ notifiedAnchor = NaN;
5669
+ notifiedFocus = NaN;
5550
5670
  constructor(text = "") {
5551
5671
  this.textValue = text;
5552
5672
  }
@@ -6310,6 +6430,26 @@ function isNodeFocusable(node) {
6310
6430
  return node.type === "button" || node.type === "editable-text";
6311
6431
  }
6312
6432
  /**
6433
+ * Whether Tab should stop here.
6434
+ *
6435
+ * Every tab stop is focusable and not every focusable node is a tab
6436
+ * stop, which is the distinction `tabindex="-1"` draws in the DOM and
6437
+ * the one this engine was missing. `focusable` answers "may this node
6438
+ * hold focus at all", and that is the wrong question for a container:
6439
+ * a dialog's body must be able to *take* the keyboard, so that opening
6440
+ * a dialog whose content is a sentence does not hand it to nothing, and
6441
+ * must not be a place Tab *lands*, or every dialog gains a stop that
6442
+ * announces nothing and does nothing.
6443
+ *
6444
+ * So `tabStop: false` means focusable, reachable by a press and by
6445
+ * `focus()`, and skipped by the cycle. It says nothing on a node that
6446
+ * is not focusable in the first place, because there is no order to be
6447
+ * out of.
6448
+ */
6449
+ function isNodeTabStop(node) {
6450
+ return isNodeFocusable(node) && node.properties.get("tabStop") !== false;
6451
+ }
6452
+ /**
6313
6453
  * Whether the user may select this node's text with the pointer.
6314
6454
  *
6315
6455
  * `selectable` inherits down the tree the way CSS `user-select` does:
@@ -6655,7 +6795,7 @@ function resolvePaintState(node, out) {
6655
6795
  }
6656
6796
  out.rtl = resolveProperty(node, UiProperties.textDirection) === "rtl";
6657
6797
  resolveFontInto(node, out);
6658
- out.spans = out.editor === void 0 ? resolvePaintSpans(node) : void 0;
6798
+ out.spans = resolvePaintSpans(node, out.editor === void 0 ? resolvedSpansOf(node) : editableSpansOf(node, out.text ?? ""));
6659
6799
  out.linkHover = out.spans === void 0 ? -1 : linkHoverOf(node);
6660
6800
  out.textDecoration = resolveProperty(node, UiProperties.textDecoration);
6661
6801
  out.textColor = resolveColor(node, UiProperties.color) ?? UiBasicColors.black;
@@ -6718,8 +6858,7 @@ function resetTextStyle(out) {
6718
6858
  * anything having to notice.
6719
6859
  */
6720
6860
  const paintSpans = /* @__PURE__ */ new WeakMap();
6721
- function resolvePaintSpans(node) {
6722
- const spans = resolvedSpansOf(node);
6861
+ function resolvePaintSpans(node, spans) {
6723
6862
  if (spans.length === 0) return;
6724
6863
  const theme = node.environment !== null ? node.environment.get(UiEnvironmentKeys.theme) : void 0;
6725
6864
  const cached = paintSpans.get(spans);
@@ -7422,6 +7561,122 @@ var EditableLayout = class {
7422
7561
  }
7423
7562
  };
7424
7563
  //#endregion
7564
+ //#region src/editing/EditingKeymap.ts
7565
+ /**
7566
+ * The platform the runtime is on, decided once. Available in a worker
7567
+ * too, since `navigator.userAgent` is; tests set it explicitly.
7568
+ */
7569
+ function detectEditingPlatform() {
7570
+ const agent = typeof navigator !== "undefined" ? navigator.userAgent : "";
7571
+ return /Mac|iPhone|iPad|iPod/.test(agent) ? "mac" : "other";
7572
+ }
7573
+ /**
7574
+ * Maps a key press to an editing command, following the host's
7575
+ * conventions:
7576
+ *
7577
+ * Mac — Option steps by word, Command jumps to line and document
7578
+ * ends, Command+Backspace deletes to the line start.
7579
+ * Others — Control steps by word, Home/End and Control+Home/End are
7580
+ * the line and document ends.
7581
+ *
7582
+ * Shift extends every move. Enter is a newline (the controller drops
7583
+ * it in a single-line field). A printable key becomes `insert` only
7584
+ * when `textFromKeys` is set: a host with an editing proxy delivers
7585
+ * text through `beforeinput` instead, where the IME and dead keys
7586
+ * have already been resolved.
7587
+ */
7588
+ function commandForKey(key, modifiers, platform, textFromKeys) {
7589
+ const mac = platform === "mac";
7590
+ const primary = mac ? modifiers.meta : modifiers.ctrl;
7591
+ const word = mac ? modifiers.alt : modifiers.ctrl;
7592
+ const extend = modifiers.shift;
7593
+ switch (key) {
7594
+ case "ArrowLeft":
7595
+ case "ArrowRight": {
7596
+ const direction = key === "ArrowLeft" ? -1 : 1;
7597
+ if (mac && modifiers.meta) return {
7598
+ kind: "move",
7599
+ unit: "line",
7600
+ direction,
7601
+ extend
7602
+ };
7603
+ return {
7604
+ kind: "move",
7605
+ unit: word ? "word" : "grapheme",
7606
+ direction,
7607
+ extend
7608
+ };
7609
+ }
7610
+ case "ArrowUp":
7611
+ case "ArrowDown": {
7612
+ const direction = key === "ArrowUp" ? -1 : 1;
7613
+ if (mac && modifiers.meta) return {
7614
+ kind: "move",
7615
+ unit: "document",
7616
+ direction,
7617
+ extend
7618
+ };
7619
+ return {
7620
+ kind: "move",
7621
+ unit: "vertical",
7622
+ direction,
7623
+ extend
7624
+ };
7625
+ }
7626
+ case "Home": return {
7627
+ kind: "move",
7628
+ unit: modifiers.ctrl ? "document" : "line",
7629
+ direction: -1,
7630
+ extend
7631
+ };
7632
+ case "End": return {
7633
+ kind: "move",
7634
+ unit: modifiers.ctrl ? "document" : "line",
7635
+ direction: 1,
7636
+ extend
7637
+ };
7638
+ case "Backspace":
7639
+ if (mac && modifiers.meta) return {
7640
+ kind: "delete",
7641
+ unit: "line",
7642
+ direction: -1
7643
+ };
7644
+ return {
7645
+ kind: "delete",
7646
+ unit: word ? "word" : "grapheme",
7647
+ direction: -1
7648
+ };
7649
+ case "Delete": return {
7650
+ kind: "delete",
7651
+ unit: word ? "word" : "grapheme",
7652
+ direction: 1
7653
+ };
7654
+ case "Enter": return primary ? null : { kind: "newline" };
7655
+ }
7656
+ if (primary && !modifiers.alt) {
7657
+ switch (key.toLowerCase()) {
7658
+ case "a": return { kind: "selectAll" };
7659
+ case "z": return modifiers.shift ? { kind: "redo" } : { kind: "undo" };
7660
+ case "y": return mac ? null : { kind: "redo" };
7661
+ }
7662
+ return null;
7663
+ }
7664
+ if (textFromKeys && isPrintable(key) && !modifiers.ctrl && !modifiers.meta) return {
7665
+ kind: "insert",
7666
+ text: key
7667
+ };
7668
+ return null;
7669
+ }
7670
+ /** A key value that is the character it types: one grapheme, not a name like 'Shift'. */
7671
+ function isPrintable(key) {
7672
+ if (key.length === 1) return true;
7673
+ if (key.length === 2) {
7674
+ const code = key.charCodeAt(0);
7675
+ return code >= 55296 && code <= 56319;
7676
+ }
7677
+ return false;
7678
+ }
7679
+ //#endregion
7425
7680
  //#region src/layout/LineBreaks.ts
7426
7681
  function segmentParagraph(paragraph, wrap) {
7427
7682
  const length = paragraph.length;
@@ -8410,6 +8665,31 @@ function scrollbarZoneAt(rec, x, y) {
8410
8665
  if (rec.contentWidth > rec.width && y >= rec.y + rec.height - 16) return "x";
8411
8666
  return null;
8412
8667
  }
8668
+ /**
8669
+ * The rectangle a press grabs a thumb by.
8670
+ *
8671
+ * The painted thumb is six pixels thick. Six pixels at the very edge
8672
+ * of a window is a hard thing to hit, and missing it is not a near
8673
+ * miss — the press lands on the track instead and pages a whole
8674
+ * screenful, which reads as the bar refusing to be dragged. So the
8675
+ * grab target is the thumb's length by the whole band that reveals it:
8676
+ * as easy to catch as it is to summon, which is what an overlay
8677
+ * scrollbar is expected to be. What is drawn is unchanged.
8678
+ */
8679
+ function scrollbarGrabBox(rec, bar) {
8680
+ if (bar.axis === "y") return {
8681
+ x: rec.mirrored ? rec.x : rec.x + rec.width - 16,
8682
+ y: bar.thumb.y,
8683
+ width: 16,
8684
+ height: bar.thumb.height
8685
+ };
8686
+ return {
8687
+ x: bar.thumb.x,
8688
+ y: rec.y + rec.height - 16,
8689
+ width: bar.thumb.width,
8690
+ height: 16
8691
+ };
8692
+ }
8413
8693
  function pointInBox(box, x, y) {
8414
8694
  return x >= box.x && x < box.x + box.width && y >= box.y && y < box.y + box.height;
8415
8695
  }
@@ -8731,11 +9011,21 @@ var UiHitTester = class {
8731
9011
  * only while the bar is showing (or when asked for the band alone).
8732
9012
  */
8733
9013
  hitScrollbar(node, rec, px, py) {
8734
- const axis = scrollbarZoneAt(rec, px, py);
8735
- if (axis === null) return false;
8736
- const bar = scrollbarThumb(rec, axis);
9014
+ const zone = scrollbarZoneAt(rec, px, py);
9015
+ if (zone === null) return false;
9016
+ let axis = zone;
9017
+ let bar = scrollbarThumb(rec, axis);
9018
+ let onThumb = bar !== null && pointInBox(scrollbarGrabBox(rec, bar), px, py);
9019
+ if (!onThumb) {
9020
+ const other = axis === "y" ? "x" : "y";
9021
+ const otherBar = scrollbarThumb(rec, other);
9022
+ if (otherBar !== null && pointInBox(scrollbarGrabBox(rec, otherBar), px, py)) {
9023
+ axis = other;
9024
+ bar = otherBar;
9025
+ onThumb = true;
9026
+ }
9027
+ }
8737
9028
  if (bar === null) return false;
8738
- const onThumb = pointInBox(bar.thumb, px, py);
8739
9029
  const visible = rec.scrollbarVisibleUntil > (typeof performance !== "undefined" ? performance.now() : Date.now());
8740
9030
  if (!onThumb && !visible && !this.zoneOnly) return false;
8741
9031
  this.recordHit(node, rec, px, py);
@@ -8992,10 +9282,24 @@ var UiFocusManager = class {
8992
9282
  * this again once the frame's tree is built, which is when a dialog
8993
9283
  * gets its caret. Idempotent: with focus already inside the scope,
8994
9284
  * or no scope at all, it does nothing.
9285
+ *
9286
+ * **The scope root is the fallback, and it is the whole reason
9287
+ * `tabStop` exists.** A scope with no tab stop inside it used to
9288
+ * blur, which hands the keyboard to nothing: a `Dialog` whose body
9289
+ * is a sentence and a button could not be dismissed with Escape,
9290
+ * because Escape had nowhere to be delivered. Falling back to the
9291
+ * root fixes that only if the root can hold focus without becoming a
9292
+ * stop of its own, or every dialog grows a tab stop that announces
9293
+ * nothing. `focusable: true, tabStop: false` on the body is exactly
9294
+ * that pair, and the fallback is what makes it worth setting.
9295
+ *
9296
+ * Blurring is still what happens when the root cannot hold focus
9297
+ * either, because a scope that has genuinely nothing to focus is
9298
+ * better admitted than faked.
8995
9299
  */
8996
9300
  settleScope() {
8997
9301
  if (this.scopes.length === 0 || this.focused !== null && this.withinScope(this.focused)) return;
8998
- if (!this.focusNext()) this.blur();
9302
+ if (!this.focusNext() && !this.focus(this.scopeRoot)) this.blur();
8999
9303
  }
9000
9304
  /**
9001
9305
  * Ends the innermost scope and hands focus back to whatever held it
@@ -9043,11 +9347,18 @@ var UiFocusManager = class {
9043
9347
  const next = focusables[(index + delta + focusables.length) % focusables.length];
9044
9348
  return this.focus(next, "keyboard");
9045
9349
  }
9046
- /** Focusable nodes in document order (parent before children). */
9350
+ /**
9351
+ * The tab stops in document order (parent before children).
9352
+ *
9353
+ * Tab stops and not every focusable node: a container with
9354
+ * `tabStop: false` can hold focus and is not a place the cycle
9355
+ * stops, which is what lets a dialog body be the keyboard's home of
9356
+ * last resort without becoming a stop of its own.
9357
+ */
9047
9358
  collectFocusable() {
9048
9359
  const result = [];
9049
9360
  const visit = (node) => {
9050
- if (isNodeFocusable(node)) result.push(node);
9361
+ if (isNodeTabStop(node)) result.push(node);
9051
9362
  for (let child = node.firstChild; child !== null; child = child.nextSibling) visit(child);
9052
9363
  };
9053
9364
  visit(this.scopeRoot);
@@ -9579,7 +9890,7 @@ const EVERYTHING_SCROLLABLE = Object.freeze({
9579
9890
  */
9580
9891
  const SCROLL_EPSILON = .5;
9581
9892
  /** Whether an offset can still move in the direction `delta` points. */
9582
- function hasRoom(offset, max, delta) {
9893
+ function hasScrollRoom(offset, max, delta) {
9583
9894
  return delta > 0 ? offset < max - SCROLL_EPSILON : offset > SCROLL_EPSILON;
9584
9895
  }
9585
9896
  /** Whether this node keeps overscroll rather than chaining it outwards. */
@@ -9718,13 +10029,10 @@ var UiWheelController = class {
9718
10029
  if (!isScrollContainer(node)) continue;
9719
10030
  const state = this.scrollSink.containerState(node);
9720
10031
  if (state === void 0) continue;
9721
- if (state.horizontal) {
9722
- left ||= hasRoom(state.scrollX, state.maxScrollX, -1);
9723
- right ||= hasRoom(state.scrollX, state.maxScrollX, 1);
9724
- } else {
9725
- up ||= hasRoom(state.scrollY, state.maxScrollY, -1);
9726
- down ||= hasRoom(state.scrollY, state.maxScrollY, 1);
9727
- }
10032
+ left ||= hasScrollRoom(state.scrollX, state.maxScrollX, -1);
10033
+ right ||= hasScrollRoom(state.scrollX, state.maxScrollX, 1);
10034
+ up ||= hasScrollRoom(state.scrollY, state.maxScrollY, -1);
10035
+ down ||= hasScrollRoom(state.scrollY, state.maxScrollY, 1);
9728
10036
  if (containsOverscroll(node)) return EVERYTHING_SCROLLABLE;
9729
10037
  }
9730
10038
  if (containsOverscroll(rootOf(target))) return EVERYTHING_SCROLLABLE;
@@ -9751,7 +10059,7 @@ var UiWheelController = class {
9751
10059
  for (const node of this.scrollSink.scrollContainers?.() ?? []) {
9752
10060
  const state = this.scrollSink.containerState(node);
9753
10061
  if (state === void 0) continue;
9754
- if (state.horizontal ? state.maxScrollX > SCROLL_EPSILON : state.maxScrollY > SCROLL_EPSILON) return true;
10062
+ if (state.maxScrollX > SCROLL_EPSILON || state.maxScrollY > SCROLL_EPSILON) return true;
9755
10063
  }
9756
10064
  return false;
9757
10065
  }
@@ -9769,11 +10077,10 @@ var UiWheelController = class {
9769
10077
  if (!isScrollContainer(node)) continue;
9770
10078
  const state = this.scrollSink.containerState(node);
9771
10079
  if (state === void 0) continue;
9772
- const delta = state.horizontal ? deltaX : deltaY;
9773
- const offset = state.horizontal ? state.scrollX : state.scrollY;
9774
- const max = state.horizontal ? state.maxScrollX : state.maxScrollY;
9775
- if (delta !== 0 && hasRoom(offset, max, delta)) {
9776
- this.applyDelta(node, state, deltaX, deltaY, deltaMode, behaviorFor(node, deltaMode, wheelDeltaY));
10080
+ const takesX = deltaX !== 0 && hasScrollRoom(state.scrollX, state.maxScrollX, deltaX);
10081
+ const takesY = deltaY !== 0 && hasScrollRoom(state.scrollY, state.maxScrollY, deltaY);
10082
+ if (takesX || takesY) {
10083
+ this.applyDelta(node, state, takesX ? deltaX : 0, takesY ? deltaY : 0, deltaMode, behaviorFor(node, deltaMode, wheelDeltaY));
9777
10084
  event.markConsumed();
9778
10085
  return;
9779
10086
  }
@@ -9785,8 +10092,7 @@ var UiWheelController = class {
9785
10092
  if (containsOverscroll(rootOf(target))) event.markConsumed();
9786
10093
  }
9787
10094
  applyDelta(container, state, deltaX, deltaY, deltaMode, behavior) {
9788
- if (state.horizontal) this.scrollSink.scrollBy(container, this.toPixels(deltaX, deltaMode, state.viewportWidth), 0, behavior);
9789
- else this.scrollSink.scrollBy(container, 0, this.toPixels(deltaY, deltaMode, state.viewportHeight), behavior);
10095
+ this.scrollSink.scrollBy(container, deltaX === 0 ? 0 : this.toPixels(deltaX, deltaMode, state.viewportWidth), deltaY === 0 ? 0 : this.toPixels(deltaY, deltaMode, state.viewportHeight), behavior);
9790
10096
  }
9791
10097
  /**
9792
10098
  * A wheel delta in whatever unit it arrived in, as pixels.
@@ -10349,9 +10655,8 @@ var UiTouchScroller = class {
10349
10655
  this.sample(event.x, event.y);
10350
10656
  const resolved = this.resolve(event.target, dx, dy);
10351
10657
  if (resolved === null) return;
10352
- const { node, state } = resolved;
10353
- if (state.horizontal) this.scrollSink.scrollBy(node, dx, 0);
10354
- else this.scrollSink.scrollBy(node, 0, dy);
10658
+ const { node, takesX, takesY } = resolved;
10659
+ this.scrollSink.scrollBy(node, takesX ? dx : 0, takesY ? dy : 0);
10355
10660
  this.scrollSink.revealScrollbars?.(node);
10356
10661
  }
10357
10662
  panEnd(event) {
@@ -10364,12 +10669,11 @@ var UiTouchScroller = class {
10364
10669
  if (velocity === null) return;
10365
10670
  const resolved = this.resolve(event.target, velocity.x, velocity.y);
10366
10671
  if (resolved === null) return;
10367
- const { node, state } = resolved;
10368
- const speed = state.horizontal ? velocity.x : velocity.y;
10369
- if (Math.abs(speed) < this.flingVelocity) return;
10370
- const distance = speed * this.momentum;
10371
- if (state.horizontal) this.scrollSink.scrollBy(node, distance, 0, "smooth");
10372
- else this.scrollSink.scrollBy(node, 0, distance, "smooth");
10672
+ const { node, takesX, takesY } = resolved;
10673
+ const flingX = takesX && Math.abs(velocity.x) >= this.flingVelocity ? velocity.x * this.momentum : 0;
10674
+ const flingY = takesY && Math.abs(velocity.y) >= this.flingVelocity ? velocity.y * this.momentum : 0;
10675
+ if (flingX === 0 && flingY === 0) return;
10676
+ this.scrollSink.scrollBy(node, flingX, flingY, "smooth");
10373
10677
  }
10374
10678
  sample(x, y) {
10375
10679
  const t = this.now();
@@ -10396,7 +10700,7 @@ var UiTouchScroller = class {
10396
10700
  };
10397
10701
  }
10398
10702
  /**
10399
- * The container that should take this movement.
10703
+ * The container that should take this movement, and which of its axes.
10400
10704
  *
10401
10705
  * The nearest scroll container that can still move in the direction
10402
10706
  * asked for, walking outward. A container already at its end is
@@ -10404,6 +10708,18 @@ var UiTouchScroller = class {
10404
10708
  * that has hit its bottom keep scrolling the page it sits in. When
10405
10709
  * nothing can move, the innermost container is returned so the
10406
10710
  * gesture still belongs somewhere.
10711
+ *
10712
+ * **Each axis is asked for separately**, because a container may
10713
+ * scroll on both, and this used to pick one from the container's
10714
+ * flex direction and drop the other on the floor. A spreadsheet is
10715
+ * the case that found it on the wheel in 38e70a3: one `ScrollView`
10716
+ * whose content overflows in both directions, classified as vertical,
10717
+ * so a sideways drag moved nothing at all. This is the same fix on
10718
+ * the same shared state, so the two input paths cannot disagree about
10719
+ * which container takes a gesture.
10720
+ *
10721
+ * A container that overflows on one axis only is unaffected: the axis
10722
+ * it does not scroll has no room, so it is not taken.
10407
10723
  */
10408
10724
  resolve(target, dx, dy) {
10409
10725
  let innermost = null;
@@ -10411,16 +10727,19 @@ var UiTouchScroller = class {
10411
10727
  if (!isScrollContainer(node)) continue;
10412
10728
  const state = this.scrollSink.containerState(node);
10413
10729
  if (state === void 0) continue;
10730
+ const takesX = dx !== 0 && hasScrollRoom(state.scrollX, state.maxScrollX, dx);
10731
+ const takesY = dy !== 0 && hasScrollRoom(state.scrollY, state.maxScrollY, dy);
10414
10732
  innermost ??= {
10415
10733
  node,
10416
- state
10734
+ state,
10735
+ takesX: false,
10736
+ takesY: false
10417
10737
  };
10418
- const delta = state.horizontal ? dx : dy;
10419
- const offset = state.horizontal ? state.scrollX : state.scrollY;
10420
- const max = state.horizontal ? state.maxScrollX : state.maxScrollY;
10421
- if (delta < 0 && offset > 0 || delta > 0 && offset < max) return {
10738
+ if (takesX || takesY) return {
10422
10739
  node,
10423
- state
10740
+ state,
10741
+ takesX,
10742
+ takesY
10424
10743
  };
10425
10744
  }
10426
10745
  return innermost;
@@ -10430,6 +10749,504 @@ function defaultClock() {
10430
10749
  return typeof performance !== "undefined" ? performance.now() : Date.now();
10431
10750
  }
10432
10751
  //#endregion
10752
+ //#region src/input/UiEditingController.ts
10753
+ /** How long two presses may be apart and still count as a double click. */
10754
+ const MULTI_CLICK_MS = 500;
10755
+ const MULTI_CLICK_SLOP = 4;
10756
+ /** Room kept around the caret when scrolling it into view. */
10757
+ const CARET_REVEAL_PADDING = 4;
10758
+ /**
10759
+ * Editing behaviour for EditableText nodes.
10760
+ *
10761
+ * The keyboard and pointer controllers call in here as their default
10762
+ * behaviour for an editable target — after the app's listeners have
10763
+ * run and only when none of them called preventDefault() — and the
10764
+ * host feeds it the shell's text input: `beforeinput` edits,
10765
+ * composition, paste. The model on the node is the single source of
10766
+ * truth; this class turns intents into model changes, marks the node
10767
+ * dirty so layout and paint follow, reports the change through an
10768
+ * `input` event, and keeps the caret in view.
10769
+ *
10770
+ * Text arrives one of two ways. A shell with an editing proxy sends
10771
+ * `beforeinput`, where the browser has already resolved the IME, dead
10772
+ * keys and the keyboard layout; then printable key presses are not
10773
+ * text and `textFromKeys` is off. Without a proxy — tests, a bare
10774
+ * runtime — printable keys become insertions.
10775
+ */
10776
+ var UiEditingController = class {
10777
+ host;
10778
+ dispatcher;
10779
+ focus;
10780
+ /** Insert printable key presses as text; off when a shell delivers text through beforeinput. */
10781
+ textFromKeys = true;
10782
+ platform;
10783
+ paint = createPaintState();
10784
+ focusedEditable = null;
10785
+ visible = true;
10786
+ /** x the current run of vertical moves keeps returning to. */
10787
+ verticalGoalX = void 0;
10788
+ dragging = null;
10789
+ lastPress = null;
10790
+ compositionOpen = false;
10791
+ constructor(host, dispatcher, focus, options = {}) {
10792
+ this.host = host;
10793
+ this.dispatcher = dispatcher;
10794
+ this.focus = focus;
10795
+ this.platform = options.platform ?? detectEditingPlatform();
10796
+ this.focus.onFocusChange((node) => this.handleFocusChange(node));
10797
+ }
10798
+ /** The editable that holds focus, or null. */
10799
+ get focused() {
10800
+ return this.focusedEditable;
10801
+ }
10802
+ /** Whether the pointer controller should hand presses on the node to this controller. */
10803
+ isEditable(node) {
10804
+ return isEditableNode(node);
10805
+ }
10806
+ /**
10807
+ * Default keyboard behaviour for the focused editable. Returns true
10808
+ * when the key was an editing command, so the caller can mark the
10809
+ * event handled.
10810
+ */
10811
+ handleKey(node, key, modifiers) {
10812
+ if (!isEditableNode(node)) return false;
10813
+ const command = commandForKey(key, modifiers, this.platform, this.textFromKeys);
10814
+ if (command === null) return false;
10815
+ return this.execute(node, command);
10816
+ }
10817
+ execute(node, command) {
10818
+ const model = editorFor(node);
10819
+ const readOnly = isReadOnly(node);
10820
+ switch (command.kind) {
10821
+ case "move":
10822
+ this.move(node, model, command.unit, command.direction, command.extend);
10823
+ return true;
10824
+ case "delete":
10825
+ if (readOnly) return true;
10826
+ return this.applyEdit(node, model, deleteInputType(command.unit, command.direction), null, () => {
10827
+ if (command.direction < 0) model.deleteBackward(command.unit);
10828
+ else model.deleteForward(command.unit);
10829
+ });
10830
+ case "newline":
10831
+ if (!isMultiline(node)) return false;
10832
+ if (readOnly) return true;
10833
+ return this.applyEdit(node, model, "insertLineBreak", "\n", () => model.insertText("\n"));
10834
+ case "insert":
10835
+ if (readOnly) return true;
10836
+ return this.applyEdit(node, model, "insertText", command.text, () => model.insertText(command.text));
10837
+ case "selectAll":
10838
+ model.selectAll();
10839
+ this.afterSelectionChange(node, model);
10840
+ return true;
10841
+ case "undo":
10842
+ if (readOnly) return true;
10843
+ this.history(node, model, "historyUndo", () => model.undo());
10844
+ return true;
10845
+ case "redo":
10846
+ if (readOnly) return true;
10847
+ this.history(node, model, "historyRedo", () => model.redo());
10848
+ return true;
10849
+ }
10850
+ }
10851
+ move(node, model, unit, direction, extend) {
10852
+ if (unit === "vertical") {
10853
+ const target = this.layoutOf(node).verticalMove(model.focus, direction, this.verticalGoalX);
10854
+ if (target === null) {
10855
+ model.moveTo(direction < 0 ? 0 : model.text.length, extend);
10856
+ this.verticalGoalX = void 0;
10857
+ } else {
10858
+ model.moveTo(target.offset, extend);
10859
+ this.verticalGoalX = target.x;
10860
+ }
10861
+ this.afterSelectionChange(node, model, true);
10862
+ return;
10863
+ }
10864
+ if (unit === "line") {
10865
+ const layout = this.layoutOf(node);
10866
+ const caret = layout.caretRect(model.focus);
10867
+ const line = layout.lines[caret.line];
10868
+ const hard = direction < 0 ? lineStartAt(model.text, model.focus) : lineEndAt(model.text, model.focus);
10869
+ const visual = direction < 0 ? line.start : line.end;
10870
+ model.moveTo(direction < 0 ? Math.max(hard, visual) : Math.min(hard, visual), extend);
10871
+ this.afterSelectionChange(node, model);
10872
+ return;
10873
+ }
10874
+ model.move(unit, direction, extend);
10875
+ this.afterSelectionChange(node, model);
10876
+ }
10877
+ /**
10878
+ * A `beforeinput` from the shell's editing proxy, in the DOM's
10879
+ * vocabulary. Returns true when it was applied to the focused
10880
+ * editable. Composition types are not handled here: the composition
10881
+ * methods carry them, and an engine that fires both must not insert
10882
+ * twice.
10883
+ */
10884
+ beforeInput(inputType, data) {
10885
+ const node = this.focusedEditable;
10886
+ if (node === null) return false;
10887
+ const model = editorFor(node);
10888
+ const readOnly = isReadOnly(node);
10889
+ switch (inputType) {
10890
+ case "insertText":
10891
+ case "insertReplacementText":
10892
+ case "insertFromDrop":
10893
+ case "insertFromYank":
10894
+ if (data === null || data.length === 0) return false;
10895
+ return readOnly || this.insert(node, model, inputType, data);
10896
+ case "insertFromPaste": return readOnly || data !== null && this.insert(node, model, inputType, data);
10897
+ case "insertLineBreak":
10898
+ case "insertParagraph":
10899
+ if (!isMultiline(node)) return false;
10900
+ return readOnly || this.applyEdit(node, model, inputType, "\n", () => model.insertText("\n"));
10901
+ case "deleteContentBackward":
10902
+ case "deleteWordBackward":
10903
+ case "deleteSoftLineBackward":
10904
+ case "deleteHardLineBackward":
10905
+ case "deleteContentForward":
10906
+ case "deleteWordForward":
10907
+ case "deleteSoftLineForward":
10908
+ case "deleteHardLineForward":
10909
+ case "deleteContent":
10910
+ case "deleteByCut":
10911
+ case "deleteByDrag": {
10912
+ if (readOnly) return true;
10913
+ const unit = /Word/.test(inputType) ? "word" : /Line/.test(inputType) ? "line" : "grapheme";
10914
+ const forward = /Forward/.test(inputType);
10915
+ return this.applyEdit(node, model, inputType, null, () => {
10916
+ if (inputType === "deleteContent" || inputType === "deleteByCut" || inputType === "deleteByDrag") model.replaceRange(model.start, model.end, "");
10917
+ else if (forward) model.deleteForward(unit);
10918
+ else model.deleteBackward(unit);
10919
+ });
10920
+ }
10921
+ case "historyUndo": return readOnly || this.history(node, model, inputType, () => model.undo());
10922
+ case "historyRedo": return readOnly || this.history(node, model, inputType, () => model.redo());
10923
+ default: return false;
10924
+ }
10925
+ }
10926
+ /** Inserts text at the selection of the focused editable, as typing would. */
10927
+ insertText(text) {
10928
+ const node = this.focusedEditable;
10929
+ if (node === null || isReadOnly(node)) return false;
10930
+ return this.insert(node, editorFor(node), "insertText", text);
10931
+ }
10932
+ /**
10933
+ * Replaces the focused editable's whole text, as an assistive
10934
+ * technology's "set value" does.
10935
+ *
10936
+ * `insertReplacementText` is the DOM's own input type for this, so an
10937
+ * `onBeforeInput` that vets edits sees the same vocabulary it sees
10938
+ * for typing, and an app that rejects the edit rejects this one too.
10939
+ * Selecting everything first is what makes it one undo step and one
10940
+ * `onInput` rather than a delete and an insert.
10941
+ */
10942
+ replaceText(text) {
10943
+ const node = this.focusedEditable;
10944
+ if (node === null || isReadOnly(node)) return false;
10945
+ const model = editorFor(node);
10946
+ model.selectAll();
10947
+ return this.insert(node, model, "insertReplacementText", text);
10948
+ }
10949
+ /**
10950
+ * Pastes text: newlines are kept in a multiline field and become
10951
+ * spaces in a single-line one.
10952
+ *
10953
+ * With nothing editable focused the text is offered to whatever is,
10954
+ * as a `Paste` event, instead of being dropped. That is the case a
10955
+ * grid needs: it owns a rectangle of cells, a block of tab-separated
10956
+ * text means something to it that it means to nothing else, and
10957
+ * before this there was no way for it to hear about one at all.
10958
+ */
10959
+ paste(text) {
10960
+ const node = this.focusedEditable;
10961
+ if (node === null || isReadOnly(node)) return this.offerPaste(text);
10962
+ return this.insert(node, editorFor(node), "insertFromPaste", text);
10963
+ }
10964
+ offerPaste(text) {
10965
+ const target = this.focus.focusedNode;
10966
+ if (target === null) return false;
10967
+ const event = new UiPasteEvent(text);
10968
+ this.dispatcher.dispatch(event, target);
10969
+ return event.defaultPrevented;
10970
+ }
10971
+ insert(node, model, inputType, text) {
10972
+ const data = isMultiline(node) ? text : text.replace(/\r\n|\r|\n/g, " ");
10973
+ return this.applyEdit(node, model, inputType, data, () => model.insertText(data));
10974
+ }
10975
+ compositionStart() {
10976
+ const node = this.focusedEditable;
10977
+ if (node === null || isReadOnly(node)) return;
10978
+ const model = editorFor(node);
10979
+ model.beginComposition();
10980
+ this.compositionOpen = true;
10981
+ this.afterTextChange(node, model, false);
10982
+ }
10983
+ /** The composition text so far and the caret within it. */
10984
+ compositionUpdate(text, caret = text.length) {
10985
+ const node = this.focusedEditable;
10986
+ if (node === null || isReadOnly(node)) return;
10987
+ const model = editorFor(node);
10988
+ model.updateComposition(text, caret);
10989
+ this.compositionOpen = true;
10990
+ this.afterTextChange(node, model, false);
10991
+ }
10992
+ /** The IME committed `text` (possibly empty: the composition was cancelled). */
10993
+ compositionEnd(text) {
10994
+ const node = this.focusedEditable;
10995
+ this.compositionOpen = false;
10996
+ if (node === null || isReadOnly(node)) return;
10997
+ const model = editorFor(node);
10998
+ const data = isMultiline(node) ? text : text.replace(/\r\n|\r|\n/g, " ");
10999
+ const before = model.text;
11000
+ model.commitComposition(data);
11001
+ this.afterTextChange(node, model, before !== model.text);
11002
+ }
11003
+ /**
11004
+ * A press on an editable places the caret (Shift extends the
11005
+ * selection to it); a second press within the double-click window
11006
+ * selects the word, a third the line. Dragging afterwards extends the
11007
+ * selection from the anchor.
11008
+ */
11009
+ pointerDown(node, x, y, modifiers) {
11010
+ if (!isEditableNode(node)) return;
11011
+ const model = editorFor(node);
11012
+ const layout = this.layoutOf(node);
11013
+ const local = this.host.toLocal(node, x, y);
11014
+ const offset = layout.offsetAt(local.x, local.y);
11015
+ const now = this.host.now();
11016
+ const last = this.lastPress;
11017
+ const count = !modifiers.shift && last !== null && last.node === node && now - last.at <= MULTI_CLICK_MS && Math.abs(last.x - x) <= MULTI_CLICK_SLOP && Math.abs(last.y - y) <= MULTI_CLICK_SLOP ? last.count + 1 : 1;
11018
+ this.lastPress = {
11019
+ node,
11020
+ x,
11021
+ y,
11022
+ at: now,
11023
+ count
11024
+ };
11025
+ this.dragging = node;
11026
+ if (modifiers.shift) model.moveTo(offset, true);
11027
+ else if (count === 2) {
11028
+ const word = wordRangeAt(model.text, offset);
11029
+ model.select(word.start, word.end);
11030
+ } else if (count >= 3) model.select(lineStartAt(model.text, offset), lineEndAt(model.text, offset));
11031
+ else model.select(offset);
11032
+ model.breakUndoGroup();
11033
+ this.afterSelectionChange(node, model);
11034
+ }
11035
+ pointerMove(node, x, y) {
11036
+ if (this.dragging !== node) return;
11037
+ const model = editorFor(node);
11038
+ const layout = this.layoutOf(node);
11039
+ const local = this.host.toLocal(node, x, y);
11040
+ const offset = layout.offsetAt(local.x, local.y);
11041
+ if (offset !== model.focus) {
11042
+ model.moveTo(offset, true);
11043
+ this.afterSelectionChange(node, model);
11044
+ }
11045
+ }
11046
+ pointerUp() {
11047
+ this.dragging = null;
11048
+ }
11049
+ /** The focused editable's state for the shell to mirror, or null. */
11050
+ state() {
11051
+ const node = this.focusedEditable;
11052
+ if (node === null || this.host.recordFor(node) === void 0) return null;
11053
+ const model = editorFor(node);
11054
+ const caret = this.layoutOf(node).caretRect();
11055
+ const visible = this.host.visibleBox(node);
11056
+ return {
11057
+ text: model.text,
11058
+ selectionStart: model.start,
11059
+ selectionEnd: model.end,
11060
+ caret: {
11061
+ x: visible.x + caret.x,
11062
+ y: visible.y + caret.y,
11063
+ width: 1,
11064
+ height: caret.height
11065
+ },
11066
+ multiline: isMultiline(node),
11067
+ composing: model.composing
11068
+ };
11069
+ }
11070
+ /**
11071
+ * When the focused caret next toggles, or undefined when nothing
11072
+ * blinks: no focused editable, a composition in progress (the caret
11073
+ * holds), or a hidden page.
11074
+ */
11075
+ nextCaretChange(now) {
11076
+ const node = this.focusedEditable;
11077
+ if (node === null || !this.visible) return;
11078
+ const model = editorFor(node);
11079
+ if (model.composing || !model.collapsed) return;
11080
+ return nextCaretToggle(model, now);
11081
+ }
11082
+ /**
11083
+ * The page was hidden or shown. A hidden page stops the blink so a
11084
+ * background tab schedules no frames; showing it restarts the caret.
11085
+ */
11086
+ setVisible(visible) {
11087
+ if (this.visible === visible) return;
11088
+ this.visible = visible;
11089
+ const node = this.focusedEditable;
11090
+ if (node !== null) {
11091
+ editorFor(node).blinkOrigin = this.host.now();
11092
+ this.host.markDirty(node, 2);
11093
+ }
11094
+ }
11095
+ /** Whether the caret may be drawn at all (the page is visible). */
11096
+ get caretEnabled() {
11097
+ return this.visible;
11098
+ }
11099
+ handleFocusChange(node) {
11100
+ const previous = this.focusedEditable;
11101
+ const next = node !== null && isEditableNode(node) ? node : null;
11102
+ if (previous === next) return;
11103
+ if (previous !== null) {
11104
+ const model = editorFor(previous);
11105
+ if (model.composing) model.commitComposition(model.text.slice(model.composition.start, model.composition.end));
11106
+ model.focused = false;
11107
+ this.host.markDirty(previous, 2);
11108
+ }
11109
+ this.focusedEditable = next;
11110
+ this.compositionOpen = false;
11111
+ this.verticalGoalX = void 0;
11112
+ if (next !== null) {
11113
+ const model = editorFor(next);
11114
+ model.focused = true;
11115
+ model.blinkOrigin = this.host.now();
11116
+ this.host.markDirty(next, 2);
11117
+ }
11118
+ }
11119
+ /**
11120
+ * Runs an edit unless a `beforeinput` listener cancels it, then
11121
+ * reports it. Returns true when the input was consumed either way.
11122
+ */
11123
+ applyEdit(node, model, inputType, data, run) {
11124
+ const before = new UiBeforeInputEvent(inputType, data);
11125
+ this.dispatcher.dispatch(before, node);
11126
+ if (before.defaultPrevented) return true;
11127
+ const text = model.text;
11128
+ run();
11129
+ this.afterTextChange(node, model, text !== model.text);
11130
+ return true;
11131
+ }
11132
+ history(node, model, inputType, run) {
11133
+ const before = new UiBeforeInputEvent(inputType, null);
11134
+ this.dispatcher.dispatch(before, node);
11135
+ if (before.defaultPrevented) return true;
11136
+ const text = model.text;
11137
+ run();
11138
+ this.afterTextChange(node, model, text !== model.text);
11139
+ return true;
11140
+ }
11141
+ afterTextChange(node, model, changed) {
11142
+ this.verticalGoalX = void 0;
11143
+ model.blinkOrigin = this.host.now();
11144
+ this.host.markDirty(node, changed || this.compositionOpen ? 5 : 2);
11145
+ if (changed) this.dispatcher.dispatch(new UiTextChangeEvent(model.text, model.start, model.end), node);
11146
+ this.notifySelection(node, model);
11147
+ this.revealCaret(node);
11148
+ }
11149
+ /**
11150
+ * Reports the selection, if it has actually moved since last time.
11151
+ *
11152
+ * The guard is what keeps this quiet: holding a right arrow against
11153
+ * the end of the text moves nothing and must say nothing, and a
11154
+ * listener that recomputed decoration on every key would otherwise
11155
+ * do it forever for no reason.
11156
+ *
11157
+ * `hasListeners` already makes an unheard dispatch free, so the
11158
+ * cost when nobody is listening is the two comparisons here.
11159
+ */
11160
+ notifySelection(node, model) {
11161
+ if (model.anchor === model.notifiedAnchor && model.focus === model.notifiedFocus) return;
11162
+ model.notifiedAnchor = model.anchor;
11163
+ model.notifiedFocus = model.focus;
11164
+ this.dispatcher.dispatch(new UiSelectionChangeEvent(model.text, model.start, model.end, model.anchor, model.focus), node);
11165
+ }
11166
+ afterSelectionChange(node, model, keepGoal = false) {
11167
+ if (!keepGoal) this.verticalGoalX = void 0;
11168
+ model.blinkOrigin = this.host.now();
11169
+ this.host.markDirty(node, 2);
11170
+ this.notifySelection(node, model);
11171
+ this.revealCaret(node);
11172
+ }
11173
+ /**
11174
+ * Asks the host to scroll the caret into view, when the node has been
11175
+ * laid out: the field's own text first, then any scroll container
11176
+ * around it.
11177
+ *
11178
+ * `layoutOf` places the text where it is seen, so the caret comes
11179
+ * back with the field's own scroll already taken off; the reveal is
11180
+ * asked for in the node's unscrolled coordinates, which is where the
11181
+ * offset it wants is measured from.
11182
+ */
11183
+ revealCaret(node) {
11184
+ const rec = this.host.recordFor(node);
11185
+ if (rec === void 0) return;
11186
+ const caret = this.layoutOf(node).caretRect();
11187
+ this.host.reveal(node, {
11188
+ x: caret.x + rec.scrollX - CARET_REVEAL_PADDING,
11189
+ y: caret.y + rec.scrollY - CARET_REVEAL_PADDING,
11190
+ width: 9,
11191
+ height: caret.height + 8
11192
+ });
11193
+ }
11194
+ /**
11195
+ * The node's text laid out in node-local coordinates: the content box
11196
+ * starts at the padding and is shifted by the field's own scroll, as
11197
+ * it is for the renderers in the parent's space. Points from the
11198
+ * pointer and the caret box the shell is given are both in that same
11199
+ * seen space, so neither has to know the offset. Before the first
11200
+ * layout the record is empty and the lines fall at the origin, which
11201
+ * is still a valid place for a caret.
11202
+ */
11203
+ /**
11204
+ * Where the caret is inside a field, in the field's own coordinates.
11205
+ *
11206
+ * Relative to the node's border-box origin, with the field's own
11207
+ * scroll already applied — so a popup placed under the caret adds
11208
+ * the node's position on screen and nothing else.
11209
+ *
11210
+ * Public because an application cannot work this out. The geometry
11211
+ * needs the paragraph as it was *laid out*, which means the
11212
+ * measurer, the resolved paint and the layout record, and an
11213
+ * application that re-measured the text to find the caret would be
11214
+ * a second measurer that must never disagree with this one. Asking
11215
+ * is the only version that stays right.
11216
+ *
11217
+ * Null when the node is not an editable, or has not been laid out.
11218
+ */
11219
+ caretRectOf(node) {
11220
+ if (!isEditableNode(node) || this.host.recordFor(node) === void 0) return null;
11221
+ return this.layoutOf(node).caretRect();
11222
+ }
11223
+ layoutOf(node) {
11224
+ const rec = this.host.recordFor(node);
11225
+ const state = resolvePaintState(node, this.paint);
11226
+ const box = rec === void 0 ? {
11227
+ x: 0,
11228
+ y: 0,
11229
+ width: 0,
11230
+ height: 0
11231
+ } : {
11232
+ x: rec.paddingLeft - rec.scrollX,
11233
+ y: rec.paddingTop - rec.scrollY,
11234
+ width: Math.max(0, rec.width - rec.paddingLeft - rec.paddingRight),
11235
+ height: Math.max(0, rec.height - rec.paddingTop - rec.paddingBottom)
11236
+ };
11237
+ return new EditableLayout(state.editor, box, state, this.host.measurer);
11238
+ }
11239
+ };
11240
+ function deleteInputType(unit, direction) {
11241
+ const side = direction < 0 ? "Backward" : "Forward";
11242
+ switch (unit) {
11243
+ case "word": return `deleteWord${side}`;
11244
+ case "line":
11245
+ case "document": return `deleteSoftLine${side}`;
11246
+ default: return `deleteContent${side}`;
11247
+ }
11248
+ }
11249
+ //#endregion
10433
11250
  //#region src/input/UiPlatformAdapter.ts
10434
11251
  /**
10435
11252
  * Bridges platform/browser input events to the framework input
@@ -12157,8 +12974,6 @@ const SCROLLBAR_FADE_MS = 350;
12157
12974
  * The limit is what stops a cycle of anchors from spinning.
12158
12975
  */
12159
12976
  const ANCHOR_CHAIN_LIMIT = 4;
12160
- /** An editable's text is the user's; runs are a property of a `Text` node. */
12161
- const EMPTY_TEXT_SPANS = [];
12162
12977
  /**
12163
12978
  * Whether a node names any of the four logical horizontal insets, and
12164
12979
  * so needs its writing direction resolved before its box is worked
@@ -13561,7 +14376,7 @@ var LayoutEngine = class {
13561
14376
  contentMain = Math.max(contentMain, this.lineOuterMain(line, config.gapMain, "final"));
13562
14377
  } else {
13563
14378
  for (const item of line.items) item.finalMain = item.hypotheticalMain;
13564
- contentMain = Math.max(contentMain, lineTotal);
14379
+ contentMain = Math.max(contentMain, this.lineMaxContentMain(line, config.gapMain));
13565
14380
  }
13566
14381
  }
13567
14382
  if (mainDefinite) contentMain = Math.max(mainMax, contentMain);
@@ -13668,6 +14483,7 @@ var LayoutEngine = class {
13668
14483
  child,
13669
14484
  rec: cRec,
13670
14485
  baseMain,
14486
+ maxContentMain: measuredMain,
13671
14487
  hypotheticalMain: this.clamp(baseMain, minMain, maxMain),
13672
14488
  finalMain: baseMain,
13673
14489
  cross: row ? cRec.measuredHeight : cRec.measuredWidth,
@@ -13776,6 +14592,33 @@ var LayoutEngine = class {
13776
14592
  });
13777
14593
  return lines;
13778
14594
  }
14595
+ /**
14596
+ * A line's main size when the container shrink-wraps it.
14597
+ *
14598
+ * The sum of the hypothetical sizes would be the answer if an item
14599
+ * never grew — but `flex: 1` is `flex-basis: 0`, and an item that
14600
+ * starts flexing from zero asks for nothing while its content still
14601
+ * wants the width of its text. A container wrapped around that sum
14602
+ * comes out too narrow; the item's text then wraps inside a box that
14603
+ * was measured as though it had not, and the container is left
14604
+ * shorter than the children it draws — a menu whose last rows fall
14605
+ * out of its own panel.
14606
+ *
14607
+ * So an item that can grow contributes whichever is larger, the size
14608
+ * its basis gives it or the size its content asks for. Chrome ignores
14609
+ * the basis entirely here and browsers disagree with each other about
14610
+ * it; the larger of the two is the reading that keeps `flex-basis`
14611
+ * counting for something, which is what `basis/sizes-container`
14612
+ * already records this engine as doing.
14613
+ */
14614
+ lineMaxContentMain(line, gap) {
14615
+ let total = gap * (line.items.length - 1);
14616
+ for (const item of line.items) {
14617
+ const wanted = item.grow > 0 ? this.clamp(item.maxContentMain, item.minMain, item.maxMain) : 0;
14618
+ total += Math.max(item.hypotheticalMain, wanted) + item.marginMainStart + item.marginMainEnd;
14619
+ }
14620
+ return total;
14621
+ }
13779
14622
  lineOuterMain(line, gap, size) {
13780
14623
  let total = gap * (line.items.length - 1);
13781
14624
  for (const item of line.items) total += (size === "final" ? item.finalMain : item.hypotheticalMain) + item.marginMainStart + item.marginMainEnd;
@@ -14104,8 +14947,9 @@ var LayoutEngine = class {
14104
14947
  const paddingV = rec.paddingTop + rec.paddingBottom;
14105
14948
  const editable = node.type === "editable-text";
14106
14949
  if (node.type === "text" || node.type === "button" || editable) {
14107
- const spans = editable ? EMPTY_TEXT_SPANS : resolvedSpansOf(node);
14108
- const text = editable ? editorFor(node).text : spans.length > 0 ? textContentOf(node) : String(node.properties.get("text") ?? "");
14950
+ const typed = editable ? editorFor(node).text : "";
14951
+ const spans = editable ? editableSpansOf(node, typed) : resolvedSpansOf(node);
14952
+ const text = editable ? typed : spans.length > 0 ? textContentOf(node) : String(node.properties.get("text") ?? "");
14109
14953
  const placeholderProp = node.properties.get("placeholder");
14110
14954
  const placeholder = editable && text.length === 0 && typeof placeholderProp === "string" ? placeholderProp : void 0;
14111
14955
  const font = resolveFont(node);
@@ -14847,6 +15691,7 @@ var LayoutEngine = class {
14847
15691
  */
14848
15692
  markLayoutDirty(node, selfCanBound = false) {
14849
15693
  let current = node;
15694
+ let bounded = false;
14850
15695
  for (;;) {
14851
15696
  const rec = this.records.get(current);
14852
15697
  if (rec !== void 0) {
@@ -14854,12 +15699,35 @@ var LayoutEngine = class {
14854
15699
  rec.placeDirty = true;
14855
15700
  }
14856
15701
  if (current === this.layoutRoot || current.parent === null) {
14857
- this.relayoutRoots.add(this.layoutRoot ?? current);
15702
+ if (!bounded) this.relayoutRoots.add(this.layoutRoot ?? current);
14858
15703
  return;
14859
15704
  }
14860
- if (rec?.relayoutBoundary && !rec.positioned && (current !== node || selfCanBound)) {
15705
+ if (!bounded && rec?.relayoutBoundary && !rec.positioned && (current !== node || selfCanBound)) {
14861
15706
  this.relayoutRoots.add(current);
14862
- return;
15707
+ /**
15708
+ * The boundary is where the *relayout* starts, and the walk
15709
+ * carries on past it marking ancestors.
15710
+ *
15711
+ * Stopping here as well is sound only while the pass really
15712
+ * does start at this boundary. It does not always: another
15713
+ * node dirtied in the same frame can drag the layout root
15714
+ * into `relayoutRoots`, and then the pass is a `fullLayout`
15715
+ * that measures from the root — down through ancestors this
15716
+ * walk left clean, every one of which short-circuits on its
15717
+ * own measure memo and never reaches the node that changed.
15718
+ *
15719
+ * Found with a bar switched into the chrome by choosing a
15720
+ * menu item: the menu's own teardown put the root in the set,
15721
+ * the frame did a full layout, and `measure` was never called
15722
+ * on the column that had gained a child. It drew at zero
15723
+ * height, with every spec passing — a spec asks what a node's
15724
+ * properties are, and they were all correct.
15725
+ *
15726
+ * Marking to the root costs a walk of the depth and two
15727
+ * booleans a node, against a memo that is otherwise believed
15728
+ * when it should not be.
15729
+ */
15730
+ bounded = true;
14863
15731
  }
14864
15732
  current = current.parent;
14865
15733
  }
@@ -15239,10 +16107,26 @@ var LayoutEngine = class {
15239
16107
  */
15240
16108
  assignBox(node, x, y, width, height) {
15241
16109
  const rec = this.record(node);
15242
- if (rec.positioned && !rec.absolute && node !== this.layoutRoot) {
16110
+ if (rec.positioned && !rec.absolute && !rec.sticky && node !== this.layoutRoot) {
15243
16111
  x += rec.left ?? (rec.right !== void 0 ? -rec.right : 0);
15244
16112
  y += rec.top ?? (rec.bottom !== void 0 ? -rec.bottom : 0);
15245
16113
  }
16114
+ /**
16115
+ * A **sticky** node is not offset by its insets, and that is the
16116
+ * difference between the two.
16117
+ *
16118
+ * `top: 0` on a sticky header is a threshold — "do not let me go
16119
+ * above the top of the scrollport" — and not a displacement, which
16120
+ * is what CSS means by it and what `resolveStickyOffset` below
16121
+ * assumes when it computes `viewLeft + rec.left - rec.x`. Offset
16122
+ * here *and* used as a threshold there, an inset is applied twice
16123
+ * and the two errors cancel only when it is zero.
16124
+ *
16125
+ * Which is why nothing noticed: every sticky node in the framework
16126
+ * and its examples sticks at zero. The first one that did not — a
16127
+ * spreadsheet's second frozen column, which sticks at the width of
16128
+ * the gutter — was drawn one gutter too far to the right.
16129
+ */
15246
16130
  if (rec.x !== x || rec.y !== y || rec.width !== width || rec.height !== height) {
15247
16131
  rec.x = x;
15248
16132
  rec.y = y;
@@ -15482,12 +16366,37 @@ function drawOverlayShapes(ctx, shapes) {
15482
16366
  */
15483
16367
  function decorationRect(shape, rec, nodeRadius) {
15484
16368
  const outset = shape.outset ?? 0;
16369
+ const horizontal = axis(rec.x, rec.width, shape.x, shape.width, shape.right, outset);
16370
+ const vertical = axis(rec.y, rec.height, shape.y, shape.height, shape.bottom, outset);
16371
+ const radius = Math.max(0, shape.radius ?? nodeRadius + outset);
15485
16372
  return {
15486
- x: rec.x + (shape.x ?? 0) - outset,
15487
- y: rec.y + (shape.y ?? 0) - outset,
15488
- width: Math.max(0, (shape.width ?? rec.width) + 2 * outset),
15489
- height: Math.max(0, (shape.height ?? rec.height) + 2 * outset),
15490
- radius: Math.max(0, shape.radius ?? nodeRadius + outset)
16373
+ x: horizontal.start,
16374
+ y: vertical.start,
16375
+ width: horizontal.size,
16376
+ height: vertical.size,
16377
+ radius
16378
+ };
16379
+ }
16380
+ /**
16381
+ * One axis of the placement, from any two of near, size and far.
16382
+ *
16383
+ * The outset applies after the three are reconciled, growing the
16384
+ * rectangle by it on both sides, so it means the same thing however
16385
+ * the rectangle was described.
16386
+ */
16387
+ function axis(origin, extent, near, size, far, outset) {
16388
+ let start;
16389
+ let length;
16390
+ if (near === void 0 && size !== void 0 && far !== void 0) {
16391
+ length = size;
16392
+ start = extent - far - size;
16393
+ } else {
16394
+ start = near ?? 0;
16395
+ length = size ?? extent - start - (far ?? 0);
16396
+ }
16397
+ return {
16398
+ start: origin + start - outset,
16399
+ size: Math.max(0, length + 2 * outset)
15491
16400
  };
15492
16401
  }
15493
16402
  /**
@@ -17108,12 +18017,16 @@ var Canvas2DRenderer = class {
17108
18017
  paintEditable(ctx, paint, context) {
17109
18018
  const model = paint.editor;
17110
18019
  const layout = new EditableLayout(model, this.contentBox, paint, context.text);
18020
+ fillTextRects(ctx, textRunBackgrounds(layout.lines, paint));
17111
18021
  if (model.focused && !model.collapsed) {
17112
18022
  ctx.fillStyle = colorToCss(paint.selectionColor);
17113
18023
  for (const box of layout.selectionBoxes()) ctx.fillRect(box.x, box.y, box.width, box.height);
17114
18024
  }
17115
18025
  if (layout.placeholderLines.length > 0) drawTextLines(ctx, layout.placeholderLines, buildFontString(paint), colorToCss(paint.placeholderColor), paint.letterSpacing, paint.rtl);
17116
- else drawTextLines(ctx, layout.lines, buildFontString(paint), colorToCss(paint.textColor), paint.letterSpacing, paint.rtl);
18026
+ else {
18027
+ drawTextLines(ctx, layout.lines, buildFontString(paint), colorToCss(paint.textColor), paint.letterSpacing, paint.rtl, paint);
18028
+ fillTextRects(ctx, textRunDecorations(layout.lines, paint));
18029
+ }
17117
18030
  if (model.composing) {
17118
18031
  ctx.fillStyle = colorToCss(paint.textColor);
17119
18032
  for (const box of layout.compositionBoxes()) {
@@ -17297,6 +18210,6 @@ function createCanvasSurface(host) {
17297
18210
  return new CanvasSurface(host);
17298
18211
  }
17299
18212
  //#endregion
17300
- export { prepareInputSurface as $, textContentOf as $i, nextWordEnd as $n, borderRadius as $r, createPaintState as $t, accumulatedOffsetTo as A, UiBeforeInputEvent as Aa, raiseContrast as Ai, editorFor as An, pathValuesEqual as Ar, EditableLayout as At, parseFlexDirection as B, UiWheelEvent as Ba, boxShadowArraysEqual as Bi, caretRectFor as Bn, radialGradient as Br, textMeasureRequest as Bt, describeLength as C, writeOverrideProperty as Ca, hasThemeExtension as Ci, linkOf as Cn, statesEqual as Cr, wordRangeIn as Ct, formatNumber as D, visualState as Da, withThemeExtension as Di, CARET_WIDTH as Dn, validateSubgrid as Dr, ELLIPSIS as Dt, formatExplanation as E, hasVisualState as Ea, themeExtensionsEqual as Ei, CARET_BLINK_MS as En, UI_SUBGRID_AXES as Er, spannedRunsFor as Et, constraintsEqual as F, UiKeyboardEvent as Fa, spacingEqual as Fi, nextCaretToggle as Fn, transformsEqual as Fr, fillTextRects as Ft, MainAxisAlignment as G, UiBinding as Ga, defaultTypography as Gi, offsetAtX as Gn, isFrLength as Gr, fontStackFor as Gt, sizeGridTracks as H, wheelDeltaYOf as Ha, defaultShapes as Hi, lineIndexForOffset as Hn, auto as Hr, textRunDecorations as Ht, tightenConstraints as I, UiPinchEvent as Ia, spacingSteps as Ii, syncEditorValue as In, MAX_GRADIENT_STOPS as Ir, layoutTextLines as It, parseMainAxisAlignment as J, defaultTextStyle as Ji, EditableTextModel as Jn, minmax as Jr, DEFAULT_FONT_SIZE as Jt, parseAlignContent as K, isTypographyRole as Ki, offsetForVerticalMove as Kn, isMinMaxTrack as Kr, registerFontStack as Kt, MAX_MEASURES_PER_CHILD as L, UiPointerEvent as La, defaultShadows as Li, resolveColorValue as Ln, gradientPaint as Lr, runCanvasStyleInto as Lt, LayoutRecord as M, UiFocusEvent as Ma, densityFactors as Mi, isEditableNode as Mn, parseTransform as Mr, buildFontString as Mt, Constraints as N, UiGestureEvent as Na, defaultSpacing as Ni, isMultiline as Nn, transform as Nr, createCanvasTextStyle as Nt, labelNode as O, visualStatesEqual as Oa, contrastRatio as Oi, EDITOR_PROP as On, inputsEqual as Or, layoutParagraph as Ot, clampSize as P, UiInputEvent as Pa, scaleSpacing as Pi, isReadOnly as Pn, transformIsIdentity as Pr, drawText as Pt, pointerDeviceOf as Q, spannedTextOf as Qi, nextGraphemeEnd as Qn, UiBorderRadiuses as Qr, computeObjectFitRect as Qt, isLayoutProtocol as R, UiTextChangeEvent as Ra, shadowsEqual as Ri, isVideoSurface as Rn, gradientsEqual as Rr, runFontString as Rt, SCROLLBAR_LINGER_MS as S, writeDeclaredProperty as Sa, defineThemeExtension as Si, linkHoverOf as Sn, normalizeStates as Sr, selectionRectsIn as St, formatConstraints as T, defaultVisualState as Ta, themeExtension as Ti, setLinkHover as Tn, validateStates as Tr, ParagraphTextMeasurer as Tt, AlignContent as U, UiNodeType as Ua, isShapeName as Ui, lineLimit as Un, fr as Ur, bumpFontStack as Ut, placeGridItems as V, noKeyModifiers as Va, boxShadowsEqual as Vi, lineIndexAtY as Vn, validateGradient as Vr, textRunBackgrounds as Vt, CrossAxisAlignment as W, DirtyFlags as Wa, shapesEqual as Wi, offsetAtPoint as Wn, isAutoLength as Wr, clearFontStacks as Wt, UiPlatformAdapter as X, resolvedSpansOf as Xi, lineEndAt as Xn, repeat as Xr, DEFAULT_TEXT_COLOR as Xt, CanvasPlatformSurface as Y, flattenTextSpans as Yi, graphemeBoundaries as Yn, percent as Yr, DEFAULT_FONT_WEIGHT as Yt, capturePointer as Z, spanAtOffset as Zi, lineStartAt as Zn, resolveLength as Zr, colorToCss as Zt, LABEL_PADDING_X as _, UiNode as _a, darkTheme as _i, isNodeInert as _n, UiProperties as _r, UiInputDispatcher as _t, PaintPictureCache as a, UiBasicColors as aa, uniformBorderRadius as ai, TEXT_MATCHES_PROP as an, resolveProperty as ar, isScrollContainer as at, LayoutEngine as b, overrideSources as ba, withContrast as bi, clearLinkHover as bn, isUiRole as br, paragraphGeometry as bt, EMPTY_RECORDING as c, colorValuesEqual as ca, UiEnvironmentKeys as ci, setMatchRanges as cn, UiGraph as cr, UiFocusManager as ct, tracePathData as d, parseColor as da, noInsets as di, selectableTextNodes as dn, getPropertyNames as dr, SCROLLBAR_INSET as dt, textSpansEqual as ea, borderRadiusCorners as ei, isPaintVisible as en, previousGraphemeStart as er, touchActionFor as et, ScaledImageCache as f, rgb8 as fa, observeViewportInsets as fi, selectableTextOf as fn, inheritedPropertyFlags as fr, SCROLLBAR_MIN_THUMB as ft, paintsAfterChildren as g, UiEnvironment as ga, unknownContainerSize as gi, isNodeHitTestable as gn, propertyValuesEqualByName as gr, scrollbarZoneAt as gt, hasDecorationPhase as h, findEnvironmentKey as ha, containerBands as hi, isNodeFocusable as hn, propertyIsInherited as hr, scrollbarThumbs as ht, traceRoundedRect as i, lightColors as ia, normalizeBorderRadius as ii, DEFAULT_LINE_HEIGHT_FACTOR as in, resolveNumber as ir, UiWheelController as it, contentOffset as j, UiEventType as ja, relativeLuminance as ji, editorOf as jn, UiTransforms as jr, applyCanvasTextStyle as jt, withOverrideSource as k, MOUSE_POINTER as ka, highContrastColors as ki, caretVisibleAt as kn, paintValuesEqual as kr, proportionalFontMetrics as kt, PaintRecorder as l, colorsEqual as la, UiInsetRegistry as li, TEXT_SELECTION_PROP as ln, closestPropertyName as lr, UiHitTester as lt, decorationRect as m, createEnvironmentKey as ma, bandOf as mi, setSelectionRange as mn, propertyEffects as mr, scrollbarThumb as mt, createCanvasSurface as n, colorsEqualPalette as na, borderRadiusIsZero as ni, normalizeVerticalAlign as nn, wordRangeAt as nr, UiGestureRecognizer as nt, paintPictures as o, colorToHex as oa, defineProperty as oi, clearMatchRanges as on, resolvePropertyByName as or, UiKeyboardController as ot, decorationColor as p, rgba as pa, UiContainerSizeSource as pi, selectionRangeOf as pn, inheritedPropertyNames as pr, SCROLLBAR_THICKNESS as pt, parseCrossAxisAlignment as q, typographyEqual as qi, selectionRects as qn, isPercentLength as qr, DEFAULT_FONT_FAMILY as qt, Canvas2DRenderer as r, darkColors as ra, borderRadiusValuesEqual as ri, resolvePaintState as rn, resolveBoolean as rr, UiPinchRecognizer as rt, PaintTarget as s, colorToRgba as sa, propertyValuesEqual as si, matchRangesOf as sn, resolveString as sr, UiPointerController as st, CanvasSurface as t, textStylesEqual as ta, borderRadiusEqual as ti, normalizeTextAlign as tn, previousWordStart as tr, UiTouchScroller as tt, replayPaint as u, normalizeColor as ua, insetsEqual as ui, clearSelectionRange as un, findPropertyDefinition as ur, SCROLLBAR_HOVER_ZONE as ut, drawOverlayShapes as v, DirtyNodeSet as va, lightTheme as vi, isNodeSelectable as vn, UI_ROLES as vr, hasDrawnText as vt, describeOverrides as w, UiVisualState as wa, noThemeExtensions as wi, linkRunAtPointIn as wn, validateRole as wr, CharacterCountTextMeasurer as wt, SCROLLBAR_FADE_MS as x, resetOverrideWarnings as xa, withDensity as xi, hasTextLinks as xn, isUiSemanticState as xr, paragraphGeometryFrom as xt, labelOrigin as y, clearOverrideProperty as ya, themesEqual as yi, resolveCursor as yn, UI_SEMANTIC_STATES as yr, offsetAtPointIn as yt, FlexDirection as z, UiWheelDeltaMode as za, boxShadow as zi, videoFrameSize as zn, linearGradient as zr, spanOf as zt };
18213
+ export { prepareInputSurface as $, UiBinding as $a, defaultTextStyle as $i, EditableTextModel as $n, minmax as $r, DEFAULT_FONT_WEIGHT as $t, accumulatedOffsetTo as A, UiVisualState as Aa, themeExtension as Ai, setLinkHover as An, validateStates as Ar, proportionalFontMetrics as At, parseFlexDirection as B, UiInputEvent as Ba, spacingEqual as Bi, nextCaretToggle as Bn, transformsEqual as Br, layoutTextLines as Bt, describeLength as C, UiNode as Ca, lightTheme as Ci, isNodeTabStop as Cn, UI_ROLES as Cr, selectionRectsIn as Ct, formatNumber as D, resetOverrideWarnings as Da, defineThemeExtension as Di, linkHoverOf as Dn, normalizeStates as Dr, spannedRunsFor as Dt, formatExplanation as E, overrideSources as Ea, withDensity as Ei, hasTextLinks as En, isUiSemanticState as Er, ParagraphTextMeasurer as Et, constraintsEqual as F, MOUSE_POINTER as Fa, raiseContrast as Fi, editorFor as Fn, pathValuesEqual as Fr, applyCanvasTextStyle as Ft, MainAxisAlignment as G, UiSelectionChangeEvent as Ga, boxShadowArraysEqual as Gi, caretRectFor as Gn, radialGradient as Gr, textRunBackgrounds as Gt, sizeGridTracks as H, UiPasteEvent as Ha, defaultShadows as Hi, resolveColorValue as Hn, gradientPaint as Hr, runFontString as Ht, tightenConstraints as I, UiBeforeInputEvent as Ia, relativeLuminance as Ii, editorOf as In, UiTransforms as Ir, buildFontString as It, parseMainAxisAlignment as J, UiWheelEvent as Ja, isShapeName as Ji, lineLimit as Jn, fr as Jr, clearFontStacks as Jt, parseAlignContent as K, UiTextChangeEvent as Ka, boxShadowsEqual as Ki, lineIndexAtY as Kn, validateGradient as Kr, textRunDecorations as Kt, MAX_MEASURES_PER_CHILD as L, UiEventType as La, densityFactors as Li, isEditableNode as Ln, parseTransform as Lr, createCanvasTextStyle as Lt, LayoutRecord as M, hasVisualState as Ma, withThemeExtension as Mi, CARET_WIDTH as Mn, validateSubgrid as Mr, detectEditingPlatform as Mt, Constraints as N, visualState as Na, contrastRatio as Ni, EDITOR_PROP as Nn, inputsEqual as Nr, isPrintable as Nt, labelNode as O, writeDeclaredProperty as Oa, hasThemeExtension as Oi, linkOf as On, statesEqual as Or, ELLIPSIS as Ot, clampSize as P, visualStatesEqual as Pa, highContrastColors as Pi, caretVisibleAt as Pn, paintValuesEqual as Pr, EditableLayout as Pt, pointerDeviceOf as Q, DirtyFlags as Qa, typographyEqual as Qi, selectionRects as Qn, isPercentLength as Qr, DEFAULT_FONT_SIZE as Qt, isLayoutProtocol as R, UiFocusEvent as Ra, defaultSpacing as Ri, isMultiline as Rn, transform as Rr, drawText as Rt, SCROLLBAR_LINGER_MS as S, UiEnvironment as Sa, darkTheme as Si, isNodeSelectable as Sn, UiProperties as Sr, paragraphGeometryFrom as St, formatConstraints as T, clearOverrideProperty as Ta, withContrast as Ti, clearLinkHover as Tn, isUiRole as Tr, CharacterCountTextMeasurer as Tt, AlignContent as U, UiPinchEvent as Ua, shadowsEqual as Ui, isVideoSurface as Un, gradientsEqual as Ur, spanOf as Ut, placeGridItems as V, UiKeyboardEvent as Va, spacingSteps as Vi, syncEditorValue as Vn, MAX_GRADIENT_STOPS as Vr, runCanvasStyleInto as Vt, CrossAxisAlignment as W, UiPointerEvent as Wa, boxShadow as Wi, videoFrameSize as Wn, linearGradient as Wr, textMeasureRequest as Wt, UiPlatformAdapter as X, wheelDeltaYOf as Xa, defaultTypography as Xi, offsetAtX as Xn, isFrLength as Xr, registerFontStack as Xt, CanvasPlatformSurface as Y, noKeyModifiers as Ya, shapesEqual as Yi, offsetAtPoint as Yn, isAutoLength as Yr, fontStackFor as Yt, capturePointer as Z, UiNodeType as Za, isTypographyRole as Zi, offsetForVerticalMove as Zn, isMinMaxTrack as Zr, DEFAULT_FONT_FAMILY as Zt, LABEL_PADDING_X as _, parseColor as _a, observeViewportInsets as _i, selectionRangeOf as _n, inheritedPropertyFlags as _r, scrollbarZoneAt as _t, PaintPictureCache as a, textContentOf as aa, borderRadiusCorners as ai, normalizeTextAlign as an, previousGraphemeStart as ar, UiWheelController as at, LayoutEngine as b, createEnvironmentKey as ba, containerBands as bi, isNodeHitTestable as bn, propertyIsInherited as br, offsetAtPointIn as bt, EMPTY_RECORDING as c, colorsEqualPalette as ca, borderRadiusValuesEqual as ci, DEFAULT_LINE_HEIGHT_FACTOR as cn, resolveBoolean as cr, UiPointerController as ct, tracePathData as d, UiBasicColors as da, defineProperty as di, matchRangesOf as dn, resolvePropertyByName as dr, SCROLLBAR_HOVER_ZONE as dt, editableSpansOf as ea, percent as ei, DEFAULT_TEXT_COLOR as en, graphemeBoundaries as er, touchActionFor as et, ScaledImageCache as f, colorToHex as fa, propertyValuesEqual as fi, setMatchRanges as fn, resolveString as fr, SCROLLBAR_INSET as ft, paintsAfterChildren as g, normalizeColor as ga, noInsets as gi, selectableTextOf as gn, getPropertyNames as gr, scrollbarThumbs as gt, hasDecorationPhase as h, colorsEqual as ha, insetsEqual as hi, selectableTextNodes as hn, findPropertyDefinition as hr, scrollbarThumb as ht, traceRoundedRect as i, spannedTextOf as ia, borderRadius as ii, isPaintVisible as in, nextWordEnd as ir, UiPinchRecognizer as it, contentOffset as j, defaultVisualState as ja, themeExtensionsEqual as ji, CARET_BLINK_MS as jn, UI_SUBGRID_AXES as jr, commandForKey as jt, withOverrideSource as k, writeOverrideProperty as ka, noThemeExtensions as ki, linkRunAtPointIn as kn, validateRole as kr, layoutParagraph as kt, PaintRecorder as l, darkColors as la, normalizeBorderRadius as li, TEXT_MATCHES_PROP as ln, resolveNumber as lr, UiFocusManager as lt, decorationRect as m, colorValuesEqual as ma, UiInsetRegistry as mi, clearSelectionRange as mn, closestPropertyName as mr, SCROLLBAR_THICKNESS as mt, createCanvasSurface as n, resolvedSpansOf as na, resolveLength as ni, computeObjectFitRect as nn, lineStartAt as nr, UiTouchScroller as nt, paintPictures as o, textSpansEqual as oa, borderRadiusEqual as oi, normalizeVerticalAlign as on, previousWordStart as or, isScrollContainer as ot, decorationColor as p, colorToRgba as pa, UiEnvironmentKeys as pi, TEXT_SELECTION_PROP as pn, UiGraph as pr, SCROLLBAR_MIN_THUMB as pt, parseCrossAxisAlignment as q, UiWheelDeltaMode as qa, defaultShapes as qi, lineIndexForOffset as qn, auto as qr, bumpFontStack as qt, Canvas2DRenderer as r, spanAtOffset as ra, UiBorderRadiuses as ri, createPaintState as rn, nextGraphemeEnd as rr, UiGestureRecognizer as rt, PaintTarget as s, textStylesEqual as sa, borderRadiusIsZero as si, resolvePaintState as sn, wordRangeAt as sr, UiKeyboardController as st, CanvasSurface as t, flattenTextSpans as ta, repeat as ti, colorToCss as tn, lineEndAt as tr, UiEditingController as tt, replayPaint as u, lightColors as ua, uniformBorderRadius as ui, clearMatchRanges as un, resolveProperty as ur, UiHitTester as ut, drawOverlayShapes as v, rgb8 as va, UiContainerSizeSource as vi, setSelectionRange as vn, inheritedPropertyNames as vr, UiInputDispatcher as vt, describeOverrides as w, DirtyNodeSet as wa, themesEqual as wi, resolveCursor as wn, UI_SEMANTIC_STATES as wr, wordRangeIn as wt, SCROLLBAR_FADE_MS as x, findEnvironmentKey as xa, unknownContainerSize as xi, isNodeInert as xn, propertyValuesEqualByName as xr, paragraphGeometry as xt, labelOrigin as y, rgba as ya, bandOf as yi, isNodeFocusable as yn, propertyEffects as yr, hasDrawnText as yt, FlexDirection as z, UiGestureEvent as za, scaleSpacing as zi, isReadOnly as zn, transformIsIdentity as zr, fillTextRects as zt };
17301
18214
 
17302
- //# sourceMappingURL=CanvasSurface-5dU-U3qQ.js.map
18215
+ //# sourceMappingURL=CanvasSurface-ifHkg0-g.js.map