import * as i0 from "@angular/core"; import { EnvironmentProviders, InjectionToken, Injector, Provider, Signal, Type, WritableSignal } from "@angular/core"; import { AccessRequirement, ActiveContent, AlertOptions, AlertOptions as AlertOptions$1, AuthSnapshot, BarButtonItem, BarComponentItem, BarItem, BarItem as BarItem$1, BarSlot, BarSlot as BarSlot$1, Capability, Command, CommandArguments, CommandOutcome, ConfirmOptions, ConfirmOptions as ConfirmOptions$1, ContentRoute, ContentTabLabel, DialogButton, DialogDismiss, DialogDismiss as DialogDismiss$1, DialogRef, DialogRef as DialogRef$1, DialogSize, DialogSize as DialogSize$1, DialogTone, DialogTone as DialogTone$1, Disposable, Disposable as Disposable$1, InvocableCommand, LwButtonSize, LwButtonSize as LwButtonSize$1, LwButtonVariant, LwButtonVariant as LwButtonVariant$1, MenuContext, MenuItem, NotificationAction, NotificationAction as NotificationAction$1, NotificationInput, NotificationInput as NotificationInput$1, NotificationKind, NotificationKind as NotificationKind$1, OpenOptions, OpenOptions as OpenOptions$1, OpenTabInput, PaneArea, PaneAreaBase, PaneColumnArea, PaneRowArea, PaneTabArea, Plugin, Plugin as Plugin$1, PluginContext, PluginHost, PluginManifest, PluginSession, PluginUi, ProgressHandle, ProgressHandle as ProgressHandle$1, ProgressOptions, ProgressOptions as ProgressOptions$1, PromptOptions, PromptOptions as PromptOptions$1, RailItem, RailItem as RailItem$1, RequireConfirmation, SelectOption, SettingButton, SettingComponent, SettingControl, SettingRow, SettingSelect, SettingSlider, SettingText, SettingToggle, SettingsSection, TabBadge, UiMenuItem, View, View as View$1, ViewAction, ViewAction as ViewAction$1 } from "@loomweaver/plugin-sdk"; import { Routes } from "@angular/router"; /** Where a region docks in the border topology. */ export type DockPosition = 'top' | 'bottom' | 'left' | 'right' | 'center'; /** Region anatomy types. */ export type RegionType = 'bar' | 'rail' | 'panel' | 'content'; /** * A side panel the distribution places into the border frame. It is the one kind of region the user * resizes, so it is the one kind that may declare its widths, in pixels. Each is optional and falls * back to the workbench's own (start 256, narrowest 180, widest 480). Those apply where the panel * stands beside the content; on a narrow viewport the panel is an overlay sized by `overlayWidth`. */ export interface PanelRegion { /** Stable id (slot targeting, collapse state, ordering). */ readonly id: string; readonly type: 'panel'; readonly dock: DockPosition; /** The width the panel shows until the user resizes it, and what resetting the layout returns to. */ readonly width?: number; /** The narrowest the panel may be made, by dragging, from the keyboard or from code. */ readonly minWidth?: number; /** The widest the panel may be made; a stored width above it is shown at this width. */ readonly maxWidth?: number; /** * The width of this panel when a narrow viewport presents it as an overlay, in pixels (default 288). * The workbench caps it at the viewport's width less a margin that leaves room to dismiss the * overlay. It is not dragged, stored or set from code, and the widths above do not reach it. */ readonly overlayWidth?: number; } /** A bar, rail or content region the distribution places into the border frame. */ export interface NonPanelRegion { /** Stable id (slot targeting, collapse state, ordering). */ readonly id: string; readonly type: Exclude; readonly dock: DockPosition; } /** One region the distribution places into the border frame. */ export type LayoutRegion = PanelRegion | NonPanelRegion; /** A distribution's declared base layout — the workbench renders it. */ export interface ShellLayout { readonly regions: readonly LayoutRegion[]; } /** * Bare default: a top Bar (header) over the Content-Area, with a status Bar under it. * * The status bar is not decoration — the shell's own default contributions include one that docks * there, and a default aimed at a region the default layout omits renders nothing and reports * nothing. Every region a shell default targets belongs here. */ export declare const DEFAULT_LAYOUT: ShellLayout; /** Active layout. Defaults to {@link DEFAULT_LAYOUT}; a distribution overrides it. */ export declare const SHELL_LAYOUT: InjectionToken; /** * A distribution declares its base layout with this provider. A panel region whose narrowest width * exceeds its widest, or whose start width lies outside its own bounds, is refused here, naming the * region. */ export declare function provideLayout(layout: ShellLayout): Provider; export declare class ViewportService { private readonly document; readonly compact: Signal; private watchCompact; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Opening a surface in its own browser window, and knowing whether this window **is** one. * * A pop-out boots the same app from a `popout/…` address under the application's base and renders * exactly one surface — no rail, no sidebars, no pane tree — so the main window stays the only * writer of the layout keys. The mode is read from the location once at startup: a pop-out never * becomes a main window, and the shell needs the answer before it renders anything. */ export declare class PopoutService { /** Whether this browser window is a pop-out. Fixed for the window's lifetime. */ readonly active: boolean; private readonly document; private readonly dialogs; private readonly base; /** * Opens `paneTarget` — a `view:` descriptor or a content-route path — in a new browser * window. The tab it came from **stays** where it is: this duplicates, it does not move. If the * pop-up blocker swallows the window, the user gets a dialog whose button is a fresh gesture that * practically always gets through. */ open(paneTarget: string): void; private offerToOpenAgain; private tryOpen; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** Layout host: renders the declared regions into the border topology. */ export declare class Shell { private readonly layout; private readonly panels; private readonly document; protected readonly viewport: ViewportService; protected readonly drawsToasts: boolean; protected readonly popout: PopoutService; protected readonly topBars: LayoutRegion[]; protected readonly bottomBars: LayoutRegion[]; protected readonly leftRails: LayoutRegion[]; protected readonly leftPanels: LayoutRegion[]; protected readonly rightPanels: LayoutRegion[]; protected readonly rightRails: LayoutRegion[]; protected readonly hasContent: boolean; protected readonly leftFloating: import("@angular/core").Signal; protected readonly rightFloating: import("@angular/core").Signal; protected readonly hasTopRow: import("@angular/core").Signal; protected readonly needsHeaderSpacer: import("@angular/core").Signal; protected skipToContent(event: Event): void; protected isOverlayOpen(regionId: string): boolean; protected anyOverlayOpen(): boolean; protected closeOverlay(): void; private floating; private regionsAt; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } type RetentionDefault = 'destroy' | 'retain'; type PaddingDefault = 'none' | 'inset'; /** Where the workbench shows its toasts: the edge of the window, then the side along it. */ type ToastPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'; /** Options a distribution can pass to {@link provideShell}. */ export interface ShellOptions { /** * Contribution ids to hide — a distribution drops a default it does not want (e.g. * `['shell.language']`). A **lasting** filter, not a one-time delete: an id a plugin * registers later stays hidden too. To *replace* a default instead of hiding it, register * your own contribution with the same id (last-in wins) and do **not** omit it. * * Covers **every** contribution kind. Chrome — commands, views, bar items, rail items — is * addressed by bare id. The other kinds carry a **prefix**, because ids from different kinds may * coincide and there is exactly one shared omit set: an unprefixed omit stays chrome-only and never * silently takes a same-named contribution of another kind with it (`shell.language`, for one, is * both a top-bar item and a settings row). * * - `menu:` — one menu entry, leaving the command itself (palette/shortcut) alive. * - `setting:` — a whole section (`'setting:shell.permissions'`) or a single row * (`'setting:shell.textSize'`); a section left without rows disappears. * - `route:` — a routable surface's content route. It leaves the tab strip, * the auto-open on deep-link and the pane target picker; its URL renders a neutral "not available * here" placeholder rather than falling back to home, so a shared deep-link explains itself. * Addressed by the surface **id**, while a route is *overridden* by its `path` (registering your * own surface on the same path wins) — two handles, deliberately, for two different operations. */ readonly omit?: readonly string[]; /** * Whether to register the application's service worker — `true` by default, and inert in dev * either way. The shell owns the registration so that the update badge, the update * toast and `ctx.host.checkForUpdate()` work in a distribution that only ships the build * artefacts; you do **not** add `provideServiceWorker` yourself. * * Pass `false` when your build emits no `ngsw-worker.js` (no `serviceWorker` option in the build * target). Registration would otherwise 404 in production and log a failure. Nothing else * changes: `UpdateService` injects `SwUpdate` optionally, so it simply reports * {@link UpdateService.enabled} as `false` and never offers an update. * * `@angular/service-worker` stays a peer dependency regardless — the opt-out removes the * registration, not the import. */ readonly serviceWorker?: boolean; /** * The app-wide retention default for **hidden** surfaces: `'destroy'` (the default) destroys a * hidden, clean surface, so state that must survive belongs in `VIEW_STATE`; `'retain'` keeps * every hidden instance alive at the price of memory growing with every surface ever shown. A * surface's own `retain: 'always' | 'never'` declaration wins over this default. `iframe` and * `container` surfaces are always rebuilt regardless. * * This is a storage policy for the developer, not a capability the user can see, which is why it * lives here rather than in `provideShellFeatures`. */ readonly retention?: RetentionDefault; /** * The app-wide inset default for surfaces: `'none'` (the default) hands every surface the full * area of the pane it is mounted in, so what stands between its content and the pane edge is * whatever the surface itself draws; `'inset'` insets every surface the product composes, which * is comfortable for the prose, forms and lists most surfaces are. * * A surface's own `padded` declaration wins over this default, in both directions: a surface may * ask to be inset where the product asks for nothing, and to be flush where the product asks for * an inset. The declaration travels with the surface, so it holds at every mount point. * * Only whether there is an inset is settled here. How wide it is stays a styling question, so a * product that wants a different amount writes plain unlayered CSS rather than asking for a * token. */ readonly padding?: PaddingDefault; /** * Whether the workbench announces updates itself — `true` by default. With `false` it shows no * notice about a waiting version, a current one, a check it could not answer, a failed * installation or broken offline storage, for a manual check and its own background checks alike, * and the distribution draws the answer instead. * * Nothing else is withheld: `UpdateService.checkForUpdate()` reports what it found, * `UpdateService.lastCheck` carries the last check the workbench made including its own, * `updateAvailable` still drives whatever marker you keep, and `activateUpdate()` still applies. */ readonly announceUpdates?: boolean; /** * Where toasts appear: `'top-left'`, `'top-center'`, `'top-right'`, `'bottom-left'`, * `'bottom-center'` or `'bottom-right'` (the default). The newest toast sits nearest that edge. * On a narrow viewport, the one at which the side panels become overlays, toasts are centred at * the chosen edge whichever side you named. * * One choice for the application: a toast cannot place itself. */ readonly toastPosition?: ToastPosition; /** * Whether the workbench draws toasts itself — `true` by default. With `false` the shell mounts no * toast outlet and you draw `NotificationService.notifications()` in a component of your own. * * Only the drawing moves. Which toasts are to be shown, when one leaves, that no more than three * are shown at once and that a repeated toast is counted are decided by the service and reach * your drawing as they reach the workbench's. Call `NotificationService.hold()` and `release()` * while the user is attending to your toasts. **Announcing them to assistive technology becomes * yours:** the workbench's outlet gives a warning or an error `role="alert"` and every other * toast `role="status"`. */ readonly drawToasts?: boolean; /** * The languages the workbench serves, by language code, as the whole set: add a language the * workbench does not ship, leave one out, or name neither English nor German. Omit it to serve the * shipped `['en', 'de']`. Codes are canonicalised (`pt-br` is `pt-BR`) and a code declared twice is * served once; an empty list or something that is not a language code throws here. * * The set decides what is loaded, what the switcher offers, what a stored or browser preference may * select and what `` declares. For a language the workbench does not ship, serve the * workbench's strings at `i18n/.json` under the application's base; a string missing there is * shown in English and named in development. */ readonly languages?: readonly string[]; } /** * Wires the neutral shell host (theme, i18n, icons, error handling) for a * distribution. A distribution adds its own router and product identity; the * bare platform falls back to the LoomWeaver identity. */ export declare function provideShell(options?: ShellOptions): (Provider | EnvironmentProviders)[]; /** * The distribution-supplied session snapshot signal. Defaults to **anonymous** so the * bare platform runs with nobody signed in; a distribution maps its product's auth (OIDC/custom/…) * into an {@link AuthSnapshot} signal via {@link provideAuthSource}. LoomWeaver never owns auth — it * only reads this signal. */ export declare const AUTH_SOURCE: InjectionToken>; /** * What the shell does when one established identity is replaced by another. * The comparison anchor is {@link AuthSnapshot.subject}. */ export interface AuthSourceOptions { /** * `'reload'`: when a snapshot carries a **different** non-empty `subject` than the one already * established this app lifetime, the shell opens the application afresh at its start (the * served base, never the previous user's address) — the guaranteed-clean user switch (no pane * trees, tab titles, address or plugin in-memory state of the previous user survive; paired * with an identity-scoped settings store, the new session re-hydrates entirely from its own * namespace). A pop-out window closes instead. First sign-in (anonymous → subject) and * sign-out (subject → anonymous) never fire, so an async session restore at boot causes no * reload flicker. Snapshots without a `subject` never fire either. Default: `'ignore'`. */ onIdentityChange?: 'reload' | 'ignore'; } /** * A distribution declares its auth source. The factory runs in the injection context, so it can * `inject()` the product's own session/BFF service and return a signal derived from it. Pass * `options` to opt into the identity-change policy: with * `{ onIdentityChange: 'reload' }` a user switch on a shared browser reloads the app instead of * leaving the previous user's in-memory state alive. */ export declare function provideAuthSource(factory: () => Signal, options?: AuthSourceOptions): EnvironmentProviders; /** * Reactive read-only view of the current session, plus the gating predicates the host chrome uses to * hide/disable contributions. Roles/claims are opaque — this service matches, never * interprets. Client-side gating is presentation, not a security boundary. */ export declare class AuthContext { private readonly source; /** The current session snapshot (reactive). */ readonly state: Signal; /** Whether someone is signed in. */ readonly authenticated: Signal; /** The current principal's roles. */ readonly roles: Signal; /** True if the current principal holds `role`. */ hasRole(role: string): boolean; /** True if the current session satisfies `access` (undefined = always). */ meets(access: AccessRequirement | undefined): boolean; /** Whether a chrome item with this requirement should render (unmet + hide → false). */ visible(access: AccessRequirement | undefined): boolean; /** Whether a rendered chrome item with this requirement should be disabled (unmet + disable). */ disabled(access: AccessRequirement | undefined): boolean; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** A compressed form a picture may be carried in. */ type WorkbenchCompression = 'jpeg' | 'webp'; /** * The form a picture of the workbench is carried in. * * `'lossless'` is what a request that says nothing gets, because compression is unkind to the small * text a fault report is usually read for. A compressed form is for carrying: name how strongly with * `quality`, between 0 and 1, where the default is already well below what a browser would choose on * its own. */ type WorkbenchPictureForm = 'lossless' | { readonly compressed: WorkbenchCompression; readonly quality?: number; }; /** What a picture turned out to be carried in, read back from the drawing rather than assumed. */ type WorkbenchCarriedForm = 'lossless' | WorkbenchCompression; /** * How large a picture of the workbench is drawn. * * `'screen'` draws at the density of the screen it is drawn on, which is what a request that says * nothing gets. `'plain'` draws one picture pixel per CSS pixel, which is the smaller picture a * fault report usually wants. `withinWidth` names a greatest width in picture pixels: the picture is * drawn no wider than that, keeping the proportions of what was pictured, and a width wider than the * workbench never enlarges it. */ type WorkbenchPictureSize = 'screen' | 'plain' | { readonly withinWidth: number; }; /** What a caller may ask for about the picture before it is drawn. */ interface WorkbenchPictureRequest { /** How large to draw it. The density of the screen when absent. */ readonly size?: WorkbenchPictureSize; /** What form to carry it in. Losslessly when absent. */ readonly form?: WorkbenchPictureForm; } /** A drawing of the workbench, made without asking the browser for permission. */ interface WorkbenchPicture { /** The drawing, as a `data:` URL, ready to show, annotate or attach to a report. */ readonly image: string; readonly width: number; readonly height: number; /** * The form the picture is carried in, read back from the drawing rather than taken from the * request: a browser that cannot produce the form asked for substitutes one silently. */ readonly form: WorkbenchCarriedForm; /** * How many surfaces are on the picture as a statement that their content is absent rather than as * their content — a surface that failed to draw, or one that withheld itself. */ readonly surfacesAbsent: number; } /** * Draws the workbench as the user is seeing it, plugin surfaces included. * * The browser will not photograph the screen without a permission prompt and a surface picker, so * the picture is rendered from the document instead. That stops at an isolated surface, whose * content nothing outside it may read, so each one is asked to draw itself and its answer is placed * where it sits. A surface that cannot answer is drawn as a statement that its content is absent, * never as a blank — a blank in a picture attached to a fault report reads as evidence. * * What the user cannot see is not in the picture: content scrolled out of sight within a region * stays out, and a surface opened in its own window is not part of it. * * This is a distribution's to call. A plugin is offered no path to it, because a picture holds what * every other surface on screen is showing. */ export declare class WorkbenchCaptureService { private readonly document; private readonly surfaces; private readonly transloco; private renderer?; capture(request?: WorkbenchPictureRequest): Promise; private window; private placements; private drawSurfaces; private assemble; private load; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } export interface CommandInvoker { invocable(callerId: string, granted: boolean): readonly InvocableCommand[]; invoke(callerId: string, granted: boolean, id: string, args?: CommandArguments): Promise; } export declare const COMMAND_INVOKER: InjectionToken; export declare class CommandInvocationService implements CommandInvoker { private readonly commands; private readonly registry; private readonly errors; private readonly transloco; private chainDepth; invocable(callerId: string, granted: boolean): readonly InvocableCommand[]; invoke(callerId: string, granted: boolean, id: string, args?: CommandArguments): Promise; private run; private startInChain; private answerOf; private reachable; private reportRefusal; private describe; private text; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } export interface Triggerable { readonly command?: string; run?(context?: MenuContext): unknown; } /** * Executes commands by id. Every trigger runs through here: rail, bar and surface action items, * menus, keybindings and the command palette. The registry only stores, so one place resolves an id, * one place fires the behaviour, and one place reports a failure. */ export declare class CommandService { private readonly registry; private readonly auth; private readonly errors; private readonly shortcuts; private readonly inPopout; /** The registered commands — the source list for a command palette. */ readonly commands: Signal; /** * Whether this command can run **here and now**: the session meets its `access`, and * this window is one it belongs in — a pop-out offers only commands that declare `popout`, since it * has no content area, rail or sidebar for the rest to reach. A palette uses this to filter. */ available(command: Command): boolean; /** * The command's keyboard chord, formatted for display and OS-correct (`'⌘K'` / `'Ctrl+K'`), or * `undefined` where it has none — **or where this distribution has switched shortcuts off**, so a * hint never promises a key that does nothing. Named keys are spelled the platform's way and in the * interface language, as {@link formatChord} spells them. A palette row, a bar button and a toolbar * entry ask here; a menu asks {@link chordOf} and spells the hint when it words its labels. */ shortcutOf(command: Command | undefined): string | undefined; /** * The command's keyboard chord as declared (`'mod+k'`), or `undefined` where it has none or where * this distribution has switched shortcuts off. For a hint that is spelled later, such as a menu * entry that is worded again when the language changes; to show it now, use {@link shortcutOf}. */ chordOf(command: Command | undefined): string | undefined; /** * Runs the command with this id; a no-op (with a warning) if none is registered. Blocks — the one * choke point every trigger flows through (keybinding, palette, item) — when the session does not * meet the command's `access`. An optional {@link MenuContext} is forwarded to * `command.run(context)` — how a menu tells `shell.tab.closeOthers` which tab. */ execute(id: string, context?: MenuContext): void; /** * Fires a resolved command's behaviour — the one place that happens, whatever triggered it — and * reports a failure the way every trigger already does. Answers what the command returned, and * rejects with what it threw, so a caller that has to tell a failure from an answer can. */ run(command: Command, context?: MenuContext, args?: CommandArguments): Promise; /** * Whether this item's trigger leads anywhere: an inline callback, or a command that is actually * registered. A rail or bar item naming a command nobody registered would render as a dead button * — the same orphan a menu entry can be, and the host drops both rather than draw them. */ triggerable(item: Triggerable): boolean; /** * Fires a UI item's trigger: its bound command if it names one, else its inline callback, either * with the context the item was activated against where the trigger has one. */ trigger(item: Triggerable, context?: MenuContext): void; private callInline; private onError; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Formats a keyboard chord for display, OS-correct: `formatChord('mod+k')` → `'⌘K'` on macOS, * `'Ctrl+K'` elsewhere. `mod` maps to ⌘/Ctrl, `alt` to ⌥/Alt, `shift` to ⇧/Shift, and so on — the * same token vocabulary a {@link Command.shortcut} uses. Named keys take the platform's symbol where * it has one (⌦ ⌫ ⇥ on macOS) and otherwise a word in the interface language once the shell is * running (`Strg+Entf` in German on Windows and Linux), so the result matches every hint the shell * draws; without a running shell the words are English. `del` is the platform's delete key and is * drawn as that key: `formatChord('mod+del')` → `'⌘⌫'` on macOS, `'Ctrl+Del'` elsewhere. Lets a distribution or plugin show a * shortcut anywhere without duplicating the platform detection the shell already does internally. */ export declare function formatChord(chord: string): string; /** * Binds command shortcuts to the keyboard. Bindings are *derived* from the registered * commands' `shortcut` (no separate registration path) and rebuild reactively as commands come and * go. One global `keydown` listener resolves the pressed chord to a command id and fires it through * the {@link CommandService}, the same path a click takes. */ export declare class KeybindingService { private readonly registry; private readonly commands; private readonly document; private readonly destroyRef; private readonly injector; private readonly enabled; private readonly isMac; private started; private readonly build; private readonly bindings; /** Attaches the global listener once; auto-detached on destroy. A chord is ignored while shortcuts are off. */ start(): void; private onKeydown; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** Where the built command-palette entry goes. Defaults: top bar, end slot, order 5. */ export interface CommandPaletteEntryOptions { /** * Target Bar region id. Default `'top-bar'`. The badge adapts to the bar it lands in: a top bar is * a fixed band, so there it pins the shared bar-control height and lines up with the theme and * language controls; a bottom bar takes the height of its tallest item, so there it renders like a * plain bar item rather than growing the bar and costing the content area. */ readonly bar?: string; /** Which Bar slot the entry renders in. Default `'end'`. */ readonly slot?: BarSlot$1; /** Lower renders first within the slot. Default `5` (left of the built update/language/theme items). */ readonly order?: number; } /** * Places a built command-palette entry in a Bar — a badge-styled affordance (search icon + * the palette's OS-correct shortcut) that opens `shell.commandPalette`, correct-by-construction and * without a distribution component. Opt-in: omit it for a palette that opens only by shortcut. Uses the * `shell.commandPaletteEntry` bar-item id, so `provideShell({ omit: ['shell.commandPaletteEntry'] })` * removes it. * * The badge never outlives what it opens: omitting `shell.commandPalette`, or a session that may not * run it, takes the badge with it rather than leaving a control that does nothing. Switching * shortcuts off is the exception — the badge stays and still opens the palette, it simply names no * chord. Independent of {@link provideQuickOpenEntry}, which places the entry to the search over * open work. */ export declare function provideCommandPaletteEntry(options?: CommandPaletteEntryOptions): EnvironmentProviders; /** Where the built quick-open entry goes. Defaults: status bar, start slot, order 5. */ export interface QuickOpenEntryOptions { /** * Target Bar region id. Default `'status-bar'` — apart from the command-palette entry, so that the * two badges are not read as a pair of duplicates. The badge adapts to the bar it lands in: a top * bar is a fixed band, so there it pins the shared bar-control height; a bottom bar takes the * height of its tallest item, so there it renders like a plain bar item rather than growing the * bar and costing the content area. */ readonly bar?: string; /** Which Bar slot the entry renders in. Default `'start'`. */ readonly slot?: BarSlot$1; /** Lower renders first within the slot. Default `5`. */ readonly order?: number; } /** * Places a built quick-open entry in a Bar — a badge-styled affordance (icon + the OS-correct * shortcut) that opens `shell.quickOpen`, the search over open work. Opt-in: omit it for a search * that opens only by shortcut. Uses the `shell.quickOpenEntry` bar-item id, so * `provideShell({ omit: ['shell.quickOpenEntry'] })` removes it. * * The badge never outlives what it opens: omitting `shell.quickOpen`, or a session that may not run * it, takes the badge with it rather than leaving a control that does nothing. A pop-out window is * the same case — it offers no search over open work, so the badge is absent there. Switching * shortcuts off is the exception: the badge stays and still opens the search, it simply names no * chord. Independent of {@link provideCommandPaletteEntry}, so a product may place either, both or * neither. */ export declare function provideQuickOpenEntry(options?: QuickOpenEntryOptions): EnvironmentProviders; /** Multi-provider token: each contribution adds one {@link BarItem}. */ export declare const BAR_ITEM: InjectionToken; /** Hooks bar items into the shell — used by the host and by distributions. */ export declare function provideBarItems(...items: BarItem$1[]): Provider[]; /** Multi-provider token: each contribution adds one {@link RailItem}. */ export declare const RAIL_ITEM: InjectionToken; /** Hooks rail items into the shell — used by the host and by distributions. */ export declare function provideRailItems(...items: RailItem$1[]): Provider[]; /** Multi-provider token: each contribution adds one {@link View}. */ export declare const VIEW: InjectionToken; /** Hooks views into regions — used by the host and by distributions. */ export declare function provideViews(...views: View$1[]): Provider[]; /** * A {@link ContentRoute} as the registry holds it: the plugin's own declaration plus the **host-stamped** * id of the plugin that registered it. The id is not part of the authoring contract — a plugin must not be * able to claim someone else's identity — so it lives here rather than in `@loomweaver/plugin-sdk`. */ type RegisteredContentRoute = ContentRoute & { readonly pluginId?: string; readonly actions?: readonly ViewAction$1[]; }; /** * A {@link View} as the registry holds it: the plugin's own declaration plus the **host-stamped** id * of the plugin that registered it, mirroring {@link RegisteredContentRoute}. The stamp is what lets * the retention guards find a plugin's dirty instances before the plugin is disabled or uninstalled. */ type RegisteredView = View$1 & { readonly pluginId?: string; }; /** * A command as the registry holds it: the declaration a plugin handed in, plus the owner the host * stamped on it. The owner is what lets a command be attributed — to let its own plugin reach it * without a grant, and to tell a plugin's commands from the shell's own. */ export interface RegisteredCommand { readonly command: Command; readonly ownerId?: string; } /** * A menu entry as the registry holds it: the entry a plugin handed in, plus the owner the host * stamped on it, so that an entry aimed at a slot nothing declares can be reported with the plugin * that contributed it. */ export interface RegisteredMenuItem { readonly item: MenuItem; readonly ownerId?: string; } /** * Holds the live UI contributions the regions render. Seeded at startup from the * static `provide*` API and added to at runtime by plugins. Signal-based so the * regions react to runtime registration/removal. * * Contributions are keyed by `id`: registering an existing id **overrides** the * previous entry in place (last contribution wins — a distribution/plugin can * replace a host default by reusing its id), and `remove*ById` drops one (a * distribution hides a default it does not want, e.g. via `provideShell({ omit })`). * * Override is **destructive**, not stacked: the superseded entry is replaced, so disposing * the *winning* handle removes the id entirely (it does not restore the entry it replaced), * and disposing a *superseded* handle is a no-op. */ export declare class ContributionRegistry { private readonly commandsSignal; private readonly surfacesSignal; private readonly badges; private readonly barItemsSignal; private readonly railItemsSignal; private readonly menuEntriesSignal; private readonly dockedSurfaces; private readonly routableSurfaces; private readonly omittedSignal; /** Registered commands with their owner — the source for anything that has to attribute one. */ readonly registeredCommands: Signal; /** Registered commands — the invocable actions triggers (items/keybindings/palette) point at. */ readonly commands: Signal; readonly views: Signal; readonly barItems: Signal; readonly railItems: Signal; /** * URL-addressed content-area routes; the host mirrors these into the router. * Omitted routes are filtered out, so every consumer — tab strip, auto-open, pane target * picker, `matchRoute` — drops them without knowing about omission. */ readonly contentRoutes: Signal; /** * The routes {@link omit} dropped. The host still maps these to a neutral * "not available here" placeholder at their path, so a shared deep-link explains itself instead of * silently falling back to home. */ readonly omittedContentRoutes: Signal; readonly registeredMenuItems: Signal; /** Menu-slot contributions; a menu filters these by slot and `when` as it opens. */ readonly menuItems: Signal; /** The ids {@link omit} hides, exactly as the distribution wrote them, prefixes and all. */ readonly omitted: Signal>; /** * Every id registered so far, of whatever kind, **including** those {@link omit} hides. An * omitted contribution is by construction absent from every other signal here, so this is the * only way to tell an `omit` that hid something from one that hit nothing at all. */ readonly registeredIds: Signal>; /** * Hides contributions by id for good — a distribution drops a default it does not want * (`provideShell({ omit })`). Unlike {@link removeCommandById} and friends, which delete what is * registered *now*, this is a lasting filter: an id a plugin registers later stays hidden too. */ omit(ids: readonly string[]): void; /** * Adds a command. Like {@link addContentRoute}, `pluginId` is stamped by the host and never * claimed by the plugin: it is what decides whether a caller is reaching its own command or * another's. The shell's own seeded commands carry none. */ addCommand(command: Command, pluginId?: string): Disposable$1; /** * Adds a panel view. Like {@link addContentRoute}, `pluginId` is stamped by the host, never by * the plugin, and overwrites anything the view carried. */ addView(view: View, pluginId?: string): Disposable$1; addBarItem(item: BarItem): Disposable$1; addRailItem(item: RailItem): Disposable$1; removeCommandById(id: string): void; removeViewById(id: string): void; removeBarItemById(id: string): void; removeRailItemById(id: string): void; /** * Adds a content route (keyed by `path`; a re-registered path overrides in place). `pluginId` is * stamped by the host, never by the plugin: it is what lets a route's surface be checked against its * owner's capability grants, so it is applied last and overwrites anything the route carried. */ addContentRoute(route: ContentRoute, pluginId?: string): Disposable$1; /** * 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. `pluginId` is stamped by * the host, never claimed by the plugin; the shell's own entries carry none. */ addMenuItem(item: MenuItem, pluginId?: string): Disposable$1; removeMenuItemById(id: string): void; /** * Replaces the title of a registered surface in place, leaving everything else it declared alone. * A rename is not a re-registration: the entry keeps its identity, so nothing mounted from it is * rebuilt. An id nothing was registered under is a no-op. */ retitleSurface(id: string, title: string): void; updateSurfaceBadge(id: string, badge: TabBadge | null, pluginId?: string): void; badgeOf(id: string | undefined): TabBadge | undefined; /** * Replaces one action of a registered surface in place, by the action's id, leaving the entry's * identity and everything else it declared alone. An action id the surface did not carry is added. * A surface id nothing was registered under is a no-op. */ updateSurfaceAction(id: string, action: ViewAction): void; ownerOfSurface(id: string): string | undefined; actionsOf(id: string | undefined): readonly ViewAction[]; private addSurface; private forgetBadgeOfGone; private visible; private add; private removeById; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * The neutral host button primitive. A directive on a real ` * */ export declare class LwButton { /** Visual weight; defaults to `default`. */ readonly variant: import("@angular/core").InputSignal; /** Size; defaults to `md`. */ readonly size: import("@angular/core").InputSignal; /** Square, equal-padding button for a single icon (needs its own `aria-label`). */ readonly iconOnly: import("@angular/core").InputSignalWithTransform; protected readonly classes: import("@angular/core").Signal; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵdir: i0.ɵɵDirectiveDeclaration; } export type DialogKind = 'confirm' | 'alert' | 'prompt' | 'custom' | 'progress'; export type ButtonRole = 'confirm' | 'cancel' | 'custom'; /** A footer button as the dialog outlet renders it. */ export interface DialogButtonView { readonly label: string; readonly variant: LwButtonVariant; readonly role: ButtonRole; readonly value?: unknown; readonly autofocus: boolean; } /** One open dialog, held by the service and rendered by the dialog outlet. */ export interface DialogInstance { readonly id: string; readonly ref: DialogRef$1; readonly kind: DialogKind; readonly tone: DialogTone$1; /** Host icon name (from options or the tone default); rendered by ``. */ readonly icon?: string; readonly title?: string; readonly message?: string; readonly component?: Type; readonly injector?: Injector; readonly placeholder?: string; readonly promptValue?: WritableSignal; /** Live status text for a `progress` dialog. */ readonly progressMessage?: WritableSignal; /** Type-to-confirm label (Markdown) shown above the input, if any. */ readonly requireLabel?: string; /** Validates the guard input; `null` = valid (enables confirm), else an inline error. */ readonly requireValidate?: (value: string) => string | null; readonly buttons: readonly DialogButtonView[]; readonly dismiss: DialogDismiss$1; readonly size?: DialogSize$1; /** Render only the component (no host chrome) — the component owns the frame contents. */ readonly bare?: boolean; /** The frame shows a maximize/restore control (framed dialogs; bare ones draw their own). */ readonly maximizable?: boolean; /** Vertical anchor: `'top'` pins the panel's top edge on every width. Defaults to `'center'`. */ readonly align?: 'center' | 'top'; } /** * Neutral host service for modal dialogs: * convenience dialogs (`confirm`/`alert`/`prompt`, the host paints everything) and rich * `open(Component)` dialogs where a plugin owns the body and the host paints the frame + * declarative footer buttons. The shell renders them once via ``. Uses * only `` chrome + semantic tokens, so plugin-authored bodies stay harmonious. */ export declare class DialogService { private readonly injector; private readonly document; private readonly items; private dismissal; private counter; /** The currently open dialogs, oldest first (the last one is topmost). */ readonly dialogs: import("@angular/core").Signal; /** Asks a yes/no question; resolves `true` only if confirmed. */ confirm(options: ConfirmOptions$1): Promise; /** Shows a message with a single acknowledge button. */ alert(options: AlertOptions$1): Promise; /** Asks for a line of text; resolves the value, or `null` if cancelled/dismissed. */ prompt(options: PromptOptions$1): Promise; /** * Opens a custom body component. The host paints the frame + any declarative footer * buttons; the component owns the body and injects {@link DialogRef} to close itself. */ open(component: Type, options?: OpenOptions$1): DialogRef$1; /** * Shows a non-dismissable busy indicator (spinner + status) so a running operation is not * interrupted. The caller closes it when done — or use {@link withProgress} to auto-close. */ progress(options: ProgressOptions$1): ProgressHandle$1; /** Runs `work` behind a progress dialog that closes automatically when it settles. */ withProgress(options: ProgressOptions$1, work: Promise): Promise; /** * Connects the outlet that draws the dialogs, so a body's `requestClose` runs the outlet's own * dismissal. Returns the disconnect. */ connectDismissal(dismiss: (dialog: DialogInstance) => Promise): () => void; private requestClose; private push; private mount; private giveFocusBack; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Renders the open dialogs once, mounted by the shell root. Draws the frame * (backdrop, panel, title, close-X, footer buttons) and either the convenience body * (message + optional prompt input) or a plugin's custom body via NgComponentOutlet. * Owns the modal mechanics: scroll-lock, Escape + backdrop dismiss (an Escape something inside * already handled is left alone), focus into the newest dialog, and a lightweight focus trap. * `DialogService` returns the focus to the opener once the closed dialog has left the page. */ export declare class DialogOutlet { private readonly service; private readonly closeGuard; private readonly document; protected readonly dialogs: import("@angular/core").Signal; private readonly panels; private readonly bodies; private readonly asking; constructor(); protected onEscape(event: Event): void; protected onFocusIn(event: FocusEvent): void; protected onTab(event: Event, backward: boolean): void; protected onScrim(dialog: DialogInstance): void; protected onCloseControl(dialog: DialogInstance): void; protected canBeDismissed(dialog: DialogInstance): boolean; protected onButton(dialog: DialogInstance, button: DialogButtonView): void; protected onPromptInput(dialog: DialogInstance, event: Event): void; protected onEnter(dialog: DialogInstance): void; protected confirmBlocked(dialog: DialogInstance, button: DialogButtonView): boolean; protected fieldLabel(dialog: DialogInstance): string | null; protected guardError(dialog: DialogInstance): string | null; protected wrapperClasses(dialog: DialogInstance): string; protected toneCircle(tone: DialogTone): string; protected panelWidth(dialog: DialogInstance): string; private onCustomButton; private requestDismiss; private bodyOf; private topPanel; private focusable; private top; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } /** The custom-element tag. */ export declare const LW_BUTTON_TAG = "lw-button"; /** * `Save` — the host button primitive as a framework-agnostic * custom element (like ``/``/``): light DOM (its content stays * in place), a `role="button"` element that emits the same **`.lw-btn` CSS-class contract** * (`theme.css`) the {@link LwButton} directive does — one source of truth for the look, on semantic tokens. * * The Angular {@link LwButton} directive stays the idiomatic choice for host chrome (it sits on a **native** * ` */ export declare class LwTooltipElement extends HTMLElement { static readonly observedAttributes: string[]; private bubble?; private trigger?; private showTimer?; private pointer?; get text(): string | null; set text(value: string | null); get position(): TooltipPosition; set position(value: TooltipPosition | null); connectedCallback(): void; disconnectedCallback(): void; attributeChangedCallback(): void; private readonly onEnter; private readonly onMove; private readonly onLeave; private readonly onFocusIn; private readonly onFocusOut; private readonly onEscape; private render; private createBubble; private scheduleShow; private delayMs; private show; private placeBubble; private hide; private clearTimer; } /** Registers `` once (idempotent) — called from {@link provideShell} at bootstrap. */ export declare function defineLwTooltip(): void; /** * One entry a `` draws, already resolved and worded by whoever drives the element: * the workbench in the page, the frame kit inside an isolated surface. The element draws; it * decides nothing about commands, sessions or words. */ interface LwToolbarEntry { readonly key: string; readonly label: string; readonly group?: string; readonly order?: number; readonly icon?: string; readonly shortcut?: string; readonly pressed?: boolean; readonly disabled?: boolean; readonly opensMenu?: boolean; readonly hasContextMenu?: boolean; readonly variant?: LwButtonVariant$1; } export declare const LW_TOOLBAR_TAG = "lw-toolbar"; /** Registers `` once (idempotent), called from {@link provideShell} at bootstrap. */ export declare function defineLwToolbar(): void; /** * Gestures the content area offers on tabs and panes. Each field takes the **affordance and the * gesture**: switching one off removes the button, the drag target and the keyboard shortcut alike, * so a capability can never come back through a second door. * * Two fields are finer than that, and say so in their own names: `splitRightButton` and * `splitDownButton` leave out the one route to splitting that has no handle of its own, the button * the pane toolbar draws. Everything else about splitting stays. A route a distribution can already * remove by naming the contribution behind it — the tab menu's entry (`menu:shell.tab.splitRight`), * the command with its shortcut and its palette row (`shell.content.splitRight`) — gets no second * handle here, because one decision belongs in one place. */ export interface ContentFeatures { /** Closing a tab: the × affordance, the `Delete` key and the close entries of the tab menu. */ readonly close: boolean; /** Pinning a tab: the menu entry, the pin affordance and the pin step of the double-click cycle. */ readonly pin: boolean; /** * The double-click cycle on a tab (preview → keep → pin → unpin). The first step is the one users * arrive expecting, and a preview tab says so itself — its tooltip reads "double-click to keep * open", which is why the hint is drawn only while this is on. Switch it off and the tooltip drops * the promise; every step of the cycle stays reachable from the tab's context menu either way. */ readonly escalate: boolean; /** Dragging a tab into another pane, and onto an empty pane. */ readonly moveTabs: boolean; /** * The one reused italic **preview** tab of the main area: a surface opened as a preview replaces the * previous preview instead of adding a tab. With it off, `openContentTab({ preview: true })` opens a * permanent tab. */ readonly preview: boolean; /** The "+" button that opens the new-tab picker. */ readonly newTab: boolean; /** Splitting a pane horizontally: the toolbar button, the left/right drop edges and `mod+\`. */ readonly splitRight: boolean; /** * The pane toolbar's button for splitting horizontally, and nothing else. Switch it off for a * product that offers splitting by dragging a tab to a pane's edge and wants no split control in * the toolbar. The drop edges, `mod+\` and the tab menu's entry are untouched; switching * {@link ContentFeatures.splitRight} off still takes the button with everything else. */ readonly splitRightButton: boolean; /** Splitting a pane vertically: the toolbar button and the top/bottom drop edges. */ readonly splitDown: boolean; /** The pane toolbar's button for splitting vertically, and nothing else — see {@link ContentFeatures.splitRightButton}. */ readonly splitDownButton: boolean; /** Blowing a pane up to the whole content area. */ readonly maximize: boolean; /** Collapsing a pane into the minimised strip. */ readonly minimize: boolean; /** Reordering tabs within their band, by drag or `Alt+Arrow`. */ readonly reorderTabs: boolean; } /** Gestures the side panels offer on their docked views. */ export interface SidebarFeatures { /** Collapsing and expanding a panel. The close button of the compact drawer is not affected. */ readonly collapse: boolean; /** Dragging a panel wider or narrower. */ readonly resize: boolean; /** Reordering view tabs **within** one panel, by drag or `Alt+Arrow`. */ readonly reorderViews: boolean; /** Moving a view to the other sidebar: the menu entry, the drag and `Alt+Shift+Arrow`. */ readonly moveViews: boolean; /** Hiding a view from this workspace (menu entry). */ readonly hideViews: boolean; /** The checklist on the icon strip that says which views live here. */ readonly curate: boolean; /** Stacking a second view below the first: the menu entry and the drop edges of a panel. */ readonly stackViews: boolean; /** Parking a foreign tab in a sidebar, by dragging it onto the strip or a drop edge. */ readonly acceptTabs: boolean; /** Opening a docked view in the content area (menu entry). */ readonly openViewInContent: boolean; /** Resetting the stored state of a view (menu entry). */ readonly resetViewState: boolean; /** The named-instance switcher in the panel header, where a surface declares `instanceable`. */ readonly instances: boolean; } /** Gestures the rail offers on its items. */ export interface RailFeatures { /** Reordering rail items **within** their band, by drag or `Alt+Arrow`. */ readonly reorder: boolean; /** Moving an item to the other rail: the menu entry, the drag and `Alt+Shift+Arrow`. */ readonly moveItems: boolean; /** Hiding a rail item (menu entry). */ readonly hideItems: boolean; /** The checklist on empty rail space that says which items live here. */ readonly curate: boolean; } /** Whether named workspaces exist for this product at all. */ export interface WorkspaceFeatures { /** * The workspace machinery the shell offers on its own: the manage and reset commands and the * workspace entries in the rail. Switching it off leaves the storage scoping untouched — the * active workspace still names the layout keys, the user just never meets the concept. */ readonly enabled: boolean; /** * Whether the user may put a workspace **they saved** in the rail. A saved workspace is never * there by itself — the user places it from *Customize activity bar* — and switching this off * withdraws that offer, so the rail holds what your product put there and nothing else. Their * placements are kept rather than erased, so switching it back on restores what each user had. * * Everything else about a saved workspace is untouched: saving, renaming, resetting and switching * all work, with the workspace dialog as the way to them. This says nothing about **whether** a * user may save workspaces, which is a different question and has no switch. */ readonly savedInRail: boolean; } /** Whether a tab or a view may be torn off into a browser window of its own. */ export interface WindowFeatures { /** "Open in new window" on a tab and on a docked view. */ readonly popout: boolean; } /** How commands reach the user beyond the buttons that name them. */ export interface CommandFeatures { /** * Keyboard shortcuts at all: the global listener that turns a chord into a command, and the * chord hints the shell prints beside menu entries, palette rows and bar buttons. With it off a * command is still reachable by its button and by the palette, and no hint promises a key that * does nothing. */ readonly shortcuts: boolean; /** The "recently used" section at the top of the command palette (and the list behind it). */ readonly recentlyUsed: boolean; } /** * Which shell capabilities a distribution offers its users. The platform ships the full workbench — * every capability here is on by default — and a product that would overwhelm its users switches * parts off. * * This is the home for **gestures**. A *contribution* (a command, a bar or rail item, a settings row, * a menu entry) is not a gesture and is removed with `provideShell({ omit })` instead. Where a * capability has both — a menu entry *and* a drag — the feature switch wins and takes the entry with * it, while `omit` stays the finer tool for "entry gone, gesture stays". */ export interface ShellFeatures { readonly content: ContentFeatures; readonly sidebar: SidebarFeatures; readonly rail: RailFeatures; readonly workspaces: WorkspaceFeatures; readonly windows: WindowFeatures; readonly commands: CommandFeatures; } /** The default workbench: every capability on. */ export declare const DEFAULT_SHELL_FEATURES: ShellFeatures; /** * The switches as the distribution **declared** them: the starting value of every switch, fixed at * composition. The current value can change while the application runs, so read it from * `FeatureSwitches` rather than from this token; the shell itself reads this token exactly once. */ export declare const SHELL_FEATURES: InjectionToken; /** A partial override: name only what you switch off, group by group. */ export type ShellFeaturesInput = { readonly [Group in keyof ShellFeatures]?: Partial; }; /** * Declares which shell capabilities this distribution starts with switched off, e.g. * `provideShellFeatures({ content: { splitDown: false, maximize: false } })`. Omit for the full * workbench; fields merge group by group, so a partial override leaves the rest of that group alone. * The declaration is the starting value: `FeatureSwitches.update` changes switches from there while * the application runs. */ export declare function provideShellFeatures(features: ShellFeaturesInput): EnvironmentProviders; /** One read-only signal per switch of a group, under the switch's own name. */ export type SwitchSignals = { readonly [Switch in keyof Group]: Signal; }; /** * The current state of every shell capability switch, readable as signals and changeable while the * application runs. What `provideShellFeatures` declares is the starting value; `update` changes * switches from there, naming only what changes, in the same shape the declaration uses: * * ```ts * const switches = inject(FeatureSwitches); * switches.content.splitRight(); // Signal, true by default * switches.update({ content: { splitRight: false } }); // the button, drop edges and mod+\ go * ``` * * A switch removes the user's routes, not the capability: what a distribution switches off, its own * code still reaches through the services that carry it. Switching off acts forward only, so a pane * that was split stays split; put the state where you want it before taking the way away. Nothing * here is persisted, and the next start begins from the declaration again; whether a change made * at runtime survives, and for whom, is the distribution's decision, made with its own stores. */ export declare class FeatureSwitches { private readonly state; /** The whole set as it stands now. */ readonly current: Signal; /** Gestures of the content area, one signal per switch. */ readonly content: SwitchSignals; /** Gestures of the side panels, one signal per switch. */ readonly sidebar: SwitchSignals; /** Gestures of the rail, one signal per switch. */ readonly rail: SwitchSignals; /** The workspace switches, one signal each. */ readonly workspaces: SwitchSignals; /** The detached-window switch. */ readonly windows: SwitchSignals; /** How commands reach the user, one signal per switch. */ readonly commands: SwitchSignals; /** * Changes switches while the application runs. Fields merge group by group, so naming one switch * leaves its neighbours alone. Every reader of the affected switches re-evaluates. */ update(input: ShellFeaturesInput): void; private group; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * How much the browser holds a sandboxed plugin back. **Isolated** strips the frame of an origin, which * is what denies it the hosting document, any storage and any session the browser would carry for * it. **Embedded** lets it keep an origin, and with it whatever the browser grants that origin — a * separation of deployments rather than of privileges. * * Isolated is the default: a plugin whose level was never stated runs isolated. */ type PluginIsolationLevel = 'isolated' | 'embedded'; /** Plugin ids a distribution declared its application cannot run without. */ export type RequiredPlugins = readonly string[]; /** * The plugins this distribution declared not optional. Defaults to **none** — every plugin is the * user's to switch off until a distribution says otherwise. */ export declare const REQUIRED_PLUGINS: InjectionToken; /** * A distribution declares which of the plugins it composes its application cannot run without. Such * a plugin is listed in the permissions settings with what it holds, and **without a switch to turn * it off**; it stays active whatever the user chose before. * * This is the distribution's statement and not the plugin's, and deliberately so: everything a plugin * declares about itself is a request the distribution grants, so a manifest field here would be the * one exemption a plugin could award itself. * * It withholds the plugin's own switch and nothing else. The capabilities such a plugin was granted * stay revocable, because needing a plugin says nothing about needing everything it asked for — which * is what distinguishes this from a plugin the operator deployed, where both are withheld. * * Naming a plugin this distribution does not compose, neither a trusted one it provides nor a * sandboxed one it declares, is a composition mistake rather than a failure: it is reported in * development, however the distribution composes its plugins, and otherwise ignored, the same way a * capability granted to a plugin that never declared it is. A plugin the store installs arrives later * and is the user's to switch off in any case, so it does not count. * * @example * provideRequiredPlugins('sign-in') */ export declare function provideRequiredPlugins(...pluginIds: string[]): Provider; /** * What the workbench recognised about workspaces that cannot work as their declaration describes. * A product reads {@link ids} to answer for itself, which is what makes silencing the workbench's * own notice a choice rather than a blindfold. */ export interface UnusableWorkspaces { /** The workspaces that declare content of their own and whose stored arrangement leaves them none. */ ids(): ReadonlySet; /** Whether the workbench itself says so, here and now, for the workspace the user is in. */ announced(): boolean; } export declare const UNUSABLE_WORKSPACES: InjectionToken; /** One language the workbench serves: its canonical code and its name, written in that language. */ interface ServedLanguage { readonly code: string; readonly name: string; } /** * The workbench's language, for a product's own language control. It is the same mechanism the * shipped switcher uses, so a control a distribution registers in place of `shell.language`, in any * bar or as a settings row, changes the language exactly as the switcher does: re-rendered at once, * declared on ``, remembered through the settings port and followed by the application's * other windows and isolated surfaces. * * The served set is what the distribution declared with `provideShell({ languages })`, or English and * German when it declared nothing. */ export declare class LocaleService { private readonly transloco; private readonly document; private readonly store; private readonly sync; /** The codes of the served languages, in the order the distribution declared them. */ readonly supported: readonly string[]; /** * The served languages with the name of each in its own language ("Deutsch", "Français"), the same * names the shipped switcher shows. A code the platform cannot name is shown as the code. */ readonly languages: readonly ServedLanguage[]; private readonly active; /** The active language code, reactive. */ readonly lang: import("@angular/core").Signal; constructor(); /** * Makes `lang` the active language and remembers it. A code the workbench does not serve changes * nothing, and the developer is told in development, rather than silently doing nothing. */ setLang(lang: string): void; private applyStored; private applyLanguage; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Extra translation namespaces a distribution composes on top of the host base, in the order they * were first declared. Each is served at `i18n//.json` under the application's base * and nested under its key, so its strings live at `name.*` and can never collide with a host key. * Used for a plugin's own strings (`demo`) and for distribution branding (`product`). * * Read it; declare through {@link provideTranslationNamespaces}. Providing this token directly * replaces every declaration made that way. */ export declare const TRANSLATION_NAMESPACES: InjectionToken; /** * A distribution declares which namespaced translation bundles to load — the plugins it bundles * (e.g. `'reports'`) and its own branding (`'product'`). The bare platform registers none; * its host keys are the whole story. * * Calls accumulate: a second call further down the composition root adds its names to the first * rather than replacing it, and a name declared twice is loaded once. */ export declare function provideTranslationNamespaces(...namespaces: string[]): Provider; /** * Directory the distribution serves its overlay bundles from, without a trailing slash: relative to * the application's base, or from the origin's root when it starts with `/` or names a scheme. */ export declare const TRANSLATION_OVERRIDES: InjectionToken; /** * Load `/.json` and merge it over everything else **key by key**, so a product * can reword the shell in its own house language ("Save as" rather than "Save as new") without * forking our bundle: name only the keys you change and inherit the rest, including every key a * later release adds. It is applied last, so it also reaches a bundled plugin's strings. * * Namespaces (`provideTranslationNamespaces`) remain the way to *add* your own strings, and they can * still never collide with a host key. This is the opposite job — replacing one — which is why it is * a separate, deliberate opt-in rather than a namespace with a magic name. * * `basePath` lets one build carry several wordings and pick one while composing — a white-label * distribution that serves three brands from the same bundle, or a demo that switches product. * A product with a backend does not need it: the default directory, `i18n/overrides` under the * application's base, is same-origin, so its server can already vary the bytes per tenant. Omit it * and nothing changes. A relative `basePath` resolves under the application's base as the default * does; one that starts with `/` or names a scheme is used as named. * * A language with no overlay file keeps the shipped strings (dev-warned). A key the overlay names but * nothing ships is dev-warned too, since a typo there would otherwise be a string that never appears. */ export declare function provideTranslationOverrides(basePath?: string): Provider; /** A notification to show, as the toast outlet or a distribution's own drawing reads it. */ interface Notification { readonly id: string; readonly kind: NotificationKind$1; readonly message: string; /** The icon the raiser named instead of the kind's (registry name); absent where the kind's is shown. */ readonly icon?: string; /** How often the notification was raised while it was there: `1`, and more once it was repeated. */ readonly count: number; readonly action?: NotificationAction$1; } /** * Neutral host service for transient notifications ("toasts"). The shell renders them once via * the toast outlet; distributions and plugins raise them through `ctx`. * * A toast leaves by itself unless it is an `error` or states `timeoutMs: 0`, at most three are * shown at once while the rest wait their turn, and the same toast raised again without an id is * counted on the one already there. All of that is decided here and not by the outlet, so it holds * for a distribution that draws the toasts itself (`provideShell({ drawToasts: false })`). */ export declare class NotificationService { private readonly board; /** * The notifications to show now, oldest first. Never more than three: a further one waits and * appears here when one of these leaves or is dismissed, and its lifetime starts only then. */ readonly notifications: import("@angular/core").Signal; /** * Shows a notification and returns its id. With an `id` it replaces the notification of that id; * without one, the same notification raised again is counted on the one still there. */ show(input: NotificationInput$1): string; /** Removes the notification with the given id (no-op if already gone). */ dismiss(id: string): void; /** * Says the user is attending to the notifications: none leaves by itself until * {@link release}. The toast outlet calls it when the pointer moves on a toast or keyboard focus * enters one; a distribution that draws the toasts itself calls it for the same moments. Calling it * twice is the same as calling it once, and calling it while nothing is shown does nothing. */ hold(): void; /** * Ends {@link hold}. Every notification runs what was left of its lifetime, and at least a * second, so that none vanishes the moment the pointer leaves. Dismissing the last notification * ends a hold too. */ release(): void; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * Renders the notifications to show as toasts at the edge the distribution chose * (`provideShell({ toastPosition })`, bottom right by default). Mounted once by the shell root, so * every distribution gets it for free. Each toast shows its kind by colour and by icon, its * message, how often it was raised, an optional action button and a dismiss control. Once the * pointer moves on a toast, and while keyboard focus is in one, no toast leaves by itself; that * attention ends when the pointer or the focus leaves, and when the toast itself is gone. */ export declare class ToastOutlet { private readonly service; private readonly injector; private readonly region; private readonly dismissButtons; private readonly placement; private readonly toastUnderPointer; private readonly toastWithFocus; private readonly isAttended; protected readonly regionPlacement: string; protected readonly enteringFrom: string; protected readonly notifications: import("@angular/core").Signal; constructor(); protected iconFor(toast: Notification): string; protected textOf(kind: NotificationKind): string; protected edgeOf(kind: NotificationKind): string; protected tintOf(kind: NotificationKind): string; protected roleFor(kind: NotificationKind): 'alert' | 'status'; protected runAction(toast: Notification, click: MouseEvent): void; protected dismiss(id: string, click: MouseEvent): void; protected pointerMoved(event: Event): void; protected pointerLeft(): void; protected focusEntered(event: FocusEvent): void; protected focusLeft(event: FocusEvent): void; private forgetWhatLeft; private holdWhileAttended; private handFocusToNeighbour; private dismissButtonBeside; private isInRegion; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } /** One capability as shown in the permissions surface: whether the user currently keeps it on. */ export interface PluginCapabilityState { readonly capability: Capability; readonly effective: boolean; } /** A plugin and the base-granted capabilities the user can manage for it. */ export interface PluginPermissions { readonly pluginId: string; readonly capabilities: readonly PluginCapabilityState[]; } /** * The live capability broker. The distribution's grants (from the composition root, or from a * product backend that provides `CAPABILITY_GRANTS`) form each plugin's **base** set * (grant ∩ declaration). On top of that the user can **revoke** a granted capability at runtime; the * decision is user-local and persisted through the {@link SETTINGS_STORE}, exactly like the rest of * the chrome state. Every `ctx` call consults {@link isGranted}, so a revocation takes effect on the * plugin's next call without a reload, and the user's choices in the Permissions settings can never * reach wider than the distribution allowed. */ export declare class CapabilityGrantService { private readonly grants; private readonly revocations; private readonly revoked; private readonly bases; /** The plugins and their base-granted capabilities, for the permissions settings surface. */ readonly permissions: import("@angular/core").Signal; /** * Records a plugin's base grant (grant ∩ declaration) so enforcement and the permissions surface know * about it. A runtime calls this once when it activates a plugin. `granted` overrides the * distribution's static grant source — used for user-installed plugins, whose grant is the user's * install consent instead of the composition root. */ register(pluginId: string, declared: readonly Capability[] | undefined, granted?: readonly Capability[]): void; /** Drops a plugin from the permissions surface (its runtime deactivated it). Idempotent. */ unregister(pluginId: string): void; /** Whether `pluginId` may use `capability` right now: base-granted and not user-revoked. */ isGranted(pluginId: string, capability: Capability): boolean; /** * Whether the distribution granted `capability` to `pluginId` (grant ∩ declaration), **ignoring** any * user revocation. Used only while a plugin activates — a revocation must not stop it from loading and * registering its contributions; it applies to the plugin's runtime `ctx` calls afterwards. */ isBaseGranted(pluginId: string, capability: Capability): boolean; /** Turns a base-granted capability on or off for a plugin (user revoke / restore). Persisted. */ setGranted(pluginId: string, capability: Capability, granted: boolean): void; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** Capabilities granted per plugin id. A missing id means "no grant" → default-deny. */ export type CapabilityGrants = Readonly>; /** * The active capability grants, composed from every {@link provideCapabilityGrants} declaration * in the injector. Defaults to **empty** (default-deny — no plugin can do anything until granted). * A product feeds per-tenant grants from its own backend by providing this token itself. */ export declare const CAPABILITY_GRANTS: InjectionToken>>; /** * A distribution declares which plugin gets which capabilities. The composition root is the * authoritative grant source, and a plugin it does not name gets nothing, first-party or not. A * product backend can replace it by providing `CAPABILITY_GRANTS`. * * Several calls add up: each declaration contributes what it lists, and a plugin named in more * than one holds the union. That is what lets a generator append one call per plugin it composes * in, and a single call naming every plugin means exactly what it always meant. */ export declare function provideCapabilityGrants(grants: CapabilityGrants): Provider; /** * The effective capability set for one plugin: what the distribution granted, **intersected** with * what the plugin declares ("plugin declares, distribution grants"). A grant for an * undeclared capability is inert, so least privilege holds in both directions; dev mode flags the * mismatch so the composition stays honest. A plugin without a declaration keeps its grants as-is. */ export declare function effectiveCapabilities(pluginId: string, granted: readonly Capability[] | undefined, declared: readonly Capability[] | undefined): ReadonlySet; /** * The shared shape of the shell's two persistence ports: `SETTINGS_STORE` for genuine * settings and `WORKING_STATE_STORE` for working state. A store implemented once fits either port — * only what the shell routes through it differs. * * It is a plain **string** key-value store: callers serialise and validate their own payloads. * `get`/`set`/`delete` are async so a network-backed store fits; the optional {@link peek} is * a synchronous fast-path for bootstrap-critical reads (theme, panel sizes) that must apply before the * first paint. A store that cannot answer synchronously (network-backed) omits `peek`; callers then start * from a default and reconcile via `get`. */ export interface KeyValueStore { /** * Reads the stored string for `key`, or `undefined` if absent. Untrusted — callers validate its shape. * Best-effort like {@link set}: prefer resolving with `undefined` over rejecting when the read fails * (a network store that is offline or unauthorized). The shell treats a rejection as "no value" rather * than failing startup, but a store that rejects makes the user's data silently unavailable. */ get(key: string): Promise; /** Writes `value` for `key`. Best-effort: implementations resolve even when persistence fails. */ set(key: string, value: string): Promise; /** Removes the value for `key`. */ delete(key: string): Promise; /** * Optional synchronous snapshot of `key` for bootstrap-critical reads that must apply before first * paint. Omitted by stores that can only answer asynchronously — callers fall back to {@link get}. */ peek?(key: string): string | undefined; } /** * Default {@link KeyValueStore}: user-local `localStorage`. Reads and writes are synchronous * under the hood — `peek` exposes that for zero-flash bootstrap — and wrapped in a best-effort * try/catch (private browsing, quota, corrupt payloads). Both persistence ports * default to an instance of this store. */ export declare class LocalStorageStore implements KeyValueStore { peek(key: string): string | undefined; get(key: string): Promise; set(key: string, value: string): Promise; delete(key: string): Promise; } /** * The storage keys that stay **device-level** by default when the stores are identity-scoped: * preferences that reasonably belong to the browser, not the signed-in user. Extend the * default via `provideIdentityScopedStores({ deviceKeys: [...DEVICE_LEVEL_KEYS, 'my.key'] })`. */ export declare const DEVICE_LEVEL_KEYS: readonly string[]; /** Configuration for {@link provideIdentityScopedStores}. */ export interface IdentityScopedStoreOptions { /** * The identity discriminator, read synchronously. Return the current principal's stable id * (typically `AuthSnapshot.subject`; encode the tenant into it if tenant switches should * separate state). `null`/`undefined`/`''` means anonymous — keys pass through **unscoped**, so * a signed-out app behaves exactly like the unwrapped stores. Both stores **latch the first * non-empty value per boot** through one shared latch and never follow a live switch afterwards: * a change to a *different* subject only takes effect across the reload boundary * (`provideAuthSource(..., { onIdentityChange: 'reload' })`), so in-flight writes of the * departing user — a pending debounce, a commit during the login transition — can never land in * the next user's namespace. The shell peeks bootstrap-critical keys before first paint, so the * discriminator must be answerable synchronously at boot (persist the last-known subject * yourself). Where it cannot be, nothing stored for that user is lost: from the moment an * anonymous session adopts a namespace, writes are held, everything already read is read again * from the adopted namespace, and what was built while nobody was known is kept only where the * adopted namespace answers with nothing. What remains is the flash before the session lands. */ identity: () => string | null | undefined; /** * Exact keys that stay unscoped regardless of identity. **Replaces** the default * ({@link DEVICE_LEVEL_KEYS}) — spread it to extend. Applied to both stores; the built-in * defaults are all settings keys, but a distribution may declare device-level keys of its own. */ deviceKeys?: readonly string[]; /** The wrapped settings store. Defaults to the built-in `localStorage` store. */ settingsStore?: KeyValueStore; /** The wrapped working-state store. Defaults to the built-in `localStorage` store. */ workingStateStore?: KeyValueStore; } /** * Scopes **both** persistence ports (`SETTINGS_STORE` and `WORKING_STATE_STORE`) per * signed-in user: every key outside `deviceKeys` is prefixed `lw.id.:` while * someone is signed in, so on a shared browser one user's pane trees, tab titles, workspaces, * view state and plugin settings are never re-hydrated for the next. Device-level keys and the * anonymous session write the original, unprefixed keys — a distribution without auth is * byte-identical to the unwrapped stores. * * Place after `provideShell()` (last provider wins). The standard answer to per-user state on a * shared browser; pair with `provideAuthSource(..., { onIdentityChange: 'reload' })` so a user * switch re-hydrates cleanly from the new namespace. The cross-tab sync wrapper is applied on top * of both stores; `peek` is offered only when the wrapped store offers it, because the * shell branches on the *presence* of `peek` (sync bootstrap vs. async hydration). */ export declare function provideIdentityScopedStores(options: IdentityScopedStoreOptions): (Provider | EnvironmentProviders)[]; /** * The **settings** persistence port: deliberate user decisions only — theme, * language, text size, plugin settings, installed/disabled plugins, capability revocations and the * saved-workspaces list. Working state (view state, layout, usage traces) never flows through this * port; it lives behind `WORKING_STATE_STORE`. That guarantee is structural, which is what makes * this port the right one for a product backend: writes are rare, small and roaming-worthy, so a * REST-backed implementation receives exactly what it expects. Swap the backing store with {@link provideSettingsStore}; the built-in default * is {@link LocalStorageStore}. */ export declare const SETTINGS_STORE: InjectionToken; /** * Provides a custom settings {@link KeyValueStore}, replacing the default `localStorage` one — * place after `provideShell()` (last provider wins). Pass a class (DI-constructed) or a ready * instance. The cross-tab sync wrapper is applied on top either way. */ export declare function provideSettingsStore(store: KeyValueStore | Type): Provider; /** * Where a synced key's fresh value is read back from after another window announced a change: * the `SETTINGS_STORE`, the `WORKING_STATE_STORE`, or — for state persisted outside * both ports, such as a product session store — `'external'`, in which case no read-back happens * and the applier receives `undefined` (it re-reads its own storage). */ export type SyncSource = 'settings' | 'working-state' | 'external'; /** * Re-applies a key that another browser window just wrote. `raw` is the value read back from the * registered {@link SyncSource}'s store (`undefined` for `'external'` registrations) — the store * stays the single source of truth, the message only names the key. An applier must **set its * state without persisting again**, or two windows would write back and forth forever. */ export type ApplySyncedState = (raw: string | undefined, key: string) => void; /** * Cross-tab live sync at the persistence ports. Every write through either port * broadcasts its **key** to the other windows of the same origin over a `BroadcastChannel`; a * window that has registered a reaction for that key reads the fresh value back through the * registered source's store and applies it — so it works with any store behind the ports, * including a product's HTTP-backed one. * * The shell registers its own keys (settings such as theme, language and text size on the * `'settings'` source; view state, view instances and the palette's recently-used list on * `'working-state'`), so plugins inherit the sync for free: their state lives in host-managed * stores. **Layout keys are deliberately not registered** — two windows are meant to be able to * show different layouts. * * A distribution registers its own keys the same way, most usefully its product session key * (source `'external'`): when it changes, the other window's `AuthSnapshot` flips and the existing * `onIdentityChange` policy takes over. Use {@link announce} when that state is * persisted outside the ports, and {@link notifyRemoteChange} when a backend push transport * reports a change made on another device. */ export declare class StateSyncService { private readonly settings; private readonly workingState; private readonly channel; private readonly exact; private readonly prefixes; private readonly adopters; constructor(); /** * Reacts to remote writes of exactly `key`; `source` names the store the fresh value is read * back from. One applier per key — registering again replaces it. Returns a disposer that * unregisters this applier (a later re-registration always wins over a stale disposer). */ register(source: SyncSource, key: string, apply: ApplySyncedState): () => void; /** * Reacts to remote writes of every key starting with `prefix` — for key families such as * `lw.shell.view-state:`. Exact registrations win over prefixes. Returns a disposer that * unregisters this applier (a later re-registration always wins over a stale disposer). */ registerPrefix(source: SyncSource, prefix: string, apply: ApplySyncedState): () => void; /** * Reacts to the workbench's storage namespace being adopted after state was already read from * another one — the first sign-in of a session that began anonymously. Registered keys are read * again on their own; this is for state whose keys are not known in advance, such as a family * keyed per plugin or per workspace: the handler re-reads what its owner holds. Returns a * disposer. */ onNamespaceAdopted(handler: () => void | Promise): () => void; /** * Reads every registered key again through its port and runs the handlers registered with * {@link onNamespaceAdopted}. Called by the shell when the identity behind the stores becomes * known after the first read; a distribution that scopes the ports itself calls it when its own * namespace changes under a running application. */ namespaceAdopted(): Promise; /** * Announces a change this window made to state that is persisted **outside** the persistence * ports — a product session store, for example. Writes through the ports broadcast on their own; * this is the escape hatch for state the ports never see. Notifies only the **other** windows. */ announce(key: string): void; /** * Runs the registered applier for `key` in **this** window, reading the fresh value back from * the registered source's store. The entry point for cross-device live sync: a * backend-backed store paired with a push transport (SSE, WebSocket) calls this when the backend * reports a key changed on another device. No-op for keys without a registration. */ notifyRemoteChange(key: string): void; private applyRemote; private readBack; private registrationFor; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * The **working-state** persistence port: state that accrues from using the app rather * than from deciding something — view state and view instances, the palette's recently-used list, * and the window-local layout keys (pane trees, panel sizes, collapse state, item order, view * placement). Writes are frequent and debounced, which is why this port defaults to the device * ({@link LocalStorageStore}) instead of a product backend. * * A distribution that wants working state to travel across devices provides a backend-backed store * with {@link provideWorkingStateStore}: a fresh tab then hydrates the last persisted state at boot * with no further wiring. For *live* cross-device updates, pair the store with a push transport * (SSE, WebSocket) that calls `StateSyncService.notifyRemoteChange` when the backend reports a * changed key. */ export declare const WORKING_STATE_STORE: InjectionToken; /** * Provides a custom working-state {@link KeyValueStore}, replacing the default `localStorage` one — * place after `provideShell()` (last provider wins). Pass a class (DI-constructed) or a ready * instance. The cross-tab sync wrapper is applied on top either way. */ export declare function provideWorkingStateStore(store: KeyValueStore | Type): Provider; /** A plugin as shown in the permissions surface: its id, display name and whether it is enabled. */ interface PluginInfo { readonly id: string; readonly name: string; readonly enabled: boolean; } /** * 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 * whole plugin so **none** of its contributions appear; enabling loads it again. The decision is * user-local and persisted through the {@link SETTINGS_STORE}, exactly like the rest of the chrome state. * * This service holds only the state; the runtimes ({@link PluginRuntime}, {@link FramePluginRuntime}) * inject it and reconcile activation against {@link disabled} reactively, so a toggle takes effect at * once without a reload. It never depends on the runtimes, which keeps the dependency one-way. */ export declare class PluginEnablementService { private readonly required; private readonly deployment; private readonly storedDisabled; private readonly names; /** * The disabled plugin ids (reactive) — a runtime reconciles activation against this. A plugin the * distribution declared its application cannot run without never appears here, and neither does * one the operator deploys, whatever is stored, so the runtimes, the store and the permissions * surface read one answer rather than several. */ readonly disabled: import("@angular/core").Signal>; /** Every known plugin with its enabled state, for the permissions settings surface. */ readonly plugins: import("@angular/core").Signal; /** Whether the distribution declared it cannot run without this plugin. */ isRequired(id: string): boolean; /** Records a plugin so the permissions surface can list it (enabled or not). Idempotent. A runtime calls it for every plugin it knows. */ register(id: string, name: string): void; /** Drops a plugin from the list — it was uninstalled, not merely disabled. Idempotent. */ unregister(id: string): void; /** Whether `id` is currently enabled (default: yes — a plugin is on until the user turns it off). */ isEnabled(id: string): boolean; /** * Turns a whole plugin on or off (persisted). The runtimes react by loading/unloading it. Turning * off a plugin the distribution declared it cannot run without does nothing: the surface offers no * switch for one, and this is the same answer read from anywhere else. */ setEnabled(id: string, enabled: boolean): void; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵprov: i0.ɵɵInjectableDeclaration; } /** * A **sandboxed** plugin a distribution registers: its code is not a {@link Plugin} object running * in the page but a URL to an isolated document, loaded into an `