@loomweaver/plugin-sdk 0.7.2

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 (71) hide show
  1. package/README.md +11 -0
  2. package/package.json +45 -0
  3. package/src/index.d.ts +22 -0
  4. package/src/index.js +23 -0
  5. package/src/index.js.map +1 -0
  6. package/src/lib/auth.d.ts +60 -0
  7. package/src/lib/auth.js +35 -0
  8. package/src/lib/auth.js.map +1 -0
  9. package/src/lib/bar-item.d.ts +67 -0
  10. package/src/lib/bar-item.js +2 -0
  11. package/src/lib/bar-item.js.map +1 -0
  12. package/src/lib/button.d.ts +4 -0
  13. package/src/lib/button.js +2 -0
  14. package/src/lib/button.js.map +1 -0
  15. package/src/lib/capability.d.ts +38 -0
  16. package/src/lib/capability.js +32 -0
  17. package/src/lib/capability.js.map +1 -0
  18. package/src/lib/command.d.ts +199 -0
  19. package/src/lib/command.js +2 -0
  20. package/src/lib/command.js.map +1 -0
  21. package/src/lib/container-handle.d.ts +36 -0
  22. package/src/lib/container-handle.js +4 -0
  23. package/src/lib/container-handle.js.map +1 -0
  24. package/src/lib/content-route.d.ts +254 -0
  25. package/src/lib/content-route.js +2 -0
  26. package/src/lib/content-route.js.map +1 -0
  27. package/src/lib/contribution.d.ts +4 -0
  28. package/src/lib/contribution.js +2 -0
  29. package/src/lib/contribution.js.map +1 -0
  30. package/src/lib/dialog-ref.d.ts +25 -0
  31. package/src/lib/dialog-ref.js +37 -0
  32. package/src/lib/dialog-ref.js.map +1 -0
  33. package/src/lib/dialog.d.ts +105 -0
  34. package/src/lib/dialog.js +2 -0
  35. package/src/lib/dialog.js.map +1 -0
  36. package/src/lib/dirty-surface.d.ts +48 -0
  37. package/src/lib/dirty-surface.js +2 -0
  38. package/src/lib/dirty-surface.js.map +1 -0
  39. package/src/lib/menu.d.ts +48 -0
  40. package/src/lib/menu.js +2 -0
  41. package/src/lib/menu.js.map +1 -0
  42. package/src/lib/notification.d.ts +28 -0
  43. package/src/lib/notification.js +2 -0
  44. package/src/lib/notification.js.map +1 -0
  45. package/src/lib/pane-area.d.ts +29 -0
  46. package/src/lib/pane-area.js +2 -0
  47. package/src/lib/pane-area.js.map +1 -0
  48. package/src/lib/plugin-state.d.ts +52 -0
  49. package/src/lib/plugin-state.js +2 -0
  50. package/src/lib/plugin-state.js.map +1 -0
  51. package/src/lib/plugin.d.ts +259 -0
  52. package/src/lib/plugin.js +2 -0
  53. package/src/lib/plugin.js.map +1 -0
  54. package/src/lib/product-identity.d.ts +25 -0
  55. package/src/lib/product-identity.js +17 -0
  56. package/src/lib/product-identity.js.map +1 -0
  57. package/src/lib/rail-item.d.ts +61 -0
  58. package/src/lib/rail-item.js +2 -0
  59. package/src/lib/rail-item.js.map +1 -0
  60. package/src/lib/settings-model.d.ts +164 -0
  61. package/src/lib/settings-model.js +2 -0
  62. package/src/lib/settings-model.js.map +1 -0
  63. package/src/lib/surface.d.ts +220 -0
  64. package/src/lib/surface.js +2 -0
  65. package/src/lib/surface.js.map +1 -0
  66. package/src/lib/view-state.d.ts +38 -0
  67. package/src/lib/view-state.js +9 -0
  68. package/src/lib/view-state.js.map +1 -0
  69. package/src/lib/view.d.ts +103 -0
  70. package/src/lib/view.js +2 -0
  71. package/src/lib/view.js.map +1 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * A handle on one key of your plugin's own store. Reads are Signal-shaped, exactly like
3
+ * {@link ViewState} and `ctx.session`, so a template re-reads reactively without subscription
4
+ * bookkeeping.
5
+ */
6
+ export interface StateHandle<T = unknown> {
7
+ /** The stored value, reactive; `undefined` when nothing is stored (apply your own default). */
8
+ readonly value: () => T | undefined;
9
+ /**
10
+ * Whether the store has answered yet. Check it before you apply a default, otherwise a
11
+ * network-backed store lands its value **after** the user has started typing and overwrites the
12
+ * input. With a local store this is true immediately; the gate costs nothing there and is the
13
+ * difference between working and losing input everywhere else.
14
+ */
15
+ readonly loaded: () => boolean;
16
+ /**
17
+ * Replace the value under this key. It replaces the **whole** value — nothing is merged — so spread
18
+ * the current one when you change a field. Writes are debounced; call it per keystroke if you like.
19
+ * Values are JSON: the store is string-valued and crosses a process boundary, so promising
20
+ * structured clone would be a lie.
21
+ */
22
+ set(next: T): void;
23
+ /** Remove the key from the store. */
24
+ clear(): void;
25
+ /** Stop watching. Any pending write is flushed first. */
26
+ dispose(): void;
27
+ }
28
+ /**
29
+ * Your plugin's **own** keyed store — `ctx.state`. Every surface of your plugin sees the
30
+ * same store, whichever dock it sits in, however many instances are open and in every browser window,
31
+ * which makes it both your persistence and the only channel between your own surfaces. It is
32
+ * plugin-private by construction: the host prefixes every key with your plugin id and you cannot
33
+ * leave that namespace, so there is nothing foreign to reach and no capability to grant.
34
+ *
35
+ * It holds **working state**, not settings. Settings have their own path precisely because the user
36
+ * can *see* them in the settings dialog; a free-form settings store would be a back door around that
37
+ * transparency. Uninstalling your plugin deletes this store (a settings section survives, an
38
+ * abandoned draft is litter).
39
+ *
40
+ * The authoring rule that follows from "set replaces the whole value": **one key per unit of
41
+ * editing** — a wizard step, not the whole form — and where a surface can exist more than once, key
42
+ * by instance as well. Two windows editing two keys converge; two windows replacing one key means
43
+ * last write wins, which is fine for convergence and not fine for the user's typing.
44
+ */
45
+ export interface PluginState {
46
+ /**
47
+ * Watch one key and get a handle on it. This is also how the host learns which keys you care
48
+ * about: with free-form keys it cannot push everything, so the call that hands out the handle is
49
+ * the interest registration. Call {@link StateHandle.dispose} when your surface goes away.
50
+ */
51
+ watch<T = unknown>(key: string): StateHandle<T>;
52
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plugin-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-state.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/plugin-state.ts"],"names":[],"mappings":""}
@@ -0,0 +1,259 @@
1
+ import { PluginState } from './plugin-state.js';
2
+ import { Type } from '@angular/core';
3
+ import { Capability } from './capability.js';
4
+ import { Command, CommandArguments, CommandOutcome, InvocableCommand } from './command.js';
5
+ import { MenuItem } from './menu.js';
6
+ import { Disposable } from './contribution.js';
7
+ import { Surface } from './surface.js';
8
+ import { OpenTabInput } from './content-route.js';
9
+ import { BarItem } from './bar-item.js';
10
+ import { RailItem } from './rail-item.js';
11
+ import { SettingsSection } from './settings-model.js';
12
+ import { DialogRef } from './dialog-ref.js';
13
+ import { AlertOptions, ConfirmOptions, OpenOptions, ProgressHandle, ProgressOptions, PromptOptions } from './dialog.js';
14
+ import { NotificationInput } from './notification.js';
15
+ /** What a plugin declares about itself (grows: activation events, version, …). */
16
+ export interface PluginManifest {
17
+ /** Stable plugin id. */
18
+ readonly id: string;
19
+ /** Human-readable name. */
20
+ readonly name?: string;
21
+ /**
22
+ * Capabilities the plugin declares it needs. This is the *request*; the
23
+ * distribution/tenant *grants* (default-deny, see `provideCapabilityGrants`). The effective set
24
+ * is what was granted — a declaration alone grants nothing. The declaration is what the install
25
+ * consent dialog shows and what the Permissions settings manage.
26
+ */
27
+ readonly capabilities?: readonly Capability[];
28
+ }
29
+ /**
30
+ * One row of an ad-hoc context menu opened via {@link PluginUi.openMenu}: a display `label` (a literal —
31
+ * the weaver localises its own body text), an optional leading `icon` (a host icon-registry name), and an
32
+ * in-process `run` handler invoked when the row is chosen. Trusted-rung only: `run` is a function, so it
33
+ * does not cross the sandbox RPC boundary — a sandboxed plugin draws its own `<lw-menu>` instead.
34
+ */
35
+ export interface UiMenuItem {
36
+ readonly label: string;
37
+ readonly icon?: string;
38
+ readonly run: () => void;
39
+ }
40
+ /**
41
+ * Host UI services a plugin reaches through `ctx.ui`: modal
42
+ * dialogs + transient toasts. This is the brokered path that replaces injecting the host
43
+ * services directly; the `ui` capability gate (default-deny) is enforced in front
44
+ * of it — a call without the grant throws.
45
+ */
46
+ export interface PluginUi {
47
+ confirm(options: ConfirmOptions): Promise<boolean>;
48
+ alert(options: AlertOptions): Promise<void>;
49
+ prompt(options: PromptOptions): Promise<string | null>;
50
+ open<R = unknown>(component: Type<unknown>, options?: OpenOptions): DialogRef<R>;
51
+ progress(options: ProgressOptions): ProgressHandle;
52
+ withProgress<T>(options: ProgressOptions, work: Promise<T>): Promise<T>;
53
+ toast(input: NotificationInput): string;
54
+ /** Opens the host settings surface; the host renders the registered sections. */
55
+ openSettings(): DialogRef;
56
+ /**
57
+ * Opens an ad-hoc context menu at a viewport point — for a right-click on the weaver's own view body
58
+ * (a Library row, a canvas node, …). The host draws it with the same `<lw-menu>` popover mechanics as
59
+ * its own menus (positioning, Escape/outside-pointer dismiss, focus restore). Trusted-rung only: the
60
+ * items' `run` handlers do not cross the sandbox boundary (a sandboxed plugin self-draws a `<lw-menu>`).
61
+ */
62
+ openMenu(items: readonly UiMenuItem[], at: {
63
+ x: number;
64
+ y: number;
65
+ }): void;
66
+ }
67
+ /**
68
+ * Read-only host facts + app-lifecycle a plugin reaches through `ctx.host` —
69
+ * so a plugin's About surface gets the version + update state through the brokered `ctx`
70
+ * instead of importing host services directly. `version`/`updateAvailable` are signal-shaped
71
+ * (`() => T`) so a template re-reads them reactively.
72
+ */
73
+ export interface PluginHost {
74
+ /** The running app version (e.g. "1.2.3"). */
75
+ readonly version: () => string;
76
+ /** True once a new version is downloaded and ready to activate. */
77
+ readonly updateAvailable: () => boolean;
78
+ /** Whether update checks are possible (a service worker is registered and enabled). */
79
+ readonly updatesEnabled: boolean;
80
+ /** Manually check for a new version. */
81
+ checkForUpdate(): Promise<void>;
82
+ /** Activate the downloaded version and reload into it. */
83
+ activateUpdate(): Promise<void>;
84
+ }
85
+ /**
86
+ * Read-only session facts a plugin reaches through `ctx.session` — so a plugin can gate its
87
+ * **own** surface/logic by the signed-in user's login state and roles, the imperative counterpart to the
88
+ * declarative `access` on contributions. LoomWeaver owns no auth: this only reflects the snapshot the
89
+ * distribution supplied (`provideAuthSource`). Signal-shaped (`() => T`) so a template re-reads reactively;
90
+ * roles are opaque strings — match, do not interpret. The snapshot's claim bag does not arrive here:
91
+ * a plugin is told the login state and the roles, and nothing else. Client-side gating is
92
+ * presentation, not security. Gated by the `session` capability (default-deny).
93
+ */
94
+ export interface PluginSession {
95
+ /** Whether someone is signed in. */
96
+ readonly authenticated: () => boolean;
97
+ /** The current principal's roles (empty when anonymous). */
98
+ readonly roles: () => readonly string[];
99
+ /** Convenience: whether the current principal holds `role`. */
100
+ hasRole(role: string): boolean;
101
+ }
102
+ /**
103
+ * The content the URL pane currently shows — the read side of the content area a plugin reaches
104
+ * through `ctx.activeContent`. Path parameters are extracted against the matched
105
+ * route's pattern (`ask/:id` on `ask/abc` → `{ id: 'abc' }`), so a plugin never parses URLs.
106
+ */
107
+ export interface ActiveContent {
108
+ /** The matched surface's id, or `null` when the matched route carries none. */
109
+ readonly surfaceId: string | null;
110
+ /** The full active content path (no leading slash), including any sub-route segment. */
111
+ readonly path: string;
112
+ /** The `:param` values of the matched route pattern (empty for parameter-less routes). */
113
+ readonly params: Readonly<Record<string, string>>;
114
+ }
115
+ /**
116
+ * The `ctx` a plugin uses to contribute to the host — one uniform contract. Trusted in-process
117
+ * plugins get a host-backed implementation; sandboxed plugins get the same surface over RPC.
118
+ */
119
+ export interface PluginContext {
120
+ /**
121
+ * Registers a named, invocable {@link Command}. Triggers — rail/bar/
122
+ * view-action items (via `command: <id>`), keybindings, and the command palette — reference it
123
+ * by id, so one behaviour has many entry points.
124
+ */
125
+ registerCommand(command: Command): Disposable;
126
+ /**
127
+ * Registers a {@link Surface} — the **one** author contract for anything the host renders.
128
+ * Declare *what the surface can do* (`routable`, `instanceable`, `docks`) rather than *where it lives*;
129
+ * the host places it (a routable surface into the content area, else into its home dock) and the user
130
+ * re-arranges it from there.
131
+ *
132
+ * Replaced `registerView` + `registerRoute`, removed in 0.5.0:
133
+ * - a panel view → a non-routable surface: `registerSurface({ id, title, docks: ['<regionId>'], component })`
134
+ * - a content route → a routable surface: `registerSurface({ id, title, routable: { path }, component })`
135
+ *
136
+ * Both migrations need an `id` and a `title`, which the old contracts did not carry.
137
+ */
138
+ registerSurface(surface: Surface): Disposable;
139
+ registerBarItem(item: BarItem): Disposable;
140
+ registerRailItem(item: RailItem): Disposable;
141
+ /** Contributes a section to the host settings surface. */
142
+ registerSettingsSection(section: SettingsSection): Disposable;
143
+ /**
144
+ * Contributes an item to a named menu slot — e.g. add an action to the tab context menu
145
+ * (`'content/tab/context'`) or a plugin's own slot. The item names a {@link Command} by id (invoked with
146
+ * the menu's {@link MenuContext}) and may declare a coarse `when` filter. The host draws the menu.
147
+ */
148
+ registerMenuItem(item: MenuItem): Disposable;
149
+ /**
150
+ * Contributes custom icon names the weaver can then reference in its contributions
151
+ * (`Command.icon` / `View.icon` / `RailItem.icon` / `BarButtonItem.icon`). Each value
152
+ * is a raw SVG string (an `@ng-icons` export or hand-authored markup). Names are flat and
153
+ * collision-safe: a name already registered by the shell or another plugin is ignored (first-wins,
154
+ * dev-warned), so pick unique names. The returned {@link Disposable} removes exactly these names.
155
+ */
156
+ contributeIcons(icons: Readonly<Record<string, string>>): Disposable;
157
+ /**
158
+ * Contributes design tokens (`--lw-*` custom properties) that re-skin the whole app — host chrome and
159
+ * every other plugin, since all read the same tokens. The vocabulary covers colors and the
160
+ * UI font (`--lw-font-sans` / `--lw-font-mono`); font *size* is a user preference, not a theme token.
161
+ * Requires the `theme` capability. Only
162
+ * whitelisted `--lw-*` names apply; unknown names are ignored. Contributed tokens sit **below** any
163
+ * tenant/distribution branding in the cascade (Tenant > Plugin > Product), so a plugin themes freely
164
+ * but never overrides a token the tenant explicitly set. Collisions across plugins are first-wins:
165
+ * the plugin that contributes a token first owns it in **both** schemes, so a later plugin cannot
166
+ * take over just its dark value. The returned {@link Disposable} removes exactly these tokens and
167
+ * the app reverts.
168
+ *
169
+ * `tokens` apply in both light and dark mode. Pass the optional `dark` map to override specific
170
+ * tokens only when dark mode is active — e.g. warm dark surfaces while `tokens` carries
171
+ * the light palette. Dark overrides win over `tokens` in dark mode; tokens absent from `dark` keep
172
+ * their `tokens` value across both modes.
173
+ */
174
+ contributeTheme(tokens: Readonly<Record<string, string>>, dark?: Readonly<Record<string, string>>): Disposable;
175
+ /** Navigates the content area to a route path — switches perspective / full-area view (`navigation`). */
176
+ navigateContent(path: string): void;
177
+ /**
178
+ * Opens a titled **dynamic** tab (e.g. an open document) in the content group of the path's
179
+ * matched route (navigating there activates that group) and navigates to it; opening the same
180
+ * path again just re-activates it (`navigation`). Set `titleIsLiteral` for a
181
+ * non-translatable title, `onClose` to free per-tab state when the tab is closed, and `preview` to
182
+ * open it as a single reused *preview* slot (see {@link OpenTabInput}).
183
+ */
184
+ openContentTab(input: OpenTabInput): void;
185
+ /**
186
+ * Promotes the **preview** tab rooted at `path` to a permanent tab — the programmatic
187
+ * "Keep Open", e.g. bound to a double-click or fired when the content is edited. A no-op if the tab
188
+ * is already permanent or not open (`navigation`).
189
+ */
190
+ keepContentTab(path: string): void;
191
+ /**
192
+ * **Pins** the dynamic tab rooted at `path`: the host sorts it to the front of its
193
+ * group and guards it against accidental close (its close control becomes an unpin control).
194
+ * Pinning also promotes a preview tab. A no-op if the tab is not open (`navigation`).
195
+ */
196
+ pinContentTab(path: string): void;
197
+ /** Unpins the tab rooted at `path` — it returns to a normal, closable tab. No-op otherwise (`navigation`). */
198
+ unpinContentTab(path: string): void;
199
+ /** Closes a dynamic content tab by path; the host activates a neighbour (`navigation`). */
200
+ closeContentTab(path: string): void;
201
+ /**
202
+ * Reveals an already-**docked** surface by id (finding #29): activates its tab wherever the user
203
+ * has placed it — a sidebar panel (expanding a collapsed one) or a content pane — so a command
204
+ * like "Focus Library" can bring a docked view to the front. Routable surfaces are reached via
205
+ * {@link navigateContent} instead; container-only children (`docks: []`) live inside their
206
+ * container and are not revealed from here. A no-op for an unknown or un-placed id (`navigation`).
207
+ */
208
+ revealSurface(id: string): void;
209
+ /**
210
+ * Signal-shaped read of the {@link ActiveContent} — which routable surface the URL pane
211
+ * currently shows, with its path parameters (`navigation`). The read side of the content area
212
+ *: a panel that reacts to "which tab is focused" (an inspector, a details view) reads
213
+ * this instead of injecting the host's router and parsing URLs, so it stays stable across host
214
+ * URL-shape changes. `null` when no content route matches. Trusted rung only (like `ui.openMenu`,
215
+ * it does not cross the sandbox RPC boundary — a sandboxed surface already receives its own state
216
+ * over the surface channel).
217
+ */
218
+ readonly activeContent: () => ActiveContent | null;
219
+ /**
220
+ * Runs a registered command by id and answers what it did (`automation`, unless the command is
221
+ * one this plugin registered itself — its own need no grant).
222
+ *
223
+ * Unlike every other member here this **answers a refusal instead of throwing one**, because a
224
+ * caller working through a list of actions has to handle "you may not" as an outcome rather than as
225
+ * an exception. The user is still told, exactly as they are for every other refused route.
226
+ *
227
+ * A refusal reads `unavailable` for every reason a command cannot be reached — no such id, not
228
+ * {@link Command.callable}, the session does not meet its {@link Command.access}, this window does
229
+ * not host it, the grant is missing — so that invoking ids and reading the reason back cannot map
230
+ * what is installed.
231
+ */
232
+ invokeCommand(id: string, args?: CommandArguments): Promise<CommandOutcome>;
233
+ /**
234
+ * The commands this plugin may invoke that it did not register itself (`automation`) — the
235
+ * workbench's own account, already narrowed by everything that would refuse the invocation, with
236
+ * every text resolved to the active language. Empty without the grant, whatever is installed.
237
+ *
238
+ * Read it instead of keeping a list of your own: a second list is a second answer to "may this
239
+ * run", and it is not the one the user can see and withdraw.
240
+ */
241
+ readonly invocableCommands: () => readonly InvocableCommand[];
242
+ /** Host UI services (dialogs, toasts). */
243
+ readonly ui: PluginUi;
244
+ /** Read-only host facts + app-lifecycle (version, update). */
245
+ readonly host: PluginHost;
246
+ /** Read-only session facts (login state + roles) for self-gating (gated by `session`). */
247
+ readonly session: PluginSession;
248
+ /**
249
+ * Your plugin's own keyed store — working state shared by all your surfaces, in every window
250
+ *. Ungated: it is plugin-private by construction, so there is nothing foreign to reach.
251
+ */
252
+ readonly state: PluginState;
253
+ }
254
+ /** A LoomWeaver plugin: declares itself and contributes on activation. */
255
+ export interface Plugin {
256
+ readonly manifest: PluginManifest;
257
+ activate(ctx: PluginContext): void | Promise<void>;
258
+ deactivate?(): void;
259
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/plugin.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import { InjectionToken, Provider } from '@angular/core';
2
+ /**
3
+ * The product a LoomWeaver installation presents as (name, tagline, logo).
4
+ *
5
+ * Branding lives in the distribution (composition root), never in the platform
6
+ * core: a distribution provides its identity, the neutral shell renders it. The
7
+ * bare platform falls back to {@link LOOMWEAVER_IDENTITY}.
8
+ */
9
+ export interface ProductIdentity {
10
+ /** Literal brand name, e.g. `Acme Studio`. Not localized. */
11
+ readonly name: string;
12
+ /** Transloco key for the tagline (a literal renders as-is if it is no key). */
13
+ readonly tagline: string;
14
+ /** URL/path to the square product logo. */
15
+ readonly logoUrl: string;
16
+ }
17
+ /** Bare-platform fallback used when no distribution configures an identity. */
18
+ export declare const LOOMWEAVER_IDENTITY: ProductIdentity;
19
+ /**
20
+ * Active product identity. Defaults to {@link LOOMWEAVER_IDENTITY}; a
21
+ * distribution overrides it via {@link provideProductIdentity}.
22
+ */
23
+ export declare const PRODUCT_IDENTITY: InjectionToken<ProductIdentity>;
24
+ /** Provider a distribution uses to declare its product identity. */
25
+ export declare function provideProductIdentity(identity: ProductIdentity): Provider;
@@ -0,0 +1,17 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ /** Bare-platform fallback used when no distribution configures an identity. */
3
+ export const LOOMWEAVER_IDENTITY = {
4
+ name: 'LoomWeaver',
5
+ tagline: 'shell.tagline',
6
+ logoUrl: 'loom-icon-64.png',
7
+ };
8
+ /**
9
+ * Active product identity. Defaults to {@link LOOMWEAVER_IDENTITY}; a
10
+ * distribution overrides it via {@link provideProductIdentity}.
11
+ */
12
+ export const PRODUCT_IDENTITY = new InjectionToken('PRODUCT_IDENTITY', { providedIn: 'root', factory: () => LOOMWEAVER_IDENTITY });
13
+ /** Provider a distribution uses to declare its product identity. */
14
+ export function provideProductIdentity(identity) {
15
+ return { provide: PRODUCT_IDENTITY, useValue: identity };
16
+ }
17
+ //# sourceMappingURL=product-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-identity.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/product-identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAY,MAAM,eAAe,CAAC;AAkBzD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAoB;IAClD,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,eAAe;IACxB,OAAO,EAAE,kBAAkB;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,cAAc,CAChD,kBAAkB,EAClB,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAC3D,CAAC;AAEF,oEAAoE;AACpE,MAAM,UAAU,sBAAsB,CAAC,QAAyB;IAC9D,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { AccessRequirement } from './auth.js';
2
+ /**
3
+ * A Rail/Ribbon item: an **independent command** triggered from the far
4
+ * rail — not a view switcher (switching views is the panel's tab bar). Mirrors
5
+ * Obsidian's ribbon (`addRibbonIcon`).
6
+ */
7
+ export interface RailItem {
8
+ readonly id: string;
9
+ /** Target Rail region id. */
10
+ readonly rail: string;
11
+ /** Icon name — resolved by the host icon registry (a plain string). */
12
+ readonly icon: string;
13
+ /**
14
+ * One or two letters the host draws **instead of** the icon, for an entry whose name is the user's
15
+ * rather than yours (a saved workspace, a project, an account) — a fixed glyph would make every
16
+ * such entry look alike. {@link icon} stays required as the fallback for a name too short or too
17
+ * foreign to abbreviate.
18
+ *
19
+ * Keep it to two characters: the rail draws them at icon size, and three no longer read at a
20
+ * glance. The host renders them in the app font and in the same colour the icon would have taken,
21
+ * so hover and the active marker behave exactly as elsewhere.
22
+ */
23
+ readonly initials?: string;
24
+ /** Transloco key (or literal) for the tooltip/label. */
25
+ readonly title: string;
26
+ /** Lower renders first within its anchor group (default 0). */
27
+ readonly order?: number;
28
+ /** Pinned to the top (default) or the bottom of the rail (e.g. settings, VS Code style). */
29
+ readonly anchor?: 'top' | 'bottom';
30
+ /**
31
+ * Id of a menu slot to open as this item's **context menu** on right-click — region-agnostic:
32
+ * the host wires the right-click uniformly and passes a serialisable context (`{ targetKind, id, region }`).
33
+ * Contribute items to the slot with `ctx.registerMenuItem({ menu, … })`. Omit for no context menu.
34
+ */
35
+ readonly menu?: string;
36
+ /**
37
+ * Id of a registered {@link Command} this item triggers. Provide this **or** {@link run}; when
38
+ * set, the host runs that command (so a keybinding/palette can share the same behaviour).
39
+ */
40
+ readonly command?: string;
41
+ /**
42
+ * Declarative auth gating: the host hides (default) or disables this item when the
43
+ * current session does not meet the requirement. Presentation only — real enforcement is
44
+ * server-side. Omit for an item everyone sees.
45
+ */
46
+ readonly access?: AccessRequirement;
47
+ /**
48
+ * Id of a workspace this item switches to. The host performs the switch itself and
49
+ * marks the item as the current one while that workspace is active, so a rail entry for a
50
+ * workspace needs no command of its own. Provide this **instead of** {@link command}/{@link run};
51
+ * when it is set those are ignored. An id no workspace answers to warns in development.
52
+ */
53
+ readonly workspace?: string;
54
+ /**
55
+ * Inline behaviour, for an item that is not backed by a registered command. May be async; the
56
+ * host fires it fire-and-forget. Typed `() => void` so a one-expression arrow whose handler
57
+ * happens to return a value (e.g. `() => ctx.ui.openSettings()`) still assigns — the return is
58
+ * ignored either way.
59
+ */
60
+ run?(): void;
61
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rail-item.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rail-item.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/rail-item.ts"],"names":[],"mappings":""}
@@ -0,0 +1,164 @@
1
+ import { Type } from '@angular/core';
2
+ import { LwButtonVariant } from './button.js';
3
+ /**
4
+ * Settings vocabulary (schema-driven). The host renders a settings
5
+ * surface from these declarations; each contributor (shell or plugin) supplies its own
6
+ * value accessors, so the *owner* keeps responsibility for storage. The host only reads
7
+ * `value()` and calls `set()`. The control vocabulary grows demand-driven (YAGNI):
8
+ * `select` (single choice) · `toggle` (on/off) · `text` (a string field) · `slider` (a number) ·
9
+ * `button` (an action) · `component` (embed a component).
10
+ */
11
+ /** An option in a {@link SettingSelect}. `label` is a Transloco key. */
12
+ export interface SelectOption {
13
+ readonly value: string;
14
+ readonly label: string;
15
+ }
16
+ /**
17
+ * A single-choice dropdown control. It owns its value binding — the contributor supplies
18
+ * `value()`/`set()`, so the *owner* keeps responsibility for storage (the host only reads
19
+ * and writes through them). Changes save immediately (the host calls `set()` on change).
20
+ */
21
+ export interface SettingSelect {
22
+ readonly kind: 'select';
23
+ readonly options: readonly SelectOption[];
24
+ /** Reads the current value — a signal works directly (it is a `() => string`). */
25
+ readonly value: () => string;
26
+ /** Persists a new value; the owner decides how and where. */
27
+ readonly set: (value: string) => void;
28
+ }
29
+ /**
30
+ * An on/off toggle (switch) control. Like {@link SettingSelect} it owns its value binding — the contributor
31
+ * supplies `value()`/`set()` (boolean), so the *owner* keeps storage. Changes save immediately.
32
+ */
33
+ export interface SettingToggle {
34
+ readonly kind: 'toggle';
35
+ readonly value: () => boolean;
36
+ readonly set: (value: boolean) => void;
37
+ }
38
+ /**
39
+ * A single-line text field control. Owns its value binding (`value()`/`set()` — a string). `inputType`
40
+ * picks the native input type (default `text`; e.g. `date`/`email`/`number`); `placeholder` is a Transloco
41
+ * key. Changes save immediately.
42
+ */
43
+ export interface SettingText {
44
+ readonly kind: 'text';
45
+ readonly value: () => string;
46
+ readonly set: (value: string) => void;
47
+ readonly inputType?: 'text' | 'date' | 'email' | 'number' | 'password';
48
+ readonly placeholder?: string;
49
+ }
50
+ /**
51
+ * A numeric slider control. Owns its value binding (`value()`/`set()` — a number). `min`/`max`/`step` bound
52
+ * and quantise it (defaults 0/100/1). Changes save immediately.
53
+ */
54
+ export interface SettingSlider {
55
+ readonly kind: 'slider';
56
+ readonly value: () => number;
57
+ readonly set: (value: number) => void;
58
+ readonly min?: number;
59
+ readonly max?: number;
60
+ readonly step?: number;
61
+ }
62
+ /**
63
+ * An action button control — no stored value. Provide a registered {@link Command} id via
64
+ * `command` (so the palette/keybindings can share the behaviour), **or** an inline `run()` for a
65
+ * one-off action (e.g. opening a dialog). `label` is a Transloco key.
66
+ */
67
+ export interface SettingButton {
68
+ readonly kind: 'button';
69
+ readonly label: string;
70
+ readonly variant?: LwButtonVariant;
71
+ /** Id of a registered command to run on click. Provide this **or** {@link run}. */
72
+ readonly command?: string;
73
+ /** Inline behaviour for a button not backed by a registered command. Provide this **or** {@link command}. */
74
+ readonly run?: () => void;
75
+ }
76
+ /**
77
+ * Embeds an existing component as the control — lets a
78
+ * setting reuse a host or plugin widget directly instead of re-describing it. The component
79
+ * reads/writes its own state via its own services.
80
+ */
81
+ export interface SettingComponent {
82
+ readonly kind: 'component';
83
+ readonly component: Type<unknown>;
84
+ /**
85
+ * Renders the component as a full-width block instead of in the row's right-hand control slot — for
86
+ * a self-contained widget that owns the whole row (e.g. a table or a list), not a compact control
87
+ * sitting beside a label. The row's `label`/`description` are then not painted; the component
88
+ * provides its own heading.
89
+ */
90
+ readonly fullWidth?: boolean;
91
+ }
92
+ /** A settings control — the host renders each kind with the matching primitive. */
93
+ export type SettingControl = SettingSelect | SettingToggle | SettingText | SettingSlider | SettingButton | SettingComponent;
94
+ /**
95
+ * One row in a settings section: a label/description the host paints and a control it
96
+ * renders. Value binding (for value controls) lives on the control itself.
97
+ */
98
+ export interface SettingRow {
99
+ readonly id: string;
100
+ /** Transloco key for the row label. */
101
+ readonly label: string;
102
+ /** Optional Transloco key for a secondary description line. */
103
+ readonly description?: string;
104
+ readonly control: SettingControl;
105
+ }
106
+ /** A contributed group of rows. The host renders sections ordered by `order` (default 0). */
107
+ export interface SettingsSection {
108
+ readonly id: string;
109
+ /** Transloco key for the section heading (and its left-nav entry). */
110
+ readonly title: string;
111
+ /** Transloco key for the left-nav group header this section sits under (e.g. Options vs Plugins). */
112
+ readonly group?: string;
113
+ readonly order?: number;
114
+ readonly rows: readonly SettingRow[];
115
+ }
116
+ /**
117
+ * The **data-only** control form a *sandboxed* plugin declares over the RPC boundary (the VS Code
118
+ * `contributes.configuration` model): the declaration carries the control kind and its
119
+ * **default value** instead of `value()`/`set()` callbacks, which cannot cross the wire. The host
120
+ * renders the control, **owns the storage** (user-local through the distribution's `SETTINGS_STORE` port)
121
+ * and pushes the current values back to the plugin — once after registration and on every change —
122
+ * by calling the `settingsChanged(sectionId, values)` method the plugin exposes on its RPC channel.
123
+ */
124
+ export type FrameSettingControl = {
125
+ readonly kind: 'toggle';
126
+ readonly value: boolean;
127
+ } | {
128
+ readonly kind: 'text';
129
+ readonly value: string;
130
+ readonly inputType?: 'text' | 'date' | 'email' | 'number' | 'password';
131
+ readonly placeholder?: string;
132
+ } | {
133
+ readonly kind: 'select';
134
+ readonly value: string;
135
+ readonly options: readonly SelectOption[];
136
+ } | {
137
+ readonly kind: 'slider';
138
+ readonly value: number;
139
+ readonly min?: number;
140
+ readonly max?: number;
141
+ readonly step?: number;
142
+ };
143
+ /**
144
+ * One declared row of a sandboxed plugin's settings. `label`/`description` may be plain literals — a
145
+ * sandboxed plugin cannot contribute translations, and the host renders an unknown key as-is.
146
+ */
147
+ export interface FrameSettingRow {
148
+ readonly id: string;
149
+ readonly label: string;
150
+ readonly description?: string;
151
+ readonly control: FrameSettingControl;
152
+ }
153
+ /**
154
+ * A sandboxed plugin's settings section. Registered over RPC via
155
+ * `ctx.registerSettingsSection`; the host decides the nav **group** (never the plugin): an
156
+ * *installed* plugin's section appears under **Community plugins**, a composed frame plugin's
157
+ * under **App plugins** — a plugin cannot masquerade as part of the app.
158
+ */
159
+ export interface FrameSettingsSection {
160
+ readonly id: string;
161
+ readonly title: string;
162
+ readonly order?: number;
163
+ readonly rows: readonly FrameSettingRow[];
164
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=settings-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-model.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/settings-model.ts"],"names":[],"mappings":""}