@evotecit/canopyx 0.3.13 → 0.3.15

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 (99) hide show
  1. package/CanopyX.Html/Assets/canopyx-calendar-edit.js +36 -18
  2. package/CanopyX.Html/Assets/canopyx-calendar-ics.js +7 -5
  3. package/CanopyX.Html/Assets/canopyx-calendar.css +124 -116
  4. package/CanopyX.Html/Assets/canopyx-calendar.js +74 -54
  5. package/CanopyX.Html/Assets/canopyx-cell-range.css +8 -8
  6. package/CanopyX.Html/Assets/canopyx-cell-range.js +1 -1
  7. package/CanopyX.Html/Assets/canopyx-changes.css +113 -113
  8. package/CanopyX.Html/Assets/canopyx-changes.js +7 -10
  9. package/CanopyX.Html/Assets/canopyx-column-groups.css +5 -5
  10. package/CanopyX.Html/Assets/canopyx-column-groups.js +1 -1
  11. package/CanopyX.Html/Assets/canopyx-data.js +2 -2
  12. package/CanopyX.Html/Assets/canopyx-detail-rows.css +9 -9
  13. package/CanopyX.Html/Assets/canopyx-detail-rows.js +2 -2
  14. package/CanopyX.Html/Assets/canopyx-editing.css +18 -18
  15. package/CanopyX.Html/Assets/canopyx-editing.js +1 -1
  16. package/CanopyX.Html/Assets/canopyx-enhance.js +2 -2
  17. package/CanopyX.Html/Assets/canopyx-facets.css +33 -33
  18. package/CanopyX.Html/Assets/canopyx-facets.js +5 -6
  19. package/CanopyX.Html/Assets/canopyx-filter-editor.css +57 -57
  20. package/CanopyX.Html/Assets/canopyx-filter-editor.js +7 -5
  21. package/CanopyX.Html/Assets/canopyx-flow-checks.css +11 -11
  22. package/CanopyX.Html/Assets/canopyx-flow-checks.js +1 -1
  23. package/CanopyX.Html/Assets/canopyx-flow-command.css +7 -7
  24. package/CanopyX.Html/Assets/canopyx-flow-command.js +1 -1
  25. package/CanopyX.Html/Assets/canopyx-flow-dialog.css +7 -7
  26. package/CanopyX.Html/Assets/canopyx-flow-dialog.js +1 -1
  27. package/CanopyX.Html/Assets/canopyx-flow-drafts.css +7 -7
  28. package/CanopyX.Html/Assets/canopyx-flow-drafts.js +1 -1
  29. package/CanopyX.Html/Assets/canopyx-flow-entry.css +17 -76
  30. package/CanopyX.Html/Assets/canopyx-flow-entry.js +9 -3
  31. package/CanopyX.Html/Assets/canopyx-flow-host.css +4 -4
  32. package/CanopyX.Html/Assets/canopyx-flow-host.js +1 -1
  33. package/CanopyX.Html/Assets/canopyx-flow-module.mjs +9 -3
  34. package/CanopyX.Html/Assets/canopyx-flow-pick.css +4 -4
  35. package/CanopyX.Html/Assets/canopyx-flow-pick.js +1 -1
  36. package/CanopyX.Html/Assets/canopyx-flow-preview.css +6 -6
  37. package/CanopyX.Html/Assets/canopyx-flow-preview.js +1 -1
  38. package/CanopyX.Html/Assets/canopyx-flow-records.css +3 -3
  39. package/CanopyX.Html/Assets/canopyx-flow-records.js +1 -1
  40. package/CanopyX.Html/Assets/canopyx-flow-tasks.css +17 -17
  41. package/CanopyX.Html/Assets/canopyx-flow-tasks.js +1 -1
  42. package/CanopyX.Html/Assets/canopyx-flow.css +79 -79
  43. package/CanopyX.Html/Assets/canopyx-flow.js +1 -1
  44. package/CanopyX.Html/Assets/canopyx-format-editor.css +23 -23
  45. package/CanopyX.Html/Assets/canopyx-format-editor.js +2 -3
  46. package/CanopyX.Html/Assets/canopyx-formatting.css +17 -17
  47. package/CanopyX.Html/Assets/canopyx-formatting.js +5 -5
  48. package/CanopyX.Html/Assets/canopyx-forms.css +5 -5
  49. package/CanopyX.Html/Assets/canopyx-forms.js +1 -1
  50. package/CanopyX.Html/Assets/canopyx-grid-module.mjs +94 -51
  51. package/CanopyX.Html/Assets/canopyx-grid-options.css +10 -0
  52. package/CanopyX.Html/Assets/canopyx-grid-options.js +47 -0
  53. package/CanopyX.Html/Assets/canopyx-grid.css +199 -261
  54. package/CanopyX.Html/Assets/canopyx-grid.js +94 -51
  55. package/CanopyX.Html/Assets/canopyx-grouping.css +17 -17
  56. package/CanopyX.Html/Assets/canopyx-grouping.js +12 -9
  57. package/CanopyX.Html/Assets/canopyx-highlight.css +14 -14
  58. package/CanopyX.Html/Assets/canopyx-highlight.js +2 -2
  59. package/CanopyX.Html/Assets/canopyx-interactive.css +94 -155
  60. package/CanopyX.Html/Assets/canopyx-interactive.js +33 -14
  61. package/CanopyX.Html/Assets/canopyx-labels-de.js +5 -0
  62. package/CanopyX.Html/Assets/canopyx-labels-es.js +5 -0
  63. package/CanopyX.Html/Assets/canopyx-labels-fr.js +5 -0
  64. package/CanopyX.Html/Assets/canopyx-labels-it.js +5 -0
  65. package/CanopyX.Html/Assets/canopyx-labels-pl.js +5 -0
  66. package/CanopyX.Html/Assets/canopyx-layouts.css +17 -17
  67. package/CanopyX.Html/Assets/canopyx-layouts.js +2 -10
  68. package/CanopyX.Html/Assets/canopyx-link.css +7 -7
  69. package/CanopyX.Html/Assets/canopyx-link.js +1 -1
  70. package/CanopyX.Html/Assets/canopyx-live.css +9 -9
  71. package/CanopyX.Html/Assets/canopyx-live.js +2 -2
  72. package/CanopyX.Html/Assets/canopyx-matrix.css +74 -74
  73. package/CanopyX.Html/Assets/canopyx-matrix.js +3 -4
  74. package/CanopyX.Html/Assets/canopyx-module.mjs +33 -14
  75. package/CanopyX.Html/Assets/canopyx-pager.css +2 -2
  76. package/CanopyX.Html/Assets/canopyx-pager.js +1 -1
  77. package/CanopyX.Html/Assets/canopyx-presenters.js +2 -2
  78. package/CanopyX.Html/Assets/canopyx-record-panel.css +49 -49
  79. package/CanopyX.Html/Assets/canopyx-record-panel.js +2 -2
  80. package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +98 -54
  81. package/CanopyX.Html/Assets/canopyx-reporting.css +214 -276
  82. package/CanopyX.Html/Assets/canopyx-reporting.js +98 -54
  83. package/CanopyX.Html/Assets/canopyx-timeline.css +28 -28
  84. package/CanopyX.Html/Assets/canopyx-timeline.js +11 -10
  85. package/CanopyX.Html/Assets/canopyx-tree-tools.css +47 -47
  86. package/CanopyX.Html/Assets/canopyx-tree-tools.js +3 -3
  87. package/CanopyX.Html/Assets/canopyx.css +81 -142
  88. package/CanopyX.Html/Assets/canopyx.js +33 -14
  89. package/JavaScript/calendar.d.ts +15 -0
  90. package/JavaScript/grid.d.ts +26 -3
  91. package/JavaScript/locales/de.mjs +5 -0
  92. package/JavaScript/locales/es.mjs +5 -0
  93. package/JavaScript/locales/fr.mjs +5 -0
  94. package/JavaScript/locales/it.mjs +5 -0
  95. package/JavaScript/locales/pl.mjs +5 -0
  96. package/JavaScript/plugins.d.ts +4 -0
  97. package/JavaScript/protocol.d.ts +1 -1
  98. package/README.md +6 -2
  99. package/package.json +5 -1
@@ -24,14 +24,15 @@ function checkPage(doc,node){checkDocument(doc,"page");if(!Array.isArray(doc.ite
24
24
  if(!Object.hasOwn(doc,"next"))throw new Error("CanopyX: page continuation is required.");if(doc.next!==null)requireIdentifier(doc.next,"page continuation");const parent=node?node.id:null;if(doc.parent!==parent)throw new Error("CanopyX: page parent does not match the requested node.");return doc}
25
25
  function isCellObject(cell){return cell!==null&&typeof cell==="object"}
26
26
  function cellValue(cell){return isCellObject(cell)?(cell.value===undefined?null:cell.value):(cell===undefined?null:cell)}
27
- const NO_TONE={cls:"",host:null};const tones=new Map();function toneOf(name){let tone=typeof name==="string"?tones.get(name):NO_TONE;if(!tone){if(!/^[a-z][a-z0-9-]{0,31}$/.test(name) || name.endsWith("-bg")) {
27
+ const NO_TONE={cls:"",host:null};const tones=new Map();function toneVariable(name,background=false,fallback){const role={success:"good",warning:"warn",danger:"bad"}[name]||name;const key="--hfx-color-status-"+role +(background?"-bg":"-fg");return "var("+key +(STATUS_FALLBACKS[key]||fallback?"," +(STATUS_FALLBACKS[key]||fallback):"")+ ")"}
28
+ function toneOf(name){let tone=typeof name==="string"?tones.get(name):NO_TONE;if(!tone){if(!/^[a-z][a-z0-9-]{0,31}$/.test(name) || name.endsWith("-bg")) {
28
29
  return NO_TONE}
29
30
  const host=!/^(neutral|info|success|warning|danger|muted)$/.test(name);
30
31
  tone={cls:" cx-tone-"+name +(host?" cx-toned":""),host:host?name:null};if(tones.size<256)tones.set(name,tone)}
31
32
  return tone}
32
33
  function applyTone(element,base,tone){element.className=base+tone.cls;toneColors(element,tone)}
33
34
  function toneColors(element,tone){if((element._tone||null)===tone.host){return}
34
- element._tone=tone.host;if(tone.host){element.style.setProperty("--cx-t","var(--cx-tone-"+tone.host+")");element.style.setProperty("--cx-t-bg","var(--cx-tone-"+tone.host+"-bg)")}else{element.style.removeProperty("--cx-t");element.style.removeProperty("--cx-t-bg")}}
35
+ element._tone=tone.host;if(tone.host){element.style.setProperty("--cx-t",toneVariable(tone.host));element.style.setProperty("--cx-t-bg",toneVariable(tone.host,true))}else{element.style.removeProperty("--cx-t");element.style.removeProperty("--cx-t-bg")}}
35
36
  function pad2(value){return value<10?"0"+value:String(value)}
36
37
  const dateTexts=new Map();function dateText(value){let texts=dateTexts.get(value);if(texts===undefined){const time=Date.parse(value);const d=new Date(time);texts=isNaN(time)?null:[String(d.getUTCFullYear()).padStart(4,"0")+ "-"+pad2(d.getUTCMonth()+ 1)+ "-"+pad2(d.getUTCDate())+ " "+pad2(d.getUTCHours())+ ":"+pad2(d.getUTCMinutes()),String(d.getFullYear()).padStart(4,"0")+ "-"+pad2(d.getMonth()+ 1)+ "-"+pad2(d.getDate())+ " "+pad2(d.getHours())+ ":"+pad2(d.getMinutes())];if(dateTexts.size>=DATE_CACHE)dateTexts.clear();dateTexts.set(value,texts)}
37
38
  return texts}
@@ -88,6 +89,12 @@ function trusted(kind,value){if(trustPolicy===undefined){try{trustPolicy=global.
88
89
  catch(_){trustPolicy=null}}
89
90
  return trustPolicy?trustPolicy["create"+kind](value):value}
90
91
  function hostHtml(element,html,sanitize,kind){const text=String(html??""),value=sanitize?sanitize(text,{kind}):text;if(value?.nodeType)element.replaceChildren(value);else if(typeof value==="string")element.innerHTML=trusted("HTML",value);else if(global.trustedTypes?.isHTML?.(value))element.innerHTML=value;else throw new Error("CanopyX: invalid sanitizeHtml result.")}
92
+ const STATUS_FALLBACKS={"--hfx-color-status-bad-bg":"light-dark(#f9e4e4,#3c272a)","--hfx-color-status-bad-fg":"light-dark(#b8292a,#f47171)","--hfx-color-status-good-bg":"light-dark(#e2f0e9,#1f362f)","--hfx-color-status-good-fg":"light-dark(#177345,#4cc78a)","--hfx-color-status-info-bg":"light-dark(#e4ecfa,#262f41)","--hfx-color-status-info-fg":"light-dark(#2b5fc0,#74a2f7)","--hfx-color-status-neutral-bg":"light-dark(#eeeff0,#2a2c31)","--hfx-color-status-neutral-fg":"light-dark(#5b6069,#a0a5ae)","--hfx-color-status-warn-bg":"light-dark(#f8efde,#3b3322)","--hfx-color-status-warn-fg":"light-dark(#8c5c05,#eeb640)"};const MODE_ATTRIBUTES=["data-theme","data-cx-theme","data-hfx-mode","data-bs-theme","data-hfx-theme","data-hfx-tokens"];const THEME_ATTRIBUTES=[...MODE_ATTRIBUTES,"data-hfx-style","data-hfx-density","data-cx-fallback-mode","style","class"];function watchTheme(host,signal){const attributes=THEME_ATTRIBUTES;const previous=host.getAttribute("data-cx-fallback-mode");const refresh=()=>{const parent=host.parentElement&&getComputedStyle(host.parentElement);const inherited=parent?.colorScheme;let mode=null;if(!parent?.getPropertyValue("--hfx-color-surface").trim()){outer:for(let owner=host;owner;owner=owner.parentElement)for(const attribute of MODE_ATTRIBUTES){const value=owner.getAttribute(attribute);if(value==="light"||value==="dark"){mode=value;break outer}}}
93
+ if(parent?.getPropertyValue("--hfx-color-surface").trim()||!mode&&inherited&&inherited!=="normal"){if(host.hasAttribute("data-cx-fallback-mode"))host.removeAttribute("data-cx-fallback-mode")}else if(host.getAttribute("data-cx-fallback-mode")!==(mode||"auto"))host.setAttribute("data-cx-fallback-mode",mode||"auto")};const observer=new MutationObserver(refresh);for(let owner=host;owner;owner=owner.parentElement)observer.observe(owner,{attributes:true,attributeFilter:attributes});refresh();const dispose=()=>{observer.disconnect();if(previous===null)host.removeAttribute("data-cx-fallback-mode");else host.setAttribute("data-cx-fallback-mode",previous)};signal?.addEventListener("abort",dispose,{once:true});return dispose}
94
+ function overlayTheme(host,element,inherit=false){for(const name of["data-hfx-style","data-hfx-mode","data-hfx-density"]){const owner=host.closest("["+name+"]");if(owner&&!inherit)element.setAttribute(name,owner.getAttribute(name));else element.removeAttribute(name)}
95
+ const theme=getComputedStyle(host),parent=inherit&&host.parentElement&&getComputedStyle(host.parentElement);element.style.colorScheme=theme.colorScheme;const copied=[];for(const name of theme)if((name.startsWith("--cx-")||name.startsWith("--hfx-"))&&(!parent||theme.getPropertyValue(name)!==parent.getPropertyValue(name))){element.style.setProperty(name,theme.getPropertyValue(name));copied.push(name)}
96
+ return copied}
97
+ function watchOverlayTheme(host,element,signal){if(signal?.aborted)return()=>{};let copied=[];const refresh=()=>{for(const name of copied)element.style.removeProperty(name);copied=overlayTheme(host,element,element.parentElement===host.parentElement)};const observer=new MutationObserver(refresh);for(let owner=host;owner;owner=owner.parentElement)observer.observe(owner,{attributes:true,attributeFilter:THEME_ATTRIBUTES});const media=["(prefers-color-scheme:dark)","(prefers-contrast:more)","(forced-colors:active)"].map(query=>matchMedia(query));for(const query of media)query.addEventListener("change",refresh);const dispose=()=>{observer.disconnect();for(const query of media)query.removeEventListener("change",refresh);signal?.removeEventListener("abort",dispose)};signal?.addEventListener("abort",dispose,{once:true});dispose.refresh=refresh;refresh();return dispose}
91
98
  DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{gridViews:"Views",gridViewName:"View name",gridSaveView:"Save view",gridRemoveView:"Remove view",gridResetView:"Reset view",gridChooseView:"Choose a saved view",gridApply:"Apply",gridFindColumn:"Find a column…",gridShowAllColumns:"Show all",gridResetColumns:"Reset columns",gridReorderColumn:"{column}: drag, or press Alt+Up or Alt+Down to move",gridPinnedLeft:"Pinned left",gridPinnedRight:"Pinned right",gridResponsiveHidden:"Hidden at this width",gridExport:"Export",gridVisibleRows:"Visible rows",gridSelectedRows:"Selected rows",gridAllRows:"All rows",gridDownloadCsv:"Download CSV",gridCopy:"Copy",gridExportProgress:"{completed} of {total} records",gridExportReading:"{completed} records",gridExportCancelled:"Export cancelled",gridCancelExport:"Cancel export",gridMore:"More",gridPrint:"Print",gridDetails:"Record details",gridDetailsSection:"Details",gridPreviousRecord:"Previous record",gridNextRecord:"Next record",gridCloseRecord:"Close"}));DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{recordBack:"Back",recordBackTo:"Back to {record}",recordTrail:"Record trail",recordShowing:"Showing {record}",recordTabs:"Record views",recordSections:"Jump to a section",recordOtherFields:"Other fields",recordShowEmpty:"Show empty fields ({count})",recordHideEmpty:"Hide empty fields",recordActions:"Record actions",recordCopyValues:"Copy values",recordCopyJson:"Copy as JSON",recordCopyLink:"Copy link",recordCopied:"Copied to the clipboard",recordPrint:"Print record",recordExportCsv:"Export CSV",recordExportJson:"Export JSON",recordOpenAll:"Open all in grid",recordLoadMore:"Load more",recordCount:"{shown} of {total}",recordItems:"{count} items",recordMore:"+{count}",recordShowAll:"Show all {count}",recordRetry:"Retry",recordLoadFailed:"Could not load the record details: {error}",recordUnavailable:"Record {id} is not available here.",recordField:"Field",recordValue:"Value",recordSection:"Section",recordBefore:"Before",recordAfter:"After",recordAdded:"Added",recordRemoved:"Removed",recordChanged:"{count} changed fields"}));function boundedPattern(source,exact=true,whole=false){const bad=()=>{throw new Error("CanopyX: pattern requires literals, character classes and at most one variable repetition anchored at the start; groups, alternatives and backreferences are unsupported.")};if(typeof source!=="string"||source.length>256)bad();let width=0,variable=0;for(let i=0;i<source.length;){const unit=source[i++];if(unit==="^"&&i===1||unit==="$"&&i===source.length)continue;if(unit==="\\"){const escape=source[i++];if(!escape||!/^[dDsSwWbBnrtfv\\.^$*+?{}\[\]()|/]$/.test(escape)) bad();
92
99
  }else if(unit==="["){let closed=false;while(i<source.length){const c=source[i++];if(c==="\\"){const escape=source[i++];if(!escape||!/^[dDsSwWbnrtfv\\.^$*+?{}\[\]()|/-]$/.test(escape)) bad(); }
93
100
  else if(c==="]"){closed=true;break}}
@@ -204,7 +211,7 @@ throw new Error("CanopyX: unknown source kind '"+kind+"'.")}
204
211
  async function remoteJson(url,type,timeoutMs,lifetime,signal){const controller=new AbortController();const signals=Array.from(new Set([lifetime,signal].filter(Boolean)));const abort=()=>controller.abort();signals.forEach((item)=>{if(item.aborted)abort();else item.addEventListener("abort",abort,{once:true})});let timedOut=false;const timer=setTimeout(()=>{timedOut=true;abort()},timeoutMs);try{const response=await fetch(url,{headers:{Accept:"application/json"},signal:controller.signal});if(!response.ok){if(response.body)await response.body.cancel().catch(()=>{});throw new Error(response.status+" "+response.statusText)}
205
212
  return checkDocument(await response.json(),type)}catch(error){if(timedOut)throw new Error("CanopyX: remote request timed out after "+timeoutMs+" milliseconds.");throw error}finally{abort();clearTimeout(timer);signals.forEach((item)=>item.removeEventListener("abort",abort))}}
206
213
  class TreeGrid{constructor(host,options){const view=checkDocument(options.view,"view");requireIdentifier(view.id,"view ID");if(view.columns.some(column=>column.format!==undefined))throw new Error("CanopyX: number formats apply to flat grids only.");this.host=host;this.view=view;this.opts=options;for(const name of["isSelectable","beforeSelect","sanitizeHtml"]){if(options[name]!==undefined&&typeof options[name]!=="function")throw new Error("CanopyX: "+name+" must be a function.")}
207
- this.id="cx" + ++instanceSeq;this.lifetime=new AbortController();this.source=options.source||sourceFor(view,{signal:this.lifetime.signal,timeoutMs:options.requestTimeoutMs});this.queryAware=this.source.queryAware===true;this.dataLifetime=new AbortController();this.acceptedQuery=Object.freeze({version:1,sort:null,filters:Object.freeze([])});this.datasetRevision=null;this.rowH=view.rowHeight||28;this.pageSize=view.pageSize||100;this.retainedNodeLimit=options.retainedNodeLimit===undefined?1024:options.retainedNodeLimit;if(!validStructuralCount(this.retainedNodeLimit))throw new Error("CanopyX: retainedNodeLimit must be a nonnegative 32-bit integer.");this.inactiveNodeLimit=options.inactiveNodeLimit===undefined?4096:options.inactiveNodeLimit;if(!validStructuralCount(this.inactiveNodeLimit))throw new Error("CanopyX: inactiveNodeLimit must be a nonnegative 32-bit integer.");this.multi=view.selection==="multiple";this.lang=document.documentElement.lang||undefined;this.labels=labelsFor(view.labels,options.labels);this.tools=Object.assign({},view.toolbar,options.toolbar);if(Object.entries(this.tools).some(([name,value])=>!/^(search|filters|columns|views|export|more|matches|layout)$/.test(name) || typeof value !== "boolean"))
214
+ this.id="cx" + ++instanceSeq;this.lifetime=new AbortController();this.source=options.source||sourceFor(view,{signal:this.lifetime.signal,timeoutMs:options.requestTimeoutMs});this.queryAware=this.source.queryAware===true;this.dataLifetime=new AbortController();this.acceptedQuery=Object.freeze({version:1,sort:null,filters:Object.freeze([])});this.datasetRevision=null;this.rowH=view.rowHeight||40;this.pageSize=view.pageSize||100;this.retainedNodeLimit=options.retainedNodeLimit===undefined?1024:options.retainedNodeLimit;if(!validStructuralCount(this.retainedNodeLimit))throw new Error("CanopyX: retainedNodeLimit must be a nonnegative 32-bit integer.");this.inactiveNodeLimit=options.inactiveNodeLimit===undefined?4096:options.inactiveNodeLimit;if(!validStructuralCount(this.inactiveNodeLimit))throw new Error("CanopyX: inactiveNodeLimit must be a nonnegative 32-bit integer.");this.multi=view.selection==="multiple";this.lang=document.documentElement.lang||undefined;this.labels=labelsFor(view.labels,options.labels);this.tools=Object.assign({},view.toolbar,options.toolbar);if(Object.entries(this.tools).some(([name,value])=>!/^(search|filters|columns|views|export|more|matches|layout)$/.test(name) || typeof value !== "boolean"))
208
215
  throw new Error("CanopyX: toolbar options are booleans by control name.");this.local=view.timeZone==="local";this.fmt=formatting(this.lang,this.local,this.labels);this.kind="tree";this.plugins=viewPlugins(options,this.kind);const renderers=hostRenderers(options.renderers||{},view.columns,this.plugins);if(!options.formatLink!==!options.parseLink){throw new Error("CanopyX: formatLink and parseLink define one link scheme and are given together.")}
209
216
  this.columns=view.columns.map((column,index)=>({...column,hidden:index>0&&!!column.hidden,render:Object.hasOwn(renderers,column.id)?renderers[column.id]:null}));this.columns.forEach(prepareWidth);this.columns.forEach(validateColumnValues);this.keyMap=new Map();this.root={key:null,h:null,parent:null,level:0,idx:0,expanded:true,children:null,total:null,next:null,loading:false,error:null};if(!options.source&&(!view.source||view.source.kind==="inline")){this.root.inlineItems=view.roots||[]}
210
217
  this.rows=[];this.tops=[0];this.total=0;this.version=1;this.rendered=new Map();this.pool=[];this.focused=null;this.selected=new Set();this.anchor=null;this.filter=null;this.declaredFilter=null;this.filterExpand=false;this.activeFilters=new Set();this.sortState=null;this.typeBuf="";this.typeAt=0;this.rafId=0;this.iconTemplates=this.buildIcons(view.icons||{});this.initializing=true;this.prepareViewModes();this.build();this.buildToolbar();if(view.deepLink)loadPart(LINKS).catch(()=>{});this.ready=this.modesReady?Promise.all([this.init(),this.modesReady]).then(([result])=>result):this.init()}
@@ -271,8 +278,7 @@ value.sort===undefined||typeof value!=="object")throw new Error("CanopyX: query
271
278
  const named=new Set(value.filters);if(named.size!==value.filters.length||value.filters.some(id=>!(grid.view.filters||[]).some(f=>f.id===id)))
272
279
  throw new Error("CanopyX: query filters must be unique declared IDs.");const filters=Object.freeze((grid.view.filters||[]).filter(f=>named.has(f.id)).map(f=>f.id));return Object.freeze({version:1,sort,filters})}
273
280
  function snapshotRevision(value,expected){requireIdentifier(value&&value.revision,"dataset revision");if(expected!=null&&value.revision!==expected)throw new Error("CanopyX: dataset revision changed; reload the roots before continuing.");return value.revision}
274
- Object.assign(TreeGrid.prototype,{getQuery(){return{version:1,sort:this.sortState&&Object.assign({},this.sortState),filters:Array.from(this.activeFilters)}},sourceRequest(signal){return this.queryAware?{signal:signal||this.dataLifetime.signal,query:this.acceptedQuery,revision:this.datasetRevision}:{signal}},syncQueryControls(){this.toolbar.querySelectorAll("[data-cx-filter]").forEach(input=>{input.checked=this.activeFilters.has(input.getAttribute("data-cx-filter"))})},querySortHeader(id){const query=this.pendingQuery||this.acceptedQuery,state=query.sort&&query.sort.column===id?query.sort.direction:null;this.sort(id,state==="asc"?"desc":state==="desc"?null:"asc").catch(()=>{})}});Object.assign(TreeGrid.prototype,{retireWrapper(w){w._invalidated=true;this.retirePage(w);if(this.inlineRetained&&this.inlineRetained.get(w.key)===w)this.inlineRetained.delete(w.key);if(this.inactiveCache)this.inactiveCache.delete(w);if(this.keyMap.get(w.key)===w)this.keyMap.delete(w.key);w.children=null;w.inlineCache=null;w.inlineOrder=null;const selected=this.selected.delete(w);if(this.anchor===w)this.anchor=null;return selected?w:null}});Object.assign(TreeGrid.prototype,{build(){const host=this.host;host.classList.add("cx-host");if(!host.hasAttribute("data-cx-theme")){host.setAttribute("data-cx-theme","auto")}
275
- host.style.setProperty("--cx-row-h",this.rowH+"px");this.setTreeColumnPinned(this.view.pinTreeColumn);this.toolbar=h("div","cx-toolbar");this.toolbar.hidden=true;const grid=this.grid=h("div","cx-grid");grid.id=this.id;grid.setAttribute("role","treegrid");grid.setAttribute("aria-label",this.view.label);if(this.multi){grid.setAttribute("aria-multiselectable","true")}
281
+ Object.assign(TreeGrid.prototype,{getQuery(){return{version:1,sort:this.sortState&&Object.assign({},this.sortState),filters:Array.from(this.activeFilters)}},sourceRequest(signal){return this.queryAware?{signal:signal||this.dataLifetime.signal,query:this.acceptedQuery,revision:this.datasetRevision}:{signal}},syncQueryControls(){this.toolbar.querySelectorAll("[data-cx-filter]").forEach(input=>{input.checked=this.activeFilters.has(input.getAttribute("data-cx-filter"))})},querySortHeader(id){const query=this.pendingQuery||this.acceptedQuery,state=query.sort&&query.sort.column===id?query.sort.direction:null;this.sort(id,state==="asc"?"desc":state==="desc"?null:"asc").catch(()=>{})}});Object.assign(TreeGrid.prototype,{retireWrapper(w){w._invalidated=true;this.retirePage(w);if(this.inlineRetained&&this.inlineRetained.get(w.key)===w)this.inlineRetained.delete(w.key);if(this.inactiveCache)this.inactiveCache.delete(w);if(this.keyMap.get(w.key)===w)this.keyMap.delete(w.key);w.children=null;w.inlineCache=null;w.inlineOrder=null;const selected=this.selected.delete(w);if(this.anchor===w)this.anchor=null;return selected?w:null}});Object.assign(TreeGrid.prototype,{build(){const host=this.host;host.classList.add("cx-host");watchTheme(host,this.lifetime.signal);host.style.setProperty("--cx-row-h",this.rowH+"px");this.setTreeColumnPinned(this.view.pinTreeColumn);this.toolbar=h("div","cx-toolbar");this.toolbar.hidden=true;const grid=this.grid=h("div","cx-grid");grid.id=this.id;grid.setAttribute("role","treegrid");grid.setAttribute("aria-label",this.view.label);if(this.multi){grid.setAttribute("aria-multiselectable","true")}
276
282
  this.head=h("div","cx-head");this.head.setAttribute("role","rowgroup");this.headRow=h("div","cx-hrow");this.headRow.setAttribute("role","row");this.headRow.setAttribute("aria-rowindex","1");this.head.appendChild(this.headRow);this.canvas=h("div","cx-canvas");this.canvas.setAttribute("role","rowgroup");grid.appendChild(this.head);grid.appendChild(this.canvas);this.live=h("div","cx-live cx-sr-only");this.live.setAttribute("aria-live","polite");this.outline=h("div","cx-outline");host.appendChild(this.toolbar);host.appendChild(grid);host.appendChild(this.live);host.appendChild(this.outline);this.buildHeader();this.onScroll=()=>{this.scheduleRender();this.stateChanged()};this.onPrint=()=>{const table=this.outlineElement();this.outline.replaceChildren(...table?[table]:[])};grid.addEventListener("scroll",this.onScroll,{passive:true});grid.addEventListener("click",(e)=>this.onClick(e));grid.addEventListener("dblclick",(e)=>this.onDblClick(e));grid.addEventListener("pointerover",(e)=>this.cellTip(e));grid.addEventListener("keydown",(e)=>this.onKey(e));this.canvas.addEventListener("focusin",(e)=>{const rowEl=e.target.closest(".cx-row");if(rowEl&&rowEl._w&&this.focused!==rowEl._w){this.focusRevision=(this.focusRevision||0)+ 1;this.focused=rowEl._w;this.scheduleRender()}});this.canvas.addEventListener("focusout",(e)=>this.settleCell(e.relatedTarget));global.addEventListener("beforeprint",this.onPrint);this.onHash=(e)=>{const href=global.location.href;const last=this.locationEvent;this.locationEvent=last&&last.href===href&&last.type!==e.type?null:{href,type:e.type};if(this.locationEvent)this.readLocation()};global.addEventListener("hashchange",this.onHash);global.addEventListener("popstate",this.onHash);const viewWidth=()=>{if(grid.clientWidth)host.style.setProperty("--cx-view-w",grid.clientWidth+"px")};viewWidth();this.resizeObserver=new ResizeObserver(()=>{viewWidth();this.scheduleRender()});this.resizeObserver.observe(grid)},visibleColumns(){return this.columns.filter((column)=>!column.hidden)},buildHeader(){const active=document.activeElement;const focusedColumn=active&&this.headRow.contains(active)?active.closest(".cx-hcell").getAttribute("data-cx-column"):null;const focusedResize=active&&active.classList.contains("cx-resize");const nextCols=this.visibleColumns();const layoutChanged=!this.cols||this.cols.length!==nextCols.length||nextCols.some((column,i)=>column!==this.cols[i]);this.cols=nextCols;this.columnIndex=this.cols.map((column)=>this.columns.indexOf(column));if(layoutChanged){this.layout=(this.layout||0)+ 1;this.version++}
277
283
  this.grid.setAttribute("aria-colcount",String(this.cols.length));this.headRow.textContent="";this.headCells=this.cols.map((column,i)=>{const cell=h("div","cx-hcell cx-kind-" +(column.kind||"text"));cell.setAttribute("role","columnheader");cell.setAttribute("aria-label",column.title);if(column.headerDescription)cell.setAttribute("aria-description",column.headerDescription);cell.title=column.headerDescription??column.title;cell.setAttribute("data-cx-column",column.id);cell.setAttribute("aria-colindex",String(i+1));if(column.sortable){const sorted=this.sortState&&this.sortState.column===column.id?this.sortState.direction:null;cell.setAttribute("aria-sort",sorted==="asc"?"ascending":sorted==="desc"?"descending":"none");const button=h("button","cx-sort");button.type="button";button.title=column.headerDescription??column.title;setText(button,column.title);button.addEventListener("click",(event)=>this.queryAware?this.querySortHeader(column.id,event):this.sort(column.id,sorted==="asc"?"desc":sorted==="desc"?null:"asc"));cell.appendChild(button)}else{const title=h("span","cx-htext");title.title=column.headerDescription??column.title;setText(title,column.title);cell.appendChild(title)}
278
284
  const grip=h("span","cx-resize");grip.setAttribute("aria-label",fill(this.labels.resizeColumn,{column:column.title}));grip.tabIndex=0;grip.setAttribute("role","separator");grip.setAttribute("aria-orientation","vertical");grip.setAttribute("aria-valuemin",column.minW);grip.setAttribute("aria-valuemax",column.maxW);grip.addEventListener("keydown",(e)=>{if(!["ArrowLeft","ArrowRight","Home"].includes(e.key))return;e.preventDefault();e.stopPropagation();column.width=columnWidth(column,e.key==="Home"?40:column.width +(e.key==="ArrowLeft"?-1:1)*(e.shiftKey?50:10));this.applyWidths();this.stateChanged()});grip.addEventListener("pointerdown",(e)=>this.startResize(e,column));cell.appendChild(grip);this.headRow.appendChild(cell);return cell});this.applyWidths();if(focusedColumn){const index=this.cols.findIndex((c)=>c.id===focusedColumn);const button=index>=0?this.headCells[index].querySelector(focusedResize?".cx-resize":".cx-sort"):null;if(button){button.focus()}}},rowCount(){return this.rows.length},rowItem(index){return this.rows[index]},rowTop(index){return this.tops[index]},rowAt(y){const tops=this.tops;let lo=0;let hi=this.rows.length-1;while(lo<hi){const mid=(lo+hi+1)>>1;if(tops[mid]<=y){lo=mid}else{hi=mid-1}}
@@ -519,7 +525,7 @@ if(active>=0){list.children[active].setAttribute("aria-selected","false")}
519
525
  active=active<0?(delta<0?hits.length-1:0):(active+delta+hits.length)%hits.length;const item=list.children[active];item.setAttribute("aria-selected","true");input.setAttribute("aria-activedescendant",item.id);item.scrollIntoView({block:"nearest"})};input.addEventListener("input",()=>{cancelNavigation();sequence++;clearTimeout(timer);hits=[];close();timer=setTimeout(run,150)});input.addEventListener("keydown",(e)=>{if(e.defaultPrevented||e.isComposing)return;if(e.key==="ArrowDown"||e.key==="ArrowUp"){move(e.key==="ArrowDown"?1:-1)}else if(e.key==="Enter"){if(!list.hidden){choose(active>=0?active:0)}}else if(e.key==="Escape"){cancelNavigation();sequence++;clearTimeout(timer);if(list.hidden){input.value=""}
520
526
  close()}else{return}
521
527
  e.preventDefault()});input.addEventListener("blur",()=>{cancelNavigation();sequence++;clearTimeout(timer);clearTimeout(blurTimer);blurTimer=setTimeout(close,150)});this.cancelSearch=()=>{cancelNavigation();sequence++;clearTimeout(timer);clearTimeout(blurTimer);hits=[];close()};box.appendChild(input);box.appendChild(list);box.appendChild(status);bar.appendChild(box);this.searchInput=input}});function watchFlyout(anchorElement,panel,isOpen){const position=()=>{if(!isOpen())return;const anchor=anchorElement.getBoundingClientRect(),fixed=getComputedStyle(panel).position==="fixed";const below=Math.max(0,global.innerHeight-anchor.bottom-8);const above=Math.max(0,anchor.top-8);const opensAbove=below<120&&above>below,room=opensAbove?above:below;panel.style.maxHeight=(panel.classList.contains("cx-fb")?room:Math.min(320,room))+ "px";panel.style.top=opensAbove?"auto":fixed?anchor.bottom+"px":"100%";panel.style.bottom=opensAbove?fixed?global.innerHeight-anchor.top+"px":"100%":"auto";const width=panel.getBoundingClientRect().width;panel.style.left=Math.max(8,Math.min(anchor.left,global.innerWidth-width-8))-(fixed?0:anchor.left)+ "px"};global.addEventListener("resize",position);global.addEventListener("scroll",position,true);return{position,dispose:()=>{global.removeEventListener("resize",position);global.removeEventListener("scroll",position,true)}}}
522
- const PART_URL=typeof document!=="undefined"&&document.currentScript?.src;const PARTS={"tree-tools":["c68e6612a9ea6eb0","canopyx-tree-tools.b8ee5c807c1d4fcd.js","canopyx-tree-tools.aae203232147f116.css"],"export":["9203882f58fa0155","canopyx-export.cedfec6462c161b4.js"],"links":["f1ab7e32b501ce37","canopyx-links.d96addeefb3eccf5.js"],"search":["521eebd6e1bbbd35","canopyx-search.14c69f4551c69f23.js"],"link":["fa0c9e888463553b","canopyx-link.4ec39bc39fa6cf50.js","canopyx-link.8435c892613926ac.css"],"highlight":["f39423961654c1e2","canopyx-highlight.a6f93893d36ab343.js","canopyx-highlight.5b6d2e0d979f9f9b.css"],"layouts":["dd1076d5684bfc6e","canopyx-layouts.670fd555e6e08904.js","canopyx-layouts.b1df38a90cb3215d.css"],"record-panel":["e78e0e62d74d6811","canopyx-record-panel.2fe5e43bb99cc818.js","canopyx-record-panel.f3691d54cefaab04.css"]},PART_CODE=new Map(),partName=name=>typeof name==="string"&&/^[a-z][a-z\d-]{0,31}$/.test(name);
528
+ const PART_URL=typeof document!=="undefined"&&document.currentScript?.src;const PARTS={"tree-tools":["ee95c5cc39223ef4","canopyx-tree-tools.ab224d048d87d4a8.js","canopyx-tree-tools.3a4ac9c0bfe85039.css"],"export":["9203882f58fa0155","canopyx-export.cedfec6462c161b4.js"],"links":["f1ab7e32b501ce37","canopyx-links.d96addeefb3eccf5.js"],"search":["521eebd6e1bbbd35","canopyx-search.14c69f4551c69f23.js"],"link":["e7268bbe59e2dfa8","canopyx-link.e883be9525ead7b7.js","canopyx-link.b872151e9cbbd0ce.css"],"highlight":["459b28e174406d32","canopyx-highlight.10ef4bb28c5a8f5b.js","canopyx-highlight.ab1040f93cbb5dbb.css"],"layouts":["606fcf6dcd00d420","canopyx-layouts.606c8fe836149ffe.js","canopyx-layouts.56a636b0f1a03185.css"],"record-panel":["94adeb844689e329","canopyx-record-panel.d0d71d38743e3476.js","canopyx-record-panel.66edd824ab6111c1.css"]},PART_CODE=new Map(),partName=name=>typeof name==="string"&&/^[a-z][a-z\d-]{0,31}$/.test(name);
523
529
  function registerPart(name,part){if(!partName(name)||!part||typeof part.version!=="string"||typeof part.install!=="function"||
524
530
  part.requires!==undefined&&!(Array.isArray(part.requires)&&part.requires.every(partName)))throw new Error("CanopyX: invalid part.");PART_CODE.set(name,part)}
525
531
  function definePart(View,name,label,context){const parts=View.prototype.parts||={};return parts[name]={name,label,context,parts}}
@@ -529,7 +535,7 @@ const load=(element,attribute,file,kind)=>new Promise((resolve,reject)=>{const u
529
535
  .catch(error=>{part.styleReady=null;throw error}));const code=PART_CODE.get(part.name)?.version===part.version||(part.scriptReady||=load(script,"src",hashed?scriptFile:"canopyx-"+part.name+".js","script")
530
536
  .finally(()=>script.remove()).catch(error=>{part.scriptReady=null;throw error}));await Promise.all([style,code])}}
531
537
  const code=PART_CODE.get(part.name);if(code?.version!==part.version)throw new Error("CanopyX: "+part.label+" and runtime must come from the same build.");for(const name of code.requires||[])await loadPart(part.parts[name]);code.install(part.context())}).catch(error=>{part.ready=null;throw error})}
532
- const partQueue=global.CanopyXParts=global.CanopyXParts||[];partQueue.forEach(entry=>registerPart(...entry));partQueue.length=0;partQueue.push=(...entries)=>{entries.forEach(entry=>registerPart(...entry));return 0};const TREE_TOOLS=definePart(TreeGrid,"tree-tools","tree tools",()=>({TreeGrid,h,setText,fill,numberFormat,watchFlyout,columnWidth,prepareWidth,cellText,cellValue,isCellObject,foldText,formatting,sortKey,compareKeys,EXPORT_FORMATS,exportFormat,exportTimeout,exportFormatsOf:EXPORT_IMPL.formatsOf,INDENT,MAX_PROTOCOL_INTEGER,hostControl,detailCell,HostCellContext,disposeHostSlot,loadPart,definePart,pluginItems,applyTone,toneOf,badgeElement,RECORD_IMPL,svgIcon,gridExportField,columnNumericTone,columnValueTone}));const SEARCH_IMPL={},SEARCH_PART=definePart(TreeGrid,"search","sidecar search",()=>({SEARCH_IMPL,checkDocument,badIndex,isCount,isIdentifier,recentCache,packBytes,shardKey,foldText,trimSeparators,rankHits,validateSearchHit,PACK_CACHE,PACK_CACHE_BYTES,SHARD_CACHE,ENTRY_CACHE,ENTRY_BATCH,SEARCH_CANDIDATES}));async function requirePart(name){await this.toolsReady;const part=Object.hasOwn(this.parts,name)?this.parts[name]:null;if(!part)throw new Error("CanopyX: unknown part '"+name+"'.");return loadPart(part)}
538
+ const partQueue=global.CanopyXParts=global.CanopyXParts||[];partQueue.forEach(entry=>registerPart(...entry));partQueue.length=0;partQueue.push=(...entries)=>{entries.forEach(entry=>registerPart(...entry));return 0};const TREE_TOOLS=definePart(TreeGrid,"tree-tools","tree tools",()=>({TreeGrid,h,setText,fill,numberFormat,watchFlyout,columnWidth,prepareWidth,cellText,cellValue,isCellObject,foldText,formatting,sortKey,compareKeys,EXPORT_FORMATS,exportFormat,exportTimeout,exportFormatsOf:EXPORT_IMPL.formatsOf,INDENT,MAX_PROTOCOL_INTEGER,hostControl,detailCell,HostCellContext,disposeHostSlot,loadPart,definePart,pluginItems,applyTone,toneOf,badgeElement,RECORD_IMPL,svgIcon,gridExportField,columnNumericTone,columnValueTone,overlayTheme}));const SEARCH_IMPL={},SEARCH_PART=definePart(TreeGrid,"search","sidecar search",()=>({SEARCH_IMPL,checkDocument,badIndex,isCount,isIdentifier,recentCache,packBytes,shardKey,foldText,trimSeparators,rankHits,validateSearchHit,PACK_CACHE,PACK_CACHE_BYTES,SHARD_CACHE,ENTRY_CACHE,ENTRY_BATCH,SEARCH_CANDIDATES}));async function requirePart(name){await this.toolsReady;const part=Object.hasOwn(this.parts,name)?this.parts[name]:null;if(!part)throw new Error("CanopyX: unknown part '"+name+"'.");return loadPart(part)}
533
539
  TreeGrid.prototype.requirePart=requirePart;function readStateColumn(c,seen){requireIdentifier(c&&c.id,"state column ID");if(seen.has(c.id)||!Number.isFinite(c.width)||c.width<40||c.width>MAX_COLUMN_WIDTH||typeof c.hidden!=="boolean")
534
540
  throw new Error("CanopyX: invalid state column.");seen.add(c.id);return{id:c.id,width:c.width,hidden:c.hidden}}
535
541
  function statePaths(value){if(!Array.isArray(value))throw new Error("CanopyX: state paths must be arrays.");return value.map(path=>{if(!Array.isArray(path)||!path.length)throw new Error("CanopyX: state paths cannot be empty.");return path.map(id=>{requireIdentifier(id,"state path ID");return id})})}
@@ -543,9 +549,9 @@ catch(error){/* Nodes removed since the snapshot are skipped. */ }
543
549
  }},applyStatePosition(state,current){if(!state.version)return;const valid=path=>{const w=path&&this.keyMap.get(path[path.length-1]);return w&&JSON.stringify(this.pathOf(w))===JSON.stringify(path)?w:null};this.setSelection(state.selection.map(valid).filter(Boolean).map(w=>w.key));if(this.destroyed||current.signal.aborted)return;const focused=valid(state.focus);if(focused&&this.indexOf(focused)>=0)this.setFocus(focused,false);const anchor=state.anchor&&valid(state.anchor.path);if(anchor)this.restoreScrollAnchor({id:anchor.key,offset:state.anchor.offset})}});const PLUGINS=new Map();function plugin(value){const item=Object.freeze({...value}),fn=handler=>typeof handler==="function";const entry=menu=>action=>action&&typeof action.label==="string"&&
544
550
  (fn(action.run)&&action.render===undefined||menu&&fn(action.render)&&action.run===undefined);if(!value||!partName(item.name)||Object.hasOwn(PARTS,item.name)||[item.init,item.destroy].some(hook=>hook!==undefined&&!fn(hook))||
545
551
  ["requires","views","toolbarItems","menuItems","exportFormats","recordActions"].some(key=>item[key]!==undefined&&!Array.isArray(item[key]))||
546
- ["cellRenderers","columnTypes","editors","events","aggregates","detailRenderers","drawerSections"].some(key=>item[key]!==undefined&&Object(item[key])!==item[key])||
552
+ ["cellRenderers","columnTypes","editors","events","aggregates","detailRenderers","drawerSections","filterOperators","sortComparators"].some(key=>item[key]!==undefined&&Object(item[key])!==item[key])||
547
553
  !(item.requires||[]).every(partName)||!(item.views||[]).every(kind=>kind==="tree"||kind==="grid")||
548
- ![...Object.values(item.cellRenderers||{}),...Object.values(item.events||{}),...Object.values(item.detailRenderers||{}),...Object.values(item.drawerSections||{})].every(fn)||
554
+ ![...Object.values(item.cellRenderers||{}),...Object.values(item.events||{}),...Object.values(item.detailRenderers||{}),...Object.values(item.drawerSections||{}),...Object.values(item.filterOperators||{}),...Object.values(item.sortComparators||{})].every(fn)||
549
555
  !(item.toolbarItems||[]).every(entry())||!(item.recordActions||[]).every(entry())||!(item.menuItems||[]).every(entry(true)))
550
556
  throw new Error("CanopyX: invalid plugin '"+item.name+"'.");item.exportFormats?.forEach(exportFormat);return item}
551
557
  function use(value){const item=plugin(value);if(PLUGINS.has(item.name))throw new Error("CanopyX: plugin '"+item.name+"' is already registered.");PLUGINS.set(item.name,item);const formats=item.exportFormats?.map(registerExportFormat)||[];return()=>{if(PLUGINS.get(item.name)===item){PLUGINS.delete(item.name);formats.forEach(dispose=>dispose())}}}
@@ -583,14 +589,27 @@ finally{navigation.dispose()}
583
589
  if(navigation.cancelled())return null;if(this.queryAware)snapshotRevision(doc,this.datasetRevision);checkDocument(doc,"counts");this.applyCounts(Object.assign({},doc,{items:doc.items.filter(item=>{const captured=mine.get(item.id);return captured&&this.keyMap.get(item.id)===captured.w&&captured.w.countRevision===captured.revision})}),true);return doc}});const LINKS=definePart(TreeGrid,"links","deep links",()=>({TreeGrid,navigationScope}));for(const name of["openDeepLink","writeDeepLink"])TreeGrid.prototype[name]=function(...args){return this.view.deepLink?loadPart(LINKS).then(()=>this[name](...args),error=>this.emit("error",{error})):Promise.resolve()};TreeGrid.prototype.readLocation=function(){if(this.destroyed)return Promise.resolve();if(this.initializing){if(this.linkOpening&&this.currentLink()!==this.link){this.linkOpening.abort();this.link=null;// Deferred reopening must not reuse the canceled link if the address changes back.
584
590
  }
585
591
  this.linkChanged=this.dataLifetime.signal;return this.ready.then(()=>undefined,()=>undefined)}
586
- return this.openDeepLink()};DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{linkFilteredBy:"Filtered by {source}: {values}",linkRemoveFilter:"Remove the filter from {source}: {values}",linkFiltered:"Filtered by {source}: {count} rows",linkHighlighted:"{count} rows highlighted from {source}",linkHighlightedRows:"{count} rows highlighted",linkCleared:"Linked filters removed"}));const LINK_IMPL={TreeGrid,queryOwn:Symbol("own conditions")},LINK_PART=definePart(TreeGrid,"link","linked views",()=>({LINK_IMPL,h,setText,filterChipContent,fill,numberFormat,cellValue,setAttr,cellMatches}));const LINK_METHODS=["setExternalFilter","clearExternalFilters","setHighlight","clearHighlight","startLinkEvents"];function linkMethods(View){for(const name of LINK_METHODS)(View.prototype[name]=function(...args){return this.destroyed?Promise.resolve(/Highlight/.test(name) ? 0 : name === "startLinkEvents" ? undefined : false) : loadPart(LINK_PART).then(() => this[name](...args));
592
+ return this.openDeepLink()};function sharedDensity(host){const value=host.closest("[data-hfx-density]")?.getAttribute("data-hfx-density");return value==="compact"||value==="comfortable"?value:null}
593
+ function watchSharedDensity(view){let previous=sharedDensity(view.host);const metrics=()=>{if(previous)view.host.style.setProperty("--cx-compact",previous==="compact"?"1":"0");else view.host.style.removeProperty("--cx-compact")};metrics();const observer=new MutationObserver(()=>{const current=sharedDensity(view.host);if(current===previous||view.destroyed)return;previous=current;metrics();refreshSharedDensity(view)});for(let owner=view.host;owner;owner=owner.parentElement){observer.observe(owner,{attributes:true,attributeFilter:["data-hfx-density"]})}
594
+ view.lifetime.signal.addEventListener("abort",()=>observer.disconnect(),{once:true})}
595
+ function layoutAnchor(view){const rows=view.rowProjection||view,top=view.logicalTop(),count=rows.rowCount();if(!count)return null;const index=rows.rowAt(top),height=(view.autoRows?null:view.rowH)||1;const offset=Math.max(0,top-rows.rowTop(index));return{index,offset,share:Math.max(0,Math.min(1,offset / height)) };
596
+ }
597
+ function layoutRows(view,anchor){const rows=view.rowProjection||view;for(const row of[...view.rowCache.values(),...view.placeholders.values()])row.hpx=view.rowH;if(view.autoRows){for(const[index,height]of view.rowHeights)if(height<view.rowH)view.rowHeights.set(index,view.rowH);view.rowOffsets=null;view.measureNeeded=true;view.render()}
598
+ else{const bounds=view.rowBounds(),count=view.rowProjection?rows.rowCount():Math.max(0,bounds.last-bounds.first+1);view.total=count*view.rowH;view.syncScrollRange(anchor?rows.rowTop(anchor.index)+ anchor.share*view.rowH:0)}
599
+ if(anchor&&view.autoRows){const top=rows.rowTop(anchor.index),height=rows.rowTop(anchor.index+1)- top;view.setLogicalTop(top+Math.min(anchor.offset,Math.max(0,height-1)))}
600
+ view.render()}
601
+ function layoutApplyTree(view){const before=view.rowH,after=densityRowHeight(view,view.density);if(before!==after){const anchor=view.captureScrollAnchor();view.rowH=after;const status=row=>{if(row&&row.hpx===before)row.hpx=after};status(view.root._status);for(const row of view.keyMap.values()){if(!row.h?.detail&&row.hpx===before)row.hpx=after;status(row._status)}
602
+ for(const row of view.rows)if(row.status)status(row);view.retop();if(anchor)view.restoreScrollAnchor(anchor)}
603
+ view.fitKey=null;view.version++;if(view.fitReady)view.applyWidths();view.render()}
604
+ function refreshSharedDensity(view){if(!view.grid)return;if(view.kind==="grid"){view.fitKey=null;view.fitFont=null;if(view.layoutMode==="cards"&&VIEW_MODES.refreshCardDensity)VIEW_MODES.refreshCardDensity(view);else{const anchor=layoutAnchor(view);view.rowH=densityRowHeight(view,view.density);view.version++;layoutRows(view,anchor);view.applyWidths()}}else layoutApplyTree(view);view.host.style.setProperty("--cx-row-h",view.rowH+"px");view.fitHeight?.()}
605
+ DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{linkFilteredBy:"Filtered by {source}: {values}",linkRemoveFilter:"Remove the filter from {source}: {values}",linkFiltered:"Filtered by {source}: {count} rows",linkHighlighted:"{count} rows highlighted from {source}",linkHighlightedRows:"{count} rows highlighted",linkCleared:"Linked filters removed"}));const LINK_IMPL={TreeGrid,queryOwn:Symbol("own conditions")},LINK_PART=definePart(TreeGrid,"link","linked views",()=>({LINK_IMPL,h,setText,filterChipContent,fill,numberFormat,cellValue,setAttr,cellMatches}));const LINK_METHODS=["setExternalFilter","clearExternalFilters","setHighlight","clearHighlight","startLinkEvents"];function linkMethods(View){for(const name of LINK_METHODS)(View.prototype[name]=function(...args){return this.destroyed?Promise.resolve(/Highlight/.test(name) ? 0 : name === "startLinkEvents" ? undefined : false) : loadPart(LINK_PART).then(() => this[name](...args));
587
606
  }).linkStub=true;View.prototype.getExternalFilters=function(){return(this.linkCommitted||[]).map(item=>JSON.parse(JSON.stringify(item)))};View.prototype.getHighlight=function(){return Array.from(this.linkHighlight||[])};View.prototype.linkStart=function(){if(this.opts.linkEvents===true)return loadPart(LINK_PART).then(()=>this.linkWatch()).catch(error=>{this.emit("error",{error});throw error})}}
588
607
  linkMethods(TreeGrid);function linkGridPart(View){(Object.hasOwn(View.prototype,"parts")?View.prototype.parts:View.prototype.parts={}).link=LINK_PART}
589
608
  function linkReady(grid,ready){return Promise.all([ready,grid.linkStart()]).then(([result])=>result)}
590
609
  function linkApi(grid){const api={};for(const name of LINK_METHODS.concat("getExternalFilters","getHighlight"))api[name]=(...args)=>grid[name](...args);return api}
591
610
  const RECORD_IMPL={},RECORD_PART=definePart(TreeGrid,"record-panel","record panel",()=>({RECORD_IMPL,TreeGrid,h,setText,fill,cellText,cellValue,isCellObject,formatValue,toneOf,applyTone,toneColors,badgeElement,loadSidecar,requireIdentifier,validateGridRows,PROTOCOL,foldText,createTreeGrid,svgIcon,gridExportField,columnNumericTone,columnValueTone,validateColumnValues}));function loadRecordPanel(){return loadPart(RECORD_PART)}
592
611
  const RECORD_STRUCTURE=["items","fields","records","events","changes","ref"];TreeGrid.prototype.recordNeeded=function(node){return!!this.view.record||!!node&&(!!node.fields||Object.values(node.cells||{}).some(cell=>isCellObject(cell)&&RECORD_STRUCTURE.some(name=>cell[name]!==undefined)))};TreeGrid.prototype.openRecord=function(id,options){return loadRecordPanel().then(()=>RECORD_IMPL.open(this,id,options))};function recordStart(grid){if(grid.view.record?.deepLink)loadRecordPanel().then(()=>RECORD_IMPL.start(grid),error=>grid.emit("error",{error}))}
593
- DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{matchesHighlight:"Highlight matches",matchesCount:"[one]{count} match[other]{count} matches",matchesPosition:"{current} of {count}",matchesNone:"No matches",matchesLoaded:"[one]{count} match in loaded rows[other]{count} matches in loaded rows",matchesPrevious:"Previous match",matchesNext:"Next match",matchesAnnounce:"Match {current} of {count}, {column}",layoutMenu:"Layout",layoutFit:"Fit to width",layoutScroll:"All columns, scroll sideways",layoutCards:"Cards",layoutDensity:"Density",layoutCompact:"Compact",layoutNormal:"Normal",layoutComfortable:"Comfortable",layoutChanged:"Layout: {mode}, {density}",layoutSort:"Sort by"}));const LAYOUT_MODES=["fit","scroll","cards"],LAYOUT_DENSITIES=["compact","default","comfortable"];const DENSITY_STEP={tree:{compact:-4,default:0,comfortable:8},grid:{compact:-6,default:0,comfortable:8}};const VIEW_MODES={};const HIGHLIGHT_KEYS=["enabled","search","filters","matchCase","ignoreAccents","minLength","filterTones"];function readSearchHighlight(value,columns){if(value===undefined||value===false)return null;const options=value===true?{}:value,tone=name=>typeof name==="string"&&/^[a-z][a-z0-9-]{0,31}$/.test(name) && !name.endsWith("-bg");
612
+ DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{matchesHighlight:"Highlight matches",matchesCount:"[one]{count} match[other]{count} matches",matchesPosition:"{current} of {count}",matchesNone:"No matches",matchesLoaded:"[one]{count} match in loaded rows[other]{count} matches in loaded rows",matchesPrevious:"Previous match",matchesNext:"Next match",matchesAnnounce:"Match {current} of {count}, {column}",layoutMenu:"Layout",layoutFit:"Fit to width",layoutScroll:"All columns, scroll sideways",layoutCards:"Cards",layoutDensity:"Density",layoutCompact:"Compact",layoutNormal:"Normal",layoutComfortable:"Comfortable",layoutChanged:"Layout: {mode}, {density}",layoutSort:"Sort by"}));const LAYOUT_MODES=["fit","scroll","cards"],LAYOUT_DENSITIES=["compact","default","comfortable"];const DENSITY_STEP={tree:{compact:-8,default:0,comfortable:0},grid:{compact:-8,default:0,comfortable:0}};const VIEW_MODES={};const HIGHLIGHT_KEYS=["enabled","search","filters","matchCase","ignoreAccents","minLength","filterTones"];function readSearchHighlight(value,columns){if(value===undefined||value===false)return null;const options=value===true?{}:value,tone=name=>typeof name==="string"&&/^[a-z][a-z0-9-]{0,31}$/.test(name) && !name.endsWith("-bg");
594
613
  if(!options||typeof options!=="object"||Array.isArray(options)||Object.keys(options).some(key=>!HIGHLIGHT_KEYS.includes(key))||
595
614
  HIGHLIGHT_KEYS.slice(0,5).some(key=>options[key]!==undefined&&typeof options[key]!=="boolean")||
596
615
  options.minLength!==undefined&&(!Number.isInteger(options.minLength)||options.minLength<1||options.minLength>32)||
@@ -606,12 +625,12 @@ function readModeState(view,state){const result={};if(state.layout!==undefined){
606
625
  throw new Error("CanopyX: invalid saved layout.");if(options)result.layout={mode:options.modes.includes(layout.mode)?layout.mode:options.mode,density:layout.density}}
607
626
  if(state.highlight!==undefined){if(typeof state.highlight!=="boolean")throw new Error("CanopyX: invalid saved highlight choice.");if(view.highlightOptions)result.highlight=state.highlight}
608
627
  return result}
609
- function densityRowHeight(view,density){const steps=DENSITY_STEP[view.kind];return Math.max(20,Math.min(64,view.baseRowH+steps[density]- steps[view.declaredDensity]))}
610
- const VIEW_MODE_METHODS={prepareViewModes(){const view=this.view,columns=this.columns||view.columns;this.highlightOptions=readSearchHighlight(view.searchHighlight,columns);this.declaredDensity=this.kind==="grid"?view.density||"default":"default";this.layoutOptions=readLayout(view.layout,this.kind,this.declaredDensity);this.baseRowH=this.rowH;this.adoptModes({layout:this.layoutOptions||{mode:"fit",density:this.declaredDensity},highlight:!!this.highlightOptions?.enabled})},adoptModes(choice){if(choice.layout){this.layoutMode=choice.layout.mode;this.density=choice.layout.density}
628
+ function densityRowHeight(view,density){const shared=sharedDensity(view.host);if(shared)return shared==="compact"?32:40;const steps=DENSITY_STEP[view.kind];return Math.max(20,Math.min(64,view.baseRowH+steps[density]- steps[view.declaredDensity]))}
629
+ const VIEW_MODE_METHODS={prepareViewModes(){const view=this.view,columns=this.columns||view.columns;this.highlightOptions=readSearchHighlight(view.searchHighlight,columns);this.declaredDensity=this.kind==="grid"?view.density||"default":"default";this.layoutOptions=readLayout(view.layout,this.kind,this.declaredDensity);this.baseRowH=this.rowH;this.adoptModes({layout:this.layoutOptions||{mode:"fit",density:this.declaredDensity},highlight:!!this.highlightOptions?.enabled});watchSharedDensity(this)},adoptModes(choice){if(choice.layout){this.layoutMode=choice.layout.mode;this.density=choice.layout.density}
611
630
  if(choice.highlight!==undefined)this.highlightOn=choice.highlight;this.rowH=densityRowHeight(this,this.density);if(this.layoutMode==="fit")this.host.removeAttribute("data-cx-layout");else this.host.setAttribute("data-cx-layout",this.layoutMode)},modeState(state){if(this.layoutOptions)state.layout={mode:this.layoutMode,density:this.density};if(this.highlightOptions)state.highlight=!!this.highlightOn;return state},applyModeState(state){if(state.layout&&(state.layout.mode!==this.layoutMode||state.layout.density!==this.density))
612
631
  this.setLayout(state.layout).catch(error=>this.emit("error",{error}));if(state.highlight!==undefined&&state.highlight!==!!this.highlightOn)
613
632
  this.setSearchHighlight(state.highlight).catch(error=>this.emit("error",{error}))},startViewModes(){const parts=[this.highlightOptions&&"highlight",this.layoutOptions&&"layouts"].filter(Boolean);return Promise.all(parts.map(name=>this.requirePart(name).then(()=>this.destroyed||VIEW_MODES[name+"Start"](this))
614
- .catch(error=>this.emit("error",{error}))))},getLayout(){return Object.freeze({mode:this.layoutMode,density:this.density,modes:this.layoutOptions?this.layoutOptions.modes:Object.freeze(["fit"])})},setLayout(change){if(!this.layoutOptions)return Promise.reject(new Error("CanopyX: this view declares no layout modes."));return this.requirePart("layouts").then(()=>VIEW_MODES.setLayout(this,change))},getMatches(){return VIEW_MODES.matches?VIEW_MODES.matches(this):Object.freeze({enabled:!!this.highlightOn,total:0,current:0,complete:true})},setSearchHighlight(enabled){if(typeof enabled!=="boolean")return Promise.reject(new Error("CanopyX: the highlight switch is a boolean."));if(!this.highlightOptions)return Promise.reject(new Error("CanopyX: this view declares no search highlighting."));return this.requirePart("highlight").then(()=>VIEW_MODES.setHighlight(this,enabled))},nextMatch(options){return this.stepMatch(1,options)},previousMatch(options){return this.stepMatch(-1,options)},stepMatch(direction,options){if(!this.highlightOptions)return Promise.reject(new Error("CanopyX: this view declares no search highlighting."));return this.requirePart("highlight").then(()=>VIEW_MODES.step(this,direction,options?.focus!==false))}};Object.assign(TreeGrid.prototype,VIEW_MODE_METHODS);const viewModeContext=()=>({VIEW_MODES,TreeGrid,h,setText,fill,numberFormat,foldText,cellText,cellValue,isCellObject,setAttr,toneOf,watchFlyout,densityRowHeight,MAX_PROTOCOL_INTEGER,LAYOUT_DENSITIES,plural,svgIcon});definePart(TreeGrid,"highlight","search highlighting",viewModeContext);definePart(TreeGrid,"layouts","layout modes",viewModeContext);const MODES_TOOLBAR=TreeGrid.prototype.buildToolbar;TreeGrid.prototype.buildToolbar=function(){MODES_TOOLBAR.call(this);if(this.highlightOptions||this.layoutOptions)this.modesReady=Promise.resolve(this.toolsReady).then(()=>this.destroyed||this.startViewModes());this.lifetime.signal.addEventListener("abort",()=>this.host.removeAttribute("data-cx-layout"),{once:true})};Object.assign(TreeGrid.prototype,{destroy(){if(this.destroyed)return;this.destroyed=true;for(const row of[...this.rendered.values(),...this.pool])disposeRowHostCells(row);this.lifetime.abort();this.dataLifetime.abort();if(this.queryController)this.queryController.abort();if(this.restoreController)this.restoreController.abort();if(this.cancelResize)this.cancelResize();clearTimeout(this.saveTimer);clearTimeout(this.layoutTimer);if(this.cancelSearch)this.cancelSearch();if(this.disposeSearchFlyout)this.disposeSearchFlyout();cancelAnimationFrame(this.rafId);this.resizeObserver.disconnect();if(this.detailObserver)this.detailObserver.disconnect();this.grid.removeEventListener("scroll",this.onScroll);this.grid.removeEventListener("wheel",this.onScaledWheel);this.grid.removeEventListener("touchstart",this.onScaledTouchStart);this.grid.removeEventListener("touchmove",this.onScaledTouchMove);this.grid.removeEventListener("touchend",this.onScaledTouchEnd);this.grid.removeEventListener("touchcancel",this.onScaledTouchEnd);global.removeEventListener("beforeprint",this.onPrint);global.removeEventListener("hashchange",this.onHash);global.removeEventListener("popstate",this.onHash);if(this.linkOpening)this.linkOpening.abort();[this.toolbar,this.grid,this.live,this.outline].forEach(element=>element.remove());this.host.classList.remove("cx-pin-tree");this.host.removeAttribute("data-cx-ready");this.host.removeAttribute("data-canopyx-mounted");this.keyMap.clear();this.selected.clear();this.rendered.clear();this.pool.length=0;if(this.inactiveCache)this.inactiveCache.clear();if(this.inlineRetained)this.inlineRetained.clear();this.rows=[];this.tops=[0];this.total=0;this.root.children=null;this.root.inlineCache=null;this.root.inlineOrder=null;this.focused=null;this.anchor=null;this.source=null;this.touchScroll=null;this.opts={};for(const column of this.columns)column.render=null;this.filter=null;this.declaredFilter=null;this.pendingRestorationToggles=null;this.scrolledCell=null;this.focusHandoff=null;this.cancelResize=null;this.cancelSearch=null;this.disposeSearchFlyout=null;this.canvas.replaceChildren();this.outline.replaceChildren()}});function createTreeGrid(host,options){if(!host||!options||!options.view){throw new Error("CanopyX: createTreeGrid needs a host element and a view document.")}
633
+ .catch(error=>this.emit("error",{error}))))},getLayout(){return Object.freeze({mode:this.layoutMode,density:this.density,modes:this.layoutOptions?this.layoutOptions.modes:Object.freeze(["fit"])})},setLayout(change){if(!this.layoutOptions)return Promise.reject(new Error("CanopyX: this view declares no layout modes."));return this.requirePart("layouts").then(()=>VIEW_MODES.setLayout(this,change))},getMatches(){return VIEW_MODES.matches?VIEW_MODES.matches(this):Object.freeze({enabled:!!this.highlightOn,total:0,current:0,complete:true})},setSearchHighlight(enabled){if(typeof enabled!=="boolean")return Promise.reject(new Error("CanopyX: the highlight switch is a boolean."));if(!this.highlightOptions)return Promise.reject(new Error("CanopyX: this view declares no search highlighting."));return this.requirePart("highlight").then(()=>VIEW_MODES.setHighlight(this,enabled))},nextMatch(options){return this.stepMatch(1,options)},previousMatch(options){return this.stepMatch(-1,options)},stepMatch(direction,options){if(!this.highlightOptions)return Promise.reject(new Error("CanopyX: this view declares no search highlighting."));return this.requirePart("highlight").then(()=>VIEW_MODES.step(this,direction,options?.focus!==false))}};Object.assign(TreeGrid.prototype,VIEW_MODE_METHODS);const viewModeContext=()=>({VIEW_MODES,TreeGrid,h,setText,fill,numberFormat,foldText,cellText,cellValue,isCellObject,setAttr,toneOf,toneVariable,watchFlyout,densityRowHeight,layoutAnchor,layoutRows,layoutApplyTree,MAX_PROTOCOL_INTEGER,LAYOUT_DENSITIES,plural,svgIcon});definePart(TreeGrid,"highlight","search highlighting",viewModeContext);definePart(TreeGrid,"layouts","layout modes",viewModeContext);const MODES_TOOLBAR=TreeGrid.prototype.buildToolbar;TreeGrid.prototype.buildToolbar=function(){MODES_TOOLBAR.call(this);if(this.highlightOptions||this.layoutOptions)this.modesReady=Promise.resolve(this.toolsReady).then(()=>this.destroyed||this.startViewModes());this.lifetime.signal.addEventListener("abort",()=>this.host.removeAttribute("data-cx-layout"),{once:true})};Object.assign(TreeGrid.prototype,{destroy(){if(this.destroyed)return;this.destroyed=true;for(const row of[...this.rendered.values(),...this.pool])disposeRowHostCells(row);this.lifetime.abort();this.dataLifetime.abort();if(this.queryController)this.queryController.abort();if(this.restoreController)this.restoreController.abort();if(this.cancelResize)this.cancelResize();clearTimeout(this.saveTimer);clearTimeout(this.layoutTimer);if(this.cancelSearch)this.cancelSearch();if(this.disposeSearchFlyout)this.disposeSearchFlyout();cancelAnimationFrame(this.rafId);this.resizeObserver.disconnect();if(this.detailObserver)this.detailObserver.disconnect();this.grid.removeEventListener("scroll",this.onScroll);this.grid.removeEventListener("wheel",this.onScaledWheel);this.grid.removeEventListener("touchstart",this.onScaledTouchStart);this.grid.removeEventListener("touchmove",this.onScaledTouchMove);this.grid.removeEventListener("touchend",this.onScaledTouchEnd);this.grid.removeEventListener("touchcancel",this.onScaledTouchEnd);global.removeEventListener("beforeprint",this.onPrint);global.removeEventListener("hashchange",this.onHash);global.removeEventListener("popstate",this.onHash);if(this.linkOpening)this.linkOpening.abort();[this.toolbar,this.grid,this.live,this.outline].forEach(element=>element.remove());this.host.classList.remove("cx-pin-tree");this.host.removeAttribute("data-cx-ready");this.host.removeAttribute("data-canopyx-mounted");this.keyMap.clear();this.selected.clear();this.rendered.clear();this.pool.length=0;if(this.inactiveCache)this.inactiveCache.clear();if(this.inlineRetained)this.inlineRetained.clear();this.rows=[];this.tops=[0];this.total=0;this.root.children=null;this.root.inlineCache=null;this.root.inlineOrder=null;this.focused=null;this.anchor=null;this.source=null;this.touchScroll=null;this.opts={};for(const column of this.columns)column.render=null;this.filter=null;this.declaredFilter=null;this.pendingRestorationToggles=null;this.scrolledCell=null;this.focusHandoff=null;this.cancelResize=null;this.cancelSearch=null;this.disposeSearchFlyout=null;this.canvas.replaceChildren();this.outline.replaceChildren()}});function createTreeGrid(host,options){if(!host||!options||!options.view){throw new Error("CanopyX: createTreeGrid needs a host element and a view document.")}
615
634
  const existing=activeGrids.get(host);if(existing)return existing;const grid=new TreeGrid(host,options);const api=Object.assign({element:host,view:grid.view,ready:startPlugins(grid,()=>api),expandPath:(ids,o)=>grid.expandPath(ids,o),setFilter:(filter,o)=>{if(!grid.queryAware)grid.clearDeclaredFilters();grid.setFilter(filter,o)},setFilterActive:(id,active)=>grid.setFilterActive(id,active),setActiveFilters:(ids)=>grid.setActiveFilters(ids),updateActiveFilters:(change)=>grid.updateActiveFilters(change),getActiveFilters:()=>Array.from(grid.activeFilters),sort:(columnId,direction)=>grid.sort(columnId,direction),getQuery:()=>grid.getQuery(),setQuery:(query,options)=>grid.setQuery(query,options),setColumnVisible:(columnId,visible)=>grid.setColumnVisible(columnId,visible),setColumnWidth:(columnId,width)=>grid.setColumnWidth(columnId,width),setTreeColumnPinned:(pinned)=>grid.setTreeColumnPinned(pinned),setTimeZone:(zone)=>grid.setTimeZone(zone),getTimeZone:()=>(grid.local?"local":"utc"),refresh:()=>{grid.version++;grid.scheduleRender()},reload:(id,options)=>grid.reload(id,options),readLocation:()=>grid.readLocation(),getSelected:()=>Array.from(grid.selected).map((w)=>w.h),setSelection:(ids)=>grid.setSelection(ids),clearSelection:()=>grid.setSelection([]),selectAll:(options)=>grid.selectAll(options),focus:()=>{if(grid.destroyed)return;const target=grid.indexOf(grid.focused)>=0?grid.focused:grid.rows[0];if(target)grid.setFocus(target,true);else grid.grid.focus({preventScroll:true})},visibleRows:()=>grid.visibleRows(),toOutline:()=>grid.toOutline(),getState:()=>grid.getState(),setState:(state)=>grid.setState(state),getLayout:()=>grid.getLayout(),setLayout:(change)=>grid.setLayout(change),getMatches:()=>grid.getMatches(),setSearchHighlight:(enabled)=>grid.setSearchHighlight(enabled),nextMatch:(options)=>grid.nextMatch(options),previousMatch:(options)=>grid.previousMatch(options),search:(query,limit)=>grid.search(query,limit||(grid.view.search&&grid.view.search.maxResults)||20),loadMore:(id)=>{const w=id==null?grid.root:grid.keyMap.get(id);return w?grid.loadMore(w):Promise.resolve()},applyCounts:(doc)=>grid.applyCounts(doc),refreshCounts:(ids)=>grid.refreshCounts(ids),closeMenus:()=>grid.closeMenus?.(),destroy:()=>{grid.destroy();if(activeGrids.get(host)===api)activeGrids.delete(host);if(host.canopyx===api)delete host.canopyx}},grid.interactionApi&&grid.interactionApi(),linkApi(grid));for(const name of["prepareExport","exportAs","getSavedViews","saveView","applySavedView","removeSavedView","resetView","setColumnOrder","resetColumns","setColumnPinned","openDetails","openRecord"])
616
635
  api[name]=(...args)=>{if(grid[name])return grid[name](...args);const error=new Error("CanopyX: "+name+" needs the tree tools part.");if(/^(exportAs|applySavedView|resetView|openDetails|openRecord)$/.test(name)) return Promise.reject(error);
617
636
  throw error};api.ready=linkReady(grid,api.ready);Object.freeze(api);activeGrids.set(host,api);host.setAttribute("data-canopyx-mounted","true");host.canopyx=api;recordStart(grid);return api}
@@ -18,6 +18,12 @@ export interface GridCalendarSource {
18
18
  label: string;
19
19
  tone?: string;
20
20
  ics?: string;
21
+ /** Same-origin JSON event array, requested with start/end/timeZone for each visible range. */
22
+ url?: string;
23
+ /** Extra query parameters; start, end and timeZone are reserved. */
24
+ fetchParams?: Readonly<Record<string, string>>;
25
+ /** A six-digit hexadecimal source colour. */
26
+ color?: string;
21
27
  /** Starts switched off. */
22
28
  hidden?: boolean;
23
29
  }
@@ -86,6 +92,8 @@ export interface GridCalendarDefinition<Row extends GridRow = GridRow> {
86
92
  allDay?: GridColumnId<Row>;
87
93
  /** A text column with the event's resource id (a node id of resources.view). */
88
94
  resource?: GridColumnId<Row>;
95
+ /** A text column containing a JSON array of resource ids, added to the single resource. */
96
+ resourceIds?: GridColumnId<Row>;
89
97
  /** The column whose presenter draws the event's status; its tone colours the event. */
90
98
  status?: GridColumnId<Row>;
91
99
  /** A text column with an RRULE (FREQ DAILY, WEEKLY, MONTHLY or YEARLY with INTERVAL, COUNT, UNTIL, BYDAY, BYMONTHDAY, BYMONTH, BYSETPOS, WKST). */
@@ -186,6 +194,7 @@ export interface GridCalendarEvent {
186
194
  readonly allDay: boolean;
187
195
  readonly title: string;
188
196
  readonly resource: string | null;
197
+ readonly resourceIds?: readonly string[];
189
198
  readonly recurring: boolean;
190
199
  readonly background?: boolean;
191
200
  /** The event's source id (sources), or null. */
@@ -252,6 +261,10 @@ export interface CalendarEventInput {
252
261
  description?: string;
253
262
  tone?: string;
254
263
  resource?: string;
264
+ resourceIds?: readonly string[];
265
+ /** Master event id and original occurrence start for an override. */
266
+ series?: string;
267
+ recurrenceId?: Date | string;
255
268
  recurrence?: string;
256
269
  exceptions?: string;
257
270
  source?: string;
@@ -356,6 +369,8 @@ export interface GridCalendarApi {
356
369
  exportCalendar(options?: { scope?: "visible" | "filtered" | "selected" | "all"; fileName?: string; download?: boolean }): Promise<{ blob: Blob; fileName: string; text: string }>;
357
370
  /** Opens an event's record (by occurrence or record id). */
358
371
  openCalendarEvent(id: string): Promise<unknown>;
372
+ /** Opens the title/time form for one recurring occurrence. False if this event cannot be edited. */
373
+ editCalendarOccurrence(id: string): Promise<boolean>;
359
374
  clearCalendarSelection(): void | Promise<void>;
360
375
  /** Shows (true) or hides a source's events; omitted switches it. Resolves with whether they show. */
361
376
  setCalendarSource(id: string, visible?: boolean): boolean | Promise<boolean>;
@@ -38,7 +38,7 @@ export type GridRowDetail =
38
38
  | { type: "grid"; rows: GridRow[] }
39
39
  | { type: "tree"; roots: unknown[] };
40
40
  /** First match wins separately for the row and each condition-column cell; raw filter semantics apply. */
41
- export type GridHighlightRule = GridFilter & { target?: "row" | "cell" } &
41
+ export type GridHighlightRule = GridBuiltinFilter & { target?: "row" | "cell" } &
42
42
  ({ tone: string; className?: string } | { tone?: string; className: string });
43
43
  /** Portable cell semantics, independent of DOM renderers and stylesheet rules. Datetime values retain the original ISO string and its explicit time zone and precision. */
44
44
  export interface GridExportCell {
@@ -84,6 +84,14 @@ export interface GridNumberFormat {
84
84
  export type GridColumn<Row extends GridRow = GridRow> = Omit<CanopyColumn, "id" | "editable" | "width"> & {
85
85
  /** The column's id; for a typed row, one of its cells. */
86
86
  id: GridColumnId<Row>;
87
+ /** CSS class tokens applied to the column header, data and footer cells. */
88
+ className?: string | null;
89
+ alignment?: "start" | "center" | "end" | null;
90
+ /** Ordered raw columns used when this column is sorted. */
91
+ orderData?: readonly string[];
92
+ orderSequence?: readonly ("asc" | "desc" | "none")[];
93
+ /** A sortComparators plugin hook. */
94
+ sortComparator?: string | null;
87
95
  /** Supports query predicates and filter controls. Default true; does not hide values, supported read-only facets, or presentation rules. */
88
96
  filterable?: boolean;
89
97
  /** Supports grid exports. Default true. Default export selection omits false; explicit requests reject it. */
@@ -179,7 +187,7 @@ export interface GridPager {
179
187
  /** Include the reporting entry point when this optional capability set is present. */
180
188
  export interface GridReporting {
181
189
  /** Fixed first ordering key. Group headings and collapse never change record paging/counts/exports. */
182
- readonly grouping?: GridSort;
190
+ readonly grouping?: GridSort & { readonly headerTemplate?: string };
183
191
  /** Initial runtime grouping levels, outermost first (at most four). Not combined with grouping. */
184
192
  readonly groupBy?: readonly GridGroupLevel[];
185
193
  /** Readers group by sortable columns from column menus, the group-by bar and dragged headers. Not combined with grouping. */
@@ -263,12 +271,15 @@ export type GridAction<Q extends AcceptedGridQuery = GridQuery> =
263
271
  /** render fills container on the entry's first open (and again after a render that threw); close() closes the menu and
264
272
  * focuses its button; signal aborts when the grid is destroyed. */
265
273
  | { label: string; render: (container: HTMLElement, context: { close(): void; signal: AbortSignal }) => void; run?: never };
266
- export type GridFilter = {
274
+ export type GridBuiltinFilter = {
267
275
  readonly column: string;
268
276
  readonly operator: "eq" | "ne" | "contains" | "startsWith" | "in" | "gt" | "ge" | "lt" | "le";
269
277
  readonly values: readonly Exclude<CellValue, null>[];
270
278
  } | { readonly column: string; readonly operator: "isNull" | "isNotNull"; readonly values: readonly []; };
271
279
 
280
+ /** A registered custom operator uses one typed, non-null operand. */
281
+ export type GridFilter = GridBuiltinFilter | { readonly column: string; readonly operator: string & {}; readonly values: readonly Exclude<CellValue, null>[] };
282
+
272
283
  /** A nonempty nested expression: at most eight levels, 64 groups and 64 total predicates including flat filters. */
273
284
  export interface GridFilterGroup {
274
285
  readonly operator: "and" | "or";
@@ -309,6 +320,10 @@ interface GridDefinition<Q extends AcceptedGridQuery = GridQuery, Row extends Gr
309
320
  id: string;
310
321
  label: string;
311
322
  columns: GridColumn<Row>[];
323
+ /** First matching rule per setting; explicit column properties win. */
324
+ columnDefaults?: readonly GridColumnDefaults[];
325
+ preSort?: readonly GridSort[];
326
+ footers?: readonly GridFooter[];
312
327
  /** Fixed CSS pixel height (20..64), or content measurement. Fixed is the default writer behavior. */
313
328
  rowHeight?: number | "auto";
314
329
  /** Graphite spacing; an explicit rowHeight remains authoritative. */
@@ -425,6 +440,8 @@ export type GridView<Row extends GridRow = GridRow> = InlineGridView<Row> | Comp
425
440
 
426
441
  /** A complete requested window from one dataset revision, with full and matching counts. */
427
442
  export interface GridPage<Row extends GridRow = GridRow> {
443
+ /** Host-computed values by footer row id, then aggregate id; tied to this accepted revision and window. */
444
+ readonly footerValues?: Readonly<Record<string, Readonly<Record<string, number | null>>>>;
428
445
  protocol: "canopyx/2";
429
446
  type: "gridPage";
430
447
  view: string;
@@ -464,6 +481,12 @@ export interface GridSource<Row extends GridRow = GridRow> extends GridFacetSour
464
481
  }
465
482
  /** Built-in operations, or the name of a custom aggregate a plugin registers (inline data) or the host source computes. */
466
483
  export type GridAggregateOperation = "count" | "sum" | "average" | "minimum" | "maximum" | "distinctCount" | (string & {});
484
+ /** Declarative defaults selected by *, #columnId, .classToken or index:N (zero based). */
485
+ export type GridColumnDefaults = Pick<GridColumn, "className" | "alignment" | "sortable" | "filterable" | "hidden" | "orderData" | "orderSequence" | "sortComparator"> & { readonly targets: readonly string[] };
486
+ export interface GridFooter {
487
+ readonly id: string; readonly label: string; readonly scope: "page" | "all";
488
+ readonly aggregates: readonly GridAggregate[];
489
+ }
467
490
  export interface GridAggregate {
468
491
  /** The label is shown beside the value in group headings and the query totals row. */
469
492
  readonly id: string; readonly column: string; readonly label: string;
@@ -506,6 +506,11 @@ const labels = Object.freeze({
506
506
  calendarOccurrenceTitle: "Wiederkehrenden Termin ändern",
507
507
  calendarOccurrenceReadOnly: "Dieser Teil der Serie kann nicht gespeichert werden, weil einige Werte schreibgeschützt sind.",
508
508
  calendarThisEvent: "Nur dieser Termin",
509
+ calendarEditOccurrence: "Dieses Vorkommen bearbeiten",
510
+ calendarRemoveOccurrence: "Dieses Vorkommen entfernen",
511
+ calendarOccurrenceScope: "Nur {date} ändert sich; andere Termine von {title} bleiben unverändert.",
512
+ calendarSharedResources: "{count} {resources}",
513
+ calendarResourceWindow: "{resources} {start}–{end} von {total}",
509
514
  calendarThisAndFollowing: "Dieser und alle folgenden Termine",
510
515
  calendarAllEvents: "Alle Termine",
511
516
  calendarSources: "Kalender",
@@ -506,6 +506,11 @@ const labels = Object.freeze({
506
506
  calendarOccurrenceTitle: "Cambiar un evento periódico",
507
507
  calendarOccurrenceReadOnly: "No se puede guardar esta parte de la serie porque algunos valores son de solo lectura.",
508
508
  calendarThisEvent: "Este evento",
509
+ calendarEditOccurrence: "Editar esta ocurrencia",
510
+ calendarRemoveOccurrence: "Eliminar esta ocurrencia",
511
+ calendarOccurrenceScope: "Solo cambia {date}; las otras fechas de {title} se mantienen.",
512
+ calendarSharedResources: "{count} {resources}",
513
+ calendarResourceWindow: "{resources} {start}–{end} de {total}",
509
514
  calendarThisAndFollowing: "Este evento y los siguientes",
510
515
  calendarAllEvents: "Todos los eventos",
511
516
  calendarSources: "Calendarios",
@@ -506,6 +506,11 @@ const labels = Object.freeze({
506
506
  calendarOccurrenceTitle: "Modifier un événement récurrent",
507
507
  calendarOccurrenceReadOnly: "Cette partie de la série ne peut pas être enregistrée, car certaines valeurs sont en lecture seule.",
508
508
  calendarThisEvent: "Cet événement",
509
+ calendarEditOccurrence: "Modifier cette occurrence",
510
+ calendarRemoveOccurrence: "Supprimer cette occurrence",
511
+ calendarOccurrenceScope: "Seul le {date} change ; les autres dates de {title} restent inchangées.",
512
+ calendarSharedResources: "{count} {resources}",
513
+ calendarResourceWindow: "{resources} {start}–{end} sur {total}",
509
514
  calendarThisAndFollowing: "Cet événement et les suivants",
510
515
  calendarAllEvents: "Tous les événements",
511
516
  calendarSources: "Calendriers",
@@ -506,6 +506,11 @@ const labels = Object.freeze({
506
506
  calendarOccurrenceTitle: "Modifica un evento ricorrente",
507
507
  calendarOccurrenceReadOnly: "Questa parte della serie non può essere salvata perché alcuni valori sono di sola lettura.",
508
508
  calendarThisEvent: "Questo evento",
509
+ calendarEditOccurrence: "Modifica questa occorrenza",
510
+ calendarRemoveOccurrence: "Rimuovi questa occorrenza",
511
+ calendarOccurrenceScope: "Cambia solo {date}; le altre date di {title} restano invariate.",
512
+ calendarSharedResources: "{count} {resources}",
513
+ calendarResourceWindow: "{resources} {start}–{end} di {total}",
509
514
  calendarThisAndFollowing: "Questo evento e i successivi",
510
515
  calendarAllEvents: "Tutti gli eventi",
511
516
  calendarSources: "Calendari",
@@ -506,6 +506,11 @@ const labels = Object.freeze({
506
506
  calendarOccurrenceTitle: "Zmień wydarzenie cykliczne",
507
507
  calendarOccurrenceReadOnly: "Nie można zapisać tej części serii, ponieważ niektóre wartości są tylko do odczytu.",
508
508
  calendarThisEvent: "To wydarzenie",
509
+ calendarEditOccurrence: "Edytuj to wystąpienie",
510
+ calendarRemoveOccurrence: "Usuń to wystąpienie",
511
+ calendarOccurrenceScope: "Zmienia się tylko {date}; pozostałe daty wydarzenia {title} pozostają bez zmian.",
512
+ calendarSharedResources: "{count} {resources}",
513
+ calendarResourceWindow: "{resources} {start}–{end} z {total}",
509
514
  calendarThisAndFollowing: "To i następne wydarzenia",
510
515
  calendarAllEvents: "Wszystkie wydarzenia",
511
516
  calendarSources: "Kalendarze",
@@ -68,6 +68,10 @@ export type CanopyPluginEventHandler = (event: CustomEvent, context: CanopyPlugi
68
68
  * grid with the plugins option. Every member except name is optional.
69
69
  */
70
70
  export interface CanopyPlugin {
71
+ /** Inline predicate hooks over raw values; cannot replace built-in operators. */
72
+ readonly filterOperators?: Readonly<Record<string, (value: unknown, operands: readonly unknown[], context: { readonly column: string; readonly signal: AbortSignal }) => boolean>>;
73
+ /** Inline ascending comparator hooks over raw sort values; missing values stay last in either direction. */
74
+ readonly sortComparators?: Readonly<Record<string, (left: unknown, right: unknown, context: { readonly column: string; readonly signal: AbortSignal }) => number>>;
71
75
  /** 1 to 32 lowercase ASCII letters, digits and hyphens, starting with a letter; unique on the page. */
72
76
  readonly name: string;
73
77
  /** Plugins that start first, and parts of the runtime (for example "export") that load before init. */
@@ -264,7 +264,7 @@ export type LabelName =
264
264
  /** The record panel (docs/record-panel.md). */
265
265
  | "recordBack" | "recordBackTo" | "recordTrail" | "recordShowing" | "recordTabs" | "recordSections" | "recordOtherFields" | "recordShowEmpty" | "recordHideEmpty" | "recordActions" | "recordCopyValues" | "recordCopyJson" | "recordCopyLink" | "recordCopied" | "recordPrint" | "recordExportCsv" | "recordExportJson" | "recordOpenAll" | "recordLoadMore" | "recordCount" | "recordItems" | "recordMore" | "recordShowAll" | "recordRetry" | "recordLoadFailed" | "recordUnavailable" | "recordField" | "recordValue" | "recordSection" | "recordBefore" | "recordAfter" | "recordAdded" | "recordRemoved" | "recordChanged"
266
266
  /** The calendar and resource timeline (the calendar and timeline parts of flat grids). */
267
- | "calendarToday" | "calendarPrevious" | "calendarNext" | "calendarViews" | "calendarMonth" | "calendarWeek" | "calendarDay" | "calendarDays"
267
+ | "calendarEditOccurrence" | "calendarRemoveOccurrence" | "calendarOccurrenceScope" | "calendarSharedResources" | "calendarResourceWindow" | "calendarToday" | "calendarPrevious" | "calendarNext" | "calendarViews" | "calendarMonth" | "calendarWeek" | "calendarDay" | "calendarDays"
268
268
  | "calendarAgenda" | "calendarYear" | "calendarTimeline" | "calendarGoTo" | "calendarAllDay" | "calendarMore" | "calendarMoreLabel" | "calendarDayEvents"
269
269
  | "calendarNoEvents" | "calendarEvents" | "calendarShowing" | "calendarRepeats" | "calendarTruncated" | "calendarLoading" | "calendarLoadError" | "calendarNow"
270
270
  | "calendarZoom" | "calendarZoomHour" | "calendarZoomDay" | "calendarZoomWeek" | "calendarZoomMonth" | "calendarZoomQuarter" | "calendarResources" | "calendarCapacity"
package/README.md CHANGED
@@ -24,13 +24,13 @@ Turn application data into searchable trees, sortable tables and standalone HTML
24
24
  - **Change views**: a text diff (inline or side by side, word marks, folded regions, windowed rows), a change list with kind badges, folders, hashes and risk flags, and how a change reached its replicas ([docs/changes.md](docs/changes.md)).
25
25
  - **Guided flows** for setup and edit tasks: steps with typed fields, conditions as data, validation, a review page with change links, secrets that never echo, and one proposal to the host ([docs/flow.md](docs/flow.md)).
26
26
 
27
- ![File explorer in the light theme, with sizes, dates, owners and an expanded detail row](https://cdn.jsdelivr.net/npm/@evotecit/canopyx@0.3.13/docs/images/tree-grid-light.png)
27
+ ![File explorer in the light theme, with sizes, dates, owners and an expanded detail row](https://cdn.jsdelivr.net/npm/@evotecit/canopyx@0.3.15/docs/images/tree-grid-light.png)
28
28
 
29
29
  The same explorer in the dark theme, alongside its script-free output for email and print:
30
30
 
31
31
  | Dark theme | Static outline |
32
32
  | --- | --- |
33
- | ![File explorer in the dark theme](https://cdn.jsdelivr.net/npm/@evotecit/canopyx@0.3.13/docs/images/tree-grid-dark.png) | ![Static outline table](https://cdn.jsdelivr.net/npm/@evotecit/canopyx@0.3.13/docs/images/static-outline.png) |
33
+ | ![File explorer in the dark theme](https://cdn.jsdelivr.net/npm/@evotecit/canopyx@0.3.15/docs/images/tree-grid-dark.png) | ![Static outline table](https://cdn.jsdelivr.net/npm/@evotecit/canopyx@0.3.15/docs/images/static-outline.png) |
34
34
 
35
35
  ## Features
36
36
 
@@ -46,6 +46,8 @@ The same explorer in the dark theme, alongside its script-free output for email
46
46
 
47
47
  Both views use virtual rows and support keyboard navigation. Both can mark search and filter matches with a match count and next/previous navigation ([search highlighting](docs/search-highlight.md)), and let readers switch between fitting the width, scrolling sideways and (on grids) cards, in three densities ([layout modes](docs/layout-modes.md)). Reporting grids also provide multi-level grouping with aggregates, detail rows, saved views, CSV export and row copying (see [docs/grouping.md](docs/grouping.md)). Grids and trees can open each record in a record panel: record-only fields in tabs and sections, lists, objects and nested collections, links between records, timelines, record deep links, and copy, print and export of one record, with details loaded on demand for large reports (see [docs/record-panel.md](docs/record-panel.md)). English labels are built in; Polish, German, Spanish, French and Italian packs are included.
48
48
 
49
+ CanopyX reads the shared `--hfx-*` design roles for colours, fonts, radii and density, and includes standalone defaults. Use `data-hfx-style` and `data-hfx-mode` on a containing element to theme a page; `data-hfx-density="compact"` selects 32px rows and `"comfortable"` selects 40px rows. See [theming](docs/architecture.md#theming) for custom roles and the migration from `--cx-*` colour variables.
50
+
49
51
  ## Installation
50
52
 
51
53
  ### .NET
@@ -141,6 +143,8 @@ Event snapshots without a key column opt in to `CanopyGridRowIdentity.Ordinal`;
141
143
 
142
144
  Grid fitting reserves readable header and content minimums before preferred whitespace. Spare width restores headers first, then preferred widths, then ordinary column growth. Headers yield with a full-title tooltip only when their capped minimums cannot collectively fit; content that still needs more room scrolls horizontally. Auto height includes the actual horizontal scrollbar gutter. The [column-fitting algorithm](docs/column-fitting.md) and `report-grid-layout.html` example describe the policy.
143
145
 
146
+ Grids can show page totals and all-matching totals in declarative footer rows, including the script-free fallback. Columns support safe CSS classes, logical alignment, selector-based defaults, alternate sort keys and direction cycles. Plugins can add filter operators, sort comparators and custom aggregates. See [column options and footers](docs/data-protocol.md#column-defaults-ordering-hooks-and-footer-rows), and the generated `grid-footers.html`, `grid-footers-dark.html` and `grid-footers-static.html` examples.
147
+
144
148
  ### Render a table for email or print
145
149
 
146
150
  ```csharp
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evotecit/canopyx",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "Interactive tree grids and data tables with virtual rows, search, filters and offline HTML reports.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,6 +41,8 @@
41
41
  "./filter-editor.css": "./CanopyX.Html/Assets/canopyx-filter-editor.css",
42
42
  "./export.js": "./CanopyX.Html/Assets/canopyx-export.js",
43
43
  "./history.js": "./CanopyX.Html/Assets/canopyx-history.js",
44
+ "./grid-options.js": "./CanopyX.Html/Assets/canopyx-grid-options.js",
45
+ "./grid-options.css": "./CanopyX.Html/Assets/canopyx-grid-options.css",
44
46
  "./pager.js": "./CanopyX.Html/Assets/canopyx-pager.js",
45
47
  "./pager.css": "./CanopyX.Html/Assets/canopyx-pager.css",
46
48
  "./enhance.js": "./CanopyX.Html/Assets/canopyx-enhance.js",
@@ -138,6 +140,8 @@
138
140
  "CanopyX.Html/Assets/canopyx-filter-editor.css",
139
141
  "CanopyX.Html/Assets/canopyx-export.js",
140
142
  "CanopyX.Html/Assets/canopyx-history.js",
143
+ "CanopyX.Html/Assets/canopyx-grid-options.js",
144
+ "CanopyX.Html/Assets/canopyx-grid-options.css",
141
145
  "CanopyX.Html/Assets/canopyx-pager.js",
142
146
  "CanopyX.Html/Assets/canopyx-pager.css",
143
147
  "CanopyX.Html/Assets/canopyx-enhance.js",