@fulate/tools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ import { Element, Layer, type BaseElementOption, type Root } from "@fulate/core";
2
+ import type { LineDecoration } from "@fulate/ui";
3
+ export interface LineToolOption extends BaseElementOption {
4
+ contentLayer?: string | Layer;
5
+ resultType?: "line" | "lineTree";
6
+ tailDecoration?: LineDecoration;
7
+ }
8
+ /**
9
+ * Line command/paint surface。Root listeners、mode、draft 与 History 全由唯一
10
+ * EditorInteractionController 拥有。
11
+ */
12
+ export declare class LineTool extends Element {
13
+ readonly type = "lineTool";
14
+ private readonly contentLayer;
15
+ private readonly resultType;
16
+ private readonly tailDecoration;
17
+ constructor(options?: LineToolOption);
18
+ startDrawing(): void;
19
+ stopDrawing(): void;
20
+ cancelDrawing(): void;
21
+ protected onDeactivate(root: Root | null, layer: Layer | null): void;
22
+ /** @internal controller 在 final confirm 时解析唯一 structure destination。 */
23
+ getContentLayerForController(): Layer<import("@fulate/core").LayerProperties, import("@fulate/core").LayerOption>;
24
+ /** @internal drawing session开始时读取一次,之后不再跟随配置变化。 */
25
+ getResultTypeForController(): "lineTree" | "line";
26
+ /** @internal drawing session 开始时读取一次。 */
27
+ getTailDecorationForController(): LineDecoration;
28
+ paint(): void;
29
+ }
@@ -0,0 +1,35 @@
1
+ import { Element, type BaseElementOption, type ElementOptionPatch, type Layer, type Root } from "@fulate/core";
2
+ import type { Select } from "../select";
3
+ export interface RuleOption extends BaseElementOption {
4
+ /** 同一编辑器组合中提供 live selection projection 的直接引用。 */
5
+ select?: Select;
6
+ rulerSize?: number;
7
+ backgroundColor?: string;
8
+ cornerBackgroundColor?: string;
9
+ majorTickColor?: string;
10
+ minorTickColor?: string;
11
+ borderColor?: string;
12
+ textColor?: string;
13
+ /** 标尺选区颜色;省略时每次绘制读取当前 root.skin.selection。 */
14
+ selectionColor?: string;
15
+ }
16
+ export declare class Rule extends Element {
17
+ readonly type = "rule";
18
+ private readonly appearance;
19
+ private readonly select;
20
+ private readonly workspaceFocusInsets;
21
+ constructor(options?: RuleOption);
22
+ get rulerSize(): number;
23
+ get backgroundColor(): string;
24
+ get cornerBackgroundColor(): string;
25
+ get majorTickColor(): string;
26
+ get minorTickColor(): string;
27
+ get borderColor(): string;
28
+ get textColor(): string;
29
+ get selectionColor(): string;
30
+ protected onActivate(root: Root | null, layer: Layer | null): void;
31
+ protected onDeactivate(root: Root | null, layer: Layer | null): void;
32
+ setOptions(options?: ElementOptionPatch<RuleOption>): this;
33
+ private setAppearance;
34
+ paint(): void;
35
+ }
@@ -0,0 +1,3 @@
1
+ import type { Select } from "./index";
2
+ export type AlignType = "justify-start" | "justify-center" | "justify-end" | "justify-between" | "align-start" | "align-center" | "align-end" | "align-between";
3
+ export declare function alignElements(select: Select, type: AlignType): void;
@@ -0,0 +1,11 @@
1
+ import type { Select } from "./index";
2
+ /** Select 支持的跨 Layer 与 sibling 排列方向。 */
3
+ export declare enum ArrangeType {
4
+ LayerUp = "layer-up",
5
+ LayerDown = "layer-down",
6
+ Forward = "forward",
7
+ Backward = "backward",
8
+ Top = "top",
9
+ Bottom = "bottom"
10
+ }
11
+ export declare function arrangeElements(select: Select, type: ArrangeType): void;
@@ -0,0 +1,2 @@
1
+ import { Element } from "@fulate/core";
2
+ export declare function checkElement(element: Element, excludes?: readonly Element[], scopeRootKey?: string): Element | undefined;
@@ -0,0 +1,40 @@
1
+ import { Element, Node } from "@fulate/core";
2
+ import { type DeserializeFactory } from "@fulate/import";
3
+ import type { HistoryScope } from "../history";
4
+ import type { Select } from "./index";
5
+ export interface ClipboardPlugin {
6
+ readonly type: string;
7
+ readonly deserialize: DeserializeFactory;
8
+ }
9
+ /** Copy producer 读取的 live selection 与一次收集的完整 subtree key 集合。 */
10
+ export interface CopyContext {
11
+ readonly sources: readonly Element[];
12
+ /** Fulate 在 snapshot 过程中已经收集的完整 source subtree keys。 */
13
+ readonly sourceKeys: ReadonlySet<string>;
14
+ }
15
+ /** 生成透明 applicationData;Fulate 不解释或额外复制返回值。 */
16
+ export type ClipboardDataProducer = (context: CopyContext) => unknown;
17
+ /** Paste 安装接缝收到的同一 applicationData、clone keyMap 与当前 History scope。 */
18
+ export interface PasteContext {
19
+ readonly applicationData: unknown;
20
+ /** Clipboard 中全部旧 key 到本次实际新 key 的同一份映射。 */
21
+ readonly keyMap: ReadonlyMap<string, string>;
22
+ /** History 关闭时为 undefined。 */
23
+ readonly history: HistoryScope | undefined;
24
+ }
25
+ /** 为一个非 Layer 顶层副本解析最终 live parent。 */
26
+ export type PasteParentResolver = (sourceParent: Node | undefined) => Node;
27
+ /** Paste 的 parent resolver 与 Canvas 安装后应用接缝。 */
28
+ export interface PasteOptions {
29
+ readonly resolveParent?: PasteParentResolver;
30
+ readonly afterInstall?: (context: PasteContext) => void;
31
+ }
32
+ /** Canvas 删除闭包安装后的应用接缝上下文。 */
33
+ export interface DeleteContext {
34
+ /** selected roots、descendants 与连带依赖节点组成的真实删除闭包。 */
35
+ readonly elements: readonly Element[];
36
+ /** History 关闭时为 undefined。 */
37
+ readonly history: HistoryScope | undefined;
38
+ }
39
+ export declare function copyElements(select: Select, producer?: ClipboardDataProducer): void;
40
+ export declare function pasteElements(select: Select, deserializers: ReadonlyMap<string, DeserializeFactory>, options?: PasteOptions): Promise<void>;
@@ -0,0 +1,3 @@
1
+ import type { DeleteContext } from "./clipboard";
2
+ import type { Select } from "./index";
3
+ export declare function deleteElements(select: Select, afterInstall?: (context: DeleteContext) => void): void;
@@ -0,0 +1,5 @@
1
+ import { Element } from "@fulate/core";
2
+ import { Group } from "@fulate/ui";
3
+ import type { Select } from "./index";
4
+ export declare function doGroup(select: Select): Group;
5
+ export declare function unGroup(select: Select): Element<import("@fulate/core").ElementProperties, import("@fulate/core").BaseElementOption<any>, Partial<Omit<import("@fulate/core").BaseElementOption<any>, "key" | "children">>>[];
@@ -0,0 +1,119 @@
1
+ import { Element, ElementProperties, type BaseElementOption, type ConnectionPortRef, type Layer, type Root } from "@fulate/core";
2
+ import { EditorInteractionController } from "../editor/interaction-controller";
3
+ import { HistoryManager } from "../history";
4
+ import { type AlignType } from "./align";
5
+ import { type ArrangeType } from "./arrange";
6
+ import { type ClipboardDataProducer, type ClipboardPlugin, type DeleteContext, type PasteOptions } from "./clipboard";
7
+ import { Snap } from "./snap";
8
+ import type { EndpointRef } from "@fulate/ui";
9
+ import type { FlipDirection, ResizeOptions } from "./transform";
10
+ import type { LineTool } from "../line";
11
+ export type { FlipDirection, ResizeAnchor, ResizeOptions } from "./transform";
12
+ export type { CopyContext, ClipboardDataProducer, DeleteContext, PasteContext, PasteOptions, PasteParentResolver } from "./clipboard";
13
+ export interface SelectKeyboardCommands {
14
+ copy(): void;
15
+ cut(): void;
16
+ paste(): void | Promise<void>;
17
+ delete(): void;
18
+ }
19
+ export interface ToolsConnectionIntent {
20
+ readonly source: EndpointRef;
21
+ readonly target: ConnectionPortRef;
22
+ connect(): void;
23
+ }
24
+ export type ConnectionGesture = "auto" | false | ((intent: ToolsConnectionIntent) => void);
25
+ export interface SelectOption extends BaseElementOption {
26
+ readonly connectionGesture?: ConnectionGesture;
27
+ /** 同一编辑器组合中用于吸附的直接工具引用。 */
28
+ readonly snap?: Snap;
29
+ /** 同一编辑器组合中由快捷键启动的直接画线工具引用。 */
30
+ readonly lineTool?: LineTool;
31
+ }
32
+ /** 一次 selection property intent;同一 canonical reference 直接交给各 owner。 */
33
+ export type SelectedPropertyPatch = Readonly<Record<string, unknown>>;
34
+ /**
35
+ * 轻量 Editor command/paint surface;交互状态由唯一 controller 拥有。
36
+ *
37
+ * `select:end` 在一次显式选择、交互选择、selection-producing command
38
+ * 或 History replay 的内部工作和 selection projection 完成后异步通知。
39
+ * `select:update` 仅在存在直接监听器时,于选中内容的几何、投影和绘制
40
+ * 在当前 Root frame 内稳定后同步派发;同一显示帧的连续变化合并为一次通知。
41
+ * `select:configured-change` 在一次 Select/History 语义操作产生最终 configured
42
+ * 属性或结构净变化时异步通知一次;适用的 History entry/stack 已先安装。事件不携带
43
+ * payload,普通 noop 与纯 runtime override 不派发;`select:end` 不承担持久化。
44
+ * 每次显式 `select()` 都是一次独立操作,包括重复传入同一 selection。全部 Select
45
+ * `end/configured-change/undo/redo` 通知只供外部读取派发时的 live state,不参与、阻止或使已完成的同步操作失败。
46
+ */
47
+ export declare class Select extends Element {
48
+ controlSize: number;
49
+ hitPadding: number;
50
+ snapAngle: number;
51
+ snapThreshold: number;
52
+ history: HistoryManager;
53
+ readonly connectionGesture: ConnectionGesture;
54
+ readonly snapTool: Snap | undefined;
55
+ readonly lineTool: LineTool | undefined;
56
+ private controller?;
57
+ private keyboardCommands?;
58
+ private readonly clipboardDeserializers;
59
+ constructor(options?: SelectOption);
60
+ get selectedElements(): readonly Element[];
61
+ get editorState(): import("../editor/interaction-controller").EditorInteractionState;
62
+ /** @internal command modules使用同一 controller/batch/History owner。 */
63
+ get interactionController(): EditorInteractionController;
64
+ /** 以一次独立操作选择给定元素,并在完成后异步通知 `select:end`。 */
65
+ select(elements: readonly Element[]): void;
66
+ /**
67
+ * Groups the live multi-selection. Accepted active Transform work finishes
68
+ * before the independent Group command; stale pointer continuation is ignored.
69
+ * An obvious insufficient-selection noop does not drain the frame runtime.
70
+ */
71
+ doGroup(): import("@fulate/ui").Group;
72
+ /**
73
+ * Ungroups the live single Group selection. Accepted active Transform work
74
+ * finishes before the independent command; stale pointer continuation is ignored.
75
+ * An obvious non-Group noop does not drain the frame runtime.
76
+ */
77
+ unGroup(): Element<ElementProperties, BaseElementOption<any>, Partial<Omit<BaseElementOption<any>, "key" | "children">>>[];
78
+ align(type: AlignType): void;
79
+ /** 按当前 Layer 与 sibling 顺序排列 live selection。 */
80
+ arrange(type: ArrangeType): void;
81
+ /**
82
+ * 把一次 authored 顶层属性 intent 应用到全部 live selected elements。
83
+ * 省略字段保持不变,`undefined` 恢复默认值,`null` 是普通领域值;
84
+ * 对象和数组不 deep merge/patch;Tools 将同一份 patch 引用直接交给全部
85
+ * selected owners。调用者需要独立副本时应在交付前复制;标量相同值仍是 noop。
86
+ * Tools 不过滤元素是否支持字段。
87
+ * 空 selection 不结束其他交互;非空 selection 先完成已接受交互,再以
88
+ * 一个 batch 和最多一条 History 提交。本命令不改变 selection,也不派发
89
+ * `select:end`;最终 configured 净变化异步通知一次 `select:configured-change`。
90
+ * geometry impact 可触发 `select:update`,纯 paint 不触发该几何事件。
91
+ * 世界有效尺寸与位置仍使用 resize/Transform 命令。
92
+ */
93
+ setProperties(patch: SelectedPropertyPatch): void;
94
+ /**
95
+ * Resizes the live selection to world visual/frame dimensions in one
96
+ * semantic command. Omitted axes keep their current size; `top-left` is the
97
+ * default fixed anchor. Parent and nested Group transforms are resolved
98
+ * internally.
99
+ */
100
+ resize(options?: ResizeOptions): void;
101
+ /** 围绕当前 selection frame 中心沿 frame 自身轴镜像。 */
102
+ flip(direction: FlipDirection): void;
103
+ /** 删除当前 selection,并在 Canvas 删除安装后、drain/finish 前交付一次应用接缝。 */
104
+ delete(afterInstall?: (context: DeleteContext) => void): void;
105
+ /**
106
+ * 复制 live selection 到 Clipboard v2;producer 最多同步执行一次,不改 Canvas、selection 或 History。
107
+ */
108
+ copy(producer?: ClipboardDataProducer): void;
109
+ /** 从 system Clipboard 或同一 memory payload 粘贴,并在安装后交付可选应用接缝。 */
110
+ paste(options?: PasteOptions): Promise<void>;
111
+ /** 绑定当前唯一应用键盘命令 owner;cleanup 只清除仍属于本次绑定的引用。 */
112
+ bindKeyboardCommands(commands: SelectKeyboardCommands): () => void;
113
+ /** @internal Controller 读取当前唯一键盘 command owner。 */
114
+ get keyboardCommandOwner(): SelectKeyboardCommands;
115
+ useClipboard(...plugins: ClipboardPlugin[]): this;
116
+ protected onActivate(root: Root | null, layer: Layer | null): void;
117
+ protected onDeactivate(root: Root | null, layer: Layer | null): void;
118
+ paint(): void;
119
+ }
@@ -0,0 +1,4 @@
1
+ import type { EditorInteractionController } from "../editor/interaction-controller";
2
+ import type { Select } from "./index";
3
+ /** paint 与 hit 直接读取 controller 当前同一 projection object。 */
4
+ export declare function paintSelect(select: Select, controller: EditorInteractionController): void;
@@ -0,0 +1,42 @@
1
+ import { Element, type ConnectionPortDefinition, type ConnectionPortRef, type Root } from "@fulate/core";
2
+ import type { PointView } from "@fulate/share";
3
+ import { type EditorSpatialClient } from "@fulate/ui";
4
+ export interface IndexedConnectionPort {
5
+ readonly ref: ConnectionPortRef;
6
+ readonly kind: "endpoint-binding";
7
+ readonly worldPosition: PointView;
8
+ readonly definition?: ConnectionPortDefinition;
9
+ readonly owner: Element;
10
+ }
11
+ /** Tools 唯一按 Root spatial phase 增量更新的 committed Port projection。 */
12
+ export declare class PortIndex implements EditorSpatialClient {
13
+ private readonly root;
14
+ private readonly tree;
15
+ private readonly itemsByOwner;
16
+ private readonly dirtyOwners;
17
+ private readonly ownerObservationCleanups;
18
+ private readonly cleanups;
19
+ private readonly frameFlush;
20
+ private lastCandidateCount;
21
+ private projectionFlushCount;
22
+ constructor(root: Root);
23
+ private hasDeclaredPorts;
24
+ private refreshOwner;
25
+ private registerOwner;
26
+ private unregisterOwner;
27
+ private markOwnerDirty;
28
+ hasPendingSpatialWork(): boolean;
29
+ flushSpatialProjection(): void;
30
+ private replaceOwnerProjection;
31
+ query(point: PointView, radius: number): readonly IndexedConnectionPort[];
32
+ portsForOwners(owners: ReadonlySet<Element>): readonly IndexedConnectionPort[];
33
+ /** @internal 性能结构门禁。 */
34
+ get debugStats(): {
35
+ indexedPorts: number;
36
+ observedOwners: number;
37
+ pendingOwners: number;
38
+ lastQueryCandidates: number;
39
+ projectionFlushes: number;
40
+ };
41
+ dispose(): void;
42
+ }
@@ -0,0 +1,139 @@
1
+ import { Element } from "@fulate/core";
2
+ import { type PointEditTarget, type PointEditingScope, type SnapSourceCapability, type TransformInteractionCapability, type TransformInteractionPolicy } from "@fulate/ui";
3
+ import { type PointView, type Rect } from "@fulate/share";
4
+ export type ResizeHandle = "tl" | "tr" | "br" | "bl" | "mt" | "mr" | "mb" | "ml";
5
+ export type SelectionRotationPivot = {
6
+ readonly kind: "selection-center";
7
+ } | {
8
+ readonly kind: "custom-owner-local";
9
+ readonly ownerKey: string;
10
+ readonly local: PointView;
11
+ };
12
+ export type SelectionOverlayTarget = {
13
+ readonly kind: "resize";
14
+ readonly handle: ResizeHandle;
15
+ } | {
16
+ readonly kind: "rotate";
17
+ } | {
18
+ readonly kind: "pivot";
19
+ } | {
20
+ readonly kind: "body";
21
+ } | {
22
+ readonly kind: "point";
23
+ readonly ownerKey: string;
24
+ readonly target: PointEditTarget;
25
+ readonly move: "free" | "locked";
26
+ readonly deletable: boolean;
27
+ readonly role: "endpoint" | "vertex" | "segment" | "handle";
28
+ readonly insertT: number | undefined;
29
+ };
30
+ export interface SelectionOverlayControl {
31
+ readonly world: PointView;
32
+ readonly cursor: string;
33
+ readonly shape: "rect" | "circle" | "diamond" | "handle" | null;
34
+ readonly guideFrom?: PointView;
35
+ readonly active?: boolean;
36
+ readonly hit: {
37
+ readonly kind: "point";
38
+ } | {
39
+ readonly kind: "segment";
40
+ readonly from: PointView;
41
+ readonly to: PointView;
42
+ } | {
43
+ readonly kind: "rotation-ring";
44
+ };
45
+ readonly target: Exclude<SelectionOverlayTarget, {
46
+ readonly kind: "body";
47
+ }>;
48
+ }
49
+ export interface SelectionFrameProjection {
50
+ readonly left: number;
51
+ readonly top: number;
52
+ readonly width: number;
53
+ readonly height: number;
54
+ readonly angle: number;
55
+ readonly matrix: DOMMatrixReadOnly;
56
+ readonly inverseMatrix: DOMMatrixReadOnly;
57
+ readonly outline: readonly PointView[];
58
+ readonly worldCenter: PointView;
59
+ }
60
+ export type SelectionRotationPivotHorizontal = "left" | "center" | "right";
61
+ export type SelectionRotationPivotVertical = "top" | "center" | "bottom";
62
+ export interface SelectionRotationPivotSnapMarker extends PointView {
63
+ readonly horizontal: SelectionRotationPivotHorizontal;
64
+ readonly vertical: SelectionRotationPivotVertical;
65
+ }
66
+ export interface SelectionOverlayProjection {
67
+ readonly selectionKeys: readonly string[];
68
+ readonly frame: SelectionFrameProjection | null;
69
+ readonly controls: readonly SelectionOverlayControl[];
70
+ readonly selectedOutlineOwners: readonly ProjectionOwner[];
71
+ readonly pointScopeOutline: readonly PointView[] | null;
72
+ readonly bodyEnabled: boolean;
73
+ readonly frameVisible: boolean;
74
+ readonly rotationPivotWorld: PointView;
75
+ readonly rotationPivotSnapMarkers: readonly SelectionRotationPivotSnapMarker[];
76
+ readonly marquee: Readonly<Rect> | null;
77
+ }
78
+ export interface ProjectionOwner {
79
+ readonly element: Element;
80
+ readonly transform: TransformInteractionCapability | undefined;
81
+ readonly pointEnabled: boolean;
82
+ readonly pointScope: PointEditingScope | null;
83
+ readonly snap: SnapSourceCapability | undefined;
84
+ readonly frameVisible: boolean;
85
+ }
86
+ export interface ProjectionPolicy {
87
+ readonly move: boolean;
88
+ readonly resize: TransformInteractionPolicy["resize"];
89
+ readonly rotate: boolean;
90
+ readonly rotationPivot: TransformInteractionPolicy["rotationPivot"];
91
+ }
92
+ export interface SelectionProjectionMembership {
93
+ readonly selectionKeys: readonly string[];
94
+ readonly selectedOutlineOwners: readonly ProjectionOwner[];
95
+ readonly bodyEnabled: boolean;
96
+ readonly frameVisible: boolean;
97
+ }
98
+ interface PointControlTopology {
99
+ readonly local: PointView;
100
+ readonly guideLocal?: PointView;
101
+ readonly cursor: string;
102
+ readonly shape: "circle" | "diamond" | "handle";
103
+ readonly target: Extract<SelectionOverlayTarget, {
104
+ readonly kind: "point";
105
+ }>;
106
+ }
107
+ export interface SelectionProjectionTopology {
108
+ readonly pointControls: readonly PointControlTopology[];
109
+ readonly pointScopeOutline: readonly PointView[] | null;
110
+ readonly pointScopeValid: boolean;
111
+ }
112
+ export declare function samePointEditTarget(left: PointEditTarget, right: PointEditTarget): boolean;
113
+ export declare function createSelectionFrameProjection({ left, top, width, height, angle }: Pick<SelectionFrameProjection, "left" | "top" | "width" | "height" | "angle">): SelectionFrameProjection;
114
+ /** @internal Build a command frame from the current owner bounds. */
115
+ export declare function createWorldVisualBoundsSelectionFrame(owners: readonly ProjectionOwner[], fallback?: SelectionFrameProjection | null): SelectionFrameProjection;
116
+ export declare function createSelectionRotationPivotSnapMarkers(frame: SelectionFrameProjection): readonly SelectionRotationPivotSnapMarker[];
117
+ export declare function createSelectionProjectionMembership(owners: readonly ProjectionOwner[], policy: ProjectionPolicy): SelectionProjectionMembership;
118
+ export declare function createSelectionProjectionTopology(owners: readonly ProjectionOwner[], isCurrent?: (owner: ProjectionOwner) => boolean): SelectionProjectionTopology;
119
+ export declare function createProjectionOwner(element: Element, frameVisible?: boolean, pointScope?: PointEditingScope | null, pointEnabled?: boolean): ProjectionOwner;
120
+ export declare function buildSelectionOverlayProjection({ owners, membership, topology, frame: frameOverride, policy, pivot, showPivot, pivotSnapMarkers, activePointTarget, marquee }: {
121
+ readonly owners: readonly ProjectionOwner[];
122
+ readonly membership: SelectionProjectionMembership;
123
+ readonly topology: SelectionProjectionTopology;
124
+ readonly frame?: SelectionFrameProjection | null;
125
+ readonly policy: ProjectionPolicy;
126
+ readonly pivot: SelectionRotationPivot;
127
+ readonly showPivot: boolean;
128
+ readonly pivotSnapMarkers?: readonly SelectionRotationPivotSnapMarker[];
129
+ readonly activePointTarget?: Readonly<{
130
+ ownerKey: string;
131
+ target: PointEditTarget;
132
+ }> | null;
133
+ readonly marquee: Readonly<Rect> | null;
134
+ }): SelectionOverlayProjection;
135
+ export declare function hitSelectionOverlay(projection: SelectionOverlayProjection, point: PointView, controlRadius: number, edgePadding: number, rotatePadding: number): {
136
+ readonly target: SelectionOverlayTarget;
137
+ readonly cursor: string;
138
+ } | null;
139
+ export {};
@@ -0,0 +1,97 @@
1
+ import { type PointView } from "@fulate/share";
2
+ import { Element, type BaseElementOption, type Layer, type Root } from "@fulate/core";
3
+ import { type EndpointConnectionRole, type EndpointRef } from "@fulate/ui";
4
+ interface SnapLine {
5
+ type: "vertical" | "horizontal";
6
+ value: number;
7
+ start: number;
8
+ end: number;
9
+ distanceText?: string;
10
+ points: PointView[];
11
+ textPos?: PointView;
12
+ }
13
+ interface SnapResult {
14
+ dx: number;
15
+ dy: number;
16
+ }
17
+ export interface PortSnapResult extends PointView {
18
+ elementKey: string;
19
+ /** target owner-local Port ID。 */
20
+ portId: string;
21
+ }
22
+ export interface PortSnapProbeResult {
23
+ readonly snap: PortSnapResult | null;
24
+ readonly state: PortInteractionState | null;
25
+ }
26
+ type PortInteractionState = "available" | "incompatible" | "forbidden";
27
+ interface PortHighlight extends PointView {
28
+ readonly element: Element;
29
+ readonly portId: string;
30
+ state: PortInteractionState | null;
31
+ }
32
+ interface AxisRange {
33
+ min: number;
34
+ max: number;
35
+ }
36
+ interface AxisSegment extends AxisRange {
37
+ value: number;
38
+ }
39
+ interface SnapAxes {
40
+ vertical: AxisSegment[];
41
+ horizontal: AxisSegment[];
42
+ }
43
+ interface AxisLineMatch {
44
+ targetVal: number;
45
+ segments: AxisRange[];
46
+ }
47
+ export declare function isAxisAlignedRect(pts: readonly PointView[]): boolean;
48
+ export declare function addEdgeMidpoints(pts: readonly PointView[]): PointView[];
49
+ export declare function createSnapAxes(snapPoints: readonly PointView[]): SnapAxes;
50
+ export declare function buildAxisSnapLines(best: AxisLineMatch, movingRange: {
51
+ min: number;
52
+ max: number;
53
+ }, type: "vertical" | "horizontal", scale: number): SnapLine[];
54
+ export declare class Snap extends Element {
55
+ readonly type = "snap";
56
+ threshold: number;
57
+ lineColor: string;
58
+ lineWidth: number;
59
+ dashPattern: number[];
60
+ isSnapping: boolean;
61
+ private snapLines;
62
+ private snapCandidates;
63
+ private axisElementExcludes;
64
+ private axisPointExcludes;
65
+ private axisTargets;
66
+ private portIndex;
67
+ private showViewportPorts;
68
+ private viewportPortTargets;
69
+ private activePortHighlight;
70
+ private portProbe;
71
+ private snapViewportState;
72
+ private sceneChangeCleanup?;
73
+ portHighlights: PortHighlight[];
74
+ constructor(options?: BaseElementOption);
75
+ protected onActivate(root: Root | null, layer: Layer | null): void;
76
+ protected onDeactivate(root: Root | null, layer: Layer | null): void;
77
+ private collectSnapCandidates;
78
+ private rebuildPortHighlights;
79
+ private refreshSnapCandidates;
80
+ private invalidateSnapshot;
81
+ private sceneChangeIsExcluded;
82
+ private handleSceneChange;
83
+ start(excludeEls: readonly Element[], excludePoints?: readonly {
84
+ element: Element;
85
+ indices: readonly number[];
86
+ }[], showViewportPorts?: boolean): void;
87
+ private getAxisTargets;
88
+ detect(originalPoints: readonly PointView[], rawDx: number, rawDy: number): SnapResult;
89
+ stop(): void;
90
+ private findClosestPortTarget;
91
+ private clearActivePortHighlight;
92
+ probeForbiddenPortMove(points: readonly PointView[], dx: number, dy: number, excludeEls?: readonly Element[]): boolean;
93
+ probePortSnap(worldX: number, worldY: number, role: EndpointConnectionRole, excludeEls?: readonly Element[], excludeSource?: EndpointRef): PortSnapProbeResult;
94
+ detectPortSnap(worldX: number, worldY: number, role: EndpointConnectionRole, excludeEls?: readonly Element[], excludeSource?: EndpointRef): PortSnapResult | null;
95
+ paint(): void;
96
+ }
97
+ export {};
@@ -0,0 +1,40 @@
1
+ import { type PointView } from "@fulate/share";
2
+ import { type ResizeHandle, type SelectionFrameProjection } from "./projection";
3
+ /** Selection frame 中保持不动的固定点。 */
4
+ export type ResizeAnchor = "top-left" | "top" | "top-right" | "left" | "center" | "right" | "bottom-left" | "bottom" | "bottom-right";
5
+ /** 按当前选择的 world visual/frame 尺寸调整选中元素。 */
6
+ export interface ResizeOptions {
7
+ /** 目标 world visual frame 宽度;省略时保持当前宽度。 */
8
+ readonly width?: number;
9
+ /** 目标 world visual frame 高度;省略时保持当前高度。 */
10
+ readonly height?: number;
11
+ /** 保持不动的 frame 固定点,默认 `top-left`。 */
12
+ readonly anchor?: ResizeAnchor;
13
+ }
14
+ /** Selection frame 内执行镜像的方向。 */
15
+ export type FlipDirection = "horizontal" | "vertical";
16
+ export declare function resolveSelectionFrameTransform(frame: SelectionFrameProjection, worldDelta: DOMMatrixReadOnly, kind: "move" | "resize" | "rotate"): SelectionFrameProjection;
17
+ export declare function resolveResizeWorldDelta(frame: SelectionFrameProjection, handle: ResizeHandle, worldPoint: PointView, proportional: boolean): DOMMatrix;
18
+ /** @internal 把 target frame 尺寸转为一个固定锚点的 world 线性变换。 */
19
+ export declare function resolveResizeWorldDeltaForSize(frame: SelectionFrameProjection, width: number, height: number, anchor?: ResizeAnchor): DOMMatrix;
20
+ /** @internal 围绕 selection frame 中心沿 frame 自身坐标轴镜像。 */
21
+ export declare function resolveFlipWorldDelta(frame: SelectionFrameProjection, direction: FlipDirection): DOMMatrix;
22
+ export declare function resolveRotationTransform({ frameAngle, pivot, unwrappedDelta, snapAngle, snapThreshold }: {
23
+ readonly frameAngle: number;
24
+ readonly pivot: PointView;
25
+ readonly unwrappedDelta: number;
26
+ readonly snapAngle: number;
27
+ readonly snapThreshold: number;
28
+ }): {
29
+ worldDelta: DOMMatrix;
30
+ unwrappedDelta: number;
31
+ };
32
+ /** 根据两个 pointer 位置解析单步旋转;连续手势由 controller 累积 unwrappedDelta。 */
33
+ export declare function resolveRotationWorldDelta({ frameAngle, pivot, start, current, snapAngle, snapThreshold }: {
34
+ readonly frameAngle: number;
35
+ readonly pivot: PointView;
36
+ readonly start: PointView;
37
+ readonly current: PointView;
38
+ readonly snapAngle: number;
39
+ readonly snapThreshold: number;
40
+ }): DOMMatrix;
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@fulate/tools",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "license": "MIT",
17
+ "scripts": {
18
+ "test": "vitest run --config vitest.config.ts",
19
+ "test:watch": "vitest --config vitest.config.ts"
20
+ },
21
+ "dependencies": {
22
+ "@fulate/share": "*",
23
+ "@fulate/core": "*",
24
+ "@fulate/ui": "*",
25
+ "@fulate/import": "*",
26
+ "lodash-es": "^4.17.22",
27
+ "rbush": "^4.0.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/rbush": "^4.0.0"
31
+ }
32
+ }