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.
- package/README.ZH.md +121 -48
- package/README.md +126 -54
- package/dist/style.css +1 -1
- package/dist/types/components/spreader/components/dropdown.vue.d.ts +3 -1
- package/dist/types/components/spreader/components/note-overlay.vue.d.ts +47 -0
- package/dist/types/components/spreader/components/pickers/border-picker.vue.d.ts +16 -2
- package/dist/types/components/spreader/components/pickers/calc-picker.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/pickers/color-palette.vue.d.ts +17 -0
- package/dist/types/components/spreader/components/pickers/color-picker.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/pickers/data-validation-dropdown.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/pickers/insert-function-dialog.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/pickers/merge-picker.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/pickers/number-format-dialog.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/pickers/outline-picker.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/pickers/sort-confirm-dialog.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/pickers/sort-picker.vue.d.ts +1 -1
- package/dist/types/components/spreader/components/spreader.vue.d.ts +17 -5
- package/dist/types/components/spreader/components/toolbar.vue.d.ts +10 -2
- package/dist/types/components/spreader/composables/borders-merge.d.ts +19 -3
- package/dist/types/components/spreader/composables/core-state.d.ts +28 -2
- package/dist/types/components/spreader/composables/find-replace.d.ts +5 -0
- package/dist/types/components/spreader/composables/interactions.d.ts +26 -0
- package/dist/types/components/spreader/composables/sheets-ops.d.ts +5 -0
- package/dist/types/components/spreader/composables/undo-styles.d.ts +5 -0
- package/dist/types/components/spreader/core/border-color.d.ts +138 -0
- package/dist/types/components/spreader/core/border-icon.d.ts +43 -0
- package/dist/types/components/spreader/core/border-pool.d.ts +1 -1
- package/dist/types/components/spreader/core/border-resolve.d.ts +12 -3
- package/dist/types/components/spreader/core/border-style.d.ts +24 -0
- package/dist/types/components/spreader/core/constants.d.ts +3 -2
- package/dist/types/components/spreader/core/model-dims.d.ts +19 -0
- package/dist/types/components/spreader/core/notes.d.ts +51 -0
- package/dist/types/components/spreader/core/sort-icon.d.ts +25 -0
- package/dist/types/components/spreader/core/style-pool.d.ts +1 -1
- package/dist/types/components/spreader/core/theme.d.ts +5 -0
- package/dist/types/components/spreader/core/types.d.ts +38 -4
- package/dist/xiaodao-spreader.es.js +8665 -7712
- package/dist/xiaodao-spreader.umd.js +9 -9
- package/package.json +1 -1
- /package/dist/types/components/spreader/components/{data-validation-input-message.vue.d.ts → pickers/data-validation-input-message.vue.d.ts} +0 -0
- /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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
218
|
-
│ ├── toolbar.vue
|
|
219
|
-
│ ├── tabbar.vue
|
|
220
|
-
│ ├── dropdown.vue
|
|
221
|
-
│ ├── find-replace-bar.vue
|
|
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
|
-
│ ├──
|
|
224
|
-
│ ├──
|
|
225
|
-
│ ├──
|
|
226
|
-
│ ├──
|
|
227
|
-
│ ├──
|
|
228
|
-
│ ├──
|
|
229
|
-
│ ├──
|
|
230
|
-
│
|
|
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
|
-
│ ├──
|
|
233
|
-
│ ├──
|
|
234
|
-
│ ├──
|
|
235
|
-
│ ├──
|
|
236
|
-
│ ├──
|
|
237
|
-
│
|
|
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
|
-
├──
|
|
240
|
-
├──
|
|
241
|
-
├──
|
|
242
|
-
├── border-pool.ts
|
|
243
|
-
├── border-resolve.ts
|
|
244
|
-
├──
|
|
245
|
-
├──
|
|
246
|
-
├──
|
|
247
|
-
├──
|
|
248
|
-
├──
|
|
249
|
-
├──
|
|
250
|
-
├──
|
|
251
|
-
|
|
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`
|
|
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`)
|
|
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
|
|
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`
|
|
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
|
|
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`
|
|
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
|
|
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>`
|
|
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
|
|
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
|
|
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;
|
|
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
|
-
- [
|
|
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
|