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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as prepareInputSurface, $i as textContentOf, $n as nextWordEnd, $r as borderRadius, $t as createPaintState, A as accumulatedOffsetTo, Aa as UiBeforeInputEvent, Ai as raiseContrast, An as editorFor, Ar as pathValuesEqual, At as EditableLayout, B as parseFlexDirection, Ba as UiWheelEvent, Bi as boxShadowArraysEqual, Bn as caretRectFor, Br as radialGradient, Bt as textMeasureRequest, C as describeLength, Ca as writeOverrideProperty, Ci as hasThemeExtension, Cn as linkOf, Cr as statesEqual, Ct as wordRangeIn, D as formatNumber, Da as visualState, Di as withThemeExtension, Dn as CARET_WIDTH, Dr as validateSubgrid, Dt as ELLIPSIS, E as formatExplanation, Ea as hasVisualState, Ei as themeExtensionsEqual, En as CARET_BLINK_MS, Er as UI_SUBGRID_AXES, Et as spannedRunsFor, F as constraintsEqual, Fa as UiKeyboardEvent, Fi as spacingEqual, Fn as nextCaretToggle, Fr as transformsEqual, Ft as fillTextRects, G as MainAxisAlignment, Ga as UiBinding, Gi as defaultTypography, Gn as offsetAtX, Gr as isFrLength, Gt as fontStackFor, H as sizeGridTracks, Ha as wheelDeltaYOf, Hi as defaultShapes, Hn as lineIndexForOffset, Hr as auto, Ht as textRunDecorations, I as tightenConstraints, Ia as UiPinchEvent, Ii as spacingSteps, In as syncEditorValue, Ir as MAX_GRADIENT_STOPS, It as layoutTextLines, J as parseMainAxisAlignment, Ji as defaultTextStyle, Jn as EditableTextModel, Jr as minmax, Jt as DEFAULT_FONT_SIZE, K as parseAlignContent, Ki as isTypographyRole, Kn as offsetForVerticalMove, Kr as isMinMaxTrack, Kt as registerFontStack, L as MAX_MEASURES_PER_CHILD, La as UiPointerEvent, Li as defaultShadows, Ln as resolveColorValue, Lr as gradientPaint, Lt as runCanvasStyleInto, M as LayoutRecord, Ma as UiFocusEvent, Mi as densityFactors, Mn as isEditableNode, Mr as parseTransform, Mt as buildFontString, N as Constraints, Na as UiGestureEvent, Ni as defaultSpacing, Nn as isMultiline, Nr as transform, Nt as createCanvasTextStyle, O as labelNode, Oa as visualStatesEqual, Oi as contrastRatio, On as EDITOR_PROP, Or as inputsEqual, Ot as layoutParagraph, P as clampSize, Pa as UiInputEvent, Pi as scaleSpacing, Pn as isReadOnly, Pr as transformIsIdentity, Pt as drawText, Q as pointerDeviceOf, Qi as spannedTextOf, Qn as nextGraphemeEnd, Qr as UiBorderRadiuses, Qt as computeObjectFitRect, R as isLayoutProtocol, Ra as UiTextChangeEvent, Ri as shadowsEqual, Rn as isVideoSurface, Rr as gradientsEqual, Rt as runFontString, S as SCROLLBAR_LINGER_MS, Sa as writeDeclaredProperty, Si as defineThemeExtension, Sn as linkHoverOf, Sr as normalizeStates, St as selectionRectsIn, T as formatConstraints, Ta as defaultVisualState, Ti as themeExtension, Tn as setLinkHover, Tr as validateStates, Tt as ParagraphTextMeasurer, U as AlignContent, Ua as UiNodeType, Ui as isShapeName, Un as lineLimit, Ur as fr, Ut as bumpFontStack, V as placeGridItems, Va as noKeyModifiers, Vi as boxShadowsEqual, Vn as lineIndexAtY, Vr as validateGradient, Vt as textRunBackgrounds, W as CrossAxisAlignment, Wa as DirtyFlags, Wi as shapesEqual, Wn as offsetAtPoint, Wr as isAutoLength, Wt as clearFontStacks, X as UiPlatformAdapter, Xi as resolvedSpansOf, Xn as lineEndAt, Xr as repeat, Xt as DEFAULT_TEXT_COLOR, Y as CanvasPlatformSurface, Yi as flattenTextSpans, Yn as graphemeBoundaries, Yr as percent, Yt as DEFAULT_FONT_WEIGHT, Z as capturePointer, Zi as spanAtOffset, Zn as lineStartAt, Zr as resolveLength, Zt as colorToCss, _ as LABEL_PADDING_X, _a as UiNode, _i as darkTheme, _n as isNodeInert, _r as UiProperties, _t as UiInputDispatcher, a as PaintPictureCache, aa as UiBasicColors, ai as uniformBorderRadius, an as TEXT_MATCHES_PROP, ar as resolveProperty, at as isScrollContainer, b as LayoutEngine, ba as overrideSources, bi as withContrast, bn as clearLinkHover, br as isUiRole, bt as paragraphGeometry, c as EMPTY_RECORDING, ca as colorValuesEqual, ci as UiEnvironmentKeys, cn as setMatchRanges, cr as UiGraph, ct as UiFocusManager, d as tracePathData, da as parseColor, di as noInsets, dn as selectableTextNodes, dr as getPropertyNames, dt as SCROLLBAR_INSET, ea as textSpansEqual, ei as borderRadiusCorners, en as isPaintVisible, er as previousGraphemeStart, et as touchActionFor, f as ScaledImageCache, fa as rgb8, fi as observeViewportInsets, fn as selectableTextOf, fr as inheritedPropertyFlags, ft as SCROLLBAR_MIN_THUMB, g as paintsAfterChildren, ga as UiEnvironment, gi as unknownContainerSize, gn as isNodeHitTestable, gr as propertyValuesEqualByName, gt as scrollbarZoneAt, h as hasDecorationPhase, ha as findEnvironmentKey, hi as containerBands, hn as isNodeFocusable, hr as propertyIsInherited, ht as scrollbarThumbs, i as traceRoundedRect, ia as lightColors, ii as normalizeBorderRadius, in as DEFAULT_LINE_HEIGHT_FACTOR, ir as resolveNumber, it as UiWheelController, j as contentOffset, ja as UiEventType, ji as relativeLuminance, jn as editorOf, jr as UiTransforms, jt as applyCanvasTextStyle, k as withOverrideSource, ka as MOUSE_POINTER, ki as highContrastColors, kn as caretVisibleAt, kr as paintValuesEqual, kt as proportionalFontMetrics, l as PaintRecorder, la as colorsEqual, li as UiInsetRegistry, ln as TEXT_SELECTION_PROP, lr as closestPropertyName, lt as UiHitTester, m as decorationRect, ma as createEnvironmentKey, mi as bandOf, mn as setSelectionRange, mr as propertyEffects, mt as scrollbarThumb, n as createCanvasSurface, na as colorsEqualPalette, ni as borderRadiusIsZero, nn as normalizeVerticalAlign, nr as wordRangeAt, nt as UiGestureRecognizer, o as paintPictures, oa as colorToHex, oi as defineProperty, on as clearMatchRanges, or as resolvePropertyByName, ot as UiKeyboardController, p as decorationColor, pa as rgba, pi as UiContainerSizeSource, pn as selectionRangeOf, pr as inheritedPropertyNames, pt as SCROLLBAR_THICKNESS, q as parseCrossAxisAlignment, qi as typographyEqual, qn as selectionRects, qr as isPercentLength, qt as DEFAULT_FONT_FAMILY, r as Canvas2DRenderer, ra as darkColors, ri as borderRadiusValuesEqual, rn as resolvePaintState, rr as resolveBoolean, rt as UiPinchRecognizer, s as PaintTarget, sa as colorToRgba, si as propertyValuesEqual, sn as matchRangesOf, sr as resolveString, st as UiPointerController, t as CanvasSurface, ta as textStylesEqual, ti as borderRadiusEqual, tn as normalizeTextAlign, tr as previousWordStart, tt as UiTouchScroller, u as replayPaint, ua as normalizeColor, ui as insetsEqual, un as clearSelectionRange, ur as findPropertyDefinition, ut as SCROLLBAR_HOVER_ZONE, v as drawOverlayShapes, va as DirtyNodeSet, vi as lightTheme, vn as isNodeSelectable, vr as UI_ROLES, vt as hasDrawnText, w as describeOverrides, wa as UiVisualState, wi as noThemeExtensions, wn as linkRunAtPointIn, wr as validateRole, wt as CharacterCountTextMeasurer, x as SCROLLBAR_FADE_MS, xa as resetOverrideWarnings, xi as withDensity, xn as hasTextLinks, xr as isUiSemanticState, xt as paragraphGeometryFrom, y as labelOrigin, ya as clearOverrideProperty, yi as themesEqual, yn as resolveCursor, yr as UI_SEMANTIC_STATES, yt as offsetAtPointIn, z as FlexDirection, za as UiWheelDeltaMode, zi as boxShadow, zn as videoFrameSize, zr as linearGradient, zt as spanOf } from "./CanvasSurface-5dU-U3qQ.js";
1
+ import { $ as prepareInputSurface, $a as UiBinding, $i as defaultTextStyle, $n as EditableTextModel, $r as minmax, $t as DEFAULT_FONT_WEIGHT, A as accumulatedOffsetTo, Aa as UiVisualState, Ai as themeExtension, An as setLinkHover, Ar as validateStates, At as proportionalFontMetrics, B as parseFlexDirection, Ba as UiInputEvent, Bi as spacingEqual, Bn as nextCaretToggle, Br as transformsEqual, Bt as layoutTextLines, C as describeLength, Ca as UiNode, Ci as lightTheme, Cn as isNodeTabStop, Cr as UI_ROLES, Ct as selectionRectsIn, D as formatNumber, Da as resetOverrideWarnings, Di as defineThemeExtension, Dn as linkHoverOf, Dr as normalizeStates, Dt as spannedRunsFor, E as formatExplanation, Ea as overrideSources, Ei as withDensity, En as hasTextLinks, Er as isUiSemanticState, Et as ParagraphTextMeasurer, F as constraintsEqual, Fa as MOUSE_POINTER, Fi as raiseContrast, Fn as editorFor, Fr as pathValuesEqual, Ft as applyCanvasTextStyle, G as MainAxisAlignment, Ga as UiSelectionChangeEvent, Gi as boxShadowArraysEqual, Gn as caretRectFor, Gr as radialGradient, Gt as textRunBackgrounds, H as sizeGridTracks, Ha as UiPasteEvent, Hi as defaultShadows, Hn as resolveColorValue, Hr as gradientPaint, Ht as runFontString, I as tightenConstraints, Ia as UiBeforeInputEvent, Ii as relativeLuminance, In as editorOf, Ir as UiTransforms, It as buildFontString, J as parseMainAxisAlignment, Ja as UiWheelEvent, Ji as isShapeName, Jn as lineLimit, Jr as fr, Jt as clearFontStacks, K as parseAlignContent, Ka as UiTextChangeEvent, Ki as boxShadowsEqual, Kn as lineIndexAtY, Kr as validateGradient, Kt as textRunDecorations, L as MAX_MEASURES_PER_CHILD, La as UiEventType, Li as densityFactors, Ln as isEditableNode, Lr as parseTransform, Lt as createCanvasTextStyle, M as LayoutRecord, Ma as hasVisualState, Mi as withThemeExtension, Mn as CARET_WIDTH, Mr as validateSubgrid, Mt as detectEditingPlatform, N as Constraints, Na as visualState, Ni as contrastRatio, Nn as EDITOR_PROP, Nr as inputsEqual, Nt as isPrintable, O as labelNode, Oa as writeDeclaredProperty, Oi as hasThemeExtension, On as linkOf, Or as statesEqual, Ot as ELLIPSIS, P as clampSize, Pa as visualStatesEqual, Pi as highContrastColors, Pn as caretVisibleAt, Pr as paintValuesEqual, Pt as EditableLayout, Q as pointerDeviceOf, Qa as DirtyFlags, Qi as typographyEqual, Qn as selectionRects, Qr as isPercentLength, Qt as DEFAULT_FONT_SIZE, R as isLayoutProtocol, Ra as UiFocusEvent, Ri as defaultSpacing, Rn as isMultiline, Rr as transform, Rt as drawText, S as SCROLLBAR_LINGER_MS, Sa as UiEnvironment, Si as darkTheme, Sn as isNodeSelectable, Sr as UiProperties, St as paragraphGeometryFrom, T as formatConstraints, Ta as clearOverrideProperty, Ti as withContrast, Tn as clearLinkHover, Tr as isUiRole, Tt as CharacterCountTextMeasurer, U as AlignContent, Ua as UiPinchEvent, Ui as shadowsEqual, Un as isVideoSurface, Ur as gradientsEqual, Ut as spanOf, V as placeGridItems, Va as UiKeyboardEvent, Vi as spacingSteps, Vn as syncEditorValue, Vr as MAX_GRADIENT_STOPS, Vt as runCanvasStyleInto, W as CrossAxisAlignment, Wa as UiPointerEvent, Wi as boxShadow, Wn as videoFrameSize, Wr as linearGradient, Wt as textMeasureRequest, X as UiPlatformAdapter, Xa as wheelDeltaYOf, Xi as defaultTypography, Xn as offsetAtX, Xr as isFrLength, Xt as registerFontStack, Y as CanvasPlatformSurface, Ya as noKeyModifiers, Yi as shapesEqual, Yn as offsetAtPoint, Yr as isAutoLength, Yt as fontStackFor, Z as capturePointer, Za as UiNodeType, Zi as isTypographyRole, Zn as offsetForVerticalMove, Zr as isMinMaxTrack, Zt as DEFAULT_FONT_FAMILY, _ as LABEL_PADDING_X, _a as parseColor, _i as observeViewportInsets, _n as selectionRangeOf, _r as inheritedPropertyFlags, _t as scrollbarZoneAt, a as PaintPictureCache, aa as textContentOf, ai as borderRadiusCorners, an as normalizeTextAlign, ar as previousGraphemeStart, at as UiWheelController, b as LayoutEngine, ba as createEnvironmentKey, bi as containerBands, bn as isNodeHitTestable, br as propertyIsInherited, bt as offsetAtPointIn, c as EMPTY_RECORDING, ca as colorsEqualPalette, ci as borderRadiusValuesEqual, cn as DEFAULT_LINE_HEIGHT_FACTOR, cr as resolveBoolean, ct as UiPointerController, d as tracePathData, da as UiBasicColors, di as defineProperty, dn as matchRangesOf, dr as resolvePropertyByName, dt as SCROLLBAR_HOVER_ZONE, ea as editableSpansOf, ei as percent, en as DEFAULT_TEXT_COLOR, er as graphemeBoundaries, et as touchActionFor, f as ScaledImageCache, fa as colorToHex, fi as propertyValuesEqual, fn as setMatchRanges, fr as resolveString, ft as SCROLLBAR_INSET, g as paintsAfterChildren, ga as normalizeColor, gi as noInsets, gn as selectableTextOf, gr as getPropertyNames, gt as scrollbarThumbs, h as hasDecorationPhase, ha as colorsEqual, hi as insetsEqual, hn as selectableTextNodes, hr as findPropertyDefinition, ht as scrollbarThumb, i as traceRoundedRect, ia as spannedTextOf, ii as borderRadius, in as isPaintVisible, ir as nextWordEnd, it as UiPinchRecognizer, j as contentOffset, ja as defaultVisualState, ji as themeExtensionsEqual, jn as CARET_BLINK_MS, jr as UI_SUBGRID_AXES, jt as commandForKey, k as withOverrideSource, ka as writeOverrideProperty, ki as noThemeExtensions, kn as linkRunAtPointIn, kr as validateRole, kt as layoutParagraph, l as PaintRecorder, la as darkColors, li as normalizeBorderRadius, ln as TEXT_MATCHES_PROP, lr as resolveNumber, lt as UiFocusManager, m as decorationRect, ma as colorValuesEqual, mi as UiInsetRegistry, mn as clearSelectionRange, mr as closestPropertyName, mt as SCROLLBAR_THICKNESS, n as createCanvasSurface, na as resolvedSpansOf, ni as resolveLength, nn as computeObjectFitRect, nr as lineStartAt, nt as UiTouchScroller, o as paintPictures, oa as textSpansEqual, oi as borderRadiusEqual, on as normalizeVerticalAlign, or as previousWordStart, ot as isScrollContainer, p as decorationColor, pa as colorToRgba, pi as UiEnvironmentKeys, pn as TEXT_SELECTION_PROP, pr as UiGraph, pt as SCROLLBAR_MIN_THUMB, q as parseCrossAxisAlignment, qa as UiWheelDeltaMode, qi as defaultShapes, qn as lineIndexForOffset, qr as auto, qt as bumpFontStack, r as Canvas2DRenderer, ra as spanAtOffset, ri as UiBorderRadiuses, rn as createPaintState, rr as nextGraphemeEnd, rt as UiGestureRecognizer, s as PaintTarget, sa as textStylesEqual, si as borderRadiusIsZero, sn as resolvePaintState, sr as wordRangeAt, st as UiKeyboardController, t as CanvasSurface, ta as flattenTextSpans, ti as repeat, tn as colorToCss, tr as lineEndAt, tt as UiEditingController, u as replayPaint, ua as lightColors, ui as uniformBorderRadius, un as clearMatchRanges, ur as resolveProperty, ut as UiHitTester, v as drawOverlayShapes, va as rgb8, vi as UiContainerSizeSource, vn as setSelectionRange, vr as inheritedPropertyNames, vt as UiInputDispatcher, w as describeOverrides, wa as DirtyNodeSet, wi as themesEqual, wn as resolveCursor, wr as UI_SEMANTIC_STATES, wt as wordRangeIn, x as SCROLLBAR_FADE_MS, xa as findEnvironmentKey, xi as unknownContainerSize, xn as isNodeInert, xr as propertyValuesEqualByName, xt as paragraphGeometry, y as labelOrigin, ya as rgba, yi as bandOf, yn as isNodeFocusable, yr as propertyEffects, yt as hasDrawnText, z as FlexDirection, za as UiGestureEvent, zi as scaleSpacing, zn as isReadOnly, zr as transformIsIdentity, zt as fillTextRects } from "./CanvasSurface-ifHkg0-g.js";
2
2
  import { BehaviorSubject, EMPTY, Subject, combineLatest, distinctUntilChanged, isObservable as isObservable$1, map, of } from "rxjs";
3
3
  //#region src/animation/UiEasing.ts
4
4
  /** Progress equals time. What a spinner and a sweep want. */
@@ -1425,9 +1425,11 @@ const EVENT_PROPS = {
1425
1425
  onKeyDown: "keydown",
1426
1426
  onKeyUp: "keyup",
1427
1427
  onFocus: "focus",
1428
+ onPaste: "paste",
1428
1429
  onBlur: "blur",
1429
1430
  onBeforeInput: "beforeinput",
1430
1431
  onInput: "input",
1432
+ onSelectionChange: "selectionchange",
1431
1433
  onClick: "click",
1432
1434
  onLongPress: "longpress",
1433
1435
  onDragStart: "dragstart",
@@ -2883,7 +2885,7 @@ var UiVirtualWindow = class {
2883
2885
  const total = this.totalExtent();
2884
2886
  const header = viewport.lead ?? 0;
2885
2887
  this.checkViewport(viewport.extent, total + header);
2886
- const scrolled = clamp$1(viewport.scroll + scrollAdjust, 0, Math.max(0, total + header - viewport.extent));
2888
+ const scrolled = clamp$2(viewport.scroll + scrollAdjust, 0, Math.max(0, total + header - viewport.extent));
2887
2889
  const scroll = Math.max(0, scrolled - header);
2888
2890
  let first = 0;
2889
2891
  let last = -1;
@@ -3054,7 +3056,7 @@ function markRow(row, props, subject) {
3054
3056
  }
3055
3057
  };
3056
3058
  }
3057
- function clamp$1(value, min, max) {
3059
+ function clamp$2(value, min, max) {
3058
3060
  return Math.min(Math.max(value, min), max);
3059
3061
  }
3060
3062
  function assertCount(count) {
@@ -3074,10 +3076,10 @@ function assertCount(count) {
3074
3076
  const lazySource = defineModifier({
3075
3077
  name: "lazySource",
3076
3078
  attach(host, args) {
3077
- follow$1(host, args.count, (value) => args.window.setCount(value));
3079
+ follow$2(host, args.count, (value) => args.window.setCount(value));
3078
3080
  if (args.revision !== void 0) {
3079
3081
  let first = true;
3080
- follow$1(host, args.revision, () => {
3082
+ follow$2(host, args.revision, () => {
3081
3083
  if (first) {
3082
3084
  first = false;
3083
3085
  return;
@@ -3087,7 +3089,7 @@ const lazySource = defineModifier({
3087
3089
  }
3088
3090
  }
3089
3091
  });
3090
- function follow$1(host, source, apply) {
3092
+ function follow$2(host, source, apply) {
3091
3093
  if (!isObservable$1(source)) {
3092
3094
  apply(source);
3093
3095
  return;
@@ -3287,120 +3289,609 @@ function Responsive(props, build) {
3287
3289
  }, [children]);
3288
3290
  }
3289
3291
  //#endregion
3290
- //#region src/editing/EditingKeymap.ts
3291
- /**
3292
- * The platform the runtime is on, decided once. Available in a worker
3293
- * too, since `navigator.userAgent` is; tests set it explicitly.
3294
- */
3295
- function detectEditingPlatform() {
3296
- const agent = typeof navigator !== "undefined" ? navigator.userAgent : "";
3297
- return /Mac|iPhone|iPad|iPod/.test(agent) ? "mac" : "other";
3298
- }
3299
- /**
3300
- * Maps a key press to an editing command, following the host's
3301
- * conventions:
3302
- *
3303
- * Mac — Option steps by word, Command jumps to line and document
3304
- * ends, Command+Backspace deletes to the line start.
3305
- * Others — Control steps by word, Home/End and Control+Home/End are
3306
- * the line and document ends.
3307
- *
3308
- * Shift extends every move. Enter is a newline (the controller drops
3309
- * it in a single-line field). A printable key becomes `insert` only
3310
- * when `textFromKeys` is set: a host with an editing proxy delivers
3311
- * text through `beforeinput` instead, where the IME and dead keys
3312
- * have already been resolved.
3313
- */
3314
- function commandForKey(key, modifiers, platform, textFromKeys) {
3315
- const mac = platform === "mac";
3316
- const primary = mac ? modifiers.meta : modifiers.ctrl;
3317
- const word = mac ? modifiers.alt : modifiers.ctrl;
3318
- const extend = modifiers.shift;
3319
- switch (key) {
3320
- case "ArrowLeft":
3321
- case "ArrowRight": {
3322
- const direction = key === "ArrowLeft" ? -1 : 1;
3323
- if (mac && modifiers.meta) return {
3324
- kind: "move",
3325
- unit: "line",
3326
- direction,
3327
- extend
3328
- };
3329
- return {
3330
- kind: "move",
3331
- unit: word ? "word" : "grapheme",
3332
- direction,
3333
- extend
3334
- };
3292
+ //#region src/composition/UiVirtualSheet.ts
3293
+ /** The sheet sits on its scroll container under this property. */
3294
+ const VIRTUAL_SHEET_PROP = "virtualSheet";
3295
+ const EMPTY_RANGE = {
3296
+ firstRow: 0,
3297
+ lastRow: -1,
3298
+ firstColumn: 0,
3299
+ lastColumn: -1
3300
+ };
3301
+ var UiVirtualSheet = class {
3302
+ children$ = new BehaviorSubject([]);
3303
+ /**
3304
+ * The range now mounted, for whoever has to fetch the values in it.
3305
+ *
3306
+ * This is the whole reason the window is reachable from outside: the
3307
+ * render thread does not hold the cells, so the range it settles on
3308
+ * each frame is what it asks the application thread for. Emits only
3309
+ * when the range changes, so binding a command to it does not send
3310
+ * one per frame.
3311
+ */
3312
+ range$ = new BehaviorSubject(EMPTY_RANGE);
3313
+ rowCount;
3314
+ columnCount;
3315
+ rowOverscan;
3316
+ columnOverscan;
3317
+ rowHeight;
3318
+ gutterWidth;
3319
+ headerHeight;
3320
+ frozenRows;
3321
+ frozenColumns;
3322
+ renderRow;
3323
+ renderHeader;
3324
+ extendRange;
3325
+ /** One width per column, or undefined when every column is the same. */
3326
+ widths;
3327
+ /** `offsets[c]` is where column `c` starts, measured past the gutter. */
3328
+ offsets = [];
3329
+ uniformWidth = 0;
3330
+ /**
3331
+ * The rows that are not `rowHeight` tall, in row order.
3332
+ *
3333
+ * Three parallel arrays rather than a map of objects: this is
3334
+ * searched on every hit test and on every frame that scrolls, and
3335
+ * the arrays are read in step.
3336
+ */
3337
+ exceptionRows = [];
3338
+ exceptionHeights = [];
3339
+ /** `exceptionStarts[i]` is where `exceptionRows[i]` starts, past the header. */
3340
+ exceptionStarts = [];
3341
+ range = EMPTY_RANGE;
3342
+ viewport;
3343
+ /** Rows built for the current column range, so a vertical scroll rebuilds one row. */
3344
+ mountedRows = /* @__PURE__ */ new Map();
3345
+ /** The frozen rows, kept separately because they never leave. */
3346
+ frozenMounted = /* @__PURE__ */ new Map();
3347
+ mountedHeader;
3348
+ constructor(options, renderRow, renderHeader) {
3349
+ this.rowCount = typeof options.rowCount === "number" ? options.rowCount : 0;
3350
+ this.columnCount = typeof options.columnCount === "number" ? options.columnCount : 0;
3351
+ this.rowHeight = positive("rowHeight", options.rowHeight);
3352
+ this.adoptHeights(options.rowHeights);
3353
+ this.gutterWidth = Math.max(0, options.gutterWidth ?? 0);
3354
+ this.headerHeight = Math.max(0, options.headerHeight ?? 0);
3355
+ this.frozenRows = Math.max(0, Math.floor(options.frozenRows ?? 0));
3356
+ this.frozenColumns = Math.max(0, Math.floor(options.frozenColumns ?? 0));
3357
+ this.rowOverscan = typeof options.rowOverscan === "number" ? options.rowOverscan : 3;
3358
+ this.columnOverscan = typeof options.columnOverscan === "number" ? options.columnOverscan : 2;
3359
+ this.renderRow = renderRow;
3360
+ this.renderHeader = renderHeader;
3361
+ this.extendRange = options.extendRange;
3362
+ this.adoptWidths(options.columnWidth);
3363
+ this.viewport = {
3364
+ scrollX: 0,
3365
+ scrollY: 0,
3366
+ width: options.initialViewport?.width ?? 800,
3367
+ height: options.initialViewport?.height ?? 600
3368
+ };
3369
+ this.recompute();
3370
+ }
3371
+ /**
3372
+ * New column widths, as a drag on a header edge produces.
3373
+ *
3374
+ * The prefix sum is rebuilt whole. It is one pass over the columns
3375
+ * and it happens once per frame of a drag at most, against a
3376
+ * per-column offset that would otherwise have to be corrected
3377
+ * everywhere downstream of the column that moved.
3378
+ */
3379
+ setColumnWidths(widths) {
3380
+ this.adoptWidths(widths);
3381
+ this.invalidate();
3382
+ }
3383
+ adoptWidths(widths) {
3384
+ if (typeof widths === "number") {
3385
+ this.uniformWidth = positive("columnWidth", widths);
3386
+ this.widths = void 0;
3387
+ this.offsets = [];
3388
+ return;
3335
3389
  }
3336
- case "ArrowUp":
3337
- case "ArrowDown": {
3338
- const direction = key === "ArrowUp" ? -1 : 1;
3339
- if (mac && modifiers.meta) return {
3340
- kind: "move",
3341
- unit: "document",
3342
- direction,
3343
- extend
3344
- };
3345
- return {
3346
- kind: "move",
3347
- unit: "vertical",
3348
- direction,
3349
- extend
3350
- };
3390
+ const owned = widths.slice();
3391
+ for (const width of owned) if (!(width >= 0)) throw new Error(`LazySheet column widths must not be negative, got ${String(width)}.`);
3392
+ this.widths = owned;
3393
+ this.offsets = Array.from({ length: owned.length + 1 });
3394
+ let running = 0;
3395
+ for (let column = 0; column < owned.length; column++) {
3396
+ this.offsets[column] = running;
3397
+ running += owned[column];
3398
+ }
3399
+ this.offsets[owned.length] = running;
3400
+ }
3401
+ /**
3402
+ * New row heights, as hiding a row or autofitting one produces.
3403
+ *
3404
+ * The exceptions are rebuilt whole, which is one pass over *them*
3405
+ * and not over the rows — the whole reason they are sparse. A
3406
+ * height of zero is a hidden row and is allowed; a negative one is
3407
+ * not.
3408
+ */
3409
+ setRowHeights(heights) {
3410
+ this.adoptHeights(heights);
3411
+ this.invalidate();
3412
+ }
3413
+ /** The height every row has unless it is an exception. */
3414
+ setRowHeight(height) {
3415
+ const next = positive("rowHeight", height);
3416
+ if (next === this.rowHeight) return;
3417
+ this.rowHeight = next;
3418
+ this.rebuildExceptionStarts();
3419
+ this.invalidate();
3420
+ }
3421
+ adoptHeights(heights) {
3422
+ this.exceptionRows = [];
3423
+ this.exceptionHeights = [];
3424
+ if (heights !== void 0) {
3425
+ const rows = [...heights.keys()].sort((a, b) => a - b);
3426
+ for (const row of rows) {
3427
+ const height = heights.get(row) ?? 0;
3428
+ if (!(height >= 0)) throw new Error(`LazySheet row heights must not be negative, got ${String(height)}.`);
3429
+ if (height !== this.rowHeight) {
3430
+ this.exceptionRows.push(row);
3431
+ this.exceptionHeights.push(height);
3432
+ }
3433
+ }
3434
+ }
3435
+ this.rebuildExceptionStarts();
3436
+ }
3437
+ /**
3438
+ * Where each exception begins, and what they cost in total.
3439
+ *
3440
+ * `start` is the row's own offset past the header, so the inverse —
3441
+ * `rowAt` — can binary search it directly rather than reconstructing
3442
+ * it per probe.
3443
+ */
3444
+ rebuildExceptionStarts() {
3445
+ this.exceptionStarts = Array.from({ length: this.exceptionRows.length });
3446
+ let extra = 0;
3447
+ for (let index = 0; index < this.exceptionRows.length; index++) {
3448
+ this.exceptionStarts[index] = this.exceptionRows[index] * this.rowHeight + extra;
3449
+ extra += this.exceptionHeights[index] - this.rowHeight;
3450
+ }
3451
+ }
3452
+ /** The index of the last exception starting at or before an offset, or -1. */
3453
+ exceptionAt(offset) {
3454
+ let low = 0;
3455
+ let high = this.exceptionStarts.length - 1;
3456
+ let found = -1;
3457
+ while (low <= high) {
3458
+ const mid = low + high >> 1;
3459
+ if (this.exceptionStarts[mid] <= offset) {
3460
+ found = mid;
3461
+ low = mid + 1;
3462
+ } else high = mid - 1;
3463
+ }
3464
+ return found;
3465
+ }
3466
+ /** How many exceptions come before a row, as an index into them. */
3467
+ exceptionsBefore(row) {
3468
+ let low = 0;
3469
+ let high = this.exceptionRows.length;
3470
+ while (low < high) {
3471
+ const mid = low + high >> 1;
3472
+ if (this.exceptionRows[mid] < row) low = mid + 1;
3473
+ else high = mid;
3351
3474
  }
3352
- case "Home": return {
3353
- kind: "move",
3354
- unit: modifiers.ctrl ? "document" : "line",
3355
- direction: -1,
3356
- extend
3475
+ return low;
3476
+ }
3477
+ /** The height of one row. Zero when it is hidden. */
3478
+ rowHeightOf(row) {
3479
+ if (this.exceptionRows.length === 0) return this.rowHeight;
3480
+ const index = this.exceptionsBefore(row);
3481
+ return this.exceptionRows[index] === row ? this.exceptionHeights[index] : this.rowHeight;
3482
+ }
3483
+ /** Where a row starts, measured past the header. */
3484
+ rowOffsetOf(row) {
3485
+ const clamped = clamp$1(row, 0, this.rowCount);
3486
+ return clamped * this.rowHeight + this.extraWithin(clamped);
3487
+ }
3488
+ /** Total height of the rows, not counting the header. */
3489
+ get rowsHeight() {
3490
+ return this.rowCount * this.rowHeight + this.extraWithin(this.rowCount);
3491
+ }
3492
+ /**
3493
+ * What the exceptions before `row` add to or take off the offset.
3494
+ *
3495
+ * Read off the last exception before it rather than summed: the
3496
+ * start of an exception already carries everything before it, so
3497
+ * its end minus where it *would* have ended is the running total.
3498
+ */
3499
+ extraWithin(row) {
3500
+ const before = this.exceptionsBefore(row);
3501
+ if (before === 0) return 0;
3502
+ const index = before - 1;
3503
+ return this.exceptionStarts[index] + this.exceptionHeights[index] - (this.exceptionRows[index] + 1) * this.rowHeight;
3504
+ }
3505
+ /** The width of one column. */
3506
+ widthOf(column) {
3507
+ if (this.widths === void 0) return this.uniformWidth;
3508
+ return this.widths[column] ?? 0;
3509
+ }
3510
+ /** Where a column starts, measured past the gutter. */
3511
+ offsetOf(column) {
3512
+ if (this.widths === void 0) return column * this.uniformWidth;
3513
+ const clamped = clamp$1(column, 0, this.offsets.length - 1);
3514
+ return this.offsets[clamped] ?? 0;
3515
+ }
3516
+ /** Total width of the columns, not counting the gutter. */
3517
+ get columnsWidth() {
3518
+ return this.widths === void 0 ? this.columnCount * this.uniformWidth : this.offsets[this.widths.length] ?? 0;
3519
+ }
3520
+ get contentWidth() {
3521
+ return this.gutterWidth + this.columnsWidth;
3522
+ }
3523
+ /** Where the scrolled columns begin, past the gutter and the frozen ones. */
3524
+ get frozenWidth() {
3525
+ return this.offsetOf(this.frozenColumns);
3526
+ }
3527
+ get contentHeight() {
3528
+ return this.headerHeight + this.rowsHeight;
3529
+ }
3530
+ /**
3531
+ * The cell under a point in the scroll container's own coordinates.
3532
+ *
3533
+ * The window already knows where everything is and how far it is
3534
+ * scrolled, which is the whole of this: a drag towards a cell — a
3535
+ * fill handle, a selection being stretched — is usually heading for
3536
+ * one that has not been mounted, so there is no node to hit-test and
3537
+ * arithmetic is the only answer available.
3538
+ */
3539
+ cellAt(x, y) {
3540
+ return {
3541
+ row: this.rowAt(this.viewport.scrollY + y),
3542
+ column: this.columnAt(this.viewport.scrollX + x)
3357
3543
  };
3358
- case "End": return {
3359
- kind: "move",
3360
- unit: modifiers.ctrl ? "document" : "line",
3361
- direction: 1,
3362
- extend
3544
+ }
3545
+ /**
3546
+ * The row at a content offset, clamped to the sheet.
3547
+ *
3548
+ * A division when every row is the default height — the common case
3549
+ * by a very long way — and otherwise a binary search over the
3550
+ * exceptions followed by a division inside the uniform run that
3551
+ * follows the one it lands in.
3552
+ *
3553
+ * A hidden row occupies no offset at all, so the offset where it
3554
+ * *would* be belongs to the row after it. That falls out of the
3555
+ * arithmetic rather than being special-cased: a zero-height
3556
+ * exception starts and ends at the same place, so the point is
3557
+ * never inside it.
3558
+ */
3559
+ rowAt(offset) {
3560
+ const last = Math.max(0, this.rowCount - 1);
3561
+ const past = offset - this.headerHeight;
3562
+ if (this.exceptionRows.length === 0) return clamp$1(Math.floor(past / this.rowHeight), 0, last);
3563
+ if (past <= 0) return 0;
3564
+ const index = this.exceptionAt(past);
3565
+ if (index === -1) return clamp$1(Math.floor(past / this.rowHeight), 0, last);
3566
+ const start = this.exceptionStarts[index];
3567
+ const height = this.exceptionHeights[index];
3568
+ if (past < start + height) return clamp$1(this.exceptionRows[index], 0, last);
3569
+ return clamp$1(this.exceptionRows[index] + 1 + Math.floor((past - start - height) / this.rowHeight), 0, last);
3570
+ }
3571
+ /**
3572
+ * The column at a content offset, clamped to the sheet.
3573
+ *
3574
+ * A binary search over the prefix sum when the columns differ, and
3575
+ * a division when they do not — the uniform case is the common one
3576
+ * and should not pay for the general one.
3577
+ */
3578
+ columnAt(offset) {
3579
+ const last = Math.max(0, this.columnCount - 1);
3580
+ const past = offset - this.gutterWidth;
3581
+ if (this.widths === void 0) return clamp$1(Math.floor(past / this.uniformWidth), 0, last);
3582
+ if (past <= 0) return 0;
3583
+ if (past >= this.columnsWidth) return last;
3584
+ let low = 0;
3585
+ let high = last;
3586
+ while (low < high) {
3587
+ const mid = low + high + 1 >> 1;
3588
+ if (this.offsets[mid] <= past) low = mid;
3589
+ else high = mid - 1;
3590
+ }
3591
+ return low;
3592
+ }
3593
+ setRowCount(count) {
3594
+ if (count === this.rowCount) return;
3595
+ this.rowCount = Math.max(0, Math.floor(count));
3596
+ this.invalidate();
3597
+ }
3598
+ setColumnCount(count) {
3599
+ if (count === this.columnCount) return;
3600
+ this.columnCount = Math.max(0, Math.floor(count));
3601
+ this.invalidate();
3602
+ }
3603
+ /**
3604
+ * The bands, which the spike tunes from the screen.
3605
+ *
3606
+ * A band is the only defence against the round trip to the
3607
+ * application thread: rows inside it already hold their values when
3608
+ * the scroll reaches them. Widening it costs nodes on every frame
3609
+ * and cells on every publish, so what it should be is a measurement,
3610
+ * not a default — hence setters rather than a constant.
3611
+ */
3612
+ setOverscan(rows, columns) {
3613
+ const nextRows = Math.max(0, Math.floor(rows));
3614
+ const nextColumns = Math.max(0, Math.floor(columns));
3615
+ if (nextRows === this.rowOverscan && nextColumns === this.columnOverscan) return;
3616
+ this.rowOverscan = nextRows;
3617
+ this.columnOverscan = nextColumns;
3618
+ this.invalidate();
3619
+ }
3620
+ /**
3621
+ * How many rows and columns stay put while the rest scrolls.
3622
+ *
3623
+ * Freezing changes which rows are in the scrolled window, so every
3624
+ * mounted row is rebuilt — a row that was scrolling is now frozen
3625
+ * or the other way about.
3626
+ */
3627
+ setFrozen(rows, columns) {
3628
+ const nextRows = Math.max(0, Math.floor(rows));
3629
+ const nextColumns = Math.max(0, Math.floor(columns));
3630
+ if (nextRows === this.frozenRows && nextColumns === this.frozenColumns) return;
3631
+ this.frozenRows = nextRows;
3632
+ this.frozenColumns = nextColumns;
3633
+ this.invalidate();
3634
+ }
3635
+ /** Drops every mounted row and rebuilds against the last viewport. */
3636
+ invalidate() {
3637
+ this.mountedRows.clear();
3638
+ this.frozenMounted.clear();
3639
+ this.mountedHeader = void 0;
3640
+ this.range = EMPTY_RANGE;
3641
+ this.recompute();
3642
+ }
3643
+ /**
3644
+ * One step, run by the host each frame before layout.
3645
+ *
3646
+ * Nothing is measured and nothing is anchored: the offsets are exact,
3647
+ * so the rows a scroll reveals are placed correctly on the frame
3648
+ * that reveals them rather than corrected on the one after.
3649
+ */
3650
+ update(viewport) {
3651
+ this.viewport = viewport;
3652
+ this.recompute();
3653
+ }
3654
+ recompute() {
3655
+ const next = this.windowFor(this.viewport);
3656
+ if (sameRange(next, this.range)) return;
3657
+ if (next.firstColumn !== this.range.firstColumn || next.lastColumn !== this.range.lastColumn) {
3658
+ this.mountedRows.clear();
3659
+ this.frozenMounted.clear();
3660
+ this.mountedHeader = void 0;
3661
+ }
3662
+ this.range = next;
3663
+ this.children$.next(this.buildChildren());
3664
+ this.range$.next(next);
3665
+ }
3666
+ /**
3667
+ * The window, in content coordinates.
3668
+ *
3669
+ * The header and the gutter are *content*: they occupy the first
3670
+ * `headerHeight` pixels and the first `gutterWidth`, and `rowAt` and
3671
+ * `columnAt` already subtract them. So the range comes out slightly
3672
+ * generous at the near edges — the rows a header is covering are
3673
+ * mounted, and the columns behind the gutter are too — which is
3674
+ * correct rather than wasteful, since a sticky strip is drawn over
3675
+ * cells that are really there.
3676
+ */
3677
+ windowFor(viewport) {
3678
+ if (this.rowCount === 0 || this.columnCount === 0) return EMPTY_RANGE;
3679
+ const firstRow = Math.max(this.frozenRows, this.rowAt(viewport.scrollY) - this.rowOverscan);
3680
+ const lastRow = Math.min(this.rowCount - 1, this.rowAt(viewport.scrollY + Math.max(0, viewport.height)) + this.rowOverscan);
3681
+ const firstColumn = Math.max(this.frozenColumns, this.columnAt(viewport.scrollX) - this.columnOverscan);
3682
+ const lastColumn = Math.min(this.columnCount - 1, this.columnAt(viewport.scrollX + Math.max(0, viewport.width)) + this.columnOverscan);
3683
+ const range = {
3684
+ firstRow,
3685
+ lastRow,
3686
+ firstColumn,
3687
+ lastColumn
3363
3688
  };
3364
- case "Backspace":
3365
- if (mac && modifiers.meta) return {
3366
- kind: "delete",
3367
- unit: "line",
3368
- direction: -1
3369
- };
3370
- return {
3371
- kind: "delete",
3372
- unit: word ? "word" : "grapheme",
3373
- direction: -1
3374
- };
3375
- case "Delete": return {
3376
- kind: "delete",
3377
- unit: word ? "word" : "grapheme",
3378
- direction: 1
3689
+ if (this.extendRange === void 0) return range;
3690
+ const wanted = this.extendRange(range);
3691
+ return {
3692
+ firstRow: clamp$1(Math.min(wanted.firstRow, firstRow), this.frozenRows, firstRow),
3693
+ lastRow: clamp$1(Math.max(wanted.lastRow, lastRow), lastRow, this.rowCount - 1),
3694
+ firstColumn: clamp$1(Math.min(wanted.firstColumn, firstColumn), this.frozenColumns, firstColumn),
3695
+ lastColumn: clamp$1(Math.max(wanted.lastColumn, lastColumn), lastColumn, this.columnCount - 1)
3379
3696
  };
3380
- case "Enter": return primary ? null : { kind: "newline" };
3381
3697
  }
3382
- if (primary && !modifiers.alt) {
3383
- switch (key.toLowerCase()) {
3384
- case "a": return { kind: "selectAll" };
3385
- case "z": return modifiers.shift ? { kind: "redo" } : { kind: "undo" };
3386
- case "y": return mac ? null : { kind: "redo" };
3698
+ buildChildren() {
3699
+ const { firstRow, lastRow, firstColumn, lastColumn } = this.range;
3700
+ if (lastRow < firstRow) return [];
3701
+ const width = this.contentWidth;
3702
+ const lead = this.offsetOf(firstColumn) - this.frozenWidth;
3703
+ const children = [];
3704
+ if (this.renderHeader !== void 0) {
3705
+ this.mountedHeader ??= this.wrapHeader(this.renderHeader(firstColumn, lastColumn), width, lead);
3706
+ children.push(this.mountedHeader);
3707
+ }
3708
+ for (let row = 0; row < this.frozenRows && row < this.rowCount; row++) {
3709
+ let built = this.frozenMounted.get(row);
3710
+ if (built === void 0) {
3711
+ built = this.wrap(row, width, lead);
3712
+ this.frozenMounted.set(row, built);
3713
+ }
3714
+ children.push(built);
3715
+ }
3716
+ children.push(spacer("sheet:top", this.rowOffsetOf(firstRow) - this.rowOffsetOf(this.frozenRows)));
3717
+ for (const row of this.mountedRows.keys()) if (row < firstRow || row > lastRow) this.mountedRows.delete(row);
3718
+ for (let row = firstRow; row <= lastRow; row++) {
3719
+ let built = this.mountedRows.get(row);
3720
+ if (built === void 0) {
3721
+ built = this.wrap(row, width, lead);
3722
+ this.mountedRows.set(row, built);
3723
+ }
3724
+ children.push(built);
3387
3725
  }
3388
- return null;
3726
+ children.push(spacer("sheet:bottom", this.rowsHeight - this.rowOffsetOf(lastRow + 1)));
3727
+ return children;
3389
3728
  }
3390
- if (textFromKeys && isPrintable(key) && !modifiers.ctrl && !modifiers.meta) return {
3391
- kind: "insert",
3392
- text: key
3393
- };
3394
- return null;
3729
+ /**
3730
+ * The header, given the same geometry a row gets.
3731
+ *
3732
+ * Its own children are laid out exactly like a row's, so that a
3733
+ * column header lines up with the column under it without either
3734
+ * being told where the other is: the same leading spacer, the same
3735
+ * gutter-sized first cell, the same widths.
3736
+ */
3737
+ wrapHeader(built, width, lead) {
3738
+ if (!isRowElement(built)) throw new Error("A sheet's header must be a Row element, laid out like the rows it labels.");
3739
+ return {
3740
+ ...built,
3741
+ props: {
3742
+ ...built.props,
3743
+ key: "sheet:header",
3744
+ width,
3745
+ height: this.headerHeight,
3746
+ flexShrink: 0
3747
+ },
3748
+ children: this.withLead(built.children, lead)
3749
+ };
3750
+ }
3751
+ /**
3752
+ * A row's children with the leading spacer in the right place.
3753
+ *
3754
+ * With a gutter, the renderer's first child *is* the gutter: it sits
3755
+ * at content x 0 and the columns begin past it, so the spacer for
3756
+ * the columns scrolled out of view goes after it. Without one the
3757
+ * spacer goes first. Either way the renderer is handed a range of
3758
+ * columns and never has to know where the window is.
3759
+ */
3760
+ withLead(children, lead) {
3761
+ const pad = spacer("sheet:lead", void 0, lead);
3762
+ const stays = (this.gutterWidth > 0 ? 1 : 0) + this.frozenColumns;
3763
+ if (stays === 0) return [pad, ...children];
3764
+ return [
3765
+ ...children.slice(0, stays),
3766
+ pad,
3767
+ ...children.slice(stays)
3768
+ ];
3769
+ }
3770
+ /**
3771
+ * The row the renderer returned, given its place in the sheet.
3772
+ *
3773
+ * The leading spacer is prepended here rather than asked of the
3774
+ * renderer, because it is windowing bookkeeping and not a cell: it is
3775
+ * what puts column `firstColumn` at its own offset without the row
3776
+ * holding the columns to its left.
3777
+ */
3778
+ wrap(row, width, lead) {
3779
+ const built = this.renderRow(row, this.range.firstColumn, this.range.lastColumn);
3780
+ if (!isRowElement(built)) throw new Error(`A sheet row must be a Row element; row ${row} rendered something else. Its cells are laid out along one axis at widths the window already knows.`);
3781
+ return {
3782
+ ...built,
3783
+ props: {
3784
+ ...built.props,
3785
+ key: `sheet:row:${row}`,
3786
+ width,
3787
+ height: this.rowHeightOf(row),
3788
+ flexShrink: 0
3789
+ },
3790
+ children: this.withLead(built.children, lead)
3791
+ };
3792
+ }
3793
+ };
3794
+ function spacer(key, height, width) {
3795
+ return Box({
3796
+ key,
3797
+ height,
3798
+ width,
3799
+ flexShrink: 0
3800
+ });
3801
+ }
3802
+ function isRowElement(child) {
3803
+ return typeof child === "object" && child !== null && child.type === "row" && Array.isArray(child.children);
3804
+ }
3805
+ function sameRange(a, b) {
3806
+ return a.firstRow === b.firstRow && a.lastRow === b.lastRow && a.firstColumn === b.firstColumn && a.lastColumn === b.lastColumn;
3395
3807
  }
3396
- /** A key value that is the character it types: one grapheme, not a name like 'Shift'. */
3397
- function isPrintable(key) {
3398
- if (key.length === 1) return true;
3399
- if (key.length === 2) {
3400
- const code = key.charCodeAt(0);
3401
- return code >= 55296 && code <= 56319;
3808
+ function clamp$1(value, min, max) {
3809
+ return Math.min(Math.max(value, min), max);
3810
+ }
3811
+ function positive(name, value) {
3812
+ if (!(value > 0)) throw new Error(`LazySheet ${name} must be positive, got ${String(value)}.`);
3813
+ return value;
3814
+ }
3815
+ /**
3816
+ * Feeds a sheet the inputs that may change, with the node's lifetime.
3817
+ *
3818
+ * The same reasoning as `lazySource`: a modifier is released inside
3819
+ * `removeSubtree`, and it attaches after the element's props and
3820
+ * before its children, so a count that arrives with the tree is in the
3821
+ * window before the first children are read from it.
3822
+ */
3823
+ const sheetSource = defineModifier({
3824
+ name: "sheetSource",
3825
+ attach(host, args) {
3826
+ follow$1(host, args.rowCount, (value) => args.sheet.setRowCount(value));
3827
+ follow$1(host, args.columnCount, (value) => args.sheet.setColumnCount(value));
3828
+ let rows;
3829
+ let columns;
3830
+ const apply = () => {
3831
+ if (rows !== void 0 || columns !== void 0) args.sheet.setOverscan(rows ?? 3, columns ?? 2);
3832
+ };
3833
+ if (args.rowOverscan !== void 0) follow$1(host, args.rowOverscan, (value) => {
3834
+ rows = value;
3835
+ apply();
3836
+ });
3837
+ if (args.columnOverscan !== void 0) follow$1(host, args.columnOverscan, (value) => {
3838
+ columns = value;
3839
+ apply();
3840
+ });
3402
3841
  }
3403
- return false;
3842
+ });
3843
+ function follow$1(host, source, apply) {
3844
+ if (!isObservable$1(source)) {
3845
+ apply(source);
3846
+ return;
3847
+ }
3848
+ host.own(source.subscribe((value) => {
3849
+ apply(value);
3850
+ host.requestFrame();
3851
+ }));
3852
+ }
3853
+ /**
3854
+ * A scrolling surface that mounts only the cells in view, on both axes.
3855
+ *
3856
+ * LazySheet({ rowCount: 10_000, columnCount: 100, rowHeight: 24, columnWidth: 96 },
3857
+ * (row, first, last) => Row({}, ...cells(row, first, last)))
3858
+ *
3859
+ * It is a ScrollView whose children are a spacer for the rows above
3860
+ * the window, the mounted rows, and a spacer for the rows below; each
3861
+ * row carries a spacer for the columns to its left and is as wide as
3862
+ * the whole sheet, so the container's content extent — and both
3863
+ * scrollbars — are the sheet's real size on the first frame rather
3864
+ * than an estimate that settles.
3865
+ */
3866
+ function LazySheet(props, renderRow) {
3867
+ const { rowCount, columnCount, rowHeight, rowHeights, columnWidth, rowOverscan, columnOverscan, gutterWidth, headerHeight, frozenRows, frozenColumns, header, extendRange, modifiers, sheetRef, ...rest } = props;
3868
+ const sheet = new UiVirtualSheet({
3869
+ rowCount,
3870
+ columnCount,
3871
+ rowHeight,
3872
+ rowHeights,
3873
+ columnWidth,
3874
+ rowOverscan,
3875
+ columnOverscan,
3876
+ gutterWidth,
3877
+ headerHeight,
3878
+ frozenRows,
3879
+ frozenColumns,
3880
+ extendRange
3881
+ }, renderRow, header);
3882
+ sheetRef?.(sheet);
3883
+ return createElement("scroll-view", {
3884
+ ...rest,
3885
+ modifiers: [...modifiers ?? [], sheetSource({
3886
+ sheet,
3887
+ rowCount,
3888
+ columnCount,
3889
+ rowOverscan,
3890
+ columnOverscan
3891
+ })],
3892
+ direction: "column",
3893
+ [VIRTUAL_SHEET_PROP]: sheet
3894
+ }, [sheet.children$]);
3404
3895
  }
3405
3896
  //#endregion
3406
3897
  //#region src/environment/EnvironmentNotifier.ts
@@ -4328,449 +4819,6 @@ function dragSessionFor(node) {
4328
4819
  return session;
4329
4820
  }
4330
4821
  //#endregion
4331
- //#region src/input/UiEditingController.ts
4332
- /** How long two presses may be apart and still count as a double click. */
4333
- const MULTI_CLICK_MS$1 = 500;
4334
- const MULTI_CLICK_SLOP$1 = 4;
4335
- /** Room kept around the caret when scrolling it into view. */
4336
- const CARET_REVEAL_PADDING = 4;
4337
- /**
4338
- * Editing behaviour for EditableText nodes.
4339
- *
4340
- * The keyboard and pointer controllers call in here as their default
4341
- * behaviour for an editable target — after the app's listeners have
4342
- * run and only when none of them called preventDefault() — and the
4343
- * host feeds it the shell's text input: `beforeinput` edits,
4344
- * composition, paste. The model on the node is the single source of
4345
- * truth; this class turns intents into model changes, marks the node
4346
- * dirty so layout and paint follow, reports the change through an
4347
- * `input` event, and keeps the caret in view.
4348
- *
4349
- * Text arrives one of two ways. A shell with an editing proxy sends
4350
- * `beforeinput`, where the browser has already resolved the IME, dead
4351
- * keys and the keyboard layout; then printable key presses are not
4352
- * text and `textFromKeys` is off. Without a proxy — tests, a bare
4353
- * runtime — printable keys become insertions.
4354
- */
4355
- var UiEditingController = class {
4356
- host;
4357
- dispatcher;
4358
- focus;
4359
- /** Insert printable key presses as text; off when a shell delivers text through beforeinput. */
4360
- textFromKeys = true;
4361
- platform;
4362
- paint = createPaintState();
4363
- focusedEditable = null;
4364
- visible = true;
4365
- /** x the current run of vertical moves keeps returning to. */
4366
- verticalGoalX = void 0;
4367
- dragging = null;
4368
- lastPress = null;
4369
- compositionOpen = false;
4370
- constructor(host, dispatcher, focus, options = {}) {
4371
- this.host = host;
4372
- this.dispatcher = dispatcher;
4373
- this.focus = focus;
4374
- this.platform = options.platform ?? detectEditingPlatform();
4375
- this.focus.onFocusChange((node) => this.handleFocusChange(node));
4376
- }
4377
- /** The editable that holds focus, or null. */
4378
- get focused() {
4379
- return this.focusedEditable;
4380
- }
4381
- /** Whether the pointer controller should hand presses on the node to this controller. */
4382
- isEditable(node) {
4383
- return isEditableNode(node);
4384
- }
4385
- /**
4386
- * Default keyboard behaviour for the focused editable. Returns true
4387
- * when the key was an editing command, so the caller can mark the
4388
- * event handled.
4389
- */
4390
- handleKey(node, key, modifiers) {
4391
- if (!isEditableNode(node)) return false;
4392
- const command = commandForKey(key, modifiers, this.platform, this.textFromKeys);
4393
- if (command === null) return false;
4394
- return this.execute(node, command);
4395
- }
4396
- execute(node, command) {
4397
- const model = editorFor(node);
4398
- const readOnly = isReadOnly(node);
4399
- switch (command.kind) {
4400
- case "move":
4401
- this.move(node, model, command.unit, command.direction, command.extend);
4402
- return true;
4403
- case "delete":
4404
- if (readOnly) return true;
4405
- return this.applyEdit(node, model, deleteInputType(command.unit, command.direction), null, () => {
4406
- if (command.direction < 0) model.deleteBackward(command.unit);
4407
- else model.deleteForward(command.unit);
4408
- });
4409
- case "newline":
4410
- if (!isMultiline(node)) return false;
4411
- if (readOnly) return true;
4412
- return this.applyEdit(node, model, "insertLineBreak", "\n", () => model.insertText("\n"));
4413
- case "insert":
4414
- if (readOnly) return true;
4415
- return this.applyEdit(node, model, "insertText", command.text, () => model.insertText(command.text));
4416
- case "selectAll":
4417
- model.selectAll();
4418
- this.afterSelectionChange(node, model);
4419
- return true;
4420
- case "undo":
4421
- if (readOnly) return true;
4422
- this.history(node, model, "historyUndo", () => model.undo());
4423
- return true;
4424
- case "redo":
4425
- if (readOnly) return true;
4426
- this.history(node, model, "historyRedo", () => model.redo());
4427
- return true;
4428
- }
4429
- }
4430
- move(node, model, unit, direction, extend) {
4431
- if (unit === "vertical") {
4432
- const target = this.layoutOf(node).verticalMove(model.focus, direction, this.verticalGoalX);
4433
- if (target === null) {
4434
- model.moveTo(direction < 0 ? 0 : model.text.length, extend);
4435
- this.verticalGoalX = void 0;
4436
- } else {
4437
- model.moveTo(target.offset, extend);
4438
- this.verticalGoalX = target.x;
4439
- }
4440
- this.afterSelectionChange(node, model, true);
4441
- return;
4442
- }
4443
- if (unit === "line") {
4444
- const layout = this.layoutOf(node);
4445
- const caret = layout.caretRect(model.focus);
4446
- const line = layout.lines[caret.line];
4447
- const hard = direction < 0 ? lineStartAt(model.text, model.focus) : lineEndAt(model.text, model.focus);
4448
- const visual = direction < 0 ? line.start : line.end;
4449
- model.moveTo(direction < 0 ? Math.max(hard, visual) : Math.min(hard, visual), extend);
4450
- this.afterSelectionChange(node, model);
4451
- return;
4452
- }
4453
- model.move(unit, direction, extend);
4454
- this.afterSelectionChange(node, model);
4455
- }
4456
- /**
4457
- * A `beforeinput` from the shell's editing proxy, in the DOM's
4458
- * vocabulary. Returns true when it was applied to the focused
4459
- * editable. Composition types are not handled here: the composition
4460
- * methods carry them, and an engine that fires both must not insert
4461
- * twice.
4462
- */
4463
- beforeInput(inputType, data) {
4464
- const node = this.focusedEditable;
4465
- if (node === null) return false;
4466
- const model = editorFor(node);
4467
- const readOnly = isReadOnly(node);
4468
- switch (inputType) {
4469
- case "insertText":
4470
- case "insertReplacementText":
4471
- case "insertFromDrop":
4472
- case "insertFromYank":
4473
- if (data === null || data.length === 0) return false;
4474
- return readOnly || this.insert(node, model, inputType, data);
4475
- case "insertFromPaste": return readOnly || data !== null && this.insert(node, model, inputType, data);
4476
- case "insertLineBreak":
4477
- case "insertParagraph":
4478
- if (!isMultiline(node)) return false;
4479
- return readOnly || this.applyEdit(node, model, inputType, "\n", () => model.insertText("\n"));
4480
- case "deleteContentBackward":
4481
- case "deleteWordBackward":
4482
- case "deleteSoftLineBackward":
4483
- case "deleteHardLineBackward":
4484
- case "deleteContentForward":
4485
- case "deleteWordForward":
4486
- case "deleteSoftLineForward":
4487
- case "deleteHardLineForward":
4488
- case "deleteContent":
4489
- case "deleteByCut":
4490
- case "deleteByDrag": {
4491
- if (readOnly) return true;
4492
- const unit = /Word/.test(inputType) ? "word" : /Line/.test(inputType) ? "line" : "grapheme";
4493
- const forward = /Forward/.test(inputType);
4494
- return this.applyEdit(node, model, inputType, null, () => {
4495
- if (inputType === "deleteContent" || inputType === "deleteByCut" || inputType === "deleteByDrag") model.replaceRange(model.start, model.end, "");
4496
- else if (forward) model.deleteForward(unit);
4497
- else model.deleteBackward(unit);
4498
- });
4499
- }
4500
- case "historyUndo": return readOnly || this.history(node, model, inputType, () => model.undo());
4501
- case "historyRedo": return readOnly || this.history(node, model, inputType, () => model.redo());
4502
- default: return false;
4503
- }
4504
- }
4505
- /** Inserts text at the selection of the focused editable, as typing would. */
4506
- insertText(text) {
4507
- const node = this.focusedEditable;
4508
- if (node === null || isReadOnly(node)) return false;
4509
- return this.insert(node, editorFor(node), "insertText", text);
4510
- }
4511
- /**
4512
- * Replaces the focused editable's whole text, as an assistive
4513
- * technology's "set value" does.
4514
- *
4515
- * `insertReplacementText` is the DOM's own input type for this, so an
4516
- * `onBeforeInput` that vets edits sees the same vocabulary it sees
4517
- * for typing, and an app that rejects the edit rejects this one too.
4518
- * Selecting everything first is what makes it one undo step and one
4519
- * `onInput` rather than a delete and an insert.
4520
- */
4521
- replaceText(text) {
4522
- const node = this.focusedEditable;
4523
- if (node === null || isReadOnly(node)) return false;
4524
- const model = editorFor(node);
4525
- model.selectAll();
4526
- return this.insert(node, model, "insertReplacementText", text);
4527
- }
4528
- /** Pastes text: newlines are kept in a multiline field and become spaces in a single-line one. */
4529
- paste(text) {
4530
- const node = this.focusedEditable;
4531
- if (node === null || isReadOnly(node)) return false;
4532
- return this.insert(node, editorFor(node), "insertFromPaste", text);
4533
- }
4534
- insert(node, model, inputType, text) {
4535
- const data = isMultiline(node) ? text : text.replace(/\r\n|\r|\n/g, " ");
4536
- return this.applyEdit(node, model, inputType, data, () => model.insertText(data));
4537
- }
4538
- compositionStart() {
4539
- const node = this.focusedEditable;
4540
- if (node === null || isReadOnly(node)) return;
4541
- const model = editorFor(node);
4542
- model.beginComposition();
4543
- this.compositionOpen = true;
4544
- this.afterTextChange(node, model, false);
4545
- }
4546
- /** The composition text so far and the caret within it. */
4547
- compositionUpdate(text, caret = text.length) {
4548
- const node = this.focusedEditable;
4549
- if (node === null || isReadOnly(node)) return;
4550
- const model = editorFor(node);
4551
- model.updateComposition(text, caret);
4552
- this.compositionOpen = true;
4553
- this.afterTextChange(node, model, false);
4554
- }
4555
- /** The IME committed `text` (possibly empty: the composition was cancelled). */
4556
- compositionEnd(text) {
4557
- const node = this.focusedEditable;
4558
- this.compositionOpen = false;
4559
- if (node === null || isReadOnly(node)) return;
4560
- const model = editorFor(node);
4561
- const data = isMultiline(node) ? text : text.replace(/\r\n|\r|\n/g, " ");
4562
- const before = model.text;
4563
- model.commitComposition(data);
4564
- this.afterTextChange(node, model, before !== model.text);
4565
- }
4566
- /**
4567
- * A press on an editable places the caret (Shift extends the
4568
- * selection to it); a second press within the double-click window
4569
- * selects the word, a third the line. Dragging afterwards extends the
4570
- * selection from the anchor.
4571
- */
4572
- pointerDown(node, x, y, modifiers) {
4573
- if (!isEditableNode(node)) return;
4574
- const model = editorFor(node);
4575
- const layout = this.layoutOf(node);
4576
- const local = this.host.toLocal(node, x, y);
4577
- const offset = layout.offsetAt(local.x, local.y);
4578
- const now = this.host.now();
4579
- const last = this.lastPress;
4580
- const count = !modifiers.shift && last !== null && last.node === node && now - last.at <= MULTI_CLICK_MS$1 && Math.abs(last.x - x) <= MULTI_CLICK_SLOP$1 && Math.abs(last.y - y) <= MULTI_CLICK_SLOP$1 ? last.count + 1 : 1;
4581
- this.lastPress = {
4582
- node,
4583
- x,
4584
- y,
4585
- at: now,
4586
- count
4587
- };
4588
- this.dragging = node;
4589
- if (modifiers.shift) model.moveTo(offset, true);
4590
- else if (count === 2) {
4591
- const word = wordRangeAt(model.text, offset);
4592
- model.select(word.start, word.end);
4593
- } else if (count >= 3) model.select(lineStartAt(model.text, offset), lineEndAt(model.text, offset));
4594
- else model.select(offset);
4595
- model.breakUndoGroup();
4596
- this.afterSelectionChange(node, model);
4597
- }
4598
- pointerMove(node, x, y) {
4599
- if (this.dragging !== node) return;
4600
- const model = editorFor(node);
4601
- const layout = this.layoutOf(node);
4602
- const local = this.host.toLocal(node, x, y);
4603
- const offset = layout.offsetAt(local.x, local.y);
4604
- if (offset !== model.focus) {
4605
- model.moveTo(offset, true);
4606
- this.afterSelectionChange(node, model);
4607
- }
4608
- }
4609
- pointerUp() {
4610
- this.dragging = null;
4611
- }
4612
- /** The focused editable's state for the shell to mirror, or null. */
4613
- state() {
4614
- const node = this.focusedEditable;
4615
- if (node === null || this.host.recordFor(node) === void 0) return null;
4616
- const model = editorFor(node);
4617
- const caret = this.layoutOf(node).caretRect();
4618
- const visible = this.host.visibleBox(node);
4619
- return {
4620
- text: model.text,
4621
- selectionStart: model.start,
4622
- selectionEnd: model.end,
4623
- caret: {
4624
- x: visible.x + caret.x,
4625
- y: visible.y + caret.y,
4626
- width: 1,
4627
- height: caret.height
4628
- },
4629
- multiline: isMultiline(node),
4630
- composing: model.composing
4631
- };
4632
- }
4633
- /**
4634
- * When the focused caret next toggles, or undefined when nothing
4635
- * blinks: no focused editable, a composition in progress (the caret
4636
- * holds), or a hidden page.
4637
- */
4638
- nextCaretChange(now) {
4639
- const node = this.focusedEditable;
4640
- if (node === null || !this.visible) return;
4641
- const model = editorFor(node);
4642
- if (model.composing || !model.collapsed) return;
4643
- return nextCaretToggle(model, now);
4644
- }
4645
- /**
4646
- * The page was hidden or shown. A hidden page stops the blink so a
4647
- * background tab schedules no frames; showing it restarts the caret.
4648
- */
4649
- setVisible(visible) {
4650
- if (this.visible === visible) return;
4651
- this.visible = visible;
4652
- const node = this.focusedEditable;
4653
- if (node !== null) {
4654
- editorFor(node).blinkOrigin = this.host.now();
4655
- this.host.markDirty(node, 2);
4656
- }
4657
- }
4658
- /** Whether the caret may be drawn at all (the page is visible). */
4659
- get caretEnabled() {
4660
- return this.visible;
4661
- }
4662
- handleFocusChange(node) {
4663
- const previous = this.focusedEditable;
4664
- const next = node !== null && isEditableNode(node) ? node : null;
4665
- if (previous === next) return;
4666
- if (previous !== null) {
4667
- const model = editorFor(previous);
4668
- if (model.composing) model.commitComposition(model.text.slice(model.composition.start, model.composition.end));
4669
- model.focused = false;
4670
- this.host.markDirty(previous, 2);
4671
- }
4672
- this.focusedEditable = next;
4673
- this.compositionOpen = false;
4674
- this.verticalGoalX = void 0;
4675
- if (next !== null) {
4676
- const model = editorFor(next);
4677
- model.focused = true;
4678
- model.blinkOrigin = this.host.now();
4679
- this.host.markDirty(next, 2);
4680
- }
4681
- }
4682
- /**
4683
- * Runs an edit unless a `beforeinput` listener cancels it, then
4684
- * reports it. Returns true when the input was consumed either way.
4685
- */
4686
- applyEdit(node, model, inputType, data, run) {
4687
- const before = new UiBeforeInputEvent(inputType, data);
4688
- this.dispatcher.dispatch(before, node);
4689
- if (before.defaultPrevented) return true;
4690
- const text = model.text;
4691
- run();
4692
- this.afterTextChange(node, model, text !== model.text);
4693
- return true;
4694
- }
4695
- history(node, model, inputType, run) {
4696
- const before = new UiBeforeInputEvent(inputType, null);
4697
- this.dispatcher.dispatch(before, node);
4698
- if (before.defaultPrevented) return true;
4699
- const text = model.text;
4700
- run();
4701
- this.afterTextChange(node, model, text !== model.text);
4702
- return true;
4703
- }
4704
- afterTextChange(node, model, changed) {
4705
- this.verticalGoalX = void 0;
4706
- model.blinkOrigin = this.host.now();
4707
- this.host.markDirty(node, changed || this.compositionOpen ? 5 : 2);
4708
- if (changed) this.dispatcher.dispatch(new UiTextChangeEvent(model.text, model.start, model.end), node);
4709
- this.revealCaret(node);
4710
- }
4711
- afterSelectionChange(node, model, keepGoal = false) {
4712
- if (!keepGoal) this.verticalGoalX = void 0;
4713
- model.blinkOrigin = this.host.now();
4714
- this.host.markDirty(node, 2);
4715
- this.revealCaret(node);
4716
- }
4717
- /**
4718
- * Asks the host to scroll the caret into view, when the node has been
4719
- * laid out: the field's own text first, then any scroll container
4720
- * around it.
4721
- *
4722
- * `layoutOf` places the text where it is seen, so the caret comes
4723
- * back with the field's own scroll already taken off; the reveal is
4724
- * asked for in the node's unscrolled coordinates, which is where the
4725
- * offset it wants is measured from.
4726
- */
4727
- revealCaret(node) {
4728
- const rec = this.host.recordFor(node);
4729
- if (rec === void 0) return;
4730
- const caret = this.layoutOf(node).caretRect();
4731
- this.host.reveal(node, {
4732
- x: caret.x + rec.scrollX - CARET_REVEAL_PADDING,
4733
- y: caret.y + rec.scrollY - CARET_REVEAL_PADDING,
4734
- width: 9,
4735
- height: caret.height + 8
4736
- });
4737
- }
4738
- /**
4739
- * The node's text laid out in node-local coordinates: the content box
4740
- * starts at the padding and is shifted by the field's own scroll, as
4741
- * it is for the renderers in the parent's space. Points from the
4742
- * pointer and the caret box the shell is given are both in that same
4743
- * seen space, so neither has to know the offset. Before the first
4744
- * layout the record is empty and the lines fall at the origin, which
4745
- * is still a valid place for a caret.
4746
- */
4747
- layoutOf(node) {
4748
- const rec = this.host.recordFor(node);
4749
- const state = resolvePaintState(node, this.paint);
4750
- const box = rec === void 0 ? {
4751
- x: 0,
4752
- y: 0,
4753
- width: 0,
4754
- height: 0
4755
- } : {
4756
- x: rec.paddingLeft - rec.scrollX,
4757
- y: rec.paddingTop - rec.scrollY,
4758
- width: Math.max(0, rec.width - rec.paddingLeft - rec.paddingRight),
4759
- height: Math.max(0, rec.height - rec.paddingTop - rec.paddingBottom)
4760
- };
4761
- return new EditableLayout(state.editor, box, state, this.host.measurer);
4762
- }
4763
- };
4764
- function deleteInputType(unit, direction) {
4765
- const side = direction < 0 ? "Backward" : "Forward";
4766
- switch (unit) {
4767
- case "word": return `deleteWord${side}`;
4768
- case "line":
4769
- case "document": return `deleteSoftLine${side}`;
4770
- default: return `deleteContent${side}`;
4771
- }
4772
- }
4773
- //#endregion
4774
4822
  //#region src/layout/LayoutNotifier.ts
4775
4823
  /**
4776
4824
  * Tells listeners when a node's layout changed.
@@ -4976,6 +5024,110 @@ const DEFAULT_RING = kind$3(Object.freeze({}));
4976
5024
  function focusRing(options) {
4977
5025
  return options === void 0 ? DEFAULT_RING : kind$3(options);
4978
5026
  }
5027
+ /**
5028
+ * A border per edge, as paint rather than as layout.
5029
+ *
5030
+ * `borderWidth` is one number and `borderColor` one colour, so a node
5031
+ * cannot have a heavy bottom edge and a hairline top. This is the gap
5032
+ * closed from the cheap side. A border in Gesso is paint-only —
5033
+ * `paintBorder` strokes inside the box and touches no layout — so four
5034
+ * edges are four filled rectangles in the node's own paint pass, with
5035
+ * nothing to lay out and nothing to hit test. A bordered cell costs
5036
+ * four draw instances and no extra nodes.
5037
+ *
5038
+ * Box({ modifiers: [borders({ bottom: 2, right: 1, color: 'border' })] })
5039
+ *
5040
+ * They lie *inside* the box, where `borderWidth` puts them, so a border
5041
+ * never changes where anything sits and never overlaps a neighbour.
5042
+ *
5043
+ * **Why this is a modifier and not four props, and why that is the
5044
+ * answer rather than a stopgap.** First-class `borderTopWidth` is a
5045
+ * much larger change, and the cost is not where it looks: the
5046
+ * property, the paint state and the Canvas2D stroke are easy, but the
5047
+ * WebGPU instance packs `radius, opacity, borderWidth` as one
5048
+ * `float32x3`, the fragment shader draws the border as an isotropic
5049
+ * SDF band, and one instance carries one colour — so four widths need
5050
+ * a wider vertex format, an anisotropic inner rect in the shader, and
5051
+ * up to four instances when the colours differ. This needs none of it,
5052
+ * because a decoration is already a rectangle with its own colour that
5053
+ * both backends draw.
5054
+ *
5055
+ * What the properties would buy over this, once written, is a rounded
5056
+ * per-edge border and one instance instead of four. The first is a
5057
+ * thing square-cornered borders are for — a grid, a table, a rule —
5058
+ * and the second is four draws on a node that already costs one. So
5059
+ * the properties are not planned. If something turns up that wants a
5060
+ * heavy bottom edge on a rounded card, that is the case to reopen it
5061
+ * with, and it will be a better case than "for completeness".
5062
+ *
5063
+ * What it does not do is round its corners. Four rectangles meeting at
5064
+ * a square corner is right for a grid, a table and a rule, which is
5065
+ * what per-edge borders are for; a rounded box wants the single
5066
+ * `borderWidth`, which the renderers draw as one band and get right.
5067
+ */
5068
+ function borders(options) {
5069
+ return decorated(borderShapes(options));
5070
+ }
5071
+ /**
5072
+ * The rectangles `borders()` draws, without the modifier around them.
5073
+ *
5074
+ * For a surface that cannot afford a modifier per node and pushes its
5075
+ * decorations in instead. A spreadsheet is the case: ten thousand
5076
+ * mounted cells, each with its own border, fed from one writer into a
5077
+ * subject per cell — a modifier per cell would be ten thousand
5078
+ * attachments to do what ten thousand array writes already do. The
5079
+ * arithmetic is the same either way, and sharing it is the point:
5080
+ * an application that rolled its own would be the second place a
5081
+ * corner could be painted twice.
5082
+ */
5083
+ function borderShapes(options) {
5084
+ const shared = options.color ?? "border";
5085
+ const edge = (side) => {
5086
+ if (side === void 0) return;
5087
+ const resolved = typeof side === "number" ? { width: side } : side;
5088
+ return resolved.width > 0 ? resolved : void 0;
5089
+ };
5090
+ const top = edge(options.top);
5091
+ const bottom = edge(options.bottom);
5092
+ const left = edge(options.left);
5093
+ const right = edge(options.right);
5094
+ const between = {
5095
+ y: top?.width ?? 0,
5096
+ bottom: bottom?.width ?? 0
5097
+ };
5098
+ const shapes = [];
5099
+ if (top !== void 0) shapes.push({
5100
+ kind: "fill",
5101
+ color: top.color ?? shared,
5102
+ y: 0,
5103
+ height: top.width,
5104
+ radius: 0
5105
+ });
5106
+ if (bottom !== void 0) shapes.push({
5107
+ kind: "fill",
5108
+ color: bottom.color ?? shared,
5109
+ bottom: 0,
5110
+ height: bottom.width,
5111
+ radius: 0
5112
+ });
5113
+ if (left !== void 0) shapes.push({
5114
+ kind: "fill",
5115
+ color: left.color ?? shared,
5116
+ x: 0,
5117
+ width: left.width,
5118
+ radius: 0,
5119
+ ...between
5120
+ });
5121
+ if (right !== void 0) shapes.push({
5122
+ kind: "fill",
5123
+ color: right.color ?? shared,
5124
+ right: 0,
5125
+ width: right.width,
5126
+ radius: 0,
5127
+ ...between
5128
+ });
5129
+ return shapes;
5130
+ }
4979
5131
  //#endregion
4980
5132
  //#region src/modifiers/focus.ts
4981
5133
  /**
@@ -9660,12 +9812,16 @@ function buildRenderList(root, layout, measurer, logicalWidth, logicalHeight, dp
9660
9812
  if (text.editor !== void 0) {
9661
9813
  const model = text.editor;
9662
9814
  const editable = new EditableLayout(model, contentBox, text, measurer);
9815
+ pushTextRects(textRunBackgrounds(editable.lines, text));
9663
9816
  if (model.focused && !model.collapsed) {
9664
9817
  const selection = parseColor$1(text.selectionColor);
9665
9818
  if (selection !== void 0) for (const box of editable.selectionBoxes()) pushContentRect(box, selection);
9666
9819
  }
9667
9820
  if (editable.placeholderLines.length > 0) pushTextRun(editable.placeholderLines, colorToCss(text.placeholderColor));
9668
- else pushTextRun(editable.lines, colorToCss(text.textColor));
9821
+ else {
9822
+ pushTextRun(editable.lines, colorToCss(text.textColor));
9823
+ pushTextRects(textRunDecorations(editable.lines, text));
9824
+ }
9669
9825
  if (model.composing) {
9670
9826
  const underline = parseColor$1(text.textColor);
9671
9827
  if (underline !== void 0) {
@@ -13380,6 +13536,7 @@ var UiScheduler = class {
13380
13536
  dirty;
13381
13537
  onFrame;
13382
13538
  beforeCollect;
13539
+ onFrameError;
13383
13540
  disposed = false;
13384
13541
  active = true;
13385
13542
  pending = false;
@@ -13398,6 +13555,9 @@ var UiScheduler = class {
13398
13555
  this.dirty = options.dirty;
13399
13556
  this.onFrame = options.onFrame;
13400
13557
  this.beforeCollect = options.beforeCollect;
13558
+ this.onFrameError = options.onFrameError ?? ((error) => {
13559
+ console.error("A Gesso frame threw. The frame was abandoned and the clock kept running.", error);
13560
+ });
13401
13561
  this.clock = options.clock((time) => {
13402
13562
  this.handleFrame(time);
13403
13563
  });
@@ -13505,30 +13665,42 @@ var UiScheduler = class {
13505
13665
  this.collecting = true;
13506
13666
  let frame;
13507
13667
  let collected = 0;
13668
+ /**
13669
+ * Everything a frame does, inside one guard.
13670
+ *
13671
+ * The guard is the whole point: an exception used to unwind past
13672
+ * the re-arm at the bottom of this method, and since `pending` had
13673
+ * already been cleared at the top, nothing asked for another
13674
+ * frame. One throw stopped the application for good.
13675
+ */
13508
13676
  try {
13509
- this.beforeCollect?.(time);
13677
+ try {
13678
+ this.beforeCollect?.(time);
13679
+ if (measuring) {
13680
+ collected = markNow();
13681
+ measureSpan("before collect", started, collected);
13682
+ }
13683
+ frame = this.collectFrame(time);
13684
+ } finally {
13685
+ this.collecting = false;
13686
+ }
13687
+ let processed = 0;
13510
13688
  if (measuring) {
13511
- collected = markNow();
13512
- measureSpan("before collect", started, collected);
13689
+ processed = markNow();
13690
+ measureSpan("collect", collected, processed);
13513
13691
  }
13514
- frame = this.collectFrame(time);
13515
- } finally {
13516
- this.collecting = false;
13517
- }
13518
- let processed = 0;
13519
- if (measuring) {
13520
- processed = markNow();
13521
- measureSpan("collect", collected, processed);
13522
- }
13523
- if (frame.size > 0) this.onFrame(frame);
13524
- if (measuring) {
13525
- const finished = markNow();
13526
- const detail = {
13527
- frame: frame.id,
13528
- nodes: frame.size
13529
- };
13530
- measureSpan("process", processed, finished, detail);
13531
- measureSpan("frame", started, finished, detail);
13692
+ if (frame.size > 0) this.onFrame(frame);
13693
+ if (measuring) {
13694
+ const finished = markNow();
13695
+ const detail = {
13696
+ frame: frame.id,
13697
+ nodes: frame.size
13698
+ };
13699
+ measureSpan("process", processed, finished, detail);
13700
+ measureSpan("frame", started, finished, detail);
13701
+ }
13702
+ } catch (error) {
13703
+ this.onFrameError(error, time);
13532
13704
  }
13533
13705
  if (!this.dirty.isEmpty()) this.notifyDirty();
13534
13706
  }
@@ -14667,6 +14839,6 @@ function recordsEqual(a, b) {
14667
14839
  return true;
14668
14840
  }
14669
14841
  //#endregion
14670
- export { AlignContent, AnimationDriver, BUTTON_INTERACTION, Box, Button, CARET_BLINK_MS, CARET_WIDTH, CLIP_STRIDE_BYTES, CLIP_STRIDE_FLOATS, Canvas2DRenderer, CanvasPlatformSurface, CanvasSurface, CanvasTextMeasurer, CharacterCountTextMeasurer, Column, CommandKind, Constraints, CrossAxisAlignment, DEFAULT_BLOCK_SIZE, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DEFAULT_FONT_WEIGHT, DEFAULT_LINE_HEIGHT_FACTOR, DEFAULT_TEXT_COLOR, DefaultImageResolver, DefaultVideoResolver, DirtyFlags, DirtyNodeSet, EDITOR_PROP, ELLIPSIS, EMPTY_RECORDING, EXTERNAL_FILES, EditableLayout, EditableText, EditableTextModel, EnvironmentNotifier, FlexDirection, FocusNotifier, GLYPH_SUBPIXEL_PHASES, GlyphShaper, Grid, INSPECTOR_HEAT_MS, INSTANCE_STRIDE_BYTES, INSTANCE_STRIDE_FLOATS, IconRasterizer, LABEL_PADDING_X, LayoutEngine, LayoutInspector, LayoutNotifier, LayoutRecord, LazyColumn, LazyGrid, LazyRow, MARK_PREFIX, MAX_GRADIENT_STOPS, MAX_MEASURES_PER_CHILD, MOTION_CHANNELS, MOTION_REST, MOUSE_POINTER, MainAxisAlignment, Mp3Frames, NO_CLIP_INDEX, NodeTransitions, Paint, PaintPictureCache, PaintRecorder, PaintTarget, ParagraphTextMeasurer, PrimitiveKind, Responsive, Row, SCROLLBAR_FADE_MS, SCROLLBAR_HOVER_ZONE, SCROLLBAR_INSET, SCROLLBAR_LINGER_MS, SCROLLBAR_MIN_THUMB, SCROLLBAR_THICKNESS, ScrollView, Stack, TEXTURED_STRIDE_BYTES, TEXTURED_STRIDE_FLOATS, TEXT_MATCHES_PROP, TEXT_RUN_ID_SEPARATOR, TEXT_SELECTION_PROP, Text, UI_ROLES, UI_SEMANTIC_STATES, UI_SUBGRID_AXES, UiAnimation, UiAnimationFrameClock, UiBasicColors, UiBeforeInputEvent, UiBinding, UiBorderRadiuses, UiChildrenBinding, UiContainerSizeSource, UiDragSession, UiEditingController, UiEnvironment, UiEnvironmentKeys, UiEventBinding, UiEventType, UiFindController, UiFocusEvent, UiFocusManager, UiFrame, UiGestureEvent, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiHostFrameClock, UiInputDispatcher, UiInputEvent, UiInsetRegistry, UiKeyboardController, UiKeyboardEvent, UiManualFrameClock, UiModifierSet, UiNode, UiNodeType, UiPinchEvent, UiPinchRecognizer, UiPlatformAdapter, UiPointerController, UiPointerEvent, UiProperties, UiScheduler, UiSelectionController, UiSharedElements, UiShortcutRegistry, UiSpring, UiTextChangeEvent, UiTimerFrameClock, UiTouchScroller, UiTransforms, UiTween, UiUnknownModifierPropertyError, UiVirtualWindow, UiVisualState, UiWheelController, UiWheelDeltaMode, UiWheelEvent, VIRTUAL_INDEX_PROP, VIRTUAL_LEAD_PROP, VIRTUAL_WINDOW_PROP, WebGPUError, WebGPUGlyphAtlas, WebGPUGlyphPages, WebGPURenderer, WebGPUSurface, WebGPUTextureCache, accumulatedOffsetTo, animateLayout, applyCanvasTextStyle, assertModifierList, assertTransitionMap, auto, autoFocus, bandOf, borderRadius, borderRadiusCorners, borderRadiusEqual, borderRadiusIsZero, borderRadiusValuesEqual, boxShadow, boxShadowArraysEqual, boxShadowsEqual, breakpoint, bufferSource, buildFontString, buildRenderList, buildSemanticsSubtree, buildSemanticsTree, bumpFontStack, bundle, canDecodeVideo, capturePointer, caretRectFor, caretVisibleAt, clampSize, clearFontStacks, clearLinkHover, clearMatchRanges, clearOverrideProperty, clearSelectionRange, clickOutside, closestPropertyName, colorToCss, colorToHex, colorToRgba, colorValuesEqual, colorsEqual, colorsEqualPalette, commandForKey, computeObjectFitRect, constraintsEqual, containerBands, contentOffset, contextMenu, contrastRatio, createCanvasSurface, createCanvasTextStyle, createElement, createEnvironmentKey, createPaintState, createTextCache, createTween, createWebGPUSurface, cubicBezier, cueAt, darkColors, darkTheme, decorated, decorationColor, decorationRect, defaultMotion, defaultShadows, defaultShapes, defaultSpacing, defaultTextStyle, defaultTypography, defaultVisualState, defineModifier, defineProperty, defineThemeExtension, demuxMp4Audio, demuxMp4Video, densityFactors, describeLength, describeOverrides, detectEditingPlatform, diffSemantics, dragSessionFor, dragSource, draggable, drawOverlayShapes, drawText, dropTarget, easings, editorFor, editorOf, fade, fillTextRects, findEnvironmentKey, findMatchesIn, findPropertyDefinition, flattenTextSpans, focusRing, fontStackFor, formatConstraints, formatExplanation, formatShortcut, fr, getPropertyNames, glyphCount, gradientPaint, gradientsEqual, graphemeBoundaries, hasDecorationPhase, hasDrawnText, hasTextLinks, hasThemeExtension, hasVisualState, highContrastColors, hoverable, iconKey, iconSource, imageSource, inheritedPropertyFlags, inheritedPropertyNames, initializeWebGPU, inputsEqual, insetPadding, insetsEqual, interactive, interpolateColor, interpolateNumber, interpolateTransform, interpolatorFor, isAutoLength, isComponentLikeElement, isEditableNode, isFrLength, isLayoutProtocol, isMinMaxTrack, isMotionRest, isMultiline, isNodeFocusable, isNodeHitTestable, isNodeInert, isNodeSelectable, isObservable, isPaintVisible, isPercentLength, isPrintable, isReadOnly, isScrollContainer, isShapeName, isTypographyRole, isUiChild, isUiElement, isUiModifier, isUiRole, isUiSemanticState, isVideoSurface, isWebGPUAvailable, labelNode, labelOrigin, layoutParagraph, layoutTextLines, lazySource, lightColors, lightTheme, lineEndAt, lineIndexAtY, lineIndexForOffset, lineLimit, lineStartAt, linear, linearGradient, linkHoverOf, linkOf, linkRunAtPointIn, markInstant, markNow, matchRangesOf, measure, measureSpan, minmax, motion, nextCaretToggle, nextGraphemeEnd, nextWordEnd, noInsets, noKeyModifiers, noModifiers, noThemeExtensions, normalizeBorderRadius, normalizeColor, normalizeStates, normalizeTextAlign, normalizeTransition, normalizeVerticalAlign, observeViewportInsets, offsetAtPoint, offsetAtPointIn, offsetAtX, offsetForVerticalMove, overrideSources, paintPictures, paintValuesEqual, paintsAfterChildren, paragraphGeometry, paragraphGeometryFrom, parseAlignContent, parseColor, parseCrossAxisAlignment, parseFlexDirection, parseMainAxisAlignment, parseShortcut, parseTransform, parseWebVtt, pathValuesEqual, percent, performanceMarksEnabled, pinchable, placeGridItems, pointerDeviceOf, pop, prepareInputSurface, pressable, previousGraphemeStart, previousWordStart, propertyEffects, propertyIsInherited, propertyValuesEqual, propertyValuesEqualByName, proportionalFontMetrics, provideEnvironment, publishInset, radialGradient, raiseContrast, rangeSource, readMp3Header, recordsEqual, registerFontStack, relativeLuminance, reorderable, repeat, replayPaint, resetOverrideWarnings, resetTransitionWarnings, resolveBoolean, resolveCursor, resolveLength, resolveMotionState, resolveNumber, resolvePaintState, resolveProperty, resolvePropertyByName, resolveString, resolvedSpansOf, rgb8, rgba, rotateFrom, runCanvasStyleInto, runFontString, sameArgs, scaleFrom, scaleSpacing, scrollPosition, scrollbarThumb, scrollbarThumbs, scrollbarZoneAt, selectableTextNodes, selectableTextOf, selectionRangeOf, selectionRects, selectionRectsIn, semanticsInertAbove, setLinkHover, setMatchRanges, setPerformanceMarks, setSelectionRange, shadowsEqual, shapesEqual, sharedElement, shortcut, shortcuts, sizeContainer, sizeGridTracks, slideDown, slideFrom, slideUp, spacingEqual, spacingSteps, spanAtOffset, spannedRunsFor, spannedTextOf, splitMp3Frames, spring, statesEqual, steps, syncEditorValue, textContentOf, textRunBackgrounds, textRunDecorations, textRunOfRecordId, textRuns, textSpansEqual, textStylesEqual, themeExtension, themeExtensionsEqual, themeTokens, themesEqual, tightenConstraints, touchActionFor, tracePathData, traceRoundedRect, transform, transformIsIdentity, transformsEqual, tween, typographyEqual, uniformBorderRadius, unknownContainerSize, validateGradient, validateRole, validateStates, validateSubgrid, videoFrameSize, videoSource, visualState, visualStatesEqual, wheelDeltaYOf, withContrast, withDensity, withOverrideSource, withThemeExtension, wordRangeAt, wordRangeIn, writeDeclaredProperty, writeOverrideProperty };
14842
+ export { AlignContent, AnimationDriver, BUTTON_INTERACTION, Box, Button, CARET_BLINK_MS, CARET_WIDTH, CLIP_STRIDE_BYTES, CLIP_STRIDE_FLOATS, Canvas2DRenderer, CanvasPlatformSurface, CanvasSurface, CanvasTextMeasurer, CharacterCountTextMeasurer, Column, CommandKind, Constraints, CrossAxisAlignment, DEFAULT_BLOCK_SIZE, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DEFAULT_FONT_WEIGHT, DEFAULT_LINE_HEIGHT_FACTOR, DEFAULT_TEXT_COLOR, DefaultImageResolver, DefaultVideoResolver, DirtyFlags, DirtyNodeSet, EDITOR_PROP, ELLIPSIS, EMPTY_RECORDING, EXTERNAL_FILES, EditableLayout, EditableText, EditableTextModel, EnvironmentNotifier, FlexDirection, FocusNotifier, GLYPH_SUBPIXEL_PHASES, GlyphShaper, Grid, INSPECTOR_HEAT_MS, INSTANCE_STRIDE_BYTES, INSTANCE_STRIDE_FLOATS, IconRasterizer, LABEL_PADDING_X, LayoutEngine, LayoutInspector, LayoutNotifier, LayoutRecord, LazyColumn, LazyGrid, LazyRow, LazySheet, MARK_PREFIX, MAX_GRADIENT_STOPS, MAX_MEASURES_PER_CHILD, MOTION_CHANNELS, MOTION_REST, MOUSE_POINTER, MainAxisAlignment, Mp3Frames, NO_CLIP_INDEX, NodeTransitions, Paint, PaintPictureCache, PaintRecorder, PaintTarget, ParagraphTextMeasurer, PrimitiveKind, Responsive, Row, SCROLLBAR_FADE_MS, SCROLLBAR_HOVER_ZONE, SCROLLBAR_INSET, SCROLLBAR_LINGER_MS, SCROLLBAR_MIN_THUMB, SCROLLBAR_THICKNESS, ScrollView, Stack, TEXTURED_STRIDE_BYTES, TEXTURED_STRIDE_FLOATS, TEXT_MATCHES_PROP, TEXT_RUN_ID_SEPARATOR, TEXT_SELECTION_PROP, Text, UI_ROLES, UI_SEMANTIC_STATES, UI_SUBGRID_AXES, UiAnimation, UiAnimationFrameClock, UiBasicColors, UiBeforeInputEvent, UiBinding, UiBorderRadiuses, UiChildrenBinding, UiContainerSizeSource, UiDragSession, UiEditingController, UiEnvironment, UiEnvironmentKeys, UiEventBinding, UiEventType, UiFindController, UiFocusEvent, UiFocusManager, UiFrame, UiGestureEvent, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiHostFrameClock, UiInputDispatcher, UiInputEvent, UiInsetRegistry, UiKeyboardController, UiKeyboardEvent, UiManualFrameClock, UiModifierSet, UiNode, UiNodeType, UiPasteEvent, UiPinchEvent, UiPinchRecognizer, UiPlatformAdapter, UiPointerController, UiPointerEvent, UiProperties, UiScheduler, UiSelectionChangeEvent, UiSelectionController, UiSharedElements, UiShortcutRegistry, UiSpring, UiTextChangeEvent, UiTimerFrameClock, UiTouchScroller, UiTransforms, UiTween, UiUnknownModifierPropertyError, UiVirtualSheet, UiVirtualWindow, UiVisualState, UiWheelController, UiWheelDeltaMode, UiWheelEvent, VIRTUAL_INDEX_PROP, VIRTUAL_LEAD_PROP, VIRTUAL_SHEET_PROP, VIRTUAL_WINDOW_PROP, WebGPUError, WebGPUGlyphAtlas, WebGPUGlyphPages, WebGPURenderer, WebGPUSurface, WebGPUTextureCache, accumulatedOffsetTo, animateLayout, applyCanvasTextStyle, assertModifierList, assertTransitionMap, auto, autoFocus, bandOf, borderRadius, borderRadiusCorners, borderRadiusEqual, borderRadiusIsZero, borderRadiusValuesEqual, borderShapes, borders, boxShadow, boxShadowArraysEqual, boxShadowsEqual, breakpoint, bufferSource, buildFontString, buildRenderList, buildSemanticsSubtree, buildSemanticsTree, bumpFontStack, bundle, canDecodeVideo, capturePointer, caretRectFor, caretVisibleAt, clampSize, clearFontStacks, clearLinkHover, clearMatchRanges, clearOverrideProperty, clearSelectionRange, clickOutside, closestPropertyName, colorToCss, colorToHex, colorToRgba, colorValuesEqual, colorsEqual, colorsEqualPalette, commandForKey, computeObjectFitRect, constraintsEqual, containerBands, contentOffset, contextMenu, contrastRatio, createCanvasSurface, createCanvasTextStyle, createElement, createEnvironmentKey, createPaintState, createTextCache, createTween, createWebGPUSurface, cubicBezier, cueAt, darkColors, darkTheme, decorated, decorationColor, decorationRect, defaultMotion, defaultShadows, defaultShapes, defaultSpacing, defaultTextStyle, defaultTypography, defaultVisualState, defineModifier, defineProperty, defineThemeExtension, demuxMp4Audio, demuxMp4Video, densityFactors, describeLength, describeOverrides, detectEditingPlatform, diffSemantics, dragSessionFor, dragSource, draggable, drawOverlayShapes, drawText, dropTarget, easings, editableSpansOf, editorFor, editorOf, fade, fillTextRects, findEnvironmentKey, findMatchesIn, findPropertyDefinition, flattenTextSpans, focusRing, fontStackFor, formatConstraints, formatExplanation, formatShortcut, fr, getPropertyNames, glyphCount, gradientPaint, gradientsEqual, graphemeBoundaries, hasDecorationPhase, hasDrawnText, hasTextLinks, hasThemeExtension, hasVisualState, highContrastColors, hoverable, iconKey, iconSource, imageSource, inheritedPropertyFlags, inheritedPropertyNames, initializeWebGPU, inputsEqual, insetPadding, insetsEqual, interactive, interpolateColor, interpolateNumber, interpolateTransform, interpolatorFor, isAutoLength, isComponentLikeElement, isEditableNode, isFrLength, isLayoutProtocol, isMinMaxTrack, isMotionRest, isMultiline, isNodeFocusable, isNodeHitTestable, isNodeInert, isNodeSelectable, isNodeTabStop, isObservable, isPaintVisible, isPercentLength, isPrintable, isReadOnly, isScrollContainer, isShapeName, isTypographyRole, isUiChild, isUiElement, isUiModifier, isUiRole, isUiSemanticState, isVideoSurface, isWebGPUAvailable, labelNode, labelOrigin, layoutParagraph, layoutTextLines, lazySource, lightColors, lightTheme, lineEndAt, lineIndexAtY, lineIndexForOffset, lineLimit, lineStartAt, linear, linearGradient, linkHoverOf, linkOf, linkRunAtPointIn, markInstant, markNow, matchRangesOf, measure, measureSpan, minmax, motion, nextCaretToggle, nextGraphemeEnd, nextWordEnd, noInsets, noKeyModifiers, noModifiers, noThemeExtensions, normalizeBorderRadius, normalizeColor, normalizeStates, normalizeTextAlign, normalizeTransition, normalizeVerticalAlign, observeViewportInsets, offsetAtPoint, offsetAtPointIn, offsetAtX, offsetForVerticalMove, overrideSources, paintPictures, paintValuesEqual, paintsAfterChildren, paragraphGeometry, paragraphGeometryFrom, parseAlignContent, parseColor, parseCrossAxisAlignment, parseFlexDirection, parseMainAxisAlignment, parseShortcut, parseTransform, parseWebVtt, pathValuesEqual, percent, performanceMarksEnabled, pinchable, placeGridItems, pointerDeviceOf, pop, prepareInputSurface, pressable, previousGraphemeStart, previousWordStart, propertyEffects, propertyIsInherited, propertyValuesEqual, propertyValuesEqualByName, proportionalFontMetrics, provideEnvironment, publishInset, radialGradient, raiseContrast, rangeSource, readMp3Header, recordsEqual, registerFontStack, relativeLuminance, reorderable, repeat, replayPaint, resetOverrideWarnings, resetTransitionWarnings, resolveBoolean, resolveCursor, resolveLength, resolveMotionState, resolveNumber, resolvePaintState, resolveProperty, resolvePropertyByName, resolveString, resolvedSpansOf, rgb8, rgba, rotateFrom, runCanvasStyleInto, runFontString, sameArgs, scaleFrom, scaleSpacing, scrollPosition, scrollbarThumb, scrollbarThumbs, scrollbarZoneAt, selectableTextNodes, selectableTextOf, selectionRangeOf, selectionRects, selectionRectsIn, semanticsInertAbove, setLinkHover, setMatchRanges, setPerformanceMarks, setSelectionRange, shadowsEqual, shapesEqual, sharedElement, sheetSource, shortcut, shortcuts, sizeContainer, sizeGridTracks, slideDown, slideFrom, slideUp, spacingEqual, spacingSteps, spanAtOffset, spannedRunsFor, spannedTextOf, splitMp3Frames, spring, statesEqual, steps, syncEditorValue, textContentOf, textRunBackgrounds, textRunDecorations, textRunOfRecordId, textRuns, textSpansEqual, textStylesEqual, themeExtension, themeExtensionsEqual, themeTokens, themesEqual, tightenConstraints, touchActionFor, tracePathData, traceRoundedRect, transform, transformIsIdentity, transformsEqual, tween, typographyEqual, uniformBorderRadius, unknownContainerSize, validateGradient, validateRole, validateStates, validateSubgrid, videoFrameSize, videoSource, visualState, visualStatesEqual, wheelDeltaYOf, withContrast, withDensity, withOverrideSource, withThemeExtension, wordRangeAt, wordRangeIn, writeDeclaredProperty, writeOverrideProperty };
14671
14843
 
14672
14844
  //# sourceMappingURL=index.js.map