@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,254 @@
1
+ import { Type } from '@angular/core';
2
+ import { AccessRequirement } from './auth.js';
3
+ import { PaneArea } from './pane-area.js';
4
+ /**
5
+ * A **container** surface ("workspace-in-a-tab"): instead of rendering one thing, the host
6
+ * draws a nested, host-managed pane tree of **child surfaces** inside this surface's content tab —
7
+ * the same drag/split/min/max mechanics as the top level, one level nested and scoped to
8
+ * the parent tab instance. A container is always `routable` (it holds its own `:id`); its children are
9
+ * non-routable surfaces mounted off-router, each receiving the container's route params (its `:id`).
10
+ */
11
+ export interface ContainerSpec {
12
+ /**
13
+ * The child surfaces this container may hold — a bare surface id, or the object form when the child
14
+ * should also carry an **address** inside the container (see {@link ContainerChildEntry.segment}).
15
+ * The list is what the inner "new tab" picker offers, access-gated.
16
+ */
17
+ readonly children: readonly ContainerChild[];
18
+ /**
19
+ * How the tree looks when a container tab is opened fresh — either a plain list of child surface
20
+ * ids, which is shorthand for one tabs area, or an arrangement in the {@link PaneArea} grammar:
21
+ *
22
+ * ```ts
23
+ * initial: {
24
+ * columns: [
25
+ * { size: 60, tabs: ['quotes.positions'] },
26
+ * { size: 40, rows: [{ tabs: ['quotes.customer'] }, { tabs: ['quotes.margin'] }] },
27
+ * ],
28
+ * }
29
+ * ```
30
+ *
31
+ * A container whose path carries an `:id` needs this: its tree is keyed per instance, so no user
32
+ * gesture can ever become the default for the next one. The declaration applies whenever the tab
33
+ * is opened fresh; while it stays open the user's own arrangement wins.
34
+ *
35
+ * Every named child must also be listed in {@link children} — an entry that is not is dropped with
36
+ * a developer warning, as is a structurally invalid area. A child the current user may not see is
37
+ * still laid out and shows the host's access placeholder in its pane, so a session that has not
38
+ * arrived yet cannot flatten the declared layout.
39
+ */
40
+ readonly initial?: readonly string[] | ContainerArea;
41
+ }
42
+ /** A container child — a bare surface id, or the object form for a child that carries an address. */
43
+ export type ContainerChild = string | ContainerChildEntry;
44
+ export interface ContainerChildEntry {
45
+ /** The child surface id. */
46
+ readonly surface: string;
47
+ /**
48
+ * The child's address **inside** this container, in Angular path syntax, so it may carry values:
49
+ * `'list'`, `'entry/:entryId'`. Declaring one turns the child into something a sibling can open
50
+ * several times over — a list child opens `entry/e-01` and `entry/e-02` as two tabs, each its own
51
+ * pane if the user splits them, each with its own `VIEW_STATE`, all of it surviving a reload
52
+ * because it lives in the container's tree.
53
+ *
54
+ * The address is **relative**: it means nothing outside this container, which is what lets the same
55
+ * child surface serve several containers under different names, and what keeps the container
56
+ * sealed. While the container tab holds the browser address, the URL shows the focused child
57
+ * (`/runs/abc123/verdict`); in a split pane or a pop-out the child stays put and the address simply
58
+ * does not express it.
59
+ *
60
+ * Without a segment the child behaves exactly as it always has: reachable from the inner picker,
61
+ * one instance, no address.
62
+ *
63
+ * A child whose segment carries a value cannot be seeded in {@link ContainerSpec.initial} or offered
64
+ * by the picker — neither knows what value to use. It is opened by a sibling, which is the point.
65
+ * Declare a pane as `{ tabs: [] }` to say where those children land.
66
+ */
67
+ readonly segment?: string;
68
+ }
69
+ /** A container's initial arrangement — the {@link PaneArea} grammar over child surface ids. */
70
+ export type ContainerArea = PaneArea<ContainerTabEntry>;
71
+ /** A tab in a container declaration — a child surface id, or the object form for the extra flags. */
72
+ export type ContainerTabEntry = string | ContainerTab;
73
+ export interface ContainerTab {
74
+ /** The child surface id the tab mounts; it must be listed in {@link ContainerSpec.children}. */
75
+ readonly surface: string;
76
+ /**
77
+ * `false` fixes the tab inside this container: it shows no close affordance, "close others" and
78
+ * "close all" spare it, and it cannot be dragged to another pane of the container.
79
+ */
80
+ readonly closable?: boolean;
81
+ /** Marks the area's initially active tab; without it the first tab is active. */
82
+ readonly active?: boolean;
83
+ }
84
+ /**
85
+ * What a content route renders from (the UI-boundary form). Exactly one is set:
86
+ *
87
+ * - `component` — an Angular class rendered in-process (trusted rung; the default today). Cannot
88
+ * cross an RPC boundary, so a **sandboxed** plugin never uses this form.
89
+ * - `iframe` — a URL the host mounts as an **isolated** `<iframe sandbox>` surface. A plain string, so it
90
+ * serialises over the `ctx`-RPC boundary; this is how a sandboxed, non-Angular plugin contributes a
91
+ * content view (the first untrusted rung). A **trusted** plugin may use it too, to embed a foreign
92
+ * origin on purpose (a dashboard, a docs site, a video): a sandboxed plugin is confined to same-origin
93
+ * URLs at the RPC seam, whereas for a trusted one the distribution's CSP `frame-src` decides.
94
+ * - `container` — the host draws a nested pane tree of child surfaces.
95
+ *
96
+ * (`element` — a Web-Component tag — is the reserved form for the later WC rung; not yet.)
97
+ */
98
+ export type ContentSurface = {
99
+ readonly component: Type<unknown>;
100
+ readonly loadComponent?: never;
101
+ readonly iframe?: never;
102
+ readonly container?: never;
103
+ } | {
104
+ readonly loadComponent: () => Promise<Type<unknown>>;
105
+ readonly component?: never;
106
+ readonly iframe?: never;
107
+ readonly container?: never;
108
+ } | {
109
+ readonly iframe: string;
110
+ readonly component?: never;
111
+ readonly loadComponent?: never;
112
+ readonly container?: never;
113
+ } | {
114
+ readonly container: ContainerSpec;
115
+ readonly component?: never;
116
+ readonly loadComponent?: never;
117
+ readonly iframe?: never;
118
+ };
119
+ /** The route metadata shared by every surface form. */
120
+ export interface ContentRouteBase {
121
+ /**
122
+ * The originating surface's id — the handle a distribution omits the route by
123
+ * (`provideShell({ omit: ['route:<id>'] })`). Distinct from {@link path}, which stays the
124
+ * **override** handle (re-registering a path replaces it in place, last-in wins). Carried through
125
+ * from `ctx.registerSurface`; a route without an id can never be omitted.
126
+ */
127
+ readonly id?: string;
128
+ /** Route path (Angular syntax), e.g. `'reports'`, `'doc/:id'`, `'dashboard'`. */
129
+ readonly path: string;
130
+ /**
131
+ * A chromeless surface owns the whole content area while active — carried through from
132
+ * `SurfaceRoutable.chromeless`: no tab strip, never a tab, excluded from splits, drags and the
133
+ * new-tab picker.
134
+ */
135
+ readonly chromeless?: boolean;
136
+ /**
137
+ * Default title/icon for the tab the host opens when navigation lands on this route without an
138
+ * explicit `openContentTab` (a shared deep-link, browser history, `navigateContent`). A plugin can
139
+ * still refine it via `openContentTab` (e.g. the real document name). Omit and the host falls back
140
+ * to the last path segment.
141
+ */
142
+ readonly title?: string;
143
+ readonly icon?: string;
144
+ /**
145
+ * Facet ordering — carried through from `SurfaceBase.order`: a following surface's permanent facet
146
+ * tab renders lower orders first.
147
+ */
148
+ readonly order?: number;
149
+ /**
150
+ * Whether {@link title} is a **literal** (shown verbatim) rather than a Transloco key. Default `false`
151
+ * (translated). The auto-open fallback (last path segment) is always treated as a literal. Set this
152
+ * `true` when the route's default title is a non-translatable string, to avoid a benign
153
+ * "missing translation" dev warning.
154
+ */
155
+ readonly titleIsLiteral?: boolean;
156
+ /**
157
+ * Nested sub-route segments under this route — the view's own level-2 tabs (e.g. `['code','preview']`),
158
+ * reflected in the URL as real path segments (`doc/:id/code`) so they are shareable and restorable
159
+ *. Angular syntax, so a segment may **carry a value** (`'structure/:structureId'`). There is
160
+ * **no forced default**: the bare tab root is a valid address and the surface decides what it shows
161
+ * there. The route's `path` stays the **tab root**: navigating between sub-routes stays in one tab and
162
+ * preserves the parent component's state. The view reads the active sub from the URL and renders it
163
+ * (a component view needs a `<router-outlet>`; an `iframe` surface is told the active sub over its
164
+ * channel).
165
+ */
166
+ readonly subRoutes?: readonly string[];
167
+ /**
168
+ * Keep this route's permanent tab pointing at the current selection — carried through from
169
+ * {@link SurfaceRoutable.follows}: the host substitutes the parameter values it knows by name into
170
+ * this pattern, truncating before the first it does not know.
171
+ */
172
+ readonly follows?: boolean;
173
+ /**
174
+ * Own everything below {@link path} — carried through from {@link SurfaceRoutable.rest}: the host
175
+ * routes any deeper address to this route and hands the remainder over as the rest (verbatim,
176
+ * including the query string), while {@link path} stays the tab root.
177
+ */
178
+ readonly rest?: boolean;
179
+ /**
180
+ * Declarative auth gating: when the current session does not meet the requirement the host
181
+ * renders a neutral **"sign-in required" placeholder** at this URL instead of the route's surface (the
182
+ * URL is preserved), or — if the distribution provided one via `provideUnauthorizedRedirect` — redirects
183
+ * to the product's login. Reactive: the surface appears once the session qualifies, no reload. A route
184
+ * is reachable or not, so `mode` is ignored. Presentation only — real enforcement is server-side. Omit
185
+ * for a route everyone can reach.
186
+ */
187
+ readonly access?: AccessRequirement;
188
+ /**
189
+ * Retention when this route's surface is hidden — carried through from
190
+ * {@link Surface.retain}. `'always'` keeps the instance alive while hidden; `'never'` forces
191
+ * destruction; omitted falls back to the distribution's retention default (destroy).
192
+ */
193
+ readonly retain?: 'always' | 'never';
194
+ /**
195
+ * Auto-save on hiding — carried through from {@link Surface.saveOn}: a hidden dirty
196
+ * instance's `surfaceSave` is called fire-and-forget.
197
+ */
198
+ readonly saveOn?: 'hide';
199
+ /** Whether the user may close a tab of this route — carried through from {@link Surface.closable}. */
200
+ readonly closable?: boolean;
201
+ /**
202
+ * Whether the host insets this surface from its pane edges (default `true`). Declare `false` for a
203
+ * surface that owns its own edges — a viewer, a canvas, a map, an edge-to-edge table. See
204
+ * `SurfaceBase.padded`.
205
+ */
206
+ readonly padded?: boolean;
207
+ }
208
+ /**
209
+ * A URL-addressed view in the content area. Unlike a {@link View} (which docks into
210
+ * a Panel region and is chrome-local), a content route is reached by its `path`, so it is a shareable
211
+ * deep-link with browser back/forward. The content area has a single `<router-outlet>`, so a route
212
+ * needs no region id. Its surface is either an Angular `component` (trusted only — it cannot cross an RPC
213
+ * boundary) or an `iframe` URL (the form a sandboxed plugin uses; a trusted plugin may also use
214
+ * it to embed a foreign origin) — see {@link ContentSurface}. This is the host's **internal** shape
215
+ * for a routable surface: authors do not build one directly — contribute a {@link Surface} via
216
+ * `ctx.registerSurface` and the host normalises it into this.
217
+ */
218
+ export type ContentRoute = ContentRouteBase & ContentSurface;
219
+ /** Input to `ctx.openContentTab` — opens a titled **dynamic** tab and navigates to it. */
220
+ export interface OpenTabInput {
221
+ /** Concrete path to navigate to, e.g. `'doc/abc'` (not a pattern). */
222
+ readonly path: string;
223
+ /** Human title for the tab (e.g. the document name) — dynamic, not known from the URL. */
224
+ readonly title: string;
225
+ /** Icon name for the tab. */
226
+ readonly icon?: string;
227
+ /**
228
+ * Whether {@link title} is a **literal** (shown verbatim) rather than a Transloco key. Default
229
+ * `false` (the host translates it, preserving key-titled dynamic tabs). Set `true` for an inherently
230
+ * dynamic title — a document name, an entity label — so the host skips the i18n lookup and does not
231
+ * log a benign "missing translation" dev warning (finding #8).
232
+ */
233
+ readonly titleIsLiteral?: boolean;
234
+ /**
235
+ * Runs once when **this** tab is closed (the host's close control, or `ctx.closeContentTab`), giving
236
+ * the weaver a hook to free per-tab state, cancel in-flight work or persist a draft. Not called when
237
+ * the tab is merely deactivated (still open) or when the whole plugin deactivates. Re-opening the same
238
+ * path replaces the handler with the latest one. (The callback itself cannot cross the sandbox RPC
239
+ * boundary and is dropped there — instead the host calls the optional `contentTabClosed(path)` method
240
+ * a sandboxed plugin's entry document may expose on its RPC channel.)
241
+ */
242
+ readonly onClose?: () => void;
243
+ /**
244
+ * Opens this as a **preview tab** (VS-Code "Preview Editors") — a single, reused, *italic*
245
+ * slot per pane for transient browsing: a subsequent `openContentTab({ preview: true })` for
246
+ * a **different** path replaces this tab's content instead of adding a tab, so browsing many items
247
+ * doesn't pile up tabs. Promotion to a permanent tab is **explicit**: double-click the tab, or call
248
+ * `ctx.keepContentTab(path)`. Re-opening the **same** path deliberately does *not* promote it and
249
+ * preserves the tab's current state — a view commonly re-opens itself on mount to refine its title,
250
+ * which would otherwise make every preview tab permanent the moment it renders. Default `false`
251
+ * (a permanent tab). Ignored when the distribution disabled preview (`provideShellFeatures({ content: { preview: false } })`).
252
+ */
253
+ readonly preview?: boolean;
254
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=content-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-route.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/content-route.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ /** Handle to undo a contribution (a plugin keeps it to clean up on deactivate). */
2
+ export interface Disposable {
3
+ dispose(): void;
4
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=contribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contribution.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/contribution.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Handle to an open dialog. A plugin's dialog body injects it to close itself
3
+ * and read its `data`; `open()` returns it so the caller can await the result via `closed`.
4
+ */
5
+ export declare class DialogRef<R = unknown> {
6
+ readonly data: unknown;
7
+ private resolveClosed;
8
+ private settled;
9
+ private readonly maximizedState;
10
+ /**
11
+ * Whether the dialog is currently maximized to near-fullscreen. The host frame reacts (panel
12
+ * size); a bare dialog body reads it to stretch its own chrome.
13
+ */
14
+ readonly maximized: import("@angular/core").Signal<boolean>;
15
+ /** Resolves with the dialog result once it closes (or `undefined` if dismissed). */
16
+ readonly closed: Promise<R | undefined>;
17
+ constructor(data?: unknown);
18
+ /**
19
+ * Toggles near-fullscreen. The host frame shows a maximize/restore control when the dialog was
20
+ * opened with `maximizable`; a bare dialog draws its own control and calls this directly.
21
+ */
22
+ toggleMaximized(): void;
23
+ /** Closes the dialog with an optional result. Idempotent — later calls are ignored. */
24
+ close(result?: R): void;
25
+ }
@@ -0,0 +1,37 @@
1
+ import { signal } from '@angular/core';
2
+ /**
3
+ * Handle to an open dialog. A plugin's dialog body injects it to close itself
4
+ * and read its `data`; `open()` returns it so the caller can await the result via `closed`.
5
+ */
6
+ export class DialogRef {
7
+ constructor(data = undefined) {
8
+ this.data = data;
9
+ this.settled = false;
10
+ this.maximizedState = signal(false);
11
+ /**
12
+ * Whether the dialog is currently maximized to near-fullscreen. The host frame reacts (panel
13
+ * size); a bare dialog body reads it to stretch its own chrome.
14
+ */
15
+ this.maximized = this.maximizedState.asReadonly();
16
+ /** Resolves with the dialog result once it closes (or `undefined` if dismissed). */
17
+ this.closed = new Promise((resolve) => {
18
+ this.resolveClosed = resolve;
19
+ });
20
+ }
21
+ /**
22
+ * Toggles near-fullscreen. The host frame shows a maximize/restore control when the dialog was
23
+ * opened with `maximizable`; a bare dialog draws its own control and calls this directly.
24
+ */
25
+ toggleMaximized() {
26
+ this.maximizedState.update((value) => !value);
27
+ }
28
+ /** Closes the dialog with an optional result. Idempotent — later calls are ignored. */
29
+ close(result) {
30
+ if (this.settled) {
31
+ return;
32
+ }
33
+ this.settled = true;
34
+ this.resolveClosed(result);
35
+ }
36
+ }
37
+ //# sourceMappingURL=dialog-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-ref.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/dialog-ref.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;GAGG;AACH,MAAM,OAAO,SAAS;IAgBpB,YAAqB,OAAgB,SAAS;QAAzB,SAAI,GAAJ,IAAI,CAAqB;QAdtC,YAAO,GAAG,KAAK,CAAC;QACP,mBAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAEhD;;;WAGG;QACM,cAAS,GAAG,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;QAEtD,oFAAoF;QAC3E,WAAM,GAA2B,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAChE,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC/B,CAAC,CAAC,CAAC;IAE8C,CAAC;IAElD;;;OAGG;IACH,eAAe;QACb,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,MAAU;QACd,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,105 @@
1
+ import { LwButtonVariant } from './button.js';
2
+ /**
3
+ * Severity of a dialog. Drives the leading icon + its tint and the confirm
4
+ * button colour in one field, so they always match the theme.
5
+ */
6
+ export type DialogTone = 'default' | 'info' | 'success' | 'warning' | 'danger';
7
+ /** Panel width for a dialog opened with a custom body. Defaults to `md`. */
8
+ export type DialogSize = 'md' | 'lg' | 'xl';
9
+ /**
10
+ * Validated single-input guard for a confirm (e.g. type-to-confirm a destructive action).
11
+ * `validate` is the uniform validation contract: return `null` to **allow**
12
+ * confirming, or a string to **block** it. A non-empty string is shown inline as the reason;
13
+ * an **empty string blocks silently** (e.g. a type-to-confirm whose instruction is in `label`).
14
+ */
15
+ export interface RequireConfirmation {
16
+ /** Label above the input (Markdown), e.g. `Type **Reset** to confirm`. */
17
+ readonly label: string;
18
+ /** `null` = allow; a string = block (non-empty shown inline, empty blocks silently). */
19
+ readonly validate: (value: string) => string | null;
20
+ readonly placeholder?: string;
21
+ }
22
+ /** Options for a yes/no confirm. `message` is Markdown. */
23
+ export interface ConfirmOptions {
24
+ readonly title?: string;
25
+ readonly message: string;
26
+ readonly confirmLabel?: string;
27
+ readonly cancelLabel?: string;
28
+ /** Severity → icon + confirm button colour. Defaults to `default` (primary). */
29
+ readonly tone?: DialogTone;
30
+ /** Override the tone's default leading icon (host icon name). */
31
+ readonly icon?: string;
32
+ /** Require the user to type an exact phrase before confirming (destructive guard). */
33
+ readonly requireConfirmation?: RequireConfirmation;
34
+ }
35
+ /** Options for an alert (single acknowledge button). `message` is Markdown. */
36
+ export interface AlertOptions {
37
+ readonly title?: string;
38
+ readonly message: string;
39
+ readonly okLabel?: string;
40
+ readonly tone?: DialogTone;
41
+ readonly icon?: string;
42
+ }
43
+ /** Options for a text prompt. `message` is Markdown. */
44
+ export interface PromptOptions {
45
+ readonly title?: string;
46
+ readonly message: string;
47
+ readonly initial?: string;
48
+ readonly placeholder?: string;
49
+ readonly confirmLabel?: string;
50
+ readonly cancelLabel?: string;
51
+ readonly tone?: DialogTone;
52
+ readonly icon?: string;
53
+ }
54
+ /** A footer button declared for a custom-body dialog. */
55
+ export interface DialogButton {
56
+ /** Transloco key or literal. */
57
+ readonly label: string;
58
+ readonly variant?: LwButtonVariant;
59
+ /** Value the dialog resolves to when this button is clicked. */
60
+ readonly value?: unknown;
61
+ }
62
+ /** Options for a progress indicator. */
63
+ export interface ProgressOptions {
64
+ readonly title?: string;
65
+ /** Status text shown next to the spinner (updatable via the handle). */
66
+ readonly message: string;
67
+ }
68
+ /** Controls a live progress dialog. */
69
+ export interface ProgressHandle {
70
+ /** Replace the status text. */
71
+ update(message: string): void;
72
+ /** Close the progress dialog. */
73
+ close(): void;
74
+ }
75
+ /** Options for opening a custom body component as a dialog. */
76
+ export interface OpenOptions {
77
+ readonly title?: string;
78
+ readonly data?: unknown;
79
+ readonly buttons?: readonly DialogButton[];
80
+ /** Backdrop click / Escape / close-X dismiss the dialog. Defaults to `true`. */
81
+ readonly dismissable?: boolean;
82
+ readonly tone?: DialogTone;
83
+ /** Host icon name. */
84
+ readonly icon?: string;
85
+ /** Panel width. Defaults to `md`. */
86
+ readonly size?: DialogSize;
87
+ /**
88
+ * The host frame shows a maximize/restore control (near-fullscreen). A bare dialog draws its own
89
+ * control instead and may call `DialogRef.toggleMaximized` regardless of this flag.
90
+ */
91
+ readonly maximizable?: boolean;
92
+ /**
93
+ * Render only the component filling the panel — no host icon/title/close/footer/padding.
94
+ * The component owns its full chrome (e.g. the two-column settings surface). Defaults to `false`.
95
+ */
96
+ readonly bare?: boolean;
97
+ /**
98
+ * Vertical anchor of the panel. `'top'` pins the panel's top edge near the top of the viewport on
99
+ * every width — phones included — so a panel whose height follows its content (a filtering list
100
+ * like the command palette) grows and shrinks downward instead of jumping around the centre, and
101
+ * an on-screen keyboard never covers it. `'center'` (the default) keeps the centred desktop panel
102
+ * and the mobile bottom sheet.
103
+ */
104
+ readonly align?: 'center' | 'top';
105
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/dialog.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Implemented by a surface **component instance** to take part in the retention/close protocol
3
+ *. It lives on the instance, not on the {@link Surface} declaration, because one
4
+ * declaration can back many open tabs (`doc/:id`) and "this tab has unsaved changes" is a question
5
+ * about one instance. The host discovers it structurally — implement the interface and you are in.
6
+ *
7
+ * While {@link surfaceDirty} returns `true` the instance is **never destroyed on hide** (a tab
8
+ * switch, a minimised pane, a collapsed sidebar — no gesture that merely hides is ever blocked or
9
+ * prompted), and **closing asks**: the host shows its own localised "unsaved changes" dialog with
10
+ * *Save* (only when {@link surfaceSave} is implemented) · *Discard* · *Cancel*. Once the instance
11
+ * reports clean again it is released back to the normal retention rule. Two boundaries: a
12
+ * **sandboxed** (`iframe`) surface — which reports dirty by pushing `setDirty(true|false)` over its
13
+ * surface channel — survives gestures that hide it *in place* (tab switch, collapse) but is rebuilt
14
+ * by any gesture that *moves* its element (a split, a drag into another pane, a minimise), because
15
+ * moving an `<iframe>` in the DOM reloads it; and a **pop-out window** closes without the ask — the
16
+ * unsaved-changes protocol guards the main window, a pop-out is a viewer onto the same state.
17
+ */
18
+ export interface DirtySurface {
19
+ /**
20
+ * Veto hook for a **user-initiated close** of this instance (tab ×, `Delete`, close pane, close
21
+ * others/all/to the right, `ctx.closeContentTab`). Return `false` to cancel the close, `true` to
22
+ * let it continue — a surface may draw its own dialog first and resolve the returned promise with
23
+ * the user's answer. It runs **before** the host's unsaved-changes dialog, and a `true` result
24
+ * does not bypass it: an instance that is still dirty afterwards still gets the standard
25
+ * *Save · Discard · Cancel* ask, so approving the close never discards silently.
26
+ *
27
+ * The host enforces a timeout with a guaranteed "close anyway" escape, and a hook that throws or
28
+ * rejects counts as approval — a broken or hung veto can never make a tab unclosable
29
+ *. Programmatic destruction (disabling or uninstalling the plugin, resetting or
30
+ * switching the workspace) does **not** consult this hook; only the unsaved-changes dialog guards
31
+ * those, because a plugin must not be able to veto its own removal.
32
+ */
33
+ surfaceBeforeClose?(): boolean | Promise<boolean>;
34
+ /**
35
+ * Unsaved changes? The host reads this **reactively** — read your own signals inside so the host
36
+ * notices the moment the instance becomes clean (a surface that stays "dirty" forever is a leak).
37
+ * A save in flight keeps this `true` until the save has actually succeeded — that is what makes
38
+ * fire-and-forget saving safe.
39
+ */
40
+ surfaceDirty(): boolean;
41
+ /**
42
+ * Persists the unsaved changes. Optional: without it the host's close dialog offers only
43
+ * *Discard* / *Cancel*. Also the target of the declaration-level `saveOn: 'hide'`
44
+ * ({@link SurfaceBase.saveOn}). A rejection keeps the instance dirty and therefore alive — the
45
+ * host reports the failure, it never discards silently.
46
+ */
47
+ surfaceSave?(): Promise<void>;
48
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dirty-surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dirty-surface.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/dirty-surface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The **serialisable** context an opener passes when it shows a menu — e.g. the tab strip
3
+ * passes `{ targetKind: 'content-tab', tabId, group, pinned, closable }`. Primitives only, so it crosses
4
+ * the sandbox RPC boundary and reaches a command's `run(context)` intact. It is both the payload handed to
5
+ * the invoked command **and** what {@link MenuItem.when} is matched against for visibility.
6
+ */
7
+ export type MenuContext = Readonly<Record<string, string | number | boolean>>;
8
+ /**
9
+ * A contribution to a named menu slot — the menu analogue of a rail/bar/view-action item: it
10
+ * names a {@link menu} slot and points at a {@link command} by id (so it serialises across the sandbox
11
+ * boundary), or carries an inline {@link run} (trusted, in-process only). The host draws the menu; a
12
+ * right-click on host chrome opens the slot with a {@link MenuContext}.
13
+ */
14
+ export interface MenuItem {
15
+ /**
16
+ * Optional stable identity. With an `id`, registering again **replaces** the entry (last-in wins — the
17
+ * same override-by-id rule as every other contribution), and a distribution can drop it via
18
+ * `provideShell({ omit: [id] })`. Built-in host entries use `menu:<commandId>` (e.g.
19
+ * `menu:shell.tab.closeOthers`) — distinct from the command id, so omitting the entry keeps the command
20
+ * itself (palette/shortcut) alive. Without an `id` the item is purely additive.
21
+ */
22
+ readonly id?: string;
23
+ /** Slot id: a host slot (`'content/tab/context'`) or a plugin's own (`'<weaver>.<surface>/context'`). */
24
+ readonly menu: string;
25
+ /** The behaviour: a registered command id (preferred — crosses the RPC boundary), invoked with the context. */
26
+ readonly command?: string;
27
+ /** Inline behaviour (trusted in-process only; a sandboxed plugin uses `command` instead). */
28
+ run?(context?: MenuContext): void;
29
+ /** Label — Transloco key or literal. Defaults to the referenced command's title when omitted. */
30
+ readonly title?: string;
31
+ /** Group id for ordering + separators; groups render in `group` order, items in `order` within a group. */
32
+ readonly group?: string;
33
+ /** Lower renders first (default 0). */
34
+ readonly order?: number;
35
+ /**
36
+ * Coarse visibility filter: the item shows only when **every** key here equals the same key
37
+ * in the opener's {@link MenuContext} (a subset match). Omit to always show. No expression language — a
38
+ * data-only predicate, so it is serialisable and sandbox-safe.
39
+ */
40
+ readonly when?: MenuContext;
41
+ /**
42
+ * Makes this a **checkbox** menu item (`role="menuitemcheckbox"`): present ⇒ the item shows a
43
+ * check indicator, checked exactly when every key here equals the same key in the opener's context (a
44
+ * subset match, like {@link when}). Lets one toggle item replace a pair (e.g. "Pinned ✓" instead of
45
+ * separate Pin/Unpin); the referenced command reads the current state from the context and flips it.
46
+ */
47
+ readonly checkedWhen?: MenuContext;
48
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/menu.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ /** Severity of a notification — drives icon and accent. */
2
+ export type NotificationKind = 'info' | 'success' | 'warning' | 'error';
3
+ /**
4
+ * A single action button on a toast (e.g. "Reload" on an update notice).
5
+ * Trusted in-process runtime only — a sandboxed plugin's `action` does not cross the RPC
6
+ * boundary and is dropped (like `OpenTabInput.onClose`).
7
+ */
8
+ export interface NotificationAction {
9
+ /** Transloco key or literal for the button label. */
10
+ readonly label: string;
11
+ run(): void;
12
+ }
13
+ /** What a caller passes to the host notification service. */
14
+ export interface NotificationInput {
15
+ /** Transloco key or literal for the message. */
16
+ readonly message: string;
17
+ /** Severity; defaults to `info`. */
18
+ readonly kind?: NotificationKind;
19
+ /**
20
+ * Optional single action button. (Trusted in-process runtime only — over the sandbox RPC
21
+ * boundary the action is dropped and the toast shows without a button.)
22
+ */
23
+ readonly action?: NotificationAction;
24
+ /** Auto-dismiss after this many ms. Omit or `0` = sticky until dismissed. */
25
+ readonly timeoutMs?: number;
26
+ /** Stable id — reusing an id replaces the existing toast instead of stacking. */
27
+ readonly id?: string;
28
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=notification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/notification.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The grammar for a **declared** pane arrangement, generic over what one of its tabs addresses.
3
+ *
4
+ * An area either holds `tabs`, splits into `rows` (top to bottom) or splits into `columns` (left to
5
+ * right) — exactly one of the three. It is the same vocabulary wherever a product or a plugin writes
6
+ * an arrangement down, so there is one grammar to learn and one conversion behind it: a distribution
7
+ * instantiates it over **route paths** for a workspace (`WorkspaceArea` in `@loomweaver/shell`), a plugin
8
+ * over **child surface ids** for a container (`ContainerArea`).
9
+ */
10
+ export type PaneArea<T> = PaneTabArea<T> | PaneRowArea<T> | PaneColumnArea<T>;
11
+ export interface PaneAreaBase {
12
+ /**
13
+ * Optional share of the parent split in percent. Unspecified siblings share the remainder evenly;
14
+ * values that do not sum to 100 are normalized proportionally rather than rejected.
15
+ */
16
+ readonly size?: number;
17
+ }
18
+ /** An area that holds tabs. */
19
+ export interface PaneTabArea<T> extends PaneAreaBase {
20
+ readonly tabs: readonly T[];
21
+ }
22
+ /** An area that splits into rows, top to bottom. */
23
+ export interface PaneRowArea<T> extends PaneAreaBase {
24
+ readonly rows: readonly PaneArea<T>[];
25
+ }
26
+ /** An area that splits into columns, left to right. */
27
+ export interface PaneColumnArea<T> extends PaneAreaBase {
28
+ readonly columns: readonly PaneArea<T>[];
29
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pane-area.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pane-area.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/pane-area.ts"],"names":[],"mappings":""}