@loidolt/theme-svelte 0.2.0 → 0.4.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 (92) hide show
  1. package/README.md +21 -0
  2. package/dist/components/Accordion.svelte +100 -0
  3. package/dist/components/Accordion.svelte.d.ts +50 -0
  4. package/dist/components/Alert.svelte +6 -3
  5. package/dist/components/Alert.svelte.d.ts +3 -2
  6. package/dist/components/AlertDialog.svelte +130 -0
  7. package/dist/components/AlertDialog.svelte.d.ts +35 -0
  8. package/dist/components/AppShell.svelte +2 -1
  9. package/dist/components/AppShell.svelte.d.ts +1 -0
  10. package/dist/components/Badge.svelte +9 -2
  11. package/dist/components/Badge.svelte.d.ts +2 -1
  12. package/dist/components/Brand.svelte +29 -15
  13. package/dist/components/Brand.svelte.d.ts +8 -4
  14. package/dist/components/Breadcrumbs.svelte +53 -0
  15. package/dist/components/Breadcrumbs.svelte.d.ts +18 -0
  16. package/dist/components/Button.svelte +43 -21
  17. package/dist/components/Button.svelte.d.ts +9 -4
  18. package/dist/components/Card.svelte +23 -5
  19. package/dist/components/Card.svelte.d.ts +11 -4
  20. package/dist/components/CodeBlock.svelte +55 -0
  21. package/dist/components/CodeBlock.svelte.d.ts +14 -0
  22. package/dist/components/CommentList.svelte +67 -0
  23. package/dist/components/CommentList.svelte.d.ts +53 -0
  24. package/dist/components/Dialog.svelte +25 -3
  25. package/dist/components/Dialog.svelte.d.ts +4 -1
  26. package/dist/components/Drawer.svelte +105 -0
  27. package/dist/components/Drawer.svelte.d.ts +31 -0
  28. package/dist/components/DropdownMenu.svelte +2 -2
  29. package/dist/components/DropdownMenu.svelte.d.ts +1 -1
  30. package/dist/components/EmptyState.svelte +40 -0
  31. package/dist/components/EmptyState.svelte.d.ts +19 -0
  32. package/dist/components/Fieldset.svelte +60 -0
  33. package/dist/components/Fieldset.svelte.d.ts +19 -0
  34. package/dist/components/FileInput.svelte +47 -0
  35. package/dist/components/FileInput.svelte.d.ts +15 -0
  36. package/dist/components/Filmstrip.svelte +72 -0
  37. package/dist/components/Filmstrip.svelte.d.ts +50 -0
  38. package/dist/components/FloatingBar.svelte +43 -0
  39. package/dist/components/FloatingBar.svelte.d.ts +16 -0
  40. package/dist/components/IconButton.svelte +1 -1
  41. package/dist/components/Input.svelte.d.ts +1 -1
  42. package/dist/components/ListRow.svelte +50 -0
  43. package/dist/components/ListRow.svelte.d.ts +18 -0
  44. package/dist/components/Marker.svelte +38 -0
  45. package/dist/components/Marker.svelte.d.ts +14 -0
  46. package/dist/components/NavMenu.svelte +6 -1
  47. package/dist/components/NavMenu.svelte.d.ts +2 -1
  48. package/dist/components/NumberField.svelte +12 -1
  49. package/dist/components/NumberField.svelte.d.ts +1 -1
  50. package/dist/components/Pagination.svelte +86 -0
  51. package/dist/components/Pagination.svelte.d.ts +22 -0
  52. package/dist/components/Popover.svelte +24 -3
  53. package/dist/components/Popover.svelte.d.ts +4 -1
  54. package/dist/components/RadioGroup.svelte.d.ts +2 -2
  55. package/dist/components/RecordStepper.svelte +58 -0
  56. package/dist/components/RecordStepper.svelte.d.ts +18 -0
  57. package/dist/components/SegmentedNav.svelte +53 -0
  58. package/dist/components/SegmentedNav.svelte.d.ts +22 -0
  59. package/dist/components/Select.svelte.d.ts +2 -2
  60. package/dist/components/Stat.svelte +41 -0
  61. package/dist/components/Stat.svelte.d.ts +17 -0
  62. package/dist/components/StatusDot.svelte +27 -0
  63. package/dist/components/StatusDot.svelte.d.ts +12 -0
  64. package/dist/components/SwatchGroup.svelte +69 -0
  65. package/dist/components/SwatchGroup.svelte.d.ts +44 -0
  66. package/dist/components/Switch.svelte +19 -0
  67. package/dist/components/Table.svelte +30 -3
  68. package/dist/components/Table.svelte.d.ts +13 -0
  69. package/dist/components/TableHeader.svelte +72 -0
  70. package/dist/components/TableHeader.svelte.d.ts +24 -0
  71. package/dist/components/Tabs.svelte +23 -5
  72. package/dist/components/Tabs.svelte.d.ts +4 -3
  73. package/dist/components/Textarea.svelte.d.ts +1 -1
  74. package/dist/components/ThemeToggle.svelte +133 -0
  75. package/dist/components/ThemeToggle.svelte.d.ts +20 -0
  76. package/dist/components/Thumbnail.svelte +51 -0
  77. package/dist/components/Thumbnail.svelte.d.ts +19 -0
  78. package/dist/components/ToggleGroup.svelte +107 -0
  79. package/dist/components/ToggleGroup.svelte.d.ts +46 -0
  80. package/dist/components/TooltipProvider.svelte +1 -0
  81. package/dist/components/Workspace.svelte +27 -6
  82. package/dist/components/Workspace.svelte.d.ts +12 -0
  83. package/dist/index.d.ts +29 -2
  84. package/dist/index.js +26 -1
  85. package/dist/media.svelte.d.ts +48 -0
  86. package/dist/media.svelte.js +69 -0
  87. package/dist/theme.svelte.d.ts +86 -0
  88. package/dist/theme.svelte.js +166 -0
  89. package/dist/toaster.svelte.d.ts +2 -0
  90. package/dist/toaster.svelte.js +51 -9
  91. package/dist/types.d.ts +21 -0
  92. package/package.json +10 -8
@@ -1,3 +1,4 @@
1
+ import { onDestroy } from 'svelte';
1
2
  /**
2
3
  * Queue behind `Toast` / `ToastViewport`: auto-dismiss with the remaining time preserved
3
4
  * across `pause()`/`resume()`, so hovering a toast does not lose the countdown.
@@ -13,24 +14,36 @@
13
14
  */
14
15
  export function createToaster(options = {}) {
15
16
  const { duration: defaultDuration = 5000, max = 5 } = options;
17
+ if (!Number.isInteger(max) || max < 1) {
18
+ throw new RangeError('createToaster: `max` must be a positive integer');
19
+ }
20
+ const validDuration = (duration) => duration === Infinity || (Number.isFinite(duration) && duration >= 0);
21
+ if (!validDuration(defaultDuration)) {
22
+ throw new RangeError('createToaster: `duration` must be non-negative or Infinity');
23
+ }
16
24
  const toasts = $state([]);
17
25
  // Plain Map on purpose: these are timer handles, not UI state, and nothing renders from them.
26
+ // `startedAt` lives per entry: toasts are pushed at different moments, so a shared start
27
+ // time would corrupt every other toast's remaining time on pause. `timeout: null` marks a
28
+ // paused entry holding only its remaining time.
18
29
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
19
30
  const timers = new Map();
20
- let startedAt = 0;
21
31
  let paused = false;
22
32
  let counter = 0;
23
33
  function clearTimer(id) {
24
34
  const timer = timers.get(id);
25
- if (timer)
35
+ if (timer?.timeout != null)
26
36
  clearTimeout(timer.timeout);
27
37
  timers.delete(id);
28
38
  }
29
39
  function arm(id, remaining) {
30
40
  if (!Number.isFinite(remaining) || remaining <= 0)
31
41
  return;
32
- startedAt = Date.now();
33
- timers.set(id, { timeout: setTimeout(() => dismiss(id), remaining), remaining });
42
+ timers.set(id, {
43
+ timeout: setTimeout(() => dismiss(id), remaining),
44
+ remaining,
45
+ startedAt: Date.now(),
46
+ });
34
47
  }
35
48
  function dismiss(id) {
36
49
  clearTimer(id);
@@ -39,16 +52,36 @@ export function createToaster(options = {}) {
39
52
  toasts.splice(index, 1);
40
53
  }
41
54
  function push(toast) {
55
+ const duration = toast.duration ?? defaultDuration;
56
+ if (!validDuration(duration)) {
57
+ throw new RangeError('createToaster: toast `duration` must be non-negative or Infinity');
58
+ }
42
59
  const id = `ldt-toast-${++counter}`;
43
60
  toasts.push({ ...toast, id });
44
61
  while (toasts.length > max)
45
62
  dismiss(toasts[0].id);
46
63
  if (!paused)
47
- arm(id, toast.duration ?? defaultDuration);
48
- else
49
- timers.set(id, { timeout: 0, remaining: toast.duration ?? defaultDuration });
64
+ arm(id, duration);
65
+ else {
66
+ timers.set(id, { timeout: null, remaining: duration, startedAt: 0 });
67
+ }
50
68
  return id;
51
69
  }
70
+ const destroy = () => {
71
+ for (const timer of timers.values()) {
72
+ if (timer.timeout !== null)
73
+ clearTimeout(timer.timeout);
74
+ }
75
+ timers.clear();
76
+ toasts.splice(0);
77
+ paused = false;
78
+ };
79
+ try {
80
+ onDestroy(destroy);
81
+ }
82
+ catch {
83
+ /* module scope: the caller owns `destroy()` */
84
+ }
52
85
  return {
53
86
  get toasts() {
54
87
  return toasts;
@@ -63,10 +96,18 @@ export function createToaster(options = {}) {
63
96
  if (paused)
64
97
  return;
65
98
  paused = true;
66
- const elapsed = Date.now() - startedAt;
99
+ const now = Date.now();
67
100
  for (const [id, timer] of timers) {
101
+ if (timer.timeout === null)
102
+ continue;
68
103
  clearTimeout(timer.timeout);
69
- timers.set(id, { timeout: 0, remaining: Math.max(0, timer.remaining - elapsed) });
104
+ timers.set(id, {
105
+ timeout: null,
106
+ // Floor of 1ms: a toast that expired while paused should dismiss right after
107
+ // resume — `0` is the "keep until dismissed" sentinel and would strand it.
108
+ remaining: Math.max(1, timer.remaining - (now - timer.startedAt)),
109
+ startedAt: 0,
110
+ });
70
111
  }
71
112
  },
72
113
  resume() {
@@ -78,5 +119,6 @@ export function createToaster(options = {}) {
78
119
  arm(id, timer.remaining);
79
120
  }
80
121
  },
122
+ destroy,
81
123
  };
82
124
  }
package/dist/types.d.ts CHANGED
@@ -8,6 +8,16 @@ export type BadgeVariant = 'default' | 'accent' | StatusVariant;
8
8
  /** Shared control scale. Dialogs extend it with `xl`. */
9
9
  export type ControlSize = 'sm' | 'md' | 'lg';
10
10
  export type DialogSize = ControlSize | 'xl';
11
+ /**
12
+ * Badges default to `inline`: a compact chip sized to the text it annotates, for table cells and
13
+ * running copy. The `ControlSize` steps match the button scale so a badge standing in a row of
14
+ * controls shares their height instead of floating at half of it.
15
+ */
16
+ export type BadgeSize = 'inline' | ControlSize;
17
+ /** Column sort state. The values are `aria-sort`'s, so they go straight onto the `<th>`. */
18
+ export type SortDirection = 'ascending' | 'descending' | 'none';
19
+ /** Horizontal text alignment for a table column. */
20
+ export type ColumnAlign = 'start' | 'center' | 'end';
11
21
  export type Placement = 'top' | 'right' | 'bottom' | 'left';
12
22
  export type Alignment = 'start' | 'center' | 'end';
13
23
  export type Orientation = 'horizontal' | 'vertical';
@@ -20,6 +30,17 @@ export interface Option<T extends string = string> {
20
30
  }
21
31
  /** `Option` is the single vocabulary for choice lists; `NavItem` is kept as a readable alias. */
22
32
  export type NavItem<T extends string = string> = Option<T>;
33
+ /** One step in a breadcrumb trail. The final crumb is the current page and takes no `href`. */
34
+ export interface Crumb {
35
+ label: string;
36
+ href?: string;
37
+ }
38
+ /** One `Accordion` panel: its heading and the value that identifies it. */
39
+ export interface Disclosure<T extends string = string> {
40
+ value: T;
41
+ title: string;
42
+ disabled?: boolean;
43
+ }
23
44
  /** A `DropdownMenu` row. Renders as a link when `href` is set. */
24
45
  export interface MenuItem<T extends string = string> extends Option<T> {
25
46
  href?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loidolt/theme-svelte",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Accessible Svelte 5 components for Loidolt applications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,31 +21,33 @@
21
21
  "sideEffects": [
22
22
  "**/*.css"
23
23
  ],
24
- "engines": {
25
- "node": ">=22"
26
- },
27
24
  "scripts": {
28
25
  "build": "svelte-package -i src/lib -o dist && cp src/styles.css dist/styles.css",
26
+ "prepack": "npm run build",
29
27
  "typecheck": "svelte-check --tsconfig ./tsconfig.json",
30
- "test": "vitest run",
28
+ "test": "vitest run && vitest run --config vitest.config.ssr.ts",
29
+ "test:coverage": "vitest run --coverage",
31
30
  "test:a11y": "vitest run tests/a11y.test.ts",
32
31
  "clean": "rimraf dist"
33
32
  },
34
33
  "dependencies": {
35
- "@loidolt/theme-styles": "^0.2.0",
36
- "@loidolt/theme-tokens": "^0.2.0",
34
+ "@loidolt/theme-styles": "^0.4.0",
35
+ "@loidolt/theme-tokens": "^0.4.0",
37
36
  "bits-ui": "^2.19.0"
38
37
  },
39
38
  "peerDependencies": {
40
39
  "svelte": "^5.33.0"
41
40
  },
42
41
  "devDependencies": {
42
+ "@axe-core/playwright": "^4.13.0",
43
+ "@playwright/test": "^1.62.1",
43
44
  "@sveltejs/package": "^2.5.8",
44
- "@types/node": "^24.3.0",
45
45
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
46
46
  "@testing-library/jest-dom": "^6.8.0",
47
47
  "@testing-library/svelte": "^5.2.8",
48
48
  "@testing-library/user-event": "^14.6.1",
49
+ "@types/node": "^24.3.0",
50
+ "@vitest/coverage-v8": "^4.1.11",
49
51
  "jsdom": "^27.0.0",
50
52
  "rimraf": "^6.0.1",
51
53
  "svelte": "^5.56.10",