@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
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
*/
|
|
67
67
|
function createStableHostProxy(
|
|
68
68
|
hostRef: { current: EditorActionHostCallbacks },
|
|
69
|
+
fallbackRef?: { current: EditorActionHostCallbacks },
|
|
69
70
|
): EditorActionHostCallbacks {
|
|
70
71
|
// Proxy construction is ~free and happens exactly once per component
|
|
71
72
|
// lifetime (see the empty-dep useMemo call site below). The `get`
|
|
@@ -76,11 +77,20 @@ function createStableHostProxy(
|
|
|
76
77
|
// `typeof host[key] === "function"` checks elsewhere behave
|
|
77
78
|
// predictably.
|
|
78
79
|
if (typeof prop !== "string") return undefined;
|
|
79
|
-
|
|
80
|
+
const hostValue = (hostRef.current as Record<string, unknown>)[prop];
|
|
81
|
+
if (hostValue !== undefined) return hostValue;
|
|
82
|
+
return fallbackRef
|
|
83
|
+
? (fallbackRef.current as Record<string, unknown>)[prop]
|
|
84
|
+
: undefined;
|
|
80
85
|
},
|
|
81
86
|
has: (_target, prop) => {
|
|
82
87
|
if (typeof prop !== "string") return false;
|
|
83
|
-
return
|
|
88
|
+
return (
|
|
89
|
+
prop in (hostRef.current as Record<string, unknown>) ||
|
|
90
|
+
(fallbackRef
|
|
91
|
+
? prop in (fallbackRef.current as Record<string, unknown>)
|
|
92
|
+
: false)
|
|
93
|
+
);
|
|
84
94
|
},
|
|
85
95
|
// `ownKeys` + `getOwnPropertyDescriptor` left default — iteration
|
|
86
96
|
// over the proxy isn't part of the registry contract.
|
|
@@ -184,7 +194,22 @@ export function TwWorkspaceChromeHost(
|
|
|
184
194
|
// `[mode]` so wholesale-snapshot re-renders don't rebuild groups.
|
|
185
195
|
const hostRef = useRef<EditorActionHostCallbacks>(editorActionHost);
|
|
186
196
|
hostRef.current = editorActionHost;
|
|
187
|
-
|
|
197
|
+
// Phase E trust/detail action fallback. Registry rows remain callback-
|
|
198
|
+
// driven, but the chrome host can satisfy product-safe detail requests
|
|
199
|
+
// through the existing rail-tab channel it already owns. Hosts still
|
|
200
|
+
// override these by supplying explicit callbacks in `editorActionHost`.
|
|
201
|
+
const railActionHostRef = useRef<EditorActionHostCallbacks>({});
|
|
202
|
+
const openRailTabRef = useRef<
|
|
203
|
+
((tab: EditorRailTab, itemId?: string) => void) | undefined
|
|
204
|
+
>(onOpenRailTab);
|
|
205
|
+
openRailTabRef.current = onOpenRailTab;
|
|
206
|
+
railActionHostRef.current = {
|
|
207
|
+
onOpenTemplateSlotInfo: () => openRailTabRef.current?.("workflow"),
|
|
208
|
+
};
|
|
209
|
+
const stableHost = useMemo(
|
|
210
|
+
() => createStableHostProxy(hostRef, railActionHostRef),
|
|
211
|
+
[],
|
|
212
|
+
);
|
|
188
213
|
|
|
189
214
|
const controller = useContextMenuController({
|
|
190
215
|
mode,
|
|
@@ -203,15 +228,6 @@ export function TwWorkspaceChromeHost(
|
|
|
203
228
|
requestWithKindsRef.current = controller.requestWithKinds;
|
|
204
229
|
const dismissRef = useRef(controller.dismiss);
|
|
205
230
|
dismissRef.current = controller.dismiss;
|
|
206
|
-
// Phase E.0 — ref-sync the rail-tab callback so the imperative handle
|
|
207
|
-
// identity stays stable while still dispatching to the freshest host
|
|
208
|
-
// closure on every call. `undefined` is kept in the ref when the host
|
|
209
|
-
// omits the prop so `openRailTab` becomes a safe no-op.
|
|
210
|
-
const openRailTabRef = useRef<
|
|
211
|
-
((tab: EditorRailTab, itemId?: string) => void) | undefined
|
|
212
|
-
>(onOpenRailTab);
|
|
213
|
-
openRailTabRef.current = onOpenRailTab;
|
|
214
|
-
|
|
215
231
|
useImperativeHandle(
|
|
216
232
|
controllerRef,
|
|
217
233
|
() => ({
|
|
@@ -18,15 +18,24 @@ import type { ScopeRailSegment } from "../../api/public-types.ts";
|
|
|
18
18
|
import type {
|
|
19
19
|
EditorRole,
|
|
20
20
|
EditorStoryTarget,
|
|
21
|
+
GeometryFacet,
|
|
21
22
|
ScopeIssueAction,
|
|
22
23
|
TableStructureContextSnapshot,
|
|
23
24
|
WordReviewEditorLayoutFacet,
|
|
25
|
+
WorkflowFacet,
|
|
24
26
|
WorkflowScopeMode,
|
|
25
27
|
} from "../../api/public-types";
|
|
28
|
+
import type {
|
|
29
|
+
UiObjectDragSession,
|
|
30
|
+
UiObjectDragStartInput,
|
|
31
|
+
} from "../../api/v3/ui/_types.ts";
|
|
26
32
|
import { TwScopeCardLayer } from "./tw-scope-card-layer";
|
|
27
33
|
import { TwPageStackChromeLayer, type PmPortalView } from "../page-stack/tw-page-stack-chrome-layer";
|
|
28
34
|
import { TwTableGripLayer } from "../chrome/tw-table-grip-layer";
|
|
29
35
|
import { TwObjectSelectionOverlay } from "./tw-object-selection-overlay";
|
|
36
|
+
import { TwPresenceOverlayLane } from "./tw-presence-overlay-lane";
|
|
37
|
+
import { TwReviewOverlayLaneMarkers } from "./tw-review-overlay-lane-markers";
|
|
38
|
+
import { TwSubstrateOverlayLanes } from "./tw-substrate-overlay-lanes";
|
|
30
39
|
|
|
31
40
|
export interface TwChromeOverlayProps {
|
|
32
41
|
/** Layout facet the overlay layers read from (layout-semantic data). */
|
|
@@ -36,13 +45,13 @@ export interface TwChromeOverlayProps {
|
|
|
36
45
|
* `facet.getRenderFrame` / `.getRenderZoom` in refactor/05
|
|
37
46
|
* cross-lane-coord §8.4.
|
|
38
47
|
*/
|
|
39
|
-
geometryFacet:
|
|
48
|
+
geometryFacet: GeometryFacet;
|
|
40
49
|
/**
|
|
41
50
|
* Workflow facet — no-provider fallback for scope rail/card reads.
|
|
42
51
|
* Mounted editor paths prefer `api.ui.scope.*`; pass `null` when no
|
|
43
52
|
* runtime is attached (e.g., during initial mount).
|
|
44
53
|
*/
|
|
45
|
-
workflowFacet:
|
|
54
|
+
workflowFacet: WorkflowFacet | null;
|
|
46
55
|
/** Optional coordinate space override. Defaults to the overlay origin. */
|
|
47
56
|
space?: OverlayCoordinateSpace;
|
|
48
57
|
/** Active scope id (for emphasis + rail tab sync). */
|
|
@@ -111,6 +120,8 @@ export interface TwChromeOverlayProps {
|
|
|
111
120
|
grabbedObjectToOffset?: number | null;
|
|
112
121
|
/** Called when the user clicks outside the selection box to deselect. */
|
|
113
122
|
onDeselectObject?: () => void;
|
|
123
|
+
/** Object handle drag lifecycle; routed through the mounted UI API seam. */
|
|
124
|
+
onBeginObjectDrag?: (input: UiObjectDragStartInput) => UiObjectDragSession;
|
|
114
125
|
|
|
115
126
|
// Table grip props (P6) -----------------------------------------------
|
|
116
127
|
/** Active table context — when present, column/row resize grips are shown. */
|
|
@@ -200,6 +211,15 @@ export interface TwChromeOverlayProps {
|
|
|
200
211
|
> | null;
|
|
201
212
|
}
|
|
202
213
|
|
|
214
|
+
export function resolveTableGripContext(
|
|
215
|
+
tableContext: TableStructureContextSnapshot | null | undefined,
|
|
216
|
+
): TableStructureContextSnapshot | null {
|
|
217
|
+
if (!tableContext || tableContext.selectionKind !== "cell") {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return tableContext;
|
|
221
|
+
}
|
|
222
|
+
|
|
203
223
|
/**
|
|
204
224
|
* Placement contract:
|
|
205
225
|
* - The overlay is an absolutely positioned `div` that fills its parent.
|
|
@@ -231,6 +251,7 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
|
|
|
231
251
|
grabbedObjectFromOffset,
|
|
232
252
|
grabbedObjectToOffset,
|
|
233
253
|
onDeselectObject,
|
|
254
|
+
onBeginObjectDrag,
|
|
234
255
|
tableContext,
|
|
235
256
|
onSetColumnWidth,
|
|
236
257
|
onSetRowHeight,
|
|
@@ -245,6 +266,8 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
|
|
|
245
266
|
mediaPreviews,
|
|
246
267
|
activeBandRibbonProps,
|
|
247
268
|
}) => {
|
|
269
|
+
const tableGripContext = resolveTableGripContext(tableContext);
|
|
270
|
+
|
|
248
271
|
return (
|
|
249
272
|
<div
|
|
250
273
|
className="wre-chrome-overlay pointer-events-none absolute inset-0 z-30"
|
|
@@ -284,7 +307,7 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
|
|
|
284
307
|
<TwTableGripLayer
|
|
285
308
|
facet={facet}
|
|
286
309
|
geometryFacet={geometryFacet}
|
|
287
|
-
tableContext={
|
|
310
|
+
tableContext={tableGripContext}
|
|
288
311
|
space={space}
|
|
289
312
|
onSetColumnWidth={onSetColumnWidth}
|
|
290
313
|
onSetRowHeight={onSetRowHeight}
|
|
@@ -296,7 +319,11 @@ export const TwChromeOverlay: React.FC<TwChromeOverlayProps> = ({
|
|
|
296
319
|
geometryFacet={geometryFacet}
|
|
297
320
|
space={space}
|
|
298
321
|
onDeselect={onDeselectObject}
|
|
322
|
+
onBeginObjectDrag={onBeginObjectDrag}
|
|
299
323
|
/>
|
|
324
|
+
<TwSubstrateOverlayLanes space={space} />
|
|
325
|
+
<TwReviewOverlayLaneMarkers space={space} />
|
|
326
|
+
<TwPresenceOverlayLane />
|
|
300
327
|
{children}
|
|
301
328
|
</div>
|
|
302
329
|
);
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
import * as React from "react";
|
|
22
22
|
import { useEffect, useRef } from "react";
|
|
23
23
|
import type { GeometryFacet } from "../../api/public-types";
|
|
24
|
+
import type {
|
|
25
|
+
UiObjectDragHandle,
|
|
26
|
+
UiObjectDragSession,
|
|
27
|
+
UiObjectDragStartInput,
|
|
28
|
+
} from "../../api/v3/ui/_types.ts";
|
|
24
29
|
import type { OverlayCoordinateSpace } from "./chrome-overlay-projector";
|
|
25
30
|
import { projectRectToOverlay } from "./chrome-overlay-projector";
|
|
26
31
|
|
|
@@ -57,6 +62,8 @@ export interface TwObjectSelectionOverlayProps {
|
|
|
57
62
|
space?: OverlayCoordinateSpace;
|
|
58
63
|
/** Called when the user clicks outside the selection box. */
|
|
59
64
|
onDeselect?: () => void;
|
|
65
|
+
/** Starts the mounted object-drag lifecycle for move/resize/rotate handles. */
|
|
66
|
+
onBeginObjectDrag?: (input: UiObjectDragStartInput) => UiObjectDragSession;
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
export function TwObjectSelectionOverlay({
|
|
@@ -66,8 +73,10 @@ export function TwObjectSelectionOverlay({
|
|
|
66
73
|
geometryFacet,
|
|
67
74
|
space,
|
|
68
75
|
onDeselect,
|
|
76
|
+
onBeginObjectDrag,
|
|
69
77
|
}: TwObjectSelectionOverlayProps) {
|
|
70
78
|
const overlayRef = useRef<HTMLDivElement>(null);
|
|
79
|
+
const dragRef = useRef<{ session: UiObjectDragSession } | null>(null);
|
|
71
80
|
|
|
72
81
|
// Click-outside to deselect.
|
|
73
82
|
useEffect(() => {
|
|
@@ -81,6 +90,42 @@ export function TwObjectSelectionOverlay({
|
|
|
81
90
|
return () => document.removeEventListener("pointerdown", handlePointerDown, { capture: true });
|
|
82
91
|
}, [grabbedObjectId, onDeselect]);
|
|
83
92
|
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!onBeginObjectDrag) return;
|
|
95
|
+
function handlePointerMove(e: PointerEvent) {
|
|
96
|
+
const active = dragRef.current;
|
|
97
|
+
if (!active) return;
|
|
98
|
+
active.session.update({
|
|
99
|
+
clientX: e.clientX,
|
|
100
|
+
clientY: e.clientY,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function handlePointerUp(e: PointerEvent) {
|
|
104
|
+
const active = dragRef.current;
|
|
105
|
+
if (!active) return;
|
|
106
|
+
dragRef.current = null;
|
|
107
|
+
active.session.commit({
|
|
108
|
+
clientX: e.clientX,
|
|
109
|
+
clientY: e.clientY,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
113
|
+
if (e.key !== "Escape") return;
|
|
114
|
+
const active = dragRef.current;
|
|
115
|
+
if (!active) return;
|
|
116
|
+
dragRef.current = null;
|
|
117
|
+
active.session.cancel();
|
|
118
|
+
}
|
|
119
|
+
document.addEventListener("pointermove", handlePointerMove, { capture: true });
|
|
120
|
+
document.addEventListener("pointerup", handlePointerUp, { capture: true });
|
|
121
|
+
document.addEventListener("keydown", handleKeyDown, { capture: true });
|
|
122
|
+
return () => {
|
|
123
|
+
document.removeEventListener("pointermove", handlePointerMove, { capture: true });
|
|
124
|
+
document.removeEventListener("pointerup", handlePointerUp, { capture: true });
|
|
125
|
+
document.removeEventListener("keydown", handleKeyDown, { capture: true });
|
|
126
|
+
};
|
|
127
|
+
}, [onBeginObjectDrag]);
|
|
128
|
+
|
|
84
129
|
if (!grabbedObjectId || grabbedObjectFromOffset == null) return null;
|
|
85
130
|
|
|
86
131
|
const frame = geometryFacet.getRenderFrame();
|
|
@@ -92,6 +137,14 @@ export function TwObjectSelectionOverlay({
|
|
|
92
137
|
if (!rawRect) return null;
|
|
93
138
|
|
|
94
139
|
const rect = projectRectToOverlay(rawRect, space);
|
|
140
|
+
const coordinateSpace = space ?? { originLeftPx: 0, originTopPx: 0 };
|
|
141
|
+
const dragInitialRect = {
|
|
142
|
+
leftPx: rawRect.leftPx - coordinateSpace.originLeftPx,
|
|
143
|
+
topPx: rawRect.topPx - coordinateSpace.originTopPx,
|
|
144
|
+
widthPx: rawRect.widthPx,
|
|
145
|
+
heightPx: rawRect.heightPx,
|
|
146
|
+
space: "overlay" as const,
|
|
147
|
+
};
|
|
95
148
|
|
|
96
149
|
const boxStyle: React.CSSProperties = {
|
|
97
150
|
position: "absolute",
|
|
@@ -104,6 +157,20 @@ export function TwObjectSelectionOverlay({
|
|
|
104
157
|
pointerEvents: "auto",
|
|
105
158
|
};
|
|
106
159
|
|
|
160
|
+
const beginDrag = (handle: UiObjectDragHandle, event: React.PointerEvent<HTMLElement>) => {
|
|
161
|
+
if (!onBeginObjectDrag) return;
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
event.stopPropagation();
|
|
164
|
+
const session = onBeginObjectDrag({
|
|
165
|
+
objectId: grabbedObjectId,
|
|
166
|
+
handle,
|
|
167
|
+
clientX: event.clientX,
|
|
168
|
+
clientY: event.clientY,
|
|
169
|
+
initialRect: dragInitialRect,
|
|
170
|
+
});
|
|
171
|
+
dragRef.current = { session };
|
|
172
|
+
};
|
|
173
|
+
|
|
107
174
|
return (
|
|
108
175
|
<div
|
|
109
176
|
ref={overlayRef}
|
|
@@ -112,16 +179,37 @@ export function TwObjectSelectionOverlay({
|
|
|
112
179
|
data-object-selection=""
|
|
113
180
|
data-object-id={grabbedObjectId}
|
|
114
181
|
aria-label="Selected object"
|
|
182
|
+
onPointerDown={(event) => {
|
|
183
|
+
if (event.target === event.currentTarget) {
|
|
184
|
+
beginDrag("move", event);
|
|
185
|
+
}
|
|
186
|
+
}}
|
|
115
187
|
>
|
|
116
188
|
{HANDLE_POSITIONS.map((pos) => (
|
|
117
|
-
<ObjectHandle
|
|
189
|
+
<ObjectHandle
|
|
190
|
+
key={pos}
|
|
191
|
+
position={pos}
|
|
192
|
+
interactive={Boolean(onBeginObjectDrag)}
|
|
193
|
+
onPointerDown={(event) => beginDrag(pos, event)}
|
|
194
|
+
/>
|
|
118
195
|
))}
|
|
119
|
-
<RotateGrip
|
|
196
|
+
<RotateGrip
|
|
197
|
+
interactive={Boolean(onBeginObjectDrag)}
|
|
198
|
+
onPointerDown={(event) => beginDrag("rotate", event)}
|
|
199
|
+
/>
|
|
120
200
|
</div>
|
|
121
201
|
);
|
|
122
202
|
}
|
|
123
203
|
|
|
124
|
-
function ObjectHandle({
|
|
204
|
+
function ObjectHandle({
|
|
205
|
+
position,
|
|
206
|
+
interactive,
|
|
207
|
+
onPointerDown,
|
|
208
|
+
}: {
|
|
209
|
+
position: HandlePosition;
|
|
210
|
+
interactive: boolean;
|
|
211
|
+
onPointerDown?: React.PointerEventHandler<HTMLDivElement>;
|
|
212
|
+
}) {
|
|
125
213
|
const HANDLE_PX = 8;
|
|
126
214
|
const half = HANDLE_PX / 2;
|
|
127
215
|
const pos = position;
|
|
@@ -135,17 +223,28 @@ function ObjectHandle({ position }: { position: HandlePosition }) {
|
|
|
135
223
|
borderRadius: 1,
|
|
136
224
|
boxSizing: "border-box",
|
|
137
225
|
cursor: CURSOR_MAP[pos],
|
|
138
|
-
|
|
139
|
-
// the click-outside listener which deselectObjects.
|
|
140
|
-
pointerEvents: "none",
|
|
226
|
+
pointerEvents: interactive ? "auto" : "none",
|
|
141
227
|
...(pos.includes("w") ? { left: -half } : pos.includes("e") ? { right: -half } : { left: "50%", transform: "translateX(-50%)" }),
|
|
142
228
|
...(pos.includes("n") ? { top: -half } : pos.includes("s") ? { bottom: -half } : { top: "50%", transform: `${pos === "w" || pos === "e" ? "translateY(-50%)" : "translateX(-50%) translateY(-50%)"}` }),
|
|
143
229
|
};
|
|
144
230
|
|
|
145
|
-
return
|
|
231
|
+
return (
|
|
232
|
+
<div
|
|
233
|
+
style={style}
|
|
234
|
+
data-handle={pos}
|
|
235
|
+
aria-hidden="true"
|
|
236
|
+
onPointerDown={onPointerDown}
|
|
237
|
+
/>
|
|
238
|
+
);
|
|
146
239
|
}
|
|
147
240
|
|
|
148
|
-
function RotateGrip(
|
|
241
|
+
function RotateGrip({
|
|
242
|
+
interactive,
|
|
243
|
+
onPointerDown,
|
|
244
|
+
}: {
|
|
245
|
+
interactive: boolean;
|
|
246
|
+
onPointerDown?: React.PointerEventHandler<HTMLDivElement>;
|
|
247
|
+
}) {
|
|
149
248
|
const style: React.CSSProperties = {
|
|
150
249
|
position: "absolute",
|
|
151
250
|
width: 10,
|
|
@@ -157,8 +256,15 @@ function RotateGrip() {
|
|
|
157
256
|
left: "50%",
|
|
158
257
|
transform: "translateX(-50%)",
|
|
159
258
|
cursor: "grab",
|
|
160
|
-
pointerEvents: "none",
|
|
259
|
+
pointerEvents: interactive ? "auto" : "none",
|
|
161
260
|
boxSizing: "border-box",
|
|
162
261
|
};
|
|
163
|
-
return
|
|
262
|
+
return (
|
|
263
|
+
<div
|
|
264
|
+
style={style}
|
|
265
|
+
data-handle="rotate"
|
|
266
|
+
aria-hidden="true"
|
|
267
|
+
onPointerDown={onPointerDown}
|
|
268
|
+
/>
|
|
269
|
+
);
|
|
164
270
|
}
|