@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,147 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Excel's Insert Function: search or pick a category, choose a function,
4
+ * read its argument list and what it does, and OK starts the active cell
5
+ * on `=NAME(` with the caret inside the parentheses, where the formula
6
+ * bar's autocomplete and signature hint take over. Excel follows with a
7
+ * Function Arguments dialog; typing the arguments in place with the
8
+ * signature shown is the same information with less chrome.
9
+ */
10
+ import { SvModal } from '@svgrid/grid'
11
+ import { functionCatalog, FUNCTION_GROUPS, type FunctionGroup, type FunctionInfo } from './sheet/function-catalog'
12
+
13
+ type Props = {
14
+ open?: boolean
15
+ onPick: (name: string) => void
16
+ onClose?: () => void
17
+ }
18
+
19
+ let { open = $bindable(false), onPick, onClose }: Props = $props()
20
+
21
+ const all = functionCatalog()
22
+ let search = $state('')
23
+ let group = $state<FunctionGroup | 'All'>('All')
24
+ let selected = $state<string>(all[0]?.name ?? '')
25
+ let searchInput = $state<HTMLInputElement | null>(null)
26
+
27
+ const shown = $derived.by<FunctionInfo[]>(() => {
28
+ const needle = search.trim().toUpperCase()
29
+ return all.filter((f) =>
30
+ (group === 'All' || f.group === group) &&
31
+ (needle === '' || f.name.includes(needle) || f.description.toUpperCase().includes(needle)),
32
+ )
33
+ })
34
+ const current = $derived(all.find((f) => f.name === selected) ?? null)
35
+
36
+ // The highlighted function follows the filter, so Enter always inserts
37
+ // something visible.
38
+ $effect(() => {
39
+ if (shown.length && !shown.some((f) => f.name === selected)) selected = shown[0]!.name
40
+ })
41
+ $effect(() => {
42
+ if (open) queueMicrotask(() => searchInput?.focus())
43
+ })
44
+
45
+ function ok() {
46
+ if (!current) return
47
+ onPick(current.name)
48
+ open = false
49
+ onClose?.()
50
+ }
51
+
52
+ function close() {
53
+ open = false
54
+ onClose?.()
55
+ }
56
+
57
+ function onListKey(event: KeyboardEvent) {
58
+ const index = shown.findIndex((f) => f.name === selected)
59
+ if (event.key === 'ArrowDown') {
60
+ event.preventDefault()
61
+ selected = shown[Math.min(index + 1, shown.length - 1)]?.name ?? selected
62
+ } else if (event.key === 'ArrowUp') {
63
+ event.preventDefault()
64
+ selected = shown[Math.max(index - 1, 0)]?.name ?? selected
65
+ } else if (event.key === 'Enter') {
66
+ event.preventDefault()
67
+ ok()
68
+ }
69
+ }
70
+ </script>
71
+
72
+ <SvModal bind:open onClose={onClose} title="Insert Function" size="md">
73
+ <div class="sv-sheet-dialog insert" role="group" aria-label="Insert Function">
74
+ <label class="field">
75
+ <span>Search for a function:</span>
76
+ <input bind:this={searchInput} type="text" bind:value={search} onkeydown={onListKey} spellcheck="false" autocomplete="off" placeholder="Type a name or what you want to do" />
77
+ </label>
78
+ <label class="field">
79
+ <span>Or select a category:</span>
80
+ <select bind:value={group}>
81
+ <option value="All">All</option>
82
+ {#each FUNCTION_GROUPS as g (g)}
83
+ <option value={g}>{g}</option>
84
+ {/each}
85
+ </select>
86
+ </label>
87
+ <div class="label">Select a function:</div>
88
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
89
+ <ul class="list" role="listbox" aria-label="Functions" tabindex="0" onkeydown={onListKey}>
90
+ {#each shown as fn (fn.name)}
91
+ <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
92
+ <li
93
+ role="option"
94
+ aria-selected={fn.name === selected}
95
+ class:selected={fn.name === selected}
96
+ onclick={() => (selected = fn.name)}
97
+ ondblclick={ok}
98
+ >{fn.name}</li>
99
+ {:else}
100
+ <li class="empty">No function matches.</li>
101
+ {/each}
102
+ </ul>
103
+ {#if current}
104
+ <div class="about">
105
+ <div class="signature">{current.signature}</div>
106
+ <div class="description">{current.description || 'No description.'}</div>
107
+ </div>
108
+ {/if}
109
+ </div>
110
+ {#snippet footer()}
111
+ <div class="sv-sheet-dialog-buttons">
112
+ <button type="button" class="btn primary" onclick={ok} disabled={!current}>OK</button>
113
+ <button type="button" class="btn" onclick={close}>Cancel</button>
114
+ </div>
115
+ {/snippet}
116
+ </SvModal>
117
+
118
+ <style>
119
+ .label { font-weight: 600; }
120
+ .insert :global(.field) { grid-template-columns: 160px 1fr; }
121
+ .list {
122
+ height: 160px;
123
+ margin: 0;
124
+ padding: 2px 0;
125
+ overflow-y: auto;
126
+ list-style: none;
127
+ background: var(--sg-bg, #fff);
128
+ border: 1px solid var(--sg-border, #d1d1d1);
129
+ border-radius: var(--sg-radius, 3px);
130
+ font-family: ui-monospace, Consolas, monospace;
131
+ font-size: 12px;
132
+ }
133
+ .list:focus { outline: none; border-color: var(--sg-accent, #217346); }
134
+ .list li {
135
+ padding: 2px 8px;
136
+ cursor: default;
137
+ }
138
+ .list li:hover { background: var(--sg-row-hover-bg, #f5f5f5); }
139
+ .list li.selected {
140
+ color: var(--sg-on-accent, #fff);
141
+ background: var(--sg-accent, #217346);
142
+ }
143
+ .list li.empty { color: var(--sg-muted, #616161); font-family: inherit; }
144
+ .about { display: flex; flex-direction: column; gap: 4px; min-height: 52px; }
145
+ .signature { font-weight: 600; font-family: ui-monospace, Consolas, monospace; font-size: 12px; }
146
+ .description { color: var(--sg-muted, #616161); }
147
+ </style>
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ open?: boolean;
3
+ onPick: (name: string) => void;
4
+ onClose?: () => void;
5
+ };
6
+ declare const SvSheetInsertFunction: import("svelte").Component<Props, {}, "open">;
7
+ type SvSheetInsertFunction = ReturnType<typeof SvSheetInsertFunction>;
8
+ export default SvSheetInsertFunction;
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The list a validated cell drops down: Excel's in-cell dropdown. The
4
+ * choices as the rule lists them, the current value marked, arrow keys
5
+ * and Enter to pick, Escape to leave the cell as it is. The shell mounts
6
+ * it inside the popover anchored to the cell.
7
+ */
8
+ type Props = {
9
+ choices: ReadonlyArray<string>
10
+ /** The cell's current text, marked in the list. */
11
+ value: string
12
+ onPick: (choice: string) => void
13
+ onCancel: () => void
14
+ }
15
+
16
+ let { choices, value, onPick, onCancel }: Props = $props()
17
+
18
+ let list = $state<HTMLDivElement | null>(null)
19
+ let index = $state(0)
20
+
21
+ $effect(() => {
22
+ const at = choices.findIndex((c) => c.toLowerCase() === value.trim().toLowerCase())
23
+ index = at >= 0 ? at : 0
24
+ queueMicrotask(() => list?.focus())
25
+ })
26
+
27
+ function onKeyDown(event: KeyboardEvent) {
28
+ if (event.key === 'ArrowDown') { event.preventDefault(); index = Math.min(index + 1, choices.length - 1); scrollTo() }
29
+ else if (event.key === 'ArrowUp') { event.preventDefault(); index = Math.max(index - 1, 0); scrollTo() }
30
+ else if (event.key === 'Home') { event.preventDefault(); index = 0; scrollTo() }
31
+ else if (event.key === 'End') { event.preventDefault(); index = choices.length - 1; scrollTo() }
32
+ else if (event.key === 'Enter' || event.key === 'Tab') { event.preventDefault(); const c = choices[index]; if (c !== undefined) onPick(c) }
33
+ else if (event.key === 'Escape') { event.preventDefault(); onCancel() }
34
+ }
35
+
36
+ function scrollTo() {
37
+ list?.querySelector<HTMLElement>(`[data-index="${index}"]`)?.scrollIntoView({ block: 'nearest' })
38
+ }
39
+ </script>
40
+
41
+ <div
42
+ bind:this={list}
43
+ class="sv-sheet-list-picker"
44
+ role="listbox"
45
+ aria-label="Choices"
46
+ tabindex="0"
47
+ aria-activedescendant={choices.length ? `sv-sheet-choice-${index}` : undefined}
48
+ onkeydown={onKeyDown}
49
+ >
50
+ {#if choices.length === 0}
51
+ <div class="empty">The list is empty.</div>
52
+ {:else}
53
+ {#each choices as choice, i (i)}
54
+ <!-- svelte-ignore a11y_click_events_have_key_events, a11y_interactive_supports_focus -->
55
+ <div
56
+ id={`sv-sheet-choice-${i}`}
57
+ class="choice"
58
+ class:on={i === index}
59
+ role="option"
60
+ aria-selected={i === index}
61
+ data-index={i}
62
+ onpointermove={() => (index = i)}
63
+ onclick={() => onPick(choice)}
64
+ >{choice}</div>
65
+ {/each}
66
+ {/if}
67
+ </div>
68
+
69
+ <style>
70
+ .sv-sheet-list-picker {
71
+ min-width: 120px;
72
+ max-width: 320px;
73
+ max-height: 200px;
74
+ overflow-y: auto;
75
+ padding: 4px 0;
76
+ font-size: 12px;
77
+ color: var(--sg-fg, #242424);
78
+ outline: none;
79
+ }
80
+ .choice {
81
+ padding: 4px 10px;
82
+ white-space: nowrap;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ cursor: default;
86
+ }
87
+ .choice.on { background: var(--sg-selection-bg, rgba(16, 124, 65, 0.12)); }
88
+ .empty { padding: 4px 10px; color: var(--sg-muted, #616161); }
89
+ </style>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The list a validated cell drops down: Excel's in-cell dropdown. The
3
+ * choices as the rule lists them, the current value marked, arrow keys
4
+ * and Enter to pick, Escape to leave the cell as it is. The shell mounts
5
+ * it inside the popover anchored to the cell.
6
+ */
7
+ type Props = {
8
+ choices: ReadonlyArray<string>;
9
+ /** The cell's current text, marked in the list. */
10
+ value: string;
11
+ onPick: (choice: string) => void;
12
+ onCancel: () => void;
13
+ };
14
+ declare const SvSheetListPicker: import("svelte").Component<Props, {}, "">;
15
+ type SvSheetListPicker = ReturnType<typeof SvSheetListPicker>;
16
+ export default SvSheetListPicker;
@@ -0,0 +1,185 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Excel's Conditional Formatting Rules Manager: the rules of the sheet
4
+ * (or of the current selection), in priority order, each with what it
5
+ * does, a sample and where it applies; Edit Rule (for the rules the small
6
+ * dialog can edit), Delete Rule, Move Up, Move Down and Stop If True. The
7
+ * list is a working copy: OK hands it back as one change, Cancel drops
8
+ * it.
9
+ */
10
+ import { SvModal } from '@svgrid/grid'
11
+ import { describeCf, hasStyle, cfIn, type CfRule, type CfStyledRule } from './sheet/conditional-formats'
12
+ import { colToLetters } from './sheet/address'
13
+ import type { Rect } from './sheet/rects'
14
+
15
+ type Props = {
16
+ open?: boolean
17
+ rules: ReadonlyArray<CfRule>
18
+ /** The selection, for the Current Selection filter. */
19
+ selection: ReadonlyArray<Rect>
20
+ onApply: (rules: CfRule[]) => void
21
+ /** Edit Rule: the shell opens the small dialog on the rule and calls
22
+ * `replace` with the result. */
23
+ onEdit: (rule: CfStyledRule, replace: (next: CfStyledRule) => void) => void
24
+ onClose?: () => void
25
+ }
26
+
27
+ let { open = $bindable(false), rules, selection, onApply, onEdit, onClose }: Props = $props()
28
+
29
+ let working = $state<CfRule[]>([])
30
+ let scope = $state<'sheet' | 'selection'>('sheet')
31
+ let selected = $state<string | null>(null)
32
+
33
+ $effect(() => {
34
+ if (!open) return
35
+ // Read the props, write the state: reading `working` back here would
36
+ // make the effect depend on what it just wrote and run without end.
37
+ const copy = rules.map((r) => ({ ...r, rects: r.rects.map((x) => [...x] as unknown as Rect) }))
38
+ working = copy
39
+ scope = 'sheet'
40
+ selected = copy[0]?.id ?? null
41
+ })
42
+
43
+ const shown = $derived(scope === 'sheet' ? working : cfIn(working, selection))
44
+ const current = $derived(working.find((r) => r.id === selected) ?? null)
45
+ const index = $derived(current ? working.indexOf(current) : -1)
46
+
47
+ const address = (rects: ReadonlyArray<Rect>) =>
48
+ rects.map(([r1, c1, r2, c2]) => {
49
+ const a = `${colToLetters(c1)}${r1 + 1}`
50
+ const b = `${colToLetters(c2)}${r2 + 1}`
51
+ return a === b ? a : `${a}:${b}`
52
+ }).join(', ')
53
+
54
+ const sample = (rule: CfRule): { background?: string; color?: string; text: string } => {
55
+ if (hasStyle(rule)) return { background: rule.style.fill, color: rule.style.color, text: 'AaBbCcYyZz' }
56
+ if (rule.kind === 'dataBar') return { background: `linear-gradient(90deg, ${rule.color} 60%, transparent 60%)`, text: '' }
57
+ if (rule.kind === 'colorScale') return { background: `linear-gradient(90deg, ${rule.colors.join(', ')})`, text: '' }
58
+ return { text: 'Icon Set' }
59
+ }
60
+
61
+ function remove() {
62
+ if (index < 0) return
63
+ working = working.filter((_, i) => i !== index)
64
+ selected = working[Math.min(index, working.length - 1)]?.id ?? null
65
+ }
66
+ function move(delta: -1 | 1) {
67
+ if (index < 0) return
68
+ const to = index + delta
69
+ if (to < 0 || to >= working.length) return
70
+ const next = working.slice()
71
+ const [rule] = next.splice(index, 1)
72
+ next.splice(to, 0, rule!)
73
+ working = next
74
+ }
75
+ function toggleStop(rule: CfRule) {
76
+ working = working.map((r) => (r.id === rule.id ? { ...r, stopIfTrue: !r.stopIfTrue } : r))
77
+ }
78
+ function edit() {
79
+ const rule = current
80
+ if (!rule || !hasStyle(rule)) return
81
+ onEdit(rule, (next) => {
82
+ working = working.map((r) => (r.id === rule.id ? next : r))
83
+ })
84
+ }
85
+ function ok() {
86
+ onApply(working)
87
+ open = false
88
+ onClose?.()
89
+ }
90
+ function close() {
91
+ open = false
92
+ onClose?.()
93
+ }
94
+ </script>
95
+
96
+ <SvModal bind:open onClose={onClose} title="Conditional Formatting Rules Manager" size="md" width={640}>
97
+ <div class="sv-sheet-dialog manage">
98
+ <label class="field auto">
99
+ <span>Show formatting rules for:</span>
100
+ <select bind:value={scope}>
101
+ <option value="sheet">This Worksheet</option>
102
+ <option value="selection">Current Selection</option>
103
+ </select>
104
+ </label>
105
+ <div class="toolbar">
106
+ <button type="button" class="btn" onclick={edit} disabled={!current || !hasStyle(current)}>Edit Rule...</button>
107
+ <button type="button" class="btn" onclick={remove} disabled={!current}>Delete Rule</button>
108
+ <button type="button" class="btn" onclick={() => move(-1)} disabled={index <= 0} aria-label="Move Up" title="Move Up">&#9650;</button>
109
+ <button type="button" class="btn" onclick={() => move(1)} disabled={index < 0 || index >= working.length - 1} aria-label="Move Down" title="Move Down">&#9660;</button>
110
+ </div>
111
+ <div class="list" role="grid" aria-label="Rules">
112
+ <div class="head" role="row">
113
+ <span role="columnheader">Rule (applied in order shown)</span>
114
+ <span role="columnheader">Format</span>
115
+ <span role="columnheader">Applies to</span>
116
+ <span role="columnheader">Stop If True</span>
117
+ </div>
118
+ {#if shown.length === 0}
119
+ <div class="empty">No rules {scope === 'sheet' ? 'on this sheet' : 'over the selection'}.</div>
120
+ {/if}
121
+ {#each shown as rule (rule.id)}
122
+ {@const s = sample(rule)}
123
+ <!-- svelte-ignore a11y_click_events_have_key_events, a11y_interactive_supports_focus -->
124
+ <div class="rule" class:on={rule.id === selected} role="row" aria-selected={rule.id === selected} onclick={() => (selected = rule.id)}>
125
+ <span role="gridcell" class="what">{describeCf(rule)}</span>
126
+ <span role="gridcell" class="sample" style:background={s.background ?? 'transparent'} style:color={s.color ?? 'inherit'}>{s.text}</span>
127
+ <span role="gridcell" class="where">{address(rule.rects)}</span>
128
+ <span role="gridcell" class="stop"><input type="checkbox" checked={!!rule.stopIfTrue} aria-label="Stop If True" onchange={() => toggleStop(rule)} onclick={(e) => e.stopPropagation()} /></span>
129
+ </div>
130
+ {/each}
131
+ </div>
132
+ </div>
133
+ {#snippet footer()}
134
+ <div class="sv-sheet-dialog-buttons">
135
+ <button type="button" class="btn primary" onclick={ok}>OK</button>
136
+ <button type="button" class="btn" onclick={close}>Cancel</button>
137
+ </div>
138
+ {/snippet}
139
+ </SvModal>
140
+
141
+ <style>
142
+ .toolbar { display: flex; gap: 6px; }
143
+ .toolbar .btn {
144
+ height: 26px;
145
+ padding: 0 10px;
146
+ font: inherit;
147
+ color: inherit;
148
+ background: var(--sg-bg, #fff);
149
+ border: 1px solid var(--sg-border, #d1d1d1);
150
+ border-radius: var(--sg-radius, 3px);
151
+ cursor: pointer;
152
+ }
153
+ .toolbar .btn:hover:not(:disabled) { background: var(--sg-row-hover-bg, #f5f5f5); }
154
+ .toolbar .btn:disabled { opacity: 0.5; cursor: default; }
155
+ .list {
156
+ border: 1px solid var(--sg-border, #d1d1d1);
157
+ border-radius: var(--sg-radius, 3px);
158
+ max-height: 260px;
159
+ overflow-y: auto;
160
+ }
161
+ .head, .rule {
162
+ display: grid;
163
+ grid-template-columns: 1fr 120px 130px 84px;
164
+ align-items: center;
165
+ gap: 8px;
166
+ padding: 4px 8px;
167
+ }
168
+ .head {
169
+ font-weight: 600;
170
+ color: var(--sg-muted, #616161);
171
+ background: var(--sg-bg-subtle, #f3f3f3);
172
+ border-bottom: 1px solid var(--sg-border, #d1d1d1);
173
+ }
174
+ .rule { cursor: default; }
175
+ .rule.on { background: var(--sg-selection-bg, rgba(16, 124, 65, 0.12)); }
176
+ .rule .sample {
177
+ padding: 2px 6px;
178
+ border: 1px solid var(--sg-border, #d1d1d1);
179
+ text-align: center;
180
+ min-height: 20px;
181
+ }
182
+ .rule .where { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
183
+ .rule .stop { text-align: center; }
184
+ .empty { padding: 10px; color: var(--sg-muted, #616161); }
185
+ </style>
@@ -0,0 +1,16 @@
1
+ import { type CfRule, type CfStyledRule } from './sheet/conditional-formats.js';
2
+ import type { Rect } from './sheet/rects.js';
3
+ type Props = {
4
+ open?: boolean;
5
+ rules: ReadonlyArray<CfRule>;
6
+ /** The selection, for the Current Selection filter. */
7
+ selection: ReadonlyArray<Rect>;
8
+ onApply: (rules: CfRule[]) => void;
9
+ /** Edit Rule: the shell opens the small dialog on the rule and calls
10
+ * `replace` with the result. */
11
+ onEdit: (rule: CfStyledRule, replace: (next: CfStyledRule) => void) => void;
12
+ onClose?: () => void;
13
+ };
14
+ declare const SvSheetManageRules: import("svelte").Component<Props, {}, "open">;
15
+ type SvSheetManageRules = ReturnType<typeof SvSheetManageRules>;
16
+ export default SvSheetManageRules;
@@ -0,0 +1,225 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Excel's Name Manager: every defined name with what it refers to and
4
+ * what that is worth right now; edit one in place, delete one, add one.
5
+ * Writes go to `workbook.names`, and the shell is told afterwards, since
6
+ * no cell was typed into and the sheet would otherwise not repaint.
7
+ */
8
+ import { SvModal } from '@svgrid/grid'
9
+ import type { Workbook } from './sheet/workbook'
10
+ import type { DefinedName } from './sheet/names'
11
+ import { isValidName } from './sheet/names'
12
+ import { parseA1 } from './sheet/address'
13
+
14
+ type Props = {
15
+ open?: boolean
16
+ workbook: Workbook
17
+ /** Called after every change, so the shell recomputes and repaints. */
18
+ onChange: () => void
19
+ onClose?: () => void
20
+ }
21
+
22
+ let { open = $bindable(false), workbook, onChange, onClose }: Props = $props()
23
+
24
+ let names = $state<DefinedName[]>([])
25
+ let newName = $state('')
26
+ let newRefersTo = $state('')
27
+ let editing = $state<string | null>(null)
28
+ let editRefersTo = $state('')
29
+ let problem = $state<string | null>(null)
30
+
31
+ function reload() {
32
+ names = workbook.names.list()
33
+ }
34
+
35
+ $effect(() => {
36
+ if (!open) return
37
+ reload()
38
+ newName = ''
39
+ newRefersTo = ''
40
+ editing = null
41
+ problem = null
42
+ })
43
+
44
+ /** What a name is worth right now, for the Value column. */
45
+ function valueOf(entry: DefinedName): string {
46
+ const text = entry.refersTo.replace(/^=/, '')
47
+ const bang = text.lastIndexOf('!')
48
+ const sheetName = bang >= 0 ? text.slice(0, bang).replace(/^'(.*)'$/, '$1') : workbook.active
49
+ const address = bang >= 0 ? text.slice(bang + 1) : text
50
+ const [first, last] = address.split(':')
51
+ const from = parseA1(first ?? '')
52
+ if (!from || from.row === null) return ''
53
+ if (last) {
54
+ const to = parseA1(last)
55
+ if (to && to.row !== null) {
56
+ const cells = (to.row - from.row + 1) * (to.col - from.col + 1)
57
+ return `${cells} cells`
58
+ }
59
+ }
60
+ const v = workbook.getValue(sheetName, from.row, from.col)
61
+ if (typeof v === 'object' && v !== null) return v.error
62
+ if (typeof v === 'number') return v.toLocaleString('en-US', { maximumFractionDigits: 4 })
63
+ return String(v)
64
+ }
65
+
66
+ /** Accept `Assumptions!B6`, `=Assumptions!$B$6`, `B6` or a range. */
67
+ function normalise(refersTo: string): string | null {
68
+ const text = refersTo.trim().replace(/^=/, '')
69
+ if (text === '') return null
70
+ const bang = text.lastIndexOf('!')
71
+ const address = bang >= 0 ? text.slice(bang + 1) : text
72
+ const [first, last] = address.split(':')
73
+ if (!parseA1(first ?? '')) return null
74
+ if (last !== undefined && !parseA1(last)) return null
75
+ const sheet = bang >= 0 ? text.slice(0, bang + 1) : `${quote(workbook.active)}!`
76
+ return `=${sheet}${address}`
77
+ }
78
+
79
+ const quote = (name: string) => (/^[A-Za-z_][A-Za-z0-9_.]*$/.test(name) ? name : `'${name.replace(/'/g, "''")}'`)
80
+
81
+ function changed() {
82
+ onChange()
83
+ reload()
84
+ }
85
+
86
+ function add() {
87
+ const name = newName.trim()
88
+ if (!isValidName(name)) { problem = `"${name}" is not a valid name: letters, digits and underscores, not a cell address`; return }
89
+ if (workbook.names.has(name)) { problem = `${name} is already defined; edit it below instead`; return }
90
+ const refersTo = normalise(newRefersTo)
91
+ if (!refersTo) { problem = `"${newRefersTo}" is not a cell or range`; return }
92
+ workbook.names.define(name, refersTo)
93
+ newName = ''
94
+ newRefersTo = ''
95
+ problem = null
96
+ changed()
97
+ }
98
+
99
+ function startEdit(entry: DefinedName) {
100
+ editing = entry.name
101
+ editRefersTo = entry.refersTo
102
+ problem = null
103
+ }
104
+
105
+ function saveEdit() {
106
+ if (!editing) return
107
+ const refersTo = normalise(editRefersTo)
108
+ if (!refersTo) { problem = `"${editRefersTo}" is not a cell or range`; return }
109
+ workbook.names.define(editing, refersTo)
110
+ editing = null
111
+ problem = null
112
+ changed()
113
+ }
114
+
115
+ function remove(name: string) {
116
+ workbook.names.remove(name)
117
+ changed()
118
+ }
119
+
120
+ function close() {
121
+ open = false
122
+ onClose?.()
123
+ }
124
+ </script>
125
+
126
+ <SvModal bind:open onClose={onClose} title="Name Manager" size="md">
127
+ <div class="sv-sheet-dialog manager" role="group" aria-label="Name Manager">
128
+ <table class="names">
129
+ <thead>
130
+ <tr><th>Name</th><th>Refers to</th><th>Value</th><th></th></tr>
131
+ </thead>
132
+ <tbody>
133
+ {#each names as entry (entry.name)}
134
+ <tr>
135
+ <td class="mono">{entry.name}</td>
136
+ <td class="mono">
137
+ {#if editing === entry.name}
138
+ <input
139
+ type="text"
140
+ bind:value={editRefersTo}
141
+ aria-label="Refers to"
142
+ spellcheck="false"
143
+ onkeydown={(e) => { if (e.key === 'Enter') { e.preventDefault(); saveEdit() } if (e.key === 'Escape') { e.stopPropagation(); editing = null } }}
144
+ />
145
+ {:else}
146
+ {entry.refersTo}
147
+ {/if}
148
+ </td>
149
+ <td class="num">{valueOf(entry)}</td>
150
+ <td class="actions">
151
+ {#if editing === entry.name}
152
+ <button type="button" class="link" onclick={saveEdit}>Save</button>
153
+ <button type="button" class="link" onclick={() => (editing = null)}>Cancel</button>
154
+ {:else}
155
+ <button type="button" class="link" onclick={() => startEdit(entry)}>Edit</button>
156
+ <button type="button" class="link" onclick={() => remove(entry.name)}>Delete</button>
157
+ {/if}
158
+ </td>
159
+ </tr>
160
+ {:else}
161
+ <tr><td colspan="4" class="empty">No defined names. Add one below.</td></tr>
162
+ {/each}
163
+ </tbody>
164
+ </table>
165
+
166
+ <form class="new" onsubmit={(e) => { e.preventDefault(); add() }}>
167
+ <label>
168
+ <span>Name:</span>
169
+ <input type="text" bind:value={newName} placeholder="TaxRate" spellcheck="false" />
170
+ </label>
171
+ <label>
172
+ <span>Refers to:</span>
173
+ <input type="text" bind:value={newRefersTo} placeholder="Inputs!B3 or B2:B13" spellcheck="false" />
174
+ </label>
175
+ <button type="submit" class="btn">New</button>
176
+ </form>
177
+ {#if problem}<p class="status problem" role="alert">{problem}</p>{/if}
178
+ </div>
179
+ {#snippet footer()}
180
+ <div class="sv-sheet-dialog-buttons">
181
+ <button type="button" class="btn" onclick={close}>Close</button>
182
+ </div>
183
+ {/snippet}
184
+ </SvModal>
185
+
186
+ <style>
187
+ .names { width: 100%; border-collapse: collapse; }
188
+ .names th {
189
+ text-align: left;
190
+ font-weight: 600;
191
+ color: var(--sg-muted, #616161);
192
+ padding: 4px 8px;
193
+ border-bottom: 1px solid var(--sg-border, #d1d1d1);
194
+ }
195
+ .names td { padding: 4px 8px; border-bottom: 1px solid var(--sg-border, #e5e5e5); vertical-align: middle; }
196
+ .mono { font-family: ui-monospace, Consolas, monospace; font-size: 12px; }
197
+ .num { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
198
+ .actions { display: flex; gap: 8px; justify-content: flex-end; white-space: nowrap; }
199
+ .empty { text-align: center; color: var(--sg-muted, #616161); }
200
+ .link {
201
+ padding: 0;
202
+ font: inherit;
203
+ color: var(--sg-accent, #217346);
204
+ background: none;
205
+ border: 0;
206
+ cursor: pointer;
207
+ }
208
+ .link:hover { text-decoration: underline; }
209
+ .new { display: grid; grid-template-columns: 1fr 1.6fr auto; gap: 8px; align-items: end; }
210
+ .new label { display: flex; flex-direction: column; gap: 4px; }
211
+ .new .btn {
212
+ height: 26px;
213
+ padding: 0 14px;
214
+ font: inherit;
215
+ color: var(--sg-fg, #242424);
216
+ background: var(--sg-bg, #fff);
217
+ border: 1px solid var(--sg-border, #d1d1d1);
218
+ border-radius: var(--sg-radius, 3px);
219
+ cursor: pointer;
220
+ }
221
+ .problem { color: var(--sg-danger, #dc2626); }
222
+ @media (max-width: 560px) {
223
+ .new { grid-template-columns: 1fr; }
224
+ }
225
+ </style>