@panphora/sapjs 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/dist/sap.js +2490 -0
- package/dist/sap.min.js +6 -0
- package/package.json +52 -0
- package/src/actions.js +310 -0
- package/src/carrier.js +128 -0
- package/src/compile.js +91 -0
- package/src/control-serialize.js +85 -0
- package/src/debug.js +0 -0
- package/src/dom.js +182 -0
- package/src/errors.js +190 -0
- package/src/helpers.js +119 -0
- package/src/lint.js +229 -0
- package/src/mount.js +84 -0
- package/src/mutation-bridge.js +195 -0
- package/src/pass.js +472 -0
- package/src/platform.js +78 -0
- package/src/sap.js +178 -0
- package/src/scheduler.js +65 -0
- package/src/scope.js +265 -0
package/dist/sap.min.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
var Sap=(()=>{var Nt=Object.defineProperty;var Ae=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames;var Ee=Object.prototype.hasOwnProperty;var Se=(t,e)=>{for(var n in e)Nt(t,n,{get:e[n],enumerable:!0})},xe=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ve(e))!Ee.call(t,s)&&s!==n&&Nt(t,s,{get:()=>e[s],enumerable:!(r=Ae(e,s))||r.enumerable});return t};var $e=t=>xe(Nt({},"__esModule",{value:!0}),t);var En={};Se(En,{Sap:()=>_,default:()=>vn,mount:()=>ye,mountAll:()=>lt,runtime:()=>K});function Pt(t){let e=!1,n=new Set,r=[];function s(l){l&&(r.push(l),r.length>8&&r.shift())}function f(l,w){s(w),!l._broken&&(n.add(l),!e&&(e=!0,queueMicrotask(u)))}function u(){e=!1;let l=[...n];n.clear();for(let w of l)T(w)}function T(l){if(!l._broken){if(l._passes=(l._passes||0)+1,l._passes>50){l._broken=!0,l._breakerRing=r.slice(),t(l,{breaker:!0});return}l._resetArmed||(l._resetArmed=!0,setTimeout(()=>{l._passes=0,l._resetArmed=!1},0)),t(l)}}function E(l,w){s(w),T(l)}function y(l){l._broken=!1,l._passes=0}return{schedule:f,runNow:E,rearm:y,ring:r}}var ke=0;function Z(t,e="sap"){return t.id||(t.id=`${e}-${(ke++).toString(36)}`),t.id}function Ne(t){return t.hasAttribute("scope")||t.hasAttribute("items")||t.hasAttribute("detail")}function Te(t){return t.hasAttribute("sap-ignore")||t.hasAttribute("template")}function W(t,e){for(let r of t.children)n(r);function n(r){if(r.nodeType!==1||Te(r))return;let s=Ne(r);if(e(r,s),!s)for(let f of r.children)n(f)}}function I(t){let e=t;for(;e;){if(e.hasAttribute("sap")||e.hasAttribute("scope")||e.hasAttribute("item")||e.hasAttribute("detail"))return e;e=e.parentElement}return null}function dt(t){let e=t;for(;e;){if(e.hasAttribute("detail")||e.hasAttribute("item"))return e;e=e.parentElement}return null}function it(t){let e=t;for(;e;){if(e.hasAttribute("detail"))return e;e=e.parentElement}return null}function tt(t,e){let n=null;return W(t,r=>{!n&&r.getAttribute("bind")===e&&(n=r)}),n}function st(t,e){let n=null;return W(t,r=>{!n&&r.getAttribute("items")===e&&(n=r)}),n}function O(t){return[...t.children].filter(e=>e.hasAttribute("item")&&!e.hasAttribute("template"))}function pt(t){return[...t.children].find(e=>e.hasAttribute("item")&&e.hasAttribute("template"))||null}function F(t){let e=[];if(!t)return e;for(let n of t.trim().split(/\s+/)){if(!n)continue;let r=n,s,f=r.indexOf("=");f>=0&&(s=r.slice(f+1),r=r.slice(0,f));let u=r.split(":"),T=u[0],E="string",y=!1;for(let l of u.slice(1))l==="num"?E="num":l==="bool"?E="bool":l==="transient"&&(y=!0);e.push({name:T,type:E,default:s,transient:y})}return e}function G(t,e){if(e==="num"){if(t==null||t==="")return 0;let n=Number(t);return Number.isNaN(n)?0:n}return e==="bool"?t==="true"||t===""||t==="1":t??""}function mt(t,e){return e==="bool"?t?"true":"false":t==null?"":String(t)}function ht(t,e){let n=t._sapTransient||(t._sapTransient={});if(!(e.name in n)){let r=t.getAttribute(e.name);r==null&&(r=e.default!=null?e.default:e.type==="num"?"0":e.type==="bool"?"false":""),n[e.name]=G(r,e.type)}return t.hasAttribute(e.name)&&t.removeAttribute(e.name),n[e.name]}function Tt(t,e,n){(t._sapTransient||(t._sapTransient={}))[e.name]=n,t.hasAttribute(e.name)&&t.removeAttribute(e.name)}function at(t,e,n){let r=String(n).split("."),s=r[r.length-1];if(r.length===1){let T=_e(e,s);return T||(t.getAttribute("items")===s?t:t.querySelector(`[items="${s}"]`))}let f=r[r.length-2],u=[...t.querySelectorAll(`[scope="${f}"]`)].find(T=>st(T,s));return u?st(u,s):null}function _e(t,e){let n=I(t);for(;n;){if(n.getAttribute("items")===e)return n;let r=st(n,e);if(r)return r;n=I(n.parentElement)}return null}function P(t){if(typeof t=="number")return t;if(t==null||t==="")return 0;let e=Number(typeof t=="string"?t.replace(/[$,%\s]/g,""):t);return Number.isNaN(e)?0:e}function bt(t,e){return Array.isArray(t)?e==null?t:t.map(n=>typeof e=="function"?e(n):n?.[e]):[]}function Bt(t,e){return bt(t,e).reduce((n,r)=>n+P(r),0)}function Le(t,e){return Array.isArray(t)?e==null?t.length:t.reduce((n,r)=>n+(e(r)?1:0),0):0}function Ce(t,e){let n=bt(t,e);return n.length?Bt(t,e)/n.length:0}function Me(t,e){let n=bt(t,e).filter(r=>r!=null&&r!=="");return n.length?n.every(r=>typeof r=="number"||!Number.isNaN(Number(r)))?Math.min(...n.map(Number)):n.reduce((r,s)=>String(r)<=String(s)?r:s):0}function Re(t,e){let n=bt(t,e).filter(r=>r!=null&&r!=="");return n.length?n.every(r=>typeof r=="number"||!Number.isNaN(Number(r)))?Math.max(...n.map(Number)):n.reduce((r,s)=>String(r)>=String(s)?r:s):0}function De(t,e,n){let r=P(t)===1?e:n??e+"s";return`${t} ${r}`}function Ie(t,e){let n=new Date(t),r=new Date(e);return Number.isNaN(+n)||Number.isNaN(+r)?0:Math.round((r-n)/864e5)}function z(t){return typeof t=="number"?t:Number(t)}var ut={usd:t=>"$"+z(t).toLocaleString("en-US",{maximumFractionDigits:0}),usd2:t=>"$"+z(t).toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2}),pct:t=>Math.round(z(t))+"%",pct1:t=>z(t).toFixed(1)+"%",int:t=>Math.round(z(t)).toLocaleString("en-US"),num:t=>z(t).toLocaleString("en-US"),num2:t=>z(t).toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2}),compact:t=>z(t).toLocaleString("en-US",{notation:"compact"}),date:t=>{let e=new Date(t);return Number.isNaN(+e)?String(t):e.toLocaleDateString("en-US")},clock:t=>{let e=z(t),n=Math.floor(e/100),r=n%10,s=Math.floor(n/10)%60;return`${Math.floor(n/600)}:${String(s).padStart(2,"0")}.${r}`}},We=new Set(["usd","usd2","pct","pct1","int","num","num2","compact","clock"]);function qt(t,e){if(!t)return e==null?"":String(e);let n=ut[t];if(typeof n!="function"){let r=new Error(`unknown format "${t}"`);throw r.sapCode="E22",r}if(We.has(t)){let r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)){let s=new Error(`format "${t}" received ${typeof e=="string"?`"${e}"`:e}`);throw s.sapCode="E22",s}}return n(e)}function zt(){return[ut,P,Bt,Le,Ce,Me,Re,De,Ie]}var Fe=["state","item","el","root","fmt","num","sum","count","avg","min","max","plural","days"],gt=new Map,Ht=/\bstate\.([a-z][a-z0-9_]*)/g,jt=/\bitem\.([a-z][a-z0-9_]*)/g;function Oe(t){let e=new Set,n=new Set,r;for(Ht.lastIndex=0;r=Ht.exec(t);)e.add(r[1]);for(jt.lastIndex=0;r=jt.exec(t);)n.add(r[1]);return{state:e,item:n}}function D(t,e=!1){let n=(e?"!":"")+t,r=gt.get(n);if(r)return r;gt.size>512&>.clear();try{let s=e?`"use strict";
|
|
2
|
+
${t}`:`"use strict"; return (${t});`;r={fn:new Function(...Fe,s),src:t,statement:e,deps:Oe(t),error:null}}catch(s){r={fn:null,src:t,statement:e,deps:{state:new Set,item:new Set},error:s}}return gt.set(n,r),r}function H(t,e){if(t.error)throw t.error;return t.fn(e.state,e.item,e.el??null,e.root,...zt())}function Vt(t){let e=new Map;for(let u of t)e.set(u.name,u);let n=[],r=new Map,s=[];function f(u){let T=r.get(u.name);if(T!==1){if(T===0){let E=[...s.slice(s.indexOf(u.name)),u.name].join(" -> "),y=new Error(`calc: cycle: ${E}`);throw y.sapCode="E07",y.cycle=E,y}r.set(u.name,0),s.push(u.name);for(let E of u.entry.deps.state){let y=e.get(E);y&&y!==u&&f(y)}for(let E of u.entry.deps.item){let y=e.get(E);y&&y!==u&&f(y)}s.pop(),r.set(u.name,1),n.push(u)}}for(let u of t)f(u);return n}function Ut(t){return(t.getAttribute("type")||"text").toLowerCase()}function et(t){let e=t.tagName;if(e==="INPUT"){let n=Ut(t);n==="checkbox"||n==="radio"?t.checked?t.setAttribute("checked",""):t.removeAttribute("checked"):t.setAttribute("value",t.value);return}if(e==="TEXTAREA"){t.setAttribute("data-value",t.value);return}if(e==="SELECT"){let n=t.options;for(let r=0;r<n.length;r++)n[r].selected?n[r].setAttribute("selected",""):n[r].removeAttribute("selected")}}function Gt(t,e=t){let n=t.tagName;if(n==="INPUT"){let r=Ut(t);r==="checkbox"||r==="radio"?e.checked?t.setAttribute("checked",""):t.removeAttribute("checked"):t.setAttribute("value",e.value);return}if(n==="TEXTAREA"){t.textContent=e.value,t.removeAttribute("data-value");return}if(n==="SELECT"){let r=t.options,s=e.options;for(let f=0;f<r.length;f++)s[f]&&s[f].selected?r[f].setAttribute("selected",""):r[f].removeAttribute("selected")}}function wt(t){t.tagName!=="TEXTAREA"||!t.hasAttribute("data-value")||t!==t.ownerDocument.activeElement&&(t.textContent=t.getAttribute("data-value"),t.removeAttribute("data-value"))}function yt(t){let e=typeof window<"u"&&window.hyperclay&&window.hyperclay.region;return!e||typeof e.resolveRegionPolicy!="function"||typeof e.skipForPolicy!="function"?!1:e.skipForPolicy(e.resolveRegionPolicy(t),void 0,["no-save","freeze"])}function Yt(){let t=typeof window<"u"&&window.hyperclay;return t&&typeof t.consent=="function"?t.consent:null}function Xt(t,e){if(typeof e!="function")throw new Error("Sap.batch(label, fn): fn must be a function");let n=typeof window<"u"&&window.hyperclay&&window.hyperclay.undo;n&&n.flush&&n.flush();let r=e();if(r&&typeof r.then=="function")throw new Error("Sap.batch fn must be synchronous (it returned a promise)");return n&&n.commitCaptured&&n.commitCaptured(t),r}function Kt(t){if(typeof window>"u"||typeof document>"u")return;let e=()=>{let r=!1;for(let s of t.apps())s.root.isConnected?t.runNow(s,"platform"):r=!0;r&&(t.pruneDisconnected(),t.remountIfPresent())};document.addEventListener("hyperclay:livesync-applied",e);let n=window.hyperclay&&window.hyperclay.undo;n&&typeof n.on=="function"?(n.on("undo",e),n.on("redo",e)):document.addEventListener("hyperclay:mutation-ready",()=>{let r=window.hyperclay&&window.hyperclay.undo;r&&typeof r.on=="function"&&(r.on("undo",e),r.on("redo",e))})}function Pe(t){let e=t.tagName;if(e==="INPUT"){let n=(t.getAttribute("type")||"text").toLowerCase();return n==="checkbox"?"checkbox":n==="radio"?"radio":n==="number"||n==="range"?"number":n==="hidden"?"hidden":"text"}return e==="TEXTAREA"?"text":e==="SELECT"?t.multiple?"select-multiple":"select-one":"leaf"}function Be(t){if(t.validity&&t.validity.badInput)return t.hasAttribute("value")?Number(t.getAttribute("value"))||0:t._sapLastGood??0;if(t.value==="")return 0;let e=Number(t.value);return Number.isNaN(e)?t._sapLastGood??0:(t._sapLastGood=e,e)}function qe(t,e){switch(e){case"checkbox":return t.checked;case"radio":return t.checked?t.value:void 0;case"number":return Be(t);case"select-one":return t.value;case"select-multiple":return[...t.selectedOptions].map(n=>n.value);case"hidden":case"text":return t.value;case"leaf":return t.textContent;default:return t.value}}function ze(t,e,n){switch(e){case"checkbox":t.checked=!!n;break;case"radio":t.checked=String(t.value)===String(n);break;case"number":case"text":case"hidden":t.value=n==null?"":String(n);break;case"select-one":t.value=n==null?"":String(n);break;case"select-multiple":{let r=new Set((Array.isArray(n)?n:[n]).map(String));for(let s of t.options)s.selected=r.has(s.value);break}case"leaf":t.textContent=n==null?"":String(n);break;default:t.value=n==null?"":String(n)}}function Jt(t){if(t.hasAttribute("transient")){t.removeAttribute("value"),t.removeAttribute("checked"),t.removeAttribute("data-value");return}yt(t)||et(t)}function He(t){t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0}))}function R(t){let e=Pe(t);return{el:t,kind:e,read(){return qe(t,e)},write(n,r={}){ze(t,e,n),r.noMirror||Jt(t),r.silent||He(t)},mirror(){Jt(t)}}}var _t={E01:{slug:"foreign-dialect",behavior:"HALT"},E02:{slug:"moustaches-in-text",behavior:"HALT"},E04:{slug:"duplicate-declaration",behavior:"HALT"},E05:{slug:"reserved-name",behavior:"HALT"},E06:{slug:"global-html-name",behavior:"HALT"},E07:{slug:"calc-cycle",behavior:"ERR"},E08:{slug:"expression-syntax",behavior:"HALT"},E10:{slug:"orphan-item",behavior:"HALT"},E12:{slug:"unknown-state-key",behavior:"ERR"},E15:{slug:"write-to-undeclared-key",behavior:"ERR"},E16:{slug:"write-to-computed-field",behavior:"ERR"},E17:{slug:"unresolvable-action-target",behavior:"HALT"},E18:{slug:"paint-on-form-control",behavior:"HALT"},E20:{slug:"bind-on-non-control",behavior:"HALT"},E22:{slug:"format-type",behavior:"ERR"},E24:{slug:"runtime-expression-throw",behavior:"ERR"},E26:{slug:"circuit-breaker",behavior:"HALT"},E30:{slug:"attr-on-bound-control",behavior:"HALT"},E31:{slug:"password-without-transient",behavior:"HALT"},E32:{slug:"file-bind",behavior:"HALT"},E33:{slug:"dialog-state-open",behavior:"HALT"},E34:{slug:"transient-persist-collision",behavior:"HALT"},W03:{slug:"unknown-colon-attribute",behavior:"WARN"},W04:{slug:"expression-in-when",behavior:"WARN"},W30:{slug:"nonzero-mount-writes",behavior:"WARN"}},At=["disabled","readonly","required","checked","selected","multiple","open","inert","autofocus","novalidate","hidden"],ct=new Set(["title","style","hidden","lang","dir","translate","class","id","nonce","autofocus","tabindex","contenteditable","spellcheck","draggable","role","slot","part","name","value","type","itemscope","itemtype","itemid","itemprop","itemref"]),vt=new Set(["sap","scope","items","item","template","bind","calc","text","show","attr","set","trigger","move","sort","detail","effect","invalid","confirm","transient","default","persist","sortable","editmode"]);function Et(t){if(!t||t.nodeType!==1)return"?";if(t.id)return`${t.tagName.toLowerCase()}#${t.id}`;let e=[],n=t,r=0;for(;n&&n.nodeType===1&&r<4;){let s=n.tagName.toLowerCase();if(n.hasAttribute("sap"))s+="[sap]";else if(n.parentElement){let f=[...n.parentElement.children].filter(u=>u.tagName===n.tagName);f.length>1&&(s+=`:nth-of-type(${f.indexOf(n)+1})`)}if(e.unshift(s),n.hasAttribute("sap"))break;n=n.parentElement,r++}return e.join(" > ")}function Qt(t){let e=t.toLowerCase();return/^x-text$|^x-html$/.test(e)?'use text="state.x" (Sap paints text only)':/^x-show$|^v-show$|^v-if$/.test(e)?'use show="expr" (toggles native hidden)':/^x-model$|^v-model$/.test(e)?'use bind="field"':/^@|^x-on:|^v-on:|^on:/.test(e)?"use native onclick + Sap(this) (no event directive)":/^:|^x-bind:class$|^v-bind:class$/.test(e)?'use class:NAME="expr" or attr:NAME="expr"':/^x-for$|^v-for$/.test(e)?'use items="name" on the container and item template on the row':/^x-data$/.test(e)?"declare state with state= / bind / items (no JS state object)":/^x-init$/.test(e)?"use a <script> or onclick + Sap(this)":/^bind:/.test(e)?'use bind="field"; one-way paint is attr:NAME="expr"':/^x-/.test(e)||/^v-/.test(e)?"this is a foreign-dialect attribute; Sap has no x-/v- attributes":null}var nt=class{constructor(e){this.appName=e,this.errors=[],this.warnings=[],this.halted=!1,this.haltReason=null}block(e,n,r={}){let s=(_t[e]||{}).slug||"error",f={code:e,slug:s,el:Et(n),attr:r.attr||null,expr:r.expr||null,key:r.key||null,problem:r.problem||"",didYouMean:r.didYouMean||null,fix:r.fix||null,phase:r.phase||"runtime"},u=[`
|
|
3
|
+
`+(e[0]==="W"?"sap \u26A0 ":"sap \u2717 ")+e+" "+s];return u.push(` at ${f.el}${f.attr?` \u2014 ${f.attr}="${f.expr??""}"`:""}`),f.problem&&u.push(` problem: ${f.problem}`),f.didYouMean&&u.push(` did you mean: ${f.didYouMean}`),f.fix&&u.push(` fix: ${f.fix}`),console.error(u.join(`
|
|
4
|
+
`)),f}error(e,n,r={}){let s=this.block(e,n,r);return this.errors.push(s),s}warn(e,n,r={}){let s=this.block(e,n,r);return this.warnings.push(s),s}report(){return{ok:this.errors.length===0&&!this.halted,app:this.appName,halted:this.halted,haltReason:this.haltReason,errors:this.errors.slice(),warnings:this.warnings.slice()}}};function Y(t,e){t&&t.getAttribute("sap-error")!==e&&t.setAttribute("sap-error",e)}function St(t){t&&t.hasAttribute("sap-error")&&t.removeAttribute("sap-error")}function ft(t,e){let n=null,r=3;for(let s of e){let f=je(t,s);f<r&&(r=f,n=s)}return n}function je(t,e){let n=t.length,r=e.length,s=Array.from({length:n+1},(f,u)=>[u,...new Array(r).fill(0)]);for(let f=0;f<=r;f++)s[0][f]=f;for(let f=1;f<=n;f++)for(let u=1;u<=r;u++)s[f][u]=Math.min(s[f-1][u]+1,s[f][u-1]+1,s[f-1][u-1]+(t[f-1]===e[u-1]?0:1));return s[n][r]}var j=null,$t=!1,X=null;function Zt(t){let e=null;return{kind:"hub",subscribe(n){let r=t.onAnyChange({debounce:0,require:"observed"},n);typeof r=="function"&&(e=r)},suppress(n){t.pause();try{return n()}finally{t.resume()}},teardown(){if(e){try{e()}catch{}e=null}}}}function Ve(){let t=null;return{kind:"native",subscribe(e){t=new MutationObserver(r=>e(Ue(r)));let n=typeof document<"u"&&document.body||typeof document<"u"&&document.documentElement;n&&t.observe(n,{childList:!0,subtree:!0,attributes:!0,characterData:!0})},suppress(e){let n=typeof window<"u"&&window.hyperclay&&window.hyperclay.undo;n&&n.pause&&n.pause();try{return e()}finally{t&&t.takeRecords(),n&&n.resume&&n.resume()}},teardown(){t&&(t.disconnect(),t=null)}}}function Ue(t){let e=[];for(let n of t)if(n.type==="attributes"){let r=n.target;r&&r.nodeType===1&&!xt(r)&&e.push({type:"attribute",element:r})}else if(n.type==="characterData"){let r=n.target&&n.target.parentElement;r&&!xt(r)&&e.push({type:"characterData",element:r})}else if(n.type==="childList"){for(let r of n.addedNodes)if(!(r.nodeType!==1||xt(r))&&(e.push({type:"add",element:r}),r.querySelectorAll))for(let s of r.querySelectorAll("*"))xt(s)||e.push({type:"add",element:s});for(let r of n.removedNodes)r.nodeType===1&&e.push({type:"remove",element:r,parent:n.target})}return e}function xt(t){return!!(t.closest&&t.closest("[sap-ignore],[template]"))}function Ge(t,e){t.pruneDisconnected();let n=new Set,r=!1;for(let s of e){let f=s.type==="remove"?s.parent:s.element,u=f&&t.appFor(f);u&&n.add(u),s.type==="add"&&s.element&&s.element.nodeType===1&&s.element.hasAttribute&&s.element.hasAttribute("sap")&&!(t.isRegistered&&t.isRegistered(s.element))&&(r=!0)}r&&t.remountIfPresent();for(let s of n)s.root.isConnected&&t.schedule(s,"mutation")}function te(t){if(j)return j.suppress(t);let e=typeof window<"u"&&window.hyperclay&&window.hyperclay.undo;if(e&&e.pause&&e.resume){e.pause();try{return t()}finally{e.resume()}}return t()}function Lt(t,e){$t=!0,j=t,t.subscribe(n=>Ge(e,n))}function Ct(t){if($t||typeof window>"u"||typeof document>"u")return;let e=window,n=e.hyperclay&&e.hyperclay.Mutation;if(n&&typeof n.onAnyChange=="function"){Lt(Zt(n),t);return}typeof MutationObserver=="function"&&(Lt(Ve(),t),X=function(){let s=e.hyperclay&&e.hyperclay.Mutation;s&&(document.removeEventListener("hyperclay:mutation-ready",X),X=null,j&&j.teardown(),$t=!1,Lt(Zt(s),t))},document.addEventListener("hyperclay:mutation-ready",X))}function ee(){if(j)try{j.teardown()}catch{}X&&typeof document<"u"&&document.removeEventListener("hyperclay:mutation-ready",X),j=null,$t=!1,X=null}var oe=new Set(At);function rt(t,e={}){if(e.breaker){t.diag.error("E26",t.root,{problem:"recompute loop halted after 50 passes; recent triggers: "+(t._breakerRing||[]).join(", ")}),Y(t.root,"loop");return}te(()=>Ye(t,e))}function Ye(t,e){let n=t.root,r={},s=[],f=[],u=[],T=[],E=[],y=new Set;t.diag.errors=[];let l={root:r,state:r,item:null,ownerEl:n,projection:!1};function w(o,i){let h=o.getAttribute(i.name);return h==null&&(h=i.default!=null?i.default:i.type==="num"?"0":i.type==="bool"?"false":"",E.push({el:o,name:i.name,value:mt(G(h,i.type),i.type)})),G(h,i.type)}function k(o,i){let h=F(o.getAttribute("state")||"");o._sapStateDecls=h;for(let p of h){if(p.type==="string"&&oe.has(p.name)&&typeof o[p.name]=="boolean"){i[p.name]=o.hasAttribute(p.name);continue}if(p.transient){i[p.name]=ht(o,p);continue}i[p.name]=w(o,p)}}function L(o,i){if(!o.hasAttribute("bind"))return;let h=o.getAttribute("bind"),p=i.item||i.state,v=R(o);v.kind==="radio"?(h in p||(p[h]=""),o.checked&&(p[h]=o.value)):p[h]=v.read(),o.hasAttribute("transient")&&(o.removeAttribute("value"),o.removeAttribute("checked"))}function b(o,i){for(let h of o.attributes){let p=h.name,v=h.value;if(p.startsWith("calc:"))s.push({owner:i.item||i.state,ownerEl:i.ownerEl,name:p.slice(5),entry:D(v),el:o,ctx:i});else if(p==="text"||p.startsWith("text:"))f.push({el:o,kind:"text",fmt:p.includes(":")?p.slice(5):null,entry:D(v),ctx:i});else if(p==="show")f.push({el:o,kind:"show",entry:D(v),ctx:i});else if(p.startsWith("attr:"))f.push({el:o,kind:"attr",arg:p.slice(5),entry:D(v),ctx:i});else if(p.startsWith("class:"))f.push({el:o,kind:"class",arg:p.slice(6),entry:D(v),ctx:i});else if(p.startsWith("css:"))f.push({el:o,kind:"css",arg:p.slice(4),entry:D(v),ctx:i});else if(p==="effect")u.push({el:o,entry:D(v,!0),ctx:i});else if(p==="invalid")T.push({el:o,entry:D(v),ctx:i});else if(p.startsWith("show-when:")||p.startsWith("option:")){let N=p.slice(p.indexOf(":")+1);y.add(N),f.push({el:o,kind:"when",match:"show",field:N,values:v.split("|"),ctx:i})}else if(p.startsWith("hide-when:")){let N=p.slice(p.indexOf(":")+1);y.add(N),f.push({el:o,kind:"when",match:"hide",field:N,values:v.split("|"),ctx:i})}else if(p.startsWith("option-not:")){let N=p.slice(p.indexOf(":")+1);y.add(N),f.push({el:o,kind:"when",match:"show-not",field:N,values:v.split("|"),ctx:i})}}i.projection&&o.hasAttribute("bind")&&f.push({el:o,kind:"projection",field:o.getAttribute("bind"),ctx:i})}function g(o,i,h){Z(i,"row"),Object.defineProperty(o,"$key",{value:i.id,enumerable:!1}),Object.defineProperty(o,"$index",{value:h,enumerable:!1}),Object.defineProperty(o,"$el",{value:i,enumerable:!1})}function a(o,i,h){k(o,i),o._sapScope=i,!h.projection&&o.hasAttribute("bind")&&L(o,h),b(o,h),o.hasAttribute("items")?x(o,i,h):A(o,i,h)}function A(o,i,h){W(o,(p,v)=>{v?p.hasAttribute("scope")?S(p,i,h):p.hasAttribute("items")?(b(p,h),x(p,i,h)):p.hasAttribute("detail")&&M(p,i,h):(h.projection||L(p,h),b(p,h))})}function S(o,i,h){let p=o.getAttribute("scope"),v={};i[p]=v;let N={root:h.root,state:v,item:null,ownerEl:o,projection:h.projection};a(o,v,N)}function x(o,i,h){let p=o.getAttribute("items"),v=[];i[p]=v,o._sapList=v,O(o).forEach((N,ot)=>{let C={};g(C,N,ot);let q={root:h.root,state:h.state,item:C,ownerEl:N,projection:h.projection};a(N,C,q),v.push(C)})}function $(o,i){let h=String(o).split("."),p=i.state;for(let v of h)p=p?.[v];if(p===void 0){p=i.root;for(let v of h)p=p?.[v]}return p}function M(o,i,h){for(let Q of F(o.getAttribute("state")||""))Q.name in i||(i[Q.name]=w(o,Q));o._sapScope=i;let p=o.getAttribute("detail")||"",v=/^([\w.]+)\s+by\s+(.+)$/.exec(p);if(!v){o.hidden=!0,o._sapDetailRow=null;return}let N=v[1],ot=v[2].trim(),C=$(N,h),q;try{q=H(D(ot),{state:i,item:h.item,el:o,root:h.root})}catch{q=void 0}let J=Array.isArray(C)?C.find(Q=>Q&&String(Q.$key)===String(q)):null;if(o._sapDetailRow=J?J.$el:null,!J){o.hidden||(o.hidden=!0);return}o.hidden&&(o.hidden=!1);let Ot={root:h.root,state:i,item:J,ownerEl:J.$el,projection:!0};b(o,Ot),A(o,J,Ot)}if(a(n,r,l),E.length&&y.size)for(let o of E)y.has(o.name)&&o.el.getAttribute(o.name)==null&&o.el.setAttribute(o.name,o.value);let m=new Map;for(let o of s){let i=m.get(o.owner);i||(i={ownerEl:o.ownerEl,list:[]},m.set(o.owner,i)),i.list.push(o)}let d=[...m.values()].sort((o,i)=>re(i.ownerEl)-re(o.ownerEl));for(let o of d){let i;try{i=Vt(o.list)}catch(h){t.diag.error("E07",o.list[0].el,{problem:h.message}),i=o.list}for(let h of i)try{h.owner[h.name]=H(h.entry,{state:h.ctx.state,item:h.ctx.item,el:h.el,root:h.ctx.root}),St(h.el)}catch(p){h.owner[h.name]=void 0,t.diag.error("E24",h.el,{attr:"calc:"+h.name,expr:h.entry.src,problem:String(p.message)}),Y(h.el,p.sapCode||"E24")}}let c=0;for(let o of f)c+=Je(o,t);for(let o of u)Qe(o,t);for(let o of T)Ze(o);t._state=r,t._stats={fields:Object.keys(r).filter(o=>typeof r[o]!="function").length,calcs:s.length,paints:f.length,lists:m.size,writes:c},t._lastPass={trigger:e.trigger||"refresh",writes:c},t._debug&&console.log(`sap pass \xB7 trigger: ${t._lastPass.trigger} \xB7 writes ${c}/${f.length}`)}function Xe(t){return t.kind==="text"?t.fmt?"text:"+t.fmt:"text":t.kind==="attr"?"attr:"+t.arg:t.kind==="class"?"class:"+t.arg:t.kind==="css"?"css:"+t.arg:t.kind==="when"?(t.match==="hide"?"hide-when:":t.match==="show-not"?"option-not:":"show-when:")+t.field:t.kind}function Mt(t,e,n,r){let s=t;for(;s&&s.nodeType===1;){if(s.hasAttribute&&s.hasAttribute(e)){let f=n.includes(s.getAttribute(e));if(r?!f:f)return!0}s=s.parentElement}return!1}function Ke(t,e){if(t===t.ownerDocument.activeElement)return 0;let n=R(t),r=n.read(),s=e??"";return String(r)!==String(s)?(n.write(s,{silent:!0}),1):0}function Je(t,e){if(t.kind==="projection")return Ke(t.el,t.ctx.item?t.ctx.item[t.field]:"");if(t.kind==="when")return ne(t,null);let n={state:t.ctx.state,item:t.ctx.item,el:t.el,root:t.ctx.root};try{let r=H(t.entry,n),s=ne(t,r);return St(t.el),s}catch(r){return e.diag.error(r.sapCode==="E22"?"E22":"E24",t.el,{attr:Xe(t),expr:t.entry&&t.entry.src,problem:String(r.message)}),Y(t.el,r.sapCode||"E24"),0}}function ne(t,e){let n=t.el;switch(t.kind){case"text":{let r=t.fmt!=null?qt(t.fmt,e):e==null?"":String(e);return n.textContent!==r?(n.textContent=r,1):0}case"show":{let r=!e;return n.hidden!==r?(n.hidden=r,1):0}case"when":{let r=typeof window<"u"&&window.hyperclay&&window.hyperclay.optionVisibility;if(r&&r._started)return n._sapWhen&&n.hidden?(n.hidden=!1,n._sapWhen=!1,1):0;let s;t.match==="hide"?s=!Mt(n,t.field,t.values,!1):t.match==="show-not"?s=Mt(n,t.field,t.values,!0):s=Mt(n,t.field,t.values,!1),n._sapWhen=!0;let f=!s;return n.hidden!==f?(n.hidden=f,1):0}case"attr":{let r=t.arg;if(oe.has(r)){let f=n.hasAttribute(r);return e&&!f?(n.setAttribute(r,""),1):!e&&f?(n.removeAttribute(r),1):0}let s=typeof e=="boolean"?String(e):e==null?"":String(e);return n.getAttribute(r)!==s?(n.setAttribute(r,s),1):0}case"class":{let r=!!e;return n.classList.contains(t.arg)!==r?(n.classList.toggle(t.arg,r),1):0}case"css":{let r=e==null?"":String(e);return n.style.getPropertyValue("--"+t.arg)!==r?(n.style.setProperty("--"+t.arg,r),1):0}default:return 0}}function Qe(t,e){try{H(t.entry,{state:t.ctx.state,item:t.ctx.item,el:t.el,root:t.ctx.root}),St(t.el)}catch(n){e.diag.error("E24",t.el,{attr:"effect",expr:t.entry&&t.entry.src,problem:String(n.message)}),Y(t.el,"E24")}}function Ze(t){let e="";try{let n=H(t.entry,{state:t.ctx.state,item:t.ctx.item,el:t.el,root:t.ctx.root});e=typeof n=="string"?n:""}catch{e=""}typeof t.el.setCustomValidity=="function"&&t.el.setCustomValidity(e)}function re(t){let e=0,n=t;for(;n&&!(n.hasAttribute&&n.hasAttribute("sap"));)n=n.parentElement,e++;return e}var tn=new Set(At),se=new Set(["calc","text","attr","class","css","set","move","sort","option","option-not","show-when","hide-when","editmode"]),en=new Set(["show-when","hide-when","option","option-not"]),nn=/\b(?:state|item|root)\.|===|!==|&&|\|\||[()]|^\s*!/,ie=new Set(["sap","scope","items","item","template","bind","show","effect","invalid","detail","state","transient","confirm","default","persist","sortable","trigger-add","trigger-remove","trigger-reset","sap-ignore","sap-error","no-save","no-watch","no-undo","no-trigger-autosave","freeze","mutations-ignore","save-remove","save-ignore","save-freeze"]);function rn(t){return t.tagName==="INPUT"||t.tagName==="SELECT"||t.tagName==="TEXTAREA"}function Rt(t){return t.hasAttribute("bind")}function on(t,e){e(t);for(let r of t.children)n(r);function n(r){if(r.nodeType===1&&!((r.hasAttribute("sap-ignore")||r.hasAttribute("template"))&&r.hasAttribute("sap-ignore"))){e(r);for(let s of r.children)n(s)}}}function kt(t,e){let n=!1,r=(u,T,E)=>{e.error(u,T,E),n=!0},s=t.ownerDocument.createTreeWalker(t,4),f;for(;f=s.nextNode();)if(/\{\{.*\}\}/.test(f.nodeValue||"")){r("E02",f.parentElement||t,{problem:"moustaches in text content are not a Sap feature",fix:'move the expression onto the element: text="state.x"'});break}return on(t,u=>{let T=u.closest&&u.closest("[detail]");for(let E of[...u.attributes]){let y=E.name,l=Qt(y);if(l){r("E01",u,{attr:y,expr:E.value,problem:`"${y}" is a foreign-dialect attribute; Sap has no ${y.split(/[:=]/)[0]} directive`,fix:l+" \u2014 or wrap the subtree in sap-ignore if intentional"});continue}if(y.includes(":")){let w=y.slice(0,y.indexOf(":"));if(se.has(w)){if(en.has(w)&&nn.test(E.value)){let k=y.slice(y.indexOf(":")+1);e.warn("W04",u,{attr:y,expr:E.value,problem:`${w}: compares the "${k}" attribute against a literal value, not a JS expression`,fix:`use show="${E.value}" for an expression, or a literal like ${w}:${k}="overview"`})}}else{let k=ft(w,[...se]);e.warn("W03",u,{attr:y,problem:`unknown "${w}:" attribute`,didYouMean:k?`${k}:`:null})}}else if(!ie.has(y)&&!ct.has(y)&&!tn.has(y)&&!/^(data-|aria-|on)/.test(y)&&!(y in u)){let w=ft(y,[...ie]);w&&e.warn("W03",u,{attr:y,problem:`unknown "${y}" attribute`,didYouMean:w})}y==="attr:hidden"&&e.warn("W03",u,{attr:y,problem:"attr:hidden paints the wrong attribute",fix:'use show="expr"'}),(y==="attr:value"||y==="attr:checked"||y==="attr:selected")&&Rt(u)&&r("E30",u,{attr:y,problem:"attr: on a bound control collides with persist ownership",fix:"remove it; bind owns this attribute"}),y==="effect"&&Rt(u)&&/\b(value|checked)\s*=[^=]/.test(E.value)&&r("E30",u,{attr:y,expr:E.value,problem:"effect writes value/checked on a bound control (writes a stale value with no synthetic event)",fix:"write through onclick + Sap(this) instead"}),(y==="text"||y.startsWith("text:")||y==="show")&&rn(u)&&(y==="show"||r("E18",u,{attr:y,problem:"paint belongs on output elements, not form controls",fix:"move the paint to an <output> or <span>"}))}if(u.hasAttribute("state")){let E=F(u.getAttribute("state")),y=new Set;for(let l of E)l.name.includes("-")&&r("E08",u,{attr:"state",problem:`field "${l.name}" is not a valid identifier (the hyphen parses as subtraction)`,fix:`rename to ${l.name.replace(/-/g,"")}`}),y.has(l.name)&&r("E04",u,{attr:"state",problem:`field "${l.name}" is declared twice in one scope`}),y.add(l.name),vt.has(l.name)&&r("E05",u,{attr:"state",problem:`"${l.name}" is a reserved Sap word`}),ct.has(l.name)&&r("E06",u,{attr:"state",problem:`"${l.name}" is a global HTML attribute name; pick another field name`}),l.name==="open"&&u.tagName==="DIALOG"&&r("E33",u,{attr:"state",problem:"dialog open is transient",fix:"use showModal() \u2014 declare state= only on <details> or popovers"})}if(Rt(u)){if(u.hasAttribute("-"),u.tagName==="INPUT"){let E=(u.getAttribute("type")||"text").toLowerCase();E==="file"&&r("E32",u,{attr:"bind",problem:"files never serialize into an HTML file",fix:"use no-save + effect instead of bind"}),E==="password"&&!u.hasAttribute("transient")&&r("E31",u,{attr:"bind",problem:"a password must never serialize into a world-readable file",fix:"add transient to the password input"})}else if(u.tagName!=="SELECT"&&u.tagName!=="TEXTAREA"){let E=u.getAttribute("contenteditable");!(E!=null&&E!=="false")&&u.children.length>0&&r("E20",u,{attr:"bind",problem:"bind on a container element is not a control; a write would overwrite its children",fix:"bind a control (input/select/textarea), a contenteditable, or an empty text leaf"})}}if(u.hasAttribute("transient")&&u.hasAttribute("persist")&&r("E34",u,{problem:"transient and persist are opposite: persist forces the live value into the saved bytes, leaking exactly what transient drops",fix:"remove one \u2014 transient to keep it out of the file, or persist to save it"}),u.hasAttribute("item")&&!u.hasAttribute("template")){let E=u.parentElement;(!E||!E.hasAttribute("items"))&&r("E10",u,{problem:"an [item] must be a direct child of an [items] list",fix:'wrap it in a container with items="name"'})}if(u.hasAttribute("items")&&T&&r("E17",u,{attr:"items",problem:"nested items inside a detail panel is a v1 mount error (nested collections ship in v1.1)"}),u.hasAttribute("items")){let E=t.querySelector(`[trigger-add="${u.getAttribute("items")}"]`);!pt(u)&&O(u).length===0&&E&&r("E17",u,{attr:"items",problem:`trigger-add targets items="${u.getAttribute("items")}" but it has no [item template] to clone`})}}),n&&(e.halted=!0,e.haltReason=e.errors[0]?e.errors[0].code:"halt",Y(t,e.haltReason)),!n}var ae="sap-styles",ue="[item][template]{display:none!important}[hidden]{display:none!important}[sap-error]{outline:2px solid #e5484d;outline-offset:1px}",Dt=new WeakSet;function sn(t){if(Dt.has(t))return;let e=t.defaultView,n=e&&e.CSSStyleSheet;if(n&&"adoptedStyleSheets"in t)try{let r=new n;r.replaceSync(ue),t.adoptedStyleSheets=[...t.adoptedStyleSheets,r],Dt.add(t);return}catch{}if(!t.getElementById(ae)){let r=t.createElement("style");r.id=ae,r.textContent=ue,(t.head||t.documentElement).appendChild(r)}Dt.add(t)}var an=0;function ce(){return typeof performance<"u"&&performance.now?performance.now():0}function It(t){let e=t.id||t.getAttribute("sap")||`app-${an++}`,n=new nt(e),r={root:t,name:e,diag:n,_state:null,_stats:null,_lastPass:null,_passes:0,_broken:!1,_mountMs:0,_mountWrites:0};if(!kt(t,n))return r.halted=!0,r;sn(t.ownerDocument);for(let u of t.querySelectorAll("textarea[data-value]"))wt(u);let f=ce();return rt(r,{trigger:"mount"}),r._mountMs=ce()-f,r._mountWrites=r._stats?r._stats.writes:0,r._mountWrites>0&&n.warn("W30",t,{problem:`${r._mountWrites} paint(s) ran at mount; the saved file was out of sync with its declared state`,fix:"re-save once so the file mounts clean (a settled file writes nothing)"}),r}function un(t,e){let n=null;return W(t,r=>{!n&&r.getAttribute("scope")===e&&(n=r)}),n}function fe(t,e,n){let r=I(t)||e;for(;r;){for(let s of F(r.getAttribute("state")||""))if(s.name===n)return{el:r,decl:s};if(r.hasAttribute("sap"))break;r=I(r.parentElement)}return null}function cn(t){return typeof t=="number"?"num":typeof t=="boolean"?"bool":"string"}function le(t){function e(b){let g=t.appFor(b);if(!g)return null;let a=it(b);if(a)return a._sapDetailRow?{ctxEl:a._sapDetailRow,appRec:g,isRow:!0}:{ctxEl:I(a.parentElement)||g.root,appRec:g,isRow:!1};let A=dt(b);if(A&&A.hasAttribute("item"))return{ctxEl:A,appRec:g,isRow:!0};let S=I(b)||g.root;return{ctxEl:S,appRec:g,isRow:S.hasAttribute("item")}}function n(b){let g=typeof b=="string"?document.querySelector(b):b;if(!g||g.nodeType!==1)return null;let a=e(g);return a?L(a.ctxEl,a.appRec,a.isRow):null}function r(b,g,a){let A=tt(b,a);if(A)return R(A).read();let S=st(b,a);if(S)return O(S).map(m=>L(m,g,!0));let x=un(b,a);if(x)return L(x,g,!1);let $=fe(b,g.root,a);if($)return $.decl.transient?ht($.el,$.decl):G($.el.getAttribute(a),$.decl.type);let M=b._sapScope;return M?M[a]:void 0}function s(b,g,a,A,S){A.transient?Tt(b,A,a):b.setAttribute(g,mt(a,A.type)),t.schedule(S,"set:"+g)}function f(b,g){let a=new Set,A=I(b)||g;for(;A;){for(let S of F(A.getAttribute("state")||""))a.add(S.name);if(A.hasAttribute("sap"))break;A=I(A.parentElement)}return W(b,S=>{let x=S.getAttribute("bind");x&&a.add(x)}),a}function u(b,g,a,A){let S=tt(b,a);if(S){R(S).write(A);return}let x=b;for(;x;){for(let h of x.attributes)if(h.name==="calc:"+a){g.diag.error("E16",x,{attr:"calc:"+a,problem:`"${a}" is a computed field; computed fields are read-only`});return}if(x.hasAttribute("sap"))break;x=x.parentElement}let $=fe(b,g.root,a);if($){s($.el,a,A,$.decl,g);return}let M=f(b,g.root);M.delete(a);let m=a.length>=4?ft(a,[...M]):null;if(m){g.diag.error("E12",b,{key:a,problem:`unknown state key "${a}"`,didYouMean:m});return}if(vt.has(a)||ct.has(a)){g.diag.error("E15",b,{key:a,problem:`cannot write undeclared field "${a}" \u2014 it is a reserved/global name and cannot become state`,fix:"pick a different field name, or declare it explicitly in a state= attribute"});return}let d=g.root,c=cn(A),o=d.getAttribute("state")||"",i=c==="string"?a:`${a}:${c}`;d.setAttribute("state",o?`${o} ${i}`:i),s(d,a,A,{name:a,type:c,transient:!1},g)}function T(b){let g=a=>{a.id&&(a.id="",Z(a,"row")),a.hasAttribute("sap-error")&&a.removeAttribute("sap-error")};b.id="",Z(b,"row"),b.querySelectorAll("[id]").forEach(g)}function E(b,g,a){let A=at(g.root,b,a);if(!A)return g.diag.error("E17",b,{problem:`cannot resolve a list named "${a}" from here`}),null;let S=pt(A);if(!S)return g.diag.error("E17",A,{problem:`items="${a}" has no [item template] to clone`}),null;let x=S.cloneNode(!0);return x.removeAttribute("template"),T(x),A.appendChild(x),t.schedule(g,"trigger-add:"+a),L(x,g,!0)}function y(b,g){W(b,a=>{if(a.hasAttribute("bind")){let A=R(a),S=a.getAttribute("default");A.kind==="checkbox"||A.kind==="radio"?A.write(S!=null?S===""||S==="true"||a.value===S:a.hasAttribute("checked")):A.write(S??(a.getAttribute("value")||""))}});for(let a of F(b.getAttribute("state")||"")){let A=a.default!=null?a.default:a.type==="num"?"0":a.type==="bool"?"false":"";a.transient?Tt(b,a,G(A,a.type)):b.setAttribute(a.name,A)}t.schedule(g,"trigger-reset")}function l(b,g){if(!b.isConnected)throw new Error("stale row handle: $remove() on a removed row");b.remove(),t.schedule(g,"trigger-remove")}function w(b,g,a){if(!b.isConnected)throw new Error("stale row handle: $move() on a removed row");let A=null;if(a&&a.nodeType===1?A=a:typeof a=="string"&&(A=at(g.root,b,a)),!A){g.diag.error("E17",b,{problem:`$move() cannot resolve target "${a}"`});return}t.moveInto(A,b),t.schedule(g,"trigger-move")}function k(b){let g=b.parentElement;return g?O(g).indexOf(b):-1}function L(b,g,a){let A={get $el(){return b},get $key(){return Z(b,a?"row":"scope")},get $index(){return k(b)},$add(x){return E(b,g,x)},$reset(){return y(b,g),S},$remove(){l(b,g)},$move(x){w(b,g,x)},get $appRec(){return g}},S=new Proxy(A,{get(x,$){return typeof $=="symbol"||$[0]==="$"?x[$]:r(b,g,$)},set(x,$,M){return typeof $=="string"&&$[0]==="$"?(x[$]=M,!0):(u(b,g,$,M),!0)},has(x,$){return typeof $=="string"&&$[0]==="$"?$ in x:r(b,g,$)!==void 0}});return S}return{Sap:n,contextElFor:e,readField:r,writeField:u,makeProxy:L}}var fn=["trigger-add","trigger-remove","trigger-reset"];function ln(t){if(t.nodeType!==1)return!1;for(let e of t.attributes){let n=e.name;if(n.startsWith("set:")||n.startsWith("move:")||n.startsWith("sort:")||fn.includes(n))return!0}return!1}function dn(t,e){let n=t,r=e.parentElement;for(;n&&n!==r;){if(n.nodeType===1&&ln(n)&&!(n.tagName==="FORM"&&n.hasAttribute("trigger-add")))return n;n=n.parentElement}return null}function de(t,e){let{Sap:n}=e;function r(m,d){let c=null,o=it(m);if(o&&o._sapDetailRow)c=o._sapDetailRow._sapScope||null;else{let p=dt(m);p&&p.hasAttribute("item")&&(c=p._sapScope||null)}return{state:(I(m)||d.root)._sapScope||d._state||{},item:c,el:m,root:d._state||{}}}function s(m,d,c){if(!m.hasAttribute("confirm")){c();return}let o=m.getAttribute("confirm"),i=o,h=D(o);if(!h.error)try{let N=H(h,r(m,d));(typeof N=="string"||typeof N=="number")&&(i=String(N))}catch{}let p=Yt();if(!p){window.confirm(i)&&c();return}if(m._sapGatePending)return;m._sapGatePending=!0;let v=()=>{m._sapGatePending=!1};p(i,()=>{v(),c()}).catch(v)}function f(m,d){for(let c of m.attributes){if(!c.name.startsWith("set:"))continue;let o=c.name.slice(4),i=D(c.value);if(i.error){d.diag.error("E08",m,{attr:c.name,expr:c.value,problem:String(i.error.message)});continue}let h;try{h=H(i,r(m,d))}catch(v){d.diag.error("E24",m,{attr:c.name,expr:c.value,problem:String(v.message)});continue}let p=n(m);p&&(p[o]=h)}}function u(m,d){let c=m.closest("[item]");if(!c)return;let o=O(c.parentElement),i=o.indexOf(c);d==="up"&&i>0?t.placeBefore(c,o[i-1]):d==="down"&&i<o.length-1&&t.placeBefore(o[i+1],c)}function T(m,d,c){if(d)return at(c.root,m,d);let o=m;for(;o;){if(o.hasAttribute&&o.hasAttribute("items"))return o;o=o.parentElement}let i=I(m)||c.root,h=[];return W(i,p=>{p.hasAttribute("items")&&h.push(p)}),h[0]||null}function E(m,d){let c=m==null||m==="",o=d==null||d==="";if(c&&o)return 0;if(c)return 1;if(o)return-1;let i=Number(m),h=Number(d);return!Number.isNaN(i)&&!Number.isNaN(h)?i-h:String(m)<String(d)?-1:String(m)>String(d)?1:0}function y(m,d,c,o){let i=T(m,c,o);if(!i){o.diag.error("E17",m,{attr:"sort:"+d,problem:`no items list resolvable for sort:${d}`});return}let h=O(i),p=C=>C._sapScope?C._sapScope[d]:void 0,v=[...h].sort((C,q)=>E(p(C),p(q))),ot=v.every((C,q)=>C===h[q])?[...v].reverse():v;for(let C of ot)t.moveInto(i,C);t.schedule(o,"sort:"+d)}function l(m){if(!m||m.hidden)return;let d=m.querySelector("[bind]:not([type=hidden]), [contenteditable]");d&&typeof d.focus=="function"&&d.focus()}function w(m,d){s(m,d,()=>{let c=n(m);for(let o of[...m.attributes]){let i=o.name;if(i==="trigger-remove")c&&c.$remove();else if(i==="trigger-reset")c&&c.$reset();else if(i==="trigger-add"){let h=c&&c.$add(o.value);h&&l(h.$el)}else i==="move:up"?(u(m,"up"),t.schedule(d,"move:up")):i==="move:down"?(u(m,"down"),t.schedule(d,"move:down")):i.startsWith("move:to")?c&&c.$move(o.value):i.startsWith("sort:")&&y(m,i.slice(5),o.value,d)}[...m.attributes].some(o=>o.name.startsWith("set:"))&&f(m,d)})}function k(m){let d=m.target;if(!d||d.nodeType!==1)return;let c=t.appFor(d);if(!c)return;let o=dn(d,c.root);o&&w(o,c)}function L(m){let d=R(m),c=m.getAttribute("default");d.kind==="checkbox"?d.write(c===""||c==="true"):d.write(c??"")}function b(m,d,c){let o=n(m);if(!o)return;let i=o.$add(c);if(!i)return;let h=[];W(m,p=>{p.hasAttribute("bind")&&h.push(p)});for(let p of h){let v=tt(i.$el,p.getAttribute("bind"));v&&R(v).write(R(p).read())}h.forEach(L),h.length?typeof h[0].focus=="function"&&h[0].focus():l(i.$el)}function g(m){let d=m.target;if(!d||!d.hasAttribute||!d.hasAttribute("trigger-add"))return;let c=t.appFor(d);c&&(m.preventDefault(),s(d,c,()=>b(d,c,d.getAttribute("trigger-add"))))}function a(m){let d=m.target,c=t.appFor(d);c&&setTimeout(()=>{d.querySelectorAll("[bind]").forEach(o=>{o.dispatchEvent(new Event("input",{bubbles:!0})),o.dispatchEvent(new Event("change",{bubbles:!0}))}),t.schedule(c,"reset")},0)}function A(m){let d=t.appFor(m.target);d&&t.schedule(d,"toggle")}function S(m){let d=m.target;if(!d||d.nodeType!==1)return;let c=t.appFor(d);if(!c)return;d.hasAttribute("persist")&&!yt(d)&&et(d);let o=it(d);if(o&&o._sapDetailRow&&d.hasAttribute("bind")){let i=tt(o._sapDetailRow,d.getAttribute("bind"));if(i&&i!==d){R(i).write(R(d).read());return}}t.schedule(c,m.type+" "+(d.id||d.tagName.toLowerCase()))}function x(m){let d=m.detail||{};for(let c of[d.from,d.to,m.target]){if(!c||c.nodeType!==1)continue;let o=t.appFor(c);o&&t.schedule(o,"sorted")}}function $(m){if(m.key!=="ArrowUp"&&m.key!=="ArrowDown")return;let d=m.target;if(!d||d.nodeType!==1||d.tagName!=="INPUT"||!d.hasAttribute("bind")||!d.hasAttribute("step"))return;let c=(d.getAttribute("type")||"text").toLowerCase();if(c!=="text"&&c!=="tel"&&c!=="search"||!t.appFor(d))return;m.preventDefault();let i=P(d.getAttribute("step"))||1,h=m.key==="ArrowUp"?1:-1,p=P(d.value)+i*(m.shiftKey?10:1)*h,v=d.getAttribute("min"),N=d.getAttribute("max");v!==null&&v!==""&&p<P(v)&&(p=P(v)),N!==null&&N!==""&&p>P(N)&&(p=P(N)),R(d).write(String(Number(p.toFixed(2))))}function M(m=document){m.addEventListener("click",k),m.addEventListener("submit",g),m.addEventListener("reset",a),m.addEventListener("toggle",A,!0),m.addEventListener("input",S),m.addEventListener("change",S),m.addEventListener("clay:sorted",x),m.addEventListener("keydown",$)}return{install:M}}var pe=/^(set:|move:|sort:|trigger-add$|trigger-remove$|trigger-reset$)/;function Wt(t){return`${t.tagName.toLowerCase()}[sap]`}function pn(t){let e=0;for(let n of t.querySelectorAll("*"))for(let r of n.attributes)if(pe.test(r.name)){e++;break}for(let n of t.attributes)if(pe.test(n.name)){e++;break}return e}function me(t){let e=[],n=r=>{for(let s of r.attributes){let f=s.name;(f.startsWith("calc:")||f==="text"||f.startsWith("text:")||f==="show"||f.startsWith("attr:")||f.startsWith("class:")||f.startsWith("css:")||f==="effect"||f==="invalid"||f.startsWith("set:")||f==="detail")&&e.push({el:r,attr:f,src:s.value})}};n(t);for(let r of t.querySelectorAll("*"))n(r);return e}function he(t){function e(l){let w=l._stats||{},k=l.root;return{root:Wt(k),fields:w.fields||0,calcs:w.calcs||0,paints:w.paints||0,actions:pn(k),lists:(k.hasAttribute("items")?1:0)+k.querySelectorAll("[items]").length,rows:k.querySelectorAll("[item]:not([template])").length,warnings:l.diag.warnings.length,errors:l.diag.errors.length,mountWrites:l._mountWrites||0,ms:Math.round((l._mountMs||0)*10)/10,passes:l._passes||0,lastPass:l._lastPass||null,ok:l.diag.errors.length===0&&!l.halted}}function n(){let l=t.apps().map(e);return{ok:l.every(w=>w.ok),apps:l}}function r(l){let w=e(l);return w.ok?`sap \u2713 ${w.root} \xB7 fields ${w.fields} \xB7 calcs ${w.calcs} \xB7 paints ${w.paints} \xB7 actions ${w.actions} \xB7 lists ${w.lists} \xB7 rows ${w.rows} \xB7 warnings ${w.warnings} \xB7 mount writes ${w.mountWrites} \xB7 ${w.ms}ms`:`sap \u2717 ${w.root} \xB7 errors ${w.errors} \xB7 warnings ${w.warnings} \xB7 ${w.ms}ms`}function s(){for(let l of t.apps()){let w=r(l);l.diag.errors.length||l.halted?console.error(w):console.log(w)}}function f(){let l=[],w=[];for(let k of t.apps())l.push(...k.diag.errors),w.push(...k.diag.warnings);return{ok:l.length===0,errors:l,warnings:w,counts:{errors:l.length,warnings:w.length}}}function u(l){return l?typeof l=="string"?document.querySelector(l):l.nodeType===1?l:null:null}function T(l,w){let k=u(l);if(!k)return console.warn("sap why: element not found"),null;let L=[`sap why <${k.tagName.toLowerCase()}> \u2014 ${Et(k)}`],b=[...k.attributes].filter(a=>/^(bind|text|show|effect|invalid|detail|calc:|text:|attr:|class:|css:|set:|state|items|scope)/.test(a.name));for(let a of b)L.push(` ${a.name}="${a.value}"`);let g=k;for(;g&&!g._sapScope;)g=g.parentElement;if(g&&g._sapScope){let a=g._sapScope,A=w?[w]:Object.keys(a);L.push(" reads:");for(let S of A){let x=a[S];typeof x!="function"&&L.push(` ${S} = ${JSON.stringify(Array.isArray(x)?`[${x.length} rows]`:x)}`)}}return L.push(" serialization: textContent and value/checked attributes save with the file; reads live from properties"),console.log(L.join(`
|
|
5
|
+
`)),null}function E(l){let w=l===void 0?!0:!!l;for(let k of t.apps())k._debug=w;return console.log(`sap debug ${w?"on":"off"} \u2014 per-pass headers will ${w?"":"no longer "}log`),w}function y(){let l=[],w=0;for(let a of t.apps()){let A=a.root,S=Wt(A);w++;let x=new nt(a.name);kt(A,x);for(let c of x.errors)l.push({code:c.code,severity:"error",message:`${S}: ${c.code} ${c.slug} at ${c.el}`});for(let c of x.warnings)l.push({code:c.code,severity:"warn",message:`${S}: ${c.code} ${c.slug} at ${c.el}`});w++,rt(a,{trigger:"doctor-dryrun"});let $=a._stats?a._stats.writes:0;$>0&&l.push({code:"W30",severity:"warn",message:`${S}: dry recompute wrote ${$} cells on a quiet page; an element is stale or non-idempotent`}),w++;let M=me(A).map(c=>c.src).join(" \0 "),m=[];for(let c of[A,...A.querySelectorAll("[state]")])for(let o of F(c.getAttribute("state")||""))m.push(o.name);for(let c of m)new RegExp(`(state|item)\\.${c}\\b`).test(M)||l.push({code:"I33",severity:"info",message:`${S}: field "${c}" is declared but read nowhere (dead state)`});w++;let d=new Map;for(let c of A.querySelectorAll("[id]"))d.set(c.id,(d.get(c.id)||0)+1);for(let[c,o]of d)o>1&&l.push({code:"W32",severity:"warn",message:`${S}: duplicate id "${c}" (${o} elements) \u2014 clone/edit collision`});w++;for(let c of me(A))c.src.length>120&&l.push({code:"I33",severity:"info",message:`${S}: ${c.attr} expression is ${c.src.length} chars; consider a calc: field`});w++;for(let c of A.querySelectorAll("[text], [text\\:num]")){let o=c.textContent||"";/\.\d{5,}/.test(o)&&l.push({code:"I33",severity:"info",message:`${S}: ${Et(c)} paints >4 decimals; consider text:FMT`})}}let k=l.filter(a=>a.severity==="error").length,L=l.filter(a=>a.severity==="warn").length,b=l.filter(a=>a.severity==="info").length;console.log(`sap doctor \xB7 ${t.apps().map(a=>Wt(a.root)).join(", ")} \xB7 errors ${k} \xB7 warnings ${L} \xB7 info ${b} \xB7 ${w} checks \xB7 0ms`);for(let a of l)(a.severity==="error"?console.error:a.severity==="warn"?console.warn:console.info)(` ${a.severity==="error"?"\u2717":a.severity==="warn"?"\u26A0":"\xB7"} ${a.message}`);return l}return{status:n,report:f,why:T,debug:E,doctor:y,greenLine:r,printGreenLines:s,REGISTRY:_t}}var mn="0.2.0",B=new Map,V=[],Ft=Pt(rt);function hn(t){let e=t;for(;e;){if(e.nodeType===1&&e.hasAttribute&&e.hasAttribute("sap"))return B.get(e)||null;e=e.parentNode&&e.parentNode.host?e.parentNode.host:e.parentElement}return null}function bn(){for(let t=V.length-1;t>=0;t--){let e=V[t];e.root.isConnected||(B.delete(e.root),V.splice(t,1))}}function gn(t,e){if(t.moveBefore)try{t.moveBefore(e,null);return}catch{}t.appendChild(e)}function wn(t,e){let n=e.parentElement;if(n){if(n.moveBefore)try{n.moveBefore(t,e);return}catch{}n.insertBefore(t,e)}}var K={apps:()=>V.slice(),appFor:hn,isRegistered:t=>B.has(t),pruneDisconnected:bn,schedule:(t,e)=>Ft.schedule(t,e),runNow:(t,e)=>Ft.runNow(t,e),moveInto:gn,placeBefore:wn,remountIfPresent:()=>lt()},ge=le(K),yn=de(K,ge),U=he(K),be=!1;function we(){be||(be=!0,yn.install(document),Kt(K))}function lt(t=document){we(),Ct(K);let e=t.querySelectorAll("[sap]"),n=[];for(let r of e){if(B.has(r))continue;let s=It(r);B.set(r,s),V.push(s),n.push(s)}for(let r of n){let s=U.greenLine(r);r.diag.errors.length||r.halted?console.error(s):console.log(s)}return n}function ye(t){if(we(),Ct(K),!t)return lt();let e=typeof t=="string"?document.querySelector(t):t;if(!e)return null;if(B.has(e))return B.get(e);let n=It(e);B.set(e,n),V.push(n);let r=U.greenLine(n);return n.diag.errors.length||n.halted?console.error(r):console.log(r),n}var _=ge.Sap;_.version=mn;_.refresh=function(){for(let e of V)e.root.isConnected&&Ft.runNow(e,"refresh")};_.batch=Xt;_.status=U.status;_.report=U.report;_.why=U.why;_.debug=U.debug;_.doctor=U.doctor;_.greenLine=U.greenLine;_.formats=ut;_.serializeControlToAttributes=et;_.finalizeControlForSave=Gt;_.rehydrateControlFromAttributes=wt;_.mount=ye;_.config=function(e={}){return e.formats&&Object.assign(ut,e.formats),_};_._registry=B;_._reset=function(){B.clear(),V.length=0,ee()};function An(){typeof document>"u"||(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>lt()):lt())}An();typeof window<"u"&&(window.Sap=_);var vn=_;return $e(En);})();
|
|
6
|
+
if (typeof window !== 'undefined' && Sap && Sap.default) window.Sap = Sap.default;
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@panphora/sapjs",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A reactive HTML library where the DOM is the only state store. Structure-first, one write path, transient rebuild. An Alpine alternative built for malleable, self-saving HTML.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/sap.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/sap.js"
|
|
9
|
+
},
|
|
10
|
+
"jsdelivr": "dist/sap.min.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "node build/bundle.mjs",
|
|
18
|
+
"build:docs": "node scripts/build-doc.mjs",
|
|
19
|
+
"copy-to-hyperclayjs": "npm run build && node scripts/copy-to-hyperclayjs.js",
|
|
20
|
+
"copy-to-clayjs": "npm run build && node scripts/copy-to-clayjs.js",
|
|
21
|
+
"test": "jest",
|
|
22
|
+
"test:watch": "jest --watch",
|
|
23
|
+
"serve": "node build/bundle.mjs && python3 -m http.server 8123"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"reactive",
|
|
27
|
+
"html",
|
|
28
|
+
"dom",
|
|
29
|
+
"alpine",
|
|
30
|
+
"hyperclay",
|
|
31
|
+
"malleable",
|
|
32
|
+
"no-build"
|
|
33
|
+
],
|
|
34
|
+
"author": "panphora",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/panphora/sapjs.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://saphtml.com",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/panphora/sapjs/issues"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@babel/core": "^7.24.0",
|
|
46
|
+
"@babel/preset-env": "^7.24.0",
|
|
47
|
+
"babel-jest": "^29.7.0",
|
|
48
|
+
"esbuild": "^0.21.0",
|
|
49
|
+
"jest": "^29.7.0",
|
|
50
|
+
"jest-environment-jsdom": "^29.7.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/actions.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// Delegated event handling, attached once at document. This is the entire input
|
|
2
|
+
// surface: a control change schedules a pass (the keystroke -> recompute path),
|
|
3
|
+
// a click on an action element runs one action (innermost wins), a form submit
|
|
4
|
+
// runs trigger-add, native reset replays synthetic events, details toggle recomputes,
|
|
5
|
+
// and a detail projection's edit writes through to its source row.
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
nearestScopeEl, nearestItemEl, isInsideDetail, ownedBind, rowsOf,
|
|
9
|
+
resolveListEl, walkOwned,
|
|
10
|
+
} from "./dom.js";
|
|
11
|
+
import { carrierFor } from "./carrier.js";
|
|
12
|
+
import { num } from "./helpers.js";
|
|
13
|
+
import { serializeControlToAttributes } from "./control-serialize.js";
|
|
14
|
+
import { regionSkipsSave, platformConsent } from "./platform.js";
|
|
15
|
+
import { compile, run } from "./compile.js";
|
|
16
|
+
|
|
17
|
+
const TRIGGER_WORDS = ["trigger-add", "trigger-remove", "trigger-reset"];
|
|
18
|
+
|
|
19
|
+
function hasAction(el) {
|
|
20
|
+
if (el.nodeType !== 1) return false;
|
|
21
|
+
for (const a of el.attributes) {
|
|
22
|
+
const n = a.name;
|
|
23
|
+
if (n.startsWith("set:") || n.startsWith("move:") || n.startsWith("sort:")) return true;
|
|
24
|
+
if (TRIGGER_WORDS.includes(n)) return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function findActionEl(target, root) {
|
|
30
|
+
let el = target;
|
|
31
|
+
const stop = root.parentElement;
|
|
32
|
+
while (el && el !== stop) {
|
|
33
|
+
if (el.nodeType === 1) {
|
|
34
|
+
// forms own trigger-add via submit, not click
|
|
35
|
+
if (hasAction(el) && !(el.tagName === "FORM" && el.hasAttribute("trigger-add"))) return el;
|
|
36
|
+
}
|
|
37
|
+
el = el.parentElement;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createActions(runtime, accessor) {
|
|
43
|
+
const { Sap } = accessor;
|
|
44
|
+
|
|
45
|
+
function actionCtx(el, appRec) {
|
|
46
|
+
let item = null;
|
|
47
|
+
const detailEl = isInsideDetail(el);
|
|
48
|
+
if (detailEl && detailEl._sapDetailRow) item = detailEl._sapDetailRow._sapScope || null;
|
|
49
|
+
else {
|
|
50
|
+
const itemEl = nearestItemEl(el);
|
|
51
|
+
if (itemEl && itemEl.hasAttribute("item")) item = itemEl._sapScope || null;
|
|
52
|
+
}
|
|
53
|
+
const scopeEl = nearestScopeEl(el) || appRec.root;
|
|
54
|
+
const state = scopeEl._sapScope || appRec._state || {};
|
|
55
|
+
return { state, item, el, root: appRec._state || {} };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Gate an action behind a `confirm` attribute, then run it. With no confirm
|
|
59
|
+
// attribute the action runs immediately. Standalone, this blocks on the native
|
|
60
|
+
// window.confirm and runs synchronously in the same tick (unchanged behavior).
|
|
61
|
+
// On the platform, it uses the themed window.hyperclay.consent dialog, which fires
|
|
62
|
+
// its callback on confirm; because that dialog does not block the page like
|
|
63
|
+
// window.confirm does, a per-element guard prevents a second click from opening a
|
|
64
|
+
// second dialog (or double-running the action) while one is pending.
|
|
65
|
+
function gateThenRun(el, appRec, proceed) {
|
|
66
|
+
if (!el.hasAttribute("confirm")) { proceed(); return; }
|
|
67
|
+
const raw = el.getAttribute("confirm");
|
|
68
|
+
let msg = raw;
|
|
69
|
+
const entry = compile(raw);
|
|
70
|
+
if (!entry.error) {
|
|
71
|
+
try {
|
|
72
|
+
const v = run(entry, actionCtx(el, appRec));
|
|
73
|
+
if (typeof v === "string" || typeof v === "number") msg = String(v);
|
|
74
|
+
} catch { /* fall back to the literal string */ }
|
|
75
|
+
}
|
|
76
|
+
const consent = platformConsent();
|
|
77
|
+
if (!consent) { if (window.confirm(msg)) proceed(); return; }
|
|
78
|
+
if (el._sapGatePending) return;
|
|
79
|
+
el._sapGatePending = true;
|
|
80
|
+
const done = () => { el._sapGatePending = false; };
|
|
81
|
+
consent(msg, () => { done(); proceed(); }).catch(done);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function runSet(el, appRec) {
|
|
85
|
+
for (const a of el.attributes) {
|
|
86
|
+
if (!a.name.startsWith("set:")) continue;
|
|
87
|
+
const field = a.name.slice(4);
|
|
88
|
+
const entry = compile(a.value);
|
|
89
|
+
if (entry.error) {
|
|
90
|
+
appRec.diag.error("E08", el, { attr: a.name, expr: a.value, problem: String(entry.error.message) });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
let value;
|
|
94
|
+
try {
|
|
95
|
+
value = run(entry, actionCtx(el, appRec));
|
|
96
|
+
} catch (err) {
|
|
97
|
+
appRec.diag.error("E24", el, { attr: a.name, expr: a.value, problem: String(err.message) });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const proxy = Sap(el);
|
|
101
|
+
if (proxy) proxy[field] = value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function moveStep(el, dir) {
|
|
106
|
+
const row = el.closest("[item]");
|
|
107
|
+
if (!row) return;
|
|
108
|
+
const siblings = rowsOf(row.parentElement);
|
|
109
|
+
const i = siblings.indexOf(row);
|
|
110
|
+
if (dir === "up" && i > 0) runtime.placeBefore(row, siblings[i - 1]);
|
|
111
|
+
else if (dir === "down" && i < siblings.length - 1) runtime.placeBefore(siblings[i + 1], row);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resolveSortList(el, value, appRec) {
|
|
115
|
+
if (value) return resolveListEl(appRec.root, el, value);
|
|
116
|
+
let cur = el;
|
|
117
|
+
while (cur) {
|
|
118
|
+
if (cur.hasAttribute && cur.hasAttribute("items")) return cur;
|
|
119
|
+
cur = cur.parentElement;
|
|
120
|
+
}
|
|
121
|
+
const scopeEl = nearestScopeEl(el) || appRec.root;
|
|
122
|
+
const lists = [];
|
|
123
|
+
walkOwned(scopeEl, (e) => { if (e.hasAttribute("items")) lists.push(e); });
|
|
124
|
+
return lists[0] || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function cmp(a, b) {
|
|
128
|
+
const ea = a == null || a === "";
|
|
129
|
+
const eb = b == null || b === "";
|
|
130
|
+
if (ea && eb) return 0;
|
|
131
|
+
if (ea) return 1;
|
|
132
|
+
if (eb) return -1;
|
|
133
|
+
const na = Number(a), nb = Number(b);
|
|
134
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb)) return na - nb;
|
|
135
|
+
return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function runSort(el, field, value, appRec) {
|
|
139
|
+
const listEl = resolveSortList(el, value, appRec);
|
|
140
|
+
if (!listEl) {
|
|
141
|
+
appRec.diag.error("E17", el, { attr: "sort:" + field, problem: `no items list resolvable for sort:${field}` });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const rows = rowsOf(listEl);
|
|
145
|
+
const valOf = (r) => (r._sapScope ? r._sapScope[field] : undefined);
|
|
146
|
+
const asc = [...rows].sort((a, b) => cmp(valOf(a), valOf(b)));
|
|
147
|
+
const isAsc = asc.every((r, i) => r === rows[i]);
|
|
148
|
+
const ordered = isAsc ? [...asc].reverse() : asc;
|
|
149
|
+
for (const r of ordered) runtime.moveInto(listEl, r);
|
|
150
|
+
runtime.schedule(appRec, "sort:" + field);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function focusFirstEditable(rowEl) {
|
|
154
|
+
if (!rowEl || rowEl.hidden) return;
|
|
155
|
+
const f = rowEl.querySelector("[bind]:not([type=hidden]), [contenteditable]");
|
|
156
|
+
if (f && typeof f.focus === "function") f.focus();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function runAction(actionEl, appRec) {
|
|
160
|
+
gateThenRun(actionEl, appRec, () => {
|
|
161
|
+
const proxy = Sap(actionEl);
|
|
162
|
+
for (const a of [...actionEl.attributes]) {
|
|
163
|
+
const n = a.name;
|
|
164
|
+
if (n === "trigger-remove") proxy && proxy.$remove();
|
|
165
|
+
else if (n === "trigger-reset") proxy && proxy.$reset();
|
|
166
|
+
else if (n === "trigger-add") {
|
|
167
|
+
const row = proxy && proxy.$add(a.value);
|
|
168
|
+
if (row) focusFirstEditable(row.$el);
|
|
169
|
+
}
|
|
170
|
+
else if (n === "move:up") { moveStep(actionEl, "up"); runtime.schedule(appRec, "move:up"); }
|
|
171
|
+
else if (n === "move:down") { moveStep(actionEl, "down"); runtime.schedule(appRec, "move:down"); }
|
|
172
|
+
else if (n.startsWith("move:to")) proxy && proxy.$move(a.value);
|
|
173
|
+
else if (n.startsWith("sort:")) runSort(actionEl, n.slice(5), a.value, appRec);
|
|
174
|
+
}
|
|
175
|
+
if ([...actionEl.attributes].some((a) => a.name.startsWith("set:"))) runSet(actionEl, appRec);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function onClick(e) {
|
|
180
|
+
const t = e.target;
|
|
181
|
+
if (!t || t.nodeType !== 1) return;
|
|
182
|
+
const appRec = runtime.appFor(t);
|
|
183
|
+
if (!appRec) return;
|
|
184
|
+
const actionEl = findActionEl(t, appRec.root);
|
|
185
|
+
if (!actionEl) return;
|
|
186
|
+
runAction(actionEl, appRec);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function clearControl(c) {
|
|
190
|
+
const cr = carrierFor(c);
|
|
191
|
+
const def = c.getAttribute("default");
|
|
192
|
+
if (cr.kind === "checkbox") cr.write(def === "" || def === "true");
|
|
193
|
+
else cr.write(def != null ? def : "");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// A <form trigger-add="list"> composes the new row from its own fields: clone
|
|
197
|
+
// the template, copy each of the form's bound controls into the new row's
|
|
198
|
+
// field of the same name, clear the form, and refocus for rapid entry.
|
|
199
|
+
function addFromForm(formEl, appRec, listName) {
|
|
200
|
+
const proxy = Sap(formEl);
|
|
201
|
+
if (!proxy) return;
|
|
202
|
+
const newRow = proxy.$add(listName);
|
|
203
|
+
if (!newRow) return;
|
|
204
|
+
const composers = [];
|
|
205
|
+
walkOwned(formEl, (el) => { if (el.hasAttribute("bind")) composers.push(el); });
|
|
206
|
+
for (const c of composers) {
|
|
207
|
+
const target = ownedBind(newRow.$el, c.getAttribute("bind"));
|
|
208
|
+
if (target) carrierFor(target).write(carrierFor(c).read());
|
|
209
|
+
}
|
|
210
|
+
composers.forEach(clearControl);
|
|
211
|
+
if (composers.length) {
|
|
212
|
+
if (typeof composers[0].focus === "function") composers[0].focus();
|
|
213
|
+
} else {
|
|
214
|
+
focusFirstEditable(newRow.$el);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function onSubmit(e) {
|
|
219
|
+
const form = e.target;
|
|
220
|
+
if (!form || !form.hasAttribute || !form.hasAttribute("trigger-add")) return;
|
|
221
|
+
const appRec = runtime.appFor(form);
|
|
222
|
+
if (!appRec) return;
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
gateThenRun(form, appRec, () => addFromForm(form, appRec, form.getAttribute("trigger-add")));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function onReset(e) {
|
|
228
|
+
const form = e.target;
|
|
229
|
+
const appRec = runtime.appFor(form);
|
|
230
|
+
if (!appRec) return;
|
|
231
|
+
// native reset restores values BEFORE the event; defer so we read post-reset.
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
form.querySelectorAll("[bind]").forEach((c) => {
|
|
234
|
+
c.dispatchEvent(new Event("input", { bubbles: true }));
|
|
235
|
+
c.dispatchEvent(new Event("change", { bubbles: true }));
|
|
236
|
+
});
|
|
237
|
+
runtime.schedule(appRec, "reset");
|
|
238
|
+
}, 0);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function onToggle(e) {
|
|
242
|
+
const appRec = runtime.appFor(e.target);
|
|
243
|
+
if (appRec) runtime.schedule(appRec, "toggle");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function onInput(e) {
|
|
247
|
+
const t = e.target;
|
|
248
|
+
if (!t || t.nodeType !== 1) return;
|
|
249
|
+
const appRec = runtime.appFor(t);
|
|
250
|
+
if (!appRec) return;
|
|
251
|
+
// Opt-in durability: a [persist] control mirrors its live value to a
|
|
252
|
+
// serializable attribute on every edit, so a typed value survives a save
|
|
253
|
+
// even without the host platform. bind is reactivity; persist is durability.
|
|
254
|
+
// Skip in a no-save / frozen region: the platform strips those bytes anyway.
|
|
255
|
+
if (t.hasAttribute("persist") && !regionSkipsSave(t)) serializeControlToAttributes(t);
|
|
256
|
+
const detailEl = isInsideDetail(t);
|
|
257
|
+
if (detailEl && detailEl._sapDetailRow && t.hasAttribute("bind")) {
|
|
258
|
+
const src = ownedBind(detailEl._sapDetailRow, t.getAttribute("bind"));
|
|
259
|
+
if (src && src !== t) {
|
|
260
|
+
carrierFor(src).write(carrierFor(t).read());
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
runtime.schedule(appRec, e.type + " " + (t.id || t.tagName.toLowerCase()));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// A hyperclay `sortable` drag reorder announces itself with a dedicated event.
|
|
268
|
+
// Re-derive both lists it touched (same-list drag: from === to; cross-list: two
|
|
269
|
+
// apps). schedule() dedups per app, so overlapping targets are still one pass.
|
|
270
|
+
function onSorted(e) {
|
|
271
|
+
const d = e.detail || {};
|
|
272
|
+
for (const el of [d.from, d.to, e.target]) {
|
|
273
|
+
if (!el || el.nodeType !== 1) continue;
|
|
274
|
+
const appRec = runtime.appFor(el);
|
|
275
|
+
if (appRec) runtime.schedule(appRec, "sorted");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function onKeydown(e) {
|
|
280
|
+
if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
|
|
281
|
+
const t = e.target;
|
|
282
|
+
if (!t || t.nodeType !== 1 || t.tagName !== "INPUT") return;
|
|
283
|
+
if (!t.hasAttribute("bind") || !t.hasAttribute("step")) return;
|
|
284
|
+
const type = (t.getAttribute("type") || "text").toLowerCase();
|
|
285
|
+
if (type !== "text" && type !== "tel" && type !== "search") return; // native numeric types already step
|
|
286
|
+
const appRec = runtime.appFor(t);
|
|
287
|
+
if (!appRec) return;
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
const step = num(t.getAttribute("step")) || 1;
|
|
290
|
+
const dir = e.key === "ArrowUp" ? 1 : -1;
|
|
291
|
+
let next = num(t.value) + step * (e.shiftKey ? 10 : 1) * dir;
|
|
292
|
+
const min = t.getAttribute("min"), max = t.getAttribute("max");
|
|
293
|
+
if (min !== null && min !== "" && next < num(min)) next = num(min);
|
|
294
|
+
if (max !== null && max !== "" && next > num(max)) next = num(max);
|
|
295
|
+
carrierFor(t).write(String(Number(next.toFixed(2))));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function install(doc = document) {
|
|
299
|
+
doc.addEventListener("click", onClick);
|
|
300
|
+
doc.addEventListener("submit", onSubmit);
|
|
301
|
+
doc.addEventListener("reset", onReset);
|
|
302
|
+
doc.addEventListener("toggle", onToggle, true);
|
|
303
|
+
doc.addEventListener("input", onInput);
|
|
304
|
+
doc.addEventListener("change", onInput);
|
|
305
|
+
doc.addEventListener("clay:sorted", onSorted);
|
|
306
|
+
doc.addEventListener("keydown", onKeydown);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return { install };
|
|
310
|
+
}
|
package/src/carrier.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// The control matrix. One Carrier per control shape: { kind, read(), write(v) }.
|
|
2
|
+
// Writes dispatch synthetic input + change events (the one write path) unless silent.
|
|
3
|
+
|
|
4
|
+
import { serializeControlToAttributes } from "./control-serialize.js";
|
|
5
|
+
import { regionSkipsSave } from "./platform.js";
|
|
6
|
+
|
|
7
|
+
function kindOf(el) {
|
|
8
|
+
const tag = el.tagName;
|
|
9
|
+
if (tag === "INPUT") {
|
|
10
|
+
const t = (el.getAttribute("type") || "text").toLowerCase();
|
|
11
|
+
if (t === "checkbox") return "checkbox";
|
|
12
|
+
if (t === "radio") return "radio";
|
|
13
|
+
if (t === "number" || t === "range") return "number";
|
|
14
|
+
if (t === "hidden") return "hidden";
|
|
15
|
+
return "text";
|
|
16
|
+
}
|
|
17
|
+
if (tag === "TEXTAREA") return "text";
|
|
18
|
+
if (tag === "SELECT") return el.multiple ? "select-multiple" : "select-one";
|
|
19
|
+
return "leaf"; // contenteditable or text leaf bound via bind=
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readNumber(el) {
|
|
23
|
+
if (el.validity && el.validity.badInput) {
|
|
24
|
+
if (el.hasAttribute("value")) return Number(el.getAttribute("value")) || 0;
|
|
25
|
+
return el._sapLastGood ?? 0;
|
|
26
|
+
}
|
|
27
|
+
if (el.value === "") return 0;
|
|
28
|
+
const n = Number(el.value);
|
|
29
|
+
if (Number.isNaN(n)) return el._sapLastGood ?? 0;
|
|
30
|
+
el._sapLastGood = n;
|
|
31
|
+
return n;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readBy(el, kind) {
|
|
35
|
+
switch (kind) {
|
|
36
|
+
case "checkbox":
|
|
37
|
+
return el.checked;
|
|
38
|
+
case "radio":
|
|
39
|
+
return el.checked ? el.value : undefined;
|
|
40
|
+
case "number":
|
|
41
|
+
return readNumber(el);
|
|
42
|
+
case "select-one":
|
|
43
|
+
return el.value;
|
|
44
|
+
case "select-multiple":
|
|
45
|
+
return [...el.selectedOptions].map((o) => o.value);
|
|
46
|
+
case "hidden":
|
|
47
|
+
case "text":
|
|
48
|
+
return el.value;
|
|
49
|
+
case "leaf":
|
|
50
|
+
return el.textContent;
|
|
51
|
+
default:
|
|
52
|
+
return el.value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writeBy(el, kind, v) {
|
|
57
|
+
switch (kind) {
|
|
58
|
+
case "checkbox":
|
|
59
|
+
el.checked = !!v;
|
|
60
|
+
break;
|
|
61
|
+
case "radio":
|
|
62
|
+
el.checked = String(el.value) === String(v);
|
|
63
|
+
break;
|
|
64
|
+
case "number":
|
|
65
|
+
case "text":
|
|
66
|
+
case "hidden":
|
|
67
|
+
el.value = v == null ? "" : String(v);
|
|
68
|
+
break;
|
|
69
|
+
case "select-one":
|
|
70
|
+
el.value = v == null ? "" : String(v);
|
|
71
|
+
break;
|
|
72
|
+
case "select-multiple": {
|
|
73
|
+
const set = new Set((Array.isArray(v) ? v : [v]).map(String));
|
|
74
|
+
for (const opt of el.options) opt.selected = set.has(opt.value);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "leaf":
|
|
78
|
+
el.textContent = v == null ? "" : String(v);
|
|
79
|
+
break;
|
|
80
|
+
default:
|
|
81
|
+
el.value = v == null ? "" : String(v);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Mirror the live value to a serializable attribute so the saved DOM reflects state.
|
|
86
|
+
// Delegates the per-control rules to the shared serializer (the single source of
|
|
87
|
+
// truth shared with hyperclayjs persist). A transient control (search boxes,
|
|
88
|
+
// passwords) is never serialized: its value lives only in the live property, so
|
|
89
|
+
// we strip the attributes instead of writing them.
|
|
90
|
+
function mirror(el) {
|
|
91
|
+
if (el.hasAttribute("transient")) {
|
|
92
|
+
el.removeAttribute("value");
|
|
93
|
+
el.removeAttribute("checked");
|
|
94
|
+
el.removeAttribute("data-value");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// A no-save / frozen region is stripped from the saved file by the platform, so
|
|
98
|
+
// writing the serializable attributes would only produce bytes that get deleted.
|
|
99
|
+
// Skip the mirror there; the live control is untouched and stays reactive.
|
|
100
|
+
if (regionSkipsSave(el)) return;
|
|
101
|
+
serializeControlToAttributes(el);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function fire(el) {
|
|
105
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
106
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function carrierFor(el) {
|
|
110
|
+
const kind = kindOf(el);
|
|
111
|
+
return {
|
|
112
|
+
el,
|
|
113
|
+
kind,
|
|
114
|
+
read() {
|
|
115
|
+
return readBy(el, kind);
|
|
116
|
+
},
|
|
117
|
+
write(v, opts = {}) {
|
|
118
|
+
writeBy(el, kind, v);
|
|
119
|
+
if (!opts.noMirror) mirror(el);
|
|
120
|
+
if (!opts.silent) fire(el);
|
|
121
|
+
},
|
|
122
|
+
mirror() {
|
|
123
|
+
mirror(el);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { kindOf };
|