@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,114 @@
1
+ <!--
2
+ Tooltip — Brief label shown on hover/focus of the trigger.
3
+
4
+ GUI mode: floating overlay with fade transition, arrow pointer.
5
+ TUI mode: inline label rendered in terminal-safe monospace.
6
+
7
+ Usage: the `children` snippet receives `{ describedBy: string }` —
8
+ apply `aria-describedby={describedBy}` to the interactive element
9
+ inside so screen readers can associate the tooltip text with it.
10
+ -->
11
+ <script lang="ts">
12
+ import { useTui } from "../useTui.js";
13
+ import type { Snippet } from "svelte";
14
+
15
+ interface Props {
16
+ /** Tooltip label text. */
17
+ content: string;
18
+ /** Position relative to the trigger. Default: "top". */
19
+ position?: "top" | "bottom" | "left" | "right";
20
+ /** Enable TUI mode. */
21
+ tui?: boolean;
22
+ /** The element that triggers the tooltip. Receives { describedBy } so you can
23
+ * apply aria-describedby to the interactive element inside. */
24
+ children: Snippet<[{ describedBy: string }]>;
25
+ }
26
+
27
+ let { content, position = "top", tui = false, children }: Props = $props();
28
+
29
+ const getTuiCtx = useTui();
30
+ const isTui = $derived(tui || getTuiCtx());
31
+
32
+ let visible = $state(false);
33
+
34
+ function createId(prefix: string) {
35
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
36
+ return `${prefix}-${crypto.randomUUID()}`;
37
+ }
38
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
39
+ const array = new Uint32Array(1);
40
+ crypto.getRandomValues(array);
41
+ return `${prefix}-${array[0].toString(36)}`;
42
+ }
43
+ return `${prefix}-${Date.now().toString(36)}`;
44
+ }
45
+
46
+ const id = createId("tooltip");
47
+ </script>
48
+
49
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
50
+ <span
51
+ class="tooltip-host"
52
+ class:tui={isTui}
53
+ onmouseenter={() => (visible = true)}
54
+ onmouseleave={() => (visible = false)}
55
+ onfocusin={() => (visible = true)}
56
+ onfocusout={() => (visible = false)}
57
+ >
58
+ {@render children({ describedBy: id })}
59
+
60
+ {#if isTui}
61
+ {#if visible}
62
+ <span class="tui-tooltip" role="tooltip" {id}>[{content}]</span>
63
+ {/if}
64
+ {:else}
65
+ <span
66
+ class="tooltip tooltip--{position}"
67
+ class:tooltip--visible={visible}
68
+ role="tooltip"
69
+ {id}
70
+ >
71
+ {content}
72
+ </span>
73
+ {/if}
74
+ </span>
75
+
76
+ <style>
77
+ .tooltip-host {
78
+ position: relative;
79
+ display: inline-flex;
80
+ align-items: center;
81
+ }
82
+
83
+ /* ===== GUI mode ===== */
84
+ .tooltip {
85
+ position: absolute;
86
+ z-index: var(--z-tooltip, 900);
87
+ padding: var(--space-1, 4px) var(--space-2, 8px);
88
+ background: var(--surface-2, #1e1e1e);
89
+ color: var(--color-text, #e8e8e8);
90
+ border: 1px solid var(--color-border, #2a2a2a);
91
+ border-radius: var(--radius-sm, 6px);
92
+ font-size: var(--text-xs, 12px);
93
+ white-space: nowrap;
94
+ pointer-events: none;
95
+ box-shadow: var(--elevation-2, var(--shadow-md));
96
+ opacity: 0;
97
+ transition: opacity 0.15s ease;
98
+ }
99
+
100
+ .tooltip--visible { opacity: 1; }
101
+
102
+ .tooltip--top { bottom: calc(100% + var(--space-1, 4px)); left: 50%; transform: translateX(-50%); }
103
+ .tooltip--bottom { top: calc(100% + var(--space-1, 4px)); left: 50%; transform: translateX(-50%); }
104
+ .tooltip--left { right: calc(100% + var(--space-1, 4px)); top: 50%; transform: translateY(-50%); }
105
+ .tooltip--right { left: calc(100% + var(--space-1, 4px)); top: 50%; transform: translateY(-50%); }
106
+
107
+ /* ===== TUI mode ===== */
108
+ .tui-tooltip {
109
+ margin-left: var(--space-1, 4px);
110
+ color: var(--tui-text-dim, #888888);
111
+ font-family: var(--font-mono, monospace);
112
+ font-size: var(--text-xs, 12px);
113
+ }
114
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ /** Tooltip label text. */
4
+ content: string;
5
+ /** Position relative to the trigger. Default: "top". */
6
+ position?: "top" | "bottom" | "left" | "right";
7
+ /** Enable TUI mode. */
8
+ tui?: boolean;
9
+ /** The element that triggers the tooltip. Receives { describedBy } so you can
10
+ * apply aria-describedby to the interactive element inside. */
11
+ children: Snippet<[{
12
+ describedBy: string;
13
+ }]>;
14
+ }
15
+ declare const Tooltip: import("svelte").Component<Props, {}, "">;
16
+ type Tooltip = ReturnType<typeof Tooltip>;
17
+ export default Tooltip;
@@ -0,0 +1,7 @@
1
+ export { default as Tooltip } from "./Tooltip.svelte";
2
+ export { default as Popover } from "./Popover.svelte";
3
+ export { default as Dialog } from "./Dialog.svelte";
4
+ export { default as Toast } from "./Toast.svelte";
5
+ export { default as Menu } from "./Menu.svelte";
6
+ export { default as ContextMenu } from "./ContextMenu.svelte";
7
+ export type { MenuItem } from "./Menu.types.js";
@@ -0,0 +1,6 @@
1
+ export { default as Tooltip } from "./Tooltip.svelte";
2
+ export { default as Popover } from "./Popover.svelte";
3
+ export { default as Dialog } from "./Dialog.svelte";
4
+ export { default as Toast } from "./Toast.svelte";
5
+ export { default as Menu } from "./Menu.svelte";
6
+ export { default as ContextMenu } from "./ContextMenu.svelte";
@@ -0,0 +1,217 @@
1
+ <!--
2
+ Button — The first test. Does clicking it feel like pressing a real thing?
3
+
4
+ Features:
5
+ - Spring-physics press animation (scale + shadow depth)
6
+ - Focus ring with gentle spring
7
+ - Ripple effect on click (optional)
8
+ - Keyboard accessible (Enter + Space)
9
+ - Reduced motion support
10
+ - Variants: solid, ghost, outline
11
+ - Sizes: sm, md, lg
12
+ - TUI mode: box-drawing borders, terminal-safe colors, no decorative CSS
13
+ -->
14
+ <script lang="ts">
15
+ import { createSpring, SPRING_PRESETS } from "../motion/spring.js";
16
+ import { useTui } from "../useTui.js";
17
+ import type { Snippet } from "svelte";
18
+
19
+ interface Props {
20
+ variant?: "solid" | "ghost" | "outline";
21
+ size?: "sm" | "md" | "lg";
22
+ disabled?: boolean;
23
+ /** Enable TUI mode: box-drawing borders, terminal-safe colors, no decorative CSS. */
24
+ tui?: boolean;
25
+ onclick?: (e: MouseEvent) => void;
26
+ children: Snippet;
27
+ }
28
+
29
+ let { variant = "solid", size = "md", disabled = false, tui = false, onclick, children }: Props = $props();
30
+
31
+ const getTuiCtx = useTui();
32
+ const isTui = $derived(tui || getTuiCtx());
33
+
34
+ let buttonEl: HTMLButtonElement | undefined = $state();
35
+ let scale = $state(1);
36
+ let shadowDepth = $state(1);
37
+ let focusRing = $state(0);
38
+
39
+ // Spring controllers
40
+ let scaleSpring: ReturnType<typeof createSpring>;
41
+ let shadowSpring: ReturnType<typeof createSpring>;
42
+ let focusSpring: ReturnType<typeof createSpring>;
43
+
44
+ // Check for reduced motion preference
45
+ const prefersReducedMotion =
46
+ typeof window !== "undefined"
47
+ ? window.matchMedia("(prefers-reduced-motion: reduce)").matches
48
+ : false;
49
+
50
+ $effect(() => {
51
+ if (prefersReducedMotion) return;
52
+
53
+ scaleSpring = createSpring(1, "snappy", (v) => (scale = v));
54
+ shadowSpring = createSpring(1, "snappy", (v) => (shadowDepth = v));
55
+ focusSpring = createSpring(0, "gentle", (v) => (focusRing = v));
56
+
57
+ return () => {
58
+ scaleSpring?.stop();
59
+ shadowSpring?.stop();
60
+ focusSpring?.stop();
61
+ };
62
+ });
63
+
64
+ function handlePointerDown() {
65
+ if (disabled || prefersReducedMotion) return;
66
+ scaleSpring?.set(0.96);
67
+ shadowSpring?.set(0.3);
68
+ }
69
+
70
+ function handlePointerUp() {
71
+ if (disabled || prefersReducedMotion) return;
72
+ scaleSpring?.set(1);
73
+ shadowSpring?.set(1);
74
+ }
75
+
76
+ function handlePointerLeave() {
77
+ if (disabled || prefersReducedMotion) return;
78
+ scaleSpring?.set(1);
79
+ shadowSpring?.set(1);
80
+ }
81
+
82
+ function handleFocus() {
83
+ if (prefersReducedMotion) { focusRing = 1; return; }
84
+ focusSpring?.set(1);
85
+ }
86
+
87
+ function handleBlur() {
88
+ if (prefersReducedMotion) { focusRing = 0; return; }
89
+ focusSpring?.set(0);
90
+ }
91
+ </script>
92
+
93
+ <button
94
+ bind:this={buttonEl}
95
+ class="btn btn-{variant} btn-{size}"
96
+ class:disabled
97
+ class:tui={isTui}
98
+ {disabled}
99
+ {onclick}
100
+ onpointerdown={handlePointerDown}
101
+ onpointerup={handlePointerUp}
102
+ onpointerleave={handlePointerLeave}
103
+ onfocus={handleFocus}
104
+ onblur={handleBlur}
105
+ style:transform={isTui ? undefined : `scale(${scale})`}
106
+ style:--shadow-depth={isTui ? undefined : shadowDepth}
107
+ style:--focus-opacity={focusRing}
108
+ >
109
+ {#if isTui}
110
+ <span class="tui-border-l">╠</span>{@render children()}<span class="tui-border-r">╣</span>
111
+ {:else}
112
+ {@render children()}
113
+ {/if}
114
+ </button>
115
+
116
+ <style>
117
+ .btn {
118
+ position: relative;
119
+ display: inline-flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ gap: var(--space-2, 8px);
123
+ border: none;
124
+ cursor: pointer;
125
+ font-family: inherit;
126
+ font-weight: 600;
127
+ white-space: nowrap;
128
+ user-select: none;
129
+ -webkit-user-select: none;
130
+ outline: none;
131
+ will-change: transform;
132
+
133
+ /* Focus ring via box-shadow */
134
+ box-shadow:
135
+ 0 calc(1px * var(--shadow-depth, 1)) calc(2px * var(--shadow-depth, 1)) rgba(0,0,0,0.05),
136
+ 0 calc(4px * var(--shadow-depth, 1)) calc(8px * var(--shadow-depth, 1)) rgba(0,0,0,0.1),
137
+ 0 0 0 calc(3px * var(--focus-opacity, 0)) var(--color-accent, #6366f1);
138
+ }
139
+
140
+ /* Sizes */
141
+ .btn-sm { padding: 6px 14px; font-size: 13px; border-radius: 6px; }
142
+ .btn-md { padding: 10px 20px; font-size: 15px; border-radius: 8px; }
143
+ .btn-lg { padding: 14px 28px; font-size: 17px; border-radius: 10px; }
144
+
145
+ /* Variants */
146
+ .btn-solid {
147
+ background: var(--color-accent, #6366f1);
148
+ color: white;
149
+ }
150
+ .btn-ghost {
151
+ background: transparent;
152
+ color: var(--color-text, #e8e8e8);
153
+ }
154
+ .btn-ghost:hover { background: var(--alpha-10, rgba(255,255,255,0.1)); }
155
+ .btn-outline {
156
+ background: transparent;
157
+ color: var(--color-accent, #6366f1);
158
+ border: 1.5px solid var(--color-accent, #6366f1);
159
+ }
160
+
161
+ /* Disabled */
162
+ .disabled {
163
+ opacity: 0.4;
164
+ cursor: not-allowed;
165
+ pointer-events: none;
166
+ }
167
+
168
+ /* ===== TUI mode ===== */
169
+ .btn.tui {
170
+ background: var(--tui-surface, #16213e);
171
+ color: var(--tui-text, #e0e0e0);
172
+ border: 1px solid var(--tui-border, #0f3460);
173
+ border-radius: var(--tui-radius, 0px);
174
+ box-shadow: var(--tui-shadow, none);
175
+ font-family: var(--font-mono, monospace);
176
+ transform: none !important;
177
+ padding: 0 var(--space-2, 8px);
178
+ }
179
+ .btn.tui:hover:not(.disabled) {
180
+ background: var(--tui-highlight, #533483);
181
+ border-color: var(--tui-accent, #00d4ff);
182
+ color: var(--tui-accent, #00d4ff);
183
+ }
184
+ .btn.tui:focus-visible {
185
+ outline: 1px solid var(--tui-accent, #00d4ff);
186
+ outline-offset: 1px;
187
+ }
188
+ .btn.tui.btn-solid {
189
+ background: var(--tui-accent, #00d4ff);
190
+ color: var(--tui-bg, #1a1a2e);
191
+ border-color: var(--tui-accent, #00d4ff);
192
+ }
193
+ .btn.tui.btn-solid:hover:not(.disabled) {
194
+ background: var(--tui-highlight, #533483);
195
+ color: var(--tui-accent, #00d4ff);
196
+ }
197
+ .btn.tui.btn-ghost {
198
+ background: transparent;
199
+ border-color: transparent;
200
+ color: var(--tui-text, #e0e0e0);
201
+ }
202
+ .btn.tui.btn-ghost:hover:not(.disabled) {
203
+ background: var(--tui-highlight, #533483);
204
+ border-color: transparent;
205
+ }
206
+ .btn.tui.btn-outline {
207
+ background: transparent;
208
+ border-color: var(--tui-border, #0f3460);
209
+ color: var(--tui-accent, #00d4ff);
210
+ }
211
+ .tui-border-l,
212
+ .tui-border-r {
213
+ color: var(--tui-border, #0f3460);
214
+ user-select: none;
215
+ font-style: normal;
216
+ }
217
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ variant?: "solid" | "ghost" | "outline";
4
+ size?: "sm" | "md" | "lg";
5
+ disabled?: boolean;
6
+ /** Enable TUI mode: box-drawing borders, terminal-safe colors, no decorative CSS. */
7
+ tui?: boolean;
8
+ onclick?: (e: MouseEvent) => void;
9
+ children: Snippet;
10
+ }
11
+ declare const Button: import("svelte").Component<Props, {}, "">;
12
+ type Button = ReturnType<typeof Button>;
13
+ export default Button;
@@ -0,0 +1,242 @@
1
+ <!--
2
+ ContextMenu — Generic right-click / programmatic context menu primitive.
3
+
4
+ Features:
5
+ - Positioned via x/y screen coordinates; auto-flips near container edges
6
+ - Keyboard navigation: ArrowUp/Down, Enter to select, Escape to close
7
+ - Click-outside closes the menu
8
+ - Separator rows (visual dividers)
9
+ - Disabled items are rendered but skipped by keyboard navigation
10
+ - ARIA: role="menu" with role="menuitem" children
11
+ -->
12
+ <script lang="ts">
13
+ import type { ContextMenuItem, ContextMenuActionItem } from "./ContextMenu.types.js";
14
+
15
+ // Module-level counter ensures unique IDs even when multiple menus mount simultaneously
16
+ let _menuCounter = 0;
17
+
18
+ interface Props {
19
+ /** Menu items to display. */
20
+ items: ContextMenuItem[];
21
+ /** Screen X coordinate (pixels from left of nearest positioned ancestor). */
22
+ x: number;
23
+ /** Screen Y coordinate (pixels from top of nearest positioned ancestor). */
24
+ y: number;
25
+ /** Fired when the user activates a non-disabled, non-separator item. */
26
+ onselect?: (item: ContextMenuActionItem) => void;
27
+ /** Fired when the menu should close (Escape, outside click, item selected). */
28
+ onclose?: () => void;
29
+ /** Custom CSS class. */
30
+ class?: string;
31
+ }
32
+
33
+ let { items, x, y, onselect, onclose, class: className = "" }: Props = $props();
34
+
35
+ let menuEl: HTMLElement | undefined = $state();
36
+
37
+ // Stable per-instance prefix for item IDs (avoids collisions when multiple menus mount)
38
+ const menuId = `ctxmenu-${_menuCounter++}`;
39
+
40
+ // Flipped position — adjusted after mount to keep within container bounds
41
+ let adjustedX = $state(0);
42
+ let adjustedY = $state(0);
43
+
44
+ let activeIdx = $state(-1);
45
+
46
+ // Type guard: is the item an actionable (non-separator) item?
47
+ function isAction(item: ContextMenuItem): item is ContextMenuActionItem {
48
+ return !item.separator;
49
+ }
50
+
51
+ // Navigable items (skip separators + disabled)
52
+ const navigable = $derived(
53
+ items
54
+ .map((it, i) => ({ it, i }))
55
+ .filter((entry): entry is { it: ContextMenuActionItem; i: number } =>
56
+ isAction(entry.it) && !entry.it.disabled,
57
+ ),
58
+ );
59
+
60
+ // DOM id of the currently active menuitem (drives aria-activedescendant)
61
+ const activeItemId = $derived(
62
+ activeIdx >= 0 && activeIdx < navigable.length
63
+ ? `${menuId}-${navigable[activeIdx].it.id}`
64
+ : undefined,
65
+ );
66
+
67
+ // Precomputed map: item-list index → navigable index (O(1) lookup during render)
68
+ const navIndexByItemIndex = $derived(new Map(navigable.map((n, idx) => [n.i, idx])));
69
+
70
+ // Flip position when menu would overflow the containing positioned element
71
+ $effect(() => {
72
+ const px = x;
73
+ const py = y;
74
+ if (!menuEl) {
75
+ adjustedX = px;
76
+ adjustedY = py;
77
+ return;
78
+ }
79
+ const menuRect = menuEl.getBoundingClientRect();
80
+ const parentEl = menuEl.offsetParent as HTMLElement | null;
81
+ const parentRect = parentEl ? parentEl.getBoundingClientRect() : { width: window.innerWidth, height: window.innerHeight };
82
+ const parentW = parentRect.width;
83
+ const parentH = parentRect.height;
84
+ // Flip horizontally: try left of cursor first; clamp so menu always stays in bounds
85
+ adjustedX = px + menuRect.width > parentW
86
+ ? Math.max(0, Math.min(px - menuRect.width, parentW - menuRect.width))
87
+ : px;
88
+ // Flip vertically: try above cursor first; clamp so menu always stays in bounds
89
+ adjustedY = py + menuRect.height > parentH
90
+ ? Math.max(0, Math.min(py - menuRect.height, parentH - menuRect.height))
91
+ : py;
92
+ });
93
+
94
+ // Close on outside click
95
+ $effect(() => {
96
+ function handleOutside(e: MouseEvent) {
97
+ if (menuEl && !menuEl.contains(e.target as Node)) onclose?.();
98
+ }
99
+ document.addEventListener("mousedown", handleOutside);
100
+ return () => document.removeEventListener("mousedown", handleOutside);
101
+ });
102
+
103
+ // Auto-focus the menu on mount for keyboard nav
104
+ $effect(() => {
105
+ menuEl?.focus();
106
+ });
107
+
108
+ function activate(item: ContextMenuActionItem) {
109
+ if (item.disabled) return;
110
+ onselect?.(item);
111
+ onclose?.();
112
+ }
113
+
114
+ function onKeyDown(e: KeyboardEvent) {
115
+ const count = navigable.length;
116
+ if (e.key === "Escape") { e.preventDefault(); onclose?.(); return; }
117
+ if (e.key === "ArrowDown") {
118
+ e.preventDefault();
119
+ if (count === 0) return;
120
+ activeIdx = activeIdx < count - 1 ? activeIdx + 1 : 0;
121
+ return;
122
+ }
123
+ if (e.key === "ArrowUp") {
124
+ e.preventDefault();
125
+ if (count === 0) return;
126
+ activeIdx = activeIdx > 0 ? activeIdx - 1 : count - 1;
127
+ return;
128
+ }
129
+ if (e.key === "Enter") {
130
+ if (count === 0 || activeIdx < 0 || activeIdx >= count) return;
131
+ e.preventDefault();
132
+ activate(navigable[activeIdx].it);
133
+ }
134
+ }
135
+ </script>
136
+
137
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
138
+ <ul
139
+ bind:this={menuEl}
140
+ class="ctx-menu {className}"
141
+ style:left="{adjustedX}px"
142
+ style:top="{adjustedY}px"
143
+ role="menu"
144
+ tabindex="-1"
145
+ aria-activedescendant={activeItemId}
146
+ onkeydown={onKeyDown}
147
+ onmousedown={(e) => e.stopPropagation()}
148
+ >
149
+ {#each items as item, i}
150
+ {#if item.separator}
151
+ <li class="ctx-menu__sep" role="separator" aria-hidden="true"></li>
152
+ {:else if isAction(item)}
153
+ {@const navIdx = navIndexByItemIndex.get(i) ?? -1}
154
+ <li
155
+ id="{menuId}-{item.id}"
156
+ class="ctx-menu__item"
157
+ class:ctx-menu__item--disabled={item.disabled}
158
+ class:ctx-menu__item--active={navIdx === activeIdx}
159
+ role="menuitem"
160
+ tabindex={item.disabled ? undefined : -1}
161
+ aria-disabled={item.disabled ? "true" : undefined}
162
+ onclick={() => activate(item)}
163
+ onkeydown={(e) => {
164
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); activate(item); }
165
+ }}
166
+ onmouseenter={() => { if (!item.disabled) activeIdx = navIdx; }}
167
+ >
168
+ {#if item.icon}
169
+ <span class="ctx-menu__icon" aria-hidden="true">{item.icon}</span>
170
+ {/if}
171
+ <span class="ctx-menu__label">{item.label}</span>
172
+ {#if item.shortcut}
173
+ <span class="ctx-menu__shortcut" aria-hidden="true">{item.shortcut}</span>
174
+ {/if}
175
+ </li>
176
+ {/if}
177
+ {/each}
178
+ </ul>
179
+
180
+ <style>
181
+ .ctx-menu {
182
+ position: absolute;
183
+ background: var(--surface-2, #1e1e1e);
184
+ border: 1px solid var(--color-border, #2a2a2a);
185
+ border-radius: var(--radius-md, 10px);
186
+ padding: var(--space-1, 4px);
187
+ min-width: 160px;
188
+ z-index: 200;
189
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
190
+ list-style: none;
191
+ margin: 0;
192
+ outline: none;
193
+ user-select: none;
194
+ }
195
+
196
+ .ctx-menu__sep {
197
+ height: 1px;
198
+ background: var(--color-border, #2a2a2a);
199
+ margin: var(--space-1, 4px) var(--space-2, 8px);
200
+ }
201
+
202
+ .ctx-menu__item {
203
+ display: flex;
204
+ align-items: center;
205
+ gap: var(--space-2, 8px);
206
+ padding: 5px var(--space-2, 8px);
207
+ border-radius: var(--radius-sm, 6px);
208
+ color: var(--color-text, #e8e8e8);
209
+ font-size: 13px;
210
+ cursor: pointer;
211
+ outline: none;
212
+ transition: background 0.1s;
213
+ }
214
+
215
+ .ctx-menu__item:hover:not(.ctx-menu__item--disabled),
216
+ .ctx-menu__item--active:not(.ctx-menu__item--disabled) {
217
+ background: var(--alpha-10, rgba(255, 255, 255, 0.1));
218
+ }
219
+
220
+ .ctx-menu__item--disabled {
221
+ color: var(--color-text-subtle, #555);
222
+ cursor: default;
223
+ }
224
+
225
+ .ctx-menu__icon {
226
+ font-size: 14px;
227
+ width: 16px;
228
+ text-align: center;
229
+ flex-shrink: 0;
230
+ }
231
+
232
+ .ctx-menu__label {
233
+ flex: 1;
234
+ }
235
+
236
+ .ctx-menu__shortcut {
237
+ font-size: 11px;
238
+ color: var(--color-text-subtle, #555);
239
+ font-family: var(--font-mono, monospace);
240
+ flex-shrink: 0;
241
+ }
242
+ </style>
@@ -0,0 +1,18 @@
1
+ import type { ContextMenuItem, ContextMenuActionItem } from "./ContextMenu.types.js";
2
+ interface Props {
3
+ /** Menu items to display. */
4
+ items: ContextMenuItem[];
5
+ /** Screen X coordinate (pixels from left of nearest positioned ancestor). */
6
+ x: number;
7
+ /** Screen Y coordinate (pixels from top of nearest positioned ancestor). */
8
+ y: number;
9
+ /** Fired when the user activates a non-disabled, non-separator item. */
10
+ onselect?: (item: ContextMenuActionItem) => void;
11
+ /** Fired when the menu should close (Escape, outside click, item selected). */
12
+ onclose?: () => void;
13
+ /** Custom CSS class. */
14
+ class?: string;
15
+ }
16
+ declare const ContextMenu: import("svelte").Component<Props, {}, "">;
17
+ type ContextMenu = ReturnType<typeof ContextMenu>;
18
+ export default ContextMenu;
@@ -0,0 +1 @@
1
+ export {};