@svgrid/grid 2.2.31 → 2.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/DockNodeView.svelte +1 -1
- package/dist/GridFooter.svelte +0 -40
- package/dist/GridMenus.svelte +0 -77
- package/dist/SvAvatar.svelte +1 -1
- package/dist/SvCheckBox.svelte +2 -1
- package/dist/SvCircularProgress.svelte +1 -1
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvGrid.controller.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.js +358 -18
- package/dist/SvGrid.css +103 -3
- package/dist/SvGrid.svelte +164 -35
- package/dist/SvGrid.types.d.ts +81 -0
- package/dist/SvGridChart.svelte +2 -10
- package/dist/SvGridDropdown.svelte +38 -6
- package/dist/SvGridDropdown.svelte.d.ts +4 -0
- package/dist/SvGridSelect.svelte +12 -5
- package/dist/SvGridSelect.svelte.d.ts +6 -0
- package/dist/SvGroupCell.svelte +9 -2
- package/dist/SvKbd.svelte +1 -1
- package/dist/SvList.svelte +1 -1
- package/dist/SvMenubar.svelte +1 -1
- package/dist/SvMultiSelect.svelte +12 -4
- package/dist/SvMultiSelect.svelte.d.ts +6 -0
- package/dist/SvOtpInput.svelte +1 -1
- package/dist/SvPasswordInput.svelte +1 -1
- package/dist/SvProgress.svelte +1 -1
- package/dist/SvRating.svelte +1 -1
- package/dist/SvResult.svelte +1 -1
- package/dist/SvRichText.svelte +35 -16
- package/dist/SvRichText.svelte.d.ts +9 -0
- package/dist/SvSwitchButton.svelte +2 -1
- package/dist/SvToaster.svelte +1 -1
- package/dist/SvTour.svelte +4 -0
- package/dist/SvTree.svelte +9 -2
- package/dist/SvTree.svelte.d.ts +5 -0
- package/dist/SvTreeSelect.svelte +6 -4
- package/dist/build-api.js +16 -0
- package/dist/cdn/{GridMenus-Ds3OpzZT.js → GridMenus-B7XhhqdN.js} +34 -36
- package/dist/cdn/{GridMenus-Da_p7SKS.js → GridMenus-BxmIvCJh.js} +35 -37
- package/dist/cdn/{SvDateTimePicker-DB0aIEk8.js → SvDateTimePicker-ClqtVIj3.js} +2 -1
- package/dist/cdn/{SvDateTimePicker-B8GopjhC.js → SvDateTimePicker-o94wfSUa.js} +3 -2
- package/dist/cdn/{SvGridChart-BcLCYvw9.js → SvGridChart-Cr_90pPD.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DvjwxLvK.js → SvGridChartPanel-ICbxiUvK.js} +4 -4
- package/dist/cdn/{SvGridChartView-PwqI4s02.js → SvGridChartView-Bx42PMvf.js} +2 -2
- package/dist/cdn/{SvGridDropdown-hpnHvOGt.js → SvGridDropdown-CUOdm7YA.js} +93 -88
- package/dist/cdn/{SvGridDropdown-DjALc_4f.js → SvGridDropdown-D0VdjeR8.js} +129 -123
- package/dist/cdn/{disclose-version-DoD9AFD_.js → disclose-version-DljhZohK.js} +45 -45
- package/dist/cdn/{src-DYDCiC0D.js → src-erjBRYfX.js} +5952 -5615
- package/dist/cdn/{src-BLJfdyL0.js → src-ktbQVOk-.js} +4190 -3851
- package/dist/cdn/svgrid.js +9 -9
- package/dist/cdn/svgrid.svelte-external.js +8 -8
- package/dist/cell-render.d.ts +1 -0
- package/dist/cell-render.js +115 -1
- package/dist/cell-values.js +5 -0
- package/dist/chart.d.ts +2 -0
- package/dist/chart.js +1 -4
- package/dist/core.d.ts +80 -9
- package/dist/core.js +89 -0
- package/dist/createMenu.svelte.js +0 -1
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.js +1 -1
- package/dist/editor-contract.d.ts +12 -5
- package/dist/editor-registry.d.ts +18 -7
- package/dist/editor-registry.js +10 -1
- package/dist/export-format.js +3 -0
- package/dist/group-display.d.ts +39 -0
- package/dist/group-display.js +52 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/keyboard-handlers.js +16 -0
- package/dist/recurrence.js +3 -2
- package/dist/selection.js +0 -1
- package/dist/svgrid-wrapper.types.d.ts +8 -2
- package/dist/themes/index.d.ts +10 -0
- package/dist/themes/index.js +26 -0
- package/package.json +3 -2
- package/src/DockNodeView.svelte +1 -1
- package/src/GridFooter.svelte +0 -40
- package/src/GridMenus.svelte +0 -77
- package/src/SvAccordion.test.ts +5 -5
- package/src/SvAvatar.svelte +1 -1
- package/src/SvButtonGroup.test.ts +6 -6
- package/src/SvCalendar.test.ts +1 -1
- package/src/SvCheckBox.svelte +2 -1
- package/src/SvCircularProgress.svelte +1 -1
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvFileUpload.test.ts +1 -1
- package/src/SvGrid.controller.svelte.ts +367 -22
- package/src/SvGrid.css +103 -3
- package/src/SvGrid.svelte +164 -35
- package/src/SvGrid.types.ts +81 -1
- package/src/SvGridChart.svelte +2 -10
- package/src/SvGridDropdown.svelte +38 -6
- package/src/SvGridSelect.svelte +12 -5
- package/src/SvGroupCell.svelte +9 -2
- package/src/SvKbd.svelte +1 -1
- package/src/SvList.svelte +1 -1
- package/src/SvMenubar.svelte +1 -1
- package/src/SvMultiSelect.svelte +12 -4
- package/src/SvOtpInput.svelte +1 -1
- package/src/SvPasswordInput.svelte +1 -1
- package/src/SvProgress.svelte +1 -1
- package/src/SvRating.svelte +1 -1
- package/src/SvResult.svelte +1 -1
- package/src/SvRichText.svelte +35 -16
- package/src/SvSwitchButton.svelte +2 -1
- package/src/SvToaster.svelte +1 -1
- package/src/SvTour.svelte +4 -0
- package/src/SvTree.svelte +9 -2
- package/src/SvTreeSelect.svelte +6 -4
- package/src/ai.test.ts +1 -1
- package/src/build-api.ts +15 -105
- package/src/builtin-editors.test.ts +3 -1
- package/src/cell-formatting.test.ts +1 -1
- package/src/cell-render.test.ts +27 -3
- package/src/cell-render.ts +122 -95
- package/src/cell-values.ts +4 -0
- package/src/chart-export.test.ts +2 -2
- package/src/chart.ts +1 -2
- package/src/clipboard.test.ts +0 -2
- package/src/clipboard.ts +0 -102
- package/src/columns.ts +0 -115
- package/src/core.ts +157 -8
- package/src/createMenu.svelte.ts +0 -1
- package/src/editing.test.ts +1 -1
- package/src/editing.ts +2 -105
- package/src/editor-contract.ts +12 -5
- package/src/editor-registry.grid.test.ts +48 -2
- package/src/editor-registry.test.ts +3 -1
- package/src/editor-registry.ts +23 -7
- package/src/export-format.ts +3 -0
- package/src/facet-buckets.test.ts +33 -33
- package/src/features.ts +0 -90
- package/src/group-display.test.ts +91 -1
- package/src/group-display.ts +83 -0
- package/src/index.ts +9 -0
- package/src/keyboard-handlers.coverage.test.ts +1 -1
- package/src/keyboard-handlers.ts +17 -120
- package/src/list-power.test.ts +2 -2
- package/src/menus.test.ts +4 -5
- package/src/menus.ts +0 -112
- package/src/named-views.coverage.test.ts +3 -3
- package/src/new-features.test.ts +3 -1
- package/src/recurrence.ts +3 -2
- package/src/row-drag.test.ts +1 -1
- package/src/row-resize.test.ts +41 -42
- package/src/scheduler-model.ts +1 -1
- package/src/scroll-sync.test.ts +4 -7
- package/src/scroll-sync.ts +0 -122
- package/src/selection.ts +0 -109
- package/src/server-data-source.coverage.test.ts +1 -1
- package/src/server-group-model.test.ts +8 -8
- package/src/spreadsheet.test.ts +0 -1
- package/src/summaries.ts +0 -112
- package/src/svgrid-wrapper.types.ts +11 -9
- package/src/svgrid.async-editor-options.test.ts +271 -0
- package/src/svgrid.auto-row-height.test.ts +204 -0
- package/src/svgrid.behavior.test.ts +0 -1
- package/src/svgrid.charting.test.ts +4 -4
- package/src/svgrid.column-reorder.test.ts +0 -8
- package/src/svgrid.comments-autocomplete.test.ts +8 -2
- package/src/svgrid.context-menu.test.ts +25 -9
- package/src/svgrid.filter-depth.test.ts +163 -0
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +16 -2
- package/src/svgrid.filter-menu-scroll.test.ts +10 -2
- package/src/svgrid.grand-total.test.ts +188 -0
- package/src/svgrid.group-display-mode.test.ts +171 -0
- package/src/svgrid.group-footers.test.ts +121 -0
- package/src/svgrid.group-pagination.test.ts +153 -0
- package/src/svgrid.tree-data.test.ts +186 -0
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -0
- package/src/themes/index.ts +42 -0
- package/src/tree-edit.test.ts +4 -4
- package/src/tree-row-model.test.ts +168 -0
- package/src/ui-localization.test.ts +113 -0
- package/themes/ag-alpine.css +22 -0
- package/themes/antd.css +22 -0
- package/themes/atlassian.css +22 -0
- package/themes/bootstrap.css +22 -0
- package/themes/carbon.css +22 -0
- package/themes/catppuccin.css +22 -0
- package/themes/dracula.css +22 -0
- package/themes/ember.css +22 -0
- package/themes/excel.css +22 -0
- package/themes/fluent.css +22 -0
- package/themes/github.css +22 -0
- package/themes/linear.css +22 -0
- package/themes/material.css +22 -0
- package/themes/nord.css +22 -0
- package/themes/notion.css +22 -0
- package/themes/salesforce.css +22 -0
- package/themes/sap.css +22 -0
- package/themes/shadcn.css +22 -0
- package/themes/tailwind.css +22 -0
- package/themes/vercel.css +22 -0
|
@@ -38,6 +38,10 @@ type Props = {
|
|
|
38
38
|
onCommit?: () => void;
|
|
39
39
|
/** Called when the user dismisses (Escape, blur with no change). */
|
|
40
40
|
onCancel?: () => void;
|
|
41
|
+
/** The option list is still being fetched (async `editorOptions`). Shows a
|
|
42
|
+
* loading line instead of "No options", which would read as "nothing to
|
|
43
|
+
* pick" when the truth is "not here yet". */
|
|
44
|
+
loading?: boolean;
|
|
41
45
|
};
|
|
42
46
|
declare const SvGridDropdown: import("svelte").Component<Props, {}, "">;
|
|
43
47
|
type SvGridDropdown = ReturnType<typeof SvGridDropdown>;
|
package/dist/SvGridSelect.svelte
CHANGED
|
@@ -38,6 +38,12 @@
|
|
|
38
38
|
loadOptions?: (query: string) => Promise<Row[]>
|
|
39
39
|
debounceMs?: number
|
|
40
40
|
loadingText?: string
|
|
41
|
+
/** Shown when the list is empty / a search matches nothing (localization). */
|
|
42
|
+
emptyText?: string
|
|
43
|
+
/** Placeholder in the panel's search box (localization). */
|
|
44
|
+
searchPlaceholder?: string
|
|
45
|
+
/** Accessible name for the panel's search box (localization). */
|
|
46
|
+
searchLabel?: string
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
let {
|
|
@@ -47,13 +53,14 @@
|
|
|
47
53
|
labelField,
|
|
48
54
|
value = $bindable<string | number | null>(null),
|
|
49
55
|
onChange,
|
|
50
|
-
onCommit,
|
|
51
|
-
onCancel,
|
|
52
56
|
placeholder = 'Select…',
|
|
53
57
|
searchable = true,
|
|
54
58
|
loadOptions,
|
|
55
59
|
debounceMs = 250,
|
|
56
60
|
loadingText = 'Loading…',
|
|
61
|
+
emptyText = 'No matches',
|
|
62
|
+
searchPlaceholder = 'Search…',
|
|
63
|
+
searchLabel = 'Search options',
|
|
57
64
|
disabled = false,
|
|
58
65
|
readonly = false,
|
|
59
66
|
label,
|
|
@@ -168,8 +175,8 @@
|
|
|
168
175
|
aria-expanded="true"
|
|
169
176
|
aria-controls={gs.panelId}
|
|
170
177
|
aria-activedescendant={gs.activeDescendant()}
|
|
171
|
-
aria-label=
|
|
172
|
-
placeholder=
|
|
178
|
+
aria-label={searchLabel}
|
|
179
|
+
placeholder={searchPlaceholder}
|
|
173
180
|
value={search}
|
|
174
181
|
oninput={(e) => { search = e.currentTarget.value; gs.active = 0; if (loadOptions) runLoad(search) }}
|
|
175
182
|
/>
|
|
@@ -205,7 +212,7 @@
|
|
|
205
212
|
{/each}
|
|
206
213
|
</div>
|
|
207
214
|
{:else}
|
|
208
|
-
<div class="sv-gs__empty" role="row">{loading ? loadingText :
|
|
215
|
+
<div class="sv-gs__empty" role="row">{loading ? loadingText : emptyText}</div>
|
|
209
216
|
{/each}
|
|
210
217
|
{#if loading && filtered.length}
|
|
211
218
|
<div class="sv-gs__empty" role="row" aria-live="polite">{loadingText}</div>
|
|
@@ -26,6 +26,12 @@ type Props = Pick<SvEditorProps, 'disabled' | 'readonly' | 'label' | 'hint' | 'e
|
|
|
26
26
|
loadOptions?: (query: string) => Promise<Row[]>;
|
|
27
27
|
debounceMs?: number;
|
|
28
28
|
loadingText?: string;
|
|
29
|
+
/** Shown when the list is empty / a search matches nothing (localization). */
|
|
30
|
+
emptyText?: string;
|
|
31
|
+
/** Placeholder in the panel's search box (localization). */
|
|
32
|
+
searchPlaceholder?: string;
|
|
33
|
+
/** Accessible name for the panel's search box (localization). */
|
|
34
|
+
searchLabel?: string;
|
|
29
35
|
};
|
|
30
36
|
declare const SvGridSelect: import("svelte").Component<Props, {}, "value">;
|
|
31
37
|
type SvGridSelect = ReturnType<typeof SvGridSelect>;
|
package/dist/SvGroupCell.svelte
CHANGED
|
@@ -39,7 +39,12 @@
|
|
|
39
39
|
aria-label={meta.expanded ? 'Collapse group' : 'Expand group'}
|
|
40
40
|
onclick={() => onToggle(row)}
|
|
41
41
|
>
|
|
42
|
-
|
|
42
|
+
<!-- Same chevron the grid's own expanders and SvTree draw, so every
|
|
43
|
+
expander in the library looks identical. -->
|
|
44
|
+
<span class="sv-group-chev" class:open={meta.expanded} aria-hidden="true">
|
|
45
|
+
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor"
|
|
46
|
+
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
|
47
|
+
</span>
|
|
43
48
|
<span class="sv-group-key">{meta.key}</span>
|
|
44
49
|
{#if meta.loading}<span class="sv-group-load" aria-hidden="true">…</span>{/if}
|
|
45
50
|
</button>
|
|
@@ -76,7 +81,9 @@
|
|
|
76
81
|
cursor: pointer;
|
|
77
82
|
}
|
|
78
83
|
.sv-group-chev {
|
|
79
|
-
display: inline-
|
|
84
|
+
display: inline-flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
justify-content: center;
|
|
80
87
|
transition: transform 0.15s;
|
|
81
88
|
color: var(--sg-muted, #64748b);
|
|
82
89
|
}
|
package/dist/SvKbd.svelte
CHANGED
package/dist/SvList.svelte
CHANGED
package/dist/SvMenubar.svelte
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
|
|
42
42
|
const rtl = $derived(resolvedDir === 'rtl')
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
const triggerEls = $state<(HTMLButtonElement | null)[]>([])
|
|
45
45
|
let panelEl = $state<HTMLDivElement | null>(null)
|
|
46
46
|
let openIndex = $state(-1)
|
|
47
47
|
let activeIndex = $state(0)
|
|
@@ -37,6 +37,12 @@
|
|
|
37
37
|
/** Debounce for remote search. Default 250ms. */
|
|
38
38
|
debounceMs?: number
|
|
39
39
|
loadingText?: string
|
|
40
|
+
/** Shown when the list is empty / a search matches nothing (localization). */
|
|
41
|
+
emptyText?: string
|
|
42
|
+
/** Placeholder in the panel's search box (localization). */
|
|
43
|
+
searchPlaceholder?: string
|
|
44
|
+
/** Accessible name for the panel's search box (localization). */
|
|
45
|
+
searchLabel?: string
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
let {
|
|
@@ -44,7 +50,6 @@
|
|
|
44
50
|
value = $bindable<ReadonlyArray<string | number>>([]),
|
|
45
51
|
onChange,
|
|
46
52
|
onCommit,
|
|
47
|
-
onCancel,
|
|
48
53
|
placeholder = 'Select…',
|
|
49
54
|
searchable = true,
|
|
50
55
|
maxTagCount = 3,
|
|
@@ -52,6 +57,9 @@
|
|
|
52
57
|
loadOptions,
|
|
53
58
|
debounceMs = 250,
|
|
54
59
|
loadingText = 'Loading…',
|
|
60
|
+
emptyText = 'No matches',
|
|
61
|
+
searchPlaceholder = 'Search…',
|
|
62
|
+
searchLabel = 'Search options',
|
|
55
63
|
disabled = false,
|
|
56
64
|
readonly = false,
|
|
57
65
|
label,
|
|
@@ -193,8 +201,8 @@
|
|
|
193
201
|
aria-expanded="true"
|
|
194
202
|
aria-controls={ms.panelId}
|
|
195
203
|
aria-activedescendant={ms.activeDescendant()}
|
|
196
|
-
aria-label=
|
|
197
|
-
placeholder=
|
|
204
|
+
aria-label={searchLabel}
|
|
205
|
+
placeholder={searchPlaceholder}
|
|
198
206
|
value={search}
|
|
199
207
|
oninput={(e) => { search = e.currentTarget.value; ms.active = 0; if (loadOptions) runLoad(search) }}
|
|
200
208
|
/>
|
|
@@ -224,7 +232,7 @@
|
|
|
224
232
|
<span class="sv-ms__label">{opt.label}</span>
|
|
225
233
|
</li>
|
|
226
234
|
{:else}
|
|
227
|
-
<li class="sv-ms__empty" aria-disabled="true">{loading ? loadingText :
|
|
235
|
+
<li class="sv-ms__empty" aria-disabled="true">{loading ? loadingText : emptyText}</li>
|
|
228
236
|
{/each}
|
|
229
237
|
{#if loading && filtered.length}
|
|
230
238
|
<li class="sv-ms__loading" aria-live="polite">{loadingText}</li>
|
|
@@ -25,6 +25,12 @@ type Props = Pick<SvEditorProps, 'disabled' | 'readonly' | 'label' | 'hint' | 'e
|
|
|
25
25
|
/** Debounce for remote search. Default 250ms. */
|
|
26
26
|
debounceMs?: number;
|
|
27
27
|
loadingText?: string;
|
|
28
|
+
/** Shown when the list is empty / a search matches nothing (localization). */
|
|
29
|
+
emptyText?: string;
|
|
30
|
+
/** Placeholder in the panel's search box (localization). */
|
|
31
|
+
searchPlaceholder?: string;
|
|
32
|
+
/** Accessible name for the panel's search box (localization). */
|
|
33
|
+
searchLabel?: string;
|
|
28
34
|
};
|
|
29
35
|
declare const SvMultiSelect: import("svelte").Component<Props, {}, "value">;
|
|
30
36
|
type SvMultiSelect = ReturnType<typeof SvMultiSelect>;
|
package/dist/SvOtpInput.svelte
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
const autoId = nextEditorId('sv-otp')
|
|
53
53
|
const uid = $derived(id ?? autoId)
|
|
54
54
|
const cells = $derived(otpCells(sanitizeOtp(value, length, numeric), length))
|
|
55
|
-
|
|
55
|
+
const inputs = $state<HTMLInputElement[]>([])
|
|
56
56
|
|
|
57
57
|
function commit(next: string) {
|
|
58
58
|
const clean = sanitizeOtp(next, length, numeric)
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
.sv-pw__meter { display: flex; gap: 4px; }
|
|
143
143
|
.sv-pw__bar { flex: 1; height: 4px; border-radius: 2px; background: var(--sg-border, #e2e8f0); transition: background 0.15s; }
|
|
144
144
|
.sv-pw__bar.on.is-lvl1 { background: var(--sg-danger, #dc2626); }
|
|
145
|
-
.sv-pw__bar.on.is-lvl2 { background: var(--sg-warning, #
|
|
145
|
+
.sv-pw__bar.on.is-lvl2 { background: var(--sg-warning, #d97706); }
|
|
146
146
|
.sv-pw__bar.on.is-lvl3 { background: #eab308; }
|
|
147
147
|
.sv-pw__bar.on.is-lvl4 { background: var(--sg-success, #16a34a); }
|
|
148
148
|
.sv-pw__strength { font-size: 11px; font-weight: 600; color: var(--sg-muted, #64748b); }
|
package/dist/SvProgress.svelte
CHANGED
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
display: flex; align-items: center; gap: 10px; width: 100%;
|
|
76
76
|
}
|
|
77
77
|
.sv-prog--success { --_c: var(--sg-success, #16a34a); }
|
|
78
|
-
.sv-prog--warning { --_c: var(--sg-warning, #
|
|
78
|
+
.sv-prog--warning { --_c: var(--sg-warning, #d97706); }
|
|
79
79
|
.sv-prog--danger { --_c: var(--sg-danger, #dc2626); }
|
|
80
80
|
.sv-prog__track {
|
|
81
81
|
position: relative; flex: 1; overflow: hidden;
|
package/dist/SvRating.svelte
CHANGED
package/dist/SvResult.svelte
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
.sv-result--success { --_c: var(--sg-success, #16a34a); }
|
|
61
61
|
.sv-result--error { --_c: var(--sg-danger, #dc2626); }
|
|
62
|
-
.sv-result--warning { --_c: var(--sg-warning, #
|
|
62
|
+
.sv-result--warning { --_c: var(--sg-warning, #d97706); }
|
|
63
63
|
.sv-result--notfound { --_c: var(--sg-muted, #64748b); }
|
|
64
64
|
.sv-result__icon {
|
|
65
65
|
display: grid; place-items: center; width: 60px; height: 60px; border-radius: 50%; margin-bottom: 4px;
|
package/dist/SvRichText.svelte
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* <SvRichText bind:value={html} placeholder="Write something…" />
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
import { nextEditorId } from './editor-contract'
|
|
22
|
+
import { nextEditorId, resolveMessages } from './editor-contract'
|
|
23
23
|
|
|
24
24
|
const DEFAULT_TOOLS: RichTextTool[] = [
|
|
25
25
|
'bold', 'italic', 'underline', 'strike', '|',
|
|
@@ -29,16 +29,27 @@
|
|
|
29
29
|
'link', 'clear', '|', 'undo', 'redo',
|
|
30
30
|
]
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ul:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
/** Toolbar glyphs. Mode-independent and not language-dependent, so they stay
|
|
33
|
+
* fixed; the LABELS beside them are localizable via `messages`. */
|
|
34
|
+
const GLYPH: Record<Exclude<RichTextTool, '|'>, string> = {
|
|
35
|
+
bold: 'B', italic: 'I', underline: 'U', strike: 'S',
|
|
36
|
+
h1: 'H1', h2: 'H2', h3: 'H3', p: '¶',
|
|
37
|
+
ul: '•', ol: '1.', quote: '❝', code: '</>',
|
|
38
|
+
alignLeft: '≡', alignCenter: '≣', alignRight: '≡', link: '🔗',
|
|
39
|
+
clear: '⌫', undo: '↶', redo: '↷',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** User-facing strings (localizable via `messages`). Every one is a tooltip +
|
|
43
|
+
* accessible name on a toolbar button, so leaving them hard-coded made the
|
|
44
|
+
* whole toolbar English-only for screen-reader users. */
|
|
45
|
+
type RichTextMessages = Record<Exclude<RichTextTool, '|'>, string> & { linkPrompt: string; toolbar: string }
|
|
46
|
+
const DEFAULT_MESSAGES: RichTextMessages = {
|
|
47
|
+
bold: 'Bold', italic: 'Italic', underline: 'Underline', strike: 'Strikethrough',
|
|
48
|
+
h1: 'Heading 1', h2: 'Heading 2', h3: 'Heading 3', p: 'Paragraph',
|
|
49
|
+
ul: 'Bullet list', ol: 'Numbered list', quote: 'Quote', code: 'Code block',
|
|
50
|
+
alignLeft: 'Align left', alignCenter: 'Align center', alignRight: 'Align right',
|
|
51
|
+
link: 'Link', clear: 'Clear formatting', undo: 'Undo', redo: 'Redo',
|
|
52
|
+
linkPrompt: 'Link URL', toolbar: 'Formatting',
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
type Props = {
|
|
@@ -52,6 +63,8 @@
|
|
|
52
63
|
/** Toolbar tools + order; use '|' for a separator. */
|
|
53
64
|
tools?: RichTextTool[]
|
|
54
65
|
ariaLabel?: string
|
|
66
|
+
/** Override any toolbar label / prompt (localization). */
|
|
67
|
+
messages?: Partial<RichTextMessages>
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
let {
|
|
@@ -63,8 +76,10 @@
|
|
|
63
76
|
minHeight = '160px',
|
|
64
77
|
tools = DEFAULT_TOOLS,
|
|
65
78
|
ariaLabel = 'Rich text editor',
|
|
79
|
+
messages,
|
|
66
80
|
}: Props = $props()
|
|
67
81
|
|
|
82
|
+
const M = $derived(resolveMessages(DEFAULT_MESSAGES, messages))
|
|
68
83
|
const uid = nextEditorId('sv-rt')
|
|
69
84
|
let editorEl = $state<HTMLDivElement | null>(null)
|
|
70
85
|
let focused = $state(false)
|
|
@@ -75,6 +90,10 @@
|
|
|
75
90
|
// (writing innerHTML while focused would collapse the caret).
|
|
76
91
|
$effect(() => {
|
|
77
92
|
const v = value ?? ''
|
|
93
|
+
// A contenteditable owns its own DOM: letting Svelte re-render the body
|
|
94
|
+
// would wipe the caret mid-typing, so the value is pushed in by hand and
|
|
95
|
+
// only while the editor is unfocused.
|
|
96
|
+
// eslint-disable-next-line svelte/no-dom-manipulating
|
|
78
97
|
if (editorEl && !focused && editorEl.innerHTML !== v) editorEl.innerHTML = v
|
|
79
98
|
if (editorEl) empty = editorEl.textContent!.trim().length === 0
|
|
80
99
|
})
|
|
@@ -109,14 +128,14 @@
|
|
|
109
128
|
ul: () => exec('insertUnorderedList'), ol: () => exec('insertOrderedList'),
|
|
110
129
|
quote: () => exec('formatBlock', 'BLOCKQUOTE'), code: () => exec('formatBlock', 'PRE'),
|
|
111
130
|
alignLeft: () => exec('justifyLeft'), alignCenter: () => exec('justifyCenter'), alignRight: () => exec('justifyRight'),
|
|
112
|
-
link: () => { const url = typeof prompt === 'function' ? prompt(
|
|
131
|
+
link: () => { const url = typeof prompt === 'function' ? prompt(M.linkPrompt, 'https://') : null; if (url) exec('createLink', url) },
|
|
113
132
|
clear: () => exec('removeFormat'), undo: () => exec('undo'), redo: () => exec('redo'),
|
|
114
133
|
}
|
|
115
134
|
</script>
|
|
116
135
|
|
|
117
136
|
<div class="sv-rt" class:is-disabled={disabled} class:is-readonly={readonly}>
|
|
118
137
|
{#if !readonly}
|
|
119
|
-
<div class="sv-rt__toolbar" role="toolbar" aria-label=
|
|
138
|
+
<div class="sv-rt__toolbar" role="toolbar" aria-label={M.toolbar} aria-controls={uid}>
|
|
120
139
|
{#each tools as t, i (i)}
|
|
121
140
|
{#if t === '|'}
|
|
122
141
|
<span class="sv-rt__sep" aria-hidden="true"></span>
|
|
@@ -126,13 +145,13 @@
|
|
|
126
145
|
class="sv-rt__btn"
|
|
127
146
|
class:is-on={activeState[t]}
|
|
128
147
|
data-tool={t}
|
|
129
|
-
title={
|
|
130
|
-
aria-label={
|
|
148
|
+
title={M[t]}
|
|
149
|
+
aria-label={M[t]}
|
|
131
150
|
aria-pressed={activeState[t] ? true : undefined}
|
|
132
151
|
{disabled}
|
|
133
152
|
onmousedown={(e) => e.preventDefault()}
|
|
134
153
|
onclick={run[t]}
|
|
135
|
-
>{
|
|
154
|
+
>{GLYPH[t]}</button>
|
|
136
155
|
{/if}
|
|
137
156
|
{/each}
|
|
138
157
|
</div>
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export type RichTextTool = 'bold' | 'italic' | 'underline' | 'strike' | 'h1' | 'h2' | 'h3' | 'p' | 'ul' | 'ol' | 'quote' | 'code' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'link' | 'clear' | 'undo' | 'redo' | '|';
|
|
2
|
+
/** User-facing strings (localizable via `messages`). Every one is a tooltip +
|
|
3
|
+
* accessible name on a toolbar button, so leaving them hard-coded made the
|
|
4
|
+
* whole toolbar English-only for screen-reader users. */
|
|
5
|
+
type RichTextMessages = Record<Exclude<RichTextTool, '|'>, string> & {
|
|
6
|
+
linkPrompt: string;
|
|
7
|
+
toolbar: string;
|
|
8
|
+
};
|
|
2
9
|
type Props = {
|
|
3
10
|
/** HTML string (bindable). */
|
|
4
11
|
value?: string;
|
|
@@ -10,6 +17,8 @@ type Props = {
|
|
|
10
17
|
/** Toolbar tools + order; use '|' for a separator. */
|
|
11
18
|
tools?: RichTextTool[];
|
|
12
19
|
ariaLabel?: string;
|
|
20
|
+
/** Override any toolbar label / prompt (localization). */
|
|
21
|
+
messages?: Partial<RichTextMessages>;
|
|
13
22
|
};
|
|
14
23
|
declare const SvRichText: import("svelte").Component<Props, {}, "value">;
|
|
15
24
|
type SvRichText = ReturnType<typeof SvRichText>;
|
|
@@ -53,8 +53,9 @@
|
|
|
53
53
|
</script>
|
|
54
54
|
|
|
55
55
|
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
56
|
+
<!-- role="switch" arrives via switchProps(), which the a11y pass cannot see through -->
|
|
57
|
+
<!-- svelte-ignore a11y_role_supports_aria_props_implicit -->
|
|
56
58
|
<button
|
|
57
|
-
role="switch"
|
|
58
59
|
class="sv-switch sv-switch--{size}"
|
|
59
60
|
class:is-on={checked}
|
|
60
61
|
class:is-readonly={readonly}
|
package/dist/SvToaster.svelte
CHANGED
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
|
|
136
136
|
:global(.sv-toast--success) { --_accent: var(--sg-success, #16a34a); }
|
|
137
137
|
:global(.sv-toast--error) { --_accent: var(--sg-danger, #dc2626); }
|
|
138
|
-
:global(.sv-toast--warning) { --_accent: var(--sg-warning, #
|
|
138
|
+
:global(.sv-toast--warning) { --_accent: var(--sg-warning, #d97706); }
|
|
139
139
|
:global(.sv-toast--info) { --_accent: var(--sg-accent, #2563eb); }
|
|
140
140
|
|
|
141
141
|
:global(.sv-toast__icon) { flex: none; width: 18px; height: 18px; display: grid; place-items: center; color: var(--_accent); font-weight: 700; }
|
package/dist/SvTour.svelte
CHANGED
|
@@ -156,6 +156,10 @@
|
|
|
156
156
|
>
|
|
157
157
|
{#if step.image}
|
|
158
158
|
<div class="sv-tour__media">
|
|
159
|
+
<!-- A step's `image` is either a URL (rendered as an img above) or
|
|
160
|
+
inline SVG markup the app author wrote for its own tour - never
|
|
161
|
+
user input, so rendering it as markup is the intent. -->
|
|
162
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
159
163
|
{#if /^(https?:|data:|\/)/.test(step.image)}<img class="sv-tour__img" src={step.image} alt="" />{:else}{@html step.image}{/if}
|
|
160
164
|
</div>
|
|
161
165
|
{/if}
|
package/dist/SvTree.svelte
CHANGED
|
@@ -48,6 +48,11 @@
|
|
|
48
48
|
/** Render a built-in search box that drives the filter. */
|
|
49
49
|
searchable?: boolean
|
|
50
50
|
searchPlaceholder?: string
|
|
51
|
+
/** Placeholder row shown under a lazy node while its children load
|
|
52
|
+
* (localization). */
|
|
53
|
+
loadingText?: string
|
|
54
|
+
/** Accessible name for the search box's clear button (localization). */
|
|
55
|
+
clearSearchLabel?: string
|
|
51
56
|
/** Sort siblings by label ('asc'/'desc') or a custom comparator. */
|
|
52
57
|
sort?: 'asc' | 'desc' | ((a: TreeNode, b: TreeNode) => number)
|
|
53
58
|
/** Allow inline rename (double-click or F2). Fires onRename. */
|
|
@@ -77,6 +82,8 @@
|
|
|
77
82
|
filter,
|
|
78
83
|
searchable = false,
|
|
79
84
|
searchPlaceholder = 'Search...',
|
|
85
|
+
loadingText = 'Loading...',
|
|
86
|
+
clearSearchLabel = 'Clear search',
|
|
80
87
|
sort,
|
|
81
88
|
editable = false,
|
|
82
89
|
onRename,
|
|
@@ -173,7 +180,7 @@
|
|
|
173
180
|
if (!node?.lazy || loaded.has(id) || loadingIds.has(id)) return
|
|
174
181
|
loadingIds = new Set(loadingIds).add(id)
|
|
175
182
|
// A placeholder row shows immediately while the promise resolves.
|
|
176
|
-
loaded = new Map(loaded).set(id, [{ id: `${id}__loading`, label:
|
|
183
|
+
loaded = new Map(loaded).set(id, [{ id: `${id}__loading`, label: loadingText, disabled: true }])
|
|
177
184
|
try {
|
|
178
185
|
const kids = await loadChildren(node)
|
|
179
186
|
loaded = new Map(loaded).set(id, kids)
|
|
@@ -268,7 +275,7 @@
|
|
|
268
275
|
<div class="sv-tree__search">
|
|
269
276
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" /></svg>
|
|
270
277
|
<input class="sv-tree__search-input" type="text" placeholder={searchPlaceholder} bind:value={query} aria-label={searchPlaceholder} />
|
|
271
|
-
{#if query}<button type="button" class="sv-tree__search-clear" aria-label=
|
|
278
|
+
{#if query}<button type="button" class="sv-tree__search-clear" aria-label={clearSearchLabel} onclick={() => (query = '')}>×</button>{/if}
|
|
272
279
|
</div>
|
|
273
280
|
{/if}
|
|
274
281
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
package/dist/SvTree.svelte.d.ts
CHANGED
|
@@ -30,6 +30,11 @@ type Props = {
|
|
|
30
30
|
/** Render a built-in search box that drives the filter. */
|
|
31
31
|
searchable?: boolean;
|
|
32
32
|
searchPlaceholder?: string;
|
|
33
|
+
/** Placeholder row shown under a lazy node while its children load
|
|
34
|
+
* (localization). */
|
|
35
|
+
loadingText?: string;
|
|
36
|
+
/** Accessible name for the search box's clear button (localization). */
|
|
37
|
+
clearSearchLabel?: string;
|
|
33
38
|
/** Sort siblings by label ('asc'/'desc') or a custom comparator. */
|
|
34
39
|
sort?: 'asc' | 'desc' | ((a: TreeNode, b: TreeNode) => number);
|
|
35
40
|
/** Allow inline rename (double-click or F2). Fires onRename. */
|
package/dist/SvTreeSelect.svelte
CHANGED
|
@@ -36,8 +36,6 @@
|
|
|
36
36
|
nodes,
|
|
37
37
|
value = $bindable<string | number | null>(null),
|
|
38
38
|
onChange,
|
|
39
|
-
onCommit,
|
|
40
|
-
onCancel,
|
|
41
39
|
placeholder = 'Select…',
|
|
42
40
|
showPath = false,
|
|
43
41
|
expandedIds = [],
|
|
@@ -186,7 +184,11 @@
|
|
|
186
184
|
{...ts.itemProps(i)}
|
|
187
185
|
>
|
|
188
186
|
{#if row.hasChildren}
|
|
189
|
-
|
|
187
|
+
<!-- Same chevron as SvTree and the grid's expanders. -->
|
|
188
|
+
<span class="sv-ts__chev" class:is-open={row.expanded} aria-hidden="true">
|
|
189
|
+
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor"
|
|
190
|
+
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
|
191
|
+
</span>
|
|
190
192
|
{:else}
|
|
191
193
|
<span class="sv-ts__chev sv-ts__chev--leaf" aria-hidden="true"></span>
|
|
192
194
|
{/if}
|
|
@@ -226,7 +228,7 @@
|
|
|
226
228
|
:global(.sv-ts__row.is-active) { background: var(--sg-row-hover-bg, #f1f5f9); }
|
|
227
229
|
:global(.sv-ts__row.is-selected) { color: var(--sg-accent, #2563eb); font-weight: 600; }
|
|
228
230
|
:global(.sv-ts__row.is-disabled) { opacity: 0.5; cursor: not-allowed; }
|
|
229
|
-
:global(.sv-ts__chev) { width: 14px; flex: none; color: var(--sg-muted, #64748b); transition: transform 0.12s;
|
|
231
|
+
:global(.sv-ts__chev) { width: 14px; flex: none; display: inline-flex; align-items: center; justify-content: center; color: var(--sg-muted, #64748b); transition: transform 0.12s; }
|
|
230
232
|
:global(.sv-ts__chev.is-open) { transform: rotate(90deg); }
|
|
231
233
|
:global(.sv-ts__chev--leaf) { visibility: hidden; }
|
|
232
234
|
:global(.sv-ts__label) { flex: 1; white-space: nowrap; }
|
package/dist/build-api.js
CHANGED
|
@@ -315,6 +315,22 @@ export function createGridApi(ctx) {
|
|
|
315
315
|
clearFilter(columnId) {
|
|
316
316
|
ctx.clearColumnFilter(columnId);
|
|
317
317
|
},
|
|
318
|
+
refreshEditorOptions(columnId) {
|
|
319
|
+
// Async `editorOptions` are cached per column (or per column+row for a
|
|
320
|
+
// per-row source) so opening an editor twice does not refetch. This
|
|
321
|
+
// drops those entries, so the next render re-runs the source - use it
|
|
322
|
+
// when the underlying list changed server-side.
|
|
323
|
+
if (columnId == null) {
|
|
324
|
+
ctx.asyncEditorOptions = {};
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const next = { ...ctx.asyncEditorOptions };
|
|
328
|
+
for (const key of Object.keys(next)) {
|
|
329
|
+
if (key === columnId || key.startsWith(`${columnId}::`))
|
|
330
|
+
delete next[key];
|
|
331
|
+
}
|
|
332
|
+
ctx.asyncEditorOptions = next;
|
|
333
|
+
},
|
|
318
334
|
clearAllFilters() {
|
|
319
335
|
// See `setFilter` for why the read-modify-write is untracked.
|
|
320
336
|
untrack(() => {
|