@spaethtech/svelte-ui 0.1.10 → 0.3.1-dev.10.ff03a8d

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 (218) hide show
  1. package/.claude/skills/svelte-ui/SKILL.md +118 -0
  2. package/.claude/skills/themes/SKILL.md +303 -0
  3. package/CLAUDE.md +248 -0
  4. package/README.md +180 -42
  5. package/dist/components/Alert.svelte +119 -0
  6. package/dist/components/Alert.svelte.d.ts +29 -0
  7. package/dist/components/Badge/Badge.svelte +142 -69
  8. package/dist/components/Badge/Badge.svelte.d.ts +26 -6
  9. package/dist/components/Badge/index.d.ts +1 -1
  10. package/dist/components/Badge/index.js +1 -1
  11. package/dist/components/Banner.svelte +133 -0
  12. package/dist/components/Banner.svelte.d.ts +31 -0
  13. package/dist/components/Button.svelte +240 -0
  14. package/dist/components/Button.svelte.d.ts +33 -0
  15. package/dist/components/ButtonDropdown.svelte +145 -0
  16. package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
  17. package/dist/components/Calendar.svelte +259 -0
  18. package/dist/components/Calendar.svelte.d.ts +27 -0
  19. package/dist/components/Card.svelte +79 -0
  20. package/dist/components/Card.svelte.d.ts +17 -0
  21. package/dist/components/CardBody.svelte +41 -0
  22. package/dist/components/CardBody.svelte.d.ts +17 -0
  23. package/dist/components/CardFooter.svelte +51 -0
  24. package/dist/components/CardFooter.svelte.d.ts +16 -0
  25. package/dist/components/CardHeader.svelte +51 -0
  26. package/dist/components/CardHeader.svelte.d.ts +16 -0
  27. package/dist/components/Checkbox.svelte +131 -0
  28. package/dist/components/Checkbox.svelte.d.ts +16 -0
  29. package/dist/components/ConfirmDialog.svelte +103 -0
  30. package/dist/components/ConfirmDialog.svelte.d.ts +33 -0
  31. package/dist/components/DataTable.svelte +518 -0
  32. package/dist/components/DataTable.svelte.d.ts +74 -0
  33. package/dist/components/DatePicker.svelte +312 -0
  34. package/dist/components/DatePicker.svelte.d.ts +32 -0
  35. package/dist/components/DateTimeInput.svelte +93 -0
  36. package/dist/components/DateTimeInput.svelte.d.ts +20 -0
  37. package/dist/components/Dialog.svelte +151 -0
  38. package/dist/components/Dialog.svelte.d.ts +30 -0
  39. package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
  40. package/dist/components/EmailInput.svelte.d.ts +21 -0
  41. package/dist/components/Input.svelte +369 -0
  42. package/dist/components/Input.svelte.d.ts +35 -0
  43. package/dist/components/{List/List.svelte → List.svelte} +194 -133
  44. package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
  45. package/dist/components/Menu.svelte +117 -0
  46. package/dist/components/Menu.svelte.d.ts +20 -0
  47. package/dist/components/NotesEditor.svelte +127 -0
  48. package/dist/components/NotesEditor.svelte.d.ts +17 -0
  49. package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
  50. package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
  51. package/dist/components/PasswordInput.svelte +52 -0
  52. package/dist/components/PasswordInput.svelte.d.ts +20 -0
  53. package/dist/components/Popup.svelte +140 -0
  54. package/dist/components/Popup.svelte.d.ts +31 -0
  55. package/dist/components/Query.svelte +284 -0
  56. package/dist/components/Query.svelte.d.ts +15 -0
  57. package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
  58. package/dist/components/Rating.svelte.d.ts +13 -0
  59. package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
  60. package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
  61. package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
  62. package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
  63. package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
  64. package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
  65. package/dist/components/SideBarMenu/index.d.ts +2 -0
  66. package/dist/components/SideBarMenu/index.js +1 -0
  67. package/dist/components/TabStrip/TabStrip.svelte +384 -0
  68. package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
  69. package/dist/components/TabStrip/index.d.ts +3 -0
  70. package/dist/components/TabStrip/index.js +2 -0
  71. package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
  72. package/dist/components/TextArea.svelte.d.ts +38 -0
  73. package/dist/components/ThemeSelector.svelte +81 -0
  74. package/dist/components/ThemeSelector.svelte.d.ts +10 -0
  75. package/dist/components/TimePicker.svelte +148 -0
  76. package/dist/components/TimePicker.svelte.d.ts +28 -0
  77. package/dist/components/TimeRangeInput.svelte +203 -0
  78. package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
  79. package/dist/components/TimeSpinner.svelte +202 -0
  80. package/dist/components/TimeSpinner.svelte.d.ts +26 -0
  81. package/dist/components/Toast/Toaster.svelte +51 -0
  82. package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
  83. package/dist/components/Toast/toast.svelte.d.ts +29 -0
  84. package/dist/components/Toast/toast.svelte.js +27 -0
  85. package/dist/components/Toggle.svelte +93 -0
  86. package/dist/components/Toggle.svelte.d.ts +15 -0
  87. package/dist/data/dataset.d.ts +42 -0
  88. package/dist/data/dataset.js +3 -0
  89. package/dist/data/index.d.ts +3 -0
  90. package/dist/data/index.js +3 -0
  91. package/dist/data/query/ast.d.ts +62 -0
  92. package/dist/data/query/ast.js +12 -0
  93. package/dist/data/query/index.d.ts +3 -0
  94. package/dist/data/query/index.js +3 -0
  95. package/dist/data/query/lexer.d.ts +33 -0
  96. package/dist/data/query/lexer.js +225 -0
  97. package/dist/data/query/parser.d.ts +11 -0
  98. package/dist/data/query/parser.js +252 -0
  99. package/dist/data/table/grid.svelte.d.ts +57 -0
  100. package/dist/data/table/grid.svelte.js +139 -0
  101. package/dist/data/table/index.d.ts +2 -0
  102. package/dist/data/table/index.js +2 -0
  103. package/dist/data/table/types.d.ts +79 -0
  104. package/dist/index.d.ts +49 -22
  105. package/dist/index.js +46 -21
  106. package/dist/positioning/anchored.d.ts +61 -0
  107. package/dist/positioning/anchored.js +122 -0
  108. package/dist/positioning/tooltip.d.ts +41 -0
  109. package/dist/positioning/tooltip.js +147 -0
  110. package/dist/theme.css +205 -0
  111. package/dist/types/breakpoints.d.ts +24 -0
  112. package/dist/types/breakpoints.js +13 -0
  113. package/dist/types/responsive.d.ts +32 -0
  114. package/dist/types/responsive.js +54 -0
  115. package/dist/types/sizes.d.ts +10 -3
  116. package/dist/types/time.d.ts +19 -0
  117. package/dist/types/time.js +10 -0
  118. package/dist/types/variants.d.ts +8 -1
  119. package/dist/types/variants.js +16 -1
  120. package/docs/components.md +255 -0
  121. package/docs/examples.md +323 -0
  122. package/docs/paradigm.md +240 -0
  123. package/docs/themes.md +170 -0
  124. package/docs/usage.md +450 -0
  125. package/package.json +97 -63
  126. package/dist/components/Button/Button.svelte +0 -172
  127. package/dist/components/Button/Button.svelte.d.ts +0 -32
  128. package/dist/components/Button/index.d.ts +0 -1
  129. package/dist/components/Button/index.js +0 -1
  130. package/dist/components/Dialog/Dialog.svelte +0 -101
  131. package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
  132. package/dist/components/Dialog/index.d.ts +0 -1
  133. package/dist/components/Dialog/index.js +0 -1
  134. package/dist/components/ElementManager/ElementManager.svelte +0 -397
  135. package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
  136. package/dist/components/ElementManager/index.d.ts +0 -1
  137. package/dist/components/ElementManager/index.js +0 -1
  138. package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
  139. package/dist/components/EmailInput/index.d.ts +0 -1
  140. package/dist/components/EmailInput/index.js +0 -1
  141. package/dist/components/Icon/Icon.svelte +0 -74
  142. package/dist/components/Icon/Icon.svelte.d.ts +0 -13
  143. package/dist/components/Icon/index.d.ts +0 -1
  144. package/dist/components/Icon/index.js +0 -1
  145. package/dist/components/Input/Input.svelte +0 -268
  146. package/dist/components/Input/Input.svelte.d.ts +0 -18
  147. package/dist/components/Input/index.d.ts +0 -1
  148. package/dist/components/Input/index.js +0 -1
  149. package/dist/components/List/index.d.ts +0 -1
  150. package/dist/components/List/index.js +0 -1
  151. package/dist/components/ListItem/ListItem.svelte +0 -175
  152. package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
  153. package/dist/components/ListItem/index.d.ts +0 -2
  154. package/dist/components/ListItem/index.js +0 -2
  155. package/dist/components/ListView/ListView.svelte +0 -463
  156. package/dist/components/ListView/ListView.svelte.d.ts +0 -37
  157. package/dist/components/ListView/index.d.ts +0 -2
  158. package/dist/components/ListView/index.js +0 -2
  159. package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
  160. package/dist/components/NodeGraph/BaseNode.js +0 -30
  161. package/dist/components/NodeGraph/Edge.svelte +0 -60
  162. package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
  163. package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
  164. package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
  165. package/dist/components/NodeGraph/Node.svelte +0 -100
  166. package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
  167. package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
  168. package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
  169. package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
  170. package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
  171. package/dist/components/NodeGraph/NodePort.svelte +0 -75
  172. package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
  173. package/dist/components/NodeGraph/decorators.d.ts +0 -81
  174. package/dist/components/NodeGraph/decorators.js +0 -148
  175. package/dist/components/NodeGraph/index.d.ts +0 -9
  176. package/dist/components/NodeGraph/index.js +0 -9
  177. package/dist/components/NodeGraph/types.d.ts +0 -94
  178. package/dist/components/NodeGraph/types.js +0 -33
  179. package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
  180. package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
  181. package/dist/components/NotesEditor/index.d.ts +0 -1
  182. package/dist/components/NotesEditor/index.js +0 -1
  183. package/dist/components/NumberInput/index.d.ts +0 -1
  184. package/dist/components/NumberInput/index.js +0 -1
  185. package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
  186. package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
  187. package/dist/components/PasswordInput/index.d.ts +0 -1
  188. package/dist/components/PasswordInput/index.js +0 -1
  189. package/dist/components/Popup/index.d.ts +0 -1
  190. package/dist/components/Popup/index.js +0 -1
  191. package/dist/components/Rating/Rating.svelte.d.ts +0 -7
  192. package/dist/components/Rating/index.d.ts +0 -1
  193. package/dist/components/Rating/index.js +0 -1
  194. package/dist/components/ScrollView/ScrollView.svelte +0 -285
  195. package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
  196. package/dist/components/ScrollView/index.d.ts +0 -1
  197. package/dist/components/ScrollView/index.js +0 -1
  198. package/dist/components/SearchInput/index.d.ts +0 -1
  199. package/dist/components/SearchInput/index.js +0 -1
  200. package/dist/components/Select/index.d.ts +0 -1
  201. package/dist/components/Select/index.js +0 -1
  202. package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
  203. package/dist/components/TextArea/index.d.ts +0 -1
  204. package/dist/components/TextArea/index.js +0 -1
  205. package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
  206. package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
  207. package/dist/components/ThemeSelector/index.d.ts +0 -1
  208. package/dist/components/ThemeSelector/index.js +0 -1
  209. package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
  210. package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
  211. package/dist/components/TooltipHandler/index.d.ts +0 -1
  212. package/dist/components/TooltipHandler/index.js +0 -1
  213. package/dist/styles/sizes.d.ts +0 -78
  214. package/dist/styles/sizes.js +0 -120
  215. package/dist/styles/variants.d.ts +0 -106
  216. package/dist/styles/variants.js +0 -194
  217. package/dist/types/ManagerTypes.d.ts +0 -42
  218. /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
@@ -0,0 +1,117 @@
1
+ <!--
2
+ /**
3
+ * Menu — a keyboard-navigable action menu rendered inside a Popup (top layer, flips vs boundary).
4
+ *
5
+ * Convention (from the data-table): render ALL items always and `disabled` the invalid ones.
6
+ * If ANY item has an icon, icon-less items reserve the icon column so labels align. Selecting an
7
+ * enabled item runs its onclick / follows its href and closes; disabled items don't close.
8
+ */
9
+ -->
10
+ <script lang="ts">
11
+ import Popup from "./Popup.svelte";
12
+ import type { Side, Align, Boundary } from "../positioning/anchored.js";
13
+ import type { MenuItem } from "../data/table/types.js";
14
+ import type { Size } from "../types/sizes.js";
15
+ import { responsiveClasses, type Responsive } from "../types/responsive.js";
16
+ import { variantToken, type Variant } from "../types/variants.js";
17
+
18
+ let {
19
+ anchor,
20
+ open = $bindable(false),
21
+ items,
22
+ side = "bottom",
23
+ align = "end",
24
+ boundary = "viewport",
25
+ size = "md",
26
+ variant,
27
+ }: {
28
+ anchor: HTMLElement | undefined;
29
+ open?: boolean;
30
+ items: MenuItem[];
31
+ side?: Side;
32
+ align?: Align;
33
+ boundary?: Boundary;
34
+ size?: Responsive<Size>;
35
+ /** Tints the highlighted item with a semantic colour (via `.ui-accent` / `--ui-accent`) instead
36
+ * of neutral grey. Omit for the default. */
37
+ variant?: Variant;
38
+ } = $props();
39
+
40
+ let active = $state(-1);
41
+ const hasIcons = $derived(items.some((i) => i.icon));
42
+ const enabledIdx = $derived(items.map((i, idx) => (i.disabled ? -1 : idx)).filter((i) => i >= 0));
43
+
44
+ // Size axis → item padding / text / icon box. Variant → --ui-accent (re-mixes the item's hover tint).
45
+ const padMap: Record<Size, string> = { sm: "px-1.5 py-1", md: "px-2 py-1.5", lg: "px-2.5 py-2" };
46
+ const textMap: Record<Size, string> = { sm: "text-xs", md: "text-sm", lg: "text-base" };
47
+ const iconMap: Record<Size, string> = { sm: "w-3.5 h-3.5", md: "w-4 h-4", lg: "w-5 h-5" };
48
+ const padClass = $derived(responsiveClasses(size, padMap));
49
+ const textClass = $derived(responsiveClasses(size, textMap));
50
+ const iconClass = $derived(responsiveClasses(size, iconMap));
51
+ const accentVar = $derived(variant ? `--ui-accent: var(${variantToken[variant]});` : "");
52
+
53
+ function choose(item: MenuItem) {
54
+ if (item.disabled) return;
55
+ item.onclick?.();
56
+ if (item.href) window.location.href = item.href;
57
+ open = false;
58
+ }
59
+
60
+ function onkeydown(e: KeyboardEvent) {
61
+ if (!enabledIdx.length) return;
62
+ const pos = enabledIdx.indexOf(active);
63
+ if (e.key === "ArrowDown") {
64
+ e.preventDefault();
65
+ active = enabledIdx[(pos + 1 + enabledIdx.length) % enabledIdx.length] ?? enabledIdx[0];
66
+ } else if (e.key === "ArrowUp") {
67
+ e.preventDefault();
68
+ active =
69
+ enabledIdx[(pos - 1 + enabledIdx.length) % enabledIdx.length] ??
70
+ enabledIdx[enabledIdx.length - 1];
71
+ } else if (e.key === "Enter" && active >= 0) {
72
+ e.preventDefault();
73
+ choose(items[active]);
74
+ }
75
+ }
76
+
77
+ $effect(() => {
78
+ if (!open) active = -1;
79
+ });
80
+ </script>
81
+
82
+ <Popup {anchor} bind:open {side} {align} {boundary}>
83
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
84
+ <ul
85
+ role="menu"
86
+ tabindex="0"
87
+ {onkeydown}
88
+ class="ui-accent min-w-48 rounded-md border p-1 shadow-lg outline-none [background-color:var(--ui-color-background)] [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)]"
89
+ style={accentVar}
90
+ >
91
+ {#each items as item, i (item.label)}
92
+ {@const danger = item.danger}
93
+ <li role="none">
94
+ <button
95
+ type="button"
96
+ role="menuitem"
97
+ disabled={item.disabled}
98
+ onmouseenter={() => (active = item.disabled ? active : i)}
99
+ onclick={() => choose(item)}
100
+ class="flex w-full items-center gap-2 rounded {padClass} {textClass} text-left transition-colors
101
+ {item.disabled ? 'cursor-not-allowed opacity-40' : 'cursor-pointer'}
102
+ {i === active && !item.disabled ? '[background-color:var(--ui-color-surface)]' : ''}
103
+ {danger ? '[color:var(--ui-color-error)]' : '[color:var(--ui-color-text)]'}"
104
+ >
105
+ {#if hasIcons}
106
+ <span
107
+ class="inline-flex {iconClass} shrink-0 items-center justify-center [&_svg]:w-full [&_svg]:h-full"
108
+ >
109
+ {#if item.icon}{@const Icon = item.icon}<Icon />{/if}
110
+ </span>
111
+ {/if}
112
+ <span class="truncate">{item.label}</span>
113
+ </button>
114
+ </li>
115
+ {/each}
116
+ </ul>
117
+ </Popup>
@@ -0,0 +1,20 @@
1
+ import type { Side, Align, Boundary } from "../positioning/anchored.js";
2
+ import type { MenuItem } from "../data/table/types.js";
3
+ import type { Size } from "../types/sizes.js";
4
+ import { type Responsive } from "../types/responsive.js";
5
+ import { type Variant } from "../types/variants.js";
6
+ type $$ComponentProps = {
7
+ anchor: HTMLElement | undefined;
8
+ open?: boolean;
9
+ items: MenuItem[];
10
+ side?: Side;
11
+ align?: Align;
12
+ boundary?: Boundary;
13
+ size?: Responsive<Size>;
14
+ /** Tints the highlighted item with a semantic colour (via `.ui-accent` / `--ui-accent`) instead
15
+ * of neutral grey. Omit for the default. */
16
+ variant?: Variant;
17
+ };
18
+ declare const Menu: import("svelte").Component<$$ComponentProps, {}, "open">;
19
+ type Menu = ReturnType<typeof Menu>;
20
+ export default Menu;
@@ -0,0 +1,127 @@
1
+ <!--
2
+ /**
3
+ * NotesEditor — an auto-saving notes field. Built on the core <TextArea> (so it inherits the shared
4
+ * frame, autosize, and the `size` + `variant` axes) plus a debounced auto-save + a status indicator.
5
+ * Saves 1s after typing stops, or immediately on Ctrl/Cmd+S while focused.
6
+ */
7
+ -->
8
+ <script lang="ts">
9
+ import TextArea from "./TextArea.svelte";
10
+ import type { Size } from "../types/sizes.js";
11
+ import type { Responsive } from "../types/responsive.js";
12
+ import type { Variant } from "../types/variants.js";
13
+
14
+ let {
15
+ notes = $bindable(""),
16
+ placeholder = "Add your notes...",
17
+ onSave,
18
+ size = "md",
19
+ variant = "secondary",
20
+ copy,
21
+ className = "",
22
+ }: {
23
+ notes?: string;
24
+ placeholder?: string;
25
+ onSave?: (notes: string) => Promise<void>;
26
+ /** Shared axes — forwarded to the underlying TextArea. */
27
+ size?: Responsive<Size>;
28
+ variant?: Variant;
29
+ /** Copy affordance — forwarded to the underlying TextArea (defaults to its `'selection'`). */
30
+ copy?: "none" | "selection" | "button";
31
+ className?: string;
32
+ } = $props();
33
+
34
+ let saveState = $state<"idle" | "typing" | "saving" | "saved" | "error">("idle");
35
+ let saveTimeout: ReturnType<typeof setTimeout>;
36
+ let lastSavedNotes = $state(notes || "");
37
+ let textareaElement = $state<HTMLTextAreaElement>();
38
+
39
+ // Auto-save: 1s debounce after typing stops.
40
+ $effect(() => {
41
+ const currentNotes = notes || "";
42
+ if (currentNotes !== lastSavedNotes && saveState !== "saving") {
43
+ saveState = "typing";
44
+ clearTimeout(saveTimeout);
45
+ saveTimeout = setTimeout(() => {
46
+ handleSave();
47
+ }, 1000);
48
+ }
49
+ });
50
+
51
+ // Ctrl/Cmd+S saves immediately while the editor is focused.
52
+ $effect(() => {
53
+ async function handleKeyDown(event: KeyboardEvent) {
54
+ if (
55
+ (event.ctrlKey || event.metaKey) &&
56
+ event.key === "s" &&
57
+ textareaElement &&
58
+ document.activeElement === textareaElement
59
+ ) {
60
+ event.preventDefault();
61
+ event.stopPropagation();
62
+ clearTimeout(saveTimeout);
63
+ await handleSave();
64
+ }
65
+ }
66
+ document.addEventListener("keydown", handleKeyDown);
67
+ return () => document.removeEventListener("keydown", handleKeyDown);
68
+ });
69
+
70
+ async function handleSave() {
71
+ const currentNotes = notes || "";
72
+
73
+ if (currentNotes === lastSavedNotes) {
74
+ // Brief "Saved" feedback for a manual save with no changes.
75
+ saveState = "saved";
76
+ setTimeout(() => {
77
+ if (saveState === "saved") saveState = "idle";
78
+ }, 1000);
79
+ return;
80
+ }
81
+
82
+ saveState = "saving";
83
+ try {
84
+ await onSave?.(currentNotes);
85
+ lastSavedNotes = currentNotes;
86
+ saveState = "saved";
87
+ setTimeout(() => {
88
+ if (saveState === "saved") saveState = "idle";
89
+ }, 2000);
90
+ } catch (error) {
91
+ console.error("Failed to save notes:", error);
92
+ saveState = "error";
93
+ setTimeout(() => {
94
+ if (saveState === "error") saveState = "idle";
95
+ }, 3000);
96
+ }
97
+ }
98
+
99
+ // Status text + colour (theme tokens, not hard-coded Tailwind colours).
100
+ const STATUS = {
101
+ idle: { text: "", color: "" },
102
+ typing: { text: "Typing…", color: "color-mix(in srgb, var(--ui-color-text) 55%, transparent)" },
103
+ saving: { text: "Saving…", color: "var(--ui-color-info)" },
104
+ saved: { text: "Saved", color: "var(--ui-color-success)" },
105
+ error: { text: "Save failed", color: "var(--ui-color-error)" },
106
+ } as const;
107
+ const status = $derived(STATUS[saveState]);
108
+ </script>
109
+
110
+ <div class={className}>
111
+ <!-- Header: label + save status -->
112
+ <div class="flex items-center justify-between mb-2">
113
+ <span class="block text-sm font-medium [color:var(--ui-color-text)]">Notes</span>
114
+ <span class="text-xs min-h-[16px]" style="color: {status.color}">{status.text}</span>
115
+ </div>
116
+
117
+ <TextArea
118
+ bind:value={notes}
119
+ bind:element={textareaElement}
120
+ {placeholder}
121
+ {size}
122
+ {variant}
123
+ {copy}
124
+ minRows={3}
125
+ maxRows={15}
126
+ />
127
+ </div>
@@ -0,0 +1,17 @@
1
+ import type { Size } from "../types/sizes.js";
2
+ import type { Responsive } from "../types/responsive.js";
3
+ import type { Variant } from "../types/variants.js";
4
+ type $$ComponentProps = {
5
+ notes?: string;
6
+ placeholder?: string;
7
+ onSave?: (notes: string) => Promise<void>;
8
+ /** Shared axes — forwarded to the underlying TextArea. */
9
+ size?: Responsive<Size>;
10
+ variant?: Variant;
11
+ /** Copy affordance — forwarded to the underlying TextArea (defaults to its `'selection'`). */
12
+ copy?: "none" | "selection" | "button";
13
+ className?: string;
14
+ };
15
+ declare const NotesEditor: import("svelte").Component<$$ComponentProps, {}, "notes">;
16
+ type NotesEditor = ReturnType<typeof NotesEditor>;
17
+ export default NotesEditor;