class e{static assertRawTreeValid(e){if(!Array.isArray(e))throw new TypeError("Tree data must be an array of tree items")}#e=new Map;get tree(){return this.#e}selection=new Set;constructor(e){this.#e=this.#t(e)}#t(e,t){return new Map(e.map((e,i)=>{const r=t?`${t}:${i}`:String(i);e.checked&&this.selection.add(r);const s={id:r,title:e.title,value:e.value,icon:e.icon,collapsed:e.children?.length?!!e.collapsed:null===e.children||void 0,children:e.children?this.#t(e.children,r):e.children};return Object.defineProperty(s,"state",{get:()=>this.selection.has(s.id)?"checked":s.children?.size?this.calcItemState(s):"unchecked"}),[r,s]}))}getItem(e){const[t,...i]=e.split(":");return i.reduce((e,t)=>e?.children?.get(`${e?.id}:${t}`),this.#e.get(t))}getParentItem(e){return this.getItem(e.slice(0,e.lastIndexOf(":")))}calcItemState(e){const t=new Set([...e.children.values()].map(({state:e})=>e));return t.has("indeterminate")?"indeterminate":t.has("checked")?t.has("unchecked")?"indeterminate":"checked":"unchecked"}setSubtree(e,t){e.children=this.#t(t,e.id)}walkTree(e,t=this.#e){t.forEach(t=>{e(t),t.children&&this.walkTree(e,t.children)})}toRaw(e=this.#e){return[...e.values()].map(e=>({title:e.title,value:e.value,icon:e.icon,checked:this.selection.has(e.id),collapsed:!0===e.collapsed||void 0,children:e.children?this.toRaw(e.children):e.children}))}}const t=(e,t=!0)=>`\n<ul part="tree" role="${t?"tree":"group"}">\n ${[...e.values()].reduce((e,t)=>e+i(t),"")}\n</ul>`,i=({id:e,title:i,icon:s,collapsed:a,children:n})=>`\n<li id="item_${e}" part="item" role="treeitem" aria-expanded="${void 0===a?"undefined":!a}">\n ${void 0!==n?'<button type="button" part="toggle" tabindex="-1"></button>':""}\n <label part="label">\n <input type="checkbox" id="cbx_${e}" part="checkbox" tabindex="-1">\n ${r(s)}\n <span part="title">${i}</span>\n </label>\n ${n?.size>0?t(n,!1):""}\n</li>`,r=e=>e?e.startsWith("<svg ")&&e.endsWith("</svg>")?'<svg part="icon"'+e.slice(4):`<img src="${(e=>["&",'"'].reduce((e,t)=>e.replaceAll(t,`&#${t.charCodeAt(0)};`),e))(e)}" alt="" part="icon">`:"",s=e=>e?.slice(e.indexOf("_")+1),a=new CSSStyleSheet;a.replaceSync(':host{--cbx-tree-toggle-closed-mask: url(\'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 14" width="8" height="14"><path d="M1.5 2.5v9L7 7z"/></svg>\');--cbx-tree-toggle-open-mask: url(\'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 8" width="14" height="8"><path d="M2.5 1.5h9L7 7z"/></svg>\');--cbx-tree-toggle-pending-mask: url(\'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" width="14" height="14"><path d="M7 1A6 6 0 1 1 1 7" fill="none" stroke="black" stroke-width="2"><animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 7 7" to="360 7 7" dur="1s" repeatCount="indefinite"/></path></svg>\');--cbx-tree-label-focus-bg: SelectedItem;--cbx-tree-label-focus-fg: SelectedItemText;--cbx-tree-nesting-indent: 1em}:host(:dir(rtl)){--cbx-tree-toggle-closed-mask: url(\'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 14" width="8" height="14"><path d="M6.5 2.5v9L1 7z"/></svg>\')}:host(:not([hidden])){display:block}[part=tree]{list-style:none;margin:0;padding:0;&:has([inert]){cursor:progress}&:not([part=tree] [part=tree]){overflow-x:clip}}[part=item]{align-items:center;display:grid;gap:0 .6ch;grid-template-areas:"toggle label" "tree tree";grid-template-columns:max(1em,16px) 1fr;&[aria-expanded=false]>[part=tree],&[hidden]:not(:has([part=item]:not([hidden])),[part=item]:not([hidden]) [part=item]){display:none}}[part=toggle]{background:none;border:none;color:inherit;font:inherit;grid-area:toggle;height:max(1em,16px);padding:0;position:relative;width:max(1em,16px);z-index:1;&:not(:disabled){cursor:pointer}&:before{background:currentColor;content:"";inset:-4px;mask:var(--cbx-tree-toggle-closed-mask) 50% 50% / contain no-repeat content-box;padding:4px;position:absolute}[aria-expanded=true]>&:before{mask-image:var(--cbx-tree-toggle-open-mask)}[inert]>&:before{mask-image:var(--cbx-tree-toggle-pending-mask)}&:has(+[part=label]:focus):before{color:var(--cbx-tree-label-focus-fg)}}[part=label]{align-items:inherit;display:flex;gap:inherit;grid-area:label;isolation:isolate;outline:none;padding-block:.2em;position:relative;&:focus{color:var(--cbx-tree-label-focus-fg);&:before{background:var(--cbx-tree-label-focus-bg)}}&:before{content:"";inset:0;inset-inline-start:-100vw;position:absolute;z-index:-1}}[part=toggle],[part=title]{forced-color-adjust:preserve-parent-color}:where([part=item]) [part=tree]{grid-area:tree;padding-inline-start:var(--cbx-tree-nesting-indent)}');class n extends HTMLElement{static get formAssociated(){return!0}static get observedAttributes(){return["nohover"]}#e;#t;#i;#r=null;get#s(){return this.#e.querySelector('[tabindex="0"]')}set#s(e){const t=this.#s;e!==t&&(t?.removeAttribute("tabindex"),e?.setAttribute("tabindex","0"))}get#a(){return[...this.#e.querySelectorAll('[part="label"]:not([aria-expanded="false"] [part="tree"] *)')]}subtreeProvider=null;get formData(){const e=new FormData,{name:t}=this;return this.#i.selection.forEach(i=>{const r=this.#i.getItem(i)?.value;void 0!==r&&e.append(t,r)}),e}get form(){return this.#t.form}get name(){return this.getAttribute("name")}set name(e){this.setAttribute("name",e)}get disabled(){return this.hasAttribute("disabled")}set disabled(e){e?this.setAttribute("disabled",""):this.removeAttribute("disabled")}get noHover(){return this.hasAttribute("nohover")}set noHover(e){e?this.setAttribute("nohover",""):this.removeAttribute("nohover")}get type(){return this.localName}constructor(){super(),this.#e=this.attachShadow({mode:"open"}),this.#e.adoptedStyleSheets=[a],this.#t=this.attachInternals(),this.setData(this.#n()),this.hasAttribute("tabindex")||(this.tabIndex=0),this.#e.addEventListener("change",e=>this.#o(e)),this.#e.addEventListener("pointerdown",e=>this.#l(e)),this.addEventListener("focus",()=>this.#h()),this.#e.addEventListener("keydown",e=>this.#c(e)),this.#d()}attributeChangedCallback(e){"nohover"===e&&this.#d()}formDisabledCallback(e){this.#g(e)}formResetCallback(){this.setData(this.#n())}formStateRestoreCallback(e,t){if("restore"===t)try{this.setData(JSON.parse(e))}catch(e){console.warn("Failed to restore the tree state",e)}}#o({target:e}){if(e.part.contains("checkbox")){this.#u(e);const t=e.closest('[part="label"]');return void this.#p(t,!0)}}#l(e){e.isPrimary&&e.target.part.contains("toggle")&&(this.#b(e.target.closest('[part="item"]')),e.preventDefault())}#h(){this.#s?.focus()}#c(e){if(!e.defaultPrevented&&!this.disabled){switch(e.key){case"ArrowRight":{const e=this.#s?.closest('[part="item"]');"true"===e?.ariaExpanded?this.#m():"false"===e?.ariaExpanded&&this.#b(e);break}case"ArrowLeft":{const e=this.#s?.closest('[part="item"]');"true"===e?.ariaExpanded?this.#b(e):this.#v();break}case"ArrowDown":this.#m();break;case"ArrowUp":this.#f();break;case"PageDown":this.#x();break;case"PageUp":this.#w();break;case"Home":this.#k();break;case"End":this.#y();break;case"Enter":{const e=this.#s?.closest('[part="item"]');"undefined"!==e.ariaExpanded&&this.#b(e);break}case" ":{const e=this.#s?.querySelector('[part="checkbox"]');e&&(e.checked=!e.checked,e.indeterminate=!1,this.#u(e));break}default:return}e.preventDefault()}}#S({target:e}){const t=e.part.contains("toggle")?e.closest('[part="item"]').querySelector('[part="label"]'):e.closest('[part="label"]');this.#p(t,!0)}#d(){this.#r?.abort(),this.noHover?this.#r=null:(this.#r=new AbortController,this.#e.addEventListener("pointerover",e=>this.#S(e),{signal:this.#r.signal}))}#E(){this.#e.setHTMLUnsafe(t(this.#i.tree)),[...this.#e.querySelectorAll('[part="checkbox"]')].forEach(e=>{const t=this.#i.getItem(s(e.id))?.state;e.checked="checked"===t,e.indeterminate="indeterminate"===t}),this.#s=this.#e.querySelector('[part="label"]')}async#A(i){if("function"!=typeof this.subtreeProvider)return;const r=this.#i.getItem(i);if(null!==r?.children)return;const s=this.#e.getElementById(`item_${i}`);s.inert=!0;try{const t=await this.subtreeProvider(r.value);e.assertRawTreeValid(t),this.#i.setSubtree(r,t)}finally{s.inert=!1}r.children.size&&(s.insertAdjacentHTML("beforeend",t(r.children,!1)),this.disabled&&this.#g(!0,s),this.#I(r),this.#$())}#M(e,t){if(!t?.size)return;const i=e?"add":"delete";t.forEach((t,r)=>{this.#i.selection[i](r);const s=this.#e.getElementById(`cbx_${r}`);s.checked=e,s.indeterminate=!1,this.#M(e,t.children)})}#g(e,t=this.#e){[...t.querySelectorAll("button, input")].forEach(t=>t.disabled=e)}#I(e){e.children&&this.#M(this.#i.selection.has(e.id),e.children)}#T(e){if(this.#i.tree.has(e.id))return;const t=this.#i.getParentItem(e.id),i=this.#i.calcItemState(t);this.#i.selection["checked"===i?"add":"delete"](t.id);const r=this.#e.getElementById(`cbx_${t.id}`);r.checked="checked"===i,r.indeterminate="indeterminate"===i,this.#T(t)}#$(){this.#t.setFormValue(this.formData,JSON.stringify(this))}#u(e){const t=s(e.id),i=e.checked?"add":"delete";this.#i.selection[i](t);const r=this.#i.getItem(t);this.#I(r),this.#T(r),this.#$(),this.dispatchEvent(new CustomEvent("cbxtreechange",{bubbles:!0,detail:this.formData}))}#b(e){const t="true"!==e.ariaExpanded;e.ariaExpanded=t?"true":"false";const i=s(e.id);t&&this.#A(i);const r=this.#i.getItem(i);r.collapsed=!t,this.#p(e.querySelector('[part="label"]'),!0),this.#$(),this.dispatchEvent(new CustomEvent("cbxtreetoggle",{bubbles:!0,detail:{title:r.title,value:r.value,newState:t?"expanded":"collapsed"}}))}#p(e,t=!1){e&&(this.#s=e,e.focus({preventScroll:t}))}#k(){const e=this.#e.querySelector('[part="label"]');this.#p(e)}#y(){const e=this.#a.at(-1);this.#p(e)}#m(){const e=this.#s;if(!e)return void this.#k();const t=this.#a,i=t[t.indexOf(e)+1];this.#p(i)}#f(){const e=this.#s;if(!e)return void this.#k();const t=this.#a,i=t[t.indexOf(e)-1];this.#p(i)}#x(){const{clientHeight:e,scrollHeight:t}=this;if(t-e<10)return void this.#y();const i=this.#a,r=t/i.length,s=Math.floor(e/r);let a=i.indexOf(this.#s);-1===a&&(a=0);const n=Math.min(a+s-1,i.length-1);this.#p(i[n])}#w(){const{clientHeight:e,scrollHeight:t}=this;if(t-e<10)return void this.#k();const i=this.#a,r=t/i.length,s=Math.round(e/r);let a=i.indexOf(this.#s);-1===a&&(a=0);const n=Math.max(a-s+1,0);this.#p(i[n])}#v(){const e=this.#s;if(!e)return void this.#k();const t=e.closest('[part="tree"]').closest('[part="item"]')?.querySelector('[part="label"]');this.#p(t)}#n(){const t=this.textContent.trim()||"[]";try{const i=JSON.parse(t);return e.assertRawTreeValid(i),i}catch{return console.error(new DOMException("<cbx-tree> contents must be a valid JSON array representation","DataError")),[]}}setData(t){e.assertRawTreeValid(t),this.#i=new e(t),this.#E(),this.#$()}toJSON(){return this.#i.toRaw()}toggleChecked(e){void 0===e&&(e=!!this.#e.querySelector('[part="checkbox"]:not(:checked)')),this.#M(e,this.#i.tree),this.#$()}toggle(e){let t=[...this.#e.querySelectorAll('[part="item"]:has([part="tree"])')];void 0===e&&(e=t.some(({ariaExpanded:e})=>"false"===e));const i=String(e);t.forEach(t=>{if(t.ariaExpanded===i)return;t.ariaExpanded=i;this.#i.getItem(s(t.id)).collapsed=!e}),this.#$()}filter(e){this.#i.walkTree(t=>{const i=e({title:t.title,value:t.value});this.#e.getElementById(`item_${t.id}`).hidden=!i})}get validity(){return this.#t.validity}get validationMessage(){return this.#t.validationMessage}get willValidate(){return this.#t.willValidate}checkValidity(){return this.#t.checkValidity()}reportValidity(){return this.#t.reportValidity()}setValidity(...e){return this.#t.setValidity(...e)}}customElements.define("cbx-tree",n);export{n as default};
0 commit comments