@fulate/core 1.0.2 → 1.0.3

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.
@@ -69,7 +69,13 @@ export interface GeometryNearestPoint extends GeometryPathPoint {
69
69
  /** 针对一个 immutable snapshot 的统一 exact query。 */
70
70
  export interface GeometryQuery {
71
71
  readonly snapshot: GeometrySnapshot;
72
- /** 返回所有 subpath line/cubic 的精确 local AABB;无顶点时为 `null`。 */
72
+ /**
73
+ * 返回所有 subpath line/cubic 的精确 local AABB;无顶点时为 `null`。
74
+ *
75
+ * 返回值是 query-owned 的 borrowed view:同一 query 在其 snapshot
76
+ * 生命周期内重复返回同一引用,不会 clone 或 freeze。需要独立修改的
77
+ * 调用者必须在消费边界自行复制。
78
+ */
73
79
  getBounds(): Readonly<Rect> | null;
74
80
  /** 判断 local 点是否位于闭合 subpath 的指定填充内或边界上。 */
75
81
  contains(point: PointView, fillRule?: CanvasFillRule): boolean;
@@ -16,6 +16,7 @@ export declare function createRectangleGeometrySnapshot(elementKey: string, revi
16
16
  export declare function rectangleSubpathsMaterializedForTest(snapshot: GeometrySnapshot): boolean;
17
17
  /** @internal 同一 canonical query 的普通矩形 O(1) 实现。 */
18
18
  export declare class RectangleSnapshotGeometryQuery extends SnapshotGeometryQuery {
19
+ private normalizedBounds;
19
20
  getBounds(): Readonly<Rect>;
20
21
  contains(point: PointView): boolean;
21
22
  hit(point: PointView, tolerance?: number): boolean;
package/dist/index.js CHANGED
@@ -250,7 +250,7 @@ var ChangeImpact = /* @__PURE__ */ function(ChangeImpact) {
250
250
  function clonePropertyValue(value) {
251
251
  return isObjectLike(value) ? structuredClone(value) : value;
252
252
  }
253
- /** 快照 API 需要比较独立副本的结构内容。 */
253
+ /** snapshot/serialization 边界按结构内容比较独立值。 */
254
254
  function sameSnapshotValue(left, right) {
255
255
  if (!isObjectLike(left) || !isObjectLike(right)) return Object.is(left, right);
256
256
  return Object.is(left, right) || isEqual(left, right);
@@ -261,47 +261,17 @@ function samePropertyValue(left, right) {
261
261
  }
262
262
  var NO_PROPERTY_CHANGES = /* @__PURE__ */ new Map();
263
263
  var NO_PROPERTY_UPDATES = /* @__PURE__ */ new Map();
264
- /** Node 持有的属性状态,以及显式生成独立结果的快照操作。 */
264
+ /** Node 持有的属性状态,并提供显式独立快照。 */
265
265
  var NodeProperties = class {
266
266
  [CONFIGURED_STATE] = null;
267
267
  [RUNTIME_OVERRIDE_STATE] = null;
268
- /** 在新 Properties 实例上应用 patch,并返回旧值变化表。 */
269
- with(patch) {
270
- const next = this.create({
271
- ...this.values(),
272
- ...patch
273
- });
274
- const changes = /* @__PURE__ */ new Map();
275
- for (const key of new Set([...Object.keys(this), ...Object.keys(next)])) {
276
- if (key === "key") continue;
277
- if (!sameSnapshotValue(this[key], next[key])) changes.set(key, this[key]);
278
- }
279
- return {
280
- changes,
281
- properties: next
282
- };
283
- }
284
268
  /** 返回可独立修改的完整属性快照。 */
285
269
  snapshot() {
286
270
  return this.configuredValues();
287
271
  }
288
- /** 判断快照与当前属性是否语义相同。 */
289
- matches(snapshot) {
290
- if (!this[RUNTIME_OVERRIDE_STATE]) return this.with(snapshot).changes.size === 0;
291
- const current = this.configuredValues();
292
- const next = this.create({
293
- ...current,
294
- ...snapshot
295
- });
296
- for (const key of new Set([...Object.keys(this), ...Object.keys(next)])) {
297
- if (key === "key") continue;
298
- if (!sameSnapshotValue(current[key], next[key])) return false;
299
- }
300
- return true;
301
- }
302
272
  /** 返回省略默认值和函数的可序列化属性。 */
303
273
  toJSON() {
304
- const defaults = this.create({});
274
+ const defaults = createPropertiesLike(this, {});
305
275
  const json = {};
306
276
  for (const key of Object.keys(this)) {
307
277
  if (key === "key") continue;
@@ -314,10 +284,6 @@ var NodeProperties = class {
314
284
  getImpact(_changes, _updates = NO_PROPERTY_UPDATES) {
315
285
  return ChangeImpact.None;
316
286
  }
317
- /** 返回当前属性的独立完整值对象。 */
318
- values() {
319
- return Object.fromEntries(Object.entries(this).filter(([key]) => key !== "key").map(([key, value]) => [key, clonePropertyValue(value)]));
320
- }
321
287
  /** 读取把 runtime 字段还原为 configured 后的当前作者值。 */
322
288
  configuredValue(key) {
323
289
  return getConfiguredPropertyValue(this, key);
@@ -326,12 +292,6 @@ var NodeProperties = class {
326
292
  configuredValues() {
327
293
  return Object.fromEntries(Object.keys(this).filter((key) => key !== "key").map((key) => [key, clonePropertyValue(this.configuredValue(key))]));
328
294
  }
329
- /** 使用当前运行时构造器创建同类型 Properties。 */
330
- create(values) {
331
- const Constructor = this.constructor;
332
- const { key: _key, ...ordinaryValues } = values;
333
- return new Constructor(ordinaryValues);
334
- }
335
295
  };
336
296
  /** @internal 借用单个字段当前的 configured source 值。 */
337
297
  function getConfiguredPropertyValue(properties, key) {
@@ -1582,8 +1542,7 @@ var SnapshotGeometryQuery = class {
1582
1542
  return { ...this.debugStats };
1583
1543
  }
1584
1544
  getBounds() {
1585
- const bounds = this.getBoundsData().bounds;
1586
- return bounds ? { ...bounds } : null;
1545
+ return this.getBoundsData().bounds;
1587
1546
  }
1588
1547
  getPathPoint(position) {
1589
1548
  if (position.revision !== this.snapshot.revision || position.t < 0 || position.t > 1) return void 0;
@@ -1767,13 +1726,15 @@ function hitRectangle(point, bounds, tolerance) {
1767
1726
  }
1768
1727
  /** @internal 同一 canonical query 的普通矩形 O(1) 实现。 */
1769
1728
  var RectangleSnapshotGeometryQuery = class extends SnapshotGeometryQuery {
1729
+ normalizedBounds;
1770
1730
  getBounds() {
1731
+ if (this.normalizedBounds) return this.normalizedBounds;
1771
1732
  const bounds = this.snapshot.layoutReferenceBounds;
1772
1733
  const right = bounds.left + bounds.width;
1773
1734
  const bottom = bounds.top + bounds.height;
1774
1735
  const left = Math.min(bounds.left, right);
1775
1736
  const top = Math.min(bounds.top, bottom);
1776
- return {
1737
+ return this.normalizedBounds = {
1777
1738
  left,
1778
1739
  top,
1779
1740
  width: Math.max(bounds.left, right) - left,
@@ -2775,27 +2736,26 @@ function resolveConnectionPortSlots(element, definitions) {
2775
2736
  for (const { position } of definitions) edgeCounts.set(position.edge, (edgeCounts.get(position.edge) ?? 0) + 1);
2776
2737
  return definitions.map((definition) => {
2777
2738
  const { edge, ratio } = definition.position;
2739
+ const geometry = element.getConnectionPortGeometry(edge, ratio);
2778
2740
  return {
2779
2741
  ref: {
2780
2742
  elementKey: element.key,
2781
2743
  portId: definition.id
2782
2744
  },
2783
2745
  kind: "endpoint-binding",
2784
- localPosition: element.getConnectionPortPosition(edge, ratio),
2746
+ localPosition: geometry.position,
2785
2747
  origin: "custom",
2786
2748
  definition,
2787
2749
  edge,
2788
2750
  ratio,
2789
- edgeCount: edgeCounts.get(edge)
2751
+ edgeCount: edgeCounts.get(edge),
2752
+ geometry
2790
2753
  };
2791
2754
  });
2792
2755
  }
2793
2756
  /** 将 local 点投影到当前已提交 world matrix。 */
2794
2757
  function projectConnectionPortPoint(matrix, point) {
2795
- return {
2796
- x: matrix.a * point.x + matrix.c * point.y + matrix.e,
2797
- y: matrix.b * point.x + matrix.d * point.y + matrix.f
2798
- };
2758
+ return transformPoint(matrix, point);
2799
2759
  }
2800
2760
  /** 按 owner-local ID 读取一个已知 Port;不受发现开关影响。 */
2801
2761
  function getElementConnectionPort(element, portId) {
@@ -3547,6 +3507,16 @@ var Element = class extends Node {
3547
3507
  getConnectionPortPosition(edge, ratio) {
3548
3508
  return rectEdgePosition(this.width ?? 0, this.height ?? 0, edge, ratio);
3549
3509
  }
3510
+ /** @internal 一次产出 custom Port position、法线与 label edge 长度。 */
3511
+ getConnectionPortGeometry(edge, ratio) {
3512
+ const resolved = rectEdgePosition(this.width ?? 0, this.height ?? 0, edge, ratio);
3513
+ return {
3514
+ position: this.getConnectionPortPosition(edge, ratio),
3515
+ nx: resolved.nx,
3516
+ ny: resolved.ny,
3517
+ edgeLength: edge === "top" || edge === "bottom" ? Math.abs(this.width ?? 0) : Math.abs(this.height ?? 0)
3518
+ };
3519
+ }
3550
3520
  /** @internal 一次借用 owner 当前 configured source 的指定字段。 */
3551
3521
  static readConfiguredPropertyValues(owner, targets) {
3552
3522
  readConfiguredPropertyValues(owner.propertyState, targets);
@@ -4012,9 +3982,12 @@ var Shape = class extends Element {
4012
3982
  /**
4013
3983
  * 返回某条逻辑边上 ratio 处的局部坐标和朝外法线。
4014
3984
  * 默认实现基于 getEdgeSegment 做线性插值;圆形等曲线形状可直接覆写。
3985
+ * Core owner 已取得 segment 时可传入它,避免重复遍历;普通调用省略该参数。
4015
3986
  */
4016
- getEdgePosition(edge, ratio) {
4017
- const seg = this.getEdgeSegment(edge);
3987
+ getEdgePosition(edge, ratio, segment = this.getEdgeSegment(edge)) {
3988
+ return this.getEdgePositionFromSegment(segment, ratio);
3989
+ }
3990
+ getEdgePositionFromSegment(seg, ratio) {
4018
3991
  const pos = createPointView(seg.start.x + (seg.end.x - seg.start.x) * ratio, seg.start.y + (seg.end.y - seg.start.y) * ratio);
4019
3992
  const dx = seg.end.x - seg.start.x;
4020
3993
  const dy = seg.end.y - seg.start.y;
@@ -4035,8 +4008,20 @@ var Shape = class extends Element {
4035
4008
  ny
4036
4009
  };
4037
4010
  }
4038
- getConnectionPortPosition(edge, ratio) {
4039
- return this.getEdgePosition(edge, ratio).pos;
4011
+ /** @internal 一次产出 custom Port 的位置、法线与 label edge 长度。 */
4012
+ getConnectionPortGeometry(edge, ratio) {
4013
+ const segment = this.getEdgeSegment(edge);
4014
+ const edgePosition = this.getEdgePosition(edge, ratio, segment);
4015
+ return {
4016
+ position: this.getConnectionPortPosition(edge, ratio, edgePosition),
4017
+ nx: edgePosition.nx,
4018
+ ny: edgePosition.ny,
4019
+ edgeLength: Math.hypot(segment.end.x - segment.start.x, segment.end.y - segment.start.y)
4020
+ };
4021
+ }
4022
+ /** 在组合 Port geometry 时可复用同一轮已解析的 edge position。 */
4023
+ getConnectionPortPosition(edge, ratio, edgePosition) {
4024
+ return (edgePosition ?? this.getEdgePosition(edge, ratio)).pos;
4040
4025
  }
4041
4026
  /** Replay the committed line/cubic snapshot into the current Canvas path. */
4042
4027
  buildResolvedPath(ctx) {
@@ -4110,7 +4095,6 @@ var Shape = class extends Element {
4110
4095
  const root = this.activeRoot;
4111
4096
  const borderColor = isUndefined(configured) ? root ? root.skin.shapeStroke : SKIN_DEFAULTS.shapeStroke : configured;
4112
4097
  if (!borderColor || !this.borderWidth) return;
4113
- this.buildBorderPath(ctx);
4114
4098
  if (isGradient(borderColor)) ctx.strokeStyle = createCanvasGradient(ctx, borderColor, this.width ?? 0, this.height ?? 0);
4115
4099
  else ctx.strokeStyle = borderColor;
4116
4100
  ctx.save();
@@ -4,7 +4,7 @@ import { Node } from "./node";
4
4
  import { ElementTransform, type ElementTransformClass } from "./element-transform";
5
5
  import { ChangeImpact, TransformProperties, type PropertyChanges, type PreparedPropertyUpdate, type TransformableOptions } from "./properties";
6
6
  import { Tween } from "@tweenjs/tween.js";
7
- import type { ConnectionPortConfiguration, DerivedConnectionPort, ResolvedConnectionPort, ResolvedConnectionPortSlot } from "../utils/connection-port";
7
+ import type { ConnectionPortConfiguration, ConnectionPortGeometry, DerivedConnectionPort, ResolvedConnectionPort, ResolvedConnectionPortSlot } from "../utils/connection-port";
8
8
  import type { Root } from "../root";
9
9
  import type { Layer } from "../layer";
10
10
  import type { OwnerPropertyChange } from "../mutation/change-batch";
@@ -251,6 +251,8 @@ export declare class Element<P extends ElementProperties = ElementProperties, O
251
251
  * @param ratio 从边起点到终点的比例,通常在 `0..1`。
252
252
  */
253
253
  getConnectionPortPosition(edge: Edge, ratio: number): PointView;
254
+ /** @internal 一次产出 custom Port position、法线与 label edge 长度。 */
255
+ getConnectionPortGeometry(edge: Edge, ratio: number): ConnectionPortGeometry;
254
256
  /** @internal 一次借用 owner 当前 configured source 的指定字段。 */
255
257
  static readConfiguredPropertyValues(owner: Element, targets: ReadonlyMap<string, {
256
258
  value: unknown;
@@ -31,35 +31,20 @@ export declare function clonePropertyValue<T>(value: T): T;
31
31
  export type PropertyChanges = ReadonlyMap<string, unknown>;
32
32
  /** 属性名到本次提交最终值的只读变化表。 */
33
33
  export type PropertyUpdates = ReadonlyMap<string, unknown>;
34
- /** Node 持有的属性状态,以及显式生成独立结果的快照操作。 */
34
+ /** Node 持有的属性状态,并提供显式独立快照。 */
35
35
  export declare class NodeProperties {
36
36
  [CONFIGURED_STATE]: Record<string, unknown> | null;
37
37
  [RUNTIME_OVERRIDE_STATE]: Record<string, unknown> | null;
38
- /** 在新 Properties 实例上应用 patch,并返回旧值变化表。 */
39
- with<T extends NodeProperties>(this: T, patch: Record<string, unknown>): {
40
- /** 属性名到修改前值的变化表。 */
41
- changes: Map<string, unknown>;
42
- /** 应用 patch 后的新 Properties。 */
43
- properties: T;
44
- };
45
38
  /** 返回可独立修改的完整属性快照。 */
46
39
  snapshot(): Record<string, unknown>;
47
- /** 判断快照与当前属性是否语义相同。 */
48
- matches(snapshot: Record<string, unknown>): boolean;
49
40
  /** 返回省略默认值和函数的可序列化属性。 */
50
41
  toJSON(): Record<string, unknown>;
51
42
  /** 将属性变化映射为提交影响,供派生 Properties 扩展。 */
52
43
  getImpact(_changes: PropertyChanges, _updates?: PropertyUpdates): ChangeImpact;
53
- /** 返回当前属性的独立完整值对象。 */
54
- protected values(): {
55
- [k: string]: any;
56
- };
57
44
  /** 读取把 runtime 字段还原为 configured 后的当前作者值。 */
58
45
  private configuredValue;
59
46
  /** 返回把 runtime 字段还原为 configured 后的独立完整值。 */
60
47
  private configuredValues;
61
- /** 使用当前运行时构造器创建同类型 Properties。 */
62
- private create;
63
48
  }
64
49
  /** @internal 借用单个字段当前的 configured source 值。 */
65
50
  export declare function getConfiguredPropertyValue(properties: NodeProperties, key: string): any;
@@ -3,6 +3,7 @@ import { ElementTransform, type ElementTransformClass } from "./element-transfor
3
3
  import { ChangeImpact, type PropertyChanges } from "./properties";
4
4
  import { type BackgroundColor, type Edge, type PointView } from "@fulate/share";
5
5
  import { type GeometryQuery, type GeometrySnapshot } from "../geometry";
6
+ import type { ConnectionPortGeometry } from "../utils/connection-port";
6
7
  /** border 相对内容轮廓的绘制位置。 */
7
8
  export type BorderPosition = "inside" | "outside";
8
9
  /** Shape 阴影样式。 */
@@ -113,8 +114,14 @@ export declare class Shape<P extends ShapeProperties = ShapeProperties, O extend
113
114
  /**
114
115
  * 返回某条逻辑边上 ratio 处的局部坐标和朝外法线。
115
116
  * 默认实现基于 getEdgeSegment 做线性插值;圆形等曲线形状可直接覆写。
117
+ * Core owner 已取得 segment 时可传入它,避免重复遍历;普通调用省略该参数。
116
118
  */
117
- getEdgePosition(edge: Edge, ratio: number): {
119
+ getEdgePosition(edge: Edge, ratio: number, segment?: {
120
+ /** 线段起点。 */
121
+ readonly start: PointView;
122
+ /** 线段终点。 */
123
+ readonly end: PointView;
124
+ }): {
118
125
  /** ratio 对应的边上位置。 */
119
126
  pos: PointView;
120
127
  /** 朝外法线的 x 分量。 */
@@ -122,7 +129,15 @@ export declare class Shape<P extends ShapeProperties = ShapeProperties, O extend
122
129
  /** 朝外法线的 y 分量。 */
123
130
  ny: number;
124
131
  };
125
- getConnectionPortPosition(edge: Edge, ratio: number): PointView;
132
+ private getEdgePositionFromSegment;
133
+ /** @internal 一次产出 custom Port 的位置、法线与 label edge 长度。 */
134
+ getConnectionPortGeometry(edge: Edge, ratio: number): ConnectionPortGeometry;
135
+ /** 在组合 Port geometry 时可复用同一轮已解析的 edge position。 */
136
+ getConnectionPortPosition(edge: Edge, ratio: number, edgePosition?: {
137
+ readonly pos: PointView;
138
+ readonly nx: number;
139
+ readonly ny: number;
140
+ }): PointView;
126
141
  /** Replay the committed line/cubic snapshot into the current Canvas path. */
127
142
  protected buildResolvedPath(ctx: CanvasRenderingContext2D): void;
128
143
  paint(ctx?: CanvasRenderingContext2D): void;
@@ -1,4 +1,4 @@
1
- import type { Edge, PointView } from "@fulate/share";
1
+ import { type Edge, type PointView } from "@fulate/share";
2
2
  import type { Element } from "../node/element";
3
3
  /** Element owner 内稳定、由调用者直接提供的普通字符串 Port ID。 */
4
4
  export type ConnectionPortId = string;
@@ -57,6 +57,13 @@ export interface ResolvedConnectionPort {
57
57
  readonly origin: "builtin" | "custom";
58
58
  readonly definition?: ConnectionPortDefinition;
59
59
  }
60
+ /** 一次解析 Port 时由几何 owner 产出的 label 所需 edge facts。 @internal */
61
+ export interface ConnectionPortGeometry {
62
+ readonly position: PointView;
63
+ readonly nx: number;
64
+ readonly ny: number;
65
+ readonly edgeLength: number;
66
+ }
60
67
  /** custom Port decoration 需要的边布局信息。 @internal */
61
68
  export interface ResolvedConnectionPortSlot extends ResolvedConnectionPort {
62
69
  readonly origin: "custom";
@@ -64,6 +71,8 @@ export interface ResolvedConnectionPortSlot extends ResolvedConnectionPort {
64
71
  readonly edge: Edge;
65
72
  readonly ratio: number;
66
73
  readonly edgeCount: number;
74
+ /** 与 `localPosition` 同一份 owner-produced geometry 结果。 */
75
+ readonly geometry: ConnectionPortGeometry;
67
76
  }
68
77
  /** top、right、bottom、left 四条逻辑边中点组成的默认 Port。 */
69
78
  export declare const DEFAULT_CONNECTION_PORT_SCHEMA: readonly DerivedConnectionPort[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fulate/core",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",