@loidolt/theme-svelte 0.3.0 → 0.5.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 (52) hide show
  1. package/README.md +21 -0
  2. package/dist/components/AlertDialog.svelte +28 -7
  3. package/dist/components/AlertDialog.svelte.d.ts +4 -1
  4. package/dist/components/AppShell.svelte +11 -3
  5. package/dist/components/AppShell.svelte.d.ts +4 -1
  6. package/dist/components/Card.svelte +23 -5
  7. package/dist/components/Card.svelte.d.ts +11 -4
  8. package/dist/components/CodeBlock.svelte +55 -0
  9. package/dist/components/CodeBlock.svelte.d.ts +14 -0
  10. package/dist/components/CommentList.svelte +67 -0
  11. package/dist/components/CommentList.svelte.d.ts +53 -0
  12. package/dist/components/Dialog.svelte +28 -6
  13. package/dist/components/Dialog.svelte.d.ts +4 -1
  14. package/dist/components/Drawer.svelte +21 -6
  15. package/dist/components/Drawer.svelte.d.ts +4 -1
  16. package/dist/components/FileInput.svelte +47 -0
  17. package/dist/components/FileInput.svelte.d.ts +15 -0
  18. package/dist/components/Filmstrip.svelte +72 -0
  19. package/dist/components/Filmstrip.svelte.d.ts +50 -0
  20. package/dist/components/FloatingBar.svelte +43 -0
  21. package/dist/components/FloatingBar.svelte.d.ts +16 -0
  22. package/dist/components/ListRow.svelte +50 -0
  23. package/dist/components/ListRow.svelte.d.ts +18 -0
  24. package/dist/components/Marker.svelte +38 -0
  25. package/dist/components/Marker.svelte.d.ts +14 -0
  26. package/dist/components/Popover.svelte +24 -3
  27. package/dist/components/Popover.svelte.d.ts +4 -1
  28. package/dist/components/RecordStepper.svelte +58 -0
  29. package/dist/components/RecordStepper.svelte.d.ts +18 -0
  30. package/dist/components/SegmentedNav.svelte +53 -0
  31. package/dist/components/SegmentedNav.svelte.d.ts +22 -0
  32. package/dist/components/Stat.svelte +41 -0
  33. package/dist/components/Stat.svelte.d.ts +17 -0
  34. package/dist/components/StatusDot.svelte +27 -0
  35. package/dist/components/StatusDot.svelte.d.ts +12 -0
  36. package/dist/components/SwatchGroup.svelte +69 -0
  37. package/dist/components/SwatchGroup.svelte.d.ts +44 -0
  38. package/dist/components/Switch.svelte +19 -0
  39. package/dist/components/Tabs.svelte +10 -0
  40. package/dist/components/ThemeToggle.svelte +102 -31
  41. package/dist/components/ThemeToggle.svelte.d.ts +5 -10
  42. package/dist/components/Thumbnail.svelte +51 -0
  43. package/dist/components/Thumbnail.svelte.d.ts +19 -0
  44. package/dist/components/ToastViewport.svelte +1 -0
  45. package/dist/components/Workspace.svelte +9 -1
  46. package/dist/components/Workspace.svelte.d.ts +4 -0
  47. package/dist/index.d.ts +13 -0
  48. package/dist/index.js +13 -0
  49. package/dist/theme.svelte.js +17 -7
  50. package/dist/toaster.svelte.d.ts +9 -2
  51. package/dist/toaster.svelte.js +37 -4
  52. package/package.json +8 -7
@@ -138,14 +138,24 @@ export function createTheme(options = {}) {
138
138
  */
139
139
  export function themeScript(options = {}) {
140
140
  const { storageKey = 'loidolt-theme', attribute = 'data-theme', defaultPreference = 'system', defaultScheme = 'light', tag = false, } = options;
141
- // JSON.stringify keeps a caller-supplied key or attribute from breaking out of its literal.
142
- const key = JSON.stringify(storageKey);
143
- const attr = JSON.stringify(attribute);
144
- const fallback = JSON.stringify(defaultPreference);
145
- const scheme = JSON.stringify(defaultScheme);
141
+ // JSON string escaping alone leaves `<` intact, including a literal `</script>` that would end
142
+ // an inline script element. Escaping HTML-significant characters and the two JavaScript line
143
+ // separators keeps every option inside its string literal in both source-only and tagged forms.
144
+ const serialize = (value) => JSON.stringify(value)
145
+ .replaceAll('<', '\\u003c')
146
+ .replaceAll('>', '\\u003e')
147
+ .replaceAll('&', '\\u0026')
148
+ .replaceAll('\u2028', '\\u2028')
149
+ .replaceAll('\u2029', '\\u2029');
150
+ const key = serialize(storageKey);
151
+ const attr = serialize(attribute);
152
+ const fallback = serialize(defaultPreference);
153
+ const scheme = serialize(defaultScheme);
146
154
  const source = `(function(){try{` +
147
- `var p=${key}===null?null:localStorage.getItem(${key});` +
148
- `if(p!=="light"&&p!=="dark"&&p!=="system")p=${fallback};` +
155
+ `var p=${fallback};` +
156
+ // Storage failure must not skip system resolution and attribute application.
157
+ `try{var v=${key}===null?null:localStorage.getItem(${key});` +
158
+ `if(v==="light"||v==="dark"||v==="system")p=v;}catch(e){}` +
149
159
  `var s=${scheme};` +
150
160
  `if(typeof matchMedia==="function")s=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";` +
151
161
  `document.documentElement.setAttribute(${attr},p==="system"?s:p);` +
@@ -10,7 +10,7 @@ export interface ToastRecord extends ToastOptions {
10
10
  id: string;
11
11
  }
12
12
  export interface ToasterOptions {
13
- /** Default `duration` for toasts that do not set one. */
13
+ /** Default `duration` for toasts that do not set one. Defaults to persistent (`0`). */
14
14
  duration?: number;
15
15
  /** Oldest toasts past this count are dropped. */
16
16
  max?: number;
@@ -26,6 +26,8 @@ export interface Toaster {
26
26
  pause(): void;
27
27
  /** Resumes with the time each toast had left — call on pointer leave / focus out. */
28
28
  resume(): void;
29
+ /** Clears the queue and every timer. Automatic for a component-scoped instance. */
30
+ destroy(): void;
29
31
  }
30
32
  /**
31
33
  * Queue behind `Toast` / `ToastViewport`: auto-dismiss with the remaining time preserved
@@ -33,7 +35,12 @@ export interface Toaster {
33
35
  *
34
36
  * ```svelte
35
37
  * const toaster = createToaster();
36
- * <ToastViewport onpointerenter={toaster.pause} onpointerleave={toaster.resume}>
38
+ * <ToastViewport
39
+ * onpointerenter={toaster.pause}
40
+ * onpointerleave={toaster.resume}
41
+ * onfocusin={toaster.pause}
42
+ * onfocusout={toaster.resume}
43
+ * >
37
44
  * {#each toaster.toasts as toast (toast.id)}
38
45
  * <Toast {...toast} onDismiss={() => toaster.dismiss(toast.id)} />
39
46
  * {/each}
@@ -1,10 +1,16 @@
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.
4
5
  *
5
6
  * ```svelte
6
7
  * const toaster = createToaster();
7
- * <ToastViewport onpointerenter={toaster.pause} onpointerleave={toaster.resume}>
8
+ * <ToastViewport
9
+ * onpointerenter={toaster.pause}
10
+ * onpointerleave={toaster.resume}
11
+ * onfocusin={toaster.pause}
12
+ * onfocusout={toaster.resume}
13
+ * >
8
14
  * {#each toaster.toasts as toast (toast.id)}
9
15
  * <Toast {...toast} onDismiss={() => toaster.dismiss(toast.id)} />
10
16
  * {/each}
@@ -12,7 +18,14 @@
12
18
  * ```
13
19
  */
14
20
  export function createToaster(options = {}) {
15
- const { duration: defaultDuration = 5000, max = 5 } = options;
21
+ const { duration: defaultDuration = 0, max = 5 } = options;
22
+ if (!Number.isInteger(max) || max < 1) {
23
+ throw new RangeError('createToaster: `max` must be a positive integer');
24
+ }
25
+ const validDuration = (duration) => duration === Infinity || (Number.isFinite(duration) && duration >= 0);
26
+ if (!validDuration(defaultDuration)) {
27
+ throw new RangeError('createToaster: `duration` must be non-negative or Infinity');
28
+ }
16
29
  const toasts = $state([]);
17
30
  // Plain Map on purpose: these are timer handles, not UI state, and nothing renders from them.
18
31
  // `startedAt` lives per entry: toasts are pushed at different moments, so a shared start
@@ -44,17 +57,36 @@ export function createToaster(options = {}) {
44
57
  toasts.splice(index, 1);
45
58
  }
46
59
  function push(toast) {
60
+ const duration = toast.duration ?? defaultDuration;
61
+ if (!validDuration(duration)) {
62
+ throw new RangeError('createToaster: toast `duration` must be non-negative or Infinity');
63
+ }
47
64
  const id = `ldt-toast-${++counter}`;
48
65
  toasts.push({ ...toast, id });
49
66
  while (toasts.length > max)
50
67
  dismiss(toasts[0].id);
51
68
  if (!paused)
52
- arm(id, toast.duration ?? defaultDuration);
69
+ arm(id, duration);
53
70
  else {
54
- timers.set(id, { timeout: null, remaining: toast.duration ?? defaultDuration, startedAt: 0 });
71
+ timers.set(id, { timeout: null, remaining: duration, startedAt: 0 });
55
72
  }
56
73
  return id;
57
74
  }
75
+ const destroy = () => {
76
+ for (const timer of timers.values()) {
77
+ if (timer.timeout !== null)
78
+ clearTimeout(timer.timeout);
79
+ }
80
+ timers.clear();
81
+ toasts.splice(0);
82
+ paused = false;
83
+ };
84
+ try {
85
+ onDestroy(destroy);
86
+ }
87
+ catch {
88
+ /* module scope: the caller owns `destroy()` */
89
+ }
58
90
  return {
59
91
  get toasts() {
60
92
  return toasts;
@@ -92,5 +124,6 @@ export function createToaster(options = {}) {
92
124
  arm(id, timer.remaining);
93
125
  }
94
126
  },
127
+ destroy,
95
128
  };
96
129
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loidolt/theme-svelte",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Accessible Svelte 5 components for Loidolt applications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,32 +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",
29
26
  "prepack": "npm run build",
30
27
  "typecheck": "svelte-check --tsconfig ./tsconfig.json",
31
28
  "test": "vitest run && vitest run --config vitest.config.ssr.ts",
29
+ "test:coverage": "vitest run --coverage",
32
30
  "test:a11y": "vitest run tests/a11y.test.ts",
33
31
  "clean": "rimraf dist"
34
32
  },
35
33
  "dependencies": {
36
- "@loidolt/theme-styles": "^0.3.0",
37
- "@loidolt/theme-tokens": "^0.3.0",
34
+ "@loidolt/theme-styles": "^0.5.0",
35
+ "@loidolt/theme-tokens": "^0.5.0",
38
36
  "bits-ui": "^2.19.0"
39
37
  },
40
38
  "peerDependencies": {
41
39
  "svelte": "^5.33.0"
42
40
  },
43
41
  "devDependencies": {
42
+ "@axe-core/playwright": "^4.13.0",
43
+ "@playwright/test": "^1.62.1",
44
44
  "@sveltejs/package": "^2.5.8",
45
- "@types/node": "^24.3.0",
46
45
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
47
46
  "@testing-library/jest-dom": "^6.8.0",
48
47
  "@testing-library/svelte": "^5.2.8",
49
48
  "@testing-library/user-event": "^14.6.1",
49
+ "@types/node": "^24.3.0",
50
+ "@vitest/coverage-v8": "^4.1.11",
50
51
  "jsdom": "^27.0.0",
51
52
  "rimraf": "^6.0.1",
52
53
  "svelte": "^5.56.10",