@fulate/core 1.0.6 → 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/dist/index.d.ts CHANGED
@@ -3,12 +3,12 @@ export type { EventName, EventCallback, FulateEvent, FulateEventDetail, CustomEv
3
3
  export { Node, NodeLifecycleEvent } from "./node/node";
4
4
  export type { NodeCreationOptions } from "./node/node";
5
5
  export { ElementTransform } from "./node/element-transform";
6
- export type { ElementTransformClass, ElementTransformConstructor, GeometryBoundsView } from "./node/element-transform";
6
+ export type { ElementTransformClass, GeometryBoundsView } from "./node/element-transform";
7
7
  export { ChangeImpact, NodeProperties, resolveOrigin, TransformProperties } from "./node/properties";
8
8
  export type { OriginX, OriginY, PropertyChanges, PropertyUpdates, TransformableOptions } from "./node/properties";
9
9
  export { Element, ElementProperties } from "./node/element";
10
10
  export type { AnyElement, BaseElementOption, ElementJSON, ElementOptionPatch, AnimateOptions, AnimateTarget } from "./node/element";
11
- export { isReparentTransformIntent, resolveReferenceTransform, resolveReferenceTransformTarget } from "./node/reference-transform";
11
+ export { isReparentTransformIntent, resolveAuthoredGeometryFramePatch, resolveReferenceTransform, resolveReferenceTransformTarget } from "./node/reference-transform";
12
12
  export type { ElementTransformBaseline, ReferenceTransformBaseline, ReferenceTransformFrame, ReferenceTransformResolution, ReferenceTransformTarget, ReparentTransformIntent, TransformIntent } from "./node/reference-transform";
13
13
  export { DEFAULT_CONNECTION_PORT_SCHEMA, getElementConnectionPort, getElementConnectionPortPoint, getElementConnectionPortPoints, projectConnectionPortPoint } from "./utils/connection-port";
14
14
  export type { ConnectionPortId, ConnectionPortConfiguration, ConnectionPortDefinition, ConnectionPortLabelStyle, ConnectionPortDirection, ConnectionPortLimit, ConnectionPortLimits, ConnectionPortRef, DerivedConnectionPort, EdgeConnectionPortPosition, ResolvedConnectionPort, ResolvedConnectionPortSlot } from "./utils/connection-port";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { clamp, isEqual, isFunction, isNull, isNumber, isObjectLike, isString, isUndefined } from "lodash-es";
2
- import { Bound, Intersection, clipPolygonToRect, createCanvasGradient, createPointView, formatPremultipliedColor, isGradient, makeBoundingBoxFromRects, parsePremultipliedColor, qrDecompose, rectEdgePosition, transformPoint } from "@fulate/share";
2
+ import { Bound, Intersection, clipPolygonToRect, createCanvasGradient, createPointView, formatPremultipliedColor, isGradient, isIdentityMatrix, makeBoundingBoxFromRects, parsePremultipliedColor, qrDecompose, rectEdgePosition, transformPoint } from "@fulate/share";
3
3
  import RBush from "rbush";
4
4
  import { Easing, Group, Tween } from "@tweenjs/tween.js";
5
5
  //#region packages/core/src/event.ts
@@ -1790,6 +1790,36 @@ function createGeometryQuery(snapshot) {
1790
1790
  return snapshot instanceof RectangleGeometrySnapshot ? new RectangleSnapshotGeometryQuery(snapshot) : new SnapshotGeometryQuery(snapshot);
1791
1791
  }
1792
1792
  //#endregion
1793
+ //#region packages/core/src/node/element-transform-geometry.ts
1794
+ /** Project a local rectangle into a matrix space and return its AABB. */
1795
+ function projectBounds(matrix, bounds) {
1796
+ if (!bounds) return new Bound();
1797
+ const left = bounds.left;
1798
+ const top = bounds.top;
1799
+ const right = left + bounds.width;
1800
+ const bottom = top + bounds.height;
1801
+ if (matrix.b === 0 && matrix.c === 0) {
1802
+ const firstX = matrix.a * left + matrix.e;
1803
+ const secondX = matrix.a * right + matrix.e;
1804
+ const firstY = matrix.d * top + matrix.f;
1805
+ const secondY = matrix.d * bottom + matrix.f;
1806
+ return new Bound(Math.min(firstX, secondX), Math.min(firstY, secondY), Math.max(firstX, secondX), Math.max(firstY, secondY));
1807
+ }
1808
+ const x0 = matrix.a * left + matrix.c * top + matrix.e;
1809
+ const x1 = matrix.a * right + matrix.c * top + matrix.e;
1810
+ const x2 = matrix.a * right + matrix.c * bottom + matrix.e;
1811
+ const x3 = matrix.a * left + matrix.c * bottom + matrix.e;
1812
+ const y0 = matrix.b * left + matrix.d * top + matrix.f;
1813
+ const y1 = matrix.b * right + matrix.d * top + matrix.f;
1814
+ const y2 = matrix.b * right + matrix.d * bottom + matrix.f;
1815
+ const y3 = matrix.b * left + matrix.d * bottom + matrix.f;
1816
+ return new Bound(Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3), Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3));
1817
+ }
1818
+ /** Compare a committed matrix with a previous scalar matrix sample. */
1819
+ function matrixChanged(matrix, previousA, previousB, previousC, previousD, previousE, previousF) {
1820
+ return matrix.a !== previousA || matrix.b !== previousB || matrix.c !== previousC || matrix.d !== previousD || matrix.e !== previousE || matrix.f !== previousF;
1821
+ }
1822
+ //#endregion
1793
1823
  //#region packages/core/src/node/element-transform.ts
1794
1824
  var VISUAL_GEOMETRY_IMPACT$1 = ChangeImpact.LocalGeometry | ChangeImpact.WorldTransform | ChangeImpact.VisualBounds;
1795
1825
  var GEOMETRY_CACHE_IMPACT = ChangeImpact.LocalGeometry | ChangeImpact.WorldTransform | ChangeImpact.VisualBounds | ChangeImpact.HitBounds;
@@ -2051,7 +2081,7 @@ var ElementTransform = class {
2051
2081
  /** 清除 world 投影;world-dependent local geometry 只在矩阵实际变化时失效。 */
2052
2082
  finishWorldMatrixCommit(previousA, previousB, previousC, previousD, previousE, previousF) {
2053
2083
  const matrix = this.worldMatrix;
2054
- const changed = matrix.a !== previousA || matrix.b !== previousB || matrix.c !== previousC || matrix.d !== previousD || matrix.e !== previousE || matrix.f !== previousF;
2084
+ const changed = matrixChanged(matrix, previousA, previousB, previousC, previousD, previousE, previousF);
2055
2085
  if (this.geometrySnapshot && this.worldMatrixAffectsLocalGeometry() && changed) this.invalidateCache(ChangeImpact.LocalGeometry);
2056
2086
  else this.clearWorldProjection();
2057
2087
  return changed;
@@ -2224,27 +2254,7 @@ var ElementTransform = class {
2224
2254
  }
2225
2255
  /** 将 local AABB 四角投影到指定 reference matrix。 */
2226
2256
  toBounds(matrix, bounds) {
2227
- if (!bounds) return new Bound();
2228
- const left = bounds.left;
2229
- const top = bounds.top;
2230
- const right = left + bounds.width;
2231
- const bottom = top + bounds.height;
2232
- if (matrix.b === 0 && matrix.c === 0) {
2233
- const firstX = matrix.a * left + matrix.e;
2234
- const secondX = matrix.a * right + matrix.e;
2235
- const firstY = matrix.d * top + matrix.f;
2236
- const secondY = matrix.d * bottom + matrix.f;
2237
- return new Bound(Math.min(firstX, secondX), Math.min(firstY, secondY), Math.max(firstX, secondX), Math.max(firstY, secondY));
2238
- }
2239
- const x0 = matrix.a * left + matrix.c * top + matrix.e;
2240
- const x1 = matrix.a * right + matrix.c * top + matrix.e;
2241
- const x2 = matrix.a * right + matrix.c * bottom + matrix.e;
2242
- const x3 = matrix.a * left + matrix.c * bottom + matrix.e;
2243
- const y0 = matrix.b * left + matrix.d * top + matrix.f;
2244
- const y1 = matrix.b * right + matrix.d * top + matrix.f;
2245
- const y2 = matrix.b * right + matrix.d * bottom + matrix.f;
2246
- const y3 = matrix.b * left + matrix.d * bottom + matrix.f;
2247
- return new Bound(Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3), Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3));
2257
+ return projectBounds(matrix, bounds);
2248
2258
  }
2249
2259
  /** 惰性构建当前快照对应的 local 绘制轮廓。 */
2250
2260
  getVisualOutline() {
@@ -2484,7 +2494,7 @@ var ElementTransform = class {
2484
2494
  const element = this.element;
2485
2495
  const cacheable = !this.needsCommit && !this.hasDirtyDescendant && element.activeRoot?.frameRuntime.isStabilizingGeometry !== true;
2486
2496
  if (cacheable && !isUndefined(this.currentRenderCoverageBounds)) return this.currentRenderCoverageBounds;
2487
- const ownBounds = this.hasGeometry() ? this.getWorldVisualBounds() : null;
2497
+ const ownBounds = this.hasGeometry() ? this.getWorldVisualBoundsView() : null;
2488
2498
  let descendantBounds = null;
2489
2499
  for (const child of element.children) {
2490
2500
  if (!child.visible) continue;
@@ -2506,7 +2516,7 @@ var ElementTransform = class {
2506
2516
  descendantBounds.maxY = Math.min(descendantBounds.maxY, clipBounds.maxY);
2507
2517
  if (descendantBounds.minX >= descendantBounds.maxX || descendantBounds.minY >= descendantBounds.maxY) descendantBounds = null;
2508
2518
  }
2509
- const bounds = ownBounds && descendantBounds ? ownBounds.merge(descendantBounds) : ownBounds ?? descendantBounds;
2519
+ const bounds = ownBounds && descendantBounds ? new Bound(ownBounds.minX, ownBounds.minY, ownBounds.maxX, ownBounds.maxY).merge(descendantBounds) : ownBounds ?? descendantBounds;
2510
2520
  if (cacheable) this.currentRenderCoverageBounds = bounds;
2511
2521
  return bounds;
2512
2522
  }
@@ -3046,7 +3056,7 @@ var ElementProperties = class extends TransformProperties {
3046
3056
  }
3047
3057
  };
3048
3058
  /** 可变换、可绘制、可命中并支持事件和动画的场景节点。 */
3049
- var Element = class extends Node {
3059
+ var Element = class Element extends Node {
3050
3060
  type = "element";
3051
3061
  /** 当前 Element 唯一的变换与几何运行时。 */
3052
3062
  transform;
@@ -3158,6 +3168,7 @@ var Element = class extends Node {
3158
3168
  * @param value `false`、`true` 或 custom definitions。
3159
3169
  */
3160
3170
  replacePorts(value) {
3171
+ if (this.ports === value && Element.getConfiguredPropertyValue(this, "ports") === value) return this;
3161
3172
  this.commitConfiguredOptions({ ports: value });
3162
3173
  return this;
3163
3174
  }
@@ -3509,11 +3520,27 @@ var Element = class extends Node {
3509
3520
  }
3510
3521
  /** @internal 一次产出 custom Port position、法线与 label edge 长度。 */
3511
3522
  getConnectionPortGeometry(edge, ratio) {
3512
- const resolved = rectEdgePosition(this.width ?? 0, this.height ?? 0, edge, ratio);
3523
+ const position = this.getConnectionPortPosition(edge, ratio);
3524
+ let nx = 0;
3525
+ let ny = -1;
3526
+ switch (edge) {
3527
+ case "right":
3528
+ nx = 1;
3529
+ ny = 0;
3530
+ break;
3531
+ case "bottom":
3532
+ nx = 0;
3533
+ ny = 1;
3534
+ break;
3535
+ case "left":
3536
+ nx = -1;
3537
+ ny = 0;
3538
+ break;
3539
+ }
3513
3540
  return {
3514
- position: this.getConnectionPortPosition(edge, ratio),
3515
- nx: resolved.nx,
3516
- ny: resolved.ny,
3541
+ position,
3542
+ nx,
3543
+ ny,
3517
3544
  edgeLength: edge === "top" || edge === "bottom" ? Math.abs(this.width ?? 0) : Math.abs(this.height ?? 0)
3518
3545
  };
3519
3546
  }
@@ -3665,7 +3692,7 @@ var IDENTITY_REFERENCE_TRANSFORM = new DOMMatrix();
3665
3692
  */
3666
3693
  function resolveSparseReferenceTranslation(baseline, intent, targetParentWorldMatrix) {
3667
3694
  const delta = intent.worldDelta;
3668
- if (delta.a !== 1 || delta.b !== 0 || delta.c !== 0 || delta.d !== 1 || !sameLinearMatrix(baseline.startParentWorldMatrix, targetParentWorldMatrix)) return null;
3695
+ if (!sameLinearMatrix(delta, IDENTITY_REFERENCE_TRANSFORM) || !sameLinearMatrix(baseline.startParentWorldMatrix, targetParentWorldMatrix)) return null;
3669
3696
  const worldX = delta.e + baseline.startParentWorldMatrix.e - targetParentWorldMatrix.e;
3670
3697
  const worldY = delta.f + baseline.startParentWorldMatrix.f - targetParentWorldMatrix.f;
3671
3698
  const parentInverse = baseline.startParentWorldInverseMatrix;
@@ -3683,6 +3710,107 @@ function sameMatrix(left, right) {
3683
3710
  function sameLinearMatrix(left, right) {
3684
3711
  return left.a === right.a && left.b === right.b && left.c === right.c && left.d === right.d;
3685
3712
  }
3713
+ function createFrameLinearMatrix(frame) {
3714
+ return new DOMMatrix().rotate(0, 0, frame.angle).skewX(frame.skewX).skewY(frame.skewY).scale(frame.scaleX, frame.scaleY);
3715
+ }
3716
+ function transformReferenceBounds(bounds, matrix) {
3717
+ const right = bounds.left + bounds.width;
3718
+ const bottom = bounds.top + bounds.height;
3719
+ const corners = [
3720
+ transformPoint(matrix, {
3721
+ x: bounds.left,
3722
+ y: bounds.top
3723
+ }),
3724
+ transformPoint(matrix, {
3725
+ x: right,
3726
+ y: bounds.top
3727
+ }),
3728
+ transformPoint(matrix, {
3729
+ x: right,
3730
+ y: bottom
3731
+ }),
3732
+ transformPoint(matrix, {
3733
+ x: bounds.left,
3734
+ y: bottom
3735
+ })
3736
+ ];
3737
+ let minX = corners[0].x;
3738
+ let minY = corners[0].y;
3739
+ let maxX = corners[0].x;
3740
+ let maxY = corners[0].y;
3741
+ for (let index = 1; index < corners.length; index++) {
3742
+ const point = corners[index];
3743
+ minX = Math.min(minX, point.x);
3744
+ minY = Math.min(minY, point.y);
3745
+ maxX = Math.max(maxX, point.x);
3746
+ maxY = Math.max(maxY, point.y);
3747
+ }
3748
+ return {
3749
+ left: minX,
3750
+ top: minY,
3751
+ width: maxX - minX,
3752
+ height: maxY - minY
3753
+ };
3754
+ }
3755
+ /**
3756
+ * Factor a target local affine transform through the existing authored frame.
3757
+ * The frame keeps its current pose; the complete linear delta is written into
3758
+ * the vector's authored points. Translation remains a frame left/top patch so
3759
+ * no synthetic point offset is introduced into the canonical geometry.
3760
+ */
3761
+ function resolveBakedAuthoredGeometryTransform(baseline, target) {
3762
+ const frameLinear = createFrameLinearMatrix(baseline.frame);
3763
+ const determinant = frameLinear.a * frameLinear.d - frameLinear.b * frameLinear.c;
3764
+ if (!Number.isFinite(determinant) || determinant === 0) return null;
3765
+ const targetLocal = target.parentInverseMatrix.multiply(target.worldMatrix);
3766
+ const targetLinear = new DOMMatrix([
3767
+ targetLocal.a,
3768
+ targetLocal.b,
3769
+ targetLocal.c,
3770
+ targetLocal.d,
3771
+ 0,
3772
+ 0
3773
+ ]);
3774
+ const authoredLocalDelta = frameLinear.inverse().multiply(targetLinear);
3775
+ const nextBounds = transformReferenceBounds(baseline.referenceBounds, authoredLocalDelta);
3776
+ const offsetX = nextBounds.left + (resolveOrigin(baseline.frame.originX) + .5) * nextBounds.width;
3777
+ const offsetY = nextBounds.top + (resolveOrigin(baseline.frame.originY) + .5) * nextBounds.height;
3778
+ return {
3779
+ framePatch: {
3780
+ left: targetLocal.e - offsetX + frameLinear.a * offsetX + frameLinear.c * offsetY,
3781
+ top: targetLocal.f - offsetY + frameLinear.b * offsetX + frameLinear.d * offsetY,
3782
+ angle: baseline.frame.angle,
3783
+ scaleX: baseline.frame.scaleX,
3784
+ scaleY: baseline.frame.scaleY,
3785
+ skewX: baseline.frame.skewX,
3786
+ skewY: baseline.frame.skewY
3787
+ },
3788
+ authoredLocalDelta
3789
+ };
3790
+ }
3791
+ /**
3792
+ * 用 owner 已算出的精确 authored bounds 修正 bake frame 的位置。
3793
+ * AABB 经过旋转/错切后不能由原 bounds 四角精确推出;owner 在生成
3794
+ * 新几何后把真实 bounds 交回这里,保持 frame world matrix 与目标一致。
3795
+ */
3796
+ function resolveAuthoredGeometryFramePatch(baseline, resolution, authoredBounds) {
3797
+ if (isIdentityMatrix(resolution.authoredLocalDelta)) return resolution.framePatch;
3798
+ const approximateBounds = transformReferenceBounds(baseline.referenceBounds, resolution.authoredLocalDelta);
3799
+ const originX = resolveOrigin(baseline.frame.originX) + .5;
3800
+ const originY = resolveOrigin(baseline.frame.originY) + .5;
3801
+ const approximateOffsetX = approximateBounds.left + originX * approximateBounds.width;
3802
+ const approximateOffsetY = approximateBounds.top + originY * approximateBounds.height;
3803
+ const authoredOffsetX = authoredBounds.left + originX * authoredBounds.width;
3804
+ const authoredOffsetY = authoredBounds.top + originY * authoredBounds.height;
3805
+ const deltaX = authoredOffsetX - approximateOffsetX;
3806
+ const deltaY = authoredOffsetY - approximateOffsetY;
3807
+ const frameLinear = createFrameLinearMatrix(baseline.frame);
3808
+ return {
3809
+ ...resolution.framePatch,
3810
+ left: (resolution.framePatch.left ?? baseline.frame.left) + -deltaX + frameLinear.a * deltaX + frameLinear.c * deltaY,
3811
+ top: (resolution.framePatch.top ?? baseline.frame.top) + -deltaY + frameLinear.b * deltaX + frameLinear.d * deltaY
3812
+ };
3813
+ }
3686
3814
  /** 判断变换意图是否包含跨父级重挂载所需的目标父级矩阵。 */
3687
3815
  function isReparentTransformIntent(intent) {
3688
3816
  return "targetParentWorldMatrix" in intent;
@@ -3694,10 +3822,14 @@ function resolveTargetParentWorldMatrix(baseline, intent) {
3694
3822
  function targetsFrozenReference(baseline, intent) {
3695
3823
  const delta = intent.worldDelta;
3696
3824
  const targetParentWorldMatrix = resolveTargetParentWorldMatrix(baseline, intent);
3697
- return delta.a === 1 && delta.b === 0 && delta.c === 0 && delta.d === 1 && delta.e === 0 && delta.f === 0 && sameMatrix(baseline.startParentWorldMatrix, targetParentWorldMatrix);
3825
+ return isIdentityMatrix(delta) && sameMatrix(baseline.startParentWorldMatrix, targetParentWorldMatrix);
3698
3826
  }
3699
3827
  /** 从已持有的最终 world pose 纯计算 frame/authored delta。 */
3700
- function resolveReferenceTransformTargetWithMode(baseline, target, preserveOwnedGeometry, unwrappedRotationDelta) {
3828
+ function resolveReferenceTransformTargetWithMode(baseline, target, preserveOwnedGeometry, unwrappedRotationDelta, bakeGeometry = false) {
3829
+ if (bakeGeometry && baseline.writeMode === "authored-geometry" && !preserveOwnedGeometry) {
3830
+ const baked = resolveBakedAuthoredGeometryTransform(baseline, target);
3831
+ if (baked) return baked;
3832
+ }
3701
3833
  const targetDecomposition = qrDecompose(target.parentInverseMatrix.multiply(target.worldMatrix));
3702
3834
  const targetAngle = unwrappedRotationDelta === void 0 ? targetDecomposition.angle : targetDecomposition.angle + 360 * Math.round((baseline.frame.angle + unwrappedRotationDelta - targetDecomposition.angle) / 360);
3703
3835
  const authoredScaleX = preserveOwnedGeometry ? 1 : baseline.frame.scaleX === 0 ? 1 : Math.abs(targetDecomposition.scaleX / baseline.frame.scaleX);
@@ -3792,7 +3924,7 @@ function resolveReferenceTransform(baseline, intent) {
3792
3924
  worldMatrix: intent.worldDelta.multiply(baseline.startWorldMatrix),
3793
3925
  worldCenter: transformPoint(intent.worldDelta, startWorldCenter),
3794
3926
  parentInverseMatrix: sameMatrix(baseline.startParentWorldMatrix, targetParentWorldMatrix) ? baseline.startParentWorldInverseMatrix : targetParentWorldMatrix.inverse()
3795
- }, preserveOwnedGeometry, intent.unwrappedRotationDelta);
3927
+ }, preserveOwnedGeometry, intent.unwrappedRotationDelta, intent.bakeGeometry);
3796
3928
  }
3797
3929
  //#endregion
3798
3930
  //#region packages/core/src/node/shape.ts
@@ -4143,20 +4275,24 @@ var Shape = class extends Element {
4143
4275
  //#region packages/core/src/root/hit-test.ts
4144
4276
  /** Fixed screen-space broad-phase aperture for scene point affordances. */
4145
4277
  var POINTER_QUERY_RADIUS = 8;
4146
- /** 判断 world 点是否通过元素所有 ancestor child clip */
4147
- function isPointWithinAncestorClips(element, point) {
4278
+ /** 合并 pointer ancestor 状态与 child clip 的单次祖先遍历。 */
4279
+ function isPointerTargetWithinAncestorClips(root, element, point) {
4280
+ if (element.pickable === false || !isActiveVisibleNode(root, element) || element.silent) return false;
4148
4281
  let ancestor = element.parent instanceof Element ? element.parent : void 0;
4149
4282
  while (ancestor) {
4283
+ if (!isActiveVisibleNode(root, ancestor) || ancestor.silent) return false;
4150
4284
  if (ancestor.getChildClipRect()) {
4151
4285
  const localPoint = ancestor.transform.toLocal(point);
4152
4286
  if (!localPoint || !ancestor.containsChildClipPoint(localPoint)) return false;
4153
4287
  }
4288
+ if (ancestor.isLayerNode()) break;
4154
4289
  ancestor = ancestor.parent instanceof Element ? ancestor.parent : void 0;
4155
4290
  }
4156
4291
  return true;
4157
4292
  }
4158
4293
  /** 判断查询区域与元素 bounds 的交集是否仍通过 ancestor clips。 */
4159
- function hasAreaIntersection(element, area, boundsKind) {
4294
+ function hasAreaIntersection(root, element, area, boundsKind) {
4295
+ if (!isActiveVisibleNode(root, element)) return false;
4160
4296
  const bounds = boundsKind === "visual" ? element.transform.getWorldVisualBoundsView() : element.transform.getHitTestBounds();
4161
4297
  if (!Intersection.isAABBIntersect(area, bounds)) return false;
4162
4298
  const left = Math.max(area.left, bounds.minX);
@@ -4183,6 +4319,7 @@ function hasAreaIntersection(element, area, boundsKind) {
4183
4319
  ];
4184
4320
  let ancestor = element.parent instanceof Element ? element.parent : void 0;
4185
4321
  while (ancestor) {
4322
+ if (!isActiveVisibleNode(root, ancestor)) return false;
4186
4323
  const clip = ancestor.getChildClipRect();
4187
4324
  if (clip) {
4188
4325
  const localIntersection = [];
@@ -4195,6 +4332,7 @@ function hasAreaIntersection(element, area, boundsKind) {
4195
4332
  if (clipped.length === 0) return false;
4196
4333
  intersection = clipped.map((point) => ancestor.transform.toWorld(point));
4197
4334
  }
4335
+ if (ancestor.isLayerNode()) break;
4198
4336
  ancestor = ancestor.parent instanceof Element ? ancestor.parent : void 0;
4199
4337
  }
4200
4338
  return true;
@@ -4202,16 +4340,6 @@ function hasAreaIntersection(element, area, boundsKind) {
4202
4340
  function isActiveVisibleNode(root, element) {
4203
4341
  return element.isActive && !element.isUnmounted && element.activeRoot === root && element.visible;
4204
4342
  }
4205
- /** 判断元素及其祖先是否参与当前 Root 的区域选择。 */
4206
- function isAreaTarget(root, element) {
4207
- let node = element;
4208
- while (node) {
4209
- if (!isActiveVisibleNode(root, node)) return false;
4210
- if (node.isLayerNode()) break;
4211
- node = node.parent instanceof Element ? node.parent : void 0;
4212
- }
4213
- return true;
4214
- }
4215
4343
  /** 判断元素是否符合当前 Root 的 pointer target 语义。@internal */
4216
4344
  function isPointerTarget(root, element) {
4217
4345
  if (element.pickable === false) return false;
@@ -4248,8 +4376,7 @@ function* iteratePointElements(root, point, options = {}) {
4248
4376
  if (!element.isActive || element.isUnmounted || element.activeRoot !== root || element.layer !== layer || root.getPaintOrder(element) === void 0) continue;
4249
4377
  if (options.exclude?.has(element.key)) continue;
4250
4378
  if (scope && !isStrictDescendant(element, scope)) continue;
4251
- if (!isPointerTarget(root, element)) continue;
4252
- if (!isPointWithinAncestorClips(element, point)) continue;
4379
+ if (!isPointerTargetWithinAncestorClips(root, element, point)) continue;
4253
4380
  if (element.transform.hasPointHint(point)) yield element;
4254
4381
  }
4255
4382
  }
@@ -4338,7 +4465,7 @@ function queryAreaElements(root, area, options = {}) {
4338
4465
  for (let index = root.layers.length - 1; index >= 0; index--) {
4339
4466
  const hitElements = root.layers[index].queryCommittedElements(area, order);
4340
4467
  for (const element of hitElements) {
4341
- if (!isAreaTarget(root, element) || !isSelectable(element) || !hasAreaIntersection(element, area, bounds)) continue;
4468
+ if (!isSelectable(element) || !hasAreaIntersection(root, element, area, bounds)) continue;
4342
4469
  result.push(element);
4343
4470
  }
4344
4471
  }
@@ -6805,4 +6932,4 @@ var Layer = class Layer extends Element {
6805
6932
  }
6806
6933
  };
6807
6934
  //#endregion
6808
- export { ChangeImpact, CustomEvent, DEFAULT_CONNECTION_PORT_SCHEMA, EVENT_NAMES, Element, ElementProperties, ElementTransform, EventEmitter, FrameStabilizationError, Layer, LayerProperties, LayerTransform, Node, NodeLifecycleEvent, NodeProperties, Root, RootFrameRuntime, RootInputController, SKIN_DEFAULTS, SceneRegistry, Shape, ShapeProperties, ShapeTransform, Skin, TransformProperties, Viewport, createAuthoredLineSubpath, createGeometryQuery, createGeometrySnapshot, createLineSubpath, createPathLocalId, evaluatePathSegmentPoint, getElementConnectionPort, getElementConnectionPortPoint, getElementConnectionPortPoints, isFormControlTarget, isReparentTransformIntent, projectConnectionPortPoint, resolveAuthoredSubpath, resolveOrigin, resolveReferenceTransform, resolveReferenceTransformTarget, splitPathSegment };
6935
+ export { ChangeImpact, CustomEvent, DEFAULT_CONNECTION_PORT_SCHEMA, EVENT_NAMES, Element, ElementProperties, ElementTransform, EventEmitter, FrameStabilizationError, Layer, LayerProperties, LayerTransform, Node, NodeLifecycleEvent, NodeProperties, Root, RootFrameRuntime, RootInputController, SKIN_DEFAULTS, SceneRegistry, Shape, ShapeProperties, ShapeTransform, Skin, TransformProperties, Viewport, createAuthoredLineSubpath, createGeometryQuery, createGeometrySnapshot, createLineSubpath, createPathLocalId, evaluatePathSegmentPoint, getElementConnectionPort, getElementConnectionPortPoint, getElementConnectionPortPoints, isFormControlTarget, isReparentTransformIntent, projectConnectionPortPoint, resolveAuthoredGeometryFramePatch, resolveAuthoredSubpath, resolveOrigin, resolveReferenceTransform, resolveReferenceTransformTarget, splitPathSegment };
@@ -0,0 +1,5 @@
1
+ import { Bound, type Rect } from "@fulate/share";
2
+ /** Project a local rectangle into a matrix space and return its AABB. */
3
+ export declare function projectBounds(matrix: DOMMatrixReadOnly, bounds: Readonly<Rect> | null): Bound;
4
+ /** Compare a committed matrix with a previous scalar matrix sample. */
5
+ export declare function matrixChanged(matrix: DOMMatrixReadOnly, previousA: number, previousB: number, previousC: number, previousD: number, previousE: number, previousF: number): boolean;
@@ -7,10 +7,11 @@ import { ChangeImpact } from "./properties";
7
7
  export type GeometryBoundsView = Readonly<Rect & BoundingBox>;
8
8
  /** 一次 Layer damage 收集共享的子树 visual bounds memo。 @internal */
9
9
  export type DirtyBoundsMemo = Map<ElementTransform, Bound | null>;
10
- /** Element 构造链传递的 companion Transform constructor。 */
11
- export type ElementTransformConstructor<E extends Element = Element> = new (element: E) => ElementTransform<E>;
12
- /** Element 继承构造链内部使用的 existential companion constructor */
13
- export type ElementTransformClass = new (element: never) => ElementTransform;
10
+ /**
11
+ * Element 继承构造链内部使用的 companion constructor。默认参数保留
12
+ * existential 入口;具体 Element 子类可以同时声明 owner Transform
13
+ */
14
+ export type ElementTransformClass<E extends Element = Element, T extends ElementTransform<any> = ElementTransform<any>> = new (element: E) => T;
14
15
  /**
15
16
  * 一个 Element 唯一的变换、几何、damage 与子树派生状态 owner。
16
17
  *
@@ -34,6 +34,11 @@ export interface TransformIntent {
34
34
  readonly worldDelta: DOMMatrixReadOnly;
35
35
  /** 连续旋转手势从 frozen baseline 累积的未折叠 world 角度。 */
36
36
  readonly unwrappedRotationDelta?: number;
37
+ /**
38
+ * Resize/flip 这类矢量编辑操作把线性变换写回 authored geometry,
39
+ * 而不是把它留在容器的 scale 上。普通 move/rotate 保持原有 pose owner。
40
+ */
41
+ readonly bakeGeometry?: boolean;
37
42
  }
38
43
  /** 只有显式 reparent 才携带的新 parent world reference。 */
39
44
  export interface ReparentTransformIntent extends TransformIntent {
@@ -50,6 +55,12 @@ export interface ReferenceTransformResolution {
50
55
  readonly framePatch: Readonly<ElementOptionPatch>;
51
56
  readonly authoredLocalDelta: DOMMatrixReadOnly;
52
57
  }
58
+ /**
59
+ * 用 owner 已算出的精确 authored bounds 修正 bake frame 的位置。
60
+ * AABB 经过旋转/错切后不能由原 bounds 四角精确推出;owner 在生成
61
+ * 新几何后把真实 bounds 交回这里,保持 frame world matrix 与目标一致。
62
+ */
63
+ export declare function resolveAuthoredGeometryFramePatch(baseline: Readonly<Pick<ReferenceTransformBaseline, "referenceBounds" | "frame">>, resolution: Readonly<ReferenceTransformResolution>, authoredBounds: Readonly<Rect>): Readonly<ElementOptionPatch>;
53
64
  /** 判断变换意图是否包含跨父级重挂载所需的目标父级矩阵。 */
54
65
  export declare function isReparentTransformIntent(intent: Readonly<TransformIntent | ReparentTransformIntent>): intent is Readonly<ReparentTransformIntent>;
55
66
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fulate/core",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",