@svgrid/grid 1.2.22 → 1.2.23
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/GridFooter.svelte +34 -20
- package/dist/GridFooter.svelte.d.ts +8 -0
- package/dist/SvAutoComplete.svelte +100 -0
- package/dist/SvAutoComplete.svelte.d.ts +16 -0
- package/dist/SvButton.svelte +97 -0
- package/dist/SvButton.svelte.d.ts +23 -0
- package/dist/SvCalendar.svelte +669 -0
- package/dist/SvCalendar.svelte.d.ts +70 -0
- package/dist/SvCheckBox.svelte +81 -0
- package/dist/SvCheckBox.svelte.d.ts +19 -0
- package/dist/SvColorInput.svelte +187 -0
- package/dist/SvColorInput.svelte.d.ts +15 -0
- package/dist/SvComboBox.svelte +130 -0
- package/dist/SvComboBox.svelte.d.ts +15 -0
- package/dist/SvCountryInput.svelte +123 -0
- package/dist/SvCountryInput.svelte.d.ts +14 -0
- package/dist/SvDateTimePicker.svelte +322 -0
- package/dist/SvDateTimePicker.svelte.d.ts +37 -0
- package/dist/SvDropDownList.svelte +143 -0
- package/dist/SvDropDownList.svelte.d.ts +15 -0
- package/dist/SvForm.svelte +147 -0
- package/dist/SvForm.svelte.d.ts +29 -0
- package/dist/SvGauge.svelte +99 -0
- package/dist/SvGauge.svelte.d.ts +31 -0
- package/dist/SvGrid.controller.svelte.d.ts +16 -2
- package/dist/SvGrid.controller.svelte.js +63 -8
- package/dist/SvGrid.css +84 -31
- package/dist/SvGrid.helpers.d.ts +4 -0
- package/dist/SvGrid.helpers.js +40 -12
- package/dist/SvGrid.svelte +84 -5
- package/dist/SvGrid.types.d.ts +36 -0
- package/dist/SvGridDropdown.svelte +16 -30
- package/dist/SvGridDropdown.svelte.d.ts +4 -0
- package/dist/SvListBox.svelte +122 -0
- package/dist/SvListBox.svelte.d.ts +19 -0
- package/dist/SvMaskedInput.svelte +0 -0
- package/dist/SvMaskedInput.svelte.d.ts +15 -0
- package/dist/SvNumberInput.svelte +0 -0
- package/dist/SvNumberInput.svelte.d.ts +22 -0
- package/dist/SvPasswordInput.svelte +110 -0
- package/dist/SvPasswordInput.svelte.d.ts +22 -0
- package/dist/SvPhoneInput.svelte +0 -0
- package/dist/SvPhoneInput.svelte.d.ts +19 -0
- package/dist/SvRadioGroup.svelte +108 -0
- package/dist/SvRadioGroup.svelte.d.ts +23 -0
- package/dist/SvRating.svelte +111 -0
- package/dist/SvRating.svelte.d.ts +20 -0
- package/dist/SvRepeatButton.svelte +89 -0
- package/dist/SvRepeatButton.svelte.d.ts +21 -0
- package/dist/SvSlider.svelte +182 -0
- package/dist/SvSlider.svelte.d.ts +25 -0
- package/dist/SvSwitchButton.svelte +94 -0
- package/dist/SvSwitchButton.svelte.d.ts +19 -0
- package/dist/SvTabs.svelte +122 -0
- package/dist/SvTabs.svelte.d.ts +26 -0
- package/dist/SvTagsInput.svelte +81 -0
- package/dist/SvTagsInput.svelte.d.ts +19 -0
- package/dist/SvTimePicker.svelte +359 -0
- package/dist/SvTimePicker.svelte.d.ts +27 -0
- package/dist/SvToggleButton.svelte +57 -0
- package/dist/SvToggleButton.svelte.d.ts +16 -0
- package/dist/SvTree.svelte +210 -0
- package/dist/SvTree.svelte.d.ts +29 -0
- package/dist/build-api.js +78 -1
- package/dist/cdn/svgrid.js +10022 -5904
- package/dist/cdn/svgrid.svelte-external.js +8531 -4429
- package/dist/cell-render.d.ts +4 -0
- package/dist/cell-render.js +35 -1
- package/dist/clipboard.js +19 -1
- package/dist/collaboration.d.ts +2 -0
- package/dist/collaboration.js +4 -0
- package/dist/core.d.ts +22 -1
- package/dist/countries.d.ts +15 -0
- package/dist/countries.js +61 -0
- package/dist/datetime/date-core.d.ts +88 -0
- package/dist/datetime/date-core.js +165 -0
- package/dist/datetime/date-format.d.ts +39 -0
- package/dist/datetime/date-format.js +371 -0
- package/dist/datetime/date-restrict.d.ts +23 -0
- package/dist/datetime/date-restrict.js +50 -0
- package/dist/datetime/date-selection.d.ts +25 -0
- package/dist/datetime/date-selection.js +94 -0
- package/dist/datetime/mask.d.ts +21 -0
- package/dist/datetime/mask.js +90 -0
- package/dist/editing.d.ts +1 -1
- package/dist/editing.js +7 -0
- package/dist/editors/cell-editors.d.ts +2 -2
- package/dist/editors/cell-editors.js +3 -1
- package/dist/export-format.d.ts +178 -0
- package/dist/export-format.js +419 -0
- package/dist/hyperformula-adapter.d.ts +4 -0
- package/dist/hyperformula-adapter.js +15 -6
- package/dist/index.d.ts +36 -0
- package/dist/index.js +41 -0
- package/dist/list-option.d.ts +10 -0
- package/dist/list-option.js +7 -0
- package/dist/menus.js +18 -2
- package/dist/popover.d.ts +48 -0
- package/dist/popover.js +65 -0
- package/dist/row-resize.d.ts +4 -4
- package/dist/row-resize.js +8 -5
- package/dist/selection.d.ts +6 -0
- package/dist/selection.js +31 -5
- package/dist/server-data-source.d.ts +43 -0
- package/dist/server-data-source.js +100 -0
- package/dist/sparkline.js +8 -4
- package/dist/svgrid-wrapper.types.d.ts +26 -1
- package/package.json +6 -1
- package/src/GridFooter.svelte +34 -20
- package/src/SvAutoComplete.svelte +100 -0
- package/src/SvButton.svelte +97 -0
- package/src/SvCalendar.svelte +669 -0
- package/src/SvCalendar.test.ts +211 -0
- package/src/SvCheckBox.svelte +81 -0
- package/src/SvColorInput.svelte +187 -0
- package/src/SvComboBox.svelte +130 -0
- package/src/SvCountryInput.svelte +123 -0
- package/src/SvDateTimePicker.svelte +322 -0
- package/src/SvDateTimePicker.test.ts +136 -0
- package/src/SvDropDownList.svelte +143 -0
- package/src/SvForm.svelte +147 -0
- package/src/SvGauge.svelte +99 -0
- package/src/SvGrid.controller.svelte.ts +74 -8
- package/src/SvGrid.css +84 -31
- package/src/SvGrid.helpers.ts +41 -12
- package/src/SvGrid.svelte +84 -5
- package/src/SvGrid.types.ts +33 -0
- package/src/SvGridDropdown.svelte +16 -30
- package/src/SvListBox.svelte +122 -0
- package/src/SvMaskedInput.svelte +0 -0
- package/src/SvNumberInput.svelte +0 -0
- package/src/SvPasswordInput.svelte +110 -0
- package/src/SvPhoneInput.svelte +0 -0
- package/src/SvRadioGroup.svelte +108 -0
- package/src/SvRating.svelte +111 -0
- package/src/SvRepeatButton.svelte +89 -0
- package/src/SvSlider.svelte +182 -0
- package/src/SvSwitchButton.svelte +94 -0
- package/src/SvTabs.svelte +122 -0
- package/src/SvTagsInput.svelte +81 -0
- package/src/SvTimePicker.svelte +359 -0
- package/src/SvTimePicker.test.ts +98 -0
- package/src/SvToggleButton.svelte +57 -0
- package/src/SvTree.svelte +210 -0
- package/src/build-api.ts +115 -0
- package/src/cell-render.test.ts +38 -0
- package/src/cell-render.ts +43 -0
- package/src/clipboard.test.ts +18 -0
- package/src/clipboard.ts +24 -1
- package/src/collaboration.test.ts +30 -0
- package/src/collaboration.ts +6 -0
- package/src/core.ts +27 -3
- package/src/countries.ts +71 -0
- package/src/datetime/date-core.test.ts +217 -0
- package/src/datetime/date-core.ts +204 -0
- package/src/datetime/date-format.test.ts +121 -0
- package/src/datetime/date-format.ts +317 -0
- package/src/datetime/date-restrict.ts +60 -0
- package/src/datetime/date-selection.test.ts +129 -0
- package/src/datetime/date-selection.ts +137 -0
- package/src/datetime/mask.test.ts +36 -0
- package/src/datetime/mask.ts +84 -0
- package/src/editing.test.ts +22 -0
- package/src/editing.ts +7 -1
- package/src/editors/cell-editors.ts +7 -1
- package/src/export-data-api.test.ts +126 -0
- package/src/export-format.test.ts +107 -0
- package/src/export-format.ts +598 -0
- package/src/hyperformula-adapter.test.ts +35 -1
- package/src/hyperformula-adapter.ts +18 -6
- package/src/index.ts +71 -0
- package/src/list-option.ts +15 -0
- package/src/menus.ts +21 -2
- package/src/popover.ts +79 -0
- package/src/row-resize.test.ts +25 -0
- package/src/row-resize.ts +8 -5
- package/src/selection.test.ts +36 -4
- package/src/selection.ts +30 -9
- package/src/server-data-source.test.ts +201 -0
- package/src/server-data-source.ts +148 -0
- package/src/sparkline.test.ts +9 -0
- package/src/sparkline.ts +9 -4
- package/src/svgrid-wrapper.types.ts +28 -1
- package/src/svgrid.behavior.test.ts +27 -0
- package/src/svgrid.new-features.wrapper.test.ts +27 -1
- package/src/ui-buttons.test.ts +105 -0
- package/src/ui-composite.test.ts +89 -0
- package/src/ui-inputs.test.ts +92 -0
- package/src/ui-range.test.ts +61 -0
- package/src/ui-selection.test.ts +106 -0
package/dist/GridFooter.svelte
CHANGED
|
@@ -47,7 +47,23 @@
|
|
|
47
47
|
} from "./SvGrid.controller.svelte";
|
|
48
48
|
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
49
49
|
|
|
50
|
-
let {
|
|
50
|
+
let {
|
|
51
|
+
ctrl,
|
|
52
|
+
pager = true,
|
|
53
|
+
showStatus = true,
|
|
54
|
+
top = false,
|
|
55
|
+
pageSizeOptions,
|
|
56
|
+
}: {
|
|
57
|
+
ctrl: SvGridController<TFeatures, TData>;
|
|
58
|
+
/** Render the pagination controls in this instance. */
|
|
59
|
+
pager?: boolean;
|
|
60
|
+
/** Render the status bar in this instance. */
|
|
61
|
+
showStatus?: boolean;
|
|
62
|
+
/** This is a top-positioned footer (styling only). */
|
|
63
|
+
top?: boolean;
|
|
64
|
+
/** Page-size choices for the selector. Defaults to `[10, 25, 50, 100]`. */
|
|
65
|
+
pageSizeOptions?: number[];
|
|
66
|
+
} = $props();
|
|
51
67
|
|
|
52
68
|
// View facade: re-bind the controller's reactive members so the markup
|
|
53
69
|
// (moved verbatim from SvGrid.svelte) stays identical.
|
|
@@ -63,7 +79,7 @@
|
|
|
63
79
|
const setPageSize = $derived(ctrl.setPageSize);
|
|
64
80
|
</script>
|
|
65
81
|
|
|
66
|
-
{#if statusBarEnabled && statusBarStats}
|
|
82
|
+
{#if showStatus && statusBarEnabled && statusBarStats}
|
|
67
83
|
{@const s = statusBarStats}
|
|
68
84
|
<div class="sv-grid-status-bar" role="status" aria-live="polite">
|
|
69
85
|
{#each statusBarAggregates as agg (agg)}
|
|
@@ -96,31 +112,29 @@
|
|
|
96
112
|
</div>
|
|
97
113
|
{/if}
|
|
98
114
|
|
|
99
|
-
{#if paginationEnabled}
|
|
100
|
-
{@const totalRows =
|
|
101
|
-
{@const pageSize =
|
|
115
|
+
{#if pager && paginationEnabled}
|
|
116
|
+
{@const totalRows = ctrl.paginationTotalRows}
|
|
117
|
+
{@const pageSize = ctrl.paginationPageSize}
|
|
102
118
|
{@const pageCount = Math.max(1, Math.ceil(totalRows / pageSize))}
|
|
103
|
-
{@const currentPage = Math.min(
|
|
119
|
+
{@const currentPage = Math.min(ctrl.paginationPageIndex + 1, pageCount)}
|
|
104
120
|
{@const rangeStart =
|
|
105
121
|
totalRows === 0 ? 0 : (currentPage - 1) * pageSize + 1}
|
|
106
122
|
{@const rangeEnd = Math.min(totalRows, currentPage * pageSize)}
|
|
107
123
|
{@const onFirst = currentPage <= 1}
|
|
108
124
|
{@const onLast = currentPage >= pageCount}
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
{@const sizes = pageSizeOptions && pageSizeOptions.length ? pageSizeOptions : [10, 25, 50, 100]}
|
|
126
|
+
<div class="sv-grid-pagination" class:sv-grid-pagination-top={top} role="navigation" aria-label="Pagination">
|
|
127
|
+
<div class="sv-grid-pagination-pagesize">
|
|
111
128
|
<span>Page Size:</span>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<option value="100" selected={pageSize === 100}>100</option>
|
|
122
|
-
</select>
|
|
123
|
-
</label>
|
|
129
|
+
<div class="sv-grid-pagination-pagesize-dd">
|
|
130
|
+
<SvGridDropdown
|
|
131
|
+
options={sizes.map((n) => ({ value: n, label: String(n) }))}
|
|
132
|
+
value={pageSize}
|
|
133
|
+
autoOpen={false}
|
|
134
|
+
onChange={(v) => setPageSize(Number(v))}
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
124
138
|
<span class="sv-grid-pagination-range">
|
|
125
139
|
<strong>{rangeStart.toLocaleString()}</strong> to
|
|
126
140
|
<strong>{rangeEnd.toLocaleString()}</strong> of
|
|
@@ -4,6 +4,14 @@ import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
|
4
4
|
declare function $$render<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(): {
|
|
5
5
|
props: {
|
|
6
6
|
ctrl: SvGridController<TFeatures, TData>;
|
|
7
|
+
/** Render the pagination controls in this instance. */
|
|
8
|
+
pager?: boolean;
|
|
9
|
+
/** Render the status bar in this instance. */
|
|
10
|
+
showStatus?: boolean;
|
|
11
|
+
/** This is a top-positioned footer (styling only). */
|
|
12
|
+
top?: boolean;
|
|
13
|
+
/** Page-size choices for the selector. Defaults to `[10, 25, 50, 100]`. */
|
|
14
|
+
pageSizeOptions?: number[];
|
|
7
15
|
};
|
|
8
16
|
exports: {};
|
|
9
17
|
bindings: "";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvAutoComplete - a free-text input with a live-filtered suggestion list.
|
|
4
|
+
* Unlike SvComboBox, it accepts ANY value (the text), suggestions are just
|
|
5
|
+
* shortcuts. Parity: Smart `smart-input` (autocomplete). Emits the text string.
|
|
6
|
+
*/
|
|
7
|
+
import { anchoredRect, portalToBody, type AnchoredRect } from './popover'
|
|
8
|
+
import { filterOptions, type ListOption } from './list-option'
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
value?: string
|
|
12
|
+
onChange?: (value: string) => void
|
|
13
|
+
/** Suggestions - strings or {value,label} (label is shown, value inserted). */
|
|
14
|
+
suggestions?: ReadonlyArray<string | ListOption>
|
|
15
|
+
minChars?: number
|
|
16
|
+
placeholder?: string
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
name?: string
|
|
19
|
+
size?: 'sm' | 'md' | 'lg'
|
|
20
|
+
ariaLabel?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let { value = '', onChange, suggestions = [], minChars = 1, placeholder, disabled = false, name, size = 'md', ariaLabel }: Props = $props()
|
|
24
|
+
|
|
25
|
+
const asOptions = $derived<ListOption[]>(
|
|
26
|
+
suggestions.map((s) => (typeof s === 'string' ? { value: s, label: s } : s)),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
let open = $state(false)
|
|
30
|
+
let active = $state(0)
|
|
31
|
+
let fieldEl = $state<HTMLInputElement | null>(null)
|
|
32
|
+
let panelEl = $state<HTMLDivElement | null>(null)
|
|
33
|
+
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
|
|
34
|
+
|
|
35
|
+
const filtered = $derived(value.length >= minChars ? filterOptions(asOptions, value).slice(0, 10) : [])
|
|
36
|
+
|
|
37
|
+
function updatePos() {
|
|
38
|
+
if (!fieldEl) return
|
|
39
|
+
rect = anchoredRect(fieldEl.getBoundingClientRect(), { estimatedHeight: Math.min(filtered.length, 8) * 34 + 8 })
|
|
40
|
+
}
|
|
41
|
+
function maybeOpen() { open = filtered.length > 0; if (open) { active = 0; updatePos() } }
|
|
42
|
+
function pick(o: ListOption | undefined) { if (!o) return; onChange?.(String(o.value)); open = false; fieldEl?.focus() }
|
|
43
|
+
function onInput(e: Event) { onChange?.((e.currentTarget as HTMLInputElement).value); queueMicrotask(maybeOpen) }
|
|
44
|
+
function onKeydown(e: KeyboardEvent) {
|
|
45
|
+
if (!open) { if (e.key === 'ArrowDown') { queueMicrotask(maybeOpen) } return }
|
|
46
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); active = Math.min(active + 1, filtered.length - 1) }
|
|
47
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); active = Math.max(active - 1, 0) }
|
|
48
|
+
else if (e.key === 'Enter') { e.preventDefault(); pick(filtered[active]) }
|
|
49
|
+
else if (e.key === 'Escape') { open = false }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
$effect(() => {
|
|
53
|
+
if (!open) return
|
|
54
|
+
const rp = () => updatePos()
|
|
55
|
+
window.addEventListener('scroll', rp, true); window.addEventListener('resize', rp)
|
|
56
|
+
const od = (e: PointerEvent) => { const t = e.target as Node | null; if (t && (fieldEl?.contains(t) || panelEl?.contains(t))) return; open = false }
|
|
57
|
+
document.addEventListener('pointerdown', od, true)
|
|
58
|
+
return () => { window.removeEventListener('scroll', rp, true); window.removeEventListener('resize', rp); document.removeEventListener('pointerdown', od, true) }
|
|
59
|
+
})
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<input
|
|
63
|
+
bind:this={fieldEl}
|
|
64
|
+
class="sv-ac sv-ac--{size}"
|
|
65
|
+
type="text"
|
|
66
|
+
role="combobox"
|
|
67
|
+
aria-expanded={open}
|
|
68
|
+
aria-autocomplete="list"
|
|
69
|
+
value={value}
|
|
70
|
+
{placeholder}
|
|
71
|
+
{disabled}
|
|
72
|
+
aria-label={ariaLabel}
|
|
73
|
+
oninput={onInput}
|
|
74
|
+
onfocus={maybeOpen}
|
|
75
|
+
onkeydown={onKeydown}
|
|
76
|
+
/>
|
|
77
|
+
|
|
78
|
+
{#if open}
|
|
79
|
+
<div bind:this={panelEl} class="sv-ddl__panel" use:portalToBody style:position="fixed" style:top={`${rect.top}px`} style:left={`${rect.left}px`} style:min-width={`${rect.width}px`} role="listbox">
|
|
80
|
+
{#each filtered as opt, i (opt.value)}
|
|
81
|
+
<!-- svelte-ignore a11y_click_events_have_key_events a11y_interactive_supports_focus -->
|
|
82
|
+
<div class="sv-ddl__opt" class:is-active={i === active} role="option" tabindex="-1" aria-selected={i === active} data-idx={i} onclick={() => pick(opt)} onpointermove={() => (active = i)}>{opt.label}</div>
|
|
83
|
+
{/each}
|
|
84
|
+
</div>
|
|
85
|
+
{/if}
|
|
86
|
+
{#if name}<input type="hidden" {name} value={value} />{/if}
|
|
87
|
+
|
|
88
|
+
<style>
|
|
89
|
+
.sv-ac {
|
|
90
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
91
|
+
box-sizing: border-box; width: 220px;
|
|
92
|
+
background: var(--sg-input-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
93
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #cbd5e1)); border-radius: var(--sg-radius, 8px);
|
|
94
|
+
font: inherit; padding: 0 10px; outline: none;
|
|
95
|
+
}
|
|
96
|
+
.sv-ac--sm { height: 28px; font-size: 12px; }
|
|
97
|
+
.sv-ac--md { height: 34px; font-size: 13px; }
|
|
98
|
+
.sv-ac--lg { height: 40px; font-size: 15px; }
|
|
99
|
+
.sv-ac:focus { border-color: var(--_accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--_accent) 22%, transparent); }
|
|
100
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ListOption } from './list-option';
|
|
2
|
+
type Props = {
|
|
3
|
+
value?: string;
|
|
4
|
+
onChange?: (value: string) => void;
|
|
5
|
+
/** Suggestions - strings or {value,label} (label is shown, value inserted). */
|
|
6
|
+
suggestions?: ReadonlyArray<string | ListOption>;
|
|
7
|
+
minChars?: number;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
name?: string;
|
|
11
|
+
size?: 'sm' | 'md' | 'lg';
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
};
|
|
14
|
+
declare const SvAutoComplete: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type SvAutoComplete = ReturnType<typeof SvAutoComplete>;
|
|
16
|
+
export default SvAutoComplete;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvButton - the base button primitive for the SvGrid UI kit. Variants, sizes,
|
|
4
|
+
* loading + icon slots. Pure `--sg-*` theming. Parity: Smart `smart-button`.
|
|
5
|
+
*/
|
|
6
|
+
import type { Snippet } from 'svelte'
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'
|
|
10
|
+
size?: 'sm' | 'md' | 'lg'
|
|
11
|
+
type?: 'button' | 'submit' | 'reset'
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
loading?: boolean
|
|
14
|
+
/** Stretch to the container width. */
|
|
15
|
+
block?: boolean
|
|
16
|
+
href?: string
|
|
17
|
+
ariaLabel?: string
|
|
18
|
+
onclick?: (e: MouseEvent) => void
|
|
19
|
+
children?: Snippet
|
|
20
|
+
/** Leading icon snippet. */
|
|
21
|
+
icon?: Snippet
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
variant = 'primary',
|
|
26
|
+
size = 'md',
|
|
27
|
+
type = 'button',
|
|
28
|
+
disabled = false,
|
|
29
|
+
loading = false,
|
|
30
|
+
block = false,
|
|
31
|
+
href,
|
|
32
|
+
ariaLabel,
|
|
33
|
+
onclick,
|
|
34
|
+
children,
|
|
35
|
+
icon,
|
|
36
|
+
}: Props = $props()
|
|
37
|
+
|
|
38
|
+
const isDisabled = $derived(disabled || loading)
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<svelte:element
|
|
42
|
+
this={href && !isDisabled ? 'a' : 'button'}
|
|
43
|
+
class="sv-btn sv-btn--{variant} sv-btn--{size}"
|
|
44
|
+
class:sv-btn--block={block}
|
|
45
|
+
class:is-loading={loading}
|
|
46
|
+
role={href ? 'button' : undefined}
|
|
47
|
+
href={href && !isDisabled ? href : undefined}
|
|
48
|
+
type={href ? undefined : type}
|
|
49
|
+
disabled={href ? undefined : isDisabled}
|
|
50
|
+
aria-disabled={isDisabled}
|
|
51
|
+
aria-label={ariaLabel}
|
|
52
|
+
aria-busy={loading}
|
|
53
|
+
onclick={(e: MouseEvent) => { if (isDisabled) { e.preventDefault(); return } onclick?.(e) }}
|
|
54
|
+
>
|
|
55
|
+
{#if loading}<span class="sv-btn__spinner" aria-hidden="true"></span>{/if}
|
|
56
|
+
{#if icon}<span class="sv-btn__icon">{@render icon()}</span>{/if}
|
|
57
|
+
{#if children}<span class="sv-btn__label">{@render children()}</span>{/if}
|
|
58
|
+
</svelte:element>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.sv-btn {
|
|
62
|
+
--_accent: var(--sg-accent, #2563eb);
|
|
63
|
+
--_on-accent: var(--sg-on-accent, #fff);
|
|
64
|
+
--_border: var(--sg-border, #cbd5e1);
|
|
65
|
+
--_radius: var(--sg-radius, 8px);
|
|
66
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 7px;
|
|
67
|
+
font: inherit; font-weight: 600; line-height: 1; white-space: nowrap;
|
|
68
|
+
border: 1px solid transparent; border-radius: var(--_radius); cursor: pointer;
|
|
69
|
+
text-decoration: none; user-select: none; transition: background 0.12s, border-color 0.12s, opacity 0.12s;
|
|
70
|
+
}
|
|
71
|
+
.sv-btn--sm { padding: 5px 10px; font-size: 12px; }
|
|
72
|
+
.sv-btn--md { padding: 8px 14px; font-size: 13px; }
|
|
73
|
+
.sv-btn--lg { padding: 11px 18px; font-size: 15px; }
|
|
74
|
+
.sv-btn--block { width: 100%; }
|
|
75
|
+
.sv-btn[disabled], .sv-btn[aria-disabled='true'] { opacity: 0.55; cursor: not-allowed; }
|
|
76
|
+
|
|
77
|
+
.sv-btn--primary { background: var(--_accent); color: var(--_on-accent); }
|
|
78
|
+
.sv-btn--primary:hover:not([disabled]):not([aria-disabled='true']) { background: color-mix(in srgb, var(--_accent) 88%, #000); }
|
|
79
|
+
.sv-btn--secondary { background: var(--sg-header-bg, #f1f5f9); color: var(--sg-fg, #0f172a); border-color: var(--_border); }
|
|
80
|
+
.sv-btn--secondary:hover:not([disabled]) { background: var(--sg-row-hover-bg, #e2e8f0); }
|
|
81
|
+
.sv-btn--outline { background: transparent; color: var(--_accent); border-color: var(--_accent); }
|
|
82
|
+
.sv-btn--outline:hover:not([disabled]) { background: color-mix(in srgb, var(--_accent) 10%, transparent); }
|
|
83
|
+
.sv-btn--ghost { background: transparent; color: var(--sg-fg, #0f172a); }
|
|
84
|
+
.sv-btn--ghost:hover:not([disabled]) { background: var(--sg-row-hover-bg, rgba(0,0,0,0.05)); }
|
|
85
|
+
.sv-btn--danger { background: var(--sg-danger, #dc2626); color: #fff; }
|
|
86
|
+
.sv-btn--danger:hover:not([disabled]) { background: color-mix(in srgb, var(--sg-danger, #dc2626) 88%, #000); }
|
|
87
|
+
|
|
88
|
+
.sv-btn:focus-visible { outline: 2px solid var(--sg-focus-ring, var(--_accent)); outline-offset: 2px; }
|
|
89
|
+
|
|
90
|
+
.sv-btn__spinner {
|
|
91
|
+
width: 13px; height: 13px; border-radius: 50%;
|
|
92
|
+
border: 2px solid currentColor; border-top-color: transparent;
|
|
93
|
+
animation: sv-btn-spin 0.6s linear infinite;
|
|
94
|
+
}
|
|
95
|
+
.sv-btn__icon { display: inline-flex; }
|
|
96
|
+
@keyframes sv-btn-spin { to { transform: rotate(360deg); } }
|
|
97
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvButton - the base button primitive for the SvGrid UI kit. Variants, sizes,
|
|
3
|
+
* loading + icon slots. Pure `--sg-*` theming. Parity: Smart `smart-button`.
|
|
4
|
+
*/
|
|
5
|
+
import type { Snippet } from 'svelte';
|
|
6
|
+
type Props = {
|
|
7
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
type?: 'button' | 'submit' | 'reset';
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
loading?: boolean;
|
|
12
|
+
/** Stretch to the container width. */
|
|
13
|
+
block?: boolean;
|
|
14
|
+
href?: string;
|
|
15
|
+
ariaLabel?: string;
|
|
16
|
+
onclick?: (e: MouseEvent) => void;
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
/** Leading icon snippet. */
|
|
19
|
+
icon?: Snippet;
|
|
20
|
+
};
|
|
21
|
+
declare const SvButton: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type SvButton = ReturnType<typeof SvButton>;
|
|
23
|
+
export default SvButton;
|