@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.
- package/dist/app/CanvasBreadcrumb.svelte +146 -0
- package/dist/app/CanvasBreadcrumb.svelte.d.ts +19 -0
- package/dist/app/CanvasBreadcrumb.types.js +1 -0
- package/dist/app/ChatInput.svelte +296 -0
- package/dist/app/ChatInput.svelte.d.ts +15 -0
- package/dist/app/ChatView.svelte +542 -0
- package/dist/app/ChatView.svelte.d.ts +19 -0
- package/dist/app/ChatView.types.js +1 -0
- package/dist/app/ConversationGraph.svelte +471 -0
- package/dist/app/ConversationGraph.svelte.d.ts +20 -0
- package/dist/app/FirstRunWizard.svelte +542 -0
- package/dist/app/FirstRunWizard.svelte.d.ts +10 -0
- package/dist/app/FirstRunWizard.types.js +1 -0
- package/dist/app/MemorySidebar.svelte +258 -0
- package/dist/app/MemorySidebar.svelte.d.ts +9 -0
- package/dist/app/MemorySidebar.types.js +1 -0
- package/dist/app/PeerStatusPanel.svelte +464 -0
- package/dist/app/PeerStatusPanel.svelte.d.ts +16 -0
- package/dist/app/ProcedureCanvas.svelte +994 -0
- package/dist/app/ProcedureCanvas.svelte.d.ts +12 -0
- package/dist/app/ProcedureCanvas.types.js +1 -0
- package/dist/app/ProcedureEditor.svelte +494 -0
- package/dist/app/ProcedureEditor.svelte.d.ts +11 -0
- package/dist/app/ProcedureEditor.types.js +1 -0
- package/dist/app/ProcedureInspector.svelte +520 -0
- package/dist/app/ProcedureInspector.svelte.d.ts +15 -0
- package/dist/app/ProcedureNode.svelte +283 -0
- package/dist/app/ProcedureNode.svelte.d.ts +26 -0
- package/dist/app/Realm.types.js +1 -0
- package/dist/app/RealmIndicator.svelte +81 -0
- package/dist/app/RealmIndicator.svelte.d.ts +10 -0
- package/dist/app/RealmSwitcher.svelte +354 -0
- package/dist/app/RealmSwitcher.svelte.d.ts +16 -0
- package/dist/app/SemanticConversation.types.js +1 -0
- package/dist/app/SemanticSearchInput.svelte +630 -0
- package/dist/app/SemanticSearchInput.svelte.d.ts +20 -0
- package/dist/app/SemanticSearchInput.types.js +1 -0
- package/dist/app/SemanticTimeline.svelte +426 -0
- package/dist/app/SemanticTimeline.svelte.d.ts +13 -0
- package/dist/app/SettingsPanel.svelte +330 -0
- package/dist/app/SettingsPanel.svelte.d.ts +12 -0
- package/dist/app/SettingsPanel.types.js +1 -0
- package/dist/app/SubCanvas.svelte +457 -0
- package/dist/app/SubCanvas.svelte.d.ts +48 -0
- package/dist/app/SubCanvas.types.js +1 -0
- package/dist/app/Sync.types.js +1 -0
- package/dist/app/SyncIndicator.svelte +219 -0
- package/dist/app/SyncIndicator.svelte.d.ts +15 -0
- package/dist/app/SyncTimeline.svelte +299 -0
- package/dist/app/SyncTimeline.svelte.d.ts +12 -0
- package/dist/app/TagCloud.svelte +287 -0
- package/dist/app/TagCloud.svelte.d.ts +12 -0
- package/dist/app/WorkModeToggle.svelte +188 -0
- package/dist/app/WorkModeToggle.svelte.d.ts +17 -0
- package/dist/data/List.svelte +104 -0
- package/dist/data/List.svelte.d.ts +18 -0
- package/dist/data/ListItem.svelte +130 -0
- package/dist/data/ListItem.svelte.d.ts +12 -0
- package/dist/data/Table.svelte +241 -0
- package/dist/data/Table.svelte.d.ts +17 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/disclosure/Accordion.svelte +48 -0
- package/dist/disclosure/Accordion.svelte.d.ts +15 -0
- package/dist/feedback/Badge.svelte +60 -0
- package/dist/feedback/Badge.svelte.d.ts +14 -0
- package/dist/feedback/Callout.svelte +52 -0
- package/dist/feedback/Callout.svelte.d.ts +12 -0
- package/dist/feedback/EmptyState.svelte +47 -0
- package/dist/feedback/EmptyState.svelte.d.ts +12 -0
- package/dist/feedback/ProgressBar.svelte +95 -0
- package/dist/feedback/ProgressBar.svelte.d.ts +16 -0
- package/dist/forms/FileUpload.svelte +99 -0
- package/dist/forms/FileUpload.svelte.d.ts +18 -0
- package/dist/forms/RadioGroup.svelte +84 -0
- package/dist/forms/RadioGroup.svelte.d.ts +19 -0
- package/dist/icons/NerdFont.svelte +44 -0
- package/dist/icons/NerdFont.svelte.d.ts +13 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +70 -6212
- package/dist/layout/Box.svelte +207 -0
- package/dist/layout/Box.svelte.d.ts +22 -0
- package/dist/layout/Sidebar.svelte +210 -0
- package/dist/layout/Sidebar.svelte.d.ts +22 -0
- package/dist/layout/SplitPane.svelte +64 -0
- package/dist/layout/SplitPane.svelte.d.ts +12 -0
- package/dist/layout/StatusBar.svelte +83 -0
- package/dist/layout/StatusBar.svelte.d.ts +12 -0
- package/dist/layout/StatusBarItem.svelte +146 -0
- package/dist/layout/StatusBarItem.svelte.d.ts +15 -0
- package/dist/layout/StatusBarSpacer.svelte +38 -0
- package/dist/layout/StatusBarSpacer.svelte.d.ts +3 -0
- package/dist/layout/Tabs.svelte +254 -0
- package/dist/layout/Tabs.svelte.d.ts +21 -0
- package/dist/layout/Tabs.types.js +1 -0
- package/dist/layout/TitleBar.svelte +422 -0
- package/dist/layout/TitleBar.svelte.d.ts +22 -0
- package/dist/layout/index.d.ts +9 -0
- package/dist/layout/index.js +8 -0
- package/dist/motion/index.d.ts +1 -0
- package/dist/motion/index.js +1 -0
- package/dist/motion/spring.js +116 -0
- package/dist/overlays/ContextMenu.svelte +268 -0
- package/dist/overlays/ContextMenu.svelte.d.ts +17 -0
- package/dist/overlays/Dialog.svelte +264 -0
- package/dist/overlays/Dialog.svelte.d.ts +20 -0
- package/dist/overlays/Menu.svelte +274 -0
- package/dist/overlays/Menu.svelte.d.ts +26 -0
- package/dist/overlays/Menu.types.js +1 -0
- package/dist/overlays/Popover.svelte +158 -0
- package/dist/overlays/Popover.svelte.d.ts +21 -0
- package/dist/overlays/Toast.svelte +179 -0
- package/dist/overlays/Toast.svelte.d.ts +19 -0
- package/dist/overlays/Tooltip.svelte +114 -0
- package/dist/overlays/Tooltip.svelte.d.ts +17 -0
- package/dist/overlays/index.d.ts +7 -0
- package/dist/overlays/index.js +6 -0
- package/dist/primitives/Button.svelte +217 -0
- package/dist/primitives/Button.svelte.d.ts +13 -0
- package/dist/primitives/ContextMenu.svelte +242 -0
- package/dist/primitives/ContextMenu.svelte.d.ts +18 -0
- package/dist/primitives/ContextMenu.types.js +1 -0
- package/dist/primitives/Input.svelte +468 -0
- package/dist/primitives/Input.svelte.d.ts +21 -0
- package/dist/primitives/MarkdownEditor.svelte +781 -0
- package/dist/primitives/MarkdownEditor.svelte.d.ts +21 -0
- package/dist/primitives/MarkdownEditor.types.js +1 -0
- package/dist/primitives/SearchInput.svelte +623 -0
- package/dist/primitives/SearchInput.svelte.d.ts +24 -0
- package/dist/primitives/Select.svelte +336 -0
- package/dist/primitives/Select.svelte.d.ts +18 -0
- package/dist/primitives/Text.svelte +177 -0
- package/dist/primitives/Text.svelte.d.ts +26 -0
- package/dist/primitives/Toggle.svelte +138 -0
- package/dist/primitives/Toggle.svelte.d.ts +9 -0
- package/dist/primitives/index.d.ts +9 -0
- package/dist/primitives/index.js +7 -0
- package/dist/primitives/search-input-types.js +1 -0
- package/dist/surfaces/ChatPane.svelte +520 -0
- package/dist/surfaces/ChatPane.svelte.d.ts +15 -0
- package/dist/surfaces/ChatPane.types.js +1 -0
- package/dist/surfaces/GlassPanel.svelte +118 -0
- package/dist/surfaces/GlassPanel.svelte.d.ts +19 -0
- package/dist/surfaces/Pane.svelte +172 -0
- package/dist/surfaces/Pane.svelte.d.ts +25 -0
- package/dist/surfaces/index.d.ts +4 -0
- package/dist/surfaces/index.js +3 -0
- package/dist/telemetry/correlation.js +26 -0
- package/dist/telemetry/index.d.ts +4 -4
- package/dist/telemetry/index.js +20 -101
- package/dist/telemetry/sampling.js +58 -0
- package/dist/telemetry/tracer.d.ts +16 -1
- package/dist/telemetry/tracer.js +112 -0
- package/dist/tokens.css +123 -0
- package/dist/tui-tokens.css +36 -0
- package/dist/useTui.js +31 -0
- package/package.json +32 -22
- package/dist/design-dojo.css +0 -1
- package/dist/enforce/index.d.ts +0 -75
- package/dist/enforce/known-components.d.ts +0 -7
- package/dist/enforce/rules/no-local-components.d.ts +0 -29
- package/dist/enforce/rules/prefer-design-dojo-imports.d.ts +0 -27
- package/dist/enforce.js +0 -132
- package/dist/lib/app/CanvasBreadcrumb.svelte.d.ts +0 -1
- package/dist/lib/app/ChatInput.svelte.d.ts +0 -1
- package/dist/lib/app/ChatView.svelte.d.ts +0 -1
- package/dist/lib/app/ConversationGraph.svelte.d.ts +0 -1
- package/dist/lib/app/FirstRunWizard.svelte.d.ts +0 -1
- package/dist/lib/app/MemorySidebar.svelte.d.ts +0 -1
- package/dist/lib/app/PeerStatusPanel.svelte.d.ts +0 -1
- package/dist/lib/app/ProcedureCanvas.svelte.d.ts +0 -1
- package/dist/lib/app/ProcedureEditor.svelte.d.ts +0 -1
- package/dist/lib/app/ProcedureInspector.svelte.d.ts +0 -1
- package/dist/lib/app/ProcedureNode.svelte.d.ts +0 -1
- package/dist/lib/app/RealmIndicator.svelte.d.ts +0 -1
- package/dist/lib/app/RealmSwitcher.svelte.d.ts +0 -1
- package/dist/lib/app/SemanticSearchInput.svelte.d.ts +0 -1
- package/dist/lib/app/SemanticTimeline.svelte.d.ts +0 -1
- package/dist/lib/app/SettingsPanel.svelte.d.ts +0 -1
- package/dist/lib/app/SubCanvas.svelte.d.ts +0 -1
- package/dist/lib/app/SyncIndicator.svelte.d.ts +0 -1
- package/dist/lib/app/SyncTimeline.svelte.d.ts +0 -1
- package/dist/lib/app/TagCloud.svelte.d.ts +0 -1
- package/dist/lib/app/WorkModeToggle.svelte.d.ts +0 -1
- package/dist/lib/data/List.svelte.d.ts +0 -1
- package/dist/lib/data/ListItem.svelte.d.ts +0 -1
- package/dist/lib/data/Table.svelte.d.ts +0 -1
- package/dist/lib/data/index.d.ts +0 -3
- package/dist/lib/disclosure/Accordion.svelte.d.ts +0 -1
- package/dist/lib/feedback/Badge.svelte.d.ts +0 -1
- package/dist/lib/feedback/Callout.svelte.d.ts +0 -1
- package/dist/lib/feedback/EmptyState.svelte.d.ts +0 -1
- package/dist/lib/feedback/ProgressBar.svelte.d.ts +0 -1
- package/dist/lib/forms/FileUpload.svelte.d.ts +0 -1
- package/dist/lib/forms/RadioGroup.svelte.d.ts +0 -1
- package/dist/lib/icons/NerdFont.svelte.d.ts +0 -1
- package/dist/lib/icons/index.d.ts +0 -1
- package/dist/lib/index.d.ts +0 -76
- package/dist/lib/layout/Box.svelte.d.ts +0 -1
- package/dist/lib/layout/Sidebar.svelte.d.ts +0 -1
- package/dist/lib/layout/SplitPane.svelte.d.ts +0 -1
- package/dist/lib/layout/StatusBar.svelte.d.ts +0 -1
- package/dist/lib/layout/StatusBarItem.svelte.d.ts +0 -1
- package/dist/lib/layout/StatusBarSpacer.svelte.d.ts +0 -1
- package/dist/lib/layout/Tabs.svelte.d.ts +0 -1
- package/dist/lib/layout/TitleBar.svelte.d.ts +0 -1
- package/dist/lib/layout/index.d.ts +0 -9
- package/dist/lib/motion/index.d.ts +0 -1
- package/dist/lib/overlays/ContextMenu.svelte.d.ts +0 -1
- package/dist/lib/overlays/Dialog.svelte.d.ts +0 -1
- package/dist/lib/overlays/Menu.svelte.d.ts +0 -1
- package/dist/lib/overlays/Popover.svelte.d.ts +0 -1
- package/dist/lib/overlays/Toast.svelte.d.ts +0 -1
- package/dist/lib/overlays/Tooltip.svelte.d.ts +0 -1
- package/dist/lib/overlays/index.d.ts +0 -7
- package/dist/lib/primitives/Button.svelte.d.ts +0 -1
- package/dist/lib/primitives/ContextMenu.svelte.d.ts +0 -1
- package/dist/lib/primitives/Input.svelte.d.ts +0 -1
- package/dist/lib/primitives/MarkdownEditor.svelte.d.ts +0 -1
- package/dist/lib/primitives/SearchInput.svelte.d.ts +0 -1
- package/dist/lib/primitives/Select.svelte.d.ts +0 -1
- package/dist/lib/primitives/Text.svelte.d.ts +0 -1
- package/dist/lib/primitives/Toggle.svelte.d.ts +0 -1
- package/dist/lib/primitives/index.d.ts +0 -9
- package/dist/lib/surfaces/ChatPane.svelte.d.ts +0 -1
- package/dist/lib/surfaces/GlassPanel.svelte.d.ts +0 -1
- package/dist/lib/surfaces/Pane.svelte.d.ts +0 -1
- package/dist/lib/surfaces/index.d.ts +0 -4
- /package/dist/{lib/app → app}/CanvasBreadcrumb.types.d.ts +0 -0
- /package/dist/{lib/app → app}/ChatView.types.d.ts +0 -0
- /package/dist/{lib/app → app}/FirstRunWizard.types.d.ts +0 -0
- /package/dist/{lib/app → app}/MemorySidebar.types.d.ts +0 -0
- /package/dist/{lib/app → app}/ProcedureCanvas.types.d.ts +0 -0
- /package/dist/{lib/app → app}/ProcedureEditor.types.d.ts +0 -0
- /package/dist/{lib/app → app}/Realm.types.d.ts +0 -0
- /package/dist/{lib/app → app}/SemanticConversation.types.d.ts +0 -0
- /package/dist/{lib/app → app}/SemanticSearchInput.types.d.ts +0 -0
- /package/dist/{lib/app → app}/SettingsPanel.types.d.ts +0 -0
- /package/dist/{lib/app → app}/SubCanvas.types.d.ts +0 -0
- /package/dist/{lib/app → app}/Sync.types.d.ts +0 -0
- /package/dist/{lib/layout → layout}/Tabs.types.d.ts +0 -0
- /package/dist/{lib/motion → motion}/spring.d.ts +0 -0
- /package/dist/{lib/overlays → overlays}/Menu.types.d.ts +0 -0
- /package/dist/{lib/primitives → primitives}/ContextMenu.types.d.ts +0 -0
- /package/dist/{lib/primitives → primitives}/MarkdownEditor.types.d.ts +0 -0
- /package/dist/{lib/primitives → primitives}/search-input-types.d.ts +0 -0
- /package/dist/{lib/surfaces → surfaces}/ChatPane.types.d.ts +0 -0
- /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 {};
|