Ryan Malloy 486258a034
Some checks are pending
Validate / HACS validation (push) Waiting to run
Validate / Hassfest (push) Waiting to run
panel: structured-OP AND record editor (TEMP > 70 etc.)
Replaces the read-only "structured comparison" banner with a real
editor. Structured AND records encode ``Arg1 OP Arg2`` where Arg1 is
a typed reference (Zone / Unit / Thermostat / Area / TimeDate) plus a
per-type field selector, and Arg2 is either another typed reference
or a literal constant.

I1 — TS types + decoders:

Wire layout (programs.py decoders, clsProgram.cs):
  cond  high byte  = and_op           (CondOP: 1=EQ, 2=NE, 3=LT,
                                       4=GT, 5=ODD, 6=EVEN, 7=MULT,
                                       8=IN, 9=NOT_IN)
  cond  low byte   = and_arg1_argtype (CondArgType)
  cond2 (whole)    = and_arg1_ix      (object idx; 0 for TimeDate)
  cmd              = and_arg1_field   (per-type field selector)
  par              = and_arg2_argtype (Constant most common)
  pr2              = and_arg2_ix      (constant value or 2nd obj idx)
  month            = and_arg2_field
  day,days         = and_compconst    (BE u16; usually 0)

decodeStructuredAnd / encodeStructuredAnd handle both directions;
round-trip exact.

Per-Arg1Type field menus in FIELDS_BY_TYPE — exact 1:1 with the
Python enuZoneField / enuUnitField / enuThermostatField /
enuTimeDateField enums in omni_pca.programs and the field handling
in StateEvaluator. Areas only expose "Security mode" (single useful
field). TimeDate exposes Year / Month / Day / DoW / Time / Hour /
Minute (skips the rarely-used Date / DST / SunriseSunset fields).

I2 — editor UI:

isEditableStructuredAnd guard: only opens the editor for records
matching the editor's scope (Arg1 in supported types, Arg2=Constant,
compConst=0). Out-of-scope structured records render with a
"read-only" tag — preserved on save, still removable.

Structured rows render with a "structured" tag and an orange-tinted
background to distinguish them from Traditional rows. Layout:

  Arg1 type ▸ object picker ▸ Field ▸ Operator ▸ Compare against

Unary operators (ODD / EVEN) hide the Arg2 input. Changing Arg1 type
resets the Arg1 index + field to defaults so the form stays self-
consistent (no stale picker values from a previous type).

Arg2 is locked to Constant in this pass. Editing record-vs-record
comparisons (e.g. "Thermostat 1 temp > Thermostat 2 temp") is a
future cut — current real-world programs use the Constant form
exclusively per my homeowner-panel sample.

_pickBucket gains the missing "thermostat" branch (was missed in
earlier passes; only mattered now that thermostat is an Arg1Type).

Live screenshot 12-structured-and.png shows an injected chain with
both a Traditional AND (CTRL UNIT 1 ON) and a Structured AND
(Thermostat(1).Temperature > 70) — both editable end-to-end.

Frontend bundle: 88 KB minified (up from 82 KB).
Full suite: 653 passed, 1 skipped (no test changes).
2026-05-17 02:24:59 -06:00

1394 lines
86 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 it=Object.defineProperty;var rt=Object.getOwnPropertyDescriptor;var m=(a,n,e,t)=>{for(var i=t>1?void 0:t?rt(n,e):n,r=a.length-1,s;r>=0;r--)(s=a[r])&&(i=(t?s(n,e,i):s(i))||i);return t&&i&&it(n,e,i),i};var U=globalThis,Y=U.ShadowRoot&&(U.ShadyCSS===void 0||U.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,K=Symbol(),ke=new WeakMap,R=class{constructor(n,e,t){if(this._$cssResult$=!0,t!==K)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=n,this.t=e}get styleSheet(){let n=this.o,e=this.t;if(Y&&n===void 0){let t=e!==void 0&&e.length===1;t&&(n=ke.get(e)),n===void 0&&((this.o=n=new CSSStyleSheet).replaceSync(this.cssText),t&&ke.set(e,n))}return n}toString(){return this.cssText}},Se=a=>new R(typeof a=="string"?a:a+"",void 0,K),J=(a,...n)=>{let e=a.length===1?a[0]:n.reduce((t,i,r)=>t+(s=>{if(s._$cssResult$===!0)return s.cssText;if(typeof s=="number")return s;throw Error("Value passed to 'css' function must be a 'css' function result: "+s+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+a[r+1],a[0]);return new R(e,a,K)},Ce=(a,n)=>{if(Y)a.adoptedStyleSheets=n.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of n){let t=document.createElement("style"),i=U.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,a.appendChild(t)}},X=Y?a=>a:a=>a instanceof CSSStyleSheet?(n=>{let e="";for(let t of n.cssRules)e+=t.cssText;return Se(e)})(a):a;var{is:at,defineProperty:ot,getOwnPropertyDescriptor:st,getOwnPropertyNames:lt,getOwnPropertySymbols:ct,getPrototypeOf:dt}=Object,B=globalThis,Te=B.trustedTypes,ut=Te?Te.emptyScript:"",pt=B.reactiveElementPolyfillSupport,D=(a,n)=>a,P={toAttribute(a,n){switch(n){case Boolean:a=a?ut:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,n){let e=a;switch(n){case Boolean:e=a!==null;break;case Number:e=a===null?null:Number(a);break;case Object:case Array:try{e=JSON.parse(a)}catch{e=null}}return e}},W=(a,n)=>!at(a,n),Fe={attribute:!0,type:String,converter:P,reflect:!1,useDefault:!1,hasChanged:W};Symbol.metadata??=Symbol("metadata"),B.litPropertyMetadata??=new WeakMap;var y=class extends HTMLElement{static addInitializer(n){this._$Ei(),(this.l??=[]).push(n)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(n,e=Fe){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(n)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(n,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(n,t,e);i!==void 0&&ot(this.prototype,n,i)}}static getPropertyDescriptor(n,e,t){let{get:i,set:r}=st(this.prototype,n)??{get(){return this[e]},set(s){this[e]=s}};return{get:i,set(s){let c=i?.call(this);r?.call(this,s),this.requestUpdate(n,c,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(n){return this.elementProperties.get(n)??Fe}static _$Ei(){if(this.hasOwnProperty(D("elementProperties")))return;let n=dt(this);n.finalize(),n.l!==void 0&&(this.l=[...n.l]),this.elementProperties=new Map(n.elementProperties)}static finalize(){if(this.hasOwnProperty(D("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(D("properties"))){let e=this.properties,t=[...lt(e),...ct(e)];for(let i of t)this.createProperty(i,e[i])}let n=this[Symbol.metadata];if(n!==null){let e=litPropertyMetadata.get(n);if(e!==void 0)for(let[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let i=this._$Eu(e,t);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(n){let e=[];if(Array.isArray(n)){let t=new Set(n.flat(1/0).reverse());for(let i of t)e.unshift(X(i))}else n!==void 0&&e.push(X(n));return e}static _$Eu(n,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof n=="string"?n.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(n=>this.enableUpdating=n),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(n=>n(this))}addController(n){(this._$EO??=new Set).add(n),this.renderRoot!==void 0&&this.isConnected&&n.hostConnected?.()}removeController(n){this._$EO?.delete(n)}_$E_(){let n=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(n.set(t,this[t]),delete this[t]);n.size>0&&(this._$Ep=n)}createRenderRoot(){let n=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return Ce(n,this.constructor.elementStyles),n}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(n=>n.hostConnected?.())}enableUpdating(n){}disconnectedCallback(){this._$EO?.forEach(n=>n.hostDisconnected?.())}attributeChangedCallback(n,e,t){this._$AK(n,t)}_$ET(n,e){let t=this.constructor.elementProperties.get(n),i=this.constructor._$Eu(n,t);if(i!==void 0&&t.reflect===!0){let r=(t.converter?.toAttribute!==void 0?t.converter:P).toAttribute(e,t.type);this._$Em=n,r==null?this.removeAttribute(i):this.setAttribute(i,r),this._$Em=null}}_$AK(n,e){let t=this.constructor,i=t._$Eh.get(n);if(i!==void 0&&this._$Em!==i){let r=t.getPropertyOptions(i),s=typeof r.converter=="function"?{fromAttribute:r.converter}:r.converter?.fromAttribute!==void 0?r.converter:P;this._$Em=i;let c=s.fromAttribute(e,r.type);this[i]=c??this._$Ej?.get(i)??c,this._$Em=null}}requestUpdate(n,e,t,i=!1,r){if(n!==void 0){let s=this.constructor;if(i===!1&&(r=this[n]),t??=s.getPropertyOptions(n),!((t.hasChanged??W)(r,e)||t.useDefault&&t.reflect&&r===this._$Ej?.get(n)&&!this.hasAttribute(s._$Eu(n,t))))return;this.C(n,e,t)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(n,e,{useDefault:t,reflect:i,wrapped:r},s){t&&!(this._$Ej??=new Map).has(n)&&(this._$Ej.set(n,s??e??this[n]),r!==!0||s!==void 0)||(this._$AL.has(n)||(this.hasUpdated||t||(e=void 0),this._$AL.set(n,e)),i===!0&&this._$Em!==n&&(this._$Eq??=new Set).add(n))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let n=this.scheduleUpdate();return n!=null&&await n,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,r]of this._$Ep)this[i]=r;this._$Ep=void 0}let t=this.constructor.elementProperties;if(t.size>0)for(let[i,r]of t){let{wrapped:s}=r,c=this[i];s!==!0||this._$AL.has(i)||c===void 0||this.C(i,void 0,r,c)}}let n=!1,e=this._$AL;try{n=this.shouldUpdate(e),n?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(t){throw n=!1,this._$EM(),t}n&&this._$AE(e)}willUpdate(n){}_$AE(n){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(n)),this.updated(n)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(n){return!0}update(n){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(n){}firstUpdated(n){}};y.elementStyles=[],y.shadowRootOptions={mode:"open"},y[D("elementProperties")]=new Map,y[D("finalized")]=new Map,pt?.({ReactiveElement:y}),(B.reactiveElementVersions??=[]).push("2.1.2");var ae=globalThis,Ae=a=>a,V=ae.trustedTypes,we=V?V.createPolicy("lit-html",{createHTML:a=>a}):void 0,ze="$lit$",$=`lit$${Math.random().toFixed(9).slice(2)}$`,Le="?"+$,ht=`<${Le}>`,S=document,M=()=>S.createComment(""),z=a=>a===null||typeof a!="object"&&typeof a!="function",oe=Array.isArray,mt=a=>oe(a)||typeof a?.[Symbol.iterator]=="function",Q=`[
\f\r]`,I=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Re=/-->/g,De=/>/g,E=RegExp(`>|${Q}(?:([^\\s"'>=/]+)(${Q}*=${Q}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`,"g"),Pe=/'/g,Ie=/"/g,Oe=/^(?:script|style|textarea|title)$/i,se=a=>(n,...e)=>({_$litType$:a,strings:n,values:e}),o=se(1),Rt=se(2),Dt=se(3),C=Symbol.for("lit-noChange"),b=Symbol.for("lit-nothing"),Me=new WeakMap,k=S.createTreeWalker(S,129);function He(a,n){if(!oe(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return we!==void 0?we.createHTML(n):n}var gt=(a,n)=>{let e=a.length-1,t=[],i,r=n===2?"<svg>":n===3?"<math>":"",s=I;for(let c=0;c<e;c++){let l=a[c],d,p,u=-1,f=0;for(;f<l.length&&(s.lastIndex=f,p=s.exec(l),p!==null);)f=s.lastIndex,s===I?p[1]==="!--"?s=Re:p[1]!==void 0?s=De:p[2]!==void 0?(Oe.test(p[2])&&(i=RegExp("</"+p[2],"g")),s=E):p[3]!==void 0&&(s=E):s===E?p[0]===">"?(s=i??I,u=-1):p[1]===void 0?u=-2:(u=s.lastIndex-p[2].length,d=p[1],s=p[3]===void 0?E:p[3]==='"'?Ie:Pe):s===Ie||s===Pe?s=E:s===Re||s===De?s=I:(s=E,i=void 0);let _=s===E&&a[c+1].startsWith("/>")?" ":"";r+=s===I?l+ht:u>=0?(t.push(d),l.slice(0,u)+ze+l.slice(u)+$+_):l+$+(u===-2?c:_)}return[He(a,r+(a[e]||"<?>")+(n===2?"</svg>":n===3?"</math>":"")),t]},L=class a{constructor({strings:n,_$litType$:e},t){let i;this.parts=[];let r=0,s=0,c=n.length-1,l=this.parts,[d,p]=gt(n,e);if(this.el=a.createElement(d,t),k.currentNode=this.el.content,e===2||e===3){let u=this.el.content.firstChild;u.replaceWith(...u.childNodes)}for(;(i=k.nextNode())!==null&&l.length<c;){if(i.nodeType===1){if(i.hasAttributes())for(let u of i.getAttributeNames())if(u.endsWith(ze)){let f=p[s++],_=i.getAttribute(u).split($),j=/([.?@])?(.*)/.exec(f);l.push({type:1,index:r,name:j[2],strings:_,ctor:j[1]==="."?te:j[1]==="?"?ne:j[1]==="@"?ie:A}),i.removeAttribute(u)}else u.startsWith($)&&(l.push({type:6,index:r}),i.removeAttribute(u));if(Oe.test(i.tagName)){let u=i.textContent.split($),f=u.length-1;if(f>0){i.textContent=V?V.emptyScript:"";for(let _=0;_<f;_++)i.append(u[_],M()),k.nextNode(),l.push({type:2,index:++r});i.append(u[f],M())}}}else if(i.nodeType===8)if(i.data===Le)l.push({type:2,index:r});else{let u=-1;for(;(u=i.data.indexOf($,u+1))!==-1;)l.push({type:7,index:r}),u+=$.length-1}r++}}static createElement(n,e){let t=S.createElement("template");return t.innerHTML=n,t}};function F(a,n,e=a,t){if(n===C)return n;let i=t!==void 0?e._$Co?.[t]:e._$Cl,r=z(n)?void 0:n._$litDirective$;return i?.constructor!==r&&(i?._$AO?.(!1),r===void 0?i=void 0:(i=new r(a),i._$AT(a,e,t)),t!==void 0?(e._$Co??=[])[t]=i:e._$Cl=i),i!==void 0&&(n=F(a,i._$AS(a,n.values),i,t)),n}var ee=class{constructor(n,e){this._$AV=[],this._$AN=void 0,this._$AD=n,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(n){let{el:{content:e},parts:t}=this._$AD,i=(n?.creationScope??S).importNode(e,!0);k.currentNode=i;let r=k.nextNode(),s=0,c=0,l=t[0];for(;l!==void 0;){if(s===l.index){let d;l.type===2?d=new O(r,r.nextSibling,this,n):l.type===1?d=new l.ctor(r,l.name,l.strings,this,n):l.type===6&&(d=new re(r,this,n)),this._$AV.push(d),l=t[++c]}s!==l?.index&&(r=k.nextNode(),s++)}return k.currentNode=S,i}p(n){let e=0;for(let t of this._$AV)t!==void 0&&(t.strings!==void 0?(t._$AI(n,t,e),e+=t.strings.length-2):t._$AI(n[e])),e++}},O=class a{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(n,e,t,i){this.type=2,this._$AH=b,this._$AN=void 0,this._$AA=n,this._$AB=e,this._$AM=t,this.options=i,this._$Cv=i?.isConnected??!0}get parentNode(){let n=this._$AA.parentNode,e=this._$AM;return e!==void 0&&n?.nodeType===11&&(n=e.parentNode),n}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(n,e=this){n=F(this,n,e),z(n)?n===b||n==null||n===""?(this._$AH!==b&&this._$AR(),this._$AH=b):n!==this._$AH&&n!==C&&this._(n):n._$litType$!==void 0?this.$(n):n.nodeType!==void 0?this.T(n):mt(n)?this.k(n):this._(n)}O(n){return this._$AA.parentNode.insertBefore(n,this._$AB)}T(n){this._$AH!==n&&(this._$AR(),this._$AH=this.O(n))}_(n){this._$AH!==b&&z(this._$AH)?this._$AA.nextSibling.data=n:this.T(S.createTextNode(n)),this._$AH=n}$(n){let{values:e,_$litType$:t}=n,i=typeof t=="number"?this._$AC(n):(t.el===void 0&&(t.el=L.createElement(He(t.h,t.h[0]),this.options)),t);if(this._$AH?._$AD===i)this._$AH.p(e);else{let r=new ee(i,this),s=r.u(this.options);r.p(e),this.T(s),this._$AH=r}}_$AC(n){let e=Me.get(n.strings);return e===void 0&&Me.set(n.strings,e=new L(n)),e}k(n){oe(this._$AH)||(this._$AH=[],this._$AR());let e=this._$AH,t,i=0;for(let r of n)i===e.length?e.push(t=new a(this.O(M()),this.O(M()),this,this.options)):t=e[i],t._$AI(r),i++;i<e.length&&(this._$AR(t&&t._$AB.nextSibling,i),e.length=i)}_$AR(n=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);n!==this._$AB;){let t=Ae(n).nextSibling;Ae(n).remove(),n=t}}setConnected(n){this._$AM===void 0&&(this._$Cv=n,this._$AP?.(n))}},A=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(n,e,t,i,r){this.type=1,this._$AH=b,this._$AN=void 0,this.element=n,this.name=e,this._$AM=i,this.options=r,t.length>2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=b}_$AI(n,e=this,t,i){let r=this.strings,s=!1;if(r===void 0)n=F(this,n,e,0),s=!z(n)||n!==this._$AH&&n!==C,s&&(this._$AH=n);else{let c=n,l,d;for(n=r[0],l=0;l<r.length-1;l++)d=F(this,c[t+l],e,l),d===C&&(d=this._$AH[l]),s||=!z(d)||d!==this._$AH[l],d===b?n=b:n!==b&&(n+=(d??"")+r[l+1]),this._$AH[l]=d}s&&!i&&this.j(n)}j(n){n===b?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,n??"")}},te=class extends A{constructor(){super(...arguments),this.type=3}j(n){this.element[this.name]=n===b?void 0:n}},ne=class extends A{constructor(){super(...arguments),this.type=4}j(n){this.element.toggleAttribute(this.name,!!n&&n!==b)}},ie=class extends A{constructor(n,e,t,i,r){super(n,e,t,i,r),this.type=5}_$AI(n,e=this){if((n=F(this,n,e,0)??b)===C)return;let t=this._$AH,i=n===b&&t!==b||n.capture!==t.capture||n.once!==t.once||n.passive!==t.passive,r=n!==b&&(t===b||i);i&&this.element.removeEventListener(this.name,this,t),r&&this.element.addEventListener(this.name,this,n),this._$AH=n}handleEvent(n){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,n):this._$AH.handleEvent(n)}},re=class{constructor(n,e,t){this.element=n,this.type=6,this._$AN=void 0,this._$AM=e,this.options=t}get _$AU(){return this._$AM._$AU}_$AI(n){F(this,n)}};var ft=ae.litHtmlPolyfillSupport;ft?.(L,O),(ae.litHtmlVersions??=[]).push("3.3.3");var Ne=(a,n,e)=>{let t=e?.renderBefore??n,i=t._$litPart$;if(i===void 0){let r=e?.renderBefore??null;t._$litPart$=i=new O(n.insertBefore(M(),r),r,void 0,e??{})}return i._$AI(a),i};var le=globalThis,x=class extends y{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let n=super.createRenderRoot();return this.renderOptions.renderBefore??=n.firstChild,n}update(n){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(n),this._$Do=Ne(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return C}};x._$litElement$=!0,x.finalized=!0,le.litElementHydrateSupport?.({LitElement:x});var bt=le.litElementPolyfillSupport;bt?.({LitElement:x});(le.litElementVersions??=[]).push("4.2.2");var je=a=>(n,e)=>{e!==void 0?e.addInitializer(()=>{customElements.define(a,n)}):customElements.define(a,n)};var vt={attribute:!0,type:String,converter:P,reflect:!1,hasChanged:W},_t=(a=vt,n,e)=>{let{kind:t,metadata:i}=e,r=globalThis.litPropertyMetadata.get(i);if(r===void 0&&globalThis.litPropertyMetadata.set(i,r=new Map),t==="setter"&&((a=Object.create(a)).wrapped=!0),r.set(e.name,a),t==="accessor"){let{name:s}=e;return{set(c){let l=n.get.call(this);n.set.call(this,c),this.requestUpdate(s,l,a,!0,c)},init(c){return c!==void 0&&this.C(s,void 0,a,c),c}}}if(t==="setter"){let{name:s}=e;return function(c){let l=this[s];n.call(this,c),this.requestUpdate(s,l,a,!0,c)}}throw Error("Unsupported decorator location: "+t)};function H(a){return(n,e)=>typeof e=="object"?_t(a,n,e):((t,i,r)=>{let s=i.hasOwnProperty(r);return i.constructor.createProperty(r,t),s?Object.getOwnPropertyDescriptor(i,r):void 0})(a,n,e)}function g(a){return H({...a,state:!0,attribute:!1})}function ce(a,n){return o`${a.map(e=>yt(e,n))}`}function yt(a,n){switch(a.k){case"newline":return o`<br />`;case"indent":return o`<span class="indent">${a.t}</span>`;case"keyword":return o`<span class="keyword">${a.t}</span>`;case"operator":return o`<span class="operator">${a.t}</span>`;case"value":return o`<span class="value">${a.t}</span>`;case"ref":{let e=n&&a.ek&&typeof a.ei=="number"?()=>n(a.ek,a.ei):void 0;return o`<button
type="button"
class="ref ref-${a.ek}"
title=${a.ek??""}
@click=${e}
>
<span class="ref-name">${a.t}</span>
${a.s?o`<span class="ref-state">${a.s}</span>`:""}
</button>`}default:return o`<span>${a.t}</span>`}}var G=[{value:0,label:"Turn OFF unit",ref_kind:"unit"},{value:1,label:"Turn ON unit",ref_kind:"unit"},{value:2,label:"All OFF",ref_kind:null},{value:3,label:"All ON",ref_kind:null},{value:4,label:"Bypass zone",ref_kind:"zone"},{value:5,label:"Restore zone",ref_kind:"zone"},{value:7,label:"Execute button",ref_kind:"button"},{value:9,label:"Set unit level %",ref_kind:"unit"},{value:48,label:"Disarm area",ref_kind:"area"},{value:49,label:"Arm area Day",ref_kind:"area"},{value:50,label:"Arm area Night",ref_kind:"area"},{value:51,label:"Arm area Away",ref_kind:"area"},{value:52,label:"Arm area Vacation",ref_kind:"area"}];function N(a){return G.find(n=>n.value===a)}var de=[{bit:2,label:"Mon"},{bit:4,label:"Tue"},{bit:8,label:"Wed"},{bit:16,label:"Thu"},{bit:32,label:"Fri"},{bit:64,label:"Sat"},{bit:128,label:"Sun"}],ue=1,pe=2,he=3;var Z=[{id:768,label:"Phone line dead"},{id:769,label:"Phone ringing"},{id:770,label:"Phone off hook"},{id:771,label:"Phone on hook"},{id:772,label:"AC power lost"},{id:773,label:"AC power restored"}];function T(a){if(Z.some(n=>n.id===a))return{category:"fixed",fixedId:a};if(!(a&65280))return{category:"button",button:a&255};if((a&64512)===1024){let n=a&1023;return{category:"zone",zone:Math.floor(n/4)+1,zoneState:n%4}}if((a&64512)===2048){let n=a&1023;return{category:"unit",unit:Math.floor(n/2)+1,unitOn:(n&1)===1}}return{category:"raw",raw:a}}function me(a){switch(a.category){case"button":return(a.button??1)&255;case"zone":{let n=(a.zone??1)-1,e=(a.zoneState??0)&3;return 1024|n*4+e&1023}case"unit":{let n=(a.unit??1)-1,e=a.unitOn?1:0;return 2048|n*2+e&1023}case"fixed":return a.fixedId??768;case"raw":default:return a.raw??0}}function w(a){return(a.month??0)<<8|(a.day??0)}function Ue(a,n){return{...a,month:n>>8&255,day:n&255}}var Ye=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],ge=[{value:0,label:"always"},{value:1,label:"never"},{value:2,label:"it is light outside"},{value:3,label:"it is dark outside"},{value:4,label:"phone line is dead"},{value:5,label:"phone is ringing"},{value:6,label:"phone is off hook"},{value:7,label:"phone is on hook"},{value:8,label:"AC power is off"},{value:9,label:"AC power is on"},{value:10,label:"battery is low"},{value:11,label:"battery is OK"},{value:12,label:"energy cost is low"},{value:13,label:"energy cost is mid"},{value:14,label:"energy cost is high"},{value:15,label:"energy cost is critical"}],fe=[{value:0,label:"Off (disarmed)"},{value:1,label:"Day"},{value:2,label:"Night"},{value:3,label:"Away"},{value:4,label:"Vacation"},{value:5,label:"Day Instant"},{value:6,label:"Night Delayed"}];function Be(a){if(a===0)return{family:"none"};let n=a>>8&252,e=(a&512)!==0;return n===0?{family:"misc",misc:a&15}:n===4?{family:"zone",index:a&255,active:e}:n===8?{family:"unit",index:a&511,active:e}:n===12?{family:"time",index:a&255,active:e}:{family:"sec",index:a>>8&15,mode:a>>12&7}}function v(a){switch(a.family){case"none":return 0;case"misc":return(a.misc??0)&15;case"zone":{let n=(a.index??0)&255;return 1024|(a.active?512:0)|n}case"unit":{let n=(a.index??0)&511;return 2048|(a.active?512:0)|n}case"time":{let n=(a.index??0)&255;return 3072|(a.active?512:0)|n}case"sec":{let n=(a.index??1)&15;return((a.mode??0)&7)<<12|n<<8}}}var We=5,Ve=6,qe=7,$t=8,be=9,xt=10;function Ge(a){let n=(a.cond??0)&255,e=(a.cond2??0)>>8&255,t=n&252,i=(n&2)!==0;return n===0&&e===0?{family:"none"}:t===0?{family:"misc",misc:n&15}:t===4?{family:"zone",index:e,active:i}:t===8?{family:"unit",index:e,active:i}:t===12?{family:"time",index:e,active:i}:{family:"sec",index:n&15,mode:n>>4&7}}function ve(a){switch(a.family){case"none":return{cond:0,cond2:0};case"misc":return{cond:(a.misc??0)&15,cond2:0};case"zone":return{cond:4|(a.active?2:0),cond2:((a.index??0)&255)<<8};case"unit":return{cond:8|(a.active?2:0),cond2:((a.index??0)&255)<<8};case"time":return{cond:12|(a.active?2:0),cond2:((a.index??0)&255)<<8};case"sec":{let n=(a.index??1)&15;return{cond:((a.mode??0)&7)<<4|n,cond2:0}}}}function Ze(a){return((a.cond??0)>>8&255)!==0}function _e(){return{prog_type:$t,cond:1,cond2:0,cmd:0,par:0,pr2:0,month:0,day:0,days:0,hour:0,minute:0}}function Ke(){return{..._e(),prog_type:be}}function Je(a=1){return{prog_type:xt,cmd:0,par:0,pr2:a,cond:0,cond2:0,month:0,day:0,days:0,hour:0,minute:0}}var Xe=[{value:1,label:"=="},{value:2,label:"!="},{value:3,label:"<"},{value:4,label:">"},{value:5,label:"is odd"},{value:6,label:"is even"},{value:7,label:"is multiple of"},{value:8,label:"in (bitmask)"},{value:9,label:"not in (bitmask)"}];function ye(a){return a===5||a===6}var $e=[{value:0,label:"Constant",kind:null},{value:2,label:"Zone",kind:"zone"},{value:3,label:"Unit",kind:"unit"},{value:4,label:"Thermostat",kind:"thermostat"},{value:6,label:"Area",kind:"area"},{value:7,label:"Time / Date",kind:null}];function Et(a){return[2,3,4,6,7].includes(a)}function xe(a){let n=$e.find(e=>e.value===a);return n?n.kind:null}var Ee={2:[{value:1,label:"Loop reading"},{value:2,label:"Current state"},{value:3,label:"Arming state"},{value:4,label:"Alarm state"}],3:[{value:1,label:"Current state"},{value:2,label:"Previous state"},{value:3,label:"Timer"},{value:4,label:"Level"}],4:[{value:1,label:"Current temperature"},{value:2,label:"Heat setpoint"},{value:3,label:"Cool setpoint"},{value:4,label:"System mode"},{value:5,label:"Fan mode"},{value:6,label:"Hold mode"},{value:7,label:"Freeze alarm"},{value:8,label:"Comm error"},{value:9,label:"Humidity"},{value:10,label:"Humidify setpoint"},{value:11,label:"Dehumidify setpoint"},{value:12,label:"Outdoor temperature"},{value:13,label:"System status"}],6:[{value:1,label:"Security mode"}],7:[{value:2,label:"Year"},{value:3,label:"Month"},{value:4,label:"Day"},{value:5,label:"Day of week (1=Mon..7=Sun)"},{value:6,label:"Time (minutes since midnight)"},{value:8,label:"Hour"},{value:9,label:"Minute"}]};function Qe(a){return{op:(a.cond??0)>>8&255,arg1Type:(a.cond??0)&255,arg1Ix:a.cond2??0,arg1Field:a.cmd??0,arg2Type:a.par??0,arg2Ix:a.pr2??0,arg2Field:a.month??0,compConst:(a.day??0)<<8|(a.days??0)}}function et(a){return{cond:(a.op&255)<<8|a.arg1Type&255,cond2:a.arg1Ix&65535,cmd:a.arg1Field&255,par:a.arg2Type&255,pr2:a.arg2Ix&65535,month:a.arg2Field&255,day:a.compConst>>8&255,days:a.compConst&255}}function tt(a){return!(!Et(a.arg1Type)||!ye(a.op)&&a.arg2Type!==0||a.compConst!==0)}var nt=new Set(["TIMED","EVENT","YEARLY"]),kt=["TIMED","EVENT","YEARLY","WHEN","AT","EVERY","REMARK"],St=5e3,h=class extends x{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._writeFeedback=null;this._cloneTargetSlot="";this._showCloneInput=!1;this._confirmingClear=!1;this._editingDraft=null;this._objects=null;this._chainDraft=null;this._refreshTimer=null}connectedCallback(){super.connectedCallback(),this._discoverEntry(),this._entryId&&(this._loadList(),this._startRefreshTimer())}disconnectedCallback(){super.disconnectedCallback(),this._stopRefreshTimer()}updated(e){e.has("hass")&&this._entryId===null&&(this._discoverEntry(),this._entryId&&(this._loadList(),this._startRefreshTimer()))}_discoverEntry(){this.hass?.connection&&this._discoverViaList()}async _discoverViaList(){try{let t=(await this.hass.connection.sendMessagePromise({type:"config_entries/get"})).filter(r=>r.domain==="omni_pca");if(t.length===0){this._error="No Omni panel configured. Add one via Settings \u2192 Devices & Services.";return}let i=t.find(r=>r.state==="loaded");this._entryId=(i??t[0]).entry_id,this._error=null,this._loadList(),this._startRefreshTimer()}catch(e){this._error=`Could not discover panels: ${e instanceof Error?e.message:String(e)}`}}async _loadList(){if(this._entryId){this._loading=!0,this._error=null;try{let e={type:"omni_pca/programs/list",entry_id:this._entryId};this._activeTriggerTypes.size>0&&(e.trigger_types=[...this._activeTriggerTypes]),this._referenceFilter&&(e.references_entity=this._referenceFilter),this._searchTerm&&(e.search=this._searchTerm);let t=await this.hass.connection.sendMessagePromise(e);this._rows=t.programs,this._total=t.total,this._filteredTotal=t.filtered_total}catch(e){this._error=e instanceof Error?e.message:String(e)}finally{this._loading=!1}}}async _loadDetail(e){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:e})}catch(t){this._error=t instanceof Error?t.message:String(t)}finally{this._detailLoading=!1}}}async _fireProgram(e){if(this._entryId){this._fireFeedback="firing\u2026";try{await this.hass.connection.sendMessagePromise({type:"omni_pca/programs/fire",entry_id:this._entryId,slot:e}),this._fireFeedback=`fired slot ${e}`}catch(t){this._fireFeedback=`error: ${t instanceof Error?t.message:t}`}setTimeout(()=>{this._fireFeedback=null},4e3)}}async _clearProgram(e){if(this._entryId){this._writeFeedback="clearing\u2026";try{await this.hass.connection.sendMessagePromise({type:"omni_pca/programs/clear",entry_id:this._entryId,slot:e}),this._writeFeedback=`cleared slot ${e}`,this._confirmingClear=!1,this._selectedSlot=null,this._detail=null,await this._loadList()}catch(t){let i=t instanceof Error?t.message:String(t);this._writeFeedback=`error: ${i}`}setTimeout(()=>{this._writeFeedback=null},4e3)}}async _cloneProgram(e){if(!this._entryId)return;let t=this._cloneTargetSlot.trim(),i=parseInt(t,10);if(!Number.isFinite(i)||i<1||i>1500){this._writeFeedback="target slot must be 1..1500",setTimeout(()=>{this._writeFeedback=null},4e3);return}if(i===e){this._writeFeedback="target must differ from source",setTimeout(()=>{this._writeFeedback=null},4e3);return}this._writeFeedback="cloning\u2026";try{await this.hass.connection.sendMessagePromise({type:"omni_pca/programs/clone",entry_id:this._entryId,source_slot:e,target_slot:i}),this._writeFeedback=`cloned to slot ${i}`,this._showCloneInput=!1,this._cloneTargetSlot="",this._selectedSlot=i,await this._loadList(),await this._loadDetail(i)}catch(r){let s=r instanceof Error?r.message:String(r);this._writeFeedback=`error: ${s}`}setTimeout(()=>{this._writeFeedback=null},4e3)}_onCloneTargetInput(e){this._cloneTargetSlot=e.target.value}async _ensureObjectsLoaded(){if(!(this._objects!==null||!this._entryId))try{this._objects=await this.hass.connection.sendMessagePromise({type:"omni_pca/objects/list",entry_id:this._entryId})}catch(e){let t=e instanceof Error?e.message:String(e);console.warn("omni_pca: objects/list failed",t)}}async _beginEdit(){if(!this._detail||(await this._ensureObjectsLoaded(),!this._entryId))return;if(this._detail.kind==="chain"){this._beginChainEdit();return}if(!nt.has(this._detail.trigger_type))return;let e=this._detail.fields??this._defaultFieldsForType(this._detail.trigger_type);e!==null&&(this._editingDraft={...e},this._stopRefreshTimer())}_beginChainEdit(){if(!this._detail||!this._detail.chain_members)return;let e=this._detail.chain_members,t=e.find(i=>i.role==="head");t&&(this._chainDraft={headSlot:t.slot,head:{...t.fields},conditions:e.filter(i=>i.role==="condition").map(i=>({...i.fields})),actions:e.filter(i=>i.role==="action").map(i=>({...i.fields}))},this._stopRefreshTimer())}_cancelChainEdit(){this._chainDraft=null,this._startRefreshTimer()}async _saveChainDraft(){if(!(!this._chainDraft||!this._entryId)){this._writeFeedback="saving chain\u2026";try{await this.hass.connection.sendMessagePromise({type:"omni_pca/programs/chain/write",entry_id:this._entryId,head_slot:this._chainDraft.headSlot,head:this._chainDraft.head,conditions:this._chainDraft.conditions,actions:this._chainDraft.actions}),this._writeFeedback=`saved chain @ slot ${this._chainDraft.headSlot}`;let e=this._chainDraft.headSlot;this._chainDraft=null,this._startRefreshTimer(),await this._loadList(),await this._loadDetail(e)}catch(e){let t=e instanceof Error?e.message:String(e);this._writeFeedback=`error: ${t}`}setTimeout(()=>{this._writeFeedback=null},4e3)}}_patchChainHead(e){this._chainDraft&&(this._chainDraft={...this._chainDraft,head:{...this._chainDraft.head,...e}})}_patchChainCondition(e,t){if(!this._chainDraft)return;let i=[...this._chainDraft.conditions];i[e]={...i[e],...t},this._chainDraft={...this._chainDraft,conditions:i}}_addChainCondition(e=!1){if(!this._chainDraft)return;let t=e?Ke():_e();this._chainDraft={...this._chainDraft,conditions:[...this._chainDraft.conditions,t]}}_removeChainCondition(e){if(!this._chainDraft)return;let t=this._chainDraft.conditions.filter((i,r)=>r!==e);this._chainDraft={...this._chainDraft,conditions:t}}_patchChainAction(e,t){if(!this._chainDraft)return;let i=[...this._chainDraft.actions];i[e]={...i[e],...t},this._chainDraft={...this._chainDraft,actions:i}}_addChainAction(){if(!this._chainDraft)return;let e=this._objects?.units?.[0]?.index??1;this._chainDraft={...this._chainDraft,actions:[...this._chainDraft.actions,Je(e)]}}_removeChainAction(e){if(!this._chainDraft||this._chainDraft.actions.length<=1)return;let t=this._chainDraft.actions.filter((i,r)=>r!==e);this._chainDraft={...this._chainDraft,actions:t}}_defaultFieldsForType(e){let t=this._objects?.units?.[0]?.index??1;if(e==="TIMED")return{prog_type:ue,cmd:1,par:0,pr2:t,hour:6,minute:0,days:62,cond:0,cond2:0,month:0,day:0};if(e==="EVENT"){let i=this._objects?.buttons?.[0]?.index??1;return{prog_type:pe,cmd:1,par:0,pr2:t,month:0,day:i&255,hour:0,minute:0,days:0,cond:0,cond2:0}}return e==="YEARLY"?{prog_type:he,cmd:1,par:0,pr2:t,month:1,day:1,hour:0,minute:0,days:0,cond:0,cond2:0}:null}async _saveDraft(){if(!(!this._editingDraft||!this._detail||!this._entryId)){this._writeFeedback="saving\u2026";try{await this.hass.connection.sendMessagePromise({type:"omni_pca/programs/write",entry_id:this._entryId,slot:this._detail.slot,program:this._editingDraft}),this._writeFeedback=`saved slot ${this._detail.slot}`,this._editingDraft=null,this._startRefreshTimer(),await this._loadList(),await this._loadDetail(this._detail.slot)}catch(e){let t=e instanceof Error?e.message:String(e);this._writeFeedback=`error: ${t}`}setTimeout(()=>{this._writeFeedback=null},4e3)}}_cancelEdit(){this._editingDraft=null,this._startRefreshTimer()}_patchDraft(e){this._editingDraft&&(this._editingDraft={...this._editingDraft,...e})}_toggleDayBit(e){if(!this._editingDraft)return;let i=(this._editingDraft.days??0)^e;this._patchDraft({days:i})}_onCommandChange(e){let t=parseInt(e.target.value,10);if(!Number.isFinite(t))return;let i=N(t),r=this._editingDraft?.pr2??0;if(i?.ref_kind&&this._objects){let s=this._pickBucket(i.ref_kind);s&&s.length>0&&!s.some(c=>c.index===r)&&(r=s[0].index)}else i?.ref_kind||(r=0);this._patchDraft({cmd:t,pr2:r})}_pickBucket(e){if(!this._objects)return null;switch(e){case"zone":return this._objects.zones;case"unit":return this._objects.units;case"area":return this._objects.areas;case"button":return this._objects.buttons;case"thermostat":return this._objects.thermostats;default:return null}}_bucketWithPreserve(e,t,i){let r=e??[];return i===0||r.some(s=>s.index===i)?r:[{index:i,name:`(undiscovered ${t} ${i} \u2014 preserve original)`},...r]}_onObjectChange(e){let t=parseInt(e.target.value,10);Number.isFinite(t)&&this._patchDraft({pr2:t})}_onHourChange(e){let t=parseInt(e.target.value,10);Number.isFinite(t)&&t>=0&&t<=23&&this._patchDraft({hour:t})}_onMinuteChange(e){let t=parseInt(e.target.value,10);Number.isFinite(t)&&t>=0&&t<=59&&this._patchDraft({minute:t})}_onParChange(e){let t=parseInt(e.target.value,10);Number.isFinite(t)&&t>=0&&t<=255&&this._patchDraft({par:t})}_onMonthChange(e){let t=parseInt(e.target.value,10);Number.isFinite(t)&&t>=1&&t<=12&&this._patchDraft({month:t})}_onDayChange(e){let t=parseInt(e.target.value,10);Number.isFinite(t)&&t>=1&&t<=31&&this._patchDraft({day:t})}_patchEvent(e){if(!this._editingDraft)return;let t=me(e);this._editingDraft=Ue(this._editingDraft,t)}_onEventCategoryChange(e){let t=e.target.value;if(t==="button"){let i=this._objects?.buttons?.[0]?.index??1;this._patchEvent({category:"button",button:i})}else if(t==="zone"){let i=this._objects?.zones?.[0]?.index??1;this._patchEvent({category:"zone",zone:i,zoneState:1})}else if(t==="unit"){let i=this._objects?.units?.[0]?.index??1;this._patchEvent({category:"unit",unit:i,unitOn:!0})}else t==="fixed"&&this._patchEvent({category:"fixed",fixedId:772})}_onEventButtonChange(e){let t=parseInt(e.target.value,10);Number.isFinite(t)&&this._patchEvent({category:"button",button:t})}_onEventZoneChange(e){if(!this._editingDraft)return;let t=parseInt(e.target.value,10);if(!Number.isFinite(t))return;let i=T(w(this._editingDraft));this._patchEvent({category:"zone",zone:t,zoneState:i.zoneState??1})}_onEventZoneStateChange(e){if(!this._editingDraft)return;let t=parseInt(e.target.value,10);if(!Number.isFinite(t))return;let i=T(w(this._editingDraft));this._patchEvent({category:"zone",zone:i.zone??1,zoneState:t})}_onEventUnitChange(e){if(!this._editingDraft)return;let t=parseInt(e.target.value,10);if(!Number.isFinite(t))return;let i=T(w(this._editingDraft));this._patchEvent({category:"unit",unit:t,unitOn:i.unitOn??!0})}_onEventUnitOnChange(e){if(!this._editingDraft)return;let t=e.target.value==="1",i=T(w(this._editingDraft));this._patchEvent({category:"unit",unit:i.unit??1,unitOn:t})}_onEventFixedChange(e){let t=parseInt(e.target.value,10);Number.isFinite(t)&&this._patchEvent({category:"fixed",fixedId:t})}_startRefreshTimer(){this._refreshTimer===null&&(this._refreshTimer=window.setInterval(()=>{this._loadList(),this._selectedSlot!==null&&this._loadDetail(this._selectedSlot)},St))}_stopRefreshTimer(){this._refreshTimer!==null&&(window.clearInterval(this._refreshTimer),this._refreshTimer=null)}_toggleTriggerFilter(e){let t=new Set(this._activeTriggerTypes);t.has(e)?t.delete(e):t.add(e),this._activeTriggerTypes=t,this._loadList()}_onSearchInput(e){this._searchTerm=e.target.value,this._loadList()}_clearReferenceFilter(){this._referenceFilter=null,this._loadList()}_onRowClick(e){this._selectedSlot=e,this._loadDetail(e)}_onRefClick(e,t){this._referenceFilter=`${e}:${t}`,this._selectedSlot=null,this._detail=null,this._loadList()}_closeDetail(){this._selectedSlot=null,this._detail=null}render(){return o`
<div class="header">
<div class="title">
<ha-icon icon="mdi:script-text-outline"></ha-icon>
<span>Omni Programs</span>
${this._total>0?o`
<span class="count">
${this._filteredTotal===this._total?`${this._total} programs`:`${this._filteredTotal} of ${this._total} shown`}
</span>`:""}
</div>
</div>
${this._error?o`
<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 o`
<div class="filters">
<input
type="search"
class="search"
placeholder="search programs..."
.value=${this._searchTerm}
@input=${this._onSearchInput}
/>
<div class="chips">
${kt.map(e=>o`
<button
type="button"
class="chip ${this._activeTriggerTypes.has(e)?"active":""}"
@click=${()=>this._toggleTriggerFilter(e)}
>${e}</button>
`)}
</div>
${this._referenceFilter?o`
<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?o`<div class="loading">loading…</div>`:this._rows.length===0?o`<div class="empty">No programs match the current filters.</div>`:o`
<div class="list">
${this._rows.map(e=>o`
<div
class="row ${this._selectedSlot===e.slot?"selected":""}"
@click=${()=>this._onRowClick(e.slot)}
>
<div class="row-slot">#${e.slot}</div>
<div class="row-summary">
${ce(e.summary,(t,i)=>this._onRefClick(t,i))}
</div>
<div class="row-meta">
<span class="trigger-badge trigger-${e.trigger_type.toLowerCase()}">
${e.trigger_type}
</span>
${e.condition_count>0?o`
<span class="meta-pill">${e.condition_count} cond</span>`:""}
${e.action_count>1?o`
<span class="meta-pill">${e.action_count} actions</span>`:""}
</div>
</div>
`)}
</div>
`}_renderDetail(){if(this._detailLoading)return o`<aside class="detail"><div class="loading">loading…</div></aside>`;if(this._detail===null)return o`<aside class="detail"></aside>`;let e=this._detail;return this._editingDraft!==null?this._renderEditor(e):this._chainDraft!==null?this._renderChainEditor(e):o`
<aside class="detail">
<header>
<div>
<span class="trigger-badge trigger-${e.trigger_type.toLowerCase()}">
${e.trigger_type}
</span>
<span class="slot">slot #${e.slot}</span>
</div>
<button type="button" class="close" @click=${this._closeDetail}>×</button>
</header>
<pre class="detail-body">${ce(e.tokens,(t,i)=>this._onRefClick(t,i))}</pre>
<footer>
<button
type="button"
class="fire"
@click=${()=>this._fireProgram(e.slot)}
>▶ Fire now</button>
${e.kind==="compact"&&nt.has(e.trigger_type)||e.kind==="chain"?o`
<button
type="button"
class="secondary"
@click=${this._beginEdit}
>Edit</button>`:""}
<button
type="button"
class="secondary"
@click=${()=>{this._showCloneInput=!this._showCloneInput,this._confirmingClear=!1}}
>Clone…</button>
<button
type="button"
class="danger"
@click=${()=>{this._confirmingClear=!this._confirmingClear,this._showCloneInput=!1}}
>Clear</button>
${this._fireFeedback?o`
<span class="fire-feedback">${this._fireFeedback}</span>`:""}
${this._writeFeedback?o`
<span class="fire-feedback">${this._writeFeedback}</span>`:""}
</footer>
${this._showCloneInput?o`
<div class="action-row">
<label>Clone slot ${e.slot} → target slot:
<input
type="number"
min="1"
max="1500"
.value=${this._cloneTargetSlot}
@input=${this._onCloneTargetInput}
@keydown=${t=>{t.key==="Enter"&&this._cloneProgram(e.slot)}}
/>
</label>
<button
type="button"
class="primary"
@click=${()=>this._cloneProgram(e.slot)}
>Clone</button>
<button
type="button"
@click=${()=>{this._showCloneInput=!1}}
>Cancel</button>
</div>`:""}
${this._confirmingClear?o`
<div class="action-row danger-row">
<span>
<strong>Clear slot ${e.slot}?</strong>
This deletes the program from the panel.
</span>
<button
type="button"
class="danger"
@click=${()=>this._clearProgram(e.slot)}
>Yes, clear</button>
<button
type="button"
@click=${()=>{this._confirmingClear=!1}}
>Cancel</button>
</div>`:""}
${e.chain_slots&&e.chain_slots.length>1?o`
<div class="chain-info">
spans slots
${e.chain_slots.map((t,i)=>o`
${i>0?"\u2192":""}#${t}`)}
</div>`:""}
</aside>
`}_renderEditor(e){let t=this._editingDraft,i=e.trigger_type;return o`
<aside class="detail editor">
<header>
<div>
<span class="trigger-badge trigger-${i.toLowerCase()}">
EDIT • ${i}
</span>
<span class="slot">slot #${e.slot}</span>
</div>
<button type="button" class="close" @click=${this._cancelEdit}>×</button>
</header>
<div class="editor-body">
${this._renderTriggerSection(t)}
${this._renderActionSection(t)}
${this._renderConditionsSection(t)}
</div>
<footer>
<button type="button" class="primary" @click=${this._saveDraft}>
Save
</button>
<button type="button" class="secondary" @click=${this._cancelEdit}>
Cancel
</button>
${this._writeFeedback?o`
<span class="fire-feedback">${this._writeFeedback}</span>`:""}
</footer>
</aside>
`}_renderTriggerSection(e){switch(e.prog_type){case ue:return this._renderTimedTrigger(e);case pe:return this._renderEventTrigger(e);case he:return this._renderYearlyTrigger(e);default:return o`<div class="conditions-readonly">
Editing program type ${e.prog_type} is not supported.
</div>`}}_renderTimedTrigger(e){return o`
<fieldset>
<legend>Time</legend>
<div class="row">
<label>
Hour
<input
type="number" min="0" max="23"
.value=${String(e.hour??0)}
@input=${this._onHourChange}
/>
</label>
<span class="time-colon">:</span>
<label>
Minute
<input
type="number" min="0" max="59" step="1"
.value=${String(e.minute??0)}
@input=${this._onMinuteChange}
/>
</label>
</div>
</fieldset>
<fieldset>
<legend>Days</legend>
<div class="days-row">
${de.map(t=>{let i=((e.days??0)&t.bit)!==0;return o`
<button
type="button"
class="day-toggle ${i?"active":""}"
@click=${()=>this._toggleDayBit(t.bit)}
>${t.label}</button>
`})}
</div>
</fieldset>
`}_renderEventTrigger(e){let t=w(e),i=T(t);return o`
<fieldset>
<legend>Trigger event</legend>
<label class="block">
Category
<select @change=${this._onEventCategoryChange}>
<option value="button"
?selected=${i.category==="button"}>
Button press
</option>
<option value="zone"
?selected=${i.category==="zone"}>
Zone state change
</option>
<option value="unit"
?selected=${i.category==="unit"}>
Unit state change
</option>
<option value="fixed"
?selected=${i.category==="fixed"}>
Fixed event (phone / AC)
</option>
${i.category==="raw"?o`
<option value="raw" selected>
Raw 0x${t.toString(16).padStart(4,"0")}
</option>`:""}
</select>
</label>
${this._renderEventCategoryFields(i)}
</fieldset>
`}_renderEventCategoryFields(e){if(e.category==="button"){let t=this._bucketWithPreserve(this._objects?.buttons??null,"button",e.button??0);return o`
<label class="block">
Button
<select @change=${this._onEventButtonChange}>
${t.map(i=>o`
<option .value=${String(i.index)}
?selected=${i.index===e.button}>
#${i.index} ${i.name}
</option>
`)}
</select>
</label>`}if(e.category==="zone"){let t=this._bucketWithPreserve(this._objects?.zones??null,"zone",e.zone??0);return o`
<label class="block">
Zone
<select @change=${this._onEventZoneChange}>
${t.map(i=>o`
<option .value=${String(i.index)}
?selected=${i.index===e.zone}>
#${i.index} ${i.name}
</option>
`)}
</select>
</label>
<label class="block">
Becomes
<select @change=${this._onEventZoneStateChange}>
<option value="0" ?selected=${e.zoneState===0}>secure</option>
<option value="1" ?selected=${e.zoneState===1}>not ready</option>
<option value="2" ?selected=${e.zoneState===2}>trouble</option>
<option value="3" ?selected=${e.zoneState===3}>tamper</option>
</select>
</label>`}if(e.category==="unit"){let t=this._bucketWithPreserve(this._objects?.units??null,"unit",e.unit??0);return o`
<label class="block">
Unit
<select @change=${this._onEventUnitChange}>
${t.map(i=>o`
<option .value=${String(i.index)}
?selected=${i.index===e.unit}>
#${i.index} ${i.name}
</option>
`)}
</select>
</label>
<label class="block">
Turns
<select @change=${this._onEventUnitOnChange}>
<option value="1" ?selected=${e.unitOn===!0}>ON</option>
<option value="0" ?selected=${e.unitOn===!1}>OFF</option>
</select>
</label>`}return e.category==="fixed"?o`
<label class="block">
Event
<select @change=${this._onEventFixedChange}>
${Z.map(t=>o`
<option .value=${String(t.id)}
?selected=${t.id===e.fixedId}>
${t.label}
</option>
`)}
</select>
</label>`:o`
<div class="conditions-readonly">
Unrecognised event ID. Switch category above to redefine.
</div>`}_renderYearlyTrigger(e){return o`
<fieldset>
<legend>Date</legend>
<div class="row">
<label>
Month
<select @change=${this._onMonthChange}>
${Ye.map((t,i)=>o`
<option .value=${String(i+1)}
?selected=${(e.month??1)===i+1}>
${t} (${i+1})
</option>
`)}
</select>
</label>
<label>
Day
<input
type="number" min="1" max="31"
.value=${String(e.day??1)}
@input=${this._onDayChange}
/>
</label>
</div>
</fieldset>
<fieldset>
<legend>Time of day</legend>
<div class="row">
<label>
Hour
<input
type="number" min="0" max="23"
.value=${String(e.hour??0)}
@input=${this._onHourChange}
/>
</label>
<span class="time-colon">:</span>
<label>
Minute
<input
type="number" min="0" max="59"
.value=${String(e.minute??0)}
@input=${this._onMinuteChange}
/>
</label>
</div>
</fieldset>
`}_renderActionSection(e){let t=N(e.cmd??0),i=t?.ref_kind?this._bucketWithPreserve(this._pickBucket(t.ref_kind),t.ref_kind,e.pr2??0):null,r=e.cmd===9;return o`
<fieldset>
<legend>Action</legend>
<label class="block">
Command
<select @change=${this._onCommandChange}>
${G.map(s=>o`
<option .value=${String(s.value)}
?selected=${s.value===e.cmd}>
${s.label}
</option>
`)}
</select>
</label>
${t?.ref_kind?o`
<label class="block">
${t.ref_kind[0].toUpperCase()+t.ref_kind.slice(1)}
<select @change=${this._onObjectChange}>
${(i??[]).map(s=>o`
<option .value=${String(s.index)}
?selected=${s.index===e.pr2}>
#${s.index} ${s.name}
</option>
`)}
</select>
</label>`:""}
${r?o`
<label class="block">
Level (0..100)
<input
type="number" min="0" max="100"
.value=${String(e.par??0)}
@input=${this._onParChange}
/>
</label>`:""}
</fieldset>
`}_renderConditionsSection(e){return o`
<fieldset>
<legend>Inline AND-IF conditions</legend>
${this._renderConditionSlot("First condition",e.cond??0,t=>this._patchDraft({cond:t}))}
${this._renderConditionSlot("Second condition",e.cond2??0,t=>this._patchDraft({cond2:t}))}
</fieldset>
`}_renderConditionSlot(e,t,i){let r=Be(t),s=c=>{let l=this._objects?.zones?.[0]?.index??1,d=this._objects?.units?.[0]?.index??1,p=this._objects?.areas?.[0]?.index??1,u;switch(c){case"none":u={family:"none"};break;case"misc":u={family:"misc",misc:1};break;case"zone":u={family:"zone",index:l,active:!1};break;case"unit":u={family:"unit",index:d,active:!0};break;case"time":u={family:"time",index:1,active:!0};break;case"sec":u={family:"sec",index:p,mode:0};break}i(v(u))};return o`
<div class="cond-slot">
<label class="block cond-family-label">
${e}
<select @change=${c=>s(c.target.value)}>
<option value="none" ?selected=${r.family==="none"}>(none)</option>
<option value="zone" ?selected=${r.family==="zone"}>Zone state</option>
<option value="unit" ?selected=${r.family==="unit"}>Unit state</option>
<option value="sec" ?selected=${r.family==="sec"}>Area in security mode</option>
<option value="time" ?selected=${r.family==="time"}>Time clock</option>
<option value="misc" ?selected=${r.family==="misc"}>Misc (light, AC power, …)</option>
</select>
</label>
${this._renderConditionSubfields(r,i)}
</div>
`}_renderConditionSubfields(e,t){if(e.family==="none")return o``;if(e.family==="zone"){let i=this._bucketWithPreserve(this._objects?.zones??null,"zone",e.index??0);return o`
<label class="block">
Zone
<select @change=${r=>{let s=parseInt(r.target.value,10);t(v({...e,index:s}))}}>
${i.map(r=>o`
<option .value=${String(r.index)}
?selected=${r.index===e.index}>
#${r.index} ${r.name}
</option>
`)}
</select>
</label>
<label class="block">
Is
<select @change=${r=>{let s=r.target.value==="1";t(v({...e,active:s}))}}>
<option value="0" ?selected=${!e.active}>secure</option>
<option value="1" ?selected=${e.active}>not ready</option>
</select>
</label>`}if(e.family==="unit"){let i=this._bucketWithPreserve(this._objects?.units??null,"unit",e.index??0);return o`
<label class="block">
Unit
<select @change=${r=>{let s=parseInt(r.target.value,10);t(v({...e,index:s}))}}>
${i.map(r=>o`
<option .value=${String(r.index)}
?selected=${r.index===e.index}>
#${r.index} ${r.name}
</option>
`)}
</select>
</label>
<label class="block">
Is
<select @change=${r=>{let s=r.target.value==="1";t(v({...e,active:s}))}}>
<option value="1" ?selected=${e.active}>ON</option>
<option value="0" ?selected=${!e.active}>OFF</option>
</select>
</label>`}if(e.family==="sec"){let i=this._bucketWithPreserve(this._objects?.areas??null,"area",e.index??0);return o`
<label class="block">
Area
<select @change=${r=>{let s=parseInt(r.target.value,10);t(v({...e,index:s}))}}>
${i.map(r=>o`
<option .value=${String(r.index)}
?selected=${r.index===e.index}>
#${r.index} ${r.name}
</option>
`)}
</select>
</label>
<label class="block">
Mode
<select @change=${r=>{let s=parseInt(r.target.value,10);t(v({...e,mode:s}))}}>
${fe.map(r=>o`
<option .value=${String(r.value)}
?selected=${r.value===e.mode}>
${r.label}
</option>
`)}
</select>
</label>`}return e.family==="time"?o`
<label class="block">
Time clock # (1..3)
<input
type="number" min="1" max="3"
.value=${String(e.index??1)}
@input=${i=>{let r=parseInt(i.target.value,10);Number.isFinite(r)&&t(v({...e,index:r}))}}
/>
</label>
<label class="block">
Is
<select @change=${i=>{let r=i.target.value==="1";t(v({...e,active:r}))}}>
<option value="1" ?selected=${e.active}>enabled</option>
<option value="0" ?selected=${!e.active}>disabled</option>
</select>
</label>`:o`
<label class="block">
Condition
<select @change=${i=>{let r=parseInt(i.target.value,10);t(v({family:"misc",misc:r}))}}>
${ge.map(i=>o`
<option .value=${String(i.value)}
?selected=${i.value===e.misc}>
${i.label}
</option>
`)}
</select>
</label>`}_renderChainEditor(e){let t=this._chainDraft;return o`
<aside class="detail editor">
<header>
<div>
<span class="trigger-badge trigger-${e.trigger_type.toLowerCase()}">
EDIT • ${e.trigger_type}
</span>
<span class="slot">head @ slot #${t.headSlot}</span>
</div>
<button type="button" class="close" @click=${this._cancelChainEdit}>×</button>
</header>
<div class="editor-body">
${this._renderChainHeadSection(t.head)}
${this._renderChainConditionsSection(t.conditions)}
${this._renderChainActionsSection(t.actions)}
<div class="chain-meta">
Chain will occupy <strong>${1+t.conditions.length+t.actions.length}</strong>
consecutive slots starting at #${t.headSlot}.
</div>
</div>
<footer>
<button type="button" class="primary" @click=${this._saveChainDraft}>
Save chain
</button>
<button type="button" class="secondary" @click=${this._cancelChainEdit}>
Cancel
</button>
${this._writeFeedback?o`
<span class="fire-feedback">${this._writeFeedback}</span>`:""}
</footer>
</aside>
`}_renderChainHeadSection(e){return e.prog_type===We?this._renderEventTriggerChain(e):e.prog_type===Ve?this._renderTimedTriggerChain(e):e.prog_type===qe?this._renderEveryTriggerChain(e):o`
<div class="conditions-readonly">
Editing trigger type ${e.prog_type} (chain head) is not supported.
</div>`}_renderTimedTriggerChain(e){return o`
<fieldset>
<legend>AT (trigger)</legend>
<div class="row">
<label>
Hour
<input type="number" min="0" max="23"
.value=${String(e.hour??0)}
@input=${t=>this._patchChainHead({hour:parseInt(t.target.value,10)||0})}
/>
</label>
<span class="time-colon">:</span>
<label>
Minute
<input type="number" min="0" max="59"
.value=${String(e.minute??0)}
@input=${t=>this._patchChainHead({minute:parseInt(t.target.value,10)||0})}
/>
</label>
</div>
<div class="days-row">
${de.map(t=>{let i=((e.days??0)&t.bit)!==0;return o`
<button type="button"
class="day-toggle ${i?"active":""}"
@click=${()=>this._patchChainHead({days:(e.days??0)^t.bit})}
>${t.label}</button>`})}
</div>
</fieldset>
`}_renderEventTriggerChain(e){let t=(e.month??0)<<8|(e.day??0),i=T(t),r=s=>{let c=me(s);this._patchChainHead({month:c>>8&255,day:c&255})};return o`
<fieldset>
<legend>WHEN (trigger event)</legend>
<label class="block">
Category
<select @change=${s=>{let c=s.target.value;if(c==="button"){let l=this._objects?.buttons?.[0]?.index??1;r({category:"button",button:l})}else if(c==="zone"){let l=this._objects?.zones?.[0]?.index??1;r({category:"zone",zone:l,zoneState:1})}else if(c==="unit"){let l=this._objects?.units?.[0]?.index??1;r({category:"unit",unit:l,unitOn:!0})}else c==="fixed"&&r({category:"fixed",fixedId:772})}}>
<option value="button" ?selected=${i.category==="button"}>Button press</option>
<option value="zone" ?selected=${i.category==="zone"}>Zone state change</option>
<option value="unit" ?selected=${i.category==="unit"}>Unit state change</option>
<option value="fixed" ?selected=${i.category==="fixed"}>Fixed (phone / AC)</option>
${i.category==="raw"?o`
<option value="raw" selected>Raw 0x${t.toString(16).padStart(4,"0")}</option>`:""}
</select>
</label>
${this._renderChainEventSubfields(i,r)}
</fieldset>
`}_renderChainEventSubfields(e,t){if(e.category==="button"){let i=this._bucketWithPreserve(this._objects?.buttons??null,"button",e.button??0);return o`
<label class="block">
Button
<select @change=${r=>t({category:"button",button:parseInt(r.target.value,10)})}>
${i.map(r=>o`
<option .value=${String(r.index)}
?selected=${r.index===e.button}>
#${r.index} ${r.name}
</option>
`)}
</select>
</label>`}if(e.category==="zone"){let i=this._bucketWithPreserve(this._objects?.zones??null,"zone",e.zone??0);return o`
<label class="block">
Zone
<select @change=${r=>t({...e,category:"zone",zone:parseInt(r.target.value,10),zoneState:e.zoneState??1})}>
${i.map(r=>o`
<option .value=${String(r.index)}
?selected=${r.index===e.zone}>
#${r.index} ${r.name}
</option>
`)}
</select>
</label>
<label class="block">
Becomes
<select @change=${r=>t({...e,category:"zone",zone:e.zone??1,zoneState:parseInt(r.target.value,10)})}>
<option value="0" ?selected=${e.zoneState===0}>secure</option>
<option value="1" ?selected=${e.zoneState===1}>not ready</option>
<option value="2" ?selected=${e.zoneState===2}>trouble</option>
<option value="3" ?selected=${e.zoneState===3}>tamper</option>
</select>
</label>`}if(e.category==="unit"){let i=this._bucketWithPreserve(this._objects?.units??null,"unit",e.unit??0);return o`
<label class="block">
Unit
<select @change=${r=>t({...e,category:"unit",unit:parseInt(r.target.value,10),unitOn:e.unitOn??!0})}>
${i.map(r=>o`
<option .value=${String(r.index)}
?selected=${r.index===e.unit}>
#${r.index} ${r.name}
</option>
`)}
</select>
</label>
<label class="block">
Turns
<select @change=${r=>t({...e,category:"unit",unit:e.unit??1,unitOn:r.target.value==="1"})}>
<option value="1" ?selected=${e.unitOn===!0}>ON</option>
<option value="0" ?selected=${e.unitOn===!1}>OFF</option>
</select>
</label>`}return e.category==="fixed"?o`
<label class="block">
Event
<select @change=${i=>t({category:"fixed",fixedId:parseInt(i.target.value,10)})}>
${Z.map(i=>o`
<option .value=${String(i.id)}
?selected=${i.id===e.fixedId}>
${i.label}
</option>
`)}
</select>
</label>`:o`<div class="conditions-readonly">Unrecognised event ID. Pick a category to redefine.</div>`}_renderEveryTriggerChain(e){let t=((e.cond??0)&255)<<8|(e.cond2??0)>>8&255;return o`
<fieldset>
<legend>EVERY (interval, seconds)</legend>
<label class="block">
Seconds between fires
<input type="number" min="1" max="65535"
.value=${String(t||1)}
@input=${i=>{let r=parseInt(i.target.value,10);!Number.isFinite(r)||r<1||this._patchChainHead({cond:r>>8&255,cond2:(r&255)<<8})}}
/>
</label>
</fieldset>
`}_renderChainConditionsSection(e){return o`
<fieldset>
<legend>
Conditions (${e.length})
<button type="button" class="mini-btn" @click=${()=>this._addChainCondition(!1)}>
+ AND IF
</button>
<button type="button" class="mini-btn" @click=${()=>this._addChainCondition(!0)}>
+ OR IF
</button>
</legend>
${e.length===0?o`
<div class="conditions-readonly">
No conditions — chain fires unconditionally when triggered.
</div>`:""}
${e.map((t,i)=>this._renderChainConditionRow(t,i))}
</fieldset>
`}_renderChainConditionRow(e,t){let i=e.prog_type===be;if(Ze(e))return this._renderStructuredChainConditionRow(e,t,i);let r=Ge(e);return o`
<div class="cond-slot">
<div class="cond-row-header">
<strong>${i?"OR IF":"AND IF"}</strong>
<button type="button" class="mini-btn danger"
@click=${()=>this._removeChainCondition(t)}>×</button>
</div>
${this._renderChainCondFamily(r,t)}
</div>`}_renderStructuredChainConditionRow(e,t,i){let r=Qe(e);return tt(r)?o`
<div class="cond-slot structured-cond">
<div class="cond-row-header">
<strong>${i?"OR IF":"AND IF"}</strong>
<span class="structured-tag">structured</span>
<button type="button" class="mini-btn danger"
@click=${()=>this._removeChainCondition(t)}>×</button>
</div>
${this._renderStructuredAndForm(r,t)}
</div>`:o`
<div class="cond-slot structured-cond">
<div class="cond-row-header">
<strong>${i?"OR IF":"AND IF"}</strong>
<span class="readonly-tag">read-only</span>
<button type="button" class="mini-btn danger"
@click=${()=>this._removeChainCondition(t)}>×</button>
</div>
<div class="conditions-readonly">
Structured comparison with a shape the editor can't drive
yet (Arg2 references another object, Arg1 is an unsupported
type, or a CompConst value is present). Preserved on save.
</div>
</div>`}_renderStructuredAndForm(e,t){let i=l=>{let d={...e,...l};d.arg2Type=0,d.arg2Field=0,this._patchChainCondition(t,et(d))},r=Ee[e.arg1Type]??[],s=xe(e.arg1Type),c=!ye(e.op);return o`
<div class="structured-row">
<label class="block">
Arg1 type
<select @change=${l=>{let d=parseInt(l.target.value,10),p=(Ee[d]??[{value:0}])[0].value,u=xe(d),f=0;u==="zone"?f=this._objects?.zones?.[0]?.index??1:u==="unit"?f=this._objects?.units?.[0]?.index??1:u==="thermostat"?f=this._objects?.thermostats?.[0]?.index??1:u==="area"&&(f=this._objects?.areas?.[0]?.index??1),i({arg1Type:d,arg1Ix:f,arg1Field:p})}}>
${$e.filter(l=>l.value!==0).map(l=>o`
<option .value=${String(l.value)} ?selected=${l.value===e.arg1Type}>
${l.label}
</option>`)}
</select>
</label>
${s?this._renderStructuredArg1Picker(e,s,i):""}
${r.length>0?o`
<label class="block">
Field
<select @change=${l=>i({arg1Field:parseInt(l.target.value,10)})}>
${r.map(l=>o`
<option .value=${String(l.value)} ?selected=${l.value===e.arg1Field}>
${l.label}
</option>`)}
</select>
</label>`:""}
<label class="block">
Operator
<select @change=${l=>i({op:parseInt(l.target.value,10)})}>
${Xe.map(l=>o`
<option .value=${String(l.value)} ?selected=${l.value===e.op}>
${l.label}
</option>`)}
</select>
</label>
${c?o`
<label class="block">
Compare against (constant)
<input type="number" min="0" max="65535"
.value=${String(e.arg2Ix)}
@input=${l=>{let d=parseInt(l.target.value,10);Number.isFinite(d)&&d>=0&&d<=65535&&i({arg2Ix:d})}}
/>
</label>`:""}
</div>`}_renderStructuredArg1Picker(e,t,i){let r=this._bucketWithPreserve(this._pickBucket(t),t,e.arg1Ix),s=t[0].toUpperCase()+t.slice(1);return o`
<label class="block">
${s}
<select @change=${c=>i({arg1Ix:parseInt(c.target.value,10)})}>
${r.map(c=>o`
<option .value=${String(c.index)} ?selected=${c.index===e.arg1Ix}>
#${c.index} ${c.name}
</option>`)}
</select>
</label>`}_renderChainCondFamily(e,t){let i=s=>{let c=this._objects?.zones?.[0]?.index??1,l=this._objects?.units?.[0]?.index??1,d=this._objects?.areas?.[0]?.index??1,p;switch(s){case"none":p={family:"none"};break;case"misc":p={family:"misc",misc:1};break;case"zone":p={family:"zone",index:c,active:!1};break;case"unit":p={family:"unit",index:l,active:!0};break;case"time":p={family:"time",index:1,active:!0};break;case"sec":p={family:"sec",index:d,mode:0};break}let u=ve(p);this._patchChainCondition(t,u)},r=s=>{this._patchChainCondition(t,ve(s))};return o`
<label class="block">
Family
<select @change=${s=>i(s.target.value)}>
<option value="zone" ?selected=${e.family==="zone"}>Zone state</option>
<option value="unit" ?selected=${e.family==="unit"}>Unit state</option>
<option value="sec" ?selected=${e.family==="sec"}>Area in security mode</option>
<option value="time" ?selected=${e.family==="time"}>Time clock</option>
<option value="misc" ?selected=${e.family==="misc"}>Misc</option>
</select>
</label>
${this._renderChainCondSubfields(e,r)}
`}_renderChainCondSubfields(e,t){if(e.family==="zone"){let i=this._bucketWithPreserve(this._objects?.zones??null,"zone",e.index??0);return o`
<label class="block">
Zone
<select @change=${r=>t({...e,index:parseInt(r.target.value,10)})}>
${i.map(r=>o`
<option .value=${String(r.index)} ?selected=${r.index===e.index}>
#${r.index} ${r.name}
</option>`)}
</select>
</label>
<label class="block">
Is
<select @change=${r=>t({...e,active:r.target.value==="1"})}>
<option value="0" ?selected=${!e.active}>secure</option>
<option value="1" ?selected=${e.active}>not ready</option>
</select>
</label>`}if(e.family==="unit"){let i=this._bucketWithPreserve(this._objects?.units??null,"unit",e.index??0);return o`
<label class="block">
Unit
<select @change=${r=>t({...e,index:parseInt(r.target.value,10)})}>
${i.map(r=>o`
<option .value=${String(r.index)} ?selected=${r.index===e.index}>
#${r.index} ${r.name}
</option>`)}
</select>
</label>
<label class="block">
Is
<select @change=${r=>t({...e,active:r.target.value==="1"})}>
<option value="1" ?selected=${e.active}>ON</option>
<option value="0" ?selected=${!e.active}>OFF</option>
</select>
</label>`}if(e.family==="sec"){let i=this._bucketWithPreserve(this._objects?.areas??null,"area",e.index??0);return o`
<label class="block">
Area
<select @change=${r=>t({...e,index:parseInt(r.target.value,10)})}>
${i.map(r=>o`
<option .value=${String(r.index)} ?selected=${r.index===e.index}>
#${r.index} ${r.name}
</option>`)}
</select>
</label>
<label class="block">
Mode
<select @change=${r=>t({...e,mode:parseInt(r.target.value,10)})}>
${fe.map(r=>o`
<option .value=${String(r.value)} ?selected=${r.value===e.mode}>
${r.label}
</option>`)}
</select>
</label>`}return e.family==="time"?o`
<label class="block">
Time clock # (1..3)
<input type="number" min="1" max="3"
.value=${String(e.index??1)}
@input=${i=>{let r=parseInt(i.target.value,10);Number.isFinite(r)&&t({...e,index:r})}}
/>
</label>
<label class="block">
Is
<select @change=${i=>t({...e,active:i.target.value==="1"})}>
<option value="1" ?selected=${e.active}>enabled</option>
<option value="0" ?selected=${!e.active}>disabled</option>
</select>
</label>`:o`
<label class="block">
Condition
<select @change=${i=>t({family:"misc",misc:parseInt(i.target.value,10)})}>
${ge.map(i=>o`
<option .value=${String(i.value)} ?selected=${i.value===e.misc}>
${i.label}
</option>`)}
</select>
</label>`}_renderChainActionsSection(e){return o`
<fieldset>
<legend>
Actions (${e.length})
<button type="button" class="mini-btn"
@click=${()=>this._addChainAction()}>+ THEN</button>
</legend>
${e.map((t,i)=>this._renderChainActionRow(t,i,e.length))}
</fieldset>
`}_renderChainActionRow(e,t,i){let r=N(e.cmd??0),s=r?.ref_kind?this._bucketWithPreserve(this._pickBucket(r.ref_kind),r.ref_kind,e.pr2??0):null,c=e.cmd===9;return o`
<div class="cond-slot">
<div class="cond-row-header">
<strong>${t===0?"THEN":"AND"}</strong>
${i>1?o`
<button type="button" class="mini-btn danger"
@click=${()=>this._removeChainAction(t)}>×</button>`:""}
</div>
<label class="block">
Command
<select @change=${l=>{let d=parseInt(l.target.value,10),p=N(d),u=e.pr2??0;if(p?.ref_kind&&this._objects){let f=this._pickBucket(p.ref_kind);f&&f.length>0&&!f.some(_=>_.index===u)&&(u=f[0].index)}else p?.ref_kind||(u=0);this._patchChainAction(t,{cmd:d,pr2:u})}}>
${G.map(l=>o`
<option .value=${String(l.value)} ?selected=${l.value===e.cmd}>
${l.label}
</option>`)}
</select>
</label>
${r?.ref_kind?o`
<label class="block">
${r.ref_kind[0].toUpperCase()+r.ref_kind.slice(1)}
<select @change=${l=>{let d=parseInt(l.target.value,10);Number.isFinite(d)&&this._patchChainAction(t,{pr2:d})}}>
${(s??[]).map(l=>o`
<option .value=${String(l.index)} ?selected=${l.index===e.pr2}>
#${l.index} ${l.name}
</option>`)}
</select>
</label>`:""}
${c?o`
<label class="block">
Level (0..100)
<input type="number" min="0" max="100"
.value=${String(e.par??0)}
@input=${l=>{let d=parseInt(l.target.value,10);Number.isFinite(d)&&d>=0&&d<=100&&this._patchChainAction(t,{par:d})}}
/>
</label>`:""}
</div>
`}};h.styles=J`
: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, .primary, .secondary, .danger {
border: none;
padding: 8px 16px;
font-size: 0.92rem;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
}
.fire, .primary {
background: var(--primary-color, #03a9f4);
color: var(--text-primary-color, #fff);
}
.secondary {
background: var(--secondary-background-color, #eee);
color: var(--primary-text-color, #000);
}
.danger {
background: transparent;
color: var(--error-color, #db4437);
border: 1px solid var(--error-color, #db4437);
}
.fire:hover, .primary:hover, .secondary:hover, .danger:hover {
filter: brightness(0.9);
}
.action-row {
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
padding: 10px;
background: var(--secondary-background-color, #f5f5f5);
border-radius: 4px;
font-size: 0.88rem;
}
.action-row.danger-row {
background: var(--error-color, #db4437);
color: white;
}
.action-row input[type="number"] {
width: 70px;
padding: 4px 6px;
font-size: 0.9rem;
border: 1px solid var(--divider-color, #ccc);
border-radius: 3px;
margin-left: 6px;
}
.action-row button {
padding: 4px 12px;
font-size: 0.85rem;
}
.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);
}
/* editor */
.editor-body { display: flex; flex-direction: column; gap: 12px; }
.editor fieldset {
border: 1px solid var(--divider-color, #ddd);
border-radius: 4px;
padding: 10px 12px;
margin: 0;
}
.editor legend {
padding: 0 6px;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--secondary-text-color, #777);
}
.editor .row {
display: flex; align-items: center; gap: 8px;
}
.editor label.block {
display: flex; flex-direction: column;
gap: 4px;
font-size: 0.85rem;
color: var(--secondary-text-color, #555);
margin-bottom: 8px;
}
.editor label.block:last-child { margin-bottom: 0; }
.editor input[type="number"], .editor select {
padding: 6px 8px;
font-size: 0.95rem;
border: 1px solid var(--divider-color, #ccc);
border-radius: 3px;
background: var(--card-background-color, #fff);
color: inherit;
}
.editor .time-colon {
font-weight: 600; font-size: 1.4rem;
margin: 0 2px;
}
.days-row { display: flex; flex-wrap: wrap; gap: 4px; }
.day-toggle {
padding: 6px 10px;
border: 1px solid var(--divider-color, #ccc);
background: var(--card-background-color, #fff);
color: var(--secondary-text-color, #555);
border-radius: 3px;
cursor: pointer;
font-family: inherit;
font-size: 0.82rem;
}
.day-toggle.active {
background: var(--primary-color, #03a9f4);
color: var(--text-primary-color, #fff);
border-color: transparent;
}
.conditions-readonly {
padding: 10px 12px;
background: var(--secondary-background-color, #f5f5f5);
border-radius: 4px;
font-size: 0.82rem;
color: var(--secondary-text-color, #666);
}
.cond-slot {
padding: 8px 10px;
margin-top: 6px;
background: var(--secondary-background-color, #f5f5f5);
border-radius: 4px;
}
.cond-slot:first-of-type { margin-top: 0; }
.cond-family-label {
font-weight: 600;
color: var(--primary-text-color, #000);
}
.cond-row-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 4px;
}
.mini-btn {
border: 1px solid var(--divider-color, #ccc);
background: var(--card-background-color, #fff);
color: inherit;
padding: 2px 8px;
border-radius: 3px;
font-size: 0.78rem;
cursor: pointer;
font-family: inherit;
margin-left: 6px;
}
.mini-btn:hover { background: var(--secondary-background-color, #eee); }
.mini-btn.danger {
color: var(--error-color, #db4437);
border-color: var(--error-color, #db4437);
}
.structured-cond {
background: rgba(255, 152, 0, 0.08); /* subtle structured tint */
}
.structured-row {
display: grid;
grid-template-columns: 1fr;
gap: 6px;
}
.structured-tag, .readonly-tag {
display: inline-block;
margin-left: 6px;
padding: 1px 6px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
border-radius: 3px;
}
.structured-tag {
background: rgba(255, 152, 0, 0.18);
color: #b35a00;
}
.readonly-tag {
background: var(--secondary-background-color, #eee);
color: var(--secondary-text-color, #888);
}
.chain-meta {
margin-top: 8px;
padding: 8px 10px;
font-size: 0.82rem;
color: var(--secondary-text-color, #666);
background: var(--secondary-background-color, #f5f5f5);
border-radius: 4px;
}
`,m([H({attribute:!1})],h.prototype,"hass",2),m([H({attribute:!1})],h.prototype,"narrow",2),m([g()],h.prototype,"_entryId",2),m([g()],h.prototype,"_rows",2),m([g()],h.prototype,"_total",2),m([g()],h.prototype,"_filteredTotal",2),m([g()],h.prototype,"_loading",2),m([g()],h.prototype,"_error",2),m([g()],h.prototype,"_activeTriggerTypes",2),m([g()],h.prototype,"_referenceFilter",2),m([g()],h.prototype,"_searchTerm",2),m([g()],h.prototype,"_selectedSlot",2),m([g()],h.prototype,"_detail",2),m([g()],h.prototype,"_detailLoading",2),m([g()],h.prototype,"_fireFeedback",2),m([g()],h.prototype,"_writeFeedback",2),m([g()],h.prototype,"_cloneTargetSlot",2),m([g()],h.prototype,"_showCloneInput",2),m([g()],h.prototype,"_confirmingClear",2),m([g()],h.prototype,"_editingDraft",2),m([g()],h.prototype,"_objects",2),m([g()],h.prototype,"_chainDraft",2),h=m([je("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
*)
*/