@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.
- package/dist/SvFormulaBar.svelte +606 -0
- package/dist/SvFormulaBar.svelte.d.ts +62 -0
- package/dist/SvPivotDesigner.svelte +65 -14
- package/dist/SvRibbonIcon.svelte +46 -0
- package/dist/SvRibbonIcon.svelte.d.ts +17 -0
- package/dist/SvSchemaChart.svelte +4 -2
- package/dist/SvSheet.svelte +4114 -0
- package/dist/SvSheet.svelte.d.ts +142 -0
- package/dist/SvSheetComment.svelte +98 -0
- package/dist/SvSheetComment.svelte.d.ts +22 -0
- package/dist/SvSheetConditionalFormat.svelte +162 -0
- package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
- package/dist/SvSheetDataValidation.svelte +249 -0
- package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
- package/dist/SvSheetFilterMenu.svelte +217 -0
- package/dist/SvSheetFilterMenu.svelte.d.ts +17 -0
- package/dist/SvSheetFindReplace.svelte +132 -0
- package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
- package/dist/SvSheetFormatCells.svelte +421 -0
- package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
- package/dist/SvSheetGoalSeek.svelte +144 -0
- package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
- package/dist/SvSheetInsertFunction.svelte +147 -0
- package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
- package/dist/SvSheetListPicker.svelte +89 -0
- package/dist/SvSheetListPicker.svelte.d.ts +16 -0
- package/dist/SvSheetManageRules.svelte +185 -0
- package/dist/SvSheetManageRules.svelte.d.ts +16 -0
- package/dist/SvSheetNameManager.svelte +225 -0
- package/dist/SvSheetNameManager.svelte.d.ts +11 -0
- package/dist/SvSheetPasteSpecial.svelte +105 -0
- package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
- package/dist/SvSheetRemoveDuplicates.svelte +149 -0
- package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
- package/dist/SvSheetRibbon.svelte +1201 -0
- package/dist/SvSheetRibbon.svelte.d.ts +34 -0
- package/dist/SvSheetSizeDialog.svelte +58 -0
- package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
- package/dist/SvSheetTabs.svelte +441 -0
- package/dist/SvSheetTabs.svelte.d.ts +36 -0
- package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
- package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
- package/dist/SvSheetTextToColumns.svelte +172 -0
- package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
- package/dist/SvSheetValidationAlert.svelte +83 -0
- package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
- package/dist/cdn/svgrid-enterprise.svelte-external.js +18997 -5396
- package/dist/designer/assets/GridMenus-BlsvL_aU.js +8 -0
- package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
- package/dist/designer/assets/SvChartMenu-D9hEeHar.js +1 -0
- package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
- package/dist/designer/assets/SvDateTimePicker-DARvKUL2.js +1 -0
- package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +4 -0
- package/dist/designer/assets/SvGridChart-B8j1SSy3.js +3 -0
- package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
- package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
- package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +1 -0
- package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
- package/dist/designer/assets/SvGridChartPanel-pT6ITc2P.js +4 -0
- package/dist/designer/assets/{SvGridChartView-DUc-1RUf.css → SvGridChartView-7msfnWZT.css} +1 -1
- package/dist/designer/assets/SvGridChartView-bg281UA1.js +1 -0
- package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
- package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
- package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
- package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
- package/dist/designer/assets/SvTabs-UFCMTHXS.js +1 -0
- package/dist/designer/assets/chart-cIPUsbM5.js +1 -0
- package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
- package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
- package/dist/designer/assets/chart-panel-messages-DtTxpBUU.js +1 -0
- package/dist/designer/assets/chart-summary-Czu_5epb.js +1 -0
- package/dist/designer/assets/chart-trend-D5I5VEe8.js +1 -0
- package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
- package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
- package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
- package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
- package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
- package/dist/designer/assets/focus-trap-CAreAhlU.js +1 -0
- package/dist/designer/assets/index-CMFgAoVV.css +1 -0
- package/dist/designer/assets/{index-C_a9Cj8Y.js → index-DwU7CAMm.js} +291 -290
- package/dist/designer/assets/list-option-BP_dWS66.js +1 -0
- package/dist/designer/assets/row-resize-DLktcqUK.js +1 -0
- package/dist/designer/assets/src-CITpcm6b.js +1 -0
- package/dist/designer/assets/src-EdI3Xqds.js +58 -0
- package/dist/designer/index.html +23 -14
- package/dist/export-pdf.d.ts +89 -0
- package/dist/export-pdf.js +130 -0
- package/dist/export.js +17 -10
- package/dist/index.d.ts +64 -1
- package/dist/index.js +68 -1
- package/dist/install.js +5 -0
- package/dist/node/studio.js +64 -2
- package/dist/pivot-chart.d.ts +25 -1
- package/dist/pivot-chart.js +25 -69
- package/dist/sheet/address.d.ts +43 -0
- package/dist/sheet/address.js +81 -0
- package/dist/sheet/ast.d.ts +94 -0
- package/dist/sheet/ast.js +56 -0
- package/dist/sheet/auto-filter.d.ts +66 -0
- package/dist/sheet/auto-filter.js +147 -0
- package/dist/sheet/autocomplete.d.ts +42 -0
- package/dist/sheet/autocomplete.js +162 -0
- package/dist/sheet/coerce.d.ts +31 -0
- package/dist/sheet/coerce.js +122 -0
- package/dist/sheet/commands.d.ts +89 -0
- package/dist/sheet/commands.js +230 -0
- package/dist/sheet/comments.d.ts +29 -0
- package/dist/sheet/comments.js +73 -0
- package/dist/sheet/conditional-formats.d.ts +144 -0
- package/dist/sheet/conditional-formats.js +316 -0
- package/dist/sheet/deps.d.ts +60 -0
- package/dist/sheet/deps.js +199 -0
- package/dist/sheet/document.d.ts +150 -0
- package/dist/sheet/document.js +264 -0
- package/dist/sheet/edit-keys.d.ts +14 -0
- package/dist/sheet/edit-keys.js +40 -0
- package/dist/sheet/entry.d.ts +31 -0
- package/dist/sheet/entry.js +76 -0
- package/dist/sheet/evaluate.d.ts +61 -0
- package/dist/sheet/evaluate.js +337 -0
- package/dist/sheet/find-replace.d.ts +62 -0
- package/dist/sheet/find-replace.js +152 -0
- package/dist/sheet/format-store.d.ts +99 -0
- package/dist/sheet/format-store.js +233 -0
- package/dist/sheet/freeze.d.ts +54 -0
- package/dist/sheet/freeze.js +58 -0
- package/dist/sheet/function-catalog.d.ts +10 -0
- package/dist/sheet/function-catalog.js +94 -0
- package/dist/sheet/functions.d.ts +28 -0
- package/dist/sheet/functions.js +362 -0
- package/dist/sheet/goal-seek.d.ts +73 -0
- package/dist/sheet/goal-seek.js +153 -0
- package/dist/sheet/index.d.ts +53 -0
- package/dist/sheet/index.js +53 -0
- package/dist/sheet/merges.d.ts +71 -0
- package/dist/sheet/merges.js +137 -0
- package/dist/sheet/names.d.ts +31 -0
- package/dist/sheet/names.js +87 -0
- package/dist/sheet/navigate.d.ts +52 -0
- package/dist/sheet/navigate.js +135 -0
- package/dist/sheet/number-format.d.ts +63 -0
- package/dist/sheet/number-format.js +508 -0
- package/dist/sheet/palette.d.ts +19 -0
- package/dist/sheet/palette.js +84 -0
- package/dist/sheet/parse.d.ts +13 -0
- package/dist/sheet/parse.js +108 -0
- package/dist/sheet/paste-special.d.ts +164 -0
- package/dist/sheet/paste-special.js +592 -0
- package/dist/sheet/protection.d.ts +21 -0
- package/dist/sheet/protection.js +48 -0
- package/dist/sheet/rects.d.ts +45 -0
- package/dist/sheet/rects.js +112 -0
- package/dist/sheet/refs.d.ts +82 -0
- package/dist/sheet/refs.js +266 -0
- package/dist/sheet/ribbon-icons.d.ts +31 -0
- package/dist/sheet/ribbon-icons.js +350 -0
- package/dist/sheet/ribbon.d.ts +173 -0
- package/dist/sheet/ribbon.js +753 -0
- package/dist/sheet/shortcuts.d.ts +164 -0
- package/dist/sheet/shortcuts.js +623 -0
- package/dist/sheet/structure.d.ts +87 -0
- package/dist/sheet/structure.js +166 -0
- package/dist/sheet/tables.d.ts +70 -0
- package/dist/sheet/tables.js +147 -0
- package/dist/sheet/tokenize.d.ts +54 -0
- package/dist/sheet/tokenize.js +304 -0
- package/dist/sheet/transforms.d.ts +76 -0
- package/dist/sheet/transforms.js +154 -0
- package/dist/sheet/validation.d.ts +92 -0
- package/dist/sheet/validation.js +241 -0
- package/dist/sheet/workbook.d.ts +72 -0
- package/dist/sheet/workbook.js +409 -0
- package/dist/sheet-enable.d.ts +6 -0
- package/dist/sheet-enable.js +46 -0
- package/dist/studio/ui-components.generated.js +63 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +9 -4
- package/dist/designer/assets/GridMenus-B1JtIN21.js +0 -8
- package/dist/designer/assets/SvDateRangeInput-CjO7nn_x.js +0 -1
- package/dist/designer/assets/SvDateTimePicker-ByBD_ov6.js +0 -1
- package/dist/designer/assets/SvField-YDLipR6O.css +0 -1
- package/dist/designer/assets/SvField-cj1PFNgZ.js +0 -1
- package/dist/designer/assets/SvGridCellEditor-0W1L2hJ1.js +0 -2
- package/dist/designer/assets/SvGridChart-DBOR4xcz.js +0 -2
- package/dist/designer/assets/SvGridChart-fkMWeh1J.css +0 -1
- package/dist/designer/assets/SvGridChartPanel-symrhNnR.js +0 -11
- package/dist/designer/assets/SvGridChartView-BmRxq6mU.js +0 -1
- package/dist/designer/assets/SvListBox-1oUF6Iav.js +0 -110
- package/dist/designer/assets/chart-DXFganU8.js +0 -1
- package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
- package/dist/designer/assets/date-format-DGagAqRc.css +0 -1
- package/dist/designer/assets/date-format-onlYDsrY.js +0 -1
- package/dist/designer/assets/easing-zaypKrr0.js +0 -3
- package/dist/designer/assets/index-k2stW6L8.css +0 -1
- package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
- package/dist/designer/assets/src-D9p27OGC.js +0 -58
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The formula bar: a Name Box showing (and accepting) the active cell's
|
|
4
|
+
* address, and an input holding the cell's RAW text rather than its computed
|
|
5
|
+
* value.
|
|
6
|
+
*
|
|
7
|
+
* That split is the whole point. The grid shows `1,234.50`; the bar shows
|
|
8
|
+
* `=B2*C2`. Editing in the grid would otherwise mean editing the result.
|
|
9
|
+
*
|
|
10
|
+
* The bar owns no grid state. It reads the active cell and the raw text
|
|
11
|
+
* through props and reports edits through `onCommit`, so it works over a
|
|
12
|
+
* `<SvGrid>`, a plain array, or a workbook with several sheets.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
suggestFunctions,
|
|
16
|
+
applySuggestion,
|
|
17
|
+
signatureAt,
|
|
18
|
+
type FunctionSuggestion,
|
|
19
|
+
} from './sheet/autocomplete'
|
|
20
|
+
import { parseA1, formatA1 } from './sheet/address'
|
|
21
|
+
import { cycleReference } from './sheet/edit-keys'
|
|
22
|
+
|
|
23
|
+
type Props = {
|
|
24
|
+
/** Active cell in display coordinates, or null when nothing is active. */
|
|
25
|
+
active: { rowIndex: number; colIndex: number } | null
|
|
26
|
+
/** The cell's raw text: a formula as typed, or the literal value. */
|
|
27
|
+
value: string
|
|
28
|
+
/**
|
|
29
|
+
* Called when the user commits with Enter (or the tick) or by leaving
|
|
30
|
+
* the field, with the cell the edit STARTED in. Leaving the field by
|
|
31
|
+
* clicking another cell moves `active` before the blur lands, so a
|
|
32
|
+
* handler that wrote to the active cell would put the text into the cell
|
|
33
|
+
* just clicked. `via` says which: after an Enter the sheet takes the
|
|
34
|
+
* focus back and the cursor moves down, as in Excel; after a blur the
|
|
35
|
+
* click that caused it has already placed the cursor.
|
|
36
|
+
*/
|
|
37
|
+
onCommit: (text: string, cell: { rowIndex: number; colIndex: number }, via: 'enter' | 'blur') => void
|
|
38
|
+
/** Called when the user types an address into the Name Box. */
|
|
39
|
+
onNavigate?: (cell: { rowIndex: number; colIndex: number }) => void
|
|
40
|
+
/** Named ranges for the Name Box dropdown. */
|
|
41
|
+
names?: ReadonlyArray<{ name: string; refersTo: string }>
|
|
42
|
+
/**
|
|
43
|
+
* Shown in the Name Box instead of the address while set: Excel prints
|
|
44
|
+
* "3R x 2C" there while a range is being dragged out.
|
|
45
|
+
*/
|
|
46
|
+
label?: string | null
|
|
47
|
+
onSelectName?: (name: string) => void
|
|
48
|
+
/** The fx button: Excel's Insert Function. Without a handler the button
|
|
49
|
+
* is not drawn, since a button that does nothing is worse than none. */
|
|
50
|
+
onInsertFunction?: () => void
|
|
51
|
+
/** The text as it is being typed, and null once editing ends. The shell
|
|
52
|
+
* colours the cells a formula refers to while it is being written. */
|
|
53
|
+
onDraft?: (text: string | null) => void
|
|
54
|
+
/**
|
|
55
|
+
* Runs of the text to colour, as Excel colours the references in a
|
|
56
|
+
* formula: the shell passes `referenceSpans`. The bar lays a mirror of
|
|
57
|
+
* the text over the field with the runs in colour and makes the field's
|
|
58
|
+
* own text transparent under it, so the caret and the selection stay
|
|
59
|
+
* the browser's.
|
|
60
|
+
*/
|
|
61
|
+
highlight?: (text: string) => ReadonlyArray<{ start: number; end: number; colour: string }>
|
|
62
|
+
showNameBox?: boolean
|
|
63
|
+
autocomplete?: boolean
|
|
64
|
+
disabled?: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let {
|
|
68
|
+
active,
|
|
69
|
+
value = '',
|
|
70
|
+
onCommit,
|
|
71
|
+
onNavigate,
|
|
72
|
+
names = [],
|
|
73
|
+
label = null,
|
|
74
|
+
onSelectName,
|
|
75
|
+
onInsertFunction,
|
|
76
|
+
onDraft,
|
|
77
|
+
highlight,
|
|
78
|
+
showNameBox = true,
|
|
79
|
+
autocomplete = true,
|
|
80
|
+
disabled = false,
|
|
81
|
+
}: Props = $props()
|
|
82
|
+
|
|
83
|
+
let draft = $state('')
|
|
84
|
+
let editing = $state(false)
|
|
85
|
+
/** The coloured runs of the draft, or none while there is nothing to colour. */
|
|
86
|
+
const runs = $derived.by(() => {
|
|
87
|
+
if (!highlight) return []
|
|
88
|
+
const spans = highlight(draft)
|
|
89
|
+
if (spans.length === 0) return []
|
|
90
|
+
const out: Array<{ text: string; colour: string | null }> = []
|
|
91
|
+
let at = 0
|
|
92
|
+
for (const span of spans) {
|
|
93
|
+
if (span.start > at) out.push({ text: draft.slice(at, span.start), colour: null })
|
|
94
|
+
out.push({ text: draft.slice(span.start, span.end), colour: span.colour })
|
|
95
|
+
at = span.end
|
|
96
|
+
}
|
|
97
|
+
if (at < draft.length) out.push({ text: draft.slice(at), colour: null })
|
|
98
|
+
return out
|
|
99
|
+
})
|
|
100
|
+
/** The cell being edited, pinned when editing starts. */
|
|
101
|
+
let editingCell: { rowIndex: number; colIndex: number } | null = null
|
|
102
|
+
|
|
103
|
+
function startEditing() {
|
|
104
|
+
if (editing) return
|
|
105
|
+
editing = true
|
|
106
|
+
editingCell = active
|
|
107
|
+
}
|
|
108
|
+
let caret = $state(0)
|
|
109
|
+
let highlighted = $state(0)
|
|
110
|
+
let input = $state<HTMLTextAreaElement | null>(null)
|
|
111
|
+
/**
|
|
112
|
+
* Excel's bar shows one line until it is expanded with the chevron at its
|
|
113
|
+
* end, and then up to six. The height never follows the active cell on
|
|
114
|
+
* its own: a bar that grew for a two-line cell and shrank on the next
|
|
115
|
+
* click would move the sheet under the pointer mid-click, so the click
|
|
116
|
+
* landed a row below where it was pressed. Alt+Enter typed in the bar
|
|
117
|
+
* expands it, since that is the user's own doing.
|
|
118
|
+
*/
|
|
119
|
+
let expanded = $state(false)
|
|
120
|
+
const lines = $derived(draft.split('\n').length)
|
|
121
|
+
const rows = $derived(expanded ? Math.min(6, Math.max(2, lines)) : 1)
|
|
122
|
+
let nameBoxText = $state('')
|
|
123
|
+
|
|
124
|
+
// Follow the active cell while the user is not mid-edit. Clobbering the
|
|
125
|
+
// draft on every selection change would throw away half-typed formulas.
|
|
126
|
+
$effect(() => {
|
|
127
|
+
const incoming = value
|
|
128
|
+
if (!editing) draft = incoming
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const address = $derived(
|
|
132
|
+
active
|
|
133
|
+
? formatA1({
|
|
134
|
+
col: active.colIndex, colAbs: false,
|
|
135
|
+
row: active.rowIndex, rowAbs: false, sheet: null,
|
|
136
|
+
})
|
|
137
|
+
: '',
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const suggestions = $derived<FunctionSuggestion[]>(
|
|
141
|
+
autocomplete && editing ? suggestFunctions(draft, caret) : [],
|
|
142
|
+
)
|
|
143
|
+
const hint = $derived(editing ? signatureAt(draft, caret) : null)
|
|
144
|
+
|
|
145
|
+
function syncCaret(event: Event) {
|
|
146
|
+
const el = event.currentTarget as HTMLTextAreaElement
|
|
147
|
+
caret = el.selectionStart ?? el.value.length
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function accept(suggestion: FunctionSuggestion) {
|
|
151
|
+
const next = applySuggestion(draft, suggestion)
|
|
152
|
+
draft = next.text
|
|
153
|
+
highlighted = 0
|
|
154
|
+
// Let Svelte write the value before moving the caret into the parens.
|
|
155
|
+
queueMicrotask(() => {
|
|
156
|
+
input?.setSelectionRange(next.caret, next.caret)
|
|
157
|
+
input?.focus()
|
|
158
|
+
caret = next.caret
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function commit(via: 'enter' | 'blur' = 'enter') {
|
|
163
|
+
const cell = editingCell ?? active
|
|
164
|
+
editing = false
|
|
165
|
+
editingCell = null
|
|
166
|
+
onDraft?.(null)
|
|
167
|
+
if (cell) onCommit(draft, cell, via)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Excel's ✕: forget what was typed and leave the cell as it was. */
|
|
171
|
+
function cancel() {
|
|
172
|
+
draft = value
|
|
173
|
+
editing = false
|
|
174
|
+
editingCell = null
|
|
175
|
+
onDraft?.(null)
|
|
176
|
+
input?.blur()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function onKeyDown(event: KeyboardEvent) {
|
|
180
|
+
if (suggestions.length > 0) {
|
|
181
|
+
if (event.key === 'ArrowDown') {
|
|
182
|
+
event.preventDefault()
|
|
183
|
+
highlighted = (highlighted + 1) % suggestions.length
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
if (event.key === 'ArrowUp') {
|
|
187
|
+
event.preventDefault()
|
|
188
|
+
highlighted = (highlighted - 1 + suggestions.length) % suggestions.length
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
if (event.key === 'Tab' || (event.key === 'Enter' && highlighted >= 0)) {
|
|
192
|
+
const picked = suggestions[highlighted]
|
|
193
|
+
if (picked) {
|
|
194
|
+
event.preventDefault()
|
|
195
|
+
accept(picked)
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (event.key === 'Enter' && event.altKey && input) {
|
|
201
|
+
// A line break in the cell's text, as in the cell editor.
|
|
202
|
+
event.preventDefault()
|
|
203
|
+
const start = input.selectionStart ?? input.value.length
|
|
204
|
+
const end = input.selectionEnd ?? start
|
|
205
|
+
startEditing()
|
|
206
|
+
expanded = true
|
|
207
|
+
draft = draft.slice(0, start) + '\n' + draft.slice(end)
|
|
208
|
+
input.value = draft
|
|
209
|
+
input.setSelectionRange(start + 1, start + 1)
|
|
210
|
+
caret = start + 1
|
|
211
|
+
onDraft?.(draft)
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
if (event.key === 'Enter') {
|
|
215
|
+
event.preventDefault()
|
|
216
|
+
commit()
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
if (event.key === 'Escape') {
|
|
220
|
+
event.preventDefault()
|
|
221
|
+
// Revert rather than commit: Escape means "forget what I typed".
|
|
222
|
+
cancel()
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
if (event.key === 'F4' && input) {
|
|
226
|
+
// Excel's F4: the reference at the caret turns through $A$1, A$1, $A1.
|
|
227
|
+
const edit = cycleReference(input.value, input.selectionStart ?? input.value.length)
|
|
228
|
+
if (!edit) return
|
|
229
|
+
event.preventDefault()
|
|
230
|
+
startEditing()
|
|
231
|
+
draft = edit.text
|
|
232
|
+
input.value = edit.text
|
|
233
|
+
input.setSelectionRange(edit.caret, edit.caret)
|
|
234
|
+
caret = edit.caret
|
|
235
|
+
onDraft?.(draft)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function onNameBoxKey(event: KeyboardEvent) {
|
|
240
|
+
if (event.key !== 'Enter') return
|
|
241
|
+
event.preventDefault()
|
|
242
|
+
const ref = parseA1(nameBoxText.trim())
|
|
243
|
+
if (ref && ref.row !== null) {
|
|
244
|
+
onNavigate?.({ rowIndex: ref.row, colIndex: ref.col })
|
|
245
|
+
nameBoxText = ''
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
if (names.some((n) => n.name.toUpperCase() === nameBoxText.trim().toUpperCase())) {
|
|
249
|
+
onSelectName?.(nameBoxText.trim())
|
|
250
|
+
nameBoxText = ''
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
</script>
|
|
254
|
+
|
|
255
|
+
<div class="sv-formula-bar" role="group" aria-label="Formula bar">
|
|
256
|
+
{#if showNameBox}
|
|
257
|
+
<!--
|
|
258
|
+
Excel's Name Box: one field that takes an address and, behind its arrow,
|
|
259
|
+
lists the defined names. The native select sits invisibly over the
|
|
260
|
+
arrow so the list is the browser's own; the field never changes shape
|
|
261
|
+
when the workbook has no names, the arrow only greys out.
|
|
262
|
+
-->
|
|
263
|
+
<div class="name-box">
|
|
264
|
+
<input
|
|
265
|
+
type="text"
|
|
266
|
+
aria-label="Name box"
|
|
267
|
+
placeholder={label ?? address}
|
|
268
|
+
value={nameBoxText}
|
|
269
|
+
{disabled}
|
|
270
|
+
oninput={(e) => (nameBoxText = e.currentTarget.value)}
|
|
271
|
+
onkeydown={onNameBoxKey}
|
|
272
|
+
onblur={() => (nameBoxText = '')}
|
|
273
|
+
/>
|
|
274
|
+
<span class="caret" class:idle={names.length === 0} aria-hidden="true">
|
|
275
|
+
<svg viewBox="0 0 10 10" width="10" height="10"><path d="M2 3.5l3 3 3-3" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
276
|
+
</span>
|
|
277
|
+
{#if names.length > 0}
|
|
278
|
+
<select
|
|
279
|
+
class="names"
|
|
280
|
+
aria-label="Defined names"
|
|
281
|
+
{disabled}
|
|
282
|
+
onchange={(e) => {
|
|
283
|
+
const picked = e.currentTarget.value
|
|
284
|
+
if (picked) onSelectName?.(picked)
|
|
285
|
+
e.currentTarget.selectedIndex = 0
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
<option value="">Names</option>
|
|
289
|
+
{#each names as entry (entry.name)}
|
|
290
|
+
<option value={entry.name}>{entry.name}</option>
|
|
291
|
+
{/each}
|
|
292
|
+
</select>
|
|
293
|
+
{/if}
|
|
294
|
+
</div>
|
|
295
|
+
{/if}
|
|
296
|
+
|
|
297
|
+
<!-- Excel's three: Cancel and Enter while editing, Insert Function always. -->
|
|
298
|
+
<div class="actions">
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
class="action"
|
|
302
|
+
title="Cancel"
|
|
303
|
+
aria-label="Cancel"
|
|
304
|
+
disabled={!editing}
|
|
305
|
+
onmousedown={(e) => e.preventDefault()}
|
|
306
|
+
onclick={cancel}
|
|
307
|
+
><svg viewBox="0 0 12 12" width="12" height="12" aria-hidden="true"><path d="M2.5 2.5l7 7M9.5 2.5l-7 7" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" /></svg></button>
|
|
308
|
+
<button
|
|
309
|
+
type="button"
|
|
310
|
+
class="action"
|
|
311
|
+
title="Enter"
|
|
312
|
+
aria-label="Enter"
|
|
313
|
+
disabled={!editing}
|
|
314
|
+
onmousedown={(e) => e.preventDefault()}
|
|
315
|
+
onclick={() => commit()}
|
|
316
|
+
><svg viewBox="0 0 12 12" width="12" height="12" aria-hidden="true"><path d="M2 6.5l2.7 2.7L10 3.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg></button>
|
|
317
|
+
{#if onInsertFunction}
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
class="action fx"
|
|
321
|
+
title="Insert Function (Shift+F3)"
|
|
322
|
+
aria-label="Insert Function"
|
|
323
|
+
{disabled}
|
|
324
|
+
onclick={() => onInsertFunction?.()}
|
|
325
|
+
>fx</button>
|
|
326
|
+
{:else}
|
|
327
|
+
<span class="action fx idle" aria-hidden="true">fx</span>
|
|
328
|
+
{/if}
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<div class="field">
|
|
332
|
+
<!-- A textarea, not an input: an input strips the line breaks a cell
|
|
333
|
+
typed with Alt+Enter holds, and would commit "twolines" for a cell
|
|
334
|
+
showing two lines. One row until there are breaks to show. -->
|
|
335
|
+
{#if runs.length > 0}
|
|
336
|
+
<div class="formula mirror" aria-hidden="true">{#each runs as run, i (i)}{#if run.colour}<span style:color={run.colour}>{run.text}</span>{:else}{run.text}{/if}{/each}</div>
|
|
337
|
+
{/if}
|
|
338
|
+
<textarea
|
|
339
|
+
bind:this={input}
|
|
340
|
+
class="formula"
|
|
341
|
+
class:coloured={runs.length > 0}
|
|
342
|
+
aria-label="Formula"
|
|
343
|
+
autocomplete="off"
|
|
344
|
+
spellcheck="false"
|
|
345
|
+
{rows}
|
|
346
|
+
value={draft}
|
|
347
|
+
disabled={disabled || active === null}
|
|
348
|
+
oninput={(e) => {
|
|
349
|
+
startEditing()
|
|
350
|
+
draft = e.currentTarget.value
|
|
351
|
+
highlighted = 0
|
|
352
|
+
syncCaret(e)
|
|
353
|
+
onDraft?.(draft)
|
|
354
|
+
}}
|
|
355
|
+
onkeyup={syncCaret}
|
|
356
|
+
onclick={syncCaret}
|
|
357
|
+
onfocus={startEditing}
|
|
358
|
+
onblur={() => { if (editing) commit('blur') }}
|
|
359
|
+
onkeydown={onKeyDown}
|
|
360
|
+
></textarea>
|
|
361
|
+
<button
|
|
362
|
+
type="button"
|
|
363
|
+
class="expand"
|
|
364
|
+
class:on={expanded}
|
|
365
|
+
title={expanded ? 'Collapse the formula bar' : 'Expand the formula bar'}
|
|
366
|
+
aria-label={expanded ? 'Collapse the formula bar' : 'Expand the formula bar'}
|
|
367
|
+
aria-expanded={expanded}
|
|
368
|
+
onclick={() => (expanded = !expanded)}
|
|
369
|
+
><svg viewBox="0 0 12 12" width="10" height="10" aria-hidden="true"><path d="M2 4l4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg></button>
|
|
370
|
+
|
|
371
|
+
{#if hint}
|
|
372
|
+
<div class="hint" role="status">{hint}</div>
|
|
373
|
+
{/if}
|
|
374
|
+
|
|
375
|
+
{#if suggestions.length > 0}
|
|
376
|
+
<ul class="suggestions" role="listbox" aria-label="Function suggestions">
|
|
377
|
+
{#each suggestions as suggestion, i (suggestion.name)}
|
|
378
|
+
<li role="option" aria-selected={i === highlighted}>
|
|
379
|
+
<button
|
|
380
|
+
type="button"
|
|
381
|
+
class:active={i === highlighted}
|
|
382
|
+
onmousedown={(e) => { e.preventDefault(); accept(suggestion) }}
|
|
383
|
+
>{suggestion.name}</button>
|
|
384
|
+
</li>
|
|
385
|
+
{/each}
|
|
386
|
+
</ul>
|
|
387
|
+
{/if}
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<style>
|
|
392
|
+
.sv-formula-bar {
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: flex-start;
|
|
395
|
+
min-height: 26px;
|
|
396
|
+
font-family: var(--sg-font, "Segoe UI", system-ui, sans-serif);
|
|
397
|
+
font-size: 12.5px;
|
|
398
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
399
|
+
border-radius: var(--sg-radius, 3px);
|
|
400
|
+
background: var(--sg-input-bg, var(--sg-bg, #fff));
|
|
401
|
+
color: var(--sg-fg, #242424);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/* ---- Name Box ------------------------------------------------------ */
|
|
405
|
+
.name-box {
|
|
406
|
+
position: relative;
|
|
407
|
+
display: flex;
|
|
408
|
+
align-items: stretch;
|
|
409
|
+
flex: 0 0 auto;
|
|
410
|
+
width: 96px;
|
|
411
|
+
height: 24px;
|
|
412
|
+
border-right: 1px solid var(--sg-border, #d1d1d1);
|
|
413
|
+
}
|
|
414
|
+
.name-box input {
|
|
415
|
+
flex: 1 1 auto;
|
|
416
|
+
width: 100%;
|
|
417
|
+
min-width: 0;
|
|
418
|
+
padding: 0 0 0 7px;
|
|
419
|
+
}
|
|
420
|
+
.name-box input::placeholder {
|
|
421
|
+
color: var(--sg-fg, #242424);
|
|
422
|
+
opacity: 1;
|
|
423
|
+
}
|
|
424
|
+
.caret {
|
|
425
|
+
display: flex;
|
|
426
|
+
align-items: center;
|
|
427
|
+
justify-content: center;
|
|
428
|
+
width: 18px;
|
|
429
|
+
color: var(--sg-muted, #616161);
|
|
430
|
+
border-left: 1px solid transparent;
|
|
431
|
+
}
|
|
432
|
+
.caret.idle { opacity: 0.35; }
|
|
433
|
+
.name-box:hover .caret:not(.idle) {
|
|
434
|
+
background: var(--sg-row-hover-bg, #f0f0f0);
|
|
435
|
+
border-left-color: var(--sg-border, #d1d1d1);
|
|
436
|
+
}
|
|
437
|
+
/* The list is the browser's own select, laid invisibly over the arrow. */
|
|
438
|
+
.names {
|
|
439
|
+
position: absolute;
|
|
440
|
+
top: 0;
|
|
441
|
+
right: 0;
|
|
442
|
+
width: 18px;
|
|
443
|
+
height: 100%;
|
|
444
|
+
opacity: 0;
|
|
445
|
+
cursor: pointer;
|
|
446
|
+
/* Not transparent: the dropdown it opens paints with this background,
|
|
447
|
+
and a see-through one is unreadable on a dark theme. */
|
|
448
|
+
background: var(--sg-input-bg, var(--sg-bg, #fff));
|
|
449
|
+
color: var(--sg-fg, #242424);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/* ---- Cancel / Enter / fx ------------------------------------------- */
|
|
453
|
+
.actions {
|
|
454
|
+
display: flex;
|
|
455
|
+
align-items: center;
|
|
456
|
+
gap: 0;
|
|
457
|
+
height: 24px;
|
|
458
|
+
padding: 0 3px;
|
|
459
|
+
border-right: 1px solid var(--sg-border, #d1d1d1);
|
|
460
|
+
}
|
|
461
|
+
.action {
|
|
462
|
+
display: inline-flex;
|
|
463
|
+
align-items: center;
|
|
464
|
+
justify-content: center;
|
|
465
|
+
width: 20px;
|
|
466
|
+
height: 20px;
|
|
467
|
+
padding: 0;
|
|
468
|
+
font: inherit;
|
|
469
|
+
border: 1px solid transparent;
|
|
470
|
+
border-radius: 3px;
|
|
471
|
+
background: transparent;
|
|
472
|
+
color: var(--sg-muted, #616161);
|
|
473
|
+
cursor: pointer;
|
|
474
|
+
}
|
|
475
|
+
.action:hover:not(:disabled):not(.idle) {
|
|
476
|
+
background: var(--sg-row-hover-bg, #f0f0f0);
|
|
477
|
+
border-color: var(--sg-border, #d1d1d1);
|
|
478
|
+
color: var(--sg-fg, #242424);
|
|
479
|
+
}
|
|
480
|
+
.action:disabled, .action.idle { opacity: 0.35; cursor: default; }
|
|
481
|
+
.action.fx {
|
|
482
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
483
|
+
font-style: italic;
|
|
484
|
+
font-size: 13px;
|
|
485
|
+
width: 22px;
|
|
486
|
+
}
|
|
487
|
+
.action:focus-visible {
|
|
488
|
+
outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #107c41));
|
|
489
|
+
outline-offset: -2px;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/* ---- the formula field --------------------------------------------- */
|
|
493
|
+
.field {
|
|
494
|
+
position: relative;
|
|
495
|
+
display: flex;
|
|
496
|
+
flex: 1 1 auto;
|
|
497
|
+
min-width: 0;
|
|
498
|
+
}
|
|
499
|
+
input {
|
|
500
|
+
font: inherit;
|
|
501
|
+
border: 0;
|
|
502
|
+
outline: none;
|
|
503
|
+
background: transparent;
|
|
504
|
+
color: inherit;
|
|
505
|
+
padding: 0 6px;
|
|
506
|
+
}
|
|
507
|
+
input:focus-visible {
|
|
508
|
+
outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #107c41));
|
|
509
|
+
outline-offset: -2px;
|
|
510
|
+
}
|
|
511
|
+
.formula {
|
|
512
|
+
width: 100%;
|
|
513
|
+
min-height: 24px;
|
|
514
|
+
padding: 3px 6px 3px 8px;
|
|
515
|
+
font: inherit;
|
|
516
|
+
line-height: 18px;
|
|
517
|
+
color: inherit;
|
|
518
|
+
background: transparent;
|
|
519
|
+
border: 0;
|
|
520
|
+
outline: none;
|
|
521
|
+
resize: none;
|
|
522
|
+
overflow: hidden;
|
|
523
|
+
white-space: pre-wrap;
|
|
524
|
+
}
|
|
525
|
+
.formula:focus-visible {
|
|
526
|
+
outline: 2px solid var(--sg-focus-ring, var(--sg-accent, #107c41));
|
|
527
|
+
outline-offset: -2px;
|
|
528
|
+
}
|
|
529
|
+
/* The references in colour: a mirror of the text under a field whose own
|
|
530
|
+
text is transparent. Same box, same type, so the glyphs coincide; the
|
|
531
|
+
textarea's rows decide the height and the mirror is clipped to it. */
|
|
532
|
+
.formula.mirror {
|
|
533
|
+
position: absolute;
|
|
534
|
+
inset: 0;
|
|
535
|
+
right: 18px;
|
|
536
|
+
overflow: hidden;
|
|
537
|
+
overflow-wrap: break-word;
|
|
538
|
+
pointer-events: none;
|
|
539
|
+
color: var(--sg-fg, #242424);
|
|
540
|
+
}
|
|
541
|
+
.formula.coloured {
|
|
542
|
+
position: relative;
|
|
543
|
+
color: transparent;
|
|
544
|
+
caret-color: var(--sg-fg, #242424);
|
|
545
|
+
}
|
|
546
|
+
/* Excel's chevron at the bar's end: expands to show every line. */
|
|
547
|
+
.expand {
|
|
548
|
+
flex: 0 0 auto;
|
|
549
|
+
align-self: flex-start;
|
|
550
|
+
width: 18px;
|
|
551
|
+
height: 24px;
|
|
552
|
+
padding: 0;
|
|
553
|
+
border: 0;
|
|
554
|
+
background: transparent;
|
|
555
|
+
color: var(--sg-muted, #616161);
|
|
556
|
+
cursor: pointer;
|
|
557
|
+
}
|
|
558
|
+
.expand:hover { color: var(--sg-fg, #242424); }
|
|
559
|
+
.expand.on svg { transform: rotate(180deg); }
|
|
560
|
+
.hint {
|
|
561
|
+
position: absolute;
|
|
562
|
+
top: 100%;
|
|
563
|
+
left: 6px;
|
|
564
|
+
z-index: 30;
|
|
565
|
+
margin-top: 3px;
|
|
566
|
+
padding: 3px 7px;
|
|
567
|
+
border-radius: 3px;
|
|
568
|
+
background: var(--sg-fg, #242424);
|
|
569
|
+
color: var(--sg-bg, #fff);
|
|
570
|
+
font-size: 11.5px;
|
|
571
|
+
white-space: nowrap;
|
|
572
|
+
}
|
|
573
|
+
.suggestions {
|
|
574
|
+
position: absolute;
|
|
575
|
+
top: 100%;
|
|
576
|
+
left: 0;
|
|
577
|
+
z-index: 40;
|
|
578
|
+
margin: 24px 0 0;
|
|
579
|
+
padding: 2px;
|
|
580
|
+
list-style: none;
|
|
581
|
+
min-width: 180px;
|
|
582
|
+
max-height: 220px;
|
|
583
|
+
overflow-y: auto;
|
|
584
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
585
|
+
border-radius: 4px;
|
|
586
|
+
background: var(--sg-bg, #fff);
|
|
587
|
+
color: var(--sg-fg, #242424);
|
|
588
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 0.14);
|
|
589
|
+
}
|
|
590
|
+
.suggestions button {
|
|
591
|
+
display: block;
|
|
592
|
+
width: 100%;
|
|
593
|
+
text-align: left;
|
|
594
|
+
font: inherit;
|
|
595
|
+
border: 0;
|
|
596
|
+
background: transparent;
|
|
597
|
+
color: inherit;
|
|
598
|
+
padding: 4px 8px;
|
|
599
|
+
border-radius: 3px;
|
|
600
|
+
cursor: pointer;
|
|
601
|
+
}
|
|
602
|
+
.suggestions button.active,
|
|
603
|
+
.suggestions button:hover {
|
|
604
|
+
background: var(--sg-row-hover-bg, #f0f0f0);
|
|
605
|
+
}
|
|
606
|
+
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
/** Active cell in display coordinates, or null when nothing is active. */
|
|
3
|
+
active: {
|
|
4
|
+
rowIndex: number;
|
|
5
|
+
colIndex: number;
|
|
6
|
+
} | null;
|
|
7
|
+
/** The cell's raw text: a formula as typed, or the literal value. */
|
|
8
|
+
value: string;
|
|
9
|
+
/**
|
|
10
|
+
* Called when the user commits with Enter (or the tick) or by leaving
|
|
11
|
+
* the field, with the cell the edit STARTED in. Leaving the field by
|
|
12
|
+
* clicking another cell moves `active` before the blur lands, so a
|
|
13
|
+
* handler that wrote to the active cell would put the text into the cell
|
|
14
|
+
* just clicked. `via` says which: after an Enter the sheet takes the
|
|
15
|
+
* focus back and the cursor moves down, as in Excel; after a blur the
|
|
16
|
+
* click that caused it has already placed the cursor.
|
|
17
|
+
*/
|
|
18
|
+
onCommit: (text: string, cell: {
|
|
19
|
+
rowIndex: number;
|
|
20
|
+
colIndex: number;
|
|
21
|
+
}, via: 'enter' | 'blur') => void;
|
|
22
|
+
/** Called when the user types an address into the Name Box. */
|
|
23
|
+
onNavigate?: (cell: {
|
|
24
|
+
rowIndex: number;
|
|
25
|
+
colIndex: number;
|
|
26
|
+
}) => void;
|
|
27
|
+
/** Named ranges for the Name Box dropdown. */
|
|
28
|
+
names?: ReadonlyArray<{
|
|
29
|
+
name: string;
|
|
30
|
+
refersTo: string;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Shown in the Name Box instead of the address while set: Excel prints
|
|
34
|
+
* "3R x 2C" there while a range is being dragged out.
|
|
35
|
+
*/
|
|
36
|
+
label?: string | null;
|
|
37
|
+
onSelectName?: (name: string) => void;
|
|
38
|
+
/** The fx button: Excel's Insert Function. Without a handler the button
|
|
39
|
+
* is not drawn, since a button that does nothing is worse than none. */
|
|
40
|
+
onInsertFunction?: () => void;
|
|
41
|
+
/** The text as it is being typed, and null once editing ends. The shell
|
|
42
|
+
* colours the cells a formula refers to while it is being written. */
|
|
43
|
+
onDraft?: (text: string | null) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Runs of the text to colour, as Excel colours the references in a
|
|
46
|
+
* formula: the shell passes `referenceSpans`. The bar lays a mirror of
|
|
47
|
+
* the text over the field with the runs in colour and makes the field's
|
|
48
|
+
* own text transparent under it, so the caret and the selection stay
|
|
49
|
+
* the browser's.
|
|
50
|
+
*/
|
|
51
|
+
highlight?: (text: string) => ReadonlyArray<{
|
|
52
|
+
start: number;
|
|
53
|
+
end: number;
|
|
54
|
+
colour: string;
|
|
55
|
+
}>;
|
|
56
|
+
showNameBox?: boolean;
|
|
57
|
+
autocomplete?: boolean;
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
};
|
|
60
|
+
declare const SvFormulaBar: import("svelte").Component<Props, {}, "">;
|
|
61
|
+
type SvFormulaBar = ReturnType<typeof SvFormulaBar>;
|
|
62
|
+
export default SvFormulaBar;
|