xiaodao-spreader 0.1.37 → 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 (41) hide show
  1. package/README.ZH.md +121 -48
  2. package/README.md +126 -54
  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/note-overlay.vue.d.ts +47 -0
  6. package/dist/types/components/spreader/components/pickers/border-picker.vue.d.ts +16 -2
  7. package/dist/types/components/spreader/components/pickers/calc-picker.vue.d.ts +1 -1
  8. package/dist/types/components/spreader/components/pickers/color-palette.vue.d.ts +17 -0
  9. package/dist/types/components/spreader/components/pickers/color-picker.vue.d.ts +1 -1
  10. package/dist/types/components/spreader/components/pickers/data-validation-dropdown.vue.d.ts +1 -1
  11. package/dist/types/components/spreader/components/pickers/insert-function-dialog.vue.d.ts +1 -1
  12. package/dist/types/components/spreader/components/pickers/merge-picker.vue.d.ts +1 -1
  13. package/dist/types/components/spreader/components/pickers/number-format-dialog.vue.d.ts +1 -1
  14. package/dist/types/components/spreader/components/pickers/outline-picker.vue.d.ts +1 -1
  15. package/dist/types/components/spreader/components/pickers/sort-confirm-dialog.vue.d.ts +1 -1
  16. package/dist/types/components/spreader/components/pickers/sort-picker.vue.d.ts +1 -1
  17. package/dist/types/components/spreader/components/spreader.vue.d.ts +17 -5
  18. package/dist/types/components/spreader/components/toolbar.vue.d.ts +10 -2
  19. package/dist/types/components/spreader/composables/borders-merge.d.ts +19 -3
  20. package/dist/types/components/spreader/composables/core-state.d.ts +28 -2
  21. package/dist/types/components/spreader/composables/find-replace.d.ts +5 -0
  22. package/dist/types/components/spreader/composables/interactions.d.ts +26 -0
  23. package/dist/types/components/spreader/composables/sheets-ops.d.ts +5 -0
  24. package/dist/types/components/spreader/composables/undo-styles.d.ts +5 -0
  25. package/dist/types/components/spreader/core/border-color.d.ts +138 -0
  26. package/dist/types/components/spreader/core/border-icon.d.ts +43 -0
  27. package/dist/types/components/spreader/core/border-pool.d.ts +1 -1
  28. package/dist/types/components/spreader/core/border-resolve.d.ts +12 -3
  29. package/dist/types/components/spreader/core/border-style.d.ts +24 -0
  30. package/dist/types/components/spreader/core/constants.d.ts +3 -2
  31. package/dist/types/components/spreader/core/model-dims.d.ts +19 -0
  32. package/dist/types/components/spreader/core/notes.d.ts +51 -0
  33. package/dist/types/components/spreader/core/sort-icon.d.ts +25 -0
  34. package/dist/types/components/spreader/core/style-pool.d.ts +1 -1
  35. package/dist/types/components/spreader/core/theme.d.ts +5 -0
  36. package/dist/types/components/spreader/core/types.d.ts +38 -4
  37. package/dist/xiaodao-spreader.es.js +8665 -7712
  38. package/dist/xiaodao-spreader.umd.js +9 -9
  39. package/package.json +1 -1
  40. /package/dist/types/components/spreader/components/{data-validation-input-message.vue.d.ts → pickers/data-validation-input-message.vue.d.ts} +0 -0
  41. /package/dist/types/components/spreader/composables/{useFloatMenuPosition.d.ts → float-menu-position.d.ts} +0 -0
package/README.md CHANGED
@@ -24,18 +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
+ - **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)*
37
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)*
38
- - **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)*
39
40
 
40
41
  ### Interaction
41
42
  - **Smart Selection**: Click, drag, Shift+Click, row/column header select, corner-cell select-all
@@ -140,6 +141,7 @@ const myData = ref<SheetModelData[]>([
140
141
  | `height` | `number \| string` | - | Component height (pixels; omit or pass `string` for responsive) |
141
142
  | `theme` | `'light' \| 'dark'` | `'light'` | Theme mode |
142
143
  | `locale` | `string` | `'zh-CN'` | Language: `'zh-CN'` \| `'en-US'` |
144
+ | `noteAuthor` | `string` | `''` | Current note author name; when empty, saved notes have empty author field that renders as i18n placeholder |
143
145
 
144
146
  ---
145
147
 
@@ -157,15 +159,18 @@ interface SheetModelData {
157
159
  cells: Record<string, {
158
160
  value: string;
159
161
  styleId?: number;
162
+ /** Note reference pointing to a CellNote in the Sheet-level notes pool */
163
+ noteId?: string;
160
164
  }>;
161
165
  merges?: Record<string, SelectionRange>;
162
166
  freeze?: { rows: number; cols: number };
163
167
  colWidths?: Record<number, number>;
164
168
  rowHeights?: Record<number, number>;
165
- /** Logic column count (0-based exclusive). Defaults to 26 when omitted. */
166
- colCount?: number;
167
- /** Logic row count (0-based exclusive). Defaults to 200 when omitted. */
168
- 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
172
+ /** Cell notes pool: CellNote objects keyed by noteId; cell.noteId references into this pool */
173
+ notes?: Record<string, CellNote>;
169
174
  }
170
175
  ```
171
176
 
@@ -185,7 +190,7 @@ import type {
185
190
  CellData, // { value: string; styleId?: number }
186
191
  CellStyle, // Typed style interface (font, color, border, alignment, etc.)
187
192
  BorderStyle, // Four-side border { top; right; bottom; left }
188
- BorderSide, // Single border side { width; color; style }
193
+ BorderSide, // Single border side { width; color; style; owner? }
189
194
  BorderSource, // Border source 'cell' | 'merge'
190
195
  Range, // { start: number; end: number }
191
196
  SpreadsheetOptions,
@@ -214,41 +219,62 @@ src/
214
219
  └── spreader/
215
220
  ├── index.ts # Unified export: component + types (barrel)
216
221
  ├── components/
217
- │ ├── spreader.vue # Entry component: template + style + composition
218
- │ ├── toolbar.vue # Toolbar with overflow dropdown
219
- │ ├── tabbar.vue # Sheet tab bar
220
- │ ├── dropdown.vue # Generic dropdown component
221
- │ ├── find-replace-bar.vue # Find/replace bar UI
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)
222
228
  │ └── pickers/
223
- │ ├── colorPicker.vue # Text & fill color picker
224
- │ ├── borderPicker.vue # Border picker
225
- │ ├── mergePicker.vue # Merge cell picker
226
- │ ├── numberFormatDialog.vue # Number format custom dialog
227
- │ ├── sortPicker.vue # Sort dropdown
228
- │ ├── sortConfirmDialog.vue # Excel-style sort warning dialog
229
- │ ├── calcPicker.vue # Sum / average / count picker
230
- │ └── 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
231
247
  ├── composables/
232
- │ ├── core-state.ts # Props, cells/merges/selection, font metrics, navigation
233
- │ ├── undo-styles.ts # Undo/redo, format painter, font/alignment/color
234
- │ ├── borders-merge.ts # Border ops, merge ops, clipboard, sum
235
- │ ├── sheets-ops.ts # Row/col ops, multi-sheet, v-model emit, theme, refs
236
- │ ├── find-replace.ts # Find/replace state & interaction (Vue-dependent)
237
- │ └── 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)
238
255
  └── core/
239
- ├── constants.ts # Layout constants, i18n, theme palettes
240
- ├── types.ts # All type definitions
241
- ├── style-pool.ts # Style pool: dedup, registration, resolve, migration, GC
242
- ├── border-pool.ts # Border pool: dedup, registration, resolve, migration, GC
243
- ├── border-resolve.ts # Shared-border conflict resolution (resolveSharedBorder)
244
- ├── formula.ts # Formula engine (parse, evaluate, deps, cache)
245
- ├── find-replace-core.ts # Find/replace pure algorithms (zero Vue deps, unit-testable)
246
- ├── sort-core.ts # Sort pure algorithms (zero Vue deps, unit-testable)
247
- ├── autofill.ts # Auto-fill pure engine (pattern inference, fill handle logic, zero Vue deps)
248
- ├── number-format.ts # Number format engine (Excel-style display formatting)
249
- ├── conditional-formatting.ts # Conditional formatting engine (condition eval, rule cache, formula ref shift)
250
- ├── theme.ts # Theme CSS variable construction
251
- └── 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)
252
278
  ```
253
279
 
254
280
  ### Design Principles
@@ -374,11 +400,11 @@ An Excel-style number formatting engine (`spreader/core/number-format.ts`) that
374
400
 
375
401
  ## Conditional Formatting
376
402
 
377
- 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`.
378
404
 
379
405
  ### Rule Structure
380
406
 
381
- 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`.
382
408
 
383
409
  ### Supported Condition Types
384
410
 
@@ -405,25 +431,25 @@ Duplicate / unique / top-bottom / above-below rules need a value-frequency stati
405
431
 
406
432
  ### Insert / Delete Propagation
407
433
 
408
- `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.
409
435
 
410
436
  ### Render-time Style Synthesis
411
437
 
412
- `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).
413
439
 
414
440
  ### UI Components
415
441
 
416
442
  | File | Role |
417
443
  |------|------|
418
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 |
419
- | `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 |
420
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 |
421
447
 
422
448
  ---
423
449
 
424
450
  ## Data Validation
425
451
 
426
- 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`.
427
453
 
428
454
  ### Rule Structure
429
455
 
@@ -433,7 +459,7 @@ Each rule has a stable `id`, a `type`, an `operator`, criterion values `formula1
433
459
 
434
460
  | Type | Description |
435
461
  |------|-------------|
436
- | `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 |
437
463
  | `list` | In-cell dropdown from fixed values or a cell range |
438
464
  | `wholeNumber` / `decimal` | Numeric constraint |
439
465
  | `date` / `time` | Date / time constraint (ISO, `YYYY年M月D日`, US `m/d/yyyy`, or Excel serial) |
@@ -448,7 +474,7 @@ List validation resolves its items from either fixed `values` (`{ type: 'values'
448
474
 
449
475
  ### Validation Flow
450
476
 
451
- 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()`).
452
478
 
453
479
  ### Range Index
454
480
 
@@ -456,7 +482,7 @@ A spatial row-band index (`DvIndex`) maps a (row, col) lookup to candidate rules
456
482
 
457
483
  ### Atomic Paste / Auto Fill
458
484
 
459
- 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.
460
486
 
461
487
  ### Insert / Delete Propagation
462
488
 
@@ -469,7 +495,7 @@ Copy/paste and Auto Fill compute every candidate first, run `validateCells`, and
469
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 |
470
496
  | `data-validation-dropdown.vue` | In-cell list dropdown: virtual list, search box, full keyboard nav (↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc) |
471
497
  | `data-validation-alert.vue` | Stop / Warning / Information alert box |
472
- | `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 |
473
499
 
474
500
  ### Persistence & Undo
475
501
 
@@ -504,6 +530,51 @@ Inserting/deleting rows or columns shifts group ranges via `addOutlineForInsert`
504
530
 
505
531
  ---
506
532
 
533
+ ## Cell Notes
534
+
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`.
536
+
537
+ ### Data Model
538
+
539
+ Notes use a **Note Pool** storage pattern: each sheet maintains a `notes` pool keyed by stable `id`, and cells only store a `noteId` reference.
540
+
541
+ `` ypescript
542
+ interface CellNote {
543
+ id: string; // Stable unique ID
544
+ text: string; // Note body (multi-line text)
545
+ author?: string; // Optional author name
546
+ createdAt: number; // Creation time (epoch ms)
547
+ updatedAt: number; // Last modification time (epoch ms)
548
+ }
549
+ ``
550
+
551
+ - **`CellData.noteId`**: Per-cell reference pointing into `SheetState.notes`
552
+ - **`SheetModelData.notes`**: v-model serialized note pool, keyed by noteId
553
+
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.
555
+
556
+ ### Entry Points
557
+
558
+ - **Context menu**: Cell right-click menu offers *New Note / Edit Note / Delete Note*; edit/delete are hidden when no note exists, new is hidden when one does
559
+ - **Shift+F2**: Toggle the edit overlay on a note-bearing cell
560
+ - **Hover preview**: A read-only view overlay pops up when the cursor hovers over a cell with a note
561
+
562
+ ### Rendering
563
+
564
+ - Cells with a note show a 6px red triangle indicator in the top-right corner, drawn on Canvas (zero DOM cost)
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
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
567
+
568
+ ### Author Name
569
+
570
+ Set the current author via the `noteAuthor` prop. When empty, saved notes store an empty `author` field, and display falls back to an i18n placeholder (`Unnamed`).
571
+
572
+ ### Persistence & Undo
573
+
574
+ - Notes persist through the sheet v-model (`notes` field), round-tripping correctly through save / load / duplicate-sheet
575
+ - Creating / editing / deleting a note each produces one undo step, sharing the same undo stack as cell values, styles, and borders
576
+
577
+ ---
507
578
  ## Auto Fill
508
579
 
509
580
  An Excel-style fill-handle mechanism (`spreader/core/autofill.ts` pure engine + `composables/interactions.ts` canvas/interaction layer). Drag the small square at the bottom-right corner of the active selection to fill cells: no DOM handles, all drawn on Canvas.
@@ -608,9 +679,10 @@ Reuses the existing engine: the title shows the column's header text (not the co
608
679
  A dedicated border storage & rendering mechanism (`spreader/core/border-pool.ts` + `border-resolve.ts`).
609
680
 
610
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.
611
- - **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.
612
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.
613
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).
614
686
 
615
687
  ---
616
688
 
@@ -732,7 +804,7 @@ x-spreader is touch-first: every mouse interaction has a symmetric touch path, v
732
804
  - [x] Sort & filter: sort by displayed content with Excel-style Sort Warning dialog (expand selection / current selection only); see [Sorting & Sort Warning](#features)
733
805
  - [x] Auto Filter (AutoFilter): toolbar "Filter" / `Ctrl+Shift+L` to enable, header drop-down arrows, intelligent downward data probing, value/text/number/date multi-type filters, separate clear-column vs remove-all; see [Auto Filter (AutoFilter)](#auto-filter-autofilter)
734
806
  - [x] Touch & mobile interaction: symmetric touch paths for select / context menu / filter / resize / range select / format brush / tab menu; popups close on outside tap; see [Touch & Mobile Interaction](#touch--mobile-interaction)
735
- - [ ] Cell comments / notes
807
+ - [x] Cell comments / notes: Note Pool storage, context-menu CRUD, hover preview, edit overlay, persistence & undo; see [Cell Notes](#cell-notes)
736
808
  - [ ] Print layout
737
809
 
738
810
  ### Long-term