@evotecit/canopyx 0.3.18 → 0.3.21
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.
- package/CanopyX.Html/Assets/canopyx-board.css +12 -12
- package/CanopyX.Html/Assets/canopyx-board.js +1 -1
- package/CanopyX.Html/Assets/canopyx-calendar.css +70 -57
- package/CanopyX.Html/Assets/canopyx-calendar.js +14 -14
- package/CanopyX.Html/Assets/canopyx-cell-range.css +4 -4
- package/CanopyX.Html/Assets/canopyx-cell-range.js +1 -1
- package/CanopyX.Html/Assets/canopyx-changes.css +55 -55
- package/CanopyX.Html/Assets/canopyx-changes.js +1 -1
- package/CanopyX.Html/Assets/canopyx-column-groups.css +5 -1
- package/CanopyX.Html/Assets/canopyx-column-groups.js +15 -9
- package/CanopyX.Html/Assets/canopyx-compare.css +4 -4
- package/CanopyX.Html/Assets/canopyx-compare.js +1 -1
- package/CanopyX.Html/Assets/canopyx-detail-rows.css +4 -4
- package/CanopyX.Html/Assets/canopyx-detail-rows.js +1 -1
- package/CanopyX.Html/Assets/canopyx-editing.css +3 -3
- package/CanopyX.Html/Assets/canopyx-editing.js +1 -1
- package/CanopyX.Html/Assets/canopyx-enhance.js +2 -2
- package/CanopyX.Html/Assets/canopyx-export.js +8 -8
- package/CanopyX.Html/Assets/canopyx-facets.css +19 -18
- package/CanopyX.Html/Assets/canopyx-facets.js +1 -1
- package/CanopyX.Html/Assets/canopyx-filter-editor.css +24 -24
- package/CanopyX.Html/Assets/canopyx-filter-editor.js +3 -3
- package/CanopyX.Html/Assets/canopyx-flow-checks.css +2 -2
- package/CanopyX.Html/Assets/canopyx-flow-checks.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-command.css +3 -3
- package/CanopyX.Html/Assets/canopyx-flow-command.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-dialog.css +2 -2
- package/CanopyX.Html/Assets/canopyx-flow-dialog.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-drafts.css +2 -2
- package/CanopyX.Html/Assets/canopyx-flow-drafts.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-entry.css +24 -23
- package/CanopyX.Html/Assets/canopyx-flow-entry.js +4 -4
- package/CanopyX.Html/Assets/canopyx-flow-host.css +2 -2
- package/CanopyX.Html/Assets/canopyx-flow-host.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-module.mjs +4 -4
- package/CanopyX.Html/Assets/canopyx-flow-pick.css +4 -4
- package/CanopyX.Html/Assets/canopyx-flow-pick.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-preview.css +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-preview.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-records.css +2 -2
- package/CanopyX.Html/Assets/canopyx-flow-records.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow-tasks.css +4 -4
- package/CanopyX.Html/Assets/canopyx-flow-tasks.js +1 -1
- package/CanopyX.Html/Assets/canopyx-flow.css +41 -41
- package/CanopyX.Html/Assets/canopyx-flow.js +1 -1
- package/CanopyX.Html/Assets/canopyx-format-editor.css +9 -9
- package/CanopyX.Html/Assets/canopyx-format-editor.js +1 -1
- package/CanopyX.Html/Assets/canopyx-formatting.css +13 -13
- package/CanopyX.Html/Assets/canopyx-formatting.js +3 -3
- package/CanopyX.Html/Assets/canopyx-forms.css +3 -3
- package/CanopyX.Html/Assets/canopyx-forms.js +1 -1
- package/CanopyX.Html/Assets/canopyx-grid-module.mjs +56 -46
- package/CanopyX.Html/Assets/canopyx-grid-options.css +8 -8
- package/CanopyX.Html/Assets/canopyx-grid-options.js +9 -2
- package/CanopyX.Html/Assets/canopyx-grid.css +99 -96
- package/CanopyX.Html/Assets/canopyx-grid.js +55 -45
- package/CanopyX.Html/Assets/canopyx-grouping.css +7 -7
- package/CanopyX.Html/Assets/canopyx-grouping.js +1 -1
- package/CanopyX.Html/Assets/canopyx-highlight.css +10 -10
- package/CanopyX.Html/Assets/canopyx-highlight.js +1 -1
- package/CanopyX.Html/Assets/canopyx-interactive.css +50 -49
- package/CanopyX.Html/Assets/canopyx-interactive.js +12 -11
- package/CanopyX.Html/Assets/canopyx-labels-de.js +4 -0
- package/CanopyX.Html/Assets/canopyx-labels-es.js +4 -0
- package/CanopyX.Html/Assets/canopyx-labels-fr.js +4 -0
- package/CanopyX.Html/Assets/canopyx-labels-it.js +4 -0
- package/CanopyX.Html/Assets/canopyx-labels-pl.js +4 -0
- package/CanopyX.Html/Assets/canopyx-layouts.css +9 -9
- package/CanopyX.Html/Assets/canopyx-layouts.js +2 -2
- package/CanopyX.Html/Assets/canopyx-link.css +3 -3
- package/CanopyX.Html/Assets/canopyx-link.js +3 -3
- package/CanopyX.Html/Assets/canopyx-links.js +2 -2
- package/CanopyX.Html/Assets/canopyx-live.css +2 -2
- package/CanopyX.Html/Assets/canopyx-live.js +1 -1
- package/CanopyX.Html/Assets/canopyx-matrix.css +31 -31
- package/CanopyX.Html/Assets/canopyx-matrix.js +1 -1
- package/CanopyX.Html/Assets/canopyx-module.mjs +13 -12
- package/CanopyX.Html/Assets/canopyx-pager.css +1 -1
- package/CanopyX.Html/Assets/canopyx-pager.js +1 -1
- package/CanopyX.Html/Assets/canopyx-path.css +8 -8
- package/CanopyX.Html/Assets/canopyx-path.js +1 -1
- package/CanopyX.Html/Assets/canopyx-pivot.css +10 -10
- package/CanopyX.Html/Assets/canopyx-pivot.js +3 -3
- package/CanopyX.Html/Assets/canopyx-plan.css +24 -17
- package/CanopyX.Html/Assets/canopyx-plan.js +13 -9
- package/CanopyX.Html/Assets/canopyx-record-panel.css +47 -42
- package/CanopyX.Html/Assets/canopyx-record-panel.js +16 -9
- package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +59 -56
- package/CanopyX.Html/Assets/canopyx-reporting.css +104 -101
- package/CanopyX.Html/Assets/canopyx-reporting.js +58 -55
- package/CanopyX.Html/Assets/canopyx-stack.css +6 -6
- package/CanopyX.Html/Assets/canopyx-stack.js +1 -1
- package/CanopyX.Html/Assets/canopyx-time-slider.css +5 -5
- package/CanopyX.Html/Assets/canopyx-time-slider.js +1 -1
- package/CanopyX.Html/Assets/canopyx-timeline.css +19 -19
- package/CanopyX.Html/Assets/canopyx-timeline.js +2 -2
- package/CanopyX.Html/Assets/canopyx-tree-tools.css +21 -21
- package/CanopyX.Html/Assets/canopyx-tree-tools.js +3 -3
- package/CanopyX.Html/Assets/canopyx-ui-accordion.css +119 -0
- package/CanopyX.Html/Assets/canopyx-ui-accordion.js +33 -0
- package/CanopyX.Html/Assets/canopyx-ui-menu.css +7 -7
- package/CanopyX.Html/Assets/canopyx-ui-menu.js +1 -1
- package/CanopyX.Html/Assets/canopyx-ui-module.mjs +39 -0
- package/CanopyX.Html/Assets/canopyx-ui-navigation.css +9 -9
- package/CanopyX.Html/Assets/canopyx-ui-navigation.js +5 -5
- package/CanopyX.Html/Assets/canopyx-ui-overlays.css +5 -5
- package/CanopyX.Html/Assets/canopyx-ui-overlays.js +5 -2
- package/CanopyX.Html/Assets/canopyx-ui-picker.css +2 -2
- package/CanopyX.Html/Assets/canopyx-ui-picker.js +1 -1
- package/CanopyX.Html/Assets/canopyx-ui-stepper.css +12 -12
- package/CanopyX.Html/Assets/canopyx-ui-stepper.js +1 -1
- package/CanopyX.Html/Assets/canopyx-ui-tabs.css +9 -8
- package/CanopyX.Html/Assets/canopyx-ui-tabs.js +7 -4
- package/CanopyX.Html/Assets/canopyx-ui.css +119 -0
- package/CanopyX.Html/Assets/canopyx-ui.js +37 -0
- package/CanopyX.Html/Assets/canopyx-view-intents.css +1 -1
- package/CanopyX.Html/Assets/canopyx-view-intents.js +1 -1
- package/CanopyX.Html/Assets/canopyx.css +47 -46
- package/CanopyX.Html/Assets/canopyx.js +12 -11
- package/JavaScript/column-groups.d.ts +24 -4
- package/JavaScript/grid.d.ts +11 -1
- package/JavaScript/hosts.d.ts +2 -0
- package/JavaScript/locales/de.mjs +4 -0
- package/JavaScript/locales/es.mjs +4 -0
- package/JavaScript/locales/fr.mjs +4 -0
- package/JavaScript/locales/it.mjs +4 -0
- package/JavaScript/locales/pl.mjs +4 -0
- package/JavaScript/protocol.d.ts +3 -0
- package/JavaScript/records.d.ts +2 -0
- package/JavaScript/ui.d.ts +70 -2
- package/README.md +76 -206
- package/README.nuget.md +63 -87
- package/package.json +14 -1
|
@@ -75,8 +75,7 @@ function fill(template,values){return template.replace(/\{(\w+)\}/g, (match, nam
|
|
|
75
75
|
const pluralRules=new Map();function plural(template,count,lang){if(!template.startsWith("["))return template;const forms={};for(const match of template.matchAll(/\[(zero|one|two|few|many|other)\]([^[]*)/g)) forms[match[1]] = match[2];
|
|
76
76
|
let rules=pluralRules.get(lang);if(!rules){try{rules=new Intl.PluralRules(lang||"en")}catch(_){rules=new Intl.PluralRules("en")}pluralRules.set(lang,rules)}
|
|
77
77
|
return forms[rules.select(Number(count))]??forms.other??template}
|
|
78
|
-
let DEFAULT_LABELS=Object.freeze({searchPlaceholder:"Search",searchResults:"Search results",searchResultCount:"{count} results",searchResultCountOfTotal:"{count} of {total} results",searchResultCountOfMany:"{count} of many results",searchNoResults:"No results",searchTooShort:"Type at least {min} characters",searchWordTooShort:"Type a word of at least {min} characters",searchFailed:"Search failed: {error}",columns:"Columns",hiddenColumns:"[one]+{count} column[other]+{count} columns",visibleColumns:"Visible columns",resizeColumn:"Resize {column}",columnWidth:"{width} pixels",loading:"Loading…",loadError:"Could not load: {error} (Enter to retry)",queryFailed:"Could not update the view: {error}",showMore:"Show more ({loaded} of {total} loaded)",showMoreUnknownTotal:"Show more ({loaded} loaded)",pageLoaded:"{loaded} of {total} loaded",itemsLoaded:"{count} items",yes:"Yes",no:"No",utcTime:"{time} UTC",localTime:"{time} local time",mountError:"This view could not be displayed: {error}"});function labelsFor(...sources){const labels=Object.assign({},DEFAULT_LABELS);for(const source of sources){if(source==null)continue;if(typeof source!=="object"||Array.isArray(source))throw new Error("CanopyX: labels must be an object of strings.");Object.keys(source).forEach((name)=>{
|
|
79
|
-
if(!Object.hasOwn(DEFAULT_LABELS,name)&&!/^(?:gridEdit|format|calendar|flow|matrix|change|board|plan|path|stack|time|compare|viewIntent)[A-Z]\w{0,63}$/.test(name) && !uiLabel) throw new Error("CanopyX: there is no label '" + name + "'.");
|
|
78
|
+
let DEFAULT_LABELS=Object.freeze({searchPlaceholder:"Search",searchResults:"Search results",searchResultCount:"{count} results",searchResultCountOfTotal:"{count} of {total} results",searchResultCountOfMany:"{count} of many results",searchNoResults:"No results",searchTooShort:"Type at least {min} characters",searchWordTooShort:"Type a word of at least {min} characters",searchFailed:"Search failed: {error}",columns:"Columns",hiddenColumns:"[one]+{count} column[other]+{count} columns",visibleColumns:"Visible columns",resizeColumn:"Resize {column}",columnWidth:"{width} pixels",loading:"Loading…",loadError:"Could not load: {error} (Enter to retry)",queryFailed:"Could not update the view: {error}",showMore:"Show more ({loaded} of {total} loaded)",showMoreUnknownTotal:"Show more ({loaded} loaded)",pageLoaded:"{loaded} of {total} loaded",itemsLoaded:"{count} items",yes:"Yes",no:"No",utcTime:"{time} UTC",localTime:"{time} local time",mountError:"This view could not be displayed: {error}"});function labelsFor(...sources){const labels=Object.assign({},DEFAULT_LABELS);for(const source of sources){if(source==null)continue;if(typeof source!=="object"||Array.isArray(source))throw new Error("CanopyX: labels must be an object of strings.");Object.keys(source).forEach((name)=>{if(!Object.hasOwn(DEFAULT_LABELS,name)&&!/^(?:gridEdit|format|calendar|flow|matrix|change|board|plan|path|stack|time|compare|viewIntent|tabs|stepper|accordion|dialog|drawer|state)[A-Z]\w{0,63}$/.test(name)) throw new Error("CanopyX: there is no label '" + name + "'.");
|
|
80
79
|
if(typeof source[name]!=="string"||!/\S/.test(source[name])) throw new Error("CanopyX: label '" + name + "' must be a string with visible text.");
|
|
81
80
|
if((name==="utcTime"||name==="localTime")&&source[name].indexOf("{time}")<0)throw new Error("CanopyX: label '"+name+"' must hold {time}.");labels[name]=source[name]})}
|
|
82
81
|
return Object.freeze(labels)}
|
|
@@ -90,8 +89,8 @@ function trusted(kind,value){if(trustPolicy===undefined){try{trustPolicy=global.
|
|
|
90
89
|
catch(_){trustPolicy=null}}
|
|
91
90
|
return trustPolicy?trustPolicy["create"+kind](value):value}
|
|
92
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.")}
|
|
93
|
-
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;
|
|
94
|
-
if(
|
|
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;const surface=parent&&["--hfx-color-surface-card","--hfx-color-surface"].some(name=>parent.getPropertyValue(name).trim());let mode=null;if(!surface){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(surface||!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}
|
|
95
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)}
|
|
96
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)}
|
|
97
96
|
return copied}
|
|
@@ -214,8 +213,9 @@ if(kind==="inline"){if(!Array.isArray(view.roots))throw new Error("CanopyX: inli
|
|
|
214
213
|
throw new Error("CanopyX: unknown source kind '"+kind+"'.")}
|
|
215
214
|
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)}
|
|
216
215
|
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))}}
|
|
217
|
-
class TreeGrid{constructor(host,options){const view=checkDocument(options.view,"view");
|
|
218
|
-
|
|
216
|
+
class TreeGrid{constructor(host,options){const view=checkDocument(options.view,"view");if(view.recordDrawerModal!==undefined&&typeof view.recordDrawerModal!=="boolean")
|
|
217
|
+
throw new Error("CanopyX: recordDrawerModal must be a Boolean.");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.")}
|
|
218
|
+
do{this.id="cx" + ++instanceSeq}while(document.getElementById(this.id));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"))
|
|
219
219
|
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.")}
|
|
220
220
|
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||[]}
|
|
221
221
|
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()}
|
|
@@ -410,7 +410,7 @@ if(accepted&&deferred&&revision===this.selectionRevision)
|
|
|
410
410
|
this.notifySelection(w,Array.from(this.selected).map((selected)=>selected.h),revision)}},async retryPage(status){const parent=status.parent;await this.loadStatusPage(parent,status,parent.next||null)},typeAhead(e,i){if(e.key.length!==1||e.ctrlKey||e.metaKey||e.altKey){return false}
|
|
411
411
|
const now=performance.now();const letter=e.key.toLowerCase();this.typeBuf=now-this.typeAt>TYPEAHEAD_MS?letter:this.typeBuf+letter;this.typeAt=now;const n=this.rows.length;const start=this.typeBuf.length===1?i+1:i;for(let k=0;k<n;k++){const w=this.rows[(start+k)%n];if(!w.status&&String(w.h.label).toLowerCase().startsWith(this.typeBuf)){this.setFocus(w);break}}
|
|
412
412
|
return true},announce(message){setText(this.live,message)}});const CELL_CONTROLS="a[href],button,input,select,textarea,summary,[tabindex],[contenteditable]:not([contenteditable=false])," +
|
|
413
|
-
"[role=button],[role=checkbox],[role=radio],[role=link],[role=switch],[role=tab],[role=menuitem],[role=textbox],[role=combobox]";const OWN_ARROWS=".cx-detail-body,textarea,select,[contenteditable]:not([contenteditable=false]),[aria-activedescendant]," +
|
|
413
|
+
"[role=button],[role=checkbox],[role=radio],[role=link],[role=switch],[role=tab],[role=menuitem],[role=textbox],[role=combobox]";const OWN_ARROWS=".cx-detail-body,.cx-phone-value,textarea,select,[contenteditable]:not([contenteditable=false]),[aria-activedescendant]," +
|
|
414
414
|
"[role=textbox],[role=combobox],[role=spinbutton],[role=slider],[role=radio],[role=tab],[role=menuitem],[role=listbox],[role=tree],[role=grid]," +
|
|
415
415
|
"input:not([type=button]):not([type=checkbox]):not([type=submit]):not([type=reset]):not([type=image]):not([type=file]):not([type=color])";function reachable(control){return!control.matches(":disabled")&&!control.closest("[inert],[aria-hidden=true]")&&
|
|
416
416
|
(control.checkVisibility?control.checkVisibility({visibilityProperty:true}):control.getClientRects().length>0)}
|
|
@@ -530,11 +530,11 @@ close()}else{return}
|
|
|
530
530
|
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 lifetime=new AbortController();let flyout=null,pending=null;const ready=()=>flyout?Promise.resolve(flyout):pending||=loadPart(SHARED_PARTS["ui-overlays"]).then(()=>{if(!lifetime.signal.aborted)flyout=UI_TOOLS.toolbarFlyout(anchorElement,panel,isOpen,lifetime.signal);return flyout}).catch(error=>{pending=null;throw error});const invoke=method=>{if(lifetime.signal.aborted||!isOpen())return;if(flyout){flyout[method]();return}
|
|
531
531
|
ready().then(value=>{if(!lifetime.signal.aborted&&isOpen())value?.[method]()}).catch(error=>{if(!lifetime.signal.aborted)(anchorElement.closest(".cx-host")||anchorElement)
|
|
532
532
|
.dispatchEvent(new CustomEvent("canopyx:error",{detail:{error},bubbles:true}))})};return{position:()=>invoke("position"),open:()=>invoke("open"),close:()=>flyout?.close(),dispose:()=>lifetime.abort()}}
|
|
533
|
-
const PART_URL=typeof document!=="undefined"&&document.currentScript?.src;const PARTS={"ui-overlays":["
|
|
533
|
+
const PART_URL=typeof document!=="undefined"&&document.currentScript?.src;const PARTS={"ui-overlays":["24afaa117ebf3fb5","canopyx-ui-overlays.dedc42d3a501a387.js","canopyx-ui-overlays.3668a9a74004ea8e.css"],"ui-menu":["352b1ee391133ff0","canopyx-ui-menu.f9abee36db8f6ad2.js","canopyx-ui-menu.2f3478cc5f5c272f.css"],"ui-picker":["d415103478faf52a","canopyx-ui-picker.13e544bd68aca998.js","canopyx-ui-picker.59c85a1988a287a9.css"],"ui-navigation":["2553d01c5276d56a","canopyx-ui-navigation.c4ebc214197f140d.js","canopyx-ui-navigation.0afd07bbdc5aecb5.css"],"ui-tabs":["19ece8184f3daf4a","canopyx-ui-tabs.6cd35b2afd89f530.js","canopyx-ui-tabs.7bb6c6f1341e87e9.css"],"ui-stepper":["5682c49db1b85314","canopyx-ui-stepper.96973189b0f6e0c4.js","canopyx-ui-stepper.1d4a9db373b8d2f2.css"],"ui-accordion":["1424f075b78d0ccd","canopyx-ui-accordion.d0d5daa7f73d0859.js","canopyx-ui-accordion.a3ef9c27cea5b7a4.css"],"tree-tools":["ef0d06c82135ae2e","canopyx-tree-tools.1c4b9e286dde6065.js","canopyx-tree-tools.350dd0ee46757029.css"],"export":["15abf3205094bda6","canopyx-export.68b90138463329fe.js"],"office-export":["4fb7a4d570390ee9","canopyx-office-export.7e62c385b884e75b.js"],"links":["c6579c9401f0971d","canopyx-links.b25760079f57e6e1.js"],"search":["521eebd6e1bbbd35","canopyx-search.14c69f4551c69f23.js"],"link":["7485f5c9875e9f9b","canopyx-link.9066770aa822fe0f.js","canopyx-link.997fefec6b85f0c5.css"],"highlight":["5ebf0042011ba4fd","canopyx-highlight.ff11a0ca8e30102b.js","canopyx-highlight.9c26291cbcd0962b.css"],"layouts":["90267e35e7edf3a4","canopyx-layouts.058b8a396f909a79.js","canopyx-layouts.ca17f42babc4192a.css"],"record-panel":["cb6bcd1baf644a51","canopyx-record-panel.557c676822e8624f.js","canopyx-record-panel.174846a640de979f.css"]},PART_CODE=new Map(),SHARED_PARTS={},partName=name=>typeof name==="string"&&/^[a-z][a-z\d-]{0,31}$/.test(name);
|
|
534
534
|
function registerPart(name,part){if(!partName(name)||!part||typeof part.version!=="string"||typeof part.install!=="function"||
|
|
535
535
|
part.requires!==undefined&&!(Array.isArray(part.requires)&&part.requires.every(partName)))throw new Error("CanopyX: invalid part.");PART_CODE.set(name,part)}
|
|
536
536
|
function definePart(View,name,label,context){const parts=View.prototype.parts||={};return parts[name]={name,label,context,parts}}
|
|
537
|
-
function loadPart(part){return part.ready||=Promise.resolve().then(async()=>{const[version,scriptFile,styleFile]=Object.hasOwn(PARTS,part.name)?PARTS[part.name]:[];part.version=version;if(!version)throw new Error("CanopyX:
|
|
537
|
+
function loadPart(part){return part.ready||=Promise.resolve().then(async()=>{const[version,scriptFile,styleFile]=Object.hasOwn(PARTS,part.name)?PARTS[part.name]:[];part.version=version;if(!version)throw new Error("CanopyX: no "+part.label+".");if(PART_CODE.get(part.name)?.version!==part.version||part.styleReady===null){const find=type=>[...document.querySelectorAll('script[type="application/x-canopyx-'+type+'"][data-cx-version="'+part.version+'"]')].find(item=>!pageNonce||item.nonce===pageNonce);const payload=find(part.name),script=h("script"),embeddedStyle=styleFile&&find(part.name+"-style");if(payload){if(embeddedStyle){const style=h("style");style.nonce=payload.nonce;setText(style,embeddedStyle.textContent);document.head.appendChild(style)}
|
|
538
538
|
script.textContent=trusted("Script",payload.textContent);script.nonce=payload.nonce;document.head.appendChild(script);script.remove()}else{const base=global.CanopyXFilterEditorBase,runtime=PART_URL||typeof base==="string"&&base||document.baseURI,hashed=PART_URL&&/\.[a-f\d]{16}\.js(?:[?#]|$)/.test(PART_URL);
|
|
539
539
|
const load=(element,attribute,file,kind)=>new Promise((resolve,reject)=>{const url=new URL(file,runtime).href;element[attribute]=attribute==="src"?trusted("ScriptURL",url):url;element.onload=resolve;element.onerror=()=>{element.remove();reject(new Error("CanopyX: "+part.label+" "+kind+" unavailable."))};document.head.appendChild(withNonce(element))});const link=h("link");link.rel="stylesheet";const style=styleFile&&(part.styleReady||=load(link,"href",hashed?styleFile:"canopyx-"+part.name+".css","stylesheet")
|
|
540
540
|
.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")
|
|
@@ -592,7 +592,7 @@ const wanted=Array.from(new Set(ids||this.rows.filter((w)=>!w.status).map((w)=>w
|
|
|
592
592
|
const navigation=navigationScope(this,this.dataLifetime.signal);let doc;try{doc=await navigation.wait(this.source.counts(wanted,this.sourceRequest(this.dataLifetime.signal)))}
|
|
593
593
|
finally{navigation.dispose()}
|
|
594
594
|
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}});function UiHost(){}
|
|
595
|
-
const UI_IMPL={},UI_TOOLS={},uiInstances=new WeakMap();const UI_KINDS={tabs:"ui-tabs",stepper:"ui-stepper",menu:"ui-menu",dialog:"ui-overlays",drawer:"ui-overlays",popover:"ui-overlays",tooltip:"ui-overlays"};const uiContext=()=>({UI_IMPL,UI_TOOLS,h,setText,fill,watchTheme,overlayTheme,hostHtml});for(const name of
|
|
595
|
+
const UI_IMPL={},UI_TOOLS={},uiInstances=new WeakMap();const UI_KINDS={tabs:"ui-tabs",stepper:"ui-stepper",accordion:"ui-accordion",menu:"ui-menu",dialog:"ui-overlays",drawer:"ui-overlays",popover:"ui-overlays",tooltip:"ui-overlays"};const uiContext=()=>({UI_IMPL,UI_TOOLS,h,setText,fill,watchTheme,overlayTheme,hostHtml});for(const name of Object.keys(PARTS).filter(part=>part.startsWith("ui-")))SHARED_PARTS[name]=definePart(UiHost,name,name.replace("ui-","UI "),uiContext);function createUi(kind,target,options={}){const root=typeof target==="string"?document.querySelector(target):target;if(root?.nodeType!==1)throw new Error("CanopyX: "+kind+" needs an element.");let registrations=uiInstances.get(root);if(!registrations)uiInstances.set(root,registrations=new Map());if(registrations.has(kind))return registrations.get(kind);const lifetime=new AbortController();watchTheme(root,lifetime.signal);let instance=null;const ready=loadPart(SHARED_PARTS[UI_KINDS[kind]]).then(()=>{if(lifetime.signal.aborted)throw new DOMException("UI component destroyed.","AbortError");instance=UI_IMPL[kind](root,options,lifetime.signal);return instance.ready||instance});const api={element:root,ready:ready.then(()=>api),destroy(){if(lifetime.signal.aborted)return;lifetime.abort();instance?.destroy?.();registrations.delete(kind)}};for(const name of["select","getSelected","refresh","preparePrint","open","close","next","back","skip","finish","setStepState","getState","toggle","getOpen","filter"])api[name]=(...args)=>{if(lifetime.signal.aborted)throw new Error("CanopyX: UI component destroyed.");return instance?instance[name]?.(...args):ready.then(()=>instance[name]?.(...args))};api.ready.catch(error=>{if(error.name!=="AbortError")root.dispatchEvent(new CustomEvent("canopyx:error",{detail:{error},bubbles:true}))});registrations.set(kind,Object.freeze(api));return api}
|
|
596
596
|
function bindContextMenu(target,items,options={}){if(typeof target!=="string"&&target?.nodeType!==1)throw new Error("CanopyX: contextMenu needs an element or selector.");if(typeof target==="string")document.documentElement.matches(target);let disposed=false,cleanup=null;const dispose=()=>{disposed=true;cleanup?.()};dispose.ready=loadPart(SHARED_PARTS["ui-menu"]).then(()=>{if(!disposed)cleanup=UI_IMPL.contextMenu(target,items,options)});dispose.open=(...args)=>disposed?false:cleanup?cleanup.open(...args):dispose.ready.then(()=>!disposed&&cleanup.open(...args));dispose.close=(...args)=>cleanup?.close(...args);return dispose}
|
|
597
597
|
function mountUi(root=document){const mounted=[];for(const kind of Object.keys(UI_KINDS)){const selector="[data-cx-"+kind+"]",nodes=[...root.querySelectorAll(selector)];if(root.matches?.(selector))nodes.unshift(root);for(const element of nodes){if(kind==="menu"&&element.parentElement?.closest(selector))continue;if(kind==="stepper"&&["report","static"].includes(element.dataset.mode))continue;mounted.push(createUi(kind,element))}}
|
|
598
598
|
return mounted}
|
|
@@ -641,7 +641,8 @@ if(choice.highlight!==undefined)this.highlightOn=choice.highlight;this.rowH=dens
|
|
|
641
641
|
this.setLayout(state.layout).catch(error=>this.emit("error",{error}));if(state.highlight!==undefined&&state.highlight!==!!this.highlightOn)
|
|
642
642
|
this.setSearchHighlight(state.highlight).catch(error=>this.emit("error",{error}))},startViewModes(){const parts=[this.highlightOptions&&"highlight",(this.layoutOptions||this.view.stickyHeader)&&"layouts"].filter(Boolean);return Promise.all(parts.map(name=>this.requirePart(name).then(()=>this.destroyed||VIEW_MODES[name+"Start"](this))
|
|
643
643
|
.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.")}
|
|
644
|
-
|
|
644
|
+
if(options.recordDrawerModal!==undefined&&typeof options.recordDrawerModal!=="boolean")
|
|
645
|
+
throw new Error("CanopyX: recordDrawerModal must be a Boolean.");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"])
|
|
645
646
|
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);
|
|
646
647
|
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}
|
|
647
648
|
const configured=new Map();function configure(viewId,options){requireIdentifier(viewId,"view ID");if(!options||typeof options!=="object"){throw new Error("CanopyX: configure needs a view id and an options object.")}
|
|
@@ -7,9 +7,14 @@ export interface GridColumnGroup {
|
|
|
7
7
|
readonly columns?: readonly string[];
|
|
8
8
|
/** Groups nested below this one. */
|
|
9
9
|
readonly groups?: readonly GridColumnGroup[];
|
|
10
|
-
/** Initial pin of every column of a top-level group; null scrolls.
|
|
10
|
+
/** Initial pin of every column of a top-level semantic group; null scrolls. Not supported in presentation mode. */
|
|
11
11
|
readonly pinned?: "left" | "right" | null;
|
|
12
12
|
}
|
|
13
|
+
/** Presentation labels preserve authored column layout; pins belong to individual columns. */
|
|
14
|
+
export interface GridColumnBandDefinition extends Omit<GridColumnGroup, "pinned" | "groups"> {
|
|
15
|
+
readonly pinned?: never;
|
|
16
|
+
readonly groups?: readonly GridColumnBandDefinition[];
|
|
17
|
+
}
|
|
13
18
|
/** A column group as the grid shows it now. */
|
|
14
19
|
export interface GridColumnGroupState {
|
|
15
20
|
readonly id: string;
|
|
@@ -18,8 +23,8 @@ export interface GridColumnGroupState {
|
|
|
18
23
|
readonly columns: readonly string[];
|
|
19
24
|
/** The columns of the group that are on screen. */
|
|
20
25
|
readonly visibleColumns: readonly string[];
|
|
21
|
-
/** The pin of the group's columns. */
|
|
22
|
-
readonly pin: "none" | "left" | "right";
|
|
26
|
+
/** The requested pin of the group's columns; presentation definitions can have mixed pins. */
|
|
27
|
+
readonly pin: "none" | "left" | "right" | "mixed";
|
|
23
28
|
readonly groups: readonly GridColumnGroupState[];
|
|
24
29
|
}
|
|
25
30
|
|
|
@@ -27,6 +32,21 @@ export interface GridColumnGroupState {
|
|
|
27
32
|
export interface GridColumnGroupApi {
|
|
28
33
|
/** The groups in their current order. */
|
|
29
34
|
getColumnGroups(): readonly GridColumnGroupState[];
|
|
30
|
-
/**
|
|
35
|
+
/** Actual displayed spans, ordered by level then visible column position. Empty before the optional part loads. */
|
|
36
|
+
getColumnBands(): readonly GridColumnBand[];
|
|
37
|
+
/** Shows or hides every column of a semantic group; rejects for presentation bands. */
|
|
31
38
|
setColumnGroupVisible(id: string, visible: boolean): Promise<void>;
|
|
32
39
|
}
|
|
40
|
+
|
|
41
|
+
/** One contiguous displayed header span. A logical group may appear in several bands. */
|
|
42
|
+
export interface GridColumnBand {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly title: string;
|
|
45
|
+
readonly columns: readonly string[];
|
|
46
|
+
/** One-based group level, visible column position and span. */
|
|
47
|
+
readonly level: number;
|
|
48
|
+
readonly columnIndex: number;
|
|
49
|
+
readonly columnSpan: number;
|
|
50
|
+
/** Effective pin of this span after the ordinary pin budget is applied. */
|
|
51
|
+
readonly pin: "none" | "left" | "right";
|
|
52
|
+
}
|
package/JavaScript/grid.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ import type { enhance, grid } from "./enhance.js";
|
|
|
25
25
|
export * from "./editing.js";
|
|
26
26
|
import type { GridExportFormat, GridFormatOptions, GridFormatRule } from "./formatting.js";
|
|
27
27
|
export * from "./formatting.js";
|
|
28
|
-
import type { GridColumnGroup, GridColumnGroupApi } from "./column-groups.js";
|
|
28
|
+
import type { GridColumnGroup, GridColumnBandDefinition, GridColumnGroupApi } from "./column-groups.js";
|
|
29
29
|
import type { GridRangeSelection, GridClipboardSettings, GridCellRange, GridPasteEvent, GridCellRangeApi } from "./cell-ranges.js";
|
|
30
30
|
export * from "./column-groups.js";
|
|
31
31
|
export * from "./cell-ranges.js";
|
|
@@ -410,6 +410,8 @@ interface GridDefinition<Q extends AcceptedGridQuery = GridQuery, Row extends Gr
|
|
|
410
410
|
rowAction?: "none" | "details" | "link";
|
|
411
411
|
/** Draw a row tone as a bar at the row start (default false). */
|
|
412
412
|
rowAccents?: boolean;
|
|
413
|
+
/** True opens the record drawer as a full-height sheet over a scrim at every width (default false: a side panel on wide screens, a sheet on narrow ones). */
|
|
414
|
+
recordDrawerModal?: boolean;
|
|
413
415
|
/** How editable columns edit; the host's editing option overrides these. */
|
|
414
416
|
editing?: GridEditingSettings;
|
|
415
417
|
/** Marks search and column-filter matches in cells and the drawer, with a match count and next/previous (docs/search-highlight.md). */
|
|
@@ -439,6 +441,8 @@ interface GridDefinition<Q extends AcceptedGridQuery = GridQuery, Row extends Gr
|
|
|
439
441
|
/** Pager settings of a paged view, read by the pager part (canopyx-pager.js); absent keeps the built-in pager. */
|
|
440
442
|
pager?: GridPager;
|
|
441
443
|
selection: "none" | "single" | "multiple";
|
|
444
|
+
/** Select rows on click instead of running the row action. Ctrl/Command toggles; Shift extends. Default false. */
|
|
445
|
+
selectOnClick?: boolean;
|
|
442
446
|
/** Ordinal IDs are source positions in one snapshot; saved selection replay and keyed live updates are unavailable. Default stableKey. */
|
|
443
447
|
rowIdentity?: "stableKey" | "ordinal";
|
|
444
448
|
search: boolean;
|
|
@@ -449,6 +453,8 @@ interface GridDefinition<Q extends AcceptedGridQuery = GridQuery, Row extends Gr
|
|
|
449
453
|
reporting?: GridReporting;
|
|
450
454
|
/** Header rows above the columns, up to three levels (docs/column-groups.md). */
|
|
451
455
|
columnGroups?: readonly GridColumnGroup[];
|
|
456
|
+
/** Presentation labels follow displayed contiguous members and pins without governing layout. Mutually exclusive with columnGroups. */
|
|
457
|
+
columnBands?: readonly GridColumnBandDefinition[];
|
|
452
458
|
/** Cell ranges by mouse, keyboard and touch, the cell menu and the clipboard keys (docs/cell-ranges.md). */
|
|
453
459
|
rangeSelection?: GridRangeSelection;
|
|
454
460
|
/** How cell ranges copy and paste. */
|
|
@@ -840,6 +846,8 @@ interface GridHostBehavior<Q extends AcceptedGridQuery = GridQuery, Row extends
|
|
|
840
846
|
source?: Q extends GridCursorQuery ? GridCursorSource<Row> : GridSource<Row>;
|
|
841
847
|
/** Space-separated host styling classes copied onto body-appended record, text and column overlays when opened. */
|
|
842
848
|
overlayClass?: string;
|
|
849
|
+
/** True opens the record drawer as a full-height sheet over a scrim at every width (default false: a side panel on wide screens, a sheet on narrow ones). */
|
|
850
|
+
recordDrawerModal?: boolean;
|
|
843
851
|
labels?: Partial<CanopyLabels>;
|
|
844
852
|
renderers?: Record<string, (row: Row, element: HTMLElement, context: GridCellContext) => boolean | void>;
|
|
845
853
|
cachePageLimit?: number;
|
|
@@ -1051,6 +1059,8 @@ export interface DataGrid<Q extends AcceptedGridQuery = GridQuery, Row extends G
|
|
|
1051
1059
|
setTimeZone(zone: TimeZone): void;
|
|
1052
1060
|
getTimeZone(): TimeZone;
|
|
1053
1061
|
/** Explicit identities only. Throws in query-selection mode; use getSelectionDescriptor instead. */
|
|
1062
|
+
/** Mutable pointer-selection opt-in, initially taken from the view. Does not add checkboxes. */
|
|
1063
|
+
selectOnClick: boolean;
|
|
1054
1064
|
getSelection(): string[];
|
|
1055
1065
|
setSelection(ids: string[]): void;
|
|
1056
1066
|
clearSelection(): void;
|
package/JavaScript/hosts.d.ts
CHANGED
|
@@ -165,6 +165,8 @@ export interface TreeExport extends ExportCapture {
|
|
|
165
165
|
export type HostTrustedHTML = typeof globalThis extends { TrustedHTML: { prototype: infer T } } ? T : never;
|
|
166
166
|
/** Hooks stay with the host. Acceptance never makes the grid perform a write. */
|
|
167
167
|
interface HostOptionsBase extends RecordPanelOptions {
|
|
168
|
+
/** Overrides the view: true opens a modal record sheet at every width; false keeps the responsive side panel/sheet. */
|
|
169
|
+
recordDrawerModal?: boolean;
|
|
168
170
|
source?: DataSource;
|
|
169
171
|
labels?: CanopyLabels;
|
|
170
172
|
requestTimeoutMs?: number;
|
|
@@ -914,6 +914,10 @@ const labels = Object.freeze({
|
|
|
914
914
|
timePosition: "Momentaufnahme {index} von {count}",
|
|
915
915
|
timeFrame: "Momentaufnahme um {time}",
|
|
916
916
|
timeReducedMotion: "Die zeitgesteuerte Wiedergabe ist wegen reduzierter Bewegung deaktiviert. Momentaufnahmen manuell durchgehen.",
|
|
917
|
+
accordionSearch: "Abschnitte filtern",
|
|
918
|
+
accordionNoMatches: "Keine passenden Abschnitte",
|
|
919
|
+
accordionCopyTitle: "Titel kopieren",
|
|
920
|
+
accordionCopied: "Kopiert",
|
|
917
921
|
tabsMore: "Mehr",
|
|
918
922
|
tabsMoreSelected: "Mehr: {label}",
|
|
919
923
|
tabsScrollPrevious: "Vorherige Tabs",
|
|
@@ -914,6 +914,10 @@ const labels = Object.freeze({
|
|
|
914
914
|
timePosition: "Instantánea {index} de {count}",
|
|
915
915
|
timeFrame: "Instantánea de {time}",
|
|
916
916
|
timeReducedMotion: "La reproducción automática está desactivada porque el movimiento reducido está activado. Recorre las instantáneas manualmente.",
|
|
917
|
+
accordionSearch: "Filtrar secciones",
|
|
918
|
+
accordionNoMatches: "No hay secciones coincidentes",
|
|
919
|
+
accordionCopyTitle: "Copiar título",
|
|
920
|
+
accordionCopied: "Copiado",
|
|
917
921
|
tabsMore: "Más",
|
|
918
922
|
tabsMoreSelected: "Más: {label}",
|
|
919
923
|
tabsScrollPrevious: "Pestañas anteriores",
|
|
@@ -914,6 +914,10 @@ const labels = Object.freeze({
|
|
|
914
914
|
timePosition: "Instantané {index} sur {count}",
|
|
915
915
|
timeFrame: "Instantané du {time}",
|
|
916
916
|
timeReducedMotion: "La lecture automatique est désactivée car les mouvements réduits sont activés. Parcourez les instantanés manuellement.",
|
|
917
|
+
accordionSearch: "Filtrer les sections",
|
|
918
|
+
accordionNoMatches: "Aucune section correspondante",
|
|
919
|
+
accordionCopyTitle: "Copier le titre",
|
|
920
|
+
accordionCopied: "Copié",
|
|
917
921
|
tabsMore: "Plus",
|
|
918
922
|
tabsMoreSelected: "Plus : {label}",
|
|
919
923
|
tabsScrollPrevious: "Onglets précédents",
|
|
@@ -914,6 +914,10 @@ const labels = Object.freeze({
|
|
|
914
914
|
timePosition: "Istantanea {index} di {count}",
|
|
915
915
|
timeFrame: "Istantanea del {time}",
|
|
916
916
|
timeReducedMotion: "La riproduzione automatica è disattivata perché è attiva la riduzione del movimento. Scorri le istantanee manualmente.",
|
|
917
|
+
accordionSearch: "Filtra le sezioni",
|
|
918
|
+
accordionNoMatches: "Nessuna sezione corrispondente",
|
|
919
|
+
accordionCopyTitle: "Copia titolo",
|
|
920
|
+
accordionCopied: "Copiato",
|
|
917
921
|
tabsMore: "Altro",
|
|
918
922
|
tabsMoreSelected: "Altro: {label}",
|
|
919
923
|
tabsScrollPrevious: "Schede precedenti",
|
|
@@ -914,6 +914,10 @@ const labels = Object.freeze({
|
|
|
914
914
|
timePosition: "Stan {index} z {count}",
|
|
915
915
|
timeFrame: "Stan z {time}",
|
|
916
916
|
timeReducedMotion: "Automatyczne odtwarzanie jest wyłączone z powodu ograniczenia ruchu. Przeglądaj stany ręcznie.",
|
|
917
|
+
accordionSearch: "Filtruj sekcje",
|
|
918
|
+
accordionNoMatches: "Brak pasujących sekcji",
|
|
919
|
+
accordionCopyTitle: "Kopiuj tytuł",
|
|
920
|
+
accordionCopied: "Skopiowano",
|
|
917
921
|
tabsMore: "Więcej",
|
|
918
922
|
tabsMoreSelected: "Więcej: {label}",
|
|
919
923
|
tabsScrollPrevious: "Poprzednie karty",
|
package/JavaScript/protocol.d.ts
CHANGED
|
@@ -275,6 +275,7 @@ export type LabelName =
|
|
|
275
275
|
| "calendarResized" | "calendarSeriesMoved" | "calendarSeriesTime" | "calendarMoveHelp" | "calendarEdit" | "calendarSaveFailed" | "calendarExport" | "calendarTimeRange" | "calendarSelected"
|
|
276
276
|
| "calendarLane" | "calendarHighlighted"
|
|
277
277
|
/** Standalone UI primitives; their English defaults load with the optional UI parts. */
|
|
278
|
+
| "accordionSearch" | "accordionNoMatches" | "accordionCopyTitle" | "accordionCopied"
|
|
278
279
|
| "tabsMore" | "tabsMoreSelected" | "tabsScrollPrevious" | "tabsScrollNext" | "tabsSection" | "tabsPosition" | "tabsPause" | "tabsPlay" | "tabsMotionDisabled"
|
|
279
280
|
| "stepperStepOf" | "stepperBack" | "stepperNext" | "stepperFinish" | "stepperSkip" | "stepperSaveDraft"
|
|
280
281
|
| "stepperAllSteps" | "stepperOptional" | "stepperCompleted" | "stepperSkipped" | "stepperIssues" | "stepperIssue"
|
|
@@ -326,6 +327,8 @@ interface ViewDefinitionBase {
|
|
|
326
327
|
deepLink?: boolean;
|
|
327
328
|
/** The record panel of the tree's drawer (records.d.ts, docs/record-panel.md). */
|
|
328
329
|
record?: RecordPanelDefinition;
|
|
330
|
+
/** True opens a modal record sheet at every width; the host option takes precedence. Default false. */
|
|
331
|
+
recordDrawerModal?: boolean;
|
|
329
332
|
labels?: CanopyLabels;
|
|
330
333
|
dragDrop?: boolean;
|
|
331
334
|
contextActions?: ContextAction[];
|
package/JavaScript/records.d.ts
CHANGED
|
@@ -134,6 +134,8 @@ export interface RecordOpenAll {
|
|
|
134
134
|
/** Options that open a record (openRecord). */
|
|
135
135
|
export interface OpenRecordOptions {
|
|
136
136
|
readonly returnFocus?: Element | null; readonly path?: readonly string[]; readonly tab?: string;
|
|
137
|
+
/** A record panel section id to open on: its tab is selected, the section expanded and the drawer body scrolled to it. */
|
|
138
|
+
readonly section?: string;
|
|
137
139
|
/** Read this record's details again after a host mutation, even when the grid's row/revision has not changed. */
|
|
138
140
|
readonly reload?: boolean;
|
|
139
141
|
}
|
package/JavaScript/ui.d.ts
CHANGED
|
@@ -70,6 +70,10 @@ export interface UiTabsOptions extends UiNavigationOptions {
|
|
|
70
70
|
transition?: "none" | "fade" | "slide";
|
|
71
71
|
hash?: boolean;
|
|
72
72
|
hashHistory?: "replace" | "push";
|
|
73
|
+
/** Remembers the selected tab in localStorage, under the root id (true) or this key. A link in the address wins. */
|
|
74
|
+
remember?: boolean | string;
|
|
75
|
+
/** Horizontal tabs share the full width equally. */
|
|
76
|
+
justified?: boolean;
|
|
73
77
|
tabs?: readonly UiTabOptions[];
|
|
74
78
|
/** Optional automatic progression. Manual selection stops it; reduced motion disables it.
|
|
75
79
|
* False disables settings emitted by the .NET builder. */
|
|
@@ -94,7 +98,65 @@ export interface UiNavigationController extends UiController {
|
|
|
94
98
|
/** Prepares deferred print bodies without navigation. Await before window.print(); rejects on load failure or cancellation. */
|
|
95
99
|
preparePrint(): Promise<void>;
|
|
96
100
|
}
|
|
97
|
-
export interface UiTabsController extends UiNavigationController {
|
|
101
|
+
export interface UiTabsController extends UiNavigationController {
|
|
102
|
+
/** Selects the next or previous enabled tab, stopping at the ends. Resolves false when there is none. */
|
|
103
|
+
next(): Promise<boolean>;
|
|
104
|
+
back(): Promise<boolean>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** An accordion section. In beforeChange, open is the state being requested. */
|
|
108
|
+
export interface UiAccordionItem extends UiNavigationItem {
|
|
109
|
+
readonly open: boolean;
|
|
110
|
+
}
|
|
111
|
+
/** Deferred settings for one section, matched by its data-accordion-id. */
|
|
112
|
+
export interface UiAccordionSectionOptions extends UiDeferredOptions<UiAccordionItem> {
|
|
113
|
+
id: string;
|
|
114
|
+
}
|
|
115
|
+
/** Accordion over the .NET heading and panel contract. Each section loads on its own; closing one aborts its pending loader. */
|
|
116
|
+
export interface UiAccordionOptions extends UiDeferredOptions<UiAccordionItem> {
|
|
117
|
+
/** Lets several sections stay open. Defaults to false: opening a section closes the open one. */
|
|
118
|
+
multiple?: boolean;
|
|
119
|
+
/** Section identities or indexes to open, instead of the markup's own. A link in the address and remembered state win over this. */
|
|
120
|
+
open?: readonly (string | number)[];
|
|
121
|
+
style?: "default" | "flush" | "separated" | "section";
|
|
122
|
+
indicator?: "chevron" | "plus";
|
|
123
|
+
indicatorPosition?: "end" | "start";
|
|
124
|
+
transition?: "none" | "slide";
|
|
125
|
+
print?: "all" | "selected";
|
|
126
|
+
/** Writes open sections to the address as repeated `root-id=section-id` pairs. Requires a root id. */
|
|
127
|
+
hash?: boolean;
|
|
128
|
+
hashHistory?: "replace" | "push";
|
|
129
|
+
/** Remembers open sections in localStorage, under the root id (true) or this key. */
|
|
130
|
+
remember?: boolean | string;
|
|
131
|
+
/** A filter box above the sections. Filtering hides sections without closing them.
|
|
132
|
+
* It reads the header text; `or`, `|`, quoted phrases and a leading `-` or `!` are understood. */
|
|
133
|
+
search?: boolean | {
|
|
134
|
+
placeholder?: string;
|
|
135
|
+
/** Delay after typing, in milliseconds. Defaults to 150. */
|
|
136
|
+
debounce?: number;
|
|
137
|
+
};
|
|
138
|
+
/** Adds a copy-title button to every section, or removes the ones the markup asks for. */
|
|
139
|
+
copyTitle?: boolean;
|
|
140
|
+
/** Renders an encoded icon name from the markup into a decorative node or plain text. */
|
|
141
|
+
renderIcon?: (icon: string, item: Partial<UiAccordionItem>) => Node | string | null | void;
|
|
142
|
+
labels?: UiLabels;
|
|
143
|
+
sections?: readonly UiAccordionSectionOptions[];
|
|
144
|
+
/** Returning false cancels. In a single-open accordion from is the section that would close; to.open is the requested state. */
|
|
145
|
+
beforeChange?: (from: UiAccordionItem | undefined, to: UiAccordionItem, signal: AbortSignal) => boolean | void | Promise<boolean | void>;
|
|
146
|
+
}
|
|
147
|
+
export interface UiAccordionController extends UiController {
|
|
148
|
+
open(idOrIndex: string | number): Promise<boolean>;
|
|
149
|
+
close(idOrIndex: string | number): Promise<boolean>;
|
|
150
|
+
toggle(idOrIndex: string | number): Promise<boolean>;
|
|
151
|
+
/** The open section identities in display order. */
|
|
152
|
+
getOpen(): UiResult<string[]>;
|
|
153
|
+
/** Filters sections by header text and returns how many remain visible. An empty query shows all. */
|
|
154
|
+
filter(query?: string): UiResult<number>;
|
|
155
|
+
/** Rediscovers the current markup and updates presentation. */
|
|
156
|
+
refresh(): UiResult<void>;
|
|
157
|
+
/** Prepares deferred print bodies without opening sections. Await before window.print(); rejects on load failure or cancellation. */
|
|
158
|
+
preparePrint(): Promise<void>;
|
|
159
|
+
}
|
|
98
160
|
|
|
99
161
|
/** Step states are presentation supplied by the host or by successful navigation and validation. */
|
|
100
162
|
export type UiStepState = "upcoming" | "current" | "done" | "error" | "skipped" | "disabled";
|
|
@@ -241,21 +303,27 @@ export function contextMenu(target: UiTarget,
|
|
|
241
303
|
options?: UiContextMenuOptions): UiContextMenuDisposer;
|
|
242
304
|
export function tabs(target: UiTarget, options?: UiTabsOptions): UiTabsController;
|
|
243
305
|
export function stepper(target: UiTarget, options?: UiStepperOptions): UiStepperController;
|
|
306
|
+
export function accordion(target: UiTarget, options?: UiAccordionOptions): UiAccordionController;
|
|
244
307
|
export function dialog(target: UiTarget, options?: UiDialogOptions): UiDialogController;
|
|
245
308
|
export function drawer(target: UiTarget, options?: UiDrawerOptions): UiDialogController;
|
|
246
309
|
export function popover(target: UiTarget, options?: UiOverlayOptions): UiOverlayController;
|
|
247
310
|
export function tooltip(target: UiTarget, options?: UiOverlayOptions): UiOverlayController;
|
|
248
311
|
export function menu(target: UiTarget, options?: UiMenuOptions): UiMenuController;
|
|
249
312
|
|
|
250
|
-
/** Named UI exports shared by tree
|
|
313
|
+
/** Named UI exports shared by the tree, grid, reporting and UI entry points. */
|
|
251
314
|
export interface UiApi {
|
|
252
315
|
mountUi: typeof mountUi;
|
|
253
316
|
contextMenu: typeof contextMenu;
|
|
254
317
|
tabs: typeof tabs;
|
|
255
318
|
stepper: typeof stepper;
|
|
319
|
+
accordion: typeof accordion;
|
|
256
320
|
dialog: typeof dialog;
|
|
257
321
|
drawer: typeof drawer;
|
|
258
322
|
popover: typeof popover;
|
|
259
323
|
tooltip: typeof tooltip;
|
|
260
324
|
menu: typeof menu;
|
|
261
325
|
}
|
|
326
|
+
|
|
327
|
+
/** The UI entry point (`@evotecit/canopyx/ui`): the UI factories, the part registration and the nonce setter. */
|
|
328
|
+
declare const CanopyXUi: UiApi & { registerPart(name: string, part: { version: string; requires?: string[]; install(context: object): void }): void; setNonce(nonce: string): void };
|
|
329
|
+
export default CanopyXUi;
|