@silo-code/sdk 0.6.0

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/context-keys.d.ts +19 -0
  4. package/dist/context-keys.d.ts.map +1 -0
  5. package/dist/context-keys.js +2 -0
  6. package/dist/context-keys.js.map +1 -0
  7. package/dist/dnd-service.d.ts +140 -0
  8. package/dist/dnd-service.d.ts.map +1 -0
  9. package/dist/dnd-service.js +17 -0
  10. package/dist/dnd-service.js.map +1 -0
  11. package/dist/domain-types.d.ts +237 -0
  12. package/dist/domain-types.d.ts.map +1 -0
  13. package/dist/domain-types.js +11 -0
  14. package/dist/domain-types.js.map +1 -0
  15. package/dist/editor-service.d.ts +175 -0
  16. package/dist/editor-service.d.ts.map +1 -0
  17. package/dist/editor-service.js +2 -0
  18. package/dist/editor-service.js.map +1 -0
  19. package/dist/extension-storage.d.ts +26 -0
  20. package/dist/extension-storage.d.ts.map +1 -0
  21. package/dist/extension-storage.js +2 -0
  22. package/dist/extension-storage.js.map +1 -0
  23. package/dist/file-service.d.ts +84 -0
  24. package/dist/file-service.d.ts.map +1 -0
  25. package/dist/file-service.js +2 -0
  26. package/dist/file-service.js.map +1 -0
  27. package/dist/index.d.ts +32 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +22 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/layout-service.d.ts +46 -0
  32. package/dist/layout-service.d.ts.map +1 -0
  33. package/dist/layout-service.js +2 -0
  34. package/dist/layout-service.js.map +1 -0
  35. package/dist/permissions.d.ts +41 -0
  36. package/dist/permissions.d.ts.map +1 -0
  37. package/dist/permissions.js +40 -0
  38. package/dist/permissions.js.map +1 -0
  39. package/dist/process-service.d.ts +132 -0
  40. package/dist/process-service.d.ts.map +1 -0
  41. package/dist/process-service.js +2 -0
  42. package/dist/process-service.js.map +1 -0
  43. package/dist/terminal-service.d.ts +38 -0
  44. package/dist/terminal-service.d.ts.map +1 -0
  45. package/dist/terminal-service.js +2 -0
  46. package/dist/terminal-service.js.map +1 -0
  47. package/dist/theme-service.d.ts +87 -0
  48. package/dist/theme-service.d.ts.map +1 -0
  49. package/dist/theme-service.js +2 -0
  50. package/dist/theme-service.js.map +1 -0
  51. package/dist/types.d.ts +495 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/ui-service.d.ts +469 -0
  56. package/dist/ui-service.d.ts.map +1 -0
  57. package/dist/ui-service.js +2 -0
  58. package/dist/ui-service.js.map +1 -0
  59. package/dist/use-focus-group.d.ts +202 -0
  60. package/dist/use-focus-group.d.ts.map +1 -0
  61. package/dist/use-focus-group.js +236 -0
  62. package/dist/use-focus-group.js.map +1 -0
  63. package/dist/use-service-state.d.ts +36 -0
  64. package/dist/use-service-state.d.ts.map +1 -0
  65. package/dist/use-service-state.js +25 -0
  66. package/dist/use-service-state.js.map +1 -0
  67. package/dist/workspace-service.d.ts +72 -0
  68. package/dist/workspace-service.d.ts.map +1 -0
  69. package/dist/workspace-service.js +2 -0
  70. package/dist/workspace-service.js.map +1 -0
  71. package/package.json +54 -0
  72. package/src/context-keys.ts +18 -0
  73. package/src/dnd-service.ts +151 -0
  74. package/src/domain-types.ts +252 -0
  75. package/src/editor-service.ts +196 -0
  76. package/src/extension-storage.ts +25 -0
  77. package/src/file-service.ts +90 -0
  78. package/src/index.ts +151 -0
  79. package/src/layout-service.ts +49 -0
  80. package/src/permissions.ts +55 -0
  81. package/src/process-service.ts +143 -0
  82. package/src/terminal-service.ts +41 -0
  83. package/src/theme-service.ts +102 -0
  84. package/src/types.ts +513 -0
  85. package/src/ui-service.ts +487 -0
  86. package/src/use-focus-group.test.ts +168 -0
  87. package/src/use-focus-group.ts +382 -0
  88. package/src/use-service-state.ts +43 -0
  89. package/src/workspace-service.ts +76 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-focus-group.d.ts","sourceRoot":"","sources":["../src/use-focus-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,OAAO,CAAC;AAEf;;;;;;;;;;GAUG;AACH,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC;AAEvE;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,2EAA2E;IAC3E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,kCAAkC;IAClC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACvD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,8EAA8E;IAC9E,iBAAiB,EAAE,EAAE,CAAC;IACtB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,qCAAqC;IACrC,cAAc,EAAE,wBAAwB,CAAC;IACzC,8BAA8B;IAC9B,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,mBAAmB,CAAC;IACrD,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAsBD;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAC;IACZ,6BAA6B;IAC7B,WAAW,EAAE,qBAAqB,CAAC;IACnC,mCAAmC;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,sDAAsD;IACtD,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CA6B7E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAEV;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAiHpE"}
@@ -0,0 +1,236 @@
1
+ import { useCallback, useEffect, useRef, useState, } from "react";
2
+ const clamp = (n, max) => Math.max(0, Math.min(n, max));
3
+ /** First navigable index at or after scanning the whole list, or `null`. */
4
+ function firstNavigable(count, isNavigable) {
5
+ for (let i = 0; i < count; i++)
6
+ if (isNavigable(i))
7
+ return i;
8
+ return null;
9
+ }
10
+ /** Last navigable index, or `null`. */
11
+ function lastNavigable(count, isNavigable) {
12
+ for (let i = count - 1; i >= 0; i--)
13
+ if (isNavigable(i))
14
+ return i;
15
+ return null;
16
+ }
17
+ /**
18
+ * The index a navigation key moves focus to within a focus group, or `null` when
19
+ * the key isn't a navigation key for this orientation, the list is empty, or no
20
+ * other navigable item exists. Steps over non-navigable items; wraps at the ends
21
+ * when `wrap`, otherwise stops (returns `null`). `Home`/`End` jump to the
22
+ * first/last navigable index regardless of orientation.
23
+ *
24
+ * This is the pure roving-index core that {@link useFocusGroup} runs internally.
25
+ * Reach for it directly only when you **can't** use the hook — e.g. a widget that
26
+ * drives keys from a document-level listener and a state-driven highlight rather
27
+ * than DOM focus (Silo's menus work this way). For an ordinary list/toolbar,
28
+ * prefer {@link useFocusGroup}, which calls this for you.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const next = focusGroupNextIndex({
33
+ * current: activeIndex, count: items.length, key: e.key,
34
+ * orientation: "vertical", wrap: true,
35
+ * isNavigable: (i) => !items[i].disabled,
36
+ * });
37
+ * if (next !== null) setActiveIndex(next);
38
+ * ```
39
+ *
40
+ * @category Consumer Services
41
+ * @public
42
+ */
43
+ export function focusGroupNextIndex(params) {
44
+ const { current, count, key, orientation, wrap, isNavigable } = params;
45
+ if (count <= 0)
46
+ return null;
47
+ if (key === "Home")
48
+ return firstNavigable(count, isNavigable);
49
+ if (key === "End")
50
+ return lastNavigable(count, isNavigable);
51
+ const vertical = orientation === "vertical" || orientation === "grid";
52
+ const horizontal = orientation === "horizontal" || orientation === "grid";
53
+ let dir = 0;
54
+ if ((vertical && key === "ArrowDown") || (horizontal && key === "ArrowRight"))
55
+ dir = 1;
56
+ else if ((vertical && key === "ArrowUp") ||
57
+ (horizontal && key === "ArrowLeft"))
58
+ dir = -1;
59
+ if (dir === 0)
60
+ return null;
61
+ let i = current;
62
+ for (let n = 0; n < count; n++) {
63
+ i += dir;
64
+ if (i < 0 || i >= count) {
65
+ if (!wrap)
66
+ return null;
67
+ i = (i + count) % count;
68
+ }
69
+ if (i === current)
70
+ return null; // came all the way around — nothing else navigable
71
+ if (isNavigable(i))
72
+ return i;
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Whether a keydown should open an item's context menu — the dedicated
78
+ * ContextMenu (Menu/Application) key, or `Shift`+`F10` for keyboards without it.
79
+ *
80
+ * @internal
81
+ */
82
+ export function isContextMenuKey(e) {
83
+ return e.key === "ContextMenu" || (e.shiftKey && e.key === "F10");
84
+ }
85
+ /**
86
+ * Headless keyboard navigation for a **focus group** — a set of peer items that
87
+ * share a single tab stop and move with the arrow keys (a list, listbox, menu,
88
+ * toolbar, tablist, radio group, or flat grid). It owns, once and correctly, the
89
+ * mechanics every such widget needs:
90
+ *
91
+ * - a single-tab-stop `tabIndex` (one item tabbable, the rest `-1`), so the group
92
+ * is one Tab stop and the host's "focus the first tabbable" entry lands on
93
+ * {@link FocusGroupOptions.start | start};
94
+ * - Arrow / Home / End movement (per
95
+ * {@link FocusGroupOptions.orientation | orientation}, wrapping or stopping per
96
+ * {@link FocusGroupOptions.wrap | wrap}, skipping non-navigable items);
97
+ * - `Enter`/`Space` → {@link FocusGroupOptions.onActivate | onActivate}, the
98
+ * context-menu key / `Shift`+`F10` → {@link FocusGroupOptions.onMenu | onMenu};
99
+ * - a **WebKit-safe, keyboard-only focus ring**: it flags the active item with a
100
+ * `data-focus-visible` attribute (state-driven, because WebKit won't repaint
101
+ * `:focus` for the programmatic focus the host's region cycle performs), and
102
+ * the host ships the ring CSS keyed on that attribute — so every group's ring
103
+ * is identical and correct without the author touching it.
104
+ *
105
+ * You keep the markup and semantics (`role`, `aria-*`, `onClick`, styling); the
106
+ * hook supplies behavior. Spread {@link FocusGroup.containerProps | containerProps}
107
+ * on the wrapper and {@link FocusGroup.getItemProps | getItemProps(i)} on each
108
+ * item. The index is clamped when {@link FocusGroupOptions.count | count} changes,
109
+ * so live-filtering a list is safe.
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * const group = useFocusGroup({
114
+ * count: items.length,
115
+ * start: activeIndex,
116
+ * onActivate: (i) => select(items[i].id),
117
+ * onMenu: (i, anchor) => showMenu(items[i], anchor),
118
+ * });
119
+ * return (
120
+ * <ul {...group.containerProps}>
121
+ * {items.map((it, i) => (
122
+ * <li key={it.id} {...group.getItemProps(i)}>{it.label}</li>
123
+ * ))}
124
+ * </ul>
125
+ * );
126
+ * ```
127
+ *
128
+ * @category Consumer Services
129
+ * @public
130
+ */
131
+ export function useFocusGroup(options) {
132
+ const { count, start = 0, orientation = "vertical", wrap = true, isNavigable = () => true, onActivate, onMenu, } = options;
133
+ const [activeIndex, setActiveIndex] = useState(0);
134
+ // Focus is somewhere inside the group (drives re-parking on the active row).
135
+ const [focused, setFocused] = useState(false);
136
+ // Focus arrived/moved via the keyboard (drives the ring), à la `:focus-visible`.
137
+ const [visible, setVisible] = useState(false);
138
+ // The most recent focus was preceded by a pointer-down on an item — so it's a
139
+ // mouse focus and must NOT show the ring. Read+reset on the following onFocus.
140
+ const pointerOrigin = useRef(false);
141
+ const itemEls = useRef(new Map());
142
+ // Read latest options in stable handlers without re-creating them each render.
143
+ const opts = useRef({
144
+ count,
145
+ orientation,
146
+ wrap,
147
+ isNavigable,
148
+ onActivate,
149
+ onMenu,
150
+ });
151
+ opts.current = { count, orientation, wrap, isNavigable, onActivate, onMenu };
152
+ // Park the active index on `start` (clamped, and nudged to a navigable item)
153
+ // whenever the group isn't focused, so the host's entry lands there; while
154
+ // focused, just keep the index in range as items come and go.
155
+ useEffect(() => {
156
+ setActiveIndex((i) => {
157
+ if (count <= 0)
158
+ return 0;
159
+ if (focused)
160
+ return Math.min(i, count - 1);
161
+ const at = clamp(start, count - 1);
162
+ return isNavigable(at) ? at : (firstNavigable(count, isNavigable) ?? at);
163
+ // isNavigable identity changes per render; `start`/`count`/`focused` are the
164
+ // real inputs — re-running on isNavigable alone would thrash focus.
165
+ // eslint-disable-next-line react-hooks/exhaustive-deps
166
+ });
167
+ }, [start, focused, count]);
168
+ const focusItem = useCallback((index) => {
169
+ itemEls.current.get(index)?.focus();
170
+ }, []);
171
+ const containerProps = {
172
+ onBlur: (e) => {
173
+ // Drop focus state only when focus leaves the whole group (not when it
174
+ // moves between two items).
175
+ if (!e.currentTarget.contains(e.relatedTarget)) {
176
+ setFocused(false);
177
+ setVisible(false);
178
+ }
179
+ },
180
+ };
181
+ const getItemProps = (index) => ({
182
+ tabIndex: index === activeIndex ? 0 : -1,
183
+ ref: (el) => {
184
+ if (el)
185
+ itemEls.current.set(index, el);
186
+ else
187
+ itemEls.current.delete(index);
188
+ },
189
+ onPointerDown: () => {
190
+ pointerOrigin.current = true;
191
+ },
192
+ onFocus: () => {
193
+ setFocused(true);
194
+ setActiveIndex(index);
195
+ // Keyboard/programmatic focus shows the ring; a pointer-driven focus doesn't.
196
+ setVisible(!pointerOrigin.current);
197
+ pointerOrigin.current = false;
198
+ },
199
+ onKeyDown: (e) => {
200
+ const o = opts.current;
201
+ if (e.key === "Enter" || e.key === " ") {
202
+ if (!o.onActivate)
203
+ return;
204
+ e.preventDefault();
205
+ setVisible(true);
206
+ o.onActivate(index);
207
+ return;
208
+ }
209
+ if (isContextMenuKey(e)) {
210
+ if (!o.onMenu)
211
+ return;
212
+ e.preventDefault();
213
+ setVisible(true);
214
+ o.onMenu(index, e.currentTarget);
215
+ return;
216
+ }
217
+ const next = focusGroupNextIndex({
218
+ current: index,
219
+ count: o.count,
220
+ key: e.key,
221
+ orientation: o.orientation,
222
+ wrap: o.wrap,
223
+ isNavigable: o.isNavigable,
224
+ });
225
+ if (next === null)
226
+ return;
227
+ e.preventDefault();
228
+ setVisible(true);
229
+ focusItem(next);
230
+ },
231
+ "data-focus-item": "",
232
+ ...(visible && index === activeIndex ? { "data-focus-visible": "" } : {}),
233
+ });
234
+ return { containerProps, getItemProps, activeIndex, focusItem };
235
+ }
236
+ //# sourceMappingURL=use-focus-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-focus-group.js","sourceRoot":"","sources":["../src/use-focus-group.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { Disposable } from "./types";
2
+ /**
3
+ * The minimal reactive contract every stateful `ctx` service satisfies: a
4
+ * `getState` returning a stable, frozen value and a `subscribe` that fires
5
+ * on change. This is exactly what React's `useSyncExternalStore` needs — and
6
+ * the only shape {@link useServiceState} requires.
7
+ *
8
+ * @category Consumer Services
9
+ * @public
10
+ */
11
+ export interface ReactiveService<T> {
12
+ getState(): T;
13
+ subscribe(listener: (s: T) => void): Disposable;
14
+ }
15
+ /**
16
+ * Subscribe a React component to a `ctx` service's reactive state. Returns the
17
+ * service's current state and re-renders when it changes — the one blessed
18
+ * way to read service state in an extension. Use it for every domain
19
+ * ({@link ExtensionContext.workspaces | workspaces},
20
+ * {@link ExtensionContext.layout | layout},
21
+ * {@link ExtensionContext.theme | theme}, …) rather than re-implementing the
22
+ * `useSyncExternalStore` boilerplate per call site.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * function Panel({ ctx }: { ctx: ExtensionContext }) {
27
+ * const ws = useServiceState(ctx.workspaces);
28
+ * return <span>{ws.open.length} open workspaces</span>;
29
+ * }
30
+ * ```
31
+ *
32
+ * @category Consumer Services
33
+ * @public
34
+ */
35
+ export declare function useServiceState<T>(service: ReactiveService<T>): T;
36
+ //# sourceMappingURL=use-service-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-service-state.d.ts","sourceRoot":"","sources":["../src/use-service-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,QAAQ,IAAI,CAAC,CAAC;IACd,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,GAAG,UAAU,CAAC;CACjD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,CAKjE"}
@@ -0,0 +1,25 @@
1
+ import { useCallback, useSyncExternalStore } from "react";
2
+ /**
3
+ * Subscribe a React component to a `ctx` service's reactive state. Returns the
4
+ * service's current state and re-renders when it changes — the one blessed
5
+ * way to read service state in an extension. Use it for every domain
6
+ * ({@link ExtensionContext.workspaces | workspaces},
7
+ * {@link ExtensionContext.layout | layout},
8
+ * {@link ExtensionContext.theme | theme}, …) rather than re-implementing the
9
+ * `useSyncExternalStore` boilerplate per call site.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * function Panel({ ctx }: { ctx: ExtensionContext }) {
14
+ * const ws = useServiceState(ctx.workspaces);
15
+ * return <span>{ws.open.length} open workspaces</span>;
16
+ * }
17
+ * ```
18
+ *
19
+ * @category Consumer Services
20
+ * @public
21
+ */
22
+ export function useServiceState(service) {
23
+ return useSyncExternalStore(useCallback((cb) => service.subscribe(cb).dispose, [service]), service.getState);
24
+ }
25
+ //# sourceMappingURL=use-service-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-service-state.js","sourceRoot":"","sources":["../src/use-service-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAiB1D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,eAAe,CAAI,OAA2B;IAC5D,OAAO,oBAAoB,CACzB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,EAC7D,OAAO,CAAC,QAAQ,CACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,72 @@
1
+ import type { Disposable } from "./types";
2
+ import type { Workspace } from "./domain-types";
3
+ export type { Workspace, TerminalRecord } from "./domain-types";
4
+ /**
5
+ * An immutable, frozen view of workspace state, returned by
6
+ * {@link WorkspaceService.getState} and delivered to subscribers — read
7
+ * access without a Valtio dependency.
8
+ *
9
+ * @category Consumer Services
10
+ * @public
11
+ */
12
+ export interface WorkspaceState {
13
+ /** All workspaces, in user-defined order. */
14
+ all: readonly Workspace[];
15
+ /** Workspaces where closedAt is null/undefined, in user-defined order. */
16
+ open: readonly Workspace[];
17
+ /** Workspaces where closedAt is set, sorted by closedAt descending. */
18
+ closed: readonly Workspace[];
19
+ activeId: string | null;
20
+ /** True once the persisted state has been loaded into the store. */
21
+ hydrated: boolean;
22
+ }
23
+ /**
24
+ * Input for {@link WorkspaceService.create}.
25
+ *
26
+ * @category Consumer Services
27
+ * @public
28
+ */
29
+ export interface CreateWorkspaceInput {
30
+ /** Absolute path of the workspace's primary folder. */
31
+ folder: string;
32
+ /** Display name for the workspace. */
33
+ name: string;
34
+ }
35
+ /**
36
+ * Consumer API for workspace state, exposed as {@link ExtensionContext.workspaces}.
37
+ * Read via {@link WorkspaceService.getState | getState} /
38
+ * {@link WorkspaceService.subscribe | subscribe}; drive via the create/rename/
39
+ * close methods. Opening editor tabs lives on {@link ExtensionContext.editors},
40
+ * not here.
41
+ *
42
+ * @category Consumer Services
43
+ * @public
44
+ */
45
+ export interface WorkspaceService {
46
+ /** Current frozen view of workspace state. */
47
+ getState(): WorkspaceState;
48
+ subscribe(listener: (s: WorkspaceState) => void): Disposable;
49
+ /**
50
+ * The workspace with this id, or `undefined`. A one-shot lookup for event
51
+ * handlers; for reactive reads use {@link useServiceState} over the state.
52
+ */
53
+ get(id: string): Workspace | undefined;
54
+ /** Show a folder picker and create a workspace from the chosen folder. */
55
+ createFromFolderPicker(): Promise<Workspace | null>;
56
+ create(input: CreateWorkspaceInput): Workspace;
57
+ rename(id: string, name: string): void;
58
+ reorder(from: string, to: string, position: "before" | "after"): void;
59
+ /** Activate (and reopen if closed). */
60
+ activate(id: string): void;
61
+ /** Soft close — workspace stays saved but is hidden from the active list. */
62
+ close(id: string): void;
63
+ /** Reverse of close. */
64
+ reopen(id: string): void;
65
+ /** Add an extra folder to a workspace (no-op if already present or is the primary). */
66
+ addFolder(id: string, folder: string): void;
67
+ /** Remove an extra folder from a workspace. */
68
+ removeFolder(id: string, folder: string): void;
69
+ /** Hard delete — permanent removal. */
70
+ delete(id: string): void;
71
+ }
72
+ //# sourceMappingURL=workspace-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-service.d.ts","sourceRoot":"","sources":["../src/workspace-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,GAAG,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1B,0EAA0E;IAC1E,IAAI,EAAE,SAAS,SAAS,EAAE,CAAC;IAC3B,uEAAuE;IACvE,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,QAAQ,IAAI,cAAc,CAAC;IAC3B,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,GAAG,UAAU,CAAC;IAC7D;;;OAGG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACvC,0EAA0E;IAC1E,sBAAsB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC/C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC;IACtE,uCAAuC;IACvC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,wBAAwB;IACxB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uFAAuF;IACvF,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,+CAA+C;IAC/C,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,uCAAuC;IACvC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workspace-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-service.js","sourceRoot":"","sources":["../src/workspace-service.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@silo-code/sdk",
3
+ "version": "0.6.0",
4
+ "description": "The public, types-first SDK for building Silo extensions — the only surface third-party extensions import.",
5
+ "license": "MIT",
6
+ "author": "Dave Weaver",
7
+ "homepage": "https://github.com/silo-code/silo/tree/main/packages/sdk#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/silo-code/silo.git",
11
+ "directory": "packages/sdk"
12
+ },
13
+ "keywords": [
14
+ "silo",
15
+ "silo-code",
16
+ "extension",
17
+ "sdk",
18
+ "editor"
19
+ ],
20
+ "type": "module",
21
+ "sideEffects": false,
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "src",
30
+ "dist"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "peerDependencies": {
36
+ "react": "^19.1.0"
37
+ },
38
+ "dependencies": {
39
+ "dockview": "~6.3.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/react": "^19.1.8",
43
+ "typescript": "~5.8.3",
44
+ "vitest": "^4.1.7"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc -p tsconfig.build.json",
48
+ "test": "vitest run",
49
+ "test:watch": "vitest"
50
+ },
51
+ "main": "./dist/index.js",
52
+ "module": "./dist/index.js",
53
+ "types": "./dist/index.d.ts"
54
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Minimal context-keys store. Menu items, keybindings, and (later) view
3
+ * contributions can declare `when` clauses that read these values; the host
4
+ * re-evaluates the clauses whenever a key changes and updates UI state
5
+ * (enabled/disabled menu items, etc.).
6
+ *
7
+ * Keys are added as needed. Start narrow: only the keys we have a real use
8
+ * for. Avoid the temptation to mirror every piece of app state into here.
9
+ *
10
+ * @category Core Types
11
+ * @public
12
+ */
13
+ export interface ContextKeys {
14
+ /** id of the viewer rendered by the dock's active panel, or null. */
15
+ activeViewerId: string | null;
16
+ /** editorId of the active dock panel if it's an editor panel, or null. */
17
+ activeEditorId: string | null;
18
+ }
@@ -0,0 +1,151 @@
1
+ import type React from "react";
2
+ import type { Disposable } from "./types";
3
+
4
+ // `ctx.dnd` — first-class drag-and-drop for extensions (public contract). The
5
+ // host owns the affordance (the floating drag chip + paste-mode overlay) and
6
+ // the modifier-mode resolution; the implementation lives in the extension host.
7
+
8
+ /**
9
+ * Well-known MIME types for Silo drag payloads. Use these constants (rather than
10
+ * raw strings) so drags interoperate across extensions and built-ins.
11
+ *
12
+ * @category Core Types
13
+ * @public
14
+ */
15
+ export const DND_MIME = {
16
+ /** Absolute filesystem path of a file or directory being dragged. */
17
+ filePath: "application/x-silo-file-path",
18
+ /** Plain-text payload (mirrors the path today). */
19
+ text: "text/plain",
20
+ } as const;
21
+
22
+ /**
23
+ * A MIME type from the {@link DND_MIME} vocabulary.
24
+ *
25
+ * @category Core Types
26
+ * @public
27
+ */
28
+ export type DndMime = (typeof DND_MIME)[keyof typeof DND_MIME];
29
+
30
+ /**
31
+ * One typed item carried by a drag — a MIME type plus its string payload.
32
+ *
33
+ * @category Core Types
34
+ * @public
35
+ */
36
+ export interface DndItem {
37
+ /** The item's MIME type; use a {@link DND_MIME} constant for interop. */
38
+ mime: string;
39
+ /** The string payload (e.g. an absolute path for {@link DND_MIME.filePath}). */
40
+ value: string;
41
+ }
42
+
43
+ /**
44
+ * What a drag carries and how its chip should read, passed to
45
+ * {@link DndService.beginDrag}.
46
+ *
47
+ * @category Core Types
48
+ * @public
49
+ */
50
+ export interface DragInit {
51
+ /** Typed payload items written onto the native `dataTransfer`. */
52
+ items: DndItem[];
53
+ /** Label shown in the floating drag chip (e.g. the file name). */
54
+ label: string;
55
+ /** `dataTransfer.effectAllowed`; defaults to `"copyMove"`. */
56
+ effect?: "copy" | "move" | "copyMove";
57
+ }
58
+
59
+ /**
60
+ * The interaction mode resolved from held modifiers at hover/drop time:
61
+ * `"copy"` is the default (e.g. open / split); `"paste"` is Shift-held (e.g.
62
+ * insert path at caret / paste into the terminal). Resolved robustly even when
63
+ * WebKit suppresses key events mid-drag.
64
+ *
65
+ * @category Core Types
66
+ * @public
67
+ */
68
+ export type DndMode = "copy" | "paste";
69
+
70
+ /**
71
+ * Context delivered to a {@link DropTargetHandlers} callback for a drag over or
72
+ * drop on a registered target.
73
+ *
74
+ * @category Core Types
75
+ * @public
76
+ */
77
+ export interface DropContext {
78
+ /**
79
+ * Typed items read from the native `dataTransfer`. Populated on `drop`;
80
+ * during `dragover` the browser exposes only MIME *types* (not values), so
81
+ * this may be empty there — branch on {@link DropContext.mode} / the target's
82
+ * `accepts` instead.
83
+ */
84
+ items: DndItem[];
85
+ /** The resolved modifier mode (Shift ⇒ `"paste"`). */
86
+ mode: DndMode;
87
+ /** Pointer X in client coordinates. */
88
+ clientX: number;
89
+ /** Pointer Y in client coordinates. */
90
+ clientY: number;
91
+ /** The underlying native event (escape hatch for advanced callers). */
92
+ nativeEvent: DragEvent;
93
+ }
94
+
95
+ /**
96
+ * Handlers for a target registered via {@link DndService.registerDropTarget}.
97
+ *
98
+ * @category Core Types
99
+ * @public
100
+ */
101
+ export interface DropTargetHandlers {
102
+ /** MIME types this target consumes; a drag is accepted if it carries one. */
103
+ accepts: string[];
104
+ /**
105
+ * Called on a matching drop. Return `true` if you handled it — the host then
106
+ * calls `preventDefault()` + `stopPropagation()` so the drop does **not**
107
+ * fall through to other targets (e.g. the center dock opening a new pane).
108
+ */
109
+ onDrop(ctx: DropContext): boolean | void;
110
+ /**
111
+ * Optional: called on each accepted drag-over (drive hover styling). Return a
112
+ * drop effect to set the cursor, or nothing to leave it. The host already
113
+ * calls `preventDefault()` so the drop is allowed.
114
+ */
115
+ onDragOver?(ctx: DropContext): "copy" | "move" | "none" | void;
116
+ /**
117
+ * Optional: the pointer left the target (clear hover styling). Receives the
118
+ * native event so callers can ignore leaves into a descendant
119
+ * (`el.contains(e.relatedTarget)`).
120
+ */
121
+ onDragLeave?(e: DragEvent): void;
122
+ /**
123
+ * Attach listeners in the capture phase so this target intercepts before
124
+ * bubble-phase handlers (e.g. the center dock). Defaults to `false`.
125
+ */
126
+ capture?: boolean;
127
+ }
128
+
129
+ /**
130
+ * The drag-and-drop domain, exposed as {@link ExtensionContext.dnd}. Be a drag
131
+ * source with {@link DndService.beginDrag} and a drop target with
132
+ * {@link DndService.registerDropTarget}; payloads are typed via {@link DND_MIME}.
133
+ *
134
+ * @category Consumer Services
135
+ * @public
136
+ */
137
+ export interface DndService {
138
+ /**
139
+ * Begin a drag from inside a `dragstart` handler: writes the typed
140
+ * {@link DragInit.items} onto the native `dataTransfer`, hides the native
141
+ * drag preview, and starts the floating chip + paste-mode overlay affordance.
142
+ * Must be called synchronously within the `dragstart` event.
143
+ */
144
+ beginDrag(event: DragEvent | React.DragEvent, init: DragInit): void;
145
+ /**
146
+ * Register `el` as a drop target. The host attaches the drag listeners,
147
+ * resolves the modifier {@link DndMode}, and delivers a {@link DropContext}.
148
+ * Returns a {@link Disposable} that removes the listeners.
149
+ */
150
+ registerDropTarget(el: HTMLElement, handlers: DropTargetHandlers): Disposable;
151
+ }