@beyondwork/docx-react-component 1.0.105 → 1.0.108
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/package.json +19 -5
- package/src/api/geometry-overlay-rects.ts +5 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/page-anchor-id.ts +5 -0
- package/src/api/public-types.ts +16 -9
- package/src/api/table-node-specs.ts +6 -0
- package/src/api/v3/_create.ts +10 -2
- package/src/api/v3/_page-anchor-id.ts +52 -0
- package/src/api/v3/_runtime-handle.ts +92 -1
- package/src/api/v3/ai/_audit-reference.ts +28 -0
- package/src/api/v3/ai/_audit-time.ts +5 -0
- package/src/api/v3/ai/_pe2-evidence.ts +310 -6
- package/src/api/v3/ai/attach.ts +29 -4
- package/src/api/v3/ai/bundle.ts +6 -2
- package/src/api/v3/ai/inspect.ts +6 -2
- package/src/api/v3/ai/replacement.ts +112 -18
- package/src/api/v3/ai/resolve.ts +2 -2
- package/src/api/v3/ai/review.ts +177 -3
- package/src/api/v3/index.ts +8 -0
- package/src/api/v3/runtime/collab.ts +462 -0
- package/src/api/v3/runtime/document.ts +503 -20
- package/src/api/v3/runtime/geometry.ts +97 -0
- package/src/api/v3/runtime/layout.ts +744 -0
- package/src/api/v3/runtime/perf-probe.ts +14 -0
- package/src/api/v3/runtime/viewport.ts +9 -8
- package/src/api/v3/ui/_types.ts +202 -55
- package/src/api/v3/ui/chrome-preset-model.ts +5 -5
- package/src/api/v3/ui/debug.ts +115 -2
- package/src/api/v3/ui/index.ts +17 -0
- package/src/api/v3/ui/overlays.ts +0 -8
- package/src/api/v3/ui/surface.ts +56 -0
- package/src/api/v3/ui/viewport.ts +119 -9
- package/src/core/commands/image-commands.ts +1 -0
- package/src/core/commands/index.ts +6 -0
- package/src/core/schema/text-schema.ts +43 -5
- package/src/core/selection/mapping.ts +8 -1
- package/src/core/selection/review-anchors.ts +5 -1
- package/src/core/state/text-transaction.ts +8 -2
- package/src/io/export/serialize-revisions.ts +149 -1
- package/src/io/normalize/normalize-text.ts +6 -0
- package/src/io/ooxml/parse-bookmark-references.ts +55 -0
- package/src/io/ooxml/parse-fields.ts +24 -2
- package/src/io/ooxml/parse-headers-footers.ts +38 -5
- package/src/io/ooxml/parse-main-document.ts +153 -9
- package/src/io/ooxml/parse-numbering.ts +20 -0
- package/src/io/ooxml/parse-revisions.ts +19 -8
- package/src/io/opc/package-reader.ts +98 -8
- package/src/model/anchor.ts +4 -3
- package/src/model/canonical-document.ts +220 -2
- package/src/model/canonical-hash.ts +221 -0
- package/src/model/canonical-layout-inputs.ts +245 -6
- package/src/model/layout/index.ts +1 -0
- package/src/model/layout/page-graph-types.ts +147 -1
- package/src/model/review/revision-types.ts +14 -3
- package/src/preservation/store.ts +20 -4
- package/src/review/README.md +1 -1
- package/src/review/store/revision-actions.ts +14 -2
- package/src/runtime/collab/event-types.ts +67 -1
- package/src/runtime/collab/runtime-collab-sync.ts +177 -5
- package/src/runtime/diagnostics/layout-guard-warning.ts +18 -0
- package/src/runtime/document-heading-outline.ts +147 -0
- package/src/runtime/document-navigation.ts +8 -243
- package/src/runtime/document-runtime.ts +279 -115
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +11 -0
- package/src/runtime/formatting/layout-inputs.ts +38 -5
- package/src/runtime/formatting/numbering/geometry.ts +28 -2
- package/src/runtime/geometry/adjacent-geometry-intake.ts +835 -0
- package/src/runtime/geometry/caret-geometry.ts +5 -6
- package/src/runtime/geometry/geometry-facet.ts +60 -10
- package/src/runtime/geometry/geometry-index.ts +661 -16
- package/src/runtime/geometry/geometry-types.ts +59 -0
- package/src/runtime/geometry/hit-test.ts +11 -1
- package/src/runtime/geometry/overlay-rects.ts +5 -3
- package/src/runtime/geometry/project-anchors.ts +1 -1
- package/src/runtime/geometry/word-layout-v2-line-intake.ts +323 -0
- package/src/runtime/layout/index.ts +6 -0
- package/src/runtime/layout/layout-engine-instance.ts +6 -1
- package/src/runtime/layout/layout-engine-version.ts +188 -16
- package/src/runtime/layout/layout-facet-types.ts +6 -0
- package/src/runtime/layout/page-graph.ts +23 -4
- package/src/runtime/layout/paginated-layout-engine.ts +149 -15
- package/src/runtime/layout/project-block-fragments.ts +351 -14
- package/src/runtime/layout/public-facet.ts +162 -24
- package/src/runtime/layout/table-row-continuation-contract.ts +107 -0
- package/src/runtime/layout/table-row-split.ts +92 -35
- package/src/runtime/prerender/cache-envelope.ts +2 -2
- package/src/runtime/prerender/cache-key.ts +5 -4
- package/src/runtime/prerender/customxml-cache.ts +0 -1
- package/src/runtime/render/render-kernel.ts +1 -1
- package/src/runtime/revision-runtime.ts +112 -10
- package/src/runtime/scopes/_scope-dependencies.ts +1 -0
- package/src/runtime/scopes/action-validation.ts +22 -2
- package/src/runtime/scopes/capabilities.ts +316 -0
- package/src/runtime/scopes/compile-scope-bundle.ts +14 -0
- package/src/runtime/scopes/compiler-service.ts +108 -4
- package/src/runtime/scopes/content-control-evidence.ts +79 -0
- package/src/runtime/scopes/create-issue.ts +5 -5
- package/src/runtime/scopes/evidence.ts +91 -0
- package/src/runtime/scopes/formatting/apply.ts +2 -0
- package/src/runtime/scopes/geometry-evidence.ts +130 -0
- package/src/runtime/scopes/index.ts +54 -0
- package/src/runtime/scopes/issue-lifecycle.ts +224 -0
- package/src/runtime/scopes/layout-evidence.ts +374 -0
- package/src/runtime/scopes/multi-paragraph-refusal.ts +37 -0
- package/src/runtime/scopes/preservation-boundary.ts +7 -1
- package/src/runtime/scopes/replacement/apply.ts +97 -34
- package/src/runtime/scopes/scope-kinds/paragraph.ts +108 -12
- package/src/runtime/scopes/semantic-scope-types.ts +242 -3
- package/src/runtime/scopes/visualization.ts +28 -0
- package/src/runtime/surface-projection.ts +44 -5
- package/src/runtime/telemetry/perf-probe.ts +216 -0
- package/src/runtime/virtualized-rendering.ts +36 -1
- package/src/runtime/workflow/ai-issue-lifecycle.ts +253 -0
- package/src/runtime/workflow/coordinator.ts +39 -11
- package/src/runtime/workflow/derived-scope-resolver.ts +63 -9
- package/src/runtime/workflow/index.ts +4 -0
- package/src/runtime/workflow/overlay-lane-types.ts +58 -0
- package/src/runtime/workflow/overlay-lanes.ts +386 -0
- package/src/runtime/workflow/overlay-store.ts +2 -2
- package/src/runtime/workflow/redline-posture-calibration.ts +257 -0
- package/src/runtime/workflow/word-field-matrix-calibration.ts +231 -0
- package/src/session/_sync-legacy.ts +17 -27
- package/src/session/import/loader.ts +6 -4
- package/src/session/import/source-package-evidence.ts +186 -2
- package/src/session/index.ts +5 -6
- package/src/session/session.ts +30 -56
- package/src/session/types.ts +8 -13
- package/src/shell/session-bootstrap.ts +155 -81
- package/src/ui/WordReviewEditor.tsx +520 -12
- package/src/ui/editor-shell-view.tsx +14 -4
- package/src/ui/editor-surface-controller.tsx +5 -3
- package/src/ui/headless/selection-tool-resolver.ts +1 -2
- package/src/ui/presence-overlay-lane.ts +130 -0
- package/src/ui/ui-controller-factory.ts +17 -0
- package/src/ui-tailwind/chrome/build-context-menu-entries.ts +5 -1
- package/src/ui-tailwind/chrome/editor-action-registry.ts +105 -5
- package/src/ui-tailwind/chrome/editor-actions-to-palette.ts +7 -0
- package/src/ui-tailwind/chrome/layer-debug-contracts.ts +208 -0
- package/src/ui-tailwind/chrome/resolve-target-kind.ts +13 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +11 -3
- package/src/ui-tailwind/chrome/tw-command-palette.tsx +36 -6
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +6 -1
- package/src/ui-tailwind/chrome/tw-display-mode-selector.tsx +42 -109
- package/src/ui-tailwind/chrome/tw-inline-find-bar.tsx +26 -6
- package/src/ui-tailwind/chrome/tw-navigation-command-bar.tsx +328 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +8 -4
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +129 -1
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +19 -5
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +5 -1
- package/src/ui-tailwind/chrome/tw-workspace-chrome-host.tsx +28 -12
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +30 -3
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +116 -10
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +223 -94
- package/src/ui-tailwind/chrome-overlay/tw-presence-overlay-lane.tsx +157 -0
- package/src/ui-tailwind/chrome-overlay/tw-review-overlay-lane-markers.tsx +259 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +5 -2
- package/src/ui-tailwind/chrome-overlay/tw-substrate-overlay-lanes.tsx +314 -0
- package/src/ui-tailwind/debug/README.md +4 -1
- package/src/ui-tailwind/debug/layer11-consumer-readiness.ts +272 -0
- package/src/ui-tailwind/debug/layer11-word-field-matrix-evidence.ts +160 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +14 -215
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +42 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +38 -2
- package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +34 -5
- package/src/ui-tailwind/editor-surface/runtime-decoration-plugin.ts +9 -19
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -2
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +145 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +16 -11
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +8 -10
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +3 -0
- package/src/ui-tailwind/page-stack/tw-page-chrome-entry.tsx +4 -2
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +60 -20
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +16 -11
- package/src/ui-tailwind/review/tw-health-panel.tsx +36 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +7 -4
- package/src/ui-tailwind/review-workspace/diagnostics-visibility.ts +44 -0
- package/src/ui-tailwind/review-workspace/page-shell-metrics.ts +11 -0
- package/src/ui-tailwind/review-workspace/tw-review-workspace-rail.tsx +16 -1
- package/src/ui-tailwind/review-workspace/types.ts +26 -12
- package/src/ui-tailwind/review-workspace/use-diagnostics-signal.ts +40 -11
- package/src/ui-tailwind/review-workspace/use-layout-facet-render-signal.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-page-markers.ts +15 -26
- package/src/ui-tailwind/review-workspace/use-scope-card-state.ts +35 -18
- package/src/ui-tailwind/review-workspace/use-selection-toolbar-placement.ts +41 -32
- package/src/ui-tailwind/review-workspace/use-status-bar-page-facts.ts +2 -1
- package/src/ui-tailwind/review-workspace/use-workspace-side-effects.ts +2 -1
- package/src/ui-tailwind/status/tw-status-bar.tsx +6 -5
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +52 -80
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +12 -48
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +9 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +328 -361
- package/src/ui-tailwind/tw-review-workspace.tsx +152 -286
|
@@ -47,13 +47,19 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import * as React from "react";
|
|
50
|
-
import type {
|
|
51
|
-
|
|
50
|
+
import type {
|
|
51
|
+
GeometryFacet,
|
|
52
|
+
PageLayoutSnapshot,
|
|
53
|
+
WordReviewEditorLayoutFacet,
|
|
54
|
+
} from "../../api/public-types";
|
|
55
|
+
import { DEFAULT_PX_PER_TWIP } from "../../api/public-types";
|
|
56
|
+
import { buildPageAnchorAttributes } from "../../api/v3/_page-anchor-id.ts";
|
|
52
57
|
import {
|
|
53
58
|
resolvePageOverlayRectsFromGeometry as resolvePageOverlayRectsFromGeometryImpl,
|
|
54
|
-
type GeometryFacet,
|
|
55
59
|
type OverlayVisiblePageIndexRange,
|
|
56
|
-
} from "../../
|
|
60
|
+
} from "../../api/geometry-overlay-rects.ts";
|
|
61
|
+
import { PAGE_CHROME_DEFAULTS } from "../editor-surface/pm-page-break-decorations.ts";
|
|
62
|
+
import { incrementInvalidationCounter } from "../editor-surface/perf-probe";
|
|
57
63
|
import type { ApiV3Ui } from "../../api/v3/ui";
|
|
58
64
|
import { useUiApi } from "../ui-api-context";
|
|
59
65
|
|
|
@@ -72,6 +78,8 @@ export interface PageOverlayRect {
|
|
|
72
78
|
bottomPx: number;
|
|
73
79
|
/** Rendered height = bottomPx - topPx. */
|
|
74
80
|
heightPx: number;
|
|
81
|
+
/** Cold fallback marker; omitted for UI/geometry/DOM-derived rects. */
|
|
82
|
+
source?: "skeletal";
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
/**
|
|
@@ -108,7 +116,8 @@ function pageOverlayRectsEqual(
|
|
|
108
116
|
left.pageIndex !== right.pageIndex ||
|
|
109
117
|
left.topPx !== right.topPx ||
|
|
110
118
|
left.bottomPx !== right.bottomPx ||
|
|
111
|
-
left.heightPx !== right.heightPx
|
|
119
|
+
left.heightPx !== right.heightPx ||
|
|
120
|
+
left.source !== right.source
|
|
112
121
|
) {
|
|
113
122
|
return false;
|
|
114
123
|
}
|
|
@@ -116,6 +125,81 @@ function pageOverlayRectsEqual(
|
|
|
116
125
|
return true;
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
function resolvePageFrameHeightPx(layout: PageLayoutSnapshot): number | null {
|
|
129
|
+
if (!Number.isFinite(layout.pageHeight) || layout.pageHeight <= 0) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return Math.max(1, Math.round(layout.pageHeight * DEFAULT_PX_PER_TWIP));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolvePageFrameHeightPxFromLayoutPage(
|
|
136
|
+
page: ReturnType<WordReviewEditorLayoutFacet["getPage"]>,
|
|
137
|
+
): number | null {
|
|
138
|
+
if (!page) return null;
|
|
139
|
+
const frameHeightTwips = page.frame?.physicalBoundsTwips.heightTwips;
|
|
140
|
+
if (
|
|
141
|
+
typeof frameHeightTwips === "number" &&
|
|
142
|
+
Number.isFinite(frameHeightTwips) &&
|
|
143
|
+
frameHeightTwips > 0
|
|
144
|
+
) {
|
|
145
|
+
return Math.max(1, Math.round(frameHeightTwips * DEFAULT_PX_PER_TWIP));
|
|
146
|
+
}
|
|
147
|
+
return resolvePageFrameHeightPx(page.layout);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* L11 page-stack consumers receive the mounted L04 page-frame substrate through
|
|
152
|
+
* the public layout facet. This mirrors the records exposed by
|
|
153
|
+
* `runtime.layout.getPageFrames()` without making the React painter depend on a
|
|
154
|
+
* runtime handle; DOM page-frame markers stay a degraded cold-open fallback.
|
|
155
|
+
*/
|
|
156
|
+
export function resolvePageOverlayPageIdsFromLayout(
|
|
157
|
+
facet: Pick<WordReviewEditorLayoutFacet, "getPageCount" | "getPage">,
|
|
158
|
+
): readonly string[] | null {
|
|
159
|
+
const pageCount = facet.getPageCount();
|
|
160
|
+
if (pageCount <= 0) return [];
|
|
161
|
+
const pageIds: string[] = [];
|
|
162
|
+
for (let pageIndex = 0; pageIndex < pageCount; pageIndex += 1) {
|
|
163
|
+
const page = facet.getPage(pageIndex);
|
|
164
|
+
if (!page) return null;
|
|
165
|
+
pageIds.push(page.frame?.pageId ?? page.pageId);
|
|
166
|
+
}
|
|
167
|
+
return pageIds;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Cold geometry fallback: before the render kernel or UI anchor provider binds,
|
|
172
|
+
* the public layout facet can still tell us how many pages exist and which
|
|
173
|
+
* L04 page frame / canonical section layout owns each page. Use only public
|
|
174
|
+
* frame or section sizing to draw paper cards, then upgrade in place when
|
|
175
|
+
* UI/geometry/DOM rects arrive.
|
|
176
|
+
*/
|
|
177
|
+
export function resolveSkeletalPageOverlayRectsFromLayout(
|
|
178
|
+
facet: Pick<WordReviewEditorLayoutFacet, "getPageCount" | "getPage">,
|
|
179
|
+
): readonly PageOverlayRect[] {
|
|
180
|
+
const pageCount = facet.getPageCount();
|
|
181
|
+
if (pageCount <= 0) return [];
|
|
182
|
+
|
|
183
|
+
const rects: PageOverlayRect[] = [];
|
|
184
|
+
let topPx = 0;
|
|
185
|
+
for (let pageIndex = 0; pageIndex < pageCount; pageIndex += 1) {
|
|
186
|
+
const page = facet.getPage(pageIndex);
|
|
187
|
+
if (!page) return [];
|
|
188
|
+
const heightPx = resolvePageFrameHeightPxFromLayoutPage(page);
|
|
189
|
+
if (heightPx === null) return [];
|
|
190
|
+
rects.push({
|
|
191
|
+
pageId: page.frame?.pageId ?? page.pageId,
|
|
192
|
+
pageIndex,
|
|
193
|
+
topPx,
|
|
194
|
+
bottomPx: topPx + heightPx,
|
|
195
|
+
heightPx,
|
|
196
|
+
source: "skeletal",
|
|
197
|
+
});
|
|
198
|
+
topPx += heightPx + PAGE_CHROME_DEFAULTS.interGapPx;
|
|
199
|
+
}
|
|
200
|
+
return rects;
|
|
201
|
+
}
|
|
202
|
+
|
|
119
203
|
function pageOverlayLastBottom(rects: readonly PageOverlayRect[]): number {
|
|
120
204
|
let bottom = 0;
|
|
121
205
|
for (const rect of rects) {
|
|
@@ -239,6 +323,19 @@ function collectTableEmbeddedBoundaryIndices(
|
|
|
239
323
|
return indices;
|
|
240
324
|
}
|
|
241
325
|
|
|
326
|
+
function containsTableBoundaryRisk(queryRoot: HTMLElement | null): boolean {
|
|
327
|
+
if (!queryRoot) return false;
|
|
328
|
+
if (queryRoot.getElementsByTagName("table").length > 0) return true;
|
|
329
|
+
const descendants = queryRoot.getElementsByTagName("*");
|
|
330
|
+
for (let i = 0; i < descendants.length; i += 1) {
|
|
331
|
+
const element = descendants[i] as HTMLElement;
|
|
332
|
+
if (element.getAttribute("data-pm-table-root") === "true") {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
242
339
|
/**
|
|
243
340
|
* Pure helper: turn pre-measured page-boundary widget positions into
|
|
244
341
|
* one `PageOverlayRect` per page. No DOM access — the caller supplies
|
|
@@ -553,6 +650,58 @@ function readOverlayFlowHeight(origin: HTMLElement | null): number {
|
|
|
553
650
|
return readElementFlowHeight(origin, { includeScrollHeight: false });
|
|
554
651
|
}
|
|
555
652
|
|
|
653
|
+
export function reconcilePageStackRectsWithFlow(input: {
|
|
654
|
+
baseRects: readonly PageOverlayRect[];
|
|
655
|
+
pageCount: number;
|
|
656
|
+
scrollRoot: HTMLElement | null;
|
|
657
|
+
originElement: HTMLElement | null;
|
|
658
|
+
}): readonly PageOverlayRect[] {
|
|
659
|
+
const { baseRects, pageCount, scrollRoot, originElement } = input;
|
|
660
|
+
if (baseRects.length === 0 || pageCount <= 0) return baseRects;
|
|
661
|
+
const flowHeight = readOverlayFlowHeight(originElement);
|
|
662
|
+
if (flowHeight <= 0) return baseRects;
|
|
663
|
+
|
|
664
|
+
const geometryBottom = pageOverlayLastBottom(baseRects);
|
|
665
|
+
const tableBoundaryRisk = containsTableBoundaryRisk(scrollRoot);
|
|
666
|
+
if (!tableBoundaryRisk && flowHeight <= geometryBottom + 1) {
|
|
667
|
+
return extendFinalPageOverlayRectToFlowHeight(baseRects, flowHeight);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const bridgedBase = extendPageOverlayRectsAcrossTableBoundaryGaps(
|
|
671
|
+
baseRects,
|
|
672
|
+
tableBoundaryRisk ? collectTableEmbeddedBoundaryIndices(scrollRoot) : [],
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
const extendedBase = extendFinalPageOverlayRectToFlowHeight(
|
|
676
|
+
bridgedBase,
|
|
677
|
+
flowHeight,
|
|
678
|
+
);
|
|
679
|
+
if (!originElement || !scrollRoot) return extendedBase;
|
|
680
|
+
|
|
681
|
+
// The common warm path has geometry and DOM flow in agreement. Avoid the
|
|
682
|
+
// full boundary-widget scan unless the PM flow is visibly taller than the
|
|
683
|
+
// geometry stack. When it is taller, page-stack consumers must follow the
|
|
684
|
+
// in-flow boundaries so content remains on page chrome instead of canvas.
|
|
685
|
+
if (flowHeight <= pageOverlayLastBottom(bridgedBase) + 1) {
|
|
686
|
+
return extendedBase;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const widgets = measureWidgetsViaBoundingRect(scrollRoot, originElement, {
|
|
690
|
+
pageCount,
|
|
691
|
+
visiblePageIndexRange: null,
|
|
692
|
+
});
|
|
693
|
+
if (widgets.length === 0) return extendedBase;
|
|
694
|
+
|
|
695
|
+
const flowRects = resolvePageOverlayRects({
|
|
696
|
+
widgets,
|
|
697
|
+
pageCount,
|
|
698
|
+
scrollHeight: flowHeight,
|
|
699
|
+
visiblePageIndexRange: null,
|
|
700
|
+
});
|
|
701
|
+
const merged = mergePageOverlayRectsByPageIndex(extendedBase, flowRects);
|
|
702
|
+
return extendFinalPageOverlayRectToFlowHeight(merged, flowHeight);
|
|
703
|
+
}
|
|
704
|
+
|
|
556
705
|
// ---------------------------------------------------------------------------
|
|
557
706
|
// Component
|
|
558
707
|
// ---------------------------------------------------------------------------
|
|
@@ -681,31 +830,34 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
681
830
|
// null here when headless / off-provider; the overlay then falls
|
|
682
831
|
// through to the existing geometry-facet / DOM paths.
|
|
683
832
|
const ui = useUiApi();
|
|
684
|
-
// Flicker-remediation 2026-04-22
|
|
685
|
-
// from the geometry facet when
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
//
|
|
689
|
-
// the empty initial state + the fallback paper rendered below.
|
|
833
|
+
// Flicker-remediation 2026-04-22 + Pass-7 hardening 2026-04-27:
|
|
834
|
+
// compute initial rects synchronously from the geometry facet when
|
|
835
|
+
// the kernel is already warm. If geometry is cold or absent, draw
|
|
836
|
+
// skeletal page cards from canonical section sizing so first paint
|
|
837
|
+
// still shows the document outline.
|
|
690
838
|
const [rects, setRects] = React.useState<readonly PageOverlayRect[]>(() => {
|
|
691
|
-
if (!geometryFacet) return [];
|
|
692
839
|
const pageCount = facet.getPageCount();
|
|
693
840
|
if (pageCount <= 0) return [];
|
|
694
841
|
// Paper-card backgrounds are cheap and must stay ahead of scroll. Heavy
|
|
695
842
|
// header/footer/footnote chrome still consumes `visiblePageIndexRange`;
|
|
696
843
|
// this decorative white-paper layer renders every card so fast scrolls
|
|
697
844
|
// never expose the gray canvas while the page window catches up.
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
845
|
+
if (geometryFacet) {
|
|
846
|
+
const warm = resolvePageOverlayRectsFromGeometry(
|
|
847
|
+
geometryFacet,
|
|
848
|
+
pageCount,
|
|
849
|
+
null,
|
|
850
|
+
);
|
|
851
|
+
if (warm) {
|
|
852
|
+
return extendPageOverlayRectsAcrossTableBoundaryGaps(
|
|
705
853
|
warm,
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
854
|
+
containsTableBoundaryRisk(scrollRoot)
|
|
855
|
+
? collectTableEmbeddedBoundaryIndices(scrollRoot)
|
|
856
|
+
: [],
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return resolveSkeletalPageOverlayRectsFromLayout(facet);
|
|
709
861
|
});
|
|
710
862
|
// P3.d fix: the overlay root acts as the **measurement origin** so
|
|
711
863
|
// widget `topPx` / `bottomPx` are expressed in the exact coordinate
|
|
@@ -746,49 +898,19 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
746
898
|
baseRects: readonly PageOverlayRect[],
|
|
747
899
|
pageCount: number,
|
|
748
900
|
): readonly PageOverlayRect[] => {
|
|
749
|
-
|
|
750
|
-
const origin = overlayRootRef.current;
|
|
751
|
-
const flowHeight = readOverlayFlowHeight(origin);
|
|
752
|
-
const bridgedBase = extendPageOverlayRectsAcrossTableBoundaryGaps(
|
|
901
|
+
return reconcilePageStackRectsWithFlow({
|
|
753
902
|
baseRects,
|
|
754
|
-
collectTableEmbeddedBoundaryIndices(scrollRoot),
|
|
755
|
-
);
|
|
756
|
-
if (flowHeight <= 0) return bridgedBase;
|
|
757
|
-
|
|
758
|
-
const extendedBase = extendFinalPageOverlayRectToFlowHeight(
|
|
759
|
-
bridgedBase,
|
|
760
|
-
flowHeight,
|
|
761
|
-
);
|
|
762
|
-
if (!origin || !scrollRoot) return extendedBase;
|
|
763
|
-
|
|
764
|
-
// The common warm path has geometry and DOM flow in agreement. Avoid the
|
|
765
|
-
// full boundary-widget scan unless the PM flow is visibly taller than the
|
|
766
|
-
// geometry stack. When it is taller, the paper-card layer must follow the
|
|
767
|
-
// in-flow boundaries so content remains on paper instead of canvas.
|
|
768
|
-
if (flowHeight <= pageOverlayLastBottom(bridgedBase) + 1) {
|
|
769
|
-
return extendedBase;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
const widgets = measureWidgetsViaBoundingRect(scrollRoot, origin, {
|
|
773
|
-
pageCount,
|
|
774
|
-
visiblePageIndexRange: null,
|
|
775
|
-
});
|
|
776
|
-
if (widgets.length === 0) return extendedBase;
|
|
777
|
-
|
|
778
|
-
const flowRects = resolvePageOverlayRects({
|
|
779
|
-
widgets,
|
|
780
903
|
pageCount,
|
|
781
|
-
|
|
782
|
-
|
|
904
|
+
scrollRoot,
|
|
905
|
+
originElement: overlayRootRef.current,
|
|
783
906
|
});
|
|
784
|
-
const merged = mergePageOverlayRectsByPageIndex(extendedBase, flowRects);
|
|
785
|
-
return extendFinalPageOverlayRectToFlowHeight(merged, flowHeight);
|
|
786
907
|
},
|
|
787
908
|
[scrollRoot],
|
|
788
909
|
);
|
|
789
910
|
|
|
790
911
|
const refreshRectsNow = React.useCallback(() => {
|
|
791
912
|
const pageCount = facet.getPageCount();
|
|
913
|
+
const skeletalRects = resolveSkeletalPageOverlayRectsFromLayout(facet);
|
|
792
914
|
|
|
793
915
|
// DS-C2 — first try the UI API seam so presentation code does not
|
|
794
916
|
// reach into the geometry facet directly. Page-ids come from the
|
|
@@ -805,7 +927,8 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
805
927
|
// threshold during steady-state typing (follow-up work — bench
|
|
806
928
|
// setup for F01/F08 typing scenarios is a separate task).
|
|
807
929
|
if (ui) {
|
|
808
|
-
const
|
|
930
|
+
const layoutPageIds = resolvePageOverlayPageIdsFromLayout(facet);
|
|
931
|
+
const geometryPageIds: string[] | null = geometryFacet
|
|
809
932
|
? Array.from({ length: pageCount }, (_, i) => {
|
|
810
933
|
const page = geometryFacet.getPage(i);
|
|
811
934
|
return page?.pageId ?? `page-${i}`;
|
|
@@ -815,7 +938,7 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
815
938
|
ui,
|
|
816
939
|
pageCount,
|
|
817
940
|
null,
|
|
818
|
-
|
|
941
|
+
layoutPageIds ?? geometryPageIds,
|
|
819
942
|
);
|
|
820
943
|
if (uiRects !== null) {
|
|
821
944
|
incrementInvalidationCounter("overlay.page.ui_api.hit");
|
|
@@ -843,10 +966,11 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
843
966
|
setRectsIfChanged(reconcilePaperRectsWithFlow(geometryRects, pageCount));
|
|
844
967
|
return;
|
|
845
968
|
}
|
|
969
|
+
incrementInvalidationCounter("overlay.page.geometry.fallthrough");
|
|
846
970
|
}
|
|
847
971
|
|
|
848
972
|
if (!scrollRoot) {
|
|
849
|
-
setRectsIfChanged(
|
|
973
|
+
setRectsIfChanged(skeletalRects);
|
|
850
974
|
return;
|
|
851
975
|
}
|
|
852
976
|
const origin = overlayRootRef.current;
|
|
@@ -854,6 +978,7 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
854
978
|
// Cold-open / pre-paint DOM fallback — warm path early-returned
|
|
855
979
|
// above via `geometryFacet` or the UI-API resolver. Lines below
|
|
856
980
|
// fire only before the first render frame.
|
|
981
|
+
incrementInvalidationCounter("overlay.page.dom_fallback");
|
|
857
982
|
if (origin) {
|
|
858
983
|
incrementInvalidationCounter("overlay.page.dom.degraded");
|
|
859
984
|
const widgets = measureWidgetsViaBoundingRect(scrollRoot, origin, {
|
|
@@ -870,7 +995,8 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
870
995
|
origin.clientHeight > 0 ? origin.clientHeight : originRect.height,
|
|
871
996
|
visiblePageIndexRange: null,
|
|
872
997
|
});
|
|
873
|
-
|
|
998
|
+
const reconciled = reconcilePaperRectsWithFlow(domRects, pageCount);
|
|
999
|
+
setRectsIfChanged(reconciled.length > 0 ? reconciled : skeletalRects);
|
|
874
1000
|
} else {
|
|
875
1001
|
incrementInvalidationCounter("overlay.page.dom.degraded");
|
|
876
1002
|
const widgets = measureWidgetsViaOffsetChain(scrollRoot, {
|
|
@@ -884,7 +1010,8 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
884
1010
|
scrollHeight: scrollRoot.clientHeight,
|
|
885
1011
|
visiblePageIndexRange: null,
|
|
886
1012
|
});
|
|
887
|
-
|
|
1013
|
+
const reconciled = reconcilePaperRectsWithFlow(domRects, pageCount);
|
|
1014
|
+
setRectsIfChanged(reconciled.length > 0 ? reconciled : skeletalRects);
|
|
888
1015
|
}
|
|
889
1016
|
}, [
|
|
890
1017
|
facet,
|
|
@@ -1007,15 +1134,9 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
1007
1134
|
// root element present + empty is cheap (one `<div>`) and makes the
|
|
1008
1135
|
// ref resolve during the first layout-effect pass.
|
|
1009
1136
|
//
|
|
1010
|
-
// Flicker-remediation 2026-04-22:
|
|
1011
|
-
//
|
|
1012
|
-
//
|
|
1013
|
-
// white page rather than the gray workspace canvas flashing through.
|
|
1014
|
-
// The fallback uses the same paper chrome (bg / border / shadow /
|
|
1015
|
-
// radius) as individual page cards, rendered as `inset-0` so it
|
|
1016
|
-
// spans whatever region the scroll root currently occupies. When
|
|
1017
|
-
// `rects` populates on the next refresh pass the fallback is
|
|
1018
|
-
// replaced by N discrete cards.
|
|
1137
|
+
// Flicker-remediation 2026-04-22: if even canonical section sizing is not
|
|
1138
|
+
// available yet, paint a full-area paper-color fallback card so the user
|
|
1139
|
+
// sees white paper rather than the gray workspace canvas flashing through.
|
|
1019
1140
|
if (rects.length === 0) {
|
|
1020
1141
|
return (
|
|
1021
1142
|
<div
|
|
@@ -1041,6 +1162,7 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
1041
1162
|
);
|
|
1042
1163
|
}
|
|
1043
1164
|
|
|
1165
|
+
const rectsAreSkeletal = rects.every((rect) => rect.source === "skeletal");
|
|
1044
1166
|
return (
|
|
1045
1167
|
<div
|
|
1046
1168
|
ref={overlayRootRef}
|
|
@@ -1048,31 +1170,38 @@ export const TwPageStackOverlayLayer: React.FC<TwPageStackOverlayLayerProps> = (
|
|
|
1048
1170
|
aria-hidden="true"
|
|
1049
1171
|
data-testid={testId ?? "page-stack-overlay"}
|
|
1050
1172
|
data-page-count={rects.length}
|
|
1173
|
+
data-fallback-paper={rectsAreSkeletal ? "skeletal" : undefined}
|
|
1051
1174
|
>
|
|
1052
|
-
{rects.map((rect) =>
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1175
|
+
{rects.map((rect) => {
|
|
1176
|
+
const anchorAttrs = buildPageAnchorAttributes(rect.pageId, rect.pageIndex);
|
|
1177
|
+
return (
|
|
1178
|
+
<div
|
|
1179
|
+
key={rect.pageId}
|
|
1180
|
+
{...anchorAttrs}
|
|
1181
|
+
className={`wre-page-stack-overlay-frame absolute${
|
|
1182
|
+
rect.source === "skeletal"
|
|
1183
|
+
? " wre-page-stack-overlay-frame--skeletal"
|
|
1184
|
+
: ""
|
|
1185
|
+
}`}
|
|
1186
|
+
style={{
|
|
1187
|
+
top: `${rect.topPx}px`,
|
|
1188
|
+
height: `${rect.heightPx}px`,
|
|
1189
|
+
left: 0,
|
|
1190
|
+
right: 0,
|
|
1191
|
+
// N1 (L8 Phase D): this component is placed at z-0 BEFORE
|
|
1192
|
+
// the z-10 PM wrapper inside `wre-page-surface`, so an opaque
|
|
1193
|
+
// page background here sits behind PM text rather than on top
|
|
1194
|
+
// of it. White card + border + shadow gives the 'N distinct
|
|
1195
|
+
// papers on a gray canvas' appearance.
|
|
1196
|
+
backgroundColor: "var(--color-page-bg, white)",
|
|
1197
|
+
border: "1px solid var(--color-page-border, rgba(148,163,184,0.2))",
|
|
1198
|
+
borderRadius: "var(--radius-page, 4px)",
|
|
1199
|
+
boxShadow:
|
|
1200
|
+
"0 8px 24px -20px var(--color-page-shadow, rgba(15,23,42,0.38))",
|
|
1201
|
+
}}
|
|
1202
|
+
/>
|
|
1203
|
+
);
|
|
1204
|
+
})}
|
|
1076
1205
|
</div>
|
|
1077
1206
|
);
|
|
1078
1207
|
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
GeometryRect,
|
|
5
|
+
UiOverlayLaneEntry,
|
|
6
|
+
UiOverlayLaneSnapshot,
|
|
7
|
+
} from "../../api/v3/ui/_types.ts";
|
|
8
|
+
import { useUiApi } from "../ui-api-context.tsx";
|
|
9
|
+
|
|
10
|
+
const FALLBACK_CURSOR_COLOR = "#6b7280";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* L11 lane painter for L10's presence overlay lane.
|
|
14
|
+
*
|
|
15
|
+
* The runtime/shell owns awareness and cursor projection. This component is
|
|
16
|
+
* deliberately only a painter: it consumes `ui.overlays.getLane("presence")`
|
|
17
|
+
* snapshots, subscribes to the same lane, and renders resolved overlay rects.
|
|
18
|
+
*/
|
|
19
|
+
export function TwPresenceOverlayLane(): React.ReactElement | null {
|
|
20
|
+
const ui = useUiApi();
|
|
21
|
+
const [lane, setLane] = React.useState<UiOverlayLaneSnapshot | null>(() =>
|
|
22
|
+
ui?.overlays.getLane("presence") ?? null,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (!ui) {
|
|
27
|
+
setLane(null);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setLane(ui.overlays.getLane("presence"));
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
return ui.overlays.subscribeLane("presence", setLane);
|
|
35
|
+
} catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}, [ui]);
|
|
39
|
+
|
|
40
|
+
const markers = React.useMemo(() => resolvedPresenceMarkers(lane), [lane]);
|
|
41
|
+
if (markers.length === 0) return null;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
aria-hidden="true"
|
|
46
|
+
className="wre-presence-overlay-lane pointer-events-none absolute inset-0"
|
|
47
|
+
data-presence-overlay-lane=""
|
|
48
|
+
data-lane-source={lane?.source ?? "unavailable"}
|
|
49
|
+
data-lane-revision={lane?.revision ?? 0}
|
|
50
|
+
>
|
|
51
|
+
{markers.map((marker) => (
|
|
52
|
+
<div
|
|
53
|
+
className="wre-presence-overlay-lane__marker pointer-events-none absolute"
|
|
54
|
+
data-presence-cursor={marker.userId}
|
|
55
|
+
key={marker.key}
|
|
56
|
+
style={marker.style}
|
|
57
|
+
title={marker.displayName}
|
|
58
|
+
>
|
|
59
|
+
<span
|
|
60
|
+
className="wre-presence-overlay-lane__caret absolute"
|
|
61
|
+
style={marker.caretStyle}
|
|
62
|
+
/>
|
|
63
|
+
{marker.displayName ? (
|
|
64
|
+
<span
|
|
65
|
+
className="wre-presence-overlay-lane__label absolute whitespace-nowrap rounded-sm px-1 py-0 text-[11px] font-medium leading-tight text-white shadow-sm"
|
|
66
|
+
style={marker.labelStyle}
|
|
67
|
+
>
|
|
68
|
+
{marker.displayName}
|
|
69
|
+
</span>
|
|
70
|
+
) : null}
|
|
71
|
+
</div>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface PresenceMarker {
|
|
78
|
+
key: string;
|
|
79
|
+
userId: string;
|
|
80
|
+
displayName: string;
|
|
81
|
+
style: React.CSSProperties;
|
|
82
|
+
caretStyle: React.CSSProperties;
|
|
83
|
+
labelStyle: React.CSSProperties;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolvedPresenceMarkers(
|
|
87
|
+
lane: UiOverlayLaneSnapshot | null,
|
|
88
|
+
): PresenceMarker[] {
|
|
89
|
+
if (!lane || lane.kind !== "presence" || lane.status !== "resolved") {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const markers: PresenceMarker[] = [];
|
|
94
|
+
for (const entry of lane.entries) {
|
|
95
|
+
if (entry.status !== "resolved" || !entry.rects?.length) continue;
|
|
96
|
+
|
|
97
|
+
const data = entry.data ?? {};
|
|
98
|
+
const userId = stringValue(data.userId) ?? entry.id;
|
|
99
|
+
const displayName = stringValue(data.displayName) ?? userId;
|
|
100
|
+
const color = safePresenceColor(data.color);
|
|
101
|
+
|
|
102
|
+
entry.rects.forEach((rect, index) => {
|
|
103
|
+
markers.push({
|
|
104
|
+
key: `${entry.id}:${index}`,
|
|
105
|
+
userId,
|
|
106
|
+
displayName,
|
|
107
|
+
style: markerStyle(rect),
|
|
108
|
+
caretStyle: caretStyle(rect, color),
|
|
109
|
+
labelStyle: labelStyle(color),
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return markers;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function markerStyle(rect: GeometryRect): React.CSSProperties {
|
|
117
|
+
return {
|
|
118
|
+
left: `${rect.leftPx}px`,
|
|
119
|
+
top: `${rect.topPx}px`,
|
|
120
|
+
width: `${Math.max(rect.widthPx, 2)}px`,
|
|
121
|
+
height: `${Math.max(rect.heightPx, 12)}px`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function caretStyle(rect: GeometryRect, color: string): React.CSSProperties {
|
|
126
|
+
return {
|
|
127
|
+
left: 0,
|
|
128
|
+
top: 0,
|
|
129
|
+
width: "2px",
|
|
130
|
+
height: `${Math.max(rect.heightPx, 12)}px`,
|
|
131
|
+
backgroundColor: color,
|
|
132
|
+
borderRadius: "1px",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function labelStyle(color: string): React.CSSProperties {
|
|
137
|
+
return {
|
|
138
|
+
left: 0,
|
|
139
|
+
top: "-1.35em",
|
|
140
|
+
backgroundColor: color,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function stringValue(value: unknown): string | null {
|
|
145
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function safePresenceColor(raw: unknown): string {
|
|
149
|
+
if (typeof raw !== "string") return FALLBACK_CURSOR_COLOR;
|
|
150
|
+
const value = raw.trim();
|
|
151
|
+
if (value.length === 0 || value.length > 32) return FALLBACK_CURSOR_COLOR;
|
|
152
|
+
if (/^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$/.test(value)) return value;
|
|
153
|
+
if (/^rgba?\(\s*\d+(\.\d+)?%?(\s*,\s*\d+(\.\d+)?%?){2,3}\s*\)$/.test(value)) return value;
|
|
154
|
+
return FALLBACK_CURSOR_COLOR;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default TwPresenceOverlayLane;
|