@svgrid/grid 1.2.21 → 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svgrid/grid",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.23",
|
|
4
4
|
"description": "SvGrid - a headless-first, Svelte 5-native data grid engine and render component.",
|
|
5
5
|
"author": "jQWidgets <boikom@jqwidgets.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,11 @@
|
|
|
42
42
|
"svelte": "./dist/static-functions.js",
|
|
43
43
|
"import": "./dist/static-functions.js"
|
|
44
44
|
},
|
|
45
|
+
"./format": {
|
|
46
|
+
"types": "./dist/export-format.d.ts",
|
|
47
|
+
"svelte": "./dist/export-format.js",
|
|
48
|
+
"import": "./dist/export-format.js"
|
|
49
|
+
},
|
|
45
50
|
"./flex-render": {
|
|
46
51
|
"types": "./dist/flex-render.d.ts",
|
|
47
52
|
"svelte": "./dist/flex-render.js",
|
package/src/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
|
|
@@ -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,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>
|