@fulate/tools 1.0.11 → 1.0.13
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/README.md +48 -2
- package/dist/editor/interaction-controller/controller.d.ts +4 -2
- package/dist/editor/interaction-controller/types.d.ts +1 -1
- package/dist/history/index.d.ts +4 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +192 -84
- package/dist/select/align.d.ts +15 -2
- package/dist/select/index.d.ts +41 -3
- package/dist/select/projection.d.ts +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
# @fulate/tools Editor integration
|
|
1
|
+
# @fulate/tools Editor integration
|
|
2
|
+
|
|
3
|
+
> 文档角色:`GUIDE`。说明当前 Tools 用法;Editor 时序的唯一合同见 [Editor 规范](../../docs/specs/editor-interaction.md)。
|
|
2
4
|
|
|
3
5
|
每个活动 Root 只有一个 `EditorInteractionController`。它拥有 selection、输入仲裁、Transform/Point
|
|
4
6
|
gesture、Content session、Line/LineTree branch draft 与 semantic History 边界;`Select` 只保留 command/paint
|
|
@@ -130,7 +132,51 @@ History 安装后排队一次 `select:configured-change` 通知;标量 noop
|
|
|
130
132
|
width/height/x/y 仍使用 `Select.resize()` 或其他 Transform 命令,不能用普通 authored patch 绕过
|
|
131
133
|
父级矩阵反算。
|
|
132
134
|
|
|
133
|
-
|
|
135
|
+
### 应用组合命令
|
|
136
|
+
|
|
137
|
+
不同 owner 的属性和结构组合使用 `select.applyCommand(createCommand)`。构造函数在旧交互结束、
|
|
138
|
+
pending 几何稳定后同步运行,参数是当时的 live selection,返回公开的 `SelectCommand`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const changed = select.applyCommand(() => ({
|
|
142
|
+
ownerChanges: [{ owner: workspace, patch: { backgroundColor: "#ffffff" } }],
|
|
143
|
+
recordHistory: false
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
select.applyCommand(() => ({
|
|
147
|
+
structureChanges: [{ owner: layer, children: [...layer.children, newShape] }],
|
|
148
|
+
selectionAfter: [newShape],
|
|
149
|
+
afterInstall: () => attachApplicationData(newShape)
|
|
150
|
+
}));
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`recordHistory: false` 只关闭本命令的记录;默认仍服从 `select.history.enabled`。返回值只表示本命令
|
|
154
|
+
Canvas configured 属性或结构的实际净变化,与 History、监听器是否开启无关。已有拖拽完成和随后命令
|
|
155
|
+
是两个操作,各自有适用的 History/配置通知;本命令 noop 不撤销前一个操作。
|
|
156
|
+
|
|
157
|
+
`selectionAfter` 省略时保留当前选择意图;结构提交后过滤已移除节点和被选中祖先覆盖的元素,成员未变
|
|
158
|
+
时保留当前 Point 等选择状态。`[]` 清空。显式选择结果或实际选择修复排队一次 `select:end`;
|
|
159
|
+
仅选择变化返回 false,不生成 History/配置通知。构造返回 undefined、空命令、相同值
|
|
160
|
+
和同 owner 最终覆盖回原值都是普通 noop,`afterInstall` 不运行。
|
|
161
|
+
|
|
162
|
+
`afterInstall` 位于 Core 字段/结构、lifecycle、hook、scene observers 及选择安装之后,frame drain、
|
|
163
|
+
History finish 和排队配置完成通知之前。它只在实际 Scene 安装后同步调用一次,不随 undo/redo 重放;
|
|
164
|
+
应用自己的数据对应关系仍由自己的注册/清理链维护。回调异常直接传播,已安装的正常变化保留。
|
|
165
|
+
构造阶段销毁 Select/Root 时停止本命令;安装后销毁则保留已安装变化并停止旧 controller 的后续完成。
|
|
166
|
+
|
|
167
|
+
命令 caller 不需要 prepare 属性、操作 History scope 或驱动帧。只需单独结束当前交互时使用
|
|
168
|
+
`select.finishCurrentInteraction()`:完成已接受交互,取消尚未提交的画线草稿,无活动交互时无操作。
|
|
169
|
+
|
|
170
|
+
需要当前 Workspace 作为对齐参考范围时,延迟到内置 align 的稳定阶段读取:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
select.align(AlignType.JustifyEnd, () => workspace.transform.getRenderCoverageBounds());
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
参考函数最多同步调用一次,几何稳定后读取;静态 world Rect 仍直接传入。矩阵/children 的构造同样应
|
|
177
|
+
放入 `applyCommand` 的同步构造函数。完整参数与时序见 [Editor 规范](../../docs/specs/editor-interaction.md#64-应用公开命令边界)。
|
|
178
|
+
|
|
179
|
+
`Select.resize()` 是属性面板使用的公开尺寸命令:
|
|
134
180
|
|
|
135
181
|
```ts
|
|
136
182
|
const bounds = element.transform.getWorldVisualBoundsView();
|
|
@@ -3,7 +3,7 @@ import { LineTree, type LineTreeBranchTailSource, type LineDecoration } from "@f
|
|
|
3
3
|
import { type PointView, type Rect } from "@fulate/share";
|
|
4
4
|
import type { HistoryManager, HistoryScope } from "../../history";
|
|
5
5
|
import type { LineTool } from "../../line";
|
|
6
|
-
import type { Select, SelectedPropertyPatch } from "../../select";
|
|
6
|
+
import type { Select, SelectCommand, SelectedPropertyPatch } from "../../select";
|
|
7
7
|
import { type SelectionOverlayProjection, type SelectionOverlayTarget } from "../../select/projection";
|
|
8
8
|
import { type FlipDirection, type ResizeOptions } from "../../select/transform";
|
|
9
9
|
import type { Snap } from "../../select/snap";
|
|
@@ -159,7 +159,9 @@ export declare class EditorInteractionController {
|
|
|
159
159
|
snap: import("../../select/snap").PortSnapResult | null;
|
|
160
160
|
};
|
|
161
161
|
hasLineToolSession(tool: LineTool): boolean;
|
|
162
|
-
|
|
162
|
+
/** 同一命令 owner 的公开入口,构造只在当前交互和几何稳定后运行。 */
|
|
163
|
+
executeCommand(createCommand: (selection: readonly Element[]) => SelectCommand | undefined): boolean;
|
|
164
|
+
applyCommand(ownerEffects: readonly OwnerPropertyChange[], structureEffects: readonly StructureChange[], scope: HistoryScope | undefined, selectionAfter: readonly Element[] | undefined, options?: ApplyCommandOptions): boolean;
|
|
163
165
|
finishCurrentInteraction(): void;
|
|
164
166
|
destroy(): void;
|
|
165
167
|
}
|
|
@@ -25,7 +25,7 @@ export interface PointerGesture {
|
|
|
25
25
|
finish(intent?: PointerIntent, cancelled?: boolean): void;
|
|
26
26
|
}
|
|
27
27
|
export interface ApplyCommandOptions {
|
|
28
|
-
readonly
|
|
28
|
+
readonly afterInstall?: () => void;
|
|
29
29
|
readonly afterHistory?: () => void;
|
|
30
30
|
readonly installSelection?: () => boolean;
|
|
31
31
|
}
|
package/dist/history/index.d.ts
CHANGED
|
@@ -65,7 +65,10 @@ export declare class HistoryManager {
|
|
|
65
65
|
get undoCount(): number;
|
|
66
66
|
get redoCount(): number;
|
|
67
67
|
bindSelection(adapter: HistorySelectionAdapter | null): void;
|
|
68
|
-
openScope(selectionBefore?: readonly Element<import("@fulate/core").ElementProperties, import("@fulate/core").BaseElementOption<any>, Partial<Omit<import("@fulate/core").BaseElementOption<any>, "key" | "children">>>[]
|
|
68
|
+
openScope(selectionBefore?: readonly Element<import("@fulate/core").ElementProperties, import("@fulate/core").BaseElementOption<any>, Partial<Omit<import("@fulate/core").BaseElementOption<any>, "key" | "children">>>[], options?: {
|
|
69
|
+
readonly recordHistory?: boolean;
|
|
70
|
+
readonly trackChanges?: boolean;
|
|
71
|
+
}): HistoryScope | undefined;
|
|
69
72
|
/** @internal Publishes one completed actual configured semantic change. */
|
|
70
73
|
publishConfiguredChange(changed: boolean | undefined): void;
|
|
71
74
|
/** @internal 只由存在Canvas净变化或外部change的scope完成时写入。 */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Select } from "./select/index";
|
|
2
|
-
export type { ConnectionGesture, CopyContext, ClipboardDataProducer, DeleteContext, FlipDirection, PasteContext, PasteOptions, PasteParentResolver, ResizeAnchor, ResizeOptions, SelectedPropertyPatch, SelectKeyboardCommands, SelectOption, ToolsConnectionIntent } from "./select/index";
|
|
2
|
+
export type { ConnectionGesture, CopyContext, ClipboardDataProducer, DeleteContext, FlipDirection, PasteContext, PasteOptions, PasteParentResolver, ResizeAnchor, ResizeOptions, SelectedPropertyPatch, SelectCommand, SelectKeyboardCommands, SelectOption, ToolsConnectionIntent } from "./select/index";
|
|
3
3
|
export { Snap } from "./select/snap";
|
|
4
4
|
export { HistoryManager } from "./history/index";
|
|
5
5
|
export type { EditorHistoryEntry, HistoryChange, HistoryPlacementChange, HistoryPropertyChange, HistoryScope } from "./history/index";
|
|
@@ -9,6 +9,7 @@ export { Rule } from "./rule/index";
|
|
|
9
9
|
export type { RuleOption } from "./rule/index";
|
|
10
10
|
export { EditorLayer } from "./editor-layer";
|
|
11
11
|
export { alignElements } from "./select/align";
|
|
12
|
-
export
|
|
12
|
+
export { AlignType } from "./select/align";
|
|
13
|
+
export type { AlignReference } from "./select/align";
|
|
13
14
|
export { ArrangeType } from "./select/arrange";
|
|
14
15
|
export type { ClipboardPlugin } from "./select/clipboard";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Element, ElementProperties, ElementTransform, Layer, isFormControlTarget, projectConnectionPortPoint, resolveOrigin, resolveReferenceTransform } from "@fulate/core";
|
|
2
2
|
import { BaseLine, ContentEditingCapabilityToken, DEFAULT_LINE_TREE_BRANCH_STYLE, Group, Line, LineTree, PointEditingCapabilityToken, SelectionBehaviorCapabilityToken, SelectionBehaviorMode, SelectionDiveMode, SelectionSnapMode, SnapSourceCapabilityToken, TransformInteractionCapabilityToken, addLineTreeBranchAtTail, applyLineTreeTopologyDelta, connectionPortAccepts, createLineTreeTopology, createLineTreeTopologyDelta, getEditorCapability, getEditorFrameFlush, getEndpointRelationIndex, hasConnectionPortCapacity, isEndpointRelationSource, moveLineTreePoint, remapLineTreeRelations, resolveConnectEndpointChange, resolveConnectionPortChanges, resolveDisconnectEndpointChanges, resolveSelectionFrameBounds, workspaceFocusInsetsKey } from "@fulate/ui";
|
|
3
|
-
import { Bound, Intersection, isIdentityMatrix, qrDecompose, transformPoint } from "@fulate/share";
|
|
3
|
+
import { Bound, Intersection, isIdentityMatrix, makeBoundingBoxFromRects, qrDecompose, transformPoint } from "@fulate/share";
|
|
4
4
|
import { cloneDeep, has, isNull, isString, isUndefined, last } from "lodash-es";
|
|
5
5
|
import { deserializeElement } from "@fulate/import";
|
|
6
6
|
import RBush from "rbush";
|
|
@@ -114,11 +114,15 @@ function rectCorners$1(rect) {
|
|
|
114
114
|
* capability is authoritative (for example Text/KeyValue authored bounds);
|
|
115
115
|
* owners without one use the shared local visual/layout fallback.
|
|
116
116
|
*/
|
|
117
|
-
function worldSelectionFramePoints(
|
|
118
|
-
const localFrame = resolveSelectionFrameBounds(
|
|
119
|
-
const matrix =
|
|
117
|
+
function worldSelectionFramePoints(element, transform) {
|
|
118
|
+
const localFrame = resolveSelectionFrameBounds(element, transform);
|
|
119
|
+
const matrix = element.transform.getWorldMatrixView();
|
|
120
120
|
return rectCorners$1(localFrame).map((point) => transformPoint(matrix, point));
|
|
121
121
|
}
|
|
122
|
+
/** @internal Resolve the world AABB used by the selection frame. */
|
|
123
|
+
function getWorldSelectionBounds(element, transform) {
|
|
124
|
+
return Bound.fromPoints(worldSelectionFramePoints(element, transform));
|
|
125
|
+
}
|
|
122
126
|
var ROTATED_RESIZE_CURSORS = [
|
|
123
127
|
"ns-resize",
|
|
124
128
|
"nesw-resize",
|
|
@@ -178,9 +182,7 @@ function createWorldSelectionFrame(owners, fallback) {
|
|
|
178
182
|
if (fallback) return fallback;
|
|
179
183
|
return frameFromSingleOwner(owners[0], ownerFrame);
|
|
180
184
|
}
|
|
181
|
-
const
|
|
182
|
-
const matrix = owners[0].element.transform.getWorldMatrixView();
|
|
183
|
-
const worldBounds = Bound.fromPoints(rectCorners$1(bounds).map((point) => transformPoint(matrix, point)));
|
|
185
|
+
const worldBounds = getWorldSelectionBounds(owners[0].element, owners[0].transform);
|
|
184
186
|
if ((worldBounds.width === 0 || worldBounds.height === 0) && fallback) return fallback;
|
|
185
187
|
return createSelectionFrameProjection({
|
|
186
188
|
left: worldBounds.minX,
|
|
@@ -190,19 +192,9 @@ function createWorldSelectionFrame(owners, fallback) {
|
|
|
190
192
|
angle: 0
|
|
191
193
|
});
|
|
192
194
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
let bottom = -Infinity;
|
|
197
|
-
for (const owner of owners) for (const point of worldSelectionFramePoints(owner)) {
|
|
198
|
-
if (point.x < left) left = point.x;
|
|
199
|
-
if (point.y < top) top = point.y;
|
|
200
|
-
if (point.x > right) right = point.x;
|
|
201
|
-
if (point.y > bottom) bottom = point.y;
|
|
202
|
-
}
|
|
203
|
-
if (!Number.isFinite(left) || !Number.isFinite(top) || !Number.isFinite(right) || !Number.isFinite(bottom)) return fallback ?? null;
|
|
204
|
-
const width = right - left;
|
|
205
|
-
const height = bottom - top;
|
|
195
|
+
const bounds = makeBoundingBoxFromRects(owners.map(({ element, transform }) => getWorldSelectionBounds(element, transform)));
|
|
196
|
+
if (!Number.isFinite(bounds.left) || !Number.isFinite(bounds.top) || !Number.isFinite(bounds.width) || !Number.isFinite(bounds.height)) return fallback ?? null;
|
|
197
|
+
const { left, top, width, height } = bounds;
|
|
206
198
|
if ((width === 0 || height === 0) && fallback) return fallback;
|
|
207
199
|
return createSelectionFrameProjection({
|
|
208
200
|
left,
|
|
@@ -1290,18 +1282,19 @@ var EditorInteractionController = class {
|
|
|
1290
1282
|
restoreSelectionForHistory(elements) {
|
|
1291
1283
|
this.replaceSelection(elements);
|
|
1292
1284
|
}
|
|
1293
|
-
replaceSelection(elements) {
|
|
1285
|
+
replaceSelection(elements, onlyIfChanged = false) {
|
|
1294
1286
|
this.pendingSelectionRepair = null;
|
|
1295
|
-
if (this.selectState?.kind !== "content") this.installSelectState({
|
|
1296
|
-
kind: "idle",
|
|
1297
|
-
gesture: null
|
|
1298
|
-
});
|
|
1299
1287
|
const selected = /* @__PURE__ */ new Set();
|
|
1300
1288
|
for (const element of elements) if (element.activeRoot === this.root) selected.add(element);
|
|
1301
1289
|
const topLevel = [...selected].filter((element) => {
|
|
1302
1290
|
for (let parent = element.parent; parent; parent = parent.parent) if (selected.has(parent)) return false;
|
|
1303
1291
|
return true;
|
|
1304
1292
|
});
|
|
1293
|
+
if (onlyIfChanged && topLevel.length === this.selection.length && topLevel.every((element, index) => element === this.selection[index])) return;
|
|
1294
|
+
if (this.selectState?.kind !== "content") this.installSelectState({
|
|
1295
|
+
kind: "idle",
|
|
1296
|
+
gesture: null
|
|
1297
|
+
});
|
|
1305
1298
|
const owners = [];
|
|
1306
1299
|
for (const element of topLevel) {
|
|
1307
1300
|
const behavior = getEditorCapability(element, SelectionBehaviorCapabilityToken);
|
|
@@ -1527,7 +1520,7 @@ var EditorInteractionController = class {
|
|
|
1527
1520
|
this.select.transform.markPaintDirty();
|
|
1528
1521
|
}
|
|
1529
1522
|
applyAuthoredBatch(ownerEffects, structureEffects, scope) {
|
|
1530
|
-
this.root.applyChangeBatch(ownerEffects, structureEffects, scope);
|
|
1523
|
+
return this.root.applyChangeBatch(ownerEffects, structureEffects, scope);
|
|
1531
1524
|
}
|
|
1532
1525
|
onPointerDown(event) {
|
|
1533
1526
|
if (this.activeGesture) return;
|
|
@@ -2558,26 +2551,53 @@ var EditorInteractionController = class {
|
|
|
2558
2551
|
hasLineToolSession(tool) {
|
|
2559
2552
|
return this.lineSession?.tool === tool || this.lineTreeSession?.tool === tool;
|
|
2560
2553
|
}
|
|
2554
|
+
/** 同一命令 owner 的公开入口,构造只在当前交互和几何稳定后运行。 */
|
|
2555
|
+
executeCommand(createCommand) {
|
|
2556
|
+
this.finishCurrentInteraction();
|
|
2557
|
+
if (this.select.interactionController !== this) return false;
|
|
2558
|
+
this.root.frameRuntime.drain();
|
|
2559
|
+
if (this.select.interactionController !== this) return false;
|
|
2560
|
+
const command = createCommand(this.selection);
|
|
2561
|
+
if (this.select.interactionController !== this || !command) return false;
|
|
2562
|
+
const scope = this.history.openScope(this.selection, {
|
|
2563
|
+
recordHistory: command.recordHistory,
|
|
2564
|
+
trackChanges: true
|
|
2565
|
+
});
|
|
2566
|
+
return this.applyCommand(resolveConnectionPortChanges(this.root, command.ownerChanges ?? []), command.structureChanges ?? [], scope, command.selectionAfter, command);
|
|
2567
|
+
}
|
|
2561
2568
|
applyCommand(ownerEffects, structureEffects, scope, selectionAfter, options) {
|
|
2562
2569
|
const selectionOwners = this.owners;
|
|
2570
|
+
const selectionBefore = this.selection;
|
|
2563
2571
|
let installedSelection;
|
|
2564
|
-
this.applyAuthoredBatch(ownerEffects, structureEffects, scope);
|
|
2565
|
-
if (
|
|
2566
|
-
|
|
2572
|
+
const installed = this.applyAuthoredBatch(ownerEffects, structureEffects, scope);
|
|
2573
|
+
if (this.select.interactionController !== this) {
|
|
2574
|
+
scope?.discard();
|
|
2575
|
+
return installed;
|
|
2576
|
+
}
|
|
2577
|
+
const canInstallSelection = (selectionAfter !== void 0 || installed && structureEffects.length > 0) && (options?.installSelection ? options.installSelection() : this.owners === selectionOwners || this.pendingSelectionRepair === this.owners);
|
|
2578
|
+
if (canInstallSelection) {
|
|
2579
|
+
this.replaceSelection(selectionAfter ?? this.selection, selectionAfter === void 0);
|
|
2567
2580
|
if (options?.installSelection) installedSelection = {
|
|
2568
2581
|
owners: this.owners,
|
|
2569
2582
|
interaction: this.controllerState
|
|
2570
2583
|
};
|
|
2571
2584
|
}
|
|
2572
|
-
|
|
2585
|
+
const selectionRepaired = canInstallSelection && this.selection.length !== selectionBefore.length;
|
|
2586
|
+
if (installed) options?.afterInstall?.();
|
|
2573
2587
|
if (this.select.interactionController !== this) {
|
|
2574
2588
|
scope?.discard();
|
|
2575
|
-
return;
|
|
2589
|
+
return installed;
|
|
2576
2590
|
}
|
|
2577
2591
|
this.root.frameRuntime.drain();
|
|
2578
|
-
this.
|
|
2592
|
+
if (this.select.interactionController !== this) {
|
|
2593
|
+
scope?.discard();
|
|
2594
|
+
return installed;
|
|
2595
|
+
}
|
|
2596
|
+
const changed = scope?.finish(this.selection) ?? installed;
|
|
2597
|
+
this.publishConfiguredChange(changed);
|
|
2579
2598
|
options?.afterHistory?.();
|
|
2580
|
-
if (!options?.installSelection || installedSelection?.owners === this.owners && installedSelection.interaction === this.controllerState) this.notifySelect("select:end");
|
|
2599
|
+
if ((selectionAfter !== void 0 || selectionRepaired) && (!options?.installSelection || installedSelection?.owners === this.owners && installedSelection.interaction === this.controllerState)) this.notifySelect("select:end");
|
|
2600
|
+
return changed;
|
|
2581
2601
|
}
|
|
2582
2602
|
finishCurrentInteraction() {
|
|
2583
2603
|
if (this.contentSession) {
|
|
@@ -2749,9 +2769,9 @@ var HistoryManager = class {
|
|
|
2749
2769
|
bindSelection(adapter) {
|
|
2750
2770
|
this.selectionAdapter = adapter;
|
|
2751
2771
|
}
|
|
2752
|
-
openScope(selectionBefore = this.selectionAdapter?.getSelection() ?? []) {
|
|
2753
|
-
const recordHistory = this.enabled;
|
|
2754
|
-
if (!recordHistory && !this.selectionAdapter?.hasConfiguredChangeListener()) return void 0;
|
|
2772
|
+
openScope(selectionBefore = this.selectionAdapter?.getSelection() ?? [], options) {
|
|
2773
|
+
const recordHistory = this.enabled && options?.recordHistory !== false;
|
|
2774
|
+
if (!recordHistory && !options?.trackChanges && !this.selectionAdapter?.hasConfiguredChangeListener()) return void 0;
|
|
2755
2775
|
return new ActiveHistoryScope(this, recordHistory ? selectionKeys(selectionBefore) : null, recordHistory);
|
|
2756
2776
|
}
|
|
2757
2777
|
/** @internal Publishes one completed actual configured semantic change. */
|
|
@@ -2887,86 +2907,142 @@ var HistoryManager = class {
|
|
|
2887
2907
|
};
|
|
2888
2908
|
//#endregion
|
|
2889
2909
|
//#region packages/tools/src/select/align.ts
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2910
|
+
/** Select 支持的对齐与分布方向。 */
|
|
2911
|
+
var AlignType = /* @__PURE__ */ function(AlignType) {
|
|
2912
|
+
AlignType["JustifyStart"] = "justify-start";
|
|
2913
|
+
AlignType["JustifyCenter"] = "justify-center";
|
|
2914
|
+
AlignType["JustifyEnd"] = "justify-end";
|
|
2915
|
+
AlignType["JustifyBetween"] = "justify-between";
|
|
2916
|
+
AlignType["AlignStart"] = "align-start";
|
|
2917
|
+
AlignType["AlignCenter"] = "align-center";
|
|
2918
|
+
AlignType["AlignEnd"] = "align-end";
|
|
2919
|
+
AlignType["AlignBetween"] = "align-between";
|
|
2920
|
+
return AlignType;
|
|
2921
|
+
}({});
|
|
2922
|
+
function compareElementKeys(left, right) {
|
|
2923
|
+
if (left.element.key < right.element.key) return -1;
|
|
2924
|
+
if (left.element.key > right.element.key) return 1;
|
|
2925
|
+
return 0;
|
|
2926
|
+
}
|
|
2927
|
+
function compareDistributionRects(left, right, axis) {
|
|
2928
|
+
const primary = left[axis.position] - right[axis.position];
|
|
2929
|
+
if (primary !== 0) return primary;
|
|
2930
|
+
const secondary = left[axis.crossPosition] - right[axis.crossPosition];
|
|
2931
|
+
if (secondary !== 0) return secondary;
|
|
2932
|
+
return compareElementKeys(left, right);
|
|
2933
|
+
}
|
|
2934
|
+
function resolveDistributionOffsets(rects, reference, axis) {
|
|
2935
|
+
const { position, size, offset } = axis;
|
|
2936
|
+
const start = reference[position];
|
|
2937
|
+
const referenceEnd = start + reference[size];
|
|
2938
|
+
let totalSize = 0;
|
|
2939
|
+
for (const rect of rects) totalSize += rect[size];
|
|
2940
|
+
const roundoff = Math.max(1, Math.abs(start), Math.abs(referenceEnd), Math.abs(totalSize)) * Number.EPSILON * rects.length * 4;
|
|
2941
|
+
const orderStep = roundoff * 8;
|
|
2942
|
+
const freeSpace = reference[size] - totalSize;
|
|
2943
|
+
const fittedFreeSpace = Math.abs(freeSpace) <= roundoff ? 0 : freeSpace;
|
|
2944
|
+
const gap = fittedFreeSpace / (rects.length - 1);
|
|
2945
|
+
const distributionEnd = start + totalSize + fittedFreeSpace;
|
|
2946
|
+
rects.sort((left, right) => compareDistributionRects(left, right, axis));
|
|
2947
|
+
const lastIndex = rects.length - 1;
|
|
2948
|
+
const latestStarts = new Array(rects.length);
|
|
2949
|
+
latestStarts[lastIndex] = distributionEnd - rects[lastIndex][size];
|
|
2950
|
+
for (let index = lastIndex - 1; index >= 0; index--) latestStarts[index] = Math.min(distributionEnd - rects[index][size], latestStarts[index + 1] - orderStep);
|
|
2951
|
+
if (latestStarts[0] < start) return;
|
|
2952
|
+
let idealTarget = start;
|
|
2953
|
+
let previousTarget = start;
|
|
2954
|
+
for (let index = 0; index <= lastIndex; index++) {
|
|
2955
|
+
let target;
|
|
2956
|
+
if (index === 0) target = start;
|
|
2957
|
+
else if (index === lastIndex) target = latestStarts[lastIndex];
|
|
2958
|
+
else {
|
|
2959
|
+
idealTarget += rects[index - 1][size] + gap;
|
|
2960
|
+
target = Math.min(latestStarts[index], Math.max(previousTarget + orderStep, idealTarget));
|
|
2961
|
+
}
|
|
2962
|
+
const delta = target - rects[index][position];
|
|
2963
|
+
rects[index][offset] = Math.abs(delta) <= roundoff ? 0 : delta;
|
|
2964
|
+
previousTarget = target;
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
function resolveOffsets(rects, type, reference) {
|
|
2968
|
+
if (type === AlignType.JustifyStart) {
|
|
2969
|
+
const target = reference.left;
|
|
2893
2970
|
rects.forEach((rect) => {
|
|
2894
2971
|
rect.dx = target - rect.left;
|
|
2895
2972
|
});
|
|
2896
2973
|
return;
|
|
2897
2974
|
}
|
|
2898
|
-
if (type ===
|
|
2899
|
-
const target =
|
|
2975
|
+
if (type === AlignType.JustifyCenter) {
|
|
2976
|
+
const target = reference.left + reference.width / 2;
|
|
2900
2977
|
rects.forEach((rect) => {
|
|
2901
2978
|
rect.dx = target - (rect.left + rect.width / 2);
|
|
2902
2979
|
});
|
|
2903
2980
|
return;
|
|
2904
2981
|
}
|
|
2905
|
-
if (type ===
|
|
2906
|
-
const target =
|
|
2982
|
+
if (type === AlignType.JustifyEnd) {
|
|
2983
|
+
const target = reference.left + reference.width;
|
|
2907
2984
|
rects.forEach((rect) => {
|
|
2908
2985
|
rect.dx = target - (rect.left + rect.width);
|
|
2909
2986
|
});
|
|
2910
2987
|
return;
|
|
2911
2988
|
}
|
|
2912
|
-
if (type ===
|
|
2913
|
-
rects
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
for (const rect of rects) {
|
|
2920
|
-
rect.dx = x - rect.left;
|
|
2921
|
-
x += rect.width + gap;
|
|
2922
|
-
}
|
|
2989
|
+
if (type === AlignType.JustifyBetween) {
|
|
2990
|
+
resolveDistributionOffsets(rects, reference, {
|
|
2991
|
+
position: "left",
|
|
2992
|
+
crossPosition: "top",
|
|
2993
|
+
size: "width",
|
|
2994
|
+
offset: "dx"
|
|
2995
|
+
});
|
|
2923
2996
|
return;
|
|
2924
2997
|
}
|
|
2925
|
-
if (type ===
|
|
2926
|
-
const target =
|
|
2998
|
+
if (type === AlignType.AlignStart) {
|
|
2999
|
+
const target = reference.top;
|
|
2927
3000
|
rects.forEach((rect) => {
|
|
2928
3001
|
rect.dy = target - rect.top;
|
|
2929
3002
|
});
|
|
2930
3003
|
return;
|
|
2931
3004
|
}
|
|
2932
|
-
if (type ===
|
|
2933
|
-
const target =
|
|
3005
|
+
if (type === AlignType.AlignCenter) {
|
|
3006
|
+
const target = reference.top + reference.height / 2;
|
|
2934
3007
|
rects.forEach((rect) => {
|
|
2935
3008
|
rect.dy = target - (rect.top + rect.height / 2);
|
|
2936
3009
|
});
|
|
2937
3010
|
return;
|
|
2938
3011
|
}
|
|
2939
|
-
if (type ===
|
|
2940
|
-
const target =
|
|
3012
|
+
if (type === AlignType.AlignEnd) {
|
|
3013
|
+
const target = reference.top + reference.height;
|
|
2941
3014
|
rects.forEach((rect) => {
|
|
2942
3015
|
rect.dy = target - (rect.top + rect.height);
|
|
2943
3016
|
});
|
|
2944
3017
|
return;
|
|
2945
3018
|
}
|
|
2946
|
-
rects
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
for (const rect of rects) {
|
|
2953
|
-
rect.dy = y - rect.top;
|
|
2954
|
-
y += rect.height + gap;
|
|
2955
|
-
}
|
|
3019
|
+
resolveDistributionOffsets(rects, reference, {
|
|
3020
|
+
position: "top",
|
|
3021
|
+
crossPosition: "left",
|
|
3022
|
+
size: "height",
|
|
3023
|
+
offset: "dy"
|
|
3024
|
+
});
|
|
2956
3025
|
}
|
|
2957
|
-
function alignElements(select, type) {
|
|
3026
|
+
function alignElements(select, type, referenceRect) {
|
|
2958
3027
|
const controller = select.interactionController;
|
|
2959
3028
|
let elements = controller.selection;
|
|
2960
|
-
|
|
3029
|
+
const between = type === AlignType.JustifyBetween || type === AlignType.AlignBetween;
|
|
3030
|
+
const minimum = referenceRect ? between ? 2 : 1 : between ? 3 : 2;
|
|
3031
|
+
if (elements.length < minimum) return;
|
|
2961
3032
|
controller.finishCurrentInteraction();
|
|
3033
|
+
if (select.interactionController !== controller) return;
|
|
2962
3034
|
controller.root.frameRuntime.drain();
|
|
2963
3035
|
if (select.interactionController !== controller) return;
|
|
2964
3036
|
elements = controller.selection;
|
|
2965
|
-
if (elements.length <
|
|
3037
|
+
if (elements.length < minimum) return;
|
|
3038
|
+
const reference = typeof referenceRect === "function" ? referenceRect() : referenceRect;
|
|
3039
|
+
if (select.interactionController !== controller) return;
|
|
2966
3040
|
const rects = elements.map((element) => {
|
|
2967
|
-
const
|
|
3041
|
+
const transform = getEditorCapability(element, TransformInteractionCapabilityToken);
|
|
3042
|
+
const bounds = getWorldSelectionBounds(element, transform);
|
|
2968
3043
|
return {
|
|
2969
3044
|
element,
|
|
3045
|
+
transform,
|
|
2970
3046
|
left: bounds.left,
|
|
2971
3047
|
top: bounds.top,
|
|
2972
3048
|
width: bounds.width,
|
|
@@ -2975,14 +3051,21 @@ function alignElements(select, type) {
|
|
|
2975
3051
|
dy: 0
|
|
2976
3052
|
};
|
|
2977
3053
|
});
|
|
2978
|
-
resolveOffsets(rects, type);
|
|
3054
|
+
resolveOffsets(rects, type, reference ?? makeBoundingBoxFromRects(rects));
|
|
2979
3055
|
const changes = [];
|
|
2980
3056
|
for (const rect of rects) {
|
|
2981
3057
|
if (rect.dx === 0 && rect.dy === 0) continue;
|
|
2982
|
-
const
|
|
2983
|
-
if (!
|
|
2984
|
-
const baseline =
|
|
2985
|
-
changes.push(...
|
|
3058
|
+
const { transform } = rect;
|
|
3059
|
+
if (!transform) continue;
|
|
3060
|
+
const baseline = transform.captureTransformBaseline();
|
|
3061
|
+
changes.push(...transform.resolveTransformChange(baseline, { worldDelta: new DOMMatrix([
|
|
3062
|
+
1,
|
|
3063
|
+
0,
|
|
3064
|
+
0,
|
|
3065
|
+
1,
|
|
3066
|
+
rect.dx,
|
|
3067
|
+
rect.dy
|
|
3068
|
+
]) }));
|
|
2986
3069
|
}
|
|
2987
3070
|
if (changes.length === 0) return;
|
|
2988
3071
|
const scope = select.history.openScope(elements);
|
|
@@ -3357,7 +3440,7 @@ function pasteData(select, controller, pasteSelection, task, data, deserializers
|
|
|
3357
3440
|
const scope = controller.history.openScope(controller.selection);
|
|
3358
3441
|
const installSelection = () => controller.ownsPasteSelection(pasteSelection, task);
|
|
3359
3442
|
controller.applyCommand(changes, appendStructure(placements), scope, elements, options?.afterInstall ? {
|
|
3360
|
-
|
|
3443
|
+
afterInstall: () => options.afterInstall({
|
|
3361
3444
|
applicationData: data.applicationData,
|
|
3362
3445
|
keyMap,
|
|
3363
3446
|
history: recordHistory ? scope : void 0
|
|
@@ -3432,7 +3515,7 @@ function deleteElements(select, afterInstall) {
|
|
|
3432
3515
|
const recordsHistory = select.history.enabled;
|
|
3433
3516
|
const scope = select.history.openScope(selected);
|
|
3434
3517
|
controller.applyCommand([], structure, scope, [], {
|
|
3435
|
-
|
|
3518
|
+
afterInstall: afterInstall ? () => afterInstall({
|
|
3436
3519
|
elements: subjects,
|
|
3437
3520
|
history: recordsHistory ? scope : void 0
|
|
3438
3521
|
}) : void 0,
|
|
@@ -3847,6 +3930,19 @@ var Select = class extends Element {
|
|
|
3847
3930
|
select(elements) {
|
|
3848
3931
|
this.controller?.setSelection(elements);
|
|
3849
3932
|
}
|
|
3933
|
+
/** 完成已接受的当前交互,取消未提交的画线草稿;无活动交互时无操作。 */
|
|
3934
|
+
finishCurrentInteraction() {
|
|
3935
|
+
this.controller?.finishCurrentInteraction();
|
|
3936
|
+
}
|
|
3937
|
+
/**
|
|
3938
|
+
* 先结束旧交互并稳定几何,再同步调用 createCommand,传入 live selection。
|
|
3939
|
+
* 返回本命令 Canvas configured 的净变化,与 History/监听器是否开启无关;不包含旧交互。
|
|
3940
|
+
* 返回 undefined 的构造或空/相同值命令是普通 noop。未激活的 Select 不调用构造。
|
|
3941
|
+
* 普通选区同一 patch 使用 setProperties;此入口用于 owner-specific 与结构组合操作。
|
|
3942
|
+
*/
|
|
3943
|
+
applyCommand(createCommand) {
|
|
3944
|
+
return this.controller?.executeCommand(createCommand) ?? false;
|
|
3945
|
+
}
|
|
3850
3946
|
/**
|
|
3851
3947
|
* Groups the live multi-selection. Accepted active Transform work finishes
|
|
3852
3948
|
* before the independent Group command; stale pointer continuation is ignored.
|
|
@@ -3863,8 +3959,20 @@ var Select = class extends Element {
|
|
|
3863
3959
|
unGroup() {
|
|
3864
3960
|
return unGroup(this);
|
|
3865
3961
|
}
|
|
3866
|
-
|
|
3867
|
-
|
|
3962
|
+
/**
|
|
3963
|
+
* Aligns the live selection in world space. Without `referenceRect`, the
|
|
3964
|
+
* current selection-frame AABB is the reference. A supplied world Rect is
|
|
3965
|
+
* used directly; a reference callback runs once after the previous interaction
|
|
3966
|
+
* and pending geometry finish. Distribution keeps strict edge gaps when they are numerically
|
|
3967
|
+
* possible and uses bounded overlap when the selected sizes exceed the reference,
|
|
3968
|
+
* while keeping every selection-frame AABB inside it and preserving order. The
|
|
3969
|
+
* current axis is a noop only when an element cannot fit or no ordered layout is
|
|
3970
|
+
* representable. A successful non-noop creates one Transform batch and one Select
|
|
3971
|
+
* History entry; a noop creates neither a transform, History entry, nor configured
|
|
3972
|
+
* change.
|
|
3973
|
+
*/
|
|
3974
|
+
align(type, referenceRect) {
|
|
3975
|
+
return alignElements(this, type, referenceRect);
|
|
3868
3976
|
}
|
|
3869
3977
|
/** 按当前 Layer 与 sibling 顺序排列 live selection。 */
|
|
3870
3978
|
arrange(type) {
|
|
@@ -5066,4 +5174,4 @@ var EditorLayer = class extends Layer {
|
|
|
5066
5174
|
}
|
|
5067
5175
|
};
|
|
5068
5176
|
//#endregion
|
|
5069
|
-
export { ArrangeType, EditorLayer, HistoryManager, LineTool, Rule, Select, Snap, alignElements };
|
|
5177
|
+
export { AlignType, ArrangeType, EditorLayer, HistoryManager, LineTool, Rule, Select, Snap, alignElements };
|
package/dist/select/align.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
import { type Rect } from "@fulate/share";
|
|
1
2
|
import type { Select } from "./index";
|
|
2
|
-
|
|
3
|
-
export declare
|
|
3
|
+
/** Select 支持的对齐与分布方向。 */
|
|
4
|
+
export declare enum AlignType {
|
|
5
|
+
JustifyStart = "justify-start",
|
|
6
|
+
JustifyCenter = "justify-center",
|
|
7
|
+
JustifyEnd = "justify-end",
|
|
8
|
+
JustifyBetween = "justify-between",
|
|
9
|
+
AlignStart = "align-start",
|
|
10
|
+
AlignCenter = "align-center",
|
|
11
|
+
AlignEnd = "align-end",
|
|
12
|
+
AlignBetween = "align-between"
|
|
13
|
+
}
|
|
14
|
+
/** World reference Rect, or a synchronous read after interaction and geometry completion. */
|
|
15
|
+
export type AlignReference = Readonly<Rect> | (() => Readonly<Rect>);
|
|
16
|
+
export declare function alignElements(select: Select, type: AlignType, referenceRect?: AlignReference): void;
|
package/dist/select/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Element, ElementProperties, type BaseElementOption, type ConnectionPortRef, type Layer, type Root } from "@fulate/core";
|
|
1
|
+
import { Element, ElementProperties, type BaseElementOption, type ConnectionPortRef, type Layer, type Root, type OwnerPropertyChange, type StructureChange } from "@fulate/core";
|
|
2
2
|
import { EditorInteractionController } from "../editor/interaction-controller";
|
|
3
3
|
import { HistoryManager } from "../history";
|
|
4
|
-
import { type AlignType } from "./align";
|
|
4
|
+
import { type AlignType, type AlignReference } from "./align";
|
|
5
5
|
import { type ArrangeType } from "./arrange";
|
|
6
6
|
import { type ClipboardDataProducer, type ClipboardPlugin, type DeleteContext, type PasteOptions } from "./clipboard";
|
|
7
7
|
import { Snap } from "./snap";
|
|
@@ -31,6 +31,23 @@ export interface SelectOption extends BaseElementOption {
|
|
|
31
31
|
}
|
|
32
32
|
/** 一次 selection property intent;同一 canonical reference 直接交给各 owner。 */
|
|
33
33
|
export type SelectedPropertyPatch = Readonly<Record<string, unknown>>;
|
|
34
|
+
/** 应用同步构造的 owner-specific 属性与结构命令;值按原引用交给对应 owner。 */
|
|
35
|
+
export interface SelectCommand {
|
|
36
|
+
readonly ownerChanges?: readonly OwnerPropertyChange[];
|
|
37
|
+
readonly structureChanges?: readonly StructureChange[];
|
|
38
|
+
/**
|
|
39
|
+
* 省略保留当前选择意图;结构提交后过滤已移除或被选中祖先覆盖的元素。
|
|
40
|
+
* 空数组清空选择;仅选择变化不进入 History 或配置通知。
|
|
41
|
+
*/
|
|
42
|
+
readonly selectionAfter?: readonly Element[];
|
|
43
|
+
/** false 只关闭本命令的 History;true/省略仍服从 history.enabled。 */
|
|
44
|
+
readonly recordHistory?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* 有实际 Scene 安装时同步调用一次,位于 Core lifecycle/hooks/observers 及选择安装之后,
|
|
47
|
+
* frame drain、History finish 与配置完成通知之前。不会随 undo/redo 重放;异常直接传播。
|
|
48
|
+
*/
|
|
49
|
+
readonly afterInstall?: () => void;
|
|
50
|
+
}
|
|
34
51
|
/**
|
|
35
52
|
* 轻量 Editor command/paint surface;交互状态由唯一 controller 拥有。
|
|
36
53
|
*
|
|
@@ -63,6 +80,15 @@ export declare class Select extends Element {
|
|
|
63
80
|
get interactionController(): EditorInteractionController;
|
|
64
81
|
/** 以一次独立操作选择给定元素,并在完成后异步通知 `select:end`。 */
|
|
65
82
|
select(elements: readonly Element[]): void;
|
|
83
|
+
/** 完成已接受的当前交互,取消未提交的画线草稿;无活动交互时无操作。 */
|
|
84
|
+
finishCurrentInteraction(): void;
|
|
85
|
+
/**
|
|
86
|
+
* 先结束旧交互并稳定几何,再同步调用 createCommand,传入 live selection。
|
|
87
|
+
* 返回本命令 Canvas configured 的净变化,与 History/监听器是否开启无关;不包含旧交互。
|
|
88
|
+
* 返回 undefined 的构造或空/相同值命令是普通 noop。未激活的 Select 不调用构造。
|
|
89
|
+
* 普通选区同一 patch 使用 setProperties;此入口用于 owner-specific 与结构组合操作。
|
|
90
|
+
*/
|
|
91
|
+
applyCommand(createCommand: (selection: readonly Element[]) => SelectCommand | undefined): boolean;
|
|
66
92
|
/**
|
|
67
93
|
* Groups the live multi-selection. Accepted active Transform work finishes
|
|
68
94
|
* before the independent Group command; stale pointer continuation is ignored.
|
|
@@ -75,7 +101,19 @@ export declare class Select extends Element {
|
|
|
75
101
|
* An obvious non-Group noop does not drain the frame runtime.
|
|
76
102
|
*/
|
|
77
103
|
unGroup(): Element<ElementProperties, BaseElementOption<any>, Partial<Omit<BaseElementOption<any>, "key" | "children">>>[];
|
|
78
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Aligns the live selection in world space. Without `referenceRect`, the
|
|
106
|
+
* current selection-frame AABB is the reference. A supplied world Rect is
|
|
107
|
+
* used directly; a reference callback runs once after the previous interaction
|
|
108
|
+
* and pending geometry finish. Distribution keeps strict edge gaps when they are numerically
|
|
109
|
+
* possible and uses bounded overlap when the selected sizes exceed the reference,
|
|
110
|
+
* while keeping every selection-frame AABB inside it and preserving order. The
|
|
111
|
+
* current axis is a noop only when an element cannot fit or no ordered layout is
|
|
112
|
+
* representable. A successful non-noop creates one Transform batch and one Select
|
|
113
|
+
* History entry; a noop creates neither a transform, History entry, nor configured
|
|
114
|
+
* change.
|
|
115
|
+
*/
|
|
116
|
+
align(type: AlignType, referenceRect?: AlignReference): void;
|
|
79
117
|
/** 按当前 Layer 与 sibling 顺序排列 live selection。 */
|
|
80
118
|
arrange(type: ArrangeType): void;
|
|
81
119
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Element } from "@fulate/core";
|
|
2
2
|
import { type PointEditTarget, type PointEditingScope, type SnapSourceCapability, type TransformInteractionCapability, type TransformInteractionPolicy } from "@fulate/ui";
|
|
3
|
-
import { type PointView, type Rect } from "@fulate/share";
|
|
3
|
+
import { Bound, type PointView, type Rect } from "@fulate/share";
|
|
4
4
|
export type ResizeHandle = "tl" | "tr" | "br" | "bl" | "mt" | "mr" | "mb" | "ml";
|
|
5
5
|
export type SelectionRotationPivot = {
|
|
6
6
|
readonly kind: "selection-center";
|
|
@@ -109,14 +109,16 @@ export interface SelectionProjectionTopology {
|
|
|
109
109
|
readonly pointScopeOutline: readonly PointView[] | null;
|
|
110
110
|
readonly pointScopeValid: boolean;
|
|
111
111
|
}
|
|
112
|
-
|
|
112
|
+
/** @internal Resolve the world AABB used by the selection frame. */
|
|
113
|
+
export declare function getWorldSelectionBounds(element: Element, transform: TransformInteractionCapability | undefined): Bound;
|
|
114
|
+
export declare function createSelectionFrameProjection({ left, top, width, height, angle, }: Pick<SelectionFrameProjection, "left" | "top" | "width" | "height" | "angle">): SelectionFrameProjection;
|
|
113
115
|
/** @internal Build a command frame from the current owner bounds. */
|
|
114
116
|
export declare function createWorldSelectionFrame(owners: readonly ProjectionOwner[], fallback?: SelectionFrameProjection | null): SelectionFrameProjection;
|
|
115
117
|
export declare function createSelectionRotationPivotSnapMarkers(frame: SelectionFrameProjection): readonly SelectionRotationPivotSnapMarker[];
|
|
116
118
|
export declare function createSelectionProjectionMembership(owners: readonly ProjectionOwner[], policy: ProjectionPolicy): SelectionProjectionMembership;
|
|
117
119
|
export declare function createSelectionProjectionTopology(owners: readonly ProjectionOwner[], isCurrent?: (owner: ProjectionOwner) => boolean): SelectionProjectionTopology;
|
|
118
120
|
export declare function createProjectionOwner(element: Element, frameVisible?: boolean, pointScope?: PointEditingScope | null, pointEnabled?: boolean): ProjectionOwner;
|
|
119
|
-
export declare function buildSelectionOverlayProjection({ owners, membership, topology, frame: frameOverride, policy, pivot, showPivot, pivotSnapMarkers, activePointTarget, marquee }: {
|
|
121
|
+
export declare function buildSelectionOverlayProjection({ owners, membership, topology, frame: frameOverride, policy, pivot, showPivot, pivotSnapMarkers, activePointTarget, marquee, }: {
|
|
120
122
|
readonly owners: readonly ProjectionOwner[];
|
|
121
123
|
readonly membership: SelectionProjectionMembership;
|
|
122
124
|
readonly topology: SelectionProjectionTopology;
|