@vizel/svelte 0.0.1-alpha.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 (101) hide show
  1. package/README.md +83 -0
  2. package/dist/components/Vizel.svelte +158 -0
  3. package/dist/components/Vizel.svelte.d.ts +82 -0
  4. package/dist/components/Vizel.svelte.d.ts.map +1 -0
  5. package/dist/components/VizelBubbleMenu.svelte +96 -0
  6. package/dist/components/VizelBubbleMenu.svelte.d.ts +28 -0
  7. package/dist/components/VizelBubbleMenu.svelte.d.ts.map +1 -0
  8. package/dist/components/VizelBubbleMenuButton.svelte +45 -0
  9. package/dist/components/VizelBubbleMenuButton.svelte.d.ts +21 -0
  10. package/dist/components/VizelBubbleMenuButton.svelte.d.ts.map +1 -0
  11. package/dist/components/VizelBubbleMenuColorPicker.svelte +135 -0
  12. package/dist/components/VizelBubbleMenuColorPicker.svelte.d.ts +19 -0
  13. package/dist/components/VizelBubbleMenuColorPicker.svelte.d.ts.map +1 -0
  14. package/dist/components/VizelBubbleMenuDefault.svelte +111 -0
  15. package/dist/components/VizelBubbleMenuDefault.svelte.d.ts +13 -0
  16. package/dist/components/VizelBubbleMenuDefault.svelte.d.ts.map +1 -0
  17. package/dist/components/VizelBubbleMenuDivider.svelte +12 -0
  18. package/dist/components/VizelBubbleMenuDivider.svelte.d.ts +8 -0
  19. package/dist/components/VizelBubbleMenuDivider.svelte.d.ts.map +1 -0
  20. package/dist/components/VizelColorPicker.svelte +269 -0
  21. package/dist/components/VizelColorPicker.svelte.d.ts +25 -0
  22. package/dist/components/VizelColorPicker.svelte.d.ts.map +1 -0
  23. package/dist/components/VizelContext.d.ts +27 -0
  24. package/dist/components/VizelContext.d.ts.map +1 -0
  25. package/dist/components/VizelContext.js +34 -0
  26. package/dist/components/VizelEditor.svelte +61 -0
  27. package/dist/components/VizelEditor.svelte.d.ts +17 -0
  28. package/dist/components/VizelEditor.svelte.d.ts.map +1 -0
  29. package/dist/components/VizelEmbedView.svelte +162 -0
  30. package/dist/components/VizelEmbedView.svelte.d.ts +13 -0
  31. package/dist/components/VizelEmbedView.svelte.d.ts.map +1 -0
  32. package/dist/components/VizelIcon.svelte +54 -0
  33. package/dist/components/VizelIcon.svelte.d.ts +32 -0
  34. package/dist/components/VizelIcon.svelte.d.ts.map +1 -0
  35. package/dist/components/VizelIconContext.d.ts +27 -0
  36. package/dist/components/VizelIconContext.d.ts.map +1 -0
  37. package/dist/components/VizelIconContext.js +31 -0
  38. package/dist/components/VizelIconProvider.svelte +43 -0
  39. package/dist/components/VizelIconProvider.svelte.d.ts +31 -0
  40. package/dist/components/VizelIconProvider.svelte.d.ts.map +1 -0
  41. package/dist/components/VizelLinkEditor.svelte +143 -0
  42. package/dist/components/VizelLinkEditor.svelte.d.ts +15 -0
  43. package/dist/components/VizelLinkEditor.svelte.d.ts.map +1 -0
  44. package/dist/components/VizelNodeSelector.svelte +172 -0
  45. package/dist/components/VizelNodeSelector.svelte.d.ts +13 -0
  46. package/dist/components/VizelNodeSelector.svelte.d.ts.map +1 -0
  47. package/dist/components/VizelPortal.svelte +70 -0
  48. package/dist/components/VizelPortal.svelte.d.ts +19 -0
  49. package/dist/components/VizelPortal.svelte.d.ts.map +1 -0
  50. package/dist/components/VizelProvider.svelte +26 -0
  51. package/dist/components/VizelProvider.svelte.d.ts +14 -0
  52. package/dist/components/VizelProvider.svelte.d.ts.map +1 -0
  53. package/dist/components/VizelSaveIndicator.svelte +94 -0
  54. package/dist/components/VizelSaveIndicator.svelte.d.ts +15 -0
  55. package/dist/components/VizelSaveIndicator.svelte.d.ts.map +1 -0
  56. package/dist/components/VizelSlashMenu.svelte +211 -0
  57. package/dist/components/VizelSlashMenu.svelte.d.ts +31 -0
  58. package/dist/components/VizelSlashMenu.svelte.d.ts.map +1 -0
  59. package/dist/components/VizelSlashMenuEmpty.svelte +22 -0
  60. package/dist/components/VizelSlashMenuEmpty.svelte.d.ts +11 -0
  61. package/dist/components/VizelSlashMenuEmpty.svelte.d.ts.map +1 -0
  62. package/dist/components/VizelSlashMenuItem.svelte +57 -0
  63. package/dist/components/VizelSlashMenuItem.svelte.d.ts +17 -0
  64. package/dist/components/VizelSlashMenuItem.svelte.d.ts.map +1 -0
  65. package/dist/components/VizelThemeProvider.svelte +79 -0
  66. package/dist/components/VizelThemeProvider.svelte.d.ts +11 -0
  67. package/dist/components/VizelThemeProvider.svelte.d.ts.map +1 -0
  68. package/dist/components/index.d.ts +23 -0
  69. package/dist/components/index.d.ts.map +1 -0
  70. package/dist/components/index.js +64 -0
  71. package/dist/iconRenderer.d.ts +6 -0
  72. package/dist/iconRenderer.d.ts.map +1 -0
  73. package/dist/iconRenderer.js +7 -0
  74. package/dist/index.d.ts +9 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +31 -0
  77. package/dist/runes/createVizelAutoSave.svelte.d.ts +44 -0
  78. package/dist/runes/createVizelAutoSave.svelte.d.ts.map +1 -0
  79. package/dist/runes/createVizelAutoSave.svelte.js +91 -0
  80. package/dist/runes/createVizelEditor.svelte.d.ts +43 -0
  81. package/dist/runes/createVizelEditor.svelte.d.ts.map +1 -0
  82. package/dist/runes/createVizelEditor.svelte.js +65 -0
  83. package/dist/runes/createVizelEditorState.svelte.d.ts +27 -0
  84. package/dist/runes/createVizelEditorState.svelte.d.ts.map +1 -0
  85. package/dist/runes/createVizelEditorState.svelte.js +35 -0
  86. package/dist/runes/createVizelMarkdown.svelte.d.ts +68 -0
  87. package/dist/runes/createVizelMarkdown.svelte.d.ts.map +1 -0
  88. package/dist/runes/createVizelMarkdown.svelte.js +123 -0
  89. package/dist/runes/createVizelSlashMenuRenderer.d.ts +22 -0
  90. package/dist/runes/createVizelSlashMenuRenderer.d.ts.map +1 -0
  91. package/dist/runes/createVizelSlashMenuRenderer.js +84 -0
  92. package/dist/runes/createVizelState.svelte.d.ts +22 -0
  93. package/dist/runes/createVizelState.svelte.d.ts.map +1 -0
  94. package/dist/runes/createVizelState.svelte.js +50 -0
  95. package/dist/runes/getVizelTheme.svelte.d.ts +23 -0
  96. package/dist/runes/getVizelTheme.svelte.d.ts.map +1 -0
  97. package/dist/runes/getVizelTheme.svelte.js +31 -0
  98. package/dist/runes/index.d.ts +8 -0
  99. package/dist/runes/index.d.ts.map +1 -0
  100. package/dist/runes/index.js +7 -0
  101. package/package.json +64 -0
@@ -0,0 +1,172 @@
1
+ <script lang="ts" module>
2
+ import type { Editor, VizelNodeTypeOption } from "@vizel/core";
3
+
4
+ export interface VizelNodeSelectorProps {
5
+ /** The editor instance */
6
+ editor: Editor;
7
+ /** Custom node types (defaults to vizelDefaultNodeTypes) */
8
+ nodeTypes?: VizelNodeTypeOption[];
9
+ /** Custom class name */
10
+ class?: string;
11
+ }
12
+ </script>
13
+
14
+ <script lang="ts">
15
+ import { vizelDefaultNodeTypes, getVizelActiveNodeType } from "@vizel/core";
16
+ import { createVizelState } from "../runes/createVizelState.svelte.ts";
17
+ import VizelIcon from "./VizelIcon.svelte";
18
+
19
+ let { editor, nodeTypes = vizelDefaultNodeTypes, class: className }: VizelNodeSelectorProps = $props();
20
+
21
+ // Subscribe to editor state changes
22
+ const editorState = createVizelState(() => editor);
23
+
24
+ let isOpen = $state(false);
25
+ let focusedIndex = $state(0);
26
+ let containerRef: HTMLDivElement | undefined = $state();
27
+ let dropdownRef: HTMLDivElement | undefined = $state();
28
+
29
+ const activeNodeType = $derived.by(() => {
30
+ void editorState.current; // Trigger reactivity
31
+ return getVizelActiveNodeType(editor, nodeTypes);
32
+ });
33
+
34
+ const currentLabel = $derived(activeNodeType?.label ?? "Text");
35
+ const currentIcon = $derived(activeNodeType?.icon ?? "paragraph");
36
+
37
+ // Close dropdown when clicking outside
38
+ function handleClickOutside(event: MouseEvent) {
39
+ if (containerRef && !containerRef.contains(event.target as Node)) {
40
+ isOpen = false;
41
+ }
42
+ }
43
+
44
+ $effect(() => {
45
+ document.addEventListener("mousedown", handleClickOutside);
46
+ return () => document.removeEventListener("mousedown", handleClickOutside);
47
+ });
48
+
49
+ // Focus the dropdown when it opens to ensure keyboard navigation works
50
+ $effect(() => {
51
+ if (isOpen && dropdownRef) {
52
+ dropdownRef.focus();
53
+ }
54
+ });
55
+
56
+ function handleKeyDown(event: KeyboardEvent) {
57
+ if (!isOpen) {
58
+ if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
59
+ event.preventDefault();
60
+ isOpen = true;
61
+ focusedIndex = 0;
62
+ }
63
+ return;
64
+ }
65
+
66
+ switch (event.key) {
67
+ case "Escape":
68
+ event.preventDefault();
69
+ isOpen = false;
70
+ break;
71
+ case "ArrowDown":
72
+ event.preventDefault();
73
+ focusedIndex = (focusedIndex + 1) % nodeTypes.length;
74
+ break;
75
+ case "ArrowUp":
76
+ event.preventDefault();
77
+ focusedIndex = (focusedIndex - 1 + nodeTypes.length) % nodeTypes.length;
78
+ break;
79
+ case "Enter":
80
+ case " ": {
81
+ event.preventDefault();
82
+ const selectedNodeType = nodeTypes[focusedIndex];
83
+ if (selectedNodeType) {
84
+ handleSelectNodeType(selectedNodeType);
85
+ }
86
+ break;
87
+ }
88
+ case "Home":
89
+ event.preventDefault();
90
+ focusedIndex = 0;
91
+ break;
92
+ case "End":
93
+ event.preventDefault();
94
+ focusedIndex = nodeTypes.length - 1;
95
+ break;
96
+ default:
97
+ // Allow other keys to propagate
98
+ break;
99
+ }
100
+ }
101
+
102
+ function handleSelectNodeType(nodeType: VizelNodeTypeOption) {
103
+ nodeType.command(editor);
104
+ isOpen = false;
105
+ }
106
+
107
+ function isNodeTypeActive(nodeType: VizelNodeTypeOption): boolean {
108
+ void editorState.current; // Trigger reactivity
109
+ return nodeType.isActive(editor);
110
+ }
111
+ </script>
112
+
113
+ <div
114
+ bind:this={containerRef}
115
+ class="vizel-node-selector {className ?? ''}"
116
+ data-vizel-node-selector
117
+ >
118
+ <button
119
+ type="button"
120
+ class="vizel-node-selector-trigger"
121
+ aria-haspopup="listbox"
122
+ aria-expanded={isOpen}
123
+ aria-label={`Current block type: ${currentLabel}`}
124
+ title="Change block type"
125
+ onclick={() => (isOpen = !isOpen)}
126
+ onkeydown={handleKeyDown}
127
+ >
128
+ <span class="vizel-node-selector-icon">
129
+ <VizelIcon name={currentIcon} />
130
+ </span>
131
+ <span class="vizel-node-selector-label">{currentLabel}</span>
132
+ <span class="vizel-node-selector-chevron" aria-hidden="true">
133
+ <VizelIcon name="chevronDown" />
134
+ </span>
135
+ </button>
136
+
137
+ {#if isOpen}
138
+ <div
139
+ bind:this={dropdownRef}
140
+ class="vizel-node-selector-dropdown"
141
+ role="listbox"
142
+ aria-label="Block types"
143
+ data-vizel-node-selector-dropdown
144
+ tabindex="-1"
145
+ onkeydown={handleKeyDown}
146
+ >
147
+ {#each nodeTypes as nodeType, index}
148
+ {@const active = isNodeTypeActive(nodeType)}
149
+ {@const focused = index === focusedIndex}
150
+ <button
151
+ type="button"
152
+ role="option"
153
+ aria-selected={active}
154
+ class="vizel-node-selector-option {active ? 'is-active' : ''} {focused ? 'is-focused' : ''}"
155
+ tabindex={focused ? 0 : -1}
156
+ onclick={() => handleSelectNodeType(nodeType)}
157
+ onmouseenter={() => (focusedIndex = index)}
158
+ >
159
+ <span class="vizel-node-selector-option-icon">
160
+ <VizelIcon name={nodeType.icon} />
161
+ </span>
162
+ <span class="vizel-node-selector-option-label">{nodeType.label}</span>
163
+ {#if active}
164
+ <span class="vizel-node-selector-check" aria-hidden="true">
165
+ <VizelIcon name="check" />
166
+ </span>
167
+ {/if}
168
+ </button>
169
+ {/each}
170
+ </div>
171
+ {/if}
172
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { Editor, VizelNodeTypeOption } from "@vizel/core";
2
+ export interface VizelNodeSelectorProps {
3
+ /** The editor instance */
4
+ editor: Editor;
5
+ /** Custom node types (defaults to vizelDefaultNodeTypes) */
6
+ nodeTypes?: VizelNodeTypeOption[];
7
+ /** Custom class name */
8
+ class?: string;
9
+ }
10
+ declare const VizelNodeSelector: import("svelte").Component<VizelNodeSelectorProps, {}, "">;
11
+ type VizelNodeSelector = ReturnType<typeof VizelNodeSelector>;
12
+ export default VizelNodeSelector;
13
+ //# sourceMappingURL=VizelNodeSelector.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VizelNodeSelector.svelte.d.ts","sourceRoot":"","sources":["../../src/components/VizelNodeSelector.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,sBAAsB;IACrC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8ID,QAAA,MAAM,iBAAiB,4DAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,70 @@
1
+ <script lang="ts" module>
2
+ import type { VizelPortalLayer } from "@vizel/core";
3
+ import type { Snippet } from "svelte";
4
+
5
+ /**
6
+ * Props for the Portal component.
7
+ */
8
+ export interface VizelPortalProps {
9
+ /** Content to render in the portal */
10
+ children: Snippet;
11
+ /** Z-index layer for the portal content */
12
+ layer?: VizelPortalLayer;
13
+ /** Additional CSS class name */
14
+ class?: string;
15
+ /** Whether the portal is disabled (renders children in place) */
16
+ disabled?: boolean;
17
+ }
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import { getVizelPortalContainer, VIZEL_PORTAL_Z_INDEX } from "@vizel/core";
22
+
23
+ const {
24
+ children,
25
+ layer = "dropdown",
26
+ class: className,
27
+ disabled = false,
28
+ }: VizelPortalProps = $props();
29
+
30
+ // Create portal action that moves content to the portal container
31
+ function portal(node: HTMLElement) {
32
+ const container = getVizelPortalContainer();
33
+
34
+ // Create wrapper element
35
+ const wrapper = document.createElement("div");
36
+ wrapper.setAttribute("data-vizel-portal-layer", layer);
37
+ if (className) {
38
+ wrapper.className = className;
39
+ }
40
+ wrapper.style.position = "absolute";
41
+ wrapper.style.top = "0";
42
+ wrapper.style.left = "0";
43
+ wrapper.style.zIndex = String(VIZEL_PORTAL_Z_INDEX[layer]);
44
+
45
+ // Move node's children to wrapper
46
+ while (node.firstChild) {
47
+ wrapper.appendChild(node.firstChild);
48
+ }
49
+
50
+ container.appendChild(wrapper);
51
+
52
+ return {
53
+ destroy() {
54
+ // Move content back to original location for cleanup
55
+ while (wrapper.firstChild) {
56
+ node.appendChild(wrapper.firstChild);
57
+ }
58
+ wrapper.remove();
59
+ },
60
+ };
61
+ }
62
+ </script>
63
+
64
+ {#if disabled}
65
+ {@render children()}
66
+ {:else}
67
+ <div use:portal>
68
+ {@render children()}
69
+ </div>
70
+ {/if}
@@ -0,0 +1,19 @@
1
+ import type { VizelPortalLayer } from "@vizel/core";
2
+ import type { Snippet } from "svelte";
3
+ /**
4
+ * Props for the Portal component.
5
+ */
6
+ export interface VizelPortalProps {
7
+ /** Content to render in the portal */
8
+ children: Snippet;
9
+ /** Z-index layer for the portal content */
10
+ layer?: VizelPortalLayer;
11
+ /** Additional CSS class name */
12
+ class?: string;
13
+ /** Whether the portal is disabled (renders children in place) */
14
+ disabled?: boolean;
15
+ }
16
+ declare const VizelPortal: import("svelte").Component<VizelPortalProps, {}, "">;
17
+ type VizelPortal = ReturnType<typeof VizelPortal>;
18
+ export default VizelPortal;
19
+ //# sourceMappingURL=VizelPortal.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VizelPortal.svelte.d.ts","sourceRoot":"","sources":["../../src/components/VizelPortal.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA6DD,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,26 @@
1
+ <script lang="ts" module>
2
+ import type { Editor } from "@vizel/core";
3
+ import type { Snippet } from "svelte";
4
+
5
+ export interface VizelProviderProps {
6
+ /** The editor instance */
7
+ editor: Editor | null;
8
+ /** Custom class name */
9
+ class?: string;
10
+ /** Children content */
11
+ children: Snippet;
12
+ }
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import { setContext } from "svelte";
17
+ import { VIZEL_CONTEXT_KEY } from "./VizelContext.ts";
18
+
19
+ let { editor, class: className, children }: VizelProviderProps = $props();
20
+
21
+ setContext(VIZEL_CONTEXT_KEY, () => editor);
22
+ </script>
23
+
24
+ <div class={className} data-vizel-root>
25
+ {@render children()}
26
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { Editor } from "@vizel/core";
2
+ import type { Snippet } from "svelte";
3
+ export interface VizelProviderProps {
4
+ /** The editor instance */
5
+ editor: Editor | null;
6
+ /** Custom class name */
7
+ class?: string;
8
+ /** Children content */
9
+ children: Snippet;
10
+ }
11
+ declare const VizelProvider: import("svelte").Component<VizelProviderProps, {}, "">;
12
+ type VizelProvider = ReturnType<typeof VizelProvider>;
13
+ export default VizelProvider;
14
+ //# sourceMappingURL=VizelProvider.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VizelProvider.svelte.d.ts","sourceRoot":"","sources":["../../src/components/VizelProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAuBD,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,94 @@
1
+ <script lang="ts" module>
2
+ import type { VizelSaveStatus } from "@vizel/core";
3
+
4
+ export interface VizelSaveIndicatorProps {
5
+ /** Current save status */
6
+ status: VizelSaveStatus;
7
+ /** Timestamp of last successful save */
8
+ lastSaved?: Date | null;
9
+ /** Show relative timestamp (default: true) */
10
+ showTimestamp?: boolean;
11
+ /** Custom class name */
12
+ class?: string;
13
+ }
14
+ </script>
15
+
16
+ <script lang="ts">
17
+ import { formatVizelRelativeTime } from "@vizel/core";
18
+ import VizelIcon from "./VizelIcon.svelte";
19
+
20
+ let {
21
+ status,
22
+ lastSaved = null,
23
+ showTimestamp = true,
24
+ class: className,
25
+ }: VizelSaveIndicatorProps = $props();
26
+
27
+ let relativeTime = $state("");
28
+
29
+ function updateTime() {
30
+ if (lastSaved) {
31
+ relativeTime = formatVizelRelativeTime(lastSaved);
32
+ } else {
33
+ relativeTime = "";
34
+ }
35
+ }
36
+
37
+ $effect(() => {
38
+ updateTime();
39
+ const intervalId = setInterval(updateTime, 10000);
40
+
41
+ return () => {
42
+ clearInterval(intervalId);
43
+ };
44
+ });
45
+
46
+ // Watch lastSaved changes
47
+ $effect(() => {
48
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
49
+ lastSaved;
50
+ updateTime();
51
+ });
52
+
53
+ const statusText = $derived.by(() => {
54
+ switch (status) {
55
+ case "saved":
56
+ return "Saved";
57
+ case "saving":
58
+ return "Saving...";
59
+ case "unsaved":
60
+ return "Unsaved";
61
+ case "error":
62
+ return "Error saving";
63
+ default:
64
+ return "";
65
+ }
66
+ });
67
+
68
+ const shouldShowTimestamp = $derived(showTimestamp && lastSaved && relativeTime && status === "saved");
69
+ </script>
70
+
71
+ <div
72
+ class="vizel-save-indicator vizel-save-indicator--{status} {className ?? ''}"
73
+ role="status"
74
+ aria-live="polite"
75
+ data-vizel-save-indicator
76
+ >
77
+ <span class="vizel-save-indicator-icon" aria-hidden="true">
78
+ {#if status === "saved"}
79
+ <VizelIcon name="check" />
80
+ {:else if status === "saving"}
81
+ <span class="vizel-save-indicator-spinner" aria-hidden="true">
82
+ <VizelIcon name="loader" />
83
+ </span>
84
+ {:else if status === "unsaved"}
85
+ <VizelIcon name="circle" />
86
+ {:else if status === "error"}
87
+ <VizelIcon name="warning" />
88
+ {/if}
89
+ </span>
90
+ <span class="vizel-save-indicator-text">{statusText}</span>
91
+ {#if shouldShowTimestamp}
92
+ <span class="vizel-save-indicator-timestamp">{relativeTime}</span>
93
+ {/if}
94
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { VizelSaveStatus } from "@vizel/core";
2
+ export interface VizelSaveIndicatorProps {
3
+ /** Current save status */
4
+ status: VizelSaveStatus;
5
+ /** Timestamp of last successful save */
6
+ lastSaved?: Date | null;
7
+ /** Show relative timestamp (default: true) */
8
+ showTimestamp?: boolean;
9
+ /** Custom class name */
10
+ class?: string;
11
+ }
12
+ declare const VizelSaveIndicator: import("svelte").Component<VizelSaveIndicatorProps, {}, "">;
13
+ type VizelSaveIndicator = ReturnType<typeof VizelSaveIndicator>;
14
+ export default VizelSaveIndicator;
15
+ //# sourceMappingURL=VizelSaveIndicator.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VizelSaveIndicator.svelte.d.ts","sourceRoot":"","sources":["../../src/components/VizelSaveIndicator.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,uBAAuB;IACtC,0BAA0B;IAC1B,MAAM,EAAE,eAAe,CAAC;IACxB,wCAAwC;IACxC,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqFD,QAAA,MAAM,kBAAkB,6DAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,211 @@
1
+ <script lang="ts" module>
2
+ import type { VizelSlashCommandItem } from "@vizel/core";
3
+ import type { Snippet } from "svelte";
4
+
5
+ export interface VizelSlashMenuRef {
6
+ onKeyDown: (event: KeyboardEvent) => boolean;
7
+ }
8
+
9
+ export interface VizelSlashMenuProps {
10
+ /** The list of slash command items */
11
+ items: VizelSlashCommandItem[];
12
+ /** Custom class name */
13
+ class?: string;
14
+ /** Command handler */
15
+ oncommand?: (item: VizelSlashCommandItem) => void;
16
+ /** Whether to show items grouped by category (default: true when not searching) */
17
+ showGroups?: boolean;
18
+ /** Custom group order */
19
+ groupOrder?: string[];
20
+ /** Custom item renderer */
21
+ renderItem?: Snippet<[{ item: VizelSlashCommandItem; isSelected: boolean; onclick: () => void }]>;
22
+ /** Custom empty state renderer */
23
+ renderEmpty?: Snippet;
24
+ }
25
+ </script>
26
+
27
+ <script lang="ts">
28
+ import { groupVizelSlashCommands, type VizelSlashCommandGroup } from "@vizel/core";
29
+ import { tick } from "svelte";
30
+ import VizelSlashMenuItem from "./VizelSlashMenuItem.svelte";
31
+ import VizelSlashMenuEmpty from "./VizelSlashMenuEmpty.svelte";
32
+
33
+ let {
34
+ items,
35
+ class: className,
36
+ oncommand,
37
+ showGroups = true,
38
+ groupOrder,
39
+ renderItem,
40
+ renderEmpty,
41
+ }: VizelSlashMenuProps = $props();
42
+
43
+ let selectedIndex = $state(0);
44
+ let itemRefs: (HTMLElement | null)[] = $state([]);
45
+
46
+ // Clean up itemRefs when items decrease
47
+ $effect(() => {
48
+ const length = flatItems.length;
49
+ if (itemRefs.length > length) {
50
+ itemRefs.length = length;
51
+ }
52
+ });
53
+
54
+ // Group items when showGroups is true and there are enough items
55
+ const groups = $derived.by<VizelSlashCommandGroup[]>(() => {
56
+ if (!showGroups || items.length <= 5) {
57
+ // Don't group if explicitly disabled or few items (likely search results)
58
+ return [{ name: "", items }];
59
+ }
60
+ return groupVizelSlashCommands(items, groupOrder);
61
+ });
62
+
63
+ // Flatten for navigation
64
+ const flatItems = $derived(groups.flatMap((g) => g.items));
65
+
66
+ // Reset selection when items change
67
+ $effect(() => {
68
+ items;
69
+ selectedIndex = 0;
70
+ });
71
+
72
+ // Scroll selected item into view when selection changes
73
+ $effect(() => {
74
+ const index = selectedIndex;
75
+ tick().then(() => {
76
+ const selectedElement = itemRefs[index];
77
+ if (selectedElement) {
78
+ selectedElement.scrollIntoView({ block: "nearest", behavior: "smooth" });
79
+ }
80
+ });
81
+ });
82
+
83
+ function selectItem(index: number) {
84
+ const item = flatItems[index];
85
+ if (item) {
86
+ oncommand?.(item);
87
+ }
88
+ }
89
+
90
+ // Navigate to next group with Tab
91
+ function tabHandler() {
92
+ if (groups.length <= 1) return;
93
+
94
+ let currentGroupIndex = 0;
95
+ let itemCount = 0;
96
+ for (let i = 0; i < groups.length; i++) {
97
+ const group = groups[i];
98
+ if (!group) continue;
99
+ if (selectedIndex < itemCount + group.items.length) {
100
+ currentGroupIndex = i;
101
+ break;
102
+ }
103
+ itemCount += group.items.length;
104
+ }
105
+
106
+ // Move to next group
107
+ const nextGroupIndex = (currentGroupIndex + 1) % groups.length;
108
+ let nextIndex = 0;
109
+ for (let i = 0; i < nextGroupIndex; i++) {
110
+ const group = groups[i];
111
+ if (group) {
112
+ nextIndex += group.items.length;
113
+ }
114
+ }
115
+ selectedIndex = nextIndex;
116
+ }
117
+
118
+ // Calculate global index for items
119
+ function getGlobalIndex(groupIndex: number, itemIndex: number): number {
120
+ let index = 0;
121
+ for (let i = 0; i < groupIndex; i++) {
122
+ const group = groups[i];
123
+ if (group) {
124
+ index += group.items.length;
125
+ }
126
+ }
127
+ return index + itemIndex;
128
+ }
129
+
130
+ export function onKeyDown(event: KeyboardEvent): boolean {
131
+ if (event.key === "ArrowUp") {
132
+ selectedIndex = (selectedIndex + flatItems.length - 1) % flatItems.length;
133
+ return true;
134
+ }
135
+
136
+ if (event.key === "ArrowDown") {
137
+ selectedIndex = (selectedIndex + 1) % flatItems.length;
138
+ return true;
139
+ }
140
+
141
+ if (event.key === "Enter") {
142
+ selectItem(selectedIndex);
143
+ return true;
144
+ }
145
+
146
+ if (event.key === "Tab") {
147
+ event.preventDefault();
148
+ tabHandler();
149
+ return true;
150
+ }
151
+
152
+ return false;
153
+ }
154
+ </script>
155
+
156
+ <div class="vizel-slash-menu {className ?? ''}" data-vizel-slash-menu>
157
+ {#if flatItems.length === 0}
158
+ {#if renderEmpty}
159
+ {@render renderEmpty()}
160
+ {:else}
161
+ <VizelSlashMenuEmpty />
162
+ {/if}
163
+ {:else}
164
+ {#each groups as group, groupIndex (group.name || groupIndex)}
165
+ {#if group.name}
166
+ <!-- Group with header -->
167
+ <div class="vizel-slash-menu-group" data-vizel-slash-menu-group>
168
+ <div class="vizel-slash-menu-group-header">{group.name}</div>
169
+ {#each group.items as item, itemIndex (item.title)}
170
+ {@const globalIdx = getGlobalIndex(groupIndex, itemIndex)}
171
+ <div bind:this={itemRefs[globalIdx]}>
172
+ {#if renderItem}
173
+ {@render renderItem({
174
+ item,
175
+ isSelected: globalIdx === selectedIndex,
176
+ onclick: () => selectItem(globalIdx),
177
+ })}
178
+ {:else}
179
+ <VizelSlashMenuItem
180
+ {item}
181
+ isSelected={globalIdx === selectedIndex}
182
+ onclick={() => selectItem(globalIdx)}
183
+ />
184
+ {/if}
185
+ </div>
186
+ {/each}
187
+ </div>
188
+ {:else}
189
+ <!-- Items without group header -->
190
+ {#each group.items as item, itemIndex (item.title)}
191
+ {@const globalIdx = getGlobalIndex(groupIndex, itemIndex)}
192
+ <div bind:this={itemRefs[globalIdx]}>
193
+ {#if renderItem}
194
+ {@render renderItem({
195
+ item,
196
+ isSelected: globalIdx === selectedIndex,
197
+ onclick: () => selectItem(globalIdx),
198
+ })}
199
+ {:else}
200
+ <VizelSlashMenuItem
201
+ {item}
202
+ isSelected={globalIdx === selectedIndex}
203
+ onclick={() => selectItem(globalIdx)}
204
+ />
205
+ {/if}
206
+ </div>
207
+ {/each}
208
+ {/if}
209
+ {/each}
210
+ {/if}
211
+ </div>
@@ -0,0 +1,31 @@
1
+ import type { VizelSlashCommandItem } from "@vizel/core";
2
+ import type { Snippet } from "svelte";
3
+ export interface VizelSlashMenuRef {
4
+ onKeyDown: (event: KeyboardEvent) => boolean;
5
+ }
6
+ export interface VizelSlashMenuProps {
7
+ /** The list of slash command items */
8
+ items: VizelSlashCommandItem[];
9
+ /** Custom class name */
10
+ class?: string;
11
+ /** Command handler */
12
+ oncommand?: (item: VizelSlashCommandItem) => void;
13
+ /** Whether to show items grouped by category (default: true when not searching) */
14
+ showGroups?: boolean;
15
+ /** Custom group order */
16
+ groupOrder?: string[];
17
+ /** Custom item renderer */
18
+ renderItem?: Snippet<[{
19
+ item: VizelSlashCommandItem;
20
+ isSelected: boolean;
21
+ onclick: () => void;
22
+ }]>;
23
+ /** Custom empty state renderer */
24
+ renderEmpty?: Snippet;
25
+ }
26
+ declare const VizelSlashMenu: import("svelte").Component<VizelSlashMenuProps, {
27
+ onKeyDown: (event: KeyboardEvent) => boolean;
28
+ }, "">;
29
+ type VizelSlashMenu = ReturnType<typeof VizelSlashMenu>;
30
+ export default VizelSlashMenu;
31
+ //# sourceMappingURL=VizelSlashMenu.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VizelSlashMenu.svelte.d.ts","sourceRoot":"","sources":["../../src/components/VizelSlashMenu.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;CAC9C;AAED,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,KAAK,EAAE,qBAAqB,EAAE,CAAC;IAC/B,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAClD,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yBAAyB;IACzB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,IAAI,EAAE,qBAAqB,CAAC;QAAC,UAAU,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAC,CAAC;IAClG,kCAAkC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA8LD,QAAA,MAAM,cAAc;uBA/EO,aAAa,KAAG,OAAO;MA+EU,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}