@loomweaver/shell 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 +49 -0
- package/fesm2022/loomweaver-shell.mjs +17016 -0
- package/fesm2022/loomweaver-shell.mjs.map +1 -0
- package/i18n/de.json +293 -0
- package/i18n/en.json +293 -0
- package/package.json +68 -0
- package/styles/shell.css +9 -0
- package/styles/theme.css +709 -0
- package/types/loomweaver-shell.d.ts +2280 -0
- package/types/loomweaver-shell.d.ts.map +1 -0
|
@@ -0,0 +1,2280 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, Provider, Signal, EnvironmentProviders, Type, Injector, WritableSignal } from '@angular/core';
|
|
3
|
+
import { LwButtonVariant, LwButtonSize, NotificationKind, NotificationAction, NotificationInput, DialogRef, DialogTone, DialogSize, ConfirmOptions, AlertOptions, PromptOptions, OpenOptions, ProgressOptions, ProgressHandle, BarItem, RailItem, View, ContentRoute, Command, MenuItem, Disposable, SettingsSection, PaneArea, PaneAreaBase, PaneColumnArea, PaneRowArea, PaneTabArea, AuthSnapshot, AccessRequirement, ViewAction, ActiveContent, OpenTabInput, MenuContext, CommandArguments, BarSlot, Plugin, Capability, InvocableCommand, CommandOutcome } from '@loomweaver/plugin-sdk';
|
|
4
|
+
export { AlertOptions, BarButtonItem, BarComponentItem, BarItem, BarSlot, ConfirmOptions, DialogButton, DialogRef, DialogSize, DialogTone, Disposable, LwButtonSize, LwButtonVariant, NotificationAction, NotificationInput, NotificationKind, OpenOptions, Plugin, PluginContext, PluginHost, PluginManifest, PluginSession, PluginUi, ProgressHandle, ProgressOptions, PromptOptions, RailItem, RequireConfirmation, SelectOption, SettingButton, SettingComponent, SettingControl, SettingRow, SettingSelect, SettingSlider, SettingText, SettingToggle, SettingsSection, UiMenuItem, View, ViewAction } from '@loomweaver/plugin-sdk';
|
|
5
|
+
import { Routes } from '@angular/router';
|
|
6
|
+
|
|
7
|
+
/** Where a region docks in the border topology. */
|
|
8
|
+
type DockPosition = 'top' | 'bottom' | 'left' | 'right' | 'center';
|
|
9
|
+
/** Region anatomy types. */
|
|
10
|
+
type RegionType = 'bar' | 'rail' | 'panel' | 'content';
|
|
11
|
+
/** One region the distribution places into the border frame. */
|
|
12
|
+
interface LayoutRegion {
|
|
13
|
+
/** Stable id (slot targeting, collapse state, ordering). */
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly type: RegionType;
|
|
16
|
+
readonly dock: DockPosition;
|
|
17
|
+
}
|
|
18
|
+
/** A distribution's declared base layout — the Core renders it (declarative, Stufe A). */
|
|
19
|
+
interface ShellLayout {
|
|
20
|
+
readonly regions: readonly LayoutRegion[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Bare default: a top Bar (header) over the Content-Area, with a status Bar under it.
|
|
24
|
+
*
|
|
25
|
+
* The status bar is not decoration — the shell's own default contributions include one that docks
|
|
26
|
+
* there, and a default aimed at a region the default layout omits renders nothing and reports
|
|
27
|
+
* nothing. Every region a shell default targets belongs here; `tools/check-region-ids.mjs` fails
|
|
28
|
+
* the build when one does not.
|
|
29
|
+
*/
|
|
30
|
+
declare const DEFAULT_LAYOUT: ShellLayout;
|
|
31
|
+
/** Active layout. Defaults to {@link DEFAULT_LAYOUT}; a distribution overrides it. */
|
|
32
|
+
declare const SHELL_LAYOUT: InjectionToken<ShellLayout>;
|
|
33
|
+
/** A distribution declares its base layout with this provider. */
|
|
34
|
+
declare function provideLayout(layout: ShellLayout): Provider;
|
|
35
|
+
|
|
36
|
+
declare class ViewportService {
|
|
37
|
+
private readonly document;
|
|
38
|
+
readonly compact: Signal<boolean>;
|
|
39
|
+
private watchCompact;
|
|
40
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ViewportService, never>;
|
|
41
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ViewportService>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Opening a surface in its own browser window, and knowing whether this window **is** one.
|
|
46
|
+
*
|
|
47
|
+
* A pop-out boots the same app from a `/popout/…` URL and renders exactly one surface — no rail, no
|
|
48
|
+
* sidebars, no pane tree — so the main window stays the only writer of the layout keys. The mode is
|
|
49
|
+
* read from the location once at startup: a pop-out never becomes a main window, and the shell needs
|
|
50
|
+
* the answer before it renders anything.
|
|
51
|
+
*/
|
|
52
|
+
declare class PopoutService {
|
|
53
|
+
/** Whether this browser window is a pop-out. Fixed for the window's lifetime. */
|
|
54
|
+
readonly active: boolean;
|
|
55
|
+
private readonly document;
|
|
56
|
+
private readonly dialogs;
|
|
57
|
+
private readonly transloco;
|
|
58
|
+
constructor();
|
|
59
|
+
/**
|
|
60
|
+
* Opens `paneTarget` — a `view:<viewId>` descriptor or a content-route path — in a new browser
|
|
61
|
+
* window. The tab it came from **stays** where it is: this duplicates, it does not move. If the
|
|
62
|
+
* pop-up blocker swallows the window, the user gets a dialog whose button is a fresh gesture that
|
|
63
|
+
* practically always gets through.
|
|
64
|
+
*/
|
|
65
|
+
open(paneTarget: string): void;
|
|
66
|
+
private tryOpen;
|
|
67
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PopoutService, never>;
|
|
68
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<PopoutService>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Layout host: renders the declared regions into the border topology. */
|
|
72
|
+
declare class Shell {
|
|
73
|
+
private readonly layout;
|
|
74
|
+
private readonly panels;
|
|
75
|
+
protected readonly viewport: ViewportService;
|
|
76
|
+
protected readonly popout: PopoutService;
|
|
77
|
+
protected readonly topBars: LayoutRegion[];
|
|
78
|
+
protected readonly bottomBars: LayoutRegion[];
|
|
79
|
+
protected readonly leftRails: LayoutRegion[];
|
|
80
|
+
protected readonly leftPanels: LayoutRegion[];
|
|
81
|
+
protected readonly rightPanels: LayoutRegion[];
|
|
82
|
+
protected readonly rightRails: LayoutRegion[];
|
|
83
|
+
protected readonly hasContent: boolean;
|
|
84
|
+
protected readonly leftFloating: i0.Signal<LayoutRegion[]>;
|
|
85
|
+
protected readonly rightFloating: i0.Signal<LayoutRegion[]>;
|
|
86
|
+
protected readonly leftEdgeBorder: i0.Signal<boolean>;
|
|
87
|
+
protected readonly rightEdgeBorder: i0.Signal<boolean>;
|
|
88
|
+
protected readonly leftRailDivider: i0.Signal<boolean>;
|
|
89
|
+
protected readonly rightRailDivider: i0.Signal<boolean>;
|
|
90
|
+
private readonly theme;
|
|
91
|
+
private readonly fontScale;
|
|
92
|
+
protected panelCollapsed(panel: LayoutRegion): boolean;
|
|
93
|
+
protected isOverlayOpen(regionId: string): boolean;
|
|
94
|
+
protected anyOverlayOpen(): boolean;
|
|
95
|
+
protected closeOverlay(): void;
|
|
96
|
+
private floating;
|
|
97
|
+
private railDivider;
|
|
98
|
+
private edgeHasBody;
|
|
99
|
+
private regionsAt;
|
|
100
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<Shell, never>;
|
|
101
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<Shell, "lw-shell", never, {}, {}, never, never, true, never>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type RetentionDefault = 'destroy' | 'retain';
|
|
105
|
+
|
|
106
|
+
/** Options a distribution can pass to {@link provideShell}. */
|
|
107
|
+
interface ShellOptions {
|
|
108
|
+
/**
|
|
109
|
+
* Contribution ids to hide — a distribution drops a default it does not want (e.g.
|
|
110
|
+
* `['shell.language']`). A **lasting** filter, not a one-time delete: an id a plugin
|
|
111
|
+
* registers later stays hidden too. To *replace* a default instead of hiding it, register
|
|
112
|
+
* your own contribution with the same id (last-in wins) and do **not** omit it.
|
|
113
|
+
*
|
|
114
|
+
* Covers **every** contribution kind. Chrome — commands, views, bar items, rail items — is
|
|
115
|
+
* addressed by bare id. The other kinds carry a **prefix**, because ids from different kinds may
|
|
116
|
+
* coincide and there is exactly one shared omit set: an unprefixed omit stays chrome-only and never
|
|
117
|
+
* silently takes a same-named contribution of another kind with it (`shell.language`, for one, is
|
|
118
|
+
* both a top-bar item and a settings row).
|
|
119
|
+
*
|
|
120
|
+
* - `menu:<commandId>` — one menu entry, leaving the command itself (palette/shortcut) alive.
|
|
121
|
+
* - `setting:<id>` — a whole section (`'setting:shell.permissions'`) or a single row
|
|
122
|
+
* (`'setting:shell.textSize'`); a section left without rows disappears.
|
|
123
|
+
* - `route:<surfaceId>` — a routable surface's content route. It leaves the tab strip,
|
|
124
|
+
* the auto-open on deep-link and the pane target picker; its URL renders a neutral "not available
|
|
125
|
+
* here" placeholder rather than falling back to home, so a shared deep-link explains itself.
|
|
126
|
+
* Addressed by the surface **id**, while a route is *overridden* by its `path` (registering your
|
|
127
|
+
* own surface on the same path wins) — two handles, deliberately, for two different operations.
|
|
128
|
+
*/
|
|
129
|
+
readonly omit?: readonly string[];
|
|
130
|
+
/**
|
|
131
|
+
* Whether to register the application's service worker — `true` by default, and inert in dev
|
|
132
|
+
* either way. The shell owns the registration so that the update badge, the update
|
|
133
|
+
* toast and `ctx.host.checkForUpdate()` work in a distribution that only ships the build
|
|
134
|
+
* artefacts; you do **not** add `provideServiceWorker` yourself.
|
|
135
|
+
*
|
|
136
|
+
* Pass `false` when your build emits no `ngsw-worker.js` (no `serviceWorker` option in the build
|
|
137
|
+
* target). Registration would otherwise 404 in production and log a failure. Nothing else
|
|
138
|
+
* changes: `UpdateService` injects `SwUpdate` optionally, so it simply reports
|
|
139
|
+
* {@link UpdateService.enabled} as `false` and never offers an update.
|
|
140
|
+
*
|
|
141
|
+
* `@angular/service-worker` stays a peer dependency regardless — the opt-out removes the
|
|
142
|
+
* registration, not the import.
|
|
143
|
+
*/
|
|
144
|
+
readonly serviceWorker?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* The app-wide retention default for **hidden** surfaces: `'destroy'` (the default) destroys a
|
|
147
|
+
* hidden, clean surface, so state that must survive belongs in `VIEW_STATE`; `'retain'` keeps
|
|
148
|
+
* every hidden instance alive at the price of memory growing with every surface ever shown. A
|
|
149
|
+
* surface's own `retain: 'always' | 'never'` declaration wins over this default. `iframe` and
|
|
150
|
+
* `container` surfaces are always rebuilt regardless.
|
|
151
|
+
*
|
|
152
|
+
* This is a storage policy for the developer, not a capability the user can see, which is why it
|
|
153
|
+
* lives here rather than in `provideShellFeatures`.
|
|
154
|
+
*/
|
|
155
|
+
readonly retention?: RetentionDefault;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Wires the neutral shell host (theme, i18n, icons, error handling) for a
|
|
159
|
+
* distribution. A distribution adds its own router and product identity; the
|
|
160
|
+
* bare platform falls back to the LoomWeaver identity.
|
|
161
|
+
*/
|
|
162
|
+
declare function provideShell(options?: ShellOptions): (Provider | EnvironmentProviders)[];
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Gestures the content area offers on tabs and panes. Each field takes the **affordance and the
|
|
166
|
+
* gesture**: switching one off removes the button, the drag target and the keyboard shortcut alike,
|
|
167
|
+
* so a capability can never come back through a second door.
|
|
168
|
+
*/
|
|
169
|
+
interface ContentFeatures {
|
|
170
|
+
/** Closing a tab: the × affordance, the `Delete` key and the close entries of the tab menu. */
|
|
171
|
+
readonly close: boolean;
|
|
172
|
+
/** Pinning a tab: the menu entry, the pin affordance and the pin step of the double-click cycle. */
|
|
173
|
+
readonly pin: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* The double-click cycle on a tab (preview → keep → pin → unpin). The first step is the one users
|
|
176
|
+
* arrive expecting, and a preview tab says so itself — its tooltip reads "double-click to keep
|
|
177
|
+
* open", which is why the hint is drawn only while this is on. Switch it off and the tooltip drops
|
|
178
|
+
* the promise; every step of the cycle stays reachable from the tab's context menu either way.
|
|
179
|
+
*/
|
|
180
|
+
readonly escalate: boolean;
|
|
181
|
+
/** Dragging a tab into another pane, and onto an empty pane. */
|
|
182
|
+
readonly moveTabs: boolean;
|
|
183
|
+
/** The single reused italic **preview** slot (VS-Code "Preview Editors"). With it off, `openContentTab({ preview: true })` opens a permanent tab. */
|
|
184
|
+
readonly preview: boolean;
|
|
185
|
+
/** The "+" button that opens the new-tab picker. */
|
|
186
|
+
readonly newTab: boolean;
|
|
187
|
+
/** Splitting a pane horizontally: the toolbar button, the left/right drop edges and `mod+\`. */
|
|
188
|
+
readonly splitRight: boolean;
|
|
189
|
+
/** Splitting a pane vertically: the toolbar button and the top/bottom drop edges. */
|
|
190
|
+
readonly splitDown: boolean;
|
|
191
|
+
/** Blowing a pane up to the whole content area. */
|
|
192
|
+
readonly maximize: boolean;
|
|
193
|
+
/** Collapsing a pane into the minimised strip. */
|
|
194
|
+
readonly minimize: boolean;
|
|
195
|
+
/** Reordering tabs within their band, by drag or `Alt+Arrow`. */
|
|
196
|
+
readonly reorderTabs: boolean;
|
|
197
|
+
}
|
|
198
|
+
/** Gestures the side panels offer on their docked views. */
|
|
199
|
+
interface SidebarFeatures {
|
|
200
|
+
/** Collapsing and expanding a panel. The close button of the compact drawer is not affected. */
|
|
201
|
+
readonly collapse: boolean;
|
|
202
|
+
/** Dragging a panel wider or narrower. */
|
|
203
|
+
readonly resize: boolean;
|
|
204
|
+
/** Reordering view tabs **within** one panel, by drag or `Alt+Arrow`. */
|
|
205
|
+
readonly reorderViews: boolean;
|
|
206
|
+
/** Moving a view to the other sidebar: the menu entry, the drag and `Alt+Shift+Arrow`. */
|
|
207
|
+
readonly moveViews: boolean;
|
|
208
|
+
/** Hiding a view from this workspace (menu entry). */
|
|
209
|
+
readonly hideViews: boolean;
|
|
210
|
+
/** The checklist on the icon strip that says which views live here. */
|
|
211
|
+
readonly curate: boolean;
|
|
212
|
+
/** Stacking a second view below the first: the menu entry and the drop edges of a panel. */
|
|
213
|
+
readonly stackViews: boolean;
|
|
214
|
+
/** Parking a foreign tab in a sidebar, by dragging it onto the strip or a drop edge. */
|
|
215
|
+
readonly acceptTabs: boolean;
|
|
216
|
+
/** Opening a docked view in the content area (menu entry). */
|
|
217
|
+
readonly openViewInContent: boolean;
|
|
218
|
+
/** Resetting the stored state of a view (menu entry). */
|
|
219
|
+
readonly resetViewState: boolean;
|
|
220
|
+
/** The named-instance switcher in the panel header, where a surface declares `instanceable`. */
|
|
221
|
+
readonly instances: boolean;
|
|
222
|
+
}
|
|
223
|
+
/** Gestures the rail offers on its items. */
|
|
224
|
+
interface RailFeatures {
|
|
225
|
+
/** Reordering rail items **within** their band, by drag or `Alt+Arrow`. */
|
|
226
|
+
readonly reorder: boolean;
|
|
227
|
+
/** Moving an item to the other rail: the menu entry, the drag and `Alt+Shift+Arrow`. */
|
|
228
|
+
readonly moveItems: boolean;
|
|
229
|
+
/** Hiding a rail item (menu entry). */
|
|
230
|
+
readonly hideItems: boolean;
|
|
231
|
+
/** The checklist on empty rail space that says which items live here. */
|
|
232
|
+
readonly curate: boolean;
|
|
233
|
+
}
|
|
234
|
+
/** Whether named workspaces exist for this product at all. */
|
|
235
|
+
interface WorkspaceFeatures {
|
|
236
|
+
/**
|
|
237
|
+
* The workspace machinery the shell offers on its own: the manage and reset commands and the
|
|
238
|
+
* workspace entries in the rail. Switching it off leaves the storage scoping untouched — the
|
|
239
|
+
* active workspace still names the layout keys, the user just never meets the concept.
|
|
240
|
+
*/
|
|
241
|
+
readonly enabled: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Whether the user may put a workspace **they saved** in the rail. A saved workspace is never
|
|
244
|
+
* there by itself — the user places it from *Customize activity bar* — and switching this off
|
|
245
|
+
* withdraws that offer, so the rail holds what your product put there and nothing else. Their
|
|
246
|
+
* placements are kept rather than erased, so switching it back on restores what each user had.
|
|
247
|
+
*
|
|
248
|
+
* Everything else about a saved workspace is untouched: saving, renaming, resetting and switching
|
|
249
|
+
* all work, with the workspace dialog as the way to them. This says nothing about **whether** a
|
|
250
|
+
* user may save workspaces, which is a different question and has no switch.
|
|
251
|
+
*/
|
|
252
|
+
readonly savedInRail: boolean;
|
|
253
|
+
}
|
|
254
|
+
/** Whether a tab or a view may be torn off into a browser window of its own. */
|
|
255
|
+
interface WindowFeatures {
|
|
256
|
+
/** "Open in new window" on a tab and on a docked view. */
|
|
257
|
+
readonly popout: boolean;
|
|
258
|
+
}
|
|
259
|
+
/** How commands reach the user beyond the buttons that name them. */
|
|
260
|
+
interface CommandFeatures {
|
|
261
|
+
/**
|
|
262
|
+
* Keyboard shortcuts at all: the global listener that turns a chord into a command, and the
|
|
263
|
+
* chord hints the shell prints beside menu entries, palette rows and bar buttons. With it off a
|
|
264
|
+
* command is still reachable by its button and by the palette, and no hint promises a key that
|
|
265
|
+
* does nothing.
|
|
266
|
+
*/
|
|
267
|
+
readonly shortcuts: boolean;
|
|
268
|
+
/** The "recently used" section at the top of the command palette (and the list behind it). */
|
|
269
|
+
readonly recentlyUsed: boolean;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Which shell capabilities a distribution offers its users. The platform ships the full workbench —
|
|
273
|
+
* every capability here is on by default — and a product that would overwhelm its users switches
|
|
274
|
+
* parts off.
|
|
275
|
+
*
|
|
276
|
+
* This is the home for **gestures**. A *contribution* (a command, a bar or rail item, a settings row,
|
|
277
|
+
* a menu entry) is not a gesture and is removed with `provideShell({ omit })` instead. Where a
|
|
278
|
+
* capability has both — a menu entry *and* a drag — the feature switch wins and takes the entry with
|
|
279
|
+
* it, while `omit` stays the finer tool for "entry gone, gesture stays".
|
|
280
|
+
*/
|
|
281
|
+
interface ShellFeatures {
|
|
282
|
+
readonly content: ContentFeatures;
|
|
283
|
+
readonly sidebar: SidebarFeatures;
|
|
284
|
+
readonly rail: RailFeatures;
|
|
285
|
+
readonly workspaces: WorkspaceFeatures;
|
|
286
|
+
readonly windows: WindowFeatures;
|
|
287
|
+
readonly commands: CommandFeatures;
|
|
288
|
+
}
|
|
289
|
+
/** The default workbench: every capability on. */
|
|
290
|
+
declare const DEFAULT_SHELL_FEATURES: ShellFeatures;
|
|
291
|
+
declare const SHELL_FEATURES: InjectionToken<ShellFeatures>;
|
|
292
|
+
/** A partial override: name only what you switch off, group by group. */
|
|
293
|
+
type ShellFeaturesInput = {
|
|
294
|
+
readonly [Group in keyof ShellFeatures]?: Partial<ShellFeatures[Group]>;
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* Switches shell capabilities off for this distribution, e.g.
|
|
298
|
+
* `provideShellFeatures({ content: { splitDown: false, maximize: false } })`. Omit for the full
|
|
299
|
+
* workbench; fields merge group by group, so a partial override leaves the rest of that group alone.
|
|
300
|
+
*/
|
|
301
|
+
declare function provideShellFeatures(features: ShellFeaturesInput): EnvironmentProviders;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Extra translation namespaces a distribution composes on top of the host base. Each is served at `/i18n/<name>/<lang>.json` and nested under its key, so its strings
|
|
305
|
+
* live at `name.*` and can never collide with a host key. Used for a weaver's own strings
|
|
306
|
+
* (`demo`) and for distribution branding (`product`).
|
|
307
|
+
*/
|
|
308
|
+
declare const TRANSLATION_NAMESPACES: InjectionToken<readonly string[]>;
|
|
309
|
+
/**
|
|
310
|
+
* A distribution declares which namespaced translation bundles to load — the weavers it bundles
|
|
311
|
+
* (e.g. `'testbed'`) and its own branding (`'product'`). The bare platform registers none;
|
|
312
|
+
* its host keys are the whole story.
|
|
313
|
+
*/
|
|
314
|
+
declare function provideTranslationNamespaces(...namespaces: string[]): Provider;
|
|
315
|
+
/** Directory the distribution serves its overlay bundles from, without a trailing slash. */
|
|
316
|
+
declare const TRANSLATION_OVERRIDES: InjectionToken<string>;
|
|
317
|
+
/**
|
|
318
|
+
* Load `<basePath>/<lang>.json` and merge it over everything else **key by key**, so a product
|
|
319
|
+
* can reword the shell in its own house language ("Save as" rather than "Save as new") without
|
|
320
|
+
* forking our bundle: name only the keys you change and inherit the rest, including every key a
|
|
321
|
+
* later release adds. It is applied last, so it also reaches a bundled weaver's strings.
|
|
322
|
+
*
|
|
323
|
+
* Namespaces (`provideTranslationNamespaces`) remain the way to *add* your own strings, and they can
|
|
324
|
+
* still never collide with a host key. This is the opposite job — replacing one — which is why it is
|
|
325
|
+
* a separate, deliberate opt-in rather than a namespace with a magic name.
|
|
326
|
+
*
|
|
327
|
+
* `basePath` lets one build carry several wordings and pick one while composing — a white-label
|
|
328
|
+
* distribution that serves three brands from the same bundle, or a demo that switches product.
|
|
329
|
+
* A product with a backend does not need it: the default path is same-origin, so its server can
|
|
330
|
+
* already vary the bytes per tenant. Omit it and nothing changes.
|
|
331
|
+
*
|
|
332
|
+
* A language with no overlay file keeps the shipped strings (dev-warned). A key the overlay names but
|
|
333
|
+
* nothing ships is dev-warned too, since a typo there would otherwise be a string that never appears.
|
|
334
|
+
*/
|
|
335
|
+
declare function provideTranslationOverrides(basePath?: string): Provider;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The neutral host button primitive. A directive on a real `<button>`/`<a>`, so it
|
|
339
|
+
* keeps native semantics (type, disabled, focus, aria) and its own content — consumers just add
|
|
340
|
+
* `lwButton` and pick a variant.
|
|
341
|
+
*
|
|
342
|
+
* The button LOOK is a CSS-class contract (`.lw-btn` in theme.css): this directive is
|
|
343
|
+
* only a thin wrapper that emits those class names from typed inputs. So the very same look is
|
|
344
|
+
* reachable by SDK-only plugins that cannot import this directive across the Nx boundary — they
|
|
345
|
+
* write `<button class="lw-btn lw-btn--primary">`. One source of truth, all on semantic tokens,
|
|
346
|
+
* so a theme / tenant override reaches host and plugin buttons alike.
|
|
347
|
+
*
|
|
348
|
+
* <button lwButton variant="primary" (click)="save()">Speichern</button>
|
|
349
|
+
* <button lwButton variant="ghost" size="sm" iconOnly aria-label="…"><lw-icon … /></button>
|
|
350
|
+
*/
|
|
351
|
+
declare class LwButton {
|
|
352
|
+
/** Visual weight; defaults to `default`. */
|
|
353
|
+
readonly variant: i0.InputSignal<LwButtonVariant>;
|
|
354
|
+
/** Size; defaults to `md`. */
|
|
355
|
+
readonly size: i0.InputSignal<LwButtonSize>;
|
|
356
|
+
/** Square, equal-padding button for a single icon (needs its own `aria-label`). */
|
|
357
|
+
readonly iconOnly: i0.InputSignalWithTransform<boolean, unknown>;
|
|
358
|
+
protected readonly classes: i0.Signal<string>;
|
|
359
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LwButton, never>;
|
|
360
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<LwButton, "button[lwButton], a[lwButton]", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "iconOnly": { "alias": "iconOnly"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** The custom-element tag. */
|
|
364
|
+
declare const LW_BUTTON_TAG = "lw-button";
|
|
365
|
+
/**
|
|
366
|
+
* `<lw-button variant="primary">Save</lw-button>` — the host button primitive as a framework-agnostic
|
|
367
|
+
* custom element (like `<lw-tooltip>`/`<lw-select>`/`<lw-menu>`): light DOM (its content stays
|
|
368
|
+
* in place), a `role="button"` element that emits the same **`.lw-btn` CSS-class contract**
|
|
369
|
+
* (`theme.css`) the {@link LwButton} directive does — one source of truth for the look, on semantic tokens.
|
|
370
|
+
*
|
|
371
|
+
* The Angular {@link LwButton} directive stays the idiomatic choice for host chrome (it sits on a **native**
|
|
372
|
+
* `<button>`/`<a>`, keeping native semantics). This element is the framework-agnostic path for a weaver body
|
|
373
|
+
* or a sandboxed iframe that cannot import the directive across the Nx boundary. It adds keyboard activation
|
|
374
|
+
* (Enter/Space) and `disabled` so a `role="button"` behaves like a button.
|
|
375
|
+
*
|
|
376
|
+
* <lw-button variant="primary" (click)="save()">Speichern</lw-button>
|
|
377
|
+
* <lw-button variant="ghost" size="sm" icon-only aria-label="…"><!-- icon --></lw-button>
|
|
378
|
+
*/
|
|
379
|
+
declare class LwButtonElement extends HTMLElement {
|
|
380
|
+
static readonly observedAttributes: string[];
|
|
381
|
+
get variant(): LwButtonVariant;
|
|
382
|
+
set variant(value: LwButtonVariant | null);
|
|
383
|
+
get size(): LwButtonSize;
|
|
384
|
+
set size(value: LwButtonSize | null);
|
|
385
|
+
get iconOnly(): boolean;
|
|
386
|
+
set iconOnly(value: boolean);
|
|
387
|
+
get disabled(): boolean;
|
|
388
|
+
set disabled(value: boolean);
|
|
389
|
+
connectedCallback(): void;
|
|
390
|
+
disconnectedCallback(): void;
|
|
391
|
+
attributeChangedCallback(): void;
|
|
392
|
+
private readonly onKeydown;
|
|
393
|
+
private render;
|
|
394
|
+
}
|
|
395
|
+
/** Registers `<lw-button>` once (idempotent) — called from {@link provideShell} at bootstrap. */
|
|
396
|
+
declare function defineLwButton(): void;
|
|
397
|
+
|
|
398
|
+
/** The custom-element tag. */
|
|
399
|
+
declare const LW_MARKDOWN_TAG = "lw-markdown";
|
|
400
|
+
/**
|
|
401
|
+
* `<lw-markdown source="…">` — the host rich-text primitive as a framework-agnostic custom element
|
|
402
|
+
* (like `<lw-tooltip>`/`<lw-select>`/`<lw-menu>`): a plain `HTMLElement`, **light DOM**, so the
|
|
403
|
+
* `prose-lw` Tailwind-Typography classes + `--lw-*` tokens cascade in (auto dark-mode). It does
|
|
404
|
+
* no formatting logic of its own — `marked` parses, **DOMPurify** sanitizes (scripts / `on*` / `javascript:`
|
|
405
|
+
* stripped; the element renders raw `innerHTML`, so this is the security seam, like the icon registry).
|
|
406
|
+
* Reusable anywhere rich text is needed (dialog bodies, About, weaver content), and — unlike the old
|
|
407
|
+
* Angular component — usable in a weaver body by tag, no `@loomweaver/shell` import.
|
|
408
|
+
*
|
|
409
|
+
* <lw-markdown [source]="'Delete **' + name + '**? This cannot be undone.'"></lw-markdown>
|
|
410
|
+
*/
|
|
411
|
+
declare class LwMarkdownElement extends HTMLElement {
|
|
412
|
+
static readonly observedAttributes: string[];
|
|
413
|
+
private prose?;
|
|
414
|
+
get source(): string;
|
|
415
|
+
set source(value: string | null);
|
|
416
|
+
connectedCallback(): void;
|
|
417
|
+
attributeChangedCallback(): void;
|
|
418
|
+
private render;
|
|
419
|
+
}
|
|
420
|
+
/** Registers `<lw-markdown>` once (idempotent) — called from {@link provideShell} at bootstrap. */
|
|
421
|
+
declare function defineLwMarkdown(): void;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* A small indeterminate spinner. Pure CSS ring on `currentColor`
|
|
425
|
+
* (brand by default), sized via the `size` input. Used by the progress dialog and anywhere a
|
|
426
|
+
* busy indicator is needed.
|
|
427
|
+
*
|
|
428
|
+
* <lw-spinner />
|
|
429
|
+
* <lw-spinner size="1rem" [label]="'…' | transloco" />
|
|
430
|
+
*/
|
|
431
|
+
declare class LwSpinner {
|
|
432
|
+
/** CSS size of the spinner (width = height). */
|
|
433
|
+
readonly size: i0.InputSignal<string>;
|
|
434
|
+
/** Accessible label announced to screen readers — pass a translated string; empty = none. */
|
|
435
|
+
readonly label: i0.InputSignal<string>;
|
|
436
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LwSpinner, never>;
|
|
437
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LwSpinner, "lw-spinner", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Tooltip placement relative to the trigger — the element positions the bubble in JS accordingly. */
|
|
441
|
+
type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
|
|
442
|
+
/** The custom-element tag. Authored as the last child of a `position: relative` trigger. */
|
|
443
|
+
declare const LW_TOOLTIP_TAG = "lw-tooltip";
|
|
444
|
+
/**
|
|
445
|
+
* `<lw-tooltip>` — the host tooltip primitive as a **framework-agnostic custom element**.
|
|
446
|
+
* It is a plain `HTMLElement` (no framework runtime), so it crosses the Nx boundary as a *tag* (an
|
|
447
|
+
* SDK-only weaver uses `<lw-tooltip text="…" position="bottom">` without importing `@loomweaver/shell`) and,
|
|
448
|
+
* later, an iframe/sandbox boundary once the element script is served alongside `theme.css`.
|
|
449
|
+
*
|
|
450
|
+
* The element renders the bubble and drives it entirely in JS — reveal, hide, and **positioning**. The
|
|
451
|
+
* bubble is a **Popover** in the browser top layer, so it is never clipped by a `transform`/`overflow`
|
|
452
|
+
* ancestor (a virtual-scroll row) nor lost under a region's z-order (#13). Its `left`/`top` are computed
|
|
453
|
+
* in JS (no CSS anchor positioning — shipping Safari does not support it): a **pointer** reveal anchors to
|
|
454
|
+
* the **cursor** (native-`title`-like, so a wide trigger's tooltip appears where the mouse is, not at the
|
|
455
|
+
* element's far edge); a **focus** reveal anchors to the trigger element per `position`. Reveal is off the
|
|
456
|
+
* trigger's hover/focus (mouse only — touch never reveals). Its **look** lives in `theme.css`
|
|
457
|
+
* (`.lw-tooltip-bubble`, tokened) like `.lw-btn`. Place it as the last child of a positioned trigger:
|
|
458
|
+
*
|
|
459
|
+
* <button class="relative …" [attr.aria-label]="'key' | transloco">
|
|
460
|
+
* <lw-icon name="…" />
|
|
461
|
+
* <lw-tooltip text="…" position="bottom"></lw-tooltip>
|
|
462
|
+
* </button>
|
|
463
|
+
*/
|
|
464
|
+
declare class LwTooltipElement extends HTMLElement {
|
|
465
|
+
static readonly observedAttributes: string[];
|
|
466
|
+
private bubble?;
|
|
467
|
+
private trigger?;
|
|
468
|
+
private showTimer?;
|
|
469
|
+
private pointer?;
|
|
470
|
+
get text(): string | null;
|
|
471
|
+
set text(value: string | null);
|
|
472
|
+
get position(): TooltipPosition;
|
|
473
|
+
set position(value: TooltipPosition | null);
|
|
474
|
+
connectedCallback(): void;
|
|
475
|
+
disconnectedCallback(): void;
|
|
476
|
+
attributeChangedCallback(): void;
|
|
477
|
+
private readonly onEnter;
|
|
478
|
+
private readonly onMove;
|
|
479
|
+
private readonly onLeave;
|
|
480
|
+
private readonly onFocusIn;
|
|
481
|
+
private readonly onFocusOut;
|
|
482
|
+
private render;
|
|
483
|
+
private createBubble;
|
|
484
|
+
private scheduleShow;
|
|
485
|
+
private delayMs;
|
|
486
|
+
private show;
|
|
487
|
+
private placeBubble;
|
|
488
|
+
private hide;
|
|
489
|
+
private clearTimer;
|
|
490
|
+
}
|
|
491
|
+
/** Registers `<lw-tooltip>` once (idempotent) — called from {@link provideShell} at bootstrap. */
|
|
492
|
+
declare function defineLwTooltip(): void;
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Displays the running build's version, e.g. `v0.1.0`. A neutral host-offered widget
|
|
496
|
+
* any distribution or plugin can embed (status bar, about dialog, …); the number comes
|
|
497
|
+
* from {@link VersionService}, never hardcoded.
|
|
498
|
+
*
|
|
499
|
+
* <lw-version /> → v0.1.0
|
|
500
|
+
* <lw-version prefix="" /> → 0.1.0
|
|
501
|
+
*/
|
|
502
|
+
declare class LwVersion {
|
|
503
|
+
private readonly versions;
|
|
504
|
+
/** Text shown before the number; set to `''` to hide. */
|
|
505
|
+
readonly prefix: i0.InputSignal<string>;
|
|
506
|
+
protected readonly label: i0.Signal<string>;
|
|
507
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LwVersion, never>;
|
|
508
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LwVersion, "lw-version", never, { "prefix": { "alias": "prefix"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
declare const LOOM_ICONS: {
|
|
512
|
+
readonly themeLight: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z\"></path></svg>";
|
|
513
|
+
readonly themeDark: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z\"></path></svg>";
|
|
514
|
+
readonly themeSystem: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25\"></path></svg>";
|
|
515
|
+
readonly chevronsLeft: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5\"></path></svg>";
|
|
516
|
+
readonly chevronsRight: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5\"></path></svg>";
|
|
517
|
+
readonly chevronDown: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m19.5 8.25-7.5 7.5-7.5-7.5\"></path></svg>";
|
|
518
|
+
readonly check: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m4.5 12.75 6 6 9-13.5\"></path></svg>";
|
|
519
|
+
readonly close: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\"></path></svg>";
|
|
520
|
+
readonly pin: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 4h8M10.5 4v7M13.5 4v7M7 11h10M12 11v9\"/></svg>";
|
|
521
|
+
readonly save: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M17 3.75H5.25a1.5 1.5 0 0 0-1.5 1.5v13.5a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5V7l-3.25-3.25Z\"/><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 3.75v4.5h6.5v-4.5M7.75 20.25v-7h8.5v7\"/></svg>";
|
|
522
|
+
readonly splitPanes: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4 5.25h16a.75.75 0 0 1 .75.75v12a.75.75 0 0 1-.75.75H4a.75.75 0 0 1-.75-.75V6a.75.75 0 0 1 .75-.75ZM12 5.25v13.5\"/></svg>";
|
|
523
|
+
readonly splitPanesDown: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4 5.25h16a.75.75 0 0 1 .75.75v12a.75.75 0 0 1-.75.75H4a.75.75 0 0 1-.75-.75V6a.75.75 0 0 1 .75-.75ZM3.25 12h17.5\"/></svg>";
|
|
524
|
+
readonly workspaces: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 0 1-1.125-1.125v-3.75ZM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-8.25ZM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-2.25Z\"></path></svg>";
|
|
525
|
+
readonly menu: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\"></path></svg>";
|
|
526
|
+
readonly document: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z\"></path></svg>";
|
|
527
|
+
readonly navigator: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\"></path></svg>";
|
|
528
|
+
readonly outline: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z\"></path></svg>";
|
|
529
|
+
readonly add: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\"></path></svg>";
|
|
530
|
+
readonly edit: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10\"></path></svg>";
|
|
531
|
+
readonly trash: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0\"></path></svg>";
|
|
532
|
+
readonly preview: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z\"></path><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z\"></path></svg>";
|
|
533
|
+
readonly sort: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 7.5 7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5\"></path></svg>";
|
|
534
|
+
readonly reset: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99\"></path></svg>";
|
|
535
|
+
readonly undo: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3\"></path></svg>";
|
|
536
|
+
readonly info: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z\"></path></svg>";
|
|
537
|
+
readonly lock: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z\"></path></svg>";
|
|
538
|
+
readonly success: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m4.5 12.75 6 6 9-13.5\"></path></svg>";
|
|
539
|
+
readonly warning: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z\"></path></svg>";
|
|
540
|
+
readonly error: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z\"></path></svg>";
|
|
541
|
+
readonly settings: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z\"></path><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z\"></path></svg>";
|
|
542
|
+
readonly help: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z\"></path></svg>";
|
|
543
|
+
readonly search: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\"></path></svg>";
|
|
544
|
+
readonly plugin: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 0 1-.657.643 48.39 48.39 0 0 1-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 0 1-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 0 0-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 0 1-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 0 0 .657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 0 1-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 0 0 5.427-.63 48.05 48.05 0 0 0 .582-4.717.532.532 0 0 0-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.96.401v0a.656.656 0 0 0 .658-.663 48.422 48.422 0 0 0-.37-5.36c-1.886.342-3.81.574-5.766.689a.578.578 0 0 1-.61-.58v0Z\"></path></svg>";
|
|
545
|
+
readonly download: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3\"></path></svg>";
|
|
546
|
+
readonly maximize: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15\"></path></svg>";
|
|
547
|
+
readonly popout: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\"></path></svg>";
|
|
548
|
+
readonly restore: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 9V4.5M9 9H4.5M9 9 3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5 5.25 5.25\"></path></svg>";
|
|
549
|
+
readonly minimize: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 12h14\"></path></svg>";
|
|
550
|
+
};
|
|
551
|
+
/** The names the shell ships. Naming one in `provideIcons` replaces that glyph app-wide. */
|
|
552
|
+
type LoomIconName = keyof typeof LOOM_ICONS;
|
|
553
|
+
|
|
554
|
+
/** A live notification held by the service and rendered by the toast outlet. */
|
|
555
|
+
interface Notification {
|
|
556
|
+
readonly id: string;
|
|
557
|
+
readonly kind: NotificationKind;
|
|
558
|
+
readonly message: string;
|
|
559
|
+
readonly action?: NotificationAction;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Neutral host service for transient notifications ("toasts"). The shell
|
|
563
|
+
* renders them once via the toast outlet; distributions and plugins raise them through
|
|
564
|
+
* `ctx`. Sticky toasts (no `timeoutMs`) stay until dismissed — the update notice uses
|
|
565
|
+
* this so dismissing it never loses the "update available" state.
|
|
566
|
+
*/
|
|
567
|
+
declare class NotificationService {
|
|
568
|
+
private readonly items;
|
|
569
|
+
private readonly timers;
|
|
570
|
+
private nextId;
|
|
571
|
+
/** The currently visible notifications, oldest first. */
|
|
572
|
+
readonly notifications: i0.Signal<readonly Notification[]>;
|
|
573
|
+
/** Shows a notification (replacing any with the same id) and returns its id. */
|
|
574
|
+
show(input: NotificationInput): string;
|
|
575
|
+
/** Removes the notification with the given id (no-op if already gone). */
|
|
576
|
+
dismiss(id: string): void;
|
|
577
|
+
private clearTimer;
|
|
578
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NotificationService, never>;
|
|
579
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NotificationService>;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Renders the active notifications as toasts in a fixed corner. Mounted once
|
|
584
|
+
* by the shell root, so every distribution gets it for free. Each toast shows its kind
|
|
585
|
+
* icon, message, an optional action button and a dismiss control. The kind selects the
|
|
586
|
+
* icon; a feedback-colour token ladder can be added later if toasts grow richer.
|
|
587
|
+
*/
|
|
588
|
+
declare class ToastOutlet {
|
|
589
|
+
private readonly service;
|
|
590
|
+
protected readonly notifications: i0.Signal<readonly Notification[]>;
|
|
591
|
+
private readonly icons;
|
|
592
|
+
private readonly iconColors;
|
|
593
|
+
protected iconFor(kind: NotificationKind): LoomIconName;
|
|
594
|
+
protected iconColorFor(kind: NotificationKind): string;
|
|
595
|
+
protected roleFor(kind: NotificationKind): 'alert' | 'status';
|
|
596
|
+
protected runAction(toast: Notification): void;
|
|
597
|
+
protected dismiss(id: string): void;
|
|
598
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ToastOutlet, never>;
|
|
599
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ToastOutlet, "lw-toasts", never, {}, {}, never, never, true, never>;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Persistent update affordance for a bar. While no update is pending it is a
|
|
604
|
+
* quiet "check for updates" button; once the service worker has a new version ready it
|
|
605
|
+
* gains a badge dot and becomes "reload to update". When installing an update failed it gains a
|
|
606
|
+
* caution dot and offers a reload instead of pretending to be current; when the worker's cache is
|
|
607
|
+
* broken outright it says so and offers the repair. Reads {@link UpdateService}, so the badge
|
|
608
|
+
* survives dismissing the toast. Renders nothing when updates are unavailable
|
|
609
|
+
* (no service worker — dev/unsupported).
|
|
610
|
+
*/
|
|
611
|
+
declare class UpdateBadge {
|
|
612
|
+
private readonly updates;
|
|
613
|
+
protected readonly enabled: boolean;
|
|
614
|
+
protected readonly available: i0.Signal<boolean>;
|
|
615
|
+
protected readonly failed: i0.Signal<boolean>;
|
|
616
|
+
protected readonly label: i0.Signal<"update.available" | "update.broken" | "update.failed" | "update.check">;
|
|
617
|
+
protected onClick(): void;
|
|
618
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<UpdateBadge, never>;
|
|
619
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<UpdateBadge, "lw-update-badge", never, {}, {}, never, never, true, never>;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
type DialogKind = 'confirm' | 'alert' | 'prompt' | 'custom' | 'progress';
|
|
623
|
+
type ButtonRole = 'confirm' | 'cancel' | 'custom';
|
|
624
|
+
/** A footer button as the dialog outlet renders it. */
|
|
625
|
+
interface DialogButtonView {
|
|
626
|
+
readonly label: string;
|
|
627
|
+
readonly variant: LwButtonVariant;
|
|
628
|
+
readonly role: ButtonRole;
|
|
629
|
+
readonly value?: unknown;
|
|
630
|
+
readonly autofocus: boolean;
|
|
631
|
+
}
|
|
632
|
+
/** One open dialog, held by the service and rendered by the dialog outlet. */
|
|
633
|
+
interface DialogInstance {
|
|
634
|
+
readonly id: string;
|
|
635
|
+
readonly ref: DialogRef;
|
|
636
|
+
readonly kind: DialogKind;
|
|
637
|
+
readonly tone: DialogTone;
|
|
638
|
+
/** Host icon name (from options or the tone default); rendered by `<lw-icon>`. */
|
|
639
|
+
readonly icon?: string;
|
|
640
|
+
readonly title?: string;
|
|
641
|
+
readonly message?: string;
|
|
642
|
+
readonly component?: Type<unknown>;
|
|
643
|
+
readonly injector?: Injector;
|
|
644
|
+
readonly placeholder?: string;
|
|
645
|
+
readonly promptValue?: WritableSignal<string>;
|
|
646
|
+
/** Live status text for a `progress` dialog. */
|
|
647
|
+
readonly progressMessage?: WritableSignal<string>;
|
|
648
|
+
/** Type-to-confirm label (Markdown) shown above the input, if any. */
|
|
649
|
+
readonly requireLabel?: string;
|
|
650
|
+
/** Validates the guard input; `null` = valid (enables confirm), else an inline error. */
|
|
651
|
+
readonly requireValidate?: (value: string) => string | null;
|
|
652
|
+
readonly buttons: readonly DialogButtonView[];
|
|
653
|
+
readonly dismissable: boolean;
|
|
654
|
+
readonly size?: DialogSize;
|
|
655
|
+
/** Render only the component (no host chrome) — the component owns the frame contents. */
|
|
656
|
+
readonly bare?: boolean;
|
|
657
|
+
/** The frame shows a maximize/restore control (framed dialogs; bare ones draw their own). */
|
|
658
|
+
readonly maximizable?: boolean;
|
|
659
|
+
/** Vertical anchor: `'top'` pins the panel's top edge on every width. Defaults to `'center'`. */
|
|
660
|
+
readonly align?: 'center' | 'top';
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Neutral host service for modal dialogs:
|
|
664
|
+
* convenience dialogs (`confirm`/`alert`/`prompt`, the host paints everything) and rich
|
|
665
|
+
* `open(Component)` dialogs where a plugin owns the body and the host paints the frame +
|
|
666
|
+
* declarative footer buttons. The shell renders them once via `<lw-dialog-outlet>`. Uses
|
|
667
|
+
* only `<lw-*>` chrome + semantic tokens, so plugin-authored bodies stay harmonious.
|
|
668
|
+
*/
|
|
669
|
+
declare class DialogService {
|
|
670
|
+
private readonly injector;
|
|
671
|
+
private readonly document;
|
|
672
|
+
private readonly items;
|
|
673
|
+
private counter;
|
|
674
|
+
/** The currently open dialogs, oldest first (the last one is topmost). */
|
|
675
|
+
readonly dialogs: i0.Signal<readonly DialogInstance[]>;
|
|
676
|
+
/** Asks a yes/no question; resolves `true` only if confirmed. */
|
|
677
|
+
confirm(options: ConfirmOptions): Promise<boolean>;
|
|
678
|
+
/** Shows a message with a single acknowledge button. */
|
|
679
|
+
alert(options: AlertOptions): Promise<void>;
|
|
680
|
+
/** Asks for a line of text; resolves the value, or `null` if cancelled/dismissed. */
|
|
681
|
+
prompt(options: PromptOptions): Promise<string | null>;
|
|
682
|
+
/**
|
|
683
|
+
* Opens a custom body component. The host paints the frame + any declarative footer
|
|
684
|
+
* buttons; the component owns the body and injects {@link DialogRef} to close itself.
|
|
685
|
+
*/
|
|
686
|
+
open<R = unknown>(component: Type<unknown>, options?: OpenOptions): DialogRef<R>;
|
|
687
|
+
/**
|
|
688
|
+
* Shows a non-dismissable busy indicator (spinner + status) so a running operation is not
|
|
689
|
+
* interrupted. The caller closes it when done — or use {@link withProgress} to auto-close.
|
|
690
|
+
*/
|
|
691
|
+
progress(options: ProgressOptions): ProgressHandle;
|
|
692
|
+
/** Runs `work` behind a progress dialog that closes automatically when it settles. */
|
|
693
|
+
withProgress<T>(options: ProgressOptions, work: Promise<T>): Promise<T>;
|
|
694
|
+
private push;
|
|
695
|
+
private mount;
|
|
696
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DialogService, never>;
|
|
697
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<DialogService>;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Renders the open dialogs once, mounted by the shell root. Draws the frame
|
|
702
|
+
* (backdrop, panel, title, close-X, footer buttons) and either the convenience body
|
|
703
|
+
* (message + optional prompt input) or a plugin's custom body via NgComponentOutlet.
|
|
704
|
+
* Owns the modal mechanics: scroll-lock, Escape + backdrop dismiss, focus into the newest
|
|
705
|
+
* dialog + restore on close, and a lightweight focus trap.
|
|
706
|
+
*/
|
|
707
|
+
declare class DialogOutlet {
|
|
708
|
+
private readonly service;
|
|
709
|
+
private readonly document;
|
|
710
|
+
protected readonly dialogs: i0.Signal<readonly DialogInstance[]>;
|
|
711
|
+
private readonly panels;
|
|
712
|
+
constructor();
|
|
713
|
+
protected onEscape(): void;
|
|
714
|
+
protected onFocusIn(event: FocusEvent): void;
|
|
715
|
+
protected onTab(event: Event, backward: boolean): void;
|
|
716
|
+
protected dismiss(dialog: DialogInstance): void;
|
|
717
|
+
protected onButton(dialog: DialogInstance, button: DialogButtonView): void;
|
|
718
|
+
protected onPromptInput(dialog: DialogInstance, event: Event): void;
|
|
719
|
+
protected onEnter(dialog: DialogInstance): void;
|
|
720
|
+
protected confirmBlocked(dialog: DialogInstance, button: DialogButtonView): boolean;
|
|
721
|
+
protected guardError(dialog: DialogInstance): string | null;
|
|
722
|
+
protected wrapperClasses(dialog: DialogInstance): string;
|
|
723
|
+
protected toneCircle(tone: DialogTone): string;
|
|
724
|
+
protected panelWidth(dialog: DialogInstance): string;
|
|
725
|
+
private topPanel;
|
|
726
|
+
private focusable;
|
|
727
|
+
private top;
|
|
728
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DialogOutlet, never>;
|
|
729
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<DialogOutlet, "lw-dialog-outlet", never, {}, {}, never, never, true, never>;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* One settings row: label (+ optional description) on the left, the control projected on
|
|
734
|
+
* the right. A pure layout primitive — the host slots the matching control
|
|
735
|
+
* (`<lw-select>`, later toggle/button) via content projection.
|
|
736
|
+
*/
|
|
737
|
+
declare class LwSettingRow {
|
|
738
|
+
readonly label: i0.InputSignal<string>;
|
|
739
|
+
readonly description: i0.InputSignal<string>;
|
|
740
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LwSettingRow, never>;
|
|
741
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LwSettingRow, "lw-setting-row", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** Multi-provider token: each contribution adds one {@link BarItem}. */
|
|
745
|
+
declare const BAR_ITEM: InjectionToken<readonly BarItem[]>;
|
|
746
|
+
/** Hooks bar items into the shell — used by the host and by distributions. */
|
|
747
|
+
declare function provideBarItems(...items: BarItem[]): Provider[];
|
|
748
|
+
|
|
749
|
+
/** Multi-provider token: each contribution adds one {@link RailItem}. */
|
|
750
|
+
declare const RAIL_ITEM: InjectionToken<readonly RailItem[]>;
|
|
751
|
+
/** Hooks rail commands into the shell — used by the host and by distributions. */
|
|
752
|
+
declare function provideRailItems(...items: RailItem[]): Provider[];
|
|
753
|
+
|
|
754
|
+
/** Multi-provider token: each contribution adds one {@link View}. */
|
|
755
|
+
declare const VIEW: InjectionToken<readonly View[]>;
|
|
756
|
+
/** Hooks views into regions — used by the host and by distributions. */
|
|
757
|
+
declare function provideViews(...views: View[]): Provider[];
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* A {@link ContentRoute} as the registry holds it: the plugin's own declaration plus the **host-stamped**
|
|
761
|
+
* id of the plugin that registered it. The id is not part of the authoring contract — a plugin must not be
|
|
762
|
+
* able to claim someone else's identity — so it lives here rather than in `@loomweaver/plugin-sdk`.
|
|
763
|
+
*/
|
|
764
|
+
type RegisteredContentRoute = ContentRoute & {
|
|
765
|
+
readonly pluginId?: string;
|
|
766
|
+
};
|
|
767
|
+
/**
|
|
768
|
+
* A {@link View} as the registry holds it: the plugin's own declaration plus the **host-stamped** id
|
|
769
|
+
* of the plugin that registered it, mirroring {@link RegisteredContentRoute}. The stamp is what lets
|
|
770
|
+
* the retention guards find a plugin's dirty instances before the plugin is disabled or uninstalled.
|
|
771
|
+
*/
|
|
772
|
+
type RegisteredView = View & {
|
|
773
|
+
readonly pluginId?: string;
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* A command as the registry holds it: the declaration a plugin handed in, plus the owner the host
|
|
778
|
+
* stamped on it. The owner is what lets a command be attributed — to let its own plugin reach it
|
|
779
|
+
* without a grant, and to tell a plugin's commands from the shell's own.
|
|
780
|
+
*/
|
|
781
|
+
interface RegisteredCommand {
|
|
782
|
+
readonly command: Command;
|
|
783
|
+
readonly ownerId?: string;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Holds the live UI contributions the regions render. Seeded at startup from the
|
|
787
|
+
* static `provide*` API and (P1.2 loader) added to at runtime by plugins. Signal-
|
|
788
|
+
* based so the regions react to runtime registration/removal.
|
|
789
|
+
*
|
|
790
|
+
* Contributions are keyed by `id`: registering an existing id **overrides** the
|
|
791
|
+
* previous entry in place (last contribution wins — a distribution/plugin can
|
|
792
|
+
* replace a host default by reusing its id), and `remove*ById` drops one (a
|
|
793
|
+
* distribution hides a default it does not want, e.g. via `provideShell({ omit })`).
|
|
794
|
+
*
|
|
795
|
+
* Override is **destructive**, not stacked: the superseded entry is replaced, so disposing
|
|
796
|
+
* the *winning* handle removes the id entirely (it does not restore the entry it replaced),
|
|
797
|
+
* and disposing a *superseded* handle is a no-op. That is fine for today's usage (defaults are
|
|
798
|
+
* overridden at composition time and never disposed); a per-id stack that reveals the previous
|
|
799
|
+
* entry on dispose is the follow-up if a plugin ever overrides a default and is then unloaded.
|
|
800
|
+
*/
|
|
801
|
+
declare class ContributionRegistry {
|
|
802
|
+
private readonly commandsSignal;
|
|
803
|
+
private readonly surfacesSignal;
|
|
804
|
+
private readonly barItemsSignal;
|
|
805
|
+
private readonly railItemsSignal;
|
|
806
|
+
private readonly menuItemsSignal;
|
|
807
|
+
private readonly dockedSurfaces;
|
|
808
|
+
private readonly routableSurfaces;
|
|
809
|
+
private readonly omittedSignal;
|
|
810
|
+
/** Registered commands with their owner — the source for anything that has to attribute one. */
|
|
811
|
+
readonly registeredCommands: Signal<readonly RegisteredCommand[]>;
|
|
812
|
+
/** Registered commands — the invocable actions triggers (items/keybindings/palette) point at. */
|
|
813
|
+
readonly commands: Signal<readonly Command[]>;
|
|
814
|
+
readonly views: Signal<readonly RegisteredView[]>;
|
|
815
|
+
readonly barItems: Signal<readonly BarItem[]>;
|
|
816
|
+
readonly railItems: Signal<readonly RailItem[]>;
|
|
817
|
+
/**
|
|
818
|
+
* URL-addressed content-area routes; the ContentRouter mirrors these into the Router.
|
|
819
|
+
* Omitted routes are filtered out, so every consumer — tab strip, auto-open, pane target
|
|
820
|
+
* picker, `matchRoute` — drops them without knowing about omission.
|
|
821
|
+
*/
|
|
822
|
+
readonly contentRoutes: Signal<readonly RegisteredContentRoute[]>;
|
|
823
|
+
/**
|
|
824
|
+
* The routes {@link omit} dropped. The ContentRouter still maps these to a neutral
|
|
825
|
+
* "not available here" placeholder at their path, so a shared deep-link explains itself instead of
|
|
826
|
+
* silently falling back to home.
|
|
827
|
+
*/
|
|
828
|
+
readonly omittedContentRoutes: Signal<readonly RegisteredContentRoute[]>;
|
|
829
|
+
/** Menu-slot contributions; the MenuService filters these by slot + `when` when a menu opens. */
|
|
830
|
+
readonly menuItems: Signal<readonly MenuItem[]>;
|
|
831
|
+
/** The ids {@link omit} hides, exactly as the distribution wrote them, prefixes and all. */
|
|
832
|
+
readonly omitted: Signal<ReadonlySet<string>>;
|
|
833
|
+
/**
|
|
834
|
+
* Every id registered so far, of whatever kind, **including** those {@link omit} hides. An
|
|
835
|
+
* omitted contribution is by construction absent from every other signal here, so this is the
|
|
836
|
+
* only way to tell an `omit` that hid something from one that hit nothing at all.
|
|
837
|
+
*/
|
|
838
|
+
readonly registeredIds: Signal<ReadonlySet<string>>;
|
|
839
|
+
/**
|
|
840
|
+
* Hides contributions by id for good — a distribution drops a default it does not want
|
|
841
|
+
* (`provideShell({ omit })`). Unlike {@link removeCommandById} and friends, which delete what is
|
|
842
|
+
* registered *now*, this is a lasting filter: an id a plugin registers later stays hidden too.
|
|
843
|
+
*/
|
|
844
|
+
omit(ids: readonly string[]): void;
|
|
845
|
+
/**
|
|
846
|
+
* Adds a command. Like {@link addContentRoute}, `pluginId` is stamped by the host and never
|
|
847
|
+
* claimed by the plugin: it is what decides whether a caller is reaching its own command or
|
|
848
|
+
* another's. The shell's own seeded commands carry none.
|
|
849
|
+
*/
|
|
850
|
+
addCommand(command: Command, pluginId?: string): Disposable;
|
|
851
|
+
/**
|
|
852
|
+
* Adds a panel view. Like {@link addContentRoute}, `pluginId` is stamped by the host, never by
|
|
853
|
+
* the plugin, and overwrites anything the view carried.
|
|
854
|
+
*/
|
|
855
|
+
addView(view: View, pluginId?: string): Disposable;
|
|
856
|
+
addBarItem(item: BarItem): Disposable;
|
|
857
|
+
addRailItem(item: RailItem): Disposable;
|
|
858
|
+
removeCommandById(id: string): void;
|
|
859
|
+
removeViewById(id: string): void;
|
|
860
|
+
removeBarItemById(id: string): void;
|
|
861
|
+
removeRailItemById(id: string): void;
|
|
862
|
+
/**
|
|
863
|
+
* Adds a content route (keyed by `path`; a re-registered path overrides in place). `pluginId` is
|
|
864
|
+
* stamped by the host, never by the plugin: it is what lets a route's surface be checked against its
|
|
865
|
+
* owner's capability grants, so it is applied last and overwrites anything the route carried.
|
|
866
|
+
*/
|
|
867
|
+
addContentRoute(route: ContentRoute, pluginId?: string): Disposable;
|
|
868
|
+
/** Adds a menu-slot item. With an `id` a re-registration replaces in place (last-in wins); without one the item is additive and dispose removes this exact contribution. */
|
|
869
|
+
addMenuItem(item: MenuItem): Disposable;
|
|
870
|
+
removeMenuItemById(id: string): void;
|
|
871
|
+
private addSurface;
|
|
872
|
+
private visible;
|
|
873
|
+
private add;
|
|
874
|
+
private removeById;
|
|
875
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ContributionRegistry, never>;
|
|
876
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ContributionRegistry>;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Host registry for settings sections (schema-driven). The shell and
|
|
881
|
+
* plugins contribute sections; the host renders them all in one {@link SettingsDialog}.
|
|
882
|
+
* Storage is deliberately NOT centralised — each section carries its own value accessors,
|
|
883
|
+
* so the owner keeps responsibility for persistence (shell settings in the shell, plugin
|
|
884
|
+
* settings in the plugin, optionally against its owner's backend). This keeps the host domain-pure and
|
|
885
|
+
* fits the per-tenant tenancy model without the host needing to persist foreign data.
|
|
886
|
+
*/
|
|
887
|
+
declare class SettingsService {
|
|
888
|
+
private current;
|
|
889
|
+
private readonly dialogs;
|
|
890
|
+
private readonly registry;
|
|
891
|
+
/** The section a caller asked to show; the settings dialog consumes it. */
|
|
892
|
+
readonly requestedSection: i0.Signal<string | undefined>;
|
|
893
|
+
/**
|
|
894
|
+
* Registered sections, ordered by `order` (default 0), then registration order — with any
|
|
895
|
+
* omitted section or row dropped, and a section left with no rows hidden entirely.
|
|
896
|
+
*/
|
|
897
|
+
readonly all: i0.Signal<SettingsSection[]>;
|
|
898
|
+
/**
|
|
899
|
+
* Every registered section, **including** those {@link omit} hides and with their omitted rows
|
|
900
|
+
* still in place. {@link all} is what the dialog draws; this is what was contributed, which is
|
|
901
|
+
* what a dev-mode report needs to tell an `omit` that hid a row from one that hit nothing.
|
|
902
|
+
*/
|
|
903
|
+
readonly registered: i0.Signal<readonly SettingsSection[]>;
|
|
904
|
+
/**
|
|
905
|
+
* Contributes a section; dispose to remove it (plugin deactivation). Registering an existing
|
|
906
|
+
* id **overrides** the previous section in place (last contribution wins), like every other
|
|
907
|
+
* contribution.
|
|
908
|
+
*/
|
|
909
|
+
register(section: SettingsSection): Disposable;
|
|
910
|
+
/**
|
|
911
|
+
* Hides settings by id — a section id drops the whole section, a row id drops that row. A
|
|
912
|
+
* lasting filter (like {@link ContributionRegistry.omit}), so a section a plugin registers
|
|
913
|
+
* later is covered too.
|
|
914
|
+
*/
|
|
915
|
+
omit(ids: readonly string[]): void;
|
|
916
|
+
/**
|
|
917
|
+
* Opens the settings dialog — a bare, wide two-column surface owning its own chrome. Pass a
|
|
918
|
+
* `sectionId` to land on (or switch an already-open dialog to) that section — e.g. the plugin
|
|
919
|
+
* store's gear action jumping to an installed plugin's own settings.
|
|
920
|
+
*/
|
|
921
|
+
open(sectionId?: string): DialogRef;
|
|
922
|
+
consumeRequestedSection(): void;
|
|
923
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SettingsService, never>;
|
|
924
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<SettingsService>;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* The shared shape of the shell's two persistence ports: `SETTINGS_STORE` for genuine
|
|
929
|
+
* settings and `WORKING_STATE_STORE` for working state. A store implemented once fits either port —
|
|
930
|
+
* only what the shell routes through it differs.
|
|
931
|
+
*
|
|
932
|
+
* It is a plain **string** key-value store: callers serialise/validate their own payloads (they already
|
|
933
|
+
* do so defensively), which keeps the wire format identical to what the shell wrote before — no data
|
|
934
|
+
* migration. `get`/`set`/`delete` are async so a network-backed store fits; the optional {@link peek} is
|
|
935
|
+
* a synchronous fast-path for bootstrap-critical reads (theme, panel sizes) that must apply before the
|
|
936
|
+
* first paint. A store that cannot answer synchronously (network-backed) omits `peek`; callers then start
|
|
937
|
+
* from a default and reconcile via `get`.
|
|
938
|
+
*/
|
|
939
|
+
interface KeyValueStore {
|
|
940
|
+
/**
|
|
941
|
+
* Reads the stored string for `key`, or `undefined` if absent. Untrusted — callers validate its shape.
|
|
942
|
+
* Best-effort like {@link set}: prefer resolving with `undefined` over rejecting when the read fails
|
|
943
|
+
* (a network store that is offline or unauthorized). The shell treats a rejection as "no value" rather
|
|
944
|
+
* than failing startup, but a store that rejects makes the user's data silently unavailable.
|
|
945
|
+
*/
|
|
946
|
+
get(key: string): Promise<string | undefined>;
|
|
947
|
+
/** Writes `value` for `key`. Best-effort: implementations resolve even when persistence fails. */
|
|
948
|
+
set(key: string, value: string): Promise<void>;
|
|
949
|
+
/** Removes the value for `key`. */
|
|
950
|
+
delete(key: string): Promise<void>;
|
|
951
|
+
/**
|
|
952
|
+
* Optional synchronous snapshot of `key` for bootstrap-critical reads that must apply before first
|
|
953
|
+
* paint. Omitted by stores that can only answer asynchronously — callers fall back to {@link get}.
|
|
954
|
+
*/
|
|
955
|
+
peek?(key: string): string | undefined;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Default {@link KeyValueStore}: user-local `localStorage`. Reads and writes are synchronous
|
|
959
|
+
* under the hood — `peek` exposes that for zero-flash bootstrap — and wrapped in the same best-effort
|
|
960
|
+
* try/catch the shell used before (private browsing, quota, corrupt payloads). Both persistence ports
|
|
961
|
+
* default to an instance of this store.
|
|
962
|
+
*/
|
|
963
|
+
declare class LocalStorageStore implements KeyValueStore {
|
|
964
|
+
peek(key: string): string | undefined;
|
|
965
|
+
get(key: string): Promise<string | undefined>;
|
|
966
|
+
set(key: string, value: string): Promise<void>;
|
|
967
|
+
delete(key: string): Promise<void>;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* The **settings** persistence port: deliberate user decisions only — theme,
|
|
972
|
+
* language, text size, plugin settings, installed/disabled plugins, capability revocations and the
|
|
973
|
+
* saved-workspaces list. Working state (view state, layout, usage traces) never flows through this
|
|
974
|
+
* port; it lives behind `WORKING_STATE_STORE`. That guarantee is structural, which is what makes
|
|
975
|
+
* this port the right seam for a product backend: writes are rare, small and roaming-worthy, so a
|
|
976
|
+
* REST-backed implementation (the `ISettingsRepository` pattern) receives exactly
|
|
977
|
+
* what it expects. Swap the backing store with {@link provideSettingsStore}; the built-in default
|
|
978
|
+
* is {@link LocalStorageStore}.
|
|
979
|
+
*/
|
|
980
|
+
declare const SETTINGS_STORE: InjectionToken<KeyValueStore>;
|
|
981
|
+
/**
|
|
982
|
+
* Provides a custom settings {@link KeyValueStore}, replacing the default `localStorage` one —
|
|
983
|
+
* place after `provideShell()` (last provider wins). Pass a class (DI-constructed) or a ready
|
|
984
|
+
* instance. The cross-tab sync wrapper is applied on top either way.
|
|
985
|
+
*/
|
|
986
|
+
declare function provideSettingsStore(store: KeyValueStore | Type<KeyValueStore>): Provider;
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* The **working-state** persistence port: state that accrues from using the app rather
|
|
990
|
+
* than from deciding something — view state and view instances, the palette's recently-used list,
|
|
991
|
+
* and the window-local layout keys (pane trees, panel sizes, collapse state, item order, view
|
|
992
|
+
* placement). Writes are frequent and debounced, which is why this port defaults to the device
|
|
993
|
+
* ({@link LocalStorageStore}) instead of a product backend.
|
|
994
|
+
*
|
|
995
|
+
* A distribution that wants working state to travel across devices provides a backend-backed store
|
|
996
|
+
* with {@link provideWorkingStateStore}: a fresh tab then hydrates the last persisted state at boot
|
|
997
|
+
* with no further wiring. For *live* cross-device updates, pair the store with a push transport
|
|
998
|
+
* (SSE, WebSocket) that calls `StateSyncService.notifyRemoteChange` when the backend reports a
|
|
999
|
+
* changed key.
|
|
1000
|
+
*/
|
|
1001
|
+
declare const WORKING_STATE_STORE: InjectionToken<KeyValueStore>;
|
|
1002
|
+
/**
|
|
1003
|
+
* Provides a custom working-state {@link KeyValueStore}, replacing the default `localStorage` one —
|
|
1004
|
+
* place after `provideShell()` (last provider wins). Pass a class (DI-constructed) or a ready
|
|
1005
|
+
* instance. The cross-tab sync wrapper is applied on top either way.
|
|
1006
|
+
*/
|
|
1007
|
+
declare function provideWorkingStateStore(store: KeyValueStore | Type<KeyValueStore>): Provider;
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* The storage keys that stay **device-level** by default when the stores are identity-scoped
|
|
1011
|
+
*: preferences that reasonably belong to the browser, not the signed-in user. Extend the
|
|
1012
|
+
* default via `provideIdentityScopedStores({ deviceKeys: [...DEVICE_LEVEL_KEYS, 'my.key'] })`.
|
|
1013
|
+
*/
|
|
1014
|
+
declare const DEVICE_LEVEL_KEYS: readonly string[];
|
|
1015
|
+
/** Configuration for {@link provideIdentityScopedStores}. */
|
|
1016
|
+
interface IdentityScopedStoreOptions {
|
|
1017
|
+
/**
|
|
1018
|
+
* The identity discriminator, read synchronously. Return the current principal's stable id
|
|
1019
|
+
* (typically `AuthSnapshot.subject`; encode the tenant into it if tenant switches should
|
|
1020
|
+
* separate state). `null`/`undefined`/`''` means anonymous — keys pass through **unscoped**, so
|
|
1021
|
+
* a signed-out app behaves exactly like the unwrapped stores. Both stores **latch the first
|
|
1022
|
+
* non-empty value per boot** through one shared latch and never follow a live switch afterwards
|
|
1023
|
+
*: a change to a *different* subject only takes effect across the reload boundary
|
|
1024
|
+
* (`provideAuthSource(..., { onIdentityChange: 'reload' })`), so in-flight writes of the
|
|
1025
|
+
* departing user — a pending debounce, a commit during the login transition — can never land in
|
|
1026
|
+
* the next user's namespace. The shell peeks bootstrap-critical keys before first paint, so the
|
|
1027
|
+
* discriminator must be answerable synchronously at boot (persist the last-known subject
|
|
1028
|
+
* yourself).
|
|
1029
|
+
*/
|
|
1030
|
+
identity: () => string | null | undefined;
|
|
1031
|
+
/**
|
|
1032
|
+
* Exact keys that stay unscoped regardless of identity. **Replaces** the default
|
|
1033
|
+
* ({@link DEVICE_LEVEL_KEYS}) — spread it to extend. Applied to both stores; the built-in
|
|
1034
|
+
* defaults are all settings keys, but a distribution may declare device-level keys of its own.
|
|
1035
|
+
*/
|
|
1036
|
+
deviceKeys?: readonly string[];
|
|
1037
|
+
/** The wrapped settings store. Defaults to the built-in `localStorage` store. */
|
|
1038
|
+
settingsStore?: KeyValueStore;
|
|
1039
|
+
/** The wrapped working-state store. Defaults to the built-in `localStorage` store. */
|
|
1040
|
+
workingStateStore?: KeyValueStore;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Scopes **both** persistence ports (`SETTINGS_STORE` and `WORKING_STATE_STORE`) per
|
|
1044
|
+
* signed-in user: every key outside `deviceKeys` is prefixed `lw.id.<identity>:` while
|
|
1045
|
+
* someone is signed in, so on a shared browser one user's pane trees, tab titles, workspaces,
|
|
1046
|
+
* view state and plugin settings are never re-hydrated for the next. Device-level keys and the
|
|
1047
|
+
* anonymous session write the original, unprefixed keys — a distribution without auth is
|
|
1048
|
+
* byte-identical to the unwrapped stores.
|
|
1049
|
+
*
|
|
1050
|
+
* Place after `provideShell()` (last provider wins). The standard answer to per-user state on a
|
|
1051
|
+
* shared browser; pair with `provideAuthSource(..., { onIdentityChange: 'reload' })` so a user
|
|
1052
|
+
* switch re-hydrates cleanly from the new namespace. The cross-tab sync wrapper is applied on top
|
|
1053
|
+
* of both stores; `peek` is offered only when the wrapped store offers it, because the
|
|
1054
|
+
* shell branches on the *presence* of `peek` (sync bootstrap vs. async hydration).
|
|
1055
|
+
*/
|
|
1056
|
+
declare function provideIdentityScopedStores(options: IdentityScopedStoreOptions): Provider;
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Where a synced key's fresh value is read back from after another window announced a change
|
|
1060
|
+
*: the `SETTINGS_STORE`, the `WORKING_STATE_STORE`, or — for state persisted outside
|
|
1061
|
+
* both ports, such as a product session store — `'external'`, in which case no read-back happens
|
|
1062
|
+
* and the applier receives `undefined` (it re-reads its own storage).
|
|
1063
|
+
*/
|
|
1064
|
+
type SyncSource = 'settings' | 'working-state' | 'external';
|
|
1065
|
+
/**
|
|
1066
|
+
* Re-applies a key that another browser window just wrote. `raw` is the value read back from the
|
|
1067
|
+
* registered {@link SyncSource}'s store (`undefined` for `'external'` registrations) — the store
|
|
1068
|
+
* stays the single source of truth, the message only names the key. An applier must **set its
|
|
1069
|
+
* state without persisting again**, or two windows would write back and forth forever.
|
|
1070
|
+
*/
|
|
1071
|
+
type ApplySyncedState = (raw: string | undefined, key: string) => void;
|
|
1072
|
+
/**
|
|
1073
|
+
* Cross-tab live sync at the persistence seams. Every write through either port
|
|
1074
|
+
* broadcasts its **key** to the other windows of the same origin over a `BroadcastChannel`; a
|
|
1075
|
+
* window that has registered a reaction for that key reads the fresh value back through the
|
|
1076
|
+
* registered source's store and applies it — so it works with any store behind the ports,
|
|
1077
|
+
* including a product's HTTP-backed one.
|
|
1078
|
+
*
|
|
1079
|
+
* The shell registers its own keys (settings such as theme, language and text size on the
|
|
1080
|
+
* `'settings'` source; view state, view instances and the palette's recently-used list on
|
|
1081
|
+
* `'working-state'`), so plugins inherit the sync for free: their state lives in host-managed
|
|
1082
|
+
* stores. **Layout keys are deliberately not registered** — two windows are meant to be able to
|
|
1083
|
+
* show different layouts.
|
|
1084
|
+
*
|
|
1085
|
+
* A distribution registers its own keys the same way, most usefully its product session key
|
|
1086
|
+
* (source `'external'`): when it changes, the other window's `AuthSnapshot` flips and the existing
|
|
1087
|
+
* `onIdentityChange` policy takes over. Use {@link announce} when that state is
|
|
1088
|
+
* persisted outside the ports, and {@link notifyRemoteChange} when a backend push transport
|
|
1089
|
+
* reports a change made on another device.
|
|
1090
|
+
*/
|
|
1091
|
+
declare class StateSyncService {
|
|
1092
|
+
private readonly settings;
|
|
1093
|
+
private readonly workingState;
|
|
1094
|
+
private readonly channel;
|
|
1095
|
+
private readonly exact;
|
|
1096
|
+
private readonly prefixes;
|
|
1097
|
+
constructor();
|
|
1098
|
+
/**
|
|
1099
|
+
* Reacts to remote writes of exactly `key`; `source` names the store the fresh value is read
|
|
1100
|
+
* back from. One applier per key — registering again replaces it. Returns a disposer that
|
|
1101
|
+
* unregisters this applier (a later re-registration always wins over a stale disposer).
|
|
1102
|
+
*/
|
|
1103
|
+
register(source: SyncSource, key: string, apply: ApplySyncedState): () => void;
|
|
1104
|
+
/**
|
|
1105
|
+
* Reacts to remote writes of every key starting with `prefix` — for key families such as
|
|
1106
|
+
* `lw.shell.view-state:`. Exact registrations win over prefixes. Returns a disposer that
|
|
1107
|
+
* unregisters this applier (a later re-registration always wins over a stale disposer).
|
|
1108
|
+
*/
|
|
1109
|
+
registerPrefix(source: SyncSource, prefix: string, apply: ApplySyncedState): () => void;
|
|
1110
|
+
/**
|
|
1111
|
+
* Announces a change this window made to state that is persisted **outside** the persistence
|
|
1112
|
+
* ports — a product session store, for example. Writes through the ports broadcast on their own;
|
|
1113
|
+
* this is the escape hatch for state the ports never see. Notifies only the **other** windows.
|
|
1114
|
+
*/
|
|
1115
|
+
announce(key: string): void;
|
|
1116
|
+
/**
|
|
1117
|
+
* Runs the registered applier for `key` in **this** window, reading the fresh value back from
|
|
1118
|
+
* the registered source's store. The entry point for cross-device live sync: a
|
|
1119
|
+
* backend-backed store paired with a push transport (SSE, WebSocket) calls this when the backend
|
|
1120
|
+
* reports a key changed on another device. No-op for keys without a registration.
|
|
1121
|
+
*/
|
|
1122
|
+
notifyRemoteChange(key: string): void;
|
|
1123
|
+
private applyRemote;
|
|
1124
|
+
private readBack;
|
|
1125
|
+
private registrationFor;
|
|
1126
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<StateSyncService, never>;
|
|
1127
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<StateSyncService>;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* A developer-defined workspace a distribution ships with {@link provideWorkspaces}: the same thing a
|
|
1132
|
+
* user can build and save, only its baseline lives in code — it is never written to storage, the user
|
|
1133
|
+
* cannot overwrite, rename or delete it, and it moves with product updates. Switching, the automatic
|
|
1134
|
+
* per-workspace working state and **Reset** (back to this declaration) work exactly as for a
|
|
1135
|
+
* user-saved workspace.
|
|
1136
|
+
*/
|
|
1137
|
+
interface WorkspaceDefinition {
|
|
1138
|
+
/**
|
|
1139
|
+
* Stable identity — keys the workspace's working state. `default` is reserved for the built-in
|
|
1140
|
+
* empty workspace; a duplicate id keeps the first declaration.
|
|
1141
|
+
*/
|
|
1142
|
+
readonly id: string;
|
|
1143
|
+
/** Display name — a translation key; a literal string falls back to itself. */
|
|
1144
|
+
readonly title: string;
|
|
1145
|
+
/** Optional icon (registry name) shown next to the name in the workspace management UI. */
|
|
1146
|
+
readonly icon?: string;
|
|
1147
|
+
/**
|
|
1148
|
+
* Makes this the workspace a fresh install opens in, instead of the empty `default` one. It applies
|
|
1149
|
+
* **once**, on a first boot with nothing stored yet: from then on the user's own last choice wins,
|
|
1150
|
+
* so switching away is not undone by the next reload. A deep link still wins over the declaration —
|
|
1151
|
+
* the baseline is laid out, but an incoming address is navigated to, so a shared link opens what it
|
|
1152
|
+
* names. If two declarations set this, the first one wins, as with a duplicate id.
|
|
1153
|
+
*/
|
|
1154
|
+
readonly initial?: boolean;
|
|
1155
|
+
/**
|
|
1156
|
+
* Which views each sidebar shows, keyed by the **panel region id** of your layout. A listed region
|
|
1157
|
+
* shows exactly the named views; everything else declared for it is hidden and can be brought back
|
|
1158
|
+
* through the strip's context menu. List a region with an **empty array** to show none of its views
|
|
1159
|
+
* — the sidebar itself stays, empty. A region you leave out, or omitting the whole field, keeps
|
|
1160
|
+
* whatever the user has there. **Whether a sidebar exists, is open and how wide it is belongs to the
|
|
1161
|
+
* window, not to the workspace**: your layout decides which sidebars the app has, the user decides
|
|
1162
|
+
* whether they are open, and switching workspaces never collapses, resizes or removes one.
|
|
1163
|
+
*/
|
|
1164
|
+
readonly sidebars?: Readonly<Record<string, readonly string[]>>;
|
|
1165
|
+
/**
|
|
1166
|
+
* The content addresses that belong to this workspace, as route paths in the same vocabulary as
|
|
1167
|
+
* {@link content} — `quotes/:id` claims every quote document and everything below one. Reaching a
|
|
1168
|
+
* claimed address activates this workspace and shows the content inside it, **however the address
|
|
1169
|
+
* was reached**: a link followed into the application, a restart, a command, a programmatic
|
|
1170
|
+
* navigation or a tab a plugin opened. Without a claim an address is shown wherever the user
|
|
1171
|
+
* already is, which is the behaviour of every workspace that declares none.
|
|
1172
|
+
*
|
|
1173
|
+
* Where two workspaces claim addresses of the same shape, neither is narrower and the claim is
|
|
1174
|
+
* dropped from both with a message naming them; a narrower claim (more segments, or fewer
|
|
1175
|
+
* parameters at the same length) simply wins. A workspace the **user** saved is never a
|
|
1176
|
+
* destination, however it came by its claim — it exists on one machine only, and an address that
|
|
1177
|
+
* led somewhere different for every user would not be an address.
|
|
1178
|
+
*/
|
|
1179
|
+
readonly claims?: readonly string[];
|
|
1180
|
+
/**
|
|
1181
|
+
* The content area as a recursive arrangement: an area either holds `tabs`, splits into `rows`
|
|
1182
|
+
* (top to bottom) or splits into `columns` (left to right). The **first** tabs area in reading
|
|
1183
|
+
* order becomes the URL pane; switching to the workspace navigates to its active tab. Omit to
|
|
1184
|
+
* start on the empty layout.
|
|
1185
|
+
*/
|
|
1186
|
+
readonly content?: WorkspaceArea;
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* One node of a {@link WorkspaceDefinition} content declaration — the shared {@link PaneArea} grammar
|
|
1190
|
+
* over route paths. A container declares its own arrangement in the same grammar over
|
|
1191
|
+
* child surface ids.
|
|
1192
|
+
*/
|
|
1193
|
+
type WorkspaceArea = PaneArea<WorkspaceTabEntry>;
|
|
1194
|
+
type WorkspaceAreaBase = PaneAreaBase;
|
|
1195
|
+
/** An area that holds tabs. */
|
|
1196
|
+
type WorkspaceTabArea = PaneTabArea<WorkspaceTabEntry>;
|
|
1197
|
+
/** An area that splits into rows, top to bottom. */
|
|
1198
|
+
type WorkspaceRowArea = PaneRowArea<WorkspaceTabEntry>;
|
|
1199
|
+
/** An area that splits into columns, left to right. */
|
|
1200
|
+
type WorkspaceColumnArea = PaneColumnArea<WorkspaceTabEntry>;
|
|
1201
|
+
/** A tab in a workspace declaration — a route path, or the object form for the extra flags. */
|
|
1202
|
+
type WorkspaceTabEntry = string | WorkspaceTab;
|
|
1203
|
+
interface WorkspaceTab {
|
|
1204
|
+
/** The surface route path the tab opens (sidebar views belong under `sidebars`, not here). */
|
|
1205
|
+
readonly path: string;
|
|
1206
|
+
/**
|
|
1207
|
+
* `false` fixes the tab in this workspace: it shows no close affordance, "close others" and
|
|
1208
|
+
* "close all" spare it, and it cannot be dragged away. Reset restores it either way.
|
|
1209
|
+
*/
|
|
1210
|
+
readonly closable?: boolean;
|
|
1211
|
+
/** Marks the area's initially active tab; without it the first tab is active. */
|
|
1212
|
+
readonly active?: boolean;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
/**
|
|
1216
|
+
* The developer-defined workspaces of the composition — each {@link provideWorkspaces} call
|
|
1217
|
+
* contributes one batch. Read by the workspace service; a distribution never injects this itself.
|
|
1218
|
+
*/
|
|
1219
|
+
declare const WORKSPACE_DEFINITIONS: InjectionToken<readonly (readonly WorkspaceDefinition[])[]>;
|
|
1220
|
+
/**
|
|
1221
|
+
* Ships developer-defined workspaces with the distribution (composition root). They are switchable,
|
|
1222
|
+
* self-remembering (each keeps its live working state per workspace) and resettable to the
|
|
1223
|
+
* declaration — but their baseline lives in code, so the user cannot overwrite, rename or delete
|
|
1224
|
+
* them. The workspace dialog therefore lists them separately from the user's own, opening on
|
|
1225
|
+
* whichever list holds the active workspace. Invalid declarations are reported to
|
|
1226
|
+
* the console in dev mode, naming what is ignored; nothing fails silently at runtime.
|
|
1227
|
+
*/
|
|
1228
|
+
declare function provideWorkspaces(...definitions: WorkspaceDefinition[]): EnvironmentProviders;
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* The running build's version, sourced from `<Version>` in Directory.Build.props
|
|
1232
|
+
* at build time (see tools/stamp-version.mjs). Exposed as a signal so a
|
|
1233
|
+
* later source (e.g. a product backend) can update it live without touching consumers.
|
|
1234
|
+
*
|
|
1235
|
+
* Neutral core chrome: the version is host-offered data; distributions and plugins
|
|
1236
|
+
* render it via `<lw-version>` or `ctx`, never hardcoded.
|
|
1237
|
+
*/
|
|
1238
|
+
declare class VersionService {
|
|
1239
|
+
/** SemVer of the running build, e.g. `0.1.0`. */
|
|
1240
|
+
readonly version: i0.WritableSignal<string>;
|
|
1241
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<VersionService, never>;
|
|
1242
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<VersionService>;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Detects when a new app version has been fetched by the service worker and offers
|
|
1247
|
+
* to activate it. Neutral core chrome: it raises a sticky toast and drives
|
|
1248
|
+
* the persistent update badge off {@link updateAvailable}. Dismissing the toast never
|
|
1249
|
+
* clears that signal, so the badge stays until the user actually reloads.
|
|
1250
|
+
*
|
|
1251
|
+
* `SwUpdate` is injected optionally: with no registered service worker (dev, tests,
|
|
1252
|
+
* unsupported browsers) the service is simply inert — {@link enabled} is `false` and
|
|
1253
|
+
* no update is ever offered.
|
|
1254
|
+
*/
|
|
1255
|
+
declare class UpdateService {
|
|
1256
|
+
private readonly swUpdate;
|
|
1257
|
+
private readonly notifications;
|
|
1258
|
+
private readonly document;
|
|
1259
|
+
private readonly destroyRef;
|
|
1260
|
+
private readonly available;
|
|
1261
|
+
private readonly failed;
|
|
1262
|
+
private readonly broken;
|
|
1263
|
+
private lastSilentCheck;
|
|
1264
|
+
/** True once a new version is downloaded and ready to activate. */
|
|
1265
|
+
readonly updateAvailable: i0.Signal<boolean>;
|
|
1266
|
+
/**
|
|
1267
|
+
* True once an update could not be installed, whichever of the two ways it went wrong.
|
|
1268
|
+
* The client keeps running its current version until a reload; the badge and the failure
|
|
1269
|
+
* toast both offer that reload. Read {@link updateBroken} to tell the two apart.
|
|
1270
|
+
*/
|
|
1271
|
+
readonly updateFailed: i0.Signal<boolean>;
|
|
1272
|
+
/**
|
|
1273
|
+
* True once the service worker reports an unrecoverable state, which is the harsher half of
|
|
1274
|
+
* {@link updateFailed}: its cached asset table no longer matches what the server serves, so it
|
|
1275
|
+
* cannot repair itself and a plain reload lands in the same state again. {@link activateUpdate}
|
|
1276
|
+
* handles it by dropping the worker rather than reloading into the same wall; read this only if
|
|
1277
|
+
* you want to say something different about it in your own UI.
|
|
1278
|
+
*/
|
|
1279
|
+
readonly updateBroken: i0.Signal<boolean>;
|
|
1280
|
+
/** Whether update checks are possible (a service worker is registered and enabled). */
|
|
1281
|
+
readonly enabled: boolean;
|
|
1282
|
+
constructor();
|
|
1283
|
+
/** Manually checks for a new version, noting when already up to date. */
|
|
1284
|
+
checkForUpdate(): Promise<void>;
|
|
1285
|
+
/**
|
|
1286
|
+
* Gets the client onto a working version and reloads into it — it reloads even if that fails,
|
|
1287
|
+
* so the affordance is never a silent no-op.
|
|
1288
|
+
*
|
|
1289
|
+
* When the worker is {@link updateBroken} activating is pointless: the broken registration would
|
|
1290
|
+
* still control the next load and report the same failure, which is a loop the user cannot leave
|
|
1291
|
+
* from inside the app. There the shell unregisters its own worker and drops its caches first, so
|
|
1292
|
+
* the reload lands uncontrolled and registers afresh. Only the shell's own `ngsw-worker.js` and
|
|
1293
|
+
* the `ngsw:` caches are touched; anything else the product registered is left alone.
|
|
1294
|
+
*/
|
|
1295
|
+
activateUpdate(): Promise<void>;
|
|
1296
|
+
private ensureControlled;
|
|
1297
|
+
private showReloadNeeded;
|
|
1298
|
+
private tryActivate;
|
|
1299
|
+
private dropWorker;
|
|
1300
|
+
private onVersionEvent;
|
|
1301
|
+
private onUpdateReady;
|
|
1302
|
+
private showUpdateAvailable;
|
|
1303
|
+
private onUpdateFailed;
|
|
1304
|
+
private onWorkerBroken;
|
|
1305
|
+
private showUpdateFailed;
|
|
1306
|
+
private startBackgroundChecks;
|
|
1307
|
+
private silentCheck;
|
|
1308
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<UpdateService, never>;
|
|
1309
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<UpdateService>;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* The distribution-supplied session snapshot signal. Defaults to **anonymous** so the
|
|
1314
|
+
* bare platform runs with nobody signed in; a distribution maps its product's auth (OIDC/custom/…)
|
|
1315
|
+
* into an {@link AuthSnapshot} signal via {@link provideAuthSource}. LoomWeaver never owns auth — it
|
|
1316
|
+
* only reads this signal.
|
|
1317
|
+
*/
|
|
1318
|
+
declare const AUTH_SOURCE: InjectionToken<Signal<AuthSnapshot>>;
|
|
1319
|
+
/**
|
|
1320
|
+
* What the shell does when one established identity is replaced by another.
|
|
1321
|
+
* The comparison anchor is {@link AuthSnapshot.subject}.
|
|
1322
|
+
*/
|
|
1323
|
+
interface AuthSourceOptions {
|
|
1324
|
+
/**
|
|
1325
|
+
* `'reload'`: when a snapshot carries a **different** non-empty `subject` than the one already
|
|
1326
|
+
* established this app lifetime, the shell performs a full `location.reload()` — the
|
|
1327
|
+
* guaranteed-clean user switch (no pane trees, tab titles or plugin in-memory state of the
|
|
1328
|
+
* previous user survive; paired with an identity-scoped settings store, the new session
|
|
1329
|
+
* re-hydrates entirely from its own namespace). First sign-in (anonymous → subject) and
|
|
1330
|
+
* sign-out (subject → anonymous) never fire, so an async session restore at boot causes no
|
|
1331
|
+
* reload flicker. Snapshots without a `subject` never fire either. Default: `'ignore'`.
|
|
1332
|
+
*/
|
|
1333
|
+
onIdentityChange?: 'reload' | 'ignore';
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* A distribution declares its auth source. The factory runs in the injection context, so it can
|
|
1337
|
+
* `inject()` the product's own session/BFF service and return a signal derived from it. Pass
|
|
1338
|
+
* `options` to opt into the identity-change policy: with
|
|
1339
|
+
* `{ onIdentityChange: 'reload' }` a user switch on a shared browser reloads the app instead of
|
|
1340
|
+
* leaving the previous user's in-memory state alive.
|
|
1341
|
+
*/
|
|
1342
|
+
declare function provideAuthSource(factory: () => Signal<AuthSnapshot>, options?: AuthSourceOptions): EnvironmentProviders;
|
|
1343
|
+
/**
|
|
1344
|
+
* Reactive read-only view of the current session, plus the gating predicates the host chrome uses to
|
|
1345
|
+
* hide/disable contributions. Roles/claims are opaque — this service matches, never
|
|
1346
|
+
* interprets. Client-side gating is presentation, not a security boundary.
|
|
1347
|
+
*/
|
|
1348
|
+
declare class AuthContext {
|
|
1349
|
+
private readonly source;
|
|
1350
|
+
/** The current session snapshot (reactive). */
|
|
1351
|
+
readonly state: Signal<AuthSnapshot>;
|
|
1352
|
+
/** Whether someone is signed in. */
|
|
1353
|
+
readonly authenticated: Signal<boolean>;
|
|
1354
|
+
/** The current principal's roles. */
|
|
1355
|
+
readonly roles: Signal<readonly string[]>;
|
|
1356
|
+
/** True if the current principal holds `role`. */
|
|
1357
|
+
hasRole(role: string): boolean;
|
|
1358
|
+
/** True if the current session satisfies `access` (undefined = always). */
|
|
1359
|
+
meets(access: AccessRequirement | undefined): boolean;
|
|
1360
|
+
/** Whether a chrome item with this requirement should render (unmet + hide → false). */
|
|
1361
|
+
visible(access: AccessRequirement | undefined): boolean;
|
|
1362
|
+
/** Whether a rendered chrome item with this requirement should be disabled (unmet + disable). */
|
|
1363
|
+
disabled(access: AccessRequirement | undefined): boolean;
|
|
1364
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AuthContext, never>;
|
|
1365
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<AuthContext>;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/** What the user picked. `system` follows the OS `prefers-color-scheme`. */
|
|
1369
|
+
type ThemeMode = 'light' | 'dark' | 'system';
|
|
1370
|
+
/** What is actually being rendered, once `system` has been resolved against the OS. */
|
|
1371
|
+
type ResolvedTheme = 'light' | 'dark';
|
|
1372
|
+
/**
|
|
1373
|
+
* Owns light/dark for the whole application: it persists the user's choice through the
|
|
1374
|
+
* the settings store, mirrors it across tabs, and toggles the `dark` class on `<html>` — which is what
|
|
1375
|
+
* flips the `--lw-*` token ladder every surface reads.
|
|
1376
|
+
*
|
|
1377
|
+
* Inject it when your own UI has to follow the same mode, most commonly to mirror it onto another
|
|
1378
|
+
* framework's switch (Bootstrap's `data-bs-theme`, for example) so the two never disagree:
|
|
1379
|
+
*
|
|
1380
|
+
* ```ts
|
|
1381
|
+
* const theme = inject(ThemeService);
|
|
1382
|
+
* effect(() => {
|
|
1383
|
+
* document.documentElement.setAttribute('data-bs-theme', theme.resolvedTheme());
|
|
1384
|
+
* });
|
|
1385
|
+
* ```
|
|
1386
|
+
*/
|
|
1387
|
+
declare class ThemeService {
|
|
1388
|
+
private readonly document;
|
|
1389
|
+
private readonly store;
|
|
1390
|
+
private readonly sync;
|
|
1391
|
+
private readonly systemDark;
|
|
1392
|
+
private readonly modeState;
|
|
1393
|
+
/** The user's choice, including `system`. Use {@link resolvedTheme} to know what is rendered. */
|
|
1394
|
+
readonly mode: Signal<ThemeMode>;
|
|
1395
|
+
/** The mode in effect, with `system` already resolved against the OS preference. */
|
|
1396
|
+
readonly resolvedTheme: Signal<ResolvedTheme>;
|
|
1397
|
+
constructor();
|
|
1398
|
+
/** Switch the mode and persist it. Other tabs follow through the settings sync. */
|
|
1399
|
+
setMode(mode: ThemeMode): void;
|
|
1400
|
+
private watchSystemPreference;
|
|
1401
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ThemeService, never>;
|
|
1402
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ThemeService>;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* The distribution's router, set up for the content area — call this **instead of**
|
|
1407
|
+
* `provideRouter([])`. It bundles the three pieces that make plugin-contributed content routes work
|
|
1408
|
+
* as one foolproof unit, so a distribution author cannot forget the initial-navigation flag:
|
|
1409
|
+
*
|
|
1410
|
+
* - `withDisabledInitialNavigation()` — the router does not navigate until the plugin routes exist.
|
|
1411
|
+
* - `ContentReuseStrategy` — keeps a surface's instance across tab/perspective switches when its
|
|
1412
|
+
* retention policy says so (`retain: 'always'` or a `retention: 'retain'` default).
|
|
1413
|
+
* - an app-initializer that runs {@link ContentRouter.start} after plugins activate: it mirrors the
|
|
1414
|
+
* registered content routes into the router and then performs the deferred initial navigation.
|
|
1415
|
+
*
|
|
1416
|
+
* Pass `extraRoutes` for any non-content routes the distribution owns.
|
|
1417
|
+
*/
|
|
1418
|
+
declare function provideShellRouter(extraRoutes?: Routes): (Provider | EnvironmentProviders)[];
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* A distribution's redirect for a gated content route the session cannot reach. Given the
|
|
1422
|
+
* attempted path, it returns an in-app URL to redirect to (e.g. the product's login route), or `null`
|
|
1423
|
+
* to fall back to the host's neutral "sign-in required" placeholder. LoomWeaver owns no login UI, so
|
|
1424
|
+
* this is how a product plugs in its own auth flow. Defaults to `null` (placeholder).
|
|
1425
|
+
*/
|
|
1426
|
+
type UnauthorizedHandler = (attemptedPath: string) => string | null;
|
|
1427
|
+
/** A distribution registers a login redirect for gated content routes. */
|
|
1428
|
+
declare function provideUnauthorizedRedirect(handler: UnauthorizedHandler): Provider;
|
|
1429
|
+
|
|
1430
|
+
/** One rendered tab in the content strip (static or dynamic), as the content-area template consumes it. */
|
|
1431
|
+
interface ContentTabView {
|
|
1432
|
+
/** The tab **root** — stable identity (tracking, active compare, close). */
|
|
1433
|
+
readonly path: string;
|
|
1434
|
+
/** The full path to navigate to when the tab is selected (root + remembered sub-route). */
|
|
1435
|
+
readonly navPath: string;
|
|
1436
|
+
readonly title: string;
|
|
1437
|
+
/** When true the host shows `title` verbatim; otherwise it is a Transloco key (finding #8). */
|
|
1438
|
+
readonly literalTitle: boolean;
|
|
1439
|
+
readonly icon?: string;
|
|
1440
|
+
readonly order: number;
|
|
1441
|
+
/** Dynamic tabs show a close affordance; static (registered) tabs do not. */
|
|
1442
|
+
readonly closable: boolean;
|
|
1443
|
+
/** A preview tab renders its title in italics until promoted. */
|
|
1444
|
+
readonly preview: boolean;
|
|
1445
|
+
/** A pinned tab is sorted to the group's front and shows an unpin control instead of close. */
|
|
1446
|
+
readonly pinned: boolean;
|
|
1447
|
+
readonly actions?: readonly ViewAction[];
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/**
|
|
1451
|
+
* A navigable content target the command palette's Quick-Open mode lists:
|
|
1452
|
+
* a registered surface reachable by plain navigation (a parameterless, non-chromeless route), or a
|
|
1453
|
+
* currently **open** tab — across every workspace pane. `lastActive` is a session-only epoch stamp of
|
|
1454
|
+
* when the target was last the active tab — it is not persisted (a fresh window starts with no
|
|
1455
|
+
* times), and it floats recently visited targets up.
|
|
1456
|
+
*/
|
|
1457
|
+
interface QuickOpenTarget {
|
|
1458
|
+
/** The tab root — stable identity and the `tabId` for the tab context menu. */
|
|
1459
|
+
readonly path: string;
|
|
1460
|
+
/** The full path to navigate to (open: root + remembered sub-route; else the route path). */
|
|
1461
|
+
readonly navPath: string;
|
|
1462
|
+
readonly title: string;
|
|
1463
|
+
readonly literalTitle: boolean;
|
|
1464
|
+
readonly icon?: string;
|
|
1465
|
+
readonly pinned: boolean;
|
|
1466
|
+
/** A target that is not an open tab is not closable — the context menu hides Close/Split/Pin. */
|
|
1467
|
+
readonly closable: boolean;
|
|
1468
|
+
readonly lastActive?: number;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/**
|
|
1472
|
+
* The **URL pane's** tab state: the tabs to show (facet tabs of `follows`
|
|
1473
|
+
* surfaces + opened ones), and the active tab — all derived from the URL plus the open set. The open
|
|
1474
|
+
* set lives in the **pane tree's primary leaf**: the URL pane's tab group works like any
|
|
1475
|
+
* pane's, so its tabs persist and reload with the tree (R10); only the non-serialisable `onClose`
|
|
1476
|
+
* hooks stay session-local. A tab is identified by its **tab root**; sub-routes (`subRoutes`) live
|
|
1477
|
+
* under it, so switching sub-routes stays in one tab and is reflected in the URL. A **chromeless**
|
|
1478
|
+
* surface shows no strip.
|
|
1479
|
+
*/
|
|
1480
|
+
declare class ContentTabsService {
|
|
1481
|
+
private readonly state;
|
|
1482
|
+
private readonly closing;
|
|
1483
|
+
private readonly registry;
|
|
1484
|
+
private readonly reuse;
|
|
1485
|
+
private readonly features;
|
|
1486
|
+
private readonly paneTree;
|
|
1487
|
+
private readonly closeHooks;
|
|
1488
|
+
/**
|
|
1489
|
+
* The Quick-Open source for the command palette: every currently **open** tab across
|
|
1490
|
+
* **all content panes** (a split's secondary panes contribute theirs too), plus every registered
|
|
1491
|
+
* route the user could open — one that takes no route parameter and is not `chromeless`. A route
|
|
1492
|
+
* the user has visited carries its session `lastActive`; an open tab wins any path collision (it
|
|
1493
|
+
* keeps its pinned identity). View tabs are excluded — those are reached through their rail item.
|
|
1494
|
+
*
|
|
1495
|
+
* **Access:** an unopened route is offered only when the session meets its `access` requirement. An
|
|
1496
|
+
* already-open tab is listed as it is — matching the tab strip, which does not re-gate content tabs
|
|
1497
|
+
* either, which is a deliberate open point. Client-side gating is
|
|
1498
|
+
* presentation, not enforcement: opening the target still passes the router's `canMatch` twin, and an
|
|
1499
|
+
* off-router mount re-checks the session, so a target the session no longer qualifies for renders the
|
|
1500
|
+
* neutral placeholder rather than its content.
|
|
1501
|
+
*/
|
|
1502
|
+
readonly quickOpenTargets: Signal<readonly QuickOpenTarget[]>;
|
|
1503
|
+
/** The active `view:` tab of the URL group, or `null` when the router drives the area (R9). */
|
|
1504
|
+
readonly activeViewPath: Signal<string | null>;
|
|
1505
|
+
readonly activeViewInstance: Signal<string | undefined>;
|
|
1506
|
+
/** Active content path (no leading slash, no query), including the sub-route, e.g. `doc/abc/preview`. */
|
|
1507
|
+
readonly activePath: Signal<string>;
|
|
1508
|
+
/** The active tab root — the leading segments the active route matched (e.g. `doc/abc`). */
|
|
1509
|
+
readonly activeTabRoot: Signal<string>;
|
|
1510
|
+
/** The active content read a plugin reaches through `ctx.activeContent` (finding #19). */
|
|
1511
|
+
readonly activeContent: Signal<ActiveContent | null>;
|
|
1512
|
+
/**
|
|
1513
|
+
* Whether the tab strip renders: whenever the pane holds tabs — and never while a **chromeless**
|
|
1514
|
+
* surface (login, onboarding) is active. That is the whole rule since groups retired:
|
|
1515
|
+
* a pane shows a strip when it holds tabs; a chromeless surface shows none.
|
|
1516
|
+
*/
|
|
1517
|
+
readonly showStrip: Signal<boolean>;
|
|
1518
|
+
/**
|
|
1519
|
+
* Everything the strip holds: the permanent facet tabs (`follows` surfaces with a computed
|
|
1520
|
+
* address), then the open tabs, then the view tabs. Groups retired — every open tab
|
|
1521
|
+
* renders, in every workspace.
|
|
1522
|
+
*/
|
|
1523
|
+
readonly tabs: Signal<readonly ContentTabView[]>;
|
|
1524
|
+
/**
|
|
1525
|
+
* Activates a `view:` tab of the URL group (O5/E7 — a view living as a tab beside the content tabs):
|
|
1526
|
+
* the content area host-mounts the view; the URL stays at its last route. No-op unless the group
|
|
1527
|
+
* actually holds that tab.
|
|
1528
|
+
*/
|
|
1529
|
+
activateViewTab(path: string): void;
|
|
1530
|
+
/**
|
|
1531
|
+
* Applies a user drag/keyboard reorder of the strip's dynamic tabs. `ids` is the new full
|
|
1532
|
+
* order of dynamic tab roots (pinned then unpinned — the directive keeps moves within their band).
|
|
1533
|
+
*
|
|
1534
|
+
* The order is written to the pane that holds the tabs, exactly as a reorder in any other pane is.
|
|
1535
|
+
* It used to be kept beside the tree under one key for the whole content dock, which meant the URL
|
|
1536
|
+
* pane sorted its strip by that key while every other pane rendered the tabs in the order the tree
|
|
1537
|
+
* held them — so the same tabs changed order as the URL role moved between panes (TreeWeaver #41).
|
|
1538
|
+
*/
|
|
1539
|
+
reorder(ids: string[]): void;
|
|
1540
|
+
/**
|
|
1541
|
+
* Moves the tab rooted at `path` to the front of the strip's unpinned band (MRU). The strip does not
|
|
1542
|
+
* scroll: tabs that no longer fit are clipped away, reachable via the overflow
|
|
1543
|
+
* dropdown or the Library. Reopening such a tab front-inserts it here so it becomes visible while the
|
|
1544
|
+
* least-recent tabs drop off the end. No-op for a pinned (anchored) or unknown tab. It moves the tab
|
|
1545
|
+
* in the pane's own tab list, exactly as a drag-reorder does, so every pane shows the one
|
|
1546
|
+
* order; the host calls it only when the active tab is actually clipped, so tabs that already fit
|
|
1547
|
+
* keep their order.
|
|
1548
|
+
*/
|
|
1549
|
+
bringToFront(path: string): void;
|
|
1550
|
+
/**
|
|
1551
|
+
* Navigates the content area to a path (a full-area screen, another tab, a specific sub-route).
|
|
1552
|
+
*
|
|
1553
|
+
* If another pane already holds that tab, **that pane becomes the URL pane** and the tab is activated
|
|
1554
|
+
* there instead of a second copy opening beside the current one: exactly one pane carries the
|
|
1555
|
+
* address, and the address follows the focused pane. Without this, a workspace that parks a surface
|
|
1556
|
+
* in its own pane would gain a duplicate of it the moment a rail item or command navigated there, because
|
|
1557
|
+
* every visit opens a tab.
|
|
1558
|
+
*
|
|
1559
|
+
* Ends any view-tab selection here and marks the navigation as ours, so the URL effect — which exists
|
|
1560
|
+
* to end the selection on **external** moves like back/forward or a deep link — leaves alone whatever
|
|
1561
|
+
* the caller selects once this settles.
|
|
1562
|
+
*
|
|
1563
|
+
* **A no-op in a pop-out window**, with a dev-mode warning. A pop-out shows exactly one surface and
|
|
1564
|
+
* has no tab strip; navigating it would take its address out of `/popout/…`, and the window would
|
|
1565
|
+
* quietly stop being a pop-out — chrome-less until the next reload, and the full app after it. Same
|
|
1566
|
+
* reasoning as a docked surface, whose `navigate` is a no-op for want of a content area.
|
|
1567
|
+
*/
|
|
1568
|
+
navigate(path: string): Promise<boolean>;
|
|
1569
|
+
/**
|
|
1570
|
+
* Fire-and-forget navigation: like {@link navigate}, but owns the "navigation may fail"
|
|
1571
|
+
* semantics — a rejected router navigation is logged instead of surfacing as an unhandled
|
|
1572
|
+
* rejection. Call sites that do not await the outcome use this.
|
|
1573
|
+
*/
|
|
1574
|
+
navigateTo(path: string): void;
|
|
1575
|
+
/**
|
|
1576
|
+
* Reveals a content tab **where it already lives** (Quick-Open): a tab held by another pane is
|
|
1577
|
+
* activated there and that pane takes the address; anything else simply navigates. Either way the tab is
|
|
1578
|
+
* never duplicated into the URL pane. Since {@link navigate} carries that rule, this is the name the
|
|
1579
|
+
* Quick-Open call site reads by — the behaviour is the same for every way of reaching a tab.
|
|
1580
|
+
*/
|
|
1581
|
+
revealContentTab(navPath: string): void;
|
|
1582
|
+
/**
|
|
1583
|
+
* Opens a titled dynamic tab (idempotent per tab root; re-opening restores its sub-route) and activates
|
|
1584
|
+
* it. With `preview` (and preview enabled) a **new** tab uses the strip's **single reused preview slot**
|
|
1585
|
+
*: a preview open for a *different* path replaces that slot in place (the old instance is
|
|
1586
|
+
* evicted + its `onClose` runs). Re-opening an **existing** tab preserves its current preview state (a
|
|
1587
|
+
* mere title/sub-route refinement never promotes); promotion is explicit via {@link keep}.
|
|
1588
|
+
*/
|
|
1589
|
+
open(input: OpenTabInput): void;
|
|
1590
|
+
/**
|
|
1591
|
+
* Promotes the preview tab rooted at `path` to a permanent tab — the programmatic
|
|
1592
|
+
* "Keep Open" (`ctx.keepContentTab`, a double-click, or an edit). No-op if it is already permanent.
|
|
1593
|
+
*/
|
|
1594
|
+
keep(path: string): void;
|
|
1595
|
+
/**
|
|
1596
|
+
* Pins the tab rooted at `path`: it moves to the front of the strip — after the tabs
|
|
1597
|
+
* already pinned — and its close control becomes an unpin control (guarding against accidental
|
|
1598
|
+
* close). Pinning also promotes a preview tab (a pinned tab is never transient). No-op if the tab is
|
|
1599
|
+
* not open.
|
|
1600
|
+
*/
|
|
1601
|
+
pin(path: string): void;
|
|
1602
|
+
/**
|
|
1603
|
+
* Unpins the tab rooted at `path` — it returns to a normal, closable tab and takes the
|
|
1604
|
+
* first seat after the tabs still pinned. No-op otherwise.
|
|
1605
|
+
*/
|
|
1606
|
+
unpin(path: string): void;
|
|
1607
|
+
/** Closes every dynamic tab in the strip except the target — pinned and unclosable tabs are kept. */
|
|
1608
|
+
closeOthers(path: string): void;
|
|
1609
|
+
/** Closes every dynamic tab in the strip — pinned and unclosable tabs are kept. */
|
|
1610
|
+
closeAll(): void;
|
|
1611
|
+
/** Closes the dynamic tabs that render after the target in the strip — pinned tabs are kept. */
|
|
1612
|
+
closeToRight(path: string): void;
|
|
1613
|
+
/**
|
|
1614
|
+
* Closes the **primary (URL) pane** of a split — the pane-toolbar "Close pane" on the URL pane: the
|
|
1615
|
+
* primary leaf collapses, a neighbour is promoted to URL pane and navigated to. Guarded like every
|
|
1616
|
+
* other user-initiated close: unsaved changes in any of the primary group's tabs run
|
|
1617
|
+
* the host's Save · Discard · Cancel dialog first.
|
|
1618
|
+
*/
|
|
1619
|
+
closePrimaryPane(): void;
|
|
1620
|
+
/**
|
|
1621
|
+
* Closes a dynamic tab (by any path under its root). If it was active, we navigate to a neighbour
|
|
1622
|
+
* **first**, then evict the stored instance (keyed by the tab root — see {@link ContentReuseStrategy});
|
|
1623
|
+
* a background tab is evicted immediately.
|
|
1624
|
+
*/
|
|
1625
|
+
close(path: string): void;
|
|
1626
|
+
/**
|
|
1627
|
+
* Runs (and clears) the close hook of the tab rooted at `path` — for a close that happens **outside**
|
|
1628
|
+
* the URL group (a moved tab closed in another pane). No-op while the tab is still open
|
|
1629
|
+
* in the URL group (its own {@link close} will run the hook).
|
|
1630
|
+
*/
|
|
1631
|
+
runCloseHook(path: string): void;
|
|
1632
|
+
/**
|
|
1633
|
+
* The URL pane's neighbour of the tab rooted at `path` — where the URL goes when that tab **leaves**
|
|
1634
|
+
* the pane (moved away) or closes: the last remaining sibling, else home (`''` — the
|
|
1635
|
+
* "no file is open" default).
|
|
1636
|
+
*/
|
|
1637
|
+
neighbourOf(path: string): string;
|
|
1638
|
+
private repin;
|
|
1639
|
+
private replacePreviewSlot;
|
|
1640
|
+
private refineElsewhere;
|
|
1641
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ContentTabsService, never>;
|
|
1642
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<ContentTabsService>;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/** What the host knows when it works out where a following tab should point. */
|
|
1646
|
+
interface TabAddressInput {
|
|
1647
|
+
/** The following surface's id, when it declared one. */
|
|
1648
|
+
readonly surfaceId?: string;
|
|
1649
|
+
/** That surface's route pattern, e.g. `cedents/:cedentId/programs/:programId/treaties`. */
|
|
1650
|
+
readonly pattern: string;
|
|
1651
|
+
/** Parameter values of the address the user is on, by name. */
|
|
1652
|
+
readonly params: Readonly<Record<string, string>>;
|
|
1653
|
+
/** The full active content path, for a resolver that needs more than the names. */
|
|
1654
|
+
readonly activePath: string;
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* A distribution's own answer to "where should this following tab point?" — part of the mapping is
|
|
1658
|
+
* domain knowledge the platform cannot have (a query parameter on one tab deciding a path segment on
|
|
1659
|
+
* another). Return `null` to fall back to the host's substitution.
|
|
1660
|
+
*/
|
|
1661
|
+
type TabAddressResolver = (input: TabAddressInput) => string | null;
|
|
1662
|
+
/**
|
|
1663
|
+
* Overrides or extends how the host computes the address of a **following** tab
|
|
1664
|
+
* (`routable: { follows: true }`). The platform supplies the default — the parameter values of the
|
|
1665
|
+
* current address substituted by name into the tab's own pattern — and the distribution supplies the
|
|
1666
|
+
* domain knowledge on top; returning `null` keeps the default for that tab.
|
|
1667
|
+
*/
|
|
1668
|
+
declare function provideTabAddressResolver(resolve: TabAddressResolver): EnvironmentProviders;
|
|
1669
|
+
|
|
1670
|
+
interface Triggerable {
|
|
1671
|
+
readonly command?: string;
|
|
1672
|
+
run?(): void | Promise<void>;
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Executes commands by id — the single seam every trigger flows through:
|
|
1676
|
+
* rail/bar/view-action items, and (next) keybindings and the command palette. Keeping execution
|
|
1677
|
+
* here (not on the registry, which only stores) means one place resolves an id, one place fires the
|
|
1678
|
+
* behaviour, and one place reports a failure.
|
|
1679
|
+
*/
|
|
1680
|
+
declare class CommandService {
|
|
1681
|
+
private readonly registry;
|
|
1682
|
+
private readonly auth;
|
|
1683
|
+
private readonly errors;
|
|
1684
|
+
private readonly shortcuts;
|
|
1685
|
+
private readonly inPopout;
|
|
1686
|
+
/** The registered commands — the source list for a command palette. */
|
|
1687
|
+
readonly commands: Signal<readonly Command[]>;
|
|
1688
|
+
/**
|
|
1689
|
+
* Whether this command can run **here and now**: the session meets its `access`, and
|
|
1690
|
+
* this window is one it belongs in — a pop-out offers only commands that declare `popout`, since it
|
|
1691
|
+
* has no content area, rail or sidebar for the rest to reach. A palette uses this to filter.
|
|
1692
|
+
*/
|
|
1693
|
+
available(command: Command): boolean;
|
|
1694
|
+
/**
|
|
1695
|
+
* The command's keyboard chord, formatted for display and OS-correct (`'⌘K'` / `'Ctrl+K'`), or
|
|
1696
|
+
* `undefined` where it has none — **or where this distribution has switched shortcuts off**, so a
|
|
1697
|
+
* hint never promises a key that does nothing. Every place the shell prints a chord (menu entry,
|
|
1698
|
+
* palette row, bar button) asks here.
|
|
1699
|
+
*/
|
|
1700
|
+
shortcutOf(command: Command | undefined): string | undefined;
|
|
1701
|
+
/**
|
|
1702
|
+
* Runs the command with this id; a no-op (with a warning) if none is registered. Blocks — the one
|
|
1703
|
+
* choke point every trigger flows through (keybinding, palette, item) — when the session does not
|
|
1704
|
+
* meet the command's `access`. An optional {@link MenuContext} is forwarded to
|
|
1705
|
+
* `command.run(context)` — how a menu tells `shell.tab.closeOthers` which tab.
|
|
1706
|
+
*/
|
|
1707
|
+
execute(id: string, context?: MenuContext): void;
|
|
1708
|
+
/**
|
|
1709
|
+
* Fires a resolved command's behaviour — the one place that happens, whatever triggered it — and
|
|
1710
|
+
* reports a failure the way every trigger already does. Answers what the command returned, and
|
|
1711
|
+
* rejects with what it threw, so a caller that has to tell a failure from an answer can.
|
|
1712
|
+
*/
|
|
1713
|
+
run(command: Command, context?: MenuContext, args?: CommandArguments): Promise<unknown>;
|
|
1714
|
+
/**
|
|
1715
|
+
* Whether this item's trigger leads anywhere: an inline callback, or a command that is actually
|
|
1716
|
+
* registered. A rail or bar item naming a command nobody registered would render as a dead button
|
|
1717
|
+
* — the same orphan a menu entry can be, and the host drops both rather than draw them.
|
|
1718
|
+
*/
|
|
1719
|
+
triggerable(item: Triggerable): boolean;
|
|
1720
|
+
/** Fires a UI item's trigger: its bound command if it names one, else its inline callback. */
|
|
1721
|
+
trigger(item: Triggerable): void;
|
|
1722
|
+
private invoke;
|
|
1723
|
+
private onError;
|
|
1724
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CommandService, never>;
|
|
1725
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<CommandService>;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* Binds command shortcuts to the keyboard. Bindings are *derived* from the registered
|
|
1730
|
+
* commands' `shortcut` (no separate registration path) and rebuild reactively as commands come and
|
|
1731
|
+
* go. One global `keydown` listener resolves the pressed chord to a command id and fires it through
|
|
1732
|
+
* the {@link CommandService} — the same seam a click uses. User rebinding is deferred.
|
|
1733
|
+
*/
|
|
1734
|
+
declare class KeybindingService {
|
|
1735
|
+
private readonly registry;
|
|
1736
|
+
private readonly commands;
|
|
1737
|
+
private readonly document;
|
|
1738
|
+
private readonly destroyRef;
|
|
1739
|
+
private readonly injector;
|
|
1740
|
+
private readonly enabled;
|
|
1741
|
+
private readonly isMac;
|
|
1742
|
+
private started;
|
|
1743
|
+
private readonly build;
|
|
1744
|
+
private readonly bindings;
|
|
1745
|
+
/** Attaches the global listener once; auto-detached on destroy. No-op where shortcuts are off. */
|
|
1746
|
+
start(): void;
|
|
1747
|
+
private onKeydown;
|
|
1748
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<KeybindingService, never>;
|
|
1749
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<KeybindingService>;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
/**
|
|
1753
|
+
* Formats a keyboard chord for display, OS-correct: `formatChord('mod+k')` → `'⌘K'` on macOS,
|
|
1754
|
+
* `'Ctrl+K'` elsewhere. `mod` maps to ⌘/Ctrl, `alt` to ⌥/Alt, `shift` to ⇧/Shift, and so on — the
|
|
1755
|
+
* same token vocabulary a {@link Command.shortcut} uses. Lets a distribution or weaver show a
|
|
1756
|
+
* shortcut anywhere without duplicating the platform detection the shell already does internally.
|
|
1757
|
+
*/
|
|
1758
|
+
declare function formatChord(chord: string): string;
|
|
1759
|
+
|
|
1760
|
+
/** Where the built command-palette entry goes (LWF-05). Defaults: top bar, end slot, order 5. */
|
|
1761
|
+
interface CommandPaletteEntryOptions {
|
|
1762
|
+
/**
|
|
1763
|
+
* Target Bar region id. Default `'top-bar'`. The badge adapts to the bar it lands in: a top bar is
|
|
1764
|
+
* a fixed band, so there it pins the shared bar-control height and lines up with the theme and
|
|
1765
|
+
* language controls; a bottom bar takes the height of its tallest item, so there it renders like a
|
|
1766
|
+
* plain bar item rather than growing the bar and costing the content area.
|
|
1767
|
+
*/
|
|
1768
|
+
readonly bar?: string;
|
|
1769
|
+
/** Which Bar slot the entry renders in. Default `'end'`. */
|
|
1770
|
+
readonly slot?: BarSlot;
|
|
1771
|
+
/** Lower renders first within the slot. Default `5` (left of the built update/language/theme items). */
|
|
1772
|
+
readonly order?: number;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Places a built command-palette entry in a Bar (LWF-05) — a badge-styled affordance (search icon +
|
|
1776
|
+
* the palette's OS-correct shortcut) that opens `shell.commandPalette`, correct-by-construction and
|
|
1777
|
+
* without a distribution component. Opt-in: omit it for a palette that opens only by shortcut. Uses the
|
|
1778
|
+
* `shell.commandPaletteEntry` bar-item id, so `provideShell({ omit: ['shell.commandPaletteEntry'] })`
|
|
1779
|
+
* removes it.
|
|
1780
|
+
*/
|
|
1781
|
+
declare function provideCommandPaletteEntry(options?: CommandPaletteEntryOptions): EnvironmentProviders;
|
|
1782
|
+
|
|
1783
|
+
/** Multi-provider token: each contribution adds one plugin to load. */
|
|
1784
|
+
declare const PLUGIN: InjectionToken<readonly Plugin[]>;
|
|
1785
|
+
/**
|
|
1786
|
+
* Activates the registered plugins. Trusted in-process runtime (the lowest isolation
|
|
1787
|
+
* rung); this abstraction lets a sandboxed runtime slot in later
|
|
1788
|
+
* without touching plugins or the host.
|
|
1789
|
+
*/
|
|
1790
|
+
declare class PluginRuntime {
|
|
1791
|
+
private readonly grants;
|
|
1792
|
+
private readonly enablement;
|
|
1793
|
+
private readonly factory;
|
|
1794
|
+
private readonly injector;
|
|
1795
|
+
private readonly plugins;
|
|
1796
|
+
private readonly active;
|
|
1797
|
+
private started;
|
|
1798
|
+
activateAll(): void;
|
|
1799
|
+
/**
|
|
1800
|
+
* Unloads a plugin: undoes every contribution it made (via its context's tracked
|
|
1801
|
+
* disposables) and runs its optional `deactivate` hook. Idempotent — unknown/inactive
|
|
1802
|
+
* ids are a no-op. This is the counterpart to {@link activateAll} that keeps the `active`
|
|
1803
|
+
* map from being write-only (a real unload path, needed before an untrusted loader lands).
|
|
1804
|
+
*/
|
|
1805
|
+
deactivate(id: string): void;
|
|
1806
|
+
/** Unloads every active plugin (e.g. on teardown). */
|
|
1807
|
+
deactivateAll(): void;
|
|
1808
|
+
private reconcile;
|
|
1809
|
+
private activate;
|
|
1810
|
+
private onActivationError;
|
|
1811
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PluginRuntime, never>;
|
|
1812
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<PluginRuntime>;
|
|
1813
|
+
}
|
|
1814
|
+
/** A distribution registers its plugins; they are activated eagerly at startup. */
|
|
1815
|
+
declare function providePlugins(...plugins: Plugin[]): (Provider | EnvironmentProviders)[];
|
|
1816
|
+
|
|
1817
|
+
/**
|
|
1818
|
+
* How much the browser holds a frame plugin back. **Isolated** strips the frame of an origin, which
|
|
1819
|
+
* is what denies it the hosting document, any storage and any session the browser would carry for
|
|
1820
|
+
* it. **Embedded** lets it keep an origin, and with it whatever the browser grants that origin — a
|
|
1821
|
+
* separation of deployments rather than of privileges.
|
|
1822
|
+
*
|
|
1823
|
+
* Isolated is the default: a plugin whose level was never stated runs isolated.
|
|
1824
|
+
*/
|
|
1825
|
+
type PluginIsolationLevel = 'isolated' | 'embedded';
|
|
1826
|
+
|
|
1827
|
+
/**
|
|
1828
|
+
* A **sandboxed** plugin a distribution registers: its code is not an in-process
|
|
1829
|
+
* {@link Plugin} object but a URL to an isolated document, loaded into an `<iframe sandbox>` and given
|
|
1830
|
+
* `ctx` over RPC. Contrast {@link providePlugins} (trusted, in-process).
|
|
1831
|
+
*/
|
|
1832
|
+
interface FramePlugin {
|
|
1833
|
+
/** Stable plugin id — the same id the distribution grants capabilities to (default-deny). */
|
|
1834
|
+
readonly id: string;
|
|
1835
|
+
/**
|
|
1836
|
+
* What the workbench calls this plugin where it names it to the user — the permissions surface,
|
|
1837
|
+
* the plugin list. Omit it and the id is shown, which is a poor name but a correct one: nothing is
|
|
1838
|
+
* derived from it. Grants, collisions and the user's stored decisions all follow {@link id}, never
|
|
1839
|
+
* this, so naming a plugin changes what is read and nothing else.
|
|
1840
|
+
*/
|
|
1841
|
+
readonly name?: string;
|
|
1842
|
+
/** URL of the plugin's entry document (served by the distribution, e.g. `/my-plugin/plugin.html`). */
|
|
1843
|
+
readonly entryUrl: string;
|
|
1844
|
+
/** Capabilities the plugin declares it needs; the distribution still has to grant them. */
|
|
1845
|
+
readonly capabilities?: readonly Capability[];
|
|
1846
|
+
/**
|
|
1847
|
+
* Origins this plugin's own surfaces may be served from, beyond the application's own — the seam
|
|
1848
|
+
* refuses anything else, and refuses an address that would execute or carry its content inline at
|
|
1849
|
+
* any level. Omit it and the application's own origin is the only one, which is the right answer
|
|
1850
|
+
* for a plugin whose files the distribution serves itself.
|
|
1851
|
+
*
|
|
1852
|
+
* A sibling subdomain belongs here: it is what gives an embedded application its own storage and
|
|
1853
|
+
* keeps it out of the hosting document, while a session cookie scoped to the shared domain still
|
|
1854
|
+
* reaches it.
|
|
1855
|
+
*/
|
|
1856
|
+
readonly origins?: readonly string[];
|
|
1857
|
+
/**
|
|
1858
|
+
* How much the browser holds this plugin back. Omitted means
|
|
1859
|
+
* {@link PluginIsolationLevel `'isolated'`} — the frame is stripped of an origin and reaches
|
|
1860
|
+
* neither the hosting document nor any storage. `'embedded'` lets it keep an origin, which is what
|
|
1861
|
+
* a first-party application composed for its own deployment needs and what a plugin you did not
|
|
1862
|
+
* write must never be given.
|
|
1863
|
+
*/
|
|
1864
|
+
readonly level?: PluginIsolationLevel;
|
|
1865
|
+
}
|
|
1866
|
+
/** Multi-provider token: each contribution adds one sandboxed plugin to load. */
|
|
1867
|
+
declare const FRAME_PLUGIN: InjectionToken<readonly FramePlugin[]>;
|
|
1868
|
+
/**
|
|
1869
|
+
* Second {@link PluginRuntime} implementation:
|
|
1870
|
+
* runs each plugin in an isolated `<iframe sandbox="allow-scripts">` and hands it `ctx` over **Penpal**
|
|
1871
|
+
* RPC. The RPC endpoints are backed by the **same** {@link HostPluginContext} the trusted runtime uses,
|
|
1872
|
+
* so the default-deny capability broker enforces grants identically — the isolation and
|
|
1873
|
+
* transport change, the broker does not. Data-oriented `ctx` calls (`registerRoute({iframe})`, `toast`)
|
|
1874
|
+
* serialise across the boundary; the reserved Angular-only surface (`component`) never crosses it.
|
|
1875
|
+
*
|
|
1876
|
+
* Activation reconciles against the union of three sets: the composed {@link FramePlugin} list,
|
|
1877
|
+
* what the operator deployed through the catalog, and what the user installed. Installing spawns a
|
|
1878
|
+
* plugin live, uninstalling unloads it, and a catalog that stops carrying a deployed entry unloads
|
|
1879
|
+
* that one — all without a reload. Authority decides an id collision: composed wins over deployed,
|
|
1880
|
+
* and deployed wins over installed, because a deployed entry holds exactly what it names and a
|
|
1881
|
+
* user's consent cannot narrow what the operator issued.
|
|
1882
|
+
*/
|
|
1883
|
+
declare class FramePluginRuntime {
|
|
1884
|
+
private readonly grants;
|
|
1885
|
+
private readonly enablement;
|
|
1886
|
+
private readonly install;
|
|
1887
|
+
private readonly deployment;
|
|
1888
|
+
private readonly isolation;
|
|
1889
|
+
private readonly catalogCap;
|
|
1890
|
+
private readonly refusals;
|
|
1891
|
+
private readonly store;
|
|
1892
|
+
private readonly sync;
|
|
1893
|
+
private readonly factory;
|
|
1894
|
+
private readonly injector;
|
|
1895
|
+
private readonly plugins;
|
|
1896
|
+
private readonly instances;
|
|
1897
|
+
private started;
|
|
1898
|
+
activateAll(): void;
|
|
1899
|
+
/**
|
|
1900
|
+
* Unloads one sandboxed plugin: closes the RPC connection, removes the frame, disposes `ctx`
|
|
1901
|
+
* (undoing its contributions). Idempotent — unknown/inactive ids are a no-op; the counterpart
|
|
1902
|
+
* to the trusted runtime's `deactivate(id)`.
|
|
1903
|
+
*/
|
|
1904
|
+
deactivate(id: string): void;
|
|
1905
|
+
/** Tears down every sandboxed plugin (e.g. on teardown). */
|
|
1906
|
+
deactivateAll(): void;
|
|
1907
|
+
private reconcile;
|
|
1908
|
+
private runnablePlugins;
|
|
1909
|
+
private dropUninstalled;
|
|
1910
|
+
private spawn;
|
|
1911
|
+
private createFrame;
|
|
1912
|
+
private reportingRefusals;
|
|
1913
|
+
private rpcMethods;
|
|
1914
|
+
private watchState;
|
|
1915
|
+
private notifyState;
|
|
1916
|
+
private notifyTabClosed;
|
|
1917
|
+
private notifySettings;
|
|
1918
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<FramePluginRuntime, never>;
|
|
1919
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<FramePluginRuntime>;
|
|
1920
|
+
}
|
|
1921
|
+
/** A distribution registers its sandboxed plugins; they are activated eagerly at startup. */
|
|
1922
|
+
declare function provideFramePlugins(...plugins: FramePlugin[]): (Provider | EnvironmentProviders)[];
|
|
1923
|
+
|
|
1924
|
+
interface CommandInvoker {
|
|
1925
|
+
invocable(callerId: string, granted: boolean): readonly InvocableCommand[];
|
|
1926
|
+
invoke(callerId: string, granted: boolean, id: string, args?: CommandArguments): Promise<CommandOutcome>;
|
|
1927
|
+
}
|
|
1928
|
+
declare const COMMAND_INVOKER: InjectionToken<CommandInvoker>;
|
|
1929
|
+
|
|
1930
|
+
declare class CommandInvocationService implements CommandInvoker {
|
|
1931
|
+
private readonly commands;
|
|
1932
|
+
private readonly registry;
|
|
1933
|
+
private readonly errors;
|
|
1934
|
+
private readonly transloco;
|
|
1935
|
+
private depth;
|
|
1936
|
+
invocable(callerId: string, granted: boolean): readonly InvocableCommand[];
|
|
1937
|
+
invoke(callerId: string, granted: boolean, id: string, args?: CommandArguments): Promise<CommandOutcome>;
|
|
1938
|
+
private run;
|
|
1939
|
+
private answerOf;
|
|
1940
|
+
private reachable;
|
|
1941
|
+
private reportRefusal;
|
|
1942
|
+
private describe;
|
|
1943
|
+
private text;
|
|
1944
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CommandInvocationService, never>;
|
|
1945
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<CommandInvocationService>;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/** Capabilities granted per plugin id. A missing id means "no grant" → default-deny. */
|
|
1949
|
+
type CapabilityGrants = Readonly<Record<string, readonly Capability[]>>;
|
|
1950
|
+
/**
|
|
1951
|
+
* The active capability grants. Defaults to **empty** (default-deny — no plugin can do anything
|
|
1952
|
+
* until granted). A distribution overrides it via {@link provideCapabilityGrants}; a product can
|
|
1953
|
+
* later feed per-tenant grants from its own backend behind the same seam.
|
|
1954
|
+
*/
|
|
1955
|
+
declare const CAPABILITY_GRANTS: InjectionToken<Readonly<Record<string, readonly ("contributions" | "ui" | "host" | "navigation" | "session" | "theme" | "automation")[]>>>;
|
|
1956
|
+
/**
|
|
1957
|
+
* A distribution declares which plugin gets which capabilities. The composition root is the
|
|
1958
|
+
* authoritative grant source — the honest default-deny model, dogfooded by the first-party plugin
|
|
1959
|
+
* like a third-party one would be. A product backend can replace it behind the same seam.
|
|
1960
|
+
*/
|
|
1961
|
+
declare function provideCapabilityGrants(grants: CapabilityGrants): Provider;
|
|
1962
|
+
/**
|
|
1963
|
+
* The effective capability set for one plugin: what the distribution granted, **intersected** with
|
|
1964
|
+
* what the plugin declares ("plugin declares, distribution grants"). A grant for an
|
|
1965
|
+
* undeclared capability is inert, so least privilege holds in both directions; dev mode flags the
|
|
1966
|
+
* mismatch so the composition stays honest. A plugin without a declaration keeps its grants as-is
|
|
1967
|
+
* (declaring is optional today; the manifest schema hardens this later).
|
|
1968
|
+
*/
|
|
1969
|
+
declare function effectiveCapabilities(pluginId: string, granted: readonly Capability[] | undefined, declared: readonly Capability[] | undefined): ReadonlySet<Capability>;
|
|
1970
|
+
|
|
1971
|
+
/** One capability as shown in the permissions surface: whether the user currently keeps it on. */
|
|
1972
|
+
interface PluginCapabilityState {
|
|
1973
|
+
readonly capability: Capability;
|
|
1974
|
+
readonly effective: boolean;
|
|
1975
|
+
}
|
|
1976
|
+
/** A plugin and the base-granted capabilities the user can manage for it. */
|
|
1977
|
+
interface PluginPermissions {
|
|
1978
|
+
readonly pluginId: string;
|
|
1979
|
+
readonly capabilities: readonly PluginCapabilityState[];
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* The live capability broker. The distribution's static grants (the platform ships no server, so
|
|
1983
|
+
* the composition root — later a per-tenant backend behind the same seam —
|
|
1984
|
+
* is the grant source) form each plugin's **base** set (grant ∩ declaration). On top of that the user can
|
|
1985
|
+
* **revoke** a granted capability at runtime; the decision is user-local and persisted through the
|
|
1986
|
+
* {@link SETTINGS_STORE}, exactly like the rest of the chrome state. Enforcement in
|
|
1987
|
+
* {@link HostPluginContext} consults {@link isGranted} on every `ctx` call, so a revocation takes effect
|
|
1988
|
+
* on the plugin's next call without a reload — the honest default-deny model made transparent and
|
|
1989
|
+
* user-controllable (a "Permissions" settings surface), never wider than the distribution allowed.
|
|
1990
|
+
*/
|
|
1991
|
+
declare class CapabilityGrantService {
|
|
1992
|
+
private readonly store;
|
|
1993
|
+
private readonly sync;
|
|
1994
|
+
private readonly grants;
|
|
1995
|
+
private readonly revoked;
|
|
1996
|
+
private readonly bases;
|
|
1997
|
+
/** The plugins and their base-granted capabilities, for the permissions settings surface. */
|
|
1998
|
+
readonly permissions: i0.Signal<readonly PluginPermissions[]>;
|
|
1999
|
+
constructor();
|
|
2000
|
+
/**
|
|
2001
|
+
* Records a plugin's base grant (grant ∩ declaration) so enforcement and the permissions surface know
|
|
2002
|
+
* about it. A runtime calls this once when it activates a plugin. `granted` overrides the
|
|
2003
|
+
* distribution's static grant source — used for user-installed plugins, whose grant is the user's
|
|
2004
|
+
* install consent instead of the composition root.
|
|
2005
|
+
*/
|
|
2006
|
+
register(pluginId: string, declared: readonly Capability[] | undefined, granted?: readonly Capability[]): void;
|
|
2007
|
+
/** Drops a plugin from the permissions surface (its runtime deactivated it). Idempotent. */
|
|
2008
|
+
unregister(pluginId: string): void;
|
|
2009
|
+
/** Whether `pluginId` may use `capability` right now: base-granted and not user-revoked. */
|
|
2010
|
+
isGranted(pluginId: string, capability: Capability): boolean;
|
|
2011
|
+
/**
|
|
2012
|
+
* Whether the distribution granted `capability` to `pluginId` (grant ∩ declaration), **ignoring** any
|
|
2013
|
+
* user revocation. Used only while a plugin activates — a revocation must not stop it from loading and
|
|
2014
|
+
* registering its contributions; it applies to the plugin's runtime `ctx` calls afterwards.
|
|
2015
|
+
*/
|
|
2016
|
+
isBaseGranted(pluginId: string, capability: Capability): boolean;
|
|
2017
|
+
/** Turns a base-granted capability on or off for a plugin (user revoke / restore). Persisted. */
|
|
2018
|
+
setGranted(pluginId: string, capability: Capability, granted: boolean): void;
|
|
2019
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CapabilityGrantService, never>;
|
|
2020
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<CapabilityGrantService>;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/** A plugin as shown in the permissions surface: its id, display name and whether it is enabled. */
|
|
2024
|
+
interface PluginInfo {
|
|
2025
|
+
readonly id: string;
|
|
2026
|
+
readonly name: string;
|
|
2027
|
+
readonly enabled: boolean;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
/**
|
|
2031
|
+
* Whether each plugin is turned on — plugin enable/disable, distinct from capability revocation. Disabling a plugin is coarse: it does not restrict a power, it unloads the
|
|
2032
|
+
* whole plugin so **none** of its contributions appear; enabling loads it again. The decision is
|
|
2033
|
+
* user-local and persisted through the {@link SETTINGS_STORE}, exactly like the rest of the chrome state.
|
|
2034
|
+
*
|
|
2035
|
+
* This service holds only the state; the runtimes ({@link PluginRuntime}, {@link FramePluginRuntime})
|
|
2036
|
+
* inject it and reconcile activation against {@link disabled} reactively, so a toggle takes effect at
|
|
2037
|
+
* once without a reload. It never depends on the runtimes, which keeps the dependency one-way.
|
|
2038
|
+
*/
|
|
2039
|
+
declare class PluginEnablementService {
|
|
2040
|
+
private readonly store;
|
|
2041
|
+
private readonly sync;
|
|
2042
|
+
private readonly disabledSet;
|
|
2043
|
+
private readonly names;
|
|
2044
|
+
/** The disabled plugin ids (reactive) — a runtime reconciles activation against this. */
|
|
2045
|
+
readonly disabled: i0.Signal<ReadonlySet<string>>;
|
|
2046
|
+
/** Every known plugin with its enabled state, for the permissions settings surface. */
|
|
2047
|
+
readonly plugins: i0.Signal<readonly PluginInfo[]>;
|
|
2048
|
+
constructor();
|
|
2049
|
+
/** Records a plugin so the permissions surface can list it (enabled or not). Idempotent. A runtime calls it for every plugin it knows. */
|
|
2050
|
+
register(id: string, name: string): void;
|
|
2051
|
+
/** Drops a plugin from the list — it was uninstalled, not merely disabled. Idempotent. */
|
|
2052
|
+
unregister(id: string): void;
|
|
2053
|
+
/** Whether `id` is currently enabled (default: yes — a plugin is on until the user turns it off). */
|
|
2054
|
+
isEnabled(id: string): boolean;
|
|
2055
|
+
/** Turns a whole plugin on or off (persisted). The runtimes react by loading/unloading it. */
|
|
2056
|
+
setEnabled(id: string, enabled: boolean): void;
|
|
2057
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PluginEnablementService, never>;
|
|
2058
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<PluginEnablementService>;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
/**
|
|
2062
|
+
* A community plugin the user installed from the distribution's catalog. Plain data — the
|
|
2063
|
+
* same shape the sandbox runtime needs to spawn it, plus display metadata for the store and
|
|
2064
|
+
* permissions surfaces.
|
|
2065
|
+
*/
|
|
2066
|
+
interface InstalledPlugin {
|
|
2067
|
+
/** Stable plugin id — also the id capabilities are granted to (default-deny). */
|
|
2068
|
+
readonly id: string;
|
|
2069
|
+
/** Display name shown in the store and permissions surfaces. */
|
|
2070
|
+
readonly name: string;
|
|
2071
|
+
/** Same-origin URL of the plugin's entry document. */
|
|
2072
|
+
readonly entryUrl: string;
|
|
2073
|
+
/**
|
|
2074
|
+
* Capabilities the plugin declares. Accepting the install dialog grants exactly these — the user's
|
|
2075
|
+
* consent replaces the composition root's grant for installed plugins.
|
|
2076
|
+
*/
|
|
2077
|
+
readonly capabilities?: readonly Capability[];
|
|
2078
|
+
/**
|
|
2079
|
+
* Catalog version at install/update time. Drives update detection (a strictly newer catalog
|
|
2080
|
+
* version offers an update) and is part of the runtime's respawn signature — a version-only
|
|
2081
|
+
* change respawns the running plugin.
|
|
2082
|
+
*/
|
|
2083
|
+
readonly version?: string;
|
|
2084
|
+
/** Same-origin URL of the plugin's list icon (an image the operator ships with the plugin). */
|
|
2085
|
+
readonly iconUrl?: string;
|
|
2086
|
+
/**
|
|
2087
|
+
* The level this entry asks to run at. It is a request, not a decision: the composition sets the
|
|
2088
|
+
* highest level a catalog may confer, an entry at or below it runs at what it asked for, and one
|
|
2089
|
+
* above it is refused rather than quietly run lower. Omitted means isolated.
|
|
2090
|
+
*/
|
|
2091
|
+
readonly level?: PluginIsolationLevel;
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* One entry of the distribution's plugin catalog: an {@link InstalledPlugin} plus the
|
|
2095
|
+
* display metadata the store dialog shows (the Obsidian community-plugins model — list of name,
|
|
2096
|
+
* author, downloads, last update and description; a detail pane rendering the plugin's README
|
|
2097
|
+
* **in-app**, never an embedded external page).
|
|
2098
|
+
*/
|
|
2099
|
+
interface PluginCatalogEntry extends InstalledPlugin {
|
|
2100
|
+
/** Short description shown in the store list. */
|
|
2101
|
+
readonly description?: string;
|
|
2102
|
+
/** Icon name for the install-consent dialog — resolved by the host icon registry. */
|
|
2103
|
+
readonly icon?: string;
|
|
2104
|
+
/** Operator-curated category, shown as a badge and matched by the store search. */
|
|
2105
|
+
readonly category?: string;
|
|
2106
|
+
/** Plugin author, shown in the list and the detail pane. */
|
|
2107
|
+
readonly author?: string;
|
|
2108
|
+
/** Install/download count, display-only (the operator's stats). */
|
|
2109
|
+
readonly downloads?: number;
|
|
2110
|
+
/** ISO date of the last update, display-only. */
|
|
2111
|
+
readonly updated?: string;
|
|
2112
|
+
/** External link to the plugin's repository/homepage — rendered as a plain link, never framed. */
|
|
2113
|
+
readonly repository?: string;
|
|
2114
|
+
/**
|
|
2115
|
+
* Same-origin URL of the plugin's README (Markdown) — the operator copies it into the store next
|
|
2116
|
+
* to the plugin files; the detail pane fetches and renders it sanitized in-app.
|
|
2117
|
+
*/
|
|
2118
|
+
readonly readmeUrl?: string;
|
|
2119
|
+
/**
|
|
2120
|
+
* `true` for a plugin the operator **deploys**: active for every user without a consent dialog,
|
|
2121
|
+
* holding exactly the capabilities this entry names, and gone again once the catalog stops
|
|
2122
|
+
* carrying it. Omitted or `false` means the entry is merely **offered** — the user browses it,
|
|
2123
|
+
* consents and installs it, which is the only path that grants anything on their say-so.
|
|
2124
|
+
*
|
|
2125
|
+
* The authority behind a deployed entry is the operator's, so the user is shown it but is not
|
|
2126
|
+
* asked about it and cannot remove it.
|
|
2127
|
+
*/
|
|
2128
|
+
readonly deployed?: boolean;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
/**
|
|
2132
|
+
* The user's installed community plugins. Holds only the state: which catalog entries the
|
|
2133
|
+
* user installed, persisted user-locally through the {@link SETTINGS_STORE} — a product that wants
|
|
2134
|
+
* tenant-wide or server-held installs implements that in its store backend, the seam does not change.
|
|
2135
|
+
* The {@link FramePluginRuntime} reconciles activation against {@link installed} reactively, so an
|
|
2136
|
+
* install spawns the plugin at once and an uninstall unloads it, both without a reload. It never
|
|
2137
|
+
* depends on the runtime, which keeps the dependency one-way (the {@link PluginEnablementService}
|
|
2138
|
+
* precedent).
|
|
2139
|
+
*/
|
|
2140
|
+
declare class PluginInstallService {
|
|
2141
|
+
private readonly store;
|
|
2142
|
+
private readonly sync;
|
|
2143
|
+
private readonly pluginState;
|
|
2144
|
+
private readonly entries;
|
|
2145
|
+
private composedIds;
|
|
2146
|
+
/** The installed plugins (reactive) — the sandbox runtime reconciles against this. */
|
|
2147
|
+
readonly installed: i0.Signal<readonly InstalledPlugin[]>;
|
|
2148
|
+
constructor();
|
|
2149
|
+
/** Records the composition-time plugin ids so an install can never shadow a composed plugin. */
|
|
2150
|
+
markComposed(ids: readonly string[]): void;
|
|
2151
|
+
isInstalled(id: string): boolean;
|
|
2152
|
+
/** The installed entry for an id, or `undefined` — the baseline an update is compared against. */
|
|
2153
|
+
find(id: string): InstalledPlugin | undefined;
|
|
2154
|
+
/**
|
|
2155
|
+
* Installs a catalog entry after the user consented to its declared capabilities. Fail-fast: the
|
|
2156
|
+
* `entryUrl` must be same-origin (the store is the distribution's own origin) and the id
|
|
2157
|
+
* must be neither composed nor already installed. Persisted; the runtime spawns the plugin live.
|
|
2158
|
+
*/
|
|
2159
|
+
install(plugin: InstalledPlugin): void;
|
|
2160
|
+
/**
|
|
2161
|
+
* Replaces an installed entry with a newer catalog entry (the update flow).
|
|
2162
|
+
* Same fail-fast validation as {@link install}, except the id must already be installed. The
|
|
2163
|
+
* persisted entry carries the plugin's capabilities, so a declaration that grew must be consented
|
|
2164
|
+
* to before this is called — see `confirmUpdate`. The runtime respawns the plugin live.
|
|
2165
|
+
*/
|
|
2166
|
+
update(plugin: InstalledPlugin): void;
|
|
2167
|
+
/** Removes an installed plugin (idempotent); the runtime unloads it live. */
|
|
2168
|
+
/**
|
|
2169
|
+
* Removes an installed plugin and **deletes its own store** — deliberately the
|
|
2170
|
+
* opposite of its settings section, which survives so a reinstall finds its configuration. An
|
|
2171
|
+
* abandoned draft of a plugin the user just removed is litter; a preference is not.
|
|
2172
|
+
*/
|
|
2173
|
+
uninstall(id: string): void;
|
|
2174
|
+
private validEntry;
|
|
2175
|
+
private persist;
|
|
2176
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PluginInstallService, never>;
|
|
2177
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<PluginInstallService>;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
/**
|
|
2181
|
+
* The distribution's plugin catalog port: the operator-curated list of community plugins a
|
|
2182
|
+
* user may install. The platform only defines the seam — what is not in the catalog does not exist
|
|
2183
|
+
* for the shell; per-tenant curation is the product backend answering the catalog request
|
|
2184
|
+
* tenant-dependently.
|
|
2185
|
+
*/
|
|
2186
|
+
interface PluginCatalog {
|
|
2187
|
+
load(): Promise<readonly PluginCatalogEntry[]>;
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Injection token for the {@link PluginCatalog}. No default — a distribution without a catalog has no
|
|
2191
|
+
* plugin store (and no store settings section). Provide one with {@link providePluginCatalog}.
|
|
2192
|
+
*/
|
|
2193
|
+
declare const PLUGIN_CATALOG: InjectionToken<PluginCatalog>;
|
|
2194
|
+
/**
|
|
2195
|
+
* A {@link PluginCatalog} that fetches a same-origin JSON document (an array of
|
|
2196
|
+
* {@link PluginCatalogEntry}). Entries are parsed defensively: junk shapes, foreign-origin
|
|
2197
|
+
* `entryUrl`s and unknown capability names are dropped. The catalog URL itself must be same-origin —
|
|
2198
|
+
* the store is the distribution's own origin; a product that wants another source
|
|
2199
|
+
* implements the {@link PluginCatalog} port directly.
|
|
2200
|
+
*/
|
|
2201
|
+
declare function urlPluginCatalog(url: string): PluginCatalog;
|
|
2202
|
+
|
|
2203
|
+
/** Options for {@link providePluginCatalog}. */
|
|
2204
|
+
interface PluginCatalogOptions {
|
|
2205
|
+
/**
|
|
2206
|
+
* Transloco key (or literal) for the store's settings-section title — brand the store per product
|
|
2207
|
+
* (e.g. `'product.marketplace'`). Defaults to the built-in `settings.pluginStore` ("Plugin store").
|
|
2208
|
+
*/
|
|
2209
|
+
readonly title?: string;
|
|
2210
|
+
/**
|
|
2211
|
+
* The highest level this catalog may confer on what it carries. Defaults to the strict one, so a
|
|
2212
|
+
* catalog can never hand out an embedded application unless the composition said it may. An entry
|
|
2213
|
+
* asking for more is refused rather than started lower — a plugin running below what it needs
|
|
2214
|
+
* fails in ways nobody can trace back to a line of configuration.
|
|
2215
|
+
*/
|
|
2216
|
+
readonly maxLevel?: PluginIsolationLevel;
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Wires the plugin store into a distribution: provides the catalog (a same-origin JSON URL
|
|
2220
|
+
* or a custom {@link PluginCatalog} implementation), makes sure the sandbox runtime is active even
|
|
2221
|
+
* when no plugin is composed statically, and registers the store's entry points — a settings section
|
|
2222
|
+
* (`setting:shell.pluginStore`, omit-able) whose Browse button opens the **store dialog** (the
|
|
2223
|
+
* Obsidian browse model: searchable list + in-app detail pane with README), plus the palette command
|
|
2224
|
+
* `shell.openPluginStore`. The title is brandable via {@link PluginCatalogOptions.title}. Installing
|
|
2225
|
+
* and uninstalling happens in the store; an installed plugin that declares its own settings gets its
|
|
2226
|
+
* own entry under the **Community plugins** nav group. Place after `provideShell()`.
|
|
2227
|
+
*/
|
|
2228
|
+
declare function providePluginCatalog(source: PluginCatalog | string, options?: PluginCatalogOptions): (Provider | EnvironmentProviders)[];
|
|
2229
|
+
|
|
2230
|
+
/**
|
|
2231
|
+
* Distribution-level icons: seed `name → SVG` into the module-global registry at bootstrap,
|
|
2232
|
+
* resolved by `<lw-icon>`. **The distribution wins:** naming one of the first-party icons replaces it
|
|
2233
|
+
* everywhere the chrome draws it, which is how a product re-skins the workbench; naming a new one adds it.
|
|
2234
|
+
* A *weaver* instead contributes at runtime via `ctx.contributeIcons` and can never shadow a name that is
|
|
2235
|
+
* already taken, so an installed plugin cannot repaint the chrome. Distribution icons are build-time and
|
|
2236
|
+
* trusted like the first-party set, so they are not re-sanitized.
|
|
2237
|
+
*
|
|
2238
|
+
* The key type suggests the shipped names while still accepting your own, so a typo in an intended
|
|
2239
|
+
* replacement shows up while writing it instead of silently adding a glyph nothing draws.
|
|
2240
|
+
*
|
|
2241
|
+
* These icons also travel into sandboxed surfaces, so a plugin drawing `<lw-icon name="trash">` shows
|
|
2242
|
+
* your glyph rather than ours.
|
|
2243
|
+
*/
|
|
2244
|
+
declare function provideIcons(icons: Readonly<Partial<Record<LoomIconName | (string & {}), string>>>): EnvironmentProviders;
|
|
2245
|
+
|
|
2246
|
+
/** The custom-element tag. */
|
|
2247
|
+
declare const LW_ICON_TAG = "lw-icon";
|
|
2248
|
+
/**
|
|
2249
|
+
* `<lw-icon name="add" size="1rem">` — the host icon primitive as a framework-agnostic custom element
|
|
2250
|
+
*: a plain `HTMLElement` that resolves a **name** to its SVG via the module-global icon
|
|
2251
|
+
* registry — no Angular DI, no `@ng-icons` runtime — so it works in a weaver body by tag, not
|
|
2252
|
+
* only in shell chrome. Light DOM: the SVG uses `currentColor`, so `text-*` tokens tint it for free.
|
|
2253
|
+
*
|
|
2254
|
+
* The SVG is already safe (first-party markup, or sanitized at registration), so it is set as raw
|
|
2255
|
+
* `innerHTML`. Decorative by default (`aria-hidden`); pass `aria-label` to expose it as an image.
|
|
2256
|
+
*
|
|
2257
|
+
* <lw-icon name="close" size="0.85rem"></lw-icon>
|
|
2258
|
+
* <lw-icon [name]="dynamicName()" aria-label="…"></lw-icon>
|
|
2259
|
+
*/
|
|
2260
|
+
declare class LwIconElement extends HTMLElement {
|
|
2261
|
+
static readonly observedAttributes: string[];
|
|
2262
|
+
get name(): string;
|
|
2263
|
+
set name(value: string | null);
|
|
2264
|
+
get size(): string;
|
|
2265
|
+
set size(value: string | null);
|
|
2266
|
+
connectedCallback(): void;
|
|
2267
|
+
attributeChangedCallback(): void;
|
|
2268
|
+
/**
|
|
2269
|
+
* Re-draws from the registry without changing the name. A sandboxed surface receives the product's
|
|
2270
|
+
* replacement icons over its channel, which can arrive after it has already painted.
|
|
2271
|
+
*/
|
|
2272
|
+
refresh(): void;
|
|
2273
|
+
private render;
|
|
2274
|
+
}
|
|
2275
|
+
/** Registers `<lw-icon>` once (idempotent) — called from {@link provideShell} at bootstrap. */
|
|
2276
|
+
declare function defineLwIcon(): void;
|
|
2277
|
+
|
|
2278
|
+
export { AUTH_SOURCE, AuthContext, BAR_ITEM, CAPABILITY_GRANTS, COMMAND_INVOKER, CapabilityGrantService, CommandInvocationService, CommandService, ContentTabsService, ContributionRegistry, DEFAULT_LAYOUT, DEFAULT_SHELL_FEATURES, DEVICE_LEVEL_KEYS, DialogOutlet, DialogService, FRAME_PLUGIN, FramePluginRuntime, KeybindingService, LW_BUTTON_TAG, LW_ICON_TAG, LW_MARKDOWN_TAG, LW_TOOLTIP_TAG, LocalStorageStore, LwButton, LwButtonElement, LwIconElement, LwMarkdownElement, LwSettingRow, LwSpinner, LwTooltipElement, LwVersion, NotificationService, PLUGIN, PLUGIN_CATALOG, PluginEnablementService, PluginInstallService, PluginRuntime, PopoutService, RAIL_ITEM, SETTINGS_STORE, SHELL_FEATURES, SHELL_LAYOUT, SettingsService, Shell, StateSyncService, TRANSLATION_NAMESPACES, TRANSLATION_OVERRIDES, ThemeService, ToastOutlet, UpdateBadge, UpdateService, VIEW, VersionService, WORKING_STATE_STORE, WORKSPACE_DEFINITIONS, defineLwButton, defineLwIcon, defineLwMarkdown, defineLwTooltip, effectiveCapabilities, formatChord, provideAuthSource, provideBarItems, provideCapabilityGrants, provideCommandPaletteEntry, provideFramePlugins, provideIcons, provideIdentityScopedStores, provideLayout, providePluginCatalog, providePlugins, provideRailItems, provideSettingsStore, provideShell, provideShellFeatures, provideShellRouter, provideTabAddressResolver, provideTranslationNamespaces, provideTranslationOverrides, provideUnauthorizedRedirect, provideViews, provideWorkingStateStore, provideWorkspaces, urlPluginCatalog };
|
|
2279
|
+
export type { ApplySyncedState, AuthSourceOptions, CapabilityGrants, CommandFeatures, CommandInvoker, CommandPaletteEntryOptions, ContentFeatures, ContentTabView, DialogButtonView, DialogInstance, DockPosition, FramePlugin, IdentityScopedStoreOptions, InstalledPlugin, KeyValueStore, LayoutRegion, LoomIconName, Notification, PluginCapabilityState, PluginCatalog, PluginCatalogEntry, PluginCatalogOptions, PluginInfo, PluginPermissions, QuickOpenTarget, RailFeatures, RegionType, RegisteredCommand, RegisteredContentRoute, RegisteredView, ResolvedTheme, RetentionDefault, ShellFeatures, ShellFeaturesInput, ShellLayout, ShellOptions, SidebarFeatures, SyncSource, TabAddressInput, TabAddressResolver, ThemeMode, TooltipPosition, UnauthorizedHandler, WindowFeatures, WorkspaceArea, WorkspaceAreaBase, WorkspaceColumnArea, WorkspaceDefinition, WorkspaceFeatures, WorkspaceRowArea, WorkspaceTab, WorkspaceTabArea, WorkspaceTabEntry };
|
|
2280
|
+
//# sourceMappingURL=loomweaver-shell.d.ts.map
|