@evotecit/canopyx 0.0.0-stage → 0.1.0
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-filter-editor.css +135 -0
- package/CanopyX.Html/Assets/canopyx-filter-editor.js +61 -0
- package/CanopyX.Html/Assets/canopyx-grid-module.mjs +1035 -0
- package/CanopyX.Html/Assets/canopyx-grid.css +554 -0
- package/CanopyX.Html/Assets/canopyx-grid.js +1033 -0
- package/CanopyX.Html/Assets/canopyx-interactive.css +351 -0
- package/CanopyX.Html/Assets/canopyx-interactive.js +664 -0
- package/CanopyX.Html/Assets/canopyx-labels-de.js +225 -0
- package/CanopyX.Html/Assets/canopyx-labels-es.js +225 -0
- package/CanopyX.Html/Assets/canopyx-labels-pl.js +225 -0
- package/CanopyX.Html/Assets/canopyx-module.mjs +665 -0
- package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +1278 -0
- package/CanopyX.Html/Assets/canopyx-reporting.css +604 -0
- package/CanopyX.Html/Assets/canopyx-reporting.js +1276 -0
- package/CanopyX.Html/Assets/canopyx.css +325 -0
- package/CanopyX.Html/Assets/canopyx.js +534 -0
- package/JavaScript/grid.d.ts +591 -0
- package/JavaScript/hosts.d.ts +109 -0
- package/JavaScript/index.d.ts +85 -0
- package/JavaScript/locales/de.d.ts +6 -0
- package/JavaScript/locales/de.mjs +223 -0
- package/JavaScript/locales/es.d.ts +6 -0
- package/JavaScript/locales/es.mjs +223 -0
- package/JavaScript/locales/pl.d.ts +6 -0
- package/JavaScript/locales/pl.mjs +223 -0
- package/JavaScript/protocol.d.ts +229 -0
- package/LICENSE +21 -0
- package/README.md +476 -2
- package/README.nuget.md +75 -0
- package/package.json +61 -4
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
const runtimeHost=typeof window==="undefined"?{}:window;if(runtimeHost.CanopyX&&(runtimeHost.CanopyX.protocol!=="canopyx/2"||!runtimeHost.CanopyX.labels.actions)){throw new Error("CanopyX: load the interactive runtime or module before the core script.")}
|
|
2
|
+
/* CanopyX: canopyx/2. */
|
|
3
|
+
(function(global){"use strict";if(global.CanopyX){return}
|
|
4
|
+
const PROTOCOL="canopyx/2";const OVERSCAN=8;const TYPEAHEAD_MS=600;const INDENT=18;const COUNTS_BATCH=50;const SHARD_CACHE=24;const ENTRY_CACHE=32;const ENTRY_BATCH=8;const PACK_CACHE=6;const PACK_CACHE_BYTES=2*1024*1024;const DATE_CACHE=4096;const SEARCH_CANDIDATES=5;const MAX_PROTOCOL_INTEGER=2147483647;const MAX_COLUMN_WIDTH=MAX_PROTOCOL_INTEGER;const activeGrids=new WeakMap();let instanceSeq=0;function h(tag,className){const element=document.createElement(tag);if(className){element.className=className}
|
|
5
|
+
return element}
|
|
6
|
+
function setText(element,value){element.textContent=value==null?"":String(value)}
|
|
7
|
+
function validStructuralCount(value){return Number.isInteger(value)&&value>=0&&value<=MAX_PROTOCOL_INTEGER}
|
|
8
|
+
function requireFlags(flags,name){if(!Array.isArray(flags)||flags.some((flag)=>typeof flag!=="string"||!flag.length)){throw new Error("CanopyX: "+name+" must be an array of nonempty strings.")}}
|
|
9
|
+
function checkDocument(doc,type){if(!doc||typeof doc!=="object"){throw new Error("CanopyX: expected a "+type+" document, got "+typeof doc)}
|
|
10
|
+
if(doc.protocol!==PROTOCOL){throw new Error("CanopyX: unsupported protocol '"+doc.protocol+"' (this runtime reads "+PROTOCOL+")")}
|
|
11
|
+
if(doc.type!==type){throw new Error("CanopyX: expected a "+type+" document, got '"+doc.type+"'")}
|
|
12
|
+
if(Object.hasOwn(doc,"revision"))requireIdentifier(doc.revision,"dataset revision");if(type==="search"){validateSearchResult(doc);if(typeof doc.truncated!=="boolean")throw new Error("CanopyX: search truncated must be a boolean.");if(Object.hasOwn(doc,"total")&&!validStructuralCount(doc.total)){throw new Error("CanopyX: search total must be a nonnegative 32-bit integer.")}}
|
|
13
|
+
if(type==="counts"){if(!Array.isArray(doc.items))throw new Error("CanopyX: counts items must be an array.");const ids=new Set();for(const item of doc.items){requireIdentifier(item&&item.id,"counts node ID");if(ids.has(item.id))throw new Error("CanopyX: duplicate counts node ID '"+item.id+"'.");ids.add(item.id);if(Object.hasOwn(item,"childCount")&&!validStructuralCount(item.childCount)){throw new Error("CanopyX: counts childCount must be a nonnegative 32-bit integer.")}
|
|
14
|
+
if(Object.hasOwn(item,"cells")&&
|
|
15
|
+
(!item.cells||typeof item.cells!=="object"||Array.isArray(item.cells))){throw new Error("CanopyX: counts cells must be an object.")}
|
|
16
|
+
if(Object.hasOwn(item,"flags"))requireFlags(item.flags,"counts flags")}}
|
|
17
|
+
return doc}
|
|
18
|
+
function validateSearchHit(hit){if(!hit||typeof hit!=="object"||!Array.isArray(hit.path)||!hit.path.length||typeof hit.label!=="string"){throw new Error("CanopyX: search hit needs an ID path and string label.")}
|
|
19
|
+
const ids=new Set();for(const id of hit.path){requireIdentifier(id,"search hit path ID");if(ids.has(id))throw new Error("CanopyX: search hit path repeats node ID '"+id+"'.");ids.add(id)}
|
|
20
|
+
if((Object.hasOwn(hit,"kind")&&typeof hit.kind!=="string")||
|
|
21
|
+
(Object.hasOwn(hit,"context")&&typeof hit.context!=="string")){throw new Error("CanopyX: search hit kind and context must be strings.")}}
|
|
22
|
+
function validateSearchResult(result){if(!result||!Array.isArray(result.hits))throw new Error("CanopyX: search hits must be an array.");result.hits.forEach(validateSearchHit);return result}
|
|
23
|
+
function checkPage(doc,node){checkDocument(doc,"page");if(!Array.isArray(doc.items))throw new Error("CanopyX: page items must be an array.");if(!validStructuralCount(doc.offset))throw new Error("CanopyX: page offset must be a nonnegative 32-bit integer.");if(Object.hasOwn(doc,"total")&&!validStructuralCount(doc.total)){throw new Error("CanopyX: page total must be a nonnegative 32-bit integer.")}
|
|
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
|
+
function isCellObject(cell){return cell!==null&&typeof cell==="object"}
|
|
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")) {
|
|
28
|
+
return NO_TONE}
|
|
29
|
+
const host=!/^(neutral|info|success|warning|danger|muted)$/.test(name);
|
|
30
|
+
tone={cls:" cx-tone-"+name +(host?" cx-toned":""),host:host?name:null};if(tones.size<256)tones.set(name,tone)}
|
|
31
|
+
return tone}
|
|
32
|
+
function applyTone(element,base,tone){element.className=base+tone.cls;toneColors(element,tone)}
|
|
33
|
+
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
|
+
const numberFormats=new Map();function numberFormat(lang){const key=lang||"";let format=numberFormats.get(key);if(!format){try{format=new Intl.NumberFormat(lang||undefined,{maximumFractionDigits:3})}catch(e){format=new Intl.NumberFormat(undefined,{maximumFractionDigits:3})}
|
|
36
|
+
if(numberFormats.size>=16)numberFormats.delete(numberFormats.keys().next().value);numberFormats.set(key,format)}
|
|
37
|
+
return format}
|
|
38
|
+
function pad2(value){return value<10?"0"+value:String(value)}
|
|
39
|
+
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)}
|
|
40
|
+
return texts}
|
|
41
|
+
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",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))throw new Error("CanopyX: there is no label '"+name+"'.");if(typeof source[name]!=="string"||!/\S/.test(source[name])) throw new Error("CanopyX: label '" + name + "' must be a string with visible text.");
|
|
42
|
+
if((name==="utcTime"||name==="localTime")&&source[name].indexOf("{time}")<0)throw new Error("CanopyX: label '"+name+"' must hold {time}.");labels[name]=source[name]})}
|
|
43
|
+
return Object.freeze(labels)}
|
|
44
|
+
function fill(template,values){return template.replace(/\{(\w+)\}/g, (match, name) => (values && Object.hasOwn(values, name) ? String(values[name]) : match));
|
|
45
|
+
}
|
|
46
|
+
function formatting(lang,local,labels){const split=(template)=>{const at=template.indexOf("{time}");return[template.slice(0,at),template.slice(at+6)]};return{lang,local,labels,utc:split(labels.utcTime),other:split(labels.localTime)}}
|
|
47
|
+
function formatDate(value,fmt){const texts=dateText(value);return texts?(fmt.local?texts[1]:fmt.utc[0]+ texts[0]+ fmt.utc[1]):String(value)}
|
|
48
|
+
function otherZone(cell,column,fmt){const value=cellValue(cell);if(column.kind!=="datetime"||typeof value!=="string"||(isCellObject(cell)&&typeof cell.text==="string")){return null}
|
|
49
|
+
const texts=dateText(value);return texts?(fmt.local?fmt.utc[0]+ texts[0]+ fmt.utc[1]:fmt.other[0]+ texts[1]+ fmt.other[1]):null}
|
|
50
|
+
function formatValue(value,kind,fmt){if(value===null||value===undefined){return ""}
|
|
51
|
+
if(typeof value==="boolean"){return value?fmt.labels.yes:fmt.labels.no}
|
|
52
|
+
if(typeof value==="number"&&kind==="number"){return numberFormat(fmt.lang).format(value)}
|
|
53
|
+
if(typeof value==="string"&&kind==="datetime"){return formatDate(value,fmt)}
|
|
54
|
+
return String(value)}
|
|
55
|
+
function cellText(cell,column,fmt){if(isCellObject(cell)&&typeof cell.text==="string"){return cell.text}
|
|
56
|
+
return formatValue(cellValue(cell),column.kind,fmt)}
|
|
57
|
+
const collator=new Intl.Collator(undefined,{numeric:true,sensitivity:"base"});function sortKey(cell,column){let value=cellValue(cell);if((value===null||value==="")&&isCellObject(cell)&&typeof cell.text==="string"&&cell.text!==""){value=cell.text}
|
|
58
|
+
if(value===null||value===""){return{rank:2,value:0}}
|
|
59
|
+
if(typeof value==="boolean"){return{rank:0,value:value?1:0}}
|
|
60
|
+
if(typeof value==="number"){return{rank:0,value}}
|
|
61
|
+
if(column.kind==="datetime"){const time=Date.parse(value);if(!isNaN(time)){return{rank:0,value:time}}}
|
|
62
|
+
return{rank:1,value:String(value)}}
|
|
63
|
+
function compareKeys(a,b,direction){if(a.rank!==b.rank){return a.rank-b.rank}
|
|
64
|
+
if(a.rank===2){return 0}
|
|
65
|
+
const order=a.rank===0?(a.value<b.value?-1:a.value>b.value?1:0):collator.compare(a.value,b.value);return order*direction}
|
|
66
|
+
const EXTRA_FOLD_PAIRS="1t.w.q.1,2u.w.n.1,2.w.7.1,y.1.o.2,4.1.3.2,3.1.8.2,3.1.n.2,2.-3d,1.1.3.2,4.5u,1.1,2.1,2.5q,1.1,2.5p,1.5p,1.1,3.27,1.5m,1.5n,1.1,2.5p,1.5r,2.5v,1.5t,1.1,4.5v,1.5x,2.5y,1.1.3.2,2.62,1.1,2.62,3.1,2.62,1.1,2.61,1.61,1.1,2.1,2.63,1.1,4.1,8.2,1.1,2.2,1.1,2.2,1.1.9.2,3.1.9.2,3.2,1.1,2.1,2.-2p,1.-1k,1.1.k.2,2.-3m,2.1.9.2,8.8bv,1.1,2.-4j,1.8bs,3.1,2.-5f,1.1x,1.1z,1.1.5.2,82.1,2.1,4.1,9.38,7.12,2.11.3.1,2.1s,2.1r,1.1r,2.w.h.1,2.w.9.1,10.8,9.1.c.2,6.-1o,3.1,2.-7,1.1,3.-3m.3.1,1.28.g.1,1.w.w.1,1d.1.h.2,a.1.r.2,2.f,1.1.7.2,3.1.1c.2,3.1c.12.1,28a.5ls.12.1,2.5ls,6.5ls,k3.tzk.28.1,1.8.6.1,1p0.1,7.-2bk.17.1,3.-2bk.3.1,8x.1.23.2,a.-5vj,2.1.1c.2,a.-8.8.1,9.-8.6.1,b.-8.8.1,9.-8.8.1,9.-8.6.1,c.-8.4.2,9.-8.8.1,p.-8.8.1,9.-8.8.1,9.-8.8.1,9.-8,1.-8,1.-22,1.-22,1.-9,c.-2e.4.1,1.-9,c.-8,1.-8,1.-2s,1.-2s,d.-8,1.-8,1.-34,1.-34,1.-7,c.-3k,1.-3k,1.-3i,1.-3i,1.-9,8a.-5st,4.-6gv,1.-6di,7.s,1a.g.g.1,k.1,mr.q.q.1,1f5.1c.1c.1,1d.1,2.-8af,1.-2xy,1.-89z,3.1.3.2,2.-8bg,1.-8al,1.-8bj,1.-8bi,2.1,3.1,9.-8cf,1.-8cf,1.1.1e.2,9.1,2.1,5.1,nym.1.n.2,k.1.e.2,3s.1.7.2,4.1.v.2,b.1,2.1,2.-r9g,1.1.5.2,5.1,2.-wmg,3.1,2.1,4.1.a.2,2.-wn8,1.-wnj,1.-wnf,1.-wn5,1.-wn8,2.-wlu,1.-wmi,1.-wlx,1.ps,1.1.8.2,2.-1c,1.-wn7,1.-raw,1.1,2.1,2.-wo7,1.1,4.1,6.1.3.2,2.-wu9,p.1,h7w.w.q.1";const EXTRA_LOWERCASE=new Map();let extraUnit=0;for(const record of EXTRA_FOLD_PAIRS.split(",")){const[gap,delta,count=1,step=0]=record.split(".").map(part=>parseInt(part,36));extraUnit +=gap;for(let i=0;i<count;i++)EXTRA_LOWERCASE.set(extraUnit+i*step,extraUnit+i*step+delta);extraUnit +=(count-1)*step}
|
|
67
|
+
function foldCode(c){if(c<0x80){return c>=65&&c<=90?c+32:c}
|
|
68
|
+
return EXTRA_LOWERCASE.get(c)||c}
|
|
69
|
+
function foldUnit(unit){return String.fromCharCode(foldCode(unit.charCodeAt(0)))}
|
|
70
|
+
function isSeparator(code){return code<128&&!((code>=48&&code<=57)||(code>=65&&code<=90)||(code>=97&&code<=122))}
|
|
71
|
+
function foldText(text){const source=String(text==null?"":text);let out="";for(let i=0;i<source.length;i++){out +=foldUnit(source.charAt(i))}
|
|
72
|
+
return out}
|
|
73
|
+
function words(text){const folded=foldText(text);const out=[];let current="";for(let i=0;i<folded.length;i++){if(isSeparator(folded.charCodeAt(i))){if(current){out.push(current);current=""}}else{current +=folded.charAt(i)}}
|
|
74
|
+
if(current){out.push(current)}
|
|
75
|
+
return out}
|
|
76
|
+
function matchesWords(labelWords,queryWords){return queryWords.every((q)=>labelWords.some((w)=>w.startsWith(q)))}
|
|
77
|
+
function trimSeparators(text){let start=0,end=text.length;while(start<end&&isSeparator(text.charCodeAt(start)))start++;while(end>start&&isSeparator(text.charCodeAt(end-1)))end--;return text.slice(start,end)}
|
|
78
|
+
function rankLabel(label,foldedQuery){const l=trimSeparators(foldText(label));return l===foldedQuery?0:l.startsWith(foldedQuery)?1:2}
|
|
79
|
+
function shardKey(prefix){let out="";for(let i=0;i<prefix.length;i++){const code=prefix.charCodeAt(i);out +=(code>=97&&code<=122)||(code>=48&&code<=57)?prefix.charAt(i):"_" +("000"+code.toString(16)).slice(-4)}
|
|
80
|
+
return out}
|
|
81
|
+
const sidecarDocs=new Map();const sidecarWaiters=new Map();function sidecarKey(view,ref){return JSON.stringify([view,ref])}
|
|
82
|
+
function acceptSidecar(entry){if(!Array.isArray(entry)||entry.length!==3){return}
|
|
83
|
+
const key=sidecarKey(entry[0],entry[1]);const waiter=sidecarWaiters.get(key);if(waiter){sidecarWaiters.delete(key);waiter.resolve(entry[2])}else{sidecarDocs.set(key,entry[2])}}
|
|
84
|
+
const sidecarQueue=global.CanopyXSidecar=global.CanopyXSidecar||[];sidecarQueue.forEach(acceptSidecar);sidecarQueue.length=0;sidecarQueue.push=function(){for(let i=0;i<arguments.length;i++){acceptSidecar(arguments[i])}
|
|
85
|
+
return 0};function loadSidecar(base,view,ref){if(ref.split("/").some((part)=>part==="."||part==="..")){return Promise.reject(new Error("Sidecar reference cannot contain a dot path component: "+ref))}
|
|
86
|
+
const key=sidecarKey(view,ref);if(sidecarDocs.has(key)){const doc=sidecarDocs.get(key);sidecarDocs.delete(key);return Promise.resolve(doc)}
|
|
87
|
+
const pending=sidecarWaiters.get(key);if(pending){return pending.promise}
|
|
88
|
+
const waiter={};waiter.promise=new Promise((resolve,reject)=>{waiter.resolve=resolve;waiter.reject=reject});sidecarWaiters.set(key,waiter);const script=document.createElement("script");script.src=base+ref.split("/").map(encodeURIComponent).join("/")+ ".js";script.async=true;script.onload=()=>{script.remove();if(sidecarWaiters.get(key)===waiter){sidecarWaiters.delete(key);waiter.reject(new Error("Sidecar file did not register its data: "+ref))}};script.onerror=()=>{script.remove();if(sidecarWaiters.get(key)===waiter){sidecarWaiters.delete(key);waiter.reject(new Error("Sidecar file is missing: "+ref))}};document.head.appendChild(script);return waiter.promise}
|
|
89
|
+
function badIndex(what){return new Error("CanopyX: search "+what+" does not match the manifest or is malformed.")}
|
|
90
|
+
function isCount(value){return Number.isInteger(value)&&value>=0}
|
|
91
|
+
function checkSearchIndex(index){if(!index||!isCount(index.entryCount)||!Array.isArray(index.entryFiles)||!Array.isArray(index.kinds)||
|
|
92
|
+
!index.shards||typeof index.shards!=="object"||(index.entryCount>0)!==(index.entryFiles.length>0)||
|
|
93
|
+
index.kinds.some((kind)=>typeof kind!=="string")||index.entryFiles.some((first,i)=>
|
|
94
|
+
!isCount(first)||first>=index.entryCount||(i?first<=index.entryFiles[i-1]:first))){throw badIndex("index")}
|
|
95
|
+
if(index.entryPacks===undefined&&index.shardPacks===undefined)return;const keys=Object.keys(index.shards).sort();if(!Array.isArray(index.entryPacks)||!Array.isArray(index.shardPacks)||
|
|
96
|
+
(index.entryFiles.length>0)!==(index.entryPacks.length>0)||(keys.length>0)!==(index.shardPacks.length>0)||
|
|
97
|
+
index.entryPacks.some((first,i)=>!isCount(first)||first>=index.entryFiles.length||(i?first<=index.entryPacks[i-1]:first))||
|
|
98
|
+
index.shardPacks.some((key,i)=>typeof key!=="string"||!Object.hasOwn(index.shards,key)||(i?key<=index.shardPacks[i-1]:key!==keys[0]))){throw badIndex("index packs")}}
|
|
99
|
+
function resolveShards(shards,keys,word){const bases=keys.filter((key)=>!/\.\d+$/.test(key));
|
|
100
|
+
const parts=(selected)=>selected.flatMap((key)=>[key].concat(keys.filter((part)=>part.startsWith(key+"."))
|
|
101
|
+
.sort((a,b)=>Number(a.slice(a.lastIndexOf(".")+ 1))- Number(b.slice(b.lastIndexOf(".")+ 1)))));for(let length=Math.min(2,word.length);length<=word.length;length++){const key=shardKey(word.slice(0,length));if(Object.hasOwn(shards,key))return parts([key]);const deeper=bases.filter((k)=>k.length>key.length&&k.startsWith(key));if(!deeper.length)return[];if(length===word.length)return parts(deeper)}
|
|
102
|
+
return[]}
|
|
103
|
+
function recentCache(limit,loadDoc,maxBytes=Infinity,bytesOf=()=>0){const cache=new Map();let bytes=0;const get=(key)=>{let entry=cache.get(key);if(entry){cache.delete(key)}else{entry={pending:loadDoc(key),ready:false,bytes:0};entry.pending.then(doc=>{if(cache.get(key)!==entry)return;entry.ready=true;entry.bytes=bytesOf(doc);bytes +=entry.bytes;for(const[oldKey,old]of cache){if(cache.size<=limit&&bytes<=maxBytes)break;if(old.ready){cache.delete(oldKey);bytes -=old.bytes}}},()=>{if(cache.get(key)===entry)cache.delete(key)})}
|
|
104
|
+
cache.set(key,entry);return entry.pending};get.clear=()=>{cache.clear();bytes=0};return get}
|
|
105
|
+
const packBytes=pages=>pages.reduce((bytes,text)=>bytes +(typeof text==="string"?text.length*2:0),0);function checkShard(doc,key,count,entryCount){checkDocument(doc,"searchShard");if(doc.key!==key)throw badIndex("shard key");if(!Array.isArray(doc.words)||!Array.isArray(doc.postings)||doc.words.length!==doc.postings.length)throw badIndex("shard");let total=0;doc.postings.forEach((list,i)=>{if(typeof doc.words[i]!=="string"||!Array.isArray(list)||!isCount(list[0]))throw badIndex("shard");let last=list[0];for(let j=1;j<list.length;j++){if(!Number.isInteger(list[j])||!list[j])throw badIndex("shard");last +=Math.abs(list[j]);total +=list[j]<0?-list[j]- 1:0}
|
|
106
|
+
total +=list.length;if(last>=entryCount)throw badIndex("shard")});if(total!==count)throw badIndex("shard posting count");return doc}
|
|
107
|
+
function mergePostings(lists){const out=[];let ascending=true;for(const list of lists){let value=0;list.forEach((step,i)=>{for(let n=i&&step<0?-step:1;n>0;n--){value=i?value +(step<0?1:step):step;if(out.length&&value<=out[out.length-1])ascending=false;out.push(value)}})}
|
|
108
|
+
if(ascending)return out;out.sort((a,b)=>a-b);return out.filter((value,i)=>!i||value!==out[i-1])}
|
|
109
|
+
function intersectPostings(a,b){let j=0;return a.filter((value)=>{while(j<b.length&&b[j]<value)j++;return b[j]===value})}
|
|
110
|
+
function labelToken(folded){let hash=2166136261;for(let i=0;i<folded.length;i++){hash=Math.imul(hash^folded.charCodeAt(i),16777619)}
|
|
111
|
+
return "\u0000" +("0000000" +(hash>>>0).toString(16)).slice(-8)}
|
|
112
|
+
function entryReference(value){return value===undefined||value===null||isIdentifier(value)}
|
|
113
|
+
function entryFile(doc,first,count){checkDocument(doc,"searchEntries");if(doc.first!==first||!Array.isArray(doc.stem)||!Array.isArray(doc.entries)||doc.entries.length!==count)throw badIndex("entry file");const stem=new Map(doc.stem.map((item)=>(Array.isArray(item)?[item[0],item.slice(1)]:[])));const records=new Map();const raw=(n)=>{const item=n>=first&&n<first+count?doc.entries[n-first]:stem.get(n);if(!Array.isArray(item)||item.length<5||item.length>8||!Number.isInteger(item[0])||item[0]<-1||item[0]>=n||
|
|
114
|
+
!isCount(item[1])||typeof item[2]!=="string"||!isCount(item[3])||typeof item[4]!=="string"||
|
|
115
|
+
(item[5]!=null&&!isCount(item[5]))||!entryReference(item[6])||!(item[7]===undefined||isIdentifier(item[7]))){throw badIndex("entry "+n)}
|
|
116
|
+
return item};return(n)=>{const pending=[];for(let k=n;k>=0&&!records.has(k);k=raw(k)[0])pending.push(k);while(pending.length){const number=pending.pop();const item=raw(number);const parent=item[0]<0?null:records.get(item[0]);const base=parent?parent.id:"";if(item[1]+ item[3]>base.length)throw badIndex("entry "+number);records.set(number,{parent,id:base.slice(0,item[1])+ item[2]+ base.slice(base.length-item[3]),label:item[4],kind:item[5]==null?undefined:item[5],ref:item[6]||null,at:item[7]||null})}
|
|
117
|
+
return records.get(n)}}
|
|
118
|
+
function entryHit(record,kinds){const path=[];for(let r=record;r;r=r.parent)path.unshift(r.id);const hit={path,label:record.label};if(record.kind!==undefined){if(record.kind>=kinds.length)throw badIndex("entry kind");hit.kind=kinds[record.kind]}
|
|
119
|
+
const parent=record.parent;if(parent)hit.context=parent.parent?parent.parent.label+" / "+parent.label:parent.label;validateSearchHit(hit);return hit}
|
|
120
|
+
function entryFileOf(starts,n){let low=0;let high=starts.length-1;while(low<high){const mid=(low+high+1)>>1;if(starts[mid]<=n)low=mid;else high=mid-1}
|
|
121
|
+
return low}
|
|
122
|
+
function sidecarSearch(load,manifest,hints){const index=async()=>(await manifest()).search;const loadPack=recentCache(PACK_CACHE,async(number)=>{const doc=checkDocument(await load("q/"+number),"pack");if(!Array.isArray(doc.pages)||doc.pages.some(text=>typeof text!=="string"))throw badIndex("pack");return doc.pages},PACK_CACHE_BYTES,packBytes);const sortedKeys=new WeakMap();const packed=async(pack,slot)=>{const text=(await loadPack(pack))[slot];if(typeof text!=="string")throw badIndex("pack");return JSON.parse(text)};const loadIndexDoc=async(search,kind,name)=>{if(!search.entryPacks)return load(kind==="e"?"e/"+name:"s/k-"+name);if(kind==="e"){const pack=entryFileOf(search.entryPacks,name);return packed(pack,name-search.entryPacks[pack])}
|
|
123
|
+
let keys=sortedKeys.get(search);if(!keys)sortedKeys.set(search,keys=Object.keys(search.shards).sort());let pack=0;while(pack+1<search.shardPacks.length&&search.shardPacks[pack+1]<=name)pack++;return packed(search.entryPacks.length+pack,keys.indexOf(name)- keys.indexOf(search.shardPacks[pack]))};const loadShard=recentCache(SHARD_CACHE,async(key)=>{const search=await index();return checkShard(await loadIndexDoc(search,"s",key),key,search.shards[key],search.entryCount)});const loadEntries=recentCache(ENTRY_CACHE,async(file)=>{const search=await index();const first=search.entryFiles[file];const end=file+1<search.entryFiles.length?search.entryFiles[file+1]:search.entryCount;return entryFile(await loadIndexDoc(search,"e",file),first,end-first)});async function postingsOf(search,keys,word){const lists=[];for(const shard of await Promise.all(resolveShards(search.shards,keys,word).map(loadShard))){shard.words.forEach((indexed,i)=>indexed.startsWith(word)&&lists.push(shard.postings[i]))}
|
|
124
|
+
return mergePostings(lists)}
|
|
125
|
+
async function run(query,queryWords,limit,sequence){const search=await index();if(!search){return{hits:[],total:0,truncated:false}}
|
|
126
|
+
const keyWords=queryWords.filter((word,i)=>queryWords.indexOf(word)===i&&
|
|
127
|
+
!queryWords.some((other)=>other.length>word.length&&other.startsWith(word)));const keys=Object.keys(search.shards);const folded=trimSeparators(foldText(query));const[exact,lists]=await Promise.all([postingsOf(search,keys,labelToken(folded)),Promise.all(keyWords.map((word)=>postingsOf(search,keys,word)))]);const candidates=lists.sort((a,b)=>a.length-b.length).reduce(intersectPostings);const order=exact.concat(candidates);const wanted=limit*SEARCH_CANDIDATES;const taken=new Set();const hits=[];const records=new Map();let next=0;while(next<order.length&&hits.length<limit){const files=[];let end=next;for(;end<order.length&&hits.length+end-next<wanted;end++){const file=entryFileOf(search.entryFiles,order[end]);if(files.indexOf(file)<0){if(files.length>=ENTRY_BATCH&&hits.length+end-next>=limit)break;files.push(file)}}
|
|
128
|
+
const loaded=await Promise.all(files.map(loadEntries));for(;next<end;next++){const number=order[next];if(taken.has(number))continue;const record=loaded[files.indexOf(entryFileOf(search.entryFiles,number))](number);if(next>=exact.length||trimSeparators(foldText(record.label))===folded){taken.add(number);const hit=entryHit(record,search.kinds);records.set(hit,record);hits.push(hit)}}}
|
|
129
|
+
const ranked=rankHits(hits,query,limit);ranked.total=candidates.length;ranked.truncated=candidates.length>ranked.hits.length;if(sequence===latest){hints.clear();ranked.hits.forEach((hit)=>{for(let r=records.get(hit);r;r=r.parent)hints.set(r.id,r)})}
|
|
130
|
+
return ranked}
|
|
131
|
+
let latest=0;return(query,limit)=>{const queryWords=words(query);if(!queryWords.length){return Promise.resolve({hits:[],total:0,truncated:false})}
|
|
132
|
+
if(!queryWords.some((word)=>word.length>=2)){const error=new Error("CanopyX: sidecar search needs a word of at least two UTF-16 units.");error.code="too-short";return Promise.reject(error)}
|
|
133
|
+
return run(query,queryWords,limit,++latest).catch((err)=>{loadShard.clear();loadEntries.clear();loadPack.clear();throw err})}}
|
|
134
|
+
function slicePage(all,cursor,pageSize){const offset=cursor?Number(cursor)||0:0;const items=all.slice(offset,offset+pageSize);const end=offset+items.length;return{items,total:all.length,next:end<all.length?String(end):null,offset}}
|
|
135
|
+
function rankHits(hits,query,limit){const folded=trimSeparators(foldText(query));const ranked=hits.map((hit)=>({hit,rank:rankLabel(hit.label,folded)}));ranked.sort((a,b)=>a.rank-b.rank||collator.compare(a.hit.label,b.hit.label));return{hits:ranked.slice(0,limit).map((item)=>item.hit),total:hits.length,truncated:hits.length>limit}}
|
|
136
|
+
function inlineSource(view){const pageSize=view.pageSize||100;const labelWords=new WeakMap();const wordsOf=(node)=>{let cached=labelWords.get(node);if(!cached){cached=words(node.label);labelWords.set(node,cached)}
|
|
137
|
+
return cached};return{kind:"inline",canSearch:true,page(node,cursor){return Promise.resolve(slicePage(node?node.children||[]:view.roots||[],cursor,pageSize))},search(query,limit){const queryWords=words(query);const hits=[];const kinds=view.search&&Array.isArray(view.search.kinds)&&view.search.kinds.length?view.search.kinds:null;if(queryWords.length){const stack=(view.roots||[]).map((node)=>({node,parent:null})).reverse();while(stack.length){const item=stack.pop();if((!kinds||kinds.indexOf(item.node.kind)>=0)&&matchesWords(wordsOf(item.node),queryWords)){const path=[];const labels=[];for(let entry=item;entry;entry=entry.parent){path.push(entry.node.id);if(entry!==item)labels.push(entry.node.label)}
|
|
138
|
+
const hit={path:path.reverse(),label:item.node.label,context:labels.reverse().join(" / ")};if(item.node.kind!=null)hit.kind=item.node.kind;hits.push(hit)}
|
|
139
|
+
const children=item.node.children||[];for(let i=children.length-1;i>=0;i--)stack.push({node:children[i],parent:item})}}
|
|
140
|
+
return Promise.resolve(rankHits(hits,query,limit))}}}
|
|
141
|
+
function sidecarSource(view){const supplied=view.source.base;if(typeof supplied!=="string"||!supplied||/[?#]/.test(supplied)) throw new Error("CanopyX: sidecar base must be a folder path without a query or fragment.");
|
|
142
|
+
const base=supplied.endsWith("/")?supplied:supplied+"/";const load=(ref)=>loadSidecar(base,view.id,ref);let manifestPromise=null;const manifest=()=>manifestPromise||(manifestPromise=load("manifest").then((doc)=>{checkDocument(doc,"manifest");if(doc.view!==view.id)throw new Error("CanopyX: manifest view does not match the requested view.");if(doc.search!==undefined)checkSearchIndex(doc.search);if(doc.packs!==undefined&&!isCount(doc.packs))throw new Error("CanopyX: manifest packs must be a count.");return doc}).catch((err)=>{manifestPromise=null;throw err}));const loadPack=recentCache(PACK_CACHE,async(number)=>{const doc=checkDocument(await load("p/"+number),"pack");if(!Array.isArray(doc.pages)||doc.pages.some(text=>typeof text!=="string"))throw new Error("CanopyX: pack pages must be an array of strings.");return doc.pages},PACK_CACHE_BYTES,packBytes);const pinned=new Map();let releaseLoose;const packOf=async(ref)=>{const packed=/^p\/(0|[1-9]\d*)#(0|[1-9]\d*)$/.exec(ref);
|
|
143
|
+
return packed&&(await manifest()).packs!==undefined?packed:null};const loadPage=async(ref)=>{const packed=await packOf(ref);if(!packed){const entry=pinned.get("file:"+ref);return entry?entry.pending:load(ref)}
|
|
144
|
+
const entry=pinned.get("pack:"+packed[1]);const text=Number(packed[1])<(await manifest()).packs?(await(entry?entry.pending:loadPack(packed[1])))[Number(packed[2])]:undefined;if(typeof text!=="string")throw new Error("CanopyX: the bundle has no packed page "+ref+".");return JSON.parse(text)};const hints=new Map();return{kind:"sidecar",canSearch:true,manifest,async prefetch(opens,finds,request){const held=new Set(),signal=request&&request.signal;let live=true;const release=()=>{if(!live)return;live=false;if(signal)signal.removeEventListener("abort",release);for(const entry of held)if(!--entry.users&&pinned.get(entry.key)===entry)pinned.delete(entry.key);held.clear()};if(signal){if(signal.aborted){release();return release}
|
|
145
|
+
signal.addEventListener("abort",release,{once:true})}else{if(releaseLoose)releaseLoose();releaseLoose=release}
|
|
146
|
+
const refs=new Set();opens.forEach((id)=>{const hint=hints.get(id);if(hint&&hint.ref)refs.add(hint.ref)});finds.forEach((id)=>{const hint=hints.get(id);if(hint&&hint.at)refs.add(hint.at)});await Promise.all(Array.from(refs,async(ref)=>{const packed=await packOf(ref);if(!live)return;const key=packed?"pack:"+packed[1]:"file:"+ref;let entry=pinned.get(key);if(!entry){entry={key,users:0,pending:packed?loadPack(packed[1]):load(ref)};pinned.set(key,entry);entry.pending.catch(()=>{if(pinned.get(key)===entry)pinned.delete(key)})}
|
|
147
|
+
if(!held.has(entry)){held.add(entry);entry.users++}
|
|
148
|
+
await entry.pending}).map((pending)=>pending.catch(()=>{})));return release},async page(node,cursor){const ref=cursor||(node?node.ref||node.id:(await manifest()).root);if(!ref){throw new Error("Node '"+node.id+"' has no sidecar reference for its children.")}
|
|
149
|
+
const doc=checkPage(await loadPage(ref),node);return{items:doc.items,total:doc.total,next:doc.next||null,offset:doc.offset||0}},search:sidecarSearch(load,manifest,hints)}}
|
|
150
|
+
function remoteSource(view,options){const source=view.source;if(source.queryAware!==undefined&&typeof source.queryAware!=="boolean")throw new Error("CanopyX: queryAware must be a boolean.");if(source.queryAware)for(const name of["children","search","counts"]){if(source[name]!=null&&!/^[^#]*\{viewQuery\}/.test(source[name])) throw new Error("CanopyX: query-aware " + name + " needs {viewQuery} before its fragment.");
|
|
151
|
+
}
|
|
152
|
+
const settings=options||{};const timeoutMs=settings.timeoutMs===undefined?30000:settings.timeoutMs;if(!Number.isInteger(timeoutMs)||timeoutMs<1||timeoutMs>2147483647){throw new Error("CanopyX: request timeout must be an integer from 1 to 2147483647 milliseconds.")}
|
|
153
|
+
const fill=(template,values)=>template.replace(/\{(\w+)\}/g, (match, name) => (Object.hasOwn(values, name) ? values[name] : match));
|
|
154
|
+
const getJson=(url,type,request)=>remoteJson(url,type,timeoutMs,settings.signal,request&&request.signal);const queryValues=request=>{if(!source.queryAware)return{};if(!request||!request.query||request.query.version!==1)throw new Error("CanopyX: a query-aware request needs a view query.");return{viewQuery:encodeURIComponent(JSON.stringify(request.query)),revision:encodeURIComponent(request.revision||"")}};return{kind:"remote",queryAware:source.queryAware===true,canSearch:!!source.search,async page(node,cursor,request){const doc=checkPage(await getJson(fill(source.children,Object.assign(queryValues(request),{ref:node?encodeURIComponent(node.ref||node.id):"",cursor:cursor?encodeURIComponent(cursor):""})),"page",request),node);return{items:doc.items,total:doc.total,next:doc.next||null,offset:doc.offset||0,revision:doc.revision}},async search(query,limit,request){if(!source.search){return{hits:[],total:0,truncated:false,revision:request&&request.revision}}
|
|
155
|
+
const doc=await getJson(fill(source.search,Object.assign(queryValues(request),{query:encodeURIComponent(query),limit:String(limit)})),"search",request);if(doc.query!==query)throw new Error("CanopyX: search response query does not match the request.");if(doc.total!=null&&(doc.total<doc.hits.length||doc.truncated!==(doc.total>doc.hits.length))){throw new Error("CanopyX: search total and truncated flag disagree with the returned hits.")}
|
|
156
|
+
return{hits:doc.hits,total:doc.total,truncated:doc.truncated,revision:doc.revision}},counts:source.counts?async(ids,request)=>{const items=[];let revision=request&&request.revision;for(let i=0;i<ids.length;i +=COUNTS_BATCH){const batch=ids.slice(i,i+COUNTS_BATCH).map((id)=>encodeURIComponent(encodeURIComponent(id))).join(",");const doc=await getJson(fill(source.counts,Object.assign(queryValues(request),{ids:batch})),"counts",request);if(source.queryAware)revision=snapshotRevision(doc,revision);items.push.apply(items,doc.items)}
|
|
157
|
+
return Object.assign({protocol:PROTOCOL,type:"counts",items},source.queryAware?{revision}:{})}:null}}
|
|
158
|
+
function sourceFor(view,options){const kind=view.source&&view.source.kind;if(kind==="sidecar"){return sidecarSource(view)}
|
|
159
|
+
if(kind==="remote"){return remoteSource(view,options)}
|
|
160
|
+
if(kind==="inline"){if(!Array.isArray(view.roots))throw new Error("CanopyX: inline view roots must be an array.");return inlineSource(view)}
|
|
161
|
+
throw new Error("CanopyX: unknown source kind '"+kind+"'.")}
|
|
162
|
+
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)}
|
|
163
|
+
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))}}
|
|
164
|
+
class TreeGrid{constructor(host,options){const view=checkDocument(options.view,"view");requireIdentifier(view.id,"view ID");this.host=host;this.view=view;this.opts=options;for(const name of["isSelectable","beforeSelect"]){if(options[name]!==undefined&&typeof options[name]!=="function")throw new Error("CanopyX: "+name+" must be a function.")}
|
|
165
|
+
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.local=view.timeZone==="local";this.fmt=formatting(this.lang,this.local,this.labels);const renderers=hostRenderers(options.renderers||{},view.columns);if(!options.formatLink!==!options.parseLink){throw new Error("CanopyX: formatLink and parseLink define one link scheme and are given together.")}
|
|
166
|
+
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.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||[]}
|
|
167
|
+
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.build();this.buildToolbar();this.ready=this.init()}
|
|
168
|
+
async search(query,limit){if(this.destroyed)throw new DOMException("Grid was destroyed.","AbortError");const navigation=navigationScope(this,this.dataLifetime.signal);try{const result=await navigation.wait(this.source.search(query,limit,this.sourceRequest(this.dataLifetime.signal)));if(navigation.cancelled())throw new DOMException("Search snapshot was retired.","AbortError");if(this.queryAware)snapshotRevision(result,this.datasetRevision);return validateSearchResult(result)}finally{navigation.dispose()}}
|
|
169
|
+
buildIcons(icons){const templates=new Map();Object.keys(icons).forEach((key)=>{const holder=h("span","cx-icon");holder.setAttribute("aria-hidden","true");holder.innerHTML=icons[key];// trusted host SVG, declared once per view
|
|
170
|
+
templates.set(key,holder)});return templates}
|
|
171
|
+
async init(){try{this.rootsReady=this.loadRoots();await this.rootsReady;await this.restoreState();await this.expandInitial(this.root);if(!this.linkChanged||!this.linkChanged.aborted)await this.openDeepLink()}finally{this.initializing=false}
|
|
172
|
+
if(this.destroyed)return;await this.flushPendingToggles();if(this.linkChanged&&!this.linkChanged.aborted)await this.openDeepLink();if(!this.destroyed){this.host.setAttribute("data-cx-ready","true");this.emit("ready",{});this.scheduleRender()}}
|
|
173
|
+
loadRoots(){if(!this.root.inlineItems){return this.loadPage(this.root,null)}
|
|
174
|
+
validateNodes(this.root.inlineItems,this.keyMap,true);this.root.inline=true;const first=this.inlinePage(this.root,null);this.root.children=first.items.map((node,i)=>this.wrap(node,this.root,first.positions[i]));this.root.total=first.total;this.root.next=first.next;this.applySort(this.root,false);this.relayout();return Promise.resolve()}
|
|
175
|
+
expandable(w){return!!(w&&w.h&&!w.h.detail&&(w.h.hasChildren||(w.children&&w.children.length)||(w.inline&&w.h.children.length)))}
|
|
176
|
+
statusRow(parent,kind){kind=parent.loading?"loading":parent.error?"error":kind;let row=parent._status;if(!row){row=parent._status={key:null,status:kind,parent,level:parent.level+1,hpx:this.rowH,h:{id:"",label:""},_v:0}}
|
|
177
|
+
row.status=kind;const format=numberFormat(this.lang);const loaded=format.format(parent.children?parent.children.length:0);const labels=this.labels;row.h.label=kind==="loading"?labels.loading:kind==="error"?fill(labels.loadError,{error:parent.error}):
|
|
178
|
+
parent.total!=null?fill(labels.showMore,{loaded,total:format.format(parent.total)}):fill(labels.showMoreUnknownTotal,{loaded});row._v=0;return row}
|
|
179
|
+
cellOf(node,column,index){const cells=node.cells;if(cells&&Object.hasOwn(cells,column.id)){return cells[column.id]}
|
|
180
|
+
return index===0?node.label:undefined}
|
|
181
|
+
emit(name,detail){if(this.destroyed)return;this.host.dispatchEvent(new CustomEvent("canopyx:"+name,{detail,bubbles:true}))}}
|
|
182
|
+
function nodeContent(node){return JSON.stringify([node.label,node.kind,node.icon,node.cells,node.badges,node.flags,node.detail,node.data])}
|
|
183
|
+
Object.assign(TreeGrid.prototype,{async expandInitial(parent){if(this.restoring)return;const generation=this.restoreGeneration||0;const pending=(parent.children||[]).slice().reverse();while(pending.length&&!this.destroyed&&!this.restoring&&generation===(this.restoreGeneration||0)){const w=pending.pop();if(w.autoExpand&&!w.children){w.autoExpand=false;await this.toggle(w,true,true)}
|
|
184
|
+
if(w.expanded&&w.children){for(let i=w.children.length-1;i>=0;i--)pending.push(w.children[i])}}},createWrapper(data,owner,position){requireIdentifier(data.id,"node ID");if(data.ref!=null)requireIdentifier(data.ref,"node reference");const w={key:data.id,h:data,parent:owner,level:owner.level+1,idx:position,expanded:false,children:null,total:null,next:null,inline:false,loading:false,error:null,hpx:data.detail?this.detailHeight(data.detail):this.rowH,auto:!!data.detail&&!(data.detail.height>0),_v:0,content:nodeContent(data)};this.keyMap.set(data.id,w);return w},wrap(node,parent,index,suppressExpansion){const initialExpansion=!suppressExpansion&&!this.restoring;const root=this.createWrapper(node,parent,index);const pending=[root];while(pending.length){const w=pending.pop();const data=w.h;if(Array.isArray(data.children)){w.inline=true;const first=this.inlinePage(w,null);w.children=first.items.map((child,i)=>this.createWrapper(child,w,first.positions[i]));w.total=first.total;w.next=first.next;w.expanded=!!data.expanded&&initialExpansion&&w.children.length>0;for(let i=w.children.length-1;i>=0;i--)pending.push(w.children[i]);this.applySort(w,false);this.rememberInactive(w)}else if(data.hasChildren){w.total=typeof data.childCount==="number"?data.childCount:null;w.autoExpand=!!data.expanded&&initialExpansion}}
|
|
185
|
+
return root}});const LAYOUT_HOLD_MS=250;Object.assign(TreeGrid.prototype,{matchState(w){const pending=[{node:w,visited:false}];while(pending.length){const entry=pending.pop();const node=entry.node;if(node._fm!==undefined)continue;if(entry.visited){node._fm=(node.children||[]).some((child)=>child._fm>0)?1:0;continue}
|
|
186
|
+
if(this.filter.match(node.h))node._fm=2;else if(this.filter.keep&&this.filter.keep(node.h))node._fm=1;else{pending.push({node,visited:true});for(const child of node.children||[]){if(child._fm===undefined)pending.push({node:child,visited:false})}}}
|
|
187
|
+
return w._fm},relayout(now){if(this.destroyed)return;if(this.layoutHolds&&!this.layoutLate&&!now){this.layoutDirty=true;return}
|
|
188
|
+
this.layoutDirty=false;const previousFocus=this.focused;const previousIndex=this.indexOf(previousFocus);const previousElement=this.rendered.get(previousFocus);const ownedFocus=previousElement&&previousElement.contains(document.activeElement);const rows=[];const filter=this.filter;if(filter){this.keyMap.forEach((w)=>{w._fm=undefined})}
|
|
189
|
+
const pending=[{parent:this.root,filtered:!!filter,index:0,pos:0}];while(pending.length){const frame=pending[pending.length-1];const children=frame.parent.children||[];if(frame.index<children.length){const w=children[frame.index++];const state=frame.filtered?this.matchState(w):2;if(!state)continue;const filterChildren=frame.filtered&&state===1;w.pos=++frame.pos;rows.push(w);const autoOpen=filterChildren&&this.filterExpand&&!w.filterCollapsed&&w.children&&w.children.length;w._open=!!((w.expanded&&(w.children||w.loading||w.error))||autoOpen);if(w._open)pending.push({parent:w,filtered:filterChildren,index:0,pos:0})}else{const parent=frame.parent;parent.visibleCount=frame.pos;parent.childrenFiltered=frame.filtered;if(parent.loading)rows.push(this.statusRow(parent,"loading"));else if(parent.error)rows.push(this.statusRow(parent,"error"));else if(parent.next)rows.push(this.statusRow(parent,"more"));pending.pop()}}
|
|
190
|
+
this.rows=rows;this.retop();this.grid.setAttribute("aria-rowcount",rows.some((row)=>row.status)?"-1":String(rows.length+1));if(this.focused&&this.indexOf(this.focused)<0&&this.focused!==this.focusHandoff){let target=this.focused.parent;while(target&&this.indexOf(target)<0)target=target.parent;target=target||rows[Math.min(Math.max(0,previousIndex),rows.length-1)];this.focused=null;if(target)this.setFocus(target,!!ownedFocus);else if(ownedFocus){this.grid.tabIndex=0;this.grid.focus({preventScroll:true})}}
|
|
191
|
+
this.grid.tabIndex=rows.length?-1:0;this.scheduleRender()},holdLayout(){if(!this.layoutHolds){this.layoutTimer=setTimeout(()=>{this.layoutTimer=0;this.layoutLate=true;this.flushLayout()},LAYOUT_HOLD_MS)}
|
|
192
|
+
this.layoutHolds=(this.layoutHolds||0)+ 1;let held=true;return()=>{if(!held)return false;held=false;if(--this.layoutHolds)return false;clearTimeout(this.layoutTimer);this.layoutTimer=0;this.layoutLate=false;return this.flushLayout()}},flushLayout(){if(!this.layoutDirty)return false;this.relayout(true);return true},retop(){const top=this.logicalTop();const rows=this.rows;const tops=new Array(rows.length+1);let y=0;for(let i=0;i<rows.length;i++){rows[i]._ri=i;tops[i]=y;y +=rows[i].hpx}
|
|
193
|
+
tops[rows.length]=y;this.tops=tops;this.total=y;this.syncScrollRange(top)},applySort(parent,deep){if(this.queryAware)return;const pending=[parent];while(pending.length){const owner=pending.pop();let children=owner.children;if(!children)continue;if(owner.inline){const first=this.inlinePage(owner,null,children.length||this.pageSize);if(first.items.some((node,index)=>!children[index]||children[index].key!==node.id)||first.items.length!==children.length){this.retirePage(owner);this.reconcileList(owner,first.items,first.total,first.next,first.positions,true);children=owner.children}}
|
|
194
|
+
const state=this.sortState;const column=state&&this.columns.find((c)=>c.id===state.column);if(column){const direction=state.direction==="desc"?-1:1;const index=this.columns.indexOf(column);const keys=new Map();const keyOf=(w)=>{let key=keys.get(w);if(!key){key=sortKey(this.cellOf(w.h,column,index),column);keys.set(w,key)}
|
|
195
|
+
return key};children.sort((a,b)=>compareKeys(keyOf(a),keyOf(b),direction)||a.idx-b.idx)}else{children.sort((a,b)=>a.idx-b.idx)}
|
|
196
|
+
if(deep){const all=this.modelChildren(owner);for(let i=all.length-1;i>=0;i--)pending.push(all[i])}}
|
|
197
|
+
this.trimInlineCache()}});function isIdentifier(value){return typeof value==="string"&&/^(?:[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])+$/.test(value);
|
|
198
|
+
}
|
|
199
|
+
function requireIdentifier(value,name){if(!isIdentifier(value)){throw new Error("CanopyX: "+name+" must be a nonempty, well-formed Unicode identifier")}}
|
|
200
|
+
function validateNodes(nodes,existing,inlineOnly=false){if(!Array.isArray(nodes))throw new Error("CanopyX: page items must be an array.");const seen=new Set();const pending=nodes.slice();while(pending.length){const node=pending.pop();requireIdentifier(node&&node.id,"node ID");if(typeof node.label!=="string")throw new Error("CanopyX: node label must be a string.");if(Object.hasOwn(node,"flags"))requireFlags(node.flags,"node flags");if(Object.hasOwn(node,"data")&&(!node.data||typeof node.data!=="object"||Array.isArray(node.data)||
|
|
201
|
+
Object.keys(node.data).some((name)=>!name||name==="__proto__"||typeof node.data[name]!=="string"))){throw new Error("CanopyX: node data must be an object of strings with nonempty names.")}
|
|
202
|
+
if(existing.has(node.id)||seen.has(node.id))throw new Error("CanopyX: duplicate node ID "+node.id);seen.add(node.id);if(node.ref!=null)requireIdentifier(node.ref,"node reference");if(Object.hasOwn(node,"children")&&!Array.isArray(node.children)){throw new Error("CanopyX: node children must be an array.")}
|
|
203
|
+
if(inlineOnly&&(node.ref!=null||node.childCount!=null&&!Array.isArray(node.children)||
|
|
204
|
+
node.hasChildren===true&&(!Array.isArray(node.children)||!node.children.length))){throw new Error("CanopyX: inline views cannot declare lazy children.")}
|
|
205
|
+
if(node.detail!=null&&(Object.hasOwn(node,"children")||
|
|
206
|
+
node.hasChildren===true||node.ref!=null||node.childCount!=null)){throw new Error("CanopyX: detail row must be a leaf.")}
|
|
207
|
+
if(Object.hasOwn(node,"childCount")&&
|
|
208
|
+
(!validStructuralCount(node.childCount)||node.childCount<1)){throw new Error("CanopyX: node childCount must be a positive 32-bit integer.")}
|
|
209
|
+
if(!Array.isArray(node.children)&&(node.ref!=null||node.childCount!=null)&&node.hasChildren!==true){throw new Error("CanopyX: lazy node reference or childCount requires hasChildren true.")}
|
|
210
|
+
if(Array.isArray(node.children)){if(node.ref!=null)throw new Error("CanopyX: inline children cannot have a load reference.");if(node.hasChildren===true&&!node.children.length)throw new Error("CanopyX: expandable inline children cannot be empty.");if(node.hasChildren===false&&node.children.length)throw new Error("CanopyX: inline children cannot declare a leaf.");if(node.childCount!=null&&node.childCount!==node.children.length)throw new Error("CanopyX: inline childCount must equal children length.");for(const child of node.children)pending.push(child)}}}
|
|
211
|
+
function validatePage(page,existing,expectedOffset){if(!page||!Array.isArray(page.items))throw new Error("CanopyX: page items must be an array.");const items=page.items;if(page.revision!==undefined)requireIdentifier(page.revision,"dataset revision");if(Object.hasOwn(page,"next")&&page.next!==null){requireIdentifier(page.next,"page continuation")}
|
|
212
|
+
if(page.offset!==undefined&&(!Number.isInteger(page.offset)||page.offset!==expectedOffset)){throw new Error("CanopyX: page offset does not match the loaded child count.")}
|
|
213
|
+
if(page.total!=null&&(!validStructuralCount(page.total)||page.total<expectedOffset+items.length)){throw new Error("CanopyX: page total is smaller than its loaded range.")}
|
|
214
|
+
if(page.total!=null&&(page.next!=null)!==(expectedOffset+items.length<page.total)){throw new Error("CanopyX: page continuation disagrees with its total.")}
|
|
215
|
+
validateNodes(items,existing);return items}
|
|
216
|
+
Object.assign(TreeGrid.prototype,{modelChildren(w){const children=w.children||[];return w.inlineCache&&w.inlineCache.size?children.concat(Array.from(w.inlineCache.values())):children},inlinePage(w,cursor,count,supplied){const source=supplied||(w===this.root?w.inlineItems:w.h.children);let order=w.inlineOrder;const version=this.inlineOrderVersion||0,generation=w.dataGeneration||0;if(!order||order.source!==source||order.version!==version||order.generation!==generation){const indices=Uint32Array.from({length:source.length},(_,index)=>index);const state=!this.queryAware&&this.sortState,column=state&&this.columns.find(c=>c.id===state.column);if(column){const columnIndex=this.columns.indexOf(column),direction=state.direction==="desc"?-1:1;const keys=source.map(node=>sortKey(this.cellOf(node,column,columnIndex),column));indices.sort((a,b)=>compareKeys(keys[a],keys[b],direction)||a-b)}
|
|
217
|
+
order=w.inlineOrder={source,indices,version,generation}}
|
|
218
|
+
const offset=cursor?Number(cursor):0,end=Math.min(source.length,offset +(count||this.pageSize));const positions=Array.from(order.indices.subarray(offset,end));return{items:positions.map(index=>source[index]),positions,total:source.length,offset,next:end<source.length?String(end):null}},inlineReplacement(owner,items,positions){const source=owner===this.root?owner.inlineItems:owner.h.children;const prior=new Map(this.modelChildren(owner).map(w=>[w.key,w]));const attached=new Set(items.map(node=>node.id)),cached=[],extraPositions=[];source.forEach((data,index)=>{if(prior.has(data.id)&&!attached.has(data.id)){cached.push(data);extraPositions.push(index)}});return{items:items.concat(cached),positions:(positions||items.map((_,index)=>index)).concat(extraPositions),attached:items.length}},retainInline(w,cached){const retained=this.inlineRetained||(this.inlineRetained=new Map());w._cached=cached;if(cached){retained.delete(w.key);retained.set(w.key,w)}
|
|
219
|
+
else retained.delete(w.key)},trimInlineCache(){const retained=this.inlineRetained;if(!retained||!retained.size)return;const groups=[];let size=0;for(const w of retained.values()){if(w._invalidated||!w._cached){retained.delete(w.key);continue}
|
|
220
|
+
let ancestor=w.parent;while(ancestor&&!ancestor._cached)ancestor=ancestor.parent;if(ancestor)continue;const nodes=[w].concat(Array.from(this.descendants(w).values()));size +=nodes.length;groups.push({w,nodes})}
|
|
221
|
+
for(const group of groups){if(size<=this.retainedNodeLimit)break;if(group.nodes.some(w=>this.cachePinned(w)))continue;group.w.parent.inlineCache.delete(group.w.key);for(const w of group.nodes)this.retireWrapper(w);size -=group.nodes.length}}});Object.assign(TreeGrid.prototype,{cachePinned(w){const focus=this.focused;const filter=this.filter;return w.expanded||w.autoExpand||w.loading||w.reloadController||this.selected.has(w)||
|
|
222
|
+
w===(focus&&focus.status?focus.parent:focus)||w===this.anchor||
|
|
223
|
+
this.pendingRestorationToggles&&this.pendingRestorationToggles.has(w)||
|
|
224
|
+
filter&&(filter.match(w.h)||filter.keep&&filter.keep(w.h))},rememberInactive(w){if(this.destroyed||w._invalidated)return;const cache=this.inactiveCache||(this.inactiveCache=new Set());cache.delete(w);if(w!==this.root&&w.children&&(w.children.length||w.inlineCache&&w.inlineCache.size)&&!w.expanded)cache.add(w);this.cacheDirty=true},trimInactiveCache(){if(!this.cacheDirty||this.destroyed||this.initializing||this.restoring||this.replayingToggles||this.layoutHolds)return;this.cacheDirty=false;this.trimInlineCache();const cache=this.inactiveCache;if(!cache||!cache.size)return;const groups=[];let size=0;for(const w of cache){if(w._invalidated||!w.children||w.expanded){cache.delete(w);continue}}
|
|
225
|
+
for(const w of cache){let ancestor=w.parent;while(ancestor&&!cache.has(ancestor))ancestor=ancestor.parent;if(ancestor)continue;const nodes=Array.from(this.descendants(w).values());groups.push({w,nodes});size +=nodes.length}
|
|
226
|
+
for(const{w,nodes}of groups){if(size<=this.inactiveNodeLimit)break;if(w.loading||w.reloadController||nodes.some(node=>this.cachePinned(node)))continue;this.invalidateChildren(w);cache.delete(w);for(const node of nodes)cache.delete(node);size -=nodes.length}}});function readQuery(grid,value){if(!value||value.version!==1||!Array.isArray(value.filters)||
|
|
227
|
+
value.sort===undefined||typeof value!=="object")throw new Error("CanopyX: query needs version 1, sort, and filters.");let sort=null;if(value.sort!==null){const state=value.sort,column=state&&grid.columns.find(c=>c.id===state.column&&c.sortable);if(!column||state.direction!=="asc"&&state.direction!=="desc")throw new Error("CanopyX: invalid query sort.");sort=Object.freeze({column:column.id,direction:state.direction})}
|
|
228
|
+
const named=new Set(value.filters);if(named.size!==value.filters.length||value.filters.some(id=>!(grid.view.filters||[]).some(f=>f.id===id)))
|
|
229
|
+
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})}
|
|
230
|
+
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}
|
|
231
|
+
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")}
|
|
232
|
+
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")}
|
|
233
|
+
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=()=>{this.outline.innerHTML=this.toOutline()};grid.addEventListener("scroll",this.onScroll,{passive:true});grid.addEventListener("click",(e)=>this.onClick(e));grid.addEventListener("dblclick",(e)=>this.onDblClick(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++}
|
|
234
|
+
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);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";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");setText(title,column.title);cell.appendChild(title)}
|
|
235
|
+
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}}
|
|
236
|
+
return Math.max(0,lo)},indexOf(w){const i=w?w._ri:undefined;return i!==undefined&&this.rows[i]===w?i:-1},render(){let measured=false;for(let pass=0;!this.destroyed;pass++){this.renderPass();if(!this.measureDetails())break;measured=true;if(pass===3){this.scheduleRender();break}}
|
|
237
|
+
return measured}});function columnWidth(column,width){return Math.min(column.maxW,Math.max(column.minW,width))}
|
|
238
|
+
function prepareWidth(column){column.minW=column.minWidth===undefined?40:column.minWidth;column.maxW=column.maxWidth===undefined?MAX_COLUMN_WIDTH:column.maxWidth;if(!Number.isInteger(column.minW)||!Number.isInteger(column.maxW)||column.minW<40||
|
|
239
|
+
column.maxW<column.minW||column.maxW>MAX_COLUMN_WIDTH)throw new Error("CanopyX: invalid bounds.");column.width=columnWidth(column,column.width??160)}
|
|
240
|
+
Object.assign(TreeGrid.prototype,{applyWidths(widths=this.cols.map(column=>column.width)){let sum=0;this.cols.forEach((column,i)=>{const width=widths[i];this.headCells[i].style.width=width+"px";const grip=this.headCells[i].querySelector(".cx-resize");grip.setAttribute("aria-valuenow",String(width));grip.setAttribute("aria-valuetext",fill(this.labels.columnWidth,{width:numberFormat(this.lang).format(width)}));sum +=width});this.rowWidth=sum;this.headRow.style.width=this.canvas.style.width=sum+"px";const resizeRow=(row)=>{row.style.width=sum+"px";if(!row._kind||!row.firstElementChild)return;if(row.children.length===1)row.firstElementChild.style.width=sum+"px";else widths.forEach((width,i)=>{if(row.children[i])row.children[i].style.width=width+"px"})};this.rendered.forEach(resizeRow);this.pool.forEach(resizeRow)},setColumnWidth(columnId,width){const column=this.columns.find((c)=>c.id===columnId);if(!column||!Number.isFinite(width)){throw new Error("CanopyX: invalid width.")}
|
|
241
|
+
if(this.cancelResize)this.cancelResize();column.width=columnWidth(column,Math.round(width));this.applyWidths();this.stateChanged()},setTreeColumnPinned(pinned){this.host.classList.toggle("cx-pin-tree",!!pinned);this.stateChanged()},startResize(e,column){if(this.destroyed||e.button!==0)return;if(this.cancelResize)this.cancelResize();e.preventDefault();const x0=e.clientX;const w0=column.width;const pointer=e.pointerId;const move=(ev)=>{if(ev.pointerId!==pointer)return;column.width=columnWidth(column,w0+ev.clientX-x0);this.applyWidths()};const stop=()=>{global.removeEventListener("pointermove",move);global.removeEventListener("pointerup",end);global.removeEventListener("pointercancel",end);global.removeEventListener("blur",stop);if(this.cancelResize===stop)this.cancelResize=null;this.stateChanged()};const end=(ev)=>{if(ev.pointerId===pointer)stop()};this.cancelResize=stop;global.addEventListener("pointermove",move);global.addEventListener("pointerup",end);global.addEventListener("pointercancel",end);global.addEventListener("blur",stop)}});const ROW_DOM_ORDER={keepDomOrder(first,last,rows){const ordered=[];const focusedIndex=rows.indexOf(this.focused);const focusedEl=focusedIndex>=0?this.rendered.get(this.focused):null;if(focusedEl&&focusedIndex<first){ordered.push(focusedEl)}
|
|
242
|
+
for(let i=first;i<=last;i++){const rowEl=this.rendered.get(rows.rowItem(i));if(rowEl){ordered.push(rowEl)}}
|
|
243
|
+
if(focusedEl&&focusedIndex>last){ordered.push(focusedEl)}
|
|
244
|
+
const kids=[...this.canvas.children];const n=ordered.length;if(kids.length===n&&ordered.every((rowEl,k)=>kids[k]===rowEl))return;if(kids.length===n&&!focusedEl){const shift=kids.indexOf(ordered[0]);if(shift>0&&ordered.every((rowEl,k)=>kids[(shift+k)%n]===rowEl)){if(shift<=n-shift){for(let k=0;k<shift;k++){this.canvas.appendChild(kids[k])}}else{for(let k=n-1;k>=shift;k--){this.canvas.insertBefore(kids[k],this.canvas.firstChild)}}
|
|
245
|
+
return}}
|
|
246
|
+
const p=focusedEl?ordered.indexOf(focusedEl):-1;if(p<0){let node=this.canvas.firstChild;for(const rowEl of ordered){if(node===rowEl){node=node.nextSibling}else{this.canvas.insertBefore(rowEl,node)}}
|
|
247
|
+
return}
|
|
248
|
+
let ref=focusedEl;for(let k=p-1;k>=0;k--){if(ordered[k].nextSibling!==ref){this.canvas.insertBefore(ordered[k],ref)}
|
|
249
|
+
ref=ordered[k]}
|
|
250
|
+
let prev=focusedEl;for(let k=p+1;k<ordered.length;k++){if(prev.nextSibling!==ordered[k]){this.canvas.insertBefore(ordered[k],prev.nextSibling)}
|
|
251
|
+
prev=ordered[k]}}};Object.assign(TreeGrid.prototype,ROW_DOM_ORDER);const NATIVE_HEIGHT=8000000;const ROW_SCROLLING={scrollRange(){const view=Math.max(0,this.grid.clientHeight-this.head.offsetHeight);return{logical:Math.max(0,this.total-view),native:Math.max(0,NATIVE_HEIGHT-view)}},logicalTop(){const physical=this.grid.scrollTop;if(!this.scaledScroll)return physical;const range=this.scrollRange();const point=this.scrollPosition;return Math.min(range.logical,point&&point.native===physical?point.logical:
|
|
252
|
+
range.native?physical*range.logical / range.native : 0);
|
|
253
|
+
},setLogicalTop(value){if(!this.scaledScroll){this.grid.scrollTop=value;this.scrollPosition=null;return}
|
|
254
|
+
const range=this.scrollRange(),logical=Math.max(0,Math.min(range.logical,value));this.grid.scrollTop=range.logical?logical*range.native / range.logical : 0;
|
|
255
|
+
this.scrollPosition={native:this.grid.scrollTop,logical};this.scheduleRender();this.stateChanged()},syncScrollRange(top){const scaled=this.total>NATIVE_HEIGHT;this.canvas.style.height=Math.min(this.total,NATIVE_HEIGHT)+ "px";if(scaled!==!!this.scaledScroll){this.scaledScroll=scaled;this.canvas.toggleAttribute("data-cx-scaled",scaled);if(!this.onScaledWheel){this.onScaledWheel=event=>this.scaledWheel(event);this.onScaledTouchStart=event=>{if(!event.defaultPrevented&&event.touches.length===1){const touch=event.touches[0];this.touchScroll={x:touch.clientX,y:touch.clientY,top:this.logicalTop(),left:this.grid.scrollLeft}}else this.touchScroll=null};this.onScaledTouchMove=event=>this.scaledTouch(event);this.onScaledTouchEnd=()=>{this.touchScroll=null}}
|
|
256
|
+
const method=scaled?"addEventListener":"removeEventListener";this.grid[method]("wheel",this.onScaledWheel,{passive:false});this.grid[method]("touchstart",this.onScaledTouchStart,{passive:true});this.grid[method]("touchmove",this.onScaledTouchMove,{passive:false});this.grid[method]("touchend",this.onScaledTouchEnd);this.grid[method]("touchcancel",this.onScaledTouchEnd);this.touchScroll=null}
|
|
257
|
+
this.setLogicalTop(top)},scaledWheel(event){if(event.defaultPrevented||event.ctrlKey||event.shiftKey||!event.deltaY||!event.cancelable)return;if(this.nestedScroll(event.target,event.deltaY))return;const unit=event.deltaMode===1?this.rowH:event.deltaMode===2?this.grid.clientHeight-this.head.offsetHeight:1;const top=this.logicalTop(),next=Math.max(0,Math.min(this.scrollRange().logical,top+event.deltaY*unit));if(next===top)return;// Preserve native scroll chaining at either boundary.
|
|
258
|
+
event.preventDefault();this.grid.scrollLeft +=event.deltaX*unit;this.setLogicalTop(next)},scaledTouch(event){const start=this.touchScroll;if(!start||event.defaultPrevented||event.touches.length!==1||!event.cancelable){this.touchScroll=null;return}
|
|
259
|
+
const touch=event.touches[0],dy=start.y-touch.clientY,dx=start.x-touch.clientX;if(Math.abs(dx)>Math.abs(dy))return;if(this.nestedScroll(event.target,dy)){this.touchScroll=null;return}
|
|
260
|
+
const next=Math.max(0,Math.min(this.scrollRange().logical,start.top+dy));if(next===this.logicalTop())return;event.preventDefault();this.grid.scrollLeft=start.left+dx;this.setLogicalTop(next)},nestedScroll(target,delta){for(let element=target;element&&element!==this.grid;element=element.parentElement){if(element.clientHeight&&element.scrollHeight>element.clientHeight&&
|
|
261
|
+
/^(auto|scroll)$/.test(getComputedStyle(element).overflowY) &&
|
|
262
|
+
(delta>0?element.scrollTop+element.clientHeight<element.scrollHeight-1:element.scrollTop>0))return true}
|
|
263
|
+
return false}};Object.assign(TreeGrid.prototype,ROW_SCROLLING);const ROW_VIEWPORT={scheduleRender(){if(this.destroyed)return;if(!this.rafId){this.rafId=requestAnimationFrame(()=>{this.rafId=0;this.render()})}},renderPass(rows=this){if(this.destroyed)return;const grid=this.grid;const held=this.heldCell();const viewHeight=this.viewportHeight??grid.clientHeight-this.head.offsetHeight;const top=this.logicalTop();const shift=this.scaledScroll?top-grid.scrollTop:0;const n=rows.rowCount();const over=this.lean?0:OVERSCAN;const first=n?Math.max(0,rows.rowAt(top)- over):0;const last=n?Math.min(n-1,rows.rowAt(top+viewHeight)+ over):-1;const want=new Set();for(let i=first;i<=last;i++)want.add(rows.rowItem(i));const visibleFocus=rows.indexOf(this.focused)>=0?this.focused:null;if(visibleFocus)want.add(visibleFocus);const free=[];const active=document.activeElement;for(const[w,rowEl]of this.rendered){if(!want.has(w)){this.rendered.delete(w);disposeRowHostCells(rowEl);rowEl.remove();if(rowEl.contains(active)){rowEl._w=null;this.observeDetail(rowEl,false)}else{free.push(rowEl)}}}
|
|
264
|
+
const tabRow=visibleFocus||rows.rowItem(n?rows.rowAt(top):0)||null;const fragment=document.createDocumentFragment();for(const w of want){const index=rows.indexOf(w);if(index<0)continue;let rowEl=this.rendered.get(w);if(!rowEl){rowEl=free.pop()||this.pool.pop()||h("div");fragment.appendChild(rowEl);this.rendered.set(w,rowEl)}
|
|
265
|
+
if(w._v!==this.version||rowEl._w!==w){this.fillRow(rowEl,w);w._v=this.version}else if(!w.status)this.refreshRowState(rowEl,w);const y=this.scaledScroll&&(index<first||index>last)?
|
|
266
|
+
(index<first?-w.hpx-viewHeight:NATIVE_HEIGHT+viewHeight):rows.rowTop(index)- shift;if(rowEl._top!==y){rowEl.style.transform="translateY("+y+"px)";rowEl._top=y}
|
|
267
|
+
if(rowEl._index!==index){rowEl.setAttribute("aria-rowindex",index+2);rowEl._index=index}
|
|
268
|
+
const alt=index%2===1&&!w.status&&!w.h.detail&&(this.view.type!=="grid"||!!this.view.zebra);if(rowEl._alt!==alt){rowEl.classList.toggle("cx-alt",alt);rowEl._alt=alt}
|
|
269
|
+
const tab=w===tabRow?0:-1;if(rowEl._tab!==tab){rowEl.tabIndex=tab;rowEl._tab=tab}}
|
|
270
|
+
for(const rowEl of free){rowEl._w=null;this.observeDetail(rowEl,false);if(this.pool.length<200)this.pool.push(rowEl)}
|
|
271
|
+
if(fragment.firstChild)this.canvas.appendChild(fragment);this.window={first,last};this.keepDomOrder(first,last,rows);if(held)this.restoreCell(held);this.trimInactiveCache()}};Object.assign(TreeGrid.prototype,ROW_VIEWPORT);function setAttr(element,name,value){const cache=element._attrs||(element._attrs={});if(cache[name]===value){return}
|
|
272
|
+
cache[name]=value;if(value===null){element.removeAttribute(name)}else{element.setAttribute(name,value)}}
|
|
273
|
+
function setClass(element,value){if(element._cls!==value){element.className=value;element._cls=value;element._alt=false}}
|
|
274
|
+
Object.assign(TreeGrid.prototype,{fillRow(rowEl,w){rowEl._w=w;const kind=w.status?"status":w.h.detail?"detail":"node";if(rowEl._kind!==kind||rowEl._layout!==this.layout){this.buildRow(rowEl,kind)}
|
|
275
|
+
setAttr(rowEl,"aria-level",String(w.level));const auto=kind==="detail"&&w.auto;this.observeDetail(rowEl,auto);const height=auto?"auto":w.hpx+"px";if(rowEl._height!==height){rowEl.style.height=height;rowEl._height=height}
|
|
276
|
+
if(kind==="status"){this.paintStatusRow(rowEl,w)}else if(kind==="detail"){this.paintDetailRow(rowEl,w)}else{this.paintNodeRow(rowEl,w)}},buildRow(rowEl,kind){disposeRowHostCells(rowEl);rowEl.textContent="";["aria-expanded","aria-busy","aria-setsize","aria-posinset","aria-selected","data-cx-id","data-cx-status","data-kind"].forEach((name)=>rowEl.removeAttribute(name));rowEl._attrs={};rowEl._cls=null;rowEl._kind=kind;rowEl._layout=this.layout;rowEl._height=null;rowEl.setAttribute("role","row");rowEl.style.width=this.rowWidth+"px";if(kind!=="node"){const cell=this.cell(rowEl,0,this.rowWidth);cell.setAttribute("aria-colspan",String(this.cols.length));rowEl._cells=[{cell}];if(kind==="status"){const indent=cell.appendChild(h("span","cx-indent"));const label=cell.appendChild(h("span"));rowEl._cells[0].indent=indent;rowEl._cells[0].label=label;rowEl._cells[0].text=label.appendChild(document.createTextNode(""))}
|
|
277
|
+
return}
|
|
278
|
+
rowEl._cells=this.cols.map((column,i)=>{const cell=this.cell(rowEl,i,column.width,column.kind);const slot={cell,base:cell.className +(i===0?" cx-tree":""),cls:null,title:null,badges:[],iconKey:null,iconEl:null,mode:"default"};cell.className=slot.base;if(i===0){slot.indent=cell.appendChild(h("span","cx-indent"));slot.exp=cell.appendChild(h("span","cx-exp"));slot.exp.setAttribute("aria-hidden","true");slot.content=cell.appendChild(h("span","cx-content"))}else{slot.content=cell}
|
|
279
|
+
slot.textSpan=slot.content.appendChild(h("span","cx-text"));slot.text=slot.textSpan.appendChild(document.createTextNode(""));return slot})},paintStatusRow(rowEl,w){setClass(rowEl,"cx-row cx-status cx-"+w.status);setAttr(rowEl,"data-cx-status",w.status);const slot=rowEl._cells[0];slot.label.className=w.status==="more"?"cx-more-btn":"cx-status-text";slot.indent.style.width=(w.level-1)*INDENT+"px";if(slot.text.data!==w.h.label){slot.text.data=w.h.label}},paintDetailRow(rowEl,w){const node=w.h;setClass(rowEl,"cx-row cx-detail" +(w.auto?" cx-detail-auto":"")+(node.group?" cx-group":"")+(node.className?" "+node.className:""));this.paintRowState(rowEl,w);const old=rowEl._cells[0].cell;const cell=this.cell(rowEl,0,this.rowWidth);rowEl.replaceChild(cell,old);rowEl._cells[0].cell=cell;cell.setAttribute("aria-colspan",String(this.cols.length));const left=(w.level-1)*INDENT+8+"px";cell.style.paddingLeft=left;cell.style.setProperty("--cx-detail-left",left);this.renderDetail(node,cell);quietControls(cell);if(w.auto)this.measureNeeded=true},paintNodeRow(rowEl,w){const node=w.h;setClass(rowEl,"cx-row" +(node.group?" cx-group":"")+(node.className?" "+node.className:""));this.paintRowState(rowEl,w);setAttr(rowEl,"aria-expanded",this.expandable(w)?(w._open?"true":"false"):null);setAttr(rowEl,"aria-busy",w.loading?"true":null);const slots=rowEl._cells;for(let i=0;i<this.cols.length;i++){const slot=slots[i];if(i===0){this.paintTreeSlot(slot,w)}
|
|
280
|
+
this.paintSlot(slot,node,this.cols[i],this.columnIndex[i],w)}},paintRowState(rowEl,w){const node=w.h;setAttr(rowEl,"data-cx-id",node.id);setAttr(rowEl,"data-kind",node.kind||null);setAttr(rowEl,"data-cx-status",null);setAttr(rowEl,"aria-selected",this.selected.has(w)?"true":"false");this.setPosition(rowEl,w)},refreshRowState(rowEl,w){setAttr(rowEl,"aria-expanded",this.expandable(w)?(w._open?"true":"false"):null);setAttr(rowEl,"aria-busy",w.loading?"true":null);this.setPosition(rowEl,w);if(rowEl._kind==="node")this.paintTreeSlot(rowEl._cells[0],w)},setPosition(rowEl,w){const parent=w.parent;const loaded=parent.children?parent.children.length:0;const size=parent.childrenFiltered?(parent.next?-1:parent.visibleCount):parent.total!=null?Math.max(parent.total,loaded):parent.next?-1:loaded;setAttr(rowEl,"aria-setsize",String(size));setAttr(rowEl,"aria-posinset",String(w.pos))},paintTreeSlot(slot,w){const level=w.level;if(slot.level!==level){slot.indent.style.width=(level-1)*INDENT+"px";slot.level=level}
|
|
281
|
+
const expander=this.expandable(w)?"cx-exp":"cx-exp cx-leaf";if(slot.expCls!==expander){slot.exp.className=expander;slot.expCls=expander}
|
|
282
|
+
const key=w.h.icon&&this.iconTemplates.has(w.h.icon)?w.h.icon:null;if(slot.iconKey!==key){if(slot.iconEl){slot.iconEl.remove()}
|
|
283
|
+
slot.iconEl=key?this.iconTemplates.get(key).cloneNode(true):null;if(slot.iconEl){slot.cell.insertBefore(slot.iconEl,slot.content)}
|
|
284
|
+
slot.iconKey=key}},paintSlot(slot,node,column,index,w){const value=this.cellOf(node,column,index);const hosted=column.render?this.paintHostSlot(slot,node,column,index,w):null;if(hosted==="host"){return}
|
|
285
|
+
const decorated=hosted==="decorated";const text=cellText(value,column,this.fmt);if(slot.text.data!==text){slot.text.data=text}
|
|
286
|
+
const object=isCellObject(value);const tone=object?toneOf(value.tone):NO_TONE;const cls=slot.base+tone.cls;if(slot.cls!==cls){if(decorated){if(tone.cls)slot.cell.className +=tone.cls}else{slot.cell.className=cls}
|
|
287
|
+
slot.cls=cls}
|
|
288
|
+
toneColors(slot.cell,tone);const title=object&&value.title?value.title:decorated?null:otherZone(value,column,this.fmt);if(slot.title!==title){if(title){slot.cell.title=title}else{slot.cell.removeAttribute("title")}
|
|
289
|
+
slot.title=title}
|
|
290
|
+
this.paintBadges(slot,(index===0&&node.badges)||null,object&&value.badges?value.badges:null)},paintBadges(slot,first,second){const count=(first?first.length:0)+(second?second.length:0);for(let k=0;k<count;k++){const badge=first&&k<first.length?first[k]:second[k -(first?first.length:0)];let span=slot.badges[k];if(!span){span=slot.content.appendChild(h("span"));span._text=span.appendChild(document.createTextNode(""));slot.badges.push(span)}
|
|
291
|
+
const tone=toneOf(badge.tone||"neutral");if(span._cls!==tone.cls){applyTone(span,"cx-badge",tone);span._cls=tone.cls}
|
|
292
|
+
if(span._text.data!==badge.text){span._text.data=badge.text}
|
|
293
|
+
const title=badge.title||null;if(span._title!==title){if(title){span.title=title}else{span.removeAttribute("title")}
|
|
294
|
+
span._title=title}}
|
|
295
|
+
while(slot.badges.length>count){slot.badges.pop().remove()}},setTimeZone(zone){if(zone!=="local"&&zone!=="utc"){throw new Error("CanopyX: the time zone is 'local' or 'utc'.")}
|
|
296
|
+
if(this.local===(zone==="local")){return}
|
|
297
|
+
this.local=zone==="local";this.fmt=formatting(this.lang,this.local,this.labels);this.version++;this.scheduleRender();this.stateChanged()},cell(rowEl,index,width,kind){const cell=this.newCell(index,width,kind);rowEl.appendChild(cell);return cell},newCell(index,width,kind){const cell=h("div","cx-cell" +(kind?" cx-kind-"+kind:""));cell.setAttribute("role","gridcell");cell.setAttribute("aria-colindex",String(index+1));cell.style.width=width+"px";return cell}});function badgeElement(badge){const span=h("span");applyTone(span,"cx-badge",toneOf(badge.tone||"neutral"));setText(span,badge.text);if(badge.title)span.title=badge.title;return span}
|
|
298
|
+
function hostBadge(badge){if(!badge||typeof badge!=="object"||typeof badge.text!=="string"||
|
|
299
|
+
(badge.tone!=null&&typeof badge.tone!=="string")||(badge.title!=null&&typeof badge.title!=="string")){throw new Error("CanopyX: badge needs { text, tone?, title? } with string values.")}
|
|
300
|
+
return badgeElement(badge)}
|
|
301
|
+
function attributeShape(element){let shape="";for(const attribute of element.attributes){shape +=attribute.name+"="+attribute.value+"\n"}
|
|
302
|
+
return shape}
|
|
303
|
+
function hostRenderers(renderers,columns){if(![Object.prototype,null].includes(Object.getPrototypeOf(renderers))||
|
|
304
|
+
Object.keys(renderers).some(id=>typeof renderers[id]!=="function"||!columns.some(column=>column.id===id)))
|
|
305
|
+
throw new Error("CanopyX: invalid column renderers.");return renderers}
|
|
306
|
+
function disposeHostSlot(slot){const lifetime=slot.hostLifetime;if(!lifetime)return;delete slot.hostLifetime;lifetime.dispose()}
|
|
307
|
+
function disposeRowHostCells(row){row._cells?.forEach(disposeHostSlot)}
|
|
308
|
+
class HostCellContext{#controller;#cleanups;constructor(slot,column,level,expanded,index){slot.hostLifetime=this;this.column=column;this.level=level;this.expanded=expanded;this.index=index}
|
|
309
|
+
get signal(){this.#controller||=new AbortController();if(this.#cleanups===null)this.#controller.abort();return this.#controller.signal}
|
|
310
|
+
onDispose=cleanup=>{if(typeof cleanup!=="function")throw new TypeError("Invalid cleanup.");if(this.#cleanups===null)cleanup();else(this.#cleanups||=[]).push(cleanup)};get used(){return this.#controller||this.#cleanups}
|
|
311
|
+
dispose(){const cleanups=this.#cleanups;this.#cleanups=null;this.#controller?.abort();while(cleanups?.length){try{cleanups.pop()()}catch(error){global.console?.error(error)}}}}
|
|
312
|
+
Object.assign(TreeGrid.prototype,{paintHostSlot(slot,node,column,index,w){const tree=slot.content!==slot.cell;const previous=slot.content;const fresh=tree?h("span","cx-content"):this.newCell(this.cols.indexOf(column),column.width,column.kind);const shape=attributeShape(fresh);disposeHostSlot(slot);previous.replaceWith(fresh);this.setSlotContent(slot,fresh,tree);let drawn;try{drawn=column.render(node,fresh,column.hostRenderer===false?{column:column.id,level:w.level,expanded:w.expanded,index}:
|
|
313
|
+
new HostCellContext(slot,column.id,w.level,w.expanded,index))===true}catch(e){disposeHostSlot(slot);if(fresh.parentNode)fresh.replaceWith(previous);this.setSlotContent(slot,previous,tree);throw e}
|
|
314
|
+
const untouched=!drawn&&!slot.hostLifetime?.used&&!fresh.firstChild&&attributeShape(fresh)===shape;if(untouched&&slot.mode==="default"){disposeHostSlot(slot);fresh.replaceWith(previous);this.setSlotContent(slot,previous,tree);return "default"}
|
|
315
|
+
if(tree){slot.cell.className=slot.base;slot.cell.removeAttribute("title");toneColors(slot.cell,NO_TONE)}
|
|
316
|
+
slot.cls=slot.base;slot.title=null;slot.badges=[];slot.mode=drawn?"host":untouched?"default":"decorated";if(!untouched){quietControls(fresh)}
|
|
317
|
+
if(!drawn){fresh.appendChild(slot.textSpan)}
|
|
318
|
+
return slot.mode},setSlotContent(slot,element,tree){slot.content=element;if(!tree){slot.cell=element}}});const detailColumn={kind:"text"};const DETAIL_ESTIMATE_SLOTS=16;function detailCell(tag,cell,fmt){const element=h(tag);setText(element,cellText(cell,detailColumn,fmt));if(isCellObject(cell)){if(cell.tone){applyTone(element,"",toneOf(cell.tone))}
|
|
319
|
+
if(cell.title){element.title=cell.title}
|
|
320
|
+
(cell.badges||[]).forEach((badge)=>element.appendChild(badgeElement(badge)))}
|
|
321
|
+
return element}
|
|
322
|
+
Object.assign(TreeGrid.prototype,{renderDetail(node,element,plainHtml=false){const body=element.appendChild(h("div","cx-detail-body"));if(!plainHtml)body.setAttribute("tabindex","-1");if(this.opts.renderDetail&&this.opts.renderDetail(node,body)===true){return}
|
|
323
|
+
const detail=node.detail;if(detail.type==="table"){const table=h("table","cx-detail-table");if(detail.caption){const caption=h("caption");setText(caption,detail.caption);table.appendChild(caption)}
|
|
324
|
+
const columns=detail.columns||[];const widths=Array.isArray(detail.widths)&&detail.widths.length===columns.length?detail.widths:null;if(widths){const total=widths.reduce((sum,share)=>sum+share,0);const group=table.appendChild(h("colgroup"));widths.forEach((share)=>{group.appendChild(h("col")).style.width=(share / total * 100) + "%"; });
|
|
325
|
+
}
|
|
326
|
+
const head=h("thead");const headRow=h("tr");columns.forEach((title)=>{const th=h("th");th.scope="col";setText(th,title);headRow.appendChild(th)});head.appendChild(headRow);table.appendChild(head);const tbody=h("tbody");(detail.rows||[]).forEach((row)=>{const tr=h("tr");row.forEach((cell)=>tr.appendChild(detailCell("td",cell,this.fmt)));tbody.appendChild(tr)});table.appendChild(tbody);body.appendChild(table)}else if(detail.type==="properties"){const list=h("dl","cx-props");(detail.items||[]).forEach((item)=>{const term=h("dt");setText(term,item.name);list.appendChild(term);list.appendChild(detailCell("dd",item.value,this.fmt))});body.appendChild(list)}else if(detail.type==="html"){if(plainHtml)setText(body,detail.html||"");else body.innerHTML=detail.html;// trusted host markup, see docs/data-protocol.md
|
|
327
|
+
}},detailHeight(detail){if(detail.height>0){return detail.height*this.rowH}
|
|
328
|
+
const slots=detail.type==="table"?(detail.rows||[]).length +(detail.caption?2:1):detail.type==="properties"?(detail.items||[]).length:6;return Math.max(1,Math.min(DETAIL_ESTIMATE_SLOTS,slots))*this.rowH},observeDetail(rowEl,auto){if(!!rowEl._auto===auto){return}
|
|
329
|
+
rowEl._auto=auto;if(!this.detailObserver){this.detailObserver=new ResizeObserver(()=>{this.measureNeeded=true;this.scheduleRender()})}
|
|
330
|
+
if(auto){this.detailObserver.observe(rowEl);this.measureNeeded=true}else{this.detailObserver.unobserve(rowEl)}},measureDetails(){if(!this.measureNeeded){return false}
|
|
331
|
+
this.measureNeeded=false;const top=this.logicalTop();let shift=0;let changed=false;for(const[w,rowEl]of this.rendered){if(!w.auto||rowEl._kind!=="detail")continue;const measured=rowEl.offsetHeight;const height=Math.max(this.rowH,measured);if(measured&&height!==w.hpx){if(this.tops[this.indexOf(w)]+ w.hpx<=top)shift +=height-w.hpx;w.hpx=height;changed=true}}
|
|
332
|
+
if(changed){this.retop();this.setLogicalTop(top+shift)}
|
|
333
|
+
return changed}});function hostControl(target){const row=target.closest(".cx-row");const control=target.closest("input,textarea,select,button,a,summary,[tabindex],[contenteditable]:not([contenteditable=false]),[role=button],[role=checkbox],[role=radio],[role=link],[role=textbox],[role=combobox],[role=menuitem],[role=separator]");return control&&row&&row!==control&&row.contains(control)&&!control.matches(".cx-cell,.cx-detail-body")?control:null}
|
|
334
|
+
Object.assign(TreeGrid.prototype,{onKey(e){if(e.defaultPrevented||e.isComposing)return;if(this.head.contains(e.target)){return}
|
|
335
|
+
const rowEl=e.target.closest(".cx-row");if(rowEl&&e.key==="Tab"&&e.shiftKey&&!e.defaultPrevented){this.focusBeforeRows(e);return}
|
|
336
|
+
if(rowEl&&rowEl!==e.target&&this.onCellKey(e)){return}
|
|
337
|
+
const w=this.focused||this.rows[0];if(!w){return}
|
|
338
|
+
const i=this.indexOf(w);const pageHeight=Math.max(this.rowH,this.grid.clientHeight-this.head.offsetHeight-this.rowH);const last=this.rows.length-1;const key=e.key===" "&&this.typeBuf&&performance.now()- this.typeAt<=TYPEAHEAD_MS?"typeahead":e.key;let target=null;let handled=true;switch(key){case "ArrowDown":target=this.rows[Math.min(last,i+1)];break;case "ArrowUp":target=this.rows[Math.max(0,i-1)];break;case "PageDown":target=this.rows[Math.min(last,Math.max(i+1,this.rowAt(this.tops[i]+ pageHeight)))];break;case "PageUp":target=this.rows[Math.max(0,Math.min(i-1,this.rowAt(this.tops[i]- pageHeight)))];break;case "Home":target=this.rows[0];break;case "End":target=this.rows[last];break;case "ArrowRight":
|
|
339
|
+
if(!w.status&&this.expandable(w)&&!w._open){this.toggle(w,true)}else if(!w.status&&this.expandable(w)&&this.rows[i+1]&&this.rows[i+1].parent===w){target=this.rows[i+1]}else if(!w.status){this.enterCells(w)}
|
|
340
|
+
break;case "F2":
|
|
341
|
+
if(!w.status)this.enterCells(w);break;case "ArrowLeft":
|
|
342
|
+
if(!w.status&&w._open){this.toggle(w,false)}else if(w.parent&&w.parent!==this.root){target=w.parent}
|
|
343
|
+
break;case "Enter":
|
|
344
|
+
case " ":
|
|
345
|
+
try{this.activate(w,key==="Enter")}
|
|
346
|
+
finally{e.preventDefault();e.stopPropagation()}
|
|
347
|
+
return;default:
|
|
348
|
+
handled=this.typeAhead(e,i)}
|
|
349
|
+
if(target){this.setFocus(target);if(!target.status&&this.multi&&e.shiftKey&&/^(Arrow(Up|Down)|Page(Up|Down)|Home|End)$/.test(key)) {
|
|
350
|
+
this.selectRange(target)}}
|
|
351
|
+
if(handled){e.preventDefault();e.stopPropagation()}},activate(w,enter){if(w.status==="more"){this.loadMore(w.parent)}else if(w.status==="error"){this.retryPage(w)}else if(!w.status){let accepted,revision;const deferred=enter&&this.expandable(w);try{accepted=this.multi&&!enter?this.select(w,{toggle:true}):
|
|
352
|
+
this.select(w,{deferNotify:deferred});revision=this.selectionRevision}finally{if(enter&&this.expandable(w))this.toggle(w)}
|
|
353
|
+
if(accepted&&deferred&&revision===this.selectionRevision)
|
|
354
|
+
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}
|
|
355
|
+
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}}
|
|
356
|
+
return true},announce(message){setText(this.live,message)}});const CELL_CONTROLS="a[href],button,input,select,textarea,summary,[tabindex],[contenteditable]:not([contenteditable=false])," +
|
|
357
|
+
"[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]," +
|
|
358
|
+
"[role=textbox],[role=combobox],[role=spinbutton],[role=slider],[role=radio],[role=tab],[role=menuitem],[role=listbox],[role=tree],[role=grid]," +
|
|
359
|
+
"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]")&&
|
|
360
|
+
(control.checkVisibility?control.checkVisibility({visibilityProperty:true}):control.getClientRects().length>0)}
|
|
361
|
+
function cellControls(cell){const controls=Array.prototype.filter.call(cell.querySelectorAll(CELL_CONTROLS),(control)=>
|
|
362
|
+
!control.classList.contains("cx-detail-body")&&reachable(control));const body=cell.querySelector(":scope > .cx-detail-body");if(body&&(body.scrollHeight>body.clientHeight+1||body.scrollWidth>body.clientWidth+1))controls.push(body);return controls}
|
|
363
|
+
function quietControls(cell){cell.querySelectorAll(CELL_CONTROLS).forEach((control)=>{if(control.tabIndex>=0)control.tabIndex=-1})}
|
|
364
|
+
Object.assign(TreeGrid.prototype,{rowCells(rowEl){return rowEl&&rowEl._cells&&rowEl._kind!=="status"?rowEl._cells.map((slot)=>slot.cell):[]},enterCells(w){const cells=this.rowCells(this.rendered.get(w));const index=cells.findIndex((cell)=>cellControls(cell).length>0);if(index<0)return false;this.focusCell(w,index,0);return true},focusCell(w,index,control,enter){this.setFocus(w,false);const cells=this.rowCells(this.rendered.get(w));if(!cells.length){this.setFocus(w,true);return}
|
|
365
|
+
const cell=cells[Math.max(0,Math.min(cells.length-1,index))];const controls=cellControls(cell);let target=cell;if(typeof control==="number"&&controls.length)target=controls[control<0?controls.length-1:Math.min(control,controls.length-1)];else if(control===undefined&&controls.length===1)target=controls[0];let parked=-1;if(target!==cell&&!enter&&!target.classList.contains("cx-detail-body")&&target.matches(OWN_ARROWS)){parked=controls.indexOf(target);target=cell}
|
|
366
|
+
cell._cxAt=parked;if(target===cell)cell.setAttribute("tabindex","-1");target.focus({preventScroll:true});this.revealInRow(cell,target,cells[0])},revealInRow(cell,target,treeCell){const grid=this.grid;if(cell.parentNode&&cell.parentNode._kind==="node"){this.revealInCell(cell,target);const pinned=cell!==treeCell&&this.host.classList.contains("cx-pin-tree")?treeCell.offsetWidth:0;const left=grid.getBoundingClientRect().left+grid.clientLeft;const box=cell.getBoundingClientRect();if(box.left<left+pinned)grid.scrollLeft -=left+pinned-box.left;else if(box.right>left+grid.clientWidth)grid.scrollLeft +=Math.min(box.right-left-grid.clientWidth,box.left-left-pinned)}
|
|
367
|
+
const body=target.closest(".cx-detail-body");if(body&&body!==target){const outer=body.getBoundingClientRect();const inner=target.getBoundingClientRect();if(inner.top<outer.top)body.scrollTop -=outer.top-inner.top;else if(inner.bottom>outer.bottom)body.scrollTop +=Math.min(inner.bottom-outer.bottom,inner.top-outer.top)}
|
|
368
|
+
const frame=grid.getBoundingClientRect();const top=frame.top+grid.clientTop+this.head.offsetHeight;const bottom=frame.top+grid.clientTop+grid.clientHeight;const box=target.getBoundingClientRect();if(box.top<top)this.setLogicalTop(this.logicalTop()-(top-box.top));else if(box.bottom>bottom)this.setLogicalTop(this.logicalTop()+ Math.min(box.bottom-bottom,box.top-top))},revealInCell(cell,target){if(target===cell||!cell.contains(target)||!cell.parentNode||cell.parentNode._kind!=="node")return;const outer=cell.getBoundingClientRect();const inner=target.getBoundingClientRect();const start=outer.left+cell.clientLeft;const end=start+cell.clientWidth;if(inner.left<start)cell.scrollLeft -=start-inner.left;else if(inner.right>end)cell.scrollLeft +=Math.min(inner.right-end,inner.left-start);if(cell.scrollLeft)this.scrolledCell=cell},settleCell(next){const cell=this.scrolledCell;if(cell&&!cell.contains(next||document.activeElement)){cell.scrollLeft=0;this.scrolledCell=null}},focusBeforeRows(event){const previous=this.headRow.querySelector(".cx-hcell:last-child .cx-resize");if(!previous)return;event.preventDefault();previous.focus()},heldCell(){const active=document.activeElement;const rowEl=active&&active.closest?active.closest(".cx-row"):null;if(!rowEl||rowEl===active||!rowEl._w||!this.canvas.contains(rowEl))return null;const cells=this.rowCells(rowEl);const index=cells.findIndex((cell)=>cell.contains(active));if(index<0)return null;return{w:rowEl._w,index,control:active===cells[index]?-1:Array.prototype.indexOf.call(cells[index].querySelectorAll(CELL_CONTROLS),active)}},restoreCell(held){const active=document.activeElement;if(!held||this.destroyed||(active&&active!==document.body)||!this.rendered.has(held.w))return;const cell=this.rowCells(this.rendered.get(held.w))[held.index];if(!cell)return;const candidate=held.control>=0?cell.querySelectorAll(CELL_CONTROLS)[held.control]:null;const target=candidate&&reachable(candidate)?candidate:cell;if(target===cell)cell.setAttribute("tabindex","-1");target.focus({preventScroll:true});this.revealInCell(cell,target)},onCellKey(e){const rowEl=e.target.closest(".cx-row");const w=rowEl&&rowEl._w;const cells=this.rowCells(rowEl);const index=cells.findIndex((cell)=>cell.contains(e.target));if(!w||w.status||index<0||e.isComposing||e.defaultPrevented)return index>=0;const cell=cells[index];const control=e.target!==cell?e.target:null;if(control&&control.matches(OWN_ARROWS)){if(e.key!=="Escape")return true;const from=cellControls(cell).indexOf(control);this.focusCell(w,index,null);cell._cxAt=from;e.preventDefault();e.stopPropagation();return true}
|
|
369
|
+
const controls=cellControls(cell);const at=control?controls.indexOf(control):cell._cxAt>=0&&cell._cxAt<controls.length?cell._cxAt:-1;const detail=rowEl._kind==="detail";switch(e.key){case "ArrowRight":
|
|
370
|
+
if(at>=0&&at<controls.length-1)this.focusCell(w,index,at+1);else if(!detail&&index<cells.length-1)this.focusCell(w,index+1,0);break;case "ArrowLeft":
|
|
371
|
+
if(at>0)this.focusCell(w,index,at-1);else if(!detail&&index>0)this.focusCell(w,index-1,-1);else this.setFocus(w,true);break;case "Home":
|
|
372
|
+
this.focusCell(w,0,0);break;case "End":
|
|
373
|
+
this.focusCell(w,cells.length-1,-1);break;case "ArrowUp":
|
|
374
|
+
case "ArrowDown":{const next=this.rows[this.indexOf(w)+(e.key==="ArrowUp"?-1:1)];if(!next)break;if(!detail&&!next.status&&!next.h.detail)this.focusCell(next,index);else this.setFocus(next,true);break}
|
|
375
|
+
case "Escape":
|
|
376
|
+
case "F2":
|
|
377
|
+
this.setFocus(w,true);break;case "Enter":
|
|
378
|
+
case " ":
|
|
379
|
+
if(control)return true;if(e.key==="Enter"&&controls.length)this.focusCell(w,index,Math.max(0,at),true);else this.activate(w,e.key==="Enter");break;default:
|
|
380
|
+
return!!control}
|
|
381
|
+
e.preventDefault();e.stopPropagation();return true}});Object.assign(TreeGrid.prototype,{async loadStatusPage(parent,status,cursor){const previous=new Set(parent.children||[]);const element=document.activeElement;const revision=this.focusRevision||0;const owned=this.focused===status&&element&&element._w===status;if(owned)this.focusHandoff=status;try{await this.loadPage(parent,cursor)}
|
|
382
|
+
finally{if(this.focusHandoff===status)this.focusHandoff=null;if(!this.destroyed){if(owned)this.flushLayout();if(owned&&(this.focusRevision||0)===revision&&
|
|
383
|
+
(document.activeElement===element||document.activeElement===document.body)){let target=parent.error?this.statusRow(parent,"error"):
|
|
384
|
+
(parent.children||[]).find((child)=>!previous.has(child))||
|
|
385
|
+
(parent.next?this.statusRow(parent,"more"):parent!==this.root?parent:this.rows[0]);while(target&&this.indexOf(target)<0)target=target.parent;target=target||this.rows[0];if(target)this.setFocus(target,true);else{this.focused=null;this.grid.tabIndex=0;this.render();this.grid.focus({preventScroll:true})}}
|
|
386
|
+
this.relayout()}}}});Object.assign(TreeGrid.prototype,{async toggle(w,expand,automatic=false){if(this.destroyed||!w||w._invalidated||w.status||!this.expandable(w)){return}
|
|
387
|
+
if((this.restoring||this.initializing||this.replayingToggles)&&!automatic){const pending=this.pendingRestorationToggles||(this.pendingRestorationToggles=new Map());const last=pending.has(w)?pending.get(w):!!w._open;pending.set(w,expand===undefined?!last:expand);return}
|
|
388
|
+
const open=!!w._open;const want=expand===undefined?!open:expand;if(want===open&&(!want||w.children)){return}
|
|
389
|
+
w.expanded=want;this.rememberInactive(w);const revision=w.toggleRevision=(w.toggleRevision||0)+ 1;w.filterCollapsed=!want&&!!this.filter;try{if(want&&!w.children){await this.loadPage(w,null)}else{this.relayout();if(want)await this.expandInitial(w)}}finally{if(!this.destroyed&&!w._invalidated&&w.toggleRevision===revision)this.stateChanged()}
|
|
390
|
+
if(this.destroyed||w._invalidated||w.toggleRevision!==revision)return;this.emit("toggle",{node:w.h,expanded:w.expanded});if(this.opts.onToggle){this.opts.onToggle(w.h,w.expanded)}},loadPage(w,cursor){if(this.destroyed||w._invalidated)return Promise.resolve();if(w.loading){return w._pending}
|
|
391
|
+
w.loading=true;w.error=null;this.relayout();const started=performance.now();const restoration=this.restoreGeneration||0;const suppressInitial=!!this.restoring;const token=w.pageToken={};const controller=w.pageController=new AbortController();const navigation=navigationScope(this,controller.signal);const generation=w.dataGeneration||0;const obsolete=()=>this.destroyed||w._invalidated||w.pageToken!==token||generation!==(w.dataGeneration||0);const run=(async()=>{let info=null;let appliedCount=null;let removedSelection=null;let removed=false;try{const inlineItems=w===this.root?this.root.inlineItems:w.inline?w.h.children:null;const page=inlineItems?this.inlinePage(w,cursor):await navigation.wait(this.source.page(w===this.root?null:w.h,cursor,this.sourceRequest(controller.signal)));if(obsolete())return;const existing=inlineItems?new Map(this.keyMap):this.keyMap;if(inlineItems)for(const key of this.descendants(w).keys())existing.delete(key);const items=validatePage(page,existing,cursor?(w.children||[]).length:0);if(this.queryAware&&!inlineItems){const revision=snapshotRevision(page,this.datasetRevision);if(this.datasetRevision==null)this.datasetRevision=revision}
|
|
392
|
+
if(cursor!=null&&!items.length&&page.next===cursor){throw new Error("CanopyX: page continuation did not advance.")}
|
|
393
|
+
if(inlineItems){const prefix=cursor?w.children||[]:[];const combined=prefix.map(child=>child.h).concat(items);validateNodes(combined,existing,this.root.inlineItems!=null);this.reconcileList(w,combined,page.total,page.next||null,prefix.map(child=>child.idx).concat(page.positions),true,suppressInitial||restoration!==(this.restoreGeneration||0))}else{if(!cursor||!w.children){w.children=[]}
|
|
394
|
+
const base=w.children.length;const suppressExpansion=suppressInitial||restoration!==(this.restoreGeneration||0);items.forEach((node,i)=>w.children.push(this.wrap(node,w,base+i,suppressExpansion)));w.total=typeof page.total==="number"?page.total:page.next?null:w.children.length;w.next=page.next||null}
|
|
395
|
+
if(w!==this.root&&!w.next&&!w.children.length){w.h.hasChildren=false;w.h.childCount=0;w.expanded=false}
|
|
396
|
+
this.applySort(w,false);appliedCount=items.length}catch(err){if(obsolete())return;w.error=err&&err.message?err.message:String(err);this.emit("error",{node:w.h,message:w.error})}finally{navigation.dispose();controller.abort();if(w.pageToken!==token)return;w.pageToken=null;w.pageController=null;w.loading=false;w._pending=null;if(!this.destroyed&&!w._invalidated&&w.pendingChildCount!=null){const count=w.pendingChildCount;const loaded=w.children?w.children.length:0;w.total=count;if(w.children&&(loaded>count||(!w.next&&loaded<count))){removedSelection=this.invalidateChildren(w);removed=true}else if(loaded===count){w.next=null;w.error=null}
|
|
397
|
+
if(!count&&!loaded)w.expanded=false;w.pendingChildCount=null}
|
|
398
|
+
if(!this.destroyed&&!w._invalidated)this.relayout(removed)}
|
|
399
|
+
if(appliedCount!=null&&!this.destroyed&&!w._invalidated&&generation===(w.dataGeneration||0)&&w.children){const loaded=w.children.length;const format=numberFormat(this.lang);this.announce(w.total!=null&&loaded<w.total?fill(this.labels.pageLoaded,{loaded:format.format(loaded),total:format.format(w.total)}):fill(this.labels.itemsLoaded,{count:format.format(loaded)}));info={node:w.h,count:appliedCount,total:w.total,ms:performance.now()- started}}
|
|
400
|
+
if(removedSelection)this.selectionChanged(removedSelection);try{if(info&&!this.destroyed&&!w._invalidated){this.emit("load",info);if(this.opts.onLoad)this.opts.onLoad(w.h,info)}}finally{try{if(!this.destroyed&&!w._invalidated&&!w.error&&(w!==this.root||!this.initializing)&&(w===this.root||w.expanded)&&!suppressInitial&&restoration===(this.restoreGeneration||0))await this.expandInitial(w)}finally{if(!this.destroyed&&!w._invalidated){this.rememberInactive(w);this.scheduleRender()}}}})();w._pending=run;return run},async loadMore(w){if(w.children&&w.next){await this.loadStatusPage(w,this.statusRow(w,"more"),w.next)}},setFocus(w,domFocus){if(!w){return}
|
|
401
|
+
this.focusRevision=(this.focusRevision||0)+ 1;this.focused=w;this.cacheDirty=true;this.ensureVisible(w);if(this.render()){this.ensureVisible(w);this.render()}
|
|
402
|
+
const rowEl=this.rendered.get(w);if(rowEl&&domFocus!==false){rowEl.focus({preventScroll:true})}
|
|
403
|
+
this.stateChanged()},ensureVisible(w){const i=this.indexOf(w);if(i<0){return}
|
|
404
|
+
const grid=this.grid;const y=this.tops[i];const height=w.hpx;const viewHeight=grid.clientHeight-this.head.offsetHeight;const before=this.logicalTop();if(y<before){this.setLogicalTop(y)}else if(y+height>before+viewHeight){this.setLogicalTop(y+height-viewHeight)}
|
|
405
|
+
if(Math.abs(this.logicalTop()- before)>viewHeight)this.leanReveal()},leanReveal(){if(this.lean)return;this.lean=true;requestAnimationFrame(()=>setTimeout(()=>{this.lean=false;this.scheduleRender()}))},rowFromEvent(e){const rowEl=e.target.closest(".cx-row");return rowEl&&rowEl._w&&!rowEl._w._invalidated?rowEl._w:null},onClick(e){const w=this.rowFromEvent(e);if(!w){return}
|
|
406
|
+
if(w.status){this.setFocus(w,true);this.activate(w,true);return}
|
|
407
|
+
const interactive=hostControl(e.target);if(interactive&&!e.target.closest(".cx-exp")){this.setFocus(w,false);return}
|
|
408
|
+
if(w.h.detail){this.setFocus(w,true);this.select(w,{toggle:e.ctrlKey||e.metaKey,range:e.shiftKey});return}
|
|
409
|
+
this.setFocus(w,true);if(e.target.classList.contains("cx-exp")){this.toggle(w)}else{this.select(w,{toggle:e.ctrlKey||e.metaKey,range:e.shiftKey})}},onDblClick(e){const w=this.rowFromEvent(e);if(w&&!w.status&&!hostControl(e.target)){this.toggle(w)}},setFilter(filter,options){this.filter=typeof filter==="function"?{match:filter}:filter||null;this.cacheDirty=true;this.filterExpand=!!(options&&options.autoExpand);this.keyMap.forEach((w)=>{w.filterCollapsed=false});this.relayout()},setFilterActive(id,active){return this.updateActiveFilters(active?{add:[id]}:{remove:[id]})},updateActiveFilters(change){const{add=[],remove=[]}=change||{};if(!change||typeof change!=="object"||!Array.isArray(add)||!Array.isArray(remove))throw new Error("CanopyX: filter changes need add/remove arrays.");this.validateFilterIds(add.concat(remove));const next=new Set(this.queryAware?(this.pendingQuery||this.acceptedQuery).filters:this.activeFilters);remove.forEach(id=>next.delete(id));add.forEach(id=>next.add(id));return this.setActiveFilters(Array.from(next))},setActiveFilters(ids){if(!Array.isArray(ids))throw new Error("CanopyX: setActiveFilters needs an array of filter ids.");if(this.queryAware)return this.setQuery({version:1,sort:(this.pendingQuery||this.acceptedQuery).sort,filters:ids});this.applyActiveFilters(ids,new Set(ids))},validateFilterIds(ids){const declared=this.view.filters||[];ids.forEach((id)=>{if(!declared.some((f)=>f.id===id))throw new Error("CanopyX: the view declares no filter '"+id+"'.")})},applyActiveFilters(named,next){this.validateFilterIds(named);const declared=this.view.filters||[];const changed=next.size!==this.activeFilters.size||Array.from(next).some((id)=>!this.activeFilters.has(id));if(!changed&&this.filter===this.declaredFilter)return;this.activeFilters=next;this.toolbar.querySelectorAll("[data-cx-filter]").forEach((input)=>{input.checked=next.has(input.getAttribute("data-cx-filter"))});const filters=declared.filter((f)=>next.has(f.id));const has=(node,flag)=>!!(flag&&node.flags&&node.flags.indexOf(flag)>=0);this.setFilter(filters.length?{match:(node)=>filters.every((f)=>has(node,f.flag)),keep:(node)=>filters.every((f)=>has(node,f.flag)||has(node,f.ancestorFlag))}:null,{autoExpand:true});this.declaredFilter=this.filter;if(changed)this.emit("filter",{active:Array.from(next)});this.stateChanged()},sort(columnId,direction){if(this.queryAware)return this.setQuery({version:1,sort:direction?{column:columnId,direction}:null,filters:(this.pendingQuery||this.acceptedQuery).filters});this.sortState=direction?{column:columnId,direction}:null;this.inlineOrderVersion=(this.inlineOrderVersion||0)+ 1;this.buildHeader();this.applySort(this.root,true);this.relayout();this.stateChanged()},setColumnVisible(columnId,visible){const index=this.columns.findIndex((c)=>c.id===columnId);if(index<=0){return}
|
|
410
|
+
this.columns[index].hidden=!visible;const input=Array.from(this.toolbar.querySelectorAll("[data-cx-column]")).find((el)=>el.getAttribute("data-cx-column")===columnId);if(input){input.checked=!!visible}
|
|
411
|
+
this.buildHeader();this.relayout();this.stateChanged()}});function navigationScope(grid,signal,dataBound=false){const signals=Array.from(new Set([grid.lifetime.signal,dataBound&&grid.dataLifetime.signal].concat(signal||[]).filter(Boolean)));const controller=new AbortController();let abort;const stopped=new Promise((resolve)=>{abort=()=>{controller.abort();resolve()}});signals.forEach((item)=>item.addEventListener("abort",abort,{once:true}));const cancelled=()=>signals.some((item)=>item.aborted);if(cancelled())abort();return{cancelled,signal:controller.signal,wait:(pending)=>Promise.race([pending,stopped]),dispose:()=>{signals.forEach((item)=>item.removeEventListener("abort",abort));controller.abort()}}}
|
|
412
|
+
Object.assign(TreeGrid.prototype,{async openForPath(w,navigation){if(navigation.cancelled()||w._invalidated)return false;const wasOpen=w.expanded&&!w.filterCollapsed;w.expanded=true;w.filterCollapsed=false;if(!wasOpen)this.relayout();const revision=w.toggleRevision||0;if(!w.children){await navigation.wait(this.loadPage(w,null));if(navigation.cancelled()||w._invalidated||!w.expanded||(w.toggleRevision||0)!==revision)return false;if(!w.children){throw new Error("CanopyX: could not load the children of '"+w.key+"': " +(w.error||"no data"))}}else if(!wasOpen){await navigation.wait(this.expandInitial(w))}
|
|
413
|
+
return!navigation.cancelled()&&!w._invalidated&&w.expanded&&(w.toggleRevision||0)===revision},async expandPath(ids,options){const navigation=navigationScope(this,options&&options.signal,true);try{return await this.navigatePath(ids,options,navigation)}
|
|
414
|
+
finally{navigation.dispose()}},async navigatePath(ids,options,navigation){const o=options||{};const cancelled=navigation.cancelled;if(cancelled())return null;await navigation.wait(this.rootsReady);if(cancelled())return null;await navigation.wait(this.prefetchPath(ids,!!o.expandLast,navigation.signal));if(cancelled())return null;const release=this.holdLayout();let w;let rebuilt=false;try{w=await this.openPath(ids,o,navigation)}finally{rebuilt=release()}
|
|
415
|
+
if(cancelled()||w===undefined)return null;if(w&&(o.focus!==false||o.reveal!==false)&&this.flushLayout())rebuilt=true;if(w&&rebuilt&&(o.focus!==false||o.reveal!==false))this.leanReveal();if(w&&o.reveal!==false&&this.filter&&this.indexOf(w)<0){if(!this.queryAware)this.clearDeclaredFilters();this.setFilter(null);this.flushLayout()}
|
|
416
|
+
this.stateChanged();if(w&&o.select!==false){this.select(w)}
|
|
417
|
+
if(w&&o.focus!==false){this.setFocus(w,true)}else if(w&&o.reveal!==false){this.ensureVisible(w)}
|
|
418
|
+
return w?w.h:null},async prefetchPath(ids,openLast,signal){if(typeof this.source.prefetch!=="function")return;const opens=[];const finds=[];let parent=this.root;ids.forEach((id,k)=>{if(k>0&&!(parent&&parent.children))opens.push(ids[k-1]);const w=this.keyMap.get(id);if(!(w&&parent&&w.parent===parent&&!w._cached))finds.push(id);parent=w&&w.parent===parent&&!w._cached?w:null});if(openLast&&ids.length&&!(parent&&parent.children))opens.push(ids[ids.length-1]);if(!opens.length&&!finds.length)return;try{await this.source.prefetch(opens,finds,this.sourceRequest(signal))}catch(err){}},async openPath(ids,o,navigation){const cancelled=navigation.cancelled;let parent=this.root;let w=null;const opened=[];for(let k=0;k<ids.length;k++){if(parent!==this.root){try{if(!await this.openForPath(parent,navigation))return undefined}
|
|
419
|
+
catch(err){if(cancelled()||parent._invalidated)return undefined;throw err}
|
|
420
|
+
opened.push([parent,parent.toggleRevision||0])}
|
|
421
|
+
if(cancelled()||parent._invalidated||(parent!==this.root&&!parent.expanded))return undefined;w=this.keyMap.get(ids[k]);const seenCursors=new Set();while((!w||w.parent!==parent||w._cached)&&parent.children&&parent.next){const before=parent.children.length;const cursor=parent.next;if(seenCursors.has(cursor)){throw new Error("CanopyX: could not load more children of '" +(parent.key||"the top level")+ "': no progress (continuation repeated)")}
|
|
422
|
+
seenCursors.add(cursor);await navigation.wait(this.loadPage(parent,parent.next));if(cancelled()||parent._invalidated||(parent!==this.root&&(!parent.expanded||(parent.toggleRevision||0)!==opened[opened.length-1][1])))return undefined;if(parent.error||(parent.children.length===before&&parent.next===cursor)){throw new Error("CanopyX: could not load more children of '" +(parent.key||"the top level")+ "': " +(parent.error||"no progress"))}
|
|
423
|
+
w=this.keyMap.get(ids[k])}
|
|
424
|
+
if(!w||w.parent!==parent||w._cached){throw new Error("CanopyX: id not found on the path: "+ids[k])}
|
|
425
|
+
parent=w}
|
|
426
|
+
if(o.expandLast&&w&&this.expandable(w)){try{if(!await this.openForPath(w,navigation))return undefined}
|
|
427
|
+
catch(err){if(cancelled()||w._invalidated)return undefined;throw err}
|
|
428
|
+
opened.push([w,w.toggleRevision||0])}
|
|
429
|
+
if(cancelled()||(w&&w._invalidated)||opened.some(([node,revision])=>
|
|
430
|
+
node._invalidated||!node.expanded||(node.toggleRevision||0)!==revision))return undefined;return w}});Object.assign(TreeGrid.prototype,{select(w,options){if(!w||w.status||w._invalidated){return false}
|
|
431
|
+
const o=options||{};const revision=this.selectionRevision||0;if(this.multi&&o.range&&this.anchor){return this.selectRange(w,revision)}
|
|
432
|
+
const next=new Set(this.selected);if(this.multi&&o.toggle){if(next.has(w))next.delete(w);else next.add(w)}else{next.clear();next.add(w)}
|
|
433
|
+
return this.commitSelection(next,w,{notify:!o.deferNotify,reason:"row",anchor:w,revision})},selectRange(w,revision=this.selectionRevision||0){const from=this.indexOf(this.anchor);const to=this.indexOf(w);if(from<0||to<0){return this.select(w)}
|
|
434
|
+
const next=new Set();for(let i=Math.min(from,to);i<=Math.max(from,to);i++){const node=this.rows[i];if(!node.status&&!node._invalidated&&this.isSelectable(node))next.add(node)}
|
|
435
|
+
return this.commitSelection(next,w,{reason:"range",revision})},isSelectable(w){return!this.opts.isSelectable||this.opts.isSelectable(w.h)!==false},commitSelection(next,target,options){if(this.destroyed)return false;const o=options||{};const revision=o.revision==null?this.selectionRevision||0:o.revision;if(revision!==(this.selectionRevision||0))return false;for(const w of next)if(!this.selected.has(w)&&!this.isSelectable(w))return false;const nodes=Array.from(next,w=>w.h),previous=Array.from(this.selected,w=>w.h);const node=target?target.h:null;const context={previous,reason:o.reason||"api"};if(this.opts.beforeSelect){const decision=this.opts.beforeSelect(node,nodes.slice(),context);if(decision&&typeof decision.then==="function"){Promise.resolve(decision).catch(()=>{});throw new Error("CanopyX: beforeSelect must return synchronously.")}
|
|
436
|
+
if(decision===false)return false}
|
|
437
|
+
const event=new CustomEvent("canopyx:beforeselect",{bubbles:true,cancelable:true,detail:{node,nodes:nodes.slice(),previous,reason:context.reason}});if(!this.host.dispatchEvent(event)||this.destroyed||revision!==(this.selectionRevision||0))return false;if(Array.from(next).some(w=>w._invalidated||this.keyMap.get(w.key)!==w))return false;this.selected=next;if(Object.hasOwn(o,"anchor"))this.anchor=o.anchor;return this.selectionChanged(target,o.notify!==false)},setSelection(ids){if(this.destroyed)return false;if(!Array.isArray(ids))throw new Error("CanopyX: setSelection needs an array of loaded node ids.");const next=new Set();for(const id of ids){requireIdentifier(id,"selection ID");const w=this.keyMap.get(id);if(!w||w._invalidated)throw new Error("CanopyX: selection node is not loaded: "+id);next.add(w)}
|
|
438
|
+
if(!this.multi&&next.size>1)throw new Error("CanopyX: this view allows only one selected node.");const target=Array.from(next).pop()||null;return this.commitSelection(next,target,{reason:ids.length?"api":"clear",anchor:target})},selectAll(options){if(this.destroyed)return false;if(!this.multi)throw new Error("CanopyX: selectAll requires multiple selection.");const scope=options&&options.scope;if(scope!=="visible"&&scope!=="loaded")throw new Error("CanopyX: selectAll scope must be visible or loaded.");const candidates=scope==="visible"?this.rows:Array.from(this.keyMap.values());const revision=this.selectionRevision||0;const next=new Set(candidates.filter(w=>!w.status&&!w._invalidated&&this.isSelectable(w)));const target=Array.from(next).pop()||null;return this.commitSelection(next,target,{reason:"all",anchor:target,revision})},selectionChanged(w,notify=true){this.selectionRevision=(this.selectionRevision||0)+ 1;this.cacheDirty=true;const revision=this.selectionRevision;for(const[node,row]of this.rendered){if(!node.status)setAttr(row,"aria-selected",this.selected.has(node)?"true":"false")}
|
|
439
|
+
this.scheduleRender();const nodes=Array.from(this.selected).map((s)=>s.h);this.writeDeepLink(this.selected.has(w)?w:Array.from(this.selected).pop(),revision);if(this.destroyed||revision!==this.selectionRevision)return false;this.stateChanged();if(notify)this.notifySelection(w,nodes,revision);return revision===this.selectionRevision&&!this.destroyed},notifySelection(w,nodes,revision=this.selectionRevision){const node=w?w.h:null;this.emit("select",{node,nodes});if(this.opts.onSelect&&revision===this.selectionRevision&&!this.destroyed){this.opts.onSelect(node,nodes)}}});Object.assign(TreeGrid.prototype,{descendants(parent){const found=new Map(),pending=this.modelChildren(parent).slice();while(pending.length){const w=pending.pop();found.set(w.key,w);for(const child of this.modelChildren(w))pending.push(child)}
|
|
440
|
+
return found},retirePage(w,reloadOwner,clearDeferred=true){w.dataGeneration=(w.dataGeneration||0)+ 1;w.pageToken=null;if(w.pageController)w.pageController.abort();w.pageController=null;w._pending=null;w.loading=false;if(w.reloadController&&w.reloadController!==reloadOwner)w.reloadController.abort();if(clearDeferred)w.pendingChildCount=null},reconcileList(parent,items,total,next,positions,ordering=false,suppressExpansion=false,resetLazy=false){const old=this.descendants(parent),retained=new Set(),incoming=new Set();const pendingData=items.slice();while(pendingData.length){const data=pendingData.pop();incoming.add(data.id);for(const child of data.children||[])pendingData.push(child)}
|
|
441
|
+
if(!ordering){for(const w of[parent,...old.values()])w.countRevision=(w.countRevision||0)+ 1}
|
|
442
|
+
const pending=[{owner:parent,items,total,next,positions}];while(pending.length){const entry=pending.pop(),owner=entry.owner;const replacement=owner.inline?this.inlineReplacement(owner,entry.items,entry.positions):
|
|
443
|
+
{items:entry.items,positions:entry.positions,attached:entry.items.length};const children=replacement.items.map((data,index)=>{const position=replacement.positions?replacement.positions[index]:index;let w=old.get(data.id);const reused=!!w;if(!w)w=this.createWrapper(data,owner,position);else{w.parent=owner;w.idx=position;w.level=owner.level+1;const content=nodeContent(data);if(w.content!==content)w._v=0;w.h=data;w.content=content;w.hpx=data.detail?this.detailHeight(data.detail):this.rowH;w.auto=!!data.detail&&!(data.detail.height>0)}
|
|
444
|
+
this.retainInline(w,index>=replacement.attached);retained.add(w);if(Array.isArray(data.children)){const count=Math.max(this.pageSize,reused&&w.children?w.children.length:0);this.retirePage(w);w.inline=true;const first=this.inlinePage(w,null,count);pending.push({owner:w,items:first.items,total:first.total,next:first.next,positions:first.positions});if(!reused)w.expanded=!suppressExpansion&&!this.restoring&&!!data.expanded&&first.items.length>0}else if(!data.hasChildren||data.detail){this.retirePage(w);w.inline=false;w.children=null;w.inlineCache=null;w.next=null;w.total=null;w.expanded=false}else{w.inline=false;const cached=this.descendants(w);const conflict=Array.from(cached.keys()).some(id=>incoming.has(id));if(resetLazy||conflict||data.childCount!=null&&w.children&&
|
|
445
|
+
(data.childCount<w.children.length||!w.next&&data.childCount!==w.children.length)){this.retirePage(w);w.children=null;w.inlineCache=null;w.next=null}else{for(const child of cached.values()){child.level=child.parent.level+1;retained.add(child)}}
|
|
446
|
+
w.total=data.childCount==null?w.total:data.childCount;if(!reused||resetLazy)w.autoExpand=!suppressExpansion&&!this.restoring&&!!(data.expanded||resetLazy&&w.expanded)}
|
|
447
|
+
return w});owner.children=children.slice(0,replacement.attached);owner.inlineCache=new Map(children.slice(replacement.attached).map(w=>[w.key,w]));owner.total=entry.total;owner.next=entry.next;owner.error=null;if(owner!==this.root&&!children.length&&!entry.next)owner.expanded=false}
|
|
448
|
+
let removedSelection=null;for(const w of old.values()){if(retained.has(w))continue;const removed=this.retireWrapper(w);if(!removedSelection)removedSelection=removed}
|
|
449
|
+
if(!ordering)this.applySort(parent,true);for(const w of retained)this.rememberInactive(w);this.rememberInactive(parent);this.trimInlineCache();if(parent!==this.root&&!parent.inline){if(total!=null)parent.h.childCount=total;parent.h.hasChildren=!!(parent.children.length||next||total>0)}
|
|
450
|
+
this.relayout(true);if(removedSelection)this.selectionChanged(null)},async reload(id,options){if(this.destroyed)return false;if(id==null&&this.queryAware)return this.setQuery(this.acceptedQuery,Object.assign({},options,{force:true}));const parent=id==null?this.root:this.keyMap.get(id);if(!parent||parent._invalidated)throw new Error("CanopyX: reload needs a loaded parent ID, or null for roots.");if(parent!==this.root&&!this.expandable(parent))throw new Error("CanopyX: reload needs a node with children.");if(parent.reloadController)parent.reloadController.abort();const controller=parent.reloadController=new AbortController();const navigation=navigationScope(this,[controller.signal,options&&options.signal]);this.retirePage(parent,controller,false);const target=Math.max(this.pageSize,(parent.children||[]).length);const started=performance.now();try{const old=this.descendants(parent),outside=new Map(this.keyMap);for(const key of old.keys())outside.delete(key);const items=[],positions=[],seen=new Set();const inlineOnly=this.root.inlineItems!=null;const isInline=parent===this.root?inlineOnly:parent.inline;const inline=isInline?parent===this.root?this.view.roots:parent.h.children:null;if(isInline)validateNodes(inline,outside,inlineOnly);let cursor=null,total=null,next=null;do{if(navigation.cancelled()||parent._invalidated)return false;const page=isInline?this.inlinePage(parent,cursor,this.pageSize,inline):
|
|
451
|
+
await navigation.wait(this.source.page(parent===this.root?null:parent.h,cursor,this.sourceRequest(controller.signal)));if(navigation.cancelled()||parent._invalidated)return false;validatePage(page,outside,items.length);if(this.queryAware&&!isInline)snapshotRevision(page,this.datasetRevision);const pending=page.items.slice();while(pending.length){const node=pending.pop();outside.set(node.id,true);for(const child of node.children||[])pending.push(child)}
|
|
452
|
+
if(page.positions)positions.push(...page.positions);items.push(...page.items);total=page.total;next=page.next||null;if(next&&(seen.has(next)||next===cursor))throw new Error("CanopyX: reload continuation did not advance.");if(next)seen.add(next);cursor=next}while(next&&items.length<target);if(navigation.cancelled()||parent._invalidated)return false;const current=new Map(this.keyMap);for(const key of this.descendants(parent).keys())current.delete(key);validateNodes(isInline?inline:items,current,inlineOnly);const anchor=this.captureScrollAnchor();this.retirePage(parent,controller);if(parent===this.root&&inlineOnly)parent.inlineItems=inline;this.reconcileList(parent,items,total==null?next?null:items.length:total,next,isInline?positions:null);this.restoreScrollAnchor(anchor);this.stateChanged();this.emit("reload",{node:parent.h,count:items.length,total:parent.total,ms:performance.now()- started});return true}catch(error){if(navigation.cancelled()||parent._invalidated)return false;this.emit("error",{node:parent.h,message:error.message||String(error)});throw error}finally{navigation.dispose();controller.abort();if(parent.reloadController===controller)parent.reloadController=null}},captureScrollAnchor(){const top=this.logicalTop();const index=this.rowAt(top);const w=this.rows[Math.min(index,this.rows.length-1)];return w&&!w.status?{id:w.key,offset:top-this.tops[this.indexOf(w)]}:null},restoreScrollAnchor(anchor){const w=anchor&&this.keyMap.get(anchor.id),index=this.indexOf(w);if(index>=0)this.setLogicalTop(Math.max(0,this.tops[index]+ anchor.offset))}});Object.assign(TreeGrid.prototype,{async setQuery(value,options,restoreOwner){if(this.destroyed)return false;if(options&&options.signal&&options.signal.aborted)return false;if(!this.queryAware)throw new Error("CanopyX: this source does not support view queries.");const query=readQuery(this,value),force=!!(options&&options.force);if(this.restoreController&&this.restoreController!==restoreOwner)this.restoreController.abort();if(this.queryController)this.queryController.abort();if(!force&&this.datasetRevision!=null&&JSON.stringify(query)===JSON.stringify(this.acceptedQuery)){this.pendingQuery=null;this.syncQueryControls();return true}
|
|
453
|
+
const controller=this.queryController=new AbortController();const navigation=navigationScope(this,[controller.signal,options&&options.signal]);this.pendingQuery=query;const started=performance.now(),target=force?Math.max(this.pageSize,(this.root.children||[]).length):1;try{const items=[],seen=new Set(),existing=new Map();let cursor=null,next=null,total=null,revision=null;do{if(navigation.cancelled())return false;const page=await navigation.wait(this.source.page(null,cursor,{signal:controller.signal,query,revision}));if(navigation.cancelled())return false;validatePage(page,existing,items.length);revision=snapshotRevision(page,revision);for(const node of page.items){const pending=[node];while(pending.length){const data=pending.pop();existing.set(data.id,true);for(const child of data.children||[])pending.push(child)}}
|
|
454
|
+
items.push(...page.items);total=page.total;next=page.next||null;if(next&&(next===cursor||seen.has(next)))throw new Error("CanopyX: query continuation did not advance.");if(next)seen.add(next);cursor=next}while(next&&items.length<target);if(navigation.cancelled())return false;const anchor=force?this.captureScrollAnchor():null;const filtersChanged=JSON.stringify(query.filters)!==JSON.stringify(this.acceptedQuery.filters);this.applyQuerySnapshot(query,revision,items,total,next,anchor);if(navigation.cancelled())return false;this.emit("query",{query:this.getQuery(),revision});if(filtersChanged&&!navigation.cancelled())this.emit("filter",{active:Array.from(this.activeFilters)});if(force&&!navigation.cancelled())this.emit("reload",{node:null,count:items.length,total:this.root.total,ms:performance.now()- started});if(!this.restoring&&!navigation.cancelled())await navigation.wait(this.expandInitial(this.root));return!navigation.cancelled()}catch(error){if(navigation.cancelled())return false;this.announce(fill(this.labels.queryFailed,{error:error.message||String(error)}));this.emit("error",{node:null,message:error.message||String(error)});throw error}finally{navigation.dispose();controller.abort();if(this.queryController===controller){this.queryController=null;this.pendingQuery=null;this.syncQueryControls()}}},applyQuerySnapshot(query,revision,items,total,next,anchor){this.dataLifetime.abort();this.dataLifetime=new AbortController();if(this.cancelSearch)this.cancelSearch();this.retirePage(this.root);for(const node of this.keyMap.values())this.retirePage(node);if(this.inactiveCache)this.inactiveCache.clear();this.acceptedQuery=query;this.datasetRevision=revision;this.sortState=query.sort;this.activeFilters=new Set(query.filters);this.filter=null;this.declaredFilter=null;this.buildHeader();this.syncQueryControls();this.reconcileList(this.root,items,total==null?next?null:items.length:total,next,null,false,!!this.restoring,true);this.rootsReady=Promise.resolve();if(anchor)this.restoreScrollAnchor(anchor);else this.setLogicalTop(0);const count=numberFormat(this.lang);this.announce(this.root.total!=null&&items.length<this.root.total?fill(this.labels.pageLoaded,{loaded:count.format(items.length),total:count.format(this.root.total)}):
|
|
455
|
+
fill(this.labels.itemsLoaded,{count:count.format(items.length)}));this.stateChanged()}});Object.assign(TreeGrid.prototype,{buildToolbar(){const bar=this.toolbar;let any=false;if(this.view.search&&this.source.canSearch){this.buildSearch(bar,this.view.search);any=true}
|
|
456
|
+
(this.view.filters||[]).forEach((filter)=>{if(filter.hidden)return;const label=h("label","cx-switch");const input=h("input");input.type="checkbox";input.setAttribute("data-cx-filter",filter.id);input.addEventListener("change",()=>{const pending=this.setFilterActive(filter.id,input.checked);if(pending&&pending.catch)pending.catch(()=>{})});label.appendChild(input);label.appendChild(document.createTextNode(filter.label));bar.appendChild(label);any=true});if(this.columns.length>1){this.buildColumnChooser(bar);any=true}
|
|
457
|
+
bar.hidden=!any},clearDeclaredFilters(){if(!this.activeFilters.size){return}
|
|
458
|
+
this.activeFilters.clear();this.toolbar.querySelectorAll("[data-cx-filter]").forEach((input)=>{input.checked=false});this.emit("filter",{active:[]});this.stateChanged()},buildSearch(bar,options){const box=h("div","cx-search");const input=h("input","cx-search-input");const list=h("ul","cx-results");const status=h("span","cx-sr-only");list.id=this.id+"-results";list.hidden=true;const labels=this.labels;const count=(value)=>numberFormat(this.lang).format(value);list.setAttribute("role","listbox");list.setAttribute("aria-label",labels.searchResults);input.type="search";input.placeholder=options.placeholder||labels.searchPlaceholder;input.setAttribute("aria-label",options.placeholder||labels.searchPlaceholder);input.setAttribute("role","combobox");input.setAttribute("aria-autocomplete","list");input.setAttribute("aria-expanded","false");input.setAttribute("aria-controls",list.id);status.setAttribute("aria-live","polite");let timer=0;let blurTimer=0;let sequence=0;let navigation=null;const cancelNavigation=()=>{if(navigation)navigation.abort()};let hits=[];let active=-1;const close=()=>{list.hidden=true;active=-1;input.setAttribute("aria-expanded","false");input.removeAttribute("aria-activedescendant")};const flyout=watchFlyout(box,list,()=>!list.hidden);this.disposeSearchFlyout=flyout.dispose;const choose=async(index)=>{const hit=hits[index];if(!hit){return}
|
|
459
|
+
cancelNavigation();const current=navigation=new AbortController();close();try{await this.expandPath(hit.path,{select:true,focus:true,signal:current.signal})}catch(err){if(!this.destroyed&&!current.signal.aborted)setText(status,fill(labels.searchFailed,{error:err.message}))}};const show=(result)=>{hits=result.hits||[];list.textContent="";hits.forEach((hit,index)=>{const item=h("li","cx-result");item.id=list.id+"-"+index;item.setAttribute("role","option");item.setAttribute("aria-selected","false");const label=h("span","cx-result-label");setText(label,hit.label);item.appendChild(label);if(hit.kind){const kind=h("span","cx-result-kind");setText(kind,hit.kind);item.appendChild(kind)}
|
|
460
|
+
if(hit.context){const context=h("span","cx-result-context");setText(context,hit.context);item.appendChild(context)}
|
|
461
|
+
item.addEventListener("mousedown",(e)=>{e.preventDefault()});item.addEventListener("click",()=>choose(index));list.appendChild(item)});list.hidden=!hits.length;flyout.position();input.setAttribute("aria-expanded",hits.length?"true":"false");setText(status,!hits.length?labels.searchNoResults:!result.truncated?fill(labels.searchResultCount,{count:count(hits.length)}):
|
|
462
|
+
result.total?fill(labels.searchResultCountOfTotal,{count:count(hits.length),total:count(result.total)}):fill(labels.searchResultCountOfMany,{count:count(hits.length)}))};const run=async()=>{if(this.destroyed)return;const query=trimSeparators(input.value);if(query.length<options.minLength){sequence++;close();setText(status,query?fill(labels.searchTooShort,{min:count(options.minLength)}):"");return}
|
|
463
|
+
const mine=++sequence;try{const result=await this.search(query,options.maxResults);if(!this.destroyed&&mine===sequence){show(result)}}catch(err){if(!this.destroyed&&mine===sequence){setText(status,err&&err.code==="too-short"?fill(labels.searchWordTooShort,{min:count(2)}):
|
|
464
|
+
fill(labels.searchFailed,{error:err&&err.message?err.message:err}))}}};const move=(delta)=>{if(list.hidden||!hits.length){return}
|
|
465
|
+
if(active>=0){list.children[active].setAttribute("aria-selected","false")}
|
|
466
|
+
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=""}
|
|
467
|
+
close()}else{return}
|
|
468
|
+
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},buildColumnChooser(bar){const details=h("details","cx-columns");const summary=h("summary");setText(summary,this.labels.columns);const group=h("fieldset","cx-columns-list");const legend=h("legend","cx-sr-only");setText(legend,this.labels.visibleColumns);group.appendChild(legend);this.columns.slice(1).forEach((column)=>{const label=h("label");const input=h("input");input.type="checkbox";input.checked=!column.hidden;input.setAttribute("data-cx-column",column.id);input.addEventListener("change",()=>this.setColumnVisible(column.id,input.checked));label.appendChild(input);label.appendChild(document.createTextNode(column.title));group.appendChild(label)});details.appendChild(summary);details.appendChild(group);bar.appendChild(details);const flyout=watchFlyout(details,group,()=>details.open);details.addEventListener("toggle",flyout.position);this.disposeColumnChooser=flyout.dispose}});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)}}}
|
|
469
|
+
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")
|
|
470
|
+
throw new Error("CanopyX: invalid state column.");seen.add(c.id);return{id:c.id,width:c.width,hidden:c.hidden}}
|
|
471
|
+
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})})}
|
|
472
|
+
Object.assign(TreeGrid.prototype,{getState(){const anchor=this.captureScrollAnchor(),anchorNode=anchor&&this.keyMap.get(anchor.id);return{version:1,expanded:this.getExpandedPaths(),selection:Array.from(this.selected,w=>this.pathOf(w)),columns:this.columns.map(c=>({id:c.id,width:c.width,hidden:!!c.hidden})),sort:this.sortState&&Object.assign({},this.sortState),filters:Array.from(this.activeFilters),timeZone:this.local?"local":"utc",pinTreeColumn:this.host.classList.contains("cx-pin-tree"),focus:this.focused&&!this.focused.status&&!this.focused._invalidated?this.pathOf(this.focused):null,anchor:anchorNode?{path:this.pathOf(anchorNode),offset:anchor.offset}:null}},readState(state){if(!state||typeof state!=="object")return null;try{const result={expanded:statePaths(state.expanded)};if(state.version===undefined)return result;if(state.version!==1)return null;result.version=1;result.selection=statePaths(state.selection);if(!this.multi&&result.selection.length>1)return null;if(!Array.isArray(state.columns)||!Array.isArray(state.filters))return null;const seen=new Set();result.columns=state.columns.map(c=>readStateColumn(c,seen));if(result.columns.some(c=>c.id===this.columns[0].id&&c.hidden))return null;result.filters=state.filters.map(id=>{requireIdentifier(id,"state filter ID");return id})
|
|
473
|
+
.filter(id=>(this.view.filters||[]).some(f=>f.id===id));result.sort=null;if(state.sort!=null){requireIdentifier(state.sort.column,"state sort column");if(state.sort.direction!=="asc"&&state.sort.direction!=="desc")return null;if(this.columns.some(c=>c.id===state.sort.column))result.sort={column:state.sort.column,direction:state.sort.direction}}
|
|
474
|
+
if(state.timeZone!=="utc"&&state.timeZone!=="local"||typeof state.pinTreeColumn!=="boolean")return null;result.timeZone=state.timeZone;result.pinTreeColumn=state.pinTreeColumn;result.focus=state.focus==null?null:statePaths([state.focus])[0];result.anchor=null;if(state.anchor!=null){if(!Number.isFinite(state.anchor.offset)||state.anchor.offset<0||state.anchor.offset>MAX_PROTOCOL_INTEGER)return null;result.anchor={path:statePaths([state.anchor.path])[0],offset:state.anchor.offset}}
|
|
475
|
+
return result}catch(error){return null}},applyStatePresentation(state){if(!state.version)return;if(this.cancelResize)this.cancelResize();for(const saved of state.columns){const column=this.columns.find(c=>c.id===saved.id);if(column){column.width=columnWidth(column,saved.width);column.hidden=saved.hidden}}
|
|
476
|
+
this.toolbar.querySelectorAll("[data-cx-column]").forEach(input=>{const column=this.columns.find(c=>c.id===input.getAttribute("data-cx-column"));if(column)input.checked=!column.hidden});this.sortState=state.sort;this.inlineOrderVersion=(this.inlineOrderVersion||0)+ 1;this.setTreeColumnPinned(state.pinTreeColumn);this.setTimeZone(state.timeZone);this.buildHeader();this.applySort(this.root,true);if(!this.queryAware)this.setActiveFilters(state.filters)},async loadStatePaths(state,current){const paths=state.selection.concat(state.focus?[state.focus]:[],state.anchor?[state.anchor.path]:[]);const attached=new Set([state.focus,state.anchor&&state.anchor.path].filter(Boolean).map(path=>JSON.stringify(path)));for(const path of paths){if(this.destroyed||current.signal.aborted)return;const existing=this.keyMap.get(path[path.length-1]);let cached=false;for(let node=existing;node;node=node.parent)if(node._cached)cached=true;if(existing&&JSON.stringify(this.pathOf(existing))===JSON.stringify(path)&&
|
|
477
|
+
(!attached.has(JSON.stringify(path))||!cached))continue;try{await this.expandPath(path,{select:false,focus:false,reveal:false,signal:current.signal})}
|
|
478
|
+
catch(error){/* Nodes removed since the snapshot are skipped. */ }
|
|
479
|
+
}},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})}});function escapeHtml(value){return String(value==null?"":value).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[c]));
|
|
480
|
+
}
|
|
481
|
+
Object.assign(TreeGrid.prototype,{pathOf(w){const path=[];for(let node=w;node&&node!==this.root;node=node.parent){path.push(node.key)}
|
|
482
|
+
return path.reverse()},getExpandedPaths(){const out=[];const pending=this.modelChildren(this.root).slice().reverse();while(pending.length){const child=pending.pop();if(child.expanded&&this.expandable(child)){out.push(this.pathOf(child));const children=this.modelChildren(child);for(let i=children.length-1;i>=0;i--)pending.push(children[i])}}
|
|
483
|
+
return out},async setState(state){if(this.destroyed)return false;state=this.readState(state);if(!state)return false;if(this.restoreController)this.restoreController.abort();const current=this.restoreController=new AbortController();this.restoreGeneration=(this.restoreGeneration||0)+ 1;this.pendingRestorationToggles=null;this.restoring=true;clearTimeout(this.saveTimer);const before=new Map();this.keyMap.forEach((w)=>before.set(w,[w.expanded,w.autoExpand,w.filterCollapsed]));let restored=0;let fallback=false;const release=this.holdLayout();try{if(this.queryAware&&state.version&&!await this.setQuery({version:1,sort:state.sort,filters:state.filters},{signal:current.signal},current))return false;if(this.destroyed||current.signal.aborted)return false;this.applyStatePresentation(state);if(state.version)await this.loadStatePaths(state,current);if(this.destroyed||current.signal.aborted)return false;this.keyMap.forEach((w)=>{w.expanded=false;w.autoExpand=false});this.relayout();const paths=state.expanded.slice().sort((a,b)=>a.length-b.length);for(const path of paths){if(this.destroyed||current.signal.aborted)break;try{const node=await this.expandPath(path,{select:false,focus:false,reveal:false,expandLast:true,signal:current.signal});const wrapper=node&&this.keyMap.get(node.id);if(wrapper&&wrapper.expanded)restored++}catch(e){const last=this.keyMap.get(path[path.length-1]);if((last&&last.expanded)||path.some((id)=>{const node=this.keyMap.get(id);return node&&node.expanded&&node.error}))restored++}}
|
|
484
|
+
fallback=!!state.expanded.length&&!restored&&!this.destroyed&&!current.signal.aborted;if(fallback){this.keyMap.forEach((w)=>{const old=before.get(w);w.expanded=old?old[0]:!!w.h.expanded&&!!w.children&&w.children.length>0;w.autoExpand=old?old[1]:!!w.h.expanded&&!w.children&&this.expandable(w);if(old)w.filterCollapsed=old[2]});this.relayout()}
|
|
485
|
+
if(!this.destroyed&&!current.signal.aborted&&state.version){this.flushLayout();this.applyStatePosition(state,current)}
|
|
486
|
+
return!this.destroyed&&!current.signal.aborted}finally{release();if(this.restoreController===current){this.restoring=false;this.keyMap.forEach(w=>this.rememberInactive(w));this.cacheDirty=true;this.scheduleRender();this.restoreController=null;if(fallback&&!this.initializing)await this.expandInitial(this.root);if(!this.initializing)await this.flushPendingToggles();if(!this.initializing)this.stateChanged()}}},async flushPendingToggles(){if(this.pendingToggleFlush){try{await this.pendingToggleFlush}
|
|
487
|
+
catch(error){if(!this.restoreController)throw error}
|
|
488
|
+
if(this.restoring||this.initializing)return;return this.flushPendingToggles()}
|
|
489
|
+
const pending=this.pendingRestorationToggles;if(!pending||!pending.size)return;const generation=this.restoreGeneration||0;this.replayingToggles=true;const run=(async()=>{let failure=null;while(!this.destroyed&&generation===(this.restoreGeneration||0)&&pending.size){const[node,expand]=pending.entries().next().value;pending.delete(node);try{await this.toggle(node,expand,true)}
|
|
490
|
+
catch(error){if(!failure)failure=error}}
|
|
491
|
+
if(failure&&generation===(this.restoreGeneration||0))throw failure})();this.pendingToggleFlush=run;try{await run}finally{if(this.pendingToggleFlush===run)this.pendingToggleFlush=null;this.replayingToggles=false;this.cacheDirty=true;this.scheduleRender();if(this.pendingRestorationToggles===pending&&!pending.size){this.pendingRestorationToggles=null;if(!this.destroyed)this.stateChanged()}}},stateKey(){return this.view.stateKey==null?null:"canopyx:"+this.view.stateKey},stateChanged(){const key=this.stateKey();if(this.destroyed||!key||this.restoring||this.initializing||this.replayingToggles){return}
|
|
492
|
+
clearTimeout(this.saveTimer);this.saveTimer=setTimeout(()=>{if(this.destroyed||this.restoring||this.initializing||this.replayingToggles)return;try{sessionStorage.setItem(key,JSON.stringify(this.getState()))}catch(e){}},250)},async restoreState(){const key=this.stateKey();if(!key){return}
|
|
493
|
+
let state=null;try{state=JSON.parse(sessionStorage.getItem(key)||"null")}catch(e){state=null}
|
|
494
|
+
await this.setState(state)},visibleRows(){return this.rows.filter((w)=>!w.status).map((w)=>({node:w.h,level:w.level,path:this.pathOf(w)}))},toOutline(){const cols=this.visibleColumns();const out=["<table class=\"cx-outline-table\"><thead><tr>"];cols.forEach((column)=>out.push("<th>"+escapeHtml(column.title)+ "</th>"));out.push("</tr></thead><tbody>");this.rows.forEach((w)=>{if(w.status){return}
|
|
495
|
+
const pad=" style=\"padding-left:" +((w.level-1)*14+4)+ "px\"";if(w.h.detail){const detail=w.h.detail;const content=h("div");this.renderDetail(w.h,content,true);out.push("<tr><td"+pad+" colspan=\""+cols.length+"\">"+content.innerHTML+"</td></tr>");return}
|
|
496
|
+
out.push("<tr>");cols.forEach((column,i)=>{const index=this.columns.indexOf(column);const value=this.cellOf(w.h,column,index);const text=cellText(value,column,this.fmt);const content=h("span");const slot={};try{if(!column.render||column.render(w.h,content,new HostCellContext(slot,column.id,w.level,w.expanded,index))!==true){const badges=(i===0?w.h.badges||[]:[]).concat(isCellObject(value)?value.badges||[]:[]);content.appendChild(detailCell("span",Object.assign({},isCellObject(value)?value:{},{text,badges}),this.fmt))}
|
|
497
|
+
out.push("<td" +(i===0?pad:"")+ ">"+content.outerHTML+"</td>")}finally{disposeHostSlot(slot)}});out.push("</tr>")});out.push("</tbody></table>");return out.join("")},invalidateChildren(w){const pending=this.modelChildren(w).slice();let removedSelection=null;while(pending.length){const child=pending.pop();for(const descendant of this.modelChildren(child))pending.push(descendant);const removed=this.retireWrapper(child);if(!removedSelection)removedSelection=removed}
|
|
498
|
+
w.children=null;w.inlineCache=null;w.inlineOrder=null;if(this.inactiveCache)this.inactiveCache.delete(w);w.next=null;w.error=null;w.expanded=false;return removedSelection},applyCounts(doc,fromRefresh=false){if(this.destroyed)return;checkDocument(doc,"counts");if(this.queryAware)snapshotRevision(doc,this.datasetRevision);let removedSelection=null;let removed=false;doc.items.forEach((item)=>{const w=this.keyMap.get(item.id);if(w&&w.inline&&item.childCount!=null&&item.childCount!==w.h.children.length){throw new Error("CanopyX: inline childCount must equal children length.")}});if(!fromRefresh){for(const id of new Set(doc.items.map((item)=>item.id))){const w=this.keyMap.get(id);if(w)w.countRevision=(w.countRevision||0)+ 1}}
|
|
499
|
+
doc.items.forEach((item)=>{const w=this.keyMap.get(item.id);if(!w){return}
|
|
500
|
+
if(item.cells)w._v=0;if(typeof item.childCount==="number"){w.h.childCount=item.childCount;w.total=item.childCount;if(!w.inline){w.h.hasChildren=item.childCount>0;const loaded=w.children?w.children.length:0;if(w.loading)w.pendingChildCount=item.childCount;else if(item.childCount<loaded||(item.childCount>loaded&&w.children&&!w.next)){removedSelection=this.invalidateChildren(w)||removedSelection;removed=true}
|
|
501
|
+
else if(item.childCount===loaded){w.next=null;w.error=null}
|
|
502
|
+
if(!item.childCount&&(!w.children||!w.children.length))w.expanded=false}}
|
|
503
|
+
if(item.cells){w.h.cells=Object.assign(Object.create(null),w.h.cells,item.cells)}
|
|
504
|
+
if(Array.isArray(item.flags)){w.h.flags=item.flags.slice()}
|
|
505
|
+
w.content=nodeContent(w.h)});this.inlineOrderVersion=(this.inlineOrderVersion||0)+ 1;this.cacheDirty=true;this.applySort(this.root,true);this.relayout(removed);if(removedSelection)this.selectionChanged(removedSelection)},async refreshCounts(ids){if(this.destroyed||!this.source.counts){return null}
|
|
506
|
+
const wanted=Array.from(new Set(ids||this.rows.filter((w)=>!w.status).map((w)=>w.key)));const mine=new Map();for(const id of wanted){const w=this.keyMap.get(id);if(w){w.countRevision=(w.countRevision||0)+ 1;mine.set(id,{w,revision:w.countRevision})}}
|
|
507
|
+
const navigation=navigationScope(this,this.dataLifetime.signal);let doc;try{doc=await navigation.wait(this.source.counts(wanted,this.sourceRequest(this.dataLifetime.signal)))}
|
|
508
|
+
finally{navigation.dispose()}
|
|
509
|
+
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}});Object.assign(TreeGrid.prototype,{deepLinkName(){return "cx."+encodeURIComponent(this.view.id)},hashParams(){return(global.location.hash||"").replace(/^#/, "").split("&").filter(Boolean);
|
|
510
|
+
},currentLink(){if(this.opts.parseLink){return(global.location.hash||"").replace(/^#/, "");
|
|
511
|
+
}
|
|
512
|
+
const prefix=this.deepLinkName()+ "=";const param=this.hashParams().find((p)=>p.startsWith(prefix));return param?param.slice(prefix.length):null},readLocation(){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.
|
|
513
|
+
}
|
|
514
|
+
this.linkChanged=this.dataLifetime.signal;return this.ready.then(()=>undefined,()=>undefined)}
|
|
515
|
+
return this.openDeepLink()},openDeepLink(){const link=this.view.deepLink?this.currentLink():null;if(link!==this.link&&this.linkOpening)this.linkOpening.abort();if(link==null||link===this.link){if(link==null)this.link=null;return link!=null&&this.linkTask&&this.linkTask.link===link?this.linkTask.promise:Promise.resolve()}
|
|
516
|
+
this.link=link;if(this.linkOpening)this.linkOpening.abort();const current=this.linkOpening=new AbortController();const promise=this.openLink(link,current);this.linkTask={link,promise};promise.then(()=>{if(this.linkTask&&this.linkTask.promise===promise)this.linkTask=null});return promise},async openLink(link,current){const navigation=navigationScope(this,current.signal,true);try{const ids=this.opts.parseLink?await navigation.wait(this.opts.parseLink(link)):link.split("/").filter(Boolean).map(decodeURIComponent);if(Array.isArray(ids)&&ids.length&&!navigation.cancelled()&&
|
|
517
|
+
!await this.navigatePath(ids,{select:true,focus:false},navigation)&&!navigation.cancelled()){this.link=null}}catch(e){if(!navigation.cancelled()){this.link=null;this.announce(e&&e.message?e.message:String(e))}}finally{const cancelled=navigation.cancelled();navigation.dispose();if(this.linkOpening===current){if(cancelled&&this.link===link)this.link=null;this.linkOpening=null}
|
|
518
|
+
current.abort()}},writeDeepLink(w,revision=this.selectionRevision){if(!this.view.deepLink){return}
|
|
519
|
+
let hash;if(this.opts.formatLink){let link=null;try{link=this.opts.formatLink(w?this.pathOf(w):[],w?w.h:null)}catch(e){if(global.console)global.console.error(e)}
|
|
520
|
+
if(link==null||this.destroyed||revision!==this.selectionRevision){return}
|
|
521
|
+
hash="#"+String(link).replace(/^#/, "");
|
|
522
|
+
}else{const prefix=this.deepLinkName()+ "=";const params=this.hashParams().filter((p)=>!p.startsWith(prefix));if(w)params.push(prefix+this.pathOf(w).map(encodeURIComponent).join("/"));hash="#"+params.join("&")}
|
|
523
|
+
try{global.history.replaceState(global.history.state,"",global.location.href.replace(/#.*$/, "") + hash);
|
|
524
|
+
}catch(e){global.location.hash=hash}
|
|
525
|
+
this.link=this.currentLink();this.locationEvent=null}});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.disposeColumnChooser)this.disposeColumnChooser();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.disposeColumnChooser=null;this.disposeSearchFlyout=null;this.canvas.replaceChildren();this.outline.replaceChildren()}});function checkIntentPath(path){if(!Array.isArray(path)||!path.length)throw new Error("CanopyX: checking needs a complete node-ID path.");const seen=new Set();return path.map(id=>{requireIdentifier(id,"checking path ID");if(seen.has(id))throw new Error("CanopyX: a checking path cannot repeat an ID.");seen.add(id);return id})}
|
|
526
|
+
function checkBranch(){return{value:null,mask:0,children:new Map()}}
|
|
527
|
+
class CheckIntents{constructor(items,mode){if(!Array.isArray(items))throw new Error("CanopyX: checkIntents must be an array.");this.mode=mode;this.root=checkBranch();for(const item of items){if(!item||typeof item.checked!=="boolean")throw new Error("CanopyX: a checking intent needs a boolean checked value.");const path=checkIntentPath(item.path);let branch=this.root;for(const id of path){if(!branch.children.has(id))branch.children.set(id,checkBranch());branch=branch.children.get(id)}
|
|
528
|
+
if(branch.value!==null)throw new Error("CanopyX: a checking path cannot have duplicate intents.");branch.value=item.checked}
|
|
529
|
+
const pending=[{branch:this.root,inherited:false,visited:false}];while(pending.length){const frame=pending.pop(),branch=frame.branch;if(frame.visited){branch.mask=branch.value===null?0:branch.value?1:2;for(const child of branch.children.values())branch.mask|=child.mask;continue}
|
|
530
|
+
if(mode==="hierarchical"&&branch.value===frame.inherited)branch.value=null;const inherited=branch.value===null?frame.inherited:branch.value;pending.push({branch,inherited,visited:true});for(const child of branch.children.values())pending.push({branch:child,inherited,visited:false})}}
|
|
531
|
+
snapshot(){const result=[],pending=[{branch:this.root,path:[]}];while(pending.length){const{branch,path}=pending.pop();if(branch.value!==null)result.push({path,checked:branch.value});const children=Array.from(branch.children).sort(([a],[b])=>a<b?-1:a>b?1:0);for(let i=children.length-1;i>=0;i--)pending.push({branch:children[i][1],path:path.concat(children[i][0])})}
|
|
532
|
+
return result}
|
|
533
|
+
state(path){let branch=this.root,checked=false;for(const id of path){branch=branch&&branch.children.get(id);if(branch&&branch.value!==null&&this.mode==="hierarchical")checked=branch.value}
|
|
534
|
+
if(this.mode==="independent")return!!branch&&branch.value===true;return branch&&(branch.mask&(checked?2:1))?"mixed":checked}
|
|
535
|
+
with(path,checked){const samePrefix=rule=>path.every((id,index)=>rule.path[index]===id);const items=this.snapshot().filter(rule=>!(samePrefix(rule)&&(this.mode==="hierarchical"||rule.path.length===path.length)));items.push({path:path.slice(),checked});return new CheckIntents(items,this.mode)}}
|
|
536
|
+
TreeGrid=class extends TreeGrid{constructor(host,options){const mode=options.view&&options.view.checkboxes;if(mode!==undefined&&mode!=="independent"&&mode!=="hierarchical")throw new Error("CanopyX: unknown checkbox mode.");if(!mode&&options.view&&options.view.checkIntents!==undefined)throw new Error("CanopyX: checking intents require checkboxes.");if(mode)new CheckIntents(options.view.checkIntents===undefined?[]:options.view.checkIntents,mode);for(const name of["isCheckable","beforeCheck","onCheck"]){if(options[name]!==undefined&&typeof options[name]!=="function")throw new Error("CanopyX: "+name+" must be a function.")}
|
|
537
|
+
super(host,options)}
|
|
538
|
+
async init(){this.checkMode=this.view.checkboxes||null;this.checkRevision=0;this.checks=new CheckIntents(this.view.checkIntents===undefined?[]:this.view.checkIntents,this.checkMode);await super.init();this.paintCheckedRows()}
|
|
539
|
+
interactionApi(){return{getCheckState:id=>{const w=this.keyMap.get(id);return!this.destroyed&&this.checkMode&&w&&!w.status&&!w.h.detail?this.checks.state(this.pathOf(w)):null},getCheckIntents:()=>!this.destroyed&&this.checkMode?this.checks.snapshot():[],setChecked:(id,checked)=>this.setChecked(id,checked),setCheckIntents:items=>this.setCheckIntents(items)}}
|
|
540
|
+
canCheck(w){return!!this.checkMode&&!w.status&&!w.h.detail&&!w._invalidated&&
|
|
541
|
+
(!this.opts.isCheckable||this.opts.isCheckable(w.h)!==false)}
|
|
542
|
+
checkSnapshot(model){return Object.freeze(model.snapshot().map(item=>Object.freeze({path:Object.freeze(item.path),checked:item.checked})))}
|
|
543
|
+
voteCheck(next,w,reason,revision){const intents=this.checkSnapshot(next),previous=this.checkSnapshot(this.checks);const node=w?w.h:null,context=Object.freeze({previous,reason});if(this.opts.beforeCheck){const decision=this.opts.beforeCheck(node,intents,context);if(decision&&typeof decision.then==="function"){Promise.resolve(decision).catch(()=>{});throw new Error("CanopyX: beforeCheck must return synchronously.")}
|
|
544
|
+
if(decision===false)return false}
|
|
545
|
+
const event=new CustomEvent("canopyx:beforecheck",{bubbles:true,cancelable:true,detail:Object.freeze({node,intents,previous,reason})});return this.host.dispatchEvent(event)&&!this.destroyed&&revision===this.checkRevision&&
|
|
546
|
+
(!w||!w._invalidated&&this.keyMap.get(w.key)===w&&this.canCheck(w))&&revision===this.checkRevision}
|
|
547
|
+
commitCheck(next,w,reason,revision){if(!this.voteCheck(next,w,reason,revision))return false;this.checks=next;this.checkRevision++;this.paintCheckedRows();this.stateChanged();this.notifyCheck(w,reason);return true}
|
|
548
|
+
notifyCheck(w,reason){const model=this.checks;const detail=Object.freeze({node:w?w.h:null,intents:this.checkSnapshot(model),reason});if(this.opts.onCheck)this.opts.onCheck(detail.node,detail.intents,{reason});if(!this.destroyed&&this.checks===model)this.emit("check",detail)}
|
|
549
|
+
setCheckIntents(items){if(this.destroyed||!this.checkMode||this.initializing||this.restoring)return false;const revision=this.checkRevision;return this.commitCheck(new CheckIntents(items,this.checkMode),null,"api",revision)}
|
|
550
|
+
setChecked(id,checked,reason){if(typeof checked!=="boolean")throw new Error("CanopyX: checked must be a boolean.");if(this.destroyed||!this.checkMode||this.initializing||this.restoring)return false;const revision=this.checkRevision,w=this.keyMap.get(id);if(!w||!this.canCheck(w)||revision!==this.checkRevision)return false;let next=this.checks.with(this.pathOf(w),checked);if(this.checkMode==="hierarchical"&&!(this.queryAware&&this.acceptedQuery.filters.length)){for(let parent=w.parent;parent&&parent!==this.root;parent=parent.parent){if(!parent.children||parent.next||!parent.children.length||parent.total!=null&&parent.total!==parent.children.length||
|
|
551
|
+
parent.children.some(child=>child.h.detail))break;const values=parent.children.map(child=>next.state(this.pathOf(child)));if(values.some(value=>value==="mixed")||!values.every(value=>value===values[0]))break;next=next.with(this.pathOf(parent),values[0])}}
|
|
552
|
+
return this.commitCheck(next,w,reason||"api",revision)}
|
|
553
|
+
toggleCheck(w,reason){return this.setChecked(w.key,this.checks.state(this.pathOf(w))!==true,reason)}
|
|
554
|
+
paintTreeSlot(slot,w){super.paintTreeSlot(slot,w);if(!this.checkMode)return;if(!slot.check){slot.check=h("input","cx-check");slot.check.type="checkbox";slot.check.tabIndex=-1}
|
|
555
|
+
if(slot.check.nextSibling!==slot.content)slot.cell.insertBefore(slot.check,slot.content);this.paintCheck(slot.check,w)}
|
|
556
|
+
paintCheck(input,w){const value=this.checks.state(this.pathOf(w));input.checked=value===true;input.indeterminate=value==="mixed";setAttr(input,"aria-checked",value==="mixed"?"mixed":String(value));setAttr(input,"aria-label",w.h.label);input.disabled=this.initializing||this.restoring||!this.canCheck(w)}
|
|
557
|
+
paintCheckedRows(){if(this.destroyed||!this.checkMode)return;for(const[w,row]of this.rendered){const input=row._kind==="node"&&row._cells[0].check;if(input)this.paintCheck(input,w)}}
|
|
558
|
+
onClick(event){if(!event.defaultPrevented&&event.target.classList.contains("cx-check")){event.preventDefault();event.stopPropagation();const w=this.rowFromEvent(event);if(w){this.setFocus(w,false);this.toggleCheck(w,"pointer");this.scheduleRender()}
|
|
559
|
+
return}
|
|
560
|
+
super.onClick(event)}
|
|
561
|
+
onKey(event){const row=event.target.closest(".cx-row");const w=row&&row===event.target&&this.rowFromEvent(event);if(!event.defaultPrevented&&!event.isComposing&&this.checkMode&&row===event.target&&event.key===" "&&
|
|
562
|
+
w&&!w.status&&!w.h.detail&&
|
|
563
|
+
!event.ctrlKey&&!event.metaKey&&!event.altKey&&!event.shiftKey&&
|
|
564
|
+
!(this.typeBuf&&performance.now()- this.typeAt<=TYPEAHEAD_MS)){event.preventDefault();event.stopPropagation();this.toggleCheck(w,"keyboard");return}
|
|
565
|
+
super.onKey(event)}
|
|
566
|
+
getState(){const state=super.getState();if(this.checkMode){state.checkboxes=this.checkMode;state.checkIntents=this.checks.snapshot()}
|
|
567
|
+
return state}
|
|
568
|
+
toOutline(){const model=this.checks,items=this.rows.filter(w=>!w.status).map(w=>
|
|
569
|
+
({path:this.pathOf(w),label:w.h.label,detail:!!w.h.detail}));const markup=super.toOutline();if(!this.checkMode)return markup;const holder=h("div");holder.innerHTML=markup;const rows=holder.querySelector("table").tBodies[0].rows;items.forEach((item,index)=>{if(item.detail)return;const value=model.state(item.path),marker=h("span");marker.setAttribute("role","checkbox");marker.setAttribute("aria-readonly","true");marker.setAttribute("aria-label",item.label);marker.setAttribute("aria-checked",value==="mixed"?"mixed":String(value));marker.style.marginRight="6px";marker.textContent=value==="mixed"?"▣ ":value?"☑ ":"☐ ";rows[index].cells[0].prepend(marker)});return holder.innerHTML}
|
|
570
|
+
readState(state){const result=super.readState(state);if(!result||!result.version)return result;if(state.checkboxes!==undefined&&state.checkboxes!==this.checkMode)return null;try{if(this.checkMode){result.checkboxes=this.checkMode;result.checkIntents=new CheckIntents(state.checkIntents===undefined?[]:state.checkIntents,this.checkMode).snapshot()}else if(state.checkIntents!==undefined&&(!Array.isArray(state.checkIntents)||state.checkIntents.length))return null;return result}catch(error){return null}}
|
|
571
|
+
async setState(state){const parsed=this.readState(state);if(!parsed||this.destroyed)return false;const revision=++this.checkRevision;if(parsed.checkIntents&&!this.voteCheck(new CheckIntents(parsed.checkIntents,this.checkMode),null,"restore",revision))return false;const operation=super.setState(parsed);this.paintCheckedRows();try{return await operation}
|
|
572
|
+
finally{this.paintCheckedRows()}}
|
|
573
|
+
applyStatePresentation(state){const owner=this.restoreController;super.applyStatePresentation(state);if(this.destroyed||this.restoreController!==owner||owner&&owner.signal.aborted)return;if(state.checkIntents){this.checks=new CheckIntents(state.checkIntents,this.checkMode);this.checkRevision++;this.paintCheckedRows();this.notifyCheck(null,"restore")}}
|
|
574
|
+
destroy(){this.checkRevision++;super.destroy();this.checks=null}};function interactionSettings(options){const view=options.view,ids=new Set();if(view.dragDrop!==undefined&&typeof view.dragDrop!=="boolean")throw new Error("CanopyX: dragDrop must be a boolean.");for(const column of view.columns||[]){if(column.editable!==undefined&&typeof column.editable!=="boolean")throw new Error("CanopyX: editable must be a boolean.")}
|
|
575
|
+
if(view.contextActions!==undefined&&!Array.isArray(view.contextActions))throw new Error("CanopyX: contextActions must be an array.");const actions=(view.contextActions||[]).map(action=>{requireIdentifier(action&&action.id,"action ID");if(ids.has(action.id)||typeof action.label!=="string"||!action.label.trim())throw new Error("CanopyX: actions need unique IDs and visible labels.");ids.add(action.id);return Object.freeze({id:action.id,label:action.label})});for(const name of["onEdit","onDrop","onAction","canEdit","canDrag","canDrop","isActionEnabled"]){if(options[name]!=null&&typeof options[name]!=="function")throw new Error("CanopyX: "+name+" must be a function.")}
|
|
576
|
+
const timeout=options.interactionTimeoutMs===undefined?30000:options.interactionTimeoutMs;if(!Number.isInteger(timeout)||timeout<1||timeout>MAX_PROTOCOL_INTEGER)throw new Error("CanopyX: interactionTimeoutMs must be a positive protocol integer.");return{actions,timeout}}
|
|
577
|
+
function frozenPath(path){return Object.freeze(path.slice())}
|
|
578
|
+
function scalarValue(value){return value===null||typeof value==="string"||typeof value==="boolean"||typeof value==="number"&&Number.isFinite(value)}
|
|
579
|
+
TreeGrid=class extends TreeGrid{constructor(host,options){const settings=interactionSettings(options);super(host,options);this.interactions=settings;this.interactionRevision=0;this.interactionPins=new Set()}
|
|
580
|
+
interactionApi(){return Object.assign(super.interactionApi(),{requestEdit:(id,column,value,options)=>this.requestEdit(id,column,value,options),requestDrop:(ids,target,placement,options)=>this.requestDrop(ids,target,placement,options),requestAction:(id,action,options)=>this.requestAction(id,action,options),cancelInteraction:()=>this.cancelInteraction()})}
|
|
581
|
+
cachePinned(w){return super.cachePinned(w)||!!(this.interactionPins&&this.interactionPins.has(w))}
|
|
582
|
+
reconcileList(parent,items,total,next,positions,ordering=false,suppressExpansion=false,resetLazy=false){if(!ordering&&this.pendingInteraction){const replaced=this.descendants(parent);if(this.interactionPins.has(parent)||Array.from(this.interactionPins).some(w=>replaced.get(w.key)===w))this.cancelInteraction()}
|
|
583
|
+
return super.reconcileList(parent,items,total,next,positions,ordering,suppressExpansion,resetLazy)}
|
|
584
|
+
retireWrapper(w){if(this.interactionPins&&this.interactionPins.has(w))this.cancelInteraction();return super.retireWrapper(w)}
|
|
585
|
+
applyCounts(doc,fromRefresh=false){const operation=this.pendingInteraction;const result=super.applyCounts(doc,fromRefresh);if(operation&&this.pendingInteraction===operation&&doc.items.some(item=>
|
|
586
|
+
Array.from(this.interactionPins).some(w=>w.key===item.id)))this.cancelInteraction();return result}
|
|
587
|
+
refreshCounts(ids){if(!this.destroyed&&this.source.counts&&this.pendingInteraction){const wanted=new Set(ids||this.rows.filter(w=>!w.status).map(w=>w.key));if(Array.from(this.interactionPins).some(w=>wanted.has(w.key)))this.cancelInteraction()}
|
|
588
|
+
return super.refreshCounts(ids)}
|
|
589
|
+
interactionNode(id){requireIdentifier(id,"interaction node ID");const w=this.keyMap.get(id);return w&&!w._invalidated&&!w.h.detail?w:null}
|
|
590
|
+
eligible(name,...args){const predicate=this.opts[name];if(!predicate)return true;const result=predicate(...args);if(result&&typeof result.then==="function"){Promise.resolve(result).catch(()=>{});throw new Error("CanopyX: "+name+" must return synchronously.")}
|
|
591
|
+
return result!==false}
|
|
592
|
+
async requestEdit(id,columnId,value,options){if(this.destroyed||this.interactionClosing||options&&options.signal&&options.signal.aborted)return false;if(!scalarValue(value))throw new Error("CanopyX: an edit value must be a finite JSON scalar.");const revision=this.interactionRevision,w=this.interactionNode(id);const index=this.columns.findIndex(column=>column.id===columnId),column=this.columns[index];if(!w||!column||!column.editable||!this.eligible("canEdit",w.h,column))return false;const raw=this.cellOf(w.h,column,index),previous=isCellObject(raw)?raw.value:raw;const proposal=Object.freeze({nodeId:id,path:frozenPath(this.pathOf(w)),column:columnId,previous:previous===undefined?null:previous,value});return this.dispatchProposal("edit",proposal,[w],revision,options,()=>this.eligible("canEdit",w.h,column))}
|
|
593
|
+
async requestDrop(ids,targetId,placement="inside",options){if(this.destroyed||this.interactionClosing||options&&options.signal&&options.signal.aborted)return false;const revision=this.interactionRevision,prepared=this.prepareDrop(ids,targetId,placement);if(!prepared)return false;const{proposal,roots,target}=prepared;return this.dispatchProposal("drop",proposal,roots.concat(target||[]),revision,options,()=>roots.every(w=>this.eligible("canDrag",w.h))&&this.eligible("canDrop",proposal))}
|
|
594
|
+
prepareDrop(ids,targetId,placement){if(!Array.isArray(ids)||!ids.length||new Set(ids).size!==ids.length)throw new Error("CanopyX: a drop needs unique loaded node IDs.");if(!["inside","before","after"].includes(placement))throw new Error("CanopyX: drop placement is inside, before, or after.");const revision=this.interactionRevision,nodes=ids.map(id=>this.interactionNode(id));const target=targetId===null?null:this.interactionNode(targetId);if(!this.view.dragDrop||nodes.some(w=>!w)||targetId!==null&&!target||targetId===null&&placement!=="inside")return false;const targetPath=target?this.pathOf(target):[];if(nodes.some(w=>targetPath.includes(w.key)||!this.eligible("canDrag",w.h)))return false;const roots=nodes.filter(w=>!this.pathOf(w).slice(0,-1).some(id=>ids.includes(id)));const proposal=Object.freeze({nodes:Object.freeze(roots.map(w=>Object.freeze({nodeId:w.key,path:frozenPath(this.pathOf(w))}))),target:Object.freeze({nodeId:targetId,path:frozenPath(targetPath)}),placement});if(!this.eligible("canDrop",proposal))return false;if(this.destroyed||this.interactionClosing||revision!==this.interactionRevision||
|
|
595
|
+
nodes.concat(target||[]).some(w=>w._invalidated||this.keyMap.get(w.key)!==w))return false;return{proposal,roots,target}}
|
|
596
|
+
async requestAction(id,actionId,options){if(this.destroyed||this.interactionClosing||options&&options.signal&&options.signal.aborted)return false;const revision=this.interactionRevision,w=this.interactionNode(id);const action=this.interactions.actions.find(item=>item.id===actionId);if(!w||!action||!this.eligible("isActionEnabled",w.h,action))return false;const proposal=Object.freeze({nodeId:id,path:frozenPath(this.pathOf(w)),action:actionId});return this.dispatchProposal("action",proposal,[w],revision,options,()=>this.eligible("isActionEnabled",w.h,action))}
|
|
597
|
+
async dispatchProposal(kind,proposal,nodes,revision,options,eligible){const handler=this.opts["on"+kind[0].toUpperCase()+ kind.slice(1)];if(!handler||this.destroyed||this.interactionClosing||this.initializing||this.restoring||revision!==this.interactionRevision||
|
|
598
|
+
options&&options.signal&&options.signal.aborted)return false;const versions=nodes.map(w=>({w,count:w.countRevision||0,path:this.pathOf(w)}));const afterCancellation=this.interactionRevision +(this.pendingInteraction?1:0);this.cancelInteraction();if(this.destroyed||this.interactionClosing||this.interactionRevision!==afterCancellation)return false;const controller=new AbortController(),operation={controller,proposal,kind};this.pendingInteraction=operation;this.interactionRevision++;this.interactionPins=new Set(nodes);this.cacheDirty=true;const navigation=navigationScope(this,[controller.signal,options&&options.signal]);const live=()=>!navigation.cancelled()&&this.pendingInteraction===operation&&versions.every(({w,count,path})=>{const current=this.pathOf(w);return!w._invalidated&&this.keyMap.get(w.key)===w&&(w.countRevision||0)===count&&
|
|
599
|
+
current.length===path.length&¤t.every((id,index)=>id===path[index])});const reason=options&&options.reason||"api";let timer,timedOut=false;try{const event=new CustomEvent("canopyx:before"+kind,{bubbles:true,cancelable:true,detail:proposal});if(!this.host.dispatchEvent(event)||!live()||!eligible()||!live())return false;timer=setTimeout(()=>{timedOut=true;if(this.pendingInteraction===operation)this.cancelInteraction();else controller.abort()},this.interactions.timeout);const context=Object.freeze({signal:navigation.signal,reason,query:this.queryAware?this.getQuery():null,revision:this.datasetRevision==null?null:this.datasetRevision});const accepted=await navigation.wait(handler(proposal,context));if(timedOut)throw new Error("CanopyX: interaction timed out after "+this.interactions.timeout+" milliseconds.");if(!live())return false;if(typeof accepted!=="boolean")throw new Error("CanopyX: a host interaction hook must return a boolean acceptance result.");this.emit(kind,{proposal,accepted,reason});return accepted}catch(error){if(timedOut)throw new Error("CanopyX: interaction timed out after "+this.interactions.timeout+" milliseconds.");if(navigation.cancelled())return false;throw error}finally{clearTimeout(timer);if(this.pendingInteraction===operation){this.pendingInteraction=null;this.interactionPins.clear();this.cacheDirty=true;this.scheduleRender()}
|
|
600
|
+
navigation.dispose();controller.abort()}}
|
|
601
|
+
cancelInteraction(){const operation=this.pendingInteraction;if(!operation)return false;this.pendingInteraction=null;this.interactionRevision++;this.interactionPins.clear();this.cacheDirty=true;this.scheduleRender();this.abortInteractionController(operation.controller);return true}
|
|
602
|
+
abortInteractionController(controller){if(!controller||controller.signal.aborted)return;if(this.interactionReplacementDepth){if(!this.interactionAbortQueue)this.interactionAbortQueue=new Set();this.interactionAbortQueue.add(controller)}else controller.abort()}
|
|
603
|
+
withInteractionReplacement(work){this.interactionReplacementDepth=(this.interactionReplacementDepth||0)+ 1;try{return work()}
|
|
604
|
+
finally{this.interactionReplacementDepth--;if(!this.interactionReplacementDepth){const controllers=this.interactionAbortQueue;this.interactionAbortQueue=null;if(controllers)for(const controller of controllers)controller.abort()}}}
|
|
605
|
+
destroy(){this.interactionClosing=true;this.cancelInteraction();super.destroy();this.interactions=null;if(this.interactionPins)this.interactionPins.clear()}};DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{actions:"Actions",editValue:"Edit {column}",saveEdit:"Save",cancelEdit:"Cancel",invalidEdit:"Enter a valid {column} value.",interactionPending:"Waiting for the host…",interactionRejected:"The host declined the operation.",interactionFailed:"Operation failed: {error}",markMove:"Move this node",moveHere:"Move here",moveBefore:"Move before",moveAfter:"Move after",cancelMove:"Cancel move"}));TreeGrid=class extends TreeGrid{build(){super.build();this.interactionUiLifetime=new AbortController();const signal=this.interactionUiLifetime.signal;this.grid.addEventListener("scroll",()=>this.closeInteractionPanel(false),{signal,passive:true});global.addEventListener("resize",()=>this.closeInteractionPanel(),{signal});document.addEventListener("pointerdown",event=>{if(this.interactionPanel&&!this.interactionPanel.contains(event.target))this.closeInteractionPanel(false)},{signal});this.grid.addEventListener("contextmenu",event=>{const row=event.target.closest(".cx-row");if(row&&row._w&&!hostControl(event.target)&&this.openActionMenu(row._w,row))event.preventDefault()},{signal})}
|
|
606
|
+
interactionApi(){return Object.assign(super.interactionApi(),{beginEdit:(id,column)=>this.beginEdit(id,column),openActions:id=>{const w=this.interactionNode(id);return!!w&&this.openActionMenu(w,this.rendered.get(w))}})}
|
|
607
|
+
openInteractionPanel(w,anchor,role,label){const revision=(this.uiWorkRevision||0)+(this.interactionPanel?1:0);this.closeInteractionPanel(false);if(this.destroyed||this.interactionClosing||this.interactionPanel||(this.uiWorkRevision||0)!==revision)return null;this.render();anchor=this.rendered.get(w);if(this.destroyed||this.interactionClosing||this.interactionPanel||(this.uiWorkRevision||0)!==revision||!anchor||!anchor.isConnected)return null;const panel=this.interactionPanel=h("div","cx-interaction-panel");panel.setAttribute("role",role);panel.setAttribute("aria-label",label);this.panelNode=w;this.host.appendChild(panel);panel.addEventListener("pointerdown",()=>{panel._cxPointerFocus=panel.contains(document.activeElement)});return panel}
|
|
608
|
+
placeInteractionPanel(anchor){const panel=this.interactionPanel;if(!panel)return;const rect=anchor.getBoundingClientRect(),bounds=panel.getBoundingClientRect();panel.style.left=Math.max(8,Math.min(rect.left,global.innerWidth-bounds.width-8))+ "px";const below=global.innerHeight-rect.bottom-8,above=rect.top-8;const top=below>=Math.min(bounds.height,120)||below>=above?rect.bottom:rect.top-bounds.height;panel.style.top=Math.max(8,Math.min(top,global.innerHeight-bounds.height-8))+ "px"}
|
|
609
|
+
closeInteractionPanel(restoreFocus=true){const panel=this.interactionPanel,controller=this.editorController,w=this.panelNode;if(!panel)return null;const active=document.activeElement;const owned=panel.contains(active)||panel._cxPointerFocus&&(active===document.body||active===document.documentElement);if(controller&&this.uiPendingOperation&&this.uiPendingOperation.controller===controller){this.uiPendingOperation=null;setText(this.live,"")}
|
|
610
|
+
this.uiWorkRevision=(this.uiWorkRevision||0)+ 1;const focus=owned?{node:w,revision:this.uiWorkRevision}:null;this.interactionPanel=null;this.panelNode=null;this.editorController=null;panel.remove();this.abortInteractionController(controller);if(restoreFocus)this.restoreInteractionFocus(focus);return focus}
|
|
611
|
+
restoreInteractionFocus(focus){if(!focus||this.destroyed||this.interactionClosing||this.interactionPanel||focus.revision!==this.uiWorkRevision)return;const active=document.activeElement;if(active&&active!==document.body&&active!==document.documentElement&&active.isConnected)return;const visible=w=>w&&!w._invalidated&&this.keyMap.get(w.key)===w&&this.indexOf(w)>=0;const target=visible(focus.node)?focus.node:visible(this.focused)?this.focused:this.rows.find(visible);if(target)this.setFocus(target,true);else{this.grid.tabIndex=0;this.grid.focus({preventScroll:true})}}
|
|
612
|
+
async performInteraction(work,controller=null){this.uiWorkRevision=(this.uiWorkRevision||0)+ 1;const operation=this.uiPendingOperation={controller};setText(this.live,this.labels.interactionPending);try{const accepted=await work();if(!this.destroyed&&this.uiPendingOperation===operation)setText(this.live,accepted?"":this.labels.interactionRejected);return accepted}catch(error){if(!this.destroyed&&this.uiPendingOperation===operation)setText(this.live,fill(this.labels.interactionFailed,{error:error&&error.message||String(error)}));return false}finally{if(this.uiPendingOperation===operation)this.uiPendingOperation=null}}
|
|
613
|
+
beginEdit(id,columnId){if(this.destroyed||this.interactionClosing||this.initializing||this.restoring||!this.opts.onEdit)return false;const w=this.interactionNode(id),index=this.columns.findIndex(column=>column.id===columnId),column=this.columns[index];if(!w||!column||!column.editable||column.hidden||!this.eligible("canEdit",w.h,column))return false;const row=this.rendered.get(w);if(!row||!row.isConnected)return false;const label=fill(this.labels.editValue,{column:column.title});const panel=this.openInteractionPanel(w,row,"dialog",label);if(!panel)return false;const controller=this.editorController=new AbortController();const raw=this.cellOf(w.h,column,index),value=isCellObject(raw)?raw.value:raw;const input=h("input","cx-editor");input.type=column.kind==="number"||typeof value==="number"?"number":typeof value==="boolean"?"checkbox":"text";if(input.type==="number"){input.step="any";input.required=true}
|
|
614
|
+
input.checked=value===true;input.value=value==null?"":String(value);input.setAttribute("aria-label",label);input.addEventListener("input",()=>input.setCustomValidity(""));const controls=h("div","cx-interaction-buttons");const save=h("button","cx-save"),cancel=h("button");save.type=cancel.type="button";setText(save,this.labels.saveEdit);setText(cancel,this.labels.cancelEdit);const title=h("div","cx-interaction-title");setText(title,label);controls.append(save,cancel);panel.append(title,input,controls);const submit=async()=>{if(save.disabled)return;let next=input.type==="checkbox"?input.checked:input.value;if(input.type==="number"){if(!input.value.trim()||!Number.isFinite(Number(input.value))){input.setCustomValidity(fill(this.labels.invalidEdit,{column:column.title}));input.reportValidity();return}
|
|
615
|
+
next=Number(input.value)}
|
|
616
|
+
input.setCustomValidity("");input.disabled=save.disabled=true;cancel.focus();const accepted=await this.performInteraction(()=>this.requestEdit(id,columnId,next,{signal:controller.signal,reason:"editor"}),controller);if(this.interactionPanel!==panel)return;if(accepted)this.closeInteractionPanel();else{input.disabled=save.disabled=false;input.focus()}};save.addEventListener("click",submit);cancel.addEventListener("click",()=>this.closeInteractionPanel());panel.addEventListener("keydown",event=>{if(event.key==="Escape"){event.preventDefault();event.stopPropagation();this.closeInteractionPanel()}
|
|
617
|
+
else if(event.key==="Enter"&&event.target===input){event.preventDefault();submit()}});this.placeInteractionPanel(row);input.focus();if(input.type!=="checkbox")input.select();return true}
|
|
618
|
+
onKey(event){if(!event.defaultPrevented&&!event.isComposing&&!hostControl(event.target)){const row=event.target.closest(".cx-row"),w=row&&row._w;if(w&&!w.status&&!w.h.detail){if(event.key==="ContextMenu"||event.key==="F10"&&event.shiftKey){if(this.openActionMenu(w,row)){event.preventDefault();event.stopPropagation();return}}
|
|
619
|
+
if(event.key==="Enter"&&event.altKey){const cell=event.target.closest(".cx-cell"),column=cell&&cell.getAttribute("data-cx-column");const editable=this.columns.find(item=>item.editable&&!item.hidden&&this.eligible("canEdit",w.h,item));if(this.beginEdit(w.key,column||editable&&editable.id)){event.preventDefault();event.stopPropagation();return}}}}
|
|
620
|
+
super.onKey(event)}
|
|
621
|
+
destroy(){this.interactionClosing=true;this.closeInteractionPanel(false);if(this.interactionUiLifetime)this.interactionUiLifetime.abort();super.destroy();this.interactionUiLifetime=null}};TreeGrid=class extends TreeGrid{cachePinned(w){return super.cachePinned(w)||this.panelNode===w||!!(this.moveNodes&&this.moveNodes.includes(w))}
|
|
622
|
+
refreshCounts(ids){return this.withInteractionReplacement(()=>super.refreshCounts(ids))}
|
|
623
|
+
sort(...args){return this.withInteractionReplacement(()=>super.sort(...args))}
|
|
624
|
+
setColumnVisible(...args){return this.withInteractionReplacement(()=>super.setColumnVisible(...args))}
|
|
625
|
+
applyQuerySnapshot(...args){return this.withInteractionReplacement(()=>{this.cancelInteraction();return super.applyQuerySnapshot(...args)})}
|
|
626
|
+
relayout(now){return this.withInteractionReplacement(()=>{const focus=this.closeInteractionPanel(false);const result=super.relayout(now);this.restoreInteractionFocus(focus);return result})}
|
|
627
|
+
buildHeader(...args){return this.withInteractionReplacement(()=>{const focus=this.closeInteractionPanel(false);const result=super.buildHeader(...args);this.restoreInteractionFocus(focus);return result})}
|
|
628
|
+
reconcileList(parent,...args){return this.withInteractionReplacement(()=>{const ordering=args[4]===true;let focus;if(!ordering){this.moveNodes=null;if(this.panelNode&&(parent===this.root||parent===this.panelNode||this.pathOf(this.panelNode).includes(parent.key)))
|
|
629
|
+
focus=this.closeInteractionPanel(false)}
|
|
630
|
+
const result=super.reconcileList(parent,...args);focus=focus||this.interactionFocusHandoff;this.interactionFocusHandoff=null;this.restoreInteractionFocus(focus);return result})}
|
|
631
|
+
retireWrapper(w){return this.withInteractionReplacement(()=>{if(this.panelNode===w)this.interactionFocusHandoff=this.closeInteractionPanel(false)||this.interactionFocusHandoff;if(this.moveNodes&&this.moveNodes.includes(w))this.moveNodes=null;return super.retireWrapper(w)})}
|
|
632
|
+
applyCounts(doc,fromRefresh=false){return this.withInteractionReplacement(()=>{const panel=this.interactionPanel,node=this.panelNode,moving=this.moveNodes;const result=super.applyCounts(doc,fromRefresh);const focus=node&&this.interactionPanel===panel&&doc.items.some(item=>item.id===node.key)?this.closeInteractionPanel(false):null;if(moving&&this.moveNodes===moving&&doc.items.some(item=>moving.some(w=>w.key===item.id)))this.moveNodes=null;const handoff=focus||this.interactionFocusHandoff;this.interactionFocusHandoff=null;this.restoreInteractionFocus(handoff);return result})}
|
|
633
|
+
applyStatePresentation(state){const focus=this.closeInteractionPanel(false);this.moveNodes=null;this.cancelInteraction();const result=super.applyStatePresentation(state);this.restoreInteractionFocus(focus);return result}
|
|
634
|
+
moveSources(w){if(!this.view.dragDrop||!this.opts.onDrop||this.destroyed||this.interactionClosing||this.initializing||this.restoring||!w||w.status||w.h.detail)return false;const nodes=this.selected.has(w)?Array.from(this.selected).filter(node=>!node.h.detail):[w];if(!nodes.length||!nodes.every(node=>this.keyMap.get(node.key)===node&&!node._invalidated&&this.eligible("canDrag",node.h)))return false;return nodes}
|
|
635
|
+
markMove(w){const nodes=this.moveSources(w);if(!nodes)return false;this.moveNodes=nodes;this.cacheDirty=true;setText(this.live,this.labels.markMove);return true}
|
|
636
|
+
moveTarget(w,placement="inside"){if(!this.moveNodes||!w||w.status||w.h.detail||this.destroyed)return false;const nodes=this.moveNodes;if(nodes.some(node=>node._invalidated||this.keyMap.get(node.key)!==node))return false;return!!this.prepareDrop(nodes.map(node=>node.key),w.key,placement)&&this.moveNodes===nodes}
|
|
637
|
+
async moveTo(w,placement,reason="menu"){if(!this.moveTarget(w,placement))return false;const nodes=this.moveNodes;this.moveNodes=null;this.cacheDirty=true;return this.performInteraction(()=>this.requestDrop(nodes.map(node=>node.key),w.key,placement,{reason}))}
|
|
638
|
+
openActionMenu(w,anchor){if(this.destroyed||this.interactionClosing||this.initializing||this.restoring||!w||w.status||w.h.detail||!anchor||!anchor.isConnected)return false;const items=[];if(this.opts.onEdit)for(const column of this.columns){if(column.editable&&!column.hidden)items.push({label:fill(this.labels.editValue,{column:column.title}),disabled:!this.eligible("canEdit",w.h,column),run:()=>this.beginEdit(w.key,column.id)})}
|
|
639
|
+
if(this.opts.onAction)for(const action of this.interactions.actions)items.push({label:action.label,disabled:!this.eligible("isActionEnabled",w.h,action),run:()=>this.performInteraction(()=>this.requestAction(w.key,action.id,{reason:"menu"}))});if(this.view.dragDrop&&this.opts.onDrop){items.push({label:this.labels.markMove,disabled:!this.moveSources(w),run:()=>this.markMove(w)});if(this.moveNodes){for(const[placement,label]of[["inside","moveHere"],["before","moveBefore"],["after","moveAfter"]])
|
|
640
|
+
items.push({label:this.labels[label],disabled:!this.moveTarget(w,placement),run:()=>this.moveTo(w,placement)});items.push({label:this.labels.cancelMove,run:()=>{this.moveNodes=null;this.cacheDirty=true}})}}
|
|
641
|
+
if(!items.length||this.destroyed||w._invalidated||this.keyMap.get(w.key)!==w)return false;const panel=this.openInteractionPanel(w,anchor,"menu",this.labels.actions);if(!panel)return false;const buttons=items.map(item=>{const button=h("button","cx-action-item");button.type="button";button.tabIndex=-1;button.setAttribute("role","menuitem");if(item.disabled)button.setAttribute("aria-disabled","true");setText(button,item.label);button.addEventListener("click",()=>{if(item.disabled)return;this.closeInteractionPanel();item.run()});panel.appendChild(button);return button});panel.addEventListener("keydown",event=>{const index=buttons.indexOf(document.activeElement);let target;if(event.key==="ArrowDown")target=buttons[(index+1)%buttons.length];else if(event.key==="ArrowUp")target=buttons[(index+buttons.length-1)%buttons.length];else if(event.key==="Home")target=buttons[0];else if(event.key==="End")target=buttons[buttons.length-1];else if(event.key==="Escape")this.closeInteractionPanel();else if(event.key==="Tab"){this.closeInteractionPanel();return}
|
|
642
|
+
else return;event.preventDefault();event.stopPropagation();if(target)target.focus()});this.placeInteractionPanel(anchor);buttons[0].focus();return true}
|
|
643
|
+
onKey(event){if(!event.defaultPrevented&&!event.isComposing&&!hostControl(event.target)&&event.ctrlKey&&event.shiftKey&&!event.altKey){const row=event.target.closest(".cx-row"),w=row&&row._w;if(event.key.toLowerCase()==="x"&&this.markMove(w)){event.preventDefault();event.stopPropagation();return}
|
|
644
|
+
if(event.key.toLowerCase()==="v"&&this.moveTarget(w)){event.preventDefault();event.stopPropagation();this.moveTo(w,"inside","keyboard");return}}
|
|
645
|
+
super.onKey(event)}
|
|
646
|
+
destroy(){this.moveNodes=null;super.destroy()}};TreeGrid=class extends TreeGrid{build(){super.build();const signal=this.interactionUiLifetime.signal;this.grid.addEventListener("scroll",()=>this.clearDropRow(),{signal,passive:true});this.grid.addEventListener("dragstart",event=>{const row=event.target.closest(".cx-row");if(!row||hostControl(event.target)||!event.dataTransfer||!this.markMove(row._w)){event.preventDefault();return}
|
|
647
|
+
this.dragLease=this.moveNodes;event.dataTransfer.effectAllowed="move";event.dataTransfer.setData("application/x-canopyx-move",this.id)},{signal});this.grid.addEventListener("dragover",event=>{const row=event.target.closest(".cx-row");const placement=row&&this.dropPlacement(event,row);if(this.dragLease&&event.dataTransfer&&Array.from(event.dataTransfer.types).includes("application/x-canopyx-move")&&row&&this.moveTarget(row._w,placement)){event.preventDefault();event.dataTransfer.dropEffect="move";if(this.dropRow!==row){this.clearDropRow();this.dropRow=row;row.classList.add("cx-drop-target")}
|
|
648
|
+
row.setAttribute("data-cx-drop-placement",placement)}else{this.clearDropRow();if(event.dataTransfer)event.dataTransfer.dropEffect="none"}},{signal});this.grid.addEventListener("drop",event=>{const row=event.target.closest(".cx-row");const placement=row&&this.dropPlacement(event,row);if(!this.dragLease||!event.dataTransfer||event.dataTransfer.getData("application/x-canopyx-move")!==this.id||!row||!this.moveTarget(row._w,placement))return;event.preventDefault();this.clearDropRow();this.dragLease=null;this.moveTo(row._w,placement,"pointer")},{signal});this.grid.addEventListener("dragend",()=>{this.clearDropRow();if(this.dragLease){this.moveNodes=null;this.cacheDirty=true}
|
|
649
|
+
this.dragLease=null},{signal})}
|
|
650
|
+
paintRowState(row,w){super.paintRowState(row,w);row.draggable=!!(this.view.dragDrop&&this.opts.onDrop&&!w.status&&!w.h.detail)}
|
|
651
|
+
clearDropRow(){if(this.dropRow){this.dropRow.classList.remove("cx-drop-target");this.dropRow.removeAttribute("data-cx-drop-placement")}
|
|
652
|
+
this.dropRow=null}
|
|
653
|
+
dropPlacement(event,row){const bounds=row.getBoundingClientRect(),fraction=(event.clientY-bounds.top)/ bounds.height;
|
|
654
|
+
return fraction<0.25?"before":fraction>0.75?"after":"inside"}
|
|
655
|
+
destroy(){this.clearDropRow();this.dragLease=null;super.destroy()}};function createTreeGrid(host,options){if(!host||!options||!options.view){throw new Error("CanopyX: createTreeGrid needs a host element and a view document.")}
|
|
656
|
+
const existing=activeGrids.get(host);if(existing)return existing;const grid=new TreeGrid(host,options);const api=Object.freeze(Object.assign({element:host,view:grid.view,ready:grid.ready,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),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),destroy:()=>{grid.destroy();if(activeGrids.get(host)===api)activeGrids.delete(host);if(host.canopyx===api)delete host.canopyx}},grid.interactionApi&&grid.interactionApi()));activeGrids.set(host,api);host.setAttribute("data-canopyx-mounted","true");host.canopyx=api;return api}
|
|
657
|
+
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.")}
|
|
658
|
+
if(Array.from(document.querySelectorAll("[data-canopyx-mounted]")).some((host)=>host.canopyx&&host.canopyx.view.id===viewId)){throw new Error("CanopyX: view '"+viewId+"' is already mounted; configure it before the page has loaded, or mark its fragment data-cx-mount=\"manual\" and call CanopyX.mount.")}
|
|
659
|
+
const registration=Object.assign({},configured.get(viewId),options,{view:undefined});configured.set(viewId,registration);return()=>{if(configured.get(viewId)===registration)configured.delete(viewId)}}
|
|
660
|
+
function unconfigure(viewId){requireIdentifier(viewId,"view ID");return configured.delete(viewId)}
|
|
661
|
+
function mount(root,automatic){const scope=root||document;const created=[];const showError=(host,err,api,view)=>{if(api){api.destroy();if(host.canopyx===api)delete host.canopyx}
|
|
662
|
+
const message=err&&err.message?err.message:String(err);let labels=DEFAULT_LABELS;for(const attempt of[()=>labelsFor(view&&view.labels,view&&configured.has(view.id)?configured.get(view.id).labels:null),()=>labelsFor(view&&view.labels)]){try{labels=attempt();break}catch(e){labels=DEFAULT_LABELS}}
|
|
663
|
+
const notice=document.createElement("div");notice.className="cx-mount-error";notice.setAttribute("role","alert");setText(notice,fill(labels.mountError,{error:message}));host.classList.add("cx-host");host.setAttribute("data-canopyx-mounted","true");host.setAttribute("data-cx-error",message);host.appendChild(notice);if(global.console)global.console.error(err)};scope.querySelectorAll("[data-canopyx-host]:not([data-canopyx-mounted])").forEach((host)=>{const config=host.querySelector("script.cx-config");if(!config||(automatic===true&&host.getAttribute("data-cx-mount")==="manual")){return}
|
|
664
|
+
host.setAttribute("data-canopyx-mounted","true");let view=null;try{view=JSON.parse(config.textContent);const create=view&&view.type==="grid"&&typeof createDataGrid==="function"?createDataGrid:createTreeGrid;const api=create(host,Object.assign({},view&&configured.get(view.id),{view}));created.push(api);api.ready.catch((err)=>{if(view.type!=="grid"&&activeGrids.get(host)===api&&host.canopyx===api)showError(host,err,api,view)})}catch(err){showError(host,err,null,view)}});return created}
|
|
665
|
+
global.CanopyX=Object.freeze(Object.assign({protocol:PROTOCOL,createTreeGrid,configure,unconfigure,mount:(root)=>mount(root,false),sources:Object.freeze({inline:inlineSource,sidecar:sidecarSource,remote:remoteSource}),badge:hostBadge,labels:DEFAULT_LABELS},typeof createDataGrid==="function"?{createDataGrid}:{}));if(typeof document!=="undefined"){const navigation=global.performance&&performance.getEntriesByType?performance.getEntriesByType("navigation")[0]:null;if(document.readyState==="loading"||(document.readyState==="interactive"&&navigation&&navigation.domContentLoadedEventStart===0)){document.addEventListener("DOMContentLoaded",()=>mount(document,true))}else{mount(document,true)}}})(runtimeHost);const CanopyX=runtimeHost.CanopyX;const{protocol,createTreeGrid,configure,unconfigure,mount,sources,badge,labels}=CanopyX;export{protocol,createTreeGrid,configure,unconfigure,mount,sources,badge,labels};export default CanopyX;
|