import{type PropertyValues,type TemplateResult}from'lit';import{LyraElement}from'../../../internal/lyra-element.js';import type{LyraFrame,LyraVariant}from'../../../internal/variants.js';import'../../layout/details/details.class.js';import'../../utility/json-viewer/json-viewer.class.js';import'../../utility/live-region/live-region.class.js';import'../../forms/button/button.class.js';import{type ApprovalAction,type ApprovalDecision}from'../approval-state.js';export type ConfirmBarDecision=ApprovalDecision|null; /** A genuine two-member subset of the shared `LyraVariant` vocabulary: a confirmation is either * routine or destructive, and `brand`/`success`/`warning` have no meaning for a proposal awaiting * a yes/no. Spelled as an `Extract` of the shared union rather than a re-declared literal pair so * the two can never drift apart. */ export type ConfirmBarVariant=Extract; /** * Where the bar hands focus once a decision lands. An element, `null` for "no preference", or a * thunk -- called at handoff time and, only if that first call does not yet name a live, * focusable control, called again once the host has had a chance to react to the decision. A host * that swaps a focused control out for this bar often re-creates that control on the way back, * asynchronously, so the element it wants focus returned to does not necessarily exist yet at the * moment the decision is made; the second call is what lets that motivating case actually work. A * plain element value is resolved once, synchronously, and never retried -- it names something * that either already exists or never will. */ export type ConfirmBarReturnFocusTarget=HTMLElement|null|(()=>HTMLElement|null); /** * The ExtendableEvent-style resolver carried by `lr-approve`/`lr-deny`'s detail. Calling it during * the dispatch holds the bar in its `pending` presentation until the promise settles: a resolution * finalizes the decision, a rejection restores the undecided state. Calling it more than once (from * one listener or several) waits for all of them. */ export type ConfirmBarWaitUntil=(promise:Promise)=>void;export interface LyraConfirmBarEventMap{'lr-approve':CustomEvent<{args:unknown;waitUntil:ConfirmBarWaitUntil;}>;'lr-deny':CustomEvent<{waitUntil:ConfirmBarWaitUntil;}>;'lr-decision-settled':CustomEvent<{decision:ApprovalDecision;}>;} /** * `` — an inline, non-modal approve/deny block for one proposed action: the * in-flow sibling of `` for confirmations that should sit in the * transcript instead of hijacking focus. Same `lr-approve`/`lr-deny` event shapes as the dialog, * and the same `toolApprovalHeading`/`toolApprovalArgsLabel`/`deny`/`approve` localization keys, so * the two always translate in lockstep. * * Non-modal by contract: no focus trap, no scroll lock, no Escape/backdrop semantics, and it never * steals focus when it appears in the transcript. DOM and tab order put Deny before Approve (the * dialog's safe-action-first rationale). On activation, focus moves synchronously to the first * available of `returnFocusTo` and `[part="status"]` (an always-rendered, `tabindex="-1"` element) * *before* the Deny/Approve buttons unmount, so focus never has a gap where it would otherwise fall * back to ``. `returnFocusTo` is the opt-in half: unset, the handoff lands on `[part="status"]` * exactly as it always has, which keeps the decided status reachable but is a dead end for a host * that is about to unmount the bar. Set it to the control the bar replaced (or a thunk resolving to * it) and the same handoff returns focus there instead, falling back to `[part="status"]` whenever * the named element is missing, detached, `inert` or otherwise refuses focus -- an `inert` element * refuses `focus()` silently, so an unchecked handoff would strand the user on `` at exactly * the moment a decision was announced. When `returnFocusTo` is a thunk and that immediate call * fails, the same handoff quietly retries once more after the host has had a real chance to react * (see `ConfirmBarReturnFocusTarget`'s doc comment) -- the case a thunk exists for in the first * place is a host that has not re-created its control yet at the instant the decision lands, and * every supported host framework re-renders asynchronously relative to this synchronous handoff. * * "Never steals focus" and "no Escape semantics" describe the bar's behavior when `autofocus` and * `escape-denies` are both left unset (the default). A host that swaps a focused control out for * this bar -- the case those two properties exist for -- can opt into either or both instead of * hand-rolling them: `autofocus` moves focus into the bar after its own first render (the Deny * control when it's present and enabled, matching the safe-action-first DOM order above, else the * always-present `[part="status"]`), and `escape-denies` maps Escape on `[part="base"]` to the same * outcome as clicking Deny. `` predates both and still implements this focus/Escape * handoff itself (`focusPendingConfirmation`/`onConfirmKeyDown`) rather than depending on them. * `escape-denies` is scoped to this element's own `[part="base"]`, never `document`: this bar is * inline and non-modal, not a member of the shared `activateOverlay()` Escape/stacking contract * (`src/internal/overlay-manager.ts`) that real overlays use, so it must not swallow Escape intended * for an unrelated enclosing dialog or popover. Neither property traps focus or locks scrolling. * * No argument editing (escalate to ``'s `editable` when edit-before-approve * matters); no blocking/modality guarantee (a user can scroll past); no decision persistence or * "remember choice" logic (the `footer` slot + host own that). * * Density and chrome are two knobs, not one: `compact` tightens the bar into a single dense inline * row and `frame="plain"` removes the card border/radius/background/padding, exactly as they do on * ``, ``, ``, ``, `` and * ``. Before 9.0.0 `compact` alone did both; a bar that relied on that now * wants `compact frame="plain"`. * * Deny/Approve are ``s. Deny is `variant="neutral" appearance="outlined"` and Approve is * `variant="brand"` (`"danger"` under `variant="danger"`) at lr-button's default `appearance="accent"`, * so the destructive-or-primary action is the loud one and the safe action recedes. Both appearances * are stated rather than inherited: a bar whose look depends on another component's default changes * silently when that default does. Both are composed children rendered by this component, each * re-exporting `lr-button`'s own `base`/`label`/`start`/`end`/`spinner` parts under * `{deny,approve}-button-{base,label,start,end,spinner}` so `--lr-button-*` theming and a consumer's * existing `lr-button` style fragments reach them like every other button in an app. * * Async decisions have two entry points, and the declarative one is preferred. `lr-approve`/ * `lr-deny`'s detail carries `waitUntil(promise)`, ExtendableEvent-style: calling it during the * dispatch puts the bar into `pending` (showing `loading` on the activated button and `disabled` on * the other) and the promise's settlement finalizes the decision or bounces it back for a retry, so * the component owns the whole state machine and no listener has to cast its `currentTarget`, write * `pending`, and remember to await `updateComplete` before unmounting. Several `waitUntil()` calls * from several listeners are awaited together. The imperative path it replaces still works and is * unchanged: `preventDefault()` alone sets `pending` to the action being persisted until the host * finalizes by setting `.decision` or bounces back by clearing `.pending` to `null`. A listener that * instead resolves the decision itself synchronously (setting `.decision` or `.pending` directly * during the dispatch) wins outright over both -- `decide()` only applies its own `pending` * bookkeeping, `waitUntil()`'s included, when the listener left both untouched, because `emit()` is * synchronous and a write that lands during it would otherwise be silently clobbered. * * `lr-decision-settled` fires after the decided status has rendered and been announced, on every * path that reaches a decision -- the bar's own, a `waitUntil()` settlement, and a host writing * `.decision` directly. It exists so a host can unmount the bar on that signal instead of guessing * whether the announcement has already happened. * * The host-writable `disabled` independently blocks both Deny and Approve and makes `decide()` a * no-op, without discarding any in-flight `decision`/`pending` state. * * @customElement lr-confirm-bar * @slot - Supplementary body content between the heading and the actions (e.g. a `lr-diff-view` of * the proposed change). * @slot footer - Extra content at the start of the action row (e.g. a "remember this choice" * checkbox), mirroring `lr-tool-approval-dialog`'s own `footer` slot. * @event lr-approve - `detail: { args, waitUntil }` — `args` is the `args` prop as-is (no editing in * the bar), matching `lr-tool-approval-dialog`'s own `args` detail. Cancelable: a listener calling * `preventDefault()` sets `pending` to `'approve'` instead of finalizing synchronously; set * `.decision` (or clear `.pending` back to `null`) once your async work settles. `waitUntil(promise)` * does the same thing declaratively and needs no `preventDefault()`: the bar stays pending until * the promise settles, then finalizes on resolution or bounces back on rejection. * @event lr-deny - `detail: { waitUntil }`, the same resolver `lr-approve` carries and no other data, * matching the dialog's detail-free `lr-deny`. Cancelable, same `pending` mechanism as `lr-approve`. * @event lr-decision-settled - `detail: { decision }`. Emitted after the decided `[part="status"]` * has rendered and its live-region announcement has been made, on every path that reaches a * decision, including a host writing `.decision` directly. Non-cancelable: the decision is already * final. A host that replaces the bar with its own result UI can do it on this event without * awaiting `updateComplete` itself. * @csspart base - The root (`role="group"`). * @csspart heading - The heading. * @csspart tool-name - The tool-name span within the heading. Only rendered when `heading` is unset. * @csspart body - The default-slot wrapper. * @csspart args - The `lr-details` + `lr-json-viewer` wrapper. Only rendered when `args` is * defined. * @csspart footer - The action row. * @csspart deny-button - The built-in Deny ``. Named identically to the dialog's part. * @csspart deny-button-base - Forwarded from the internal Deny ``'s same-node `base` * and `button` wrapper aliases. * @csspart deny-button-label - Forwarded from the internal Deny ``'s own `label` part. * @csspart deny-button-start - Forwarded from the internal Deny ``'s own `start` part. * @csspart deny-button-end - Forwarded from the internal Deny ``'s own `end` part. * @csspart deny-button-spinner - Forwarded from the internal Deny ``'s own `spinner` * part, present only while `pending` is `'deny'`. * @csspart approve-button - The built-in Approve ``. Named identically to the dialog's * part. * @csspart approve-button-base - Forwarded from the internal Approve ``'s same-node * `base` and `button` wrapper aliases. * @csspart approve-button-label - Forwarded from the internal Approve ``'s own `label` * part. * @csspart approve-button-start - Forwarded from the internal Approve ``'s own `start` * part. * @csspart approve-button-end - Forwarded from the internal Approve ``'s own `end` part. * @csspart approve-button-spinner - Forwarded from the internal Approve ``'s own * `spinner` part, present only while `pending` is `'approve'`. * @csspart status - The decided-state text. Always present in the DOM (`tabindex="-1"`) so focus has * a stable, synchronous landing spot on activation. * @cssprop [--lr-confirm-bar-bg=var(--lr-color-surface)] - Resting background of `[part='base']`. * `frame="plain"` still paints transparent. * @cssprop [--lr-confirm-bar-compact-padding=var(--lr-space-s)] - Padding of `[part='base']` while * `compact`. Accepts any padding shorthand. Overridden entirely by `frame="plain"`. * @cssprop [--lr-confirm-bar-compact-gap=var(--lr-space-s)] - Gap between the row's items while * `compact`. * @cssprop [--lr-confirm-bar-approved-color=var(--lr-color-success)] - `[part='status']` text/icon * color once `decision` is `'approved'`. * @cssprop [--lr-confirm-bar-denied-color=var(--lr-color-danger)] - `[part='status']` text/icon * color once `decision` is `'denied'`. * @status stable * @since 4.0.0 */ export declare class LyraConfirmBar extends LyraElement{static styles:import("lit").CSSResultGroup[]; /** Drives the default heading through the existing dialog keys. */ toolName:string; /** Free-form heading override for non-tool proposals. Wins over `toolName` when set. */ heading:string; /** Shown read-only inside a collapsed `lr-details` + `lr-json-viewer` when defined. */ args:unknown;private _decision;private _pending; /** Marked on every write to `decision`/`pending`, from any source. `decide()` opens it * immediately before dispatching `lr-approve`/`lr-deny` and reads it back afterward: since the * dispatch is synchronous, only a listener invoked during that same `emit()` call can have * marked it in between. */ private readonly dispatchWriteGuard; /** Decided state. Set by the component on activation *and* host-writable (an externally-resolved * decision -- timeout, another reviewer -- renders identically and emits no `lr-approve`/`lr-deny` * of its own; the settled notification still fires, because the status really did render). */ get decision():ConfirmBarDecision;set decision(value:ConfirmBarDecision); /** Which action is awaiting host resolution, while an lr-approve/lr-deny listener has called * preventDefault(). Host-writable: set back to null to bounce back to the undecided state (e.g. * on failure, so the user can retry), or set `decision` to finalize. */ get pending():ApprovalAction|null;set pending(value:ApprovalAction|null); /** Disables both Deny and Approve and makes `decide()` a no-op, without discarding any * in-flight `decision`/`pending` state. Distinct from `pending`: `pending` marks one specific * action as awaiting the host while the other stays interactive, while `disabled` blocks both * regardless of `pending`. Reflects as an attribute. */ disabled:boolean; /** Token-mapped emphasis for destructive proposals. */ variant:ConfirmBarVariant; /** Collapses the bar from a stacked `display: block` card to a single tightly-padded inline row, * for a confirmation that has to live inside an existing container -- a table cell, a card's * action row, a toolbar. The host becomes `inline-flex`, and the narrow-allocation `@container` * treatment is switched off (a compact bar is *expected* to be narrow, so stretching the buttons * to fill would be exactly wrong). Purely a density/layout knob -- same convention as * ``'s `compact`: the border, corner radius and background stay, so use * `frame="plain"` to drop the chrome. Retune the density through * `--lr-confirm-bar-compact-padding`/`-gap`. Everything else -- the event shapes, the * focus-to-`[part='status']`-before-unmount contract, `role="group"` and its heading label -- * is unchanged. */ compact:boolean; /** Visual chrome, in the library's shared container-frame vocabulary. `'card'` (the default) * keeps the bordered, filled, padded box. `'plain'` removes the border, background, padding and * corner radius, so a bar nested inside a host container that already draws a border (a table * cell, an `` action row) doesn't double it. `plain` wins over `compact` when * both are set -- there is no padding left to tighten. The Deny/Approve ``s keep * their own border/background either way, so a chrome-less bar still has a visible interactive * affordance. */ frame:LyraFrame; /** Opt-in focus-on-mount: moves focus into the bar after its own first render, once this * element and (when present) the Deny `` have both completed it. Named after the * native global attribute it stands in for -- the platform's own `autofocus` algorithm only * fires for an element already in the document when it finishes parsing, never for one a host * swaps in afterward (this bar's primary use, per the class doc), so this implements the same * intent explicitly instead. Defaults to `false`: nothing here steals focus from a host that * doesn't ask for it. */ autofocus:boolean; /** Opt-in: maps Escape on `[part="base"]` to the same outcome as clicking Deny. See the class * doc for why this is scoped to this element's own base rather than `document`. A no-op while * `disabled`, already decided, or `pending`, exactly like clicking Deny itself. Defaults to * `false` -- an unlabelled Escape denying a proposal is a real behavior change a host must * choose explicitly. */ escapeDenies:boolean; /** Where focus goes once a decision lands, instead of parking on `[part="status"]`. Property-only * (an element reference has no attribute form), and a thunk is accepted so the lookup happens at * handoff time rather than at assignment time -- and, if that first lookup does not yet name a * live control, is repeated once more after the host has had a chance to react (see * `ConfirmBarReturnFocusTarget`). The motivating case is the one the class doc opens with: a * host swaps a focused control out for this bar, and once the decision is made focus belongs * back on that control (or on whatever replaced it), not on a status line the host is about to * unmount -- and that replacement control is typically re-created asynchronously, after the * decision has already landed. Unset (`null`) keeps the shipped behavior exactly. A named * target that is missing, detached, `inert` or otherwise refuses focus falls back to * `[part="status"]` rather than to ``. */ returnFocusTo:ConfirmBarReturnFocusTarget;private statusEl?;private liveRegion?;private hasBodySlot;private readonly headingId; /** Bumped by every `decide()` that starts awaiting a `waitUntil()` promise, so a settlement that * arrives after a newer decision began (a bounce, then a second activation) is discarded instead * of resolving the wrong one. */ private deferralGeneration;protected willUpdate(changed:PropertyValues):void;protected firstUpdated(changed:PropertyValues):void;private onBodySlotChange; /** * `autofocus`'s implementation: the Deny control when it's present and actually focusable, else * `[part="status"]` -- the same fallback `decide()` itself already leans on when Deny is * unavailable. Awaits this element's own first update (already true by the time `firstUpdated()` * calls this, but `updateComplete` also resolves once any update it scheduled settles) and, when * a Deny `` exists, its own first update too: whenever a host mounts this bar in place * of a control it just removed (the motivating case in the class doc), that button is brand new * and has not necessarily rendered its own focusable internals yet. */ private focusInitial; /** `escape-denies`'s implementation, bound to `[part="base"]` -- see the class doc for why this * is not routed through `activateOverlay()`. Stops propagation only when Escape actually denied * something (mirroring ``'s own `onConfirmKeyDown`): `decide()` is a no-op * while `disabled`, already decided, or `pending`, and swallowing Escape in that case would * refuse to close an unrelated enclosing dialog for no reason. */ private onBaseKeyDown;private decide; /** * The settlement half of `waitUntil()`. Every write it performs is re-checked against the state * it left behind rather than applied blind: the promise settles in a later task, and by then the * host may have finalized the decision out of band, bounced `pending` itself, or started a whole * new decision. `generation` covers that last case, which the property checks alone cannot -- a * second pending decision for the same action is state-identical to the first. */ private awaitDeferredDecision; /** * A rejection restores the bar, and focus is part of it: the handoff into the pending state * parked focus on `[part="status"]` because the activated button was about to become `disabled`, * so leaving it there would hand a retryable bar back to a keyboard user with focus on a static * line of text. `repairComposedFocus()` rather than an unconditional move: if focus has since * gone somewhere else entirely, it belongs to whatever the user is doing now. The button is only * focusable again once `loading`/`disabled` have actually come off it, hence the awaited update. */ private returnFocusAfterBounce; /** Resolves `returnFocusTo`, which may be a thunk, at the moment focus is actually handed over. */ private resolvedReturnFocusTarget; /** * The terminal focus handoff: the host's named return target when it names one that can really * take focus right now, else the always-present `[part="status"]` -- unconditional, exactly as * the `[part="status"]` move it replaces always was, because the control the user just activated * is about to unmount and the handoff cannot wait to be sure that control held focus. * * `returnFocusTo` accepts a thunk precisely so a host can name a control it has not re-created * yet -- the class doc's motivating case: a host swaps this bar back out for the control it * replaced, and every supported host framework does that asynchronously relative to this * synchronous handoff. Resolving the thunk only here would therefore always find that control * missing, which is the whole defect this guards against. When the immediate resolution fails * (null, disconnected, `inert`, or otherwise not focusable) and `returnFocusTo` is itself a * function, `deferComposedFocusRepair()` re-resolves it once the host has had a real chance to * react, and moves focus there only if it has since appeared and nothing else has claimed focus * in the meantime. A plain element value is never retried: it names something that either * already exists or never will, so the single synchronous resolution above is already the final * answer, and every previously-working synchronous case (an immediately-resolving thunk or a * live element) returns before scheduling anything. */ private handOffDecidedFocus;protected updated(changed:PropertyValues):void;private renderHeading;private statusText;private stopNestedLifecycle;render():TemplateResult;}declare global{interface HTMLElementTagNameMap{'lr-confirm-bar':LyraConfirmBar;}}