@evotecit/canopyx 0.1.3 → 0.2.1

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.
Files changed (85) hide show
  1. package/CanopyX.Html/Assets/canopyx-calendar-edit.js +114 -0
  2. package/CanopyX.Html/Assets/canopyx-calendar-ics.js +87 -0
  3. package/CanopyX.Html/Assets/canopyx-calendar-recurrence.js +36 -0
  4. package/CanopyX.Html/Assets/canopyx-calendar.css +279 -0
  5. package/CanopyX.Html/Assets/canopyx-calendar.js +307 -0
  6. package/CanopyX.Html/Assets/canopyx-cell-range.css +38 -0
  7. package/CanopyX.Html/Assets/canopyx-cell-range.js +141 -0
  8. package/CanopyX.Html/Assets/canopyx-column-groups.css +18 -0
  9. package/CanopyX.Html/Assets/canopyx-column-groups.js +51 -0
  10. package/CanopyX.Html/Assets/canopyx-data.js +14 -0
  11. package/CanopyX.Html/Assets/canopyx-detail-rows.css +18 -0
  12. package/CanopyX.Html/Assets/canopyx-detail-rows.js +51 -0
  13. package/CanopyX.Html/Assets/canopyx-editing.css +44 -0
  14. package/CanopyX.Html/Assets/canopyx-editing.js +134 -0
  15. package/CanopyX.Html/Assets/canopyx-enhance.js +126 -0
  16. package/CanopyX.Html/Assets/canopyx-export.js +18 -0
  17. package/CanopyX.Html/Assets/canopyx-facets.css +86 -0
  18. package/CanopyX.Html/Assets/canopyx-facets.js +65 -0
  19. package/CanopyX.Html/Assets/canopyx-filter-editor.css +15 -18
  20. package/CanopyX.Html/Assets/canopyx-filter-editor.js +23 -17
  21. package/CanopyX.Html/Assets/canopyx-format-editor.css +77 -0
  22. package/CanopyX.Html/Assets/canopyx-format-editor.js +30 -0
  23. package/CanopyX.Html/Assets/canopyx-formatting.css +40 -0
  24. package/CanopyX.Html/Assets/canopyx-formatting.js +101 -0
  25. package/CanopyX.Html/Assets/canopyx-grid-module.mjs +352 -246
  26. package/CanopyX.Html/Assets/canopyx-grid.css +139 -117
  27. package/CanopyX.Html/Assets/canopyx-grid.js +352 -246
  28. package/CanopyX.Html/Assets/canopyx-grouping.css +39 -0
  29. package/CanopyX.Html/Assets/canopyx-grouping.js +85 -0
  30. package/CanopyX.Html/Assets/canopyx-highlight.css +34 -0
  31. package/CanopyX.Html/Assets/canopyx-highlight.js +80 -0
  32. package/CanopyX.Html/Assets/canopyx-history.js +13 -0
  33. package/CanopyX.Html/Assets/canopyx-interactive.css +18 -6
  34. package/CanopyX.Html/Assets/canopyx-interactive.js +179 -100
  35. package/CanopyX.Html/Assets/canopyx-labels-de.js +304 -0
  36. package/CanopyX.Html/Assets/canopyx-labels-es.js +304 -0
  37. package/CanopyX.Html/Assets/canopyx-labels-fr.js +529 -0
  38. package/CanopyX.Html/Assets/canopyx-labels-it.js +529 -0
  39. package/CanopyX.Html/Assets/canopyx-labels-pl.js +304 -0
  40. package/CanopyX.Html/Assets/canopyx-layouts.css +48 -0
  41. package/CanopyX.Html/Assets/canopyx-layouts.js +39 -0
  42. package/CanopyX.Html/Assets/canopyx-link.css +19 -0
  43. package/CanopyX.Html/Assets/canopyx-link.js +38 -0
  44. package/CanopyX.Html/Assets/canopyx-links.js +14 -0
  45. package/CanopyX.Html/Assets/canopyx-live.css +24 -0
  46. package/CanopyX.Html/Assets/canopyx-live.js +88 -0
  47. package/CanopyX.Html/Assets/canopyx-module.mjs +180 -100
  48. package/CanopyX.Html/Assets/canopyx-pager.css +10 -0
  49. package/CanopyX.Html/Assets/canopyx-pager.js +14 -0
  50. package/CanopyX.Html/Assets/canopyx-record-panel.css +111 -0
  51. package/CanopyX.Html/Assets/canopyx-record-panel.js +178 -0
  52. package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +406 -342
  53. package/CanopyX.Html/Assets/canopyx-reporting.css +140 -135
  54. package/CanopyX.Html/Assets/canopyx-reporting.js +406 -342
  55. package/CanopyX.Html/Assets/canopyx-search.js +32 -0
  56. package/CanopyX.Html/Assets/canopyx-timeline.css +80 -0
  57. package/CanopyX.Html/Assets/canopyx-timeline.js +88 -0
  58. package/CanopyX.Html/Assets/canopyx-tree-tools.css +103 -0
  59. package/CanopyX.Html/Assets/canopyx-tree-tools.js +92 -0
  60. package/CanopyX.Html/Assets/canopyx.css +17 -5
  61. package/CanopyX.Html/Assets/canopyx.js +175 -96
  62. package/JavaScript/calendar.d.ts +362 -0
  63. package/JavaScript/cell-ranges.d.ts +76 -0
  64. package/JavaScript/column-groups.d.ts +32 -0
  65. package/JavaScript/editing.d.ts +257 -0
  66. package/JavaScript/enhance.d.ts +202 -0
  67. package/JavaScript/facets.d.ts +58 -0
  68. package/JavaScript/formatting.d.ts +88 -0
  69. package/JavaScript/grid.d.ts +409 -56
  70. package/JavaScript/hosts.d.ts +177 -2
  71. package/JavaScript/index.d.ts +53 -4
  72. package/JavaScript/live.d.ts +115 -0
  73. package/JavaScript/locales/de.mjs +304 -0
  74. package/JavaScript/locales/es.mjs +304 -0
  75. package/JavaScript/locales/fr.d.ts +6 -0
  76. package/JavaScript/locales/fr.mjs +527 -0
  77. package/JavaScript/locales/it.d.ts +6 -0
  78. package/JavaScript/locales/it.mjs +527 -0
  79. package/JavaScript/locales/pl.mjs +304 -0
  80. package/JavaScript/plugins.d.ts +113 -0
  81. package/JavaScript/protocol.d.ts +139 -6
  82. package/JavaScript/records.d.ts +136 -0
  83. package/README.md +61 -9
  84. package/README.nuget.md +1 -1
  85. package/package.json +99 -3
@@ -0,0 +1,38 @@
1
+ /* Cell ranges (canopyx.cell-range.source): a tint,an outline on each range's outer edges,touch handles and the fill handle. */
2
+ .cx-data-grid{
3
+ --cx-range-fill:color-mix(in srgb,var(--cx-accent) 14%,transparent);
4
+ --cx-range-edge:var(--cx-accent);
5
+ }
6
+ .cx-data-grid.cx-cell-ranges .cx-canvas .cx-cell{-webkit-touch-callout:none;}
7
+ .cx-data-grid .cx-row .cx-cell.cx-range{
8
+ --cx-rt:0px;--cx-rb:0px;--cx-rl:0px;--cx-rr:0px;
9
+ background-image:linear-gradient(var(--cx-range-edge),var(--cx-range-edge)),linear-gradient(var(--cx-range-edge),var(--cx-range-edge)),
10
+ linear-gradient(var(--cx-range-edge),var(--cx-range-edge)),linear-gradient(var(--cx-range-edge),var(--cx-range-edge)),linear-gradient(var(--cx-range-fill),var(--cx-range-fill));
11
+ background-size:100% var(--cx-rt),100% var(--cx-rb),var(--cx-rl) 100%,var(--cx-rr) 100%,100% 100%;
12
+ background-position:top,bottom,left,right,0 0;background-repeat:no-repeat;
13
+ }
14
+ .cx-data-grid .cx-row .cx-cell.cx-range-t{--cx-rt:var(--cx-size-2px);}
15
+ .cx-data-grid .cx-row .cx-cell.cx-range-b{--cx-rb:var(--cx-size-2px);}
16
+ .cx-data-grid .cx-row .cx-cell.cx-range-l{--cx-rl:var(--cx-size-2px);}
17
+ .cx-data-grid .cx-row .cx-cell.cx-range-r{--cx-rr:var(--cx-size-2px);}
18
+ .cx-data-grid .cx-row .cx-cell.cx-range-fill{outline:var(--cx-size-1px) dashed var(--cx-range-edge);outline-offset:var(--cx-size-neg-2px);}
19
+ .cx-data-grid .cx-row .cx-cell:is(.cx-range,.cx-range-fill):not([data-cx-pin]){position:relative;}
20
+ .cx-data-grid :is(.cx-range-handle,.cx-fill-handle){position:absolute;z-index:6;box-sizing:border-box;background:var(--cx-range-edge);touch-action:none;}
21
+ .cx-data-grid .cx-range-handle{width:var(--cx-size-14px);height:var(--cx-size-14px);border:var(--cx-size-2px) solid var(--cx-surface);border-radius:50%;box-shadow:0 0 0 var(--cx-size-1px) var(--cx-range-edge);}
22
+ .cx-data-grid .cx-range-handle-start{left:0;top:0;}
23
+ .cx-data-grid .cx-range-handle-end{right:0;bottom:0;}
24
+ .cx-data-grid .cx-fill-handle{right:0;bottom:0;width:var(--cx-size-7px);height:var(--cx-size-7px);border:var(--cx-size-1px) solid var(--cx-surface);cursor:crosshair;}
25
+ .cx-data-grid .cx-fill-handle::before{content:"";position:absolute;inset:calc(var(--cx-size-6px) * -1);}
26
+ .cx-data-grid .cx-paste-report{display:grid;grid-template-columns:1fr auto;align-items:center;gap:var(--cx-size-4px) var(--cx-size-8px);margin:0 0 var(--cx-size-6px);
27
+ padding:var(--cx-size-6px) var(--cx-size-6px) var(--cx-size-6px) var(--cx-size-12px);border:var(--cx-size-1px) solid var(--cx-line);border-left:var(--cx-size-3px) solid var(--cx-tone-warning);
28
+ border-radius:var(--cx-radius-sm);background:var(--cx-surface);font-size:var(--cx-size-13px);}
29
+ .cx-data-grid .cx-paste-report p{margin:0;}
30
+ .cx-data-grid .cx-paste-report details{grid-column:1 / -1;}
31
+ .cx-data-grid .cx-paste-report summary{cursor:pointer;color:var(--cx-accent-text,var(--cx-accent));}
32
+ .cx-data-grid .cx-paste-report ul{margin:var(--cx-size-4px) 0 0;padding-left:var(--cx-size-20px);max-height:var(--cx-size-160px);overflow:auto;}
33
+ @media (forced-colors:active){
34
+ .cx-data-grid{--cx-range-fill:Highlight;--cx-range-edge:CanvasText;}
35
+ .cx-data-grid .cx-row .cx-cell.cx-range{forced-color-adjust:none;color:HighlightText;background-color:Highlight;--cx-range-edge:HighlightText;}
36
+ .cx-data-grid .cx-row .cx-cell.cx-range *{forced-color-adjust:none;color:HighlightText;background-color:transparent;border-color:HighlightText;fill:currentColor;stroke:currentColor;}
37
+ .cx-data-grid .cx-row .cx-cell.cx-range :is(.cx-range-handle,.cx-fill-handle){background:HighlightText;border-color:Highlight;}
38
+ }
@@ -0,0 +1,141 @@
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"e99a39e7cccc57b1",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,fill,columnWidth,foldText,gridButton,gridIcon,gridDecorateButton,cellText,cellValue,isCellObject,numberFormat,gridFreeze,gridMomentFromParts,requireIdentifier,loadGridFilterEditor,CELL_CONTROLS,reachable,gridExportField}=context;const CR_LIMIT=200000;const crBad=problem=>{throw new Error("CanopyX: "+problem+".")};function crOptions(grid){const declared=grid.opts.rangeSelection??grid.view.rangeSelection,clipboard=Object.assign({},grid.view.clipboard,grid.opts.clipboard);const object=declared!==null&&typeof declared==="object"&&!Array.isArray(declared),range=object?declared:{};const flag=value=>value===undefined||typeof value==="boolean";if(declared!==undefined&&typeof declared!=="boolean"&&!object||!flag(range.enabled)||!flag(range.multiple)||!flag(range.fillHandle)||
2
+ !flag(clipboard.copyHeaders)||!flag(clipboard.paste)||clipboard.copyValues!==undefined&&!["display","raw"].includes(clipboard.copyValues))
3
+ crBad("rangeSelection takes true or { enabled, multiple, fillHandle }, and clipboard { copyHeaders, copyValues, paste }");return Object.freeze({enabled:declared===true||object&&range.enabled!==false,multiple:range.multiple!==false,fillHandle:!!range.fillHandle,headers:!!clipboard.copyHeaders,values:clipboard.copyValues||"display",paste:clipboard.paste!==false})}
4
+ const crRows=grid=>grid.rowProjection||grid;function crBounds(grid){if(grid.rowProjection)return{first:0,last:grid.rowProjection.rowCount()- 1};const bounds=grid.rowBounds();return{first:Math.max(0,bounds.first),last:bounds.last}}
5
+ function crDataBounds(grid,top,bottom){const projection=grid.rowProjection;if(!projection)return{first:top,count:Math.max(0,bottom-top+1)};let first=-1,count=0;for(const range of projection.recordWindow(top,bottom).ranges){const start=range.first +(projection.recordIndex(range.first)<top?1:0);const end=range.last -(projection.recordIndex(range.last)>bottom?1:0);if(start>end)continue;if(first<0)first=projection.recordIndex(start);count +=end-start+1}
6
+ return{first,count}}
7
+ function crRect(grid,range){const left=grid.cols.findIndex(column=>column.id===range.anchor.column),right=grid.cols.findIndex(column=>column.id===range.end.column);if(left<0||right<0)return null;return{top:Math.min(range.anchor.row,range.end.row),bottom:Math.max(range.anchor.row,range.end.row),left:Math.min(left,right),right:Math.max(left,right)}}
8
+ const crIndex=(grid,id)=>grid.cols.findIndex(column=>column.id===id);const crRects=grid=>grid.cellRanges.map(range=>crRect(grid,range)).filter(Boolean);function crHit(grid,target){const cell=target?.closest?.(".cx-cell"),row=cell?.parentElement;if(!row||row._kind!=="node"||!row._w||row._index===undefined||!grid.canvas.contains(row))return null;const index=row._cells.findIndex(slot=>slot.cell===cell),column=grid.cols[index];return column?{row:row._index,column:column.id,cell,element:row}:null}
9
+ function crPaint(grid){for(const cell of grid.cellPainted||[]){cell.classList.remove("cx-range","cx-range-t","cx-range-b","cx-range-l","cx-range-r","cx-range-fill");cell.removeAttribute("aria-selected")}
10
+ grid.cellPainted=[];const rects=crRects(grid),fill=grid.cellFillPreview;if(!rects.length&&!fill){crHandles(grid,{});return}
11
+ let corner=null,start=null;const last=rects[rects.length-1];for(const row of grid.rendered.values()){if(row._kind!=="node"||row._index===undefined)continue;const index=row._index,cells=row._cells;for(const rect of rects){if(index<rect.top||index>rect.bottom)continue;for(let column=rect.left;column<=rect.right&&column<cells.length;column++){const cell=cells[column].cell;cell.classList.add("cx-range");if(index===rect.top)cell.classList.add("cx-range-t");if(index===rect.bottom)cell.classList.add("cx-range-b");if(column===rect.left)cell.classList.add("cx-range-l");if(column===rect.right)cell.classList.add("cx-range-r");cell.setAttribute("aria-selected","true");grid.cellPainted.push(cell)}}
12
+ if(fill&&index>=fill.top&&index<=fill.bottom)
13
+ for(let column=fill.left;column<=fill.right&&column<cells.length;column++){cells[column].cell.classList.add("cx-range-fill");grid.cellPainted.push(cells[column].cell)}
14
+ if(last&&index===last.bottom&&cells[last.right])corner=cells[last.right].cell;if(last&&index===last.top&&cells[last.left])start=cells[last.left].cell}
15
+ crHandles(grid,{start:grid.cellHandles&&start,end:grid.cellHandles&&corner,fill:grid.cellOptions.fillHandle&&rects.length===1&&!fill&&!grid.cellHandles&&corner})}
16
+ function crHandles(grid,places){const make=className=>{const node=h("span",className);node.setAttribute("aria-hidden","true");return node};grid.cellHandleNodes||={start:make("cx-range-handle cx-range-handle-start"),end:make("cx-range-handle cx-range-handle-end"),fill:Object.assign(make("cx-fill-handle"),{title:grid.labels.gridFillHandle})};for(const[name,node]of Object.entries(grid.cellHandleNodes)){node.dataset.cxCorner=name;const cell=places[name];if(!cell)node.remove();else if(node.parentNode!==cell)cell.appendChild(node)}}
17
+ function crAnnounce(grid){const rects=crRects(grid),count=value=>numberFormat(grid.lang).format(value);const rows=rect=>crDataBounds(grid,rect.top,rect.bottom).count,size=rect=>rows(rect)*(rect.right-rect.left+1);const text=!rects.length?grid.cellAnnounced?grid.labels.gridCellRangeCleared:"":rects.length===1?
18
+ fill(grid.labels.gridCellRangeSelected,{rows:count(rows(rects[0])),columns:count(rects[0].right-rects[0].left+1)}):
19
+ fill(grid.labels.gridCellRangesSelected,{ranges:count(rects.length),cells:count(rects.reduce((sum,rect)=>sum+size(rect),0))});grid.cellAnnounced=rects.length>0;if(text)setText(grid.live,text)}
20
+ function crChanged(grid,announce=true){crPaint(grid);if(announce)crAnnounce(grid);grid.emit("cellRangeChange",{ranges:grid.getCellRanges()})}
21
+ function crReveal(grid,row,column){const rows=crRows(grid),item=rows.rowItem(row);if(item){grid.revealGridRow(item,row,rows);grid.render()}
22
+ const index=grid.cols.findIndex(item=>item.id===column),cell=index>=0&&grid.headCells[index];if(!cell||cell.dataset.cxPin)return;const left=cell.offsetLeft,right=left+cell.offsetWidth,view=grid.grid,start=view.scrollLeft +(grid.pinnedLeftWidth||0),end=view.scrollLeft+view.clientWidth -(grid.pinnedRightWidth||0);if(left<start)view.scrollLeft -=start-left;else if(right>end)view.scrollLeft +=right-end}
23
+ const CR_CONTROLS="a[href],button,input,select,textarea,summary,label,[contenteditable]:not([contenteditable=false]),[role=button],[role=checkbox],.cx-hidden-fields";function crHitAt(grid,x,y){const frame=grid.grid.getBoundingClientRect(),top=frame.top+grid.head.offsetHeight,left=frame.left+grid.grid.clientLeft;const px=Math.max(left+1,Math.min(x,left+grid.grid.clientWidth-2)),py=Math.max(top+1,Math.min(y,frame.top+grid.grid.clientHeight-2));for(const element of document.elementsFromPoint(px,py)){if(element.classList.contains("cx-range-handle")||element.classList.contains("cx-fill-handle"))continue;const hit=crHit(grid,element);if(hit)return hit;if(element===grid.grid)break}
24
+ return null}
25
+ function crTrack(grid,event,update,done){const pointer=event.pointerId,x0=event.clientX,y0=event.clientY;let x=x0,y=y0,frame=0,last=null,moved=false;const step=()=>{frame=requestAnimationFrame(step);if(!moved)return;const box=grid.grid.getBoundingClientRect(),top=box.top+grid.head.offsetHeight;const dy=y<top+24?-Math.ceil((top+24-y)/ 2) : y > box.bottom - 24 ? Math.ceil((y - box.bottom + 24) / 2) : 0;
26
+ const dx=x<box.left+24?-12:x>box.right-24?12:0;if(dy)grid.setLogicalTop(Math.max(0,grid.logicalTop()+ Math.min(40,Math.max(-40,dy))));if(dx)grid.grid.scrollLeft +=dx;if(dx||dy){grid.render();look()}};const look=()=>{const hit=crHitAt(grid,x,y);if(hit&&(!last||hit.row!==last.row||hit.column!==last.column)){last=hit;update(hit)}};const move=ev=>{if(ev.pointerId!==pointer)return;x=ev.clientX;y=ev.clientY;if(!moved&&Math.hypot(x-x0,y-y0)<4)return;moved=true;look()};const stop=ev=>{if(ev&&ev.pointerId!==undefined&&ev.pointerId!==pointer)return;cancelAnimationFrame(frame);global.removeEventListener("pointermove",move);global.removeEventListener("pointerup",stop);global.removeEventListener("pointercancel",stop);grid.lifetime.signal.removeEventListener("abort",stop);if(grid.cellDrag===stop)grid.cellDrag=null;done(ev?.type==="pointerup")};grid.cellDrag?.();grid.cellDrag=stop;global.addEventListener("pointermove",move);global.addEventListener("pointerup",stop);global.addEventListener("pointercancel",stop);grid.lifetime.signal.addEventListener("abort",stop,{once:true});frame=requestAnimationFrame(step)}
27
+ function crSwallowClick(grid){const swallow=event=>{event.preventDefault();event.stopPropagation()};grid.grid.addEventListener("click",swallow,{capture:true,once:true});setTimeout(()=>grid.grid.removeEventListener("click",swallow,{capture:true}),400)}
28
+ function crPointerDown(grid,event){if(!grid.cellOptions.enabled||event.target.closest(".cx-range-handle,.cx-fill-handle"))return;if(event.pointerType==="touch"){crTouchStart(grid,event);return}
29
+ const hit=crHit(grid,event.target);if(event.button!==0||!hit||event.target.closest(CR_CONTROLS))return;event.preventDefault();grid.cellHandles=false;const additive=(event.ctrlKey||event.metaKey)&&grid.cellOptions.multiple,at={row:hit.row,column:hit.column};const before=JSON.stringify(grid.cellRanges);let range;if(event.shiftKey&&grid.cellAnchor){range={anchor:grid.cellAnchor,end:at};grid.cellRanges=additive?[...grid.cellRanges.slice(0,-1),range]:[range]}else{grid.cellAnchor=at;range={anchor:at,end:at};grid.cellRanges=additive?[...grid.cellRanges,range]:[];if(additive)grid.cellRanges=grid.cellRanges.slice(0,1000)}
30
+ crPaint(grid);let dragged=false;crTrack(grid,event,next=>{if(next.row===range.anchor.row&&next.column===range.anchor.column&&!grid.cellRanges.includes(range))return;range.end={row:next.row,column:next.column};dragged=true;if(!grid.cellRanges.includes(range))grid.cellRanges=[...grid.cellRanges,range];crPaint(grid)},()=>{if(dragged||event.shiftKey||event.ctrlKey||event.metaKey)crSwallowClick(grid);if(JSON.stringify(grid.cellRanges)!==before||event.shiftKey||additive)crChanged(grid);grid.cellFocusAfterClick=!dragged&&!event.shiftKey&&!additive?hit:null;if(dragged||event.shiftKey||additive)crFocus(grid,grid.cellAnchor)})}
31
+ function crFocus(grid,at){if(!at)return;const row=crRows(grid).rowItem(at.row),index=grid.cols.findIndex(column=>column.id===at.column);if(row&&!row.status&&index>=0&&grid.rendered.get(row))grid.focusCell(row,index,null)}
32
+ const CR_PRESS_MS=500;function crTouchStart(grid,event){const hit=crHit(grid,event.target);if(!hit||event.target.closest(CR_CONTROLS))return;const pointer=event.pointerId,x0=event.clientX,y0=event.clientY;crGuard(grid,true);const quit=pressed=>{clearTimeout(timer);grid.lifetime.signal.removeEventListener("abort",quit);global.removeEventListener("pointermove",watch);global.removeEventListener("pointerup",lift);global.removeEventListener("pointercancel",quit);if(pressed!==true)crGuard(grid,false)};const watch=ev=>{if(ev.pointerId===pointer&&Math.hypot(ev.clientX-x0,ev.clientY-y0)>8)quit()};const lift=ev=>{if(ev.pointerId!==pointer)return;quit();if(grid.cellRanges.length){grid.cellRanges=[];grid.cellHandles=false;crChanged(grid)}};const timer=setTimeout(()=>{quit(true);if(grid.destroyed)return;const inside=crRects(grid).some(rect=>hit.row>=rect.top&&hit.row<=rect.bottom&&crIndex(grid,hit.column)>=rect.left&&crIndex(grid,hit.column)<=rect.right);crSwallowClick(grid);if(inside){crGuard(grid,false);crMenu(grid,hit.cell);return}
33
+ const at={row:hit.row,column:hit.column},range={anchor:at,end:at};grid.cellAnchor=at;grid.cellRanges=[range];grid.cellHandles=true;crTouching(grid,true);crChanged(grid,false);crTrack(grid,event,next=>{range.end={row:next.row,column:next.column};crPaint(grid)},()=>{crTouching(grid,false);crChanged(grid)})},CR_PRESS_MS);grid.lifetime.signal.addEventListener("abort",quit,{once:true});global.addEventListener("pointermove",watch);global.addEventListener("pointerup",lift);global.addEventListener("pointercancel",quit)}
34
+ function crTouching(grid,on){grid.cellTouching=on;if(!on)crGuard(grid,false)}
35
+ function crGuard(grid,on){grid.cellTouchGuard||=event=>{if(grid.cellTouching)event.preventDefault()};if(on)grid.grid.addEventListener("touchmove",grid.cellTouchGuard,{passive:false});else grid.grid.removeEventListener("touchmove",grid.cellTouchGuard,{passive:false})}
36
+ function crHandleDown(grid,event){const handle=event.target.closest(".cx-range-handle"),range=grid.cellRanges[grid.cellRanges.length-1];if(!handle||!range)return;event.preventDefault();event.stopPropagation();const rect=crRect(grid,range);if(!rect)return;const corner=handle.dataset.cxCorner==="start";range.anchor={row:corner?rect.bottom:rect.top,column:grid.cols[corner?rect.right:rect.left].id};range.end={row:corner?rect.top:rect.bottom,column:grid.cols[corner?rect.left:rect.right].id};crGuard(grid,true);crTouching(grid,true);crTrack(grid,event,next=>{range.end={row:next.row,column:next.column};crPaint(grid)},()=>{crTouching(grid,false);crChanged(grid)})}
37
+ const CR_STEPS={ArrowUp:[-1,0],ArrowDown:[1,0],ArrowLeft:[0,-1],ArrowRight:[0,1],PageUp:[-1,0],PageDown:[1,0],Home:[0,-1],End:[0,1]};function crKey(grid,event){if(event.defaultPrevented||event.isComposing||event.altKey||!grid.cellOptions.enabled)return;const target=event.target,typing=target.closest("input,textarea,select,[contenteditable]:not([contenteditable=false])");if(typing||!grid.canvas.contains(target))return;const key=event.key,letter=key.length===1?key.toLowerCase():"",command=event.ctrlKey||event.metaKey,hit=crHit(grid,target);const consume=()=>{event.preventDefault();event.stopPropagation()};if(command&&letter==="c"&&(grid.cellRanges.length||hit)&&!String(global.getSelection()||"")){consume();crCopyKey(grid);return}
38
+ if(command&&letter==="a"){consume();grid.selectAllCells();return}
39
+ if(command&&letter==="d"&&grid.cellOptions.fillHandle&&grid.cellRanges.length===1){consume();crFillDown(grid).catch(error=>grid.emit("error",{error}));return}
40
+ if(key==="Escape"&&grid.cellRanges.length){consume();grid.clearCellRanges();return}
41
+ if((key==="ContextMenu"||key==="F10"&&event.shiftKey)&&hit){consume();crMenu(grid,hit.cell);return}
42
+ if(!CR_STEPS[key])return;if(!event.shiftKey){if(grid.cellRanges.length&&!command){grid.cellRanges=[];grid.cellHandles=false;crChanged(grid)}return}
43
+ if(!hit)return;consume();let range=grid.cellRanges[grid.cellRanges.length-1];if(!range||range.anchor.row!==hit.row||range.anchor.column!==hit.column){range={anchor:{row:hit.row,column:hit.column},end:{row:hit.row,column:hit.column}};grid.cellAnchor=range.anchor;grid.cellRanges=[range]}
44
+ const bounds=crBounds(grid),[dRow,dColumn]=CR_STEPS[key],last=grid.cols.length-1;let row=range.end.row,column=Math.max(0,grid.cols.findIndex(item=>item.id===range.end.column));if(key==="PageUp"||key==="PageDown")row=crRows(grid)===grid?grid.gridPageStep({_ri:row,hpx:grid.rowH},dRow):row+dRow*Math.max(1,Math.floor(grid.viewportHeight / grid.rowH || 10));
45
+ else if(key==="Home"||key==="End"){column=dColumn<0?0:last;if(command)row=dColumn<0?bounds.first:bounds.last}
46
+ else if(command){if(dRow)row=dRow<0?bounds.first:bounds.last;if(dColumn)column=dColumn<0?0:last}
47
+ else{row +=dRow;column +=dColumn}
48
+ range.end={row:Math.max(bounds.first,Math.min(bounds.last,row)),column:grid.cols[Math.max(0,Math.min(last,column))].id};crReveal(grid,range.end.row,range.end.column);crChanged(grid)}
49
+ function crMenu(grid,cell){loadGridFilterEditor().then(()=>{if(grid.destroyed||!cell.isConnected)return;const labels=grid.labels,panel=grid.openGridPopover(cell,"cx-column-menu cx-cell-menu",labels.gridCellMenu),menu=h("div");grid.gridPopover.controller.signal.addEventListener("abort",()=>queueMicrotask(()=>{cell.removeAttribute("aria-controls");cell.removeAttribute("aria-expanded")}),{once:true});menu.setAttribute("role","menu");menu.setAttribute("aria-label",panel.getAttribute("aria-label"));panel.appendChild(menu);const add=(label,icon,action,disabled)=>{const button=gridButton(label,()=>{grid.closeGridPopover();Promise.resolve().then(action).catch(error=>grid.emit("error",{error}))});const mark=h("span","ico");if(icon)mark.appendChild(gridIcon(icon));button.prepend(mark);button.classList.add("cx-mi");button.setAttribute("role","menuitem");button.tabIndex=-1;button.disabled=!!disabled;menu.appendChild(button)};const copy=headers=>grid.copyCellRanges({headers});add(labels.gridCopyCells,"copy",()=>copy(false));add(labels.gridCopyCellsWithHeaders,null,()=>copy(true));if(grid.cellOptions.paste)add(labels.gridPasteCells,null,()=>global.navigator.clipboard.readText().then(text=>crPaste(grid,text,null,"clipboard")),typeof global.navigator.clipboard?.readText!=="function");menu.appendChild(h("hr","cx-menu-rule"));add(labels.gridSelectAllCells,null,()=>grid.selectAllCells());add(labels.gridClearCellRanges,"close",()=>grid.clearCellRanges(),!grid.cellRanges.length);const first=menu.querySelector('[role="menuitem"]:not(:disabled)');if(first){first.tabIndex=0;first.focus()}}).catch(error=>grid.emit("error",{error}))}
50
+ const crPad=value=>String(value).padStart(2,"0");function crDate(value,local){const date=new Date(value);if(!Number.isFinite(date.getTime()))return String(value);const part=name=>local?date["get"+name]():date["getUTC"+name]();return part("FullYear")+ "-"+crPad(part("Month")+ 1)+ "-"+crPad(part("Date"))+ " "+crPad(part("Hours"))+ ":"+crPad(part("Minutes"))+ ":"+crPad(part("Seconds"))}
51
+ function crField(value,typed){return gridExportField(value,"\t",typed)}
52
+ const crHtml=text=>String(text).replace(/[&<>"']/g, character => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character]);
53
+ function crCell(grid,row,column,values){const cell=row.h.cells[column.id],value=cellValue(cell),shown=cellText(cell,column,grid.fmt);const typed=values==="raw"||!isCellObject(cell)||typeof cell.text!=="string";if(value==null||value==="")return{text:values==="raw"?"":shown,html:"<td>"+crHtml(values==="raw"?"":shown)+ "</td>"};if(column.kind==="number"&&typeof value==="number")
54
+ return{typed,text:values==="raw"?value:shown,html:'<td x:num="'+value+"\" data-sheets-value='{\"1\":3,\"3\":"+value+"}' style=\"mso-number-format:General\">"+crHtml(values==="raw"?String(value):shown)+ "</td>"};if(column.kind==="datetime"){const text=crDate(value,grid.local);return{typed,text:values==="raw"?text:shown,html:'<td style="mso-number-format:\'yyyy\\-mm\\-dd\\ hh\\:mm\\:ss\'">'+crHtml(text)+ "</td>"}}
55
+ if(typeof value==="boolean")return{text:values==="raw"?(value?"TRUE":"FALSE"):shown,html:'<td x:bool="' +(value?"TRUE":"FALSE")+ '">'+crHtml(value?"TRUE":"FALSE")+ "</td>"};const text=values==="raw"?String(value):shown;return{text,html:"<td data-sheets-value='"+crHtml(JSON.stringify({1:2,2:text}))+ "' style=\"mso-number-format:'\\@'\">"+crHtml(text)+ "</td>"}}
56
+ function crBlocks(grid,rects){if(!rects.length)return[];if(rects.every(rect=>rect.left===rects[0].left&&rect.right===rects[0].right)){const rows=new Set();for(const rect of[...rects].sort((a,b)=>a.top-b.top))for(let row=rect.top;row<=rect.bottom;row++)rows.add(row);return[{rows:[...rows].sort((a,b)=>a-b),left:rects[0].left,right:rects[0].right}]}
57
+ return[...rects].sort((a,b)=>a.top-b.top||a.left-b.left).map(rect=>
58
+ ({rows:Array.from({length:rect.bottom-rect.top+1},(_,index)=>rect.top+index),left:rect.left,right:rect.right}))}
59
+ function crCount(blocks){let cells=0;for(const block of blocks)cells +=block.rows.length*(block.right-block.left+1);if(cells>CR_LIMIT)crBad("a copy or paste covers at most "+CR_LIMIT+" cells")}
60
+ function crLoaded(grid,blocks){const rows=crRows(grid),loaded=new Map();crCount(blocks);for(const block of blocks)for(const position of block.rows){const row=rows.rowItem(position);if(row?.status==="loading")return null;if(row?.status==="error")crBad("a row of the range could not be loaded");if(row&&!row.status)loaded.set(position,row)}
61
+ return loaded}
62
+ async function crLoad(grid,blocks){const rows=crRows(grid),epoch=grid.queryEpoch,loaded=new Map();crCount(blocks);for(const block of blocks)for(const position of block.rows){let row=rows.rowItem(position);if(row?.status==="loading")row=await grid.waitForRow(row._ri,epoch);if(row?.status==="error")crBad("a row of the range could not be loaded");if(row&&!row.status)loaded.set(position,row)}
63
+ if(epoch!==grid.queryEpoch)throw new DOMException("The ranges changed while their rows loaded.","AbortError");return loaded}
64
+ function crHeaders(grid,columns){const lines=[],model=grid.columnGroupModel,paths=model?.roots.length?columns.map(column=>cgPathOf(model,column.id)):[];const depth=Math.max(0,...paths.map(path=>path.length));for(let level=0;level<depth;level++){const texts=[],cells=[];for(let index=0;index<columns.length;){const node=paths[index][level];let end=index+1;if(node)while(end<columns.length&&paths[end][level]===node)end++;for(let at=index;at<end;at++)texts.push(node&&at===index?crField(node.title):"");cells.push(node?'<th colspan="' +(end-index)+ '">'+crHtml(node.title)+ "</th>":"<th></th>".repeat(end-index));index=end}
65
+ lines.push({text:texts.join("\t"),html:"<tr>"+cells.join("")+ "</tr>"})}
66
+ lines.push({text:columns.map(column=>crField(column.title)).join("\t"),html:"<tr>"+columns.map(column=>"<th>"+crHtml(column.title)+ "</th>").join("")+ "</tr>"});return lines}
67
+ function cgPathOf(model,id){const path=[];for(let node=model.owner.get(id);node;node=node.parent)path.unshift(node);return path}
68
+ function crContent(grid,blocks,loaded,options){const values=options.values??grid.cellOptions.values,headers=options.headers??grid.cellOptions.headers,texts=[],tables=[];let rowCount=0,columnCount=0;for(const block of blocks){const columns=grid.cols.slice(block.left,block.right+1),lines=headers?crHeaders(grid,columns):[];for(const position of block.rows){const row=loaded.get(position);if(!row)continue;const cells=columns.map(column=>crCell(grid,row,column,values));lines.push({text:cells.map(cell=>crField(cell.text,cell.typed)).join("\t"),html:"<tr>"+cells.map(cell=>cell.html).join("")+ "</tr>"});rowCount++}
69
+ columnCount=Math.max(columnCount,columns.length);texts.push(lines.map(line=>line.text).join("\r\n"));tables.push("<table>"+lines.map(line=>line.html).join("")+ "</table>")}
70
+ return{text:texts.join("\r\n\r\n")+ "\r\n",html:"<meta charset=\"utf-8\">"+tables.join("<br>"),rows:rowCount,columns:columnCount}}
71
+ function crCopyRects(grid){const rects=crRects(grid);if(rects.length)return rects;const hit=crHit(grid,document.activeElement),focused=grid.focused,rows=crRows(grid);const position=hit?hit.row:focused?rows.indexOf(focused):-1,column=hit?grid.cols.findIndex(item=>item.id===hit.column):0;return position>=0&&column>=0?[{top:position,bottom:position,left:column,right:hit?column:grid.cols.length-1}]:[]}
72
+ async function crWrite(grid,content){const clipboard=global.navigator.clipboard;if(typeof global.ClipboardItem==="function"&&typeof clipboard?.write==="function"){try{await clipboard.write([new global.ClipboardItem({"text/plain":new Blob([content.text],{type:"text/plain"}),"text/html":new Blob([content.html],{type:"text/html"})})]);return}catch(error){if(error?.name==="AbortError")throw error}}
73
+ if(typeof clipboard?.writeText!=="function")crBad("the clipboard is unavailable");await clipboard.writeText(content.text)}
74
+ function crCopied(grid,content){setText(grid.live,fill(grid.labels.gridCellsCopied,{rows:numberFormat(grid.lang).format(content.rows),columns:numberFormat(grid.lang).format(content.columns)}))}
75
+ function crCopyKey(grid){try{const blocks=crBlocks(grid,crCopyRects(grid)),loaded=crLoaded(grid,blocks);if(!loaded){grid.copyCellRanges().catch(error=>grid.emit("error",{error}));return}
76
+ const content=grid.cellCopyPending=crContent(grid,blocks,loaded,{});document.execCommand?.("copy");const handled=grid.cellCopyPending===null;grid.cellCopyPending=null;if(!handled)crWrite(grid,content).then(()=>crCopied(grid,content)).catch(error=>grid.emit("error",{error}))}catch(error){grid.cellCopyPending=null;grid.emit("error",{error})}}
77
+ function crCopyEvent(grid,event){let content=grid.cellCopyPending;if(!content&&grid.cellRanges.length&&grid.canvas.contains(document.activeElement)&&!String(global.getSelection()||"")){const blocks=crBlocks(grid,crRects(grid)),loaded=crLoaded(grid,blocks);if(loaded)content=crContent(grid,blocks,loaded,{})}
78
+ if(!content||!event.clipboardData)return;event.clipboardData.setData("text/plain",content.text);event.clipboardData.setData("text/html",content.html);event.preventDefault();grid.cellCopyPending=null;crCopied(grid,content)}
79
+ function crParse(text){const rows=[[]];let field="",quoted=false;const source=String(text).replace(/\r\n?/g, "\n").replace(/\n$/, "");
80
+ for(let index=0;index<source.length;index++){const character=source[index];if(quoted){if(character==='"'&&source[index+1]==='"'){field +='"';index++}
81
+ else if(character==='"')quoted=false;else field +=character}else if(character==='"'&&field==="")quoted=true;else if(character==="\t"){rows[rows.length-1].push(field);field=""}
82
+ else if(character==="\n"){rows[rows.length-1].push(field);field="";rows.push([])}
83
+ else field +=character}
84
+ rows[rows.length-1].push(field);return rows}
85
+ const crSeparators=new Map();function crNumber(text,lang){if(!crSeparators.has(lang||"")){const parts=new Intl.NumberFormat(lang||undefined).formatToParts(12345.6);crSeparators.set(lang||"",{group:parts.find(part=>part.type==="group")?.value||",",decimal:parts.find(part=>part.type==="decimal")?.value||"."})}
86
+ const{group,decimal}=crSeparators.get(lang||""),escape=value=>value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
87
+ const affixes=/^[^\d+\-.,()]+|[^\d+\-.,()]+$/g;
88
+ let value=text.trim().replace(/\u2212/g, "-").replace(/[\s\u00a0\u202f'\u2019]/g, "").replace(affixes, "");
89
+ const accounting=/^\(.*\)$/.test(value);
90
+ if(accounting)value=value.slice(1,-1).replace(affixes,"");const negative=accounting||value[0]==="-";if(accounting&&/^[-+]/.test(value)) return undefined;
91
+ value=value.replace(/^[-+]/, "").replace(affixes, "");
92
+ const groups=/[\s\u2019']/.test(group) ? "(?!)" : escape(group), local = new RegExp("^\\d{1,3}(" + groups + "\\d{3})*(" + escape(decimal) + "\\d+)?$|^\\d+(" + escape(decimal) + "\\d+)?$");
93
+ let number=NaN;if(local.test(value))number=Number(value.replace(new RegExp(groups,"g"),"").replace(decimal,"."));else if(/^(\d{1,3}(,\d{3})*|\d+)(\.\d+)?$/.test(value)) number = Number(value.replace(/,/g, ""));
94
+ else if(/^\d+(\.\d+)?e[-+]?\d+$/i.test(value)) number = Number(value);
95
+ return Number.isFinite(number)&&Math.abs(number)<=Number.MAX_SAFE_INTEGER?negative?-number:number:undefined}
96
+ function crDateOrder(lang){try{return new Intl.DateTimeFormat(lang||undefined).formatToParts(new Date(2001,10,22)).filter(part=>["day","month","year"].includes(part.type)).map(part=>part.type)}
97
+ catch(_){return["month","day","year"]}}
98
+ function crDateValue(text,grid){let value=text.trim();for(const affix of[...grid.fmt.utc,...grid.fmt.other])if(affix.trim())value=value.split(affix.trim()).join("").trim();const zoned=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(?:\.\d+)?)?(?:Z|[+-]\d\d:\d\d)$/.exec(value);
99
+ if(zoned){const parts=zoned.slice(1).map(part=>Number(part||0));if(!gridMomentFromParts(...parts,false))return undefined;const moment=Date.parse(value);return Number.isFinite(moment)?new Date(moment).toISOString():undefined}
100
+ let parts=/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?)?$/.exec(value), year, month, day;
101
+ if(parts)[,year,month,day]=parts.map(Number);else{parts=/^(\d{1,4})[./\-\s]+(\d{1,2})[./\-\s]+(\d{1,4})\.?(?:[,\sT]+(\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?)?$/.exec(value);
102
+ if(!parts)return undefined;const order=crDateOrder(grid.lang),read={};order.forEach((name,index)=>read[name]=Number(parts[index+1]));({year,month,day}=read);if(year<100)year +=2000}
103
+ const[hours=0,minutes=0,seconds=0]=parts.slice(4,7).map(item=>item===undefined?0:Number(item));const date=gridMomentFromParts(year,month,day,hours,minutes,seconds,grid.local);if(date)date.setTime(date.getTime()+ Number((parts[7]||"").padEnd(3,"0")));return date?date.toISOString():undefined}
104
+ function crValue(grid,column,text,previous){const spec=grid.edit?.specs.get(column.id),trimmed=text.trim();if(!trimmed)return column.kind==="text"&&!["toggle","select"].includes(spec?.type)&&typeof previous!=="boolean"?"":null;const option=spec?.options?.find(item=>foldText(item.label)===foldText(trimmed)||foldText(String(item.value))===foldText(trimmed));if(spec?.type==="select")return option?.value;if(column.kind==="number")return crNumber(trimmed,grid.lang);if(column.kind==="datetime")return crDateValue(trimmed,grid);if(spec?.type==="toggle"||typeof previous==="boolean"){const folded=foldText(trimmed);if(["true","1","yes",foldText(grid.labels.yes)].includes(folded))return true;if(["false","0","no",foldText(grid.labels.no)].includes(folded))return false;return undefined}
105
+ return option?option.value:text}
106
+ async function crTargets(grid,matrix,origin){const rects=crRects(grid),bounds=crBounds(grid),single=matrix.length===1&&matrix[0].length===1;let blocks;if(single&&rects.length&&!origin)blocks=rects.map(rect=>({rows:Array.from({length:rect.bottom-rect.top+1},(_,index)=>rect.top+index),left:rect.left,right:rect.right}));else{const start=origin||(rects[0]?{row:rects[0].top,column:grid.cols[rects[0].left].id}:crHit(grid,document.activeElement));const left=start?grid.cols.findIndex(column=>column.id===start.column):-1;if(!start||left<0||start.row<bounds.first||start.row>bounds.last)crBad("pasting needs a range, a focused cell or a row and column to start at");const width=Math.max(...matrix.map(line=>line.length)),rows=[],view=crRows(grid);for(let position=start.row;rows.length<matrix.length&&position<=bounds.last;position++){const row=view.rowItem(position);if(row&&(!row.status||row.status==="loading"||row.status==="error"))rows.push(position)}
107
+ blocks=[{rows,left,right:Math.min(grid.cols.length-1,left+width-1)}]}
108
+ if(!single&&grid.cellOptions?.enabled&&blocks[0].rows.length){const block=blocks[0];grid.cellRanges=[{anchor:{row:block.rows[0],column:grid.cols[block.left].id},end:{row:block.rows[block.rows.length-1],column:grid.cols[block.right].id}}];grid.cellAnchor=grid.cellRanges[0].anchor;crChanged(grid,false)}
109
+ const loaded=await crLoad(grid,blocks),cells=[],refused=[];for(const block of blocks)block.rows.forEach((position,line)=>{const row=loaded.get(position);if(!row)return;for(let index=block.left;index<=block.right;index++){const column=grid.cols[index],text=single?matrix[0][0]:matrix[line]?.[index-block.left];if(text===undefined)continue;const previous=cellValue(row.h.cells[column.id]),value=crValue(grid,column,text,previous);const cell={id:row.key,column:column.id,row:position,text,value,previous:previous??null};if(value===undefined)refused.push({...cell,value:null,reason:fill(grid.labels.gridPasteType,{value:text,column:column.title})});else cells.push(cell)}});return{cells,refused}}
110
+ async function crApplyEditing(grid,cells,source,refused){let applied=0;const candidates=[],proposals=new Map(),epoch=grid.queryEpoch;const current=proposed=>{if(grid.destroyed||epoch!==grid.queryEpoch||JSON.stringify(proposed.record.cells)!==proposed.original)
111
+ throw new DOMException("The paste row or query changed during validation.","AbortError")};const reject=(candidate,reason)=>{refused.push({...candidate.cell,reason});candidate.refused=true};grid.cancelEdit?.();for(const cell of cells){const spec=grid.edit.specs.get(cell.column),record=grid.editRecord(cell.id);if(!spec||!record||!grid.canEditCell(record,cell.column)){refused.push({...cell,reason:grid.labels.gridPasteReadOnly});continue}
112
+ const proposed=proposals.get(cell.id)||{record,original:JSON.stringify(record.cells),pending:[],rounds:0,dirty:true};const candidate={cell,spec,proposed};proposed.pending.push(candidate);proposals.set(cell.id,proposed);candidates.push(candidate)}
113
+ for(const proposed of proposals.values())proposed.limit=Math.min(8,proposed.pending.length);while([...proposals.values()].some(proposed=>proposed.dirty&&proposed.pending.length)){for(const proposed of proposals.values()){while(proposed.dirty&&proposed.pending.length){current(proposed);if(proposed.rounds++===proposed.limit){for(const candidate of proposed.pending)reject(candidate,proposed.reason||grid.labels.gridPasteReadOnly);proposed.pending=[];break}
114
+ proposed.dirty=false;proposed.preview=grid.editPreview(proposed.record,proposed.pending.map(({spec,cell})=>[spec,cell.value]));proposed.checked={result:null,row:JSON.stringify(proposed.preview.cells),prepared:true};for(const candidate of proposed.pending){let message=grid.editRules(candidate.spec,candidate.cell.value,proposed.preview);if(message&&typeof message.then==="function"){message=await message;current(proposed)}
115
+ if(message){reject(candidate,message);proposed.reason=message;proposed.dirty=true}}
116
+ current(proposed);proposed.pending=proposed.pending.filter(candidate=>!candidate.refused)}}
117
+ const prefixes=new Map();for(const candidate of candidates){if(candidate.refused)continue;const{cell,spec,proposed}=candidate;let prefix=prefixes.get(proposed);if(!prefix){prefix=grid.editPreview(proposed.record,[]);prefixes.set(proposed,prefix)}
118
+ const previous=cellValue(prefix.cells[cell.column]);let accepted=grid.canEditCell(prefix,cell.column);if(accepted&&!candidate.prepared&&!(previous===cell.value||previous==null&&cell.value==null))
119
+ accepted=grid.emit("cellValueChanging",{id:cell.id,column:cell.column,value:cell.value,previous,row:gridFreeze(JSON.parse(JSON.stringify(prefix))),source});if(!accepted){reject(candidate,grid.labels.gridPasteReadOnly);proposed.dirty=true}
120
+ else{candidate.prepared=true;prefix.cells[cell.column]=proposed.preview.cells[cell.column]}}
121
+ for(const proposed of proposals.values()){current(proposed);proposed.pending=proposed.pending.filter(candidate=>!candidate.refused)}}
122
+ for(const proposed of proposals.values())current(proposed);grid.editGroup(()=>{for(const{cell,spec,proposed,refused:rejected}of candidates){if(rejected)continue;if(grid.editApply(cell.id,spec,cell.value,source,proposed.record,proposed.checked))applied++;else refused.push({...cell,reason:grid.labels.gridPasteReadOnly})}});return applied}
123
+ async function crApply(grid,cells,refused,source){let answer;const detail={source,cells:gridFreeze(JSON.parse(JSON.stringify(cells))),refused:gridFreeze(JSON.parse(JSON.stringify(refused))),respondWith(result){answer=result}};if(!grid.emit("paste",detail))return Object.freeze({applied:0,refused:[],cancelled:true});const declined=new Set(),editing=!!grid.edit&&typeof grid.editApply==="function";if(answer!==undefined){const result=await answer,list=Array.isArray(result?.refused)?result.refused:result===false?cells:[];for(const item of list){const cell=cells.find(entry=>entry.id===item?.id&&entry.column===item?.column);if(!cell||declined.has(cell))continue;declined.add(cell);refused.push({...cell,reason:typeof item.reason==="string"&&item.reason.trim()&&item!==cell?item.reason:grid.labels.gridPasteReadOnly})}}
124
+ const remaining=cells.filter(cell=>!declined.has(cell));const applied=editing?await crApplyEditing(grid,remaining,source,refused):answer!==undefined?remaining.length:0;crReport(grid,applied,refused,source);return gridFreeze({applied,refused:JSON.parse(JSON.stringify(refused)),cancelled:false})}
125
+ async function crPaste(grid,text,origin,source){if(typeof text!=="string")crBad("pasteCells takes text");const matrix=crParse(text);if(!text)return gridFreeze({applied:0,refused:[],cancelled:false});const{cells,refused}=await crTargets(grid,matrix,origin);return crApply(grid,cells,refused,source)}
126
+ function crReport(grid,applied,refused,source){const count=value=>numberFormat(grid.lang).format(value);const said=[fill(grid.labels[source==="fill"?"gridCellsFilled":"gridCellsPasted"],{count:count(applied)})];if(refused.length)said.push(fill(grid.labels.gridPasteRefused,{count:count(refused.length)}));setText(grid.live,said.join(". "));grid.pasteReport?.remove();grid.pasteReport=null;if(!refused.length)return;const panel=h("div","cx-paste-report"),summary=h("p"),details=h("details"),title=h("summary"),list=h("ul");panel.setAttribute("role","region");panel.setAttribute("aria-label",grid.labels.gridPasteReport);setText(summary,said.join(". "));setText(title,grid.labels.gridPasteReport);const identity=grid.columns.find(column=>column.rowHeader)||grid.cols[0],rows=crRows(grid);for(const cell of refused.slice(0,200)){const item=h("li"),row=rows.rowItem(cell.row),column=grid.columns.find(entry=>entry.id===cell.column);const name=row&&!row.status?cellText(row.h.cells[identity.id],identity,grid.fmt)||cell.id:String(cell.row+1);setText(item,fill(grid.labels.gridPasteCell,{row:name,column:column?column.title:cell.column,reason:cell.reason}));list.appendChild(item)}
127
+ const close=gridDecorateButton(gridButton("",()=>{panel.remove();if(grid.pasteReport===panel)grid.pasteReport=null;grid.grid.focus({preventScroll:true})}),"close",true);close.classList.add("icon");close.setAttribute("aria-label",grid.labels.gridDone);details.append(title,list);panel.append(summary,close,details);grid.host.insertBefore(panel,grid.grid);grid.pasteReport=panel}
128
+ function crSeries(values,count){const numbers=values.every(value=>typeof value==="number"),dates=!numbers&&values.every(value=>typeof value==="string"&&/^\d{4}-\d\d-\d\dT/.test(value) && Number.isFinite(Date.parse(value)));
129
+ if(values.length>1&&(numbers||dates)){const points=values.map(value=>numbers?value:Date.parse(value)),step=points[1]- points[0];if(points.every((point,index)=>!index||Math.abs(point-points[index-1]- step)<1e-9)){const places=value=>{const[digits,power]=String(value).split("e");return Math.max(0,(digits.split(".")[1]||"").length-Number(power||0))};const decimals=numbers?Math.min(20,Math.max(...values.map(places))):0;return Array.from({length:count},(_,index)=>{const next=points[points.length-1]+ step*(index+1);return numbers?Number(next.toFixed(decimals)):new Date(next).toISOString()})}}
130
+ return Array.from({length:count},(_,index)=>values[index%values.length])}
131
+ async function crFill(grid,rect,top,bottom,sourceRows){const blocks=[{rows:Array.from({length:bottom-rect.top+1},(_,index)=>rect.top+index),left:rect.left,right:rect.right}];const loaded=await crLoad(grid,blocks),cells=[];const targets=blocks[0].rows.filter(position=>position>=top&&position<=bottom&&loaded.has(position));for(let index=rect.left;index<=rect.right;index++){const column=grid.cols[index],values=[];for(let position=rect.top;position<rect.top+sourceRows;position++){const row=loaded.get(position);if(row)values.push(cellValue(row.h.cells[column.id])??null)}
132
+ if(!values.length)continue;const series=crSeries(values,targets.length);for(const[index,position]of targets.entries()){const row=loaded.get(position);const value=series[index],previous=cellValue(row.h.cells[column.id]);cells.push({id:row.key,column:column.id,row:position,text:value==null?"":cellText(value,column,grid.fmt),value,previous:previous??null})}}
133
+ return crApply(grid,cells,[],"fill")}
134
+ function crFillDown(grid){const rect=crRects(grid)[0];if(!rect||rect.bottom===rect.top)return Promise.resolve();const first=crDataBounds(grid,rect.top,rect.bottom).first;return first<0||first===rect.bottom?Promise.resolve():crFill(grid,{...rect,top:first},first+1,rect.bottom,1)}
135
+ function crFillDrag(grid,event){const rect=crRects(grid)[0];if(!rect||event.button!==0)return;event.preventDefault();event.stopPropagation();let bottom=rect.bottom;crTrack(grid,event,hit=>{bottom=Math.max(rect.bottom,hit.row);grid.cellFillPreview=bottom>rect.bottom?{top:rect.bottom+1,bottom,left:rect.left,right:rect.right}:null;crPaint(grid)},released=>{grid.cellFillPreview=null;if(released&&bottom>rect.bottom){const range=grid.cellRanges[grid.cellRanges.length-1];range.anchor={row:rect.top,column:grid.cols[rect.left].id};range.end={row:bottom,column:grid.cols[rect.right].id};crFill(grid,rect,rect.bottom+1,bottom,rect.bottom-rect.top+1).catch(error=>grid.emit("error",{error}));crChanged(grid,false);crFocus(grid,range.anchor)}else crPaint(grid);crSwallowClick(grid)})}
136
+ function crCopyOptions(options){if(!options||typeof options!=="object"||options.headers!==undefined&&typeof options.headers!=="boolean"||
137
+ options.values!==undefined&&!["display","raw"].includes(options.values))crBad("copy options are headers (boolean) and values (display or raw)")}
138
+ function crAt(grid,row,column,name){const bounds=crBounds(grid);if(!Number.isInteger(row)||row<bounds.first||row>bounds.last||!grid.cols.some(item=>item.id===column))crBad(name+" needs row positions in the result and visible columns");return{row,column}}
139
+ Object.assign(DataGrid.prototype,{startCellRanges(){if(this.cellOptions||this.destroyed)return;const options=this.cellOptions=crOptions(this),signal=this.lifetime.signal,shared=Object.getPrototypeOf(this);this.cellRanges=[];this.cellAnchor=null;this.renderPass=function(...args){const result=shared.renderPass.apply(this,args);crPaint(this);return result};const columns=()=>{const before=this.cellRanges.length;this.cellRanges=this.cellRanges.filter(range=>crRect(this,range));if(before!==this.cellRanges.length)crChanged(this);else crPaint(this)};const shape=(query,revision)=>query&&JSON.stringify([query.search,query.searchMode,query.sort,query.filters,query.filterGroup,query.groupBy,this.paged?query.offset:0,revision]);const clear=()=>{if(this.cellRanges.length){this.cellRanges=[];this.cellAnchor=null;this.cellHandles=false;crChanged(this)}};let accepted=shape(this.getQuery(),this.snapshot?.revision);this.host.addEventListener("canopyx:query",event=>{const next=shape(event.detail.query,event.detail.revision);if(next===accepted)return;accepted=next;clear()},{signal});this.host.addEventListener("canopyx:group",clear,{signal});this.host.addEventListener("canopyx:detail",clear,{signal});this.host.addEventListener("canopyx:state",columns,{signal});document.addEventListener("copy",event=>crCopyEvent(this,event),{signal});if(!options.enabled)return;this.host.classList.add("cx-cell-ranges");signal.addEventListener("abort",()=>{this.host.classList.remove("cx-cell-ranges");crGuard(this,false)},{once:true});if(options.multiple)this.grid.setAttribute("aria-multiselectable","true");this.canvas.addEventListener("pointerdown",event=>{if(event.target.closest(".cx-fill-handle"))crFillDrag(this,event);else if(event.target.closest(".cx-range-handle"))crHandleDown(this,event);else crPointerDown(this,event)},{signal});this.grid.addEventListener("keydown",event=>crKey(this,event),{capture:true,signal});this.grid.addEventListener("click",()=>{const hit=this.cellFocusAfterClick;this.cellFocusAfterClick=null;if(hit&&this.grid.contains(document.activeElement))crFocus(this,hit)},{signal});this.canvas.addEventListener("contextmenu",event=>{const hit=crHit(this,event.target);if(!hit||event.target.closest(CR_CONTROLS))return;event.preventDefault();if(this.cellTouching||event.pointerType==="touch")return;const inside=crRects(this).some(rect=>hit.row>=rect.top&&hit.row<=rect.bottom&&crIndex(this,hit.column)>=rect.left&&crIndex(this,hit.column)<=rect.right);if(!inside){this.cellAnchor={row:hit.row,column:hit.column};if(this.cellRanges.length){this.cellRanges=[];crChanged(this)}}
140
+ crMenu(this,hit.cell)},{signal});this.grid.addEventListener("paste",event=>{if(!options.paste||event.target.closest("input,textarea,select,[contenteditable]:not([contenteditable=false])")||!this.canvas.contains(event.target))return;const text=event.clipboardData?.getData("text/plain");if(typeof text!=="string")return;event.preventDefault();crPaste(this,text,null,"clipboard").catch(error=>this.emit("error",{error}))},{signal});if(this.overflow)this.overflow.panel.prepend(gridDecorateButton(gridButton(this.labels.gridCopyCells,()=>
141
+ this.copyCellRanges().catch(error=>this.emit("error",{error}))),"copy",true));signal.addEventListener("abort",()=>{this.cellDrag?.();this.pasteReport?.remove()},{once:true})},getCellRanges(){return gridFreeze((this.cellRanges?crRects(this):[]).map(rect=>({startRow:rect.top,endRow:rect.bottom,startColumn:this.cols[rect.left].id,endColumn:this.cols[rect.right].id})))},setCellRanges(ranges){this.startCellRanges();if(!Array.isArray(ranges)||ranges.length>1000||ranges.length>1&&!this.cellOptions.multiple)crBad("setCellRanges takes up to 1000 ranges, one unless multiple ranges are on");this.cellRanges=ranges.map(range=>({anchor:crAt(this,range?.startRow,range?.startColumn,"setCellRanges"),end:crAt(this,range.endRow??range.startRow,range.endColumn??range.startColumn,"setCellRanges")}));this.cellAnchor=this.cellRanges[0]?.anchor||null;this.cellHandles=false;crChanged(this)},clearCellRanges(){this.startCellRanges();if(!this.cellRanges?.length)return;this.cellRanges=[];this.cellHandles=false;crChanged(this)},selectAllCells(){this.startCellRanges();const bounds=crBounds(this);if(bounds.last<bounds.first||!this.cols?.length)return;this.setCellRanges([{startRow:bounds.first,endRow:bounds.last,startColumn:this.cols[0].id,endColumn:this.cols[this.cols.length-1].id}])},async getCellRangeText(options={}){this.startCellRanges();crCopyOptions(options);const blocks=crBlocks(this,crCopyRects(this));return crContent(this,blocks,await crLoad(this,blocks),options).text},async copyCellRanges(options={}){this.startCellRanges();crCopyOptions(options);const blocks=crBlocks(this,crCopyRects(this));if(!blocks.length)crBad("copying needs a range or a focused cell");const content=crContent(this,blocks,await crLoad(this,blocks),options);await crWrite(this,content);crCopied(this,content);return content.text},pasteCells(text,at){this.startCellRanges();return crPaste(this,text,at===undefined?null:crAt(this,at?.row,at?.column,"pasteCells"),"api")}});installed.add(context.DataGrid.prototype)}};(global.CanopyXParts||=[]).push(["cell-range",part])})(typeof window!=="undefined"?window:globalThis);
@@ -0,0 +1,18 @@
1
+ /* Column groups: header rows above the column headers (canopyx.column-groups.source),and the nested Columns menu. */
2
+ .cx-data-grid .cx-hgroup-row{height:var(--cx-head-h);}
3
+ .cx-data-grid.cx-phone .cx-hgroup-row{height:var(--cx-size-32px);}
4
+ .cx-data-grid .cx-hgroup{overflow:visible;justify-content:flex-start;border-bottom:var(--cx-size-1px) solid var(--cx-line);color:var(--cx-text-2);}
5
+ .cx-data-grid .cx-hgroup::after{top:0;bottom:0;}
6
+ .cx-data-grid .cx-hgroup-row .cx-hcell:last-child::after{display:block;}
7
+ .cx-data-grid .cx-hgroup-row .cx-hcell:last-child:not(.cx-hgroup)::after,.cx-data-grid .cx-hgroup-pad::after{display:none;}
8
+ .cx-data-grid .cx-hgroup-title{position:sticky;left:calc(var(--cx-pin-left-w,0px) + var(--cx-cell-px));min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
9
+ .cx-data-grid .cx-hgroup[data-cx-pin] .cx-hgroup-title{position:static;}
10
+ .cx-data-grid .cx-hgroup .cx-column-move{flex:none;}
11
+ .cx-data-grid .cx-hgroup.cx-drop-before{box-shadow:inset var(--cx-size-2px) 0 var(--cx-accent);}
12
+ .cx-data-grid .cx-hgroup.cx-drop-after{box-shadow:inset var(--cx-size-neg-2px) 0 var(--cx-accent);}
13
+ .cx-data-grid.cx-column-groups .cx-hrow:not(.cx-hgroup-row) .cx-hcell::after{top:var(--cx-size-6px);bottom:var(--cx-size-6px);}
14
+ .cx-grid-column-choice{padding-inline-start:calc(var(--cx-level,0) * var(--cx-size-16px));}
15
+ .cx-grid-column-group-choice .cx-grid-column-name{font-weight:600;}
16
+ @media (forced-colors:active){
17
+ .cx-data-grid .cx-hgroup{border-bottom-color:CanvasText;border-inline-end:var(--cx-size-1px) solid CanvasText;}
18
+ }
@@ -0,0 +1,51 @@
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"b42238fd108a8614",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,fill,columnWidth,foldText,gridButton,gridIcon,gridDecorateButton,cellText,cellValue,isCellObject,numberFormat,gridFreeze,requireIdentifier,loadGridFilterEditor,CELL_CONTROLS,reachable}=context;const CG_DEPTH=3,CG_PINS=["left","none","right"];const cgBad=problem=>{throw new Error("CanopyX: column groups "+problem+".")};function cgRead(view){const declared=new Set(view.columns.map(column=>column.id)),owner=new Map(),byId=new Map();if(!Array.isArray(view.columnGroups)||view.columnGroups.length>1000)cgBad("must be an array of at most 1000 groups");const read=(input,parent,depth)=>{if(!input||typeof input!=="object"||Array.isArray(input))cgBad("must be objects");requireIdentifier(input.id,"column group ID");const columns=input.columns??[],groups=input.groups??[];if(byId.has(input.id)||typeof input.title!=="string"||!input.title.trim()||input.title.length>256||depth>CG_DEPTH||
2
+ !Array.isArray(columns)||!Array.isArray(groups)||!columns.length&&!groups.length||
3
+ input.pinned!==undefined&&(parent||![null,"left","right"].includes(input.pinned)))
4
+ cgBad("need unique IDs, titles, members, at most "+CG_DEPTH+" levels, and a pin only on a top-level group");const node={id:input.id,title:input.title,pinned:input.pinned,parent,depth,columns:[],groups:[],leaves:[]};byId.set(node.id,node);for(const id of columns){if(!declared.has(id)||owner.has(id))cgBad("name declared columns, each in one group at most");owner.set(id,node);node.columns.push(id)}
5
+ for(const group of groups)node.groups.push(read(group,node,depth+1));return node};const roots=view.columnGroups.map(group=>read(group,null,1));const gather=node=>node.leaves=[...node.columns,...node.groups.flatMap(gather)];roots.forEach(gather);return{roots,byId,owner,depth:Math.max(0,...[...byId.values()].map(node=>node.depth))}}
6
+ function cgPath(model,id){const path=[];for(let node=model.owner.get(id);node;node=node.parent)path.unshift(node);return path}
7
+ function cgOrder(grid){const model=grid.columnGroupModel,rank=new Map(grid.columns.map((column,index)=>[column.id,index]));const byId=new Map(grid.columns.map(column=>[column.id,column]));const middle=ids=>ids.map(id=>rank.get(id)).sort((a,b)=>a-b)[(ids.length-1)>>1];const place=node=>[...node.columns.map(id=>({at:rank.get(id),ids:[id]})),...node.groups.map(group=>({at:middle(group.leaves),ids:place(group)}))]
8
+ .sort((a,b)=>a.at-b.at).flatMap(item=>item.ids);const items=model.roots.map(root=>{const members=root.leaves.map(id=>byId.get(id)).sort((a,b)=>rank.get(a.id)- rank.get(b.id)),counts=new Map();for(const column of members)counts.set(column.pin||"none",(counts.get(column.pin||"none")||0)+ 1);let pin=members[0].pin||"none";for(const[side,count]of counts)if(count>counts.get(pin))pin=side;for(const column of members)column.pin=pin;return{at:middle(root.leaves),ids:place(root),pin}});for(const column of grid.columns)if(!model.owner.has(column.id))items.push({at:rank.get(column.id),ids:[column.id],pin:column.pin||"none"});items.sort((a,b)=>CG_PINS.indexOf(a.pin)- CG_PINS.indexOf(b.pin)||a.at-b.at);const order=items.flatMap(item=>item.ids);if(order.some((id,index)=>grid.columns[index].id!==id))grid.columns=order.map(id=>byId.get(id))}
9
+ function cgParent(model,id){return model.byId.has(id)?model.byId.get(id).parent:model.owner.get(id)||null}
10
+ function cgSibling(grid,itemId,targetId,after){const model=grid.columnGroupModel,parent=cgParent(model,itemId);for(let id=targetId;id;){const up=cgParent(model,id);if(up===parent)return{id,after};id=up&&up.id}
11
+ const order=grid.columns.map(column=>column.id);return order.indexOf(cgFirst(grid,targetId))<order.indexOf(cgFirst(grid,parent.id))?
12
+ {id:cgTop(grid,parent,true),after:false}:{id:cgTop(grid,parent,false),after:true}}
13
+ function cgFirst(grid,id){const node=grid.columnGroupModel.byId.get(id);return node?grid.columns.find(column=>node.leaves.includes(column.id)).id:id}
14
+ function cgTop(grid,node,first){const members=[...node.columns,...node.groups.map(group=>group.id)],order=grid.columns.map(column=>column.id);const at=id=>order.indexOf(cgFirst(grid,id));return members.sort((a,b)=>first?at(a)- at(b):at(b)- at(a))[0]}
15
+ function cgMoved(grid,id,targetId,after){const model=grid.columnGroupModel,block=model.byId.get(id)?.leaves||[id],targetBlock=model.byId.get(targetId)?.leaves||[targetId];const order=grid.columns.map(column=>column.id).filter(item=>!block.includes(item));const positions=targetBlock.map(item=>order.indexOf(item)).filter(index=>index>=0);if(!positions.length)return null;order.splice(after?Math.max(...positions)+ 1:Math.min(...positions),0,...grid.columns.map(column=>column.id).filter(item=>block.includes(item)));return order}
16
+ function cgHeader(grid){const model=grid.columnGroupModel,cols=grid.cols,paths=cols.map(column=>cgPath(model,column.id));const depth=Math.max(0,...paths.map(path=>path.length));for(const row of grid.groupRows||[])row.remove();grid.groupRows=[];grid.groupCells=[];for(let level=0;level<depth;level++){const row=h("div","cx-hrow cx-hgroup-row");row.setAttribute("role","row");row.setAttribute("aria-rowindex",String(level+1));for(let first=0;first<cols.length;){const node=paths[first][level];let end=first+1;if(node)while(end<cols.length&&paths[end][level]===node)end++;const cell=node?cgGroupCell(grid,node,first,end-first):h("div","cx-hcell cx-hgroup-pad");if(!node)cell.setAttribute("aria-hidden","true");cell._cxSpan={first,last:end-1};row.appendChild(cell);grid.groupCells.push(cell);first=end}
17
+ grid.groupRows.push(row)}
18
+ grid.head.prepend(...grid.groupRows);grid.headRow.setAttribute("aria-rowindex",String(depth+1));grid.host.classList.toggle("cx-column-groups",depth>0);cgDepth(grid,depth);cgLayout(grid)}
19
+ function cgGroupCell(grid,node,first,span){const cell=h("div","cx-hcell cx-hgroup"),title=h("span","cx-hgroup-title");cell.setAttribute("role","columnheader");cell.setAttribute("aria-colindex",String(first+1));cell.setAttribute("aria-colspan",String(span));cell.setAttribute("aria-label",node.title);cell.dataset.cxGroup=node.id;cell.dataset.cxLevel=String(node.depth);setText(title,node.title);title.title=node.title;cell.appendChild(title);if(grid.view.reporting?.columnReorder)cgMoveButton(grid,node,cell);cell.appendChild(cgGrip(grid,node));return cell}
20
+ function cgDepth(grid,depth){const previous=grid.headDepth||0;if(previous===depth)return;grid.headDepth=depth;const count=grid.grid.getAttribute("aria-rowcount");if(count&&count!=="-1")grid.grid.setAttribute("aria-rowcount",String(Number(count)- previous+depth));for(const row of[...grid.rendered.values(),...grid.pool])row._index=undefined;grid.scheduleRender()}
21
+ function cgLayout(grid){const cells=grid.headCells||[],widths=cells.map(cell=>parseFloat(cell.style.width)||0);for(const row of grid.groupRows||[])row.style.width=grid.headRow.style.width;for(const cell of grid.groupCells||[]){const{first,last}=cell._cxSpan;if(last>=cells.length)continue;let width=0;for(let index=first;index<=last;index++)width +=widths[index];const head=cells[first],tail=cells[last],pin=head.dataset.cxPin;cell.style.width=width+"px";if(pin)cell.dataset.cxPin=pin;else cell.removeAttribute("data-cx-pin");cell.style.left=pin==="left"?head.style.left:"";cell.style.right=pin==="right"?tail.style.right:"";cell.classList.toggle("cx-pin-edge",cells.slice(first,last+1).some(item=>item.classList.contains("cx-pin-edge")));const grip=cell.querySelector(":scope > .cx-resize"),columns=grid.cols.slice(first,last+1);if(grip){grip.setAttribute("aria-valuemin",String(columns.reduce((sum,column)=>sum+column.minW,0)));grip.setAttribute("aria-valuemax",String(Math.min(2147483647,columns.reduce((sum,column)=>sum+column.maxW,0))));grip.setAttribute("aria-valuenow",String(Math.round(width)));grip.setAttribute("aria-valuetext",fill(grid.labels.columnWidth,{width:numberFormat(grid.lang).format(Math.round(width))}))}}
22
+ grid.host.style.setProperty("--cx-pin-left-w",(grid.pinnedLeftWidth||0)+ "px")}
23
+ function cgUnsplit(grid){const model=grid.columnGroupModel,shown=new Set(grid.cols),byId=new Map(grid.columns.map(column=>[column.id,column]));const split=model.roots.map(root=>root.leaves.map(id=>byId.get(id)).filter(column=>shown.has(column)))
24
+ .filter(members=>new Set(members.map(column=>column.effectivePin||"none")).size>1);if(!split.length)return;for(const members of split)for(const column of members)column.effectivePin="none";const width=column=>column.renderWidth??column.width;let start=0,end=0;for(const column of grid.cols){column.pinOffset=column.effectivePin==="left"?start:0;if(column.effectivePin==="left")start +=width(column)}
25
+ for(const column of[...grid.cols].reverse())if(column.effectivePin==="right"){column.pinOffset=end;end +=width(column)}
26
+ grid.pinnedLeftWidth=start;grid.pinnedRightWidth=end;grid.host.classList.toggle("cx-pins",start+end>0);grid.cols.forEach((column,index)=>grid.applyColumnPin(grid.headCells[index],column));for(const row of[...grid.rendered.values(),...grid.pool])
27
+ if(row._kind==="node")grid.cols.forEach((column,index)=>{if(row.children[index])grid.applyColumnPin(row.children[index],column)});grid.syncPinnedShadows()}
28
+ function cgGrip(grid,node){const grip=h("span","cx-resize");grip.tabIndex=0;grip.setAttribute("role","separator");grip.setAttribute("aria-orientation","vertical");grip.setAttribute("aria-label",fill(grid.labels.resizeColumn,{column:node.title}));const members=()=>grid.cols.filter(column=>node.leaves.includes(column.id));grip.addEventListener("keydown",event=>{if(!["ArrowLeft","ArrowRight","Home"].includes(event.key))return;event.preventDefault();event.stopPropagation();const columns=members();if(event.key==="Home"){for(const column of columns){grid.fitManual(column);column.width=column.minW}grid.applyWidths()}
29
+ else cgResize(grid,columns,(event.key==="ArrowLeft"?-1:1)*(event.shiftKey?50:10)*columns.length);cgResized(grid,columns)});grip.addEventListener("pointerdown",event=>{if(grid.destroyed||event.button!==0)return;if(grid.cancelResize)grid.cancelResize();event.preventDefault();const columns=members(),widths=columns.map(column=>column.renderWidth??column.width),x0=event.clientX,pointer=event.pointerId;const move=ev=>{if(ev.pointerId===pointer)cgResize(grid,columns,ev.clientX-x0,widths)};const stop=()=>{global.removeEventListener("pointermove",move);global.removeEventListener("pointerup",end);global.removeEventListener("pointercancel",end);global.removeEventListener("blur",stop);if(grid.cancelResize===stop)grid.cancelResize=null;cgResized(grid,columns)};const end=ev=>{if(ev.pointerId===pointer)stop()};grid.cancelResize=stop;global.addEventListener("pointermove",move);global.addEventListener("pointerup",end);global.addEventListener("pointercancel",end);global.addEventListener("blur",stop)});return grip}
30
+ function cgResize(grid,columns,delta,from=columns.map(column=>column.renderWidth??column.width)){const total=from.reduce((sum,width)=>sum+width,0)||1;columns.forEach((column,index)=>{grid.fitManual(column);column.width=columnWidth(column,Math.round(from[index]+ delta*from[index]/ total)); });
31
+ grid.applyWidths()}
32
+ function cgResized(grid,columns){grid.noteViewPreferenceIntent?.();grid.stateChanged();for(const column of columns)grid.emit("columnResize",{column:column.id,width:column.width})}
33
+ function cgSiblings(grid,parent){const model=grid.columnGroupModel,items=parent?[...parent.columns,...parent.groups.map(group=>group.id)]:
34
+ [...model.roots.map(root=>root.id),...grid.columns.filter(column=>!model.owner.has(column.id)).map(column=>column.id)];const order=grid.columns.map(column=>column.id);return items.sort((a,b)=>order.indexOf(cgFirst(grid,a))- order.indexOf(cgFirst(grid,b)))}
35
+ function cgMoveGroup(grid,node,targetId,after){const to=cgSibling(grid,node.id,targetId,after);if(to.id===node.id)return;const order=cgMoved(grid,node.id,to.id,to.after);if(!order)return;const pin=grid.columns.find(column=>column.id===cgFirst(grid,to.id)).pin;if(!node.parent)for(const id of node.leaves)grid.columns.find(column=>column.id===id).pin=pin;grid.setColumnOrder(order);const position=cgSiblings(grid,node.parent).indexOf(node.id)+ 1;const handle=grid.head.querySelector('[data-cx-group="'+CSS.escape(node.id)+ '"] .cx-column-move');if(handle)(grid.focusReportingHeader?grid.focusReportingHeader(handle):handle.focus({preventScroll:true}));if(grid.labels.gridColumnMoved)setText(grid.live,fill(grid.labels.gridColumnMoved,{column:node.title,position}))}
36
+ function cgMoveButton(grid,node,cell){const move=h("button","cx-column-move");move.type="button";setText(move,"⋮⋮");move.setAttribute("aria-label",fill(grid.labels.gridMoveColumn,{column:node.title}));move.title=move.getAttribute("aria-label");move.addEventListener("keydown",event=>{if(!["ArrowLeft","ArrowRight"].includes(event.key))return;event.preventDefault();event.stopPropagation();const siblings=cgSiblings(grid,node.parent),target=siblings[siblings.indexOf(node.id)+(event.key==="ArrowLeft"?-1:1)];if(target)cgMoveGroup(grid,node,target,event.key==="ArrowRight")});move.addEventListener("pointerdown",event=>cgDragGroup(grid,node,event));cell.prepend(move)}
37
+ function cgDragGroup(grid,node,event){if(grid.destroyed||event.button!==0)return;grid.cancelColumnDrag?.();event.preventDefault();event.currentTarget.focus({preventScroll:true});const pointer=event.pointerId,x0=event.clientX,y0=event.clientY;let ghost=null,target=null,marked=null;const clear=()=>{marked?.classList.remove("cx-drop-before","cx-drop-after");marked=null};const locate=(x,y)=>{clear();target=null;const cell=document.elementFromPoint(x,y)?.closest(".cx-hcell");if(!cell||!grid.head.contains(cell))return;const span=cell._cxSpan,id=cell.dataset.cxGroup||cell.dataset.cxColumn||span&&grid.cols[span.first]?.id;if(!id)return;const box=cell.getBoundingClientRect(),to=cgSibling(grid,node.id,id,x>box.left+box.width / 2);
38
+ if(to.id===node.id)return;target=to;const shown=grid.head.querySelector('[data-cx-group="'+CSS.escape(to.id)+ '"]')||grid.head.querySelector('.cx-hrow:last-child [data-cx-column="'+CSS.escape(to.id)+ '"]');if(shown){marked=shown;shown.classList.add(to.after?"cx-drop-after":"cx-drop-before")}};const move=ev=>{if(ev.pointerId!==pointer)return;if(!ghost&&Math.hypot(ev.clientX-x0,ev.clientY-y0)<5)return;if(!ghost){ghost=h("div","cx-column-drag");ghost.setAttribute("aria-hidden","true");setText(ghost,node.title);const theme=getComputedStyle(grid.host);for(const token of["surface","text","accent","line"])ghost.style.setProperty("--cx-"+token,theme.getPropertyValue("--cx-"+token));document.body.appendChild(ghost)}
39
+ ghost.style.left=ev.clientX+12+"px";ghost.style.top=ev.clientY+12+"px";locate(ev.clientX,ev.clientY)};const stop=()=>{global.removeEventListener("pointermove",move);global.removeEventListener("pointerup",end);global.removeEventListener("pointercancel",cancel);global.removeEventListener("keydown",key);grid.lifetime.signal.removeEventListener("abort",stop);ghost?.remove();clear();if(grid.cancelColumnDrag===stop)grid.cancelColumnDrag=null};const end=ev=>{if(ev.pointerId!==pointer)return;const to=ghost&&target;stop();if(to&&!grid.destroyed)cgMoveGroup(grid,node,to.id,to.after)};const cancel=ev=>{if(ev.pointerId===pointer)stop()};const key=ev=>{if(ev.key==="Escape"){ev.preventDefault();stop()}};grid.cancelColumnDrag=stop;grid.lifetime.signal.addEventListener("abort",stop,{once:true});global.addEventListener("pointermove",move);global.addEventListener("pointerup",end);global.addEventListener("pointercancel",cancel);global.addEventListener("keydown",key)}
40
+ function cgColumnControls(grid){const list=grid.columnList,model=grid.columnGroupModel,active=document.activeElement,query=foldText(grid.columnFind?.value.trim()||"");const held=list.contains(active)?active.closest(".cx-grid-column-choice"):null,heldId=held&&(held.dataset.cxColumn||held.dataset.cxGroup),heldHandle=active?.matches?.(".cx-grid-column-handle");const byId=new Map(grid.columns.map(column=>[column.id,column])),shown=()=>grid.columns.filter(column=>!column.hidden).length;const matches=id=>!query||foldText(model.byId.get(id)?.title??byId.get(id).title).includes(query);const visible=id=>{const node=model.byId.get(id);return!query||matches(id)||!!node&&node.leaves.some(matches)||cgPath(model,id).some(group=>matches(group.id))};list.textContent="";const render=(parent,container,level)=>{for(const id of cgSiblings(grid,parent)){const node=model.byId.get(id),column=byId.get(id),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");const title=node?node.title:column.title,members=node?node.leaves.map(leaf=>byId.get(leaf)):[column],hiddenCount=members.filter(item=>item.hidden).length;input.type="checkbox";input.checked=hiddenCount===0||!node&&hiddenCount<members.length;input.indeterminate=hiddenCount>0&&hiddenCount<members.length;input.disabled=input.checked&&shown()<=members.length-hiddenCount;input.onchange=()=>{input.focus({preventScroll:true});node?grid.setColumnGroupVisible(id,input.checked):grid.setColumnVisible(id,input.checked)};setText(name,name.title=title);handle.type="button";setText(handle,"⋮⋮");handle.setAttribute("aria-label",fill(grid.labels.gridReorderColumn,{column:title}));label.append(input,name);row.append(handle,label);row.style.setProperty("--cx-level",String(level));row.hidden=!visible(id);if(node){row.classList.add("cx-grid-column-group-choice");row.dataset.cxGroup=id}else row.dataset.cxColumn=id;const note=!node&&(column.fitHidden||column.responsiveHidden?grid.labels.gridResponsiveHidden:column.pin==="left"?grid.labels.gridPinnedLeft:column.pin==="right"?grid.labels.gridPinnedRight:"");if(note){const span=h("span","cx-grid-column-note");setText(span,note);row.append(span)}
41
+ row.onkeydown=event=>{if(!event.altKey||!["ArrowUp","ArrowDown"].includes(event.key))return;const siblings=cgSiblings(grid,parent),target=siblings[siblings.indexOf(id)+(event.key==="ArrowUp"?-1:1)];event.preventDefault();event.stopPropagation();if(target)cgChoose(grid,id,target,event.key==="ArrowDown")};handle.onpointerdown=event=>cgDragChoice(grid,list,row,handle,id,event);container.appendChild(row);if(heldId===id)(heldHandle?handle:input).focus({preventScroll:true});if(node){const children=h("div","cx-grid-column-children");children.setAttribute("role","group");children.setAttribute("aria-label",title);children.hidden=row.hidden;container.appendChild(children);render(node,children,level+1)}}};render(null,list,0)}
42
+ function cgChoose(grid,id,targetId,after){const node=grid.columnGroupModel.byId.get(id);if(node){cgMoveGroup(grid,node,targetId,after);return}
43
+ const to=cgSibling(grid,id,targetId,after),order=to.id!==id&&cgMoved(grid,id,to.id,to.after);if(!order)return;if(!grid.columnGroupModel.owner.has(id))grid.columns.find(column=>column.id===id).pin=grid.columns.find(column=>column.id===cgFirst(grid,to.id)).pin;grid.setColumnOrder(order)}
44
+ function cgDragChoice(grid,list,row,handle,id,event){if(event.button)return;event.preventDefault();handle.focus();handle.setPointerCapture(event.pointerId);let target=null;const clear=()=>{for(const item of list.querySelectorAll(".cx-drop-before,.cx-drop-after"))item.classList.remove("cx-drop-before","cx-drop-after")};handle.onpointermove=move=>{clear();target=null;const over=document.elementFromPoint(move.clientX,move.clientY)?.closest(".cx-grid-column-choice");if(!over||over===row||!list.contains(over))return;const box=over.getBoundingClientRect(),to=cgSibling(grid,id,over.dataset.cxColumn||over.dataset.cxGroup,move.clientY>box.top+box.height / 2);
45
+ if(to.id===id)return;target=to;const marked=list.querySelector('[data-cx-column="'+CSS.escape(to.id)+ '"],[data-cx-group="'+CSS.escape(to.id)+ '"]');marked?.classList.add(to.after?"cx-drop-after":"cx-drop-before")};handle.onpointerup=handle.onpointercancel=up=>{handle.onpointermove=handle.onpointerup=handle.onpointercancel=null;clear();if(up.type==="pointerup"&&target)cgChoose(grid,id,target.id,target.after)}}
46
+ function cgColumn(grid,to){const node=grid.columnGroupModel.byId.get(to.id);if(!node)return grid.columns.find(column=>column.id===to.id);const members=grid.columns.filter(column=>node.leaves.includes(column.id));return to.after?members[members.length-1]:members[0]}
47
+ function cgInstall(grid){const shared=Object.getPrototypeOf(grid);grid.buildHeader=function(){this.groupCells=[];cgOrder(this);shared.buildHeader.call(this);cgHeader(this)};grid.applyWidths=function(...args){shared.applyWidths.apply(this,args);if(this.cols&&this.headCells?.length===this.cols.length)cgUnsplit(this);cgLayout(this)};grid.setColumnPinned=function(id,pin){const root=cgPath(this.columnGroupModel,id)[0];if(root&&CG_PINS.includes(pin))for(const column of this.columns)if(root.leaves.includes(column.id))column.pin=pin;return shared.setColumnPinned.call(this,id,pin)};grid.moveReportingColumn=function(column,target,after){const to=cgSibling(this,column.id,target.id,after),landing=cgColumn(this,to);if(to.id!==column.id&&landing&&landing!==column)shared.moveReportingColumn.call(this,column,landing,to.after)};grid.moveColumnChoice=function(column,target,after){cgChoose(this,column.id,target.id,after)};grid.syncColumnControls=function(...args){return this.columnList?cgColumnControls(this):shared.syncColumnControls.apply(this,args)}}
48
+ Object.assign(DataGrid.prototype,{startColumnGroups(){if(this.columnGroupModel||this.destroyed)return;if(!this.view.columnGroups?.length){this.columnGroupModel={roots:[],byId:new Map(),owner:new Map(),depth:0};return}
49
+ const model=this.columnGroupModel=cgRead(this.view),byId=new Map(this.columns.map(column=>[column.id,column]));if(!this.initialState)for(const root of model.roots)if(root.pinned!==undefined)for(const id of root.leaves)byId.get(id).pin=root.pinned||"none";const titles=new Map();for(const node of model.byId.values())titles.set(node.title,(titles.get(node.title)||0)+ 1);for(const[id,node]of model.owner){const column=byId.get(id);column.section??=titles.get(node.title)>1?cgPath(model,id).map(group=>group.title).join(" · "):node.title}
50
+ cgOrder(this);if(this.defaultState){const saved=new Map(this.defaultState.columns.map(column=>[column.id,column]));const declared={columnGroupModel:model,columns:this.defaultState.columns.map(entry=>({id:entry.id,pin:entry.pin??byId.get(entry.id).pin}))};for(const root of model.roots)if(root.pinned!==undefined)for(const column of declared.columns)if(root.leaves.includes(column.id))column.pin=root.pinned||"none";cgOrder(declared);this.defaultState.columns=declared.columns.map(column=>{const entry=saved.get(column.id);if("pin" in entry)entry.pin=column.pin||"none";return entry})}
51
+ cgInstall(this);this.lifetime.signal.addEventListener("abort",()=>{this.cancelColumnDrag?.();this.host.classList.remove("cx-column-groups");this.host.style.removeProperty("--cx-pin-left-w")},{once:true});this.buildHeader();this.syncColumnControls();this.render()},getColumnGroups(){const model=this.columnGroupModel;if(!model?.roots.length)return[];const shown=new Set((this.cols||[]).map(column=>column.id)),order=this.columns.map(column=>column.id);const describe=node=>{const columns=order.filter(id=>node.leaves.includes(id));return Object.freeze({id:node.id,title:node.title,columns:Object.freeze(columns),visibleColumns:Object.freeze(columns.filter(id=>shown.has(id))),pin:this.columns.find(column=>column.id===columns[0]).pin||"none",groups:Object.freeze(cgSiblings(this,node).filter(id=>model.byId.has(id)).map(id=>describe(model.byId.get(id))))})};return Object.freeze(cgSiblings(this,null).filter(id=>model.byId.has(id)).map(id=>describe(model.byId.get(id))))},setColumnGroupVisible(id,visible){const node=this.columnGroupModel?.byId.get(id);if(!node||typeof visible!=="boolean"||this.destroyed)throw new Error("CanopyX: visibility needs a declared column group and a boolean.");const members=this.columns.filter(column=>node.leaves.includes(column.id));if(!visible&&this.columns.every(column=>column.hidden||members.includes(column)))throw new Error("CanopyX: keep at least one column visible.");for(const column of members)column.hidden=!visible;this.buildHeader();this.syncColumnControls();this.scheduleRender();this.noteViewPreferenceIntent?.();this.stateChanged()}});installed.add(context.DataGrid.prototype)}};(global.CanopyXParts||=[]).push(["column-groups",part])})(typeof window!=="undefined"?window:globalThis);
@@ -0,0 +1,14 @@
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"40fe19a1046d6813",install(context){if(installed.has(context.GRID_DATA_IMPL))return;const{GRID_DATA_IMPL,PROTOCOL,validateGridRows,inlineGridSource,loadSidecar}=context;const ROW_SCRIPTS=':scope>script[type="application/x-canopyx-rows"]';const nextTask=()=>globalThis.scheduler?.yield?scheduler.yield():new Promise(resolve=>setTimeout(resolve));async function inflateRows(text){if(typeof DecompressionStream!=="function")throw new Error("CanopyX: this browser cannot read compressed row data.");let bytes;if(Uint8Array.fromBase64)bytes=Uint8Array.fromBase64(text);else{const binary=atob(text);bytes=new Uint8Array(binary.length);for(let i=0;i<binary.length;i++)bytes[i]=binary.charCodeAt(i)}
2
+ return new Response(new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"))).text()}
3
+ function rowDataSource(grid,outer){const view=grid.view,data=view.rowData,rows=view.rows,ids=new Set(),declared=new Set(view.columns.map(column=>column.id));const columns=data.columns,chunks=data.chunks,sidecar=data.base!==undefined;if(data.version!==1||!Array.isArray(columns)||!columns.length||new Set(columns).size!==columns.length||columns.some(id=>!declared.has(id))||
4
+ ![undefined,"gzip"].includes(data.encoding)||![undefined,true,false].includes(data.ordered)||
5
+ sidecar&&typeof data.base!=="string"||
6
+ !(Array.isArray(chunks)?!sidecar:Number.isInteger(chunks)&&chunks>=0))
7
+ throw new Error("CanopyX: invalid grid rowData.");const scripts=!sidecar&&!Array.isArray(chunks)?grid.host.querySelectorAll(ROW_SCRIPTS):null;const total=Array.isArray(chunks)?chunks.length:chunks;if(scripts&&scripts.length!==total)throw new Error("CanopyX: the grid has "+scripts.length+" row data scripts, not "+total+".");const files=[];rows.length=0;const read=index=>Array.isArray(chunks)?chunks[index]:sidecar?files[index]||=loadSidecar(data.base,view.id,"rows/"+index):scripts[index].textContent;const expand=row=>{if(!Array.isArray(row)||row.length>columns.length+1)throw new Error("CanopyX: invalid compact grid row.");const key=row[0],cells={};for(let c=1;c<row.length;c++){if(row[c]===undefined)throw new Error("CanopyX: compact grid cells cannot be undefined; use null for an empty cell.");if(row[c]!=null)columns[c-1]==="__proto__"?Object.defineProperty(cells,"__proto__",{value:row[c],enumerable:true,writable:true,configurable:true}):cells[columns[c-1]]=row[c]}
8
+ if(key===null||typeof key!=="object")return{id:key,cells};const record={id:key.id,cells};if(key.tone!==undefined)record.tone=key.tone;if(key.className!==undefined)record.className=key.className;return record};let previous=null,finished=false,advance,progress;const waitForChunk=()=>progress=new Promise(resolve=>advance=resolve);waitForChunk();const done=(async()=>{const active=()=>{if(grid.destroyed)throw new DOMException("Grid was destroyed.","AbortError")};for(let index=0;index<total;index++){active();let chunk=await read(index);active();if(sidecar&&index+1<total)read(index+1).catch(()=>{});if(typeof chunk==="string"){if(data.encoding){chunk=await inflateRows(chunk);active()}
9
+ chunk=JSON.parse(chunk)}
10
+ if(!Array.isArray(chunk))throw new Error("CanopyX: a row data chunk is not an array of rows.");const decoded=chunk.map(expand);validateGridRows(decoded,view.columns,ids);if(data.ordered)for(const row of decoded){if(previous!==null&&!(previous<row.id))throw new Error("CanopyX: row data is not in its declared identity order.");previous=row.id}
11
+ for(const row of decoded)rows.push(row);if(rows.length>data.count)break;files[index]=null;advance();waitForChunk();await nextTask()}
12
+ active();if(rows.length!==data.count)throw new Error("CanopyX: row data holds "+rows.length+" rows, not "+data.count+".");finished=true;ids.clear();if(Array.isArray(chunks))chunks.fill(null);scripts?.forEach(script=>{script.textContent=""});grid.version++;grid.scheduleRender()})();done.catch(()=>outer.reset());done.then(()=>{if(!grid.destroyed){grid.version++;grid.scheduleRender?.()}},()=>{});let complete=null;outer.invalidate=()=>{complete=null};const all=async()=>{await done;return complete||=inlineGridSource(view)};outer.decoded=done;if(!Object.hasOwn(grid,"columnFacetItems"))grid.columnFacetItems=async function(...args){await outer.decoded;return Object.getPrototypeOf(this).columnFacetItems.apply(this,args)};if(view.reporting?.facetColumns?.length)outer.facet=async(request,options)=>(await all()).facet(request,options);return{async query(query,options){if(data.ordered&&!view.reporting&&!query.search&&!query.filters.length&&!query.filterGroup&&!query.sort.length){const end=Math.min(data.count,query.offset+query.limit);while(rows.length<end&&!finished)await Promise.race([progress,done]);if(options.signal.aborted)throw new DOMException("Grid cancelled.","AbortError");if(!finished)return{protocol:PROTOCOL,type:"gridPage",view:view.id,revision:"inline",offset:query.offset,total:data.count,matched:data.count,items:rows.slice(query.offset,end)}}
13
+ return(await all()).query(query,options)}}}
14
+ GRID_DATA_IMPL.source=rowDataSource;installed.add(context.GRID_DATA_IMPL)}};(global.CanopyXParts||=[]).push(["data",part])})(typeof window!=="undefined"?window:globalThis);
@@ -0,0 +1,18 @@
1
+ /* ---------- detail rows: the expander,the row and the content inside the visible width ---------- */
2
+ .cx-detail-toggle{flex:none;display:inline-grid;place-items:center;width:var(--cx-size-20px);height:var(--cx-size-20px);margin-inline-end:var(--cx-size-4px);padding:0;border:0;border-radius:var(--cx-radius-sm);background:transparent;color:var(--cx-muted);cursor:pointer;}
3
+ .cx-detail-toggle::before{content:"";width:var(--cx-size-6px);height:var(--cx-size-6px);border-right:var(--cx-size-1d6px) solid currentColor;border-bottom:var(--cx-size-1d6px) solid currentColor;transform:translateX(var(--cx-size-neg-1px)) rotate(-45deg);transition:transform .12s;}
4
+ .cx-detail-toggle[aria-expanded="true"]::before{transform:translateY(var(--cx-size-neg-1px)) rotate(45deg);}
5
+ .cx-detail-toggle:hover{color:var(--cx-text);background:var(--cx-hover-bg);}
6
+ .cx-detail-toggle:focus-visible{outline:var(--cx-size-2px) solid var(--cx-accent);outline-offset:var(--cx-size-neg-2px);}
7
+ .cx-detail-toggle[aria-expanded="true"]{color:var(--cx-accent);}
8
+ .cx-data-grid .cx-row.cx-detail-row{background:var(--cx-alt-bg);border-bottom:var(--cx-size-1px) solid var(--cx-line-strong);}
9
+ .cx-data-grid .cx-row.cx-detail-row:hover{background:var(--cx-alt-bg);}
10
+ .cx-data-grid .cx-detail-cell{flex:none;width:100%;padding:0;border-right:0;overflow:visible;}
11
+ .cx-data-grid .cx-detail-row .cx-detail-body{padding:var(--cx-size-10px) var(--cx-size-12px) var(--cx-size-12px);box-sizing:border-box;}
12
+ .cx-detail-pending{color:var(--cx-muted);font-style:italic;}
13
+ .cx-detail-error{margin:0 0 var(--cx-size-8px);color:var(--cx-tone-danger);}
14
+ .cx-detail-view{height:var(--cx-size-320px);border:var(--cx-size-1px) solid var(--cx-line);border-radius:var(--cx-radius);background:var(--cx-surface);}
15
+ .cx-detail-view.cx-host{padding:0;}
16
+ .cx-phone .cx-detail-view{height:var(--cx-size-280px);}
17
+ @media (prefers-reduced-motion:reduce){.cx-detail-toggle::before{transition:none;}}
18
+ @media (forced-colors:active){.cx-detail-toggle[aria-expanded="true"]{color:Highlight;}}
@@ -0,0 +1,51 @@
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"6c96974326d65a4c",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,GRID_ROWS,GRID_REPORTING,h,setText,fill,gridButton,gridFreeze,cellText,formatting,labelsFor,requireIdentifier,GridRowOffsets,TreeGrid,createDataGrid,createTreeGrid,disposeRowHostCells,OWN_ARROWS,hostHtml}=context;const ROWS_BASE={};for(const name of["render","renderPass","trimInactiveCache","syncRowOffsets","setFocus","onKey","fillRow","destroy"])
2
+ ROWS_BASE[name]=DataGrid.prototype[name];GRID_ROWS.view=ROWS_BASE;function rowsLowerBound(list,value){let low=0,high=list.length;while(low<high){const middle=(low+high)>>>1;if(list[middle]<value)low=middle+1;else high=middle}
3
+ return low}
4
+ function rowsAtOrBefore(list,value,field){let low=0,high=list.length;while(low<high){const middle=(low+high)>>>1;if(list[middle][field]<=value)low=middle+1;else high=middle}
5
+ return low?list[low-1]:null}
6
+ function rowsResolve(segment,k){const list=segment.details,first=segment.first;let low=0,high=list.length;while(low<high){const middle=(low+high)>>>1;if(list[middle]- first+middle+1<k)low=middle+1;else high=middle}
7
+ return low<list.length&&list[low]- first+low+1===k?[list[low],true]:[first+k-low,false]}
8
+ Object.assign(DataGrid.prototype,{prepareRowProjection(){this.resolveDetails();const bounds=this.rowBounds(),groups=this.projectionGroups?.()||null;const key=[this.snapshot,groups,bounds.first,bounds.last,this.groupCollapseVersion||0,this.detailVersion||0,this.rowH];if(this.rowProjectionKey?.every((value,index)=>value===key[index]))return;const previous=this.rowProjection,sameResult=previous&&this.rowProjectionKey[0]===this.snapshot,top=this.logicalTop();const anchorIndex=sameResult&&previous.rowCount()?previous.rowAt(top):-1;const anchor=anchorIndex>=0?{item:previous.rowItem(anchorIndex),offset:top-previous.rowTop(anchorIndex)}:null;const details=this.detailOrdinals||[],segments=[],runs=[],headers=new Map();let count=0;const run=(first,last)=>{if(first>last)return;const list=details.slice(rowsLowerBound(details,first),rowsLowerBound(details,last+1));const segment={start:count,length:last-first+1+list.length,first,last,details:list};segments.push(segment);runs.push(segment);count +=segment.length};const walk=(list,level,path)=>{for(const group of list){const first=Math.max(bounds.first,group.offset),last=Math.min(bounds.last,group.offset+group.count-1);if(first>last)continue;headers.set(group.key,count);segments.push({start:count,length:1,header:this.groupHeader(group,level,path)});count++;if(this.collapsedGroups?.has(group.key))continue;if(group.groups?.length)walk(group.groups,level+1,[...path,group]);else run(first,last)}};if(groups)walk(groups,1,[]);else run(bounds.first,bounds.last);const recordIndex=ordinal=>{const segment=rowsAtOrBefore(runs,ordinal,"first");return!segment||ordinal>segment.last?-1:segment.start+ordinal-segment.first+rowsLowerBound(segment.details,ordinal)};const offsets=new GridRowOffsets(count,this.rowH);for(const segment of runs)segment.details.forEach((ordinal,index)=>{const height=this.detailHeights?.get(this.detailKeyAt(ordinal));offsets.set(segment.start+ordinal-segment.first+index+1,height??this.detailEstimate())});if(this.autoRows)for(const[ordinal,height]of this.rowHeights){const index=recordIndex(ordinal);if(index>=0)offsets.set(index,height)}
9
+ const projection={rowCount:()=>count,rowAt:top=>offsets.at(top),rowTop:index=>offsets.top(index),recordIndex,rowItem:index=>{const segment=rowsAtOrBefore(segments,index,"start");if(!segment||index>=segment.start+segment.length)return null;if(segment.header)return segment.header;const[ordinal,detail]=rowsResolve(segment,index-segment.start);return detail?this.detailItem(ordinal):this.rowItem(ordinal)},indexOf:row=>{if(!row)return -1;if(row.group)return this.groupHeaders?.get(row.group.key)===row?headers.get(row.group.key)??-1:-1;if(row.status==="detail"){const index=this.detailRecords?.get(row.detailOf)===row&&details.includes(row.ordinal)?recordIndex(row.ordinal):-1;return index<0?-1:index+1}
10
+ return row._ri<0||this.indexOf(row)<0?-1:recordIndex(row._ri)},recordWindow:(first,last)=>{const ranges=[];for(const segment of runs){const start=Math.max(first,segment.start),end=Math.min(last,segment.start+segment.length-1);if(start<=end)ranges.push({first:rowsResolve(segment,start-segment.start)[0],last:rowsResolve(segment,end-segment.start)[0]})}
11
+ return{first:ranges[0]?.first??bounds.first,last:ranges.at(-1)?.last??-1,ranges}}};this.rowProjection=projection;this.rowProjectionKey=key;this.rowOffsets=offsets;this.rowOffsetOwner=projection;this.rowOffsetFirst=bounds.first;this.relocateFocus?.(projection);this.total=offsets.top(count);this.grid.setAttribute("aria-rowcount",String(count +(this.summaryControls?.parentElement===this.grid?2:1)+(this.headDepth||0)));const index=anchor?projection.indexOf(anchor.item):-1;this.syncScrollRange(index>=0?projection.rowTop(index)+ anchor.offset:top)},syncRowOffsets(){if(!this.rowProjection)ROWS_BASE.syncRowOffsets.call(this)},render(){if(!this.rowsAttached)return ROWS_BASE.render.call(this);if(!this.destroyed&&this.acceptedQuery){this.acceptRows?.();this.prepareRowProjection()}
12
+ let measured=ROWS_BASE.render.call(this);for(let pass=0;this.rowProjection&&pass<3&&this.measureDetailRows();pass++){measured=true;ROWS_BASE.render.call(this)}
13
+ return measured},renderPass(){return ROWS_BASE.renderPass.call(this,this.rowProjection||this)},trimInactiveCache(){if(this.rowProjection)this.window=this.rowProjection.recordWindow(this.window.first,this.window.last);return ROWS_BASE.trimInactiveCache.call(this)},measureDetailRows(){if(!this.detailMeasure||!this.rowProjection||!this.hasGridSize())return false;this.detailMeasure=false;const rows=this.rowProjection,anchor=this.gridRowAnchor();let changed=false;for(const[row,element]of this.rendered){if(row.status!=="detail")continue;const index=rows.indexOf(row),measured=element.offsetHeight;if(index<0||!measured)continue;const height=Math.max(this.rowH,measured);row.hpx=height;if(height!==(this.rowOffsets.heights.get(index)??this.rowH)){this.rowOffsets.set(index,height);this.detailHeights.set(row.detailOf,height);changed=true}}
14
+ if(changed){this.total=this.rowOffsets.top(rows.rowCount());this.syncScrollRange(rows.rowTop(anchor.index)+ anchor.offset)}
15
+ return changed},setFocus(row,focus){if(!this.rowProjection)return ROWS_BASE.setFocus.call(this,row,focus);if(row&&!row.group&&row.status!=="detail"&&!this.pendingQuery)this.revealRecord?.(row);if(this.rowProjection.indexOf(row)<0||this.destroyed)return;this.pendingNavigation=null;this.focused=row;this.revealGridRow(row,this.rowProjection.indexOf(row),this.rowProjection);if(this.render()){this.revealGridRow(row,this.rowProjection.indexOf(row),this.rowProjection);this.render()}
16
+ if(focus)this.rendered.get(this.focused)?.focus({preventScroll:true})},onKey(event){const row=event.target.closest(".cx-row")?._w;if(!row||!this.rowProjection||event.defaultPrevented||event.isComposing||event.altKey||event.target.matches(OWN_ARROWS))
17
+ return ROWS_BASE.onKey.call(this,event);const navigation=this.pendingNavigation;if(navigation?.focus&&navigation.epoch===this.queryEpoch&&this.focused===row)return ROWS_BASE.onKey.call(this,event);if(this.pendingQuery&&["ArrowUp","ArrowDown","PageUp","PageDown","Home","End"].includes(event.key)){event.preventDefault();return}
18
+ if(this.projectedRowKey?.(event,row)||this.detailRowKey(event,row))return;const synthetic=row.group||row.status==="detail";if(synthetic&&["Home","End"].includes(event.key)){event.preventDefault();event.stopPropagation();if(event.ctrlKey||event.metaKey)this.focusResult(event.key==="Home"?0:this.rowCount()- 1,true)
19
+ .catch(error=>{if(error.name!=="AbortError")this.emit("error",{error})});else this.setFocus(row,true);return}
20
+ if(!["ArrowUp","ArrowDown","PageUp","PageDown"].includes(event.key))return ROWS_BASE.onKey.call(this,event);const rows=this.rowProjection,step=event.key.startsWith("Page")?Math.max(1,Math.floor((this.grid.clientHeight-this.head.offsetHeight)/ this.rowH)) : 1;
21
+ const direction=/Up$/.test(event.key) ? -1 : 1, here = rows.indexOf(row), page = Math.max(this.rowH, this.grid.clientHeight - this.head.offsetHeight);
22
+ const proposed=!event.key.startsWith("Page")?here+direction*step:!this.detailOrdinals?.length?(this.autoRows?this.gridPageStep(row,direction,rows):here+direction*step):
23
+ direction>0?Math.max(here+1,rows.rowAt(rows.rowTop(here)+ page)):Math.min(here-1,rows.rowAt(Math.max(0,rows.rowTop(here)- page+this.rowH-1)));const cells=this.rowCells(this.rendered.get(row)),column=synthetic?-1:cells.findIndex(cell=>cell.contains(event.target));event.preventDefault();event.stopPropagation();if(this.paged&&(proposed<0||proposed>=rows.rowCount())){const bounds=this.rowBounds();if(synthetic&&(direction<0&&bounds.first===0||direction>0&&bounds.last===this.rowCount()- 1)){this.setFocus(rows.rowItem(Math.max(0,Math.min(rows.rowCount()- 1,proposed))),true);return}
24
+ const ordinal=synthetic?(direction<0?bounds.first-step:bounds.last+step):row._ri+direction*step;this.focusResult(Math.max(0,Math.min(this.rowCount()- 1,ordinal)),true,column,null)
25
+ .catch(error=>{if(error.name!=="AbortError")this.emit("error",{error})});return}
26
+ const target=rows.rowItem(Math.max(0,Math.min(rows.rowCount()- 1,proposed)));if(target?.group||target?.status==="detail")this.setFocus(target,true);else if(target)this.focusResult(target._ri,true,column,null).catch(error=>{if(error.name!=="AbortError")this.emit("error",{error})})}});const DETAIL_MEASURE=DataGrid.prototype.measureGridColumn;Object.assign(DataGrid.prototype,{attachRows(){if(this.rowsAttached)return;this.rowsAttached=true;this.detailRecords=new Map();this.detailByOrdinal=new Map();this.detailHeights=new Map();this.detailBodies=new Map();this.detailOrdinals=[];this.detailVersion=0;this.detailRowObserver=new ResizeObserver(()=>{this.detailMeasure=true;this.scheduleRender()});const width=()=>{if(this.grid.clientWidth)this.host.style.setProperty("--cx-view-w",this.grid.clientWidth+"px")};this.viewWidthObserver=new ResizeObserver(width);this.viewWidthObserver.observe(this.grid);width();this.host.classList.toggle("cx-has-details",this.detailEnabled())},detailEnabled(){return!!(this.view.reporting?.detail||this.opts.detail)},measureGridColumn(column){const result=DETAIL_MEASURE.call(this,column);return this.rowsAttached&&this.detailEnabled()&&column===this.columns.find(item=>!item.hidden&&!item.responsiveHidden)&&!column.fixed?
27
+ Object.freeze({...result,width:result.width+24}):result},hasDetail(record){if(!this.detailEnabled())return false;const options=this.opts.detail||{},declared=this.view.reporting?.detail||{};if(typeof options.hasDetail==="function")return options.hasDetail(record)!==false;if(["render","html","grid","tree"].some(name=>typeof options[name]==="function")||declared.template||declared.renderer||
28
+ typeof options.source==="function"&&declared.view)return true;return!!record.detail&&typeof record.detail==="object"},detailEstimate(){return this.rowH*4},detailKeyAt(ordinal){return this.detailByOrdinal.get(ordinal)},detailItem(ordinal){return this.detailRecords.get(this.detailByOrdinal.get(ordinal))||null},isDetailExpanded(id){return!!this.detailRecords?.has(id)},syncDetailOrdinals(){this.detailOrdinals=[...this.detailByOrdinal.keys()].sort((a,b)=>a-b);this.detailVersion++;this.version++},expandDetail(id){return this.setDetailExpanded(id,true)},collapseDetail(id){return this.setDetailExpanded(id,false)},toggleDetail(id){return this.setDetailExpanded(id,!this.isDetailExpanded(id))},setDetailExpanded(id,expanded){requireIdentifier(id,"detail row ID");const record=[...this.rowCache.values()].find(row=>row.key===id);if(this.destroyed||typeof expanded!=="boolean"||!this.acceptedQuery||this.pendingQuery||!record||!this.hasDetail(record.h))
29
+ throw new Error("CanopyX: detail rows need a loaded record of the accepted result that has details.");if(expanded===this.isDetailExpanded(id))return Promise.resolve(this.detailBodies.get(id)?.ready);const ownedFocus=this.canvas.contains(document.activeElement),focus=this.focused;if(expanded){this.detailRecords.set(id,{_ri:-1,hpx:this.detailEstimate(),status:"detail",h:{cells:{},detail:true},detailOf:id,ordinal:record._ri});this.detailByOrdinal.set(record._ri,id)}else{this.detailRecords.delete(id);this.detailByOrdinal.delete(record._ri);this.disposeDetail(id)}
30
+ this.syncDetailOrdinals();this.render();if(ownedFocus&&focus&&this.rowProjection.indexOf(this.focused)>=0)this.rendered.get(this.focused)?.focus({preventScroll:true});this.emit("detail",{id,expanded});return expanded?this.detailContent(id,record.h).ready:Promise.resolve()},resolveDetails(){if(this.detailSnapshot===this.snapshot)return;this.detailSnapshot=this.snapshot;if(!this.detailRecords?.size)return;const loaded=new Map([...this.rowCache.values()].map(row=>[row.key,row._ri]));this.detailByOrdinal.clear();for(const[id,item]of[...this.detailRecords]){if(loaded.has(id)){item.ordinal=loaded.get(id);this.detailByOrdinal.set(item.ordinal,id)}
31
+ else{this.detailRecords.delete(id);this.disposeDetail(id)}}
32
+ this.syncDetailOrdinals()},relocateFocus(projection){const focus=this.focused;if(!focus||projection.indexOf(focus)>=0)return;if(focus.status==="detail"){const record=this.rowCache.get(focus.ordinal);if(record)this.focused=record}
33
+ else this.focusedHeader?.(projection)},fillRow(element,row){if(row.status==="detail")return this.paintDetailRow(element,row);this.releaseDetailRow(element);ROWS_BASE.fillRow.call(this,element,row);if(this.rowsAttached&&!row.status&&element._kind==="node")this.paintExpander(element,row)},paintExpander(element,row){const slot=element._cells?.[0];if(!slot)return;const offered=this.hasDetail(row.h),expanded=offered&&this.isDetailExpanded(row.key);let button=slot.detailToggle;if(!offered){button?.remove();slot.detailToggle=null;element.removeAttribute("aria-expanded");return}
34
+ if(!button){button=h("button","cx-detail-toggle");button.type="button";button.tabIndex=-1;button.addEventListener("click",event=>{event.stopPropagation();const record=button.closest(".cx-row")?._w;if(record&&!record.status&&!this.pendingQuery)Promise.resolve().then(()=>this.toggleDetail(record.key)).catch(error=>this.emit("error",{error}))});slot.detailToggle=button}
35
+ if(button.parentNode!==slot.cell||slot.cell.firstChild!==button)slot.cell.prepend(button);button.setAttribute("aria-expanded",String(expanded));button.disabled=!!this.pendingQuery;const label=expanded?this.labels.gridHideDetail:this.labels.gridShowDetail;if(button.getAttribute("aria-label")!==label){button.setAttribute("aria-label",label);button.title=label}},paintDetailRow(element,row){const entry=this.detailContent(row.detailOf,this.rowCache.get(row.ordinal)?.h);element._w=row;if(element._kind!=="detail"||element._detail!==entry){disposeRowHostCells(element);this.releaseDetailRow(element);element.textContent="";element._kind="detail";element._layout=this.layout;element._attrs={};element._cls=null;element.className="cx-row cx-detail cx-detail-auto cx-detail-row";element.setAttribute("role","row");for(const name of["data-cx-id","data-cx-status","aria-selected","aria-busy","data-cx-group","aria-expanded"])element.removeAttribute(name);const cell=h("div","cx-cell cx-detail-cell");cell.setAttribute("role","gridcell");cell.appendChild(entry.body);element.appendChild(cell);element._cells=[{cell,content:cell}];element._detail=entry;this.detailRowObserver.observe(element);this.detailMeasure=true}
36
+ const cell=element._cells[0].cell;cell.id=this.id+"-detail-"+entry.slot;cell.setAttribute("aria-colspan",String(this.cols.length));element.dataset.cxDetail=row.detailOf;element.style.height="auto";element.style.width=this.rowWidth+"px"},releaseDetailRow(element){if(element._kind!=="detail")return;this.detailRowObserver?.unobserve(element);element._detail?.body.remove();element._detail=null;element._kind=null;element._cells=null;element.classList.remove("cx-detail","cx-detail-auto","cx-detail-row");element.removeAttribute("data-cx-detail");element.style.height=this.rowH+"px"},destroy(){if(!this.destroyed){for(const id of[...(this.detailBodies?.keys()||[])])this.disposeDetail(id);this.detailRowObserver?.disconnect();this.viewWidthObserver?.disconnect()}
37
+ return ROWS_BASE.destroy.call(this)}});const DETAIL_FOCUSABLE="a[href],button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),[tabindex]:not([tabindex='-1']),[contenteditable=true]";Object.assign(DataGrid.prototype,{detailContent(id,record){let entry=this.detailBodies.get(id);if(entry)return entry;const body=h("div","cx-detail-body"),controller=new AbortController(),events=new AbortController(),signal=events.signal;body.tabIndex=-1;body.setAttribute("role","group");const primary=this.visibleColumns()[0];body.setAttribute("aria-label",fill(this.labels.gridDetailOf,{record:record&&primary&&cellText(record.cells[primary.id],primary,this.fmt)||id}));entry={body,controller,events,slot:this.detailSeq=(this.detailSeq||0)+ 1,api:null};this.detailBodies.set(id,entry);for(const type of["click","dblclick","pointerover","focusout","contextmenu"])body.addEventListener(type,event=>event.stopPropagation(),{signal});body.addEventListener("focusin",event=>{event.stopPropagation();const item=this.detailRecords.get(id);if(item&&this.focused!==item){this.pendingNavigation=null;this.focused=item;this.scheduleRender()}},{signal});body.addEventListener("keydown",event=>{event.stopPropagation();if(event.key!=="Escape"||event.isComposing||event.defaultPrevented&&!event.target.matches(".cx-row"))return;const item=this.detailRecords.get(id);if(item){event.preventDefault();this.setFocus(item,true)}},{signal});entry.ready=this.fillDetail(entry,id,record);return entry},async fillDetail(entry,id,record){const body=entry.body,options=this.opts.detail||{},declared=this.view.reporting?.detail||{},signal=entry.controller.signal;const context=Object.freeze({id,signal,api:this.host.canopyx,labels:this.labels,onDispose:cleanup=>{const dispose=()=>{try{cleanup()}catch(error){console.error(error)}};if(signal.aborted)dispose();else signal.addEventListener("abort",dispose,{once:true})}});const row=gridFreeze(JSON.parse(JSON.stringify(record||{id,cells:{}})));setText(body,this.labels.loading);body.classList.add("cx-detail-pending");try{const renderer=declared.renderer&&this.plugins.map(item=>item.detailRenderers?.[declared.renderer]).find(item=>typeof item==="function");if(typeof options.render==="function"||renderer){body.replaceChildren();await(renderer||options.render)(row,body,context)}else if(typeof options.html==="function"){const html=await options.html(row,context);if(typeof html!=="string")throw new Error("CanopyX: a detail html renderer returns a string.");if(!signal.aborted)hostHtml(body,html,this.opts.sanitizeHtml,"detail")}else if(declared.template){const template=document.getElementById(declared.template);if(!(template instanceof HTMLTemplateElement))throw new Error("CanopyX: the detail template is not a <template> element.");const fragment=template.content.cloneNode(true);for(const slot of fragment.querySelectorAll("[data-cx-field]")){const column=this.columns.find(item=>item.id===slot.getAttribute("data-cx-field"));setText(slot,column?cellText(row.cells[column.id],column,this.fmt):"")}
38
+ body.replaceChildren(fragment)}else if(["grid","tree","source"].some(name=>typeof options[name]==="function")||declared.view){await this.nestedDetail(entry,row,context)}else this.payloadDetail(body,row.detail)}catch(error){if(signal.aborted||this.destroyed)return;body.replaceChildren();const message=h("p","cx-detail-error");setText(message,fill(this.labels.gridDetailFailed,{error:error.message}));body.appendChild(message);body.appendChild(gridButton(this.labels.gridRetry,()=>{entry.controller.abort();entry.controller=new AbortController();entry.api?.destroy();entry.api=null;entry.ready=this.fillDetail(entry,id,record)}));this.emit("error",{error})}finally{body.classList.remove("cx-detail-pending");if(!this.destroyed){this.detailMeasure=true;this.scheduleRender()}}},async nestedDetail(entry,row,context){const options=this.opts.detail||{},declared=this.view.reporting?.detail||{};const factory=typeof options.grid==="function"?options.grid:typeof options.tree==="function"?options.tree:null;const spec=factory?await factory(row,context):null;if(factory&&(!spec||typeof spec!=="object"||!spec.view||typeof spec.view!=="object"))
39
+ throw new Error("CanopyX: a nested detail returns { view, source?, options? }.");if(!spec&&!declared.view)throw new Error("CanopyX: a detail source needs the definition's detail view.");const source=spec?spec.source:typeof options.source==="function"?await options.source(row,context):undefined;const view=JSON.parse(JSON.stringify(spec?spec.view:declared.view)),tree=view?.type==="view",payload=row.detail;if(!view||!["grid","view"].includes(view.type))throw new Error("CanopyX: a nested detail view is a grid or a tree document.");if(!source&&view.source?.kind!=="host"&&(tree?!view.roots?.length:!view.rows?.length)){if(tree?payload?.type!=="tree"||!Array.isArray(payload.roots):payload?.type!=="grid"||!Array.isArray(payload.rows))
40
+ throw new Error("CanopyX: the record has no " +(tree?"tree":"grid")+ " detail payload.");view.source={kind:"inline"};if(tree)view.roots=payload.roots;else view.rows=payload.rows}
41
+ delete view.stateKey;delete view.deepLink;if(context.signal.aborted)return;const host=h("div","cx-detail-view");host.setAttribute("data-cx-theme",this.host.getAttribute("data-cx-theme")||"auto");entry.body.replaceChildren(host);entry.api=(tree?createTreeGrid:createDataGrid)(host,{labels:this.opts.labels,toolbar:false,...(spec?.options||{}),view,...(source?{source}:{})});await entry.api.ready},payloadDetail(body,payload){if(!payload||typeof payload!=="object"||!["table","properties","html"].includes(payload.type)||
42
+ payload.type==="table"&&(!Array.isArray(payload.columns)||!Array.isArray(payload.rows)||payload.rows.some(item=>!Array.isArray(item)))||
43
+ payload.type==="properties"&&!Array.isArray(payload.items)||payload.type==="html"&&typeof payload.html!=="string")
44
+ throw new Error("CanopyX: a detail payload is a table, properties or html; grid and tree payloads need the definition's detail view.");const holder=h("div");TreeGrid.prototype.renderDetail.call({opts:this.opts,fmt:this.fmt},{detail:payload},holder);body.replaceChildren(...holder.firstChild.childNodes)},disposeDetail(id){const entry=this.detailBodies?.get(id);if(!entry)return;this.detailBodies.delete(id);entry.controller.abort();entry.events.abort();try{entry.api?.destroy()}catch(error){console.error(error)}
45
+ entry.body.remove();this.detailHeights.delete(id)},enterDetail(row){const entry=this.detailBodies.get(row.detailOf);if(!entry)return;if(entry.api?.focus){entry.api.focus();return}
46
+ const target=[...entry.body.querySelectorAll(DETAIL_FOCUSABLE)].find(element=>element.getClientRects().length)||entry.body;target.focus({preventScroll:true})},detailRowKey(event,row){if(row.status!=="detail"||event.target.closest(".cx-detail-body"))return false;if(["Enter","F2"].includes(event.key)){event.preventDefault();this.enterDetail(row);return true}
47
+ if(event.key==="Escape"){event.preventDefault();const record=this.rowCache.get(row.ordinal);if(record)this.setFocus(record,true);else this.focusResult(row.ordinal,true).catch(()=>{});return true}
48
+ if(["ArrowLeft","ArrowRight"," "].includes(event.key)){event.preventDefault();return true}
49
+ return false}});const DETAIL_REQUEST=GRID_REPORTING.exportRequest,DETAIL_RECORD=GRID_REPORTING.exportRecord;GRID_REPORTING.exportRequest=(grid,options)=>{const extra=DETAIL_REQUEST?.(grid,options)||{};if(options.includeDetails!==undefined&&typeof options.includeDetails!=="boolean")throw new Error("CanopyX: includeDetails is a boolean.");return grid.detailEnabled?.()&&(options.includeDetails??grid.view.reporting?.detail?.print!==false)?{...extra,details:true}:extra};GRID_REPORTING.exportRecord=(grid,request,row)=>{const record=DETAIL_RECORD(grid,request,row);if(!request.details||!grid.hasDetail(row))return record;const detail=grid.detailExport(row);return detail?gridFreeze({...record,detail}):record};Object.assign(DataGrid.prototype,{detailExport(row){const options=this.opts.detail||{},view=this.view.reporting?.detail?.view;let payload=typeof options.export==="function"?options.export(gridFreeze(JSON.parse(JSON.stringify(row)))):row.detail;if(payload==null)return null;if(payload.type==="grid"&&view?.type==="grid"||payload.type==="tree"&&view?.type==="view"){const columns=view.columns.filter(column=>!column.hidden),rows=[];const fmt=formatting(this.lang,view.timeZone==="local",labelsFor(view.labels,this.opts.labels));const text=(cells,column)=>cellText(cells?.[column.id],column,fmt);if(payload.type==="grid")for(const item of payload.rows||[])rows.push(columns.map(column=>text(item.cells,column)));else{const walk=(nodes,level)=>{for(const node of nodes||[]){rows.push(columns.map((column,index)=>index?text(node.cells,column):" ".repeat(level)+(Object.hasOwn(node.cells||{},column.id)?text(node.cells,column):node.label)));walk(node.children,level+1)}};walk(payload.roots,0)}
50
+ payload={type:"table",columns:columns.map(column=>column.title),rows}}
51
+ if(!payload||typeof payload!=="object"||!["table","properties","html"].includes(payload.type))return null;return gridFreeze(JSON.parse(JSON.stringify(payload)))}});installed.add(context.DataGrid.prototype)}};(global.CanopyXParts||=[]).push(["detail-rows",part])})(typeof window!=="undefined"?window:globalThis);
@@ -0,0 +1,44 @@
1
+ /* Editing part: editors in cells,changed and invalid marks,messages,the edit bar and the drawer's edit form. */
2
+ .cx-data-grid .cx-cell.cx-dirty{background-image:linear-gradient(135deg,var(--cx-accent) 0 var(--cx-size-6px),transparent var(--cx-size-6px));background-repeat:no-repeat;}
3
+ .cx-data-grid .cx-cell.cx-invalid{background-image:linear-gradient(135deg,var(--cx-tone-danger) 0 var(--cx-size-6px),transparent var(--cx-size-6px));background-repeat:no-repeat;box-shadow:inset 0 0 0 var(--cx-size-1px) var(--cx-tone-danger);}
4
+ .cx-data-grid .cx-row.cx-row-invalid > .cx-cell:first-child{box-shadow:inset var(--cx-size-3px) 0 0 var(--cx-tone-danger);}
5
+ .cx-data-grid .cx-cell.cx-editable{cursor:text;}
6
+ .cx-data-grid .cx-cell.cx-editing{padding:0 var(--cx-size-2px);overflow:visible;}
7
+ .cx-data-grid .cx-cell.cx-editing > :not(.cx-editor-host){display:none;}
8
+ .cx-editor-host{flex:1;min-width:0;height:100%;display:flex;align-items:center;}
9
+ .cx-editor-combo{display:flex;flex:1;min-width:0;height:100%;align-items:center;}
10
+ .cx-editor{box-sizing:border-box;width:100%;min-width:0;height:calc(100% - var(--cx-size-4px));margin:0;padding:0 var(--cx-size-6px);font:inherit;color:var(--cx-text);background:var(--cx-surface);border:var(--cx-size-1px) solid var(--cx-accent);border-radius:var(--cx-radius-sm);outline:none;color-scheme:inherit;}
11
+ .cx-editor:focus-visible{box-shadow:0 0 0 var(--cx-size-2px) var(--cx-accent-soft);}
12
+ .cx-editor[aria-invalid="true"]{border-color:var(--cx-tone-danger);}
13
+ .cx-kind-number .cx-editor{text-align:right;font-variant-numeric:tabular-nums;}
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
+ .cx-editor-list,.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-editor-list{max-height:var(--cx-size-240px);overflow:auto;padding:var(--cx-size-4px);}
17
+ .cx-editor-option,.cx-editor-note{padding:var(--cx-size-6px) var(--cx-size-8px);border-radius:var(--cx-radius-sm);white-space:nowrap;}
18
+ .cx-editor-option{cursor:pointer;}
19
+ .cx-editor-option[aria-selected="true"],.cx-editor-option:hover{background:var(--cx-selected-bg);}
20
+ .cx-editor-note{color:var(--cx-muted);}
21
+ .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;}
22
+ .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);}
23
+ .cx-editbar[hidden]{display:none;}
24
+ .cx-editbar-text{margin-right:var(--cx-size-4px);white-space:nowrap;}
25
+ .cx-editbar-errors{color:var(--cx-tone-danger);}
26
+ .cx-data-grid .cx-editbar .cx-btn{height:var(--cx-size-28px);min-height:var(--cx-size-28px);font-size:var(--cx-size-13px);padding:0 var(--cx-size-9px);}
27
+ .cx-data-grid .cx-editbar .icon{width:var(--cx-size-28px);padding:0;}
28
+ .cx-host .cx-edit-save,.cx-host .cx-edit-save:hover{background:var(--cx-accent);border-color:var(--cx-accent);color:var(--cx-on-accent);}
29
+ .cx-host .cx-edit-save:disabled{opacity:.45;}
30
+ .cx-phone .cx-editbar{order:10;flex:1 1 100%;flex-wrap:wrap;height:auto;min-height:var(--cx-size-44px);padding-block:var(--cx-size-4px);}
31
+ .cx-phone .cx-editbar-text{flex:1 1 100%;min-width:0;margin-right:0;white-space:normal;overflow-wrap:anywhere;}
32
+ .cx-drawer.cx-edit-form .cx-dl dd .cx-editor{height:var(--cx-size-34px);border-color:var(--cx-control-border);}
33
+ .cx-drawer.cx-edit-form .cx-dl dd .cx-editor:focus-visible{border-color:var(--cx-accent);}
34
+ .cx-drawer.cx-edit-form .cx-dl dd .cx-editor[aria-invalid="true"]{border-color:var(--cx-tone-danger);}
35
+ .cx-drawer.cx-edit-form.cx-phone .cx-dl dd .cx-editor{height:var(--cx-size-44px);}
36
+ .cx-drawer.cx-edit-form .cx-editor-host{height:auto;}
37
+ .cx-edit-message{margin-top:var(--cx-size-4px);font-size:var(--cx-size-13px);color:var(--cx-tone-danger);}
38
+ .cx-drawer-f{flex:none;display:flex;align-items:center;justify-content:flex-end;gap:var(--cx-size-8px);padding:var(--cx-size-12px) var(--cx-size-16px);border-top:var(--cx-size-1px) solid var(--cx-line);}
39
+ .cx-drawer-summary{margin:0 auto 0 0;font-size:var(--cx-size-13px);color:var(--cx-tone-danger);}
40
+ @media (forced-colors:active){
41
+ .cx-data-grid .cx-cell.cx-dirty{text-decoration:underline dotted;}
42
+ .cx-data-grid .cx-cell.cx-invalid{outline:var(--cx-size-2px) dashed CanvasText;outline-offset:calc(-1 * var(--cx-size-2px));}
43
+ .cx-edit-save{forced-color-adjust:auto;}
44
+ }