@human-kit/svelte-components 1.0.0-alpha.6 → 1.0.0-alpha.8

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 (39) hide show
  1. package/dist/button/README.md +48 -0
  2. package/dist/button/TODO.md +13 -0
  3. package/dist/button/index.d.ts +5 -0
  4. package/dist/button/index.js +4 -0
  5. package/dist/button/index.parts.d.ts +1 -0
  6. package/dist/button/index.parts.js +1 -0
  7. package/dist/button/root/README.md +43 -0
  8. package/dist/button/root/button-root.svelte +362 -0
  9. package/dist/button/root/button-root.svelte.d.ts +21 -0
  10. package/dist/button/root/button-test.svelte +76 -0
  11. package/dist/button/root/button-test.svelte.d.ts +11 -0
  12. package/dist/calendar/trigger-next/calendar-trigger-next.svelte +9 -4
  13. package/dist/calendar/trigger-next/calendar-trigger-next.svelte.d.ts +2 -1
  14. package/dist/calendar/trigger-previous/calendar-trigger-previous.svelte +9 -4
  15. package/dist/calendar/trigger-previous/calendar-trigger-previous.svelte.d.ts +2 -1
  16. package/dist/combobox/button/combobox-button.svelte +5 -4
  17. package/dist/combobox/list/combobox-listbox.svelte.d.ts +1 -1
  18. package/dist/combobox/popover/combobox-popover.svelte +34 -4
  19. package/dist/combobox/root/combobox.svelte +8 -0
  20. package/dist/combobox/tag-remove/combobox-tag-remove.svelte +3 -2
  21. package/dist/datepicker/trigger/date-picker-trigger.svelte +5 -5
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +2 -0
  24. package/dist/input/README.md +36 -0
  25. package/dist/input/TODO.md +12 -0
  26. package/dist/input/input-test.svelte +38 -0
  27. package/dist/input/input-test.svelte.d.ts +11 -0
  28. package/dist/input/input.svelte +142 -7
  29. package/dist/input/input.svelte.d.ts +7 -2
  30. package/dist/listbox/item/README.md +2 -1
  31. package/dist/listbox/item/listbox-item.svelte +129 -1
  32. package/dist/listbox/item/listbox-item.svelte.d.ts +2 -0
  33. package/dist/listbox/root/listbox-test.svelte +14 -2
  34. package/dist/listbox/root/listbox-test.svelte.d.ts +1 -0
  35. package/dist/listbox/root/listbox.svelte.d.ts +2 -2
  36. package/dist/popover/trigger/popover-trigger-button.svelte +4 -3
  37. package/dist/table/root/table-root.svelte.d.ts +1 -1
  38. package/dist/timepicker/trigger/time-picker-trigger.svelte +5 -5
  39. package/package.json +6 -1
@@ -0,0 +1,48 @@
1
+ # Button
2
+
3
+ ## Description
4
+
5
+ `Button` is a headless native button with RAC-aligned pending semantics, pressed-state exposure, and modality-aware focus data attributes.
6
+
7
+ ## Anatomy
8
+
9
+ - `Button.Root`
10
+
11
+ ```svelte
12
+ <Button.Root>
13
+ {#snippet children({ isPending, isPressed })}
14
+ {#if isPending}
15
+ <SavingSpinner />
16
+ {:else}
17
+ <span class:is-pressed={isPressed}>Save</span>
18
+ {/if}
19
+ {/snippet}
20
+ </Button.Root>
21
+ ```
22
+
23
+ ## Usage guidelines
24
+
25
+ - Use native button props such as `type`, `name`, `value`, and form attributes directly on `Button.Root`.
26
+ - Use `isPending` to keep the button focusable while blocking activation and hover state.
27
+ - Style interaction states with `data-hovered`, `data-pressed`, `data-focused`, `data-focus-visible`, `data-disabled`, and `data-pending`.
28
+
29
+ ## API reference
30
+
31
+ `Button.Root` supports:
32
+
33
+ - `isPending?: boolean`
34
+ - `isDisabled?: boolean`
35
+ - `children?: Snippet<[ButtonRenderState]> | Snippet`
36
+ - `type?: 'button' | 'submit' | 'reset'`
37
+ - `...restProps: HTMLButtonAttributes`
38
+
39
+ ## Accessibility
40
+
41
+ - `Button.Root` renders a native `<button>`.
42
+ - `isPending` applies `aria-disabled="true"`, preserves focusability, blocks press behavior, and announces the pending state through an internal polite live region.
43
+ - `data-focus-visible` follows the shared modality contract and is only exposed for keyboard or virtual focus.
44
+
45
+ ## Notes
46
+
47
+ - When `type="submit"` and `isPending` is true, the rendered button type switches to `button` to prevent implicit and explicit form submission.
48
+ - Pending does not serialize `data-disabled`; it is represented by `data-pending`.
@@ -0,0 +1,13 @@
1
+ # Button TODO
2
+
3
+ ## Goal
4
+
5
+ Track Button work with a single mandatory TODO format.
6
+
7
+ ## Backlog
8
+
9
+ - [x] [S][P0][Area: Architecture][Owner: Unassigned][Target: Done] Create base `root` part with namespace exports.
10
+ - [x] [S][P0][Area: Interaction][Owner: Unassigned][Target: Done] Implement native press, hover, focus, and focus-visible state exposure.
11
+ - [x] [S][P0][Area: Accessibility][Owner: Unassigned][Target: Done] Match RAC-style pending semantics with `aria-disabled`, live announcement, and submit suppression.
12
+ - [x] [S][P0][Area: Testing][Owner: Unassigned][Target: Done] Add baseline tests for press, pending, disabled, focus, and form behavior.
13
+ - [ ] [S][P1][Area: API][Owner: Unassigned][Target: TBD] Evaluate whether to expose a dedicated `pendingLabel` override if consumers need localized announcement text without custom child content.
@@ -0,0 +1,5 @@
1
+ export * as Button from './index.parts.js';
2
+ export { default as ButtonRoot } from './root/button-root.svelte';
3
+ export type { ButtonRenderState } from './root/button-root.svelte';
4
+ import * as ButtonParts from './index.parts.js';
5
+ export default ButtonParts;
@@ -0,0 +1,4 @@
1
+ export * as Button from './index.parts.js';
2
+ export { default as ButtonRoot } from './root/button-root.svelte';
3
+ import * as ButtonParts from './index.parts.js';
4
+ export default ButtonParts;
@@ -0,0 +1 @@
1
+ export { default as Root } from './root/button-root.svelte';
@@ -0,0 +1 @@
1
+ export { default as Root } from './root/button-root.svelte';
@@ -0,0 +1,43 @@
1
+ # Button Root
2
+
3
+ ## API reference
4
+
5
+ ### Button.Root
6
+
7
+ Name: `Button.Root`
8
+ Description: Native button root with pressed, hovered, focused, focus-visible, disabled, and pending render state.
9
+
10
+ | Prop | Type | Default | Description |
11
+ | -------------- | ----------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------- |
12
+ | `id` | `string` | `$props.id()` | Stable id for the native button and pending announcement labelling. |
13
+ | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | Native button type. While pending, `submit` is rendered as `button`. |
14
+ | `isPending` | `boolean` | `false` | Keeps the button focusable while disabling press and hover behavior. |
15
+ | `isDisabled` | `boolean` | `false` | Disables the native button. |
16
+ | `children` | `Snippet<[ButtonRenderState]> \| Snippet` | `undefined` | Optional renderer receiving the current interaction state. |
17
+ | `class` | `string` | `''` | CSS class names applied to the button element. |
18
+ | `...restProps` | `HTMLButtonAttributes` | `-` | Additional native button attributes forwarded to the element, excluding reserved disabled semantics. |
19
+
20
+ ### Types
21
+
22
+ Name: `ButtonRenderState`
23
+ Description: Render-state payload available to the `children` snippet.
24
+
25
+ | Prop | Type | Default | Description |
26
+ | ---------------- | --------- | ------- | --------------------------------------------------------------------- |
27
+ | `isHovered` | `boolean` | `false` | Whether the button is currently hovered by a mouse. |
28
+ | `isPressed` | `boolean` | `false` | Whether the button is currently being pressed. Cleared while pending. |
29
+ | `isFocused` | `boolean` | `false` | Whether the button currently holds DOM focus. |
30
+ | `isFocusVisible` | `boolean` | `false` | Whether focus is visibly keyboard or virtual driven. |
31
+ | `isDisabled` | `boolean` | `false` | Whether the button is disabled. |
32
+ | `isPending` | `boolean` | `false` | Whether the button is pending. |
33
+
34
+ ```svelte
35
+ <Button.Root type="submit" isPending={saving} aria-label="Save changes">
36
+ {#snippet children({ isPending, isPressed })}
37
+ <span class:opacity-0={isPending}>Save</span>
38
+ {#if isPending}
39
+ <ProgressCircle aria-label="Saving" isIndeterminate />
40
+ {/if}
41
+ {/snippet}
42
+ </Button.Root>
43
+ ```
@@ -0,0 +1,362 @@
1
+ <script lang="ts">
2
+ import { untrack, type Snippet } from 'svelte';
3
+ import type { HTMLButtonAttributes } from 'svelte/elements';
4
+ import {
5
+ shouldShowFocusVisible,
6
+ trackInteractionModality
7
+ } from '../../primitives/input-modality';
8
+ import { cn } from '../../utils/cn';
9
+
10
+ export type ButtonRenderState = {
11
+ isHovered: boolean;
12
+ isPressed: boolean;
13
+ isFocused: boolean;
14
+ isFocusVisible: boolean;
15
+ isDisabled: boolean;
16
+ isPending: boolean;
17
+ };
18
+
19
+ type ButtonRootProps = Omit<
20
+ HTMLButtonAttributes,
21
+ 'children' | 'class' | 'disabled' | 'aria-disabled'
22
+ > & {
23
+ children?: Snippet<[ButtonRenderState]> | Snippet;
24
+ class?: string;
25
+ isPending?: boolean;
26
+ isDisabled?: boolean;
27
+ element?: HTMLButtonElement | null;
28
+ pressed?: boolean;
29
+ };
30
+
31
+ function composeEventHandlers<TEvent extends Event>(
32
+ internalHandler: ((event: TEvent) => void) | undefined,
33
+ externalHandler: ((event: TEvent) => void) | undefined,
34
+ options?: { skipExternalOnDefaultPrevented?: boolean }
35
+ ): (event: TEvent) => void {
36
+ return (event: TEvent) => {
37
+ let preventDefaultCalled = false;
38
+ const originalPreventDefault = event.preventDefault.bind(event);
39
+ event.preventDefault = () => {
40
+ preventDefaultCalled = true;
41
+ originalPreventDefault();
42
+ };
43
+
44
+ internalHandler?.(event);
45
+ event.preventDefault = originalPreventDefault;
46
+
47
+ if (
48
+ options?.skipExternalOnDefaultPrevented &&
49
+ (event.defaultPrevented || preventDefaultCalled)
50
+ ) {
51
+ return;
52
+ }
53
+
54
+ externalHandler?.(event);
55
+ };
56
+ }
57
+
58
+ const generatedId = $props.id();
59
+
60
+ let {
61
+ id,
62
+ type = 'button',
63
+ children,
64
+ class: className = '',
65
+ isPending = false,
66
+ isDisabled = false,
67
+ element = $bindable<HTMLButtonElement | null>(null),
68
+ pressed: pressedOverride,
69
+ onclick: onClickExternal,
70
+ onfocus: onFocusExternal,
71
+ onblur: onBlurExternal,
72
+ onkeydown: onKeyDownExternal,
73
+ onkeyup: onKeyUpExternal,
74
+ onpointerdown: onPointerDownExternal,
75
+ onpointerup: onPointerUpExternal,
76
+ onpointercancel: onPointerCancelExternal,
77
+ onpointerenter: onPointerEnterExternal,
78
+ onpointerleave: onPointerLeaveExternal,
79
+ onmousedown: onMouseDownExternal,
80
+ onmouseup: onMouseUpExternal,
81
+ onmouseenter: onMouseEnterExternal,
82
+ onmouseleave: onMouseLeaveExternal,
83
+ 'aria-label': ariaLabel,
84
+ 'aria-labelledby': ariaLabelledby,
85
+ ...restProps
86
+ }: ButtonRootProps = $props();
87
+
88
+ const resolvedId = untrack(() => id) ?? generatedId;
89
+
90
+ let buttonRef: HTMLButtonElement | null = $state(null);
91
+ let hovered = $state(false);
92
+ let pressed = $state(false);
93
+ let focused = $state(false);
94
+ let focusVisible = $state(false);
95
+ let pressedKey: 'Enter' | 'Space' | null = $state(null);
96
+
97
+ const renderedType = $derived(type === 'submit' && isPending ? 'button' : type);
98
+ const renderedPressed = $derived(
99
+ pressedOverride !== undefined ? Boolean(pressedOverride) && !isPending : pressed && !isPending
100
+ );
101
+ const renderState = $derived.by<ButtonRenderState>(() => ({
102
+ isHovered: hovered,
103
+ isPressed: renderedPressed,
104
+ isFocused: focused,
105
+ isFocusVisible: focusVisible,
106
+ isDisabled,
107
+ isPending
108
+ }));
109
+ const pendingAnnouncement = $derived.by(() =>
110
+ isPending ? `${resolveButtonLabel() || 'Button'}, pending` : ''
111
+ );
112
+
113
+ function resolveReferencedLabel(ids: string | null | undefined): string {
114
+ if (!ids || !buttonRef) return '';
115
+ const ownerDocument = buttonRef.ownerDocument;
116
+ return ids
117
+ .split(/\s+/)
118
+ .map((token) => ownerDocument.getElementById(token)?.textContent?.trim() ?? '')
119
+ .filter(Boolean)
120
+ .join(' ')
121
+ .trim();
122
+ }
123
+
124
+ function resolveButtonLabel(): string {
125
+ const directLabel = ariaLabel?.trim();
126
+ if (directLabel) return directLabel;
127
+
128
+ const labelledByLabel = resolveReferencedLabel(ariaLabelledby);
129
+ if (labelledByLabel) return labelledByLabel;
130
+
131
+ if (!buttonRef) return '';
132
+
133
+ const liveRegion = buttonRef.querySelector('[data-button-live-region="true"]');
134
+ return Array.from(buttonRef.childNodes)
135
+ .filter((node) => node !== liveRegion)
136
+ .map((node) => node.textContent?.trim() ?? '')
137
+ .filter(Boolean)
138
+ .join(' ')
139
+ .trim();
140
+ }
141
+
142
+ function clearInteractionState() {
143
+ hovered = false;
144
+ pressed = false;
145
+ pressedKey = null;
146
+ }
147
+
148
+ $effect(() => {
149
+ element = buttonRef;
150
+ });
151
+
152
+ $effect(() => {
153
+ if (!isPending && !isDisabled) return;
154
+ clearInteractionState();
155
+ if (isDisabled) {
156
+ focusVisible = false;
157
+ }
158
+ });
159
+
160
+ function handlePointerDown(event: PointerEvent) {
161
+ trackInteractionModality(event, buttonRef);
162
+ focusVisible = false;
163
+
164
+ if (isDisabled || isPending) {
165
+ event.preventDefault();
166
+ clearInteractionState();
167
+ return;
168
+ }
169
+
170
+ if (event.button !== 0) return;
171
+ pressed = true;
172
+ pressedKey = null;
173
+ }
174
+
175
+ function handlePointerUp(event: PointerEvent) {
176
+ if (event.button !== 0) return;
177
+ pressed = false;
178
+ pressedKey = null;
179
+ }
180
+
181
+ function handlePointerCancel() {
182
+ pressed = false;
183
+ pressedKey = null;
184
+ }
185
+
186
+ function handlePointerEnter(event: PointerEvent) {
187
+ if (isDisabled || isPending) return;
188
+
189
+ if ((event.buttons & 1) === 1 && pressedKey === null) {
190
+ pressed = true;
191
+ }
192
+ }
193
+
194
+ function handlePointerLeave() {
195
+ if (pressedKey === null) {
196
+ pressed = false;
197
+ }
198
+ }
199
+
200
+ function handleMouseDown(event: MouseEvent) {
201
+ trackInteractionModality(event, buttonRef);
202
+ focusVisible = false;
203
+
204
+ if (isDisabled || isPending) {
205
+ event.preventDefault();
206
+ clearInteractionState();
207
+ return;
208
+ }
209
+
210
+ if (event.button !== 0) return;
211
+ pressed = true;
212
+ pressedKey = null;
213
+ }
214
+
215
+ function handleMouseUp(event: MouseEvent) {
216
+ if (event.button !== 0) return;
217
+ if (pressedKey === null) {
218
+ pressed = false;
219
+ }
220
+ }
221
+
222
+ function handleMouseEnter() {
223
+ if (isDisabled || isPending) {
224
+ hovered = false;
225
+ return;
226
+ }
227
+
228
+ hovered = true;
229
+ }
230
+
231
+ function handleMouseLeave() {
232
+ hovered = false;
233
+ }
234
+
235
+ function handleFocus() {
236
+ focused = true;
237
+ focusVisible = shouldShowFocusVisible(buttonRef);
238
+ }
239
+
240
+ function handleBlur() {
241
+ focused = false;
242
+ focusVisible = false;
243
+ clearInteractionState();
244
+ }
245
+
246
+ function handleKeyDown(event: KeyboardEvent) {
247
+ const key =
248
+ event.key === 'Enter'
249
+ ? 'Enter'
250
+ : event.key === ' ' || event.key === 'Spacebar'
251
+ ? 'Space'
252
+ : null;
253
+
254
+ if (!key) return;
255
+
256
+ trackInteractionModality(event, buttonRef);
257
+ focusVisible = focused ? true : shouldShowFocusVisible(buttonRef);
258
+
259
+ if (isDisabled || isPending) {
260
+ event.preventDefault();
261
+ clearInteractionState();
262
+ return;
263
+ }
264
+
265
+ if (event.repeat && pressed && pressedKey === key) return;
266
+
267
+ pressed = true;
268
+ pressedKey = key;
269
+ }
270
+
271
+ function handleKeyUp(event: KeyboardEvent) {
272
+ const key =
273
+ event.key === 'Enter'
274
+ ? 'Enter'
275
+ : event.key === ' ' || event.key === 'Spacebar'
276
+ ? 'Space'
277
+ : null;
278
+
279
+ if (!key) return;
280
+
281
+ trackInteractionModality(event, buttonRef);
282
+ focusVisible = focused ? true : shouldShowFocusVisible(buttonRef);
283
+
284
+ if (isDisabled || isPending) {
285
+ event.preventDefault();
286
+ clearInteractionState();
287
+ return;
288
+ }
289
+
290
+ if (pressedKey === key) {
291
+ pressed = false;
292
+ pressedKey = null;
293
+ }
294
+ }
295
+
296
+ function handleClick(event: MouseEvent) {
297
+ if (event.detail > 0) {
298
+ trackInteractionModality(event, buttonRef);
299
+ }
300
+
301
+ if (isPending) {
302
+ event.preventDefault();
303
+ clearInteractionState();
304
+ }
305
+ }
306
+ </script>
307
+
308
+ <button
309
+ {...restProps}
310
+ bind:this={buttonRef}
311
+ id={resolvedId}
312
+ type={renderedType}
313
+ disabled={isDisabled}
314
+ aria-label={ariaLabel}
315
+ aria-labelledby={ariaLabelledby}
316
+ aria-disabled={isPending ? 'true' : undefined}
317
+ data-button-root="true"
318
+ data-disabled={isDisabled || undefined}
319
+ data-pending={isPending || undefined}
320
+ data-hovered={hovered || undefined}
321
+ data-pressed={renderedPressed || undefined}
322
+ data-focused={focused || undefined}
323
+ data-focus-visible={focusVisible || undefined}
324
+ class={cn('outline-none', className)}
325
+ onclick={composeEventHandlers(handleClick, onClickExternal ?? undefined, {
326
+ skipExternalOnDefaultPrevented: true
327
+ })}
328
+ onfocus={composeEventHandlers(handleFocus, onFocusExternal ?? undefined)}
329
+ onblur={composeEventHandlers(handleBlur, onBlurExternal ?? undefined)}
330
+ onkeydown={composeEventHandlers(handleKeyDown, onKeyDownExternal ?? undefined, {
331
+ skipExternalOnDefaultPrevented: true
332
+ })}
333
+ onkeyup={composeEventHandlers(handleKeyUp, onKeyUpExternal ?? undefined, {
334
+ skipExternalOnDefaultPrevented: true
335
+ })}
336
+ onpointerdown={composeEventHandlers(handlePointerDown, onPointerDownExternal ?? undefined, {
337
+ skipExternalOnDefaultPrevented: true
338
+ })}
339
+ onpointerup={composeEventHandlers(handlePointerUp, onPointerUpExternal ?? undefined)}
340
+ onpointercancel={composeEventHandlers(handlePointerCancel, onPointerCancelExternal ?? undefined)}
341
+ onpointerenter={composeEventHandlers(handlePointerEnter, onPointerEnterExternal ?? undefined)}
342
+ onpointerleave={composeEventHandlers(handlePointerLeave, onPointerLeaveExternal ?? undefined)}
343
+ onmousedown={composeEventHandlers(handleMouseDown, onMouseDownExternal ?? undefined, {
344
+ skipExternalOnDefaultPrevented: true
345
+ })}
346
+ onmouseup={composeEventHandlers(handleMouseUp, onMouseUpExternal ?? undefined)}
347
+ onmouseenter={composeEventHandlers(handleMouseEnter, onMouseEnterExternal ?? undefined)}
348
+ onmouseleave={composeEventHandlers(handleMouseLeave, onMouseLeaveExternal ?? undefined)}
349
+ >
350
+ {#if children}
351
+ {@render (children as Snippet<[ButtonRenderState]>)(renderState)}
352
+ {/if}
353
+
354
+ <span
355
+ data-button-live-region="true"
356
+ role="status"
357
+ aria-live="polite"
358
+ aria-atomic="true"
359
+ style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;"
360
+ >{pendingAnnouncement}</span
361
+ >
362
+ </button>
@@ -0,0 +1,21 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ export type ButtonRenderState = {
4
+ isHovered: boolean;
5
+ isPressed: boolean;
6
+ isFocused: boolean;
7
+ isFocusVisible: boolean;
8
+ isDisabled: boolean;
9
+ isPending: boolean;
10
+ };
11
+ type ButtonRootProps = Omit<HTMLButtonAttributes, 'children' | 'class' | 'disabled' | 'aria-disabled'> & {
12
+ children?: Snippet<[ButtonRenderState]> | Snippet;
13
+ class?: string;
14
+ isPending?: boolean;
15
+ isDisabled?: boolean;
16
+ element?: HTMLButtonElement | null;
17
+ pressed?: boolean;
18
+ };
19
+ declare const ButtonRoot: import("svelte").Component<ButtonRootProps, {}, "element">;
20
+ type ButtonRoot = ReturnType<typeof ButtonRoot>;
21
+ export default ButtonRoot;
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import { Button } from '../index';
3
+
4
+ type Props = {
5
+ isDisabled?: boolean;
6
+ isPending?: boolean;
7
+ type?: 'button' | 'submit' | 'reset';
8
+ ariaLabel?: string;
9
+ onMouseEnter?: (event: MouseEvent) => void;
10
+ onFocus?: (event: FocusEvent) => void;
11
+ };
12
+
13
+ let {
14
+ isDisabled = false,
15
+ isPending = false,
16
+ type = 'button',
17
+ ariaLabel = 'Save',
18
+ onMouseEnter,
19
+ onFocus
20
+ }: Props = $props();
21
+
22
+ let pendingOverride = $state<boolean | null>(null);
23
+ let clickCount = $state(0);
24
+ let submitCount = $state(0);
25
+ const pending = $derived(pendingOverride ?? isPending);
26
+
27
+ function handleClick() {
28
+ clickCount += 1;
29
+ }
30
+
31
+ function handleSubmit(event: SubmitEvent) {
32
+ event.preventDefault();
33
+ submitCount += 1;
34
+ }
35
+ </script>
36
+
37
+ <form onsubmit={handleSubmit}>
38
+ <label for="name">Name</label>
39
+ <input id="name" type="text" />
40
+
41
+ <Button.Root
42
+ {type}
43
+ {isDisabled}
44
+ isPending={pending}
45
+ onclick={handleClick}
46
+ onmouseenter={onMouseEnter}
47
+ onfocus={onFocus}
48
+ aria-label={ariaLabel}
49
+ class="inline-flex items-center"
50
+ >
51
+ {#snippet children(state)}
52
+ <span data-button-label>{pending ? 'Saving' : 'Save'}</span>
53
+ <span
54
+ data-render-state="true"
55
+ data-render-pressed={state.isPressed || undefined}
56
+ data-render-hovered={state.isHovered || undefined}
57
+ data-render-pending={state.isPending || undefined}
58
+ data-render-focused={state.isFocused || undefined}
59
+ data-render-focus-visible={state.isFocusVisible || undefined}
60
+ >
61
+ {state.isPressed ? 'pressed' : state.isPending ? 'pending' : 'idle'}
62
+ </span>
63
+ {/snippet}
64
+ </Button.Root>
65
+
66
+ <button type="button" data-set-pending onclick={() => (pendingOverride = true)}
67
+ >Set pending</button
68
+ >
69
+ <button type="button" data-clear-pending onclick={() => (pendingOverride = false)}>
70
+ Clear pending
71
+ </button>
72
+
73
+ <output data-click-count>{String(clickCount)}</output>
74
+ <output data-submit-count>{String(submitCount)}</output>
75
+ <output data-pending-state>{String(pending)}</output>
76
+ </form>
@@ -0,0 +1,11 @@
1
+ type Props = {
2
+ isDisabled?: boolean;
3
+ isPending?: boolean;
4
+ type?: 'button' | 'submit' | 'reset';
5
+ ariaLabel?: string;
6
+ onMouseEnter?: (event: MouseEvent) => void;
7
+ onFocus?: (event: FocusEvent) => void;
8
+ };
9
+ declare const ButtonTest: import("svelte").Component<Props, {}, "">;
10
+ type ButtonTest = ReturnType<typeof ButtonTest>;
11
+ export default ButtonTest;
@@ -1,9 +1,14 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
+ import { ButtonRoot } from '../../button/index.js';
4
5
  import { useCalendarContext } from '../root/context';
5
6
 
6
- type CalendarTriggerNextProps = Omit<HTMLButtonAttributes, 'children'> & {
7
+ type CalendarTriggerNextProps = Omit<
8
+ HTMLButtonAttributes,
9
+ 'children' | 'class' | 'disabled' | 'aria-disabled'
10
+ > & {
11
+ class?: string;
7
12
  children?: Snippet;
8
13
  };
9
14
 
@@ -22,11 +27,11 @@
22
27
  }
23
28
  </script>
24
29
 
25
- <button
30
+ <ButtonRoot
26
31
  type="button"
27
32
  class={className}
28
33
  aria-label="Next page"
29
- disabled={isDisabled}
34
+ {isDisabled}
30
35
  onclick={handleClick}
31
36
  {...restProps}
32
37
  >
@@ -35,4 +40,4 @@
35
40
  {:else}
36
41
  Next
37
42
  {/if}
38
- </button>
43
+ </ButtonRoot>
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
- type CalendarTriggerNextProps = Omit<HTMLButtonAttributes, 'children'> & {
3
+ type CalendarTriggerNextProps = Omit<HTMLButtonAttributes, 'children' | 'class' | 'disabled' | 'aria-disabled'> & {
4
+ class?: string;
4
5
  children?: Snippet;
5
6
  };
6
7
  declare const CalendarTriggerNext: import("svelte").Component<CalendarTriggerNextProps, {}, "">;
@@ -1,9 +1,14 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLButtonAttributes } from 'svelte/elements';
4
+ import { ButtonRoot } from '../../button/index.js';
4
5
  import { useCalendarContext } from '../root/context';
5
6
 
6
- type CalendarTriggerPreviousProps = Omit<HTMLButtonAttributes, 'children'> & {
7
+ type CalendarTriggerPreviousProps = Omit<
8
+ HTMLButtonAttributes,
9
+ 'children' | 'class' | 'disabled' | 'aria-disabled'
10
+ > & {
11
+ class?: string;
7
12
  children?: Snippet;
8
13
  };
9
14
 
@@ -22,11 +27,11 @@
22
27
  }
23
28
  </script>
24
29
 
25
- <button
30
+ <ButtonRoot
26
31
  type="button"
27
32
  class={className}
28
33
  aria-label="Previous page"
29
- disabled={isDisabled}
34
+ {isDisabled}
30
35
  onclick={handleClick}
31
36
  {...restProps}
32
37
  >
@@ -35,4 +40,4 @@
35
40
  {:else}
36
41
  Prev
37
42
  {/if}
38
- </button>
43
+ </ButtonRoot>
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
- type CalendarTriggerPreviousProps = Omit<HTMLButtonAttributes, 'children'> & {
3
+ type CalendarTriggerPreviousProps = Omit<HTMLButtonAttributes, 'children' | 'class' | 'disabled' | 'aria-disabled'> & {
4
+ class?: string;
4
5
  children?: Snippet;
5
6
  };
6
7
  declare const CalendarTriggerPrevious: import("svelte").Component<CalendarTriggerPreviousProps, {}, "">;