ui/components/base/List.min.js

2 lines
7.4 KiB
JavaScript

globalThis.Component.register("List",t=>{t.mode=t.getAttribute("mode")||"normal",t.fast=t.hasAttribute("fast"),t.collapsible=t.hasAttribute("collapsible");const e=t.fast?t.querySelector(".vs-pad-top"):null,i=t.fast?t.querySelector(".vs-pad-bottom"):null,s={idfield:"id",labelfield:"label",summaryfield:"summary",groupidfield:"id",grouplabelfield:"label",groupsummaryfield:"summary",groupfield:"group",parentfield:"parent",groupicon:"folder",itemicon:"file"};Object.keys(s).forEach(e=>{t.hasAttribute(e)&&(t[e]=t.getAttribute(e))}),t.collapsed=globalThis.NewState({}),t.state.renderedList=[],t.addEventListener("bind",e=>{t.state.selectedItem=e.detail});const l=(t,e)=>null==e||""===e||String(t||"").toLowerCase().includes(String(e).toLowerCase()),a=(e,i,s)=>{if(s&&!s(e))return!1;if(0===i&&(i=""),null!=i&&""!==i)if("object"==typeof i){for(const[t,s]of Object.entries(i))if(null!=s&&""!==s&&!l(e[t],s))return!1}else{const s=String(i).trim();if(s){const i=l(e[t.labelfield],s),a=l(e[t.summaryfield],s);if(!i&&!a)return!1}}return!0},r=()=>{globalThis.Util.updateDefaults(t,s);let e=t.state.list||[],i=t.state.filter,r=t.state.order;const o=t._filterFunc,n=t._orderFunc;if(0===i&&(i=""),0===r&&(r=""),n)e=[...e].sort(n);else if(r&&"string"==typeof r){const t=r.trim().split(/\s+/),i=t[0],s="desc"===t[1]?.toLowerCase()||i.startsWith("-"),l=i.startsWith("-")?i.slice(1):i;e=[...e].sort((t,e)=>{const i=t[l],a=e[l];return null==i?s?1:-1:null==a?s?-1:1:"string"==typeof i&&"string"==typeof a?s?a.localeCompare(i):i.localeCompare(a):s?a>i?1:-1:i>a?1:-1})}const d=[];if("group"===t.mode){const s=e.filter(t=>a(t,i,o)),r={};s.forEach(e=>(r[e[t.groupfield]]??=[]).push(e)),(t.state.groups||[]).forEach(e=>{const s=r[e[t.groupidfield]];let a=!1;if(null!=i&&""!==i)if("object"==typeof i){a=!0;for(const[t,s]of Object.entries(i))if(null!=s&&""!==s&&!l(e[t],s)){a=!1;break}}else{const s=String(i).trim();s&&(a=l(e[t.grouplabelfield],s)||l(e[t.groupsummaryfield],s))}(s&&s.length>0||a||!i&&!o)&&(d.push({type:"group",...e}),s&&s.forEach(t=>d.push({type:"item",...t})))})}else if("tree"===t.mode){const s={};e.forEach(e=>{s[e[t.idfield]]=e});const l=new Set;e.forEach(e=>{a(e,i,o)&&l.add(e[t.idfield])});const r=new Set(l);(i||o)&&l.forEach(e=>{let i=s[e];for(;i;){const e=i[t.parentfield];if(!e)break;if(r.has(e))break;r.add(e),i=s[e]}});const n={};e.forEach(e=>{if(!i&&!o||r.has(e[t.idfield])){const i=e[t.parentfield]||"";(n[i]??=[]).push(e)}});const p=(e,i,s)=>e.forEach(e=>{const l=e[t.idfield],a=!!n[l]?.length,r=t.collapsed[l];d.push({type:"item",...e,_level:i,_hasChildren:a,_parents:s}),a&&!r&&p(n[l],i+1,[...s,l])});p(n[""]||[],0,[])}else{e.filter(t=>a(t,i,o)).forEach(t=>d.push({type:"item",...t}))}t.state.flatList=d};t.state.__watch("list",r),t.state.__watch("filter",r),t.state.__watch("order",r);const o=t.fast?globalThis.VirtualScroll():null;let n=!1;t.refresh=()=>{if(t.fast&&!n){n=!0;try{const s=o.calc(t,t.state.flatList);s&&(e&&(e.style.height=`${s.prevHeight}px`),i&&(i.style.height=`${s.postHeight}px`),t.state.listStartIndex=s.listStartIndex,t.state.renderedList=s.renderedList)}finally{setTimeout(()=>{n=!1},0)}}},t.onItemUpdate=(e,i)=>{t.fast&&o.update(e+(t.state.listStartIndex||0),i)},t.state.__watch("flatList",s=>{t.fast?(e&&(e.style.height="0px"),i&&(i.style.height="0px"),t.state.listStartIndex=0,t.state.renderedList=o.reset(s,t)||[],setTimeout(()=>{t.state.flatList===s&&o.init(s,t.refresh)})):t.state.renderedList=s}),t.selectItem=(e,i)=>{t.hasAttribute("auto-select")&&(t.state.selectedItem=t.state.selectedItem===e[t.idfield]?null:e[t.idfield]),t.dispatchEvent(new CustomEvent("change",{detail:t.state.selectedItem,bubbles:!1})),t.dispatchEvent(new CustomEvent("itemclick",{bubbles:!1,detail:{item:e,index:i+(t.fast&&t.state.listStartIndex||0)}}))},t.selectGroup=(e,i)=>{t.hasAttribute("auto-select-group")&&(t.state.selectedGroup=t.state.selectedGroup===e[t.groupidfield]?null:e[t.groupidfield]),t.dispatchEvent(new CustomEvent("groupclick",{bubbles:!1,detail:{item:e,index:i}}))},t.toggleCollapse=e=>{t.collapsible&&e._hasChildren&&(t.collapsed[e[t.idfield]]=!t.collapsed[e[t.idfield]],r())},Object.defineProperty(t,"filterFunc",{get:()=>t._filterFunc,set:e=>{t._filterFunc=e,r()}}),Object.defineProperty(t,"orderFunc",{get:()=>t._orderFunc,set:e=>{t._orderFunc=e,r()}}),r()},globalThis.Util.makeDom('\n<div class="list-group overflow-auto" onscroll="this.refresh()" style="overflow-anchor:none">\n <div class="vs-pad-top flex-shrink-0" style="height:0px;"></div>\n <template slot-id="item" $each="this.state.renderedList">\n <div $onupdate="this.onItemUpdate(index, thisNode)" $class="list-group-item d-inline-flex align-items-center pe-2 ${item.type===\'group\'?\'bg-body-tertiary fw-bold ps-2\':\'list-group-item-action \' + (this.mode===\'group\'?\'ps-4\':\'ps-2\')} ${item.type===\'group\'?(this.state?.selectedGroup===item[this.groupidfield]?\'active\':\'\'):(this.state?.selectedItem===item[this.idfield]?\'active\':\'\')}" $onclick="item.type===\'group\'?this.selectGroup(item,index):this.selectItem(item,index)">\n <template $if="item.type === \'group\'">\n <template $if="this.groupicon">\n <span $class="bi bi-${this.groupicon} text-body"></span>\n </template>\n <div class="flex-shrink-0 px-1" $text="${item[this.grouplabelfield]}"></div>\n <div class="text-muted small flex-fill text-end" $text="${item[this.groupsummaryfield] || \'\'}"></div>\n <div slot-id="group-actions"></div>\n </template>\n <template $if="item.type === \'item\'">\n <template $if="this.mode === \'tree\'">\n <div $style="width:${item._level * 16 + (this.collapsible ? 16 : 0)}px; cursor:${this.collapsible ? \'pointer\' : \'default\'}" class="text-end text-muted flex-shrink-0" $onclick="event.stopPropagation(); this.toggleCollapse(item)">\n <template $if="this.collapsible && item._hasChildren">\n <i $class="bi ${this.collapsed[item[this.idfield]] ? \'bi-caret-right-fill\' : \'bi-caret-down-fill\'}"></i>\n </template>\n </div>\n </template>\n <template $if="this.mode === \'tree\'">\n <span $class="text-muted bi bi-${item._hasChildren ? this.groupicon : this.itemicon}"></span>\n </template>\n <template $if="this.mode !== \'tree\' && this.itemicon">\n <span $class="bi bi-${this.itemicon} text-body"></span>\n </template>\n <div class="flex-shrink-0 px-1" $text="${item[this.labelfield] || \'\'}"></div>\n <div class="text-muted fs-7 small ms-2 flex-fill text-truncate text-end" $text="${item[this.summaryfield] || \'\'}"></div>\n <div slot-id="item-actions"></div>\n </template>\n </div>\n </template>\n <div class="vs-pad-bottom flex-shrink-0" style="height:0px;"></div>\n</div>\n'));