2 lines
9.2 KiB
JavaScript
2 lines
9.2 KiB
JavaScript
import{Component as t,NewState as e,Util as i,RefreshState as n}from"@web/state";import{VirtualScroll as s}from"@web/base";if(t.register("DataTable",t=>{const i=s(),n=t.state;Object.assign(n,{list:[],fields:[],renderedList:[],prevHeight:0,postHeight:0,_listStartIndex:0,selStartR:-1,selStartF:-1,selEndR:-1,selEndF:-1,multiSelections:[],isSelecting:!1});const d=()=>{const e=t.querySelector(".dt-body");if(!e)return;const s=i.calc(e,n.list);s&&Object.assign(n,{prevHeight:s.prevHeight,postHeight:s.postHeight,_listStartIndex:s.listStartIndex,renderedList:s.renderedList})};t.refresh=d,n.__watch("list",s=>{if(s&&s.length>0&&!s[0].__watch)return void(n.list=s.map(t=>e(t)));n._listStartIndex=0;const r=t.querySelector(".dt-body");n.renderedList=i.reset(s,r||{clientHeight:800})||[],r&&(i.init(s,d),requestAnimationFrame(()=>d()))}),t.onItemUpdate=(t,e)=>i.update(t+n._listStartIndex,e),t.getOffset=(t,e,i)=>{const n=t.slice(0,e).filter(t=>"left"===t.pinned),s=t.slice(e+1).filter(t=>"right"===t.pinned);return("left"===i?n:s).reduce((t,e)=>t+(e.width||150),0)},t.isCellSelected=(t,e)=>{const i=Math.min(n.selStartR,n.selEndR),s=Math.max(n.selStartR,n.selEndR),d=Math.min(n.selStartF,n.selEndF),r=Math.max(n.selStartF,n.selEndF);return t>=i&&t<=s&&e>=d&&e<=r||n.multiSelections.some(i=>t>=i.r1&&t<=i.r2&&e>=i.f1&&e<=i.f2)},t.clearAllActive=(t=!1)=>{n.list.forEach(t=>{null!==t._editingF&&(t._editingF=null),null!==t._activeF&&(t._activeF=null)}),t||(n.selStartR=-1,n.multiSelections=[])},t.startSelect=(e,i,s)=>{const d=t.isCellSelected(e,i);n.editingCell&&(n.editingCell=null),s.shiftKey&&-1!==n.selStartR?(n.selEndR=e,n.selEndF=i):(d||(s.ctrlKey||s.metaKey?-1!==n.selStartR&&n.multiSelections.push({r1:Math.min(n.selStartR,n.selEndR),r2:Math.max(n.selStartR,n.selEndR),f1:Math.min(n.selStartF,n.selEndF),f2:Math.max(n.selStartF,n.selEndF)}):t.clearAllActive(),n.selStartR=n.selEndR=e,n.selStartF=n.selEndF=i),n.isSelecting=!0,n.list[e]._activeF=i)},t.updateSelect=(t,e)=>n.isSelecting&&(n.selEndR=t,n.selEndF=e),t.endSelect=()=>n.isSelecting=!1,t.editCell=(e,i,s)=>{var d;const r=Math.min(n.selStartR,n.selEndR),l=Math.max(n.selStartR,n.selEndR),a=Math.min(n.selStartF,n.selEndF),o=Math.max(n.selStartF,n.selEndF),c=n.list.indexOf(e),h=-1!==n.selStartR&&c>=r&&c<=l&&s>=a&&s<=o?l-r+1:0;if(h>1&&(null==(d=globalThis.UI)?void 0:d.toast)&&UI.toast(`{#Bulk Editing {num} rows... || ${h}#}`),t.clearAllActive(!0),e._editingF=i.id,e._activeF=s,h>1){const t=e.__watch(i.id,s=>{for(let t=r;t<=l;t++)n.list[t]!==e&&(n.list[t][i.id]=s);t()})}};t.copy=async()=>{const t=Math.min(n.selStartR,n.selEndR),e=Math.max(n.selStartR,n.selEndR),i=Math.min(n.selStartF,n.selEndF),s=Math.max(n.selStartF,n.selEndF);if(-1===t)return;const d=n.list.slice(t,e+1).map(t=>n.fields.slice(i,s+1).map(e=>(t=>{const e=String(t??"");return e.includes("\t")||e.includes("\n")||e.includes('"')?'"'+e.replace(/"/g,'""')+'"':e})(t[e.id])).join("\t")).join("\n");await navigator.clipboard.writeText(d)},t.paste=async()=>{const t=(t=>{const e=[];let i=[],n="",s=!1;for(let d=0;d<t.length;d++){const r=t[d],l=t[d+1];s?'"'===r&&'"'===l?(n+='"',d++):'"'===r?s=!1:n+=r:'"'===r?s=!0:"\t"===r?(i.push(n),n=""):"\n"===r?(i.push(n),e.push(i),i=[],n=""):"\r"!==r&&(n+=r)}return i.push(n),e.push(i),e})(await navigator.clipboard.readText()),e=Math.min(n.selStartR,n.selEndR),i=Math.min(n.selStartF,n.selEndF);-1!==e&&t.forEach((t,s)=>{const d=n.list[e+s];d&&t.forEach((t,e)=>{const s=n.fields[i+e];s&&("boolean"==typeof d[s.id]?d[s.id]="true"===t.toLowerCase():"number"==typeof d[s.id]?d[s.id]=Number(t):d[s.id]=t)})})},t.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&("c"===e.key&&(e.preventDefault(),t.copy()),"v"===e.key&&(e.preventDefault(),t.paste()))});const r=e=>!t.contains(e.target)&&t.clearAllActive();document.addEventListener("mousedown",r),window.addEventListener("mouseup",t.endSelect),t._onUnload=()=>{document.removeEventListener("mousedown",r),window.removeEventListener("mouseup",t.endSelect)}},i.makeDom("\n<div class=\"dt-root d-flex flex-column h-100 border bg-body text-body overflow-hidden\" style=\"position:relative; user-select:none\" tabindex=\"0\" onunload=\"this._onUnload()\">\n\t<div class=\"dt-header d-flex flex-shrink-0 border-bottom bg-body-tertiary fw-bold text-muted small\" style=\"overflow:hidden; position:sticky; top:0; z-index:20; height:48px\">\n\t\t<div $each=\"this.state.fields\" as=\"f\" class=\"dt-col border-end d-flex align-items-center px-2 bg-body-tertiary\"\n\t\t\t$style=\"'width: var(--w-' + f.id + ', ' + (f.width || 150) + 'px); min-width: var(--w-' + f.id + ', ' + (f.width || 150) + 'px); ' + (f.pinned ? 'position:sticky; z-index:11;' : '') + (f.pinned === 'left' ? 'left:' + this.getOffset(this.state.fields, index, 'left') + 'px;' : '') + (f.pinned === 'right' ? 'right:' + this.getOffset(this.state.fields, index, 'right') + 'px;' : '')\">\n\t\t\t<span $text=\"f.name\" $class=\"'text-truncate flex-grow-1 ' + (f.type === 'number' ? 'text-end' : '')\"></span>\n\t\t\t<Resizer class=\"dt-resizer\" $.target=\"thisNode.parentNode\" min=\"50\" max=\"600\" $bind=\"f.width\" $onresizing=\"thisNode.closest('.dt-root').style.setProperty('--w-' + f.id, event.detail.newSize + 'px')\"/>\n\t\t</div>\n\t</div>\n\t<div class=\"dt-body flex-grow-1 overflow-auto bg-body\" $onscroll=\"requestAnimationFrame(() => this.refresh())\" style=\"overflow-anchor:none\">\n\t\t<div $if=\"this.state.prevHeight>0\" $style=\"'height:' + this.state.prevHeight + 'px;'\"></div>\n\t\t<div $each=\"this.state.renderedList\" as=\"row\" class=\"dt-row d-flex border-bottom\" $onupdate=\"this.onItemUpdate(rIdx, thisNode)\" index=\"rIdx\">\n\t\t\t<div $each=\"this.state.fields\" as=\"f\" class=\"dt-cell border-end d-flex align-items-center bg-body\"\n\t\t\t\t$class=\"'dt-cell border-end d-flex align-items-center bg-body' + (this.isCellSelected(rIdx + this.state._listStartIndex, index) ? ' bg-primary-subtle' : '') + (row._activeF === index ? ' dt-cell-active' : '')\"\n\t\t\t\t$style=\"'width: var(--w-' + f.id + ', ' + (f.width || 150) + 'px); min-width: var(--w-' + f.id + ', ' + (f.width || 150) + 'px); height:40px; ' + (f.pinned ? 'position:sticky; z-index:10;' : '') + (f.pinned === 'left' ? 'left:' + this.getOffset(this.state.fields, index, 'left') + 'px;' : '') + (f.pinned === 'right' ? 'right:' + this.getOffset(this.state.fields, index, 'right') + 'px;' : '')\"\n\t\t\t\t$onmousedown=\"this.startSelect(rIdx + this.state._listStartIndex, index, event)\"\n\t\t\t\t$onmouseenter=\"this.updateSelect(rIdx + this.state._listStartIndex, index)\"\n\t\t\t\t$ondblclick=\"this.editCell(row, f, index)\">\n\t\t\t\t\n\t\t\t\t<div $if=\"row._editingF !== f.id\" $class=\"'px-2 text-truncate w-100 h-100 d-flex align-items-center ' + (f.type === 'number' ? 'justify-content-end' : (f.type === 'switch' ? 'justify-content-center' : ''))\">\n\t\t\t\t\t<span $if=\"typeof row[f.id] !== 'boolean'\" $text=\"row[f.id] ?? ''\" class=\"text-truncate\"></span>\n\t\t\t\t\t<div $if=\"typeof row[f.id] === 'boolean'\" class=\"form-switch fs-5 m-0 d-flex align-items-center justify-content-center\" style=\"padding-left:0\">\n\t\t\t\t\t\t<input class=\"form-check-input m-0\" type=\"checkbox\" $checked=\"row[f.id]\" disabled>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t<div $if=\"row._editingF === f.id\" $class=\"'dt-editor-container' + (f.type === 'textarea' ? ' dt-editor-textarea' : '')\" $onclick=\"event.stopPropagation()\" $onmousedown=\"event.stopPropagation()\">\n\t\t\t\t\t<AutoForm inline class=\"dt-editor h-100 w-100\" $.state.schema=\"[{ ...f, name: f.id, label: f.name }]\" $.data=\"row\" \n\t\t\t\t\t\t$onkeydown=\"(event.key === 'Enter' && f.type !== 'textarea') && (row._editingF = null); event.key === 'Escape' && (row._editingF = null)\"/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div $if=\"this.state.postHeight>0\" $style=\"'height:' + this.state.postHeight + 'px;'\"></div>\n\t</div>\n\t<style>\n\t\t.dt-root { font-size: 0.875rem; --dt-active-border: var(--bs-primary); outline: none; }\n\t\t.dt-row:hover .dt-cell { background-color: var(--bs-secondary-bg) !important; }\n\t\t.dt-cell { position: relative; transition: background 0.05s; border-color: var(--bs-border-color) !important; }\n\t\t.dt-cell.bg-primary-subtle { background-color: var(--bs-primary-bg-subtle) !important; }\n\t\t.dt-cell-active::after { content: ''; position: absolute; inset: 0; border: 2px solid var(--dt-active-border); pointer-events: none; z-index: 12; }\n\t\t.dt-col { position: relative; }\n\t\t.dt-editor-container { position: absolute; left: -1px; top: -1px; width: fit-content; min-width: calc(100% + 2px); height: auto; min-height: calc(100% + 2px); background: var(--bs-body-bg); z-index: 100; box-shadow: 0 4px 12px rgba(0,0,0,.3); border: 1px solid var(--dt-active-border); display: flex; align-items: center; }\n\t\t.dt-editor-textarea { height: auto; min-height: 100%; min-width: 260px; align-items: flex-start; }\n\t\t.dt-resizer { width: 4px !important; opacity: 0; transition: opacity 0.2s; position: absolute; right: 0; top: 0; height: 100%; background: var(--bs-primary); cursor: col-resize; z-index: 15; }\n\t\t.dt-col:hover .dt-resizer { opacity: 0.5; }\n\t\t.dt-resizer:hover { opacity: 1 !important; }\n\t</style>\n</div>\n")),"undefined"!=typeof document){const t=()=>n(document.documentElement);"loading"!==document.readyState?t():document.addEventListener("DOMContentLoaded",t,!0)}
|