@fulate/ui 1.0.5 → 1.0.7

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 CHANGED
@@ -133,8 +133,9 @@ Rectangle、Triangle及其他Shape省略`backgroundColor`/`borderColor`时读取
133
133
  `skin.lineStroke`。显式值始终优先,Shape的显式`null`关闭绘制,显式字段清回`undefined`后立即重新
134
134
  继承Skin。Skin改变只触发现有Root/Layer重绘,不改写element、branch topology或History。
135
135
 
136
- LineTree整体resize继续使用O(1) pose。随后Point移动或插入anchor时在同一owner change内保留当前
137
- world pose;包括反向scale在内,只有目标anchor改变world位置,其他points保持不动。
136
+ LineTree整体resize把全部拓扑点按完整仿射写回 authored topology。随后Point移动或插入anchor时在
137
+ 同一owner change内保留当前world pose;包括反向scale在内,只有目标anchor改变world位置,其他points
138
+ 保持不动。
138
139
 
139
140
  ## Text 尺寸与显示
140
141
 
@@ -234,14 +235,15 @@ const behavior = {
234
235
  };
235
236
  ```
236
237
 
237
- `OwnerDirect` 用于不绘制单选 frame、首次位移可直接提升 owner drag 的自定义对象;Group 使用
238
- `DirectChild` dive,普通 Shape/Line 使用 `Standard`。Transform provider 在 gesture start 捕获 frozen
239
- reference 与领域 owned state,每帧只纯解析最终 owner change;frame-authored Shape、pose-authored
240
- Group 与 authored-geometry Line/Polygon/VectorPath 共用 Core reference-transform primitive,结构化
241
- authored value 不为 caller isolation clone。Group 的稳定 reference 由创建时的 width/height/origin 与
242
- pose 拥有;当前 content bounds 是只读 local derived view。Line/LineTree 的
243
- 固定world paint padding不贡献ancestor content bounds,但仍进入各自render coverage。group/ungroup
244
- 只转换 local pose,保留 width/path/contour/topology、effect 与 relation 的值和引用。
238
+ `OwnerDirect` 用于不绘制单选 frame、首次位移可直接提升 owner drag 的自定义对象;Group 使用
239
+ `DirectChild` dive,普通 Shape/Line 使用 `Standard`。Transform provider 在 gesture start 捕获 frozen
240
+ reference 与领域 owned state,每帧只纯解析最终 owner change;frame-authored Shape、pose-authored
241
+ Group 与 authored-geometry Line/Polygon/VectorPath 共用 Core reference-transform primitive,结构化
242
+ authored value 不为 caller isolation clone。Group 的稳定 reference 由创建时的 width/height/origin 与
243
+ pose 拥有;当前 content bounds 是只读 local derived view。Line/LineTree 的
244
+ 固定world paint padding不贡献ancestor content bounds,但仍进入各自render coverage。普通Group
245
+ move/rotate只写自身pose;Group或多选resize/flip递归写入子元素的frame或矢量geometry。group/ungroup
246
+ 只转换 local pose,保留 width/path/contour/topology、effect 与 relation 的值和引用。
245
247
 
246
248
  `selectable:false`只限制Editor交互选择,并对该owner分支生效:普通点击、marquee、Select All与
247
249
  Group DirectChild下钻都不能进入该分支。公开`Select.select(elements)`和History selection replay由
@@ -1,5 +1,5 @@
1
1
  import { createAuthoredLineSubpath, type AnchorId, type AuthoredSubpath, type SegmentId } from "@fulate/core";
2
- import type { PointView, Rect } from "@fulate/share";
2
+ import { type PointView, type Rect } from "@fulate/share";
3
3
  export { createAuthoredLineSubpath };
4
4
  /** Replace one authored anchor point while retaining its record and all IDs. */
5
5
  export declare function replaceAuthoredAnchorPoint(subpath: AuthoredSubpath, anchorId: AnchorId, point: PointView): AuthoredSubpath;
@@ -22,7 +22,7 @@ export declare function insertAuthoredLineAnchor(subpath: AuthoredSubpath, segme
22
22
  export declare function replaceAuthoredCubicHandle(subpath: AuthoredSubpath, segmentId: SegmentId, role: "cubic-control-1" | "cubic-control-2", point: PointView): AuthoredSubpath;
23
23
  /** Delete one anchor and merge its predecessor directly to its successor. */
24
24
  export declare function removeAuthoredAnchor(subpath: AuthoredSubpath, anchorId: AnchorId, minimumAnchorCount?: number): AuthoredSubpath;
25
- /** Scale coordinates while retaining every subpath, anchor, segment, and record ID. */
26
- export declare function scaleAuthoredSubpath(subpath: AuthoredSubpath, scaleX: number, scaleY: number): AuthoredSubpath;
25
+ /** Transform coordinates while retaining every subpath, anchor, segment, and record ID. */
26
+ export declare function transformAuthoredSubpath(subpath: AuthoredSubpath, matrix: DOMMatrixReadOnly): AuthoredSubpath;
27
27
  /** Read bounds directly from authored anchor coordinates. */
28
28
  export declare function getAuthoredSubpathBounds(subpath: AuthoredSubpath): Readonly<Rect> | null;
@@ -12,6 +12,11 @@ export interface EditorCapabilityHost {
12
12
  }
13
13
  /** 创建一个不携带字符串 key 或元数据的 capability token。 */
14
14
  export declare function defineEditorCapability<T>(): EditorCapabilityToken<T>;
15
+ /**
16
+ * @internal Match one token and check that its provider implements the capability.
17
+ * TypeScript cannot narrow the requested generic from the opaque token identity.
18
+ */
19
+ export declare function provideEditorCapability<T, Capability>(token: EditorCapabilityToken<T>, supported: EditorCapabilityToken<Capability>, provider: NoInfer<Capability>): T | undefined;
15
20
  /** 判断元素是否直接提供 capability 查询入口。 */
16
21
  export declare function isEditorCapabilityHost(element: Element): element is Element & EditorCapabilityHost;
17
22
  /** @internal 执行一次窄 host 判断和一次 direct capability 查询。 */
@@ -47,7 +52,11 @@ export interface TransformInteractionCapability<Owned = unknown> {
47
52
  /** Optional authored/layout bounds used by the editor transform frame. */
48
53
  getSelectionFrameBounds?(): Readonly<Rect> | null;
49
54
  captureTransformBaseline(): ElementTransformBaseline<Owned>;
50
- resolveTransformChange(baseline: Readonly<ElementTransformBaseline<Owned>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): OwnerPropertyChange;
55
+ /**
56
+ * Resolves every owner change for this transform. Composite owners may return
57
+ * multiple child changes; the editor commits the complete result in one batch.
58
+ */
59
+ resolveTransformChange(baseline: Readonly<ElementTransformBaseline<Owned>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): readonly OwnerPropertyChange[];
51
60
  }
52
61
  export interface SnapSourceCapability {
53
62
  getWorldSnapPointsView(): readonly PointView[];
@@ -146,5 +155,11 @@ export declare const SelectionBehaviorCapabilityToken: EditorCapabilityToken<Sel
146
155
  export declare const TransformInteractionCapabilityToken: EditorCapabilityToken<TransformInteractionCapability<unknown>>;
147
156
  export declare const SnapSourceCapabilityToken: EditorCapabilityToken<SnapSourceCapability>;
148
157
  export declare const PointEditingCapabilityToken: EditorCapabilityToken<PointEditingCapability>;
158
+ /**
159
+ * Resolve the local rectangle used as an editor selection frame. An owner
160
+ * supplied frame is authoritative; owners without one retain a deterministic
161
+ * local visual/layout fallback.
162
+ */
163
+ export declare function resolveSelectionFrameBounds(element: Element, capability?: TransformInteractionCapability): Readonly<Rect>;
149
164
  export declare const ContentEditingCapabilityToken: EditorCapabilityToken<ContentEditingCapability>;
150
165
  export {};
@@ -1,9 +1,23 @@
1
1
  import { type Layer, type PropertyChanges, type Root, Shape, ShapeProperties, ShapeTransform, type ConnectionPortConfiguration, type ElementOptionPatch, type ElementTransformBaseline, type ReparentTransformIntent, type ShapeOption, type TransformIntent } from "@fulate/core";
2
- import { Bound } from "@fulate/share";
2
+ import { Bound, type PointView, type Rect } from "@fulate/share";
3
3
  import { SelectionBehaviorMode, SelectionDiveMode, SelectionSnapMode, type EditorCapabilityToken, type SelectionBehaviorCapability, type SnapSourceCapability, type TransformInteractionCapability } from "./capability";
4
+ export interface WorldRectPointsViewCache {
5
+ source: Readonly<Rect> | null;
6
+ a: number;
7
+ b: number;
8
+ c: number;
9
+ d: number;
10
+ e: number;
11
+ f: number;
12
+ points: readonly PointView[] | null;
13
+ }
14
+ export declare function createWorldRectPointsViewCache(): WorldRectPointsViewCache;
15
+ /** Project one stable local frame to a borrowed world-point view. */
16
+ export declare function getWorldRectPointsView(cache: WorldRectPointsViewCache, frame: Readonly<Rect>, matrix: DOMMatrixReadOnly): readonly PointView[];
4
17
  /** UI frame-authored Shape 系列共享的 direct Editor provider。 */
5
18
  export declare abstract class EditorShape<P extends ShapeProperties = ShapeProperties, O extends ShapeOption<any> = ShapeOption<any>> extends Shape<P, O> implements SelectionBehaviorCapability, TransformInteractionCapability, SnapSourceCapability {
6
19
  private connectionPortDecoration;
20
+ private readonly worldSnapPointsCache;
7
21
  getEditorCapability<T>(token: EditorCapabilityToken<T>): T | undefined;
8
22
  getSelectionBehavior(): {
9
23
  readonly mode: SelectionBehaviorMode.Standard;
@@ -17,11 +31,12 @@ export declare abstract class EditorShape<P extends ShapeProperties = ShapePrope
17
31
  readonly rotationPivot: "adjustable";
18
32
  };
19
33
  captureTransformBaseline(): ElementTransformBaseline;
20
- resolveTransformChange(baseline: Readonly<ElementTransformBaseline>, intent: Readonly<TransformIntent | ReparentTransformIntent>): import("@fulate/core").OwnerPropertyChange;
34
+ resolveTransformChange(baseline: Readonly<ElementTransformBaseline>, intent: Readonly<TransformIntent | ReparentTransformIntent>): readonly import("@fulate/core").OwnerPropertyChange[];
35
+ getSelectionFrameBounds(): Readonly<Rect> | null;
21
36
  setOptions(options?: ElementOptionPatch<O>): this;
22
37
  replacePorts(value: ConnectionPortConfiguration): this;
23
38
  restorePropertySnapshot(snapshot: Record<string, unknown>): this;
24
- getWorldSnapPointsView(): readonly import("@fulate/share").PointView[];
39
+ getWorldSnapPointsView(): readonly PointView[];
25
40
  paint(ctx?: CanvasRenderingContext2D): void;
26
41
  /** 绘制宿主本体;`paint()` 随后统一绘制 custom Port decoration。 */
27
42
  protected paintHost(ctx: CanvasRenderingContext2D): void;
@@ -1,7 +1,7 @@
1
1
  import { Element, type ElementOptionPatch, type ElementTransformBaseline, type OwnerPropertyChange, type ReparentTransformIntent, type TransformIntent } from "@fulate/core";
2
2
  import { type Rect } from "@fulate/share";
3
3
  /** @internal frame-authored provider 共用的纯 resolver。 */
4
- export declare function resolveFrameTransformChange(owner: Element, baseline: Readonly<ElementTransformBaseline>, intent: Readonly<TransformIntent | ReparentTransformIntent>): OwnerPropertyChange;
4
+ export declare function resolveFrameTransformChange(owner: Element, baseline: Readonly<ElementTransformBaseline>, intent: Readonly<TransformIntent | ReparentTransformIntent>): readonly OwnerPropertyChange[];
5
5
  /** @internal 捕获一次 owner reference;structured ownedState 直接保留原引用。 */
6
6
  export declare function captureElementTransformBaseline<Owned>(owner: Element, writeMode: "frame-authored" | "authored-geometry" | "pose-authored", ownedState: Owned): ElementTransformBaseline<Owned>;
7
7
  /** @internal Geometry reference 变化时保持当前 world pose 的最小 frame patch。 */
@@ -1,9 +1,7 @@
1
- import { Element, type Root } from "@fulate/core";
2
- import { type ComputePositionConfig, type Platform } from "@floating-ui/core";
3
- export { arrow, autoPlacement, detectOverflow, flip, hide, limitShift, offset, shift, size } from "@floating-ui/core";
4
- export type { ComputePositionReturn, Middleware, MiddlewareData, Placement, Strategy } from "@floating-ui/core";
1
+ import { Element } from "@fulate/core";
2
+ import { type ComputePositionConfig } from "@floating-ui/core";
3
+ export { flip, offset, shift } from "@floating-ui/core";
4
+ export type { Placement } from "@floating-ui/core";
5
5
  export type ComputePositionOptions = Omit<ComputePositionConfig, "platform" | "strategy">;
6
- /** Creates a Floating UI platform backed by one Fulate Root's world space. */
7
- export declare function createFloatingPlatform(root: Root): Platform;
8
6
  /** Computes world-space coordinates without mutating either element. */
9
7
  export declare function computePosition(reference: Element, floating: Element, options?: ComputePositionOptions): Promise<import("@floating-ui/core").ComputePositionReturn>;
package/dist/group.d.ts CHANGED
@@ -1,19 +1,36 @@
1
- import { Element, ElementTransform, type BaseElementOption, type ElementTransformBaseline, type ReparentTransformIntent, type TransformIntent } from "@fulate/core";
2
- import { Bound, type PointView } from "@fulate/share";
3
- import { SelectionBehaviorMode, SelectionDiveMode, SelectionSnapMode, type EditorCapabilityToken } from "./editor/capability";
1
+ import { ChangeImpact, Element, ElementTransform, type BaseElementOption, type ElementTransformBaseline, type ReparentTransformIntent, type TransformIntent } from "@fulate/core";
2
+ import { Bound, type BoundingBox, type PointView } from "@fulate/share";
3
+ import { SelectionBehaviorMode, SelectionDiveMode, SelectionSnapMode, type EditorCapabilityToken, type TransformInteractionCapability } from "./editor/capability";
4
+ interface GroupTransformChildBaseline {
5
+ readonly capability: TransformInteractionCapability;
6
+ readonly baseline: ReturnType<TransformInteractionCapability["captureTransformBaseline"]>;
7
+ }
8
+ interface GroupTransformOwnedState {
9
+ readonly children: readonly GroupTransformChildBaseline[];
10
+ }
4
11
  export interface GroupOption extends BaseElementOption<Group> {
5
12
  }
6
13
  export declare class GroupTransform<E extends Group = Group> extends ElementTransform<E> {
7
14
  private localContentBounds;
15
+ private localSelectionFrameBounds;
8
16
  protected onChildrenChanged(): void;
9
17
  protected onDescendantDirty(_child: ElementTransform, affectsContentBounds: boolean): void;
10
18
  private invalidateContentBounds;
19
+ invalidate(impact: ChangeImpact, previousBounds?: Readonly<BoundingBox> | null): void;
11
20
  /** @internal Group-local canonical content subtree bounds. */
12
21
  getLocalContentBoundsView(): Readonly<Bound> | null;
22
+ /** @internal Group-local selection frame derived from child owner frames. */
23
+ getLocalSelectionFrameBoundsView(): Readonly<Bound> | null;
13
24
  /** Group 是 content bounds owner;ancestor 只投影这份 local view。 */
14
25
  protected collectContentBoundsInReference(parentMatrix: DOMMatrixReadOnly, _referenceWorldInverse: DOMMatrixReadOnly): Bound;
15
26
  /** 只合并真实 content members;不贡献 content bounds 的 branch 返回 null。 */
16
27
  protected buildLocalContentBounds(): Bound | null;
28
+ /**
29
+ * Build the frame used by selection/transform/snap. Text-like owners expose
30
+ * authored frame bounds that intentionally exclude paint overflow; owners
31
+ * without that capability retain an explicit content/visual fallback.
32
+ */
33
+ protected buildLocalSelectionFrameBounds(): Bound | null;
17
34
  protected buildLocalHitBounds(): Readonly<Bound>;
18
35
  protected containsLocalHitPoint(point: PointView): boolean;
19
36
  protected buildVisualBounds(): Readonly<Bound>;
@@ -25,6 +42,8 @@ export declare class GroupTransform<E extends Group = Group> extends ElementTran
25
42
  }
26
43
  export declare class Group extends Element {
27
44
  readonly type = "group";
45
+ private readonly worldSnapPointsCache;
46
+ readonly transform: GroupTransform<this>;
28
47
  constructor(options?: GroupOption);
29
48
  getEditorCapability<T>(token: EditorCapabilityToken<T>): T | undefined;
30
49
  getSelectionBehavior(): {
@@ -38,7 +57,11 @@ export declare class Group extends Element {
38
57
  readonly rotate: true;
39
58
  readonly rotationPivot: "adjustable";
40
59
  };
41
- captureTransformBaseline(): ElementTransformBaseline<any>;
42
- resolveTransformChange(baseline: Readonly<ElementTransformBaseline>, intent: Readonly<TransformIntent | ReparentTransformIntent>): import("@fulate/core").OwnerPropertyChange;
60
+ captureTransformBaseline(): ElementTransformBaseline<{
61
+ children: GroupTransformChildBaseline[];
62
+ }>;
63
+ resolveTransformChange(baseline: Readonly<ElementTransformBaseline<GroupTransformOwnedState>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): readonly import("@fulate/core").OwnerPropertyChange[];
43
64
  getWorldSnapPointsView(): readonly PointView[];
65
+ getSelectionFrameBounds(): Readonly<Bound>;
44
66
  }
67
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { measureTextWidth, fitTextWithEllipsis, subscribeTextMeasurementChanges } from "./text/layout";
1
+ export { measureTextWidth, subscribeTextMeasurementChanges } from "./text/layout";
2
2
  export type { TextLayoutLine, TextLayoutMetrics, TextLayoutOptions, TextLayoutSnapshot } from "./text/layout";
3
3
  export { measureTextRun } from "./text/paint";
4
4
  export { paintTextBlock } from "./text/paint";
@@ -22,11 +22,11 @@ export { WorkerSurfacePool } from "./worker-surface";
22
22
  export type { WorkerSurfaceHandle, WorkerSurfacePoolOptions } from "./worker-surface";
23
23
  export { fitWorkspace, workspaceFocusInsetsKey, Workspace, WorkspaceProperties } from "./workspace";
24
24
  export type { WorkspaceFocusInsetsSource, WorkspaceFitOptions, WorkspaceJSON, WorkspaceOption } from "./workspace";
25
- export { arrow, autoPlacement, computePosition, createFloatingPlatform, detectOverflow, flip, hide, limitShift, offset, shift, size } from "./floating";
26
- export type { ComputePositionOptions, ComputePositionReturn, Middleware, MiddlewareData, Placement, Strategy } from "./floating";
25
+ export { computePosition, flip, offset, shift } from "./floating";
26
+ export type { ComputePositionOptions, Placement } from "./floating";
27
27
  export { Group, GroupTransform } from "./group";
28
28
  export type { GroupOption } from "./group";
29
- export { ContentEditingCapabilityToken, PointEditingCapabilityToken, SelectionBehaviorMode, SelectionBehaviorCapabilityToken, SelectionDiveMode, SelectionSnapMode, SnapSourceCapabilityToken, TransformInteractionCapabilityToken, defineEditorCapability, getEditorCapability, isEditorCapabilityHost } from "./editor/capability";
29
+ export { ContentEditingCapabilityToken, PointEditingCapabilityToken, SelectionBehaviorMode, SelectionBehaviorCapabilityToken, SelectionDiveMode, SelectionSnapMode, SnapSourceCapabilityToken, TransformInteractionCapabilityToken, defineEditorCapability, getEditorCapability, isEditorCapabilityHost, resolveSelectionFrameBounds } from "./editor/capability";
30
30
  export type { ContentEditingCapability, ContentEditingConsumer, ContentEditingSession, EditorCapabilityHost, EditorCapabilityToken, PointControlDescriptor, PointEditRequest, PointEditTarget, PointEditingCapability, PointEditingScope, PointOwnerEdit, SelectionBehavior, SelectionBehaviorCapability, SnapSourceCapability, TransformInteractionCapability, TransformInteractionPolicy } from "./editor/capability";
31
31
  export { EditorFrameFlush, getEditorFrameFlush } from "./editor/frame-flush";
32
32
  export type { EditorAuthoredGesture, EditorOverlayClient, EditorSpatialClient } from "./editor/frame-flush";
@@ -37,7 +37,7 @@ export type { RippleOption } from "./ripple";
37
37
  export { BaseLine, LineTransform, Line } from "./line/index";
38
38
  export { connectionPortAccepts, connectEndpoint, disconnectEndpoint, getEndpointRelationIndex, hasConnectionPortCapacity, isEndpointRelationSource, resolveConnectionPortChanges, resolveConnectEndpointChange, resolveDisconnectEndpointChanges, EndpointRelationIndex } from "./connection";
39
39
  export type { BindableEndpoint, EndpointConnectionRole, EndpointRef, EndpointRelation, EndpointRelationBinding, EndpointRelationSource } from "./connection";
40
- export { LineTree, LineTreeProperties, LineTreeTransform, EMPTY_LINE_TREE_TOPOLOGY, DEFAULT_LINE_TREE_BRANCH_STYLE, addLineTreeBranchAtTail, createLineTreeTopology, getLineTreeBranch, getLineTreeBindableEndpoints, getLineTreeEndpointRelationBindings, getLineTreeJunctionAnchorIds, getLineTreePoint, getLineTreePointReferenceCounts, insertLineTreePoint, moveLineTreePoint, removeLineTreeBranch, removeLineTreePoint, remapLineTreeRelations, scaleLineTreeTopology, updateLineTreeBranchStyle } from "./line-tree";
40
+ export { LineTree, LineTreeProperties, LineTreeTransform, EMPTY_LINE_TREE_TOPOLOGY, DEFAULT_LINE_TREE_BRANCH_STYLE, addLineTreeBranchAtTail, createLineTreeTopology, getLineTreeBranch, getLineTreeBindableEndpoints, getLineTreeEndpointRelationBindings, getLineTreeJunctionAnchorIds, getLineTreePoint, getLineTreePointReferenceCounts, insertLineTreePoint, moveLineTreePoint, removeLineTreeBranch, removeLineTreePoint, remapLineTreeRelations, transformLineTreeTopology, updateLineTreeBranchStyle } from "./line-tree";
41
41
  export type { LineTreeBranch, LineTreeBranchRemoval, LineTreeBranchTailSource, LineTreeBranchStyle, LineTreeBranchVertex, LineTreeOption, LineTreePoint, LineTreeTopology } from "./line-tree";
42
42
  export { applyLineTreeTopologyDelta, createLineTreeTopologyDelta } from "./line-tree/history-delta";
43
43
  export type { LineTreeTopologyDelta } from "./line-tree/history-delta";
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { clearCache, layoutNextLineRange, layoutWithLines, materializeLineRange, measureLineStats, measureNaturalWidth, prepareWithSegments } from "@chenglou/pretext";
2
- import { ChangeImpact, CustomEvent, Element, ElementProperties, ElementTransform, SKIN_DEFAULTS, Shape, ShapeProperties, ShapeTransform, createAuthoredLineSubpath, createGeometrySnapshot, createLineSubpath, createPathLocalId, evaluatePathSegmentPoint, getElementConnectionPort, getElementConnectionPortPoint, isReparentTransformIntent, resolveAuthoredSubpath, resolveReferenceTransform, resolveReferenceTransformTarget, splitPathSegment } from "@fulate/core";
2
+ import { ChangeImpact, CustomEvent, Element, ElementProperties, ElementTransform, SKIN_DEFAULTS, Shape, ShapeProperties, ShapeTransform, createAuthoredLineSubpath, createGeometrySnapshot, createLineSubpath, createPathLocalId, evaluatePathSegmentPoint, getElementConnectionPort, getElementConnectionPortPoint, isReparentTransformIntent, resolveAuthoredGeometryFramePatch, resolveAuthoredSubpath, resolveReferenceTransform, resolveReferenceTransformTarget, splitPathSegment } from "@fulate/core";
3
3
  import { clamp, isFunction, isNil, isNull, isString, isUndefined } from "lodash-es";
4
- import { Bound, Intersection, createCanvasGradient, createPointView, isGradient, isPointWithinDistance, rectEdgePosition, transformPoint } from "@fulate/share";
5
- import { arrow, autoPlacement, computePosition as computePosition$1, detectOverflow, flip, hide, limitShift, offset, shift, size } from "@floating-ui/core";
4
+ import { Bound, Intersection, createCanvasGradient, createPointView, isGradient, isIdentityMatrix, isPointWithinDistance, rectEdgePosition, transformPoint } from "@fulate/share";
5
+ import { computePosition as computePosition$1, flip, offset, shift } from "@floating-ui/core";
6
6
  import { Easing, Tween } from "@tweenjs/tween.js";
7
7
  //#region packages/ui/src/text/layout.ts
8
8
  var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
@@ -136,10 +136,6 @@ function ellipsizeText(text, maxWidth, font, letterSpacing, force) {
136
136
  width: measure(value)
137
137
  };
138
138
  }
139
- /** Fit a single run and append an ellipsis only when it does not fit. */
140
- function fitTextWithEllipsis(text, maxWidth, font, letterSpacing = 0) {
141
- return ellipsizeText(text, maxWidth, font, letterSpacing, false).text;
142
- }
143
139
  var PreparedTextLayout = class {
144
140
  normalizedText;
145
141
  naturalWidth;
@@ -530,7 +526,7 @@ function updateTextareaPosition(self) {
530
526
  if (!root) return;
531
527
  const { scale, x: vx, y: vy } = root.viewport;
532
528
  self.transform.bubbleUpdateTransform();
533
- const m = self.transform.getWorldMatrix();
529
+ const m = self.transform.getWorldMatrixView();
534
530
  const style = self.getResolvedTextStyle();
535
531
  const { x: cx, y: cy, h } = getCaretLocalXY(self, ta.selectionStart ?? self.text.length);
536
532
  const screenX = (m.a * cx + m.c * cy + m.e) * scale + vx;
@@ -1124,8 +1120,9 @@ var ConnectionPortDecoration = class {
1124
1120
  traceCirclePath(ctx, position, DOT_RADIUS);
1125
1121
  ctx.fill();
1126
1122
  }
1123
+ const worldMatrix = entries.some(({ block }) => block) ? this.host.transform.getWorldMatrixView() : void 0;
1127
1124
  for (const entry of entries) {
1128
- const placement = this.resolveLabelPlacement(entry);
1125
+ const placement = this.resolveLabelPlacement(entry, worldMatrix);
1129
1126
  if (!placement) continue;
1130
1127
  const { block } = entry;
1131
1128
  if (!block) continue;
@@ -1156,7 +1153,7 @@ var ConnectionPortDecoration = class {
1156
1153
  height: DOT_RADIUS * 2
1157
1154
  });
1158
1155
  bounds = bounds ? bounds.merge(dotBounds) : dotBounds;
1159
- const placement = this.resolveLabelPlacement(entry);
1156
+ const placement = this.resolveLabelPlacement(entry, worldMatrix);
1160
1157
  if (!placement?.entry.block) continue;
1161
1158
  const textBounds = getTextBlockVisualBounds(placement.entry.block.layout, placement.style, placement.width, placement.height);
1162
1159
  if (!textBounds) continue;
@@ -1182,7 +1179,7 @@ var ConnectionPortDecoration = class {
1182
1179
  }
1183
1180
  return this.entries;
1184
1181
  }
1185
- resolveLabelPlacement(entry) {
1182
+ resolveLabelPlacement(entry, worldMatrix) {
1186
1183
  const { slot, block } = entry;
1187
1184
  if (!block) return null;
1188
1185
  const definition = slot.definition;
@@ -1194,7 +1191,7 @@ var ConnectionPortDecoration = class {
1194
1191
  if (width === 0) return null;
1195
1192
  const localAlign = slot.edge === "left" ? "right" : slot.edge === "right" ? "left" : "center";
1196
1193
  const upright = definition.keepLabelUpright !== false;
1197
- const worldNormal = upright ? transformVector(this.host.transform.getWorldMatrixView(), nx, ny) : null;
1194
+ const worldNormal = upright ? transformVector(worldMatrix, nx, ny) : null;
1198
1195
  const align = worldNormal ? Math.abs(worldNormal.x) >= Math.abs(worldNormal.y) ? worldNormal.x >= 0 ? "left" : "right" : "center" : localAlign;
1199
1196
  const style = this.getStyle(entry, align);
1200
1197
  const lineHeight = style.fontSize * style.lineHeight;
@@ -1235,7 +1232,7 @@ var ConnectionPortDecoration = class {
1235
1232
  width,
1236
1233
  height
1237
1234
  };
1238
- const uprightMatrix = upright ? this.buildUprightMatrix(position, nx, ny, width, height) : null;
1235
+ const uprightMatrix = upright ? this.buildUprightMatrix(position, nx, ny, width, height, worldMatrix) : null;
1239
1236
  if (upright && !uprightMatrix) return null;
1240
1237
  return {
1241
1238
  entry,
@@ -1264,8 +1261,7 @@ var ConnectionPortDecoration = class {
1264
1261
  entry.styles[align] = style;
1265
1262
  return style;
1266
1263
  }
1267
- buildUprightMatrix(position, nx, ny, width, height) {
1268
- const matrix = this.host.transform.getWorldMatrixView();
1264
+ buildUprightMatrix(position, nx, ny, width, height, matrix) {
1269
1265
  const worldPosition = transformPoint(matrix, position);
1270
1266
  const normal = transformVector(matrix, nx, ny);
1271
1267
  const normalLength = Math.hypot(normal.x, normal.y);
@@ -1576,9 +1572,13 @@ function resolveConnectionPortChanges(root, changes) {
1576
1572
  if (Object.is(owner.ports, next)) continue;
1577
1573
  const nextIds = new Set(next === false ? [] : next === true ? owner.resolveConnectionPorts(true).map(({ ref }) => ref.portId) : next.map(({ id }) => id));
1578
1574
  index ??= getEndpointRelationIndex(root);
1579
- for (const { ref } of owner.resolveConnectionPorts(owner.ports)) {
1580
- if (nextIds.has(ref.portId)) continue;
1581
- const targeting = index.relationsTargetingPort(ref);
1575
+ const previousPortIds = owner.ports === false ? [] : owner.ports === true ? owner.resolveConnectionPorts(true).map(({ ref }) => ref.portId) : owner.ports.map(({ id }) => id);
1576
+ for (const portId of previousPortIds) {
1577
+ if (nextIds.has(portId)) continue;
1578
+ const targeting = index.relationsTargetingPort({
1579
+ elementKey: owner.key,
1580
+ portId
1581
+ });
1582
1582
  if (targeting.length > 0) (sources ??= []).push(...targeting);
1583
1583
  }
1584
1584
  }
@@ -1604,6 +1604,13 @@ function commitConnectionPortPatch(owner, patch) {
1604
1604
  function defineEditorCapability() {
1605
1605
  return {};
1606
1606
  }
1607
+ /**
1608
+ * @internal Match one token and check that its provider implements the capability.
1609
+ * TypeScript cannot narrow the requested generic from the opaque token identity.
1610
+ */
1611
+ function provideEditorCapability(token, supported, provider) {
1612
+ return token === supported ? provider : void 0;
1613
+ }
1607
1614
  /** 判断元素是否直接提供 capability 查询入口。 */
1608
1615
  function isEditorCapabilityHost(element) {
1609
1616
  return isFunction(element.getEditorCapability);
@@ -1631,6 +1638,14 @@ var SelectionBehaviorCapabilityToken = defineEditorCapability();
1631
1638
  var TransformInteractionCapabilityToken = defineEditorCapability();
1632
1639
  var SnapSourceCapabilityToken = defineEditorCapability();
1633
1640
  var PointEditingCapabilityToken = defineEditorCapability();
1641
+ /**
1642
+ * Resolve the local rectangle used as an editor selection frame. An owner
1643
+ * supplied frame is authoritative; owners without one retain a deterministic
1644
+ * local visual/layout fallback.
1645
+ */
1646
+ function resolveSelectionFrameBounds(element, capability) {
1647
+ return capability?.getSelectionFrameBounds?.() ?? element.transform.getLocalVisualBoundsView() ?? element.transform.getLayoutReferenceBoundsView();
1648
+ }
1634
1649
  var ContentEditingCapabilityToken = defineEditorCapability();
1635
1650
  //#endregion
1636
1651
  //#region packages/ui/src/editor/transform.ts
@@ -1664,10 +1679,10 @@ function readElementTransformBaseline(owner, writeMode, ownedState) {
1664
1679
  }
1665
1680
  /** @internal frame-authored provider 共用的纯 resolver。 */
1666
1681
  function resolveFrameTransformChange(owner, baseline, intent) {
1667
- return {
1682
+ return [{
1668
1683
  owner,
1669
1684
  patch: resolveReferenceTransform(baseline.reference, intent).framePatch
1670
- };
1685
+ }];
1671
1686
  }
1672
1687
  /** @internal 捕获一次 owner reference;structured ownedState 直接保留原引用。 */
1673
1688
  function captureElementTransformBaseline(owner, writeMode, ownedState) {
@@ -1703,11 +1718,58 @@ var FRAME_TRANSFORM_POLICY = {
1703
1718
  rotate: true,
1704
1719
  rotationPivot: "adjustable"
1705
1720
  };
1721
+ function getRectCorners(rect) {
1722
+ const right = rect.left + rect.width;
1723
+ const bottom = rect.top + rect.height;
1724
+ return [
1725
+ {
1726
+ x: rect.left,
1727
+ y: rect.top
1728
+ },
1729
+ {
1730
+ x: right,
1731
+ y: rect.top
1732
+ },
1733
+ {
1734
+ x: right,
1735
+ y: bottom
1736
+ },
1737
+ {
1738
+ x: rect.left,
1739
+ y: bottom
1740
+ }
1741
+ ];
1742
+ }
1743
+ function createWorldRectPointsViewCache() {
1744
+ return {
1745
+ source: null,
1746
+ a: NaN,
1747
+ b: NaN,
1748
+ c: NaN,
1749
+ d: NaN,
1750
+ e: NaN,
1751
+ f: NaN,
1752
+ points: null
1753
+ };
1754
+ }
1755
+ /** Project one stable local frame to a borrowed world-point view. */
1756
+ function getWorldRectPointsView(cache, frame, matrix) {
1757
+ if (cache.source === frame && cache.points && cache.a === matrix.a && cache.b === matrix.b && cache.c === matrix.c && cache.d === matrix.d && cache.e === matrix.e && cache.f === matrix.f) return cache.points;
1758
+ cache.source = frame;
1759
+ cache.a = matrix.a;
1760
+ cache.b = matrix.b;
1761
+ cache.c = matrix.c;
1762
+ cache.d = matrix.d;
1763
+ cache.e = matrix.e;
1764
+ cache.f = matrix.f;
1765
+ return cache.points = getRectCorners(frame).map((point) => transformPoint(matrix, point));
1766
+ }
1706
1767
  /** UI frame-authored Shape 系列共享的 direct Editor provider。 */
1707
- var EditorShape = class extends Shape {
1768
+ var EditorShape = class EditorShape extends Shape {
1708
1769
  connectionPortDecoration = null;
1770
+ worldSnapPointsCache = createWorldRectPointsViewCache();
1709
1771
  getEditorCapability(token) {
1710
- return token === SelectionBehaviorCapabilityToken || token === TransformInteractionCapabilityToken || token === SnapSourceCapabilityToken ? this : void 0;
1772
+ return provideEditorCapability(token, SelectionBehaviorCapabilityToken, this) ?? provideEditorCapability(token, TransformInteractionCapabilityToken, this) ?? provideEditorCapability(token, SnapSourceCapabilityToken, this);
1711
1773
  }
1712
1774
  getSelectionBehavior() {
1713
1775
  return STANDARD_SELECTION;
@@ -1721,12 +1783,16 @@ var EditorShape = class extends Shape {
1721
1783
  resolveTransformChange(baseline, intent) {
1722
1784
  return resolveFrameTransformChange(this, baseline, intent);
1723
1785
  }
1786
+ getSelectionFrameBounds() {
1787
+ return null;
1788
+ }
1724
1789
  setOptions(options) {
1725
1790
  if (!options) return this;
1726
1791
  if (commitConnectionPortPatch(this, options)) return this;
1727
1792
  return super.setOptions(options);
1728
1793
  }
1729
1794
  replacePorts(value) {
1795
+ if (this.ports === value && EditorShape.getConfiguredPropertyValue(this, "ports") === value) return this;
1730
1796
  return this.setOptions({ ports: value });
1731
1797
  }
1732
1798
  restorePropertySnapshot(snapshot) {
@@ -1734,6 +1800,8 @@ var EditorShape = class extends Shape {
1734
1800
  return super.restorePropertySnapshot(snapshot);
1735
1801
  }
1736
1802
  getWorldSnapPointsView() {
1803
+ const frame = this.getSelectionFrameBounds();
1804
+ if (frame) return getWorldRectPointsView(this.worldSnapPointsCache, frame, this.transform.getWorldMatrixView());
1737
1805
  return this.transform.getWorldVisualPointsView();
1738
1806
  }
1739
1807
  paint(ctx = this.layer.ctx) {
@@ -2254,24 +2322,18 @@ function removeAuthoredAnchor(subpath, anchorId, minimumAnchorCount = 2) {
2254
2322
  anchors
2255
2323
  };
2256
2324
  }
2257
- function scalePoint(point, scaleX, scaleY) {
2258
- return {
2259
- x: point.x * scaleX,
2260
- y: point.y * scaleY
2261
- };
2262
- }
2263
- /** Scale coordinates while retaining every subpath, anchor, segment, and record ID. */
2264
- function scaleAuthoredSubpath(subpath, scaleX, scaleY) {
2265
- if (scaleX === 1 && scaleY === 1) return subpath;
2325
+ /** Transform coordinates while retaining every subpath, anchor, segment, and record ID. */
2326
+ function transformAuthoredSubpath(subpath, matrix) {
2327
+ if (isIdentityMatrix(matrix)) return subpath;
2266
2328
  return {
2267
2329
  ...subpath,
2268
2330
  anchors: subpath.anchors.map((anchor) => {
2269
- const point = scalePoint(anchor.point, scaleX, scaleY);
2331
+ const point = transformPoint(matrix, anchor.point);
2270
2332
  return "control1" in anchor ? {
2271
2333
  ...anchor,
2272
2334
  point,
2273
- control1: scalePoint(anchor.control1, scaleX, scaleY),
2274
- control2: scalePoint(anchor.control2, scaleX, scaleY)
2335
+ control1: transformPoint(matrix, anchor.control1),
2336
+ control2: transformPoint(matrix, anchor.control2)
2275
2337
  } : {
2276
2338
  ...anchor,
2277
2339
  point
@@ -2410,7 +2472,7 @@ var Polygon = class extends EditorShape {
2410
2472
  };
2411
2473
  }
2412
2474
  getEditorCapability(token) {
2413
- return token === PointEditingCapabilityToken ? this : super.getEditorCapability(token);
2475
+ return provideEditorCapability(token, PointEditingCapabilityToken, this) ?? super.getEditorCapability(token);
2414
2476
  }
2415
2477
  resolvePointScope(worldPoint) {
2416
2478
  return this.transform.hasPointHint(worldPoint) ? { kind: "owner" } : null;
@@ -2476,19 +2538,25 @@ var Polygon = class extends EditorShape {
2476
2538
  }
2477
2539
  resolveTransformChange(baseline, intent) {
2478
2540
  const transform = resolveReferenceTransform(baseline.reference, intent);
2479
- const contour = scaleAuthoredSubpath(baseline.ownedState, transform.authoredLocalDelta.a, transform.authoredLocalDelta.d);
2541
+ const contour = transformAuthoredSubpath(baseline.ownedState, transform.authoredLocalDelta);
2480
2542
  const bounds = contour === baseline.ownedState || contour.anchors.length < 2 ? null : getAuthoredSubpathBounds(contour);
2481
- return {
2543
+ const framePatch = bounds && intent.bakeGeometry && !isReparentTransformIntent(intent) ? resolveAuthoredGeometryFramePatch(baseline.reference, transform, bounds) : transform.framePatch;
2544
+ const restoredFrameSize = intent.bakeGeometry && !isReparentTransformIntent(intent) && contour === baseline.ownedState ? {
2545
+ width: baseline.reference.frame.width,
2546
+ height: baseline.reference.frame.height
2547
+ } : {};
2548
+ return [{
2482
2549
  owner: this,
2483
2550
  patch: {
2484
- ...transform.framePatch,
2551
+ ...framePatch,
2552
+ ...restoredFrameSize,
2485
2553
  contour,
2486
2554
  ...bounds ? {
2487
2555
  width: bounds.width || 1,
2488
2556
  height: bounds.height || 1
2489
2557
  } : {}
2490
2558
  }
2491
- };
2559
+ }];
2492
2560
  }
2493
2561
  buildPath(ctx) {
2494
2562
  if (this.contour.anchors.length < 3) {
@@ -2713,7 +2781,7 @@ var VectorPath = class extends EditorShape {
2713
2781
  };
2714
2782
  }
2715
2783
  getEditorCapability(token) {
2716
- return token === PointEditingCapabilityToken ? this : super.getEditorCapability(token);
2784
+ return provideEditorCapability(token, PointEditingCapabilityToken, this) ?? super.getEditorCapability(token);
2717
2785
  }
2718
2786
  resolvePointScope(worldPoint) {
2719
2787
  return this.transform.hasPointHint(worldPoint) ? { kind: "owner" } : null;
@@ -2814,21 +2882,25 @@ var VectorPath = class extends EditorShape {
2814
2882
  }
2815
2883
  resolveTransformChange(baseline, intent) {
2816
2884
  const transform = resolveReferenceTransform(baseline.reference, intent);
2817
- const scaleX = transform.authoredLocalDelta.a;
2818
- const scaleY = transform.authoredLocalDelta.d;
2819
- const path = scaleX === 1 && scaleY === 1 ? baseline.ownedState : { subpaths: baseline.ownedState.subpaths.map((subpath) => scaleAuthoredSubpath(subpath, scaleX, scaleY)) };
2885
+ const path = isIdentityMatrix(transform.authoredLocalDelta) ? baseline.ownedState : { subpaths: baseline.ownedState.subpaths.map((subpath) => transformAuthoredSubpath(subpath, transform.authoredLocalDelta)) };
2820
2886
  const bounds = path === baseline.ownedState ? null : createVectorPathSnapshot("", 0, path).layoutReferenceBounds;
2821
- return {
2887
+ const framePatch = bounds && intent.bakeGeometry && !isReparentTransformIntent(intent) ? resolveAuthoredGeometryFramePatch(baseline.reference, transform, bounds) : transform.framePatch;
2888
+ const restoredFrameSize = intent.bakeGeometry && !isReparentTransformIntent(intent) && path === baseline.ownedState ? {
2889
+ width: baseline.reference.frame.width,
2890
+ height: baseline.reference.frame.height
2891
+ } : {};
2892
+ return [{
2822
2893
  owner: this,
2823
2894
  patch: {
2824
- ...transform.framePatch,
2895
+ ...framePatch,
2896
+ ...restoredFrameSize,
2825
2897
  path,
2826
2898
  ...bounds ? {
2827
2899
  width: bounds.width || 1,
2828
2900
  height: bounds.height || 1
2829
2901
  } : {}
2830
2902
  }
2831
- };
2903
+ }];
2832
2904
  }
2833
2905
  buildPath(ctx) {
2834
2906
  this.buildResolvedPath(ctx);
@@ -3031,7 +3103,7 @@ var Text = class extends Rectangle {
3031
3103
  super(options, properties, Transform);
3032
3104
  }
3033
3105
  getEditorCapability(token) {
3034
- return token === ContentEditingCapabilityToken ? this : super.getEditorCapability(token);
3106
+ return provideEditorCapability(token, ContentEditingCapabilityToken, this) ?? super.getEditorCapability(token);
3035
3107
  }
3036
3108
  beginContentEditing(consumer) {
3037
3109
  if (!this.editable || this.isEditing) return;
@@ -3440,7 +3512,10 @@ var Image = class extends Bitmap {
3440
3512
  const pixelRatio = this.renderPixelRatio;
3441
3513
  const needW = Math.ceil((this.width || this.naturalWidth) * pixelRatio);
3442
3514
  const needH = Math.ceil((this.height || this.naturalHeight) * pixelRatio);
3443
- if (needW > this.renderSize.w * UPGRADE_RATIO || needH > this.renderSize.h * UPGRADE_RATIO) this.loadAndProcess();
3515
+ if (needW > this.renderSize.w * UPGRADE_RATIO || needH > this.renderSize.h * UPGRADE_RATIO) {
3516
+ const nextSize = this.calcAutoSize(this.naturalWidth, this.naturalHeight);
3517
+ if (nextSize.w > this.renderSize.w || nextSize.h > this.renderSize.h) this.loadAndProcess();
3518
+ }
3444
3519
  }
3445
3520
  loadHTMLImage(src) {
3446
3521
  return new Promise((resolve, reject) => {
@@ -3680,7 +3755,7 @@ function isRect(value) {
3680
3755
  return typeof rect.x === "number" && typeof rect.y === "number" && typeof rect.width === "number" && typeof rect.height === "number";
3681
3756
  }
3682
3757
  function getDimensions(element) {
3683
- const bounds = element.transform.getWorldVisualBounds();
3758
+ const bounds = element.transform.getWorldVisualBoundsView();
3684
3759
  return {
3685
3760
  width: element.width ?? bounds.width,
3686
3761
  height: element.height ?? bounds.height
@@ -3688,15 +3763,15 @@ function getDimensions(element) {
3688
3763
  }
3689
3764
  function getClippingRect(root, boundary, rootBoundary) {
3690
3765
  if (isRect(boundary)) return boundary;
3691
- if (boundary instanceof Element) return toRect(boundary.transform.getWorldVisualBounds());
3766
+ if (boundary instanceof Element) return toRect(boundary.transform.getWorldVisualBoundsView());
3692
3767
  if (isRect(rootBoundary)) return rootBoundary;
3693
- return toRect(root.viewport.getWorldRect());
3768
+ return toRect(root.viewport.getWorldRectView());
3694
3769
  }
3695
3770
  /** Creates a Floating UI platform backed by one Fulate Root's world space. */
3696
3771
  function createFloatingPlatform(root) {
3697
3772
  return {
3698
3773
  getElementRects: ({ reference, floating }) => ({
3699
- reference: toRect(reference.transform.getWorldVisualBounds()),
3774
+ reference: toRect(reference.transform.getWorldVisualBoundsView()),
3700
3775
  floating: {
3701
3776
  x: 0,
3702
3777
  y: 0,
@@ -3722,8 +3797,31 @@ function computePosition(reference, floating, options = {}) {
3722
3797
  }
3723
3798
  //#endregion
3724
3799
  //#region packages/ui/src/group.ts
3800
+ function rectCorners(rect) {
3801
+ const right = rect.left + rect.width;
3802
+ const bottom = rect.top + rect.height;
3803
+ return [
3804
+ {
3805
+ x: rect.left,
3806
+ y: rect.top
3807
+ },
3808
+ {
3809
+ x: right,
3810
+ y: rect.top
3811
+ },
3812
+ {
3813
+ x: right,
3814
+ y: bottom
3815
+ },
3816
+ {
3817
+ x: rect.left,
3818
+ y: bottom
3819
+ }
3820
+ ];
3821
+ }
3725
3822
  var GroupTransform = class extends ElementTransform {
3726
3823
  localContentBounds;
3824
+ localSelectionFrameBounds;
3727
3825
  onChildrenChanged() {
3728
3826
  this.invalidateContentBounds();
3729
3827
  }
@@ -3731,14 +3829,25 @@ var GroupTransform = class extends ElementTransform {
3731
3829
  if (affectsContentBounds) this.invalidateContentBounds();
3732
3830
  }
3733
3831
  invalidateContentBounds() {
3734
- this.localContentBounds = void 0;
3735
3832
  this.invalidate(ChangeImpact.VisualBounds | ChangeImpact.HitBounds);
3736
3833
  }
3834
+ invalidate(impact, previousBounds) {
3835
+ if (impact & (ChangeImpact.LocalGeometry | ChangeImpact.VisualBounds | ChangeImpact.HitBounds)) {
3836
+ this.localContentBounds = void 0;
3837
+ this.localSelectionFrameBounds = void 0;
3838
+ }
3839
+ super.invalidate(impact, previousBounds);
3840
+ }
3737
3841
  /** @internal Group-local canonical content subtree bounds. */
3738
3842
  getLocalContentBoundsView() {
3739
3843
  if (this.localContentBounds !== void 0) return this.localContentBounds;
3740
3844
  return this.localContentBounds = this.buildLocalContentBounds();
3741
3845
  }
3846
+ /** @internal Group-local selection frame derived from child owner frames. */
3847
+ getLocalSelectionFrameBoundsView() {
3848
+ if (this.localSelectionFrameBounds !== void 0) return this.localSelectionFrameBounds;
3849
+ return this.localSelectionFrameBounds = this.buildLocalSelectionFrameBounds();
3850
+ }
3742
3851
  /** Group 是 content bounds owner;ancestor 只投影这份 local view。 */
3743
3852
  collectContentBoundsInReference(parentMatrix, _referenceWorldInverse) {
3744
3853
  if (!this.element.visible) return null;
@@ -3766,6 +3875,33 @@ var GroupTransform = class extends ElementTransform {
3766
3875
  bounds.maxY = Math.min(bounds.maxY, clip.top + clip.height);
3767
3876
  return bounds.minX < bounds.maxX && bounds.minY < bounds.maxY ? bounds : null;
3768
3877
  }
3878
+ /**
3879
+ * Build the frame used by selection/transform/snap. Text-like owners expose
3880
+ * authored frame bounds that intentionally exclude paint overflow; owners
3881
+ * without that capability retain an explicit content/visual fallback.
3882
+ */
3883
+ buildLocalSelectionFrameBounds() {
3884
+ const childWorldInverse = this.getChildWorldInverseMatrixView();
3885
+ let bounds = null;
3886
+ for (const child of this.element.children) {
3887
+ if (!child.visible) continue;
3888
+ const localFrame = getEditorCapability(child, TransformInteractionCapabilityToken)?.getSelectionFrameBounds?.();
3889
+ const points = localFrame ? rectCorners(localFrame).map((point) => transformPoint(childWorldInverse, transformPoint(child.transform.getWorldMatrixView(), point))) : (() => {
3890
+ const content = child.transform.getContentBoundsInParentView();
3891
+ return content ? rectCorners(content) : [];
3892
+ })();
3893
+ if (points.length === 0) continue;
3894
+ const childBounds = Bound.fromPoints(points);
3895
+ bounds = bounds ? bounds.merge(childBounds) : childBounds;
3896
+ }
3897
+ const clip = bounds && this.element.getChildClipRect();
3898
+ if (!bounds || !clip) return bounds;
3899
+ bounds.minX = Math.max(bounds.minX, clip.left);
3900
+ bounds.minY = Math.max(bounds.minY, clip.top);
3901
+ bounds.maxX = Math.min(bounds.maxX, clip.left + clip.width);
3902
+ bounds.maxY = Math.min(bounds.maxY, clip.top + clip.height);
3903
+ return bounds.minX < bounds.maxX && bounds.minY < bounds.maxY ? bounds : null;
3904
+ }
3769
3905
  buildLocalHitBounds() {
3770
3906
  return this.getLocalContentBoundsView();
3771
3907
  }
@@ -3799,16 +3935,18 @@ var GroupTransform = class extends ElementTransform {
3799
3935
  }
3800
3936
  dispose() {
3801
3937
  this.localContentBounds = void 0;
3938
+ this.localSelectionFrameBounds = void 0;
3802
3939
  super.dispose();
3803
3940
  }
3804
3941
  };
3805
3942
  var Group = class extends Element {
3806
3943
  type = "group";
3944
+ worldSnapPointsCache = createWorldRectPointsViewCache();
3807
3945
  constructor(options = {}) {
3808
3946
  super(options, new ElementProperties(options), GroupTransform);
3809
3947
  }
3810
3948
  getEditorCapability(token) {
3811
- return token === SelectionBehaviorCapabilityToken || token === TransformInteractionCapabilityToken || token === SnapSourceCapabilityToken ? this : void 0;
3949
+ return provideEditorCapability(token, SelectionBehaviorCapabilityToken, this) ?? provideEditorCapability(token, TransformInteractionCapabilityToken, this) ?? provideEditorCapability(token, SnapSourceCapabilityToken, this);
3812
3950
  }
3813
3951
  getSelectionBehavior() {
3814
3952
  return {
@@ -3826,14 +3964,29 @@ var Group = class extends Element {
3826
3964
  };
3827
3965
  }
3828
3966
  captureTransformBaseline() {
3829
- return captureElementTransformBaseline(this, "pose-authored", void 0);
3967
+ const children = [];
3968
+ for (const child of this.children) {
3969
+ const capability = getEditorCapability(child, TransformInteractionCapabilityToken);
3970
+ if (!capability) continue;
3971
+ children.push({
3972
+ capability,
3973
+ baseline: capability.captureTransformBaseline()
3974
+ });
3975
+ }
3976
+ return captureElementTransformBaseline(this, "pose-authored", { children });
3830
3977
  }
3831
3978
  resolveTransformChange(baseline, intent) {
3979
+ if (intent.bakeGeometry && !isReparentTransformIntent(intent)) return baseline.ownedState.children.flatMap(({ capability, baseline }) => capability.resolveTransformChange(baseline, intent));
3832
3980
  return resolveFrameTransformChange(this, baseline, intent);
3833
3981
  }
3834
3982
  getWorldSnapPointsView() {
3983
+ const frame = this.transform.getLocalSelectionFrameBoundsView();
3984
+ if (frame) return getWorldRectPointsView(this.worldSnapPointsCache, frame, this.transform.getWorldMatrixView());
3835
3985
  return this.transform.getWorldVisualPointsView();
3836
3986
  }
3987
+ getSelectionFrameBounds() {
3988
+ return this.transform.getLocalSelectionFrameBoundsView();
3989
+ }
3837
3990
  };
3838
3991
  //#endregion
3839
3992
  //#region packages/ui/src/editor/frame-flush.ts
@@ -4694,7 +4847,7 @@ var BaseLine = class extends Element {
4694
4847
  return bindings;
4695
4848
  }
4696
4849
  getEditorCapability(token) {
4697
- return token === SelectionBehaviorCapabilityToken || token === TransformInteractionCapabilityToken || token === SnapSourceCapabilityToken ? this : void 0;
4850
+ return provideEditorCapability(token, SelectionBehaviorCapabilityToken, this) ?? provideEditorCapability(token, TransformInteractionCapabilityToken, this) ?? provideEditorCapability(token, SnapSourceCapabilityToken, this);
4698
4851
  }
4699
4852
  getSelectionBehavior() {
4700
4853
  return {
@@ -4745,16 +4898,19 @@ var BaseLine = class extends Element {
4745
4898
  }
4746
4899
  resolveTransformChange(baseline, intent) {
4747
4900
  const effective = isReparentTransformIntent(intent) ? void 0 : baseline.ownedState.effective;
4748
- const transform = resolveReferenceTransform(effective?.reference ?? baseline.reference, intent);
4749
- const path = scaleAuthoredSubpath(effective?.path ?? baseline.ownedState.path, transform.authoredLocalDelta.a, transform.authoredLocalDelta.d);
4750
- return {
4901
+ const reference = effective?.reference ?? baseline.reference;
4902
+ const transform = resolveReferenceTransform(reference, intent);
4903
+ const path = transformAuthoredSubpath(effective?.path ?? baseline.ownedState.path, transform.authoredLocalDelta);
4904
+ const bounds = intent.bakeGeometry && !isReparentTransformIntent(intent) && path.anchors.length > 1 ? getAuthoredSubpathBounds(path) : null;
4905
+ const framePatch = bounds ? resolveAuthoredGeometryFramePatch(reference, transform, bounds) : transform.framePatch;
4906
+ return [{
4751
4907
  owner: this,
4752
4908
  patch: {
4753
- ...transform.framePatch,
4909
+ ...framePatch,
4754
4910
  path,
4755
4911
  ...effective?.relationPatch ? { relations: effective.relationPatch } : {}
4756
4912
  }
4757
- };
4913
+ }];
4758
4914
  }
4759
4915
  resolveOptions(options) {
4760
4916
  const resolved = resolveLinePatch(options);
@@ -4978,7 +5134,7 @@ var Line = class extends BaseLine {
4978
5134
  super(options);
4979
5135
  }
4980
5136
  getEditorCapability(token) {
4981
- return token === PointEditingCapabilityToken ? this : super.getEditorCapability(token);
5137
+ return provideEditorCapability(token, PointEditingCapabilityToken, this) ?? super.getEditorCapability(token);
4982
5138
  }
4983
5139
  resolvePointScope(worldPoint) {
4984
5140
  return this.transform.hasPointHint(worldPoint) ? { kind: "owner" } : null;
@@ -5174,11 +5330,6 @@ function getLineTreeBindableEndpoints(topology, elementKey) {
5174
5330
  anchorId
5175
5331
  } }));
5176
5332
  }
5177
- function getLineTreeEndpointRole(topology, anchorId) {
5178
- if (!getLineTreePoint(topology, anchorId)) return null;
5179
- if (getLineTreePointDegrees(topology).get(anchorId) !== 1) return null;
5180
- return (topology.rootSubpathId === void 0 ? void 0 : getLineTreeBranch(topology, topology.rootSubpathId))?.vertices[0]?.anchorId === anchorId ? "output" : "input";
5181
- }
5182
5333
  function moveLineTreePoint(topology, anchorId, point, relation) {
5183
5334
  const index = topology.points.findIndex((candidate) => candidate.anchorId === anchorId);
5184
5335
  const current = topology.points[index];
@@ -5328,16 +5479,13 @@ function updateLineTreeBranchStyle(topology, subpathId, style) {
5328
5479
  branches
5329
5480
  };
5330
5481
  }
5331
- function scaleLineTreeTopology(topology, scaleX, scaleY) {
5332
- if (scaleX === 1 && scaleY === 1) return topology;
5482
+ function transformLineTreeTopology(topology, matrix) {
5483
+ if (isIdentityMatrix(matrix)) return topology;
5333
5484
  return {
5334
5485
  ...topology,
5335
5486
  points: topology.points.map((point) => ({
5336
5487
  ...point,
5337
- point: {
5338
- x: point.point.x * scaleX,
5339
- y: point.point.y * scaleY
5340
- }
5488
+ point: transformPoint(matrix, point.point)
5341
5489
  }))
5342
5490
  };
5343
5491
  }
@@ -5375,12 +5523,14 @@ var LineTreeTransform = class extends ElementTransform {
5375
5523
  topologySource = null;
5376
5524
  pointById = /* @__PURE__ */ new Map();
5377
5525
  branchById = /* @__PURE__ */ new Map();
5526
+ endpointRoleById;
5378
5527
  junctionAnchorIds = [];
5379
5528
  junctionAnchorIdSet = /* @__PURE__ */ new Set();
5380
5529
  layoutBounds = null;
5381
5530
  resolvedPointById = /* @__PURE__ */ new Map();
5382
5531
  worldGeometrySource = null;
5383
5532
  worldBranchPoints = [];
5533
+ worldBranchPointsById = /* @__PURE__ */ new Map();
5384
5534
  worldPointById = /* @__PURE__ */ new Map();
5385
5535
  worldSnapPoints = [];
5386
5536
  worldMatrixA = NaN;
@@ -5395,10 +5545,22 @@ var LineTreeTransform = class extends ElementTransform {
5395
5545
  this.topologySource = topology;
5396
5546
  this.pointById = new Map(topology.points.map((point) => [point.anchorId, point]));
5397
5547
  this.branchById = new Map(topology.branches.map((branch) => [branch.subpathId, branch]));
5548
+ this.endpointRoleById = void 0;
5398
5549
  this.junctionAnchorIds = getLineTreeJunctionAnchorIds(topology);
5399
5550
  this.junctionAnchorIdSet = new Set(this.junctionAnchorIds);
5400
5551
  this.layoutBounds = getLineTreeTopologyBounds(topology);
5401
5552
  }
5553
+ buildEndpointRoles() {
5554
+ const topology = this.element.topology;
5555
+ const degrees = getLineTreePointDegrees(topology);
5556
+ const rootAnchorId = topology.rootSubpathId === void 0 ? void 0 : this.branchById.get(topology.rootSubpathId)?.vertices[0]?.anchorId;
5557
+ const roles = /* @__PURE__ */ new Map();
5558
+ for (const point of topology.points) {
5559
+ if (degrees.get(point.anchorId) !== 1) continue;
5560
+ roles.set(point.anchorId, point.anchorId === rootAnchorId ? "output" : "input");
5561
+ }
5562
+ return roles;
5563
+ }
5402
5564
  getPoint(anchorId) {
5403
5565
  this.ensureTopologyViews();
5404
5566
  return this.pointById.get(anchorId);
@@ -5407,6 +5569,11 @@ var LineTreeTransform = class extends ElementTransform {
5407
5569
  this.ensureTopologyViews();
5408
5570
  return this.branchById.get(subpathId);
5409
5571
  }
5572
+ getEndpointRole(anchorId) {
5573
+ this.ensureTopologyViews();
5574
+ this.endpointRoleById ??= this.buildEndpointRoles();
5575
+ return this.endpointRoleById.get(anchorId) ?? null;
5576
+ }
5410
5577
  getResolvedPoint(anchorId) {
5411
5578
  this.getGeometrySnapshot();
5412
5579
  return this.resolvedPointById.get(anchorId);
@@ -5524,14 +5691,17 @@ var LineTreeTransform = class extends ElementTransform {
5524
5691
  if (vertex.anchorId !== void 0) this.worldPointById.set(vertex.anchorId, world);
5525
5692
  return world;
5526
5693
  }));
5694
+ this.worldBranchPointsById = /* @__PURE__ */ new Map();
5695
+ snapshot.subpaths.forEach((subpath, index) => {
5696
+ if (subpath.subpathId !== void 0) this.worldBranchPointsById.set(subpath.subpathId, this.worldBranchPoints[index]);
5697
+ });
5527
5698
  this.worldSnapPoints = this.element.topology.points.map((point) => {
5528
5699
  return this.worldPointById.get(point.anchorId);
5529
5700
  });
5530
5701
  }
5531
5702
  getWorldBranchPointsView(subpathId) {
5532
5703
  this.updateWorldPoints();
5533
- const index = this.getGeometrySnapshot().subpaths.findIndex((subpath) => subpath.subpathId === subpathId);
5534
- return this.worldBranchPoints[index];
5704
+ return this.worldBranchPointsById.get(subpathId);
5535
5705
  }
5536
5706
  getWorldPointView(anchorId) {
5537
5707
  this.updateWorldPoints();
@@ -5636,7 +5806,7 @@ var LineTree = class extends Element {
5636
5806
  return this;
5637
5807
  }
5638
5808
  getEditorCapability(token) {
5639
- return token === SelectionBehaviorCapabilityToken || token === TransformInteractionCapabilityToken || token === SnapSourceCapabilityToken || token === PointEditingCapabilityToken ? this : void 0;
5809
+ return provideEditorCapability(token, SelectionBehaviorCapabilityToken, this) ?? provideEditorCapability(token, TransformInteractionCapabilityToken, this) ?? provideEditorCapability(token, SnapSourceCapabilityToken, this) ?? provideEditorCapability(token, PointEditingCapabilityToken, this);
5640
5810
  }
5641
5811
  getSelectionBehavior() {
5642
5812
  return {
@@ -5714,20 +5884,28 @@ var LineTree = class extends Element {
5714
5884
  }
5715
5885
  resolveTransformChange(baseline, intent) {
5716
5886
  const effective = isReparentTransformIntent(intent) ? void 0 : baseline.ownedState.effective;
5717
- const transform = resolveReferenceTransform(effective?.reference ?? baseline.reference, intent);
5718
- return {
5887
+ const bakeGeometry = intent.bakeGeometry && !isReparentTransformIntent(intent);
5888
+ const reference = effective?.reference ?? (bakeGeometry ? {
5889
+ ...baseline.reference,
5890
+ writeMode: "authored-geometry"
5891
+ } : baseline.reference);
5892
+ const transform = resolveReferenceTransform(reference, intent);
5893
+ const topology = bakeGeometry ? transformLineTreeTopology(effective?.topology ?? baseline.ownedState.topology, transform.authoredLocalDelta) : effective ? transformLineTreeTopology(effective.topology, transform.authoredLocalDelta) : void 0;
5894
+ const bounds = bakeGeometry && topology ? getLineTreeTopologyBounds(topology) : null;
5895
+ const framePatch = bounds ? resolveAuthoredGeometryFramePatch(reference, transform, bounds) : transform.framePatch;
5896
+ return [{
5719
5897
  owner: this,
5720
5898
  patch: {
5721
- ...transform.framePatch,
5722
- ...effective ? { topology: scaleLineTreeTopology(effective.topology, transform.authoredLocalDelta.a, transform.authoredLocalDelta.d) } : {}
5899
+ ...framePatch,
5900
+ ...topology ? { topology } : {}
5723
5901
  }
5724
- };
5902
+ }];
5725
5903
  }
5726
5904
  getWorldSnapPointsView() {
5727
5905
  return this.transform.getWorldSnapPointsView();
5728
5906
  }
5729
5907
  getEndpointRole(anchorId) {
5730
- return getLineTreeEndpointRole(this.topology, anchorId);
5908
+ return this.transform.getEndpointRole(anchorId);
5731
5909
  }
5732
5910
  getEndpointRelation(anchorId) {
5733
5911
  return this.transform.getPoint(anchorId)?.relation;
@@ -5748,7 +5926,7 @@ var LineTree = class extends Element {
5748
5926
  resolveDisconnectEndpointChange(anchorIds, localPositions) {
5749
5927
  let topology = this.topology;
5750
5928
  for (const anchorId of new Set(anchorIds)) {
5751
- if (!getLineTreePoint(topology, anchorId)?.relation) continue;
5929
+ if (!this.transform.getPoint(anchorId)?.relation) continue;
5752
5930
  topology = moveLineTreePoint(topology, anchorId, localPositions?.get(anchorId) ?? this.transform.getResolvedPoint(anchorId).point, void 0);
5753
5931
  }
5754
5932
  return topology === this.topology ? null : {
@@ -5833,7 +6011,7 @@ var LineTree = class extends Element {
5833
6011
  };
5834
6012
  }
5835
6013
  getBranch(subpathId) {
5836
- return getLineTreeBranch(this.topology, subpathId);
6014
+ return this.transform.getBranch(subpathId);
5837
6015
  }
5838
6016
  resolveSubpathAtWorldPoint(point, preferredSubpathId) {
5839
6017
  return this.transform.resolveSubpathAtWorldPoint(point, preferredSubpathId);
@@ -5967,4 +6145,4 @@ function applyLineTreeTopologyDelta(topology, delta, direction) {
5967
6145
  };
5968
6146
  }
5969
6147
  //#endregion
5970
- export { BaseLine, Bitmap, Circle, CircleTransform, ContentEditingCapabilityToken, DEFAULT_LINE_TREE_BRANCH_STYLE, EMPTY_LINE_TREE_TOPOLOGY, EditorFrameFlush, EndpointRelationIndex, Group, GroupTransform, Image, ImageProperties, ImageTransform, Line, LineTransform, LineTree, LineTreeProperties, LineTreeTransform, PointEditingCapabilityToken, Polygon, PolygonTransform, Rectangle, RectangleProperties, RectangleTransform, RippleOverlay, ScrollView, ScrollViewTransform, SelectionBehaviorCapabilityToken, SelectionBehaviorMode, SelectionDiveMode, SelectionSnapMode, SnapSourceCapabilityToken, Text, TextBlock, TextProperties, TextTransform, TransformInteractionCapabilityToken, Triangle, TriangleTransform, VectorPath, VectorPathProperties, VectorPathTransform, WorkerSurfacePool, Workspace, WorkspaceProperties, addLineTreeBranchAtTail, applyLineTreeTopologyDelta, arrow, autoPlacement, computePosition, connectEndpoint, connectionPortAccepts, createFloatingPlatform, createLineTreeTopology, createLineTreeTopologyDelta, defineEditorCapability, detectOverflow, disconnectEndpoint, fitTextWithEllipsis, fitWorkspace, flip, getEditorCapability, getEditorFrameFlush, getEndpointRelationIndex, getLineTreeBindableEndpoints, getLineTreeBranch, getLineTreeEndpointRelationBindings, getLineTreeJunctionAnchorIds, getLineTreePoint, getLineTreePointReferenceCounts, getTextBlockVisualBounds, hasConnectionPortCapacity, hide, insertLineTreePoint, isEditorCapabilityHost, isEndpointRelationSource, limitShift, measureTextRun, measureTextWidth, moveLineTreePoint, offset, paintTextBlock, remapLineTreeRelations, removeLineTreeBranch, removeLineTreePoint, resolveConnectEndpointChange, resolveConnectionPortChanges, resolveDisconnectEndpointChanges, resolveTextStyle, scaleLineTreeTopology, shift, size, subscribeTextMeasurementChanges, updateLineTreeBranchStyle, workspaceFocusInsetsKey };
6148
+ export { BaseLine, Bitmap, Circle, CircleTransform, ContentEditingCapabilityToken, DEFAULT_LINE_TREE_BRANCH_STYLE, EMPTY_LINE_TREE_TOPOLOGY, EditorFrameFlush, EndpointRelationIndex, Group, GroupTransform, Image, ImageProperties, ImageTransform, Line, LineTransform, LineTree, LineTreeProperties, LineTreeTransform, PointEditingCapabilityToken, Polygon, PolygonTransform, Rectangle, RectangleProperties, RectangleTransform, RippleOverlay, ScrollView, ScrollViewTransform, SelectionBehaviorCapabilityToken, SelectionBehaviorMode, SelectionDiveMode, SelectionSnapMode, SnapSourceCapabilityToken, Text, TextBlock, TextProperties, TextTransform, TransformInteractionCapabilityToken, Triangle, TriangleTransform, VectorPath, VectorPathProperties, VectorPathTransform, WorkerSurfacePool, Workspace, WorkspaceProperties, addLineTreeBranchAtTail, applyLineTreeTopologyDelta, computePosition, connectEndpoint, connectionPortAccepts, createLineTreeTopology, createLineTreeTopologyDelta, defineEditorCapability, disconnectEndpoint, fitWorkspace, flip, getEditorCapability, getEditorFrameFlush, getEndpointRelationIndex, getLineTreeBindableEndpoints, getLineTreeBranch, getLineTreeEndpointRelationBindings, getLineTreeJunctionAnchorIds, getLineTreePoint, getLineTreePointReferenceCounts, getTextBlockVisualBounds, hasConnectionPortCapacity, insertLineTreePoint, isEditorCapabilityHost, isEndpointRelationSource, measureTextRun, measureTextWidth, moveLineTreePoint, offset, paintTextBlock, remapLineTreeRelations, removeLineTreeBranch, removeLineTreePoint, resolveConnectEndpointChange, resolveConnectionPortChanges, resolveDisconnectEndpointChanges, resolveSelectionFrameBounds, resolveTextStyle, shift, subscribeTextMeasurementChanges, transformLineTreeTopology, updateLineTreeBranchStyle, workspaceFocusInsetsKey };
@@ -110,7 +110,7 @@ export declare abstract class BaseLine extends Element<LineProperties, LineOptio
110
110
  };
111
111
  protected resolvePointPathPatch(path: AuthoredSubpath): LinePatch;
112
112
  captureTransformBaseline(): ElementTransformBaseline<LineTransformOwnedState>;
113
- resolveTransformChange(baseline: Readonly<ElementTransformBaseline<LineTransformOwnedState>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): OwnerPropertyChange;
113
+ resolveTransformChange(baseline: Readonly<ElementTransformBaseline<LineTransformOwnedState>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): readonly OwnerPropertyChange[];
114
114
  protected resolveOptions(options: LinePatch): LinePatch;
115
115
  get headAnchor(): AuthoredPathAnchor;
116
116
  get tailAnchor(): AuthoredPathAnchor;
@@ -45,7 +45,7 @@ export declare class LineTree extends Element<LineTreeProperties, LineTreeOption
45
45
  private materializeEffectiveTopology;
46
46
  private preservePoseForTopology;
47
47
  captureTransformBaseline(): ElementTransformBaseline<LineTreeTransformOwnedState>;
48
- resolveTransformChange(baseline: Readonly<ElementTransformBaseline<LineTreeTransformOwnedState>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): OwnerPropertyChange;
48
+ resolveTransformChange(baseline: Readonly<ElementTransformBaseline<LineTreeTransformOwnedState>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): readonly OwnerPropertyChange[];
49
49
  getWorldSnapPointsView(): readonly PointView[];
50
50
  getEndpointRole(anchorId: AnchorId): "input" | "output";
51
51
  getEndpointRelation(anchorId: AnchorId): EndpointRelation;
@@ -70,5 +70,5 @@ export declare class LineTree extends Element<LineTreeProperties, LineTreeOption
70
70
  protected onDeactivate(root: Root | null, layer: Layer | null): void;
71
71
  }
72
72
  export { LineTreeTransform } from "./transform";
73
- export { EMPTY_LINE_TREE_TOPOLOGY, DEFAULT_LINE_TREE_BRANCH_STYLE, addLineTreeBranchAtTail, createLineTreeTopology, getLineTreeBranch, getLineTreeBindableEndpoints, getLineTreeEndpointRelationBindings, getLineTreeEndpointRole, getLineTreeJunctionAnchorIds, getLineTreePoint, getLineTreePointReferenceCounts, insertLineTreePoint, moveLineTreePoint, removeLineTreeBranch, removeLineTreePoint, remapLineTreeRelations, scaleLineTreeTopology, updateLineTreeBranchStyle } from "./topology";
73
+ export { EMPTY_LINE_TREE_TOPOLOGY, DEFAULT_LINE_TREE_BRANCH_STYLE, addLineTreeBranchAtTail, createLineTreeTopology, getLineTreeBranch, getLineTreeBindableEndpoints, getLineTreeEndpointRelationBindings, getLineTreeEndpointRole, getLineTreeJunctionAnchorIds, getLineTreePoint, getLineTreePointReferenceCounts, insertLineTreePoint, moveLineTreePoint, removeLineTreeBranch, removeLineTreePoint, remapLineTreeRelations, transformLineTreeTopology, updateLineTreeBranchStyle } from "./topology";
74
74
  export type { LineTreeBranch, LineTreeBranchRemoval, LineTreeBranchTailSource, LineTreeBranchStyle, LineTreeBranchVertex, LineTreeOption, LineTreePoint, LineTreeTopology } from "./types";
@@ -1,6 +1,5 @@
1
1
  import { type AnchorId, type SegmentId, type SubpathId } from "@fulate/core";
2
- import type { PointView } from "@fulate/share";
3
- import type { Rect } from "@fulate/share";
2
+ import { type PointView, type Rect } from "@fulate/share";
4
3
  import type { LineEndpointRelationPatch } from "../line/base";
5
4
  import type { BindableEndpoint, EndpointRelation, EndpointRelationBinding } from "../connection/types";
6
5
  import type { LineTreeBranch, LineTreeBranchRemoval, LineTreeBranchStyle, LineTreePoint, LineTreeTopology } from "./types";
@@ -12,6 +11,7 @@ export declare function getLineTreePoint(topology: LineTreeTopology, anchorId: A
12
11
  export declare function getLineTreeBranch(topology: LineTreeTopology, subpathId: SubpathId): LineTreeBranch;
13
12
  export declare function getLineTreePointReferenceCounts(topology: LineTreeTopology): Map<AnchorId, number>;
14
13
  export declare function getLineTreeJunctionAnchorIds(topology: LineTreeTopology): AnchorId[];
14
+ export declare function getLineTreePointDegrees(topology: LineTreeTopology): Map<AnchorId, number>;
15
15
  export declare function getLineTreeEndpointRelationBindings(topology: LineTreeTopology, elementKey: string): readonly EndpointRelationBinding[];
16
16
  export declare function getLineTreeBindableEndpoints(topology: LineTreeTopology, elementKey: string): readonly BindableEndpoint[];
17
17
  export declare function getLineTreeEndpointRole(topology: LineTreeTopology, anchorId: AnchorId): "input" | "output";
@@ -35,5 +35,5 @@ export declare function addLineTreeBranchAtTail(topology: LineTreeTopology, pare
35
35
  };
36
36
  export declare function removeLineTreeBranch(topology: LineTreeTopology, subpathId: SubpathId): LineTreeBranchRemoval;
37
37
  export declare function updateLineTreeBranchStyle(topology: LineTreeTopology, subpathId: SubpathId, style: LineTreeBranchStyle): LineTreeTopology;
38
- export declare function scaleLineTreeTopology(topology: LineTreeTopology, scaleX: number, scaleY: number): LineTreeTopology;
38
+ export declare function transformLineTreeTopology(topology: LineTreeTopology, matrix: DOMMatrixReadOnly): LineTreeTopology;
39
39
  export declare function remapLineTreeRelations(topology: LineTreeTopology, keyMap: ReadonlyMap<string, string>): LineTreeTopology;
@@ -6,12 +6,14 @@ export declare class LineTreeTransform<E extends LineTree = LineTree> extends El
6
6
  private topologySource;
7
7
  private pointById;
8
8
  private branchById;
9
+ private endpointRoleById;
9
10
  private junctionAnchorIds;
10
11
  private junctionAnchorIdSet;
11
12
  private layoutBounds;
12
13
  private resolvedPointById;
13
14
  private worldGeometrySource;
14
15
  private worldBranchPoints;
16
+ private worldBranchPointsById;
15
17
  private worldPointById;
16
18
  private worldSnapPoints;
17
19
  private worldMatrixA;
@@ -21,8 +23,10 @@ export declare class LineTreeTransform<E extends LineTree = LineTree> extends El
21
23
  private worldMatrixE;
22
24
  private worldMatrixF;
23
25
  private ensureTopologyViews;
26
+ private buildEndpointRoles;
24
27
  getPoint(anchorId: AnchorId): LineTreePoint;
25
28
  getBranch(subpathId: SubpathId): LineTreeBranch;
29
+ getEndpointRole(anchorId: AnchorId): "input" | "output";
26
30
  getResolvedPoint(anchorId: AnchorId): LineTreePoint;
27
31
  getJunctionAnchorIds(): readonly AnchorId[];
28
32
  isJunctionAnchor(anchorId: AnchorId): boolean;
package/dist/polygon.d.ts CHANGED
@@ -37,7 +37,7 @@ export declare class Polygon extends EditorShape<PolygonProperties, PolygonOptio
37
37
  getPointScopeOutline(_scope: PointEditingScope): any;
38
38
  resolvePointEdit(request: PointEditRequest): PointOwnerEdit | null;
39
39
  captureTransformBaseline(): ElementTransformBaseline<AuthoredSubpath<import("@fulate/core").AuthoredPathAnchor>>;
40
- resolveTransformChange(baseline: Readonly<ElementTransformBaseline<AuthoredSubpath>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): OwnerPropertyChange;
40
+ resolveTransformChange(baseline: Readonly<ElementTransformBaseline<AuthoredSubpath>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): readonly OwnerPropertyChange[];
41
41
  protected buildPath(ctx: CanvasRenderingContext2D): void;
42
42
  protected buildBorderPath(ctx: CanvasRenderingContext2D): void;
43
43
  getEdgeSegment(edge: Edge): {
@@ -38,8 +38,6 @@ export declare function measureTextAlphabeticBaselineOffset(text: string, font:
38
38
  * `white-space: pre-wrap` keeps the caller's spaces and hard breaks intact.
39
39
  */
40
40
  export declare function measureTextWidth(text: string, font: string, letterSpacing?: number): number;
41
- /** Fit a single run and append an ellipsis only when it does not fit. */
42
- export declare function fitTextWithEllipsis(text: string, maxWidth: number, font: string, letterSpacing?: number): string;
43
41
  declare class PreparedTextLayout {
44
42
  readonly text: string;
45
43
  readonly font: string;
@@ -16,16 +16,6 @@ export interface TextDecorationOptions {
16
16
  thickness?: number;
17
17
  }
18
18
  export declare function drawTextDecoration(ctx: CanvasRenderingContext2D, opts: TextDecorationOptions): void;
19
- export interface TextStrokeOptions {
20
- line: string;
21
- x: number;
22
- y: number;
23
- font: string;
24
- letterSpacing: number;
25
- strokeColor: string | CanvasGradient;
26
- strokeWidth: number;
27
- }
28
- export declare function drawTextWithStroke(ctx: CanvasRenderingContext2D, opts: TextStrokeOptions): void;
29
19
  export declare function applyTextShadow(ctx: CanvasRenderingContext2D, shadow: ShadowOption): void;
30
20
  export declare function clearTextShadow(ctx: CanvasRenderingContext2D): void;
31
21
  export interface TextRunMetrics {
@@ -1,4 +1,4 @@
1
- import type { PointView } from "@fulate/share";
1
+ import { type PointView } from "@fulate/share";
2
2
  import { ChangeImpact, ShapeProperties, type AuthoredSubpath, type GeometrySnapshot, type OwnerPropertyChange, type PropertyChanges, type ReparentTransformIntent, type ShapeOption, type ShapeOptionPatch, type ElementTransformBaseline, type TransformIntent } from "@fulate/core";
3
3
  import { EditorShape, EditorShapeTransform } from "./editor/shape";
4
4
  import { type EditorCapabilityToken, type PointControlDescriptor, type PointEditRequest, type PointEditingScope, type PointOwnerEdit } from "./editor/capability";
@@ -52,7 +52,7 @@ export declare class VectorPath extends EditorShape<VectorPathProperties, Vector
52
52
  private findSegment;
53
53
  private ownerPathChange;
54
54
  captureTransformBaseline(): ElementTransformBaseline<VectorPathGeometry>;
55
- resolveTransformChange(baseline: Readonly<ElementTransformBaseline<VectorPathGeometry>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): OwnerPropertyChange;
55
+ resolveTransformChange(baseline: Readonly<ElementTransformBaseline<VectorPathGeometry>>, intent: Readonly<TransformIntent | ReparentTransformIntent>): readonly OwnerPropertyChange[];
56
56
  protected buildPath(ctx: CanvasRenderingContext2D): void;
57
57
  }
58
58
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fulate/ui",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",