@evotecit/canopyx 0.3.10 → 0.3.13

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.
@@ -13,7 +13,7 @@
13
13
  .cx-kind-number .cx-editor{text-align:right;font-variant-numeric:tabular-nums;}
14
14
  .cx-editor.cx-editor-toggle{flex:none;width:var(--cx-size-18px);height:var(--cx-size-18px);margin:0 auto 0 var(--cx-size-6px);accent-color:var(--cx-accent);}
15
15
  .cx-edit-tip{position:fixed;inset:auto;margin:0;box-sizing:border-box;background:var(--cx-surface);color:var(--cx-text);border:var(--cx-size-1px) solid var(--cx-line-strong);border-radius:var(--cx-radius);box-shadow:var(--cx-panel-shadow);font:var(--cx-size-13d5px)/1.4 var(--cx-font);}
16
- .cx-edit-tip{max-width:var(--cx-size-280px);padding:var(--cx-size-6px) var(--cx-size-10px);border-left:var(--cx-size-3px) solid var(--cx-tone-danger);white-space:normal;}
16
+ .cx-edit-tip{max-width:var(--cx-size-280px);padding:var(--cx-size-6px) var(--cx-size-10px);border-left:var(--cx-size-3px) solid var(--cx-tone-danger);white-space:normal;pointer-events:none;}
17
17
  .cx-editbar{display:flex;align-items:center;gap:var(--cx-size-4px);height:var(--cx-control-h);padding:0 var(--cx-size-4px) 0 var(--cx-size-10px);border-radius:var(--cx-radius);background:var(--cx-head-bg);font-size:var(--cx-size-13d5px);}
18
18
  .cx-editbar[hidden]{display:none;}
19
19
  .cx-editbar-text{margin-right:var(--cx-size-4px);white-space:nowrap;}
@@ -1,4 +1,4 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"c82648dfaa7e5ee5",requires:["filter-editor","forms"],install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,FORMS_IMPL,h,setText,fill,gridButton,gridDecorateButton,GRID_ICONS,cellValue,cellText,isCellObject,gridFreeze,gridMomentFromParts,numberFormat,gridQuery,foldText,requireIdentifier,checkDocument,validateGridRows,inlineGridSource,PROTOCOL,boundedPattern,validateGridFacetPage,RECORD_IMPL}=context;const{EDIT_EDITORS,editOptions,formRuleSpec,formCheck,FORM_LABELS}=FORMS_IMPL;const EDIT_LABELS={...FORM_LABELS,gridEditCell:"Edit {column}",gridEditSave:"Save",gridEditCancel:"Cancel",gridEditDiscard:"Discard",gridEditUndo:"Undo",gridEditRedo:"Redo",gridEditEdited:"Edited",gridEditChanges:"Unsaved changes: {count}",gridEditErrors:"Errors: {count}",gridEditSaving:"Saving…",gridEditSaved:"Saved rows: {count}",gridEditFailed:"Rows not saved: {count}",gridEditConflict:"Someone else changed this row. Reload it and try again.",gridEditRejected:"Not saved: {error}",gridEditNotSaved:"Not saved.",gridEditUndone:"Undone: {column}",gridEditRedone:"Redone: {column}"};const editCopy=value=>gridFreeze(JSON.parse(JSON.stringify(value===undefined?null:value)));const editSame=(left,right)=>left===right||left==null&&right==null;const editScalar=value=>value===null||typeof value==="string"||typeof value==="boolean"||Number.isFinite(value);const editBad=(id,problem)=>{throw new Error("CanopyX: column "+id+" "+problem+".")};function editSpec(column,editors,columns){const id=column.id,raw=column.editor===undefined?{}:typeof column.editor==="string"?{type:column.editor}:column.editor;const rules=column.validation===undefined?{}:column.validation,object=value=>value&&typeof value==="object"&&!Array.isArray(value);if(!object(raw)||!object(rules))editBad(id,"has an invalid editor or validation");const type=raw.type??column.kind;if(typeof type!=="string"||!Object.hasOwn(editors,type))editBad(id,"names an editor nobody registered");const fits={number:["number"],text:["text"],toggle:["text"],date:["datetime","text"],datetime:["datetime","text"],time:["datetime","text"]}[type];if(fits&&!fits.includes(column.kind))editBad(id,"cannot use a "+type+" editor for its kind");const{pattern,rules:checks}=formRuleSpec(raw,rules,problem=>editBad(id,problem));const lookup=raw.lookup??(type==="lookup"?{}:null);if(lookup&&(!object(lookup)||lookup.column!==undefined&&!columns.some(item=>item.id===lookup.column)||
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"05fcb2c8be754b50",requires:["filter-editor","forms"],install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,FORMS_IMPL,h,setText,fill,gridButton,gridDecorateButton,GRID_ICONS,cellValue,cellText,isCellObject,gridFreeze,gridMomentFromParts,numberFormat,gridQuery,foldText,requireIdentifier,checkDocument,validateGridRows,inlineGridSource,PROTOCOL,boundedPattern,validateGridFacetPage,RECORD_IMPL}=context;const{EDIT_EDITORS,editOptions,formRuleSpec,formCheck,FORM_LABELS}=FORMS_IMPL;const EDIT_LABELS={...FORM_LABELS,gridEditCell:"Edit {column}",gridEditSave:"Save",gridEditCancel:"Cancel",gridEditDiscard:"Discard",gridEditUndo:"Undo",gridEditRedo:"Redo",gridEditEdited:"Edited",gridEditChanges:"Unsaved changes: {count}",gridEditErrors:"Errors: {count}",gridEditSaving:"Saving…",gridEditSaved:"Saved rows: {count}",gridEditFailed:"Rows not saved: {count}",gridEditConflict:"Someone else changed this row. Reload it and try again.",gridEditRejected:"Not saved: {error}",gridEditNotSaved:"Not saved.",gridEditUndone:"Undone: {column}",gridEditRedone:"Redone: {column}"};const editCopy=value=>gridFreeze(JSON.parse(JSON.stringify(value===undefined?null:value)));const editSame=(left,right)=>left===right||left==null&&right==null;const editScalar=value=>value===null||typeof value==="string"||typeof value==="boolean"||Number.isFinite(value);const editBad=(id,problem)=>{throw new Error("CanopyX: column "+id+" "+problem+".")};function editSpec(column,editors,columns){const id=column.id,raw=column.editor===undefined?{}:typeof column.editor==="string"?{type:column.editor}:column.editor;const rules=column.validation===undefined?{}:column.validation,object=value=>value&&typeof value==="object"&&!Array.isArray(value);if(!object(raw)||!object(rules))editBad(id,"has an invalid editor or validation");const type=raw.type??column.kind;if(typeof type!=="string"||!Object.hasOwn(editors,type))editBad(id,"names an editor nobody registered");const fits={number:["number"],text:["text"],toggle:["text"],date:["datetime","text"],datetime:["datetime","text"],time:["datetime","text"]}[type];if(fits&&!fits.includes(column.kind))editBad(id,"cannot use a "+type+" editor for its kind");const{pattern,rules:checks}=formRuleSpec(raw,rules,problem=>editBad(id,problem));const lookup=raw.lookup??(type==="lookup"?{}:null);if(lookup&&(!object(lookup)||lookup.column!==undefined&&!columns.some(item=>item.id===lookup.column)||
2
2
  lookup.limit!==undefined&&!(Number.isInteger(lookup.limit)&&lookup.limit>=1&&lookup.limit<=50)||
3
3
  lookup.minLength!==undefined&&!(Number.isInteger(lookup.minLength)&&lookup.minLength>=0&&lookup.minLength<=16)))editBad(id,"has invalid lookup settings");return Object.freeze({id,column,type,editor:gridFreeze({...raw,type}),options:editOptions(raw.options,id),lookup:lookup&&Object.freeze({column:lookup.column??id,limit:lookup.limit??20,minLength:lookup.minLength??1}),pattern,rules:checks})}
4
4
  function editCell(original,value,text){if(!isCellObject(original)&&text===undefined)return value;const cell=isCellObject(original)?{...original}:{};delete cell.text;delete cell.searchText;delete cell.sort;delete cell.secondary;cell.value=value;if(text!==undefined)cell.text=text;return cell}
@@ -73,13 +73,14 @@ this.editRowsChanged()}
73
73
  for(const list of[state.undo,state.redo])for(let at=list.length-1;at>=0;at--)if(list[at].some(step=>step.insert&&step.id===from))list.splice(at,1);if(to!==from)for(const steps of[...state.undo,...state.redo])for(const step of steps)if(step.id===from){step.id=to;if(step.insert)step.insert.id=to}
74
74
  if(to!==from)this.emit("rowIdChanged",{from,to});if(later.length){const outer=state.group,target=this.editRecord(to)||record;state.group=[];try{for(const column of later)this.editApply(to,state.specs.get(column),entry.values[column],"accept",target)}
75
75
  finally{state.group=outer}}
76
- if(to!==from&&this.editOwnsRows())Promise.resolve(this.reload?.()).catch(()=>{})}});GRID_ICONS.undo='<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>';GRID_ICONS.redo='<path d="m15 14 5-5-5-5"/><path d="M20 9H9.5a5.5 5.5 0 0 0 0 11H13"/>';const editAttr=(element,name,value)=>{if(value==null)element.removeAttribute(name);else if(element.getAttribute(name)!==value)element.setAttribute(name,value)};Object.assign(DataGrid.prototype,{buildEditing(){const state=this.edit,signal=this.lifetime.signal,grid=this.grid,labels=state.labels;state.tip=h("div","cx-edit-tip");state.tip.id=this.id+"-edit-tip";state.tip.popover="manual";state.notes=h("div","cx-sr-only");state.notes.id=this.id+"-edit-notes";this.host.append(state.tip,state.notes);grid.addEventListener("keydown",event=>this.editGridKey(event),{capture:true,signal});grid.addEventListener("pointerdown",event=>{const cell=event.target.closest?.(".cx-cell");state.armed=cell&&cell===document.activeElement?cell:null},{capture:true,signal});grid.addEventListener("click",event=>this.editClick(event),{signal});grid.addEventListener("dblclick",event=>{const at=!this.phone&&this.editCellAt(event.target);if(at){event.stopPropagation();this.setFocus(at.row,false);this.editStart(at.row,at.column)}},{capture:true,signal});this.canvas.addEventListener("focusin",event=>{const row=event.target.closest(".cx-row")?._w;if(row&&!row.status)this.editEnterRow(row.key);this.editRefreshMessage()},{signal});this.canvas.addEventListener("focusout",event=>{if(!event.relatedTarget||!this.canvas.contains(event.relatedTarget))this.editHideMessage()},{signal});grid.addEventListener("scroll",()=>{if(state.tip.matches(":popover-open"))this.editRefreshMessage()},{passive:true,signal});signal.addEventListener("abort",()=>{if(state.session)for(const field of state.session.fields.values())this.editDispose(field);state.tip.remove();state.notes.remove();state.barEl?.remove()},{once:true});if(!state.bar)return;const bar=state.barEl=h("div","cx-editbar"),status=h("span","cx-editbar-text");state.barText=status.appendChild(h("span"));state.barErrors=status.appendChild(h("span","cx-editbar-errors"));status.setAttribute("role","status");const act=run=>()=>Promise.resolve().then(run).catch(error=>this.emit("error",{error}));const icon=(name,text,run)=>{const button=gridDecorateButton(gridButton("",act(run)),name,true);button.classList.add("icon");button.setAttribute("aria-label",text);button.title=text;return button};state.undoButton=icon("undo",labels.gridEditUndo,()=>this.undo());state.redoButton=icon("redo",labels.gridEditRedo,()=>this.redo());state.discardButton=gridDecorateButton(gridButton(labels.gridEditDiscard,act(()=>this.revertChanges())),null,true);state.saveButton=gridDecorateButton(gridButton(labels.gridEditSave,act(()=>this.commitChanges())),"check");state.saveButton.classList.add("cx-edit-save");bar.append(status,state.undoButton,state.redoButton,state.discardButton,state.saveButton);(this.spacer&&this.spacer.parentNode===this.toolbar?this.spacer:this.toolbar.lastChild)?.after(bar);this.syncEditBar()},editGridKey(event){const state=this.edit;if(event.defaultPrevented||event.isComposing||event.target.closest(".cx-editor-host"))return;const key=event.key.toLowerCase(),command=(event.ctrlKey||event.metaKey)&&!event.altKey;if(command&&(key==="z"||key==="y")&&!event.target.closest("input,textarea,select,[contenteditable]")){event.preventDefault();event.stopPropagation();if(key==="y"||event.shiftKey)this.redo();else this.undo();return}
76
+ if(to!==from&&this.editOwnsRows())Promise.resolve(this.reload?.()).catch(()=>{})}});GRID_ICONS.undo='<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>';GRID_ICONS.redo='<path d="m15 14 5-5-5-5"/><path d="M20 9H9.5a5.5 5.5 0 0 0 0 11H13"/>';const editAttr=(element,name,value)=>{if(value==null)element.removeAttribute(name);else if(element.getAttribute(name)!==value)element.setAttribute(name,value)};Object.assign(DataGrid.prototype,{buildEditing(){const state=this.edit,signal=this.lifetime.signal,grid=this.grid,labels=state.labels;state.tip=h("div","cx-edit-tip");state.tip.id=this.id+"-edit-tip";state.tip.popover="manual";state.notes=h("div","cx-sr-only");state.notes.id=this.id+"-edit-notes";state.tipObserver=new ResizeObserver(()=>{if(state.tip.matches(":popover-open"))this.editTip(state.tipAnchor,state.tip.textContent)});this.host.append(state.tip,state.notes);grid.addEventListener("keydown",event=>this.editGridKey(event),{capture:true,signal});grid.addEventListener("pointerdown",event=>{const cell=event.target.closest?.(".cx-cell");state.armed=cell&&cell===document.activeElement?cell:null},{capture:true,signal});grid.addEventListener("click",event=>this.editClick(event),{signal});grid.addEventListener("dblclick",event=>{const at=!this.phone&&this.editCellAt(event.target);if(at){event.stopPropagation();this.setFocus(at.row,false);this.editStart(at.row,at.column)}},{capture:true,signal});this.canvas.addEventListener("focusin",event=>{const row=event.target.closest(".cx-row")?._w;if(row&&!row.status)this.editEnterRow(row.key);this.editRefreshMessage()},{signal});this.canvas.addEventListener("focusout",event=>{if(!event.relatedTarget||!this.canvas.contains(event.relatedTarget))this.editHideMessage()},{signal});grid.addEventListener("scroll",()=>{if(state.tip.matches(":popover-open"))this.editRefreshMessage()},{passive:true,signal});signal.addEventListener("abort",()=>{state.tipObserver.disconnect();if(state.session)for(const field of state.session.fields.values())this.editDispose(field);state.tip.remove();state.notes.remove();state.barEl?.remove()},{once:true});if(!state.bar)return;const bar=state.barEl=h("div","cx-editbar"),status=h("span","cx-editbar-text");state.barText=status.appendChild(h("span"));state.barErrors=status.appendChild(h("span","cx-editbar-errors"));status.setAttribute("role","status");const act=run=>()=>Promise.resolve().then(run).catch(error=>this.emit("error",{error}));const icon=(name,text,run)=>{const button=gridDecorateButton(gridButton("",act(run)),name,true);button.classList.add("icon");button.setAttribute("aria-label",text);button.title=text;return button};state.undoButton=icon("undo",labels.gridEditUndo,()=>this.undo());state.redoButton=icon("redo",labels.gridEditRedo,()=>this.redo());state.discardButton=gridDecorateButton(gridButton(labels.gridEditDiscard,act(()=>this.revertChanges())),null,true);state.saveButton=gridDecorateButton(gridButton(labels.gridEditSave,act(()=>this.commitChanges())),"check");state.saveButton.classList.add("cx-edit-save");bar.append(status,state.undoButton,state.redoButton,state.discardButton,state.saveButton);(this.spacer&&this.spacer.parentNode===this.toolbar?this.spacer:this.toolbar.lastChild)?.after(bar);this.syncEditBar()},editGridKey(event){const state=this.edit;if(event.defaultPrevented||event.isComposing||event.target.closest(".cx-editor-host"))return;const key=event.key.toLowerCase(),command=(event.ctrlKey||event.metaKey)&&!event.altKey;if(command&&(key==="z"||key==="y")&&!event.target.closest("input,textarea,select,[contenteditable]")){event.preventDefault();event.stopPropagation();if(key==="y"||event.shiftKey)this.redo();else this.undo();return}
77
77
  const at=!command&&!event.altKey&&this.editCellAt(event.target,true);if(!at)return;const typed=event.key.length===1&&event.key!==" ",empty=event.key==="Backspace"||event.key==="Delete";if((typed||empty||event.key==="F2"||event.key==="Enter")&&this.editStart(at.row,at.column,typed?event.key:empty?"":undefined)){event.preventDefault();event.stopPropagation()}
78
78
  if(state.session===null&&event.key==="Escape")this.editHideMessage()},editCellAt(target,own){const cell=target.closest?.(".cx-cell"),element=cell?.parentNode,row=element?._w;if(!row||row.status||own&&target!==cell||target.closest(".cx-editor-host"))return null;const column=this.visibleColumns()[this.rowCells(element).indexOf(cell)];return column&&this.canEditCell(row.h,column.id)?{row,column:column.id,cell}:null},editClick(event){const at=!this.phone&&!event.target.closest("a,button,input,select,textarea,[contenteditable]")&&this.editCellAt(event.target);if(!at||event.detail>1)return;if(this.edit.armed===at.cell)this.editStart(at.row,at.column);else this.focusCell(at.row,this.editColumnIndex(at.column),null)},editNote(key,text){const state=this.edit;let note=state.messages.get(key);if(!note){note=h("span");note.id=this.id+"-en" + ++state.sequence;state.notes.appendChild(note);state.messages.set(key,note)}
79
79
  if(note.textContent!==text)setText(note,text);return note.id},paintEdits(element,row){const state=this.edit,entry=state.changes.get(row.key),session=state.session?.wrapper===row?state.session:null;const rowError=entry&&(entry.error||entry.rowError);element.classList.toggle("cx-row-invalid",!!rowError);editAttr(element,"aria-describedby",rowError?this.editNote("\u0001"+row.key,rowError):null);editAttr(element,"aria-busy",entry?.status==="saving"?"true":null);const errors=entry?editErrors(entry):Object.create(null);this.cols.forEach((column,index)=>{const slot=element._cells[index],cell=slot.cell,dirty=!!entry&&Object.hasOwn(entry.values,column.id),error=errors[column.id];const editable=state.specs.has(column.id)&&this.canEditCell(row.h,column.id),field=session?.fields.get(column.id);cell.classList.toggle("cx-dirty",dirty);cell.classList.toggle("cx-invalid",!!error);cell.classList.toggle("cx-editable",editable);editAttr(cell,"aria-readonly",editable?null:"true");editAttr(cell,"aria-invalid",error?"true":null);editAttr(cell,"aria-describedby",[dirty&&this.editNote("edited",state.labels.gridEditEdited),error&&this.editNote(row.key+"\u0000"+column.id,error)].filter(Boolean).join(" ")||null);if(field&&field.cell!==cell){field.cell.classList.remove("cx-editing");field.cell=cell}
80
80
  if(field){cell.classList.add("cx-editing");if(field.host.parentNode!==cell)cell.appendChild(field.host)}})},editShowMessage(field,message){const state=this.edit,entry=state.changes.get(field.record.id);const text=message||(entry&&!Object.hasOwn(entry.values,field.spec.id)?null:entry&&editErrors(entry)[field.spec.id])||null;editAttr(field.control,"aria-invalid",text?"true":null);editAttr(field.control,"aria-describedby",text?state.tip.id:null);if(text)this.editTip(field.cell||field.host,text);else this.editHideMessage()},editRefreshMessage(){const state=this.edit,active=document.activeElement,session=state.session;const field=session&&[...session.fields.values()].find(item=>item.host.contains(active));if(field){this.editShowMessage(field);return}
81
- const cell=active?.classList?.contains("cx-cell")&&this.canvas.contains(active)?active:null,row=cell?.parentNode._w;const column=row&&this.visibleColumns()[this.rowCells(cell.parentNode).indexOf(cell)],entry=row&&state.changes.get(row.key);const text=entry&&column?editErrors(entry)[column.id]||(column===this.visibleColumns()[0]?entry.error||entry.rowError:null):null;if(text)this.editTip(cell,text);else this.editHideMessage()},editTip(anchor,text){const tip=this.edit.tip,box=anchor.getBoundingClientRect();if(!anchor.isConnected||!box.width){this.editHideMessage();return}
82
- setText(tip,text);tip.style.left=Math.max(4,Math.min(box.left,innerWidth-280))+ "px";tip.style.top=box.bottom+4+"px";if(!tip.matches(":popover-open"))tip.showPopover()},editHideMessage(){const tip=this.edit?.tip;if(tip?.matches(":popover-open"))tip.hidePopover()},editAnnounce(text){if(text)setText(this.live,text +((this.edit.flip=!this.edit.flip)?" ":""))},syncEditBar(){const state=this.edit;if(!state)return;for(const[key,note]of state.messages){const row=key[0]==="\u0001",[id,column]=(row?key.slice(1):key).split("\u0000"),entry=state.changes.get(id);if(key!=="edited"&&!(entry&&(row?entry.error||entry.rowError:editErrors(entry)[column]))){note.remove();state.messages.delete(key)}}
81
+ const cell=active?.classList?.contains("cx-cell")&&this.canvas.contains(active)?active:null,row=cell?.parentNode._w;const column=row&&this.visibleColumns()[this.rowCells(cell.parentNode).indexOf(cell)],entry=row&&state.changes.get(row.key);const text=entry&&column?editErrors(entry)[column.id]||(column===this.visibleColumns()[0]?entry.error||entry.rowError:null):null;if(text)this.editTip(cell,text);else this.editHideMessage()},editTip(anchor,text){const state=this.edit,tip=state.tip,box=anchor.getBoundingClientRect();if(!anchor.isConnected||!box.width){this.editHideMessage();return}
82
+ setText(tip,text);tip.style.left=Math.max(4,Math.min(box.left,innerWidth-280))+ "px";if(!tip.matches(":popover-open"))tip.showPopover();const control=anchor.querySelector("[aria-controls]"),list=document.getElementById(control?.getAttribute("aria-controls"));const popup=list?.closest("[popover]");state.tipAnchor=anchor;if(state.tipPopup!==popup){state.tipObserver.disconnect();state.tipPopup=popup;if(popup)state.tipObserver.observe(popup)}
83
+ const options=popup?.matches(":popover-open")?popup.getBoundingClientRect():box;const top=Math.min(box.top,options.top),bottom=Math.max(box.bottom,options.bottom);const left=Math.min(box.left,options.left),right=Math.max(box.right,options.right);if(top-tip.offsetHeight>=8)tip.style.top=top-tip.offsetHeight-4+"px";else if(right+tip.offsetWidth+8<=innerWidth||left-tip.offsetWidth>=8){tip.style.left=(right+tip.offsetWidth+8<=innerWidth?right+4:left-tip.offsetWidth-4)+ "px";tip.style.top=Math.max(4,Math.min(box.top,innerHeight-tip.offsetHeight-4))+ "px"}else tip.style.top=bottom+4+"px"},editHideMessage(){const state=this.edit;state?.tipObserver.disconnect();if(state){state.tipAnchor=state.tipPopup=null;if(state.tip.matches(":popover-open"))state.tip.hidePopover()}},editAnnounce(text){if(text)setText(this.live,text +((this.edit.flip=!this.edit.flip)?" ":""))},syncEditBar(){const state=this.edit;if(!state)return;for(const[key,note]of state.messages){const row=key[0]==="\u0001",[id,column]=(row?key.slice(1):key).split("\u0000"),entry=state.changes.get(id);if(key!=="edited"&&!(entry&&(row?entry.error||entry.rowError:editErrors(entry)[column]))){note.remove();state.messages.delete(key)}}
83
84
  if(!state.barEl)return;const entries=this.editEntries(),count=entries.length,errors=entries.filter(editInvalid).length,saving=[...state.changes.values()].some(entry=>entry.status==="saving");state.barEl.hidden=!count&&!state.undo.length&&!state.redo.length&&!saving;setText(state.barText,saving?state.labels.gridEditSaving:fill(state.labels.gridEditChanges,{count:numberFormat(this.lang).format(count)}));setText(state.barErrors,errors&&!saving?" · "+fill(state.labels.gridEditErrors,{count:numberFormat(this.lang).format(errors)}):"");state.undoButton.disabled=!state.undo.length;state.redoButton.disabled=!state.redo.length;state.discardButton.disabled=state.saveButton.disabled=!count||saving},editRemember(column,option){const seen=(this.edit.seen||=new Map()),list=seen.get(column)||[];if(!list.some(item=>item.value===option.value)){list.push(option);if(list.length>1000)list.shift()}
84
85
  seen.set(column,list)},async editLookup(spec,search,record,signal){const limit=spec.lookup.limit,lookups=this.edit.options.lookups,own=lookups&&Object.hasOwn(lookups,spec.id)?lookups[spec.id]:null,text=foldText(search.trim());const matching=list=>list.filter(option=>foldText(option.label).includes(text)).slice(0,limit);if(typeof own==="function")return editOptions(await own({column:spec.id,search,limit,row:editCopy(record),signal}),spec.id).slice(0,limit);if(spec.options)return matching(spec.options);const column=this.columns.find(item=>item.id===spec.lookup.column);if(this.view.source.kind==="inline"){await this.source.decoded;const values=new Map();for(const row of this.view.rows){const value=cellValue(row.cells[column.id]);if(value!=null&&!values.has(value))values.set(value,{value,label:cellText(row.cells[column.id],column,this.fmt)})}
85
86
  return matching([...values.values()].sort((left,right)=>left.label.localeCompare(right.label,this.lang)))}
@@ -1,4 +1,4 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"8b99c0b7b0ec37c5",install(context){if(installed.has(context.EXPORT_IMPL))return;const{DataGrid,EXPORT_IMPL,h,setText,gridButton,gridOption,fill,numberFormat,gridOperationActive,formatting,gridQuery,validateGridCursorPage,validateGridPage,GRID_REPORTING,gridFreeze,cellValue,cellText,isCellObject,exportTimeout,exportFormat,EXPORT_FORMATS,gridExportField}=context;function saveExport(blob,name){const url=URL.createObjectURL(blob),link=h("a");link.href=url;link.download=String(name).replace(/[\\/:*?"<>|\x00-\x1f\x7f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "_").replace(/^[. ]+|[. ]+$/g, "_");
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"9203882f58fa0155",install(context){if(installed.has(context.EXPORT_IMPL))return;const{DataGrid,EXPORT_IMPL,h,setText,gridButton,gridOption,fill,numberFormat,gridOperationActive,formatting,gridQuery,validateGridCursorPage,validateGridPage,GRID_REPORTING,gridFreeze,cellValue,cellText,isCellObject,exportTimeout,exportFormat,EXPORT_FORMATS,gridExportField}=context;function saveExport(blob,name){const url=URL.createObjectURL(blob),link=h("a");link.href=url;link.download=String(name).replace(/[\\/:*?"<>|\x00-\x1f\x7f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "_").replace(/^[. ]+|[. ]+$/g, "_");
2
2
  document.body.appendChild(link);link.click();link.remove();setTimeout(()=>URL.revokeObjectURL(url),30000)}
3
3
  Object.assign(EXPORT_IMPL,{async run(grid,id,options,capture,format){if(!format)throw new Error("CanopyX: export format '"+id+"' is not available.");const total=capture.request.recordCount,onProgress=options.onProgress;if(onProgress!==undefined&&typeof onProgress!=="function"||options.download!==undefined&&typeof options.download!=="boolean")
4
4
  throw new Error("CanopyX: export progress is a callback and download is a boolean.");const timer=new AbortController(),clock=setTimeout(()=>timer.abort(new DOMException("Export timed out.","TimeoutError")),exportTimeout(grid,options));const signal=AbortSignal.any([grid.lifetime.signal,timer.signal,options.signal].filter(Boolean));const failure=()=>signal.reason?.name==="TimeoutError"?new Error("CanopyX: export timed out."):new DOMException("Export cancelled.","AbortError");const phases=["reading","generating","ready"];let phase=-1,done=0,read=0,stop;const report=(name,completed)=>{const next=phases.indexOf(name),count=total===null?completed:Math.min(completed,total);if(signal.aborted||next<phase||!Number.isSafeInteger(completed)||completed<0||next===phase&&count<=done)return;phase=next;done=Math.max(done,count);onProgress?.(Object.freeze({phase:name,completed:done,total}))};const stopped=new Promise((resolve,reject)=>{stop=()=>reject(failure())});signal.addEventListener("abort",stop,{once:true});try{if(signal.aborted)throw failure();const counted=Object.freeze({request:capture.request,rows:async function*(more){read=0;for await(const row of capture.rows({signal:AbortSignal.any([signal,more?.signal].filter(Boolean))})){yield row;if(++read%100===0)report("reading",read)}
@@ -11,8 +11,12 @@ throw new Error("CanopyX: incomplete cursor export.");expected.matched=page.matc
11
11
  offset +=this.cursor?page.items.length:request.query.limit;if(this.cursor){cursor=page.nextCursor;if(cursor===undefined)break;if(tokens.has(cursor))throw new Error("CanopyX: cursor export repeated a token.");tokens.add(cursor)}}
12
12
  gridOperationActive(this,signal);if(remaining.size)throw new Error("CanopyX: selected IDs absent from revision.")},async exportText(exported,maxRows=50000,delimiter,signal){if(!Number.isInteger(maxRows)||maxRows<1||maxRows>1000000)throw new Error("CanopyX: export row limit is 1 to 1000000.");const request=exported.request;if(request.recordCount>maxRows)throw new Error("CanopyX: client row limit exceeded.");const lines=[request.columns.map(column=>gridExportField(column.title,delimiter)).join(delimiter)];let count=0;for await(const row of exported.rows()){if(++count>maxRows)throw new Error("CanopyX: client row limit exceeded.");lines.push(request.columns.map(column=>{const value=request.values==="raw"?row.cells[column.id]?.value:row.cells[column.id]?.text;return gridExportField(row.level>1&&column.id===this.columns[0].id?" ".repeat(row.level-1)+(value??""):value,delimiter)}).join(delimiter))}
13
13
  gridOperationActive(this,signal);return lines.join("\r\n")+ "\r\n"},toCsv(capture,options){return this.exportText(capture,options.maxRows,",",options.signal)},async copy(capture,options){const text=await this.exportText(capture,options.maxRows===undefined?10000:options.maxRows,"\t",options.signal);await gridWriteClipboard(this,text,options.signal);return text},copyFocused(row,cell){if(this.selected.size)return this.copy({scope:"selected"});const columns=this.visibleColumns(),element=this.rendered.get(row),index=cell&&element?this.rowCells(element).indexOf(cell):-1;const text=index>=0?gridExportField(cellText(row.h.cells[columns[index].id],columns[index],this.fmt),"\t"):
14
- columns.map(column=>gridExportField(cellText(row.h.cells[column.id],column,this.fmt),"\t")).join("\t");return gridWriteClipboard(this,text)},exportToHost(capture,format,options){if(typeof format!=="string"||!format.trim()||typeof this.opts.onExport!=="function")throw new Error("CanopyX: invalid host export.");return this.runGridHook(signal=>this.opts.onExport({format,request:capture.request,capture,signal}),options.signal,exportTimeout(this,options))}});Object.assign(EXPORT_IMPL,{exportFromMenu(action){const{details,panel}=this.exportControl;this.renderGridExport(panel);details.open=true;if(!this.phone&&this.overflow)this.overflow.details.open=false;panel.cxRun(action)}});const exportMenu=function(panel){if(panel.cxRun)return panel.cxFormats();panel.replaceChildren();const reporting=!!this.view.reporting,scope=h("select"),labels=this.labels;scope.setAttribute("aria-label",labels.gridExport);for(const[value,label]of this.exportScopes||[["filtered","gridFilteredResults"],["selected","gridSelectedRows"],["all","gridAllResults"]])
15
- scope.appendChild(gridOption(value,labels[label]));const status=h("p","cx-grid-delivery-status");status.setAttribute("role","status");const meter=h("progress");meter.setAttribute("aria-label",labels.gridExport);meter.hidden=true;let operation=null,formats=[];panel.cxExportBusy=()=>operation!==null;const cancel=gridButton(labels.gridCancelExport,()=>operation?.abort());cancel.hidden=true;const progress=value=>{if(this.destroyed||!operation||operation.signal.aborted)return;setText(status,fill(value.total===null?labels.gridExportReading:labels.gridExportProgress,{completed:numberFormat(this.lang).format(value.completed),total:value.total===null?"":numberFormat(this.lang).format(value.total)}));if(value.total===null)meter.removeAttribute("value");else{meter.max=Math.max(1,value.total);meter.value=value.completed}};const canExport=()=>this.view.type!=="grid"||this.defaultExportColumns().length>0;const busy=state=>{scope.disabled=state;cancel.hidden=meter.hidden=!state;for(const button of[csv,...formats,copy,this.printButton])if(button)button.disabled=state||!canExport()};const traced=(options,onProgress)=>reporting?{...options,onProgress}:options;panel.cxRun=action=>{if(operation)return;const controller=operation=new AbortController();busy(true);meter.removeAttribute("value");setText(status,labels.loading);Promise.resolve().then(()=>action({scope:scope.value,signal:controller.signal},progress)).then(()=>{if(!this.destroyed)setText(status,"")},error=>{if(!this.destroyed)setText(status,error.name==="AbortError"?labels.gridExportCancelled:error.message);if(error.name!=="AbortError")this.emit("error",{error})}).finally(()=>{if(operation===controller)operation=null;if(!this.destroyed){const returnFocus=document.activeElement===cancel;busy(false);if(returnFocus)scope.focus()}})};const csv=gridButton(labels.gridDownloadCsv,()=>panel.cxRun(async(options,onProgress)=>{const exported=this.prepareExport(traced(options,onProgress));const text=await this.exportText(exported,50000,",",options.signal);gridOperationActive(this,options.signal);saveExport(new Blob([text],{type:"text/csv;charset=utf-8"}),(exported.request.fileName||this.view.id.replace(/[^a-z0-9_-]/gi, "_")) + ".csv");
16
- }));const copy=this.copy?gridButton(labels.gridCopy,()=>panel.cxRun((options,onProgress)=>this.copy(traced(options,onProgress)))):h("span");panel.cxFormats=()=>{if(operation)return;formats.forEach(button=>button.remove());try{formats=EXPORT_IMPL.formatsOf(this).filter(format=>format.showInMenu).map(format=>Object.assign(gridButton(format.label,()=>
14
+ columns.map(column=>gridExportField(cellText(row.h.cells[column.id],column,this.fmt),"\t")).join("\t");return gridWriteClipboard(this,text)},exportToHost(capture,format,options){if(typeof format!=="string"||!format.trim()||typeof this.opts.onExport!=="function")throw new Error("CanopyX: invalid host export.");return this.runGridHook(signal=>this.opts.onExport({format,request:capture.request,capture,signal}),options.signal,exportTimeout(this,options))}});Object.assign(EXPORT_IMPL,{exportFromMenu(action){const{details,panel}=this.exportControl;this.renderGridExport(panel);details.open=true;if(!this.phone&&this.overflow)this.overflow.details.open=false;panel.cxRun(action)}});const exportMenu=function(panel){if(panel.cxRun)return panel.cxFormats();panel.replaceChildren();const reporting=!!this.view.reporting,scope=h("select"),labels=this.labels;const menu=this.opts.exportMenu??{};if(!menu||typeof menu!=="object"||Array.isArray(menu)||
15
+ Object.keys(menu).some(key=>!["csv","copy","values"].includes(key))||
16
+ ["csv","copy"].some(key=>menu[key]!==undefined&&typeof menu[key]!=="boolean")||
17
+ menu.values!==undefined&&!["raw","display"].includes(menu.values))
18
+ throw new Error("CanopyX: exportMenu supports csv/copy booleans and raw/display values.");scope.setAttribute("aria-label",labels.gridExport);for(const[value,label]of this.exportScopes||[["filtered","gridFilteredResults"],["selected","gridSelectedRows"],["all","gridAllResults"]])
19
+ scope.appendChild(gridOption(value,labels[label]));const status=h("p","cx-grid-delivery-status");status.setAttribute("role","status");const meter=h("progress");meter.setAttribute("aria-label",labels.gridExport);meter.hidden=true;let operation=null,formats=[];panel.cxExportBusy=()=>operation!==null;const cancel=gridButton(labels.gridCancelExport,()=>operation?.abort());cancel.hidden=true;const progress=value=>{if(this.destroyed||!operation||operation.signal.aborted)return;setText(status,fill(value.total===null?labels.gridExportReading:labels.gridExportProgress,{completed:numberFormat(this.lang).format(value.completed),total:value.total===null?"":numberFormat(this.lang).format(value.total)}));if(value.total===null)meter.removeAttribute("value");else{meter.max=Math.max(1,value.total);meter.value=value.completed}};const canExport=()=>this.view.type!=="grid"||this.defaultExportColumns().length>0;const busy=state=>{scope.disabled=state;cancel.hidden=meter.hidden=!state;for(const button of[csv,...formats,copy,this.printButton])if(button)button.disabled=state||!canExport()};const traced=(options,onProgress)=>reporting?{...options,onProgress}:options;panel.cxRun=action=>{if(operation)return;const controller=operation=new AbortController();busy(true);meter.removeAttribute("value");setText(status,labels.loading);Promise.resolve().then(()=>action({scope:scope.value,signal:controller.signal,...(menu.values?{values:menu.values}:{})},progress)).then(()=>{if(!this.destroyed)setText(status,"")},error=>{if(!this.destroyed)setText(status,error.name==="AbortError"?labels.gridExportCancelled:error.message);if(error.name!=="AbortError")this.emit("error",{error})}).finally(()=>{if(operation===controller)operation=null;if(!this.destroyed){const returnFocus=document.activeElement===cancel;busy(false);if(returnFocus)scope.focus()}})};const csv=gridButton(labels.gridDownloadCsv,()=>panel.cxRun(async(options,onProgress)=>{const exported=this.prepareExport(traced(options,onProgress));const text=await this.exportText(exported,50000,",",options.signal);gridOperationActive(this,options.signal);saveExport(new Blob([text],{type:"text/csv;charset=utf-8"}),(exported.request.fileName||this.view.id.replace(/[^a-z0-9_-]/gi, "_")) + ".csv");
20
+ }));const copy=this.copy?gridButton(labels.gridCopy,()=>panel.cxRun((options,onProgress)=>this.copy(traced(options,onProgress)))):h("span");csv.hidden=menu.csv===false;copy.hidden=menu.copy===false;panel.cxFormats=()=>{if(operation)return;formats.forEach(button=>button.remove());try{formats=EXPORT_IMPL.formatsOf(this).filter(format=>format.showInMenu).map(format=>Object.assign(gridButton(format.label,()=>
17
21
  panel.cxRun((options,onProgress)=>this.exportAs(format.id,{...options,onProgress}))),{value:format.id}))}catch(error){formats=[];this.emit("error",{error})}
18
22
  copy.before(...formats);busy(false);if(!canExport())setText(status,labels.gridNoExportColumns);else if(status.textContent===labels.gridNoExportColumns)setText(status,"")};panel.append(scope,csv,copy,cancel,meter,status);panel.cxFormats()};DataGrid.prototype.renderGridExport=exportMenu;installed.add(context.EXPORT_IMPL)}};(global.CanopyXParts||=[]).push(["export",part])})(typeof window!=="undefined"?window:globalThis);
@@ -1,4 +1,4 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"f9219d35a87635f8",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,fill,gridButton,gridIcon,gridQueryStart,gridQueryRows,gridColumnProjection,gridScalar,gridOrder,cellText,foldText,numberFormat,gridCellRenderer,gridFreeze}=context;function gridColumnGroup(operator,items){const filters=[],groups=[];for(const item of items){if(typeof item==="boolean"){if(item===(operator==="or"))return item;continue}
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"b46aac5ddb8be693",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,fill,gridButton,gridIcon,gridQueryStart,gridQueryRows,gridColumnProjection,gridScalar,gridOrder,gridKeyCompare,gridFacetKey,cellText,foldText,numberFormat,gridCellRenderer,gridFreeze}=context;function gridColumnGroup(operator,items){const filters=[],groups=[];for(const item of items){if(typeof item==="boolean"){if(item===(operator==="or"))return item;continue}
2
2
  if(item.operator===operator){filters.push(...item.filters);groups.push(...item.groups)}
3
3
  else(item.groups?groups:filters).push(item)}
4
4
  return!filters.length&&!groups.length?operator==="and":!filters.length&&groups.length===1?groups[0]:{operator,filters,groups}}
@@ -17,8 +17,8 @@ merged.label!==undefined&&(typeof merged.label!=="string"||!merged.label.trim()|
17
17
  merged.target!==undefined&&!(merged.target instanceof HTMLElement))
18
18
  throw new Error("CanopyX: invalid facet panel options.");const hosted=grid.view.source.kind!=="inline"||!!grid.opts.source;if(hosted&&typeof grid.getFacets!=="function")throw new Error("CanopyX: a hosted grid's facet panel needs the reporting runtime.");if(hosted&&facets.some(facet=>!grid.view.reporting?.facetColumns?.includes(facet.column)))
19
19
  throw new Error("CanopyX: a hosted grid's facets must be declared reporting facet columns.");return{facets,hosted,position:merged.position||"side",open:merged.open!==false,collapsible:merged.collapsible!==false,width:merged.width||260,label:merged.label||null,target:merged.target||null}}
20
- function facetDeclaredOrder(grid,column){const presenter=column.presenter,values=grid.view.reporting?.ordering?.[column.id]?.categories||
21
- (presenter&&(presenter.kind==="enum"||presenter.kind==="state")&&presenter.tones?Object.keys(presenter.tones):null);return values&&values.length?new Map(values.map((value,index)=>[foldText(String(value)),index])):null}
20
+ function facetDeclaredOrder(grid,column){const presenter=column.presenter,values=column.valueOrder||grid.view.reporting?.ordering?.[column.id]?.categories||
21
+ (presenter&&(presenter.kind==="enum"||presenter.kind==="state")&&presenter.tones?Object.keys(presenter.tones):null);return values&&values.length?new Map(values.map((value,index)=>[gridFacetKey(value,column),index])):null}
22
22
  function facetSelection(query,facet){let clauses;try{({clauses}=gridColumnClauses(query,facet.column))}catch(_){return{custom:true,locked:true}}
23
23
  if(!clauses.length)return{none:true};if(facet.kind==="list"){const listed=clause=>["in","eq","isNull"].includes(clause.operator)&&!clause.groups;const items=clauses.length!==1?null:clauses[0].groups?clauses[0].operator==="or"&&!clauses[0].groups.length&&clauses[0].filters.every(listed)?clauses[0].filters:null:listed(clauses[0])?clauses:null;return items?{values:items.flatMap(clause=>clause.operator==="isNull"?[null]:clause.values)}:{custom:true}}
24
24
  let low=null,high=null,lowOpen=false,open=false;for(const clause of clauses){if(clause.groups||clauses.length>2)return{custom:true};if((clause.operator==="ge"||clause.operator==="gt")&&low===null){low=clause.values[0];lowOpen=clause.operator==="gt"}
@@ -27,17 +27,17 @@ else return{custom:true}}
27
27
  return{low,high,lowOpen,open}}
28
28
  function facetQuery(query,facet,choice){const predicate=(operator,values=[])=>({column:facet.column,operator,values});try{if(facet.kind==="range"){const base=gridColumnClauses(query,facet.column).query;const bounds=[choice.low!=null&&predicate(choice.lowOpen?"gt":"ge",[choice.low]),choice.high!=null&&predicate(choice.open?"lt":"le",[choice.high])].filter(Boolean);return gridQueryStart(base,{filters:[...base.filters,...bounds]})}
29
29
  const raw=choice.filter(value=>value!==null),empty=raw.length<choice.length;const items=[raw.length&&predicate("in",raw),empty&&predicate("isNull")].filter(Boolean);return gridInstallColumnClause(query,facet.column,!items.length?null:items.length===1?items[0]:{operator:"or",filters:items,groups:[]})}catch(_){return query}}
30
- const facetShape=query=>query?JSON.stringify([query.search,query.searchMode,query.filters,query.filterGroup,query.revision]):"";const facetKey=(value,column)=>value===null?"null":column.kind==="text"&&typeof value==="string"?"s:"+foldText(value):typeof value+":"+value;function facetInlineCounts(grid,facet,query){const projection=gridColumnProjection(query,facet.column),columns=new Map(grid.view.columns.map(column=>[column.id,column]));const rows=gridQueryRows(grid.view,projection,columns,false),column=facet.def;if(facet.kind==="range"){const values=[];for(const row of rows){const value=gridScalar(row.cells[column.id],column);if(value!==null)values.push(value)}
30
+ const facetShape=query=>query?JSON.stringify([query.search,query.searchMode,query.filters,query.filterGroup,query.revision]):"";const facetKey=gridFacetKey;function facetInlineCounts(grid,facet,query){const projection=gridColumnProjection(query,facet.column),columns=new Map(grid.view.columns.map(column=>[column.id,column]));const rows=gridQueryRows(grid.view,projection,columns,false),column=facet.def;if(facet.kind==="range"){const values=[];for(const row of rows){const value=gridScalar(row.cells[column.id],column);if(value!==null)values.push(value)}
31
31
  return{matched:rows.length,values,complete:true}}
32
32
  const buckets=new Map();for(const row of rows){const raw=gridScalar(row.cells[column.id],column),value=column.kind==="datetime"&&raw!==null?new Date(raw).toISOString():raw,key=facetKey(value,column);const bucket=buckets.get(key);if(bucket){bucket.count++;if(typeof value==="string"&&value<bucket.value)bucket.value=value}else buckets.set(key,{value,count:1})}
33
33
  const items=[...buckets.values()];return{matched:rows.length,items,totalValues:items.length,truncated:false}}
34
34
  async function facetHostedCounts(grid,facet,search,signal){const page=await grid.getFacets(facet.column,{search:search||"",includeCurrentFilter:false,limit:FACET_VALUES,signal});const column=facet.def;if(facet.kind==="range")
35
35
  return{matched:page.matched,buckets:page.items.filter(item=>item.value!==null).map(item=>({value:gridScalar(item.value,column),count:item.count})),complete:!page.truncated};return{matched:page.matched,items:page.items.map(item=>({value:item.value,count:item.count})),totalValues:page.totalValues,truncated:page.truncated}}
36
- const FACET_SEARCH_FROM=12;function facetListBody(panel,facet,body){const grid=panel.grid,search=h("input","cx-input cx-facet-search"),list=h("ul","cx-facet-values"),more=gridButton("",()=>{state.expanded=!state.expanded;draw()});const note=h("p","cx-facet-note"),state={expanded:false,search:"",counts:null,timer:0};search.type="search";search.placeholder=grid.labels.gridFacetSearch;search.setAttribute("aria-label",fill(grid.labels.gridFacetSearch,{})+ ": "+facet.label);search.hidden=true;more.classList.add("cx-facet-more");more.hidden=true;note.hidden=true;list.setAttribute("aria-label",facet.label);body.append(search,list,more,note);search.addEventListener("input",()=>{state.search=search.value;clearTimeout(state.timer);state.timer=setTimeout(()=>facet.hosted?panel.refresh([facet]):draw(),facet.hosted?250:0)});list.addEventListener("keydown",event=>{const boxes=[...list.querySelectorAll("input")],at=boxes.indexOf(document.activeElement);const next={ArrowDown:at+1,ArrowUp:at-1,Home:0,End:boxes.length-1}[event.key];if(at<0||next===undefined||!boxes[next])return;event.preventDefault();boxes.forEach(box=>{box.tabIndex=-1});boxes[next].tabIndex=0;boxes[next].focus()});const choose=(item,checked)=>{const chosen=new Map((state.chosen||[]).map(value=>[facetKey(value,facet.def),value]));if(checked)chosen.set(facetKey(item.value,facet.def),item.value);else chosen.delete(facetKey(item.value,facet.def));state.chosen=[...chosen.values()];panel.apply(facet,state.chosen)};function draw(){const counts=state.counts,selection=panel.selection(facet);if(!counts)return;state.chosen=selection.values||[];const chosen=new Set(state.chosen.map(value=>facetKey(value,facet.def))),focused=list.contains(document.activeElement)?document.activeElement:null;const focusKey=focused?.getAttribute("data-cx-key"),focusPosition=focused?[...list.querySelectorAll("input")].indexOf(focused):0;const needle=facet.hosted?"":foldText(state.search),locked=!!selection.locked;let items=counts.items.filter(item=>!needle||item.value!==null&&foldText(String(item.value)).includes(needle));if(!(facet.hosted&&state.search))for(const value of state.chosen)if(!items.some(item=>facetKey(item.value,facet.def)===facetKey(value,facet.def)))items.push({value,count:0});items.sort(facet.sort==="count"?(a,b)=>b.count-a.count||facetValueOrder(a.value,b.value,facet):(a,b)=>facetValueOrder(a.value,b.value,facet));const shown=state.expanded?items.filter((item,index)=>index<FACET_VALUES||chosen.has(facetKey(item.value,facet.def))):items.filter((item,index)=>index<facet.limit||chosen.has(facetKey(item.value,facet.def)));const retainedIndex=shown.findIndex(item=>facetKey(item.value,facet.def)===focusKey);const focusIndex=retainedIndex>=0?retainedIndex:Math.min(Math.max(0,focusPosition),Math.max(0,shown.length-1));const numbers=numberFormat(grid.lang);list.replaceChildren(...shown.map((item,index)=>{const row=h("li"),label=h("label","cx-facet-val"),box=h("input"),text=h("span","cx-facet-text"),count=h("span","cx-facet-n"),key=facetKey(item.value,facet.def);box.type="checkbox";box.checked=chosen.has(key);box.disabled=locked;box.setAttribute("data-cx-key",key);box.tabIndex=index===focusIndex?0:-1;const empty=item.value===null||item.value==="",display=empty?grid.labels.gridFacetEmptyValue:facetValueText(grid,facet,item.value,text);if(empty){setText(text,display);text.classList.add("cx-facet-empty")}
36
+ const FACET_SEARCH_FROM=12;function facetListBody(panel,facet,body){const grid=panel.grid,search=h("input","cx-input cx-facet-search"),list=h("ul","cx-facet-values"),more=gridButton("",()=>{state.expanded=!state.expanded;draw()});const note=h("p","cx-facet-note"),state={expanded:false,search:"",counts:null,timer:0};search.type="search";search.placeholder=grid.labels.gridFacetSearch;search.setAttribute("aria-label",fill(grid.labels.gridFacetSearch,{})+ ": "+facet.label);search.hidden=true;more.classList.add("cx-facet-more");more.hidden=true;note.hidden=true;list.setAttribute("aria-label",facet.label);body.append(search,list,more,note);search.addEventListener("input",()=>{state.search=search.value;clearTimeout(state.timer);state.timer=setTimeout(()=>facet.hosted?panel.refresh([facet]):draw(),facet.hosted?250:0)});list.addEventListener("keydown",event=>{const boxes=[...list.querySelectorAll("input")],at=boxes.indexOf(document.activeElement);const next={ArrowDown:at+1,ArrowUp:at-1,Home:0,End:boxes.length-1}[event.key];if(at<0||next===undefined||!boxes[next])return;event.preventDefault();boxes.forEach(box=>{box.tabIndex=-1});boxes[next].tabIndex=0;boxes[next].focus()});const choose=(item,checked)=>{const chosen=new Map((state.chosen||[]).map(value=>[facetKey(value,facet.def),value]));if(checked)chosen.set(facetKey(item.value,facet.def),item.value);else chosen.delete(facetKey(item.value,facet.def));state.chosen=[...chosen.values()];panel.apply(facet,state.chosen)};function draw(){const counts=state.counts,selection=panel.selection(facet);if(!counts)return;state.chosen=selection.values||[];const chosen=new Set(state.chosen.map(value=>facetKey(value,facet.def))),focused=list.contains(document.activeElement)?document.activeElement:null;const focusKey=focused?.getAttribute("data-cx-key"),focusPosition=focused?[...list.querySelectorAll("input")].indexOf(focused):0;const needle=facet.hosted?"":foldText(state.search),locked=!!selection.locked;let items=counts.items.filter(item=>!needle||item.value!==null&&foldText(cellText(item.value,facet.def,grid.fmt)).includes(needle));if(!(facet.hosted&&state.search))for(const value of state.chosen)if(!items.some(item=>facetKey(item.value,facet.def)===facetKey(value,facet.def)))items.push({value,count:0});items.sort(facet.sort==="count"?(a,b)=>b.count-a.count||facetValueOrder(a.value,b.value,facet):(a,b)=>facetValueOrder(a.value,b.value,facet));const shown=state.expanded?items.filter((item,index)=>index<FACET_VALUES||chosen.has(facetKey(item.value,facet.def))):items.filter((item,index)=>index<facet.limit||chosen.has(facetKey(item.value,facet.def)));const retainedIndex=shown.findIndex(item=>facetKey(item.value,facet.def)===focusKey);const focusIndex=retainedIndex>=0?retainedIndex:Math.min(Math.max(0,focusPosition),Math.max(0,shown.length-1));const numbers=numberFormat(grid.lang);list.replaceChildren(...shown.map((item,index)=>{const row=h("li"),label=h("label","cx-facet-val"),box=h("input"),text=h("span","cx-facet-text"),count=h("span","cx-facet-n"),key=facetKey(item.value,facet.def);box.type="checkbox";box.checked=chosen.has(key);box.disabled=locked;box.setAttribute("data-cx-key",key);box.tabIndex=index===focusIndex?0:-1;const empty=item.value===null||item.value==="",display=empty?grid.labels.gridFacetEmptyValue:facetValueText(grid,facet,item.value,text);if(empty){setText(text,display);text.classList.add("cx-facet-empty")}
37
37
  box.setAttribute("aria-label",fill(grid.labels.gridFacetValueCount,{value:display,count:numbers.format(item.count)}));setText(count,numbers.format(item.count));count.setAttribute("aria-hidden","true");if(!item.count)label.classList.add("cx-facet-zero");box.addEventListener("change",()=>choose(item,box.checked));label.append(box,text,count);row.appendChild(label);return row}));if(focusKey){const target=!locked&&list.querySelector('input[tabindex="0"]')||body.closest(".cx-facet")?.querySelector(".cx-facet-toggle");target?.focus({preventScroll:!locked&&retainedIndex>=0})}
38
38
  const hidden=items.length-shown.length,capped=state.expanded&&hidden>0;more.hidden=!hidden&&!state.expanded||items.length<=facet.limit;setText(more,state.expanded?grid.labels.gridFacetShowFewer:fill(grid.labels.gridFacetShowMore,{count:numbers.format(Math.min(hidden,Math.max(1,FACET_VALUES-shown.length)))}));search.hidden=!state.search&&document.activeElement!==search&&counts.totalValues<=FACET_SEARCH_FROM;search.disabled=locked;note.hidden=!counts.truncated&&!capped;if(!note.hidden)setText(note,fill(grid.labels.gridFacetCounts,{count:numbers.format(counts.matched),shown:numbers.format(counts.truncated?counts.items.length:shown.length),total:numbers.format(counts.totalValues)}))}
39
39
  return{draw,search:()=>state.search,update(counts){state.counts=counts;draw()}}}
40
- function facetValueOrder(a,b,facet){const column=facet.def;if(a===null||b===null)return a===b?0:a===null?1:-1;if(facet.order){const at=facet.order.get(foldText(String(a)))??Infinity,bt=facet.order.get(foldText(String(b)))??Infinity;if(at!==bt)return at<bt?-1:1}
40
+ function facetValueOrder(a,b,facet){const column=facet.def;if(column.valueOrder?.length)return gridKeyCompare(a,b,column,"asc");if(a===null||b===null)return a===b?0:a===null?1:-1;if(facet.order){const at=facet.order.get(facetKey(a,column))??Infinity,bt=facet.order.get(facetKey(b,column))??Infinity;if(at!==bt)return at<bt?-1:1}
41
41
  if(column.kind!=="text")return gridOrder(gridScalar(a,column),gridScalar(b,column));if(typeof a!==typeof b)return typeof a==="boolean"?-1:1;return gridOrder(typeof a==="string"?foldText(a):a,typeof b==="string"?foldText(b):b)}
42
42
  function facetValueText(grid,facet,value,element){const column=facet.def,options=column.presenter,text=cellText(value,column,grid.fmt)||String(value);if(options&&!["statusStrip","sparklineBars","sparklineLine","change","identifier","longText","list","tags","structured"].includes(options.kind)){try{gridCellRenderer(grid,column,options)({id:"facet",cells:{[column.id]:value}},element);return text}catch(_){element.replaceChildren()}}
43
43
  setText(element,text);return text}
@@ -1,9 +1,10 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"8a058a87bbba3cbe",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,gridButton,gridInputLabel,gridOption,gridScalar,gridDateInputMoment,gridQuery,gridQueryStart,GRID_FILTER_LABELS,gridResultShape,gridQueryRows,gridOrder,gridColumnProjection,gridCellRenderer,cellValue,cellText,foldText,reachable,gridMatchesGroup,gridDecorateButton,gridIcon,numberFormat,numberText,fill,announceMenu,reportingRangeRows,applyTone,toneOf,badgeElement,isCellObject,RECORD_IMPL}=context;const buildFilters=DataGrid.prototype.buildGridFilters;function gridOverlayTheme(grid,element){for(const name of(grid.opts.overlayClass||"").split(/\s+/).filter(Boolean)) element.classList.add(name);
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"ad3c3d19c0c13c6d",requires:["presenters"],install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,gridButton,gridInputLabel,gridOption,gridScalar,gridDateInputMoment,gridQuery,gridQueryStart,GRID_FILTER_LABELS,gridResultShape,gridQueryRows,gridOrder,gridKeyCompare,gridFacetKey,columnNumericTone,columnValueTone,gridColumnProjection,gridCellRenderer,cellValue,cellText,foldText,reachable,gridMatchesGroup,gridDecorateButton,gridIcon,numberFormat,numberText,fill,announceMenu,reportingRangeRows,applyTone,toneOf,badgeElement,isCellObject,RECORD_IMPL}=context;const buildFilters=DataGrid.prototype.buildGridFilters;function gridOverlayTheme(grid,element){for(const name of(grid.opts.overlayClass||"").split(/\s+/).filter(Boolean)) element.classList.add(name);
2
2
  element.setAttribute("data-cx-theme",grid.host.getAttribute("data-cx-theme"));const theme=getComputedStyle(grid.host);element.style.colorScheme=theme.colorScheme;for(const name of theme)if(name.startsWith("--cx-"))element.style.setProperty(name,theme.getPropertyValue(name))}
3
3
  function gridRestoreFocus(grid,returnFocus,rowId,column){if(grid.destroyed)return;if(returnFocus?.isConnected&&reachable(returnFocus)){returnFocus.focus({preventScroll:true});return}
4
4
  const row=[...grid.rowCache.values()].find(item=>!item.status&&item.key===rowId);const index=grid.visibleColumns().findIndex(item=>item.id===column?.id);if(row&&index>=0)grid.focusCell(row,index,0);else if(row)grid.setFocus(row,true);else grid.grid.focus({preventScroll:true})}
5
5
  const COLUMNS_MENU={moveColumnChoice(column,target,after){column.pin=target.pin;const ids=this.columns.map(item=>item.id).filter(id=>id!==column.id);ids.splice(ids.indexOf(target.id)+ after,0,column.id);this.setColumnOrder(ids)},renderGridColumns(panel){if(!this.columnList){const find=this.columnFind=h("input","cx-grid-column-find"),actions=h("div","cx-grid-column-actions");find.type="search";find.placeholder=this.labels.gridFindColumn;find.setAttribute("aria-label",find.placeholder);find.hidden=this.columns.length<9;find.oninput=()=>this.syncColumnControls();actions.append(gridButton(this.labels.gridShowAllColumns,()=>this.showAllColumns()),gridButton(this.labels.gridResetColumns,()=>this.resetColumns()));panel.replaceChildren(find,this.columnList=h("div"),actions)}
6
6
  this.syncColumnControls()},syncColumnControls(){const list=this.columnList,active=document.activeElement,query=foldText(this.columnFind?.value.trim()||"");if(!list)return;const focus=list.contains(active)&&active.closest(".cx-grid-column-choice").dataset.cxColumn,held=focus&&active.matches(".cx-grid-column-handle"),last=this.columns.filter(item=>!item.hidden).length<2;list.textContent="";for(const column of this.columns){const row=h("div","cx-grid-column-choice"),label=h("label"),input=h("input"),name=h("span","cx-grid-column-name"),handle=h("button","cx-grid-column-handle"),note=column.fitHidden||column.responsiveHidden?this.labels.gridResponsiveHidden:column.pin==="left"?this.labels.gridPinnedLeft:column.pin==="right"?this.labels.gridPinnedRight:"";input.type="checkbox";input.checked=!column.hidden;input.disabled=!column.hidden&&last;input.onchange=()=>{input.focus({preventScroll:true});this.setColumnVisible(column.id,input.checked)};setText(name,name.title=column.title);handle.type="button";setText(handle,"⋮⋮");handle.setAttribute("aria-label",fill(this.labels.gridReorderColumn,{column:column.title}));label.append(input,name);row.append(handle,label);row.hidden=!!query&&!foldText(column.title).includes(query);if(note){const span=h("span","cx-grid-column-note");setText(span,note);row.append(span)}
7
+ if(this.openColumnMenu&&this.tools.columnMenus!==false){const menu=gridDecorateButton(gridButton("",()=>this.openColumnMenu(column.id,menu)),"more",true);menu.classList.add("cx-grid-column-options","icon");menu.setAttribute("aria-label",fill(this.labels.gridColumnMenu,{column:column.title}));menu.setAttribute("aria-haspopup","menu");row.append(menu)}
7
8
  row.onkeydown=event=>{const target=event.altKey&&this.columns[this.columns.indexOf(column)+(event.key==="ArrowUp"?-1:event.key==="ArrowDown"?1:NaN)];if(target){event.preventDefault();event.stopPropagation();this.moveColumnChoice(column,target,event.key==="ArrowDown")}};handle.onpointerdown=event=>{if(event.button)return;event.preventDefault();handle.focus();handle.setPointerCapture(event.pointerId);let target,after;const clear=()=>{for(const item of list.children)item.classList.remove("cx-drop-before","cx-drop-after")};handle.onpointermove=move=>{clear();const over=document.elementFromPoint(move.clientX,move.clientY)?.closest(".cx-grid-column-choice");target=over!==row&&list.contains(over)&&this.columns[[...list.children].indexOf(over)];if(target){const box=over.getBoundingClientRect();after=move.clientY>box.top+box.height / 2; over.classList.add(after ? "cx-drop-after" : "cx-drop-before"); }
8
9
  };handle.onpointerup=handle.onpointercancel=up=>{handle.onpointermove=handle.onpointerup=handle.onpointercancel=null;clear();if(up.type==="pointerup"&&target)this.moveColumnChoice(column,target,after)}};row.dataset.cxColumn=column.id;list.appendChild(row);if(focus===column.id)(held?handle:input).focus({preventScroll:true})}}};function gridFilterValue(grid,column,type,raw){if(column.kind==="number"){if(!raw.trim()||!Number.isFinite(Number(raw)))throw new Error(grid.labels.gridInvalidFilter);return Number(raw)}
9
10
  if(column.kind==="datetime"){let text=raw;if(!/(?:Z|[+-]\d\d:\d\d)$/.test(raw)) {
@@ -11,7 +12,7 @@ const moment=gridDateInputMoment(raw,grid.local);if(!moment)throw new Error(grid
11
12
  gridScalar(text,column);return text}
12
13
  if(type==="boolean"){if(!["true","false"].includes(raw))throw new Error(grid.labels.gridInvalidFilter);return raw==="true"}
13
14
  if(type==="mixed")throw new Error(grid.labels.gridInvalidFilter);return raw}
14
- function gridMultiSelect(grid,columnOf,typeOf){const box=h("details","cx-ms"),summary=h("summary","cx-input sel"),text=h("span"),list=h("div","cx-ms-list"),search=h("input","cx-input"),values=h("div","cx-val-list"),selected=new Map(),facets=new Map(),key=value=>typeof value+":"+foldText(value),name=value=>cellText(value,columnOf(),grid.fmt);let loaded;search.type="search";search.placeholder=grid.labels.gridFacetSearch;search.setAttribute("aria-label",search.placeholder);summary.appendChild(text);list.append(search,values);box.append(summary,list);const sync=()=>{setText(text,[...selected.values()].map(name).join(", ")||grid.labels.gridFacetChoose);text.classList.toggle("ph",!selected.size)};const render=()=>{const all=new Map(facets);values.replaceChildren();for(const[id,value]of selected)if(!all.has(id))all.set(id,{value});for(const[id,item]of all){const label=h("label","cx-val"),check=h("input"),count=h("span","cx-val-n"),title=name(item.value);check.type="checkbox";check.checked=selected.has(id);if(item.count)setText(count,numberFormat(grid.lang).format(item.count));label.append(check,title,count);label.hidden=!foldText(title).includes(foldText(search.value));values.appendChild(label);check.addEventListener("change",()=>{if(check.checked)selected.set(id,item.value);else selected.delete(id);sync()})}};box.addEventListener("toggle",async()=>{if(box.open&&!loaded){const column=columnOf();loaded=true;try{for(const item of(await grid.columnFacetItems(column,"")).items)if(item.value!=null&&column===columnOf())facets.set(key(item.value),item)}catch(_){}}
15
+ function gridMultiSelect(grid,columnOf,typeOf){const box=h("details","cx-ms"),summary=h("summary","cx-input sel"),text=h("span"),list=h("div","cx-ms-list"),search=h("input","cx-input"),values=h("div","cx-val-list"),selected=new Map(),facets=new Map(),key=value=>gridFacetKey(value,columnOf()),name=value=>cellText(value,columnOf(),grid.fmt);let loaded;search.type="search";search.placeholder=grid.labels.gridFacetSearch;search.setAttribute("aria-label",search.placeholder);summary.appendChild(text);list.append(search,values);box.append(summary,list);const sync=()=>{setText(text,[...selected.values()].map(name).join(", ")||grid.labels.gridFacetChoose);text.classList.toggle("ph",!selected.size)};const render=()=>{const all=new Map(facets);values.replaceChildren();for(const[id,value]of selected)if(!all.has(id))all.set(id,{value});for(const[id,item]of all){const label=h("label","cx-val"),check=h("input"),count=h("span","cx-val-n"),title=name(item.value);check.type="checkbox";check.checked=selected.has(id);if(item.count)setText(count,numberFormat(grid.lang).format(item.count));label.append(check,title,count);label.hidden=!foldText(title).includes(foldText(search.value));values.appendChild(label);check.addEventListener("change",()=>{if(check.checked)selected.set(id,item.value);else selected.delete(id);sync()})}};box.addEventListener("toggle",async()=>{if(box.open&&!loaded){const column=columnOf();loaded=true;try{for(const item of(await grid.columnFacetItems(column,"")).items)if(item.value!=null&&column===columnOf())facets.set(key(item.value),item)}catch(_){}}
15
16
  render();if(box.open)list.scrollIntoView({block:"nearest"})});search.addEventListener("input",render);search.addEventListener("keydown",event=>{if(event.key!=="Enter")return;event.preventDefault();try{box._add(gridFilterValue(grid,columnOf(),typeOf(),search.value));search.value="";render();box.dispatchEvent(new Event("change",{bubbles:true}))}catch(_){}});return Object.assign(box,{_values:selected,_sync:sync,_add(value){selected.set(key(value),value);sync()},_reset(){selected.clear();facets.clear();loaded=box.open=false;sync()}})}
16
17
  Object.assign(DataGrid.prototype,{addGridFilter(parent,predicate){if(parent.children.length>=64)return;const row=h("fieldset","cx-grid-filter"),column=h("select"),operator=h("select"),type=h("select"),multiple=gridMultiSelect(this,()=>this.columns.find(item=>item.id===column.value),()=>type.value),multipleField=h("div","cx-grid-field cx-condition-value");multipleField.setAttribute("role","group");multipleField.setAttribute("aria-label",this.labels.gridConditionValue);let remove,input=h("input","cx-grid-filter-value"),current;const legend=h("legend","cx-sr-only");setText(legend,this.labels.gridFilters);row.appendChild(legend);for(const item of this.columns)if(item.filterable!==false)column.appendChild(gridOption(item.id,item.title));if(!column.options.length)return;if(predicate)column.value=predicate.column;for(const[value,name]of[["string","gridText"],["boolean","gridBoolean"]]){type.appendChild(gridOption(value,this.labels[name]))}
17
18
  if(predicate&&predicate.values.length&&predicate.values.some(value=>typeof value==="boolean")){if(predicate.values.every(value=>typeof value==="boolean"))type.value="boolean";else{type.appendChild(gridOption("mixed",this.labels.gridText+" / "+this.labels.gridBoolean));type.value="mixed"}}
@@ -30,10 +31,10 @@ return!filters.length&&!groups.length?operator==="and":!filters.length&&groups.l
30
31
  function gridColumnClauses(query,id){const clauses=[],has=group=>group.filters.some(item=>item.column===id)||group.groups.some(has),only=group=>group.filters.every(item=>item.column===id)&&group.groups.every(only);const remove=group=>{if(!group)return undefined;if(only(group)){clauses.push(group);return undefined}
31
32
  if(group.operator==="or"&&has(group))throw new Error("complex");const filters=group.filters.filter(item=>{if(item.column!==id)return true;clauses.push(item);return false}),groups=group.groups.map(remove).filter(Boolean);if(!filters.length&&!groups.length)return undefined;return gridColumnGroup(group.operator,[...filters,...groups])};const filters=(query.filters||[]).filter(item=>{if(item.column!==id)return true;clauses.push(item);return false}),filterGroup=remove(query.filterGroup);return{clauses,query:{...query,filters,filterGroup}}}
32
33
  function gridInstallColumnClause(query,id,clause){const base=gridColumnClauses(query,id).query;if(!clause)return gridQueryStart(base);if(!clause.groups)return gridQueryStart(base,{filters:[...base.filters,clause]});return gridQueryStart(base,{filterGroup:base.filterGroup?gridColumnGroup("and",[base.filterGroup,clause]):clause})}
33
- Object.assign(DataGrid.prototype,{columnFacetItems(column,search="",signal){if(!column||column.filterable===false)throw new Error("CanopyX: facets require a filterable column.");if(this.view.source.kind==="host")return this.getFacets(column.id,{search,includeCurrentFilter:false,limit:200,signal});const query=gridColumnProjection(this.queryForChange(false,true),column.id),columns=new Map(this.view.columns.map(item=>[item.id,item])),rows=gridQueryRows(this.view,query,columns,false),buckets=new Map();for(const row of rows){const value=cellValue(row.cells[column.id])??null,key=typeof value+":"+foldText(value),item=buckets.get(key);if(item)item.count++;else buckets.set(key,{value,count:1})}
34
- const items=[...buckets.values()].filter(item=>foldText(item.value).includes(foldText(search)));items.sort((a,b)=>a.value===null||b.value===null?(a.value===b.value?0:a.value===null?1:-1):
35
- gridOrder(typeof a.value,typeof b.value)||gridOrder(foldText(a.value),foldText(b.value)));return{items:items.slice(0,200),matched:rows.length,totalValues:items.length,truncated:items.length>200}},openColumnFilter(id,anchor){if(!this.acceptedQuery||this.pendingQuery)return;const column=this.columns.find(item=>item.id===id);if(!column||column.filterable===false)throw new Error("CanopyX: column filters require a filterable column.");const panel=this.openGridPopover(anchor,"cx-column-filter",column.title+": "+this.labels.gridFilterColumn,id);const head=h("div","cx-pop-h"),body=h("div","cx-pop-b"),footer=h("div","cx-pop-f"),error=h("p","cx-grid-control-error");setText(head,column.title);error.setAttribute("role","status");panel.append(head,body,error,footer);let timer,clauses,draft;const signal=this.gridPopover.controller.signal,filters=this.reportingFilters;const complex=()=>{setText(body,this.labels.gridComplexFilter);if(filters)body.appendChild(gridButton(this.labels.gridEditAsText,()=>{this.closeGridPopover(false);filters.details.open=true}))};try{({clauses}=gridColumnClauses(this.queryForChange(),id))}catch(_){complex();return}
36
- const flush=()=>{clearTimeout(timer);timer=null;if(signal.aborted||draft===undefined)return;try{const promise=this.setQuery(gridInstallColumnClause(this.queryForChange(true),id,draft));draft=undefined;this.reportingFilterSubmitEpoch=this.queryEpoch;promise.then(()=>setText(error,""),failure=>setText(error,failure.message))}catch(failure){setText(error,failure.message)}};const apply=clause=>{draft=clause??null;clearTimeout(timer);timer=setTimeout(flush,200)};apply.cancel=()=>{clearTimeout(timer);draft=undefined};signal.addEventListener("abort",()=>clearTimeout(timer),{once:true});footer.append(gridButton(this.labels.gridClearColumnFilter,()=>{clearTimeout(timer);this.setQuery(gridInstallColumnClause(this.queryForChange(true),id,null)).catch(failure=>this.emit("error",{error:failure}));this.closeGridPopover()}),gridButton(this.labels.gridDone,()=>{flush();this.closeGridPopover()}));const faceted=column.kind==="text"&&(this.view.source.kind==="inline"||this.view.reporting?.facetColumns?.includes(id)&&typeof this.source.facet==="function");if(!faceted&&clauses.some(item=>item.groups&&(item.groups.length||item.filters.length>2))||!faceted&&clauses.length>2)complex();else if(faceted)this.renderColumnValues(column,body,clauses,apply,error,signal);else this.renderColumnConditions(column,body,clauses,apply,error);this.gridPopover.position()},renderColumnValues(column,body,clauses,apply,error,signal){const search=h("input","cx-input"),all=h("input"),list=h("div","cx-val-list"),status=h("p","cx-muted");search.type="search";search.setAttribute("aria-label",this.labels.gridFilterValues);search.placeholder=this.labels.gridFilterValues;all.type="checkbox";all.checked=!clauses.length;const allLabel=h("label","cx-val-all"),allCount=h("span","cx-val-n");allCount.setAttribute("aria-hidden","true");allLabel.append(all,this.labels.gridSelectAllValues,allCount);body.append(search,allLabel,list,status);const criteria=()=>JSON.stringify([gridResultShape(gridColumnProjection(this.getQuery(),column.id)),this.snapshot.revision]),originalCriteria=criteria();this.host.addEventListener("canopyx:query",()=>{if(criteria()!==originalCriteria)this.closeGridPopover(false)},{signal});let baseline=gridColumnGroup("and",clauses),items=[],selected=new Map(),request=0,facetController;signal.addEventListener("abort",()=>facetController?.abort(),{once:true});const key=value=>typeof value+":"+foldText(value),columns=new Map([[column.id,column]]),matchesClause=(value,clause)=>typeof clause==="boolean"?clause:gridMatchesGroup({cells:{[column.id]:value}},clause,columns),predicate=(operator,values=[])=>({column:column.id,operator,values}),matches=value=>matchesClause(value,baseline),chosen=value=>selected.get(key(value))?.checked??matches(value);const syncAll=()=>{all.checked=items.length>0&&items.every(item=>chosen(item.value));all.indeterminate=!all.checked&&items.some(item=>chosen(item.value))};const commit=()=>{const withoutEdited=clause=>{if(clause.groups)return gridColumnGroup(clause.operator,[...clause.filters,...clause.groups].map(withoutEdited));if(clause.operator==="in"){const values=clause.values.filter(value=>!selected.has(key(value)));return values.length?{...clause,values}:false}
34
+ Object.assign(DataGrid.prototype,{columnFacetItems(column,search="",signal){if(!column||column.filterable===false)throw new Error("CanopyX: facets require a filterable column.");if(this.view.source.kind==="host")return this.getFacets(column.id,{search,includeCurrentFilter:false,limit:200,signal});const query=gridColumnProjection(this.queryForChange(false,true),column.id),columns=new Map(this.view.columns.map(item=>[item.id,item])),rows=gridQueryRows(this.view,query,columns,false),buckets=new Map();for(const row of rows){const value=cellValue(row.cells[column.id])??null,key=gridFacetKey(value,column),item=buckets.get(key);if(item)item.count++;else buckets.set(key,{value,count:1})}
35
+ const items=[...buckets.values()].filter(item=>foldText(cellText(item.value,column,this.fmt)).includes(foldText(search)));items.sort((a,b)=>a.value===null||b.value===null?(a.value===b.value?0:a.value===null?1:-1):
36
+ column.valueOrder?.length?gridKeyCompare(a.value,b.value,column,"asc"):gridOrder(typeof a.value,typeof b.value)||gridOrder(foldText(a.value),foldText(b.value)));return{items:items.slice(0,200),matched:rows.length,totalValues:items.length,truncated:items.length>200}},openColumnFilter(id,anchor){if(!this.acceptedQuery||this.pendingQuery)return;const column=this.columns.find(item=>item.id===id);if(!column||column.filterable===false)throw new Error("CanopyX: column filters require a filterable column.");const panel=this.openGridPopover(anchor,"cx-column-filter",column.title+": "+this.labels.gridFilterColumn,id);const head=h("div","cx-pop-h"),body=h("div","cx-pop-b"),footer=h("div","cx-pop-f"),error=h("p","cx-grid-control-error");setText(head,column.title);error.setAttribute("role","status");panel.append(head,body,error,footer);let timer,clauses,draft;const signal=this.gridPopover.controller.signal,filters=this.reportingFilters;const complex=()=>{setText(body,this.labels.gridComplexFilter);if(filters)body.appendChild(gridButton(this.labels.gridEditAsText,()=>{this.closeGridPopover(false);filters.details.open=true}))};try{({clauses}=gridColumnClauses(this.queryForChange(),id))}catch(_){complex();return}
37
+ const flush=()=>{clearTimeout(timer);timer=null;if(signal.aborted||draft===undefined)return;try{const promise=this.setQuery(gridInstallColumnClause(this.queryForChange(true),id,draft));draft=undefined;this.reportingFilterSubmitEpoch=this.queryEpoch;promise.then(()=>setText(error,""),failure=>setText(error,failure.message))}catch(failure){setText(error,failure.message)}};const apply=clause=>{draft=clause??null;clearTimeout(timer);timer=setTimeout(flush,200)};apply.cancel=()=>{clearTimeout(timer);draft=undefined};signal.addEventListener("abort",()=>clearTimeout(timer),{once:true});footer.append(gridButton(this.labels.gridClearColumnFilter,()=>{clearTimeout(timer);this.setQuery(gridInstallColumnClause(this.queryForChange(true),id,null)).catch(failure=>this.emit("error",{error:failure}));this.closeGridPopover()}),gridButton(this.labels.gridDone,()=>{flush();this.closeGridPopover()}));const faceted=column.kind==="text"&&(this.view.source.kind==="inline"||this.view.reporting?.facetColumns?.includes(id)&&typeof this.source.facet==="function");if(!faceted&&clauses.some(item=>item.groups&&(item.groups.length||item.filters.length>2))||!faceted&&clauses.length>2)complex();else if(faceted)this.renderColumnValues(column,body,clauses,apply,error,signal);else this.renderColumnConditions(column,body,clauses,apply,error);this.gridPopover.position()},renderColumnValues(column,body,clauses,apply,error,signal){const search=h("input","cx-input"),all=h("input"),list=h("div","cx-val-list"),status=h("p","cx-muted");search.type="search";search.setAttribute("aria-label",this.labels.gridFilterValues);search.placeholder=this.labels.gridFilterValues;all.type="checkbox";all.checked=!clauses.length;const allLabel=h("label","cx-val-all"),allCount=h("span","cx-val-n");allCount.setAttribute("aria-hidden","true");allLabel.append(all,this.labels.gridSelectAllValues,allCount);body.append(search,allLabel,list,status);const criteria=()=>JSON.stringify([gridResultShape(gridColumnProjection(this.getQuery(),column.id)),this.snapshot.revision]),originalCriteria=criteria();this.host.addEventListener("canopyx:query",()=>{if(criteria()!==originalCriteria)this.closeGridPopover(false)},{signal});let baseline=gridColumnGroup("and",clauses),items=[],selected=new Map(),request=0,facetController;signal.addEventListener("abort",()=>facetController?.abort(),{once:true});const key=value=>gridFacetKey(value,column),columns=new Map([[column.id,column]]),matchesClause=(value,clause)=>typeof clause==="boolean"?clause:gridMatchesGroup({cells:{[column.id]:value}},clause,columns),predicate=(operator,values=[])=>({column:column.id,operator,values}),matches=value=>matchesClause(value,baseline),chosen=value=>selected.get(key(value))?.checked??matches(value);const syncAll=()=>{all.checked=items.length>0&&items.every(item=>chosen(item.value));all.indeterminate=!all.checked&&items.some(item=>chosen(item.value))};const commit=()=>{const withoutEdited=clause=>{if(clause.groups)return gridColumnGroup(clause.operator,[...clause.filters,...clause.groups].map(withoutEdited));if(clause.operator==="in"){const values=clause.values.filter(value=>!selected.has(key(value)));return values.length?{...clause,values}:false}
37
38
  if(["eq","ne","isNull","isNotNull"].includes(clause.operator)&&selected.has(key(clause.values[0]??null)))return["ne","isNotNull"].includes(clause.operator);return clause};let clause=typeof baseline==="boolean"?baseline:withoutEdited(baseline);const included=[],excluded=[];for(const item of selected.values())if(item.checked!==matchesClause(item.value,clause))(item.checked?included:excluded).push(item.value);clause=gridColumnGroup("and",[clause,...excluded.map(value=>predicate(value==null?"isNotNull":"ne",value==null?[]:[value]))]);const additions=[],raw=included.filter(value=>value!=null);if(raw.length<included.length)additions.push(predicate("isNull"));if(raw.length)additions.push(predicate("in",raw));clause=gridColumnGroup("or",[clause,...additions]);apply(clause===true?undefined:clause===false?gridColumnGroup("and",[predicate("isNull"),predicate("isNotNull")]):clause)};const render=()=>{list.replaceChildren();for(const item of items){const label=h("label","cx-val"),check=h("input"),value=h("span","cx-facet-value"),count=h("span","cx-val-n");check.type="checkbox";check.checked=chosen(item.value);const options=column.presenter;if(options&&!['statusStrip','sparklineBars','sparklineLine','change','identifier','longText','list','tags'].includes(options.kind))gridCellRenderer(this,column,options)({id:"facet",cells:{[column.id]:item.value}},value);else setText(value,cellText(item.value,column,this.fmt)||"—");setText(count,numberFormat(this.lang).format(item.count));label.append(check,value,count);list.appendChild(label);check.addEventListener("change",()=>{if(check.checked===matches(item.value))selected.delete(key(item.value));else selected.set(key(item.value),{value:item.value,checked:check.checked});syncAll();commit()})}
38
39
  syncAll()};const load=async()=>{const generation=++request;facetController?.abort();facetController=new AbortController();setText(status,this.labels.loading);try{const page=await this.columnFacetItems(column,search.value,facetController.signal);if(signal.aborted||generation!==request)return;const previous=new Map(items.map(item=>[key(item.value),item]));for(const item of page.items)previous.set(key(item.value),item);items=[...previous.values()];render();for(const row of list.children)row.hidden=!foldText(row.querySelector('.cx-facet-value').textContent).includes(foldText(search.value));if(!search.value){search.hidden=page.totalValues<=12;setText(allCount,numberFormat(this.lang).format(page.totalValues))}
39
40
  setText(status,page.truncated?fill(this.labels.gridFacetCounts,{count:page.matched,shown:page.items.length,total:page.totalValues}):"");this.gridPopover?.position()}catch(failure){if(!signal.aborted&&generation===request&&failure.name!=="AbortError")setText(error,failure.message)}};all.addEventListener("change",()=>{baseline=all.checked;selected.clear();render();commit()});search.addEventListener("input",load);load()}});function gridDateInput(value,local){const date=new Date(value);return Number.isFinite(date.getTime())?new Date(date.getTime()-(local?date.getTimezoneOffset()*60000:0)).toISOString().slice(0,-1):""}
@@ -54,15 +55,17 @@ const predicates=definition?.filters||[];(this.view.reporting&&reportingRangeRow
54
55
  return filters.length||groups.length?{operator:segment._operator,filters,groups}:null};const readQuery=()=>{if(editingText){const value=JSON.parse(text.value);return gridQueryStart(this.queryForChange(true),{filters:value.filters||[],filterGroup:value.filterGroup})}
55
56
  const value=readBlock(root);if(!value)return gridQueryStart(this.queryForChange(true),{filters:[],filterGroup:undefined});if(value.operator==="or")return gridQueryStart(this.queryForChange(true),{filters:[],filterGroup:value});return gridQueryStart(this.queryForChange(true),{filters:value.filters,filterGroup:value.groups.length===1?value.groups[0]:value.groups.length?{operator:"and",filters:[],groups:value.groups}:undefined})};const root=makeGroup(body,{operator:"and",filters:this.queryForChange().filters||[],groups:this.queryForChange().filterGroup?[this.queryForChange().filterGroup]:[]},0);syncWords(root);form.addEventListener("input",update);form.addEventListener("change",update);form.addEventListener("submit",event=>{event.preventDefault();update()})};function gridMenuFocus(panel,event){const items=[...panel.querySelectorAll('[role="menuitem"]')].filter(item=>!item.disabled),index=items.indexOf(document.activeElement);if(!items.length)return;let next;if(["ArrowDown","ArrowUp","Home","End"].includes(event.key))next=event.key==="Home"?0:event.key==="End"?items.length-1:(index+(event.key==="ArrowDown"?1:-1)+items.length)%items.length;else if(event.key.length===1&&!event.ctrlKey&&!event.metaKey&&!event.altKey){const now=performance.now();panel._typeAhead=now-(panel._typeAt||0)>700?event.key:panel._typeAhead+event.key;panel._typeAt=now;const matches=items.map((_,i)=>(index+i+1)%items.length);next=matches.find(i=>foldText(items[i].textContent.trim()).startsWith(foldText(panel._typeAhead)))}else return;if(next===undefined)return;event.preventDefault();items.forEach((item,i)=>item.tabIndex=i===next?0:-1);items[next].focus({preventScroll:true})}
56
57
  Object.assign(DataGrid.prototype,{openGridPopover(anchor,className,title,columnId){this.closeGridPopover(false);const panel=h("div","cx-host cx-grid-overlay cx-pop "+className),controller=new AbortController(),signal=controller.signal;gridOverlayTheme(this,panel);panel.setAttribute("role","dialog");panel.setAttribute("aria-label",title);panel.tabIndex=-1;panel.id=this.id+"-popover";anchor.setAttribute("aria-controls",panel.id);anchor.setAttribute("aria-expanded","true");document.body.appendChild(panel);announceMenu(panel);const pressed=new Set();let positionPending=false,releaseFrame=0;const position=()=>{if(signal.aborted)return;if(pressed.size||releaseFrame){positionPending=true;return}
57
- positionPending=false;const current=this.headCells.find(cell=>cell.dataset.cxColumn===columnId)||anchor,bounds=current.getBoundingClientRect();if(panel.classList.toggle("cx-phone",this.host.clientWidth<=640)){panel.style.left="0";panel.style.top="auto";panel.style.bottom="0";panel.style.width="100%";panel.style.maxHeight="80vh"}
58
- else{panel.style.width="";panel.style.bottom="auto";panel.style.maxHeight=Math.min(500,global.innerHeight-16)+"px";panel.style.left=Math.max(8,Math.min(bounds.left,global.innerWidth-panel.offsetWidth-8))+"px";panel.style.top=Math.max(8,Math.min(bounds.bottom,global.innerHeight-panel.offsetHeight-8))+"px"}};this.gridPopover={panel,controller,anchor,columnId,position};panel.addEventListener("pointerdown",event=>pressed.add(event.pointerId),{signal});const release=event=>{if(!pressed.delete(event.pointerId))return;if(!releaseFrame)releaseFrame=global.requestAnimationFrame(()=>{releaseFrame=0;if(positionPending)position()})};for(const type of["pointerup","pointercancel"])global.addEventListener(type,release,{capture:true,signal});global.addEventListener("resize",position,{signal});global.addEventListener("scroll",position,{capture:true,signal});panel.addEventListener("keydown",event=>{if(event.key==="Escape"){event.preventDefault();event.stopPropagation();this.closeGridPopover()}else gridMenuFocus(panel,event)},{signal});this.lifetime.signal.addEventListener("abort",()=>controller.abort(),{once:true,signal});signal.addEventListener("abort",()=>{global.cancelAnimationFrame(releaseFrame);pressed.clear();panel.remove()},{once:true});queueMicrotask(position);return panel},openColumnMenu(id,anchor){const column=this.columns.find(item=>item.id===id),panel=this.openGridPopover(anchor,"cx-column-menu",fill(this.labels.gridColumnMenu,{column:column.title}),id);const menu=h("div");menu.setAttribute("role","menu");menu.setAttribute("aria-label",panel.getAttribute("aria-label"));panel.appendChild(menu);const icons=new Map([[this.labels.gridSortAscending,"up"],[this.labels.gridSortDescending,"down"],[this.labels.gridFilterColumn,"filter"],[this.labels.gridPinLeft,"pin"],[this.labels.gridPinRight,"pin"],[this.labels.gridFitContent,"fit"],[this.labels.gridHideColumn,"hide"]]);const paths={pin:'<path d="M9 4h6l-1 6 3 3H7l3-3zM12 13v7"/>',fit:'<path d="M4 12h16M7 9l-3 3 3 3M17 9l3 3-3 3M4 5v14M20 5v14"/>',hide:'<path d="M3 3l18 18M10.6 6.1A9.8 9.8 0 0 1 12 6c5 0 8.5 4.5 9 6-.3.8-1.2 2.3-2.6 3.6M6.4 7.6C4.6 8.9 3.4 10.8 3 12c.5 1.5 4 6 9 6 1.6 0 3-.4 4.3-1.1"/>'};const add=(label,action,disabled=false)=>{const button=gridButton(label,()=>{this.closeGridPopover(false);Promise.resolve().then(action).catch(error=>this.emit("error",{error}))});const icon=h("span","ico"),name=icons.get(label);if(name){icon.appendChild(gridIcon(name,paths[name]))}button.prepend(icon);button.classList.add("cx-mi");button.setAttribute("role","menuitem");button.tabIndex=-1;button.disabled=disabled;menu.appendChild(button)};const sort=direction=>this.setQuery(gridQueryStart(this.queryForChange(true),{sort:[...(this.queryForChange().sort||[]).filter(item=>item.column!==id),...direction?[{column:id,direction}]:[]]}));add(this.labels.gridSortAscending,()=>sort("asc"),!column.sortable||this.view.reporting?.grouping?.column===id);add(this.labels.gridSortDescending,()=>sort("desc"),!column.sortable||this.view.reporting?.grouping?.column===id);add(this.labels.gridClearSort,()=>sort(null),!(this.getQuery()?.sort||[]).some(item=>item.column===id));menu.appendChild(h("hr","cx-menu-rule"));add(this.labels.gridFilterColumn,()=>this.openColumnFilter(id,this.headRow.querySelector('[data-cx-column-menu="'+CSS.escape(id)+'"]')),column.filterable===false);menu.appendChild(h("hr","cx-menu-rule"));for(const[pin,label]of[["left","gridPinLeft"],["right","gridPinRight"],["none","gridUnpin"]])add(this.labels[label],()=>this.setColumnPinned(id,pin));menu.appendChild(h("hr","cx-menu-rule"));add(this.labels.gridFitContent,()=>this.autoFitColumn(id),!this.acceptedQuery||!!this.pendingQuery);add(this.labels.gridFitAllColumns,()=>this.autoFitColumns(),!this.acceptedQuery||!!this.pendingQuery);add(this.labels.gridHideColumn,()=>this.setColumnVisible(id,false),this.columns.filter(item=>!item.hidden).length===1);const first=panel.querySelector('[role="menuitem"]:not(:disabled)');if(first){first.tabIndex=0;first.focus()}}});let activeRecordDrawer;DataGrid.prototype.openGridDrawer=function(record,index,returnFocus,options){if(activeRecordDrawer?.contains(returnFocus))returnFocus=activeRecordDrawer._returnFocus;if(activeRecordDrawer){activeRecordDrawer._replaced=true;activeRecordDrawer.close();activeRecordDrawer.remove()}
58
+ positionPending=false;const current=this.headCells.find(cell=>cell.dataset.cxColumn===columnId)||(anchor.checkVisibility()?anchor:this.grid),bounds=current.getBoundingClientRect();if(panel.classList.toggle("cx-phone",this.host.clientWidth<=640)){panel.style.left="0";panel.style.top="auto";panel.style.bottom="0";panel.style.width="100%";panel.style.maxHeight="80vh"}
59
+ else{panel.style.width="";panel.style.bottom="auto";panel.style.maxHeight=Math.min(500,global.innerHeight-16)+"px";panel.style.left=Math.max(8,Math.min(bounds.left,global.innerWidth-panel.offsetWidth-8))+"px";panel.style.top=Math.max(8,Math.min(bounds.bottom,global.innerHeight-panel.offsetHeight-8))+"px"}};this.gridPopover={panel,controller,anchor,columnId,position};panel.addEventListener("pointerdown",event=>pressed.add(event.pointerId),{signal});const release=event=>{if(!pressed.delete(event.pointerId))return;if(!releaseFrame)releaseFrame=global.requestAnimationFrame(()=>{releaseFrame=0;if(positionPending)position()})};for(const type of["pointerup","pointercancel"])global.addEventListener(type,release,{capture:true,signal});global.addEventListener("resize",position,{signal});global.addEventListener("scroll",position,{capture:true,signal});panel.addEventListener("keydown",event=>{if(event.key==="Escape"){event.preventDefault();event.stopPropagation();this.closeGridPopover()}else gridMenuFocus(panel,event)},{signal});this.lifetime.signal.addEventListener("abort",()=>controller.abort(),{once:true,signal});signal.addEventListener("abort",()=>{global.cancelAnimationFrame(releaseFrame);pressed.clear();panel.remove()},{once:true});queueMicrotask(()=>{if(signal.aborted)return;position();if(!panel.contains(document.activeElement))panel.focus({preventScroll:true})});return panel},openColumnMenu(id,anchor){const column=this.columns.find(item=>item.id===id),panel=this.openGridPopover(anchor,"cx-column-menu",fill(this.labels.gridColumnMenu,{column:column.title}),id);const menu=h("div");menu.setAttribute("role","menu");menu.setAttribute("aria-label",panel.getAttribute("aria-label"));panel.appendChild(menu);const icons=new Map([[this.labels.gridSortAscending,"up"],[this.labels.gridSortDescending,"down"],[this.labels.gridFilterColumn,"filter"],[this.labels.gridPinLeft,"pin"],[this.labels.gridPinRight,"pin"],[this.labels.gridFitContent,"fit"],[this.labels.gridHideColumn,"hide"]]);const paths={pin:'<path d="M9 4h6l-1 6 3 3H7l3-3zM12 13v7"/>',fit:'<path d="M4 12h16M7 9l-3 3 3 3M17 9l3 3-3 3M4 5v14M20 5v14"/>',hide:'<path d="M3 3l18 18M10.6 6.1A9.8 9.8 0 0 1 12 6c5 0 8.5 4.5 9 6-.3.8-1.2 2.3-2.6 3.6M6.4 7.6C4.6 8.9 3.4 10.8 3 12c.5 1.5 4 6 9 6 1.6 0 3-.4 4.3-1.1"/>'};const add=(label,action,disabled=false)=>{const button=gridButton(label,()=>{this.closeGridPopover(false);Promise.resolve().then(action).catch(error=>this.emit("error",{error}))});const icon=h("span","ico"),name=icons.get(label);if(name){icon.appendChild(gridIcon(name,paths[name]))}button.prepend(icon);button.classList.add("cx-mi");button.setAttribute("role","menuitem");button.tabIndex=-1;button.disabled=disabled;menu.appendChild(button)};const sort=direction=>this.setQuery(gridQueryStart(this.queryForChange(true),{sort:[...(this.queryForChange().sort||[]).filter(item=>item.column!==id),...direction?[{column:id,direction}]:[]]}));add(this.labels.gridSortAscending,()=>sort("asc"),!column.sortable||this.view.reporting?.grouping?.column===id);add(this.labels.gridSortDescending,()=>sort("desc"),!column.sortable||this.view.reporting?.grouping?.column===id);add(this.labels.gridClearSort,()=>sort(null),!(this.getQuery()?.sort||[]).some(item=>item.column===id));menu.appendChild(h("hr","cx-menu-rule"));add(this.labels.gridFilterColumn,()=>this.openColumnFilter(id,this.headRow.querySelector('[data-cx-column-menu="'+CSS.escape(id)+'"]')||anchor),column.filterable===false);menu.appendChild(h("hr","cx-menu-rule"));for(const[pin,label]of[["left","gridPinLeft"],["right","gridPinRight"],["none","gridUnpin"]])add(this.labels[label],()=>this.setColumnPinned(id,pin));menu.appendChild(h("hr","cx-menu-rule"));add(this.labels.gridFitContent,()=>this.autoFitColumn(id),!this.acceptedQuery||!!this.pendingQuery);add(this.labels.gridFitAllColumns,()=>this.autoFitColumns(),!this.acceptedQuery||!!this.pendingQuery);add(this.labels.gridHideColumn,()=>this.setColumnVisible(id,false),this.columns.filter(item=>!item.hidden).length===1);const first=panel.querySelector('[role="menuitem"]:not(:disabled)');if(first){first.tabIndex=0;first.focus()}}});let activeRecordDrawer;DataGrid.prototype.openGridDrawer=function(record,index,returnFocus,options){if(activeRecordDrawer?.contains(returnFocus))returnFocus=activeRecordDrawer._returnFocus;if(activeRecordDrawer){activeRecordDrawer._replaced=true;activeRecordDrawer.close();activeRecordDrawer.remove()}
59
60
  const dialog=h("dialog","cx-host cx-grid-overlay cx-drawer"),head=h("div","cx-drawer-h"),identity=h("div"),title=h("h4"),subtitle=h("p"),body=h("div","cx-drawer-body");dialog._returnFocus=returnFocus;gridOverlayTheme(this,dialog);title.id=this.id+"-record-title";title.tabIndex=-1;title.setAttribute("aria-level","2");dialog.setAttribute("aria-labelledby",title.id);const previous=gridDecorateButton(gridButton("",()=>navigate(-1)),"up",true),next=gridDecorateButton(gridButton("",()=>navigate(1)),"down",true),close=gridDecorateButton(gridButton("",()=>dialog.close()),"close",true);previous.setAttribute("aria-label",this.labels.gridPreviousRecord);next.setAttribute("aria-label",this.labels.gridNextRecord);close.setAttribute("aria-label",this.labels.gridCloseRecord);for(const button of[previous,next,close])button.classList.add("icon");identity.append(title,subtitle);head.append(identity,previous,next,close);dialog.append(head,body);document.body.appendChild(dialog);activeRecordDrawer=dialog;this.recordDrawer=dialog;DataGrid.activeDrawers.set(dialog,this);const primary=this.visibleColumns()[0],shape=gridResultShape(this.acceptedQuery),owner=this.dataLifetime;let retiredForNavigation=false,paints=null;const placed=()=>Number.isInteger(index);const steps=()=>{previous.disabled=!placed()||index<=0||this.cursor&&index<=this.rowBounds().first;next.disabled=!placed()||index>=this.rowCount()-1};const paint=(row,position)=>{record=row;index=position;paints?.abort();paints=new AbortController();setText(title,cellText(row.cells[primary.id],primary,this.fmt)||row.id);const options=primary.presenter;const secondary=row.cells[primary.id]?.secondary||options?.secondaryColumn&&cellText(row.cells[options.secondaryColumn],this.columns.find(column=>column.id===options.secondaryColumn),this.fmt)||"";setText(subtitle,secondary);subtitle.hidden=!secondary;dialog._cxRecord=row;dialog._cxIndex=index;dialog.setAttribute("data-cx-record",row.id);dialog.setAttribute("data-cx-view",this.view.id);steps();this.paintDrawerBody(row,body,dialog,{index,signal:paints.signal});dialog.dispatchEvent(new CustomEvent("cx-drawer-paint",{detail:{id:row.id,index}}))};const navigate=async direction=>{const position=index+direction;if(!placed()||position<0||position>=this.rowCount()||this.pendingQuery)return;dialog._navigating=true;previous.disabled=next.disabled=true;try{await this.focusResult(position,false);if(this.destroyed||this.pendingQuery||gridResultShape(this.acceptedQuery)!==shape)return;const row=this.rowCache.get(position);if(!row)return;if(dialog.isConnected){paint(row.h,position);this.focusRing(title)}else if(retiredForNavigation)await this.openDetails(row.key)}catch(error){if(error.name!=="AbortError")this.emit("error",{error})}
60
61
  finally{dialog._navigating=false;if(dialog.isConnected)steps()}};dialog._cxPaint=paint;dialog.addEventListener("keydown",event=>{if(event.key==="Escape"&&!event.defaultPrevented){event.preventDefault();dialog.close()}
61
62
  else if(["ArrowUp","ArrowDown"].includes(event.key)&&!event.defaultPrevented&&!event.target.matches('input,textarea,select,[contenteditable],[role=tab]')&&!event.target.closest(".cx-record-nested")){event.preventDefault();navigate(event.key==="ArrowUp"?-1:1)}});const abort=()=>{retiredForNavigation=dialog._navigating;dialog.close()};owner.signal.addEventListener("abort",abort,{once:true});const refreshHooks=()=>DataGrid.refreshDrawerHooks(this,dialog);this.host.addEventListener("canopyx:format",refreshHooks);dialog.addEventListener("close",()=>{owner.signal.removeEventListener("abort",abort);this.host.removeEventListener("canopyx:format",refreshHooks);DataGrid.activeDrawers.delete(dialog);cancelAnimationFrame(dialog._cxHooksFrame);paints?.abort();dialog.remove();if(activeRecordDrawer===dialog)activeRecordDrawer=null;if(this.recordDrawer===dialog)this.recordDrawer=null;if(!this.recordDrawer&&!dialog._navigating&&!dialog._replaced&&!this.destroyed)gridRestoreFocus(this,returnFocus,record.id,primary)},{once:true});RECORD_IMPL.decorate?.(this,dialog,{head,identity,title,subtitle,body,options:options||{}});DataGrid.applyDrawerHooks(this,dialog);paint(record,index);const phone=this.host.clientWidth<=640;dialog.classList.toggle("cx-phone",phone);if(phone)dialog.showModal();else dialog.show();this.focusRing(title);return dialog};Object.assign(DataGrid.prototype,{paintDrawerBody(row,body,dialog,context){if(RECORD_IMPL.paint&&this.recordNeeded(row))return RECORD_IMPL.paint(this,row,body,dialog,context);body.replaceChildren();const sections=new Map();for(const column of this.columns){if(column.drawer===false)continue;const name=column.section||this.labels.gridDetailsSection;if(!sections.has(name)){const heading=h("div","cx-drawer-s"),list=h("dl","cx-dl");setText(heading,name);sections.set(name,list);body.append(heading,list)}
62
63
  const label=h("dt"),value=h("dd");setText(label,column.title);label.setAttribute("data-cx-field",column.id);value.setAttribute("data-cx-field",column.id);this.paintDrawerValue(column,row,value,context);sections.get(name).append(label,value)}},paintDrawerValue(column,row,value,context){const cell=row.cells[column.id],object=isCellObject(cell),presenter=column.presenter||(column.wrap?{kind:"longText"}:null);if(column.render&&column.hostRenderer!==false){try{if(column.render(row,value,{column:column.id,index:this.columns.indexOf(column),signal:context.signal,onDispose:cleanup=>{if(context.signal.aborted)cleanup();else context.signal.addEventListener("abort",cleanup,{once:true})}})===true)return}
63
64
  catch(error){this.emit("error",{error})}}
64
- if(presenter){gridCellRenderer(this,{...column,renderWidth:context.width||260},presenter)(row,value);return}
65
- const text=cellText(cell,column,this.fmt),span=h("span");applyTone(span,"cx-drawer-text"+(text?"":" p-missing"),toneOf(object?cell.tone:null));setText(span,text||"—");if(object&&cell.title)span.title=cell.title;value.appendChild(span);if(object)for(const badge of cell.badges||[])value.appendChild(badgeElement(badge))},drawerFields(dialog=this.recordDrawer){return dialog?[...dialog.querySelectorAll("dd[data-cx-field],.cx-record-block-b[data-cx-field]")].filter(value=>value.closest(".cx-drawer")===dialog).map(value=>{const id=value.getAttribute("data-cx-field");return{id,column:this.columns.find(column=>column.id===id)||null,term:value.previousElementSibling,value}}):[]}});DataGrid.prototype.showReportingText=function(column,text,returnFocus,rowId,mono=false){if(this.destroyed||this.pendingQuery||!this.acceptedQuery)return;this.reportingText?.close();const dialog=h("dialog","cx-host cx-grid-overlay cx-text-dialog cx-reporting-text"),head=h("div","cx-drawer-h"),title=h("h4"),content=h("pre",mono?"p-mono":"");gridOverlayTheme(this,dialog);title.id=this.id+"-text-title";title.setAttribute("aria-level","2");setText(title,column.title);dialog.setAttribute("aria-labelledby",title.id);const copy=gridDecorateButton(gridButton(this.labels.gridCopy,()=>this.writeClipboard(text).catch(error=>this.emit("error",{error}))),"copy",true);const close=gridDecorateButton(gridButton("",()=>dialog.close()),"close",true);close.classList.add("icon");close.setAttribute("aria-label",this.labels.gridCloseCell);setText(content,text);content.tabIndex=0;head.append(title,copy,close);dialog.append(head,content);document.body.appendChild(dialog);this.reportingText=dialog;const signal=this.dataLifetime.signal,abort=()=>dialog.close();signal.addEventListener("abort",abort,{once:true});dialog.addEventListener("keydown",event=>{if(event.key!=="Tab"||event.altKey||event.ctrlKey||event.metaKey)return;const controls=[content,copy,close],index=controls.indexOf(document.activeElement);event.preventDefault();controls[(index+(event.shiftKey?-1:1)+controls.length)%controls.length].focus({preventScroll:true})});dialog.addEventListener("close",()=>{signal.removeEventListener("abort",abort);dialog.remove();if(this.reportingText===dialog)this.reportingText=null;if(!this.reportingText&&!signal.aborted)gridRestoreFocus(this,returnFocus,rowId,column)},{once:true});dialog.showModal();content.focus()};DataGrid.prototype.calculateGridAggregates=async function(columns){const controller=this.statusAggregateController=new AbortController(),signal=controller.signal,owner=this.dataLifetime,abort=()=>controller.abort();owner.signal.addEventListener("abort",abort,{once:true});setText(this.statusAggregates,this.labels.loading);try{const totals=new Map(columns.map(column=>[column.id,{count:0,sum:0,min:Infinity,max:-Infinity}]));let read=0;const selected=this.prepareExport({scope:"selected",columns:columns.map(column=>column.id),signal}).rows();for await(const row of selected){if(++read>50000)throw new Error(this.labels.gridUnknownCount);for(const column of columns){const value=cellValue(row.cells[column.id]);if(typeof value!=="number")continue;const total=totals.get(column.id);total.count++;total.sum+=value;total.min=Math.min(total.min,value);total.max=Math.max(total.max,value)}}
65
+ if(presenter){gridCellRenderer(this,{...column,renderWidth:context.width||260},presenter,true,this.columns.includes(column))(row,value);const rule=columnNumericTone(column,cellValue(cell));if(rule){const label=h("span","p-sub");setText(label,rule.label);value.appendChild(label)}
66
+ return}
67
+ const text=cellText(cell,column,this.fmt),blank=object&&cellValue(cell)==null&&cell.text==="",span=h("span");applyTone(span,"cx-drawer-text"+(text||blank?"":" p-missing"),toneOf(columnValueTone(column,cell)||(object?cell.tone:null)));setText(span,text||blank?text:"—");if(object&&cell.title)span.title=cell.title;value.appendChild(span);const rule=columnNumericTone(column,cellValue(cell));if(rule){span.title=rule.label;const label=h("span","p-sub");setText(label,rule.label);value.appendChild(label)}
68
+ if(object)for(const badge of cell.badges||[])value.appendChild(badgeElement(badge))},drawerFields(dialog=this.recordDrawer){return dialog?[...dialog.querySelectorAll("dd[data-cx-field],.cx-record-block-b[data-cx-field]")].filter(value=>value.closest(".cx-drawer")===dialog).map(value=>{const id=value.getAttribute("data-cx-field");return{id,column:this.columns.find(column=>column.id===id)||null,term:value.previousElementSibling,value}}):[]}});DataGrid.prototype.showReportingText=function(column,text,returnFocus,rowId,mono=false){if(this.destroyed||this.pendingQuery||!this.acceptedQuery)return;this.reportingText?.close();const dialog=h("dialog","cx-host cx-grid-overlay cx-text-dialog cx-reporting-text"),head=h("div","cx-drawer-h"),title=h("h4"),content=h("pre",mono?"p-mono":"");gridOverlayTheme(this,dialog);title.id=this.id+"-text-title";title.setAttribute("aria-level","2");setText(title,column.title);dialog.setAttribute("aria-labelledby",title.id);const copy=gridDecorateButton(gridButton(this.labels.gridCopy,()=>this.writeClipboard(text).catch(error=>this.emit("error",{error}))),"copy",true);const close=gridDecorateButton(gridButton("",()=>dialog.close()),"close",true);close.classList.add("icon");close.setAttribute("aria-label",this.labels.gridCloseCell);setText(content,text);content.tabIndex=0;head.append(title,copy,close);dialog.append(head,content);document.body.appendChild(dialog);this.reportingText=dialog;const signal=this.dataLifetime.signal,abort=()=>dialog.close();signal.addEventListener("abort",abort,{once:true});dialog.addEventListener("keydown",event=>{if(event.key!=="Tab"||event.altKey||event.ctrlKey||event.metaKey)return;const controls=[content,copy,close],index=controls.indexOf(document.activeElement);event.preventDefault();controls[(index+(event.shiftKey?-1:1)+controls.length)%controls.length].focus({preventScroll:true})});dialog.addEventListener("close",()=>{signal.removeEventListener("abort",abort);dialog.remove();if(this.reportingText===dialog)this.reportingText=null;if(!this.reportingText&&!signal.aborted)gridRestoreFocus(this,returnFocus,rowId,column)},{once:true});dialog.showModal();content.focus()};DataGrid.prototype.calculateGridAggregates=async function(columns){const controller=this.statusAggregateController=new AbortController(),signal=controller.signal,owner=this.dataLifetime,abort=()=>controller.abort();owner.signal.addEventListener("abort",abort,{once:true});setText(this.statusAggregates,this.labels.loading);try{const totals=new Map(columns.map(column=>[column.id,{count:0,sum:0,min:Infinity,max:-Infinity}]));let read=0;const selected=this.prepareExport({scope:"selected",columns:columns.map(column=>column.id),signal}).rows();for await(const row of selected){if(++read>50000)throw new Error(this.labels.gridUnknownCount);for(const column of columns){const value=cellValue(row.cells[column.id]);if(typeof value!=="number")continue;const total=totals.get(column.id);total.count++;total.sum+=value;total.min=Math.min(total.min,value);total.max=Math.max(total.max,value)}}
66
69
  if(signal.aborted||this.destroyed)return;this.statusAggregates.replaceChildren();for(const column of columns){const total=totals.get(column.id),item=h("span"),number={format:value=>numberText(value,this.lang,column.format)};setText(item,column.title+": "+(total.count?number.format(total.sum):"—"));item.title=fill(this.labels.gridSelectionAggregate,{count:total.count,sum:number.format(total.sum),average:total.count?number.format(total.sum/total.count):"—",minimum:total.count?number.format(total.min):"—",maximum:total.count?number.format(total.max):"—"});this.statusAggregates.appendChild(item);}
67
70
  }catch(error){if(!signal.aborted&&!this.destroyed)setText(this.statusAggregates,this.labels.gridNoData)}
68
71
  finally{owner.signal.removeEventListener("abort",abort)}};Object.assign(DataGrid.prototype,COLUMNS_MENU,{buildGridFilters:buildFilters});context.GRID_REPORTING?.installFilters?.({gridDateInput});installed.add(context.DataGrid.prototype)}};(global.CanopyXParts||=[]).push(["filter-editor",part])})(typeof window!=="undefined"?window:globalThis);