Ryan Malloy f38777e219
Some checks are pending
Validate / HACS validation (push) Waiting to run
Validate / Hassfest (push) Waiting to run
HA: side panel frontend (Lit/TypeScript) for the program viewer
Phase C of the program viewer. Replaces the "panel coming soon" stub
with a real Lit-based side panel that consumes the Phase-B websocket
commands.

Layout (top to bottom):

* Header — title + total/filtered count
* Filter bar — search box (substring match), trigger-type chips
  (TIMED / EVENT / YEARLY / WHEN / AT / EVERY / REMARK), a clearable
  "filtering on <ref>" pill when an entity filter is active
* Two-column body: program list on the left, slide-in detail panel
  on the right. Collapses to single-column on `narrow` view.

The program list renders one row per program (or per chain head, for
clausal multi-record programs). Each row carries the slot number,
the rendered one-line summary token stream, and meta pills for
trigger type / condition count / multi-action count.

The detail panel renders the full structured-English token stream
inside a styled <pre>. A "Fire now" button calls
``omni_pca/programs/fire`` over the wire — the panel actually
runs the program. For chain detail the spanned slot range is shown
underneath.

REF tokens are rendered as `<button>` elements that click to filter
the list to "programs that mention this entity" — the most useful
navigational affordance for the "why is this happening?" use case.

Live-state badges (SECURE / NOT READY / ON 60% / Away / 72°F / …) are
appended to REF tokens via the Phase-B coordinator-backed
StateResolver. The panel polls ``programs/list`` every 5 seconds to
refresh badges; switching to push-event subscriptions is a follow-up
when polling overhead becomes visible.

Theming uses HA's standard CSS variables (--primary-color,
--card-background-color, --divider-color, etc.) so the panel inherits
the user's HA theme automatically.

Build pipeline:

* TypeScript source under ``custom_components/omni_pca/frontend/src/``
* esbuild bundles entry → ESM in one self-contained file
* Output at ``custom_components/omni_pca/www/panel.js`` (~34 KB
  minified) is committed so end-users don't need Node installed
* ``npm run watch`` for HA-dev-time iteration
* tsconfig has strict mode + noUnusedLocals; bundle currently
  type-checks clean

Manifest declares deps on ``http`` and ``websocket_api``; ``frontend``
and ``panel_custom`` are loaded opportunistically (they require
``hass_frontend`` which the test harness doesn't ship — keeping them
out of the manifest deps keeps tests green).

Full suite: 634 passed, 1 skipped (no test changes; the integration
side hasn't moved since Phase B).
2026-05-15 21:21:41 -06:00

444 lines
33 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// omni_pca side panel — generated by frontend/build.mjs. Edit src/, not this file.
var xe=Object.defineProperty;var Ae=Object.getOwnPropertyDescriptor;var f=(i,e,t,r)=>{for(var s=r>1?void 0:r?Ae(e,t):e,o=i.length-1,n;o>=0;o--)(n=i[o])&&(s=(r?n(e,t,s):n(s))||s);return r&&s&&xe(e,t,s),s};var D=globalThis,I=D.ShadowRoot&&(D.ShadyCSS===void 0||D.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,V=Symbol(),ie=new WeakMap,T=class{constructor(e,t,r){if(this._$cssResult$=!0,r!==V)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o,t=this.t;if(I&&e===void 0){let r=t!==void 0&&t.length===1;r&&(e=ie.get(t)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),r&&ie.set(t,e))}return e}toString(){return this.cssText}},oe=i=>new T(typeof i=="string"?i:i+"",void 0,V),B=(i,...e)=>{let t=i.length===1?i[0]:e.reduce((r,s,o)=>r+(n=>{if(n._$cssResult$===!0)return n.cssText;if(typeof n=="number")return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+i[o+1],i[0]);return new T(t,i,V)},ne=(i,e)=>{if(I)i.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(let t of e){let r=document.createElement("style"),s=D.litNonce;s!==void 0&&r.setAttribute("nonce",s),r.textContent=t.cssText,i.appendChild(r)}},W=I?i=>i:i=>i instanceof CSSStyleSheet?(e=>{let t="";for(let r of e.cssRules)t+=r.cssText;return oe(t)})(i):i;var{is:Se,defineProperty:Ee,getOwnPropertyDescriptor:we,getOwnPropertyNames:ke,getOwnPropertySymbols:Te,getPrototypeOf:Ce}=Object,O=globalThis,ae=O.trustedTypes,Re=ae?ae.emptyScript:"",Me=O.reactiveElementPolyfillSupport,C=(i,e)=>i,R={toAttribute(i,e){switch(e){case Boolean:i=i?Re:null;break;case Object:case Array:i=i==null?i:JSON.stringify(i)}return i},fromAttribute(i,e){let t=i;switch(e){case Boolean:t=i!==null;break;case Number:t=i===null?null:Number(i);break;case Object:case Array:try{t=JSON.parse(i)}catch{t=null}}return t}},F=(i,e)=>!Se(i,e),le={attribute:!0,type:String,converter:R,reflect:!1,useDefault:!1,hasChanged:F};Symbol.metadata??=Symbol("metadata"),O.litPropertyMetadata??=new WeakMap;var v=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=le){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){let r=Symbol(),s=this.getPropertyDescriptor(e,r,t);s!==void 0&&Ee(this.prototype,e,s)}}static getPropertyDescriptor(e,t,r){let{get:s,set:o}=we(this.prototype,e)??{get(){return this[t]},set(n){this[t]=n}};return{get:s,set(n){let l=s?.call(this);o?.call(this,n),this.requestUpdate(e,l,r)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??le}static _$Ei(){if(this.hasOwnProperty(C("elementProperties")))return;let e=Ce(this);e.finalize(),e.l!==void 0&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(C("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(C("properties"))){let t=this.properties,r=[...ke(t),...Te(t)];for(let s of r)this.createProperty(s,t[s])}let e=this[Symbol.metadata];if(e!==null){let t=litPropertyMetadata.get(e);if(t!==void 0)for(let[r,s]of t)this.elementProperties.set(r,s)}this._$Eh=new Map;for(let[t,r]of this.elementProperties){let s=this._$Eu(t,r);s!==void 0&&this._$Eh.set(s,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){let t=[];if(Array.isArray(e)){let r=new Set(e.flat(1/0).reverse());for(let s of r)t.unshift(W(s))}else e!==void 0&&t.push(W(e));return t}static _$Eu(e,t){let r=t.attribute;return r===!1?void 0:typeof r=="string"?r:typeof e=="string"?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),this.renderRoot!==void 0&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){let e=new Map,t=this.constructor.elementProperties;for(let r of t.keys())this.hasOwnProperty(r)&&(e.set(r,this[r]),delete this[r]);e.size>0&&(this._$Ep=e)}createRenderRoot(){let e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return ne(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,r){this._$AK(e,r)}_$ET(e,t){let r=this.constructor.elementProperties.get(e),s=this.constructor._$Eu(e,r);if(s!==void 0&&r.reflect===!0){let o=(r.converter?.toAttribute!==void 0?r.converter:R).toAttribute(t,r.type);this._$Em=e,o==null?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(e,t){let r=this.constructor,s=r._$Eh.get(e);if(s!==void 0&&this._$Em!==s){let o=r.getPropertyOptions(s),n=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:R;this._$Em=s;let l=n.fromAttribute(t,o.type);this[s]=l??this._$Ej?.get(s)??l,this._$Em=null}}requestUpdate(e,t,r,s=!1,o){if(e!==void 0){let n=this.constructor;if(s===!1&&(o=this[e]),r??=n.getPropertyOptions(e),!((r.hasChanged??F)(o,t)||r.useDefault&&r.reflect&&o===this._$Ej?.get(e)&&!this.hasAttribute(n._$Eu(e,r))))return;this.C(e,t,r)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(e,t,{useDefault:r,reflect:s,wrapped:o},n){r&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,n??t??this[e]),o!==!0||n!==void 0)||(this._$AL.has(e)||(this.hasUpdated||r||(t=void 0),this._$AL.set(e,t)),s===!0&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}let e=this.scheduleUpdate();return e!=null&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[s,o]of this._$Ep)this[s]=o;this._$Ep=void 0}let r=this.constructor.elementProperties;if(r.size>0)for(let[s,o]of r){let{wrapped:n}=o,l=this[s];n!==!0||this._$AL.has(s)||l===void 0||this.C(s,void 0,o,l)}}let e=!1,t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(r=>r.hostUpdate?.()),this.update(t)):this._$EM()}catch(r){throw e=!1,this._$EM(),r}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(e){}firstUpdated(e){}};v.elementStyles=[],v.shadowRootOptions={mode:"open"},v[C("elementProperties")]=new Map,v[C("finalized")]=new Map,Me?.({ReactiveElement:v}),(O.reactiveElementVersions??=[]).push("2.1.2");var X=globalThis,ce=i=>i,j=X.trustedTypes,de=j?j.createPolicy("lit-html",{createHTML:i=>i}):void 0,me="$lit$",$=`lit$${Math.random().toFixed(9).slice(2)}$`,_e="?"+$,Ue=`<${_e}>`,S=document,U=()=>S.createComment(""),L=i=>i===null||typeof i!="object"&&typeof i!="function",ee=Array.isArray,Le=i=>ee(i)||typeof i?.[Symbol.iterator]=="function",K=`[
\f\r]`,M=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,he=/-->/g,pe=/>/g,x=RegExp(`>|${K}(?:([^\\s"'>=/]+)(${K}*=${K}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`,"g"),ue=/'/g,fe=/"/g,ve=/^(?:script|style|textarea|title)$/i,te=i=>(e,...t)=>({_$litType$:i,strings:e,values:t}),c=te(1),Ke=te(2),Ye=te(3),E=Symbol.for("lit-noChange"),u=Symbol.for("lit-nothing"),ge=new WeakMap,A=S.createTreeWalker(S,129);function ye(i,e){if(!ee(i)||!i.hasOwnProperty("raw"))throw Error("invalid template strings array");return de!==void 0?de.createHTML(e):e}var Pe=(i,e)=>{let t=i.length-1,r=[],s,o=e===2?"<svg>":e===3?"<math>":"",n=M;for(let l=0;l<t;l++){let a=i[l],p,g,d=-1,_=0;for(;_<a.length&&(n.lastIndex=_,g=n.exec(a),g!==null);)_=n.lastIndex,n===M?g[1]==="!--"?n=he:g[1]!==void 0?n=pe:g[2]!==void 0?(ve.test(g[2])&&(s=RegExp("</"+g[2],"g")),n=x):g[3]!==void 0&&(n=x):n===x?g[0]===">"?(n=s??M,d=-1):g[1]===void 0?d=-2:(d=n.lastIndex-g[2].length,p=g[1],n=g[3]===void 0?x:g[3]==='"'?fe:ue):n===fe||n===ue?n=x:n===he||n===pe?n=M:(n=x,s=void 0);let y=n===x&&i[l+1].startsWith("/>")?" ":"";o+=n===M?a+Ue:d>=0?(r.push(p),a.slice(0,d)+me+a.slice(d)+$+y):a+$+(d===-2?l:y)}return[ye(i,o+(i[t]||"<?>")+(e===2?"</svg>":e===3?"</math>":"")),r]},P=class i{constructor({strings:e,_$litType$:t},r){let s;this.parts=[];let o=0,n=0,l=e.length-1,a=this.parts,[p,g]=Pe(e,t);if(this.el=i.createElement(p,r),A.currentNode=this.el.content,t===2||t===3){let d=this.el.content.firstChild;d.replaceWith(...d.childNodes)}for(;(s=A.nextNode())!==null&&a.length<l;){if(s.nodeType===1){if(s.hasAttributes())for(let d of s.getAttributeNames())if(d.endsWith(me)){let _=g[n++],y=s.getAttribute(d).split($),z=/([.?@])?(.*)/.exec(_);a.push({type:1,index:o,name:z[2],strings:y,ctor:z[1]==="."?G:z[1]==="?"?J:z[1]==="@"?Z:k}),s.removeAttribute(d)}else d.startsWith($)&&(a.push({type:6,index:o}),s.removeAttribute(d));if(ve.test(s.tagName)){let d=s.textContent.split($),_=d.length-1;if(_>0){s.textContent=j?j.emptyScript:"";for(let y=0;y<_;y++)s.append(d[y],U()),A.nextNode(),a.push({type:2,index:++o});s.append(d[_],U())}}}else if(s.nodeType===8)if(s.data===_e)a.push({type:2,index:o});else{let d=-1;for(;(d=s.data.indexOf($,d+1))!==-1;)a.push({type:7,index:o}),d+=$.length-1}o++}}static createElement(e,t){let r=S.createElement("template");return r.innerHTML=e,r}};function w(i,e,t=i,r){if(e===E)return e;let s=r!==void 0?t._$Co?.[r]:t._$Cl,o=L(e)?void 0:e._$litDirective$;return s?.constructor!==o&&(s?._$AO?.(!1),o===void 0?s=void 0:(s=new o(i),s._$AT(i,t,r)),r!==void 0?(t._$Co??=[])[r]=s:t._$Cl=s),s!==void 0&&(e=w(i,s._$AS(i,e.values),s,r)),e}var Y=class{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:t},parts:r}=this._$AD,s=(e?.creationScope??S).importNode(t,!0);A.currentNode=s;let o=A.nextNode(),n=0,l=0,a=r[0];for(;a!==void 0;){if(n===a.index){let p;a.type===2?p=new H(o,o.nextSibling,this,e):a.type===1?p=new a.ctor(o,a.name,a.strings,this,e):a.type===6&&(p=new Q(o,this,e)),this._$AV.push(p),a=r[++l]}n!==a?.index&&(o=A.nextNode(),n++)}return A.currentNode=S,s}p(e){let t=0;for(let r of this._$AV)r!==void 0&&(r.strings!==void 0?(r._$AI(e,r,t),t+=r.strings.length-2):r._$AI(e[t])),t++}},H=class i{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,t,r,s){this.type=2,this._$AH=u,this._$AN=void 0,this._$AA=e,this._$AB=t,this._$AM=r,this.options=s,this._$Cv=s?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode,t=this._$AM;return t!==void 0&&e?.nodeType===11&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,t=this){e=w(this,e,t),L(e)?e===u||e==null||e===""?(this._$AH!==u&&this._$AR(),this._$AH=u):e!==this._$AH&&e!==E&&this._(e):e._$litType$!==void 0?this.$(e):e.nodeType!==void 0?this.T(e):Le(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==u&&L(this._$AH)?this._$AA.nextSibling.data=e:this.T(S.createTextNode(e)),this._$AH=e}$(e){let{values:t,_$litType$:r}=e,s=typeof r=="number"?this._$AC(e):(r.el===void 0&&(r.el=P.createElement(ye(r.h,r.h[0]),this.options)),r);if(this._$AH?._$AD===s)this._$AH.p(t);else{let o=new Y(s,this),n=o.u(this.options);o.p(t),this.T(n),this._$AH=o}}_$AC(e){let t=ge.get(e.strings);return t===void 0&&ge.set(e.strings,t=new P(e)),t}k(e){ee(this._$AH)||(this._$AH=[],this._$AR());let t=this._$AH,r,s=0;for(let o of e)s===t.length?t.push(r=new i(this.O(U()),this.O(U()),this,this.options)):r=t[s],r._$AI(o),s++;s<t.length&&(this._$AR(r&&r._$AB.nextSibling,s),t.length=s)}_$AR(e=this._$AA.nextSibling,t){for(this._$AP?.(!1,!0,t);e!==this._$AB;){let r=ce(e).nextSibling;ce(e).remove(),e=r}}setConnected(e){this._$AM===void 0&&(this._$Cv=e,this._$AP?.(e))}},k=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(e,t,r,s,o){this.type=1,this._$AH=u,this._$AN=void 0,this.element=e,this.name=t,this._$AM=s,this.options=o,r.length>2||r[0]!==""||r[1]!==""?(this._$AH=Array(r.length-1).fill(new String),this.strings=r):this._$AH=u}_$AI(e,t=this,r,s){let o=this.strings,n=!1;if(o===void 0)e=w(this,e,t,0),n=!L(e)||e!==this._$AH&&e!==E,n&&(this._$AH=e);else{let l=e,a,p;for(e=o[0],a=0;a<o.length-1;a++)p=w(this,l[r+a],t,a),p===E&&(p=this._$AH[a]),n||=!L(p)||p!==this._$AH[a],p===u?e=u:e!==u&&(e+=(p??"")+o[a+1]),this._$AH[a]=p}n&&!s&&this.j(e)}j(e){e===u?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,e??"")}},G=class extends k{constructor(){super(...arguments),this.type=3}j(e){this.element[this.name]=e===u?void 0:e}},J=class extends k{constructor(){super(...arguments),this.type=4}j(e){this.element.toggleAttribute(this.name,!!e&&e!==u)}},Z=class extends k{constructor(e,t,r,s,o){super(e,t,r,s,o),this.type=5}_$AI(e,t=this){if((e=w(this,e,t,0)??u)===E)return;let r=this._$AH,s=e===u&&r!==u||e.capture!==r.capture||e.once!==r.once||e.passive!==r.passive,o=e!==u&&(r===u||s);s&&this.element.removeEventListener(this.name,this,r),o&&this.element.addEventListener(this.name,this,e),this._$AH=e}handleEvent(e){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,e):this._$AH.handleEvent(e)}},Q=class{constructor(e,t,r){this.element=e,this.type=6,this._$AN=void 0,this._$AM=t,this.options=r}get _$AU(){return this._$AM._$AU}_$AI(e){w(this,e)}};var He=X.litHtmlPolyfillSupport;He?.(P,H),(X.litHtmlVersions??=[]).push("3.3.3");var $e=(i,e,t)=>{let r=t?.renderBefore??e,s=r._$litPart$;if(s===void 0){let o=t?.renderBefore??null;r._$litPart$=s=new H(e.insertBefore(U(),o),o,void 0,t??{})}return s._$AI(i),s};var re=globalThis,b=class extends v{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=$e(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return E}};b._$litElement$=!0,b.finalized=!0,re.litElementHydrateSupport?.({LitElement:b});var Ne=re.litElementPolyfillSupport;Ne?.({LitElement:b});(re.litElementVersions??=[]).push("4.2.2");var be=i=>(e,t)=>{t!==void 0?t.addInitializer(()=>{customElements.define(i,e)}):customElements.define(i,e)};var ze={attribute:!0,type:String,converter:R,reflect:!1,hasChanged:F},De=(i=ze,e,t)=>{let{kind:r,metadata:s}=t,o=globalThis.litPropertyMetadata.get(s);if(o===void 0&&globalThis.litPropertyMetadata.set(s,o=new Map),r==="setter"&&((i=Object.create(i)).wrapped=!0),o.set(t.name,i),r==="accessor"){let{name:n}=t;return{set(l){let a=e.get.call(this);e.set.call(this,l),this.requestUpdate(n,a,i,!0,l)},init(l){return l!==void 0&&this.C(n,void 0,i,l),l}}}if(r==="setter"){let{name:n}=t;return function(l){let a=this[n];e.call(this,l),this.requestUpdate(n,a,i,!0,l)}}throw Error("Unsupported decorator location: "+r)};function N(i){return(e,t)=>typeof t=="object"?De(i,e,t):((r,s,o)=>{let n=s.hasOwnProperty(o);return s.constructor.createProperty(o,r),n?Object.getOwnPropertyDescriptor(s,o):void 0})(i,e,t)}function m(i){return N({...i,state:!0,attribute:!1})}function se(i,e){return c`${i.map(t=>Ie(t,e))}`}function Ie(i,e){switch(i.k){case"newline":return c`<br />`;case"indent":return c`<span class="indent">${i.t}</span>`;case"keyword":return c`<span class="keyword">${i.t}</span>`;case"operator":return c`<span class="operator">${i.t}</span>`;case"value":return c`<span class="value">${i.t}</span>`;case"ref":{let t=e&&i.ek&&typeof i.ei=="number"?()=>e(i.ek,i.ei):void 0;return c`<button
type="button"
class="ref ref-${i.ek}"
title=${i.ek??""}
@click=${t}
>
<span class="ref-name">${i.t}</span>
${i.s?c`<span class="ref-state">${i.s}</span>`:""}
</button>`}default:return c`<span>${i.t}</span>`}}var Oe=["TIMED","EVENT","YEARLY","WHEN","AT","EVERY","REMARK"],Fe=5e3,h=class extends b{constructor(){super(...arguments);this.narrow=!1;this._entryId=null;this._rows=[];this._total=0;this._filteredTotal=0;this._loading=!1;this._error=null;this._activeTriggerTypes=new Set;this._referenceFilter=null;this._searchTerm="";this._selectedSlot=null;this._detail=null;this._detailLoading=!1;this._fireFeedback=null;this._refreshTimer=null}connectedCallback(){super.connectedCallback(),this._discoverEntry(),this._entryId&&(this._loadList(),this._startRefreshTimer())}disconnectedCallback(){super.disconnectedCallback(),this._stopRefreshTimer()}updated(t){t.has("hass")&&this._entryId===null&&(this._discoverEntry(),this._entryId&&(this._loadList(),this._startRefreshTimer()))}_discoverEntry(){this.hass?.connection&&this._discoverViaList()}async _discoverViaList(){try{let r=(await this.hass.connection.sendMessagePromise({type:"config_entries/get"})).filter(s=>s.domain==="omni_pca");if(r.length===0){this._error="No Omni panel configured. Add one via Settings \u2192 Devices & Services.";return}this._entryId=r[0].entry_id,this._error=null}catch(t){this._error=`Could not discover panels: ${t instanceof Error?t.message:String(t)}`}}async _loadList(){if(this._entryId){this._loading=!0,this._error=null;try{let t={type:"omni_pca/programs/list",entry_id:this._entryId};this._activeTriggerTypes.size>0&&(t.trigger_types=[...this._activeTriggerTypes]),this._referenceFilter&&(t.references_entity=this._referenceFilter),this._searchTerm&&(t.search=this._searchTerm);let r=await this.hass.connection.sendMessagePromise(t);this._rows=r.programs,this._total=r.total,this._filteredTotal=r.filtered_total}catch(t){this._error=t instanceof Error?t.message:String(t)}finally{this._loading=!1}}}async _loadDetail(t){if(this._entryId){this._detailLoading=!0,this._detail=null;try{this._detail=await this.hass.connection.sendMessagePromise({type:"omni_pca/programs/get",entry_id:this._entryId,slot:t})}catch(r){this._error=r instanceof Error?r.message:String(r)}finally{this._detailLoading=!1}}}async _fireProgram(t){if(this._entryId){this._fireFeedback="firing\u2026";try{await this.hass.connection.sendMessagePromise({type:"omni_pca/programs/fire",entry_id:this._entryId,slot:t}),this._fireFeedback=`fired slot ${t}`}catch(r){this._fireFeedback=`error: ${r instanceof Error?r.message:r}`}setTimeout(()=>{this._fireFeedback=null},4e3)}}_startRefreshTimer(){this._refreshTimer===null&&(this._refreshTimer=window.setInterval(()=>{this._loadList(),this._selectedSlot!==null&&this._loadDetail(this._selectedSlot)},Fe))}_stopRefreshTimer(){this._refreshTimer!==null&&(window.clearInterval(this._refreshTimer),this._refreshTimer=null)}_toggleTriggerFilter(t){let r=new Set(this._activeTriggerTypes);r.has(t)?r.delete(t):r.add(t),this._activeTriggerTypes=r,this._loadList()}_onSearchInput(t){this._searchTerm=t.target.value,this._loadList()}_clearReferenceFilter(){this._referenceFilter=null,this._loadList()}_onRowClick(t){this._selectedSlot=t,this._loadDetail(t)}_onRefClick(t,r){this._referenceFilter=`${t}:${r}`,this._selectedSlot=null,this._detail=null,this._loadList()}_closeDetail(){this._selectedSlot=null,this._detail=null}render(){return c`
<div class="header">
<div class="title">
<ha-icon icon="mdi:script-text-outline"></ha-icon>
<span>Omni Programs</span>
${this._total>0?c`
<span class="count">
${this._filteredTotal===this._total?`${this._total} programs`:`${this._filteredTotal} of ${this._total} shown`}
</span>`:""}
</div>
</div>
${this._error?c`
<div class="error">${this._error}</div>`:""}
${this._renderFilters()}
<div class="body" data-narrow=${this.narrow}>
${this._renderList()}
${this._selectedSlot!==null?this._renderDetail():""}
</div>
`}_renderFilters(){return c`
<div class="filters">
<input
type="search"
class="search"
placeholder="search programs..."
.value=${this._searchTerm}
@input=${this._onSearchInput}
/>
<div class="chips">
${Oe.map(t=>c`
<button
type="button"
class="chip ${this._activeTriggerTypes.has(t)?"active":""}"
@click=${()=>this._toggleTriggerFilter(t)}
>${t}</button>
`)}
</div>
${this._referenceFilter?c`
<div class="ref-filter">
<span>filtering on <strong>${this._referenceFilter}</strong></span>
<button type="button" @click=${this._clearReferenceFilter}>clear</button>
</div>`:""}
</div>
`}_renderList(){return this._loading&&this._rows.length===0?c`<div class="loading">loading…</div>`:this._rows.length===0?c`<div class="empty">No programs match the current filters.</div>`:c`
<div class="list">
${this._rows.map(t=>c`
<div
class="row ${this._selectedSlot===t.slot?"selected":""}"
@click=${()=>this._onRowClick(t.slot)}
>
<div class="row-slot">#${t.slot}</div>
<div class="row-summary">
${se(t.summary,(r,s)=>this._onRefClick(r,s))}
</div>
<div class="row-meta">
<span class="trigger-badge trigger-${t.trigger_type.toLowerCase()}">
${t.trigger_type}
</span>
${t.condition_count>0?c`
<span class="meta-pill">${t.condition_count} cond</span>`:""}
${t.action_count>1?c`
<span class="meta-pill">${t.action_count} actions</span>`:""}
</div>
</div>
`)}
</div>
`}_renderDetail(){if(this._detailLoading)return c`<aside class="detail"><div class="loading">loading…</div></aside>`;if(this._detail===null)return c`<aside class="detail"></aside>`;let t=this._detail;return c`
<aside class="detail">
<header>
<div>
<span class="trigger-badge trigger-${t.trigger_type.toLowerCase()}">
${t.trigger_type}
</span>
<span class="slot">slot #${t.slot}</span>
</div>
<button type="button" class="close" @click=${this._closeDetail}>×</button>
</header>
<pre class="detail-body">${se(t.tokens,(r,s)=>this._onRefClick(r,s))}</pre>
<footer>
<button
type="button"
class="fire"
@click=${()=>this._fireProgram(t.slot)}
>▶ Fire now</button>
${this._fireFeedback?c`
<span class="fire-feedback">${this._fireFeedback}</span>`:""}
</footer>
${t.chain_slots&&t.chain_slots.length>1?c`
<div class="chain-info">
spans slots
${t.chain_slots.map((r,s)=>c`
${s>0?"\u2192":""}#${r}`)}
</div>`:""}
</aside>
`}};h.styles=B`
:host {
display: block;
min-height: 100vh;
background: var(--primary-background-color, #fafafa);
color: var(--primary-text-color, #000);
font-family: var(--paper-font-body1_-_font-family, sans-serif);
}
.header {
display: flex; align-items: center;
padding: 16px 20px;
background: var(--primary-color, #03a9f4);
color: var(--text-primary-color, #fff);
}
.header .title { display: flex; align-items: center; gap: 10px; font-size: 1.2rem; }
.header .count {
margin-left: 12px;
font-size: 0.85rem; opacity: 0.85; font-weight: normal;
}
.error {
margin: 12px 16px;
padding: 10px 14px;
background: var(--error-color, #db4437);
color: white;
border-radius: 4px;
}
.filters {
padding: 12px 16px 8px;
border-bottom: 1px solid var(--divider-color, #ddd);
}
.search {
width: 100%;
padding: 8px 10px;
font-size: 0.95rem;
border: 1px solid var(--divider-color, #ccc);
border-radius: 4px;
background: var(--card-background-color, #fff);
color: inherit;
box-sizing: border-box;
}
.chips {
display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px;
}
.chip {
border: 1px solid var(--divider-color, #ccc);
background: var(--card-background-color, #fff);
color: var(--secondary-text-color, #555);
padding: 4px 10px;
border-radius: 12px;
font-size: 0.78rem;
cursor: pointer;
font-family: inherit;
}
.chip:hover { background: var(--secondary-background-color, #eee); }
.chip.active {
background: var(--primary-color, #03a9f4);
color: var(--text-primary-color, #fff);
border-color: transparent;
}
.ref-filter {
margin-top: 8px;
font-size: 0.85rem;
color: var(--secondary-text-color, #555);
display: flex; align-items: center; gap: 8px;
}
.ref-filter button {
border: 1px solid var(--divider-color, #ccc);
background: transparent; color: inherit;
padding: 2px 8px; border-radius: 8px;
font-size: 0.75rem; cursor: pointer;
}
.body {
display: grid;
grid-template-columns: 1fr;
gap: 0;
}
.body[data-narrow="false"] { grid-template-columns: 1fr 380px; }
.list {
max-height: calc(100vh - 200px);
overflow-y: auto;
}
.row {
display: grid;
grid-template-columns: 60px 1fr auto;
align-items: start;
gap: 12px;
padding: 10px 16px;
border-bottom: 1px solid var(--divider-color, #eee);
cursor: pointer;
}
.row:hover { background: var(--secondary-background-color, #f5f5f5); }
.row.selected { background: var(--state-active-color, #e3f2fd); }
.row-slot {
font-family: var(--code-font-family, monospace);
font-size: 0.78rem;
color: var(--secondary-text-color, #888);
padding-top: 2px;
}
.row-summary {
font-size: 0.92rem;
line-height: 1.45;
}
.row-meta {
display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
}
/* trigger-type badges */
.trigger-badge {
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.5px;
padding: 2px 6px;
border-radius: 3px;
text-transform: uppercase;
}
.trigger-timed { background: #e3f2fd; color: #1565c0; }
.trigger-event { background: #fff3e0; color: #e65100; }
.trigger-yearly { background: #f3e5f5; color: #6a1b9a; }
.trigger-when { background: #e8f5e9; color: #2e7d32; }
.trigger-at { background: #e3f2fd; color: #1565c0; }
.trigger-every { background: #fce4ec; color: #ad1457; }
.trigger-remark { background: #f5f5f5; color: #616161; }
.meta-pill {
font-size: 0.7rem;
color: var(--secondary-text-color, #888);
background: var(--secondary-background-color, #eee);
padding: 1px 6px;
border-radius: 8px;
}
/* token-renderer styles */
.row-summary, .detail-body {
font-family: var(--paper-font-body1_-_font-family, system-ui, sans-serif);
}
.keyword { font-weight: 600; color: var(--primary-color, #1565c0); }
.operator { color: var(--secondary-text-color, #666); font-style: italic; }
.value { font-family: var(--code-font-family, monospace); color: var(--accent-color, #ff6f00); }
.ref {
display: inline-flex; align-items: baseline; gap: 4px;
border: none; background: transparent; padding: 0 2px;
cursor: pointer; font: inherit; color: inherit;
border-bottom: 1px dotted var(--secondary-text-color, #999);
}
.ref:hover { background: var(--secondary-background-color, #eee); }
.ref-name { font-weight: 500; }
.ref-state {
font-size: 0.72rem;
padding: 1px 5px;
border-radius: 3px;
background: var(--secondary-background-color, #eee);
color: var(--secondary-text-color, #666);
vertical-align: 1px;
}
.ref-zone .ref-name { color: var(--info-color, #0288d1); }
.ref-unit .ref-name { color: var(--warning-color, #f57c00); }
.ref-area .ref-name { color: var(--success-color, #388e3c); }
.ref-thermostat .ref-name { color: var(--accent-color, #c2185b); }
.ref-button .ref-name { color: var(--state-light-color, #7e57c2); }
.indent { display: inline-block; width: 1.5em; }
/* detail panel */
.detail {
border-left: 1px solid var(--divider-color, #ddd);
padding: 16px;
max-height: calc(100vh - 200px);
overflow-y: auto;
box-sizing: border-box;
}
.body[data-narrow="true"] .detail {
border-left: none;
border-top: 1px solid var(--divider-color, #ddd);
}
.detail header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 12px;
}
.detail header .slot {
margin-left: 8px;
font-family: var(--code-font-family, monospace);
font-size: 0.85rem;
color: var(--secondary-text-color, #888);
}
.detail .close {
background: transparent; border: none;
font-size: 1.4rem; cursor: pointer;
color: var(--secondary-text-color, #888);
}
.detail-body {
font-size: 0.95rem;
line-height: 1.6;
white-space: pre-wrap;
word-wrap: break-word;
background: var(--card-background-color, #fff);
padding: 12px;
border-radius: 4px;
border: 1px solid var(--divider-color, #eee);
margin: 0;
}
.detail footer {
display: flex; align-items: center; gap: 12px; margin-top: 14px;
}
.fire {
background: var(--primary-color, #03a9f4);
color: var(--text-primary-color, #fff);
border: none;
padding: 8px 16px;
font-size: 0.92rem;
border-radius: 4px;
cursor: pointer;
}
.fire:hover { filter: brightness(0.9); }
.fire-feedback {
font-size: 0.85rem; color: var(--secondary-text-color, #666);
}
.chain-info {
margin-top: 12px;
font-size: 0.8rem;
color: var(--secondary-text-color, #888);
}
.loading, .empty {
padding: 40px 20px;
text-align: center;
color: var(--secondary-text-color, #888);
}
`,f([N({attribute:!1})],h.prototype,"hass",2),f([N({attribute:!1})],h.prototype,"narrow",2),f([m()],h.prototype,"_entryId",2),f([m()],h.prototype,"_rows",2),f([m()],h.prototype,"_total",2),f([m()],h.prototype,"_filteredTotal",2),f([m()],h.prototype,"_loading",2),f([m()],h.prototype,"_error",2),f([m()],h.prototype,"_activeTriggerTypes",2),f([m()],h.prototype,"_referenceFilter",2),f([m()],h.prototype,"_searchTerm",2),f([m()],h.prototype,"_selectedSlot",2),f([m()],h.prototype,"_detail",2),f([m()],h.prototype,"_detailLoading",2),f([m()],h.prototype,"_fireFeedback",2),h=f([be("omni-panel-programs")],h);export{h as OmniPanelPrograms};
/*! Bundled license information:
@lit/reactive-element/css-tag.js:
(**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/reactive-element.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-html/lit-html.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-element/lit-element.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-html/is-server.js:
(**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/custom-element.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/property.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/state.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/event-options.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/base.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/query.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/query-all.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/query-async.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/query-assigned-elements.js:
(**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/query-assigned-nodes.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
*/