@fulate/tools 1.0.12 → 1.0.14
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 +51 -3
- 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 +2 -1
- package/dist/index.js +92 -25
- package/dist/select/align.d.ts +3 -1
- package/dist/select/index.d.ts +31 -5
- 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();
|
|
@@ -162,7 +208,9 @@ aggregate frame 为 world 轴对齐,因此沿 world 水平/垂直轴镜像。
|
|
|
162
208
|
策略,使用同一批 selected owner baseline、trusted batch 与 semantic History;Group 不承载这次镜像的
|
|
163
209
|
scale,镜像会递归 bake children authored geometry。空选择或被策略禁止的方向是普通 noop。
|
|
164
210
|
|
|
165
|
-
## Keyboard、Clipboard 与应用命令 owner
|
|
211
|
+
## Keyboard、Clipboard 与应用命令 owner
|
|
212
|
+
|
|
213
|
+
HTTP 页面在 Async Clipboard 不可用时,会把当前序列化 payload 共享到同源 tab 的 `localStorage`;跨不同 origin 的 tab 仍需要 HTTPS 或外部中转能力。
|
|
166
214
|
|
|
167
215
|
每个活动 Root 由当前 `EditorInteractionController` 安装一个 DOM `keydown` listener。应用若要接管
|
|
168
216
|
`Ctrl/Meta+C`、`Ctrl/Meta+X`、`Ctrl/Meta+V`、`Delete` 或 `Backspace`,只绑定一个命令 owner:
|
|
@@ -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";
|
|
@@ -10,5 +10,6 @@ export type { RuleOption } from "./rule/index";
|
|
|
10
10
|
export { EditorLayer } from "./editor-layer";
|
|
11
11
|
export { alignElements } from "./select/align";
|
|
12
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
|
@@ -1282,18 +1282,19 @@ var EditorInteractionController = class {
|
|
|
1282
1282
|
restoreSelectionForHistory(elements) {
|
|
1283
1283
|
this.replaceSelection(elements);
|
|
1284
1284
|
}
|
|
1285
|
-
replaceSelection(elements) {
|
|
1285
|
+
replaceSelection(elements, onlyIfChanged = false) {
|
|
1286
1286
|
this.pendingSelectionRepair = null;
|
|
1287
|
-
if (this.selectState?.kind !== "content") this.installSelectState({
|
|
1288
|
-
kind: "idle",
|
|
1289
|
-
gesture: null
|
|
1290
|
-
});
|
|
1291
1287
|
const selected = /* @__PURE__ */ new Set();
|
|
1292
1288
|
for (const element of elements) if (element.activeRoot === this.root) selected.add(element);
|
|
1293
1289
|
const topLevel = [...selected].filter((element) => {
|
|
1294
1290
|
for (let parent = element.parent; parent; parent = parent.parent) if (selected.has(parent)) return false;
|
|
1295
1291
|
return true;
|
|
1296
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
|
+
});
|
|
1297
1298
|
const owners = [];
|
|
1298
1299
|
for (const element of topLevel) {
|
|
1299
1300
|
const behavior = getEditorCapability(element, SelectionBehaviorCapabilityToken);
|
|
@@ -1519,7 +1520,7 @@ var EditorInteractionController = class {
|
|
|
1519
1520
|
this.select.transform.markPaintDirty();
|
|
1520
1521
|
}
|
|
1521
1522
|
applyAuthoredBatch(ownerEffects, structureEffects, scope) {
|
|
1522
|
-
this.root.applyChangeBatch(ownerEffects, structureEffects, scope);
|
|
1523
|
+
return this.root.applyChangeBatch(ownerEffects, structureEffects, scope);
|
|
1523
1524
|
}
|
|
1524
1525
|
onPointerDown(event) {
|
|
1525
1526
|
if (this.activeGesture) return;
|
|
@@ -2550,26 +2551,53 @@ var EditorInteractionController = class {
|
|
|
2550
2551
|
hasLineToolSession(tool) {
|
|
2551
2552
|
return this.lineSession?.tool === tool || this.lineTreeSession?.tool === tool;
|
|
2552
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
|
+
}
|
|
2553
2568
|
applyCommand(ownerEffects, structureEffects, scope, selectionAfter, options) {
|
|
2554
2569
|
const selectionOwners = this.owners;
|
|
2570
|
+
const selectionBefore = this.selection;
|
|
2555
2571
|
let installedSelection;
|
|
2556
|
-
this.applyAuthoredBatch(ownerEffects, structureEffects, scope);
|
|
2557
|
-
if (
|
|
2558
|
-
|
|
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);
|
|
2559
2580
|
if (options?.installSelection) installedSelection = {
|
|
2560
2581
|
owners: this.owners,
|
|
2561
2582
|
interaction: this.controllerState
|
|
2562
2583
|
};
|
|
2563
2584
|
}
|
|
2564
|
-
|
|
2585
|
+
const selectionRepaired = canInstallSelection && this.selection.length !== selectionBefore.length;
|
|
2586
|
+
if (installed) options?.afterInstall?.();
|
|
2565
2587
|
if (this.select.interactionController !== this) {
|
|
2566
2588
|
scope?.discard();
|
|
2567
|
-
return;
|
|
2589
|
+
return installed;
|
|
2568
2590
|
}
|
|
2569
2591
|
this.root.frameRuntime.drain();
|
|
2570
|
-
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);
|
|
2571
2598
|
options?.afterHistory?.();
|
|
2572
|
-
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;
|
|
2573
2601
|
}
|
|
2574
2602
|
finishCurrentInteraction() {
|
|
2575
2603
|
if (this.contentSession) {
|
|
@@ -2741,9 +2769,9 @@ var HistoryManager = class {
|
|
|
2741
2769
|
bindSelection(adapter) {
|
|
2742
2770
|
this.selectionAdapter = adapter;
|
|
2743
2771
|
}
|
|
2744
|
-
openScope(selectionBefore = this.selectionAdapter?.getSelection() ?? []) {
|
|
2745
|
-
const recordHistory = this.enabled;
|
|
2746
|
-
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;
|
|
2747
2775
|
return new ActiveHistoryScope(this, recordHistory ? selectionKeys(selectionBefore) : null, recordHistory);
|
|
2748
2776
|
}
|
|
2749
2777
|
/** @internal Publishes one completed actual configured semantic change. */
|
|
@@ -3002,10 +3030,13 @@ function alignElements(select, type, referenceRect) {
|
|
|
3002
3030
|
const minimum = referenceRect ? between ? 2 : 1 : between ? 3 : 2;
|
|
3003
3031
|
if (elements.length < minimum) return;
|
|
3004
3032
|
controller.finishCurrentInteraction();
|
|
3033
|
+
if (select.interactionController !== controller) return;
|
|
3005
3034
|
controller.root.frameRuntime.drain();
|
|
3006
3035
|
if (select.interactionController !== controller) return;
|
|
3007
3036
|
elements = controller.selection;
|
|
3008
3037
|
if (elements.length < minimum) return;
|
|
3038
|
+
const reference = typeof referenceRect === "function" ? referenceRect() : referenceRect;
|
|
3039
|
+
if (select.interactionController !== controller) return;
|
|
3009
3040
|
const rects = elements.map((element) => {
|
|
3010
3041
|
const transform = getEditorCapability(element, TransformInteractionCapabilityToken);
|
|
3011
3042
|
const bounds = getWorldSelectionBounds(element, transform);
|
|
@@ -3020,7 +3051,7 @@ function alignElements(select, type, referenceRect) {
|
|
|
3020
3051
|
dy: 0
|
|
3021
3052
|
};
|
|
3022
3053
|
});
|
|
3023
|
-
resolveOffsets(rects, type,
|
|
3054
|
+
resolveOffsets(rects, type, reference ?? makeBoundingBoxFromRects(rects));
|
|
3024
3055
|
const changes = [];
|
|
3025
3056
|
for (const rect of rects) {
|
|
3026
3057
|
if (rect.dx === 0 && rect.dy === 0) continue;
|
|
@@ -3202,6 +3233,7 @@ function arrangeElements(select, type) {
|
|
|
3202
3233
|
//#endregion
|
|
3203
3234
|
//#region packages/tools/src/select/clipboard.ts
|
|
3204
3235
|
var PASTE_OFFSET = 20;
|
|
3236
|
+
var CLIPBOARD_STORAGE_KEY = "fulate:clipboard";
|
|
3205
3237
|
var memoryClipboard = null;
|
|
3206
3238
|
function collectSubtreeKeys(elements) {
|
|
3207
3239
|
const keys = /* @__PURE__ */ new Set();
|
|
@@ -3271,9 +3303,25 @@ function snapshotSelection(select, producer) {
|
|
|
3271
3303
|
if (!isCurrentSelection(select, controller, sources)) return;
|
|
3272
3304
|
return data;
|
|
3273
3305
|
}
|
|
3274
|
-
function
|
|
3306
|
+
function serializeClipboard(data) {
|
|
3307
|
+
return JSON.stringify(data);
|
|
3308
|
+
}
|
|
3309
|
+
function writeSharedClipboard(text) {
|
|
3310
|
+
try {
|
|
3311
|
+
globalThis.localStorage.setItem(CLIPBOARD_STORAGE_KEY, text);
|
|
3312
|
+
} catch {}
|
|
3313
|
+
}
|
|
3314
|
+
function readSharedClipboard() {
|
|
3315
|
+
try {
|
|
3316
|
+
return globalThis.localStorage.getItem(CLIPBOARD_STORAGE_KEY);
|
|
3317
|
+
} catch {
|
|
3318
|
+
return null;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
function writeClipboard(data) {
|
|
3275
3322
|
try {
|
|
3276
|
-
const text =
|
|
3323
|
+
const text = serializeClipboard(data);
|
|
3324
|
+
writeSharedClipboard(text);
|
|
3277
3325
|
Promise.resolve(navigator.clipboard.writeText(text)).catch(() => {});
|
|
3278
3326
|
} catch {}
|
|
3279
3327
|
}
|
|
@@ -3281,7 +3329,7 @@ function copyElements(select, producer) {
|
|
|
3281
3329
|
const data = snapshotSelection(select, producer);
|
|
3282
3330
|
if (!data) return;
|
|
3283
3331
|
memoryClipboard = data;
|
|
3284
|
-
|
|
3332
|
+
writeClipboard(data);
|
|
3285
3333
|
}
|
|
3286
3334
|
function removeKeys(data) {
|
|
3287
3335
|
delete data.key;
|
|
@@ -3409,7 +3457,7 @@ function pasteData(select, controller, pasteSelection, task, data, deserializers
|
|
|
3409
3457
|
const scope = controller.history.openScope(controller.selection);
|
|
3410
3458
|
const installSelection = () => controller.ownsPasteSelection(pasteSelection, task);
|
|
3411
3459
|
controller.applyCommand(changes, appendStructure(placements), scope, elements, options?.afterInstall ? {
|
|
3412
|
-
|
|
3460
|
+
afterInstall: () => options.afterInstall({
|
|
3413
3461
|
applicationData: data.applicationData,
|
|
3414
3462
|
keyMap,
|
|
3415
3463
|
history: recordHistory ? scope : void 0
|
|
@@ -3431,8 +3479,13 @@ function pasteElements(select, deserializers, options) {
|
|
|
3431
3479
|
try {
|
|
3432
3480
|
text = await navigator.clipboard.readText();
|
|
3433
3481
|
} catch {
|
|
3434
|
-
if (isCurrentPasteController(select, controller)) pasteMemory(select, controller, pasteSelection, task, deserializers, options);
|
|
3435
|
-
|
|
3482
|
+
if (isCurrentPasteController(select, controller)) if (memoryClipboard) pasteMemory(select, controller, pasteSelection, task, deserializers, options);
|
|
3483
|
+
else {
|
|
3484
|
+
const sharedText = readSharedClipboard();
|
|
3485
|
+
if (sharedText === null) return;
|
|
3486
|
+
text = sharedText;
|
|
3487
|
+
}
|
|
3488
|
+
if (text === void 0) return;
|
|
3436
3489
|
}
|
|
3437
3490
|
if (!isCurrentPasteController(select, controller)) return;
|
|
3438
3491
|
const data = JSON.parse(text);
|
|
@@ -3484,7 +3537,7 @@ function deleteElements(select, afterInstall) {
|
|
|
3484
3537
|
const recordsHistory = select.history.enabled;
|
|
3485
3538
|
const scope = select.history.openScope(selected);
|
|
3486
3539
|
controller.applyCommand([], structure, scope, [], {
|
|
3487
|
-
|
|
3540
|
+
afterInstall: afterInstall ? () => afterInstall({
|
|
3488
3541
|
elements: subjects,
|
|
3489
3542
|
history: recordsHistory ? scope : void 0
|
|
3490
3543
|
}) : void 0,
|
|
@@ -3899,6 +3952,19 @@ var Select = class extends Element {
|
|
|
3899
3952
|
select(elements) {
|
|
3900
3953
|
this.controller?.setSelection(elements);
|
|
3901
3954
|
}
|
|
3955
|
+
/** 完成已接受的当前交互,取消未提交的画线草稿;无活动交互时无操作。 */
|
|
3956
|
+
finishCurrentInteraction() {
|
|
3957
|
+
this.controller?.finishCurrentInteraction();
|
|
3958
|
+
}
|
|
3959
|
+
/**
|
|
3960
|
+
* 先结束旧交互并稳定几何,再同步调用 createCommand,传入 live selection。
|
|
3961
|
+
* 返回本命令 Canvas configured 的净变化,与 History/监听器是否开启无关;不包含旧交互。
|
|
3962
|
+
* 返回 undefined 的构造或空/相同值命令是普通 noop。未激活的 Select 不调用构造。
|
|
3963
|
+
* 普通选区同一 patch 使用 setProperties;此入口用于 owner-specific 与结构组合操作。
|
|
3964
|
+
*/
|
|
3965
|
+
applyCommand(createCommand) {
|
|
3966
|
+
return this.controller?.executeCommand(createCommand) ?? false;
|
|
3967
|
+
}
|
|
3902
3968
|
/**
|
|
3903
3969
|
* Groups the live multi-selection. Accepted active Transform work finishes
|
|
3904
3970
|
* before the independent Group command; stale pointer continuation is ignored.
|
|
@@ -3918,7 +3984,8 @@ var Select = class extends Element {
|
|
|
3918
3984
|
/**
|
|
3919
3985
|
* Aligns the live selection in world space. Without `referenceRect`, the
|
|
3920
3986
|
* current selection-frame AABB is the reference. A supplied world Rect is
|
|
3921
|
-
* used directly
|
|
3987
|
+
* used directly; a reference callback runs once after the previous interaction
|
|
3988
|
+
* and pending geometry finish. Distribution keeps strict edge gaps when they are numerically
|
|
3922
3989
|
* possible and uses bounded overlap when the selected sizes exceed the reference,
|
|
3923
3990
|
* while keeping every selection-frame AABB inside it and preserving order. The
|
|
3924
3991
|
* current axis is a noop only when an element cannot fit or no ordered layout is
|
package/dist/select/align.d.ts
CHANGED
|
@@ -11,4 +11,6 @@ export declare enum AlignType {
|
|
|
11
11
|
AlignEnd = "align-end",
|
|
12
12
|
AlignBetween = "align-between"
|
|
13
13
|
}
|
|
14
|
-
|
|
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,8 +1,7 @@
|
|
|
1
|
-
import { Element, ElementProperties, type BaseElementOption, type ConnectionPortRef, type Layer, type Root } from "@fulate/core";
|
|
2
|
-
import type { Rect } from "@fulate/share";
|
|
1
|
+
import { Element, ElementProperties, type BaseElementOption, type ConnectionPortRef, type Layer, type Root, type OwnerPropertyChange, type StructureChange } from "@fulate/core";
|
|
3
2
|
import { EditorInteractionController } from "../editor/interaction-controller";
|
|
4
3
|
import { HistoryManager } from "../history";
|
|
5
|
-
import { type AlignType } from "./align";
|
|
4
|
+
import { type AlignType, type AlignReference } from "./align";
|
|
6
5
|
import { type ArrangeType } from "./arrange";
|
|
7
6
|
import { type ClipboardDataProducer, type ClipboardPlugin, type DeleteContext, type PasteOptions } from "./clipboard";
|
|
8
7
|
import { Snap } from "./snap";
|
|
@@ -32,6 +31,23 @@ export interface SelectOption extends BaseElementOption {
|
|
|
32
31
|
}
|
|
33
32
|
/** 一次 selection property intent;同一 canonical reference 直接交给各 owner。 */
|
|
34
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
|
+
}
|
|
35
51
|
/**
|
|
36
52
|
* 轻量 Editor command/paint surface;交互状态由唯一 controller 拥有。
|
|
37
53
|
*
|
|
@@ -64,6 +80,15 @@ export declare class Select extends Element {
|
|
|
64
80
|
get interactionController(): EditorInteractionController;
|
|
65
81
|
/** 以一次独立操作选择给定元素,并在完成后异步通知 `select:end`。 */
|
|
66
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;
|
|
67
92
|
/**
|
|
68
93
|
* Groups the live multi-selection. Accepted active Transform work finishes
|
|
69
94
|
* before the independent Group command; stale pointer continuation is ignored.
|
|
@@ -79,7 +104,8 @@ export declare class Select extends Element {
|
|
|
79
104
|
/**
|
|
80
105
|
* Aligns the live selection in world space. Without `referenceRect`, the
|
|
81
106
|
* current selection-frame AABB is the reference. A supplied world Rect is
|
|
82
|
-
* used directly
|
|
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
|
|
83
109
|
* possible and uses bounded overlap when the selected sizes exceed the reference,
|
|
84
110
|
* while keeping every selection-frame AABB inside it and preserving order. The
|
|
85
111
|
* current axis is a noop only when an element cannot fit or no ordered layout is
|
|
@@ -87,7 +113,7 @@ export declare class Select extends Element {
|
|
|
87
113
|
* History entry; a noop creates neither a transform, History entry, nor configured
|
|
88
114
|
* change.
|
|
89
115
|
*/
|
|
90
|
-
align(type: AlignType, referenceRect?:
|
|
116
|
+
align(type: AlignType, referenceRect?: AlignReference): void;
|
|
91
117
|
/** 按当前 Layer 与 sibling 顺序排列 live selection。 */
|
|
92
118
|
arrange(type: ArrangeType): void;
|
|
93
119
|
/**
|