Issue body
中文
背景
PR #875 尝试支持将普通绘图元素绑定到表格:当图形、图片或连线位于表格内部时,通过 tableId 建立关联,并在移动、复制或删除表格时一并处理这些元素。
该方向有价值,但 #875 创建时间较早,当前已无法直接合并;同时原实现仍属于未完成的原型,暂不继续在旧 PR 上深入处理。
原方案
- 为 geometry、image、line 增加可选的
tableId。
- 以元素中心点是否位于表格矩形内作为绑定判定。
- 在拖动及插入节点时设置或清除
tableId。
- 通过
board.getRelatedFragment 找出表格关联元素,使其参与移动、复制和删除。
“平铺数据 + ID 引用 + related fragment”可以作为后续实现的参考。
需要重新明确的问题
- 哪些元素允许绑定到表格,是否包含连线、文本或其他容器。
- 绑定关系只属于整个 table,还是需要进一步绑定到具体 cell。
- 移动、复制、删除、resize table 时,内部元素应如何处理。
- 调整行高、列宽,以及插入、删除、合并单元格时,内部元素应如何处理。
- 元素跨越表格边界时的绑定和解绑规则。
- 存在重叠表格时,元素应归属于哪个表格。
- 复制表格及内部元素时,
tableId 应如何重新映射。
- undo/redo 和协同操作中如何保证绑定关系一致。
对 PR #875 的分析结论
原实现不适合直接合并,主要存在以下问题。
1. 可能发生 table 自绑定
移动元素的过滤范围包含 table。移动表格时,表格可能将自身的 tableId 设置为自己的 id。
后续需要明确定义可绑定元素类型,并排除:
- table 自身;
- swimlane 等其他容器;
- 暂未支持的容器嵌套场景。
2. Cell 命中逻辑不正确
getHitCellByCenterPoints 原本用于根据元素中心点确定所属 cell,但实现实际判断的是元素中心是否位于元素自己的矩形内。
该条件通常始终成立,无法识别真正命中的 cell。
3. 在 pointerMove 中直接修改模型
原实现会在拖动过程中调用 Transforms.setNode 设置或清除 tableId。
这可能带来:
- 每帧扫描 table 和 moving elements 的性能开销;
- undo/redo 历史被拆分或污染;
- 拖动操作和绑定操作的 operation batch 不清晰;
- 拖动取消或异常结束时难以恢复原有关系。
更合适的方案是:
- 拖动过程中只计算候选 table,用于视觉反馈;
- 在移动完成后一次性提交绑定关系变化。
4. 在 onChange 中再次创建 operation
原实现会在 board.onChange 中读取 insert_node operation,然后调用 Transforms.setNode。
这种方式可能导致:
onChange 重入;
- 插入和绑定成为不同的历史步骤;
- operation 批次边界不明确;
- 协同编辑中产生额外操作。
绑定信息更适合在插入数据构造阶段补齐,或者由明确的 normalize/plugin hook 处理。
5. Table resize 和 cell 布局行为没有完成
原 PR 新增了 cell 关联查询等辅助函数,但没有形成完整行为。
以下场景仍未处理:
- 调整列宽时,右侧 cell 中元素是否平移;
- 调整行高时,下方元素是否平移;
- 整体缩放表格时,内部元素是否缩放;
- 插入或删除行列后,内部元素如何重新定位;
- merged cell 中元素如何处理;
- merge/split 后元素属于哪个 cell。
因此,原实现只初步覆盖了“移动整个 table 时带着内部元素移动”,尚未形成完整的 table binding 功能。
6. 重叠表格归属不明确
原实现通过 find 获取第一个命中的 table。
当多个 table 重叠时,实际结果取决于 board.children 顺序,而不是明确的产品规则。
后续需要确定:
- 使用视觉最上层 table;
- 使用面积最小的 table;
- 禁止重叠绑定;
- 或保持元素原有绑定,直到显式移出。
7. 只有 tableId,但部分逻辑开始依赖 cell
如果需求只是让元素跟随整个 table 移动、复制和删除,tableId 基本足够。
如果需求还包括:
- 行列 resize;
- 插入或删除行列;
- merge/split;
- cell 级布局;
则需要进一步确定是否保存:
containerId?: string;
cellId?: string;
也需要明确 cell 归属是持久关系,还是每次根据元素坐标动态计算。
8. 缺少复制和生命周期处理
原实现没有完整覆盖:
- 删除 table 后的异常引用;
tableId 指向不存在 table 的脏数据;
- 复制 table 时,内部元素的
tableId 是否重映射到新 table;
- 单独复制内部元素时,是否保留原
tableId;
- 多选 table 和部分内部元素时如何去重;
- undo/redo 后关系是否正确;
- 连线是否适合仅通过中心点判断所属 table。
可保留的设计思路
虽然原实现不能直接合并,但以下方向仍然值得保留:
- 保持元素平铺,不直接改变为 table 的嵌套子节点;
- 使用 ID 引用表达 table 和内部元素的关系;
- 使用元素中心点作为默认的归属判定规则;
- 通过
board.getRelatedFragment 接入移动、复制和删除流程;
- 将 table binding 作为插件能力,而不是散落到各个命令中。
后续建议
后续应基于当前 develop 新建 PR,不继续复用 #875 的旧分支。
建议分阶段处理。
第一阶段:Table 级容器关系
先实现最小闭环:
- 明确定义可绑定元素类型;
- 元素进入 table 后绑定;
- 元素移出 table 后解绑;
- 移动 table 时携带绑定元素;
- 复制 table 时复制绑定元素并重映射引用;
- 删除 table 时一并处理绑定元素;
- 排除 table 自绑定和未支持的容器嵌套;
- 补充 undo/redo 测试。
第一阶段可以暂不处理行列和 cell 布局变化。
第二阶段:Cell 级布局关系
确认产品需求后,再设计:
- 是否持久保存
cellId;
- 行列 resize 时元素平移、缩放还是保持绝对坐标;
- 插入或删除行列时如何迁移元素;
- merge/split 时如何重新分配元素;
- 是否允许元素跨越多个 cell;
- 是否需要 cell clipping。
建议测试范围
后续实现至少需要覆盖:
- 拖入 table 后建立绑定;
- 拖出 table 后解除绑定;
- 在两个 table 之间移动;
- 移动 table 时携带绑定元素;
- 单独移动绑定元素;
- 复制 table 和绑定元素;
- 单独复制绑定元素;
- 删除 table;
- undo/redo 绑定和解绑;
- 重叠 table;
- 异常或不存在的
tableId;
- 普通 cell 和 merged cell;
- 行高、列宽及整体 resize。
当前决定
关闭 PR #875,不再基于该旧分支继续深入处理。
该 PR 保留为需求和技术方案参考。后续在需求边界明确后,基于当前 develop 创建新的实现 PR。
Related: #875
English
Background
PR #875 attempted to bind regular drawing elements to a table through tableId. When a geometry, image, or line is placed inside a table, the relationship allows those elements to move, copy, or delete together with the table.
The feature direction is useful, but #875 is stale and can no longer be merged directly. Its implementation is also an incomplete prototype, so development will not continue in the old PR.
Original approach
- Add an optional
tableId to geometry, image, and line elements.
- Determine ownership by checking whether the element center is inside the table rectangle.
- Set or clear
tableId while moving or inserting nodes.
- Extend
board.getRelatedFragment so bound elements participate in movement, copy, and deletion.
The model of “flat elements + ID reference + related fragment” remains useful for a future implementation.
Questions to clarify
- Which element types can be bound, including lines, text, or other containers?
- Is ownership table-level only, or should it target an individual cell?
- How should bound elements behave when the table is moved, copied, deleted, or resized?
- What should happen during row/column resize, insertion, deletion, and cell merge/split?
- When should an element bind or unbind while crossing a table boundary?
- Which table should own an element when tables overlap?
- How should
tableId references be remapped during copy?
- How should undo/redo and collaborative editing preserve relationship consistency?
Findings from PR #875
The implementation should not be merged as-is.
1. A table can bind to itself
The moving-element filter includes tables. When a table is moved, the implementation can set the table's own tableId to its own id.
A future implementation should explicitly define bindable element types and exclude:
- the table itself;
- other containers such as swimlanes;
- unsupported container nesting.
2. The cell hit-test logic is incorrect
getHitCellByCenterPoints was intended to identify the cell containing an element's center.
However, the implementation checks whether the element center is inside the element's own rectangle. This condition is normally always true and cannot identify the correct cell.
3. The model is modified from pointerMove
The implementation calls Transforms.setNode to set or clear tableId while dragging.
This can cause:
- per-frame table and moving-element scans;
- fragmented or polluted undo/redo history;
- unclear operation batching between movement and binding;
- difficulty restoring the relationship when a drag is cancelled.
A better approach would be:
- calculate the candidate table during dragging for visual feedback;
- commit the relationship change once movement completes.
4. New operations are created from onChange
The implementation reads insert_node operations from board.onChange and then calls Transforms.setNode.
This can result in:
onChange re-entry;
- insertion and binding becoming separate history entries;
- unclear operation boundaries;
- additional operations in collaborative editing.
Binding metadata should preferably be added while constructing inserted data or through an explicit normalization/plugin hook.
5. Table resize and cell layout behavior are incomplete
The PR adds cell-related helper functions but does not connect them into a complete behavior.
The following scenarios remain undefined:
- moving elements in cells to the right when a column width changes;
- moving elements below a row when its height changes;
- scaling or preserving elements during whole-table resize;
- repositioning elements after row/column insertion or deletion;
- handling elements inside merged cells;
- assigning elements after cell merge/split.
The original implementation therefore only partially addresses moving elements with the entire table.
6. Ownership is undefined for overlapping tables
The implementation uses find to select the first matching table.
When tables overlap, ownership depends on board.children order instead of an explicit product rule.
A future implementation should define whether to:
- choose the visually topmost table;
- choose the smallest matching table;
- prevent overlapping bindings;
- or preserve the existing relationship until the element is explicitly moved out.
7. Only tableId is stored, while some behavior depends on cells
If the requirement is limited to moving, copying, and deleting elements with the whole table, tableId may be sufficient.
If the requirement includes row/column resize, insertion/deletion, and merge/split behavior, it may need explicit metadata such as:
containerId?: string;
cellId?: string;
It must also be decided whether cell ownership is persistent or dynamically calculated from coordinates.
8. Copy and lifecycle cases are incomplete
The implementation does not fully cover:
- stale references after table deletion;
- a
tableId that points to a missing table;
- remapping
tableId when copying a table and its children;
- whether copying a child alone should preserve its original
tableId;
- deduplication when selecting a table and some of its children;
- relationship consistency after undo/redo;
- whether a line can be assigned to a table from its center point alone.
Design ideas worth preserving
Although the old implementation should not be merged, the following ideas remain useful:
- keep elements flat instead of converting them into nested table children;
- represent the relationship using an ID reference;
- use the element center as the default ownership rule;
- use
board.getRelatedFragment to integrate with move, copy, and deletion;
- implement table binding as a plugin capability rather than spreading it across commands.
Suggested follow-up
Create a fresh PR from the current develop branch instead of continuing the old #875 branch.
The implementation should be divided into stages.
Stage 1: Table-level container relationship
First implement a minimal complete behavior:
- define bindable element types;
- bind an element when it enters a table;
- unbind it when it leaves the table;
- move bound elements with the table;
- copy bound elements with the table and remap references;
- handle bound elements when deleting the table;
- prevent table self-binding and unsupported container nesting;
- add undo/redo coverage.
Row, column, and cell layout changes can remain out of scope in this stage.
Stage 2: Cell-level layout relationship
After the product behavior is confirmed, design:
- whether
cellId should be persisted;
- whether elements move, scale, or keep absolute coordinates during row/column resize;
- how elements migrate when rows or columns are inserted or deleted;
- how merge/split redistributes elements;
- whether an element can span multiple cells;
- whether cell clipping is required.
Suggested test coverage
A future implementation should cover at least:
- binding after moving an element into a table;
- unbinding after moving it out;
- moving an element between two tables;
- moving a table with its bound elements;
- moving a bound element independently;
- copying a table and its bound elements;
- copying a bound element independently;
- deleting a table;
- undo/redo of binding and unbinding;
- overlapping tables;
- missing or invalid
tableId;
- regular and merged cells;
- row, column, and whole-table resize.
Current decision
Close PR #875 and stop developing the old branch.
The PR will remain as a reference for the requirement and the original technical exploration. A fresh implementation PR can be created from the current develop branch after the product scope is clarified.
Related: #875
PR #875 closing comment
Replace #NEW_ISSUE_NUMBER with the issue number created from the content above.
Closing this PR in favor of #NEW_ISSUE_NUMBER.
This PR explored binding drawing elements to tables through tableId and board.getRelatedFragment. The direction remains useful, but the implementation is now stale and incomplete, especially around self-binding, cell hit testing, operation batching, copy reference remapping, table resize, and merged-cell behavior.
We will not continue developing this old branch. The requirement, analysis, and suggested follow-up have been moved to #NEW_ISSUE_NUMBER. A fresh implementation can be created from the current develop branch after the expected table-level and cell-level behaviors are clarified.
关闭此 PR,后续统一在 #NEW_ISSUE_NUMBER 中跟踪。
这个 PR 探索了通过 tableId 和 board.getRelatedFragment 将绘图元素绑定到表格的方案。整体方向仍有价值,但当前实现已经过时且不完整,尤其是 table 自绑定、cell 命中、operation 批次、复制时引用重映射、table resize 和 merged-cell 行为等方面仍需要重新设计。
当前不再继续维护这个旧分支。需求、分析结论和后续建议已经整理到 #NEW_ISSUE_NUMBER。待 table 级和 cell 级行为明确后,再基于当前 develop 创建新的实现 PR。
Issue body
中文
背景
PR #875 尝试支持将普通绘图元素绑定到表格:当图形、图片或连线位于表格内部时,通过
tableId建立关联,并在移动、复制或删除表格时一并处理这些元素。该方向有价值,但 #875 创建时间较早,当前已无法直接合并;同时原实现仍属于未完成的原型,暂不继续在旧 PR 上深入处理。
原方案
tableId。tableId。board.getRelatedFragment找出表格关联元素,使其参与移动、复制和删除。“平铺数据 + ID 引用 + related fragment”可以作为后续实现的参考。
需要重新明确的问题
tableId应如何重新映射。对 PR #875 的分析结论
原实现不适合直接合并,主要存在以下问题。
1. 可能发生 table 自绑定
移动元素的过滤范围包含 table。移动表格时,表格可能将自身的
tableId设置为自己的id。后续需要明确定义可绑定元素类型,并排除:
2. Cell 命中逻辑不正确
getHitCellByCenterPoints原本用于根据元素中心点确定所属 cell,但实现实际判断的是元素中心是否位于元素自己的矩形内。该条件通常始终成立,无法识别真正命中的 cell。
3. 在
pointerMove中直接修改模型原实现会在拖动过程中调用
Transforms.setNode设置或清除tableId。这可能带来:
更合适的方案是:
4. 在
onChange中再次创建 operation原实现会在
board.onChange中读取insert_nodeoperation,然后调用Transforms.setNode。这种方式可能导致:
onChange重入;绑定信息更适合在插入数据构造阶段补齐,或者由明确的 normalize/plugin hook 处理。
5. Table resize 和 cell 布局行为没有完成
原 PR 新增了 cell 关联查询等辅助函数,但没有形成完整行为。
以下场景仍未处理:
因此,原实现只初步覆盖了“移动整个 table 时带着内部元素移动”,尚未形成完整的 table binding 功能。
6. 重叠表格归属不明确
原实现通过
find获取第一个命中的 table。当多个 table 重叠时,实际结果取决于
board.children顺序,而不是明确的产品规则。后续需要确定:
7. 只有
tableId,但部分逻辑开始依赖 cell如果需求只是让元素跟随整个 table 移动、复制和删除,
tableId基本足够。如果需求还包括:
则需要进一步确定是否保存:
也需要明确 cell 归属是持久关系,还是每次根据元素坐标动态计算。
8. 缺少复制和生命周期处理
原实现没有完整覆盖:
tableId指向不存在 table 的脏数据;tableId是否重映射到新 table;tableId;可保留的设计思路
虽然原实现不能直接合并,但以下方向仍然值得保留:
board.getRelatedFragment接入移动、复制和删除流程;后续建议
后续应基于当前
develop新建 PR,不继续复用 #875 的旧分支。建议分阶段处理。
第一阶段:Table 级容器关系
先实现最小闭环:
第一阶段可以暂不处理行列和 cell 布局变化。
第二阶段:Cell 级布局关系
确认产品需求后,再设计:
cellId;建议测试范围
后续实现至少需要覆盖:
tableId;当前决定
关闭 PR #875,不再基于该旧分支继续深入处理。
该 PR 保留为需求和技术方案参考。后续在需求边界明确后,基于当前
develop创建新的实现 PR。Related: #875
English
Background
PR #875 attempted to bind regular drawing elements to a table through
tableId. When a geometry, image, or line is placed inside a table, the relationship allows those elements to move, copy, or delete together with the table.The feature direction is useful, but #875 is stale and can no longer be merged directly. Its implementation is also an incomplete prototype, so development will not continue in the old PR.
Original approach
tableIdto geometry, image, and line elements.tableIdwhile moving or inserting nodes.board.getRelatedFragmentso bound elements participate in movement, copy, and deletion.The model of “flat elements + ID reference + related fragment” remains useful for a future implementation.
Questions to clarify
tableIdreferences be remapped during copy?Findings from PR #875
The implementation should not be merged as-is.
1. A table can bind to itself
The moving-element filter includes tables. When a table is moved, the implementation can set the table's own
tableIdto its ownid.A future implementation should explicitly define bindable element types and exclude:
2. The cell hit-test logic is incorrect
getHitCellByCenterPointswas intended to identify the cell containing an element's center.However, the implementation checks whether the element center is inside the element's own rectangle. This condition is normally always true and cannot identify the correct cell.
3. The model is modified from
pointerMoveThe implementation calls
Transforms.setNodeto set or cleartableIdwhile dragging.This can cause:
A better approach would be:
4. New operations are created from
onChangeThe implementation reads
insert_nodeoperations fromboard.onChangeand then callsTransforms.setNode.This can result in:
onChangere-entry;Binding metadata should preferably be added while constructing inserted data or through an explicit normalization/plugin hook.
5. Table resize and cell layout behavior are incomplete
The PR adds cell-related helper functions but does not connect them into a complete behavior.
The following scenarios remain undefined:
The original implementation therefore only partially addresses moving elements with the entire table.
6. Ownership is undefined for overlapping tables
The implementation uses
findto select the first matching table.When tables overlap, ownership depends on
board.childrenorder instead of an explicit product rule.A future implementation should define whether to:
7. Only
tableIdis stored, while some behavior depends on cellsIf the requirement is limited to moving, copying, and deleting elements with the whole table,
tableIdmay be sufficient.If the requirement includes row/column resize, insertion/deletion, and merge/split behavior, it may need explicit metadata such as:
It must also be decided whether cell ownership is persistent or dynamically calculated from coordinates.
8. Copy and lifecycle cases are incomplete
The implementation does not fully cover:
tableIdthat points to a missing table;tableIdwhen copying a table and its children;tableId;Design ideas worth preserving
Although the old implementation should not be merged, the following ideas remain useful:
board.getRelatedFragmentto integrate with move, copy, and deletion;Suggested follow-up
Create a fresh PR from the current
developbranch instead of continuing the old #875 branch.The implementation should be divided into stages.
Stage 1: Table-level container relationship
First implement a minimal complete behavior:
Row, column, and cell layout changes can remain out of scope in this stage.
Stage 2: Cell-level layout relationship
After the product behavior is confirmed, design:
cellIdshould be persisted;Suggested test coverage
A future implementation should cover at least:
tableId;Current decision
Close PR #875 and stop developing the old branch.
The PR will remain as a reference for the requirement and the original technical exploration. A fresh implementation PR can be created from the current
developbranch after the product scope is clarified.Related: #875
PR #875 closing comment
Replace
#NEW_ISSUE_NUMBERwith the issue number created from the content above.Closing this PR in favor of #NEW_ISSUE_NUMBER.
This PR explored binding drawing elements to tables through
tableIdandboard.getRelatedFragment. The direction remains useful, but the implementation is now stale and incomplete, especially around self-binding, cell hit testing, operation batching, copy reference remapping, table resize, and merged-cell behavior.We will not continue developing this old branch. The requirement, analysis, and suggested follow-up have been moved to #NEW_ISSUE_NUMBER. A fresh implementation can be created from the current
developbranch after the expected table-level and cell-level behaviors are clarified.关闭此 PR,后续统一在 #NEW_ISSUE_NUMBER 中跟踪。
这个 PR 探索了通过
tableId和board.getRelatedFragment将绘图元素绑定到表格的方案。整体方向仍有价值,但当前实现已经过时且不完整,尤其是 table 自绑定、cell 命中、operation 批次、复制时引用重映射、table resize 和 merged-cell 行为等方面仍需要重新设计。当前不再继续维护这个旧分支。需求、分析结论和后续建议已经整理到 #NEW_ISSUE_NUMBER。待 table 级和 cell 级行为明确后,再基于当前
develop创建新的实现 PR。