@fulate/tools 1.0.10 → 1.0.12

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/dist/index.d.ts CHANGED
@@ -9,6 +9,6 @@ export { Rule } from "./rule/index";
9
9
  export type { RuleOption } from "./rule/index";
10
10
  export { EditorLayer } from "./editor-layer";
11
11
  export { alignElements } from "./select/align";
12
- export type { AlignType } from "./select/align";
12
+ export { AlignType } from "./select/align";
13
13
  export { ArrangeType } from "./select/arrange";
14
14
  export type { ClipboardPlugin } from "./select/clipboard";
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Element, ElementProperties, ElementTransform, Layer, isFormControlTarget, projectConnectionPortPoint, resolveOrigin, resolveReferenceTransform } from "@fulate/core";
2
2
  import { BaseLine, ContentEditingCapabilityToken, DEFAULT_LINE_TREE_BRANCH_STYLE, Group, Line, LineTree, PointEditingCapabilityToken, SelectionBehaviorCapabilityToken, SelectionBehaviorMode, SelectionDiveMode, SelectionSnapMode, SnapSourceCapabilityToken, TransformInteractionCapabilityToken, addLineTreeBranchAtTail, applyLineTreeTopologyDelta, connectionPortAccepts, createLineTreeTopology, createLineTreeTopologyDelta, getEditorCapability, getEditorFrameFlush, getEndpointRelationIndex, hasConnectionPortCapacity, isEndpointRelationSource, moveLineTreePoint, remapLineTreeRelations, resolveConnectEndpointChange, resolveConnectionPortChanges, resolveDisconnectEndpointChanges, resolveSelectionFrameBounds, workspaceFocusInsetsKey } from "@fulate/ui";
3
- import { Bound, Intersection, isIdentityMatrix, qrDecompose, transformPoint } from "@fulate/share";
3
+ import { Bound, Intersection, isIdentityMatrix, makeBoundingBoxFromRects, qrDecompose, transformPoint } from "@fulate/share";
4
+ import { cloneDeep, has, isNull, isString, isUndefined, last } from "lodash-es";
4
5
  import { deserializeElement } from "@fulate/import";
5
- import { isNull, isString, isUndefined } from "lodash-es";
6
6
  import RBush from "rbush";
7
7
  //#region packages/tools/src/select/checkElement.ts
8
8
  function isSelectable(element, excludes) {
@@ -114,11 +114,15 @@ function rectCorners$1(rect) {
114
114
  * capability is authoritative (for example Text/KeyValue authored bounds);
115
115
  * owners without one use the shared local visual/layout fallback.
116
116
  */
117
- function worldSelectionFramePoints(owner) {
118
- const localFrame = resolveSelectionFrameBounds(owner.element, owner.transform);
119
- const matrix = owner.element.transform.getWorldMatrixView();
117
+ function worldSelectionFramePoints(element, transform) {
118
+ const localFrame = resolveSelectionFrameBounds(element, transform);
119
+ const matrix = element.transform.getWorldMatrixView();
120
120
  return rectCorners$1(localFrame).map((point) => transformPoint(matrix, point));
121
121
  }
122
+ /** @internal Resolve the world AABB used by the selection frame. */
123
+ function getWorldSelectionBounds(element, transform) {
124
+ return Bound.fromPoints(worldSelectionFramePoints(element, transform));
125
+ }
122
126
  var ROTATED_RESIZE_CURSORS = [
123
127
  "ns-resize",
124
128
  "nesw-resize",
@@ -178,9 +182,7 @@ function createWorldSelectionFrame(owners, fallback) {
178
182
  if (fallback) return fallback;
179
183
  return frameFromSingleOwner(owners[0], ownerFrame);
180
184
  }
181
- const bounds = resolveSelectionFrameBounds(owners[0].element, owners[0].transform);
182
- const matrix = owners[0].element.transform.getWorldMatrixView();
183
- const worldBounds = Bound.fromPoints(rectCorners$1(bounds).map((point) => transformPoint(matrix, point)));
185
+ const worldBounds = getWorldSelectionBounds(owners[0].element, owners[0].transform);
184
186
  if ((worldBounds.width === 0 || worldBounds.height === 0) && fallback) return fallback;
185
187
  return createSelectionFrameProjection({
186
188
  left: worldBounds.minX,
@@ -190,19 +192,9 @@ function createWorldSelectionFrame(owners, fallback) {
190
192
  angle: 0
191
193
  });
192
194
  }
193
- let left = Infinity;
194
- let top = Infinity;
195
- let right = -Infinity;
196
- let bottom = -Infinity;
197
- for (const owner of owners) for (const point of worldSelectionFramePoints(owner)) {
198
- if (point.x < left) left = point.x;
199
- if (point.y < top) top = point.y;
200
- if (point.x > right) right = point.x;
201
- if (point.y > bottom) bottom = point.y;
202
- }
203
- if (!Number.isFinite(left) || !Number.isFinite(top) || !Number.isFinite(right) || !Number.isFinite(bottom)) return fallback ?? null;
204
- const width = right - left;
205
- const height = bottom - top;
195
+ const bounds = makeBoundingBoxFromRects(owners.map(({ element, transform }) => getWorldSelectionBounds(element, transform)));
196
+ if (!Number.isFinite(bounds.left) || !Number.isFinite(bounds.top) || !Number.isFinite(bounds.width) || !Number.isFinite(bounds.height)) return fallback ?? null;
197
+ const { left, top, width, height } = bounds;
206
198
  if ((width === 0 || height === 0) && fallback) return fallback;
207
199
  return createSelectionFrameProjection({
208
200
  left,
@@ -2404,7 +2396,7 @@ var EditorInteractionController = class {
2404
2396
  kind: "endpoint-binding",
2405
2397
  target: session.tailTarget
2406
2398
  } : void 0);
2407
- const tailAnchorId = added.topology.branches.find(({ subpathId }) => subpathId === added.subpathId).vertices.at(-1).anchorId;
2399
+ const tailAnchorId = last(added.topology.branches.find(({ subpathId }) => subpathId === added.subpathId).vertices).anchorId;
2408
2400
  const scope = this.history.openScope(this.selection);
2409
2401
  this.applyAuthoredBatch([{
2410
2402
  owner: session.owner,
@@ -2533,7 +2525,7 @@ var EditorInteractionController = class {
2533
2525
  }) });
2534
2526
  const bindable = line.getBindableEndpoints();
2535
2527
  const headSource = bindable[0]?.ref;
2536
- const tailSource = bindable.at(-1)?.ref;
2528
+ const tailSource = last(bindable)?.ref;
2537
2529
  const structure = [{
2538
2530
  owner: parent,
2539
2531
  children: [...parent.children, line]
@@ -2621,7 +2613,7 @@ function getEditorInteractionController(root) {
2621
2613
  //#endregion
2622
2614
  //#region packages/tools/src/history/index.ts
2623
2615
  function cloneHistoryValue(value) {
2624
- return value !== null && typeof value === "object" ? structuredClone(value) : value;
2616
+ return value !== null && typeof value === "object" ? cloneDeep(value) : value;
2625
2617
  }
2626
2618
  function selectionKeys(elements) {
2627
2619
  return elements.map((element) => element.key);
@@ -2864,7 +2856,7 @@ var HistoryManager = class {
2864
2856
  */
2865
2857
  undo() {
2866
2858
  this.selectionAdapter?.finishInteraction();
2867
- const entry = this.undoStack.at(-1);
2859
+ const entry = last(this.undoStack);
2868
2860
  if (!entry) return;
2869
2861
  const result = this.replay(entry, "before", entry.selectionBefore);
2870
2862
  this.undoStack.pop();
@@ -2877,7 +2869,7 @@ var HistoryManager = class {
2877
2869
  */
2878
2870
  redo() {
2879
2871
  this.selectionAdapter?.finishInteraction();
2880
- const entry = this.redoStack.at(-1);
2872
+ const entry = last(this.redoStack);
2881
2873
  if (!entry) return;
2882
2874
  const result = this.replay(entry, "after", entry.selectionAfter);
2883
2875
  this.redoStack.pop();
@@ -2887,86 +2879,139 @@ var HistoryManager = class {
2887
2879
  };
2888
2880
  //#endregion
2889
2881
  //#region packages/tools/src/select/align.ts
2890
- function resolveOffsets(rects, type) {
2891
- if (type === "justify-start") {
2892
- const target = Math.min(...rects.map(({ left }) => left));
2882
+ /** Select 支持的对齐与分布方向。 */
2883
+ var AlignType = /* @__PURE__ */ function(AlignType) {
2884
+ AlignType["JustifyStart"] = "justify-start";
2885
+ AlignType["JustifyCenter"] = "justify-center";
2886
+ AlignType["JustifyEnd"] = "justify-end";
2887
+ AlignType["JustifyBetween"] = "justify-between";
2888
+ AlignType["AlignStart"] = "align-start";
2889
+ AlignType["AlignCenter"] = "align-center";
2890
+ AlignType["AlignEnd"] = "align-end";
2891
+ AlignType["AlignBetween"] = "align-between";
2892
+ return AlignType;
2893
+ }({});
2894
+ function compareElementKeys(left, right) {
2895
+ if (left.element.key < right.element.key) return -1;
2896
+ if (left.element.key > right.element.key) return 1;
2897
+ return 0;
2898
+ }
2899
+ function compareDistributionRects(left, right, axis) {
2900
+ const primary = left[axis.position] - right[axis.position];
2901
+ if (primary !== 0) return primary;
2902
+ const secondary = left[axis.crossPosition] - right[axis.crossPosition];
2903
+ if (secondary !== 0) return secondary;
2904
+ return compareElementKeys(left, right);
2905
+ }
2906
+ function resolveDistributionOffsets(rects, reference, axis) {
2907
+ const { position, size, offset } = axis;
2908
+ const start = reference[position];
2909
+ const referenceEnd = start + reference[size];
2910
+ let totalSize = 0;
2911
+ for (const rect of rects) totalSize += rect[size];
2912
+ const roundoff = Math.max(1, Math.abs(start), Math.abs(referenceEnd), Math.abs(totalSize)) * Number.EPSILON * rects.length * 4;
2913
+ const orderStep = roundoff * 8;
2914
+ const freeSpace = reference[size] - totalSize;
2915
+ const fittedFreeSpace = Math.abs(freeSpace) <= roundoff ? 0 : freeSpace;
2916
+ const gap = fittedFreeSpace / (rects.length - 1);
2917
+ const distributionEnd = start + totalSize + fittedFreeSpace;
2918
+ rects.sort((left, right) => compareDistributionRects(left, right, axis));
2919
+ const lastIndex = rects.length - 1;
2920
+ const latestStarts = new Array(rects.length);
2921
+ latestStarts[lastIndex] = distributionEnd - rects[lastIndex][size];
2922
+ for (let index = lastIndex - 1; index >= 0; index--) latestStarts[index] = Math.min(distributionEnd - rects[index][size], latestStarts[index + 1] - orderStep);
2923
+ if (latestStarts[0] < start) return;
2924
+ let idealTarget = start;
2925
+ let previousTarget = start;
2926
+ for (let index = 0; index <= lastIndex; index++) {
2927
+ let target;
2928
+ if (index === 0) target = start;
2929
+ else if (index === lastIndex) target = latestStarts[lastIndex];
2930
+ else {
2931
+ idealTarget += rects[index - 1][size] + gap;
2932
+ target = Math.min(latestStarts[index], Math.max(previousTarget + orderStep, idealTarget));
2933
+ }
2934
+ const delta = target - rects[index][position];
2935
+ rects[index][offset] = Math.abs(delta) <= roundoff ? 0 : delta;
2936
+ previousTarget = target;
2937
+ }
2938
+ }
2939
+ function resolveOffsets(rects, type, reference) {
2940
+ if (type === AlignType.JustifyStart) {
2941
+ const target = reference.left;
2893
2942
  rects.forEach((rect) => {
2894
2943
  rect.dx = target - rect.left;
2895
2944
  });
2896
2945
  return;
2897
2946
  }
2898
- if (type === "justify-center") {
2899
- const target = (Math.min(...rects.map((rect) => rect.left)) + Math.max(...rects.map((rect) => rect.left + rect.width))) / 2;
2947
+ if (type === AlignType.JustifyCenter) {
2948
+ const target = reference.left + reference.width / 2;
2900
2949
  rects.forEach((rect) => {
2901
2950
  rect.dx = target - (rect.left + rect.width / 2);
2902
2951
  });
2903
2952
  return;
2904
2953
  }
2905
- if (type === "justify-end") {
2906
- const target = Math.max(...rects.map((rect) => rect.left + rect.width));
2954
+ if (type === AlignType.JustifyEnd) {
2955
+ const target = reference.left + reference.width;
2907
2956
  rects.forEach((rect) => {
2908
2957
  rect.dx = target - (rect.left + rect.width);
2909
2958
  });
2910
2959
  return;
2911
2960
  }
2912
- if (type === "justify-between") {
2913
- rects.sort((left, right) => left.left - right.left);
2914
- const start = rects[0].left;
2915
- const end = rects.at(-1);
2916
- const total = rects.reduce((sum, rect) => sum + rect.width, 0);
2917
- const gap = (end.left + end.width - start - total) / (rects.length - 1);
2918
- let x = start;
2919
- for (const rect of rects) {
2920
- rect.dx = x - rect.left;
2921
- x += rect.width + gap;
2922
- }
2961
+ if (type === AlignType.JustifyBetween) {
2962
+ resolveDistributionOffsets(rects, reference, {
2963
+ position: "left",
2964
+ crossPosition: "top",
2965
+ size: "width",
2966
+ offset: "dx"
2967
+ });
2923
2968
  return;
2924
2969
  }
2925
- if (type === "align-start") {
2926
- const target = Math.min(...rects.map(({ top }) => top));
2970
+ if (type === AlignType.AlignStart) {
2971
+ const target = reference.top;
2927
2972
  rects.forEach((rect) => {
2928
2973
  rect.dy = target - rect.top;
2929
2974
  });
2930
2975
  return;
2931
2976
  }
2932
- if (type === "align-center") {
2933
- const target = (Math.min(...rects.map((rect) => rect.top)) + Math.max(...rects.map((rect) => rect.top + rect.height))) / 2;
2977
+ if (type === AlignType.AlignCenter) {
2978
+ const target = reference.top + reference.height / 2;
2934
2979
  rects.forEach((rect) => {
2935
2980
  rect.dy = target - (rect.top + rect.height / 2);
2936
2981
  });
2937
2982
  return;
2938
2983
  }
2939
- if (type === "align-end") {
2940
- const target = Math.max(...rects.map((rect) => rect.top + rect.height));
2984
+ if (type === AlignType.AlignEnd) {
2985
+ const target = reference.top + reference.height;
2941
2986
  rects.forEach((rect) => {
2942
2987
  rect.dy = target - (rect.top + rect.height);
2943
2988
  });
2944
2989
  return;
2945
2990
  }
2946
- rects.sort((left, right) => left.top - right.top);
2947
- const start = rects[0].top;
2948
- const end = rects.at(-1);
2949
- const total = rects.reduce((sum, rect) => sum + rect.height, 0);
2950
- const gap = (end.top + end.height - start - total) / (rects.length - 1);
2951
- let y = start;
2952
- for (const rect of rects) {
2953
- rect.dy = y - rect.top;
2954
- y += rect.height + gap;
2955
- }
2991
+ resolveDistributionOffsets(rects, reference, {
2992
+ position: "top",
2993
+ crossPosition: "left",
2994
+ size: "height",
2995
+ offset: "dy"
2996
+ });
2956
2997
  }
2957
- function alignElements(select, type) {
2998
+ function alignElements(select, type, referenceRect) {
2958
2999
  const controller = select.interactionController;
2959
3000
  let elements = controller.selection;
2960
- if (elements.length < 2 || type.endsWith("between") && elements.length < 3) return;
3001
+ const between = type === AlignType.JustifyBetween || type === AlignType.AlignBetween;
3002
+ const minimum = referenceRect ? between ? 2 : 1 : between ? 3 : 2;
3003
+ if (elements.length < minimum) return;
2961
3004
  controller.finishCurrentInteraction();
2962
3005
  controller.root.frameRuntime.drain();
2963
3006
  if (select.interactionController !== controller) return;
2964
3007
  elements = controller.selection;
2965
- if (elements.length < 2 || type.endsWith("between") && elements.length < 3) return;
3008
+ if (elements.length < minimum) return;
2966
3009
  const rects = elements.map((element) => {
2967
- const bounds = element.transform.getWorldVisualBoundsView();
3010
+ const transform = getEditorCapability(element, TransformInteractionCapabilityToken);
3011
+ const bounds = getWorldSelectionBounds(element, transform);
2968
3012
  return {
2969
3013
  element,
3014
+ transform,
2970
3015
  left: bounds.left,
2971
3016
  top: bounds.top,
2972
3017
  width: bounds.width,
@@ -2975,14 +3020,21 @@ function alignElements(select, type) {
2975
3020
  dy: 0
2976
3021
  };
2977
3022
  });
2978
- resolveOffsets(rects, type);
3023
+ resolveOffsets(rects, type, referenceRect ?? makeBoundingBoxFromRects(rects));
2979
3024
  const changes = [];
2980
3025
  for (const rect of rects) {
2981
3026
  if (rect.dx === 0 && rect.dy === 0) continue;
2982
- const capability = getEditorCapability(rect.element, TransformInteractionCapabilityToken);
2983
- if (!capability) continue;
2984
- const baseline = capability.captureTransformBaseline();
2985
- changes.push(...capability.resolveTransformChange(baseline, { worldDelta: new DOMMatrix().translate(rect.dx, rect.dy) }));
3027
+ const { transform } = rect;
3028
+ if (!transform) continue;
3029
+ const baseline = transform.captureTransformBaseline();
3030
+ changes.push(...transform.resolveTransformChange(baseline, { worldDelta: new DOMMatrix([
3031
+ 1,
3032
+ 0,
3033
+ 0,
3034
+ 1,
3035
+ rect.dx,
3036
+ rect.dy
3037
+ ]) }));
2986
3038
  }
2987
3039
  if (changes.length === 0) return;
2988
3040
  const scope = select.history.openScope(elements);
@@ -3284,7 +3336,7 @@ function referenceChanges(elements, keyMap) {
3284
3336
  function deserializeClones(entries, deserializers) {
3285
3337
  const keyMap = /* @__PURE__ */ new Map();
3286
3338
  const elements = entries.map((entry) => {
3287
- const json = structuredClone(entry);
3339
+ const json = cloneDeep(entry);
3288
3340
  removeKeys(json);
3289
3341
  offsetTopLevel(json);
3290
3342
  const element = deserializeElement(json, deserializers);
@@ -3863,8 +3915,19 @@ var Select = class extends Element {
3863
3915
  unGroup() {
3864
3916
  return unGroup(this);
3865
3917
  }
3866
- align(type) {
3867
- return alignElements(this, type);
3918
+ /**
3919
+ * Aligns the live selection in world space. Without `referenceRect`, the
3920
+ * current selection-frame AABB is the reference. A supplied world Rect is
3921
+ * used directly. Distribution keeps strict edge gaps when they are numerically
3922
+ * possible and uses bounded overlap when the selected sizes exceed the reference,
3923
+ * while keeping every selection-frame AABB inside it and preserving order. The
3924
+ * current axis is a noop only when an element cannot fit or no ordered layout is
3925
+ * representable. A successful non-noop creates one Transform batch and one Select
3926
+ * History entry; a noop creates neither a transform, History entry, nor configured
3927
+ * change.
3928
+ */
3929
+ align(type, referenceRect) {
3930
+ return alignElements(this, type, referenceRect);
3868
3931
  }
3869
3932
  /** 按当前 Layer 与 sibling 顺序排列 live selection。 */
3870
3933
  arrange(type) {
@@ -4853,14 +4916,14 @@ var Rule = class extends Element {
4853
4916
  if (!options) return this;
4854
4917
  const { rulerSize, backgroundColor, cornerBackgroundColor, majorTickColor, minorTickColor, borderColor, textColor, selectionColor, ...baseOptions } = options;
4855
4918
  let changed = false;
4856
- if (Object.hasOwn(options, "rulerSize")) changed = this.setAppearance("rulerSize", rulerSize ?? DEFAULT_RULER_SIZE) || changed;
4857
- if (Object.hasOwn(options, "backgroundColor")) changed = this.setAppearance("backgroundColor", backgroundColor ?? DEFAULT_BACKGROUND_COLOR) || changed;
4858
- if (Object.hasOwn(options, "cornerBackgroundColor")) changed = this.setAppearance("cornerBackgroundColor", cornerBackgroundColor ?? DEFAULT_CORNER_BACKGROUND_COLOR) || changed;
4859
- if (Object.hasOwn(options, "majorTickColor")) changed = this.setAppearance("majorTickColor", majorTickColor ?? DEFAULT_MAJOR_TICK_COLOR) || changed;
4860
- if (Object.hasOwn(options, "minorTickColor")) changed = this.setAppearance("minorTickColor", minorTickColor ?? DEFAULT_MINOR_TICK_COLOR) || changed;
4861
- if (Object.hasOwn(options, "borderColor")) changed = this.setAppearance("borderColor", borderColor ?? DEFAULT_BORDER_COLOR) || changed;
4862
- if (Object.hasOwn(options, "textColor")) changed = this.setAppearance("textColor", textColor ?? DEFAULT_TEXT_COLOR) || changed;
4863
- if (Object.hasOwn(options, "selectionColor")) changed = this.setAppearance("selectionColor", selectionColor) || changed;
4919
+ if (has(options, "rulerSize")) changed = this.setAppearance("rulerSize", rulerSize ?? DEFAULT_RULER_SIZE) || changed;
4920
+ if (has(options, "backgroundColor")) changed = this.setAppearance("backgroundColor", backgroundColor ?? DEFAULT_BACKGROUND_COLOR) || changed;
4921
+ if (has(options, "cornerBackgroundColor")) changed = this.setAppearance("cornerBackgroundColor", cornerBackgroundColor ?? DEFAULT_CORNER_BACKGROUND_COLOR) || changed;
4922
+ if (has(options, "majorTickColor")) changed = this.setAppearance("majorTickColor", majorTickColor ?? DEFAULT_MAJOR_TICK_COLOR) || changed;
4923
+ if (has(options, "minorTickColor")) changed = this.setAppearance("minorTickColor", minorTickColor ?? DEFAULT_MINOR_TICK_COLOR) || changed;
4924
+ if (has(options, "borderColor")) changed = this.setAppearance("borderColor", borderColor ?? DEFAULT_BORDER_COLOR) || changed;
4925
+ if (has(options, "textColor")) changed = this.setAppearance("textColor", textColor ?? DEFAULT_TEXT_COLOR) || changed;
4926
+ if (has(options, "selectionColor")) changed = this.setAppearance("selectionColor", selectionColor) || changed;
4864
4927
  super.setOptions(baseOptions);
4865
4928
  if (changed) this.transform.markPaintDirty();
4866
4929
  return this;
@@ -5066,4 +5129,4 @@ var EditorLayer = class extends Layer {
5066
5129
  }
5067
5130
  };
5068
5131
  //#endregion
5069
- export { ArrangeType, EditorLayer, HistoryManager, LineTool, Rule, Select, Snap, alignElements };
5132
+ export { AlignType, ArrangeType, EditorLayer, HistoryManager, LineTool, Rule, Select, Snap, alignElements };
@@ -1,3 +1,14 @@
1
+ import { type Rect } from "@fulate/share";
1
2
  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;
3
+ /** Select 支持的对齐与分布方向。 */
4
+ export declare enum AlignType {
5
+ JustifyStart = "justify-start",
6
+ JustifyCenter = "justify-center",
7
+ JustifyEnd = "justify-end",
8
+ JustifyBetween = "justify-between",
9
+ AlignStart = "align-start",
10
+ AlignCenter = "align-center",
11
+ AlignEnd = "align-end",
12
+ AlignBetween = "align-between"
13
+ }
14
+ export declare function alignElements(select: Select, type: AlignType, referenceRect?: Readonly<Rect>): void;
@@ -1,4 +1,5 @@
1
1
  import { Element, ElementProperties, type BaseElementOption, type ConnectionPortRef, type Layer, type Root } from "@fulate/core";
2
+ import type { Rect } from "@fulate/share";
2
3
  import { EditorInteractionController } from "../editor/interaction-controller";
3
4
  import { HistoryManager } from "../history";
4
5
  import { type AlignType } from "./align";
@@ -75,7 +76,18 @@ export declare class Select extends Element {
75
76
  * An obvious non-Group noop does not drain the frame runtime.
76
77
  */
77
78
  unGroup(): Element<ElementProperties, BaseElementOption<any>, Partial<Omit<BaseElementOption<any>, "key" | "children">>>[];
78
- align(type: AlignType): void;
79
+ /**
80
+ * Aligns the live selection in world space. Without `referenceRect`, the
81
+ * current selection-frame AABB is the reference. A supplied world Rect is
82
+ * used directly. Distribution keeps strict edge gaps when they are numerically
83
+ * possible and uses bounded overlap when the selected sizes exceed the reference,
84
+ * while keeping every selection-frame AABB inside it and preserving order. The
85
+ * current axis is a noop only when an element cannot fit or no ordered layout is
86
+ * representable. A successful non-noop creates one Transform batch and one Select
87
+ * History entry; a noop creates neither a transform, History entry, nor configured
88
+ * change.
89
+ */
90
+ align(type: AlignType, referenceRect?: Readonly<Rect>): void;
79
91
  /** 按当前 Layer 与 sibling 顺序排列 live selection。 */
80
92
  arrange(type: ArrangeType): void;
81
93
  /**
@@ -1,6 +1,6 @@
1
1
  import { Element } from "@fulate/core";
2
2
  import { type PointEditTarget, type PointEditingScope, type SnapSourceCapability, type TransformInteractionCapability, type TransformInteractionPolicy } from "@fulate/ui";
3
- import { type PointView, type Rect } from "@fulate/share";
3
+ import { Bound, type PointView, type Rect } from "@fulate/share";
4
4
  export type ResizeHandle = "tl" | "tr" | "br" | "bl" | "mt" | "mr" | "mb" | "ml";
5
5
  export type SelectionRotationPivot = {
6
6
  readonly kind: "selection-center";
@@ -109,14 +109,16 @@ export interface SelectionProjectionTopology {
109
109
  readonly pointScopeOutline: readonly PointView[] | null;
110
110
  readonly pointScopeValid: boolean;
111
111
  }
112
- export declare function createSelectionFrameProjection({ left, top, width, height, angle }: Pick<SelectionFrameProjection, "left" | "top" | "width" | "height" | "angle">): SelectionFrameProjection;
112
+ /** @internal Resolve the world AABB used by the selection frame. */
113
+ export declare function getWorldSelectionBounds(element: Element, transform: TransformInteractionCapability | undefined): Bound;
114
+ export declare function createSelectionFrameProjection({ left, top, width, height, angle, }: Pick<SelectionFrameProjection, "left" | "top" | "width" | "height" | "angle">): SelectionFrameProjection;
113
115
  /** @internal Build a command frame from the current owner bounds. */
114
116
  export declare function createWorldSelectionFrame(owners: readonly ProjectionOwner[], fallback?: SelectionFrameProjection | null): SelectionFrameProjection;
115
117
  export declare function createSelectionRotationPivotSnapMarkers(frame: SelectionFrameProjection): readonly SelectionRotationPivotSnapMarker[];
116
118
  export declare function createSelectionProjectionMembership(owners: readonly ProjectionOwner[], policy: ProjectionPolicy): SelectionProjectionMembership;
117
119
  export declare function createSelectionProjectionTopology(owners: readonly ProjectionOwner[], isCurrent?: (owner: ProjectionOwner) => boolean): SelectionProjectionTopology;
118
120
  export declare function createProjectionOwner(element: Element, frameVisible?: boolean, pointScope?: PointEditingScope | null, pointEnabled?: boolean): ProjectionOwner;
119
- export declare function buildSelectionOverlayProjection({ owners, membership, topology, frame: frameOverride, policy, pivot, showPivot, pivotSnapMarkers, activePointTarget, marquee }: {
121
+ export declare function buildSelectionOverlayProjection({ owners, membership, topology, frame: frameOverride, policy, pivot, showPivot, pivotSnapMarkers, activePointTarget, marquee, }: {
120
122
  readonly owners: readonly ProjectionOwner[];
121
123
  readonly membership: SelectionProjectionMembership;
122
124
  readonly topology: SelectionProjectionTopology;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fulate/tools",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",