@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,249 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Excel's Data Validation dialog, the two tabs with something behind them:
|
|
4
|
+
* Settings (Allow, Data, the bounds or the source, Ignore blank, In-cell
|
|
5
|
+
* dropdown) and Error Alert (Style, Title, Message). It opens on the rule
|
|
6
|
+
* at the active cell and, on OK, hands back one rule for the shell to put
|
|
7
|
+
* over the selection; Clear All removes the validation from the selection.
|
|
8
|
+
*/
|
|
9
|
+
import { SvModal } from '@svgrid/grid'
|
|
10
|
+
import {
|
|
11
|
+
OPERATOR_LABELS,
|
|
12
|
+
type ValidationRule, type ValidationAllow, type ValidationOperator, type ValidationSpec,
|
|
13
|
+
} from './sheet/validation'
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
open?: boolean
|
|
17
|
+
/** The rule at the active cell, what the dialog opens on. */
|
|
18
|
+
rule: ValidationRule | undefined
|
|
19
|
+
/** The selection as A1, for the heading. */
|
|
20
|
+
address: string
|
|
21
|
+
onApply: (spec: ValidationSpec) => void
|
|
22
|
+
onClear: () => void
|
|
23
|
+
onClose?: () => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let { open = $bindable(false), rule, address, onApply, onClear, onClose }: Props = $props()
|
|
27
|
+
|
|
28
|
+
type Tab = 'settings' | 'alert'
|
|
29
|
+
let tab = $state<Tab>('settings')
|
|
30
|
+
|
|
31
|
+
const ALLOWS: ReadonlyArray<{ id: ValidationAllow; label: string }> = [
|
|
32
|
+
{ id: 'any', label: 'Any value' },
|
|
33
|
+
{ id: 'whole', label: 'Whole number' },
|
|
34
|
+
{ id: 'decimal', label: 'Decimal' },
|
|
35
|
+
{ id: 'list', label: 'List' },
|
|
36
|
+
{ id: 'date', label: 'Date' },
|
|
37
|
+
{ id: 'textLength', label: 'Text length' },
|
|
38
|
+
{ id: 'custom', label: 'Custom' },
|
|
39
|
+
]
|
|
40
|
+
const OPERATORS = Object.entries(OPERATOR_LABELS) as Array<[ValidationOperator, string]>
|
|
41
|
+
|
|
42
|
+
let allow = $state<ValidationAllow>('any')
|
|
43
|
+
let operator = $state<ValidationOperator>('between')
|
|
44
|
+
let value1 = $state('')
|
|
45
|
+
let value2 = $state('')
|
|
46
|
+
let ignoreBlank = $state(true)
|
|
47
|
+
let inCellDropdown = $state(true)
|
|
48
|
+
let style = $state<'stop' | 'warning'>('stop')
|
|
49
|
+
let title = $state('')
|
|
50
|
+
let message = $state('')
|
|
51
|
+
let first = $state<HTMLSelectElement | null>(null)
|
|
52
|
+
|
|
53
|
+
$effect(() => {
|
|
54
|
+
if (!open) return
|
|
55
|
+
tab = 'settings'
|
|
56
|
+
allow = rule?.allow ?? 'any'
|
|
57
|
+
operator = rule?.operator ?? 'between'
|
|
58
|
+
value1 = rule?.value1 ?? ''
|
|
59
|
+
value2 = rule?.value2 ?? ''
|
|
60
|
+
ignoreBlank = rule?.ignoreBlank ?? true
|
|
61
|
+
inCellDropdown = rule?.inCellDropdown ?? true
|
|
62
|
+
style = rule?.alert.style ?? 'stop'
|
|
63
|
+
title = rule?.alert.title ?? ''
|
|
64
|
+
message = rule?.alert.message ?? ''
|
|
65
|
+
queueMicrotask(() => first?.focus())
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const bounded = $derived(allow === 'whole' || allow === 'decimal' || allow === 'date' || allow === 'textLength')
|
|
69
|
+
const twoBounds = $derived(operator === 'between' || operator === 'notBetween')
|
|
70
|
+
const firstLabel = $derived(
|
|
71
|
+
allow === 'list' ? 'Source:'
|
|
72
|
+
: allow === 'custom' ? 'Formula:'
|
|
73
|
+
: twoBounds ? (allow === 'date' ? 'Start date:' : 'Minimum:')
|
|
74
|
+
: allow === 'date' ? 'Date:' : allow === 'textLength' ? 'Length:' : 'Value:',
|
|
75
|
+
)
|
|
76
|
+
const secondLabel = $derived(allow === 'date' ? 'End date:' : 'Maximum:')
|
|
77
|
+
const valid = $derived(
|
|
78
|
+
allow === 'any'
|
|
79
|
+
|| (allow === 'list' && value1.trim() !== '')
|
|
80
|
+
|| (allow === 'custom' && value1.trim() !== '')
|
|
81
|
+
|| (bounded && value1.trim() !== '' && (!twoBounds || value2.trim() !== '')),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
function ok() {
|
|
85
|
+
if (!valid) return
|
|
86
|
+
const spec: ValidationSpec = {
|
|
87
|
+
allow,
|
|
88
|
+
ignoreBlank,
|
|
89
|
+
inCellDropdown: allow === 'list' ? inCellDropdown : false,
|
|
90
|
+
alert: { style, title: title.trim() || undefined, message: message.trim() || undefined },
|
|
91
|
+
}
|
|
92
|
+
if (bounded) {
|
|
93
|
+
spec.operator = operator
|
|
94
|
+
spec.value1 = value1.trim()
|
|
95
|
+
if (twoBounds) spec.value2 = value2.trim()
|
|
96
|
+
} else if (allow === 'list' || allow === 'custom') {
|
|
97
|
+
spec.value1 = value1.trim()
|
|
98
|
+
}
|
|
99
|
+
onApply(spec)
|
|
100
|
+
open = false
|
|
101
|
+
onClose?.()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function clearAll() {
|
|
105
|
+
onClear()
|
|
106
|
+
open = false
|
|
107
|
+
onClose?.()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function close() {
|
|
111
|
+
open = false
|
|
112
|
+
onClose?.()
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<SvModal bind:open onClose={onClose} title="Data Validation" size="sm" width={400}>
|
|
117
|
+
<form class="sv-sheet-dialog validation" onsubmit={(e) => { e.preventDefault(); ok() }}>
|
|
118
|
+
<div class="where">{address}</div>
|
|
119
|
+
<div class="tabs" role="tablist" aria-label="Data Validation tabs">
|
|
120
|
+
{#each [['settings', 'Settings'], ['alert', 'Error Alert']] as [id, label] (id)}
|
|
121
|
+
<button type="button" role="tab" class="tab" class:on={tab === id} aria-selected={tab === id} onclick={() => (tab = id as Tab)}>{label}</button>
|
|
122
|
+
{/each}
|
|
123
|
+
</div>
|
|
124
|
+
<div class="panel" role="tabpanel">
|
|
125
|
+
{#if tab === 'settings'}
|
|
126
|
+
<div class="lead">Validation criteria</div>
|
|
127
|
+
<label class="field">
|
|
128
|
+
<span>Allow:</span>
|
|
129
|
+
<select bind:this={first} bind:value={allow}>
|
|
130
|
+
{#each ALLOWS as a (a.id)}<option value={a.id}>{a.label}</option>{/each}
|
|
131
|
+
</select>
|
|
132
|
+
</label>
|
|
133
|
+
{#if bounded}
|
|
134
|
+
<label class="field">
|
|
135
|
+
<span>Data:</span>
|
|
136
|
+
<select bind:value={operator}>
|
|
137
|
+
{#each OPERATORS as [id, label] (id)}<option value={id}>{label}</option>{/each}
|
|
138
|
+
</select>
|
|
139
|
+
</label>
|
|
140
|
+
{/if}
|
|
141
|
+
{#if allow !== 'any'}
|
|
142
|
+
<label class="field">
|
|
143
|
+
<span>{firstLabel}</span>
|
|
144
|
+
<input type="text" bind:value={value1} spellcheck="false" placeholder={allow === 'list' ? 'Red, Green, Blue or =$D$1:$D$5' : allow === 'custom' ? '=B1<>""' : ''} />
|
|
145
|
+
</label>
|
|
146
|
+
{/if}
|
|
147
|
+
{#if bounded && twoBounds}
|
|
148
|
+
<label class="field">
|
|
149
|
+
<span>{secondLabel}</span>
|
|
150
|
+
<input type="text" bind:value={value2} spellcheck="false" />
|
|
151
|
+
</label>
|
|
152
|
+
{/if}
|
|
153
|
+
<div class="checks">
|
|
154
|
+
<label class="check"><input type="checkbox" bind:checked={ignoreBlank} /> Ignore blank</label>
|
|
155
|
+
{#if allow === 'list'}
|
|
156
|
+
<label class="check"><input type="checkbox" bind:checked={inCellDropdown} /> In-cell dropdown</label>
|
|
157
|
+
{/if}
|
|
158
|
+
</div>
|
|
159
|
+
{#if allow === 'custom'}
|
|
160
|
+
<p class="hint">Written for the top-left cell of the selection; it moves with each cell, as a copied formula would. TRUE (or a number other than 0) allows the entry.</p>
|
|
161
|
+
{:else if allow === 'list'}
|
|
162
|
+
<p class="hint">A comma-separated list, or a range or a defined name starting with =.</p>
|
|
163
|
+
{:else if allow !== 'any'}
|
|
164
|
+
<p class="hint">A bound can be a formula, so =$B$1 follows B1.</p>
|
|
165
|
+
{/if}
|
|
166
|
+
{:else}
|
|
167
|
+
<div class="lead">When the user enters invalid data, show this alert:</div>
|
|
168
|
+
<label class="field">
|
|
169
|
+
<span>Style:</span>
|
|
170
|
+
<select bind:value={style}>
|
|
171
|
+
<option value="stop">Stop</option>
|
|
172
|
+
<option value="warning">Warning</option>
|
|
173
|
+
</select>
|
|
174
|
+
</label>
|
|
175
|
+
<label class="field">
|
|
176
|
+
<span>Title:</span>
|
|
177
|
+
<input type="text" bind:value={title} />
|
|
178
|
+
</label>
|
|
179
|
+
<label class="field message">
|
|
180
|
+
<span>Error message:</span>
|
|
181
|
+
<textarea rows="4" bind:value={message}></textarea>
|
|
182
|
+
</label>
|
|
183
|
+
<p class="hint">Stop refuses the entry and offers Retry; Warning asks whether to keep it anyway.</p>
|
|
184
|
+
{/if}
|
|
185
|
+
</div>
|
|
186
|
+
</form>
|
|
187
|
+
{#snippet footer()}
|
|
188
|
+
<div class="sv-sheet-dialog-buttons">
|
|
189
|
+
<button type="button" class="btn" onclick={clearAll}>Clear All</button>
|
|
190
|
+
<span class="spacer"></span>
|
|
191
|
+
<button type="button" class="btn primary" onclick={ok} disabled={!valid}>OK</button>
|
|
192
|
+
<button type="button" class="btn" onclick={close}>Cancel</button>
|
|
193
|
+
</div>
|
|
194
|
+
{/snippet}
|
|
195
|
+
</SvModal>
|
|
196
|
+
|
|
197
|
+
<style>
|
|
198
|
+
.where { color: var(--sg-muted, #616161); }
|
|
199
|
+
.tabs {
|
|
200
|
+
display: flex;
|
|
201
|
+
gap: 2px;
|
|
202
|
+
border-bottom: 1px solid var(--sg-border, #d1d1d1);
|
|
203
|
+
}
|
|
204
|
+
.tab {
|
|
205
|
+
padding: 5px 12px;
|
|
206
|
+
font: inherit;
|
|
207
|
+
font-size: 13px;
|
|
208
|
+
color: var(--sg-muted, #616161);
|
|
209
|
+
background: transparent;
|
|
210
|
+
border: 1px solid transparent;
|
|
211
|
+
border-bottom: 0;
|
|
212
|
+
border-radius: var(--sg-radius, 3px) var(--sg-radius, 3px) 0 0;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
}
|
|
215
|
+
.tab:hover { color: var(--sg-fg, #242424); }
|
|
216
|
+
.tab.on {
|
|
217
|
+
color: var(--sg-fg, #242424);
|
|
218
|
+
background: var(--sg-bg, #fff);
|
|
219
|
+
border-color: var(--sg-border, #d1d1d1);
|
|
220
|
+
margin-bottom: -1px;
|
|
221
|
+
}
|
|
222
|
+
.panel {
|
|
223
|
+
display: flex;
|
|
224
|
+
flex-direction: column;
|
|
225
|
+
gap: 10px;
|
|
226
|
+
min-height: 220px;
|
|
227
|
+
padding-top: 4px;
|
|
228
|
+
}
|
|
229
|
+
.lead { font-weight: 600; }
|
|
230
|
+
.hint { margin: 0; color: var(--sg-muted, #616161); }
|
|
231
|
+
.field.message { align-items: start; }
|
|
232
|
+
textarea {
|
|
233
|
+
width: 100%;
|
|
234
|
+
box-sizing: border-box;
|
|
235
|
+
padding: 4px 6px;
|
|
236
|
+
font: inherit;
|
|
237
|
+
color: inherit;
|
|
238
|
+
background: var(--sg-bg, #fff);
|
|
239
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
240
|
+
border-radius: var(--sg-radius, 3px);
|
|
241
|
+
resize: vertical;
|
|
242
|
+
}
|
|
243
|
+
textarea:focus {
|
|
244
|
+
outline: none;
|
|
245
|
+
border-color: var(--sg-accent, #217346);
|
|
246
|
+
box-shadow: 0 0 0 1px var(--sg-accent, #217346);
|
|
247
|
+
}
|
|
248
|
+
.spacer { flex: 1; }
|
|
249
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ValidationRule, type ValidationSpec } from './sheet/validation.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
/** The rule at the active cell, what the dialog opens on. */
|
|
5
|
+
rule: ValidationRule | undefined;
|
|
6
|
+
/** The selection as A1, for the heading. */
|
|
7
|
+
address: string;
|
|
8
|
+
onApply: (spec: ValidationSpec) => void;
|
|
9
|
+
onClear: () => void;
|
|
10
|
+
onClose?: () => void;
|
|
11
|
+
};
|
|
12
|
+
declare const SvSheetDataValidation: import("svelte").Component<Props, {}, "open">;
|
|
13
|
+
type SvSheetDataValidation = ReturnType<typeof SvSheetDataValidation>;
|
|
14
|
+
export default SvSheetDataValidation;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The menu an AutoFilter arrow drops: Excel's, in its order. Sort A to Z
|
|
4
|
+
* and Z to A, Clear Filter From the column, a condition (Text Filters or
|
|
5
|
+
* Number Filters: equals, greater than, contains, between and the rest,
|
|
6
|
+
* two of them joined with And / Or), a search box, (Select All) and the
|
|
7
|
+
* column's values to tick, OK and Cancel. The shell mounts it inside the
|
|
8
|
+
* popover anchored to the header cell.
|
|
9
|
+
*/
|
|
10
|
+
import type { ExcelFilterOperator } from '@svgrid/grid/filtering'
|
|
11
|
+
import { valuesFilter, type ColumnFilter, type FilterValue, type FilterCondition } from './sheet/auto-filter'
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
/** The header text, for Clear Filter From "X". */
|
|
15
|
+
header: string
|
|
16
|
+
/** The column's values with their counts. */
|
|
17
|
+
values: ReadonlyArray<FilterValue>
|
|
18
|
+
/** The filter on the column now, if any. */
|
|
19
|
+
filter: ColumnFilter | null
|
|
20
|
+
/** Whether the column holds numbers, for Number Filters over Text Filters. */
|
|
21
|
+
numeric: boolean
|
|
22
|
+
onSort: (direction: 'asc' | 'desc') => void
|
|
23
|
+
onApply: (filter: ColumnFilter | null) => void
|
|
24
|
+
onCancel: () => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let { header, values, filter, numeric, onSort, onApply, onCancel }: Props = $props()
|
|
28
|
+
|
|
29
|
+
const TEXT_OPS: ReadonlyArray<[ExcelFilterOperator, string]> = [
|
|
30
|
+
['equals', 'Equals'], ['notEquals', 'Does Not Equal'], ['startsWith', 'Begins With'], ['endsWith', 'Ends With'],
|
|
31
|
+
['contains', 'Contains'], ['notContains', 'Does Not Contain'], ['isBlank', 'Is Blank'], ['isNotBlank', 'Is Not Blank'],
|
|
32
|
+
]
|
|
33
|
+
const NUMBER_OPS: ReadonlyArray<[ExcelFilterOperator, string]> = [
|
|
34
|
+
['equals', 'Equals'], ['notEquals', 'Does Not Equal'], ['greaterThan', 'Greater Than'], ['lessThan', 'Less Than'],
|
|
35
|
+
['between', 'Between'], ['isBlank', 'Is Blank'], ['isNotBlank', 'Is Not Blank'],
|
|
36
|
+
]
|
|
37
|
+
const ops = $derived(numeric ? NUMBER_OPS : TEXT_OPS)
|
|
38
|
+
|
|
39
|
+
let search = $state('')
|
|
40
|
+
let ticked = $state<Set<string>>(new Set())
|
|
41
|
+
let mode = $state<'values' | 'condition'>('values')
|
|
42
|
+
let op1 = $state<ExcelFilterOperator>('equals')
|
|
43
|
+
let v1 = $state('')
|
|
44
|
+
let v1to = $state('')
|
|
45
|
+
let join = $state<'and' | 'or'>('and')
|
|
46
|
+
let op2 = $state<ExcelFilterOperator | ''>('')
|
|
47
|
+
let v2 = $state('')
|
|
48
|
+
let v2to = $state('')
|
|
49
|
+
let searchBox = $state<HTMLInputElement | null>(null)
|
|
50
|
+
|
|
51
|
+
$effect(() => {
|
|
52
|
+
// Open on the column's state: the ticks it has, or the condition it has.
|
|
53
|
+
const f = filter
|
|
54
|
+
const all = values.map((v) => v.text)
|
|
55
|
+
if (f?.kind === 'values') ticked = new Set(all.filter((t) => !f.excluded.includes(t)))
|
|
56
|
+
else ticked = new Set(all)
|
|
57
|
+
mode = f?.kind === 'condition' ? 'condition' : 'values'
|
|
58
|
+
if (f?.kind === 'condition') {
|
|
59
|
+
op1 = f.first.op; v1 = f.first.value ?? ''; v1to = f.first.valueTo ?? ''
|
|
60
|
+
join = f.join ?? 'and'
|
|
61
|
+
op2 = f.second?.op ?? ''; v2 = f.second?.value ?? ''; v2to = f.second?.valueTo ?? ''
|
|
62
|
+
} else {
|
|
63
|
+
op1 = 'equals'; v1 = ''; v1to = ''; join = 'and'; op2 = ''; v2 = ''; v2to = ''
|
|
64
|
+
}
|
|
65
|
+
search = ''
|
|
66
|
+
queueMicrotask(() => searchBox?.focus())
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const shown = $derived.by(() => {
|
|
70
|
+
const needle = search.trim().toLowerCase()
|
|
71
|
+
return needle ? values.filter((v) => v.text.toLowerCase().includes(needle)) : values
|
|
72
|
+
})
|
|
73
|
+
const allShownTicked = $derived(shown.length > 0 && shown.every((v) => ticked.has(v.text)))
|
|
74
|
+
const noneShownTicked = $derived(shown.every((v) => !ticked.has(v.text)))
|
|
75
|
+
|
|
76
|
+
function toggle(text: string) {
|
|
77
|
+
const next = new Set(ticked)
|
|
78
|
+
if (next.has(text)) next.delete(text)
|
|
79
|
+
else next.add(text)
|
|
80
|
+
ticked = next
|
|
81
|
+
mode = 'values'
|
|
82
|
+
}
|
|
83
|
+
function toggleAll() {
|
|
84
|
+
const next = new Set(ticked)
|
|
85
|
+
if (allShownTicked) for (const v of shown) next.delete(v.text)
|
|
86
|
+
else for (const v of shown) next.add(v.text)
|
|
87
|
+
ticked = next
|
|
88
|
+
mode = 'values'
|
|
89
|
+
}
|
|
90
|
+
const needsValue = (op: ExcelFilterOperator | '') => op !== '' && op !== 'isBlank' && op !== 'isNotBlank'
|
|
91
|
+
const conditionValid = $derived(
|
|
92
|
+
(!needsValue(op1) || v1.trim() !== '') && (op1 !== 'between' || v1to.trim() !== '')
|
|
93
|
+
&& (op2 === '' || !needsValue(op2) || v2.trim() !== '') && (op2 !== 'between' || v2to.trim() !== ''),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
function ok() {
|
|
97
|
+
if (mode === 'condition') {
|
|
98
|
+
if (!conditionValid) return
|
|
99
|
+
const first: FilterCondition = { op: op1, ...(needsValue(op1) ? { value: v1.trim() } : {}), ...(op1 === 'between' ? { valueTo: v1to.trim() } : {}) }
|
|
100
|
+
const second: FilterCondition | undefined = op2 === ''
|
|
101
|
+
? undefined
|
|
102
|
+
: { op: op2, ...(needsValue(op2) ? { value: v2.trim() } : {}), ...(op2 === 'between' ? { valueTo: v2to.trim() } : {}) }
|
|
103
|
+
onApply({ kind: 'condition', first, ...(second ? { join, second } : {}) })
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
// A search applies its results, as Excel's does: the values it hides
|
|
107
|
+
// are out, whatever their ticks, and the ticked matches stay.
|
|
108
|
+
const keep = search.trim() ? new Set(shown.filter((v) => ticked.has(v.text)).map((v) => v.text)) : ticked
|
|
109
|
+
onApply(valuesFilter(values.map((v) => v.text), keep))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function onKeyDown(event: KeyboardEvent) {
|
|
113
|
+
if (event.key === 'Enter' && !(event.target instanceof HTMLButtonElement)) { event.preventDefault(); ok() }
|
|
114
|
+
}
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
118
|
+
<div class="sv-sheet-filter-menu" role="dialog" aria-label={`Filter ${header}`} onkeydown={onKeyDown}>
|
|
119
|
+
<button type="button" class="row" onclick={() => onSort('asc')}>{numeric ? 'Sort Smallest to Largest' : 'Sort A to Z'}</button>
|
|
120
|
+
<button type="button" class="row" onclick={() => onSort('desc')}>{numeric ? 'Sort Largest to Smallest' : 'Sort Z to A'}</button>
|
|
121
|
+
<hr />
|
|
122
|
+
<button type="button" class="row" disabled={!filter} onclick={() => onApply(null)}>Clear Filter From "{header}"</button>
|
|
123
|
+
<details class="conditions" open={mode === 'condition'} ontoggle={(e) => { if ((e.currentTarget as HTMLDetailsElement).open) mode = 'condition' }}>
|
|
124
|
+
<summary>{numeric ? 'Number Filters' : 'Text Filters'}</summary>
|
|
125
|
+
<div class="condition">
|
|
126
|
+
<select bind:value={op1} aria-label="First condition" onchange={() => (mode = 'condition')}>
|
|
127
|
+
{#each ops as [op, label] (op)}<option value={op}>{label}</option>{/each}
|
|
128
|
+
</select>
|
|
129
|
+
{#if needsValue(op1)}<input type="text" bind:value={v1} aria-label="First value" oninput={() => (mode = 'condition')} />{/if}
|
|
130
|
+
{#if op1 === 'between'}<span>and</span><input type="text" bind:value={v1to} aria-label="First upper value" />{/if}
|
|
131
|
+
</div>
|
|
132
|
+
<div class="join">
|
|
133
|
+
<label class="check"><input type="radio" name="sv-sheet-filter-join" value="and" bind:group={join} /> And</label>
|
|
134
|
+
<label class="check"><input type="radio" name="sv-sheet-filter-join" value="or" bind:group={join} /> Or</label>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="condition">
|
|
137
|
+
<select bind:value={op2} aria-label="Second condition">
|
|
138
|
+
<option value="">(none)</option>
|
|
139
|
+
{#each ops as [op, label] (op)}<option value={op}>{label}</option>{/each}
|
|
140
|
+
</select>
|
|
141
|
+
{#if needsValue(op2)}<input type="text" bind:value={v2} aria-label="Second value" />{/if}
|
|
142
|
+
{#if op2 === 'between'}<span>and</span><input type="text" bind:value={v2to} aria-label="Second upper value" />{/if}
|
|
143
|
+
</div>
|
|
144
|
+
</details>
|
|
145
|
+
<hr />
|
|
146
|
+
<input bind:this={searchBox} type="search" class="search" placeholder="Search" aria-label="Search values" bind:value={search} />
|
|
147
|
+
<div class="values" role="group" aria-label="Values">
|
|
148
|
+
<label class="check all">
|
|
149
|
+
<input type="checkbox" checked={allShownTicked} indeterminate={!allShownTicked && !noneShownTicked} onchange={toggleAll} />
|
|
150
|
+
(Select All{search.trim() ? ' Search Results' : ''})
|
|
151
|
+
</label>
|
|
152
|
+
{#each shown as v (v.text)}
|
|
153
|
+
<label class="check">
|
|
154
|
+
<input type="checkbox" checked={ticked.has(v.text)} onchange={() => toggle(v.text)} />
|
|
155
|
+
<span class="text">{v.text === '' ? '(Blanks)' : v.text}</span>
|
|
156
|
+
<span class="count">{v.count}</span>
|
|
157
|
+
</label>
|
|
158
|
+
{/each}
|
|
159
|
+
{#if shown.length === 0}<div class="none">No matches.</div>{/if}
|
|
160
|
+
</div>
|
|
161
|
+
<div class="sv-sheet-dialog-buttons">
|
|
162
|
+
<button type="button" class="btn primary" onclick={ok} disabled={mode === 'condition' ? !conditionValid : noneShownTicked && !search.trim()}>OK</button>
|
|
163
|
+
<button type="button" class="btn" onclick={onCancel}>Cancel</button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<style>
|
|
168
|
+
.sv-sheet-filter-menu {
|
|
169
|
+
display: flex;
|
|
170
|
+
flex-direction: column;
|
|
171
|
+
gap: 4px;
|
|
172
|
+
width: 260px;
|
|
173
|
+
padding: 6px;
|
|
174
|
+
font-size: 12px;
|
|
175
|
+
color: var(--sg-fg, #242424);
|
|
176
|
+
}
|
|
177
|
+
hr { width: 100%; margin: 2px 0; border: 0; border-top: 1px solid var(--sg-border, #d1d1d1); }
|
|
178
|
+
.row {
|
|
179
|
+
padding: 5px 8px;
|
|
180
|
+
font: inherit;
|
|
181
|
+
text-align: left;
|
|
182
|
+
color: inherit;
|
|
183
|
+
background: transparent;
|
|
184
|
+
border: 0;
|
|
185
|
+
border-radius: 3px;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
}
|
|
188
|
+
.row:hover:not(:disabled) { background: var(--sg-row-hover-bg, #f0f0f0); }
|
|
189
|
+
.row:disabled { opacity: 0.5; cursor: default; }
|
|
190
|
+
.conditions summary { padding: 5px 8px; cursor: pointer; border-radius: 3px; }
|
|
191
|
+
.conditions summary:hover { background: var(--sg-row-hover-bg, #f0f0f0); }
|
|
192
|
+
.condition, .join { display: flex; align-items: center; gap: 6px; padding: 3px 8px; }
|
|
193
|
+
.condition select { flex: 0 0 120px; }
|
|
194
|
+
.condition input { flex: 1 1 60px; min-width: 40px; }
|
|
195
|
+
select, input[type="text"], .search {
|
|
196
|
+
height: 24px;
|
|
197
|
+
padding: 0 6px;
|
|
198
|
+
font: inherit;
|
|
199
|
+
color: inherit;
|
|
200
|
+
background: var(--sg-bg, #fff);
|
|
201
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
202
|
+
border-radius: 3px;
|
|
203
|
+
}
|
|
204
|
+
.search { width: 100%; box-sizing: border-box; }
|
|
205
|
+
.values {
|
|
206
|
+
max-height: 180px;
|
|
207
|
+
overflow-y: auto;
|
|
208
|
+
padding: 2px 4px;
|
|
209
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
210
|
+
border-radius: 3px;
|
|
211
|
+
}
|
|
212
|
+
.check { display: flex; align-items: center; gap: 6px; padding: 2px 4px; cursor: default; }
|
|
213
|
+
.check.all { font-weight: 600; }
|
|
214
|
+
.check .text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
215
|
+
.check .count { color: var(--sg-muted, #616161); }
|
|
216
|
+
.none { padding: 4px; color: var(--sg-muted, #616161); }
|
|
217
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ColumnFilter, type FilterValue } from './sheet/auto-filter.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
/** The header text, for Clear Filter From "X". */
|
|
4
|
+
header: string;
|
|
5
|
+
/** The column's values with their counts. */
|
|
6
|
+
values: ReadonlyArray<FilterValue>;
|
|
7
|
+
/** The filter on the column now, if any. */
|
|
8
|
+
filter: ColumnFilter | null;
|
|
9
|
+
/** Whether the column holds numbers, for Number Filters over Text Filters. */
|
|
10
|
+
numeric: boolean;
|
|
11
|
+
onSort: (direction: 'asc' | 'desc') => void;
|
|
12
|
+
onApply: (filter: ColumnFilter | null) => void;
|
|
13
|
+
onCancel: () => void;
|
|
14
|
+
};
|
|
15
|
+
declare const SvSheetFilterMenu: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type SvSheetFilterMenu = ReturnType<typeof SvSheetFilterMenu>;
|
|
17
|
+
export default SvSheetFilterMenu;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Excel's Find and Replace, the Replace tab: what to find, what to put in
|
|
4
|
+
* its place, whether case and the whole cell must match, and whether to
|
|
5
|
+
* look at what a cell shows or at the formula behind it. Find Next walks
|
|
6
|
+
* the sheet from the active cell and wraps; Replace changes the active
|
|
7
|
+
* cell when it matches and moves on; Replace All is one undo.
|
|
8
|
+
*
|
|
9
|
+
* The shell opens it for Ctrl+H, the ribbon's Find & Select and the
|
|
10
|
+
* `find-replace` action. It reads and writes through the find target the
|
|
11
|
+
* shell registers, so a replacement lands in the workbook and in the
|
|
12
|
+
* grid's history like a typed edit. Styled by the `.sv-sheet-dialog`
|
|
13
|
+
* rules the shell carries for all four of its dialogs.
|
|
14
|
+
*/
|
|
15
|
+
import { SvModal } from '@svgrid/grid'
|
|
16
|
+
import type { GridCommandContext } from '@svgrid/grid/shortcuts'
|
|
17
|
+
import { findAll, findNext, replaceOne, replaceAll, type FindOptions } from './sheet/find-replace'
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
open?: boolean
|
|
21
|
+
/** The command context to search with; null while the grid has no api. */
|
|
22
|
+
cmd: () => GridCommandContext | null
|
|
23
|
+
onClose?: () => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let { open = $bindable(false), cmd, onClose }: Props = $props()
|
|
27
|
+
|
|
28
|
+
let findText = $state('')
|
|
29
|
+
let replaceText = $state('')
|
|
30
|
+
let matchCase = $state(false)
|
|
31
|
+
let entireCell = $state(false)
|
|
32
|
+
let lookIn = $state<'values' | 'formulas'>('values')
|
|
33
|
+
let status = $state('')
|
|
34
|
+
let findInput = $state<HTMLInputElement | null>(null)
|
|
35
|
+
|
|
36
|
+
const options = (): FindOptions => ({ matchCase, matchEntireCell: entireCell, lookIn })
|
|
37
|
+
|
|
38
|
+
// A fresh open starts with the caret in Find what, as Excel's does.
|
|
39
|
+
$effect(() => {
|
|
40
|
+
if (open) {
|
|
41
|
+
status = ''
|
|
42
|
+
queueMicrotask(() => findInput?.focus())
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
function land(hit: { rowIndex: number; colIndex: number } | null) {
|
|
47
|
+
const c = cmd()
|
|
48
|
+
if (!c) return
|
|
49
|
+
if (!hit) {
|
|
50
|
+
status = findText ? `We couldn't find what you were looking for.` : ''
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
c.setActiveCell(hit.rowIndex, hit.colIndex)
|
|
54
|
+
c.setSelection(hit.rowIndex, hit.colIndex)
|
|
55
|
+
c.scrollIntoView(hit.rowIndex, hit.colIndex)
|
|
56
|
+
status = ''
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function next(direction: 1 | -1 = 1) {
|
|
60
|
+
const c = cmd()
|
|
61
|
+
if (!c || !findText) return
|
|
62
|
+
land(findNext(c, findText, options(), direction))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function countAll() {
|
|
66
|
+
const c = cmd()
|
|
67
|
+
if (!c || !findText) return
|
|
68
|
+
const n = findAll(c, findText, options()).length
|
|
69
|
+
status = n === 0 ? `We couldn't find what you were looking for.` : `${n} cell${n === 1 ? '' : 's'} found`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function replace() {
|
|
73
|
+
const c = cmd()
|
|
74
|
+
if (!c || !findText) return
|
|
75
|
+
replaceOne(c, findText, replaceText, options())
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function all() {
|
|
79
|
+
const c = cmd()
|
|
80
|
+
if (!c || !findText) return
|
|
81
|
+
const n = replaceAll(c, findText, replaceText, options())
|
|
82
|
+
status = n === 0
|
|
83
|
+
? `We couldn't find anything to replace.`
|
|
84
|
+
: `All done. We made ${n} replacement${n === 1 ? '' : 's'}.`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function close() {
|
|
88
|
+
open = false
|
|
89
|
+
onClose?.()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function onKey(event: KeyboardEvent) {
|
|
93
|
+
if (event.key === 'Enter') {
|
|
94
|
+
event.preventDefault()
|
|
95
|
+
next(event.shiftKey ? -1 : 1)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<SvModal bind:open onClose={onClose} title="Find and Replace" size="sm">
|
|
101
|
+
<div class="sv-sheet-dialog" role="group" aria-label="Find and Replace">
|
|
102
|
+
<label class="field">
|
|
103
|
+
<span>Find what:</span>
|
|
104
|
+
<input bind:this={findInput} type="text" bind:value={findText} onkeydown={onKey} spellcheck="false" autocomplete="off" />
|
|
105
|
+
</label>
|
|
106
|
+
<label class="field">
|
|
107
|
+
<span>Replace with:</span>
|
|
108
|
+
<input type="text" bind:value={replaceText} onkeydown={onKey} spellcheck="false" autocomplete="off" />
|
|
109
|
+
</label>
|
|
110
|
+
<div class="checks">
|
|
111
|
+
<label class="check"><input type="checkbox" bind:checked={matchCase} /> Match case</label>
|
|
112
|
+
<label class="check"><input type="checkbox" bind:checked={entireCell} /> Match entire cell contents</label>
|
|
113
|
+
<label class="field auto">
|
|
114
|
+
<span>Look in:</span>
|
|
115
|
+
<select bind:value={lookIn}>
|
|
116
|
+
<option value="values">Values</option>
|
|
117
|
+
<option value="formulas">Formulas</option>
|
|
118
|
+
</select>
|
|
119
|
+
</label>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="status" role="status" aria-live="polite">{status}</div>
|
|
122
|
+
</div>
|
|
123
|
+
{#snippet footer()}
|
|
124
|
+
<div class="sv-sheet-dialog-buttons">
|
|
125
|
+
<button type="button" class="btn" onclick={all} disabled={!findText}>Replace All</button>
|
|
126
|
+
<button type="button" class="btn" onclick={replace} disabled={!findText}>Replace</button>
|
|
127
|
+
<button type="button" class="btn" onclick={countAll} disabled={!findText}>Find All</button>
|
|
128
|
+
<button type="button" class="btn primary" onclick={() => next(1)} disabled={!findText}>Find Next</button>
|
|
129
|
+
<button type="button" class="btn" onclick={close}>Close</button>
|
|
130
|
+
</div>
|
|
131
|
+
{/snippet}
|
|
132
|
+
</SvModal>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GridCommandContext } from '@svgrid/grid/shortcuts';
|
|
2
|
+
type Props = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
/** The command context to search with; null while the grid has no api. */
|
|
5
|
+
cmd: () => GridCommandContext | null;
|
|
6
|
+
onClose?: () => void;
|
|
7
|
+
};
|
|
8
|
+
declare const SvSheetFindReplace: import("svelte").Component<Props, {}, "open">;
|
|
9
|
+
type SvSheetFindReplace = ReturnType<typeof SvSheetFindReplace>;
|
|
10
|
+
export default SvSheetFindReplace;
|