@plures/design-dojo 0.5.3 → 0.7.1

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 (250) hide show
  1. package/dist/app/CanvasBreadcrumb.svelte +146 -0
  2. package/dist/app/CanvasBreadcrumb.svelte.d.ts +19 -0
  3. package/dist/app/CanvasBreadcrumb.types.js +1 -0
  4. package/dist/app/ChatInput.svelte +296 -0
  5. package/dist/app/ChatInput.svelte.d.ts +15 -0
  6. package/dist/app/ChatView.svelte +542 -0
  7. package/dist/app/ChatView.svelte.d.ts +19 -0
  8. package/dist/app/ChatView.types.js +1 -0
  9. package/dist/app/ConversationGraph.svelte +471 -0
  10. package/dist/app/ConversationGraph.svelte.d.ts +20 -0
  11. package/dist/app/FirstRunWizard.svelte +542 -0
  12. package/dist/app/FirstRunWizard.svelte.d.ts +10 -0
  13. package/dist/app/FirstRunWizard.types.js +1 -0
  14. package/dist/app/MemorySidebar.svelte +258 -0
  15. package/dist/app/MemorySidebar.svelte.d.ts +9 -0
  16. package/dist/app/MemorySidebar.types.js +1 -0
  17. package/dist/app/PeerStatusPanel.svelte +464 -0
  18. package/dist/app/PeerStatusPanel.svelte.d.ts +16 -0
  19. package/dist/app/ProcedureCanvas.svelte +994 -0
  20. package/dist/app/ProcedureCanvas.svelte.d.ts +12 -0
  21. package/dist/app/ProcedureCanvas.types.js +1 -0
  22. package/dist/app/ProcedureEditor.svelte +494 -0
  23. package/dist/app/ProcedureEditor.svelte.d.ts +11 -0
  24. package/dist/app/ProcedureEditor.types.js +1 -0
  25. package/dist/app/ProcedureInspector.svelte +520 -0
  26. package/dist/app/ProcedureInspector.svelte.d.ts +15 -0
  27. package/dist/app/ProcedureNode.svelte +283 -0
  28. package/dist/app/ProcedureNode.svelte.d.ts +26 -0
  29. package/dist/app/Realm.types.js +1 -0
  30. package/dist/app/RealmIndicator.svelte +81 -0
  31. package/dist/app/RealmIndicator.svelte.d.ts +10 -0
  32. package/dist/app/RealmSwitcher.svelte +354 -0
  33. package/dist/app/RealmSwitcher.svelte.d.ts +16 -0
  34. package/dist/app/SemanticConversation.types.js +1 -0
  35. package/dist/app/SemanticSearchInput.svelte +630 -0
  36. package/dist/app/SemanticSearchInput.svelte.d.ts +20 -0
  37. package/dist/app/SemanticSearchInput.types.js +1 -0
  38. package/dist/app/SemanticTimeline.svelte +426 -0
  39. package/dist/app/SemanticTimeline.svelte.d.ts +13 -0
  40. package/dist/app/SettingsPanel.svelte +330 -0
  41. package/dist/app/SettingsPanel.svelte.d.ts +12 -0
  42. package/dist/app/SettingsPanel.types.js +1 -0
  43. package/dist/app/SubCanvas.svelte +457 -0
  44. package/dist/app/SubCanvas.svelte.d.ts +48 -0
  45. package/dist/app/SubCanvas.types.js +1 -0
  46. package/dist/app/Sync.types.js +1 -0
  47. package/dist/app/SyncIndicator.svelte +219 -0
  48. package/dist/app/SyncIndicator.svelte.d.ts +15 -0
  49. package/dist/app/SyncTimeline.svelte +299 -0
  50. package/dist/app/SyncTimeline.svelte.d.ts +12 -0
  51. package/dist/app/TagCloud.svelte +287 -0
  52. package/dist/app/TagCloud.svelte.d.ts +12 -0
  53. package/dist/app/WorkModeToggle.svelte +188 -0
  54. package/dist/app/WorkModeToggle.svelte.d.ts +17 -0
  55. package/dist/data/List.svelte +104 -0
  56. package/dist/data/List.svelte.d.ts +18 -0
  57. package/dist/data/ListItem.svelte +130 -0
  58. package/dist/data/ListItem.svelte.d.ts +12 -0
  59. package/dist/data/Table.svelte +241 -0
  60. package/dist/data/Table.svelte.d.ts +17 -0
  61. package/dist/data/index.d.ts +3 -0
  62. package/dist/data/index.js +3 -0
  63. package/dist/disclosure/Accordion.svelte +48 -0
  64. package/dist/disclosure/Accordion.svelte.d.ts +15 -0
  65. package/dist/feedback/Badge.svelte +60 -0
  66. package/dist/feedback/Badge.svelte.d.ts +14 -0
  67. package/dist/feedback/Callout.svelte +52 -0
  68. package/dist/feedback/Callout.svelte.d.ts +12 -0
  69. package/dist/feedback/EmptyState.svelte +47 -0
  70. package/dist/feedback/EmptyState.svelte.d.ts +12 -0
  71. package/dist/feedback/ProgressBar.svelte +95 -0
  72. package/dist/feedback/ProgressBar.svelte.d.ts +16 -0
  73. package/dist/forms/FileUpload.svelte +99 -0
  74. package/dist/forms/FileUpload.svelte.d.ts +18 -0
  75. package/dist/forms/RadioGroup.svelte +84 -0
  76. package/dist/forms/RadioGroup.svelte.d.ts +19 -0
  77. package/dist/icons/NerdFont.svelte +44 -0
  78. package/dist/icons/NerdFont.svelte.d.ts +13 -0
  79. package/dist/icons/index.d.ts +1 -0
  80. package/dist/icons/index.js +1 -0
  81. package/dist/index.d.ts +76 -0
  82. package/dist/index.js +70 -6212
  83. package/dist/layout/Box.svelte +207 -0
  84. package/dist/layout/Box.svelte.d.ts +22 -0
  85. package/dist/layout/Sidebar.svelte +210 -0
  86. package/dist/layout/Sidebar.svelte.d.ts +22 -0
  87. package/dist/layout/SplitPane.svelte +64 -0
  88. package/dist/layout/SplitPane.svelte.d.ts +12 -0
  89. package/dist/layout/StatusBar.svelte +83 -0
  90. package/dist/layout/StatusBar.svelte.d.ts +12 -0
  91. package/dist/layout/StatusBarItem.svelte +146 -0
  92. package/dist/layout/StatusBarItem.svelte.d.ts +15 -0
  93. package/dist/layout/StatusBarSpacer.svelte +38 -0
  94. package/dist/layout/StatusBarSpacer.svelte.d.ts +3 -0
  95. package/dist/layout/Tabs.svelte +254 -0
  96. package/dist/layout/Tabs.svelte.d.ts +21 -0
  97. package/dist/layout/Tabs.types.js +1 -0
  98. package/dist/layout/TitleBar.svelte +422 -0
  99. package/dist/layout/TitleBar.svelte.d.ts +22 -0
  100. package/dist/layout/index.d.ts +9 -0
  101. package/dist/layout/index.js +8 -0
  102. package/dist/motion/index.d.ts +1 -0
  103. package/dist/motion/index.js +1 -0
  104. package/dist/motion/spring.js +116 -0
  105. package/dist/overlays/ContextMenu.svelte +268 -0
  106. package/dist/overlays/ContextMenu.svelte.d.ts +17 -0
  107. package/dist/overlays/Dialog.svelte +264 -0
  108. package/dist/overlays/Dialog.svelte.d.ts +20 -0
  109. package/dist/overlays/Menu.svelte +274 -0
  110. package/dist/overlays/Menu.svelte.d.ts +26 -0
  111. package/dist/overlays/Menu.types.js +1 -0
  112. package/dist/overlays/Popover.svelte +158 -0
  113. package/dist/overlays/Popover.svelte.d.ts +21 -0
  114. package/dist/overlays/Toast.svelte +179 -0
  115. package/dist/overlays/Toast.svelte.d.ts +19 -0
  116. package/dist/overlays/Tooltip.svelte +114 -0
  117. package/dist/overlays/Tooltip.svelte.d.ts +17 -0
  118. package/dist/overlays/index.d.ts +7 -0
  119. package/dist/overlays/index.js +6 -0
  120. package/dist/primitives/Button.svelte +217 -0
  121. package/dist/primitives/Button.svelte.d.ts +13 -0
  122. package/dist/primitives/ContextMenu.svelte +242 -0
  123. package/dist/primitives/ContextMenu.svelte.d.ts +18 -0
  124. package/dist/primitives/ContextMenu.types.js +1 -0
  125. package/dist/primitives/Input.svelte +468 -0
  126. package/dist/primitives/Input.svelte.d.ts +21 -0
  127. package/dist/primitives/MarkdownEditor.svelte +781 -0
  128. package/dist/primitives/MarkdownEditor.svelte.d.ts +21 -0
  129. package/dist/primitives/MarkdownEditor.types.js +1 -0
  130. package/dist/primitives/SearchInput.svelte +623 -0
  131. package/dist/primitives/SearchInput.svelte.d.ts +24 -0
  132. package/dist/primitives/Select.svelte +336 -0
  133. package/dist/primitives/Select.svelte.d.ts +18 -0
  134. package/dist/primitives/Text.svelte +177 -0
  135. package/dist/primitives/Text.svelte.d.ts +26 -0
  136. package/dist/primitives/Toggle.svelte +138 -0
  137. package/dist/primitives/Toggle.svelte.d.ts +9 -0
  138. package/dist/primitives/index.d.ts +9 -0
  139. package/dist/primitives/index.js +7 -0
  140. package/dist/primitives/search-input-types.js +1 -0
  141. package/dist/surfaces/ChatPane.svelte +520 -0
  142. package/dist/surfaces/ChatPane.svelte.d.ts +15 -0
  143. package/dist/surfaces/ChatPane.types.js +1 -0
  144. package/dist/surfaces/GlassPanel.svelte +118 -0
  145. package/dist/surfaces/GlassPanel.svelte.d.ts +19 -0
  146. package/dist/surfaces/Pane.svelte +172 -0
  147. package/dist/surfaces/Pane.svelte.d.ts +25 -0
  148. package/dist/surfaces/index.d.ts +4 -0
  149. package/dist/surfaces/index.js +3 -0
  150. package/dist/telemetry/correlation.js +26 -0
  151. package/dist/telemetry/index.d.ts +4 -4
  152. package/dist/telemetry/index.js +20 -101
  153. package/dist/telemetry/sampling.js +58 -0
  154. package/dist/telemetry/tracer.d.ts +16 -1
  155. package/dist/telemetry/tracer.js +112 -0
  156. package/dist/tokens.css +123 -0
  157. package/dist/tui-tokens.css +36 -0
  158. package/dist/useTui.js +31 -0
  159. package/package.json +32 -22
  160. package/dist/design-dojo.css +0 -1
  161. package/dist/enforce/index.d.ts +0 -75
  162. package/dist/enforce/known-components.d.ts +0 -7
  163. package/dist/enforce/rules/no-local-components.d.ts +0 -29
  164. package/dist/enforce/rules/prefer-design-dojo-imports.d.ts +0 -27
  165. package/dist/enforce.js +0 -132
  166. package/dist/lib/app/CanvasBreadcrumb.svelte.d.ts +0 -1
  167. package/dist/lib/app/ChatInput.svelte.d.ts +0 -1
  168. package/dist/lib/app/ChatView.svelte.d.ts +0 -1
  169. package/dist/lib/app/ConversationGraph.svelte.d.ts +0 -1
  170. package/dist/lib/app/FirstRunWizard.svelte.d.ts +0 -1
  171. package/dist/lib/app/MemorySidebar.svelte.d.ts +0 -1
  172. package/dist/lib/app/PeerStatusPanel.svelte.d.ts +0 -1
  173. package/dist/lib/app/ProcedureCanvas.svelte.d.ts +0 -1
  174. package/dist/lib/app/ProcedureEditor.svelte.d.ts +0 -1
  175. package/dist/lib/app/ProcedureInspector.svelte.d.ts +0 -1
  176. package/dist/lib/app/ProcedureNode.svelte.d.ts +0 -1
  177. package/dist/lib/app/RealmIndicator.svelte.d.ts +0 -1
  178. package/dist/lib/app/RealmSwitcher.svelte.d.ts +0 -1
  179. package/dist/lib/app/SemanticSearchInput.svelte.d.ts +0 -1
  180. package/dist/lib/app/SemanticTimeline.svelte.d.ts +0 -1
  181. package/dist/lib/app/SettingsPanel.svelte.d.ts +0 -1
  182. package/dist/lib/app/SubCanvas.svelte.d.ts +0 -1
  183. package/dist/lib/app/SyncIndicator.svelte.d.ts +0 -1
  184. package/dist/lib/app/SyncTimeline.svelte.d.ts +0 -1
  185. package/dist/lib/app/TagCloud.svelte.d.ts +0 -1
  186. package/dist/lib/app/WorkModeToggle.svelte.d.ts +0 -1
  187. package/dist/lib/data/List.svelte.d.ts +0 -1
  188. package/dist/lib/data/ListItem.svelte.d.ts +0 -1
  189. package/dist/lib/data/Table.svelte.d.ts +0 -1
  190. package/dist/lib/data/index.d.ts +0 -3
  191. package/dist/lib/disclosure/Accordion.svelte.d.ts +0 -1
  192. package/dist/lib/feedback/Badge.svelte.d.ts +0 -1
  193. package/dist/lib/feedback/Callout.svelte.d.ts +0 -1
  194. package/dist/lib/feedback/EmptyState.svelte.d.ts +0 -1
  195. package/dist/lib/feedback/ProgressBar.svelte.d.ts +0 -1
  196. package/dist/lib/forms/FileUpload.svelte.d.ts +0 -1
  197. package/dist/lib/forms/RadioGroup.svelte.d.ts +0 -1
  198. package/dist/lib/icons/NerdFont.svelte.d.ts +0 -1
  199. package/dist/lib/icons/index.d.ts +0 -1
  200. package/dist/lib/index.d.ts +0 -76
  201. package/dist/lib/layout/Box.svelte.d.ts +0 -1
  202. package/dist/lib/layout/Sidebar.svelte.d.ts +0 -1
  203. package/dist/lib/layout/SplitPane.svelte.d.ts +0 -1
  204. package/dist/lib/layout/StatusBar.svelte.d.ts +0 -1
  205. package/dist/lib/layout/StatusBarItem.svelte.d.ts +0 -1
  206. package/dist/lib/layout/StatusBarSpacer.svelte.d.ts +0 -1
  207. package/dist/lib/layout/Tabs.svelte.d.ts +0 -1
  208. package/dist/lib/layout/TitleBar.svelte.d.ts +0 -1
  209. package/dist/lib/layout/index.d.ts +0 -9
  210. package/dist/lib/motion/index.d.ts +0 -1
  211. package/dist/lib/overlays/ContextMenu.svelte.d.ts +0 -1
  212. package/dist/lib/overlays/Dialog.svelte.d.ts +0 -1
  213. package/dist/lib/overlays/Menu.svelte.d.ts +0 -1
  214. package/dist/lib/overlays/Popover.svelte.d.ts +0 -1
  215. package/dist/lib/overlays/Toast.svelte.d.ts +0 -1
  216. package/dist/lib/overlays/Tooltip.svelte.d.ts +0 -1
  217. package/dist/lib/overlays/index.d.ts +0 -7
  218. package/dist/lib/primitives/Button.svelte.d.ts +0 -1
  219. package/dist/lib/primitives/ContextMenu.svelte.d.ts +0 -1
  220. package/dist/lib/primitives/Input.svelte.d.ts +0 -1
  221. package/dist/lib/primitives/MarkdownEditor.svelte.d.ts +0 -1
  222. package/dist/lib/primitives/SearchInput.svelte.d.ts +0 -1
  223. package/dist/lib/primitives/Select.svelte.d.ts +0 -1
  224. package/dist/lib/primitives/Text.svelte.d.ts +0 -1
  225. package/dist/lib/primitives/Toggle.svelte.d.ts +0 -1
  226. package/dist/lib/primitives/index.d.ts +0 -9
  227. package/dist/lib/surfaces/ChatPane.svelte.d.ts +0 -1
  228. package/dist/lib/surfaces/GlassPanel.svelte.d.ts +0 -1
  229. package/dist/lib/surfaces/Pane.svelte.d.ts +0 -1
  230. package/dist/lib/surfaces/index.d.ts +0 -4
  231. /package/dist/{lib/app → app}/CanvasBreadcrumb.types.d.ts +0 -0
  232. /package/dist/{lib/app → app}/ChatView.types.d.ts +0 -0
  233. /package/dist/{lib/app → app}/FirstRunWizard.types.d.ts +0 -0
  234. /package/dist/{lib/app → app}/MemorySidebar.types.d.ts +0 -0
  235. /package/dist/{lib/app → app}/ProcedureCanvas.types.d.ts +0 -0
  236. /package/dist/{lib/app → app}/ProcedureEditor.types.d.ts +0 -0
  237. /package/dist/{lib/app → app}/Realm.types.d.ts +0 -0
  238. /package/dist/{lib/app → app}/SemanticConversation.types.d.ts +0 -0
  239. /package/dist/{lib/app → app}/SemanticSearchInput.types.d.ts +0 -0
  240. /package/dist/{lib/app → app}/SettingsPanel.types.d.ts +0 -0
  241. /package/dist/{lib/app → app}/SubCanvas.types.d.ts +0 -0
  242. /package/dist/{lib/app → app}/Sync.types.d.ts +0 -0
  243. /package/dist/{lib/layout → layout}/Tabs.types.d.ts +0 -0
  244. /package/dist/{lib/motion → motion}/spring.d.ts +0 -0
  245. /package/dist/{lib/overlays → overlays}/Menu.types.d.ts +0 -0
  246. /package/dist/{lib/primitives → primitives}/ContextMenu.types.d.ts +0 -0
  247. /package/dist/{lib/primitives → primitives}/MarkdownEditor.types.d.ts +0 -0
  248. /package/dist/{lib/primitives → primitives}/search-input-types.d.ts +0 -0
  249. /package/dist/{lib/surfaces → surfaces}/ChatPane.types.d.ts +0 -0
  250. /package/dist/{lib/useTui.d.ts → useTui.d.ts} +0 -0
@@ -0,0 +1,468 @@
1
+ <!--
2
+ Input — Text input widget.
3
+
4
+ Features:
5
+ - GUI mode: styled input with floating label spring animation, focus ring
6
+ - TUI mode: box-drawing borders, terminal-safe colors, blinking block cursor
7
+ - Value binding ($bindable), placeholder, floating label
8
+ - Disabled, password masking, maxLength
9
+ - Events: onchange, onsubmit (Enter key), onkeydown
10
+ - Reduced motion support
11
+ -->
12
+ <script lang="ts">
13
+ import { createSpring } from "../motion/spring.js";
14
+ import { useTui } from "../useTui.js";
15
+
16
+ interface Props {
17
+ tui?: boolean;
18
+ value?: string;
19
+ placeholder?: string;
20
+ label?: string;
21
+ focused?: boolean;
22
+ disabled?: boolean;
23
+ /** Mask input characters */
24
+ password?: boolean;
25
+ maxLength?: number;
26
+ class?: string;
27
+ /** TUI mode: total character width including border columns. Default: 32 */
28
+ cols?: number;
29
+ onchange?: (value: string) => void;
30
+ /** Fired when Enter is pressed */
31
+ onsubmit?: (value: string) => void;
32
+ onkeydown?: (e: KeyboardEvent) => void;
33
+ }
34
+
35
+ let {
36
+ tui = false,
37
+ value = $bindable(""),
38
+ placeholder = "",
39
+ label = "",
40
+ focused: externalFocused = false,
41
+ disabled = false,
42
+ password = false,
43
+ maxLength = undefined,
44
+ class: className = "",
45
+ cols = 32,
46
+ onchange,
47
+ onsubmit,
48
+ onkeydown,
49
+ }: Props = $props();
50
+
51
+ const getTuiCtx = useTui();
52
+ const isTui = $derived(tui || getTuiCtx());
53
+
54
+ // Unique id to associate <label for="..."> with the <input id="...">
55
+ function createInputId() {
56
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
57
+ return `input-${crypto.randomUUID()}`;
58
+ }
59
+
60
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
61
+ const array = new Uint32Array(1);
62
+ crypto.getRandomValues(array);
63
+ return `input-${array[0].toString(36)}`;
64
+ }
65
+
66
+ // Fallback: timestamp-based ID if crypto is unavailable
67
+ return `input-${Date.now().toString(36)}`;
68
+ }
69
+
70
+ const inputId = createInputId();
71
+ let inputEl: HTMLInputElement | undefined = $state();
72
+ // Internal DOM focus state; externalFocused is handled via isVisuallyFocused
73
+ let isFocused = $state(false);
74
+ // TUI cursor position (character index in the actual value string)
75
+ let cursorPos = $state(0);
76
+
77
+ // Spring values for GUI mode animations
78
+ let labelFloat = $state(0);
79
+ let focusRing = $state(0);
80
+
81
+ const prefersReducedMotion =
82
+ typeof window !== "undefined"
83
+ ? window.matchMedia("(prefers-reduced-motion: reduce)").matches
84
+ : false;
85
+
86
+ let labelSpring: ReturnType<typeof createSpring>;
87
+ let focusSpring: ReturnType<typeof createSpring>;
88
+
89
+ $effect(() => {
90
+ if (prefersReducedMotion || isTui) return;
91
+ labelSpring = createSpring(0, "gentle", (v) => (labelFloat = v));
92
+ focusSpring = createSpring(0, "gentle", (v) => (focusRing = v));
93
+ return () => {
94
+ labelSpring?.stop();
95
+ focusSpring?.stop();
96
+ };
97
+ });
98
+
99
+ // Label floats when focused or when there is content
100
+ const isActive = $derived(isFocused || externalFocused || !!(value));
101
+
102
+ $effect(() => {
103
+ const target = isActive ? 1 : 0;
104
+ if (prefersReducedMotion || isTui) {
105
+ labelFloat = target;
106
+ } else {
107
+ labelSpring?.set(target);
108
+ }
109
+ });
110
+
111
+ // Programmatically focus the native input when externalFocused is set
112
+ $effect(() => {
113
+ if (externalFocused && inputEl && !disabled) {
114
+ inputEl.focus();
115
+ }
116
+ });
117
+
118
+ // ── TUI helpers ──────────────────────────────────────────────────────────────
119
+
120
+ const isVisuallyFocused = $derived(isFocused || externalFocused);
121
+
122
+ // Ensure we always have room for a minimum viable box (1 char + 2 borders)
123
+ const safeCols = $derived(Math.max(3, cols));
124
+ // Inner content width (minus 2 side border chars)
125
+ const innerWidth = $derived(safeCols - 2);
126
+
127
+ // Password masking
128
+ const displayValue = $derived(
129
+ password ? "●".repeat((value ?? "").length) : (value ?? ""),
130
+ );
131
+
132
+ // Top border: ┌─ Label ──...──┐ or ┌──...──┐
133
+ // Truncate label if it would overflow the border line
134
+ const tuiTopBorder = $derived(
135
+ label
136
+ ? (() => {
137
+ // "─ " + label + " " must fit inside innerWidth; ┌┐ corners are separate
138
+ const maxLabelLen = Math.max(0, innerWidth - 4);
139
+ const safeLabel = label.length > maxLabelLen ? label.substring(0, maxLabelLen) : label;
140
+ const section = `─ ${safeLabel} `;
141
+ const fill = Math.max(0, innerWidth - section.length);
142
+ return `┌${section}${"─".repeat(fill)}┐`;
143
+ })()
144
+ : `┌${"─".repeat(innerWidth)}┐`,
145
+ );
146
+
147
+ /**
148
+ * Compute what to render in the content row.
149
+ *
150
+ * When focused: [before-cursor][█][after-cursor][padding] = innerWidth chars
151
+ * When unfocused: [text or placeholder] = innerWidth chars
152
+ *
153
+ * For long text, a scroll window is used so the cursor stays visible.
154
+ */
155
+ const tuiContent = $derived.by(() => {
156
+ const text = displayValue;
157
+ const len = text.length;
158
+ const cp = Math.min(Math.max(0, cursorPos), len);
159
+
160
+ if (!isVisuallyFocused) {
161
+ if (!text) {
162
+ return {
163
+ kind: "placeholder" as const,
164
+ text: (placeholder || "").substring(0, innerWidth).padEnd(innerWidth, " "),
165
+ };
166
+ }
167
+ return {
168
+ kind: "text" as const,
169
+ text: text.substring(0, innerWidth).padEnd(innerWidth, " "),
170
+ };
171
+ }
172
+
173
+ // Focused: reserve 1 char for the cursor block █
174
+ const textSlots = innerWidth - 1;
175
+
176
+ // Compute scroll window so cursor is always visible
177
+ let windowStart: number;
178
+ if (len <= textSlots) {
179
+ windowStart = 0;
180
+ } else {
181
+ windowStart = Math.min(cp, len - textSlots);
182
+ windowStart = Math.max(0, windowStart);
183
+ if (cp < windowStart) windowStart = cp;
184
+ }
185
+
186
+ const before = text.substring(windowStart, cp);
187
+ const after = text.substring(cp, windowStart + textSlots);
188
+ const pad = " ".repeat(Math.max(0, textSlots - before.length - after.length));
189
+
190
+ return { kind: "cursor" as const, before, after, pad };
191
+ });
192
+
193
+ // Sync cursor position from the native input element.
194
+ // queueMicrotask lets the browser update selectionStart before we read it.
195
+ function syncCursor() {
196
+ const el = inputEl;
197
+ if (!el) return;
198
+ if (typeof queueMicrotask !== "undefined") {
199
+ queueMicrotask(() => {
200
+ cursorPos = el.selectionStart ?? (value ?? "").length;
201
+ });
202
+ } else {
203
+ cursorPos = el.selectionStart ?? (value ?? "").length;
204
+ }
205
+ }
206
+
207
+ // Bottom border: └──...──┘
208
+ const tuiBottomBorder = $derived(`└${"─".repeat(innerWidth)}┘`);
209
+
210
+ // ── Event handlers ───────────────────────────────────────────────────────────
211
+
212
+ function handleInput(e: Event) {
213
+ const target = e.target as HTMLInputElement;
214
+ value = target.value;
215
+ onchange?.(value);
216
+ syncCursor();
217
+ }
218
+
219
+ function handleKeydown(e: KeyboardEvent) {
220
+ onkeydown?.(e);
221
+ if (e.key === "Enter") {
222
+ onsubmit?.(value ?? "");
223
+ }
224
+ // Sync cursor after arrow keys, Home, End, Delete, Backspace, etc.
225
+ syncCursor();
226
+ }
227
+
228
+ function handleFocus() {
229
+ isFocused = true;
230
+ syncCursor();
231
+ if (prefersReducedMotion || isTui) {
232
+ focusRing = 1;
233
+ } else {
234
+ focusSpring?.set(1);
235
+ }
236
+ }
237
+
238
+ function handleBlur() {
239
+ isFocused = false;
240
+ if (prefersReducedMotion || isTui) {
241
+ focusRing = 0;
242
+ } else {
243
+ focusSpring?.set(0);
244
+ }
245
+ }
246
+ </script>
247
+
248
+ {#if isTui}
249
+ <!-- TUI mode: box-drawing characters, hidden real <input> for event capture -->
250
+ <!-- Wrapping in <label> means clicking the visual box focuses the hidden input -->
251
+ <label
252
+ class="tui-input {className}"
253
+ class:focused={isVisuallyFocused}
254
+ class:disabled
255
+ >
256
+ <div class="tui-top" class:focused={isVisuallyFocused}>{tuiTopBorder}</div>
257
+ <div class="tui-row">
258
+ <span class="tui-side" class:focused={isVisuallyFocused}>│</span>
259
+ <span class="tui-content" class:placeholder={tuiContent.kind === "placeholder"}>
260
+ {#if tuiContent.kind === "placeholder"}
261
+ {tuiContent.text}
262
+ {:else if tuiContent.kind === "text"}
263
+ {tuiContent.text}
264
+ {:else}
265
+ {tuiContent.before}<span class="tui-cursor">█</span>{tuiContent.after}{tuiContent.pad}
266
+ {/if}
267
+ </span>
268
+ <span class="tui-side" class:focused={isVisuallyFocused}>│</span>
269
+ </div>
270
+ <div class="tui-bottom" class:focused={isVisuallyFocused}>{tuiBottomBorder}</div>
271
+
272
+ <!-- Hidden real input: captures keyboard events and value -->
273
+ <input
274
+ bind:this={inputEl}
275
+ class="tui-hidden"
276
+ type={password ? "password" : "text"}
277
+ {value}
278
+ {disabled}
279
+ maxlength={maxLength}
280
+ aria-label={label || placeholder || undefined}
281
+ oninput={handleInput}
282
+ onkeydown={handleKeydown}
283
+ onfocus={handleFocus}
284
+ onblur={handleBlur}
285
+ onclick={syncCursor}
286
+ onselect={syncCursor}
287
+ />
288
+ </label>
289
+ {:else}
290
+ <!-- GUI mode: floating label with spring animation + focus ring -->
291
+ <div
292
+ class="input-wrapper {className}"
293
+ class:focused={isVisuallyFocused}
294
+ class:filled={!!(value)}
295
+ class:disabled
296
+ >
297
+ <input
298
+ bind:this={inputEl}
299
+ id={inputId}
300
+ class="input-field"
301
+ type={password ? "password" : "text"}
302
+ {value}
303
+ placeholder={label ? "" : placeholder}
304
+ {disabled}
305
+ maxlength={maxLength}
306
+ style:--focus-ring={focusRing}
307
+ oninput={handleInput}
308
+ onkeydown={handleKeydown}
309
+ onfocus={handleFocus}
310
+ onblur={handleBlur}
311
+ />
312
+ {#if label}
313
+ <label
314
+ class="input-label"
315
+ class:active={isActive}
316
+ for={inputId}
317
+ style:transform="translateY(calc(-50% - {(labelFloat * 14).toFixed(2)}px)) scale({(1 - labelFloat * 0.2).toFixed(3)})"
318
+ >{label}</label>
319
+ {/if}
320
+ </div>
321
+ {/if}
322
+
323
+ <style>
324
+ /* ===== GUI mode ===== */
325
+ .input-wrapper {
326
+ position: relative;
327
+ display: inline-flex;
328
+ flex-direction: column;
329
+ width: 100%;
330
+ }
331
+
332
+ .input-field {
333
+ width: 100%;
334
+ box-sizing: border-box;
335
+ padding: var(--space-4, 16px) var(--space-3, 12px) var(--space-2, 8px);
336
+ background: var(--surface-2, #1e1e1e);
337
+ border: 1.5px solid var(--color-border, #2a2a2a);
338
+ border-radius: var(--radius-md, 10px);
339
+ color: var(--color-text, #e8e8e8);
340
+ font-family: inherit;
341
+ font-size: var(--text-base, 16px);
342
+ line-height: var(--leading-normal, 1.5);
343
+ outline: none;
344
+ transition: border-color 0.15s ease;
345
+ box-shadow: 0 0 0 calc(3px * var(--focus-ring, 0))
346
+ color-mix(in srgb, var(--color-accent, #6366f1) 40%, transparent);
347
+ }
348
+
349
+ .focused .input-field,
350
+ .input-field:focus {
351
+ border-color: var(--color-accent, #6366f1);
352
+ }
353
+
354
+ .input-label {
355
+ position: absolute;
356
+ left: var(--space-3, 12px);
357
+ top: 50%;
358
+ font-size: var(--text-base, 16px);
359
+ color: var(--color-text-muted, #888888);
360
+ cursor: text;
361
+ transform-origin: left center;
362
+ /* transform is driven by spring via inline style */
363
+ transition: color 0.15s ease;
364
+ white-space: nowrap;
365
+ user-select: none;
366
+ }
367
+
368
+ .input-label.active {
369
+ color: var(--color-accent, #6366f1);
370
+ }
371
+
372
+ .disabled .input-field {
373
+ opacity: 0.4;
374
+ cursor: not-allowed;
375
+ pointer-events: none;
376
+ }
377
+
378
+ .disabled .input-label {
379
+ pointer-events: none;
380
+ cursor: not-allowed;
381
+ }
382
+
383
+ /* ===== TUI mode ===== */
384
+ .tui-input {
385
+ position: relative;
386
+ display: inline-block;
387
+ font-family: var(--font-mono, monospace);
388
+ font-size: var(--text-sm, 14px);
389
+ line-height: 1.4;
390
+ color: var(--tui-text, #e0e0e0);
391
+ background: var(--tui-surface, #16213e);
392
+ cursor: text;
393
+ }
394
+
395
+ .tui-input.disabled {
396
+ opacity: 0.4;
397
+ cursor: not-allowed;
398
+ pointer-events: none;
399
+ }
400
+
401
+ .tui-top,
402
+ .tui-bottom {
403
+ color: var(--tui-border, #0f3460);
404
+ white-space: pre;
405
+ user-select: none;
406
+ }
407
+
408
+ .tui-top.focused,
409
+ .tui-bottom.focused {
410
+ color: var(--tui-accent, #00d4ff);
411
+ }
412
+
413
+ .tui-row {
414
+ display: flex;
415
+ white-space: pre;
416
+ }
417
+
418
+ .tui-side {
419
+ color: var(--tui-border, #0f3460);
420
+ user-select: none;
421
+ }
422
+
423
+ .tui-side.focused {
424
+ color: var(--tui-accent, #00d4ff);
425
+ }
426
+
427
+ .tui-content {
428
+ flex: 1;
429
+ color: var(--tui-text, #e0e0e0);
430
+ white-space: pre;
431
+ overflow: hidden;
432
+ }
433
+
434
+ .tui-content.placeholder {
435
+ color: var(--tui-text-dim, #888888);
436
+ }
437
+
438
+ .tui-cursor {
439
+ color: var(--tui-accent, #00d4ff);
440
+ animation: cursor-blink 1s step-end infinite;
441
+ }
442
+
443
+ @keyframes cursor-blink {
444
+ 0%, 100% { opacity: 1; }
445
+ 50% { opacity: 0; }
446
+ }
447
+
448
+ @media (prefers-reduced-motion: reduce) {
449
+ .tui-cursor {
450
+ animation: none;
451
+ }
452
+ }
453
+
454
+ /* Visually hidden but accessible to keyboard and screen readers */
455
+ .tui-hidden {
456
+ position: absolute;
457
+ opacity: 0;
458
+ width: 1px;
459
+ height: 1px;
460
+ padding: 0;
461
+ margin: -1px;
462
+ overflow: hidden;
463
+ clip: rect(0, 0, 0, 0);
464
+ white-space: nowrap;
465
+ border: 0;
466
+ pointer-events: none;
467
+ }
468
+ </style>
@@ -0,0 +1,21 @@
1
+ interface Props {
2
+ tui?: boolean;
3
+ value?: string;
4
+ placeholder?: string;
5
+ label?: string;
6
+ focused?: boolean;
7
+ disabled?: boolean;
8
+ /** Mask input characters */
9
+ password?: boolean;
10
+ maxLength?: number;
11
+ class?: string;
12
+ /** TUI mode: total character width including border columns. Default: 32 */
13
+ cols?: number;
14
+ onchange?: (value: string) => void;
15
+ /** Fired when Enter is pressed */
16
+ onsubmit?: (value: string) => void;
17
+ onkeydown?: (e: KeyboardEvent) => void;
18
+ }
19
+ declare const Input: import("svelte").Component<Props, {}, "value">;
20
+ type Input = ReturnType<typeof Input>;
21
+ export default Input;