gesso-core 0.2.1 → 0.4.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-BR7hECHm.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,10 +1425,13 @@ 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",
1434
+ onDoubleClick: "doubleclick",
1432
1435
  onLongPress: "longpress",
1433
1436
  onDragStart: "dragstart",
1434
1437
  onDragMove: "dragmove",
@@ -2883,7 +2886,7 @@ var UiVirtualWindow = class {
2883
2886
  const total = this.totalExtent();
2884
2887
  const header = viewport.lead ?? 0;
2885
2888
  this.checkViewport(viewport.extent, total + header);
2886
- const scrolled = clamp$1(viewport.scroll + scrollAdjust, 0, Math.max(0, total + header - viewport.extent));
2889
+ const scrolled = clamp$2(viewport.scroll + scrollAdjust, 0, Math.max(0, total + header - viewport.extent));
2887
2890
  const scroll = Math.max(0, scrolled - header);
2888
2891
  let first = 0;
2889
2892
  let last = -1;
@@ -3054,7 +3057,7 @@ function markRow(row, props, subject) {
3054
3057
  }
3055
3058
  };
3056
3059
  }
3057
- function clamp$1(value, min, max) {
3060
+ function clamp$2(value, min, max) {
3058
3061
  return Math.min(Math.max(value, min), max);
3059
3062
  }
3060
3063
  function assertCount(count) {
@@ -3074,10 +3077,10 @@ function assertCount(count) {
3074
3077
  const lazySource = defineModifier({
3075
3078
  name: "lazySource",
3076
3079
  attach(host, args) {
3077
- follow$1(host, args.count, (value) => args.window.setCount(value));
3080
+ follow$2(host, args.count, (value) => args.window.setCount(value));
3078
3081
  if (args.revision !== void 0) {
3079
3082
  let first = true;
3080
- follow$1(host, args.revision, () => {
3083
+ follow$2(host, args.revision, () => {
3081
3084
  if (first) {
3082
3085
  first = false;
3083
3086
  return;
@@ -3087,7 +3090,7 @@ const lazySource = defineModifier({
3087
3090
  }
3088
3091
  }
3089
3092
  });
3090
- function follow$1(host, source, apply) {
3093
+ function follow$2(host, source, apply) {
3091
3094
  if (!isObservable$1(source)) {
3092
3095
  apply(source);
3093
3096
  return;
@@ -3287,120 +3290,609 @@ function Responsive(props, build) {
3287
3290
  }, [children]);
3288
3291
  }
3289
3292
  //#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
- };
3293
+ //#region src/composition/UiVirtualSheet.ts
3294
+ /** The sheet sits on its scroll container under this property. */
3295
+ const VIRTUAL_SHEET_PROP = "virtualSheet";
3296
+ const EMPTY_RANGE = {
3297
+ firstRow: 0,
3298
+ lastRow: -1,
3299
+ firstColumn: 0,
3300
+ lastColumn: -1
3301
+ };
3302
+ var UiVirtualSheet = class {
3303
+ children$ = new BehaviorSubject([]);
3304
+ /**
3305
+ * The range now mounted, for whoever has to fetch the values in it.
3306
+ *
3307
+ * This is the whole reason the window is reachable from outside: the
3308
+ * render thread does not hold the cells, so the range it settles on
3309
+ * each frame is what it asks the application thread for. Emits only
3310
+ * when the range changes, so binding a command to it does not send
3311
+ * one per frame.
3312
+ */
3313
+ range$ = new BehaviorSubject(EMPTY_RANGE);
3314
+ rowCount;
3315
+ columnCount;
3316
+ rowOverscan;
3317
+ columnOverscan;
3318
+ rowHeight;
3319
+ gutterWidth;
3320
+ headerHeight;
3321
+ frozenRows;
3322
+ frozenColumns;
3323
+ renderRow;
3324
+ renderHeader;
3325
+ extendRange;
3326
+ /** One width per column, or undefined when every column is the same. */
3327
+ widths;
3328
+ /** `offsets[c]` is where column `c` starts, measured past the gutter. */
3329
+ offsets = [];
3330
+ uniformWidth = 0;
3331
+ /**
3332
+ * The rows that are not `rowHeight` tall, in row order.
3333
+ *
3334
+ * Three parallel arrays rather than a map of objects: this is
3335
+ * searched on every hit test and on every frame that scrolls, and
3336
+ * the arrays are read in step.
3337
+ */
3338
+ exceptionRows = [];
3339
+ exceptionHeights = [];
3340
+ /** `exceptionStarts[i]` is where `exceptionRows[i]` starts, past the header. */
3341
+ exceptionStarts = [];
3342
+ range = EMPTY_RANGE;
3343
+ viewport;
3344
+ /** Rows built for the current column range, so a vertical scroll rebuilds one row. */
3345
+ mountedRows = /* @__PURE__ */ new Map();
3346
+ /** The frozen rows, kept separately because they never leave. */
3347
+ frozenMounted = /* @__PURE__ */ new Map();
3348
+ mountedHeader;
3349
+ constructor(options, renderRow, renderHeader) {
3350
+ this.rowCount = typeof options.rowCount === "number" ? options.rowCount : 0;
3351
+ this.columnCount = typeof options.columnCount === "number" ? options.columnCount : 0;
3352
+ this.rowHeight = positive("rowHeight", options.rowHeight);
3353
+ this.adoptHeights(options.rowHeights);
3354
+ this.gutterWidth = Math.max(0, options.gutterWidth ?? 0);
3355
+ this.headerHeight = Math.max(0, options.headerHeight ?? 0);
3356
+ this.frozenRows = Math.max(0, Math.floor(options.frozenRows ?? 0));
3357
+ this.frozenColumns = Math.max(0, Math.floor(options.frozenColumns ?? 0));
3358
+ this.rowOverscan = typeof options.rowOverscan === "number" ? options.rowOverscan : 3;
3359
+ this.columnOverscan = typeof options.columnOverscan === "number" ? options.columnOverscan : 2;
3360
+ this.renderRow = renderRow;
3361
+ this.renderHeader = renderHeader;
3362
+ this.extendRange = options.extendRange;
3363
+ this.adoptWidths(options.columnWidth);
3364
+ this.viewport = {
3365
+ scrollX: 0,
3366
+ scrollY: 0,
3367
+ width: options.initialViewport?.width ?? 800,
3368
+ height: options.initialViewport?.height ?? 600
3369
+ };
3370
+ this.recompute();
3371
+ }
3372
+ /**
3373
+ * New column widths, as a drag on a header edge produces.
3374
+ *
3375
+ * The prefix sum is rebuilt whole. It is one pass over the columns
3376
+ * and it happens once per frame of a drag at most, against a
3377
+ * per-column offset that would otherwise have to be corrected
3378
+ * everywhere downstream of the column that moved.
3379
+ */
3380
+ setColumnWidths(widths) {
3381
+ this.adoptWidths(widths);
3382
+ this.invalidate();
3383
+ }
3384
+ adoptWidths(widths) {
3385
+ if (typeof widths === "number") {
3386
+ this.uniformWidth = positive("columnWidth", widths);
3387
+ this.widths = void 0;
3388
+ this.offsets = [];
3389
+ return;
3335
3390
  }
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
- };
3391
+ const owned = widths.slice();
3392
+ for (const width of owned) if (!(width >= 0)) throw new Error(`LazySheet column widths must not be negative, got ${String(width)}.`);
3393
+ this.widths = owned;
3394
+ this.offsets = Array.from({ length: owned.length + 1 });
3395
+ let running = 0;
3396
+ for (let column = 0; column < owned.length; column++) {
3397
+ this.offsets[column] = running;
3398
+ running += owned[column];
3399
+ }
3400
+ this.offsets[owned.length] = running;
3401
+ }
3402
+ /**
3403
+ * New row heights, as hiding a row or autofitting one produces.
3404
+ *
3405
+ * The exceptions are rebuilt whole, which is one pass over *them*
3406
+ * and not over the rows — the whole reason they are sparse. A
3407
+ * height of zero is a hidden row and is allowed; a negative one is
3408
+ * not.
3409
+ */
3410
+ setRowHeights(heights) {
3411
+ this.adoptHeights(heights);
3412
+ this.invalidate();
3413
+ }
3414
+ /** The height every row has unless it is an exception. */
3415
+ setRowHeight(height) {
3416
+ const next = positive("rowHeight", height);
3417
+ if (next === this.rowHeight) return;
3418
+ this.rowHeight = next;
3419
+ this.rebuildExceptionStarts();
3420
+ this.invalidate();
3421
+ }
3422
+ adoptHeights(heights) {
3423
+ this.exceptionRows = [];
3424
+ this.exceptionHeights = [];
3425
+ if (heights !== void 0) {
3426
+ const rows = [...heights.keys()].sort((a, b) => a - b);
3427
+ for (const row of rows) {
3428
+ const height = heights.get(row) ?? 0;
3429
+ if (!(height >= 0)) throw new Error(`LazySheet row heights must not be negative, got ${String(height)}.`);
3430
+ if (height !== this.rowHeight) {
3431
+ this.exceptionRows.push(row);
3432
+ this.exceptionHeights.push(height);
3433
+ }
3434
+ }
3435
+ }
3436
+ this.rebuildExceptionStarts();
3437
+ }
3438
+ /**
3439
+ * Where each exception begins, and what they cost in total.
3440
+ *
3441
+ * `start` is the row's own offset past the header, so the inverse —
3442
+ * `rowAt` — can binary search it directly rather than reconstructing
3443
+ * it per probe.
3444
+ */
3445
+ rebuildExceptionStarts() {
3446
+ this.exceptionStarts = Array.from({ length: this.exceptionRows.length });
3447
+ let extra = 0;
3448
+ for (let index = 0; index < this.exceptionRows.length; index++) {
3449
+ this.exceptionStarts[index] = this.exceptionRows[index] * this.rowHeight + extra;
3450
+ extra += this.exceptionHeights[index] - this.rowHeight;
3451
+ }
3452
+ }
3453
+ /** The index of the last exception starting at or before an offset, or -1. */
3454
+ exceptionAt(offset) {
3455
+ let low = 0;
3456
+ let high = this.exceptionStarts.length - 1;
3457
+ let found = -1;
3458
+ while (low <= high) {
3459
+ const mid = low + high >> 1;
3460
+ if (this.exceptionStarts[mid] <= offset) {
3461
+ found = mid;
3462
+ low = mid + 1;
3463
+ } else high = mid - 1;
3464
+ }
3465
+ return found;
3466
+ }
3467
+ /** How many exceptions come before a row, as an index into them. */
3468
+ exceptionsBefore(row) {
3469
+ let low = 0;
3470
+ let high = this.exceptionRows.length;
3471
+ while (low < high) {
3472
+ const mid = low + high >> 1;
3473
+ if (this.exceptionRows[mid] < row) low = mid + 1;
3474
+ else high = mid;
3351
3475
  }
3352
- case "Home": return {
3353
- kind: "move",
3354
- unit: modifiers.ctrl ? "document" : "line",
3355
- direction: -1,
3356
- extend
3476
+ return low;
3477
+ }
3478
+ /** The height of one row. Zero when it is hidden. */
3479
+ rowHeightOf(row) {
3480
+ if (this.exceptionRows.length === 0) return this.rowHeight;
3481
+ const index = this.exceptionsBefore(row);
3482
+ return this.exceptionRows[index] === row ? this.exceptionHeights[index] : this.rowHeight;
3483
+ }
3484
+ /** Where a row starts, measured past the header. */
3485
+ rowOffsetOf(row) {
3486
+ const clamped = clamp$1(row, 0, this.rowCount);
3487
+ return clamped * this.rowHeight + this.extraWithin(clamped);
3488
+ }
3489
+ /** Total height of the rows, not counting the header. */
3490
+ get rowsHeight() {
3491
+ return this.rowCount * this.rowHeight + this.extraWithin(this.rowCount);
3492
+ }
3493
+ /**
3494
+ * What the exceptions before `row` add to or take off the offset.
3495
+ *
3496
+ * Read off the last exception before it rather than summed: the
3497
+ * start of an exception already carries everything before it, so
3498
+ * its end minus where it *would* have ended is the running total.
3499
+ */
3500
+ extraWithin(row) {
3501
+ const before = this.exceptionsBefore(row);
3502
+ if (before === 0) return 0;
3503
+ const index = before - 1;
3504
+ return this.exceptionStarts[index] + this.exceptionHeights[index] - (this.exceptionRows[index] + 1) * this.rowHeight;
3505
+ }
3506
+ /** The width of one column. */
3507
+ widthOf(column) {
3508
+ if (this.widths === void 0) return this.uniformWidth;
3509
+ return this.widths[column] ?? 0;
3510
+ }
3511
+ /** Where a column starts, measured past the gutter. */
3512
+ offsetOf(column) {
3513
+ if (this.widths === void 0) return column * this.uniformWidth;
3514
+ const clamped = clamp$1(column, 0, this.offsets.length - 1);
3515
+ return this.offsets[clamped] ?? 0;
3516
+ }
3517
+ /** Total width of the columns, not counting the gutter. */
3518
+ get columnsWidth() {
3519
+ return this.widths === void 0 ? this.columnCount * this.uniformWidth : this.offsets[this.widths.length] ?? 0;
3520
+ }
3521
+ get contentWidth() {
3522
+ return this.gutterWidth + this.columnsWidth;
3523
+ }
3524
+ /** Where the scrolled columns begin, past the gutter and the frozen ones. */
3525
+ get frozenWidth() {
3526
+ return this.offsetOf(this.frozenColumns);
3527
+ }
3528
+ get contentHeight() {
3529
+ return this.headerHeight + this.rowsHeight;
3530
+ }
3531
+ /**
3532
+ * The cell under a point in the scroll container's own coordinates.
3533
+ *
3534
+ * The window already knows where everything is and how far it is
3535
+ * scrolled, which is the whole of this: a drag towards a cell — a
3536
+ * fill handle, a selection being stretched — is usually heading for
3537
+ * one that has not been mounted, so there is no node to hit-test and
3538
+ * arithmetic is the only answer available.
3539
+ */
3540
+ cellAt(x, y) {
3541
+ return {
3542
+ row: this.rowAt(this.viewport.scrollY + y),
3543
+ column: this.columnAt(this.viewport.scrollX + x)
3357
3544
  };
3358
- case "End": return {
3359
- kind: "move",
3360
- unit: modifiers.ctrl ? "document" : "line",
3361
- direction: 1,
3362
- extend
3545
+ }
3546
+ /**
3547
+ * The row at a content offset, clamped to the sheet.
3548
+ *
3549
+ * A division when every row is the default height — the common case
3550
+ * by a very long way — and otherwise a binary search over the
3551
+ * exceptions followed by a division inside the uniform run that
3552
+ * follows the one it lands in.
3553
+ *
3554
+ * A hidden row occupies no offset at all, so the offset where it
3555
+ * *would* be belongs to the row after it. That falls out of the
3556
+ * arithmetic rather than being special-cased: a zero-height
3557
+ * exception starts and ends at the same place, so the point is
3558
+ * never inside it.
3559
+ */
3560
+ rowAt(offset) {
3561
+ const last = Math.max(0, this.rowCount - 1);
3562
+ const past = offset - this.headerHeight;
3563
+ if (this.exceptionRows.length === 0) return clamp$1(Math.floor(past / this.rowHeight), 0, last);
3564
+ if (past <= 0) return 0;
3565
+ const index = this.exceptionAt(past);
3566
+ if (index === -1) return clamp$1(Math.floor(past / this.rowHeight), 0, last);
3567
+ const start = this.exceptionStarts[index];
3568
+ const height = this.exceptionHeights[index];
3569
+ if (past < start + height) return clamp$1(this.exceptionRows[index], 0, last);
3570
+ return clamp$1(this.exceptionRows[index] + 1 + Math.floor((past - start - height) / this.rowHeight), 0, last);
3571
+ }
3572
+ /**
3573
+ * The column at a content offset, clamped to the sheet.
3574
+ *
3575
+ * A binary search over the prefix sum when the columns differ, and
3576
+ * a division when they do not — the uniform case is the common one
3577
+ * and should not pay for the general one.
3578
+ */
3579
+ columnAt(offset) {
3580
+ const last = Math.max(0, this.columnCount - 1);
3581
+ const past = offset - this.gutterWidth;
3582
+ if (this.widths === void 0) return clamp$1(Math.floor(past / this.uniformWidth), 0, last);
3583
+ if (past <= 0) return 0;
3584
+ if (past >= this.columnsWidth) return last;
3585
+ let low = 0;
3586
+ let high = last;
3587
+ while (low < high) {
3588
+ const mid = low + high + 1 >> 1;
3589
+ if (this.offsets[mid] <= past) low = mid;
3590
+ else high = mid - 1;
3591
+ }
3592
+ return low;
3593
+ }
3594
+ setRowCount(count) {
3595
+ if (count === this.rowCount) return;
3596
+ this.rowCount = Math.max(0, Math.floor(count));
3597
+ this.invalidate();
3598
+ }
3599
+ setColumnCount(count) {
3600
+ if (count === this.columnCount) return;
3601
+ this.columnCount = Math.max(0, Math.floor(count));
3602
+ this.invalidate();
3603
+ }
3604
+ /**
3605
+ * The bands, which the spike tunes from the screen.
3606
+ *
3607
+ * A band is the only defence against the round trip to the
3608
+ * application thread: rows inside it already hold their values when
3609
+ * the scroll reaches them. Widening it costs nodes on every frame
3610
+ * and cells on every publish, so what it should be is a measurement,
3611
+ * not a default — hence setters rather than a constant.
3612
+ */
3613
+ setOverscan(rows, columns) {
3614
+ const nextRows = Math.max(0, Math.floor(rows));
3615
+ const nextColumns = Math.max(0, Math.floor(columns));
3616
+ if (nextRows === this.rowOverscan && nextColumns === this.columnOverscan) return;
3617
+ this.rowOverscan = nextRows;
3618
+ this.columnOverscan = nextColumns;
3619
+ this.invalidate();
3620
+ }
3621
+ /**
3622
+ * How many rows and columns stay put while the rest scrolls.
3623
+ *
3624
+ * Freezing changes which rows are in the scrolled window, so every
3625
+ * mounted row is rebuilt — a row that was scrolling is now frozen
3626
+ * or the other way about.
3627
+ */
3628
+ setFrozen(rows, columns) {
3629
+ const nextRows = Math.max(0, Math.floor(rows));
3630
+ const nextColumns = Math.max(0, Math.floor(columns));
3631
+ if (nextRows === this.frozenRows && nextColumns === this.frozenColumns) return;
3632
+ this.frozenRows = nextRows;
3633
+ this.frozenColumns = nextColumns;
3634
+ this.invalidate();
3635
+ }
3636
+ /** Drops every mounted row and rebuilds against the last viewport. */
3637
+ invalidate() {
3638
+ this.mountedRows.clear();
3639
+ this.frozenMounted.clear();
3640
+ this.mountedHeader = void 0;
3641
+ this.range = EMPTY_RANGE;
3642
+ this.recompute();
3643
+ }
3644
+ /**
3645
+ * One step, run by the host each frame before layout.
3646
+ *
3647
+ * Nothing is measured and nothing is anchored: the offsets are exact,
3648
+ * so the rows a scroll reveals are placed correctly on the frame
3649
+ * that reveals them rather than corrected on the one after.
3650
+ */
3651
+ update(viewport) {
3652
+ this.viewport = viewport;
3653
+ this.recompute();
3654
+ }
3655
+ recompute() {
3656
+ const next = this.windowFor(this.viewport);
3657
+ if (sameRange(next, this.range)) return;
3658
+ if (next.firstColumn !== this.range.firstColumn || next.lastColumn !== this.range.lastColumn) {
3659
+ this.mountedRows.clear();
3660
+ this.frozenMounted.clear();
3661
+ this.mountedHeader = void 0;
3662
+ }
3663
+ this.range = next;
3664
+ this.children$.next(this.buildChildren());
3665
+ this.range$.next(next);
3666
+ }
3667
+ /**
3668
+ * The window, in content coordinates.
3669
+ *
3670
+ * The header and the gutter are *content*: they occupy the first
3671
+ * `headerHeight` pixels and the first `gutterWidth`, and `rowAt` and
3672
+ * `columnAt` already subtract them. So the range comes out slightly
3673
+ * generous at the near edges — the rows a header is covering are
3674
+ * mounted, and the columns behind the gutter are too — which is
3675
+ * correct rather than wasteful, since a sticky strip is drawn over
3676
+ * cells that are really there.
3677
+ */
3678
+ windowFor(viewport) {
3679
+ if (this.rowCount === 0 || this.columnCount === 0) return EMPTY_RANGE;
3680
+ const firstRow = Math.max(this.frozenRows, this.rowAt(viewport.scrollY) - this.rowOverscan);
3681
+ const lastRow = Math.min(this.rowCount - 1, this.rowAt(viewport.scrollY + Math.max(0, viewport.height)) + this.rowOverscan);
3682
+ const firstColumn = Math.max(this.frozenColumns, this.columnAt(viewport.scrollX) - this.columnOverscan);
3683
+ const lastColumn = Math.min(this.columnCount - 1, this.columnAt(viewport.scrollX + Math.max(0, viewport.width)) + this.columnOverscan);
3684
+ const range = {
3685
+ firstRow,
3686
+ lastRow,
3687
+ firstColumn,
3688
+ lastColumn
3363
3689
  };
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
3690
+ if (this.extendRange === void 0) return range;
3691
+ const wanted = this.extendRange(range);
3692
+ return {
3693
+ firstRow: clamp$1(Math.min(wanted.firstRow, firstRow), this.frozenRows, firstRow),
3694
+ lastRow: clamp$1(Math.max(wanted.lastRow, lastRow), lastRow, this.rowCount - 1),
3695
+ firstColumn: clamp$1(Math.min(wanted.firstColumn, firstColumn), this.frozenColumns, firstColumn),
3696
+ lastColumn: clamp$1(Math.max(wanted.lastColumn, lastColumn), lastColumn, this.columnCount - 1)
3379
3697
  };
3380
- case "Enter": return primary ? null : { kind: "newline" };
3381
3698
  }
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" };
3699
+ buildChildren() {
3700
+ const { firstRow, lastRow, firstColumn, lastColumn } = this.range;
3701
+ if (lastRow < firstRow) return [];
3702
+ const width = this.contentWidth;
3703
+ const lead = this.offsetOf(firstColumn) - this.frozenWidth;
3704
+ const children = [];
3705
+ if (this.renderHeader !== void 0) {
3706
+ this.mountedHeader ??= this.wrapHeader(this.renderHeader(firstColumn, lastColumn), width, lead);
3707
+ children.push(this.mountedHeader);
3708
+ }
3709
+ for (let row = 0; row < this.frozenRows && row < this.rowCount; row++) {
3710
+ let built = this.frozenMounted.get(row);
3711
+ if (built === void 0) {
3712
+ built = this.wrap(row, width, lead);
3713
+ this.frozenMounted.set(row, built);
3714
+ }
3715
+ children.push(built);
3716
+ }
3717
+ children.push(spacer("sheet:top", this.rowOffsetOf(firstRow) - this.rowOffsetOf(this.frozenRows)));
3718
+ for (const row of this.mountedRows.keys()) if (row < firstRow || row > lastRow) this.mountedRows.delete(row);
3719
+ for (let row = firstRow; row <= lastRow; row++) {
3720
+ let built = this.mountedRows.get(row);
3721
+ if (built === void 0) {
3722
+ built = this.wrap(row, width, lead);
3723
+ this.mountedRows.set(row, built);
3724
+ }
3725
+ children.push(built);
3387
3726
  }
3388
- return null;
3727
+ children.push(spacer("sheet:bottom", this.rowsHeight - this.rowOffsetOf(lastRow + 1)));
3728
+ return children;
3389
3729
  }
3390
- if (textFromKeys && isPrintable(key) && !modifiers.ctrl && !modifiers.meta) return {
3391
- kind: "insert",
3392
- text: key
3393
- };
3394
- return null;
3730
+ /**
3731
+ * The header, given the same geometry a row gets.
3732
+ *
3733
+ * Its own children are laid out exactly like a row's, so that a
3734
+ * column header lines up with the column under it without either
3735
+ * being told where the other is: the same leading spacer, the same
3736
+ * gutter-sized first cell, the same widths.
3737
+ */
3738
+ wrapHeader(built, width, lead) {
3739
+ if (!isRowElement(built)) throw new Error("A sheet's header must be a Row element, laid out like the rows it labels.");
3740
+ return {
3741
+ ...built,
3742
+ props: {
3743
+ ...built.props,
3744
+ key: "sheet:header",
3745
+ width,
3746
+ height: this.headerHeight,
3747
+ flexShrink: 0
3748
+ },
3749
+ children: this.withLead(built.children, lead)
3750
+ };
3751
+ }
3752
+ /**
3753
+ * A row's children with the leading spacer in the right place.
3754
+ *
3755
+ * With a gutter, the renderer's first child *is* the gutter: it sits
3756
+ * at content x 0 and the columns begin past it, so the spacer for
3757
+ * the columns scrolled out of view goes after it. Without one the
3758
+ * spacer goes first. Either way the renderer is handed a range of
3759
+ * columns and never has to know where the window is.
3760
+ */
3761
+ withLead(children, lead) {
3762
+ const pad = spacer("sheet:lead", void 0, lead);
3763
+ const stays = (this.gutterWidth > 0 ? 1 : 0) + this.frozenColumns;
3764
+ if (stays === 0) return [pad, ...children];
3765
+ return [
3766
+ ...children.slice(0, stays),
3767
+ pad,
3768
+ ...children.slice(stays)
3769
+ ];
3770
+ }
3771
+ /**
3772
+ * The row the renderer returned, given its place in the sheet.
3773
+ *
3774
+ * The leading spacer is prepended here rather than asked of the
3775
+ * renderer, because it is windowing bookkeeping and not a cell: it is
3776
+ * what puts column `firstColumn` at its own offset without the row
3777
+ * holding the columns to its left.
3778
+ */
3779
+ wrap(row, width, lead) {
3780
+ const built = this.renderRow(row, this.range.firstColumn, this.range.lastColumn);
3781
+ 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.`);
3782
+ return {
3783
+ ...built,
3784
+ props: {
3785
+ ...built.props,
3786
+ key: `sheet:row:${row}`,
3787
+ width,
3788
+ height: this.rowHeightOf(row),
3789
+ flexShrink: 0
3790
+ },
3791
+ children: this.withLead(built.children, lead)
3792
+ };
3793
+ }
3794
+ };
3795
+ function spacer(key, height, width) {
3796
+ return Box({
3797
+ key,
3798
+ height,
3799
+ width,
3800
+ flexShrink: 0
3801
+ });
3802
+ }
3803
+ function isRowElement(child) {
3804
+ return typeof child === "object" && child !== null && child.type === "row" && Array.isArray(child.children);
3805
+ }
3806
+ function sameRange(a, b) {
3807
+ return a.firstRow === b.firstRow && a.lastRow === b.lastRow && a.firstColumn === b.firstColumn && a.lastColumn === b.lastColumn;
3808
+ }
3809
+ function clamp$1(value, min, max) {
3810
+ return Math.min(Math.max(value, min), max);
3811
+ }
3812
+ function positive(name, value) {
3813
+ if (!(value > 0)) throw new Error(`LazySheet ${name} must be positive, got ${String(value)}.`);
3814
+ return value;
3395
3815
  }
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;
3816
+ /**
3817
+ * Feeds a sheet the inputs that may change, with the node's lifetime.
3818
+ *
3819
+ * The same reasoning as `lazySource`: a modifier is released inside
3820
+ * `removeSubtree`, and it attaches after the element's props and
3821
+ * before its children, so a count that arrives with the tree is in the
3822
+ * window before the first children are read from it.
3823
+ */
3824
+ const sheetSource = defineModifier({
3825
+ name: "sheetSource",
3826
+ attach(host, args) {
3827
+ follow$1(host, args.rowCount, (value) => args.sheet.setRowCount(value));
3828
+ follow$1(host, args.columnCount, (value) => args.sheet.setColumnCount(value));
3829
+ let rows;
3830
+ let columns;
3831
+ const apply = () => {
3832
+ if (rows !== void 0 || columns !== void 0) args.sheet.setOverscan(rows ?? 3, columns ?? 2);
3833
+ };
3834
+ if (args.rowOverscan !== void 0) follow$1(host, args.rowOverscan, (value) => {
3835
+ rows = value;
3836
+ apply();
3837
+ });
3838
+ if (args.columnOverscan !== void 0) follow$1(host, args.columnOverscan, (value) => {
3839
+ columns = value;
3840
+ apply();
3841
+ });
3402
3842
  }
3403
- return false;
3843
+ });
3844
+ function follow$1(host, source, apply) {
3845
+ if (!isObservable$1(source)) {
3846
+ apply(source);
3847
+ return;
3848
+ }
3849
+ host.own(source.subscribe((value) => {
3850
+ apply(value);
3851
+ host.requestFrame();
3852
+ }));
3853
+ }
3854
+ /**
3855
+ * A scrolling surface that mounts only the cells in view, on both axes.
3856
+ *
3857
+ * LazySheet({ rowCount: 10_000, columnCount: 100, rowHeight: 24, columnWidth: 96 },
3858
+ * (row, first, last) => Row({}, ...cells(row, first, last)))
3859
+ *
3860
+ * It is a ScrollView whose children are a spacer for the rows above
3861
+ * the window, the mounted rows, and a spacer for the rows below; each
3862
+ * row carries a spacer for the columns to its left and is as wide as
3863
+ * the whole sheet, so the container's content extent — and both
3864
+ * scrollbars — are the sheet's real size on the first frame rather
3865
+ * than an estimate that settles.
3866
+ */
3867
+ function LazySheet(props, renderRow) {
3868
+ const { rowCount, columnCount, rowHeight, rowHeights, columnWidth, rowOverscan, columnOverscan, gutterWidth, headerHeight, frozenRows, frozenColumns, header, extendRange, modifiers, sheetRef, ...rest } = props;
3869
+ const sheet = new UiVirtualSheet({
3870
+ rowCount,
3871
+ columnCount,
3872
+ rowHeight,
3873
+ rowHeights,
3874
+ columnWidth,
3875
+ rowOverscan,
3876
+ columnOverscan,
3877
+ gutterWidth,
3878
+ headerHeight,
3879
+ frozenRows,
3880
+ frozenColumns,
3881
+ extendRange
3882
+ }, renderRow, header);
3883
+ sheetRef?.(sheet);
3884
+ return createElement("scroll-view", {
3885
+ ...rest,
3886
+ modifiers: [...modifiers ?? [], sheetSource({
3887
+ sheet,
3888
+ rowCount,
3889
+ columnCount,
3890
+ rowOverscan,
3891
+ columnOverscan
3892
+ })],
3893
+ direction: "column",
3894
+ [VIRTUAL_SHEET_PROP]: sheet
3895
+ }, [sheet.children$]);
3404
3896
  }
3405
3897
  //#endregion
3406
3898
  //#region src/environment/EnvironmentNotifier.ts
@@ -4263,7 +4755,13 @@ var UiDragSession = class {
4263
4755
  return null;
4264
4756
  case "drop":
4265
4757
  if (this.current === null) this.begin(payload, message.x, message.y, null, true);
4266
- else this.move(message.x, message.y);
4758
+ else {
4759
+ this.current = {
4760
+ ...this.current,
4761
+ payload
4762
+ };
4763
+ this.move(message.x, message.y);
4764
+ }
4267
4765
  return this.end();
4268
4766
  }
4269
4767
  }
@@ -4328,449 +4826,6 @@ function dragSessionFor(node) {
4328
4826
  return session;
4329
4827
  }
4330
4828
  //#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
4829
  //#region src/layout/LayoutNotifier.ts
4775
4830
  /**
4776
4831
  * Tells listeners when a node's layout changed.
@@ -4976,6 +5031,110 @@ const DEFAULT_RING = kind$3(Object.freeze({}));
4976
5031
  function focusRing(options) {
4977
5032
  return options === void 0 ? DEFAULT_RING : kind$3(options);
4978
5033
  }
5034
+ /**
5035
+ * A border per edge, as paint rather than as layout.
5036
+ *
5037
+ * `borderWidth` is one number and `borderColor` one colour, so a node
5038
+ * cannot have a heavy bottom edge and a hairline top. This is the gap
5039
+ * closed from the cheap side. A border in Gesso is paint-only —
5040
+ * `paintBorder` strokes inside the box and touches no layout — so four
5041
+ * edges are four filled rectangles in the node's own paint pass, with
5042
+ * nothing to lay out and nothing to hit test. A bordered cell costs
5043
+ * four draw instances and no extra nodes.
5044
+ *
5045
+ * Box({ modifiers: [borders({ bottom: 2, right: 1, color: 'border' })] })
5046
+ *
5047
+ * They lie *inside* the box, where `borderWidth` puts them, so a border
5048
+ * never changes where anything sits and never overlaps a neighbour.
5049
+ *
5050
+ * **Why this is a modifier and not four props, and why that is the
5051
+ * answer rather than a stopgap.** First-class `borderTopWidth` is a
5052
+ * much larger change, and the cost is not where it looks: the
5053
+ * property, the paint state and the Canvas2D stroke are easy, but the
5054
+ * WebGPU instance packs `radius, opacity, borderWidth` as one
5055
+ * `float32x3`, the fragment shader draws the border as an isotropic
5056
+ * SDF band, and one instance carries one colour — so four widths need
5057
+ * a wider vertex format, an anisotropic inner rect in the shader, and
5058
+ * up to four instances when the colours differ. This needs none of it,
5059
+ * because a decoration is already a rectangle with its own colour that
5060
+ * both backends draw.
5061
+ *
5062
+ * What the properties would buy over this, once written, is a rounded
5063
+ * per-edge border and one instance instead of four. The first is a
5064
+ * thing square-cornered borders are for — a grid, a table, a rule —
5065
+ * and the second is four draws on a node that already costs one. So
5066
+ * the properties are not planned. If something turns up that wants a
5067
+ * heavy bottom edge on a rounded card, that is the case to reopen it
5068
+ * with, and it will be a better case than "for completeness".
5069
+ *
5070
+ * What it does not do is round its corners. Four rectangles meeting at
5071
+ * a square corner is right for a grid, a table and a rule, which is
5072
+ * what per-edge borders are for; a rounded box wants the single
5073
+ * `borderWidth`, which the renderers draw as one band and get right.
5074
+ */
5075
+ function borders(options) {
5076
+ return decorated(borderShapes(options));
5077
+ }
5078
+ /**
5079
+ * The rectangles `borders()` draws, without the modifier around them.
5080
+ *
5081
+ * For a surface that cannot afford a modifier per node and pushes its
5082
+ * decorations in instead. A spreadsheet is the case: ten thousand
5083
+ * mounted cells, each with its own border, fed from one writer into a
5084
+ * subject per cell — a modifier per cell would be ten thousand
5085
+ * attachments to do what ten thousand array writes already do. The
5086
+ * arithmetic is the same either way, and sharing it is the point:
5087
+ * an application that rolled its own would be the second place a
5088
+ * corner could be painted twice.
5089
+ */
5090
+ function borderShapes(options) {
5091
+ const shared = options.color ?? "border";
5092
+ const edge = (side) => {
5093
+ if (side === void 0) return;
5094
+ const resolved = typeof side === "number" ? { width: side } : side;
5095
+ return resolved.width > 0 ? resolved : void 0;
5096
+ };
5097
+ const top = edge(options.top);
5098
+ const bottom = edge(options.bottom);
5099
+ const left = edge(options.left);
5100
+ const right = edge(options.right);
5101
+ const between = {
5102
+ y: top?.width ?? 0,
5103
+ bottom: bottom?.width ?? 0
5104
+ };
5105
+ const shapes = [];
5106
+ if (top !== void 0) shapes.push({
5107
+ kind: "fill",
5108
+ color: top.color ?? shared,
5109
+ y: 0,
5110
+ height: top.width,
5111
+ radius: 0
5112
+ });
5113
+ if (bottom !== void 0) shapes.push({
5114
+ kind: "fill",
5115
+ color: bottom.color ?? shared,
5116
+ bottom: 0,
5117
+ height: bottom.width,
5118
+ radius: 0
5119
+ });
5120
+ if (left !== void 0) shapes.push({
5121
+ kind: "fill",
5122
+ color: left.color ?? shared,
5123
+ x: 0,
5124
+ width: left.width,
5125
+ radius: 0,
5126
+ ...between
5127
+ });
5128
+ if (right !== void 0) shapes.push({
5129
+ kind: "fill",
5130
+ color: right.color ?? shared,
5131
+ right: 0,
5132
+ width: right.width,
5133
+ radius: 0,
5134
+ ...between
5135
+ });
5136
+ return shapes;
5137
+ }
4979
5138
  //#endregion
4980
5139
  //#region src/modifiers/focus.ts
4981
5140
  /**
@@ -9660,12 +9819,16 @@ function buildRenderList(root, layout, measurer, logicalWidth, logicalHeight, dp
9660
9819
  if (text.editor !== void 0) {
9661
9820
  const model = text.editor;
9662
9821
  const editable = new EditableLayout(model, contentBox, text, measurer);
9822
+ pushTextRects(textRunBackgrounds(editable.lines, text));
9663
9823
  if (model.focused && !model.collapsed) {
9664
9824
  const selection = parseColor$1(text.selectionColor);
9665
9825
  if (selection !== void 0) for (const box of editable.selectionBoxes()) pushContentRect(box, selection);
9666
9826
  }
9667
9827
  if (editable.placeholderLines.length > 0) pushTextRun(editable.placeholderLines, colorToCss(text.placeholderColor));
9668
- else pushTextRun(editable.lines, colorToCss(text.textColor));
9828
+ else {
9829
+ pushTextRun(editable.lines, colorToCss(text.textColor));
9830
+ pushTextRects(textRunDecorations(editable.lines, text));
9831
+ }
9669
9832
  if (model.composing) {
9670
9833
  const underline = parseColor$1(text.textColor);
9671
9834
  if (underline !== void 0) {
@@ -13380,6 +13543,7 @@ var UiScheduler = class {
13380
13543
  dirty;
13381
13544
  onFrame;
13382
13545
  beforeCollect;
13546
+ onFrameError;
13383
13547
  disposed = false;
13384
13548
  active = true;
13385
13549
  pending = false;
@@ -13398,6 +13562,9 @@ var UiScheduler = class {
13398
13562
  this.dirty = options.dirty;
13399
13563
  this.onFrame = options.onFrame;
13400
13564
  this.beforeCollect = options.beforeCollect;
13565
+ this.onFrameError = options.onFrameError ?? ((error) => {
13566
+ console.error("A Gesso frame threw. The frame was abandoned and the clock kept running.", error);
13567
+ });
13401
13568
  this.clock = options.clock((time) => {
13402
13569
  this.handleFrame(time);
13403
13570
  });
@@ -13505,30 +13672,42 @@ var UiScheduler = class {
13505
13672
  this.collecting = true;
13506
13673
  let frame;
13507
13674
  let collected = 0;
13675
+ /**
13676
+ * Everything a frame does, inside one guard.
13677
+ *
13678
+ * The guard is the whole point: an exception used to unwind past
13679
+ * the re-arm at the bottom of this method, and since `pending` had
13680
+ * already been cleared at the top, nothing asked for another
13681
+ * frame. One throw stopped the application for good.
13682
+ */
13508
13683
  try {
13509
- this.beforeCollect?.(time);
13684
+ try {
13685
+ this.beforeCollect?.(time);
13686
+ if (measuring) {
13687
+ collected = markNow();
13688
+ measureSpan("before collect", started, collected);
13689
+ }
13690
+ frame = this.collectFrame(time);
13691
+ } finally {
13692
+ this.collecting = false;
13693
+ }
13694
+ let processed = 0;
13510
13695
  if (measuring) {
13511
- collected = markNow();
13512
- measureSpan("before collect", started, collected);
13696
+ processed = markNow();
13697
+ measureSpan("collect", collected, processed);
13513
13698
  }
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);
13699
+ if (frame.size > 0) this.onFrame(frame);
13700
+ if (measuring) {
13701
+ const finished = markNow();
13702
+ const detail = {
13703
+ frame: frame.id,
13704
+ nodes: frame.size
13705
+ };
13706
+ measureSpan("process", processed, finished, detail);
13707
+ measureSpan("frame", started, finished, detail);
13708
+ }
13709
+ } catch (error) {
13710
+ this.onFrameError(error, time);
13532
13711
  }
13533
13712
  if (!this.dirty.isEmpty()) this.notifyDirty();
13534
13713
  }
@@ -14667,6 +14846,6 @@ function recordsEqual(a, b) {
14667
14846
  return true;
14668
14847
  }
14669
14848
  //#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 };
14849
+ 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
14850
 
14672
14851
  //# sourceMappingURL=index.js.map