@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.
- package/README.md +11 -0
- package/package.json +45 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +23 -0
- package/src/index.js.map +1 -0
- package/src/lib/auth.d.ts +60 -0
- package/src/lib/auth.js +35 -0
- package/src/lib/auth.js.map +1 -0
- package/src/lib/bar-item.d.ts +67 -0
- package/src/lib/bar-item.js +2 -0
- package/src/lib/bar-item.js.map +1 -0
- package/src/lib/button.d.ts +4 -0
- package/src/lib/button.js +2 -0
- package/src/lib/button.js.map +1 -0
- package/src/lib/capability.d.ts +38 -0
- package/src/lib/capability.js +32 -0
- package/src/lib/capability.js.map +1 -0
- package/src/lib/command.d.ts +199 -0
- package/src/lib/command.js +2 -0
- package/src/lib/command.js.map +1 -0
- package/src/lib/container-handle.d.ts +36 -0
- package/src/lib/container-handle.js +4 -0
- package/src/lib/container-handle.js.map +1 -0
- package/src/lib/content-route.d.ts +254 -0
- package/src/lib/content-route.js +2 -0
- package/src/lib/content-route.js.map +1 -0
- package/src/lib/contribution.d.ts +4 -0
- package/src/lib/contribution.js +2 -0
- package/src/lib/contribution.js.map +1 -0
- package/src/lib/dialog-ref.d.ts +25 -0
- package/src/lib/dialog-ref.js +37 -0
- package/src/lib/dialog-ref.js.map +1 -0
- package/src/lib/dialog.d.ts +105 -0
- package/src/lib/dialog.js +2 -0
- package/src/lib/dialog.js.map +1 -0
- package/src/lib/dirty-surface.d.ts +48 -0
- package/src/lib/dirty-surface.js +2 -0
- package/src/lib/dirty-surface.js.map +1 -0
- package/src/lib/menu.d.ts +48 -0
- package/src/lib/menu.js +2 -0
- package/src/lib/menu.js.map +1 -0
- package/src/lib/notification.d.ts +28 -0
- package/src/lib/notification.js +2 -0
- package/src/lib/notification.js.map +1 -0
- package/src/lib/pane-area.d.ts +29 -0
- package/src/lib/pane-area.js +2 -0
- package/src/lib/pane-area.js.map +1 -0
- package/src/lib/plugin-state.d.ts +52 -0
- package/src/lib/plugin-state.js +2 -0
- package/src/lib/plugin-state.js.map +1 -0
- package/src/lib/plugin.d.ts +259 -0
- package/src/lib/plugin.js +2 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/product-identity.d.ts +25 -0
- package/src/lib/product-identity.js +17 -0
- package/src/lib/product-identity.js.map +1 -0
- package/src/lib/rail-item.d.ts +61 -0
- package/src/lib/rail-item.js +2 -0
- package/src/lib/rail-item.js.map +1 -0
- package/src/lib/settings-model.d.ts +164 -0
- package/src/lib/settings-model.js +2 -0
- package/src/lib/settings-model.js.map +1 -0
- package/src/lib/surface.d.ts +220 -0
- package/src/lib/surface.js +2 -0
- package/src/lib/surface.js.map +1 -0
- package/src/lib/view-state.d.ts +38 -0
- package/src/lib/view-state.js +9 -0
- package/src/lib/view-state.js.map +1 -0
- package/src/lib/view.d.ts +103 -0
- package/src/lib/view.js +2 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"content-route.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/content-route.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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
|
+
}
|
package/src/lib/menu.js
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"pane-area.js","sourceRoot":"","sources":["../../../../../../libs/core/plugin-sdk/src/lib/pane-area.ts"],"names":[],"mappings":""}
|