xiaodao-spreader 0.1.38 → 0.1.41

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.
Files changed (40) hide show
  1. package/README.ZH.md +67 -49
  2. package/README.md +77 -58
  3. package/dist/style.css +1 -1
  4. package/dist/types/components/spreader/components/dropdown.vue.d.ts +3 -1
  5. package/dist/types/components/spreader/components/pickers/border-picker.vue.d.ts +16 -2
  6. package/dist/types/components/spreader/components/pickers/calc-picker.vue.d.ts +1 -1
  7. package/dist/types/components/spreader/components/pickers/color-palette.vue.d.ts +17 -0
  8. package/dist/types/components/spreader/components/pickers/color-picker.vue.d.ts +1 -1
  9. package/dist/types/components/spreader/components/pickers/data-validation-dropdown.vue.d.ts +1 -1
  10. package/dist/types/components/spreader/components/pickers/insert-function-dialog.vue.d.ts +1 -1
  11. package/dist/types/components/spreader/components/pickers/merge-picker.vue.d.ts +1 -1
  12. package/dist/types/components/spreader/components/pickers/number-format-dialog.vue.d.ts +1 -1
  13. package/dist/types/components/spreader/components/pickers/outline-picker.vue.d.ts +1 -1
  14. package/dist/types/components/spreader/components/pickers/sort-confirm-dialog.vue.d.ts +1 -1
  15. package/dist/types/components/spreader/components/pickers/sort-picker.vue.d.ts +1 -1
  16. package/dist/types/components/spreader/components/spreader.vue.d.ts +15 -5
  17. package/dist/types/components/spreader/components/toolbar.vue.d.ts +10 -2
  18. package/dist/types/components/spreader/composables/borders-merge.d.ts +19 -3
  19. package/dist/types/components/spreader/composables/core-state.d.ts +9 -1
  20. package/dist/types/components/spreader/composables/find-replace.d.ts +5 -0
  21. package/dist/types/components/spreader/composables/interactions.d.ts +5 -0
  22. package/dist/types/components/spreader/composables/sheets-ops.d.ts +5 -0
  23. package/dist/types/components/spreader/composables/undo-styles.d.ts +5 -0
  24. package/dist/types/components/spreader/core/border-color.d.ts +138 -0
  25. package/dist/types/components/spreader/core/border-icon.d.ts +43 -0
  26. package/dist/types/components/spreader/core/border-pool.d.ts +1 -1
  27. package/dist/types/components/spreader/core/border-resolve.d.ts +12 -3
  28. package/dist/types/components/spreader/core/border-style.d.ts +24 -0
  29. package/dist/types/components/spreader/core/constants.d.ts +1 -2
  30. package/dist/types/components/spreader/core/model-dims.d.ts +19 -0
  31. package/dist/types/components/spreader/core/notes.d.ts +4 -0
  32. package/dist/types/components/spreader/core/sort-icon.d.ts +25 -0
  33. package/dist/types/components/spreader/core/style-pool.d.ts +1 -1
  34. package/dist/types/components/spreader/core/theme.d.ts +5 -0
  35. package/dist/types/components/spreader/core/types.d.ts +14 -4
  36. package/dist/xiaodao-spreader.es.js +8347 -7759
  37. package/dist/xiaodao-spreader.umd.js +9 -9
  38. package/package.json +1 -1
  39. /package/dist/types/components/spreader/components/{data-validation-input-message.vue.d.ts → pickers/data-validation-input-message.vue.d.ts} +0 -0
  40. /package/dist/types/components/spreader/composables/{useFloatMenuPosition.d.ts → float-menu-position.d.ts} +0 -0
package/README.ZH.md CHANGED
@@ -25,7 +25,7 @@
25
25
  - **合并单元格**: 合并居中、跨列合并、取消合并;合并区域边框按锚点存储、公共边渲染时统一解析
26
26
  - **冻结窗格**: 固定顶部行/左侧列,使其在主体滚动时保持可见。工具栏「冻结窗格」下拉提供「冻结窗格」(以当前选区/活动单元格左上角为冻结点,等同 Excel 的 Freeze Panes)、「冻结首行」、「冻结首列」以及「取消冻结」(仅在已冻结时显示,替换「冻结窗格」项)。冻结状态为按工作表的视图状态(`freeze: { rows, cols }`),通过 v-model 持久化;跨冻结线的合并单元格也能正确处理,仅冻结部分固定,非冻结部分随滚动移出
27
27
  - **丰富单元格格式**: 字体、字号(5–72)、加粗、斜体、下划线、删除线、文字颜色、填充颜色、水平/垂直对齐、自动换行
28
- - **边框**: 上/下/左/右/外边框/全部/无,5 种预设样式及自定义颜色;独立边框池存储、公共边渲染时统一解析
28
+ - **边框**: 上/下/左/右/外边框/全部/无,5 种预设样式、自定义颜色及按边线型(实线 / 虚线 / 点线);独立边框池存储、公共边渲染时统一解析
29
29
  - **撤销 / 重做**: 单元格、列宽、行高的完整状态快照(50 步)
30
30
  - **格式刷**: 复制并应用单元格格式到任意区域
31
31
  - **数字格式**: Excel 风格数字格式(常规 / 文本 / 数值 / 货币 / 会计 / 百分比 / 科学计数 / 日期 / 时间 / 日期时间 / 持续时间),提供自定义格式对话框;仅影响显示,绝不修改底层单元格值
@@ -34,7 +34,7 @@
34
34
  - **自动筛选(AutoFilter)**: 类 Excel 的普通区域自动筛选。工具栏「数据 → 筛选」或 `Ctrl+Shift+L` 启用;选择一个数据单元格(或单行多列选区)后启用,即从该表头行向下智能探测连续数据区;各列表头显示筛选下箭头,点击箭头打开筛选面板(值 / 文本 / 数值 / 日期 / 搜索 / 空值 / 多列 AND)。清除某列条件只清该列、移除 AutoFilter 清全部;仅在 Filter Range 内的列拥有箭头;单选已合并单元格且无筛选态时禁用筛选按钮,*见 [自动筛选(AutoFilter)](#自动筛选autofilter)*
35
35
  - **查找与替换**: 工具栏查找按钮或 `Ctrl/Cmd+F`(含 `Ctrl/Cmd+H`)打开;支持「当前工作表 / 整个工作簿 / 当前选区」三种范围,区分大小写、匹配整个单元格;高亮全部匹配并定位当前项、循环查找;单次与全部替换均接入撤销/重做,仅修改单元格原始 `value`(始终保持字符串),不改动格式 / 边框 / 合并
36
36
  - **条件格式**: 按工作表存储条件格式规则(`SheetState.conditionalFormats`)。工具栏「条件格式」下拉提供高亮单元格预设(大于 / 小于 / 介于 / 文本包含 / 重复 / 唯一 / 空白 / 非空白)以及「新建规则」对话框(支持单元格值条件和自定义公式条件)。规则渲染为基础单元格样式之上的临时覆盖层,绝不写回底层 `value` 或 `styleId`。规则优先级(数值越小优先级越高)、`stopIfTrue` 语义、单元格值变化时自动失效缓存。插入/删除行列时通过与自动填充相同的 `shiftFormulaRefs` 引擎自动调整规则范围与公式引用(`$B$2` 绝对引用保持固定)。内置「管理规则」对话框可编辑、排序、删除规则;「清除规则」子菜单支持清除全部或按选区清除。渲染时与基础样式正确合成背景色、文字颜色、粗体、斜体、下划线、删除线。*见 [条件格式](#条件格式)*
37
- - **数据验证**: 按工作表存储类 Excel 数据验证规则。工具栏「数据验证」按钮打开对话框,设置允许类型(任何值 / 列表 / 整数 / 小数 / 日期 / 时间 / 文本长度 / 自定义公式)、运算符(介于 / 不介于 / 等于 / 大于 ……)与条件值。列表验证在单元格内显示下拉,支持搜索与完整键盘操作(↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc)。支持逐条输入信息提示与出错警告(停止 / 警告 / 信息):验证发生在值写入之前,停止级违规直接拒绝录入,警告/信息级由用户确认后继续。同一范围的多条规则必须全部通过。规则随 v-model 持久化、接入撤销/重做,并在行列增删时自动调整范围。复制/粘贴与自动填充走原子校验——出现停止级违规则整次取消。*见 [数据验证](#数据验证)*
37
+ - **数据验证**: 按工作表存储类 Excel 数据验证规则。工具栏「数据验证」按钮打开对话框,设置允许类型(任何值 / 列表 / 整数 / 小数 / 日期 / 时间 / 文本长度 / 自定义公式)、运算符(介于 / 不介于 / 等于 / 大于 ……)与条件值。列表验证在单元格内显示下拉,支持搜索与完整键盘操作(↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc)。支持逐条输入信息提示与出错警告(停止 / 警告 / 信息):验证发生在值写入之前,停止级违规直接拒绝录入,警告/信息级由用户确认后继续。同一范围的多条规则必须全部通过。规则随 v-model 持久化、接入撤销/重做,并在行列增删时自动调整范围。复制/粘贴与自动填充走原子校验-出现停止级违规则整次取消。*见 [数据验证](#数据验证)*
38
38
  - **行列分组与折叠**: 类 Excel 的行/列分组(Outline)。选中整行或整列后,工具栏「分组」下拉(位于排序与筛选之间)提供添加分组 / 取消分组 / 清除分组 / 全部展开 / 全部折叠,直接作用于选中轴;行/列右键菜单提供同构的「分组」子菜单。仅支持一层、互不相交的分组,嵌套或部分重叠会被校验拒绝并提示。分组区间在行号/列号带用交替背景色区分,± 折叠按钮浮动于表头带内,不占用独立 gutter。折叠的行/列不参与内容绘制;插入/删除行列时分组范围自动平移、被清空的分组自动删除。分组状态按工作表持久化(`rowOutlines` / `columnOutlines`),增删与折叠均接入撤销/重做。*见 [行列分组与折叠](#行列分组与折叠)*
39
39
  - **单元格批注**: 类 Excel 的单元格批注(Note/Comment)。右键菜单新建 / 编辑 / 删除;单元格右上角红色三角指示器标识含批注;hover 悬停查看、点按编辑;批注独立于单元格值与样式,采用 Note Pool 存储(Sheet 级 notes 池 + cell.noteId 引用),便于排序 / 插入删除时随 cell 一起移动。批注支持多行文本、可选作者、创建 / 修改时间。可通过 `noteAuthor` prop 设置当前作者名。*见 [单元格批注](#单元格批注)*
40
40
  - **自动填充(填充柄)**: 类 Excel 的活动选区右下角填充柄,按住向上/下/左/右拖拽即可填充单元格:单值复制、数字/日期/文本数字自动续序列(如 `1,2 → 3,4,5`)、公式引用按相对/绝对/混合规则平移(如 `=A1*2 → =A2*2`)、源 `styleId` 经样式池复用。拖拽期间实时预览、边缘自动滚动、动态扩展工作表、冻结窗格兼容、单次拖拽仅产生一个撤销步骤,*见 [自动填充](#自动填充)*
@@ -169,10 +169,8 @@ interface SheetModelData {
169
169
  freeze?: { rows: number; cols: number };
170
170
  colWidths?: Record<number, number>;
171
171
  rowHeights?: Record<number, number>;
172
- /** 逻辑列数(0 基,不含)。省略时默认为 26。 */
173
- colCount?: number;
174
- /** 逻辑行数(0 基,不含)。省略时默认为 200。 */
175
- rowCount?: number;
172
+ // 网格尺寸(colCount/rowCount)不持久化:加载时按内容推导(deriveModelDims),
173
+ // 至少 props 默认 26×200,并按数据 / 自定义行列尺寸 / 各类规则范围扩展
176
174
  /** 单元格批注池:按 noteId 存放 CellNote,cell.noteId 引用之;与 cell value/style 完全独立 */
177
175
  notes?: Record<string, CellNote>;
178
176
  }
@@ -194,7 +192,7 @@ import type {
194
192
  CellData, // { value: string; styleId?: number }
195
193
  CellStyle, // 类型化样式接口(字体、颜色、边框、对齐等)
196
194
  BorderStyle, // 四边边框组合 { top; right; bottom; left }
197
- BorderSide, // 单边边框 { width; color; style }
195
+ BorderSide, // 单边边框 { width; color; style; owner? }
198
196
  BorderSource, // 边框来源 'cell' | 'merge'
199
197
  Range, // { start: number; end: number }
200
198
  SpreadsheetOptions,
@@ -223,43 +221,62 @@ src/
223
221
  └── spreader/
224
222
  ├── index.ts # 统一导出口: 组件 + 类型(barrel)
225
223
  ├── components/
226
- │ ├── spreader.vue # 入口组件: 模板 + 样式 + 组合
227
- │ ├── toolbar.vue # 工具栏(含溢出下拉)
228
- │ ├── tabbar.vue # Sheet 标签栏
229
- │ ├── dropdown.vue # 通用下拉组件
230
- │ ├── find-replace-bar.vue # 查找替换栏 UI
231
- │ └── note-overlay.vue # 批注查看/编辑浮层(view + edit 双模)
224
+ │ ├── spreader.vue # 入口组件:模板 + 样式 + 组合
225
+ │ ├── toolbar.vue # 工具栏(带溢出下拉)
226
+ │ ├── tabbar.vue # Sheet 标签栏
227
+ │ ├── dropdown.vue # 通用下拉组件
228
+ │ ├── find-replace-bar.vue # 查找/替换栏 UI
229
+ │ ├── note-overlay.vue # 批注查看/编辑浮层(view + edit 双模)
232
230
  │ └── pickers/
233
- │ ├── colorPicker.vue # 文字 / 填充颜色选择器
234
- │ ├── borderPicker.vue # 边框选择器
235
- │ ├── mergePicker.vue # 合并单元格选择器
236
- │ ├── numberFormatDialog.vue # 数字格式自定义对话框
237
- │ ├── sortPicker.vue # 排序下拉
238
- │ ├── sortConfirmDialog.vue # Excel 风格排序提醒对话框
239
- │ ├── calcPicker.vue # 求和 / 平均值 / 计数选择器
240
- │ └── insertFunctionDialog.vue # 插入函数对话框
231
+ │ ├── border-picker.vue # 边框选择器(按边类型 / 颜色 / 线型 + 子菜单)
232
+ │ ├── calc-picker.vue # 求和 / 平均值 / 计数选择器
233
+ │ ├── color-palette.vue # 文字 / 填充 / 边框共用的色板(单一数据源)
234
+ │ ├── color-picker.vue # 文字 / 填充颜色选择器
235
+ │ ├── conditional-format-manager.vue # 条件格式「管理规则」对话框
236
+ │ ├── conditional-format-menu.vue # 条件格式工具栏下拉(预设 + 新建 / 管理 / 清除规则)
237
+ │ ├── conditional-format-rule-editor.vue # 条件格式「新建 / 编辑规则」对话框
238
+ │ ├── data-validation-alert.vue # 数据验证出错警告(Stop / Warning / Information)
239
+ │ ├── data-validation-dialog.vue # 数据验证设置对话框
240
+ │ ├── data-validation-dropdown.vue # 数据验证列表下拉(搜索 + 虚拟列表 + 键盘)
241
+ │ ├── data-validation-input-message.vue # 数据验证输入信息提示气泡(选中单元格时显示)
242
+ │ ├── filter-popup.vue # 自动筛选面板(值 / 文本 / 数值 / 日期 + 搜索 + 空值 + 多列 AND)
243
+ │ ├── insert-function-dialog.vue # 插入函数对话框
244
+ │ ├── merge-picker.vue # 合并单元格选择器
245
+ │ ├── number-format-dialog.vue # 数字格式对话框
246
+ │ ├── outline-picker.vue # 工具栏「分组」下拉(添加 / 取消 / 清除分组,展开 / 折叠)
247
+ │ ├── sort-confirm-dialog.vue # Excel 风格排序提醒对话框
248
+ │ └── sort-picker.vue # 排序下拉
241
249
  ├── composables/
242
- │ ├── core-state.ts # Props、cells/merges/selection、字体度量、导航
243
- │ ├── undo-styles.ts # 撤销重做、格式刷、字体/对齐/颜色
244
- │ ├── borders-merge.ts # 边框操作、合并操作、剪贴板、求和
245
- │ ├── sheets-ops.ts # 行列增删、多 Sheet、v-model 发射、主题、refs
246
- │ ├── find-replace.ts # 查找替换状态与交互(依赖 Vue)
247
- │ └── notes.ts # 批注池管理:CRUD、持久化、Undo 集成
248
- │ └── interactions.ts # 渲染器、编辑栏、标签栏、右键菜单、滚动条、事件
250
+ │ ├── borders-merge.ts # 边框操作、合并操作、剪贴板、求和/平均/计数
251
+ │ ├── core-state.ts # Props、cells/merges/selection、字体度量、导航、行列分组状态
252
+ │ ├── find-replace.ts # 查找/替换状态与交互(依赖 Vue)
253
+ │ ├── interactions.ts # 渲染器、公式栏、标签栏、右键菜单、滚动条、事件
254
+ │ ├── sheets-ops.ts # 行列操作、多 Sheet、v-model emit、主题、refs
255
+ │ ├── undo-styles.ts # 撤销/重做、格式刷、字体/对齐/颜色
256
+ │ └── float-menu-position.ts # 工具栏下拉菜单共享定位(右锚 + 上下翻向夹紧)
249
257
  └── core/
250
- ├── constants.ts # 布局常量、国际化、主题调色板
251
- ├── types.ts # 全部类型定义
252
- ├── style-pool.ts # 样式池:去重、注册、解析、迁移、GC
253
- ├── border-pool.ts # 边框池:去重、注册、解析、迁移、GC
254
- ├── border-resolve.ts # 公共边冲突解析(resolveSharedBorder)
255
- ├── formula.ts # 公式引擎(解析、计算、依赖、缓存)
256
- ├── find-replace-core.ts # 查找替换纯算法(零 Vue 依赖,可单测)
257
- ├── sort-core.ts # 排序纯算法(零 Vue 依赖,可单测)
258
- ├── autofill.ts # 自动填充纯引擎(模式推断、填充柄逻辑,零 Vue 依赖)
259
- ├── number-format.ts # 数字格式引擎(Excel 风格显示格式化)
260
- ├── conditional-formatting.ts # 条件格式引擎(条件求值、规则缓存、公式引用平移)
261
- ├── theme.ts # 主题 CSS 变量构建
262
- └── utils.ts # 纯工具函数(列标签、命中测试、尺寸解析)
258
+ ├── autofill.ts # 自动填充纯引擎(模式推断、填充柄逻辑,零 Vue 依赖)
259
+ ├── border-color.ts # 按边边框颜色:颜色与线型解耦、改色计划
260
+ ├── border-icon.ts # 边框图标单一数据源(toolbar + picker)
261
+ ├── border-pool.ts # 边框池:去重、注册、解析、迁移、GC
262
+ ├── border-resolve.ts # 公共边冲突解析(resolveSharedBorder)
263
+ ├── border-style.ts # 边框线型(实线/虚线/点线):归一化与 dash 图案
264
+ ├── conditional-formatting.ts # 条件格式引擎(条件求值 + 规则缓存,纯逻辑)
265
+ ├── constants.ts # 布局常量、i18n 文案、主题配色
266
+ ├── data-validation.ts # 数据验证引擎(纯逻辑)
267
+ ├── filter-core.ts # 自动筛选引擎(级联候选值,纯逻辑)
268
+ ├── find-replace-core.ts # 查找/替换纯算法(零 Vue 依赖,可单测)
269
+ ├── formula.ts # 公式引擎(解析、求值、依赖追踪)
270
+ ├── model-dims.ts # 模型尺寸由内容推导(纯逻辑)
271
+ ├── notes.ts # 批注池管理:CRUD、持久化、Undo 集成
272
+ ├── number-format.ts # 数字格式引擎(Excel 风格显示格式化)
273
+ ├── outline-core.ts # 行列分组纯引擎(校验 / 平移 / 折叠,零 Vue 依赖)
274
+ ├── sort-core.ts # 排序纯算法(零 Vue 依赖,可单测)
275
+ ├── sort-icon.ts # 排序图标单一数据源(toolbar + picker)
276
+ ├── style-pool.ts # 样式池:去重、注册、解析、迁移、GC
277
+ ├── theme.ts # 主题 CSS 变量构建
278
+ ├── types.ts # 全部类型定义
279
+ └── utils.ts # 纯工具函数(列标转换、命中测试等)
263
280
  ```
264
281
 
265
282
  ### 设计原则
@@ -416,18 +433,18 @@ src/
416
433
 
417
434
  ### 插入 / 删除传播
418
435
 
419
- CF 引擎的 `shiftFormulaRefsSafe` 复用公式引擎的 `shiftFormulaRefs` 函数——因此 `$B$2`(绝对引用)固定不动,`A1`(相对引用)正确平移。规则范围随之扩展/收缩;完全被移除的规则自动删除。
436
+ CF 引擎的 `shiftFormulaRefsSafe` 复用公式引擎的 `shiftFormulaRefs` 函数:因此 `$B$2`(绝对引用)固定不动,`A1`(相对引用)正确平移。规则范围随之扩展/收缩;完全被移除的规则自动删除。
420
437
 
421
438
  ### 渲染时样式合成
422
439
 
423
- `resolveConditionalFormatting` 在 `drawCells` 内按视口单元格逐个调用,遍历所有范围包含该单元格的启用规则,按优先级合并后生成临时 `cfStyle`。`applyCfFormat` 将其合成到基础解析样式上,正确处理背景色、文字颜色、粗体、斜体、下划线、删除线——含合并单元格(CF 对锚点求值后传播到整个合并区域)。
440
+ `resolveConditionalFormatting` 在 `drawCells` 内按视口单元格逐个调用,遍历所有范围包含该单元格的启用规则,按优先级合并后生成临时 `cfStyle`。`applyCfFormat` 将其合成到基础解析样式上,正确处理背景色、文字颜色、粗体、斜体、下划线、删除线,含合并单元格(CF 对锚点求值后传播到整个合并区域)。
424
441
 
425
442
  ### UI 组件
426
443
 
427
444
  | 文件 | 职责 |
428
445
  |------|------|
429
446
  | `conditional-format-menu.vue` | 工具栏下拉:高亮单元格预设、空白、重复、唯一、新建规则、管理规则、清除规则子菜单。子菜单采用 Teleport + hover-intent(150ms 延迟)实现跨间隙悬停不丢失 |
430
- | `conditional-format-rule-editor.vue` | 「新建规则」对话框:条件类型下拉(复用 `SpDropdown`)、阈值输入、格式面板(背景色选择器、文字颜色选择器、粗体/斜体/下划线/删除线——全部复用工具栏控件样式),应用范围自动填入当前选区 |
447
+ | `conditional-format-rule-editor.vue` | 「新建规则」对话框:条件类型下拉(复用 `SpDropdown`)、阈值输入、格式面板(背景色选择器、文字颜色选择器、粗体/斜体/下划线/删除线,全部复用工具栏控件样式),应用范围自动填入当前选区 |
431
448
  | `conditional-format-manager.vue` | 「管理规则」对话框:列出当前工作表全部规则,支持拖拽排序(修改 priority)、编辑单条规则、删除单条规则、一键清除全部或清除选区规则 |
432
449
 
433
450
  ---
@@ -444,7 +461,7 @@ CF 引擎的 `shiftFormulaRefsSafe` 复用公式引擎的 `shiftFormulaRefs` 函
444
461
 
445
462
  | 类型 | 说明 |
446
463
  |------|------|
447
- | `any` | 无约束——在对话框选中「任何值」并确定即清除该范围验证 |
464
+ | `any` | 无约束:在对话框选中「任何值」并确定即清除该范围验证 |
448
465
  | `list` | 单元格内下拉,来源于固定值或单元格范围 |
449
466
  | `wholeNumber` / `decimal` | 数值约束 |
450
467
  | `date` / `time` | 日期 / 时间约束(ISO、`YYYY年M月D日`、美式 `m/d/yyyy` 或 Excel 序列号) |
@@ -459,7 +476,7 @@ CF 引擎的 `shiftFormulaRefsSafe` 复用公式引擎的 `shiftFormulaRefs` 函
459
476
 
460
477
  ### 验证流程
461
478
 
462
- 验证发生在值写入**之前**。`commitEdit` 返回 `Promise<boolean>`——`false` 表示停止级违规拒绝了录入、编辑器状态保留。警告/信息级会调用 `showValidationAlert` 并等待用户确认后再继续。同一范围有多条规则时**必须全部通过**(`rules.every()`)。
479
+ 验证发生在值写入**之前**。`commitEdit` 返回 `Promise<boolean>`-`false` 表示停止级违规拒绝了录入、编辑器状态保留。警告/信息级会调用 `showValidationAlert` 并等待用户确认后再继续。同一范围有多条规则时**必须全部通过**(`rules.every()`)。
463
480
 
464
481
  ### 范围索引
465
482
 
@@ -467,7 +484,7 @@ CF 引擎的 `shiftFormulaRefsSafe` 复用公式引擎的 `shiftFormulaRefs` 函
467
484
 
468
485
  ### 原子粘贴 / 自动填充
469
486
 
470
- 复制/粘贴与自动填充先算出全部候选值、跑 `validateCells`,只要出现一条停止级违规就整次取消——不会出现部分提交。
487
+ 复制/粘贴与自动填充先算出全部候选值、跑 `validateCells`,只要出现一条停止级违规就整次取消,不会出现部分提交。
471
488
 
472
489
  ### 插入 / 删除传播
473
490
 
@@ -480,7 +497,7 @@ CF 引擎的 `shiftFormulaRefsSafe` 复用公式引擎的 `shiftFormulaRefs` 函
480
497
  | `data-validation-dialog.vue` | 工具栏「数据验证」对话框:类型/运算符/值、列表来源、输入信息、出错警告、应用范围;保存时校验最小值/最大值字面量 |
481
498
  | `data-validation-dropdown.vue` | 单元格内列表下拉:虚拟列表、搜索框、完整键盘操作(↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc) |
482
499
  | `data-validation-alert.vue` | 停止 / 警告 / 信息 警告框 |
483
- | `data-validation-input-message.vue` | 悬停提示气泡,显示规则的输入信息(位于 `components/`,不在 `pickers/`) |
500
+ | `data-validation-input-message.vue` | 悬停提示气泡,显示规则的输入信息 |
484
501
 
485
502
  ### 持久化与撤销
486
503
 
@@ -666,9 +683,10 @@ scheduleRender()
666
683
  一套独立的边框存储与渲染机制(`spreader/core/border-pool.ts` + `border-resolve.ts`)。
667
684
 
668
685
  - **独立边框池**:每个 Sheet 维护 `borders: BorderStyle[]` 池(`borders[0]` 恒为空边框 `{}`),样式通过 `borderId`(数组下标)引用边框,相同边框自动去重。
669
- - **公共边统一解析**:相邻单元格的公共边在渲染时经 `resolveSharedBorder()` 解析,宽者优先、同宽取先侧;设置边框**不再**同步修改相邻单元格。
686
+ - **公共边统一解析**:相邻单元格的公共边在渲染时经 `resolveSharedBorder()` 解析,宽者优先、同宽取先侧;若仅一侧带 `owner` 标记(由边框或选区操作显式写入),则该侧在公共边解析中优先、不受宽度影响,使新设置的选区边界边优先于相邻的旧边。设置边框**不再**同步修改相邻单元格。
670
687
  - **合并单元格**:合并区域边框统一存储在锚点(左上角),内部边屏蔽,外边界按行/列分段与相邻区域解析,四角补齐角方块。
671
688
  - **旧数据兼容**:旧版内联边框属性(`borderTopWidth` 等,已废弃)在加载时经 `migrateBordersInStyles()` 自动迁移。
689
+ - **按边颜色与线型(解耦)**:二者均挂在每条独立边 `BorderSide`(`{ width; color; style }`)上。颜色为标准 HEX 字符串;`''` 或省略表示「自动」,渲染回退默认色 `#444`。线型 `style` 取 `solid` / `dashed` / `dotted`;旧数据缺 `style` 一律按 `solid` 处理,且 `solid` 不写 `style` 键,使其去重键与历史空边框数据一致。改颜色只动 `color`、保留 `width`/`style`;改线型只动 `style`、保留 `width`/`color`。边框下拉框提供两个子菜单:颜色选择器(复用文字色板并含「自动」项,只给已存在的边上色)与**线型选择器**(实线 / 虚线 / 点线预览,设置当前画笔线型,随后作用于后续边框操作及选区已存在的边,绝不凭空创建边框)。纯逻辑集中在 `core/border-color.ts`(`withBorderColor` / `withBorderWidth` / `planBorderColorChanges` / `resolveSelectionBorderColor`)与 `core/border-style.ts`(`normalizeBorderLineStyle` / `borderLineDash`),与 Vue/Canvas 完全解耦并具备单元测试。渲染器经 `paintBorderEdge` 绘制虚线/点线(实线保留原 `fillRect` 路径,网格线与选区框不受影响)。
672
690
 
673
691
  ---
674
692
 
package/README.md CHANGED
@@ -24,19 +24,19 @@ A high-performance, canvas-based spreadsheet component for Vue 3: bringing an Ex
24
24
  - **Merge Cells**: Merge & Center, Merge Across, Unmerge; merged-region borders stored at the anchor, shared borders resolved at render time
25
25
  - **Freeze Panes**: Pin frozen top rows / left columns so they keep visible while the body scrolls. The toolbar "Freeze Panes" dropdown exposes *Freeze Panes* (freezes at the top-left corner of the current selection / active cell: Excel's "Freeze Panes" behavior), *Freeze First Row*, *Freeze First Column*, and *Unfreeze* (shown only when a pane is already frozen, replacing the "Freeze Panes" entry). Per-sheet view state (`freeze: { rows, cols }`) persisted through v-model; merged cells straddling the freeze line are handled correctly: only the frozen portion stays pinned, the rest scrolls away
26
26
  - **Rich Cell Formatting**: Font family, font size (5–72), bold, italic, underline, strikethrough, text color, fill color, horizontal & vertical alignment, wrap text
27
- - **Borders**: Top / bottom / left / right / outside / all / none with 5 predefined styles and custom color; stored in a dedicated border pool, shared borders resolved at render time
27
+ - **Borders**: Top / bottom / left / right / outside / all / none with 5 predefined styles, custom color, and per-side line type (solid / dashed / dotted); stored in a dedicated border pool, shared borders resolved at render time
28
28
  - **Undo / Redo**: Full state snapshots for cells, column widths, and row heights (50 steps)
29
29
  - **Format Painter**: Copy & apply cell formatting across ranges
30
30
  - **Number Format**: Excel-style number formatting (General / Text / Number / Currency / Accounting / Percent / Scientific / Date / Time / DateTime / Duration) with a custom format dialog; display-only, never mutates the stored cell value
31
31
  - **Smart Data Recognition**: Typing `100%`, `1,234`, `¥1,234.56` auto-converts the text to a number and applies the matching format; common date/time text auto-converts to dates: matching Excel input behavior
32
32
  - **Sorting & Sort Warning**: Sort any column by its **displayed content** (numbers / dates / text); sorting moves data only, never cell styles; ranges containing merged cells or formulas are auto-disabled; when adjacent data sits outside the selection, an Excel-style **Sort Warning** dialog lets you choose "Expand the selection" or "Sort the current selection only"
33
- - **Auto Filter (AutoFilter)**: Excel-style auto filter on a normal range. Enable via the toolbar **Data → Filter** or `Ctrl+Shift+L`; select a data cell (or a single-row multi-column range) and the filter is created by intelligently probing the contiguous data region **downward** from that header row. Each column header shows a filter drop-down arrow; opening it reveals the filter panel (values / text / number / date / search / blanks / multi-column AND). Clearing one column's criteria clears only that column; removing the AutoFilter clears everything. Only columns inside the Filter Range get an arrow; selecting a single merged cell with no active filter disables the filter button — *see [Auto Filter (AutoFilter)](#auto-filter-autofilter)*
33
+ - **Auto Filter (AutoFilter)**: Excel-style auto filter on a normal range. Enable via the toolbar **Data → Filter** or `Ctrl+Shift+L`; select a data cell (or a single-row multi-column range) and the filter is created by intelligently probing the contiguous data region **downward** from that header row. Each column header shows a filter drop-down arrow; opening it reveals the filter panel (values / text / number / date / search / blanks / multi-column AND). Clearing one column's criteria clears only that column; removing the AutoFilter clears everything. Only columns inside the Filter Range get an arrow; selecting a single merged cell with no active filter disables the filter button - *see [Auto Filter (AutoFilter)](#auto-filter-autofilter)*
34
34
  - **Find & Replace**: Open via the toolbar find button or `Ctrl/Cmd+F` (also `Ctrl/Cmd+H`); three scopes: current sheet / entire workbook / current selection; match case and match entire cell; highlights all matches and locates the active one with wrap-around navigation; single and replace-all both integrate with undo/redo, mutating only the raw `value` (always kept a string), never format / border / merge
35
- - **Conditional Formatting**: Excel-style conditional formatting rules stored per-sheet. The toolbar "Conditional Formatting" dropdown offers preset rules (greater than / less than / between / text contains / duplicate / unique / blank / not blank) and a "New Rule" dialog with cell value and formula-based conditions. Rules render as temporary style overlays on top of the base cell style — no mutation of the underlying cell `value` or `styleId`. Rule priority (smaller number = higher), `stopIfTrue` semantics, and automatic cache invalidation on cell value change. Inserting / deleting rows or columns adjusts rule ranges and formula references via the same `shiftFormulaRefs` engine used by auto-fill. Includes a "Manage Rules" dialog for editing, reordering, and deleting rules; the "Clear Rules" sub-dialog lets you clear all rules or rules from selected ranges. Render-time style synthesis correctly composes CF background, font color, bold, italic, underline, and strikethrough with the base cell style. *see [Conditional Formatting](#conditional-formatting)*
36
- - **Data Validation**: Excel-style data validation rules stored per-sheet. The toolbar "Data Validation" button opens a dialog to set the allowed type (any value / list / whole number / decimal / date / time / text length / custom formula), the operator (between, not between, equal, greater than, …), and the criterion values. List validation shows an in-cell dropdown with search and full keyboard navigation (↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc). Per-rule input message and error alert (Stop / Warning / Information) are supported; validation runs *before* the value is committed, so Stop-level violations reject the edit while Warning/Information let the user confirm. All rules on a range must pass. Rules persist through v-model, integrate with undo/redo, and adjust automatically on row/column insert/delete. Copy/paste and Auto Fill validate atomically — a Stop violation cancels the whole operation. *see [Data Validation](#data-validation)*
35
+ - **Conditional Formatting**: Excel-style conditional formatting rules stored per-sheet. The toolbar "Conditional Formatting" dropdown offers preset rules (greater than / less than / between / text contains / duplicate / unique / blank / not blank) and a "New Rule" dialog with cell value and formula-based conditions. Rules render as temporary style overlays on top of the base cell style - no mutation of the underlying cell `value` or `styleId`. Rule priority (smaller number = higher), `stopIfTrue` semantics, and automatic cache invalidation on cell value change. Inserting / deleting rows or columns adjusts rule ranges and formula references via the same `shiftFormulaRefs` engine used by auto-fill. Includes a "Manage Rules" dialog for editing, reordering, and deleting rules; the "Clear Rules" sub-dialog lets you clear all rules or rules from selected ranges. Render-time style synthesis correctly composes CF background, font color, bold, italic, underline, and strikethrough with the base cell style. *see [Conditional Formatting](#conditional-formatting)*
36
+ - **Data Validation**: Excel-style data validation rules stored per-sheet. The toolbar "Data Validation" button opens a dialog to set the allowed type (any value / list / whole number / decimal / date / time / text length / custom formula), the operator (between, not between, equal, greater than, …), and the criterion values. List validation shows an in-cell dropdown with search and full keyboard navigation (↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc). Per-rule input message and error alert (Stop / Warning / Information) are supported; validation runs *before* the value is committed, so Stop-level violations reject the edit while Warning/Information let the user confirm. All rules on a range must pass. Rules persist through v-model, integrate with undo/redo, and adjust automatically on row/column insert/delete. Copy/paste and Auto Fill validate atomically - a Stop violation cancels the whole operation. *see [Data Validation](#data-validation)*
37
37
  - **Cell Notes**: Excel-style cell notes (comments). Right-click menu to create / edit / delete; red triangle indicator at cell corner marks cells with notes; hover to preview, click to edit. Notes are independent from cell values and styles, stored via a Note Pool (Sheet-level notes pool + cell.noteId reference), so sort / insert-delete moves cells together with their notes. Notes support multi-line text, optional author, and create/update timestamps. Author name can be set via the noteAuthor prop. *see [Cell Notes](#cell-notes)*
38
38
  - **Auto Fill (Fill Handle)**: Excel-style fill handle at the bottom-right corner of the active selection. Drag it up/down/left/right to fill cells: single values copy, number/date/text-number sequences auto-continue (e.g. `1,2 → 3,4,5`), formula references adjust per relative/absolute/mixed rules (e.g. `=A1*2` → `=A2*2`), and source `styleId` is reused via the style pool. Live preview during drag, edge auto-scroll, dynamic sheet expansion, freeze-pane compatibility, and a single undo step per operation: *see [Auto Fill](#auto-fill)*
39
- - **Row / Column Grouping & Collapsing**: Excel-style row/column outlines. Select whole rows or columns, then the toolbar "Group" dropdown (between Sort and Filter) offers Add Group / Ungroup / Clear Outline / Expand All / Collapse All, applied directly to the selected axis; row/column context menus expose a matching "Group" submenu. Only one level of non-overlapping groups is supported — nesting and partial overlap are rejected with a message. Group ranges get alternating background colors in the row/column header band; ± collapse buttons float inside the header band without reserving a separate gutter. Collapsed rows/columns are skipped during content rendering; inserting/deleting rows or columns shifts group ranges and auto-removes emptied groups. Groups persist per-sheet (`rowOutlines` / `columnOutlines`) through v-model, and every mutation integrates with undo/redo. *see [Row / Column Grouping & Collapsing](#row--column-grouping--collapsing)*
39
+ - **Row / Column Grouping & Collapsing**: Excel-style row/column outlines. Select whole rows or columns, then the toolbar "Group" dropdown (between Sort and Filter) offers Add Group / Ungroup / Clear Outline / Expand All / Collapse All, applied directly to the selected axis; row/column context menus expose a matching "Group" submenu. Only one level of non-overlapping groups is supported - nesting and partial overlap are rejected with a message. Group ranges get alternating background colors in the row/column header band; ± collapse buttons float inside the header band without reserving a separate gutter. Collapsed rows/columns are skipped during content rendering; inserting/deleting rows or columns shifts group ranges and auto-removes emptied groups. Groups persist per-sheet (`rowOutlines` / `columnOutlines`) through v-model, and every mutation integrates with undo/redo. *see [Row / Column Grouping & Collapsing](#row--column-grouping--collapsing)*
40
40
 
41
41
  ### Interaction
42
42
  - **Smart Selection**: Click, drag, Shift+Click, row/column header select, corner-cell select-all
@@ -166,10 +166,9 @@ interface SheetModelData {
166
166
  freeze?: { rows: number; cols: number };
167
167
  colWidths?: Record<number, number>;
168
168
  rowHeights?: Record<number, number>;
169
- /** Logic column count (0-based exclusive). Defaults to 26 when omitted. */
170
- colCount?: number;
171
- /** Logic row count (0-based exclusive). Defaults to 200 when omitted. */
172
- rowCount?: number;
169
+ // Grid dimensions (colCount/rowCount) are not persisted: they are derived from
170
+ // content on load (deriveModelDims), at least the props default 26x200, then
171
+ // extended by data / custom row/column sizes / rule ranges
173
172
  /** Cell notes pool: CellNote objects keyed by noteId; cell.noteId references into this pool */
174
173
  notes?: Record<string, CellNote>;
175
174
  }
@@ -191,7 +190,7 @@ import type {
191
190
  CellData, // { value: string; styleId?: number }
192
191
  CellStyle, // Typed style interface (font, color, border, alignment, etc.)
193
192
  BorderStyle, // Four-side border { top; right; bottom; left }
194
- BorderSide, // Single border side { width; color; style }
193
+ BorderSide, // Single border side { width; color; style; owner? }
195
194
  BorderSource, // Border source 'cell' | 'merge'
196
195
  Range, // { start: number; end: number }
197
196
  SpreadsheetOptions,
@@ -220,43 +219,62 @@ src/
220
219
  └── spreader/
221
220
  ├── index.ts # Unified export: component + types (barrel)
222
221
  ├── components/
223
- │ ├── spreader.vue # Entry component: template + style + composition
224
- │ ├── toolbar.vue # Toolbar with overflow dropdown
225
- │ ├── tabbar.vue # Sheet tab bar
226
- │ ├── dropdown.vue # Generic dropdown component
227
- │ ├── find-replace-bar.vue # Find/replace bar UI
228
- note-overlay.vue # Note view/edit overlay (dual-mode: view + edit)
222
+ │ ├── spreader.vue # Entry component: template + style + composition
223
+ │ ├── toolbar.vue # Toolbar with overflow dropdown
224
+ │ ├── tabbar.vue # Sheet tab bar
225
+ │ ├── dropdown.vue # Generic dropdown component
226
+ │ ├── find-replace-bar.vue # Find/replace bar UI
227
+ │ ├── note-overlay.vue # Note view/edit overlay (dual-mode: view + edit)
229
228
  │ └── pickers/
230
- │ ├── colorPicker.vue # Text & fill color picker
231
- │ ├── borderPicker.vue # Border picker
232
- │ ├── mergePicker.vue # Merge cell picker
233
- │ ├── numberFormatDialog.vue # Number format custom dialog
234
- │ ├── sortPicker.vue # Sort dropdown
235
- │ ├── sortConfirmDialog.vue # Excel-style sort warning dialog
236
- │ ├── calcPicker.vue # Sum / average / count picker
237
- │ └── insertFunctionDialog.vue # Insert function dialog
229
+ │ ├── border-picker.vue # Border picker (per-side type/color/line style + submenus)
230
+ │ ├── calc-picker.vue # Sum / average / count picker
231
+ │ ├── color-palette.vue # Shared color swatch palette for text / fill / border
232
+ │ ├── color-picker.vue # Text & fill color picker
233
+ │ ├── conditional-format-manager.vue # Conditional formatting "Manage Rules" dialog
234
+ │ ├── conditional-format-menu.vue # Conditional formatting toolbar dropdown (presets + new / manage / clear rules)
235
+ │ ├── conditional-format-rule-editor.vue # Conditional formatting "New / Edit Rule" dialog
236
+ │ ├── data-validation-alert.vue # Data validation error alert (Stop / Warning / Information)
237
+ │ ├── data-validation-dialog.vue # Data validation settings dialog
238
+ │ ├── data-validation-dropdown.vue # Data validation list dropdown (search + virtual list + keyboard)
239
+ │ ├── data-validation-input-message.vue # Cell-focus input message tooltip
240
+ │ ├── filter-popup.vue # Auto Filter panel (values / text / number / date + search + blanks + multi-column AND)
241
+ │ ├── insert-function-dialog.vue # Insert function dialog
242
+ │ ├── merge-picker.vue # Merge cell picker
243
+ │ ├── number-format-dialog.vue # Number format dialog
244
+ │ ├── outline-picker.vue # Toolbar "Group" dropdown (add / ungroup / clear, expand / collapse)
245
+ │ ├── sort-confirm-dialog.vue # Excel-style sort warning dialog
246
+ │ └── sort-picker.vue # Sort dropdown
238
247
  ├── composables/
239
- │ ├── core-state.ts # Props, cells/merges/selection, font metrics, navigation
240
- │ ├── undo-styles.ts # Undo/redo, format painter, font/alignment/color
241
- │ ├── borders-merge.ts # Border ops, merge ops, clipboard, sum
242
- │ ├── sheets-ops.ts # Row/col ops, multi-sheet, v-model emit, theme, refs
243
- │ ├── find-replace.ts # Find/replace state & interaction (Vue-dependent)
244
- notes.ts # Note pool management: CRUD, persistence, undo integration
245
- │ └── interactions.ts # Renderer, formula bar, tab bar, context menu, scrollbar, events
248
+ │ ├── borders-merge.ts # Border ops, merge ops, clipboard, sum/avg/count
249
+ │ ├── core-state.ts # Props, cells/merges/selection, font metrics, navigation, outline state
250
+ │ ├── find-replace.ts # Find/replace state & interaction (Vue-dependent)
251
+ │ ├── interactions.ts # Renderer, formula bar, tab bar, context menu, scrollbar, events
252
+ │ ├── sheets-ops.ts # Row/col ops, multi-sheet, v-model emit, theme, refs
253
+ │ ├── undo-styles.ts # Undo/redo, format painter, font/alignment/color
254
+ │ └── float-menu-position.ts # Shared toolbar dropdown positioning (right-anchor + flip/clamp)
246
255
  └── core/
247
- ├── constants.ts # Layout constants, i18n, theme palettes
248
- ├── types.ts # All type definitions
249
- ├── style-pool.ts # Style pool: dedup, registration, resolve, migration, GC
250
- ├── border-pool.ts # Border pool: dedup, registration, resolve, migration, GC
251
- ├── border-resolve.ts # Shared-border conflict resolution (resolveSharedBorder)
252
- ├── formula.ts # Formula engine (parse, evaluate, deps, cache)
253
- ├── find-replace-core.ts # Find/replace pure algorithms (zero Vue deps, unit-testable)
254
- ├── sort-core.ts # Sort pure algorithms (zero Vue deps, unit-testable)
255
- ├── autofill.ts # Auto-fill pure engine (pattern inference, fill handle logic, zero Vue deps)
256
- ├── number-format.ts # Number format engine (Excel-style display formatting)
257
- ├── conditional-formatting.ts # Conditional formatting engine (condition eval, rule cache, formula ref shift)
258
- ├── theme.ts # Theme CSS variable construction
259
- └── utils.ts # Pure utilities (col label, hit test, resolve size)
256
+ ├── autofill.ts # Auto-fill pure engine (pattern inference, fill handle, zero Vue deps)
257
+ ├── border-color.ts # Per-side border color: color decoupled from line type, plan color changes
258
+ ├── border-icon.ts # Border icon single source (toolbar + picker)
259
+ ├── border-pool.ts # Border pool: dedup, registration, resolve, migration, GC
260
+ ├── border-resolve.ts # Shared-border conflict resolution (resolveSharedBorder)
261
+ ├── border-style.ts # Border line type (solid / dashed / dotted): normalize + dash pattern
262
+ ├── conditional-formatting.ts # Conditional formatting engine (condition eval + rule cache, pure)
263
+ ├── constants.ts # Layout constants, i18n text, theme color palette
264
+ ├── data-validation.ts # Data validation engine (pure)
265
+ ├── filter-core.ts # AutoFilter engine (pure, cascading candidates)
266
+ ├── find-replace-core.ts # Find/replace pure algorithms (zero Vue deps, unit-testable)
267
+ ├── formula.ts # Formula engine (parsing, evaluation, dependency tracking)
268
+ ├── model-dims.ts # Model dimensions derived from content (pure)
269
+ ├── notes.ts # Note pool management: CRUD, persistence, undo integration
270
+ ├── number-format.ts # Number format engine (Excel-style display formatting)
271
+ ├── outline-core.ts # Row/column grouping pure engine (validation / shifting / collapse)
272
+ ├── sort-core.ts # Sort pure algorithms (zero Vue deps, unit-testable)
273
+ ├── sort-icon.ts # Sort icon single source (toolbar + picker)
274
+ ├── style-pool.ts # Style pool: dedup, registration, resolve, migration, GC
275
+ ├── theme.ts # Theme CSS variable construction
276
+ ├── types.ts # All type definitions
277
+ └── utils.ts # Pure utilities (column label, hit test, resolve size)
260
278
  ```
261
279
 
262
280
  ### Design Principles
@@ -382,11 +400,11 @@ An Excel-style number formatting engine (`spreader/core/number-format.ts`) that
382
400
 
383
401
  ## Conditional Formatting
384
402
 
385
- Excel-style conditional formatting stored per-sheet in `SheetState.conditionalFormats` (array of `ConditionalFormattingRule`). The engine lives in `spreader/core/conditional-formatting.ts` — pure-logic, zero Vue/Canvas deps, with unit tests in `test/conditional-formatting.test.ts`.
403
+ Excel-style conditional formatting stored per-sheet in `SheetState.conditionalFormats` (array of `ConditionalFormattingRule`). The engine lives in `spreader/core/conditional-formatting.ts` - pure-logic, zero Vue/Canvas deps, with unit tests in `test/conditional-formatting.test.ts`.
386
404
 
387
405
  ### Rule Structure
388
406
 
389
- Each rule has a stable `id`, a `condition`, a `format` override, one or more `ranges`, a numeric `priority` (smaller = higher precedence), `stopIfTrue`, and `enabled` flag. The `format` carries only visual properties (`backgroundColor`, `color`, `fontWeight`, `fontStyle`, `underline`, `strikethrough`) — it never writes back to `cell.styleId`.
407
+ Each rule has a stable `id`, a `condition`, a `format` override, one or more `ranges`, a numeric `priority` (smaller = higher precedence), `stopIfTrue`, and `enabled` flag. The `format` carries only visual properties (`backgroundColor`, `color`, `fontWeight`, `fontStyle`, `underline`, `strikethrough`) - it never writes back to `cell.styleId`.
390
408
 
391
409
  ### Supported Condition Types
392
410
 
@@ -413,25 +431,25 @@ Duplicate / unique / top-bottom / above-below rules need a value-frequency stati
413
431
 
414
432
  ### Insert / Delete Propagation
415
433
 
416
- `shiftFormulaRefsSafe` in the CF engine reuses the same `shiftFormulaRefs` function from the formula engine — so `$B$2` (absolute) stays locked while `A1` (relative) shifts. Rule ranges also expand/shrink accordingly; ranges completely removed from the sheet are auto-deleted.
434
+ `shiftFormulaRefsSafe` in the CF engine reuses the same `shiftFormulaRefs` function from the formula engine - so `$B$2` (absolute) stays locked while `A1` (relative) shifts. Rule ranges also expand/shrink accordingly; ranges completely removed from the sheet are auto-deleted.
417
435
 
418
436
  ### Render-time Style Synthesis
419
437
 
420
- `resolveConditionalFormatting` is called per viewport cell inside `drawCells`. It walks all enabled rules whose ranges contain the cell, applies priority-merging, and produces a temporary `cfStyle`. `applyCfFormat` merges this onto the base resolved style, composing `backgroundColor`, `color`, `fontWeight`, `fontStyle`, `underline`, and `strikethrough` — including correct handling of merged cells (CF applies to the anchor cell and propagates to the entire merge region).
438
+ `resolveConditionalFormatting` is called per viewport cell inside `drawCells`. It walks all enabled rules whose ranges contain the cell, applies priority-merging, and produces a temporary `cfStyle`. `applyCfFormat` merges this onto the base resolved style, composing `backgroundColor`, `color`, `fontWeight`, `fontStyle`, `underline`, and `strikethrough` - including correct handling of merged cells (CF applies to the anchor cell and propagates to the entire merge region).
421
439
 
422
440
  ### UI Components
423
441
 
424
442
  | File | Role |
425
443
  |------|------|
426
444
  | `conditional-format-menu.vue` | Toolbar dropdown: preset rules (highlight cells, blank, duplicate, unique), New Rule, Manage Rules, Clear Rules submenu. Teleport-based submenus with hover-intent (150ms debounce) for cross-gap hover transitions |
427
- | `conditional-format-rule-editor.vue` | "New Rule" dialog: condition type selector (reuses `SpDropdown`), threshold inputs, format panel (background color picker, text color picker, bold, italic, underline, strikethrough — all reuse toolbar picker/button styles), application range auto-filled from current selection |
445
+ | `conditional-format-rule-editor.vue` | "New Rule" dialog: condition type selector (reuses `SpDropdown`), threshold inputs, format panel (background color picker, text color picker, bold, italic, underline, strikethrough - all reuse toolbar picker/button styles), application range auto-filled from current selection |
428
446
  | `conditional-format-manager.vue` | "Manage Rules" dialog: list all rules for the active sheet, drag-to-reorder priority toggle, edit/delete per rule, clear all / clear selection |
429
447
 
430
448
  ---
431
449
 
432
450
  ## Data Validation
433
451
 
434
- Excel-style data validation stored per-sheet in `SheetState.dataValidations` (array of `DataValidationRule`). The engine lives in `spreader/core/data-validation.ts` — pure-logic, zero Vue/Canvas deps, with unit tests in `test/data-validation.test.ts` and `test/data-validation-integration.test.ts`.
452
+ Excel-style data validation stored per-sheet in `SheetState.dataValidations` (array of `DataValidationRule`). The engine lives in `spreader/core/data-validation.ts` - pure-logic, zero Vue/Canvas deps, with unit tests in `test/data-validation.test.ts` and `test/data-validation-integration.test.ts`.
435
453
 
436
454
  ### Rule Structure
437
455
 
@@ -441,7 +459,7 @@ Each rule has a stable `id`, a `type`, an `operator`, criterion values `formula1
441
459
 
442
460
  | Type | Description |
443
461
  |------|-------------|
444
- | `any` | No constraint — selecting it on save clears validation for the range |
462
+ | `any` | No constraint - selecting it on save clears validation for the range |
445
463
  | `list` | In-cell dropdown from fixed values or a cell range |
446
464
  | `wholeNumber` / `decimal` | Numeric constraint |
447
465
  | `date` / `time` | Date / time constraint (ISO, `YYYY年M月D日`, US `m/d/yyyy`, or Excel serial) |
@@ -456,7 +474,7 @@ List validation resolves its items from either fixed `values` (`{ type: 'values'
456
474
 
457
475
  ### Validation Flow
458
476
 
459
- Validation runs **before** the value is committed. `commitEdit` returns `Promise<boolean>` — `false` means a Stop-level violation rejected the edit and the editor state is preserved. Warning/Information alerts call `showValidationAlert` and await user confirmation before proceeding. When multiple rules target the same range, **all** must pass (`rules.every()`).
477
+ Validation runs **before** the value is committed. `commitEdit` returns `Promise<boolean>` - `false` means a Stop-level violation rejected the edit and the editor state is preserved. Warning/Information alerts call `showValidationAlert` and await user confirmation before proceeding. When multiple rules target the same range, **all** must pass (`rules.every()`).
460
478
 
461
479
  ### Range Index
462
480
 
@@ -464,7 +482,7 @@ A spatial row-band index (`DvIndex`) maps a (row, col) lookup to candidate rules
464
482
 
465
483
  ### Atomic Paste / Auto Fill
466
484
 
467
- Copy/paste and Auto Fill compute every candidate first, run `validateCells`, and cancel the whole operation if any Stop-level violation occurs — no partial commit.
485
+ Copy/paste and Auto Fill compute every candidate first, run `validateCells`, and cancel the whole operation if any Stop-level violation occurs - no partial commit.
468
486
 
469
487
  ### Insert / Delete Propagation
470
488
 
@@ -477,7 +495,7 @@ Copy/paste and Auto Fill compute every candidate first, run `validateCells`, and
477
495
  | `data-validation-dialog.vue` | Toolbar "Data Validation" dialog: type/operator/value, list source, input message, error alert, apply range; validates min/max literals on save |
478
496
  | `data-validation-dropdown.vue` | In-cell list dropdown: virtual list, search box, full keyboard nav (↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc) |
479
497
  | `data-validation-alert.vue` | Stop / Warning / Information alert box |
480
- | `data-validation-input-message.vue` | Hover tooltip showing the rule's input message (lives in `components/`, not `pickers/`) |
498
+ | `data-validation-input-message.vue` | Hover tooltip showing the rule's input message |
481
499
 
482
500
  ### Persistence & Undo
483
501
 
@@ -514,7 +532,7 @@ Inserting/deleting rows or columns shifts group ranges via `addOutlineForInsert`
514
532
 
515
533
  ## Cell Notes
516
534
 
517
- Excel-style cell notes (comments), fully independent from cell values and styles. The engine lives in `spreader/core/notes.ts` — pure-logic, zero Vue/Canvas deps; the UI overlay is `components/note-overlay.vue`.
535
+ Excel-style cell notes (comments), fully independent from cell values and styles. The engine lives in `spreader/core/notes.ts` - pure-logic, zero Vue/Canvas deps; the UI overlay is `components/note-overlay.vue`.
518
536
 
519
537
  ### Data Model
520
538
 
@@ -533,7 +551,7 @@ interface CellNote {
533
551
  - **`CellData.noteId`**: Per-cell reference pointing into `SheetState.notes`
534
552
  - **`SheetModelData.notes`**: v-model serialized note pool, keyed by noteId
535
553
 
536
- The Note Pool design ensures that sort, insert/delete row/column, and other cell-moving operations carry `noteId` together with the cell data — notes never get orphaned.
554
+ The Note Pool design ensures that sort, insert/delete row/column, and other cell-moving operations carry `noteId` together with the cell data - notes never get orphaned.
537
555
 
538
556
  ### Entry Points
539
557
 
@@ -544,7 +562,7 @@ The Note Pool design ensures that sort, insert/delete row/column, and other cell
544
562
  ### Rendering
545
563
 
546
564
  - Cells with a note show a 6px red triangle indicator in the top-right corner, drawn on Canvas (zero DOM cost)
547
- - The `note-overlay.vue` component is a Vue DOM overlay with `position: absolute` mounted inside `.spreadsheet-wrapper`, constrained by `overflow: hidden` to the table area — it never spills over the toolbar, row/column headers, frozen panes, or scrollbars
565
+ - The `note-overlay.vue` component is a Vue DOM overlay with `position: absolute` mounted inside `.spreadsheet-wrapper`, constrained by `overflow: hidden` to the table area - it never spills over the toolbar, row/column headers, frozen panes, or scrollbars
548
566
  - Z-index layering: non-frozen overlays sit *below* the freeze canvas (z-index: 0 vs 1), frozen-region overlays sit *above* it (z-index: 2); scrollbars (z-index: 3) always stay on top for interactivity
549
567
 
550
568
  ### Author Name
@@ -661,9 +679,10 @@ Reuses the existing engine: the title shows the column's header text (not the co
661
679
  A dedicated border storage & rendering mechanism (`spreader/core/border-pool.ts` + `border-resolve.ts`).
662
680
 
663
681
  - **Dedicated border pool**: each sheet maintains a `borders: BorderStyle[]` pool (`borders[0]` is always the empty border `{}`); styles reference borders by index (`borderId`), and identical borders are deduplicated.
664
- - **Shared-border resolution**: adjacent cells' shared border is resolved at render time via `resolveSharedBorder()`: wider wins, then the first side on equal width; setting a border no longer mutates neighboring cells.
682
+ - **Shared-border resolution**: adjacent cells' shared border is resolved at render time via `resolveSharedBorder()`: wider wins, then the first side on equal width; when exactly one side carries an `owner` flag (set by an explicit border or selection operation) that side wins the shared edge regardless of width, so a freshly applied selection-boundary edge takes priority over a pre-existing neighbor edge. Setting a border no longer mutates neighboring cells.
665
683
  - **Merged cells**: merged-region borders are stored at the anchor (top-left); internal borders are masked, outer boundaries are resolved segment-wise against neighbors, and the four corners fill in corner blocks.
666
684
  - **Legacy compatibility**: legacy inline border props (`borderTopWidth`, etc., deprecated) are auto-migrated via `migrateBordersInStyles()` on load.
685
+ - **Per-side color & line type (decoupled)**: both live on each `BorderSide` (`{ width; color; style }`). Color is a standard HEX string; `''` / omitted means "auto" and renders as the default `#444`. Line `style` is one of `solid` / `dashed` / `dotted`; legacy data with no `style` defaults to `solid`, and a `solid` side stores no `style` key so its dedup key matches historical empty-border data. Changing the color only touches `color` and keeps `width`/`style`; changing the line type only touches `style` and keeps `width`/`color`. The border picker exposes two submenus: a color selector (reuses the text-color palette with an **Automatic** entry, colors only existing borders) and a **line-type** selector (solid / dashed / dotted previews, sets the active pen line type, then applies it to subsequent border operations and to the selection's existing edges, never creating a border that isn't there). Pure logic lives in `core/border-color.ts` (`withBorderColor` / `withBorderWidth` / `planBorderColorChanges` / `resolveSelectionBorderColor`) and `core/border-style.ts` (`normalizeBorderLineStyle` / `borderLineDash`), fully decoupled from Vue/Canvas and unit-tested. The renderer draws dashed/dotted via `paintBorderEdge` (solid keeps the original `fillRect` path so grid lines and the selection frame stay untouched).
667
686
 
668
687
  ---
669
688