@aceshooting/lyra-ui 24.0.0 → 24.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,10 @@ import type{MarkdownStreamingRender}from'../markdown/markdown-shared.js';import
15
15
  * Composer value, follow state, and retrieval selection are request-only:
16
16
  * child events are forwarded, but the workspace never writes those public
17
17
  * properties. The host applies accepted state back to the component.
18
+ * A vetoed tool approval in the built-in timeline stays pending until the host calls
19
+ * `finalizePendingApproval()` after persistence or `revertPendingApproval()` after failure.
20
+ * Async hosts must ignore stale completions after replacing the run or tools because these
21
+ * methods act on the currently pending approval.
18
22
  *
19
23
  * Public collection properties take bounded, clone-owned readonly snapshots. Create a new
20
24
  * collection and reassign it after changes; mutating the assigned array does not update the view.
@@ -117,7 +121,13 @@ private composerStatusValue;get composerStatus():ChatComposerStatus;set composer
117
121
  /** Placeholder for the built-in composer. */
118
122
  composerPlaceholder?:string;
119
123
  /** Minimum and maximum rows for the built-in composer. */
120
- composerMinRows:number;composerMaxRows:number;private hasSlotted;private get safeContextTotal();private get effectiveTools();private get effectiveRetrievalChunks();private get effectiveCitations();private get effectiveContextSegments();private get effectiveMessages();private get safeUnreadStartIndex();private get safeComposerMinRows();private get safeComposerMaxRows();private onRetrievalSelect;private onMessageRetry;private stopOwnedEvent;private renderMessage;private renderMessages;private messageWindowOffsetFor;
124
+ composerMinRows:number;composerMaxRows:number;private hasSlotted;private builtInToolTimeline;
125
+ /** Closes a vetoed approval in the built-in tool timeline after the host persists its decision.
126
+ * No-op when that timeline or a pending approval is absent, including with a custom details slot. */
127
+ finalizePendingApproval():void;
128
+ /** Releases a vetoed approval in the built-in tool timeline after persistence fails, preserving
129
+ * the open dialog and its edited arguments for retry. No-op without the built-in timeline. */
130
+ revertPendingApproval():void;private get safeContextTotal();private get effectiveTools();private get effectiveRetrievalChunks();private get effectiveCitations();private get effectiveContextSegments();private get effectiveMessages();private get safeUnreadStartIndex();private get safeComposerMinRows();private get safeComposerMaxRows();private onRetrievalSelect;private onMessageRetry;private stopOwnedEvent;private renderMessage;private renderMessages;private messageWindowOffsetFor;
121
131
  /** Bound to every named slot whose assignment `render()` reads back through `hasSlotted()` --
122
132
  * `messages` (which decides how `safeUnreadStartIndex` is projected), `details`, `composer`, and
123
133
  * `header-actions` (whose empty wrapper is hidden).
@@ -1,4 +1,4 @@
1
- import{__decorate}from"tslib";import{collectionSupport}from"../../../internal/collection-snapshot.js";import{html,nothing}from"lit";import{property}from"lit/decorators.js";import{repeat}from"lit/directives/repeat.js";import{LyraElement}from"../../../internal/lyra-element.js";import{finiteCount}from"../../../internal/numbers.js";import{normalizeChatComposerStatus}from"../chat-composer/chat-composer.class.js";import{styles}from"./agent-workspace.styles.js";import{trueDefaultBooleanConverter}from"../../../internal/converters.js";import{LYRA_DEFAULT_agentWorkspaceContext,LYRA_DEFAULT_agentWorkspaceConversation,LYRA_DEFAULT_agentWorkspaceDetails,LYRA_DEFAULT_agentWorkspaceEmpty,LYRA_DEFAULT_agentWorkspaceGrounding,LYRA_DEFAULT_agentWorkspaceLabel,LYRA_DEFAULT_agentWorkspaceRetrieval,LYRA_DEFAULT_agentWorkspaceRun,LYRA_DEFAULT_agentWorkspaceTools,LYRA_DEFAULT_composerPlaceholder,LYRA_DEFAULT_fieldRequired}from"../../../internal/default-strings.generated.js";const MAX_RENDERED_MESSAGES=500;function firstByWorkspaceIdentity(items,identity){const result=[],seen=new Set;for(const item of items){let id;try{id=identity(item)}catch{continue}typeof id!="string"||id.trim()===""||seen.has(id)||(seen.add(id),result.push(item))}return result}class LyraAgentWorkspace extends LyraElement{constructor(){super(...arguments),this.streamingRender="plain",this.accessibleLabel=null,this.messages=[],this.run=null,this.metrics=[],this.tools=[],this.retrievalChunks=[],this.selectedRetrievalChunkIds=[],this.retrievalLoading=!1,this.retrievalHasMore=!1,this.retrievalErrorText="",this.groundingAssessment=null,this.citations=[],this.contextSegments=[],this.contextTotal=0,this.follow=!0,this.unreadStartIndex=null,this.withoutDetails=!1,this.withoutComposer=!1,this.composerValue="",this.composerStatusValue="idle",this.composerMinRows=1,this.composerMaxRows=8,this.onRetrievalSelect=event=>{event.stopPropagation(),this.emit("lr-retrieval-select",event.detail)},this.onMessageRetry=event=>{event.stopPropagation(),event.detail.messageId&&this.emit("lr-message-retry",{messageId:event.detail.messageId})},this.onNamedSlotChange=()=>{this.requestUpdate()}}static{this.defaultStrings={...super.defaultStrings,agentWorkspaceContext:LYRA_DEFAULT_agentWorkspaceContext,agentWorkspaceConversation:LYRA_DEFAULT_agentWorkspaceConversation,agentWorkspaceDetails:LYRA_DEFAULT_agentWorkspaceDetails,agentWorkspaceEmpty:LYRA_DEFAULT_agentWorkspaceEmpty,agentWorkspaceGrounding:LYRA_DEFAULT_agentWorkspaceGrounding,agentWorkspaceLabel:LYRA_DEFAULT_agentWorkspaceLabel,agentWorkspaceRetrieval:LYRA_DEFAULT_agentWorkspaceRetrieval,agentWorkspaceRun:LYRA_DEFAULT_agentWorkspaceRun,agentWorkspaceTools:LYRA_DEFAULT_agentWorkspaceTools,composerPlaceholder:LYRA_DEFAULT_composerPlaceholder,fieldRequired:LYRA_DEFAULT_fieldRequired}}static{this.collectionSupport=collectionSupport}static{this.immutableEventDetails=Object.freeze(["lr-retrieval-select"])}static{this.ownedCollectionProperties=Object.freeze(["messages","run","metrics","tools","retrievalChunks","selectedRetrievalChunkIds","groundingAssessment","citations","contextSegments"])}static{this.styles=[LyraElement.styles,styles]}get composerStatus(){return this.composerStatusValue}set composerStatus(value){const previous=this.composerStatusValue;this.composerStatusValue=normalizeChatComposerStatus(value),this.requestUpdate("composerStatus",previous)}hasSlotted(name){return Array.from(this.children).some(element=>element.getAttribute("slot")===name)}get safeContextTotal(){return finiteCount(this.contextTotal)}get effectiveTools(){return firstByWorkspaceIdentity(this.tools,entry=>{const id=entry.id;if(typeof id!="string"||id.trim()==="")return;const sourceKey=entry.sourceKey;return`${typeof sourceKey=="string"?sourceKey:""}\0${id}`})}get effectiveRetrievalChunks(){return firstByWorkspaceIdentity(this.retrievalChunks,chunk=>chunk.id)}get effectiveCitations(){return firstByWorkspaceIdentity(this.citations,citation=>citation.id)}get effectiveContextSegments(){return firstByWorkspaceIdentity(this.contextSegments,segment=>segment.id)}get effectiveMessages(){const source=this.messages,seen=new Set,messages=[];for(let sourceIndex=0;sourceIndex<source.length;sourceIndex++){const message=source[sourceIndex];if(!message)continue;const id=message?.id;typeof id!="string"||id.trim()===""||seen.has(id)||(seen.add(id),messages.push({message,sourceIndex}))}return messages}get safeUnreadStartIndex(){if(this.unreadStartIndex==null)return null;const normalized=finiteCount(this.unreadStartIndex);if(this.hasSlotted("messages"))return normalized;const messages=this.effectiveMessages,effectiveIndex=messages.filter(({sourceIndex})=>sourceIndex<normalized).length;return Math.max(0,effectiveIndex-this.messageWindowOffsetFor(messages))}get safeComposerMinRows(){return Math.max(1,finiteCount(this.composerMinRows,1))}get safeComposerMaxRows(){return Math.max(this.safeComposerMinRows,finiteCount(this.composerMaxRows,8))}stopOwnedEvent(event){event.stopPropagation()}renderMessage(message){return html`
1
+ import{__decorate}from"tslib";import{collectionSupport}from"../../../internal/collection-snapshot.js";import{html,nothing}from"lit";import{property}from"lit/decorators.js";import{repeat}from"lit/directives/repeat.js";import{LyraElement}from"../../../internal/lyra-element.js";import{finiteCount}from"../../../internal/numbers.js";import{tag}from"../../../internal/prefix.js";import{normalizeChatComposerStatus}from"../chat-composer/chat-composer.class.js";import{styles}from"./agent-workspace.styles.js";import{trueDefaultBooleanConverter}from"../../../internal/converters.js";import{LYRA_DEFAULT_agentWorkspaceContext,LYRA_DEFAULT_agentWorkspaceConversation,LYRA_DEFAULT_agentWorkspaceDetails,LYRA_DEFAULT_agentWorkspaceEmpty,LYRA_DEFAULT_agentWorkspaceGrounding,LYRA_DEFAULT_agentWorkspaceLabel,LYRA_DEFAULT_agentWorkspaceRetrieval,LYRA_DEFAULT_agentWorkspaceRun,LYRA_DEFAULT_agentWorkspaceTools,LYRA_DEFAULT_composerPlaceholder,LYRA_DEFAULT_fieldRequired}from"../../../internal/default-strings.generated.js";const MAX_RENDERED_MESSAGES=500;function firstByWorkspaceIdentity(items,identity){const result=[],seen=new Set;for(const item of items){let id;try{id=identity(item)}catch{continue}typeof id!="string"||id.trim()===""||seen.has(id)||(seen.add(id),result.push(item))}return result}class LyraAgentWorkspace extends LyraElement{constructor(){super(...arguments),this.streamingRender="plain",this.accessibleLabel=null,this.messages=[],this.run=null,this.metrics=[],this.tools=[],this.retrievalChunks=[],this.selectedRetrievalChunkIds=[],this.retrievalLoading=!1,this.retrievalHasMore=!1,this.retrievalErrorText="",this.groundingAssessment=null,this.citations=[],this.contextSegments=[],this.contextTotal=0,this.follow=!0,this.unreadStartIndex=null,this.withoutDetails=!1,this.withoutComposer=!1,this.composerValue="",this.composerStatusValue="idle",this.composerMinRows=1,this.composerMaxRows=8,this.onRetrievalSelect=event=>{event.stopPropagation(),this.emit("lr-retrieval-select",event.detail)},this.onMessageRetry=event=>{event.stopPropagation(),event.detail.messageId&&this.emit("lr-message-retry",{messageId:event.detail.messageId})},this.onNamedSlotChange=()=>{this.requestUpdate()}}static{this.defaultStrings={...super.defaultStrings,agentWorkspaceContext:LYRA_DEFAULT_agentWorkspaceContext,agentWorkspaceConversation:LYRA_DEFAULT_agentWorkspaceConversation,agentWorkspaceDetails:LYRA_DEFAULT_agentWorkspaceDetails,agentWorkspaceEmpty:LYRA_DEFAULT_agentWorkspaceEmpty,agentWorkspaceGrounding:LYRA_DEFAULT_agentWorkspaceGrounding,agentWorkspaceLabel:LYRA_DEFAULT_agentWorkspaceLabel,agentWorkspaceRetrieval:LYRA_DEFAULT_agentWorkspaceRetrieval,agentWorkspaceRun:LYRA_DEFAULT_agentWorkspaceRun,agentWorkspaceTools:LYRA_DEFAULT_agentWorkspaceTools,composerPlaceholder:LYRA_DEFAULT_composerPlaceholder,fieldRequired:LYRA_DEFAULT_fieldRequired}}static{this.collectionSupport=collectionSupport}static{this.immutableEventDetails=Object.freeze(["lr-retrieval-select"])}static{this.ownedCollectionProperties=Object.freeze(["messages","run","metrics","tools","retrievalChunks","selectedRetrievalChunkIds","groundingAssessment","citations","contextSegments"])}static{this.styles=[LyraElement.styles,styles]}get composerStatus(){return this.composerStatusValue}set composerStatus(value){const previous=this.composerStatusValue;this.composerStatusValue=normalizeChatComposerStatus(value),this.requestUpdate("composerStatus",previous)}hasSlotted(name){return Array.from(this.children).some(element=>element.getAttribute("slot")===name)}builtInToolTimeline(){return this.withoutDetails||this.hasSlotted("details")?null:this.renderRoot?.querySelector(tag("tool-timeline"))??null}finalizePendingApproval(){this.builtInToolTimeline()?.finalizePendingApproval()}revertPendingApproval(){this.builtInToolTimeline()?.revertPendingApproval()}get safeContextTotal(){return finiteCount(this.contextTotal)}get effectiveTools(){return firstByWorkspaceIdentity(this.tools,entry=>{const id=entry.id;if(typeof id!="string"||id.trim()==="")return;const sourceKey=entry.sourceKey;return`${typeof sourceKey=="string"?sourceKey:""}\0${id}`})}get effectiveRetrievalChunks(){return firstByWorkspaceIdentity(this.retrievalChunks,chunk=>chunk.id)}get effectiveCitations(){return firstByWorkspaceIdentity(this.citations,citation=>citation.id)}get effectiveContextSegments(){return firstByWorkspaceIdentity(this.contextSegments,segment=>segment.id)}get effectiveMessages(){const source=this.messages,seen=new Set,messages=[];for(let sourceIndex=0;sourceIndex<source.length;sourceIndex++){const message=source[sourceIndex];if(!message)continue;const id=message?.id;typeof id!="string"||id.trim()===""||seen.has(id)||(seen.add(id),messages.push({message,sourceIndex}))}return messages}get safeUnreadStartIndex(){if(this.unreadStartIndex==null)return null;const normalized=finiteCount(this.unreadStartIndex);if(this.hasSlotted("messages"))return normalized;const messages=this.effectiveMessages,effectiveIndex=messages.filter(({sourceIndex})=>sourceIndex<normalized).length;return Math.max(0,effectiveIndex-this.messageWindowOffsetFor(messages))}get safeComposerMinRows(){return Math.max(1,finiteCount(this.composerMinRows,1))}get safeComposerMaxRows(){return Math.max(this.safeComposerMinRows,finiteCount(this.composerMaxRows,8))}stopOwnedEvent(event){event.stopPropagation()}renderMessage(message){return html`
2
2
  <lr-chat-message
3
3
  part="messages message"
4
4
  .messageRole=${message.role}
@@ -35,6 +35,8 @@ readonly gemstone?:GemstoneKey;}export type LyraSwatchPickerMode='swatch'|'gemst
35
35
  * display order and `value` still controls the initial selection. Live option reorders preserve
36
36
  * focus by option identity; removing the focused option moves focus to the nearest surviving
37
37
  * swatch without changing the controlled `value` or emitting `lr-change`.
38
+ * Equivalent fresh items retain their immutable snapshots and radio nodes without blurring
39
+ * keyboard focus. Duplicate values remain distinct occurrences; custom icons match by identity.
38
40
  *
39
41
  * `disabled` locks the whole picker: every swatch renders as a real `disabled` `<button>` (out of
40
42
  * the tab sequence, inert to activation), keyboard navigation and `click()` become no-ops, and the
@@ -128,7 +130,7 @@ accessibleLabel:string;
128
130
  * nothing and has no name, validity or reset semantics — so, exactly like a native `<div>`,
129
131
  * it takes no disablement from an ancestor `<fieldset disabled>`. Disable the picker itself
130
132
  * alongside the fieldset when a form needs both. */
131
- disabled:boolean;private selectedOption?;private selectedIndex;private fallbackTabbableIndex;private pendingFocusIndex?;private resolveSelectedIndex;private select;private focusSwatch;protected willUpdate(changed:PropertyValues<this>):void;protected updated(changed:PropertyValues<this>):void;
133
+ disabled:boolean;private selectedOption?;private fallbackTabbableIndex;private pendingFocusIndex?;private resolveSelectedIndex;private select;private focusSwatch;protected willUpdate(changed:PropertyValues<this>):void;protected updated(changed:PropertyValues<this>):void;
132
134
  /** The single `role="radio"` swatch that is currently in the roving tab sequence -- the
133
135
  * selected one, the nearest survivor of a live palette mutation, or the first swatch when
134
136
  * nothing has been selected yet. Mirrors the identical `tabbableIndex` fallback in `render()`. */
@@ -1,4 +1,4 @@
1
- import{__decorate}from"tslib";import{html,nothing}from"lit";import{property}from"lit/decorators.js";import{repeat}from"lit/directives/repeat.js";import{styleMap}from"lit/directives/style-map.js";import{LyraElement}from"../../../internal/lyra-element.js";import{renderInertPresentation}from"../../../internal/inert-presentation.js";import{isRtl}from"../../../internal/rtl.js";import{gemstoneGlyph,gemstoneSelectedGlyphStyles}from"../../../theme/gemstones.js";import{styles}from"./swatch-picker.styles.js";import{sanitizeCssColor}from"../../../internal/safe-css.js";import{literalSetConverter}from"../../../internal/converters.js";import{activeElementIn}from"../../../internal/active-element.js";const SWATCH_PICKER_MODE=literalSetConverter(["swatch","gemstone"],"swatch");class LyraSwatchPicker extends LyraElement{constructor(){super(...arguments),this._items=Object.freeze([]),this.itemSnapshots=new WeakMap,this.value=null,this.size="m",this._mode="swatch",this.accessibleLabel="",this.disabled=!1,this.selectedIndex=-1,this.fallbackTabbableIndex=0,this.onKeyDown=e=>{if(this.disabled)return;const navigable=this.items;if(navigable.length===0)return;const currentIndex=this.keyboardOriginIndex(e),rtl=isRtl(this),forwardKey=rtl?"ArrowLeft":"ArrowRight",backwardKey=rtl?"ArrowRight":"ArrowLeft";let targetIndex;switch(e.key){case forwardKey:targetIndex=currentIndex<0?0:(currentIndex+1)%navigable.length;break;case backwardKey:targetIndex=currentIndex<0?navigable.length-1:(currentIndex-1+navigable.length)%navigable.length;break;case"Home":targetIndex=0;break;case"End":targetIndex=navigable.length-1;break;default:return}e.preventDefault();const target=navigable[targetIndex];this.select(target,targetIndex),this.focusSwatch(targetIndex)}}static{this.styles=[LyraElement.styles,styles,gemstoneSelectedGlyphStyles]}static{this.properties={items:{attribute:!1,noAccessor:!0}}}get items(){return this._items}set items(next){const old=this._items,snapshots=[];if(Array.isArray(next))for(let index=0;index<Math.min(next.length,512);index+=1)try{const raw=next[index];if(raw===null||typeof raw!="object")continue;const cached=this.itemSnapshots.get(raw);if(cached){snapshots.push(cached);continue}const value=raw.value,color=raw.color,label=raw.label;if(typeof value!="string"||typeof color!="string"||typeof label!="string"||label.trim()==="")continue;const snapshot=Object.freeze({value,color,label,...raw.icon===void 0?{}:{icon:raw.icon},...raw.gemstone===void 0?{}:{gemstone:raw.gemstone}});this.itemSnapshots.set(raw,snapshot),snapshots.push(snapshot)}catch{}this._items=Object.freeze(snapshots),this.requestUpdate("items",old)}get mode(){return this._mode}set mode(next){const normalized=SWATCH_PICKER_MODE.normalizeReflected(this,"mode",next),old=this._mode;old!==normalized&&(this._mode=normalized,this.requestUpdate("mode",old))}resolveSelectedIndex(){if(this.value===null)return this.selectedOption=void 0,this.selectedIndex=-1,-1;if(this.selectedIndex>=0&&this.items[this.selectedIndex]===this.selectedOption&&this.selectedOption?.value===this.value)return this.selectedIndex;const identityIndex=this.selectedOption?this.items.indexOf(this.selectedOption):-1;return this.selectedIndex=identityIndex>=0&&this.items[identityIndex]?.value===this.value?identityIndex:this.items.findIndex(option=>option.value===this.value),this.selectedOption=this.selectedIndex>=0?this.items[this.selectedIndex]:void 0,this.selectedIndex}select(option,index){if(this.disabled)return;const previousIndex=this.resolveSelectedIndex();if(index!==previousIndex||option.value!==this.value){const previousValue=this.value;this.selectedOption=option,this.selectedIndex=index,this.value=option.value,previousValue===option.value&&this.requestUpdate(),this.emit("lr-change",{value:option.value})}this.emit("lr-activate",{value:option.value})}focusSwatch(index){if(this.disabled)return;this.renderRoot.querySelector(`[part="swatch"][data-index="${index}"]`)?.focus()}willUpdate(changed){if(super.willUpdate(changed),!changed.has("items"))return;const active=activeElementIn(this.shadowRoot);if(typeof active?.getAttribute!="function"||active.getAttribute("part")!=="swatch")return;const previousOptions=changed.get("items"),previousIndex=Number(active.dataset.index);if(!Number.isInteger(previousIndex)||previousIndex<0)return;const focusedOption=previousOptions?.[previousIndex],identityIndex=focusedOption===void 0?-1:this.items.indexOf(focusedOption),nextIndex=identityIndex>=0?identityIndex:Math.min(previousIndex,this.items.length-1);if(nextIndex<0){this.pendingFocusIndex=void 0,this.fallbackTabbableIndex=0;return}this.pendingFocusIndex=nextIndex,this.fallbackTabbableIndex=nextIndex}updated(changed){super.updated(changed),!(!changed.has("items")||this.pendingFocusIndex===void 0)&&this.scheduleAfterUpdate(()=>{const pendingIndex=this.pendingFocusIndex;this.pendingFocusIndex=void 0,!(pendingIndex===void 0||this.items.length===0)&&this.focusSwatch(Math.min(pendingIndex,this.items.length-1))},"restore-swatch-picker-focus")}tabbableSwatch(){const selectedIndex=this.resolveSelectedIndex(),tabbableIndex=selectedIndex!==-1?selectedIndex:Math.min(this.fallbackTabbableIndex,Math.max(0,this.items.length-1));return this.renderRoot?.querySelector(`[part="swatch"][data-index="${tabbableIndex}"]`)??null}click(){this.disabled||this.tabbableSwatch()?.click()}focus(options){this.disabled||this.tabbableSwatch()?.focus(options)}blur(){this.tabbableSwatch()?.blur()}keyboardOriginIndex(event){const fromEvent=event.composedPath().find(candidate2=>candidate2.nodeType===1&&candidate2.getAttribute?.("part")==="swatch"&&candidate2.getRootNode()===this.renderRoot),focused=activeElementIn(this.shadowRoot),candidate=fromEvent??(typeof focused?.getAttribute=="function"&&focused.getAttribute("part")==="swatch"?focused:void 0),index=Number(candidate?.dataset.index);return Number.isInteger(index)&&index>=0&&index<this.items.length?index:this.resolveSelectedIndex()}render(){const hostLabel=this.getAttribute("aria-label"),ariaLabel=hostLabel!==null?hostLabel:this.accessibleLabel||nothing,selectedIndex=this.resolveSelectedIndex(),tabbableIndex=selectedIndex!==-1?selectedIndex:Math.min(this.fallbackTabbableIndex,Math.max(0,this.items.length-1));return html`
1
+ import{__decorate}from"tslib";import{html,nothing}from"lit";import{property}from"lit/decorators.js";import{repeat}from"lit/directives/repeat.js";import{styleMap}from"lit/directives/style-map.js";import{LyraElement}from"../../../internal/lyra-element.js";import{renderInertPresentation}from"../../../internal/inert-presentation.js";import{isRtl}from"../../../internal/rtl.js";import{gemstoneGlyph,gemstoneSelectedGlyphStyles}from"../../../theme/gemstones.js";import{styles}from"./swatch-picker.styles.js";import{sanitizeCssColor}from"../../../internal/safe-css.js";import{literalSetConverter}from"../../../internal/converters.js";import{activeElementIn}from"../../../internal/active-element.js";const SWATCH_PICKER_MODE=literalSetConverter(["swatch","gemstone"],"swatch");class LyraSwatchPicker extends LyraElement{constructor(){super(...arguments),this._items=Object.freeze([]),this.itemSnapshots=new WeakMap,this.value=null,this.size="m",this._mode="swatch",this.accessibleLabel="",this.disabled=!1,this.fallbackTabbableIndex=0,this.onKeyDown=e=>{if(this.disabled)return;const navigable=this.items;if(navigable.length===0)return;const currentIndex=this.keyboardOriginIndex(e),rtl=isRtl(this),forwardKey=rtl?"ArrowLeft":"ArrowRight",backwardKey=rtl?"ArrowRight":"ArrowLeft";let targetIndex;switch(e.key){case forwardKey:targetIndex=currentIndex<0?0:(currentIndex+1)%navigable.length;break;case backwardKey:targetIndex=currentIndex<0?navigable.length-1:(currentIndex-1+navigable.length)%navigable.length;break;case"Home":targetIndex=0;break;case"End":targetIndex=navigable.length-1;break;default:return}e.preventDefault();const target=navigable[targetIndex];this.select(target,targetIndex),this.focusSwatch(targetIndex)}}static{this.styles=[LyraElement.styles,styles,gemstoneSelectedGlyphStyles]}static{this.properties={items:{attribute:!1,noAccessor:!0}}}get items(){return this._items}set items(next){const old=this._items,snapshots=[],rows=[],reserved=new Set;if(Array.isArray(next))for(let index=0;index<Math.min(next.length,512);index++)try{const raw=next[index];if(raw===null||typeof raw!="object")continue;rows.push(raw),reserved.add(this.itemSnapshots.get(raw))}catch{}const identities=new Map,identity=value=>(identities.has(value)||identities.set(value,identities.size),identities.get(value)),snapshotKey=item=>JSON.stringify([item.value,item.color,item.label,identity(item.gemstone),identity(item.icon)]),buckets=new Map;for(const item of[...old].reverse()){if(reserved.has(item))continue;const key=snapshotKey(item);let queue=buckets.get(key);queue||buckets.set(key,queue=[]),queue.push(item)}for(const raw of rows)try{let item=this.itemSnapshots.get(raw);if(item&&reserved.delete(item)){snapshots.push(item);continue}if(!item){const{value,color,label}=raw;if(typeof value!="string"||typeof color!="string"||typeof label!="string"||label.trim()==="")continue;const{icon,gemstone}=raw;item={value,color,label,...icon===void 0?{}:{icon},...gemstone===void 0?{}:{gemstone}}}const snapshot=buckets.get(snapshotKey(item))?.pop()??Object.freeze({...item});this.itemSnapshots.has(raw)||this.itemSnapshots.set(raw,snapshot),snapshots.push(snapshot)}catch{}this._items=Object.freeze(snapshots),this.requestUpdate("items",old)}get mode(){return this._mode}set mode(next){const normalized=SWATCH_PICKER_MODE.normalizeReflected(this,"mode",next),old=this._mode;old!==normalized&&(this._mode=normalized,this.requestUpdate("mode",old))}resolveSelectedIndex(){if(this.value===null)return this.selectedOption=void 0,-1;const identityIndex=this.selectedOption?.value===this.value?this.items.indexOf(this.selectedOption):-1,selectedIndex=identityIndex>=0?identityIndex:this.items.findIndex(option=>option.value===this.value);return this.selectedOption=this.items[selectedIndex],selectedIndex}select(option,index){if(this.disabled)return;const previousIndex=this.resolveSelectedIndex();if(index!==previousIndex||option.value!==this.value){const previousValue=this.value;this.selectedOption=option,this.value=option.value,previousValue===option.value&&this.requestUpdate(),this.emit("lr-change",{value:option.value})}this.emit("lr-activate",{value:option.value})}focusSwatch(index){if(this.disabled)return;this.renderRoot.querySelector(`[part="swatch"][data-index="${index}"]`)?.focus()}willUpdate(changed){if(super.willUpdate(changed),!changed.has("items"))return;const active=activeElementIn(this.shadowRoot);if(typeof active?.getAttribute!="function"||active.getAttribute("part")!=="swatch")return;const previousOptions=changed.get("items"),previousIndex=Number(active.dataset.index);if(!Number.isInteger(previousIndex)||previousIndex<0)return;const focusedOption=previousOptions?.[previousIndex],identityIndex=focusedOption===void 0?-1:this.items.indexOf(focusedOption),nextIndex=identityIndex>=0?identityIndex:Math.min(previousIndex,this.items.length-1);if(nextIndex<0){this.pendingFocusIndex=void 0,this.fallbackTabbableIndex=0;return}this.pendingFocusIndex=nextIndex,this.fallbackTabbableIndex=nextIndex}updated(changed){super.updated(changed),!(!changed.has("items")||this.pendingFocusIndex===void 0)&&this.scheduleAfterUpdate(()=>{const pendingIndex=this.pendingFocusIndex;this.pendingFocusIndex=void 0,!(pendingIndex===void 0||this.items.length===0)&&this.focusSwatch(Math.min(pendingIndex,this.items.length-1))},"restore-swatch-picker-focus")}tabbableSwatch(){const selectedIndex=this.resolveSelectedIndex(),tabbableIndex=selectedIndex!==-1?selectedIndex:Math.min(this.fallbackTabbableIndex,Math.max(0,this.items.length-1));return this.renderRoot?.querySelector(`[part="swatch"][data-index="${tabbableIndex}"]`)??null}click(){this.disabled||this.tabbableSwatch()?.click()}focus(options){this.disabled||this.tabbableSwatch()?.focus(options)}blur(){this.tabbableSwatch()?.blur()}keyboardOriginIndex(event){const fromEvent=event.composedPath().find(candidate2=>candidate2.nodeType===1&&candidate2.getAttribute?.("part")==="swatch"&&candidate2.getRootNode()===this.renderRoot),focused=activeElementIn(this.shadowRoot),candidate=fromEvent??(typeof focused?.getAttribute=="function"&&focused.getAttribute("part")==="swatch"?focused:void 0),index=Number(candidate?.dataset.index);return Number.isInteger(index)&&index>=0&&index<this.items.length?index:this.resolveSelectedIndex()}render(){const hostLabel=this.getAttribute("aria-label"),ariaLabel=hostLabel!==null?hostLabel:this.accessibleLabel||nothing,selectedIndex=this.resolveSelectedIndex(),tabbableIndex=selectedIndex!==-1?selectedIndex:Math.min(this.fallbackTabbableIndex,Math.max(0,this.items.length-1));return html`
2
2
  <div
3
3
  part="base"
4
4
  role="radiogroup"
@@ -2,4 +2,4 @@
2
2
  * Generated from package.json by scripts/generate-package-metadata.mjs.
3
3
  * Run `pnpm package-metadata` after changing package identity or version.
4
4
  */
5
- export declare const LYRA_PACKAGE_NAME:'@aceshooting/lyra-ui';export declare const LYRA_PACKAGE_VERSION:'24.0.0';
5
+ export declare const LYRA_PACKAGE_NAME:'@aceshooting/lyra-ui';export declare const LYRA_PACKAGE_VERSION:'24.1.0';
@@ -1 +1 @@
1
- const LYRA_PACKAGE_NAME="@aceshooting/lyra-ui",LYRA_PACKAGE_VERSION="24.0.0";export{LYRA_PACKAGE_NAME,LYRA_PACKAGE_VERSION};
1
+ const LYRA_PACKAGE_NAME="@aceshooting/lyra-ui",LYRA_PACKAGE_VERSION="24.1.0";export{LYRA_PACKAGE_NAME,LYRA_PACKAGE_VERSION};
@@ -103,12 +103,34 @@ RetrievalChunk[] }`) — forwarded from the built-in retrieval results.
103
103
  - `lr-tool-approval-decide-request` (`detail: ToolTimelineApprovalDetail` = `ToolApprovalEventDetail &
104
104
  { args?: unknown }` = `{ invocationId: string; approved: boolean; args?: unknown }`) — forwarded
105
105
  from the built-in tool timeline; `args` is present only on approval and may differ from what the
106
- entry originally proposed (the dialog's inline edit step).
106
+ entry originally proposed (the dialog's inline edit step). Call `preventDefault()` synchronously
107
+ to hold the built-in dialog while persisting the decision.
107
108
  - `lr-cancel` (`detail: CancelEventDetail = { reason?: string }`) / `lr-run-retry` (`detail: RetryEventDetail` =
108
109
  `{ attempt: number; messageId?: string }`, from `@aceshooting/lyra-ui/ai`) — forwarded from the
109
110
  built-in agent run. The distinct retry name prevents a rendered message or attachment retry from
110
111
  being mistaken for a whole-run retry.
111
112
 
113
+ **Methods:** `finalizePendingApproval(): void` closes a vetoed approval in the built-in tool timeline
114
+ after persistence succeeds; `revertPendingApproval(): void` releases its pending state after a failed
115
+ save while retaining the open dialog and edited arguments for retry. Both forward to the built-in
116
+ timeline only and are no-ops when no timeline or pending decision exists, including when `details`
117
+ is replaced by a custom slot or `withoutDetails` is set. The host still owns `tools` updates.
118
+ These methods settle whichever approval is currently pending; an async host must discard stale
119
+ completions when it replaces the run or tools before persistence returns.
120
+
121
+ ```ts
122
+ workspace.addEventListener('lr-tool-approval-decide-request', async (event) => {
123
+ event.preventDefault();
124
+ try {
125
+ await persistDecision(event.detail);
126
+ workspace.tools = applyDecision(workspace.tools, event.detail);
127
+ workspace.finalizePendingApproval();
128
+ } catch {
129
+ workspace.revertPendingApproval();
130
+ }
131
+ });
132
+ ```
133
+
112
134
  **Slots:** `messages` (replaces the data-driven transcript message list; assign ordinary messages
113
135
  directly, or exactly one `lr-virtual-list` when the slot itself owns virtualization), `details` (replaces the
114
136
  built-in run/tool/retrieval/grounding/context details pane while keeping the responsive shell),
@@ -43,6 +43,9 @@ gemstone?: GemstoneKey }`; a valid CSS `color` is used as the
43
43
  remains the sole action. `gemstone` selects the canonical faceted glyph when
44
44
  `mode="gemstone"`. An explicit `icon` wins over `gemstone`. Assignments are bounded and copied
45
45
  into a frozen owned snapshot; mutate a new array/item and reassign it to update the palette.
46
+ Fresh items with unchanged fields retain their radio nodes and keyboard focus without a blur.
47
+ Custom icons match by identity. Duplicate values remain distinct occurrences; surviving original
48
+ item identities take precedence when a palette mixes original and equivalent fresh items.
46
49
  - `value: string | null = null` — the currently selected option's `value` (controlled); `null`
47
50
  leaves nothing selected while keeping the first swatch tabbable.
48
51
  - `size: '2xs' | 'xs' | 's' | 'm' | 'l' | 'xl' = 'm'` (reflected — scales the swatch hit-area and
@@ -305,8 +308,6 @@ locale's own separator), and `translateGemstone()` come from the application's c
305
308
  `onAccentChange()` validates the string and updates application state:
306
309
 
307
310
  ```ts
308
- // Build once per locale. A new array on every render re-creates every swatch and briefly blurs
309
- // the focused one.
310
311
  const items = GEMSTONE_KEYS.map((key) => ({
311
312
  value: key,
312
313
  color: GEMSTONES[key].fill,
@@ -24,7 +24,7 @@ Every one of those names is also typed — per component through its own event m
24
24
  through `@aceshooting/lyra-ui/events` for listeners on an ancestor, `document` or `window`. See
25
25
  "TypeScript" below.
26
26
 
27
- For the manually reviewed, retained event-detail compatibility fields in the v23-to-v24 upgrade,
27
+ For event-detail compatibility fields removed in the v23-to-v24 upgrade,
28
28
  use step 7 of the [migration checklist](v23-to-v24-migration.md#migrate-a-project-in-reviewable-steps).
29
29
  Those nested detail fields are separate from deprecated event names and are not automatic renames.
30
30
 
@@ -36,7 +36,7 @@ Those nested detail fields are separate from deprecated event names and are not
36
36
  ```ts
37
37
  import { LyraTable } from "@aceshooting/lyra-ui/components/data/table/table.class.js";
38
38
  const table = document.querySelector("lr-table") as LyraTable;
39
- table.addEventListener("lr-sort", (event) => event.detail.key); // typed
39
+ table.addEventListener("lr-sort", (event) => event.detail.sortKey); // typed
40
40
  ```
41
41
  - **`HTMLElementTagNameMap`** is augmented in the `.class.d.ts` files. `document.querySelector('lr-table')`
42
42
  is only typed as `LyraTable` when that class module is in the type graph — importing just the
@@ -75,13 +75,13 @@ Those nested detail fields are separate from deprecated event names and are not
75
75
  `LyraGlobalEventMap` is exported as well, for writing your own typed helper over it.
76
76
 
77
77
  - **The surface is per-event type aliases, not runtime event classes.** `LyraSortEvent` and its
78
- generated siblings are `type` aliases over the owning component's own map entry
79
- (`LyraTableEventMap['lr-sort']`) — there is nothing to `new`, and `instanceof LyraSortEvent` is
80
- not a thing. The module compiles to `export {};`: shipping runtime event subclasses to type a
81
- listener would cost every consumer runtime bytes for a compile-time concern, so it deliberately
82
- costs zero.
78
+ generated siblings are `type` aliases over component event-map entries. `LyraSortEvent` unions
79
+ the `lr-sort` entries for document-library, eval-dataset and table. These aliases have no runtime
80
+ constructor and cannot be used with `new` or `instanceof`. The module compiles to `export {};`:
81
+ shipping runtime event subclasses to type a listener would cost every consumer runtime bytes for
82
+ a compile-time concern, so it deliberately costs zero.
83
83
  - **A shared event name narrows to the union of its dispatchers.** One name can come from several
84
- components with different details — `lr-select` from five, `lr-selection-change` from five — so
84
+ components with different details, such as `lr-select` and `lr-selection-change`, so
85
85
  its global entry is the _union_ of their entries, and `event.detail` there exposes only what all
86
86
  of them share. Index the owning component's own map when you need one component's exact detail:
87
87
 
package/llms/shared.md CHANGED
@@ -394,7 +394,7 @@ Every one of those names is also typed — per component through its own event m
394
394
  through `@aceshooting/lyra-ui/events` for listeners on an ancestor, `document` or `window`. See
395
395
  "TypeScript" below.
396
396
 
397
- For the manually reviewed, retained event-detail compatibility fields in the v23-to-v24 upgrade,
397
+ For event-detail compatibility fields removed in the v23-to-v24 upgrade,
398
398
  use step 7 of the [migration checklist](v23-to-v24-migration.md#migrate-a-project-in-reviewable-steps).
399
399
  Those nested detail fields are separate from deprecated event names and are not automatic renames.
400
400
 
@@ -406,7 +406,7 @@ Those nested detail fields are separate from deprecated event names and are not
406
406
  ```ts
407
407
  import { LyraTable } from "@aceshooting/lyra-ui/components/data/table/table.class.js";
408
408
  const table = document.querySelector("lr-table") as LyraTable;
409
- table.addEventListener("lr-sort", (event) => event.detail.key); // typed
409
+ table.addEventListener("lr-sort", (event) => event.detail.sortKey); // typed
410
410
  ```
411
411
  - **`HTMLElementTagNameMap`** is augmented in the `.class.d.ts` files. `document.querySelector('lr-table')`
412
412
  is only typed as `LyraTable` when that class module is in the type graph — importing just the
@@ -445,13 +445,13 @@ Those nested detail fields are separate from deprecated event names and are not
445
445
  `LyraGlobalEventMap` is exported as well, for writing your own typed helper over it.
446
446
 
447
447
  - **The surface is per-event type aliases, not runtime event classes.** `LyraSortEvent` and its
448
- generated siblings are `type` aliases over the owning component's own map entry
449
- (`LyraTableEventMap['lr-sort']`) — there is nothing to `new`, and `instanceof LyraSortEvent` is
450
- not a thing. The module compiles to `export {};`: shipping runtime event subclasses to type a
451
- listener would cost every consumer runtime bytes for a compile-time concern, so it deliberately
452
- costs zero.
448
+ generated siblings are `type` aliases over component event-map entries. `LyraSortEvent` unions
449
+ the `lr-sort` entries for document-library, eval-dataset and table. These aliases have no runtime
450
+ constructor and cannot be used with `new` or `instanceof`. The module compiles to `export {};`:
451
+ shipping runtime event subclasses to type a listener would cost every consumer runtime bytes for
452
+ a compile-time concern, so it deliberately costs zero.
453
453
  - **A shared event name narrows to the union of its dispatchers.** One name can come from several
454
- components with different details — `lr-select` from five, `lr-selection-change` from five — so
454
+ components with different details, such as `lr-select` and `lr-selection-change`, so
455
455
  its global entry is the _union_ of their entries, and `event.detail` there exposes only what all
456
456
  of them share. Index the owning component's own map when you need one component's exact detail:
457
457
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aceshooting/lyra-ui",
3
- "version": "24.0.0",
3
+ "version": "24.1.0",
4
4
  "description": "Free, independent Lit web components: an open-source alternative to Shoelace and Web Awesome with accessible forms, dashboards, charts, and agent UI.",
5
5
  "keywords": [
6
6
  "web-components",