@fulate/tools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # @fulate/tools Editor integration
2
+
3
+ > 文档角色:`GUIDE`。权威来源:Tools 源码 TSDoc 与测试;Editor 的跨域长期契约见 [`docs/specs/editor-interaction.md`](../../docs/specs/editor-interaction.md)。
4
+
5
+ 每个活动 Root 只有一个 `EditorInteractionController`。它拥有 selection、输入仲裁、Transform/Point
6
+ gesture、Content session、Line/LineTree branch draft 与 semantic History 边界;`Select` 只保留 command/paint
7
+ surface,`LineTool` 只保留 command/preview surface。Transform 的每个已派发 pointer 输入都立即从
8
+ frozen baseline 解析并提交一个 trusted batch;Point gesture 继续只在每个显示帧消费最后一个 intent。
9
+ 每个 owner 都把 world delta 交给同一个 Core reference-transform kernel;普通 transform 与显式
10
+ reparent intent由该 intent决定写 pose、resize-owned geometry或preserve-owned geometry。Group手势只写
11
+ Group,DirectChild手势只写该child;derived content bounds不形成command、History或补偿写入。
12
+ Line/LineTree固定world paint padding只属于自身visual/render coverage,不进入Group selection、resize或
13
+ snap bounds。所有工作复用现有`RootFrameRuntime`,Tools 不创建 RAF、timer 或第二 scheduler。
14
+ `Select`、`Snap`、`Rule` 与 `LineTool` 都使用 Core 生成的普通 Node key。工具间协作由应用组合时交付
15
+ 的对象引用完成,不把固定 key 当作 Root 服务定位:
16
+
17
+ ```ts
18
+ const snap = new Snap();
19
+ const lineTool = new LineTool({ contentLayer });
20
+ const select = new Select({ snap, lineTool });
21
+ const rule = new Rule({ select });
22
+ const editorLayer = new EditorLayer({
23
+ children: [select, snap, rule, lineTool]
24
+ });
25
+ ```
26
+
27
+ DirectChild开始axis Snap时,手势排除集按候选使用的同一selection-owner规则解析;包含该child的
28
+ ancestor Group不会成为自身拖拽候选,Group外部元素仍正常参与对齐。
29
+
30
+ LineTree Point改变一个anchor时,即使当前pose包含resize或反向scale,也在同一owner change内保持
31
+ 既有world matrix,未编辑points不发生位移;整树resize继续走O(1) pose路径。Group默认旋转中心与九点
32
+ pivot吸附按当前local visual/content selection bounds解析;稳定layout reference只拥有Group pose。
33
+
34
+ Selection controls 是一份 immutable `SelectionOverlayProjection`。paint、control/body hit 与 cursor
35
+ 仲裁读取同一对象;场景内容 hit/hover 仍由 Core `Root.hoverPath/currentElement` 唯一拥有。Tools 不写
36
+ DOM cursor,不保存 `currentControl`、`hoverElement` 或第二份 selection frame。
37
+ 单选与多选的每个 selected owner 都由这份投影调用其唯一 `paintHover()` 绘制本体高亮,不依赖鼠标
38
+ 是否仍停留在图元上。
39
+ `Rule` 也直接消费这份投影:它把当前单选或多选 frame 的 world AABB 在水平、垂直标尺上各绘制
40
+ 为一段区间,并标出两端坐标。尺寸、背景、角块、主/次刻度、边框、文字与选区颜色都可以在构造时
41
+ 配置,并可在挂载后通过一次 `setOptions()` 更新;实际变化只登记一次 Editor overlay 重绘,相同值为
42
+ noop。主刻度与次刻度分别绘制。省略 `selectionColor` 时每次绘制读取当前
43
+ `root.skin.selection`,自动跟随 Select 外框:
44
+
45
+ ```ts
46
+ const rule = new Rule({
47
+ select,
48
+ rulerSize: 50,
49
+ backgroundColor: "#000000",
50
+ cornerBackgroundColor: "#0f0f0f",
51
+ majorTickColor: "#3f3f3f",
52
+ minorTickColor: "#3f3f3f",
53
+ borderColor: "#252525",
54
+ textColor: "#cccccc"
55
+ });
56
+
57
+ rule.setOptions({ rulerSize: 40, textColor: "#ffffff" });
58
+ ```
59
+
60
+ Rule 是覆盖在 Root 顶部和左侧的 screen-space 编辑器层,不会平移 Viewport。活动 Rule 会把当前
61
+ 标尺占用提供给同一 Root;Workspace 在初始几何提交后、首个Canvas paint前执行首次无动画聚焦,
62
+ Root尺寸变化后按最终尺寸执行一次无动画聚焦。两条路径都会读取标尺占用,并从可见区域的顶部和
63
+ 左侧扣除标尺尺寸:
64
+
65
+ ```ts
66
+ const rule = new Rule({ select, rulerSize: 40 });
67
+ const workspace = new Workspace({
68
+ width: 1920,
69
+ height: 1080
70
+ });
71
+ ```
72
+
73
+ 这项组合信息只参与 Workspace 的首次与尺寸变化自动聚焦,不进入场景属性、History 或序列化结果。
74
+
75
+ 这些配置只属于 `EditorLayer` 中的 Rule,不进入 Scene History 或 FileData。移动、缩放、旋转、选择
76
+ 清空/重入与 viewport 变化随唯一 Root frame 更新;Rule 不重新遍历 selected elements、查询它们的
77
+ geometry 或保存独立 range/resolved-color 状态。
78
+ Ctrl/Meta拖拽是additive marquee:pointerup只执行一次visual area query,并且只有visual bounds被框完整
79
+ 包围的最终selection owner会加入结果;只包住Group内部child不会选中整个Group。普通无修饰marquee保留
80
+ 真实轮廓相交语义。marquee候选遵守`visible/selectable`且不受`silent`取值影响,`true/false`都可框选;
81
+ `silent`仍阻止pointer target。pointermove期间只有ephemeral rectangle,零area query、owner write和History。
82
+
83
+ Group dive每次只选择当前Group scope内命中的DirectChild。下钻后,若下一次raw content hit仍在当前
84
+ 单选owner分支内,无位移点击保持该owner,使连续双击可以继续进入更深Group、Text Content或当前已有的
85
+ LineTree Point;点击该分支外的内容仍按普通selection owner规则解析,可以重新选中ancestor Group。
86
+ 这一交接只使用当前selection和raw ancestry,不保存第二份Group scope状态。
87
+
88
+ `Select.arrange()`接受公开枚举`ArrangeType`:
89
+
90
+ ```ts
91
+ import { ArrangeType } from "@fulate/tools";
92
+
93
+ select.arrange(ArrangeType.LayerUp);
94
+ select.arrange(ArrangeType.LayerDown);
95
+ select.arrange(ArrangeType.Forward);
96
+ select.arrange(ArrangeType.Backward);
97
+ select.arrange(ArrangeType.Top);
98
+ select.arrange(ArrangeType.Bottom);
99
+ ```
100
+
101
+ Layer默认不参与排列;应用创建内容Layer时必须显式设置`elementArrangementEnabled: true`。
102
+ 跨Layer移动按当前`Root.layers`绘制顺序寻找相邻的已启用Layer,跳过未启用Layer;上移进入目标Layer
103
+ 的children开头,下移进入末尾,并通过既有reparent Transform writer保持world pose。
104
+ `ArrangeType.Forward`/`ArrangeType.Backward`不读取Layer顺序,只让各元素直接parent内的selected
105
+ sibling块移动一步;`ArrangeType.Top`/`ArrangeType.Bottom`同样只在直接parent内把selected siblings
106
+ 稳定移动到children末尾/开头。四种父级内操作均不拆Group或跨parent。一次有效命令只提交一个trusted
107
+ batch和一个semantic History entry;边界noop为零batch/History。
108
+
109
+ `Select.setProperties()` 把同一份 authored 顶层属性补丁应用到当前全部选中元素:
110
+
111
+ ```ts
112
+ select.setProperties({
113
+ backgroundColor: "#ff0000",
114
+ opacity: 0.8
115
+ });
116
+ ```
117
+
118
+ 面板需要具体元素字段检查时,可让该对象 `satisfies ShapeOptionPatch` 或自定义元素自己的 patch 类型,
119
+ `setProperties()` 本身保持对第三方 authored 字段开放。
120
+
121
+ 省略字段保持不变,`undefined` 恢复该字段默认值,`null` 是属性自身的普通领域值。不做deep merge或
122
+ deep patch;命令把同一份完整patch引用直接交给每个selected element。结构化字段按引用作为
123
+ whole-value replacement;同一引用是普通noop,调用者需要独立副本时在交付前自行复制。标量相同值
124
+ 仍是noop。Tools不按元素类型过滤字段,
125
+ 属性面板负责只展示当前selection可编辑的属性。空selection不结束其他活动交互;非空selection
126
+ 先完成已经接受的交互,再从 live selection 以一次 trusted batch 提交,最多形成一条 History。
127
+ `setProperties()` 不改变 selection,也不派发 `select:end`;影响 visual geometry 的属性可在稳定后触发
128
+ 既有 `select:update` 通知,纯 paint 属性不会触发该几何事件。只要最终 configured 值有净变化,命令就在
129
+ History 安装后排队一次 `select:configured-change` 通知;标量 noop 不派发。表示 world 有效尺寸或位置的
130
+ width/height/x/y 仍使用 `Select.resize()` 或其他 Transform 命令,不能用普通 authored patch 绕过
131
+ 父级矩阵反算。
132
+
133
+ `Select.resize()` 是属性面板使用的公开尺寸命令:
134
+
135
+ ```ts
136
+ const bounds = element.transform.getWorldVisualBoundsView();
137
+ select.resize({
138
+ width: bounds.width * 0.8,
139
+ height: bounds.height * 0.8,
140
+ anchor: "top-left"
141
+ });
142
+ ```
143
+
144
+ `width` 或 `height` 省略时保持该轴当前的 selection frame 尺寸;对 `Text`,selection frame
145
+ 始终读取 authored/layout box,即使 `overflow="visible"` 且未换行的 glyph 超出该 box;`anchor` 省略等于
146
+ `"top-left"`,支持九个 frame 固定点:`top-left`/`top`/`top-right`/`left`/`center`/`right`/
147
+ `bottom-left`/`bottom`/`bottom-right`。这些值是 world logical 单位,不包含 Viewport 缩放;
148
+ controller 会自动处理父级和多层 Group 变换,并通过同一个 trusted batch 和 History 记录。选中
149
+ Group 时只改 Group pose/scale,不改写直接子元素的 authored width/height。
150
+
151
+ `Select.flip()` 是同一 Transform 命令链上的镜像操作:
152
+
153
+ ```ts
154
+ select.flip("horizontal");
155
+ select.flip("vertical");
156
+ ```
157
+
158
+ 镜像轴经过当前 selection frame 中心。单选旋转元素沿其 frame 自身水平/垂直轴镜像;多选的
159
+ aggregate frame 为 world 轴对齐,因此沿 world 水平/垂直轴镜像。命令遵守 selection 的 resize 轴
160
+ 策略,使用同一批 selected owner baseline、trusted batch 与 semantic History;选中 Group 时只写
161
+ Group pose/scale,不 bake children authored geometry。空选择或被策略禁止的方向是普通 noop。
162
+
163
+ ## Keyboard、Clipboard 与应用命令 owner
164
+
165
+ 每个活动 Root 由当前 `EditorInteractionController` 安装一个 DOM `keydown` listener。应用若要接管
166
+ `Ctrl/Meta+C`、`Ctrl/Meta+X`、`Ctrl/Meta+V`、`Delete` 或 `Backspace`,只绑定一个命令 owner:
167
+
168
+ ```ts
169
+ const unbind = select.bindKeyboardCommands({
170
+ copy: () => select.copy(),
171
+ cut: () => {
172
+ select.copy();
173
+ select.delete();
174
+ },
175
+ paste: () => select.paste({ resolveParent, afterInstall }),
176
+ delete: () => select.delete(afterDelete)
177
+ });
178
+ ```
179
+
180
+ 再次绑定直接替换当前引用;`unbind` 只会清除仍属于它的绑定。Controller 保留平台修饰键、form/Content
181
+ 排除、活动 gesture gate 与 `preventDefault()` 时机;未绑定或 gate 拒绝时不消费元素级按键,未绑定应用
182
+ owner 时 Point target 仍由现有 Point owner 处理。Fulate 不提供额外的
183
+ 单键复制快捷键或独立 cut API;应用的 cut 只在上层按 Copy 后 Delete 编排。
184
+
185
+ Clipboard v2 的唯一 payload 是 `elements`、`parentKeys` 和透明 `applicationData`。`select.copy(producer)`
186
+ 在不改 Canvas/selection/History 的前提下最多同步调用一次 producer,并把完整 source subtree keys 交给
187
+ `CopyContext`。`select.paste(options)` 在 clone、parent placement 和新 selection 安装后、Root drain 与
188
+ History finish 前调用 `afterInstall({ applicationData, keyMap, history })`;`keyMap` 是 clone codec 一次生成的
189
+ 完整旧/新 key 映射。应用 owner 先完成自己的首次 create/delete,再用 `history.record()` 登记 undo/redo,
190
+ 不调用首次 change 的 `redo()`。History 关闭时 hook 的 `history` 为 `undefined`,Canvas 操作仍照常执行。
191
+
192
+ `PasteParentResolver` 对每个非 Layer 顶层副本调用一次;Fulate 先按 `parentKeys` 解析当前 live source
193
+ parent,未提供 resolver 时只有 source parent 不存在才使用 Root 第一个 Layer。副本自身为 Layer 时直接放入
194
+ 当前 Root。`select.delete(afterInstall)` 在真实 Canvas 删除闭包安装后、drain/finish 前交付完整
195
+ `DeleteContext`;Point 删除不调用该 hook。System Clipboard 读失败时使用同一 memory payload,不读取旧格式,
196
+ 不做版本协商或迁移。
197
+
198
+ 异步 paste 在 Clipboard 读取前捕获当前 controller、selection owner、interaction state 与本次 Promise
199
+ 引用。Clipboard 返回后,只要 controller 生命周期仍有效,合法文档就正常提交并形成实际 History;
200
+ 只有这些 selection/interaction/task 引用仍共同证明本次 paste 拥有选择意图时,才把 selection 改为新
201
+ 元素。等待期间建立的新选择或手势保持当前,旧 paste 不结束它也不派发虚假的 `select:end`;两个 paste
202
+ 倒序完成时两次文档提交都保留,较新的 paste 意图拥有最终 selection。
203
+
204
+ 属性面板应同时监听 selection 完成和已选几何更新:
205
+
206
+ ```ts
207
+ function syncPanel() {
208
+ const element = select.selectedElements[0];
209
+ if (!element) return;
210
+ const { left, top, width, height } =
211
+ element.transform.getWorldVisualBoundsView();
212
+ updatePanel({ x: left, y: top, width, height });
213
+ }
214
+
215
+ const stopSelection = select.addEventListener("select:end", syncPanel);
216
+ const stopGeometry = select.addEventListener("select:update", syncPanel);
217
+ ```
218
+
219
+ 场景持久化只监听 configured 语义完成事件:
220
+
221
+ ```ts
222
+ const stopPersistence = select.addEventListener(
223
+ "select:configured-change",
224
+ () => saveToIndexedDB(serializeScene(root))
225
+ );
226
+ ```
227
+
228
+ `select:configured-change` 不携带 payload。一次 Transform/Point/Content/Line、`setProperties()`、
229
+ resize/flip、align、group/ungroup、delete、copy/paste 或 History replay,只要最终 configured
230
+ 属性或结构确有净变化,就在最终状态和适用的唯一 History entry/stack 安装后排队恰好一次通知;连续手势中的
231
+ 多个内部 batch 不逐批派发,最终回到起点的净空操作也不派发。`setRuntimeOptions()` 与
232
+ `clearRuntimeOptions()` 只改变 runtime override,因此不派发。选择、marquee、copy 等不写 configured
233
+ 状态的操作同样不派发。
234
+
235
+ `select:update` 只在 `Select` 自身存在该事件的直接监听器时登记;没有监听器时 pending completion 和
236
+ 事件派发均为 0。它在当前 Root 的 geometry、spatial、selection projection、paint、hover 与既有
237
+ `transformUpdated` completion 完成后同步派发,不携带 bounds 副本;监听器直接读取 live selection。
238
+ 同一显示帧内连续 move/resize/rotate/Point 或外部 selected geometry 变化合并为一次通知。监听器在
239
+ completion 中再次写入时,新变化进入下一轮稳定后再通知。Viewport zoom 不改变 world logical bounds,
240
+ 因此不会单独触发该事件。对同步 `resize()/flip()` 命令,顺序是 frame稳定及`transformUpdated` phase
241
+ → `select:update` → History finish → 排队 `select:configured-change` → 排队 `select:end`;
242
+ 这些通知在同步命令返回后按同一顺序执行。
243
+ `getWorldVisualBoundsView()` 是当前帧的 borrowed view,不应跨帧保存对象引用。`setProperties()` 若改变
244
+ 几何,则同样先稳定并同步派发`select:update`、再完成 History 与排队`select:configured-change`,但不派发
245
+ `select:end`;纯paint patch按 batch → History → 排队 `select:configured-change` 完成。
246
+
247
+ Transform、Point、`setProperties()`、align、group/ungroup、delete、copy/paste 与 Text editing
248
+ 都把最终 typed owner/structure effects 交给同一 trusted batch。`setProperties()`复用同一输入引用,
249
+ Core batch不复制。History只在semantic scope完成时保存actual changed
250
+ fields/placements 的 earliest-before/final-after,以及外部 owner 通过`HistoryScope.record()`登记的
251
+ 最小`HistoryChange`。`record()`只登记已经正常执行的change,不调用其`undo()`/`redo()`。History 关闭时
252
+ 不创建 History entry、完整
253
+ Properties/tree snapshot、receipt 或结构化副本;仅当直接订阅`select:configured-change`时,semantic
254
+ scope用已交付的actual before和最终live引用判断净变化。undo/redo把history-owned typed delta重新交给
255
+ 同一batch,并在Canvas安装后、drain前调用外部change的`undo()`/`redo()`;drain后恢复selection并转移
256
+ History stack。回放是线性、非重入操作;当前 `undo()`/`redo()` 返回前,不得再次调用同一 manager 的
257
+ `undo()`、`redo()`、`clear()`或`push()`,外部 `HistoryChange` 只恢复自己拥有的数据。只有Canvas
258
+ configured存在实际变化时才先派发
259
+ `select:configured-change`,再依次排队`select:end`与`select:undo`/`select:redo`;这些通知在 replay
260
+ 返回后按同一顺序执行。
261
+
262
+ 应用层可以在 Paste 的 Canvas 节点和新 selection 安装后、同一 semantic scope 尚未 finish 时补齐自己的 authored owner:
263
+
264
+ ```ts
265
+ select.paste({
266
+ afterInstall: ({ applicationData, keyMap, history }) => {
267
+ const created = dataDocument.createDefinitions(applicationData, keyMap);
268
+ if (!history || created.length === 0) return;
269
+ history.record({
270
+ undo: () => dataDocument.removeDefinitions(created),
271
+ redo: () => dataDocument.restoreDefinitions(created)
272
+ });
273
+ }
274
+ });
275
+ ```
276
+
277
+ hook运行时Canvas副本和新selection已经安装,但frame drain、History finish与最终事件尚未发生。
278
+ `history`在History关闭时为`undefined`,调用方因此不创建回放对象。首次Data提交由其owner正常完成,
279
+ 不能通过调用`redo()`代替。Canvas与Data最终只形成一条entry;Data-only entry同样可undo/redo,但不会把
280
+ `HistoryScope.finish()`的返回值变成`true`,也不会单独触发`select:configured-change`。
281
+
282
+ `select:end`是selection-producing操作的异步完成通知,不是通用Transform更新事件,也不是持久化
283
+ 合同:显式`select()`、点击/框选、selection-producing command与History replay都先结束旧交互、安装
284
+ 最终selection并完成当前selection projection;command还会先完成History及适用的
285
+ `select:configured-change`。
286
+ 外部scene removal引起的selection修复复用唯一Root frame的completion phase,在geometry、spatial、
287
+ overlay、RBush、paint与hit完成后排队通知。每次显式`select()`都独立完成并排队通知,包括再次传入同一个数组;
288
+ 不按数组内容或引用过滤。删除固定为structure/selection → drain → History →
289
+ `select:configured-change` → hit refresh → 返回;完成通知只在返回后按顺序执行。Delete 不再派发
290
+ 专用 Delete 完成事件;应用需要在删除闭包安装后登记外部 owner 时使用 `afterInstall`。
291
+ 所有 Select 完成通知都只读取派发时的 live state,不参与、阻止或使已完成的内部操作失败;listener
292
+ 异常在异步通知任务中自然传播。
293
+
294
+ 公开`doGroup()`/`unGroup()`支持活动Transform中的程序化调用:先保留并结束已经接受的Transform,随后
295
+ 把group/ungroup记录为独立History命令;结构变化后旧pointer continuation失效,下一次真实Transform从
296
+ live selection开始。明显不足的selection或非Group选择在`drain()`前直接noop;有效候选才drain,并在
297
+ 该可覆写边界返回后重新读取live selection。
298
+
299
+ Line、LineTree、Polygon与VectorPath普通选择都只投影Transform frame,Point capability、controls、paint
300
+ 和hit为0;双击当前单选owner后才进入唯一显式Point state。进入后隐藏Transform外框和信息面板,只显示
301
+ 当前owner/scope轮廓与Point controls。Line、Polygon与VectorPath使用owner scope,
302
+ LineTree只请求active branch scope。四类owner都从live canonical topology投影稳定`anchorId/segmentId`;
303
+ VectorPath的cubic handle只用`segmentId + cubic-control-1|cubic-control-2`,与anchor/segment共用同一flat
304
+ projection和gesture。所有Point control使用同一外径:anchor是实心圆,segment midpoint是菱形,cubic
305
+ handle是由细线连接anchor的空心圆;guide只绘制、不命中。handle可自由移动、不可删除、不是endpoint,也不触发Port probe。Line保持open
306
+ line-only;Polygon保持closed contour且至少三个anchors;VectorPath保留各subpath的open/closed状态并只
307
+ 编辑当前line/cubic记录。move、midpoint insert-and-drag、handle move与anchor delete/merge都不持久化
308
+ authored array index,未受影响的records与IDs保持引用。负缩放Line拖点时同一owner change保持world pose和
309
+ 未编辑anchors稳定。
310
+
311
+ Point control点击后会保持stable Delete target,移开鼠标再按Delete/Backspace仍处理该target;只有可删除
312
+ anchor显示active删除反馈。不可删除或已失效target不显示该反馈,Delete是已处理noop,不会误删整个element。
313
+ 只有没有active/hover target时,
314
+ LineTree active child branch通过capability产生一个topology change并进入parent scope;root branch以及
315
+ owner-scope adopter返回`null`,随后复用普通Select element delete。dispatch不读取LineTree私有topology。
316
+
317
+ LineTree按L直接把active branch tail视为已点击并进入draft;同一junction tail可以反复产生sibling
318
+ branches。Transform状态下当前pointer已在tail时第一次L
319
+ 直达,否则进入只命中branch tail的source-pick,第二次L或click均可确认;树干segment和中间anchor
320
+ 不可作为source。keydown只消费Core唯一hover/cursor链已经解析的当前pointer来确认source,下一次真实
321
+ pointermove才开始preview。source-pick或未提交branch-draw中双击真实branch会先取消当前session并进入
322
+ 该branch Point,随后L从active tail正常开始,不保留重叠状态。自由终点已经确认后Escape/Enter会像普通Line drawing一样提交,不会丢弃整段。draft确认前
323
+ 零document write;确认只安装一个最终topology change。`LineTool`的`resultType`与`tailDecoration`
324
+ 在drawing session开始时读取一次;`resultType`省略为`line`,也可创建单branch `lineTree`;
325
+ `tailDecoration`省略为`arrow`,传`none`时新Line、LineTree root及新branch均不带尾装饰。
326
+
327
+ LineTree Point/add/remove branch History只保存actual changed point/branch records;不深拷贝完整topology。
328
+ source-pick、branch scope切换和取消都不创建History或cleanup batch。
329
+ Standalone Line和LineTree都是EndpointRelation的source owner,不进入普通Port target索引;空白按L新建
330
+ 结果时不会为零frame LineTree投影默认Ports。Root默认关闭Port;只有显式`enablePorts: true`且元素
331
+ 自身显式配置`ports: true`或custom数组时,Ports才进入Root-scoped增量`PortIndex`和连接picker。未开启时LineTool不启动Port picker,
332
+ Line和LineTree仍可正常绘制、编辑与选择,Point/Transform继续复用同一个Snap执行普通axis对齐。
333
+ 按L进入LineTool或开始endpoint连接手势时,picker立即显示当前Viewport内所有已声明且可作为pointer
334
+ target的Port,不需要鼠标先接近或命中元素。鼠标进入固定10屏幕像素半径后,只把最近且同距时paint
335
+ order最上层的一项设为active并用于吸附;离开半径只取消active,其他Port继续显示。取消或结束session
336
+ 才清空全部marker。marker不绘制owner矩形轮廓,也不写authored state或History。
337
+
338
+ `Select`的`connectionGesture`只有`"auto" | false | callback`三种值。auto在pointerup把最终relation
339
+ 合入当前semantic History;callback在交互完全结束后只发布stable source/target refs和新的同步
340
+ `connect()`命令,回调本身不写relation也不创建Connection History;false不探测Port。普通Point的
341
+ pointercancel、Escape、selection switch与teardown会消费已经排队的latest accepted intent;endpoint派生的
342
+ Port手势才丢弃尚未消费的intent/candidate、停止picker且不连接/回调。两者下一次手势都从当前live
343
+ authored state重入,已正常安装的Point变化不回滚。
344
+ 启用Port手势时,Line/LineTree首端只接受`output | both`,尾端只接受`input | both`;方向不兼容或
345
+ 容量已满的候选只显示阻止反馈,不确认Line/branch point,也不提交endpoint移动。离开Port命中范围后
346
+ 仍可确认普通自由点。Port ownership只来自元素自身的`ports`;未声明的Text、Button或内部装饰默认
347
+ 没有Port。`PortIndex`再按当前Root的pointer-target语义过滤owner;`silent`和`pickable`只决定指针
348
+ 资格,不启用Port,也不改写authored配置。
349
+
350
+ Clipboard copy 先同步生成独立 memory snapshot;系统 clipboard 是不阻塞 authored command 的
351
+ best-effort 边界。paste 为 element 生成新 key,保留 owner-local path/Port IDs;source与
352
+ target一起复制时只重映射element key,只复制source时把当前resolved source-local位置写入clone
353
+ fallback并删除外部relation。所有结果通过一个trusted batch安装。Clipboard v2 只保存`elements`、
354
+ `parentKeys`与透明`applicationData`;memory 与 system 使用同一 payload。非 Layer 副本先解析当前 live
355
+ source parent;未提供`resolveParent`时使用该 parent,只有 parent 不存在时才落到 Root 第一个 Layer;提供
356
+ resolver 时对每个非 Layer 顶层副本调用一次。v2 reader 不读取旧格式,不做版本协商或迁移。