@loidolt/theme-svelte 0.2.0 → 0.3.0

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 (55) hide show
  1. package/dist/components/Accordion.svelte +100 -0
  2. package/dist/components/Accordion.svelte.d.ts +50 -0
  3. package/dist/components/Alert.svelte +6 -3
  4. package/dist/components/Alert.svelte.d.ts +3 -2
  5. package/dist/components/AlertDialog.svelte +109 -0
  6. package/dist/components/AlertDialog.svelte.d.ts +32 -0
  7. package/dist/components/Badge.svelte +9 -2
  8. package/dist/components/Badge.svelte.d.ts +2 -1
  9. package/dist/components/Brand.svelte +29 -15
  10. package/dist/components/Brand.svelte.d.ts +8 -4
  11. package/dist/components/Breadcrumbs.svelte +53 -0
  12. package/dist/components/Breadcrumbs.svelte.d.ts +18 -0
  13. package/dist/components/Button.svelte +43 -21
  14. package/dist/components/Button.svelte.d.ts +9 -4
  15. package/dist/components/Drawer.svelte +90 -0
  16. package/dist/components/Drawer.svelte.d.ts +28 -0
  17. package/dist/components/DropdownMenu.svelte +2 -2
  18. package/dist/components/DropdownMenu.svelte.d.ts +1 -1
  19. package/dist/components/EmptyState.svelte +40 -0
  20. package/dist/components/EmptyState.svelte.d.ts +19 -0
  21. package/dist/components/Fieldset.svelte +60 -0
  22. package/dist/components/Fieldset.svelte.d.ts +19 -0
  23. package/dist/components/IconButton.svelte +1 -1
  24. package/dist/components/Input.svelte.d.ts +1 -1
  25. package/dist/components/NavMenu.svelte +6 -1
  26. package/dist/components/NavMenu.svelte.d.ts +2 -1
  27. package/dist/components/NumberField.svelte +12 -1
  28. package/dist/components/NumberField.svelte.d.ts +1 -1
  29. package/dist/components/Pagination.svelte +86 -0
  30. package/dist/components/Pagination.svelte.d.ts +22 -0
  31. package/dist/components/RadioGroup.svelte.d.ts +2 -2
  32. package/dist/components/Select.svelte.d.ts +2 -2
  33. package/dist/components/Table.svelte +30 -3
  34. package/dist/components/Table.svelte.d.ts +13 -0
  35. package/dist/components/TableHeader.svelte +72 -0
  36. package/dist/components/TableHeader.svelte.d.ts +24 -0
  37. package/dist/components/Tabs.svelte +15 -7
  38. package/dist/components/Tabs.svelte.d.ts +4 -3
  39. package/dist/components/Textarea.svelte.d.ts +1 -1
  40. package/dist/components/ThemeToggle.svelte +62 -0
  41. package/dist/components/ThemeToggle.svelte.d.ts +25 -0
  42. package/dist/components/ToggleGroup.svelte +107 -0
  43. package/dist/components/ToggleGroup.svelte.d.ts +46 -0
  44. package/dist/components/TooltipProvider.svelte +1 -0
  45. package/dist/components/Workspace.svelte +18 -5
  46. package/dist/components/Workspace.svelte.d.ts +8 -0
  47. package/dist/index.d.ts +16 -2
  48. package/dist/index.js +13 -1
  49. package/dist/media.svelte.d.ts +48 -0
  50. package/dist/media.svelte.js +69 -0
  51. package/dist/theme.svelte.d.ts +86 -0
  52. package/dist/theme.svelte.js +156 -0
  53. package/dist/toaster.svelte.js +22 -8
  54. package/dist/types.d.ts +21 -0
  55. package/package.json +5 -4
@@ -0,0 +1,90 @@
1
+ <script lang="ts">
2
+ import { Dialog as DialogPrimitive } from 'bits-ui';
3
+ import type { Snippet } from 'svelte';
4
+ import type { DialogContentProps } from 'bits-ui';
5
+ import type { HeadingLevel, TriggerChildProps } from '../types.js';
6
+ import { cx } from '../utils.js';
7
+
8
+ type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
9
+ open?: boolean;
10
+ title: string;
11
+ description?: string;
12
+ /** Edge the panel is anchored to. `left`/`right` size by width, `top`/`bottom` by height. */
13
+ side?: 'left' | 'right' | 'top' | 'bottom';
14
+ /** Trigger contents, rendered inside a button styled with `triggerClass`. */
15
+ trigger?: Snippet;
16
+ /** Full control of the trigger element — receives the props Bits needs on it. */
17
+ triggerChild?: Snippet<[TriggerChildProps]>;
18
+ triggerClass?: string;
19
+ children: Snippet;
20
+ footer?: Snippet;
21
+ headingLevel?: HeadingLevel;
22
+ showClose?: boolean;
23
+ closeLabel?: string;
24
+ /** `class` lands on the portaled Content element. */
25
+ class?: string;
26
+ overlayClass?: string;
27
+ onOpenChange?: (open: boolean) => void;
28
+ ref?: HTMLElement | null;
29
+ };
30
+
31
+ let {
32
+ open = $bindable(false),
33
+ title,
34
+ description,
35
+ side = 'left',
36
+ trigger,
37
+ triggerChild,
38
+ triggerClass = 'ldt-button',
39
+ children,
40
+ footer,
41
+ headingLevel = 2,
42
+ showClose = true,
43
+ closeLabel = 'Close',
44
+ class: className,
45
+ overlayClass,
46
+ onOpenChange,
47
+ ref = $bindable(null),
48
+ ...rest
49
+ }: Props = $props();
50
+ </script>
51
+
52
+ <!--
53
+ A modal panel anchored to one edge — the narrow-viewport form of a sidebar, and the usual home
54
+ for navigation that has no room to sit in the topbar. Same primitive as `Dialog`, so focus is
55
+ trapped, Escape closes, and the page behind is inert; set `--ldt-drawer-size` for the measure
56
+ across the anchored edge.
57
+ -->
58
+ <DialogPrimitive.Root bind:open {onOpenChange}>
59
+ {#if triggerChild}<DialogPrimitive.Trigger
60
+ >{#snippet child(childProps)}{@render triggerChild(
61
+ childProps
62
+ )}{/snippet}</DialogPrimitive.Trigger
63
+ >{:else if trigger}<DialogPrimitive.Trigger class={triggerClass}
64
+ >{@render trigger()}</DialogPrimitive.Trigger
65
+ >{/if}
66
+ <DialogPrimitive.Portal>
67
+ <DialogPrimitive.Overlay class={cx('ldt-overlay', overlayClass)} />
68
+ <DialogPrimitive.Content
69
+ bind:ref
70
+ class={cx('ldt-drawer', `ldt-drawer--${side}`, className)}
71
+ {...rest}
72
+ >
73
+ <header class="ldt-drawer__header">
74
+ <div>
75
+ <DialogPrimitive.Title level={headingLevel} class="ldt-drawer__title"
76
+ >{title}</DialogPrimitive.Title
77
+ >{#if description}<DialogPrimitive.Description class="ldt-drawer__description"
78
+ >{description}</DialogPrimitive.Description
79
+ >{/if}
80
+ </div>
81
+ {#if showClose}<DialogPrimitive.Close
82
+ class="ldt-button ldt-icon-button ldt-button--ghost"
83
+ aria-label={closeLabel}>×</DialogPrimitive.Close
84
+ >{/if}
85
+ </header>
86
+ <div class="ldt-drawer__body ldt-scrollbar">{@render children()}</div>
87
+ {#if footer}<footer class="ldt-drawer__footer">{@render footer()}</footer>{/if}
88
+ </DialogPrimitive.Content>
89
+ </DialogPrimitive.Portal>
90
+ </DialogPrimitive.Root>
@@ -0,0 +1,28 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { DialogContentProps } from 'bits-ui';
3
+ import type { HeadingLevel, TriggerChildProps } from '../types.js';
4
+ type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
5
+ open?: boolean;
6
+ title: string;
7
+ description?: string;
8
+ /** Edge the panel is anchored to. `left`/`right` size by width, `top`/`bottom` by height. */
9
+ side?: 'left' | 'right' | 'top' | 'bottom';
10
+ /** Trigger contents, rendered inside a button styled with `triggerClass`. */
11
+ trigger?: Snippet;
12
+ /** Full control of the trigger element — receives the props Bits needs on it. */
13
+ triggerChild?: Snippet<[TriggerChildProps]>;
14
+ triggerClass?: string;
15
+ children: Snippet;
16
+ footer?: Snippet;
17
+ headingLevel?: HeadingLevel;
18
+ showClose?: boolean;
19
+ closeLabel?: string;
20
+ /** `class` lands on the portaled Content element. */
21
+ class?: string;
22
+ overlayClass?: string;
23
+ onOpenChange?: (open: boolean) => void;
24
+ ref?: HTMLElement | null;
25
+ };
26
+ declare const Drawer: import("svelte").Component<Props, {}, "ref" | "open">;
27
+ type Drawer = ReturnType<typeof Drawer>;
28
+ export default Drawer;
@@ -58,13 +58,13 @@
58
58
  {#if item.href}
59
59
  <a {...props} href={item.href}
60
60
  >{#if item.icon}{@render item.icon()}{/if}<span>{item.label}</span>{#if item.hint}<span
61
- aria-hidden="true">{item.hint}</span
61
+ class="ldt-menu__hint">{item.hint}</span
62
62
  >{/if}</a
63
63
  >
64
64
  {:else}
65
65
  <button {...props} type="button"
66
66
  >{#if item.icon}{@render item.icon()}{/if}<span>{item.label}</span>{#if item.hint}<span
67
- aria-hidden="true">{item.hint}</span
67
+ class="ldt-menu__hint">{item.hint}</span
68
68
  >{/if}</button
69
69
  >
70
70
  {/if}
@@ -2,7 +2,7 @@ import { DropdownMenu as MenuPrimitive } from 'bits-ui';
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { Alignment, MenuItem, Placement, TriggerChildProps } from '../types.js';
4
4
  declare function $$render<T extends string = string>(): {
5
- props: Omit<MenuPrimitive.ContentProps, "class" | "children" | "child"> & {
5
+ props: Omit<MenuPrimitive.ContentProps, "children" | "class" | "child"> & {
6
6
  open?: boolean;
7
7
  /** Heading rendered above `items`, associated with them via a menu group. */
8
8
  groupLabel?: string;
@@ -0,0 +1,40 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import type { HeadingLevel } from '../types.js';
5
+ import { cx } from '../utils.js';
6
+
7
+ interface Props extends HTMLAttributes<HTMLDivElement> {
8
+ title: string;
9
+ /** What to do about it. An empty state without a next step is just a blank screen. */
10
+ description?: string;
11
+ /** Leading visual, sized by the consumer. */
12
+ icon?: Snippet;
13
+ /** Buttons or links offering the next step. */
14
+ actions?: Snippet;
15
+ headingLevel?: HeadingLevel;
16
+ class?: string;
17
+ children?: Snippet;
18
+ ref?: HTMLDivElement | null;
19
+ }
20
+
21
+ let {
22
+ title,
23
+ description,
24
+ icon,
25
+ actions,
26
+ headingLevel = 3,
27
+ class: className,
28
+ children,
29
+ ref = $bindable(null),
30
+ ...rest
31
+ }: Props = $props();
32
+ </script>
33
+
34
+ <div bind:this={ref} class={cx('ldt-empty-state', className)} {...rest}>
35
+ {#if icon}<div class="ldt-empty-state__icon" aria-hidden="true">{@render icon()}</div>{/if}
36
+ <svelte:element this={`h${headingLevel}`} class="ldt-empty-state__title">{title}</svelte:element>
37
+ {#if description}<p class="ldt-empty-state__description">{description}</p>{/if}
38
+ {#if children}{@render children()}{/if}
39
+ {#if actions}<div class="ldt-empty-state__actions">{@render actions()}</div>{/if}
40
+ </div>
@@ -0,0 +1,19 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { HeadingLevel } from '../types.js';
4
+ interface Props extends HTMLAttributes<HTMLDivElement> {
5
+ title: string;
6
+ /** What to do about it. An empty state without a next step is just a blank screen. */
7
+ description?: string;
8
+ /** Leading visual, sized by the consumer. */
9
+ icon?: Snippet;
10
+ /** Buttons or links offering the next step. */
11
+ actions?: Snippet;
12
+ headingLevel?: HeadingLevel;
13
+ class?: string;
14
+ children?: Snippet;
15
+ ref?: HTMLDivElement | null;
16
+ }
17
+ declare const EmptyState: import("svelte").Component<Props, {}, "ref">;
18
+ type EmptyState = ReturnType<typeof EmptyState>;
19
+ export default EmptyState;
@@ -0,0 +1,60 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLFieldsetAttributes } from 'svelte/elements';
4
+ import { cx } from '../utils.js';
5
+
6
+ interface Props extends HTMLFieldsetAttributes {
7
+ /** Defaults to an SSR-stable generated id, used to wire the description and error. */
8
+ id?: string;
9
+ /** Visible group label, rendered as a `<legend>`. */
10
+ legend: string;
11
+ description?: string;
12
+ error?: string;
13
+ optional?: boolean;
14
+ /** Text of the "optional" marker, for localisation. */
15
+ optionalText?: string;
16
+ class?: string;
17
+ children: Snippet;
18
+ ref?: HTMLFieldSetElement | null;
19
+ }
20
+
21
+ const generatedId = $props.id();
22
+
23
+ let {
24
+ id = generatedId,
25
+ legend,
26
+ description,
27
+ error,
28
+ optional = false,
29
+ optionalText = 'Optional',
30
+ class: className,
31
+ children,
32
+ ref = $bindable(null),
33
+ ...rest
34
+ }: Props = $props();
35
+
36
+ const describedBy = $derived(
37
+ [description && `${id}-description`, error && `${id}-error`].filter(Boolean).join(' ') ||
38
+ undefined
39
+ );
40
+ </script>
41
+
42
+ <!--
43
+ `Field` wires one control; `Fieldset` wires a group of them. The description and error hang off
44
+ the `<fieldset>` rather than off each control: grouping context is announced once on entry, so
45
+ a shared message stays shared instead of repeating on every checkbox in the set.
46
+ -->
47
+ <fieldset
48
+ bind:this={ref}
49
+ {id}
50
+ class={cx('ldt-fieldset', className)}
51
+ aria-describedby={describedBy}
52
+ {...rest}
53
+ >
54
+ <legend class="ldt-legend"
55
+ ><span>{legend}</span>{#if optional}<small>{optionalText}</small>{/if}</legend
56
+ >
57
+ <div class="ldt-fieldset__body">{@render children()}</div>
58
+ {#if description}<p class="ldt-field__description" id={`${id}-description`}>{description}</p>{/if}
59
+ {#if error}<p class="ldt-field__error" id={`${id}-error`} role="alert">{error}</p>{/if}
60
+ </fieldset>
@@ -0,0 +1,19 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLFieldsetAttributes } from 'svelte/elements';
3
+ interface Props extends HTMLFieldsetAttributes {
4
+ /** Defaults to an SSR-stable generated id, used to wire the description and error. */
5
+ id?: string;
6
+ /** Visible group label, rendered as a `<legend>`. */
7
+ legend: string;
8
+ description?: string;
9
+ error?: string;
10
+ optional?: boolean;
11
+ /** Text of the "optional" marker, for localisation. */
12
+ optionalText?: string;
13
+ class?: string;
14
+ children: Snippet;
15
+ ref?: HTMLFieldSetElement | null;
16
+ }
17
+ declare const Fieldset: import("svelte").Component<Props, {}, "ref">;
18
+ type Fieldset = ReturnType<typeof Fieldset>;
19
+ export default Fieldset;
@@ -34,7 +34,7 @@
34
34
  {disabled}
35
35
  class={cx(
36
36
  'ldt-button ldt-icon-button',
37
- `ldt-button--${variant}`,
37
+ variant !== 'default' && `ldt-button--${variant}`,
38
38
  size !== 'md' && `ldt-button--${size}`,
39
39
  className
40
40
  )}
@@ -6,6 +6,6 @@ interface Props extends Omit<HTMLInputAttributes, 'value'> {
6
6
  class?: string;
7
7
  ref?: HTMLInputElement | null;
8
8
  }
9
- declare const Input: import("svelte").Component<Props, {}, "ref" | "value">;
9
+ declare const Input: import("svelte").Component<Props, {}, "value" | "ref">;
10
10
  type Input = ReturnType<typeof Input>;
11
11
  export default Input;
@@ -1,10 +1,11 @@
1
1
  <script lang="ts" generics="T extends string = string">
2
2
  import type { Snippet } from 'svelte';
3
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
4
  import DropdownMenu from './DropdownMenu.svelte';
4
5
  import type { Alignment, MenuItem, Placement } from '../types.js';
5
6
  import { cx } from '../utils.js';
6
7
 
7
- interface Props {
8
+ interface Props extends Omit<HTMLButtonAttributes, 'children' | 'class' | 'type'> {
8
9
  open?: boolean;
9
10
  /** Trigger text. */
10
11
  label: string;
@@ -36,11 +37,15 @@
36
37
  menuClass,
37
38
  onSelect,
38
39
  onOpenChange,
40
+ ...rest
39
41
  }: Props = $props();
40
42
  </script>
41
43
 
42
44
  {#snippet navTrigger({ props }: { props: Record<string, unknown> })}
45
+ <!-- `rest` first: the menu's trigger wiring (from `props`) and this component's own
46
+ attributes are policy and must win over forwarded attributes. -->
43
47
  <button
48
+ {...rest}
44
49
  {...props}
45
50
  type="button"
46
51
  class={cx('ldt-nav-menu', active && 'ldt-nav-menu--active', className)}
@@ -1,7 +1,8 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
2
3
  import type { Alignment, MenuItem, Placement } from '../types.js';
3
4
  declare function $$render<T extends string = string>(): {
4
- props: {
5
+ props: Omit<HTMLButtonAttributes, "children" | "class" | "type"> & {
5
6
  open?: boolean;
6
7
  /** Trigger text. */
7
8
  label: string;
@@ -29,7 +29,9 @@
29
29
  decrementLabel = (name) => `Decrease ${name}`,
30
30
  incrementLabel = (name) => `Increase ${name}`,
31
31
  boxed = false,
32
+ disabled = false,
32
33
  onValueChange,
34
+ onchange,
33
35
  class: className,
34
36
  inputClass,
35
37
  ref = $bindable(null),
@@ -52,6 +54,7 @@
52
54
  <button
53
55
  class="ldt-number-field__button"
54
56
  type="button"
57
+ {disabled}
55
58
  aria-label={decrementLabel(label)}
56
59
  aria-disabled={atMin || undefined}
57
60
  onclick={() => !atMin && set(value - step)}>−</button
@@ -64,13 +67,21 @@
64
67
  min={Number.isFinite(min) ? min : undefined}
65
68
  max={Number.isFinite(max) ? max : undefined}
66
69
  {step}
70
+ {disabled}
67
71
  aria-label={label}
68
72
  {...rest}
69
- onchange={(event) => set(Number(event.currentTarget.value))}
73
+ onchange={(event) => {
74
+ // `Number('')` is 0, not NaN — a cleared field must hit the documented fallback,
75
+ // not silently clamp to zero.
76
+ const raw = event.currentTarget.value;
77
+ set(raw === '' ? NaN : Number(raw));
78
+ onchange?.(event);
79
+ }}
70
80
  />
71
81
  <button
72
82
  class="ldt-number-field__button"
73
83
  type="button"
84
+ {disabled}
74
85
  aria-label={incrementLabel(label)}
75
86
  aria-disabled={atMax || undefined}
76
87
  onclick={() => !atMax && set(value + step)}>+</button
@@ -16,6 +16,6 @@ interface Props extends Omit<HTMLInputAttributes, 'type' | 'value' | 'min' | 'ma
16
16
  inputClass?: string;
17
17
  ref?: HTMLInputElement | null;
18
18
  }
19
- declare const NumberField: import("svelte").Component<Props, {}, "ref" | "value">;
19
+ declare const NumberField: import("svelte").Component<Props, {}, "value" | "ref">;
20
20
  type NumberField = ReturnType<typeof NumberField>;
21
21
  export default NumberField;
@@ -0,0 +1,86 @@
1
+ <script lang="ts">
2
+ import { Pagination as PaginationPrimitive } from 'bits-ui';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cx } from '../utils.js';
5
+
6
+ interface Props extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
7
+ /** Total number of items being paged through — not the number of pages. */
8
+ count: number;
9
+ perPage?: number;
10
+ /** 1-based current page. */
11
+ page?: number;
12
+ /** Pages kept either side of the current one before the trail turns into an ellipsis. */
13
+ siblingCount?: number;
14
+ /** Accessible name of the landmark. */
15
+ label?: string;
16
+ previousLabel?: string;
17
+ nextLabel?: string;
18
+ /** Accessible name for a page button, e.g. `(page) => `Page ${page}``. */
19
+ pageLabel?: (page: number) => string;
20
+ onPageChange?: (page: number) => void;
21
+ class?: string;
22
+ ref?: HTMLElement | null;
23
+ }
24
+
25
+ let {
26
+ count,
27
+ perPage = 10,
28
+ page = $bindable(1),
29
+ siblingCount = 1,
30
+ label = 'Pagination',
31
+ previousLabel = 'Previous',
32
+ nextLabel = 'Next',
33
+ pageLabel = (value: number) => `Page ${value}`,
34
+ onPageChange,
35
+ class: className,
36
+ ref = $bindable(null),
37
+ ...rest
38
+ }: Props = $props();
39
+ </script>
40
+
41
+ <!--
42
+ Bits' Root is a `<div>`; the landmark is ours, so the control set is reachable as "Pagination"
43
+ from a rotor without the page having to add one.
44
+ -->
45
+ <nav bind:this={ref} class={cx('ldt-pagination', className)} aria-label={label} {...rest}>
46
+ <PaginationPrimitive.Root
47
+ {count}
48
+ {perPage}
49
+ {siblingCount}
50
+ bind:page
51
+ onPageChange={(next) => onPageChange?.(next)}
52
+ class="ldt-pagination__list"
53
+ >
54
+ {#snippet children({ pages, currentPage })}
55
+ <PaginationPrimitive.PrevButton class="ldt-button ldt-button--quiet ldt-button--sm"
56
+ >{previousLabel}</PaginationPrimitive.PrevButton
57
+ >
58
+ {#each pages as item (item.key)}
59
+ {#if item.type === 'ellipsis'}
60
+ <!-- Presentational: the gap is a rendering device, and "…" read aloud between page
61
+ numbers is noise. The page buttons either side already say where the trail jumps. -->
62
+ <span class="ldt-pagination__ellipsis" aria-hidden="true">…</span>
63
+ {:else}
64
+ <!--
65
+ Rendered through Bits' `child` snippet rather than as a styled primitive: the
66
+ primitive merges its own `aria-label` last and would overwrite `pageLabel`. Spreading
67
+ `props` first leaves its behaviour intact while the naming stays ours.
68
+ -->
69
+ <PaginationPrimitive.Page page={item}>
70
+ {#snippet child({ props })}
71
+ <button
72
+ {...props}
73
+ class="ldt-button ldt-button--quiet ldt-button--sm ldt-pagination__page"
74
+ aria-label={pageLabel(item.value)}
75
+ aria-current={item.value === currentPage ? 'page' : undefined}>{item.value}</button
76
+ >
77
+ {/snippet}
78
+ </PaginationPrimitive.Page>
79
+ {/if}
80
+ {/each}
81
+ <PaginationPrimitive.NextButton class="ldt-button ldt-button--quiet ldt-button--sm"
82
+ >{nextLabel}</PaginationPrimitive.NextButton
83
+ >
84
+ {/snippet}
85
+ </PaginationPrimitive.Root>
86
+ </nav>
@@ -0,0 +1,22 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ interface Props extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
3
+ /** Total number of items being paged through — not the number of pages. */
4
+ count: number;
5
+ perPage?: number;
6
+ /** 1-based current page. */
7
+ page?: number;
8
+ /** Pages kept either side of the current one before the trail turns into an ellipsis. */
9
+ siblingCount?: number;
10
+ /** Accessible name of the landmark. */
11
+ label?: string;
12
+ previousLabel?: string;
13
+ nextLabel?: string;
14
+ /** Accessible name for a page button, e.g. `(page) => `Page ${page}``. */
15
+ pageLabel?: (page: number) => string;
16
+ onPageChange?: (page: number) => void;
17
+ class?: string;
18
+ ref?: HTMLElement | null;
19
+ }
20
+ declare const Pagination: import("svelte").Component<Props, {}, "ref" | "page">;
21
+ type Pagination = ReturnType<typeof Pagination>;
22
+ export default Pagination;
@@ -14,7 +14,7 @@ declare function $$render<T extends string = string>(): {
14
14
  ref?: HTMLFieldSetElement | null;
15
15
  };
16
16
  exports: {};
17
- bindings: "ref" | "value";
17
+ bindings: "value" | "ref";
18
18
  slots: {};
19
19
  events: {};
20
20
  };
@@ -22,7 +22,7 @@ declare class __sveltets_Render<T extends string = string> {
22
22
  props(): ReturnType<typeof $$render<T>>['props'];
23
23
  events(): ReturnType<typeof $$render<T>>['events'];
24
24
  slots(): ReturnType<typeof $$render<T>>['slots'];
25
- bindings(): "ref" | "value";
25
+ bindings(): "value" | "ref";
26
26
  exports(): {};
27
27
  }
28
28
  interface $$IsomorphicComponent {
@@ -18,7 +18,7 @@ declare function $$render<T extends string = string>(): {
18
18
  ref?: HTMLSelectElement | null;
19
19
  };
20
20
  exports: {};
21
- bindings: "ref" | "value";
21
+ bindings: "value" | "ref";
22
22
  slots: {};
23
23
  events: {};
24
24
  };
@@ -26,7 +26,7 @@ declare class __sveltets_Render<T extends string = string> {
26
26
  props(): ReturnType<typeof $$render<T>>['props'];
27
27
  events(): ReturnType<typeof $$render<T>>['events'];
28
28
  slots(): ReturnType<typeof $$render<T>>['slots'];
29
- bindings(): "ref" | "value";
29
+ bindings(): "value" | "ref";
30
30
  exports(): {};
31
31
  }
32
32
  interface $$IsomorphicComponent {
@@ -8,6 +8,19 @@
8
8
  caption?: string;
9
9
  /** Accessible name for the scroll region when there is no `caption`. */
10
10
  regionLabel?: string;
11
+ /**
12
+ * Pins the header row while the body scrolls. Needs a bounded scroll container — set
13
+ * `--ldt-table-height` on the wrapper, or the page scrolls instead and nothing sticks.
14
+ */
15
+ sticky?: boolean;
16
+ /**
17
+ * Rendered in place of the rows, as a single full-width cell. Pass it only when there is
18
+ * nothing to show — `empty={rows.length === 0 ? nothingYet : undefined}` — since the
19
+ * component cannot see inside `children` to count rows.
20
+ */
21
+ empty?: Snippet;
22
+ /** Columns the empty cell spans. Defaults to the whole row. */
23
+ columns?: number;
11
24
  /** `class` lands on the `<table>`; use `wrapperClass` for the scroll container. */
12
25
  class?: string;
13
26
  wrapperClass?: string;
@@ -19,6 +32,9 @@
19
32
  let {
20
33
  caption,
21
34
  regionLabel = 'Data table',
35
+ sticky = false,
36
+ empty,
37
+ columns,
22
38
  class: className,
23
39
  wrapperClass,
24
40
  children,
@@ -32,12 +48,23 @@
32
48
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
33
49
  <div
34
50
  bind:this={wrapperRef}
35
- class={cx('ldt-table-wrap', wrapperClass)}
51
+ class={cx('ldt-table-wrap', sticky && 'ldt-table-wrap--sticky', wrapperClass)}
36
52
  role="region"
37
53
  tabindex="0"
38
54
  aria-label={caption ?? regionLabel}
39
55
  >
40
- <table bind:this={ref} class={cx('ldt-table', className)} {...rest}>
41
- {#if caption}<caption class="ldt-sr-only">{caption}</caption>{/if}{@render children()}
56
+ <table
57
+ bind:this={ref}
58
+ class={cx('ldt-table', sticky && 'ldt-table--sticky', className)}
59
+ {...rest}
60
+ >
61
+ {#if caption}<caption class="ldt-sr-only">{caption}</caption
62
+ >{/if}{@render children()}{#if empty}<tbody class="ldt-table__empty">
63
+ <tr>
64
+ <!-- `colspan` defaults to the row-spanning maximum so the cell never leaves a gap
65
+ beside it, whatever the column count turns out to be. -->
66
+ <td colspan={columns ?? 1000}>{@render empty()}</td>
67
+ </tr>
68
+ </tbody>{/if}
42
69
  </table>
43
70
  </div>
@@ -5,6 +5,19 @@ interface Props extends HTMLTableAttributes {
5
5
  caption?: string;
6
6
  /** Accessible name for the scroll region when there is no `caption`. */
7
7
  regionLabel?: string;
8
+ /**
9
+ * Pins the header row while the body scrolls. Needs a bounded scroll container — set
10
+ * `--ldt-table-height` on the wrapper, or the page scrolls instead and nothing sticks.
11
+ */
12
+ sticky?: boolean;
13
+ /**
14
+ * Rendered in place of the rows, as a single full-width cell. Pass it only when there is
15
+ * nothing to show — `empty={rows.length === 0 ? nothingYet : undefined}` — since the
16
+ * component cannot see inside `children` to count rows.
17
+ */
18
+ empty?: Snippet;
19
+ /** Columns the empty cell spans. Defaults to the whole row. */
20
+ columns?: number;
8
21
  /** `class` lands on the `<table>`; use `wrapperClass` for the scroll container. */
9
22
  class?: string;
10
23
  wrapperClass?: string;