@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.
Files changed (190) hide show
  1. package/dist/GridFooter.svelte +34 -20
  2. package/dist/GridFooter.svelte.d.ts +8 -0
  3. package/dist/SvAutoComplete.svelte +100 -0
  4. package/dist/SvAutoComplete.svelte.d.ts +16 -0
  5. package/dist/SvButton.svelte +97 -0
  6. package/dist/SvButton.svelte.d.ts +23 -0
  7. package/dist/SvCalendar.svelte +669 -0
  8. package/dist/SvCalendar.svelte.d.ts +70 -0
  9. package/dist/SvCheckBox.svelte +81 -0
  10. package/dist/SvCheckBox.svelte.d.ts +19 -0
  11. package/dist/SvColorInput.svelte +187 -0
  12. package/dist/SvColorInput.svelte.d.ts +15 -0
  13. package/dist/SvComboBox.svelte +130 -0
  14. package/dist/SvComboBox.svelte.d.ts +15 -0
  15. package/dist/SvCountryInput.svelte +123 -0
  16. package/dist/SvCountryInput.svelte.d.ts +14 -0
  17. package/dist/SvDateTimePicker.svelte +322 -0
  18. package/dist/SvDateTimePicker.svelte.d.ts +37 -0
  19. package/dist/SvDropDownList.svelte +143 -0
  20. package/dist/SvDropDownList.svelte.d.ts +15 -0
  21. package/dist/SvForm.svelte +147 -0
  22. package/dist/SvForm.svelte.d.ts +29 -0
  23. package/dist/SvGauge.svelte +99 -0
  24. package/dist/SvGauge.svelte.d.ts +31 -0
  25. package/dist/SvGrid.controller.svelte.d.ts +16 -2
  26. package/dist/SvGrid.controller.svelte.js +63 -8
  27. package/dist/SvGrid.css +84 -31
  28. package/dist/SvGrid.helpers.d.ts +4 -0
  29. package/dist/SvGrid.helpers.js +40 -12
  30. package/dist/SvGrid.svelte +84 -5
  31. package/dist/SvGrid.types.d.ts +36 -0
  32. package/dist/SvGridDropdown.svelte +16 -30
  33. package/dist/SvGridDropdown.svelte.d.ts +4 -0
  34. package/dist/SvListBox.svelte +122 -0
  35. package/dist/SvListBox.svelte.d.ts +19 -0
  36. package/dist/SvMaskedInput.svelte +0 -0
  37. package/dist/SvMaskedInput.svelte.d.ts +15 -0
  38. package/dist/SvNumberInput.svelte +0 -0
  39. package/dist/SvNumberInput.svelte.d.ts +22 -0
  40. package/dist/SvPasswordInput.svelte +110 -0
  41. package/dist/SvPasswordInput.svelte.d.ts +22 -0
  42. package/dist/SvPhoneInput.svelte +0 -0
  43. package/dist/SvPhoneInput.svelte.d.ts +19 -0
  44. package/dist/SvRadioGroup.svelte +108 -0
  45. package/dist/SvRadioGroup.svelte.d.ts +23 -0
  46. package/dist/SvRating.svelte +111 -0
  47. package/dist/SvRating.svelte.d.ts +20 -0
  48. package/dist/SvRepeatButton.svelte +89 -0
  49. package/dist/SvRepeatButton.svelte.d.ts +21 -0
  50. package/dist/SvSlider.svelte +182 -0
  51. package/dist/SvSlider.svelte.d.ts +25 -0
  52. package/dist/SvSwitchButton.svelte +94 -0
  53. package/dist/SvSwitchButton.svelte.d.ts +19 -0
  54. package/dist/SvTabs.svelte +122 -0
  55. package/dist/SvTabs.svelte.d.ts +26 -0
  56. package/dist/SvTagsInput.svelte +81 -0
  57. package/dist/SvTagsInput.svelte.d.ts +19 -0
  58. package/dist/SvTimePicker.svelte +359 -0
  59. package/dist/SvTimePicker.svelte.d.ts +27 -0
  60. package/dist/SvToggleButton.svelte +57 -0
  61. package/dist/SvToggleButton.svelte.d.ts +16 -0
  62. package/dist/SvTree.svelte +210 -0
  63. package/dist/SvTree.svelte.d.ts +29 -0
  64. package/dist/build-api.js +78 -1
  65. package/dist/cdn/svgrid.js +10022 -5904
  66. package/dist/cdn/svgrid.svelte-external.js +8531 -4429
  67. package/dist/cell-render.d.ts +4 -0
  68. package/dist/cell-render.js +35 -1
  69. package/dist/clipboard.js +19 -1
  70. package/dist/collaboration.d.ts +2 -0
  71. package/dist/collaboration.js +4 -0
  72. package/dist/core.d.ts +22 -1
  73. package/dist/countries.d.ts +15 -0
  74. package/dist/countries.js +61 -0
  75. package/dist/datetime/date-core.d.ts +88 -0
  76. package/dist/datetime/date-core.js +165 -0
  77. package/dist/datetime/date-format.d.ts +39 -0
  78. package/dist/datetime/date-format.js +371 -0
  79. package/dist/datetime/date-restrict.d.ts +23 -0
  80. package/dist/datetime/date-restrict.js +50 -0
  81. package/dist/datetime/date-selection.d.ts +25 -0
  82. package/dist/datetime/date-selection.js +94 -0
  83. package/dist/datetime/mask.d.ts +21 -0
  84. package/dist/datetime/mask.js +90 -0
  85. package/dist/editing.d.ts +1 -1
  86. package/dist/editing.js +7 -0
  87. package/dist/editors/cell-editors.d.ts +2 -2
  88. package/dist/editors/cell-editors.js +3 -1
  89. package/dist/export-format.d.ts +178 -0
  90. package/dist/export-format.js +419 -0
  91. package/dist/hyperformula-adapter.d.ts +4 -0
  92. package/dist/hyperformula-adapter.js +15 -6
  93. package/dist/index.d.ts +36 -0
  94. package/dist/index.js +41 -0
  95. package/dist/list-option.d.ts +10 -0
  96. package/dist/list-option.js +7 -0
  97. package/dist/menus.js +18 -2
  98. package/dist/popover.d.ts +48 -0
  99. package/dist/popover.js +65 -0
  100. package/dist/row-resize.d.ts +4 -4
  101. package/dist/row-resize.js +8 -5
  102. package/dist/selection.d.ts +6 -0
  103. package/dist/selection.js +31 -5
  104. package/dist/server-data-source.d.ts +43 -0
  105. package/dist/server-data-source.js +100 -0
  106. package/dist/sparkline.js +8 -4
  107. package/dist/svgrid-wrapper.types.d.ts +26 -1
  108. package/package.json +6 -1
  109. package/src/GridFooter.svelte +34 -20
  110. package/src/SvAutoComplete.svelte +100 -0
  111. package/src/SvButton.svelte +97 -0
  112. package/src/SvCalendar.svelte +669 -0
  113. package/src/SvCalendar.test.ts +211 -0
  114. package/src/SvCheckBox.svelte +81 -0
  115. package/src/SvColorInput.svelte +187 -0
  116. package/src/SvComboBox.svelte +130 -0
  117. package/src/SvCountryInput.svelte +123 -0
  118. package/src/SvDateTimePicker.svelte +322 -0
  119. package/src/SvDateTimePicker.test.ts +136 -0
  120. package/src/SvDropDownList.svelte +143 -0
  121. package/src/SvForm.svelte +147 -0
  122. package/src/SvGauge.svelte +99 -0
  123. package/src/SvGrid.controller.svelte.ts +74 -8
  124. package/src/SvGrid.css +84 -31
  125. package/src/SvGrid.helpers.ts +41 -12
  126. package/src/SvGrid.svelte +84 -5
  127. package/src/SvGrid.types.ts +33 -0
  128. package/src/SvGridDropdown.svelte +16 -30
  129. package/src/SvListBox.svelte +122 -0
  130. package/src/SvMaskedInput.svelte +0 -0
  131. package/src/SvNumberInput.svelte +0 -0
  132. package/src/SvPasswordInput.svelte +110 -0
  133. package/src/SvPhoneInput.svelte +0 -0
  134. package/src/SvRadioGroup.svelte +108 -0
  135. package/src/SvRating.svelte +111 -0
  136. package/src/SvRepeatButton.svelte +89 -0
  137. package/src/SvSlider.svelte +182 -0
  138. package/src/SvSwitchButton.svelte +94 -0
  139. package/src/SvTabs.svelte +122 -0
  140. package/src/SvTagsInput.svelte +81 -0
  141. package/src/SvTimePicker.svelte +359 -0
  142. package/src/SvTimePicker.test.ts +98 -0
  143. package/src/SvToggleButton.svelte +57 -0
  144. package/src/SvTree.svelte +210 -0
  145. package/src/build-api.ts +115 -0
  146. package/src/cell-render.test.ts +38 -0
  147. package/src/cell-render.ts +43 -0
  148. package/src/clipboard.test.ts +18 -0
  149. package/src/clipboard.ts +24 -1
  150. package/src/collaboration.test.ts +30 -0
  151. package/src/collaboration.ts +6 -0
  152. package/src/core.ts +27 -3
  153. package/src/countries.ts +71 -0
  154. package/src/datetime/date-core.test.ts +217 -0
  155. package/src/datetime/date-core.ts +204 -0
  156. package/src/datetime/date-format.test.ts +121 -0
  157. package/src/datetime/date-format.ts +317 -0
  158. package/src/datetime/date-restrict.ts +60 -0
  159. package/src/datetime/date-selection.test.ts +129 -0
  160. package/src/datetime/date-selection.ts +137 -0
  161. package/src/datetime/mask.test.ts +36 -0
  162. package/src/datetime/mask.ts +84 -0
  163. package/src/editing.test.ts +22 -0
  164. package/src/editing.ts +7 -1
  165. package/src/editors/cell-editors.ts +7 -1
  166. package/src/export-data-api.test.ts +126 -0
  167. package/src/export-format.test.ts +107 -0
  168. package/src/export-format.ts +598 -0
  169. package/src/hyperformula-adapter.test.ts +35 -1
  170. package/src/hyperformula-adapter.ts +18 -6
  171. package/src/index.ts +71 -0
  172. package/src/list-option.ts +15 -0
  173. package/src/menus.ts +21 -2
  174. package/src/popover.ts +79 -0
  175. package/src/row-resize.test.ts +25 -0
  176. package/src/row-resize.ts +8 -5
  177. package/src/selection.test.ts +36 -4
  178. package/src/selection.ts +30 -9
  179. package/src/server-data-source.test.ts +201 -0
  180. package/src/server-data-source.ts +148 -0
  181. package/src/sparkline.test.ts +9 -0
  182. package/src/sparkline.ts +9 -4
  183. package/src/svgrid-wrapper.types.ts +28 -1
  184. package/src/svgrid.behavior.test.ts +27 -0
  185. package/src/svgrid.new-features.wrapper.test.ts +27 -1
  186. package/src/ui-buttons.test.ts +105 -0
  187. package/src/ui-composite.test.ts +89 -0
  188. package/src/ui-inputs.test.ts +92 -0
  189. package/src/ui-range.test.ts +61 -0
  190. package/src/ui-selection.test.ts +106 -0
@@ -47,7 +47,23 @@
47
47
  } from "./SvGrid.controller.svelte";
48
48
  import type { SvGridController } from "./SvGrid.controller.svelte";
49
49
 
50
- let { ctrl }: { ctrl: SvGridController<TFeatures, TData> } = $props();
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 = allRowsBeforePagination.length}
101
- {@const pageSize = paginationState.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(paginationState.pageIndex + 1, pageCount)}
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
- <div class="sv-grid-pagination" role="navigation" aria-label="Pagination">
110
- <label class="sv-grid-pagination-pagesize">
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
- <select
113
- onchange={(event) =>
114
- setPageSize(
115
- parseInt((event.currentTarget as HTMLSelectElement).value, 10),
116
- )}
117
- >
118
- <option value="10" selected={pageSize === 10}>10</option>
119
- <option value="25" selected={pageSize === 25}>25</option>
120
- <option value="50" selected={pageSize === 50}>50</option>
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;