@svgrid/enterprise 3.0.1 → 3.0.2

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 (197) hide show
  1. package/dist/SvFormulaBar.svelte +606 -0
  2. package/dist/SvFormulaBar.svelte.d.ts +62 -0
  3. package/dist/SvPivotDesigner.svelte +65 -14
  4. package/dist/SvRibbonIcon.svelte +46 -0
  5. package/dist/SvRibbonIcon.svelte.d.ts +17 -0
  6. package/dist/SvSchemaChart.svelte +4 -2
  7. package/dist/SvSheet.svelte +4114 -0
  8. package/dist/SvSheet.svelte.d.ts +142 -0
  9. package/dist/SvSheetComment.svelte +98 -0
  10. package/dist/SvSheetComment.svelte.d.ts +22 -0
  11. package/dist/SvSheetConditionalFormat.svelte +162 -0
  12. package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
  13. package/dist/SvSheetDataValidation.svelte +249 -0
  14. package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
  15. package/dist/SvSheetFilterMenu.svelte +217 -0
  16. package/dist/SvSheetFilterMenu.svelte.d.ts +17 -0
  17. package/dist/SvSheetFindReplace.svelte +132 -0
  18. package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
  19. package/dist/SvSheetFormatCells.svelte +421 -0
  20. package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
  21. package/dist/SvSheetGoalSeek.svelte +144 -0
  22. package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
  23. package/dist/SvSheetInsertFunction.svelte +147 -0
  24. package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
  25. package/dist/SvSheetListPicker.svelte +89 -0
  26. package/dist/SvSheetListPicker.svelte.d.ts +16 -0
  27. package/dist/SvSheetManageRules.svelte +185 -0
  28. package/dist/SvSheetManageRules.svelte.d.ts +16 -0
  29. package/dist/SvSheetNameManager.svelte +225 -0
  30. package/dist/SvSheetNameManager.svelte.d.ts +11 -0
  31. package/dist/SvSheetPasteSpecial.svelte +105 -0
  32. package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
  33. package/dist/SvSheetRemoveDuplicates.svelte +149 -0
  34. package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
  35. package/dist/SvSheetRibbon.svelte +1201 -0
  36. package/dist/SvSheetRibbon.svelte.d.ts +34 -0
  37. package/dist/SvSheetSizeDialog.svelte +58 -0
  38. package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
  39. package/dist/SvSheetTabs.svelte +441 -0
  40. package/dist/SvSheetTabs.svelte.d.ts +36 -0
  41. package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
  42. package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
  43. package/dist/SvSheetTextToColumns.svelte +172 -0
  44. package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
  45. package/dist/SvSheetValidationAlert.svelte +83 -0
  46. package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
  47. package/dist/cdn/svgrid-enterprise.svelte-external.js +18997 -5396
  48. package/dist/designer/assets/GridMenus-BlsvL_aU.js +8 -0
  49. package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
  50. package/dist/designer/assets/SvChartMenu-D9hEeHar.js +1 -0
  51. package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
  52. package/dist/designer/assets/SvDateTimePicker-DARvKUL2.js +1 -0
  53. package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +4 -0
  54. package/dist/designer/assets/SvGridChart-B8j1SSy3.js +3 -0
  55. package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
  56. package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
  57. package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +1 -0
  58. package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
  59. package/dist/designer/assets/SvGridChartPanel-pT6ITc2P.js +4 -0
  60. package/dist/designer/assets/{SvGridChartView-DUc-1RUf.css → SvGridChartView-7msfnWZT.css} +1 -1
  61. package/dist/designer/assets/SvGridChartView-bg281UA1.js +1 -0
  62. package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
  63. package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
  64. package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
  65. package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
  66. package/dist/designer/assets/SvTabs-UFCMTHXS.js +1 -0
  67. package/dist/designer/assets/chart-cIPUsbM5.js +1 -0
  68. package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
  69. package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
  70. package/dist/designer/assets/chart-panel-messages-DtTxpBUU.js +1 -0
  71. package/dist/designer/assets/chart-summary-Czu_5epb.js +1 -0
  72. package/dist/designer/assets/chart-trend-D5I5VEe8.js +1 -0
  73. package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
  74. package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
  75. package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
  76. package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
  77. package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
  78. package/dist/designer/assets/focus-trap-CAreAhlU.js +1 -0
  79. package/dist/designer/assets/index-CMFgAoVV.css +1 -0
  80. package/dist/designer/assets/{index-C_a9Cj8Y.js → index-DwU7CAMm.js} +291 -290
  81. package/dist/designer/assets/list-option-BP_dWS66.js +1 -0
  82. package/dist/designer/assets/row-resize-DLktcqUK.js +1 -0
  83. package/dist/designer/assets/src-CITpcm6b.js +1 -0
  84. package/dist/designer/assets/src-EdI3Xqds.js +58 -0
  85. package/dist/designer/index.html +23 -14
  86. package/dist/export-pdf.d.ts +89 -0
  87. package/dist/export-pdf.js +130 -0
  88. package/dist/export.js +17 -10
  89. package/dist/index.d.ts +64 -1
  90. package/dist/index.js +68 -1
  91. package/dist/install.js +5 -0
  92. package/dist/node/studio.js +64 -2
  93. package/dist/pivot-chart.d.ts +25 -1
  94. package/dist/pivot-chart.js +25 -69
  95. package/dist/sheet/address.d.ts +43 -0
  96. package/dist/sheet/address.js +81 -0
  97. package/dist/sheet/ast.d.ts +94 -0
  98. package/dist/sheet/ast.js +56 -0
  99. package/dist/sheet/auto-filter.d.ts +66 -0
  100. package/dist/sheet/auto-filter.js +147 -0
  101. package/dist/sheet/autocomplete.d.ts +42 -0
  102. package/dist/sheet/autocomplete.js +162 -0
  103. package/dist/sheet/coerce.d.ts +31 -0
  104. package/dist/sheet/coerce.js +122 -0
  105. package/dist/sheet/commands.d.ts +89 -0
  106. package/dist/sheet/commands.js +230 -0
  107. package/dist/sheet/comments.d.ts +29 -0
  108. package/dist/sheet/comments.js +73 -0
  109. package/dist/sheet/conditional-formats.d.ts +144 -0
  110. package/dist/sheet/conditional-formats.js +316 -0
  111. package/dist/sheet/deps.d.ts +60 -0
  112. package/dist/sheet/deps.js +199 -0
  113. package/dist/sheet/document.d.ts +150 -0
  114. package/dist/sheet/document.js +264 -0
  115. package/dist/sheet/edit-keys.d.ts +14 -0
  116. package/dist/sheet/edit-keys.js +40 -0
  117. package/dist/sheet/entry.d.ts +31 -0
  118. package/dist/sheet/entry.js +76 -0
  119. package/dist/sheet/evaluate.d.ts +61 -0
  120. package/dist/sheet/evaluate.js +337 -0
  121. package/dist/sheet/find-replace.d.ts +62 -0
  122. package/dist/sheet/find-replace.js +152 -0
  123. package/dist/sheet/format-store.d.ts +99 -0
  124. package/dist/sheet/format-store.js +233 -0
  125. package/dist/sheet/freeze.d.ts +54 -0
  126. package/dist/sheet/freeze.js +58 -0
  127. package/dist/sheet/function-catalog.d.ts +10 -0
  128. package/dist/sheet/function-catalog.js +94 -0
  129. package/dist/sheet/functions.d.ts +28 -0
  130. package/dist/sheet/functions.js +362 -0
  131. package/dist/sheet/goal-seek.d.ts +73 -0
  132. package/dist/sheet/goal-seek.js +153 -0
  133. package/dist/sheet/index.d.ts +53 -0
  134. package/dist/sheet/index.js +53 -0
  135. package/dist/sheet/merges.d.ts +71 -0
  136. package/dist/sheet/merges.js +137 -0
  137. package/dist/sheet/names.d.ts +31 -0
  138. package/dist/sheet/names.js +87 -0
  139. package/dist/sheet/navigate.d.ts +52 -0
  140. package/dist/sheet/navigate.js +135 -0
  141. package/dist/sheet/number-format.d.ts +63 -0
  142. package/dist/sheet/number-format.js +508 -0
  143. package/dist/sheet/palette.d.ts +19 -0
  144. package/dist/sheet/palette.js +84 -0
  145. package/dist/sheet/parse.d.ts +13 -0
  146. package/dist/sheet/parse.js +108 -0
  147. package/dist/sheet/paste-special.d.ts +164 -0
  148. package/dist/sheet/paste-special.js +592 -0
  149. package/dist/sheet/protection.d.ts +21 -0
  150. package/dist/sheet/protection.js +48 -0
  151. package/dist/sheet/rects.d.ts +45 -0
  152. package/dist/sheet/rects.js +112 -0
  153. package/dist/sheet/refs.d.ts +82 -0
  154. package/dist/sheet/refs.js +266 -0
  155. package/dist/sheet/ribbon-icons.d.ts +31 -0
  156. package/dist/sheet/ribbon-icons.js +350 -0
  157. package/dist/sheet/ribbon.d.ts +173 -0
  158. package/dist/sheet/ribbon.js +753 -0
  159. package/dist/sheet/shortcuts.d.ts +164 -0
  160. package/dist/sheet/shortcuts.js +623 -0
  161. package/dist/sheet/structure.d.ts +87 -0
  162. package/dist/sheet/structure.js +166 -0
  163. package/dist/sheet/tables.d.ts +70 -0
  164. package/dist/sheet/tables.js +147 -0
  165. package/dist/sheet/tokenize.d.ts +54 -0
  166. package/dist/sheet/tokenize.js +304 -0
  167. package/dist/sheet/transforms.d.ts +76 -0
  168. package/dist/sheet/transforms.js +154 -0
  169. package/dist/sheet/validation.d.ts +92 -0
  170. package/dist/sheet/validation.js +241 -0
  171. package/dist/sheet/workbook.d.ts +72 -0
  172. package/dist/sheet/workbook.js +409 -0
  173. package/dist/sheet-enable.d.ts +6 -0
  174. package/dist/sheet-enable.js +46 -0
  175. package/dist/studio/ui-components.generated.js +63 -1
  176. package/dist/version.d.ts +1 -1
  177. package/dist/version.js +1 -1
  178. package/package.json +9 -4
  179. package/dist/designer/assets/GridMenus-B1JtIN21.js +0 -8
  180. package/dist/designer/assets/SvDateRangeInput-CjO7nn_x.js +0 -1
  181. package/dist/designer/assets/SvDateTimePicker-ByBD_ov6.js +0 -1
  182. package/dist/designer/assets/SvField-YDLipR6O.css +0 -1
  183. package/dist/designer/assets/SvField-cj1PFNgZ.js +0 -1
  184. package/dist/designer/assets/SvGridCellEditor-0W1L2hJ1.js +0 -2
  185. package/dist/designer/assets/SvGridChart-DBOR4xcz.js +0 -2
  186. package/dist/designer/assets/SvGridChart-fkMWeh1J.css +0 -1
  187. package/dist/designer/assets/SvGridChartPanel-symrhNnR.js +0 -11
  188. package/dist/designer/assets/SvGridChartView-BmRxq6mU.js +0 -1
  189. package/dist/designer/assets/SvListBox-1oUF6Iav.js +0 -110
  190. package/dist/designer/assets/chart-DXFganU8.js +0 -1
  191. package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
  192. package/dist/designer/assets/date-format-DGagAqRc.css +0 -1
  193. package/dist/designer/assets/date-format-onlYDsrY.js +0 -1
  194. package/dist/designer/assets/easing-zaypKrr0.js +0 -3
  195. package/dist/designer/assets/index-k2stW6L8.css +0 -1
  196. package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
  197. package/dist/designer/assets/src-D9p27OGC.js +0 -58
@@ -0,0 +1,142 @@
1
+ /**
2
+ * The spreadsheet shell: everything between the ribbon and the sheet tabs.
3
+ *
4
+ * SvSheetRibbon Home / Insert / Formulas / Data
5
+ * SvFormulaBar Name Box + fx, showing the RAW text of the active cell
6
+ * SvGrid A..Z column headers over the built-in 1..N gutter
7
+ * SvSheetTabs sheet tabs
8
+ * status bar Sum / Average / Count of the selection, as Excel has
9
+ *
10
+ * Composition, not a second grid. Every part here already existed and was
11
+ * being re-assembled by hand in four separate demos; the value of the
12
+ * component is that the wiring BETWEEN them is done once and correctly -
13
+ * the formula bar shows the raw text rather than the computed value, the
14
+ * format store is keyed by row id so sorting cannot strand formatting, the
15
+ * ribbon and the keyboard run the same actions, and the tab strip is told
16
+ * when a shortcut moved the active sheet.
17
+ *
18
+ * <SvSheet workbook={wb} />
19
+ *
20
+ * That is the whole required API. Everything else is opt-out.
21
+ */
22
+ import { type SvGridApi, type TableFeatures } from '@svgrid/grid';
23
+ import type { GridCommandContext } from '@svgrid/grid/shortcuts';
24
+ import type { Workbook, SheetData } from './sheet/workbook.js';
25
+ import { type SheetDocument, type SheetState, type SheetChangeReason } from './sheet/document.js';
26
+ import { type CellFormatEntry } from './sheet/format-store.js';
27
+ import type { RibbonActionId } from './sheet/ribbon.js';
28
+ /**
29
+ * A grid row carries the RAW text of every cell on it, keyed by column
30
+ * letter, as well as its position.
31
+ *
32
+ * It would be tempting to keep the values only in the workbook and let the
33
+ * cell snippet read them, since the snippet is what paints. That does not
34
+ * work, and not only for rendering: the grid's own machinery reads
35
+ * `row[field]`, so an empty row object means `cmd.getCellValue` returns
36
+ * undefined for every cell, and then Ctrl+Arrow thinks the sheet is blank,
37
+ * AutoSum finds no run to total, and the inline editor has nothing to open
38
+ * on. The workbook stays authoritative; this is its projection.
39
+ */
40
+ type SheetRow = {
41
+ id: string;
42
+ index: number;
43
+ } & Record<string, string | number>;
44
+ type SheetApi = SvGridApi<TableFeatures, SheetRow>;
45
+ type Props = {
46
+ /**
47
+ * The document to edit: the workbook plus everything a sheet keeps beside
48
+ * its cells (formats, sizes, hidden lines, frozen panes, comments). Build
49
+ * one with `createSheetDocument` to save it, restore it or listen to it
50
+ * outside the component; without it the shell makes its own from
51
+ * `workbook` or `data` and hands it back through `onReady`.
52
+ */
53
+ document?: SheetDocument;
54
+ /** The workbook to edit. One is created from `data` when absent. */
55
+ workbook?: Workbook;
56
+ /** Seed sheets, when not supplying a workbook. */
57
+ data?: ReadonlyArray<SheetData>;
58
+ /** Minimum grid size, so a sparse sheet still looks like a sheet. */
59
+ rows?: number;
60
+ columns?: number;
61
+ /**
62
+ * Grid viewport height in pixels, or `'100%'` to fill the parent.
63
+ *
64
+ * The chrome around the grid - ribbon, formula bar, tabs, status bar -
65
+ * is about 290px tall, so a fixed grid height is a fixed component
66
+ * height with that added on. In a flex column that is too tall for the
67
+ * space, the tab strip is what gets pushed under whatever comes next.
68
+ * With `'100%'` the component is a flex item that fills its column and
69
+ * the grid takes whatever the chrome leaves.
70
+ */
71
+ height?: number | '100%';
72
+ /** Default column width. */
73
+ columnWidth?: number;
74
+ /** Row height in pixels. Excel's is 20 at 100%; 22 fits the grid's 13px font. */
75
+ rowHeight?: number;
76
+ /**
77
+ * `theme` (the default) paints the shell with the host's `--sg-*`
78
+ * tokens, so it follows whatever theme the app around it runs, as the
79
+ * grid does. `excel` pins Excel's own palette and geometry, light or
80
+ * dark with the page, for an app that wants it to read as Excel
81
+ * whatever the theme.
82
+ */
83
+ look?: 'excel' | 'theme';
84
+ /**
85
+ * Per-column overrides, keyed by column letter: `{ A: 150 }`.
86
+ *
87
+ * A sheet's first column usually holds labels and the rest hold numbers,
88
+ * so one width for all of them truncates the only column that needs the
89
+ * room. Columns stay drag-resizable either way.
90
+ */
91
+ columnWidths?: Readonly<Record<string, number>>;
92
+ /**
93
+ * Cell formats to start with, keyed by A1 address: `{ B2: { bold: true },
94
+ * 'E13': { numFmt: '0.0%' } }`.
95
+ *
96
+ * A bare address formats the sheet that is active when the shell mounts.
97
+ * Qualify it to reach another sheet, the way a formula would:
98
+ * `{ 'Orders!F2': { numFmt: '$#,##0.00' }, "'Price list'!C2": ... }`.
99
+ * Formats are per sheet, as they are in Excel; a fill on Summary!C5 says
100
+ * nothing about Orders!C5.
101
+ *
102
+ * Without this the sheet can only be formatted by hand after it loads,
103
+ * which makes it impossible to ship a document that opens looking the way
104
+ * it was saved. Applied once at mount; after that the stores own it.
105
+ */
106
+ formats?: Readonly<Record<string, CellFormatEntry>>;
107
+ /**
108
+ * Ribbon buttons the shell has nothing behind: Insert > Table and
109
+ * Insert > Chart. Left off unless listed here, which says the
110
+ * application answers them in `onAction`.
111
+ */
112
+ extras?: ReadonlyArray<'insert-table' | 'insert-chart'>;
113
+ /** Hide any part of the chrome. */
114
+ showRibbon?: boolean;
115
+ showFormulaBar?: boolean;
116
+ showTabs?: boolean;
117
+ showStatusBar?: boolean;
118
+ /**
119
+ * Every action the ribbon, the cell menu, the formula bar and the
120
+ * shortcuts raise. The shell has a dialog of its own for Find and
121
+ * Replace, Paste Special, Format Cells and Insert Function; a handler
122
+ * that returns `true` has taken the action over and the shell's dialog
123
+ * stays closed, so an application can put its own in their place.
124
+ */
125
+ onAction?: (action: RibbonActionId, cmd: GridCommandContext) => void | boolean;
126
+ onReady?: (api: SheetApi, document: SheetDocument) => void;
127
+ /**
128
+ * Something the user did landed in the document: a cell, a format, a
129
+ * size, a hidden line, a frozen pane, a sheet, a comment, or a structural
130
+ * edit. Called once per tick with every reason since the last call, so a
131
+ * paste of forty cells is one call; the natural place for an autosave
132
+ * (`getState()`, debounced). Undo and redo report too.
133
+ */
134
+ onChange?: (reasons: ReadonlyArray<SheetChangeReason>) => void;
135
+ };
136
+ declare const SvSheet: import("svelte").Component<Props, {
137
+ getState: () => SheetState;
138
+ setState: (state: SheetState) => void;
139
+ refresh: () => void;
140
+ }, "">;
141
+ type SvSheet = ReturnType<typeof SvSheet>;
142
+ export default SvSheet;
@@ -0,0 +1,98 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The comment editor: Excel's note box, opened by New Comment, Edit
4
+ * Comment, Shift+F2 and the cell menu. A textarea over the text, Save,
5
+ * and Delete when there is something to delete. As in Excel the text is
6
+ * kept whatever closes the box: Escape and a click elsewhere save what
7
+ * was typed, which is why every keystroke is reported through `onDraft`
8
+ * and the shell saves the draft on dismissal. Ctrl+Enter saves too.
9
+ * The shell mounts it inside the popover anchored to the cell.
10
+ */
11
+ type Props = {
12
+ /** The comment as it is, empty for a new one. */
13
+ text: string
14
+ /** A1 of the cell, for the heading. */
15
+ address: string
16
+ /** Every change of the text, so the shell can keep it on dismissal. */
17
+ onDraft: (text: string) => void
18
+ onSave: (text: string) => void
19
+ onDelete: () => void
20
+ }
21
+
22
+ let { text, address, onDraft, onSave, onDelete }: Props = $props()
23
+
24
+ let draft = $state('')
25
+ let box = $state<HTMLTextAreaElement | null>(null)
26
+
27
+ $effect(() => {
28
+ draft = text
29
+ queueMicrotask(() => {
30
+ if (!box) return
31
+ box.focus()
32
+ box.setSelectionRange(box.value.length, box.value.length)
33
+ })
34
+ })
35
+
36
+ function onKeyDown(event: KeyboardEvent) {
37
+ if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) { event.preventDefault(); onSave(draft) }
38
+ }
39
+ </script>
40
+
41
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
42
+ <div class="sv-sheet-comment" role="dialog" aria-label="Comment on {address}" onkeydown={onKeyDown}>
43
+ <div class="head">{address}</div>
44
+ <textarea
45
+ bind:this={box}
46
+ bind:value={draft}
47
+ oninput={() => onDraft(draft)}
48
+ rows="4"
49
+ aria-label="Comment"
50
+ placeholder="Type a comment"
51
+ spellcheck="false"
52
+ ></textarea>
53
+ <div class="sv-sheet-dialog-buttons">
54
+ <span class="keys">Ctrl+Enter saves</span>
55
+ <button type="button" class="btn primary" onclick={() => onSave(draft)}>Save</button>
56
+ {#if text}
57
+ <button type="button" class="btn" onclick={onDelete}>Delete</button>
58
+ {/if}
59
+ </div>
60
+ </div>
61
+
62
+ <style>
63
+ .sv-sheet-comment {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 6px;
67
+ width: 260px;
68
+ padding: 8px;
69
+ font-size: 12px;
70
+ color: var(--sg-fg, #242424);
71
+ }
72
+ .head {
73
+ font-weight: 600;
74
+ color: var(--sg-muted, #616161);
75
+ }
76
+ .keys {
77
+ margin-right: auto;
78
+ font-size: 11px;
79
+ color: var(--sg-muted, #616161);
80
+ }
81
+ textarea {
82
+ width: 100%;
83
+ box-sizing: border-box;
84
+ resize: vertical;
85
+ min-height: 64px;
86
+ padding: 6px 8px;
87
+ font: inherit;
88
+ line-height: 1.4;
89
+ color: inherit;
90
+ background: var(--sg-bg, #fff);
91
+ border: 1px solid var(--sg-border, #d1d1d1);
92
+ border-radius: 3px;
93
+ }
94
+ textarea:focus-visible {
95
+ outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #107c41));
96
+ outline-offset: -1px;
97
+ }
98
+ </style>
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The comment editor: Excel's note box, opened by New Comment, Edit
3
+ * Comment, Shift+F2 and the cell menu. A textarea over the text, Save,
4
+ * and Delete when there is something to delete. As in Excel the text is
5
+ * kept whatever closes the box: Escape and a click elsewhere save what
6
+ * was typed, which is why every keystroke is reported through `onDraft`
7
+ * and the shell saves the draft on dismissal. Ctrl+Enter saves too.
8
+ * The shell mounts it inside the popover anchored to the cell.
9
+ */
10
+ type Props = {
11
+ /** The comment as it is, empty for a new one. */
12
+ text: string;
13
+ /** A1 of the cell, for the heading. */
14
+ address: string;
15
+ /** Every change of the text, so the shell can keep it on dismissal. */
16
+ onDraft: (text: string) => void;
17
+ onSave: (text: string) => void;
18
+ onDelete: () => void;
19
+ };
20
+ declare const SvSheetComment: import("svelte").Component<Props, {}, "">;
21
+ type SvSheetComment = ReturnType<typeof SvSheetComment>;
22
+ export default SvSheetComment;
@@ -0,0 +1,162 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Excel's small conditional-formatting dialogs, one component: Greater
4
+ * Than..., Less Than..., Between..., Equal To..., Text that Contains...,
5
+ * Duplicate Values..., Top 10 Items..., Bottom 10 Items..., Above Average
6
+ * and Below Average. Each is a sentence with its inputs and the "with"
7
+ * picker of Excel's six styles. It opens empty for a new rule from the
8
+ * ribbon, or on a rule from Manage Rules > Edit, and hands back the rule's
9
+ * body for the shell to place.
10
+ */
11
+ import { SvModal } from '@svgrid/grid'
12
+ import { CF_PRESET_STYLES, type CfPreset, type CfBody, type CfStyledRule } from './sheet/conditional-formats'
13
+
14
+ type Props = {
15
+ open?: boolean
16
+ preset: CfPreset
17
+ /** The rule being edited, for Manage Rules > Edit. */
18
+ rule?: CfStyledRule | null
19
+ /** The selection as A1, for the heading. */
20
+ address: string
21
+ onApply: (body: CfBody) => void
22
+ onClose?: () => void
23
+ }
24
+
25
+ let { open = $bindable(false), preset, rule = null, address, onApply, onClose }: Props = $props()
26
+
27
+ const TITLES: Record<CfPreset, string> = {
28
+ greater: 'Greater Than', less: 'Less Than', between: 'Between', equal: 'Equal To',
29
+ text: 'Text That Contains', duplicates: 'Duplicate Values',
30
+ top10: 'Top 10 Items', bottom10: 'Bottom 10 Items', aboveAverage: 'Above Average', belowAverage: 'Below Average',
31
+ }
32
+ const LEADS: Record<CfPreset, string> = {
33
+ greater: 'Format cells that are GREATER THAN:', less: 'Format cells that are LESS THAN:',
34
+ between: 'Format cells that are BETWEEN:', equal: 'Format cells that are EQUAL TO:',
35
+ text: 'Format cells that contain the text:', duplicates: 'Format cells that contain:',
36
+ top10: 'Format cells that rank in the TOP:', bottom10: 'Format cells that rank in the BOTTOM:',
37
+ aboveAverage: 'Format cells that are ABOVE AVERAGE for the selected range:',
38
+ belowAverage: 'Format cells that are BELOW AVERAGE for the selected range:',
39
+ }
40
+
41
+ let value1 = $state('')
42
+ let value2 = $state('')
43
+ let rank = $state('10')
44
+ let percent = $state(false)
45
+ let unique = $state(false)
46
+ let styleId = $state(CF_PRESET_STYLES[0]!.id)
47
+ let first = $state<HTMLInputElement | HTMLSelectElement | null>(null)
48
+
49
+ $effect(() => {
50
+ if (!open) return
51
+ value1 = ''
52
+ value2 = ''
53
+ rank = '10'
54
+ percent = false
55
+ unique = false
56
+ styleId = CF_PRESET_STYLES[0]!.id
57
+ const r = rule
58
+ if (r) {
59
+ if (r.kind === 'cellIs') { value1 = r.value1; value2 = r.value2 ?? '' }
60
+ else if (r.kind === 'text') value1 = r.value
61
+ else if (r.kind === 'duplicates') unique = !!r.unique
62
+ else if (r.kind === 'topBottom') { rank = String(r.rank); percent = !!r.percent }
63
+ const match = CF_PRESET_STYLES.find((p) => JSON.stringify(p.style) === JSON.stringify(r.style))
64
+ if (match) styleId = match.id
65
+ }
66
+ queueMicrotask(() => first?.focus())
67
+ })
68
+
69
+ const needsValue = $derived(preset === 'greater' || preset === 'less' || preset === 'between' || preset === 'equal' || preset === 'text')
70
+ const valid = $derived(
71
+ (!needsValue || value1.trim() !== '')
72
+ && (preset !== 'between' || value2.trim() !== '')
73
+ && ((preset !== 'top10' && preset !== 'bottom10') || (Number.isInteger(Number(rank)) && Number(rank) > 0)),
74
+ )
75
+
76
+ function body(): CfBody {
77
+ const style = CF_PRESET_STYLES.find((p) => p.id === styleId)?.style ?? CF_PRESET_STYLES[0]!.style
78
+ switch (preset) {
79
+ case 'greater': return { kind: 'cellIs', operator: 'greater', value1: value1.trim(), style }
80
+ case 'less': return { kind: 'cellIs', operator: 'less', value1: value1.trim(), style }
81
+ case 'between': return { kind: 'cellIs', operator: 'between', value1: value1.trim(), value2: value2.trim(), style }
82
+ case 'equal': return { kind: 'cellIs', operator: 'equal', value1: value1.trim(), style }
83
+ case 'text': return { kind: 'text', match: 'contains', value: value1.trim(), style }
84
+ case 'duplicates': return { kind: 'duplicates', unique, style }
85
+ case 'top10': return { kind: 'topBottom', top: true, rank: Number(rank), percent, style }
86
+ case 'bottom10': return { kind: 'topBottom', top: false, rank: Number(rank), percent, style }
87
+ case 'aboveAverage': return { kind: 'average', above: true, style }
88
+ case 'belowAverage': return { kind: 'average', above: false, style }
89
+ }
90
+ }
91
+
92
+ function ok() {
93
+ if (!valid) return
94
+ onApply(body())
95
+ open = false
96
+ onClose?.()
97
+ }
98
+
99
+ function close() {
100
+ open = false
101
+ onClose?.()
102
+ }
103
+
104
+ const sample = $derived(CF_PRESET_STYLES.find((p) => p.id === styleId)?.style ?? {})
105
+ </script>
106
+
107
+ <SvModal bind:open onClose={onClose} title={TITLES[preset]} size="sm" width={440}>
108
+ <form class="sv-sheet-dialog cf" onsubmit={(e) => { e.preventDefault(); ok() }}>
109
+ <div class="where">{address}</div>
110
+ <div class="lead">{LEADS[preset]}</div>
111
+ <div class="row">
112
+ {#if preset === 'between'}
113
+ <input bind:this={first} type="text" bind:value={value1} aria-label="Lower value" spellcheck="false" />
114
+ <span>and</span>
115
+ <input type="text" bind:value={value2} aria-label="Upper value" spellcheck="false" />
116
+ {:else if needsValue}
117
+ <input bind:this={first} type="text" bind:value={value1} aria-label="Value" spellcheck="false" placeholder={preset === 'text' ? 'text' : 'value or =formula'} />
118
+ {:else if preset === 'duplicates'}
119
+ <select bind:this={first} bind:value={unique} aria-label="Duplicate or unique">
120
+ <option value={false}>Duplicate</option>
121
+ <option value={true}>Unique</option>
122
+ </select>
123
+ <span>values</span>
124
+ {:else if preset === 'top10' || preset === 'bottom10'}
125
+ <input bind:this={first} type="number" min="1" bind:value={rank} aria-label="How many" />
126
+ <label class="check"><input type="checkbox" bind:checked={percent} /> % of selected range</label>
127
+ {/if}
128
+ <span>with</span>
129
+ <select bind:value={styleId} aria-label="Format style">
130
+ {#each CF_PRESET_STYLES as p (p.id)}<option value={p.id}>{p.label}</option>{/each}
131
+ </select>
132
+ </div>
133
+ <div class="sample" style:background={sample.fill ?? 'transparent'} style:color={sample.color ?? 'inherit'}>AaBbCcYyZz</div>
134
+ </form>
135
+ {#snippet footer()}
136
+ <div class="sv-sheet-dialog-buttons">
137
+ <button type="button" class="btn primary" onclick={ok} disabled={!valid}>OK</button>
138
+ <button type="button" class="btn" onclick={close}>Cancel</button>
139
+ </div>
140
+ {/snippet}
141
+ </SvModal>
142
+
143
+ <style>
144
+ .where { color: var(--sg-muted, #616161); }
145
+ .lead { font-weight: 600; }
146
+ .row {
147
+ display: flex;
148
+ flex-wrap: wrap;
149
+ align-items: center;
150
+ gap: 8px;
151
+ }
152
+ .row input[type="text"] { flex: 1 1 110px; min-width: 90px; }
153
+ .row input[type="number"] { width: 64px; }
154
+ .row select { max-width: 240px; }
155
+ .sample {
156
+ width: 120px;
157
+ padding: 4px 10px;
158
+ border: 1px solid var(--sg-border, #d1d1d1);
159
+ border-radius: var(--sg-radius, 3px);
160
+ text-align: center;
161
+ }
162
+ </style>
@@ -0,0 +1,14 @@
1
+ import { type CfPreset, type CfBody, type CfStyledRule } from './sheet/conditional-formats.js';
2
+ type Props = {
3
+ open?: boolean;
4
+ preset: CfPreset;
5
+ /** The rule being edited, for Manage Rules > Edit. */
6
+ rule?: CfStyledRule | null;
7
+ /** The selection as A1, for the heading. */
8
+ address: string;
9
+ onApply: (body: CfBody) => void;
10
+ onClose?: () => void;
11
+ };
12
+ declare const SvSheetConditionalFormat: import("svelte").Component<Props, {}, "open">;
13
+ type SvSheetConditionalFormat = ReturnType<typeof SvSheetConditionalFormat>;
14
+ export default SvSheetConditionalFormat;