@evotecit/canopyx 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CanopyX.Html/Assets/canopyx-calendar.js +4 -3
  2. package/CanopyX.Html/Assets/canopyx-changes.css +240 -0
  3. package/CanopyX.Html/Assets/canopyx-changes.js +224 -0
  4. package/CanopyX.Html/Assets/canopyx-detail-rows.js +2 -2
  5. package/CanopyX.Html/Assets/canopyx-filter-editor.js +2 -1
  6. package/CanopyX.Html/Assets/canopyx-formatting.js +7 -6
  7. package/CanopyX.Html/Assets/canopyx-grid-module.mjs +71 -44
  8. package/CanopyX.Html/Assets/canopyx-grid.css +24 -0
  9. package/CanopyX.Html/Assets/canopyx-grid.js +71 -44
  10. package/CanopyX.Html/Assets/canopyx-interactive.js +6 -6
  11. package/CanopyX.Html/Assets/canopyx-labels-de.js +76 -2
  12. package/CanopyX.Html/Assets/canopyx-labels-es.js +76 -2
  13. package/CanopyX.Html/Assets/canopyx-labels-fr.js +76 -2
  14. package/CanopyX.Html/Assets/canopyx-labels-it.js +76 -2
  15. package/CanopyX.Html/Assets/canopyx-labels-pl.js +76 -2
  16. package/CanopyX.Html/Assets/canopyx-live.js +4 -3
  17. package/CanopyX.Html/Assets/canopyx-matrix.css +16 -3
  18. package/CanopyX.Html/Assets/canopyx-matrix.js +22 -14
  19. package/CanopyX.Html/Assets/canopyx-module.mjs +6 -6
  20. package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +73 -46
  21. package/CanopyX.Html/Assets/canopyx-reporting.css +24 -0
  22. package/CanopyX.Html/Assets/canopyx-reporting.js +73 -46
  23. package/CanopyX.Html/Assets/canopyx-tree-tools.js +7 -6
  24. package/CanopyX.Html/Assets/canopyx.js +6 -6
  25. package/JavaScript/changes.d.ts +252 -0
  26. package/JavaScript/grid.d.ts +15 -5
  27. package/JavaScript/locales/de.mjs +76 -2
  28. package/JavaScript/locales/es.mjs +76 -2
  29. package/JavaScript/locales/fr.mjs +76 -2
  30. package/JavaScript/locales/it.mjs +76 -2
  31. package/JavaScript/locales/pl.mjs +76 -2
  32. package/JavaScript/matrix.d.ts +2 -0
  33. package/JavaScript/protocol.d.ts +1 -1
  34. package/README.md +7 -2
  35. package/package.json +5 -1
@@ -0,0 +1,224 @@
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"2db8e138da4b5658",install(context){if(installed.has(context.CHANGES_IMPL))return;const{CHANGES_IMPL,h,setText,fill,plural,numberFormat,gridFreeze,toneOf,applyTone,svgIcon,PROTOCOL}=context;const CHANGE_LABELS={changeInline:"Inline",changeSplit:"Side by side",changeLayout:"Layout",changeCopyDiff:"Copy diff",changeCopied:"Copied",changeCopyFailed:"Could not copy",changeNext:"Next change",changePrevious:"Previous change",changePosition:"Change {index} of {count}",changeShowLines:"[one]Show {count} more line[other]Show {count} more lines",changeHiddenLines:"[one]{count} unchanged line not included[other]{count} unchanged lines not included",changeLineCounts:"Lines added: {added}, removed: {removed}",changeLineAdded:"Added line {line}",changeLineRemoved:"Removed line {line}",changeLineContext:"Line {line}",changeBefore:"Before",changeAfter:"After",changeBinary:"Binary file: compared by size and hash, not by lines.",changeUnavailable:"The content of this file is not available.",changeIdentical:"The text did not change.",changeTruncated:"Some changes are not shown: the comparison was too large.",changeTooLarge:"These texts are too long to compare here.",changeDiffOf:"Changes in {name}",changeAdded:"Added",changeRemoved:"Removed",changeModified:"Modified",changeRenamed:"Renamed",changeFlaggedOnly:"Flagged only",changeFlaggedCount:"{count} flagged",changeNoItems:"No changes to show.",changeSize:"Size",changeHash:"Hash",changeTime:"Time",changeCopyHash:"Copy the full hash",changeDetails:"Details of {name}",changeFileCount:"[one]{count} file[other]{count} files",changeFrom:"from {path}",changeShowMore:"Show more",changeFlags:"Flags",changeFacts:"Details",changeRoot:"Top level",changeShown:"{shown} of {total} shown",changeFilter:"Show changes",changeActions:"Actions for {name}",changeConverged:"Converged",changePropagating:"Propagating",changeFault:"Fault",changeUnknown:"Unknown",changeReplica:"Replica",changeState:"State",changeFirstSeen:"First seen",changeLastSeen:"Last seen",changeTimeline:"Delay",changeNotSeen:"Not seen yet",changeDelay:"+{duration}",changeSource:"Source",changeProgress:"{converged} of {total} have the change",changeSeconds:"{count} s",changeMinutes:"{count} min",changeHours:"{count} h",changeDays:"{count} d",changeUtcTime:"{time} UTC",changeExportCsv:"Export CSV",changeExportJson:"Export JSON",changeReason:"Reason",changeReasonHint:"{action}: say why. The reason is kept with the action.",changeReasonRequired:"Enter a reason first.",changeCancel:"Cancel",changeUnclaimed:"This page cannot carry out “{action}” itself. Copy the request to pass it on.",changeCopyRequest:"Copy request",changeFiltered:"Filtered: {filters}",changePrintLimit:"[one]{count} more line is not printed.[other]{count} more lines are not printed."};function changeLabels(...sources){const labels={...CHANGE_LABELS};for(const source of sources){if(source==null)continue;if(typeof source!=="object"||Array.isArray(source))changeFail("labels must be an object of strings");for(const[name,text]of Object.entries(source)){if(!Object.hasOwn(labels,name)){if(/^change[A-Z]/.test(name)) changeFail("there is no change label '" + name + "'"); continue; }
2
+ if(typeof text!=="string"||!/\S/.test(text)) changeFail("label '" + name + "' must be a string with visible text");
3
+ labels[name]=text}}
4
+ return Object.freeze(labels)}
5
+ function changeFail(message){throw new Error("CanopyX: the change document is not valid: "+message+".")}
6
+ function changeCount(template,count,lang,values){return fill(plural(template,count,lang),values)}
7
+ const changeObject=value=>value!==null&&typeof value==="object"&&!Array.isArray(value);function changeKeys(value,keys,what){if(!changeObject(value))changeFail(what+" must be an object");for(const key of Object.keys(value))if(!keys.includes(key))changeFail(what+" has the unknown property '"+key+"'");return value}
8
+ function changeText(value,what,max,optional){if(value===undefined&&optional)return null;if(typeof value!=="string"||!/\S/.test(value) || value.length > max) changeFail(what + " must be visible text of at most " + max + " characters");
9
+ return value}
10
+ function changeLine(value,what,max){if(value===undefined)return null;if(typeof value!=="string"||value.length>max||/[\r\n]/.test(value)) changeFail(what + " must be one line of at most " + max + " characters");
11
+ return value}
12
+ function changeId(value,what,max=256){if(typeof value!=="string"||!value.length||value.length>max||/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(value)) changeFail(what + " must be a nonempty id of at most " + max + " characters");
13
+ return value}
14
+ function changeTone(value,what){if(value===undefined)return null;if(typeof value!=="string"||!/^[a-z][a-z0-9-]{0,31}$/.test(value) || /-bg$/.test(value)) changeFail(what + " has an invalid tone");
15
+ return value}
16
+ function changeFlag(value,what){if(value!==undefined&&typeof value!=="boolean")changeFail(what+" takes true or false");return value===true}
17
+ function changeInteger(value,what,min,max,fallback){if(value===undefined)return fallback;if(!Number.isInteger(value)||value<min||value>max)changeFail(what+" is a whole number from "+min+" to "+max);return value}
18
+ function changeTime(value,what){if(value===undefined)return null;const time=typeof value==="string"&&value.includes("T")?Date.parse(/(?:Z|[+-]\d\d:\d\d)$/.test(value) ? value : value + "Z") : NaN;
19
+ if(!Number.isFinite(time))changeFail(what+" needs an ISO 8601 date and time");return time}
20
+ function changeEnum(value,names,what,fallback){if(value===undefined)return fallback;if(!names.includes(value))changeFail(what+" has an unknown value");return value}
21
+ function changeList(value,what,max){if(value===undefined)return[];if(!Array.isArray(value)||value.length>max)changeFail(what+" is a list of at most "+max+" items");return value}
22
+ function changePluralText(text){if(!text.startsWith("["))return text.includes("{count}");const forms=[...text.matchAll(/\[(zero|one|two|few|many|other)\]([^[]*)/g)];
23
+ return forms.reduce((sum,form)=>sum+form[0].length,0)===text.length&&forms.some(form=>form[1]==="other")&&forms.every(form=>form[2].includes("{count}"))}
24
+ function changeEnvelope(input,type,keys){const doc=changeKeys(input,["protocol","type",...keys],"the document");if(doc.protocol!==PROTOCOL||doc.type!==type)changeFail("it needs protocol "+PROTOCOL+" and type \""+type+"\"");return doc}
25
+ function changeSplit(text){const lines=text.split(/\r\n|\n|\r/);
26
+ if(lines.length&&lines[lines.length-1]==="")lines.pop();return lines}
27
+ const DIFF_KEYS=["id","label","path","previousPath","content","message","beforeTitle","afterTitle","before","after","hunks","context","layout","notes","truncated","unified","labels"];const DIFF_MAX_LINES=200000,DIFF_MAX_LINE=20000,DIFF_MAX_TEXT=2000000,DIFF_CLIENT_LINES=5000;function diffBody(input,what){const doc=changeKeys(input,what==="the document"?["protocol","type",...DIFF_KEYS]:DIFF_KEYS,what);const content=changeEnum(doc.content,["binary","unavailable","identical"],"content","text");const before=doc.before===undefined?null:doc.before,after=doc.after===undefined?null:doc.after;for(const text of[before,after])if(text!==null&&(typeof text!=="string"||text.length>DIFF_MAX_TEXT))changeFail("a diff text is text of at most "+DIFF_MAX_TEXT+" characters");if((before===null)!==(after===null))changeFail("a diff has both texts or neither");const hunks=changeList(doc.hunks,"hunks",DIFF_MAX_LINES).map(entry=>{const hunk=changeKeys(entry,["before","after","lines"],"a hunk");const lines=changeList(hunk.lines,"a hunk's lines",DIFF_MAX_LINES).map(line=>{if(typeof line!=="string"||!/^[ +-]/.test(line) || line.length > DIFF_MAX_LINE + 1 || /[\r\n]/.test(line)) changeFail("a diff line is one line that starts with \" \", \"-\" or \"+\"");
28
+ return{kind:line[0]===" "?"=":line[0],text:line.slice(1)}});return{before:changeInteger(hunk.before,"a hunk's before",1,2147483647),after:changeInteger(hunk.after,"a hunk's after",1,2147483647),lines}});if(content!=="text"&&(hunks.length||before!==null))changeFail("a diff without text content has no hunks and no texts");if(content==="text"&&!hunks.length&&before===null)changeFail("a text diff needs hunks or both texts");const left=before===null?null:changeSplit(before),right=after===null?null:changeSplit(after);const mismatch=reason=>changeFail("This diff can't be shown: the supplied hunks don't match the file text ("+reason+")");let nextBefore=1,nextAfter=1,total=0;const unchanged=(b,a,count)=>{if(!left||!right)return;if(b+count-1>left.length||a+count-1>right.length)mismatch("the diff's hunks do not account for every difference between its texts");for(let i=0;i<count;i++)if(left[b-1+i]!==right[a-1+i])mismatch("the diff's hunks do not account for every difference between its texts")};for(const hunk of hunks){if(!hunk.lines.length||hunk.before<nextBefore||hunk.after<nextAfter||hunk.before-nextBefore!==hunk.after-nextAfter)changeFail("diff hunks are in order, do not overlap and leave as many unchanged lines on both sides");unchanged(nextBefore,nextAfter,hunk.before-nextBefore);let b=hunk.before,a=hunk.after;for(const line of hunk.lines){if(line.kind!=="+"&&left&&left[b-1]!==line.text||line.kind!=="-"&&right&&right[a-1]!==line.text)mismatch("a hunk's line does not match its text");if(line.kind!=="+")b++;if(line.kind!=="-")a++}
29
+ total +=hunk.lines.length;if(total>DIFF_MAX_LINES)changeFail("a diff holds at most "+DIFF_MAX_LINES+" lines");nextBefore=b;nextAfter=a}
30
+ if(left&&right&&hunks.length&&left.length-nextBefore!==right.length-nextAfter)mismatch("the diff's hunks do not account for every difference between its texts");if(left&&hunks.length)unchanged(nextBefore,nextAfter,left.length-nextBefore+1);const notes=changeList(doc.notes,"notes",16).map(entry=>{const note=changeKeys(entry,["text","tone"],"a note");return Object.freeze({text:changeText(note.text,"a note",500),tone:changeTone(note.tone,"a note")})});if(doc.unified!==undefined&&(typeof doc.unified!=="string"||doc.unified.length>2*DIFF_MAX_TEXT))changeFail("the unified diff is too long");return Object.freeze({id:changeId(doc.id,"the diff id"),label:changeText(doc.label,"the diff label",200),path:changeLine(doc.path,"the path",2048),previousPath:changeLine(doc.previousPath,"the previous path",2048),content,message:changeText(doc.message,"the message",1000,true),beforeTitle:changeText(doc.beforeTitle,"the before title",200,true),afterTitle:changeText(doc.afterTitle,"the after title",200,true),before,after,left,right,hunks,context:changeInteger(doc.context,"context",0,20,3),layout:changeEnum(doc.layout,["inline","split"],"layout","auto"),notes,truncated:changeFlag(doc.truncated,"truncated"),unified:doc.unified??null,labels:doc.labels})}
31
+ function diffDocument(input){changeEnvelope(input,"diff",DIFF_KEYS);return diffBody(input,"the document")}
32
+ const CHANGE_KINDS=["added","removed","modified","renamed"],CHANGE_ICONS=["none","copy","diff","check","open","download","compare","flag"];const PROPAGATION_STATES=["converged","propagating","fault","unknown"];function changeVersion(value,what){if(value===undefined)return null;const version=changeKeys(value,["size","hash","time"],what);if(version.size!==undefined&&(!Number.isSafeInteger(version.size)||version.size<0))changeFail(what+" has a size outside 0 to 2^53 - 1");const hash=changeLine(version.hash,what+" hash",256);if(hash!==null&&!/\S/.test(hash)) changeFail(what + " has an empty hash");
33
+ return Object.freeze({size:version.size??null,hash,time:changeTime(version.time,what+" time")})}
34
+ function changeAction(entry,ids){const action=changeKeys(entry,["id","label","icon","copy","link","reveal","reason"],"an action"),id=changeId(action.id,"an action id",64);if(ids.has(id))changeFail("the action '"+id+"' is declared twice");ids.add(id);for(const key of["copy","link"])if(action[key]!==undefined&&(typeof action[key]!=="string"||!action[key].length||action[key].length>64))changeFail("the action '"+id+"' "+key+"s a value key of 1 to 64 characters");const reveal=changeEnum(action.reveal,["none","details","diff"],"an action reveal","none"),reason=changeFlag(action.reason,"reason");const kinds=(action.copy!==undefined)+(action.link!==undefined)+(reveal!=="none");if(kinds>1||reason&&kinds)changeFail("the action '"+id+"' copies, opens a link, reveals or asks for a reason: one of them");return Object.freeze({id,label:changeText(action.label,"the label of action '"+id+"'",64),icon:changeEnum(action.icon,CHANGE_ICONS,"an action icon","none"),copy:action.copy??null,link:action.link??null,reveal,reason})}
35
+ function changeValues(input,what){const values=Object.create(null);if(input===undefined)return Object.freeze(values);if(!changeObject(input)||Object.keys(input).length>16)changeFail(what+" has at most 16 values");for(const[key,value]of Object.entries(input)){if(!key.length||key.length>64||typeof value!=="string"||value.length>4096)changeFail(what+" has an invalid value");values[key]=value}
36
+ return Object.freeze(values)}
37
+ function changeDiffChecked(diff){changeLabels(diff.labels);return diff}
38
+ function changesDocument(input){const doc=changeEnvelope(input,"changes",["id","label","actions","toolbar","values","exports","items","groupByFolder","flaggedOnly","expandFlagged","hashLength","pageSize","timeZone","labels"]);changeLabels(doc.labels);const actionIds=new Set(),actions=changeList(doc.actions,"actions",8).map(entry=>changeAction(entry,actionIds));const barIds=new Set(actionIds),listValues=changeValues(doc.values,"the change list"),toolbar=changeList(doc.toolbar,"toolbar",8).map(entry=>{const action=changeAction(entry,barIds),key=action.copy??action.link;if(action.reveal!=="none")changeFail("the bar action '"+action.id+"' has no item to reveal");if(key!==null&&!Object.hasOwn(listValues,key))changeFail("the bar action '"+action.id+"' uses the list value '"+key+"', which the list does not have");return action});const exports=changeList(doc.exports,"exports",2);if(exports.some(name=>name!=="csv"&&name!=="json")||new Set(exports).size!==exports.length)changeFail("exports are csv and json, each once");const ids=new Set(),items=changeList(doc.items,"items",10000).map((entry,index)=>{const item=changeKeys(entry,["id","kind","path","previousPath","kindLabel","secondary","before","after","facts","flags","diff","actions","values"],"an item");const id=changeId(item.id,"an item id"),what="the item '"+id+"'";if(ids.has(id))changeFail(what+" appears twice");ids.add(id);const kind=changeEnum(item.kind,CHANGE_KINDS,what+" kind"),path=changeLine(item.path,what+" path",2048),previousPath=changeLine(item.previousPath,what+" previous path",2048);if(kind===undefined||path===null||!/\S/.test(path)) changeFail(what + " needs a kind and a path");
39
+ if(kind==="renamed"&&previousPath===null||(kind==="added"||kind==="removed")&&previousPath!==null)changeFail(what+" has a previous path only when it is renamed or modified, and always when renamed");const facts=changeList(item.facts,what+" facts",32).map(value=>{const fact=changeKeys(value,["label","before","after","tone","note"],"a fact");if(fact.before===undefined&&fact.after===undefined)changeFail(what+" has a fact without a value");return Object.freeze({label:changeText(fact.label,"a fact label",200),before:changeText(fact.before,"a fact value",2000,true),after:changeText(fact.after,"a fact value",2000,true),tone:changeTone(fact.tone,"a fact"),note:changeText(fact.note,"a fact note",500,true)})});const flagIds=new Set(),flags=changeList(item.flags,what+" flags",16).map(value=>{const flag=changeKeys(value,["id","label","reason","tone"],"a flag"),flagId=changeId(flag.id,"a flag id",64);if(flagIds.has(flagId))changeFail(what+" has the flag '"+flagId+"' twice");flagIds.add(flagId);return Object.freeze({id:flagId,label:changeText(flag.label,"a flag label",64),reason:changeText(flag.reason,"a flag reason",500),tone:changeTone(flag.tone,"a flag")||"warning"})});const offered=changeList(item.actions,what+" actions",8);for(const action of offered)if(!actionIds.has(action))changeFail(what+" offers the undeclared action '"+action+"'");const values=changeValues(item.values,what);return Object.freeze({id,index,kind,path,previousPath,kindLabel:changeText(item.kindLabel,what+" kind label",64,true),secondary:changeText(item.secondary,what+" secondary text",200,true),before:changeVersion(item.before,what+" before"),after:changeVersion(item.after,what+" after"),facts:Object.freeze(facts),flags:Object.freeze(flags),diff:item.diff===undefined?null:changeDiffChecked(diffBody(item.diff,what+" diff")),actions:Object.freeze([...offered]),values})});return Object.freeze({id:changeId(doc.id,"the change list id"),label:changeText(doc.label,"the label",200),actions:Object.freeze(actions),toolbar:Object.freeze(toolbar),values:listValues,exports:Object.freeze([...exports]),items:Object.freeze(items),groupByFolder:doc.groupByFolder!==false&&changeFlag(doc.groupByFolder??true,"groupByFolder"),flaggedOnly:changeFlag(doc.flaggedOnly,"flaggedOnly"),expandFlagged:changeFlag(doc.expandFlagged,"expandFlagged"),hashLength:changeInteger(doc.hashLength,"hashLength",4,64,8),pageSize:changeInteger(doc.pageSize,"pageSize",10,1000,200),local:changeEnum(doc.timeZone,["utc","local"],"timeZone","utc")==="local",labels:doc.labels})}
40
+ function propagationDocument(input){const doc=changeEnvelope(input,"propagation",["id","label","start","asOf","state","summary","title","groupCount","witnesses","timeZone","labels"]);const start=changeTime(doc.start,"start"),asOf=changeTime(doc.asOf,"asOf");if(start!==null&&asOf!==null&&start>asOf)changeFail("a propagation view starts before its evidence time");const groupCount=changeText(doc.groupCount,"the group count",200,true);if(groupCount!==null&&!changePluralText(groupCount))changeFail("the group count needs {count} in every form, and plural forms that end with [other]");const ids=new Set(),witnesses=changeList(doc.witnesses,"witnesses",2000).map(entry=>{const witness=changeKeys(entry,["id","label","secondary","state","firstSeen","lastSeen","note","source","group"],"a replica"),id=changeId(witness.id,"a replica id");if(ids.has(id))changeFail("the replica '"+id+"' appears twice");ids.add(id);const firstSeen=changeTime(witness.firstSeen,"firstSeen"),lastSeen=changeTime(witness.lastSeen,"lastSeen");if(firstSeen!==null&&lastSeen!==null&&firstSeen>lastSeen)changeFail("the replica '"+id+"' was first seen after it last reported");const state=changeEnum(witness.state,PROPAGATION_STATES,"a replica state");if(state===undefined)changeFail("the replica '"+id+"' needs a state");return Object.freeze({id,label:changeText(witness.label,"a replica label",200),secondary:changeText(witness.secondary,"a replica's secondary text",200,true),state,firstSeen,lastSeen,note:changeText(witness.note,"a replica note",500,true),source:changeFlag(witness.source,"source"),group:changeText(witness.group,"a replica group",200,true)})});if(!witnesses.length)changeFail("a propagation view has 1 to 2000 replicas");return Object.freeze({id:changeId(doc.id,"the propagation id"),label:changeText(doc.label,"the label",200),start,asOf,state:changeEnum(doc.state,PROPAGATION_STATES,"state",null),summary:changeText(doc.summary,"the summary",500,true),title:changeText(doc.title,"the title",200,true),groupCount,witnesses:Object.freeze(witnesses),local:changeEnum(doc.timeZone,["utc","local"],"timeZone","utc")==="local",labels:doc.labels})}
41
+ const CHANGE_UNITS=["B","KB","MB","GB","TB"];function changeSize(bytes,lang){if(bytes<1024)return numberFormat(lang,0,0).format(bytes)+ " B";let value=bytes,unit=0;while(value>=1024&&unit<CHANGE_UNITS.length-1){value /= 1024; unit++; }
42
+ return numberFormat(lang,0,1).format(value)+ " "+CHANGE_UNITS[unit]}
43
+ const changePad=value=>(value<10?"0":"")+ value;function changeTimeText(ms,local,labels){const d=new Date(ms),get=local?["getFullYear","getMonth","getDate","getHours","getMinutes"]:["getUTCFullYear","getUTCMonth","getUTCDate","getUTCHours","getUTCMinutes"];const[year,month,day,hours,minutes]=get.map(name=>d[name]());const text=String(year).padStart(4,"0")+ "-"+changePad(month+1)+ "-"+changePad(day)+ " "+changePad(hours)+ ":"+changePad(minutes);return local?text:fill(labels.changeUtcTime,{time:text})}
44
+ function changeDuration(ms,labels,format){const seconds=Math.max(0,Math.floor(ms / 1000)), unit = (template, count) => fill(template, { count: format.format(count) });
45
+ if(seconds<60)return unit(labels.changeSeconds,seconds);const minutes=Math.floor(seconds / 60);
46
+ if(minutes<60)return unit(labels.changeMinutes,minutes);const hours=Math.floor(minutes / 60);
47
+ if(hours<24)return unit(labels.changeHours,hours)+(minutes%60?" "+unit(labels.changeMinutes,minutes%60):"");return unit(labels.changeDays,Math.floor(hours / 24)) + (hours % 24 ? " " + unit(labels.changeHours, hours % 24) : "");
48
+ }
49
+ function changePath(path){const at=Math.max(path.lastIndexOf("/"),path.lastIndexOf("\\"));return at<0?["",path]:[path.slice(0,at),path.slice(at+1)]}
50
+ const CHANGE_TONES={added:"success",removed:"danger",modified:"info",renamed:"neutral"};const PROPAGATION_LOOK={converged:["check","success"],propagating:["clock","warning"],fault:["cross","danger"],unknown:["question","muted"]};const CHANGE_ICONS_SVG={copy:'<rect x="8" y="8" width="11" height="11" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/>',diff:'<path d="M4 5h7M4 9h5M4 13h7M4 17h4M13 5h7M15 9h5M13 13h7M16 17h4"/>',check:'<path d="m5 12.5 4.5 4.5L19 7.5"/>',open:'<path d="M14 4h6v6M20 4l-9 9M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>',download:'<path d="M12 4v11M7.5 10.5 12 15l4.5-4.5M5 19h14"/>',compare:'<path d="M7 7h12M15 3l4 4-4 4M17 17H5M9 13l-4 4 4 4"/>',flag:'<path d="M5 21V4M5 4h11l-2 4 2 4H5"/>',cross:'<path d="M7 7l10 10M17 7 7 17"/>',clock:'<circle cx="12" cy="12" r="8"/><path d="M12 8v4.5l3 2"/>',question:'<path d="M9.5 9a2.5 2.5 0 1 1 3.5 2.3c-.6.3-1 .9-1 1.6v.6"/><circle cx="12" cy="17" r=".6"/>',chevron:'<path d="m9 6 6 6-6 6"/>',up:'<path d="M12 19V5M6 11l6-6 6 6"/>',down:'<path d="M12 5v14M6 13l6 6 6-6"/>'};function changeTips(root,signal){root.addEventListener("keydown",event=>{if(event.key==="Escape")event.target.closest?.("[data-cx-tip]")?.setAttribute("data-cx-tip-off","")},{signal});for(const type of["focusout","pointerout"])root.addEventListener(type,event=>event.target.closest?.("[data-cx-tip]")?.removeAttribute("data-cx-tip-off"),{signal})}
51
+ function changeIcon(name){const icon=svgIcon(CHANGE_ICONS_SVG[name]||CHANGE_ICONS_SVG.check);icon.classList.add("cx-chg-ico");return icon}
52
+ const DIFF_MAX_EDITS=2000,DIFF_MAX_WORD_EDITS=200,DIFF_MAX_TOKENS=1000;function diffReplace(script,n,m,from){for(let i=0;i<n;i++)script.push(["-",from+i,-1]);for(let j=0;j<m;j++)script.push(["+",-1,from+j])}
53
+ function diffMyers(a,b,offset,n,m,maxEdits){const limit=Math.min(n+m,maxEdits),v=new Int32Array(2*limit+3),zero=limit+1,trace=[];let found=-1;for(let d=0;d<=limit&&found<0;d++){trace.push(d>0?v.slice(zero -(d-1),zero+d):new Int32Array(0));for(let k=-d;k<=d;k +=2){let x=k===-d||k!==d&&v[zero+k-1]<v[zero+k+1]?v[zero+k+1]:v[zero+k-1]+ 1,y=x-k;while(x<n&&y<m&&a[offset+x]===b[offset+y]){x++;y++}
54
+ v[zero+k]=x;if(x>=n&&y>=m){found=d;break}}}
55
+ if(found<0)return null;const reversed=[];let px=n,py=m;for(let d=found;d>0;d--){const previous=trace[d],get=key=>previous[key+d-1],k=px-py;const prevK=k===-d||k!==d&&get(k-1)<get(k+1)?k+1:k-1,prevX=get(prevK),prevY=prevX-prevK;while(px>prevX&&py>prevY){reversed.push(["=",offset+px-1,offset+py-1]);px--;py--}
56
+ reversed.push(px===prevX?["+",-1,offset+py-1]:["-",offset+px-1,-1]);px=prevX;py=prevY}
57
+ while(px>0&&py>0){reversed.push(["=",offset+px-1,offset+py-1]);px--;py--}
58
+ return reversed.reverse()}
59
+ function diffScript(a,b,maxEdits){const n=a.length,m=b.length;let prefix=0,suffix=0;while(prefix<n&&prefix<m&&a[prefix]===b[prefix])prefix++;while(suffix<n-prefix&&suffix<m-prefix&&a[n-1-suffix]===b[m-1-suffix])suffix++;const script=[];for(let i=0;i<prefix;i++)script.push(["=",i,i]);const bigN=n-prefix-suffix,bigM=m-prefix-suffix;if(!bigN||!bigM)diffReplace(script,bigN,bigM,prefix);else{const middle=diffMyers(a,b,prefix,bigN,bigM,maxEdits);if(middle)script.push(...middle);else if(maxEdits<DIFF_MAX_EDITS)return null;else diffReplace(script,bigN,bigM,prefix)}
60
+ for(let i=0;i<suffix;i++)script.push(["=",n-suffix+i,m-suffix+i]);const ordered=[],added=[];for(const entry of script){if(entry[0]==="="){ordered.push(...added,entry);added.length=0}
61
+ else if(entry[0]==="-")ordered.push(entry);else added.push(entry)}
62
+ ordered.push(...added);return ordered}
63
+ function diffHunks(left,right,context){const script=diffScript(left,right,DIFF_MAX_EDITS),changes=[],hunks=[];script.forEach((entry,index)=>{if(entry[0]!=="=")changes.push(index)});for(let at=0;at<changes.length;){let last=changes[at];const first=last;while(at+1<changes.length&&changes[at+1]- last-1<=2*context)last=changes[++at];at++;const from=Math.max(0,first-context),to=Math.min(script.length-1,last+context);let before=1,after=1;for(let i=0;i<from;i++){if(script[i][0]!=="+")before++;if(script[i][0]!=="-")after++}
64
+ const lines=[];for(let i=from;i<=to;i++){const[op,b,a]=script[i];lines.push({kind:op,text:op==="+"?right[a]:left[b]})}
65
+ hunks.push({before,after,lines})}
66
+ return hunks}
67
+ const DIFF_TOKENS=/[\p{L}\p{N}_]+|[\t-\r\u0085\u2028\u2029\p{Zs}]+|[^\p{L}\p{N}_\t-\r\u0085\u2028\u2029\p{Zs}]/gu;
68
+ function diffWords(before,after){const left=before.match(DIFF_TOKENS)||[],right=after.match(DIFF_TOKENS)||[];if(left.length>DIFF_MAX_TOKENS||right.length>DIFF_MAX_TOKENS)return null;const script=diffScript(left,right,DIFF_MAX_WORD_EDITS);if(!script)return null;const removed=[],added=[],extend=(ranges,start,end)=>{const last=ranges[ranges.length-1];if(last&&last[1]===start)last[1]=end;else ranges.push([start,end])};let same=0,beforeAt=0,afterAt=0;for(const[op,b,a]of script){if(op==="="){same +=left[b].length;beforeAt +=left[b].length;afterAt +=right[a].length}
69
+ else if(op==="-"){extend(removed,beforeAt,beforeAt+left[b].length);beforeAt +=left[b].length}
70
+ else{extend(added,afterAt,afterAt+right[a].length);afterAt +=right[a].length}}
71
+ const longest=Math.max(before.length,after.length);return!longest||same*100<longest*35?null:{before:removed,after:added}}
72
+ function diffLine(kind,text,b,a){return{t:"line",kind,text,b,a,partner:null,marks:undefined}}
73
+ function diffGap(count,b,a,left,lines){if(!lines&&left){lines=[];for(let i=0;i<count;i++)lines.push(diffLine("=",left[b-1+i],b+i,a+i))}
74
+ return{t:"gap",count,b,a,lines:lines||null}}
75
+ function diffFold(lines,rows,context){for(let i=0;i<lines.length;){if(lines[i].kind!=="="){rows.push(lines[i++]);continue}
76
+ const start=i;while(i<lines.length&&lines[i].kind==="=")i++;const length=i-start,keepStart=start===0?0:context,keepEnd=i===lines.length?0:context;if(length<=keepStart+keepEnd+1){rows.push(...lines.slice(start,i));continue}
77
+ rows.push(...lines.slice(start,start+keepStart));const hidden=lines.slice(start+keepStart,i-keepEnd);rows.push(diffGap(hidden.length,hidden[0].b,hidden[0].a,null,hidden));rows.push(...lines.slice(i-keepEnd,i))}}
78
+ function diffRows(doc,hunks){const rows=[];let nextBefore=1,nextAfter=1,added=0,removed=0;for(const hunk of hunks){if(hunk.before>nextBefore)rows.push(diffGap(hunk.before-nextBefore,nextBefore,nextAfter,doc.left));const lines=[];let b=hunk.before,a=hunk.after;for(const line of hunk.lines){lines.push(diffLine(line.kind,line.text,line.kind!=="+"?b++:null,line.kind!=="-"?a++:null));if(line.kind==="+")added++;else if(line.kind==="-")removed++}
79
+ for(let i=0;i<lines.length;){if(lines[i].kind==="="){i++;continue}
80
+ const from=i;while(i<lines.length&&lines[i].kind==="-")i++;const middle=i;while(i<lines.length&&lines[i].kind==="+")i++;for(let k=0;k<Math.min(middle-from,i-middle);k++){lines[from+k].partner=lines[middle+k];lines[middle+k].partner=lines[from+k]}}
81
+ diffFold(lines,rows,doc.context);nextBefore=b;nextAfter=a}
82
+ if(doc.left&&hunks.length&&doc.left.length>=nextBefore)rows.push(diffGap(doc.left.length-nextBefore+1,nextBefore,nextAfter,doc.left));return{rows,added,removed}}
83
+ function diffMarks(row){if(row.marks===undefined){const words=row.partner?(row.kind==="-"?diffWords(row.text,row.partner.text):diffWords(row.partner.text,row.text)):null;row.marks=words?(row.kind==="-"?words.before:words.after):null;if(row.partner)row.partner.marks=words?(row.kind==="-"?words.after:words.before):null}
84
+ return row.marks}
85
+ function diffPairs(rows){const pairs=[];for(let i=0;i<rows.length;){const row=rows[i];if(row.t==="gap"||row.kind==="="){pairs.push({t:row.t==="gap"?"gap":"pair",gap:row.t==="gap"?row:null,left:row.t==="gap"?null:row,right:row.t==="gap"?null:row});i++;continue}
86
+ const removed=[],added=[];while(i<rows.length&&rows[i].t==="line"&&rows[i].kind==="-")removed.push(rows[i++]);while(i<rows.length&&rows[i].t==="line"&&rows[i].kind==="+")added.push(rows[i++]);for(let k=0;k<Math.max(removed.length,added.length);k++)pairs.push({t:"pair",gap:null,left:removed[k]||null,right:added[k]||null})}
87
+ return pairs}
88
+ function diffChanged(row){return row.t==="line"?row.kind!=="=":row.t==="pair"&&(row.left!==row.right)}
89
+ function diffUnified(doc,hunks){if(doc.unified!==null)return doc.unified;const beforeOpen=!!doc.before&&!/[\r\n]$/.test(doc.before), afterOpen = !!doc.after && !/[\r\n]$/.test(doc.after);
90
+ const last=hunks[hunks.length-1],left=doc.left,right=doc.right;if(left?.length&&right?.length&&beforeOpen!==afterOpen&&left[left.length-1]===right[right.length-1]&&(!last||
91
+ last.before+last.lines.filter(line=>line.kind!=="+").length<=left.length&&last.after+last.lines.filter(line=>line.kind!=="-").length<=right.length))
92
+ hunks=hunks.concat([{before:left.length,after:right.length,lines:[{kind:"=",text:left[left.length-1]}]}]);if(!hunks.length)return "";const name=doc.path||doc.label,range=(start,count)=>(count===0?start-1:start)+(count===1?"":","+count);let text="--- a/" +(doc.previousPath||name)+ "\n+++ b/"+name+"\n";const write=(kind,line,open)=>{text +=kind+line+"\n";if(open)text +="\\n"};for(const hunk of hunks){const before=hunk.lines.filter(line=>line.kind!=="+").length,after=hunk.lines.filter(line=>line.kind!=="-").length;text +="@@ -"+range(hunk.before,before)+ " +"+range(hunk.after,after)+ " @@\n";let b=hunk.before,a=hunk.after;for(const line of hunk.lines){const openBefore=line.kind!=="+"&&beforeOpen&&b===left.length,openAfter=line.kind!=="-"&&afterOpen&&a===right.length;if(line.kind==="="&&openBefore!==openAfter){write("-",line.text,openBefore);write("+",line.text,openAfter)}
93
+ else write(line.kind==="="?" ":line.kind,line.text,openBefore||openAfter);if(line.kind!=="+")b++;if(line.kind!=="-")a++}}
94
+ return text}
95
+ const DIFF_ROW=20,DIFF_OVERSCAN=30,DIFF_SPLIT_WIDTH=900;let changesPrinting=false;global.addEventListener("beforeprint",()=>{changesPrinting=true});global.addEventListener("afterprint",()=>{changesPrinting=false});function changesInPrint(){return changesPrinting||!!(global.matchMedia&&global.matchMedia("print").matches)}
96
+ class DiffView{constructor(host,options,doc){this.host=host;this.options=options;this.lifetime=new AbortController();this.doc=doc||diffDocument(options.view);this.labels=changeLabels(options.baseLabels,this.doc.labels,options.labels);this.lang=options.lang||host.closest("[lang]")?.getAttribute("lang")||document.documentElement.lang||"en";this.format=numberFormat(this.lang);this.mounted=new Map();this.pool=[];this.current=-1;this.chosen=this.doc.layout==="auto"?null:this.doc.layout;this.prepare();this.build()}
97
+ emit(name,detail,callback){const event=new CustomEvent("canopyx:"+name,{detail,bubbles:true});this.host.dispatchEvent(event);if(typeof this.options[callback]==="function"){try{this.options[callback](detail,event)}catch(error){this.host.dispatchEvent(new CustomEvent("canopyx:error",{detail:{error,hook:callback},bubbles:true}))}}}
98
+ prepare(){const doc=this.doc;this.tooLarge=doc.content==="text"&&!doc.hunks.length&&(doc.left.length>DIFF_CLIENT_LINES||doc.right.length>DIFF_CLIENT_LINES);this.hunks=doc.content!=="text"||this.tooLarge?[]:doc.hunks.length?doc.hunks:diffHunks(doc.left,doc.right,doc.context);const built=diffRows(doc,this.hunks);this.rows=built.rows;this.added=built.added;this.removed=built.removed}
99
+ build(){const doc=this.doc,labels=this.labels,signal=this.lifetime.signal;if(!this.host.closest("[data-cx-theme]")){this.host.setAttribute("data-cx-theme","auto");this.addedTheme=true}
100
+ const root=this.root=h("div","cx-diff");const head=h("div","cx-diff-head"),title=this.titleBox=h("div","cx-diff-title"),tools=this.tools=h("div","cx-diff-tools");head.append(title,tools);this.notes=h("div","cx-diff-notes");this.state=h("div","cx-diff-state");this.state.hidden=true;const scroll=this.scroll=h("div","cx-diff-scroll");scroll.tabIndex=0;scroll.setAttribute("role","table");this.columns=h("div","cx-diff-columns");this.columns.setAttribute("role","row");this.columns.setAttribute("aria-rowindex","1");this.canvas=h("div","cx-diff-canvas");this.canvas.setAttribute("role","rowgroup");scroll.append(this.columns,this.canvas);this.live=h("div","cx-sr-only");this.live.setAttribute("aria-live","polite");this.print=h("div","cx-diff-print");const frame=this.frameBox=h("div","cx-diff-frame");this.edgeStart=h("div","cx-diff-edge cx-diff-edge-start");this.edgeEnd=h("div","cx-diff-edge cx-diff-edge-end");this.edgeStart.hidden=this.edgeEnd.hidden=true;frame.append(scroll,this.edgeStart,this.edgeEnd);root.append(head,this.notes,this.state,frame,this.live,this.print);this.host.appendChild(root);this.probe=h("span","cx-diff-probe");setText(this.probe,"0000000000");root.appendChild(this.probe);this.buildTools();changeTips(root,signal);scroll.addEventListener("scroll",()=>{this.schedule();this.edges()},{passive:true,signal});scroll.addEventListener("keydown",event=>this.onKey(event),{signal});scroll.addEventListener("click",event=>this.onClick(event),{signal});global.addEventListener("beforeprint",()=>this.renderPrint(),{signal});this.resize=new ResizeObserver(()=>{if(!this.destroyed)this.layout()});this.resize.observe(root);this.refresh();if(changesInPrint())this.renderPrint()}
101
+ buildTools(){const labels=this.labels,tools=this.tools,signal=this.lifetime.signal;const group=this.layoutGroup=h("div","cx-diff-seg");group.setAttribute("role","radiogroup");group.setAttribute("aria-label",labels.changeLayout);this.layoutButtons=["inline","split"].map(mode=>{const button=h("button","cx-diff-segbtn");button.type="button";button.setAttribute("role","radio");button._mode=mode;setText(button,mode==="inline"?labels.changeInline:labels.changeSplit);button.addEventListener("click",()=>this.setLayout(mode),{signal});button.addEventListener("keydown",event=>{if(!["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"].includes(event.key))return;event.preventDefault();const next=mode==="inline"?"split":"inline";this.setLayout(next);this.layoutButtons.find(item=>item._mode===next).focus()},{signal});group.appendChild(button);return button});const button=(label,icon,action,text)=>{const element=h("button","cx-diff-btn");element.type="button";element.appendChild(changeIcon(icon));if(text){const name=h("span");setText(name,label);element.appendChild(name)}else{element.setAttribute("aria-label",label);element.setAttribute("data-cx-tip",label)}
102
+ element.addEventListener("click",action,{signal});return element};this.prevButton=button(labels.changePrevious,"up",()=>this.move(-1));this.nextButton=button(labels.changeNext,"down",()=>this.move(1));this.copyButton=button(labels.changeCopyDiff,"copy",()=>this.copy(),true);tools.append(group,this.prevButton,this.nextButton,this.copyButton)}
103
+ refresh(){const doc=this.doc,labels=this.labels,title=this.titleBox;title.replaceChildren();const name=h("span","cx-diff-path");setText(name,doc.path?(doc.previousPath&&doc.previousPath!==doc.path?doc.previousPath+" → "+doc.path:doc.path):doc.label);title.appendChild(name);if(doc.content==="text"&&!this.tooLarge){const stats=h("span","cx-diff-stats"),plus=h("span","cx-diff-plus"),minus=h("span","cx-diff-minus");setText(plus,"+"+this.format.format(this.added));setText(minus,"−"+this.format.format(this.removed));const spoken=h("span","cx-sr-only");setText(spoken,fill(labels.changeLineCounts,{added:this.format.format(this.added),removed:this.format.format(this.removed)}));plus.setAttribute("aria-hidden","true");minus.setAttribute("aria-hidden","true");stats.append(plus,minus,spoken);title.appendChild(stats)}
104
+ const notes=[];for(const note of doc.notes)notes.push([note.text,note.tone||"info"]);if(doc.truncated)notes.push([labels.changeTruncated,"warning"]);if(doc.message&&doc.content==="text")notes.push([doc.message,"info"]);this.notes.replaceChildren(...notes.map(([text,tone])=>{const note=h("div","cx-diff-note");applyTone(note,"cx-diff-note",toneOf(tone));setText(note,text);return note}));this.notes.hidden=!notes.length;const empty=doc.content!=="text"?doc.message||(doc.content==="binary"?labels.changeBinary:doc.content==="identical"?labels.changeIdentical:labels.changeUnavailable)
105
+ :this.tooLarge?labels.changeTooLarge:!this.rows.some(row=>row.t==="line"&&row.kind!=="=")?labels.changeIdentical:null;this.state.hidden=empty===null;setText(this.state,empty||"");this.state.className="cx-diff-state cx-diff-state-" +(this.tooLarge?"unavailable":doc.content);this.scroll.hidden=!this.rows.some(row=>row.t==="line"&&row.kind!=="=");this.tools.hidden=this.scroll.hidden;this.frameBox.hidden=this.scroll.hidden;this.scroll.setAttribute("aria-label",fill(labels.changeDiffOf,{name:doc.label}));this.layout(true)}
106
+ mode(){return this.chosen||(this.root.clientWidth>=DIFF_SPLIT_WIDTH?"split":"inline")}
107
+ setLayout(mode){if(mode!=="inline"&&mode!=="split")throw new Error("CanopyX: a diff layout is \"inline\" or \"split\".");this.chosen=mode;this.layout();this.emit("diffLayout",{diff:this.doc.id,layout:mode},"onLayout")}
108
+ layout(force){const mode=this.mode();for(const button of this.layoutButtons){const on=button._mode===mode;button.setAttribute("aria-checked",String(on));button.tabIndex=on?0:-1}
109
+ if(!force&&mode===this.shown&&this.width===this.scroll.clientWidth)return;const changed=mode!==this.shown;this.shown=mode;this.width=this.scroll.clientWidth;if(changed||force)this.display();else this.size()}}
110
+ Object.assign(DiffView.prototype,{display(){const split=this.shown==="split",labels=this.labels;this.view=split?diffPairs(this.rows):this.rows;this.starts=[];this.view.forEach((row,index)=>{if(diffChanged(row)&&(index===0||!diffChanged(this.view[index-1])))this.starts.push(index)});if(this.current>=this.starts.length)this.current=this.starts.length-1;this.root.classList.toggle("cx-diff-split",split);this.columns.hidden=!split;if(split){const before=h("span","cx-diff-colhead"),after=h("span","cx-diff-colhead");before.setAttribute("role","columnheader");after.setAttribute("role","columnheader");setText(before,this.doc.beforeTitle||labels.changeBefore);setText(after,this.doc.afterTitle||labels.changeAfter);this.columns.replaceChildren(before,after)}
111
+ let digits=1,chars=1;for(const row of this.rows){for(const line of row.t==="gap"?[]:[row]){digits=Math.max(digits,String(line.b||line.a||1).length);chars=Math.max(chars,line.text.length +(line.text.match(/\t/g)?.length || 0) * 3);
112
+ }
113
+ if(row.t==="gap")digits=Math.max(digits,String(row.b+row.count).length)}
114
+ this.digits=digits;this.chars=Math.min(chars,20000);this.scroll.setAttribute("aria-rowcount",String(this.view.length+1));this.root.style.setProperty("--cx-diff-num",(digits+2)+ "ch");for(const row of this.mounted.values())this.release(row);this.mounted.clear();this.size()},size(){const cw=this.probe.getBoundingClientRect().width / 10 || 7.5, number = (this.digits + 2) * cw, code = (this.chars + 1) * cw + 32;
115
+ const needed=this.shown==="split"?2*(number+code):2*number+2*cw+code;this.canvas.style.height=this.view.length*DIFF_ROW+"px";this.canvas.style.width=Math.max(this.scroll.clientWidth,Math.ceil(needed))+ "px";this.columns.style.width=this.canvas.style.width;this.render();this.edges()},edges(){if(!this.edgeEnd)return;const scroll=this.scroll,max=scroll.scrollWidth-scroll.clientWidth,at=Math.abs(scroll.scrollLeft);const style=getComputedStyle(scroll),borders=(parseFloat(style.borderInlineStartWidth)||0)+(parseFloat(style.borderInlineEndWidth)||0);this.frameBox.style.setProperty("--cx-diff-bar-y",Math.max(0,scroll.offsetWidth-scroll.clientWidth-borders)+ "px");this.frameBox.style.setProperty("--cx-diff-bar-x",Math.max(0,scroll.offsetHeight-scroll.clientHeight -(parseFloat(style.borderTopWidth)||0)-(parseFloat(style.borderBottomWidth)||0))+ "px");this.frameBox.style.setProperty("--cx-diff-head",(this.shown==="split"?this.columns.offsetHeight:0)+ "px");this.edgeStart.hidden=scroll.hidden||at<=1;this.edgeEnd.hidden=scroll.hidden||max-at<=1},schedule(){if(this.destroyed||this.frame)return;this.frame=requestAnimationFrame(()=>{this.frame=0;this.render()})},render(){if(this.destroyed||this.scroll.hidden)return;const top=this.scroll.scrollTop-this.canvas.offsetTop,height=this.scroll.clientHeight||600;const first=Math.max(0,Math.floor(top / DIFF_ROW) - DIFF_OVERSCAN), last = Math.min(this.view.length - 1, Math.ceil((top + height) / DIFF_ROW) + DIFF_OVERSCAN);
116
+ for(const[index,element]of this.mounted)if(index<first||index>last||element._row!==this.view[index]){this.release(element);this.mounted.delete(index)}
117
+ const from=this.current>=0?this.starts[this.current]:-1;let to=from;while(to>=0&&to<this.view.length&&diffChanged(this.view[to]))to++;for(let index=first;index<=last;index++){let element=this.mounted.get(index);if(!element){element=this.pool.pop()||h("div");this.fill(element,this.view[index],index);this.canvas.appendChild(element);this.mounted.set(index,element)}
118
+ element.classList.toggle("cx-diff-current",index>=from&&index<to)}},release(element){element.remove();element._row=null;if(this.pool.length<200)this.pool.push(element)},fill(element,row,index){element._row=row;element._index=index;element.replaceChildren();element.style.transform="translateY("+index*DIFF_ROW+"px)";element.setAttribute("role","row");element.setAttribute("aria-rowindex",String(index+2));if(row.t==="gap"){element.className="cx-diff-row cx-diff-gaprow";element.appendChild(this.gapCell(row.gap||row));return}
119
+ if(row.t==="line"){element.className="cx-diff-row " +(row.kind==="-"?"cx-diff-del":row.kind==="+"?"cx-diff-add":"cx-diff-ctx");element.append(this.numberCell(row.b),this.numberCell(row.a),this.signCell(row.kind),this.codeCell(row));return}
120
+ element.className="cx-diff-row cx-diff-pairrow";for(const[line,side]of[[row.left,"b"],[row.right,"a"]]){const changed=line&&line.kind!=="="?(line.kind==="-"?" cx-diff-del":" cx-diff-add"):"";const number=this.numberCell(line?line[side]:null),code=line?this.codeCell(line):h("span","cx-diff-code cx-diff-blank");number.className +=changed;code.className +=changed;if(!line)code.setAttribute("role","cell");element.append(number,code)}},numberCell(value){const cell=h("span","cx-diff-num");cell.setAttribute("role","cell");if(value!=null)setText(cell,value);return cell},signCell(kind){const cell=h("span","cx-diff-sign");cell.setAttribute("aria-hidden","true");setText(cell,kind==="-"?"−":kind==="+"?"+":"");return cell},codeCell(line){const cell=h("span","cx-diff-code"),labels=this.labels,sr=h("span","cx-sr-only");cell.setAttribute("role","cell");setText(sr,fill(line.kind==="-"?labels.changeLineRemoved:line.kind==="+"?labels.changeLineAdded:labels.changeLineContext,{line:line.kind==="+"?line.a:line.b})+ ": ");cell.appendChild(sr);const marks=line.kind==="="?null:diffMarks(line),text=line.text;if(!marks||!marks.length){cell.appendChild(document.createTextNode(text));return cell}
121
+ let at=0;for(const[start,end]of marks){if(start>at)cell.appendChild(document.createTextNode(text.slice(at,start)));const mark=h(line.kind==="-"?"del":"ins","cx-diff-mark");setText(mark,text.slice(start,end));cell.appendChild(mark);at=end}
122
+ if(at<text.length)cell.appendChild(document.createTextNode(text.slice(at)));return cell},gapCell(gap){const cell=h("span","cx-diff-gap"),labels=this.labels,count=this.format.format(gap.count);cell.setAttribute("role","cell");cell.setAttribute("aria-colspan","4");if(gap.lines){const button=h("button","cx-diff-expand");button.type="button";button._gap=gap;setText(button,changeCount(labels.changeShowLines,gap.count,this.lang,{count}));cell.appendChild(button)}else{setText(cell,changeCount(labels.changeHiddenLines,gap.count,this.lang,{count}))}
123
+ return cell}});async function changeCopy(text){try{if(global.navigator?.clipboard?.writeText){await global.navigator.clipboard.writeText(text);return true}}catch(error){}
124
+ const area=h("textarea","cx-sr-only");area.value=text;area.setAttribute("readonly","");document.body.appendChild(area);area.select();let done=false;try{done=document.execCommand("copy")}catch(error){done=false}
125
+ area.remove();return done}
126
+ Object.assign(DiffView.prototype,{onKey(event){if(event.target!==this.scroll)return;const scroll=this.scroll,page=Math.max(DIFF_ROW,scroll.clientHeight-2*DIFF_ROW),key=event.key;const moves={ArrowDown:DIFF_ROW,ArrowUp:-DIFF_ROW,PageDown:page,PageUp:-page};if(event.altKey&&(key==="ArrowDown"||key==="ArrowUp")){event.preventDefault();this.move(key==="ArrowDown"?1:-1);return}
127
+ if(!event.ctrlKey&&!event.metaKey&&!event.altKey&&(key==="n"||key==="p")){event.preventDefault();this.move(key==="n"?1:-1);return}
128
+ if(Object.hasOwn(moves,key)){event.preventDefault();scroll.scrollTop +=moves[key]}
129
+ else if(key==="Home"||key==="End"){event.preventDefault();scroll.scrollTop=key==="Home"?0:scroll.scrollHeight}},onClick(event){const button=event.target.closest?.(".cx-diff-expand");if(!button||!this.scroll.contains(button))return;this.expand(button._gap)},expand(gap){const at=this.rows.indexOf(gap);if(at<0||!gap.lines)return;this.rows=this.rows.slice(0,at).concat(gap.lines,this.rows.slice(at+1));const current=this.current>=0?this.view[this.starts[this.current]]:null;this.display();if(current)this.current=this.starts.indexOf(this.view.indexOf(current));this.render();this.scroll.focus({preventScroll:true});setText(this.live,changeCount(this.labels.changeShowLines,gap.count,this.lang,{count:this.format.format(gap.count)}))},expandAll(){for(const gap of this.rows.filter(row=>row.t==="gap"&&row.lines))this.expand(gap)},move(step){if(!this.starts.length)return false;this.current=this.current<0?(step>0?0:this.starts.length-1):(this.current+step+this.starts.length)%this.starts.length;const top=this.canvas.offsetTop+Math.max(0,this.starts[this.current]- 2)*DIFF_ROW-this.columns.offsetHeight;this.scroll.scrollTop=Math.max(0,top);this.render();setText(this.live,fill(this.labels.changePosition,{index:this.format.format(this.current+1),count:this.format.format(this.starts.length)}));return true},async copy(){const ok=await changeCopy(diffUnified(this.doc,this.hunks));if(this.destroyed)return ok;const text=ok?this.labels.changeCopied:this.labels.changeCopyFailed;setText(this.live,text);this.copyButton.classList.toggle("cx-diff-done",ok);clearTimeout(this.copyTimer);this.copyTimer=setTimeout(()=>this.copyButton.classList.remove("cx-diff-done"),1600);this.emit("diffCopy",{diff:this.doc.id,copied:ok},"onCopy");return ok},renderPrint(){if(this.destroyed||this.scroll.hidden){this.print.replaceChildren();return}
130
+ const table=h("table","cx-diff-printtable"),body=h("tbody");let written=0;for(const row of this.rows){if(written++>=5000)break;const tr=h("tr");if(row.t==="gap"){const td=h("td","cx-diff-gap");td.colSpan=4;setText(td,"⋯ "+changeCount(this.labels.changeHiddenLines,row.count,this.lang,{count:this.format.format(row.count)}));tr.appendChild(td)}
131
+ else{tr.className=row.kind==="-"?"cx-diff-del":row.kind==="+"?"cx-diff-add":"";for(const[text,name]of[[row.b,"cx-diff-num"],[row.a,"cx-diff-num"],[row.kind==="-"?"−":row.kind==="+"?"+":"","cx-diff-sign"]]){const td=h("td",name);setText(td,text??"");tr.appendChild(td)}
132
+ const code=this.codeCell(row),td=h("td","cx-diff-code");td.append(...code.childNodes);tr.appendChild(td)}
133
+ body.appendChild(tr)}
134
+ if(written>5000){const rest=this.rows.slice(5000).reduce((sum,row)=>sum +(row.t==="gap"?row.count:1),0);if(rest>0){const tr=h("tr"),td=h("td","cx-diff-gap");td.colSpan=4;setText(td,"⋯ "+changeCount(this.labels.changePrintLimit,rest,this.lang,{count:this.format.format(rest)}));tr.appendChild(td);body.appendChild(tr)}}
135
+ table.appendChild(body);this.print.replaceChildren(table)},getHunks(){return this.hunks.map(hunk=>({before:hunk.before,after:hunk.after,lines:hunk.lines.map(line=>(line.kind==="="?" ":line.kind)+ line.text)}))},update(view){this.doc=diffDocument(view);this.labels=changeLabels(this.options.baseLabels,this.doc.labels,this.options.labels);this.source=view;this.current=-1;this.prepare();this.refresh()}});function diffCreate(host,options,doc){const view=new DiffView(host,options,doc);const impl={ready:Promise.resolve().then(()=>{if(view.destroyed)return impl;host.setAttribute("data-cx-ready","");view.emit("diffReady",{diff:view.doc.id},"onReady");return impl}),document:()=>view.source||options.view,getLayout:()=>view.shown,setLayout:mode=>view.setLayout(mode),next:()=>view.move(1),previous:()=>view.move(-1),expandAll:()=>view.expandAll(),copy:()=>view.copy(),getHunks:()=>view.getHunks(),update:next=>view.update(next),destroy:()=>{if(view.destroyed)return;view.destroyed=true;view.lifetime.abort();view.resize.disconnect();cancelAnimationFrame(view.frame);clearTimeout(view.copyTimer);view.root.remove();host.removeAttribute("data-cx-ready");if(view.addedTheme)host.removeAttribute("data-cx-theme")}};return impl}
136
+ class ChangeListView{constructor(host,options){this.host=host;this.options=options;this.lifetime=new AbortController();this.doc=changesDocument(options.view);this.labels=changeLabels(this.doc.labels,options.labels);this.lang=host.closest("[lang]")?.getAttribute("lang")||document.documentElement.lang||"en";this.format=numberFormat(this.lang);this.kinds=new Set();this.flaggedOnly=this.doc.flaggedOnly;this.limit=this.doc.pageSize;this.diffs=new Map();this.open=new Set(this.doc.expandFlagged?this.doc.items.filter(item=>item.flags.length).map(item=>item.id):[]);if(options.state)this.applyState(options.state,false);this.build()}
137
+ emit(name,detail,callback){const event=new CustomEvent("canopyx:"+name,{detail,bubbles:true});this.host.dispatchEvent(event);if(typeof this.options[callback]==="function"){try{this.options[callback](detail,event)}catch(error){this.host.dispatchEvent(new CustomEvent("canopyx:error",{detail:{error,hook:callback},bubbles:true}))}}}
138
+ build(){const signal=this.lifetime.signal;if(!this.host.closest("[data-cx-theme]")){this.host.setAttribute("data-cx-theme","auto");this.addedTheme=true}
139
+ const root=this.root=h("div","cx-changes");this.bar=h("div","cx-chg-bar");this.list=h("ul","cx-chg-list");this.list.setAttribute("aria-label",this.doc.label);this.empty=h("div","cx-chg-empty");this.empty.hidden=true;this.more=h("button","cx-chg-more");this.more.type="button";this.more.hidden=true;this.live=h("div","cx-sr-only");this.live.setAttribute("aria-live","polite");this.barPanel=h("div","cx-chg-barpanel");root.append(this.bar,this.barPanel,this.list,this.empty,this.more,this.live);this.host.appendChild(root);root.addEventListener("click",event=>this.onClick(event),{signal});root.addEventListener("keydown",event=>this.onKey(event),{signal});changeTips(root,signal);root.addEventListener("change",event=>{if(event.target===this.flagSwitch){this.applyState({flaggedOnly:event.target.checked});this.flagSwitch?.focus()}},{signal});this.more.addEventListener("click",()=>{const shown=new Set([...this.list.querySelectorAll("[data-cx-item]")].map(item=>item.getAttribute("data-cx-item")));this.limit +=this.doc.pageSize;this.render();if(this.more.hidden)[...this.list.querySelectorAll("[data-cx-item]")].find(item=>!shown.has(item.getAttribute("data-cx-item")))?.querySelector(".cx-chg-toggle")?.focus()},{signal});this.printing=changesInPrint();global.addEventListener("beforeprint",()=>{this.printing=true;this.render()},{signal});global.addEventListener("afterprint",()=>{this.printing=false;this.render()},{signal});this.render()}
140
+ visible(){return this.doc.items.filter(item=>(!this.kinds.size||this.kinds.has(item.kind))&&(!this.flaggedOnly||item.flags.length))}
141
+ folders(items){if(!this.doc.groupByFolder)return[["","",items]];const groups=new Map();for(const item of items){const folder=changePath(item.path)[0],key=folder.replace(/\\/g, "/");
142
+ if(!groups.has(key))groups.set(key,[key,folder,[]]);groups.get(key)[2].push(item)}
143
+ return[...groups.values()].sort((a,b)=>a[0]<b[0]?-1:a[0]>b[0]?1:0)}
144
+ renderBar(){const labels=this.labels,items=this.doc.items,kinds=h("div","cx-chg-kinds");kinds.setAttribute("role","group");kinds.setAttribute("aria-label",labels.changeFilter);for(const kind of CHANGE_KINDS){const count=items.filter(item=>item.kind===kind).length;if(!count)continue;const button=h("button","cx-chg-kind"),badge=h("span"),number=h("span","cx-chg-count");button.type="button";button._kind=kind;button.setAttribute("aria-pressed",String(this.kinds.has(kind)));applyTone(badge,"cx-chg-badge",toneOf(CHANGE_TONES[kind]));setText(badge,labels["change"+kind[0].toUpperCase()+ kind.slice(1)]);setText(number,this.format.format(count));button.append(badge,number);kinds.appendChild(button)}
145
+ const children=[kinds],flagged=items.filter(item=>item.flags.length).length;if(flagged){const label=h("label","cx-chg-switch"),box=this.flagSwitch=h("input"),text=h("span");box.type="checkbox";box.checked=this.flaggedOnly;setText(text,labels.changeFlaggedOnly+" · "+changeCount(labels.changeFlaggedCount,flagged,this.lang,{count:this.format.format(flagged)}));label.append(box,text);children.push(label)}
146
+ const summary=h("span","cx-chg-summary"),shown=Math.min(this.visible().length,this.shownLimit());setText(summary,shown===items.length?"":fill(labels.changeShown,{shown:this.format.format(shown),total:this.format.format(items.length)}));const filters=[...CHANGE_KINDS.filter(kind=>this.kinds.has(kind)).map(kind=>labels["change"+kind[0].toUpperCase()+ kind.slice(1)]),...(this.flaggedOnly?[labels.changeFlaggedOnly]:[])];const printed=h("span","cx-chg-printnote");setText(printed,filters.length?fill(labels.changeFiltered,{filters:filters.join(", ")}):"");children.push(summary,printed);const tools=this.tools();if(tools)children.push(tools);this.bar.replaceChildren(...children)}
147
+ shownLimit(){return this.printing?Math.max(this.limit,2000):this.limit}
148
+ render(){const focused=document.activeElement&&this.root.contains(document.activeElement)?document.activeElement.closest("[data-cx-item]")?.getAttribute("data-cx-item"):null;this.renderBar();const items=this.visible(),shown=items.slice(0,this.shownLimit()),labels=this.labels,rows=[];for(const[key,folder,members]of this.folders(shown)){if(this.doc.groupByFolder){const group=h("li","cx-chg-group"),title=h("div","cx-chg-folder"),name=h("span","cx-chg-foldername"),count=h("span","cx-chg-foldercount"),list=h("ul","cx-chg-sublist");setText(name,folder||labels.changeRoot);setText(count,changeCount(labels.changeFileCount,members.length,this.lang,{count:this.format.format(members.length)}));title.append(name,count);list.setAttribute("aria-label",folder||labels.changeRoot);for(const item of members)list.appendChild(this.row(item));group.append(title,list);rows.push(group)}else{for(const item of members)rows.push(this.row(item))}}
149
+ for(const[id,diff]of this.diffs)if(!shown.some(item=>item.id===id&&this.open.has(id))){diff.destroy();this.diffs.delete(id)}
150
+ this.list.replaceChildren(...rows);this.empty.hidden=items.length>0;setText(this.empty,labels.changeNoItems);this.more.hidden=items.length<=this.shownLimit();setText(this.more,labels.changeShowMore);if(!this.pending)this.panelLife?.abort();this.barPanel.replaceChildren(...(this.pending&&!this.pending.item?[this.panel()]:[]));if(focused)this.focusItem(focused)}
151
+ applyState(state,render=true){if(!state||typeof state!=="object")throw new Error("CanopyX: a change list state is an object.");if(state.kinds!==undefined){if(!Array.isArray(state.kinds)||state.kinds.some(kind=>!CHANGE_KINDS.includes(kind)))throw new Error("CanopyX: change list kinds are added, removed, modified and renamed.");this.kinds=new Set(state.kinds)}
152
+ if(state.flaggedOnly!==undefined)this.flaggedOnly=!!state.flaggedOnly;if(state.open!==undefined){if(!Array.isArray(state.open)||state.open.some(id=>typeof id!=="string"))throw new Error("CanopyX: open items are a list of item ids.");this.open=new Set(state.open.filter(id=>this.doc.items.some(item=>item.id===id)))}
153
+ this.limit=this.doc.pageSize;if(render){this.render();this.emit("changesState",this.getState(),"onStateChange")}}
154
+ getState(){return gridFreeze({kinds:[...this.kinds],flaggedOnly:this.flaggedOnly,open:[...this.open]})}
155
+ toggle(id,open){const item=this.doc.items.find(entry=>entry.id===id);if(!item)return false;const next=open===undefined?!this.open.has(id):!!open;if(next===this.open.has(id))return true;if(next)this.open.add(id);else this.open.delete(id);this.render();this.emit("changeToggle",{list:this.doc.id,item:id,open:next},"onToggle");return true}
156
+ focusItem(id){const toggle=[...this.list.querySelectorAll(".cx-chg-toggle")].find(button=>button.closest("[data-cx-item]").getAttribute("data-cx-item")===id);if(!toggle)return false;toggle.focus();return true}
157
+ update(view){this.doc=changesDocument(view);this.labels=changeLabels(this.doc.labels,this.options.labels);this.source=view;this.open=new Set([...this.open].filter(id=>this.doc.items.some(item=>item.id===id)));for(const diff of this.diffs.values())diff.destroy();this.diffs.clear();this.limit=this.doc.pageSize;this.pending=null;this.list.setAttribute("aria-label",this.doc.label);this.render()}}
158
+ Object.assign(ChangeListView.prototype,{row(item){const labels=this.labels,doc=this.doc,open=this.open.has(item.id),[folder,name]=changePath(item.path);const li=h("li","cx-chg-item" +(item.flags.length?" cx-chg-flagged":"")),row=h("div","cx-chg-row");li.setAttribute("data-cx-item",item.id);const toggle=h("button","cx-chg-toggle"),detailId=(this.host.id||"cx-changes")+ "-"+this.uid()+ "-detail";toggle.type="button";toggle.setAttribute("aria-expanded",String(open));toggle.setAttribute("aria-label",fill(labels.changeDetails,{name:doc.groupByFolder?name:item.path}));toggle.appendChild(changeIcon("chevron"));const badge=h("span");applyTone(badge,"cx-chg-badge",toneOf(CHANGE_TONES[item.kind]));setText(badge,item.kindLabel||labels["change"+item.kind[0].toUpperCase()+ item.kind.slice(1)]);const main=h("div","cx-chg-main"),title=h("div","cx-chg-title"),nameText=h("span","cx-chg-name");setText(nameText,doc.groupByFolder?name:item.path);nameText.title=item.path;title.appendChild(nameText);if(item.previousPath){const from=h("span","cx-chg-from");setText(from,fill(labels.changeFrom,{path:item.previousPath}));title.appendChild(from)}
159
+ main.appendChild(title);if(item.flags.length){const flags=h("div","cx-chg-flags");for(const flag of item.flags){const chip=h("span");applyTone(chip,"cx-chg-flag",toneOf(flag.tone));chip.title=flag.reason;chip.append(changeIcon("flag"),document.createTextNode(flag.label));flags.appendChild(chip)}
160
+ main.appendChild(flags)}
161
+ const second=[item.secondary,(item.after||item.before)?.time!=null?changeTimeText((item.after||item.before).time,doc.local,labels):null].filter(Boolean);if(second.length){const line=h("div","cx-chg-sub");setText(line,second.join(" · "));main.appendChild(line)}
162
+ const meta=h("div","cx-chg-meta"),size=h("span","cx-chg-size"),hash=h("span","cx-chg-hash");setText(size,this.pair(item.before?.size,item.after?.size,value=>changeSize(value,this.lang)));for(const[index,version]of[item.before,item.after].entries()){if(!version||!version.hash||index===1&&item.before?.hash===version.hash)continue;if(hash.childNodes.length){const arrow=h("span","cx-chg-arrow");setText(arrow,"→");arrow.setAttribute("aria-hidden","true");hash.appendChild(arrow)}
163
+ const button=h("button","cx-chg-hashbtn");button.type="button";button._copy=version.hash;button.title=labels.changeCopyHash+": "+version.hash;button.setAttribute("aria-label",labels.changeCopyHash+": "+version.hash);setText(button,version.hash.length>doc.hashLength?version.hash.slice(0,doc.hashLength)+ "…":version.hash);hash.appendChild(button)}
164
+ meta.append(size,hash);const actions=h("div","cx-chg-actions");actions.setAttribute("role","group");actions.setAttribute("aria-label",fill(labels.changeActions,{name}));for(const action of doc.actions){const control=this.actionControl(action,item);if(control)actions.appendChild(control)}
165
+ row.append(toggle,badge,main,meta,actions);li.appendChild(row);if(this.pending&&this.pending.item===item)li.appendChild(this.panel());if(open){const detail=this.detail(item);detail.id=detailId;toggle.setAttribute("aria-controls",detailId);li.appendChild(detail)}
166
+ return li},uid(){return(this.seq=(this.seq||0)+ 1)},pair(before,after,format){const left=before==null?null:format(before),right=after==null?null:format(after);return left===null?right??"":right===null||left===right?left:left+" → "+right},detail(item){const labels=this.labels,doc=this.doc,box=h("div","cx-chg-detail");if(item.flags.length){const heading=h("div","cx-chg-dhead"),list=h("ul","cx-chg-reasons");setText(heading,labels.changeFlags);for(const flag of item.flags){const entry=h("li"),label=h("strong");applyTone(label,"cx-chg-reason",toneOf(flag.tone));setText(label,flag.label);entry.append(label,document.createTextNode(" — "+flag.reason));list.appendChild(entry)}
167
+ box.append(heading,list)}
168
+ const rows=[],local=doc.local;if(item.before?.size!=null||item.after?.size!=null)rows.push([labels.changeSize,item.before?.size!=null?changeSize(item.before.size,this.lang):null,item.after?.size!=null?changeSize(item.after.size,this.lang):null,null,false]);if(item.before?.hash||item.after?.hash)rows.push([labels.changeHash,item.before?.hash??null,item.after?.hash??null,null,true]);if(item.before?.time!=null||item.after?.time!=null)rows.push([labels.changeTime,item.before?.time!=null?changeTimeText(item.before.time,local,labels):null,item.after?.time!=null?changeTimeText(item.after.time,local,labels):null,null,false]);for(const fact of item.facts)rows.push([fact.label,fact.before,fact.after,fact.tone,false,fact.note]);if(rows.length){const other=item.kind==="added"?0:item.kind==="removed"?1:-1;const sides=other<0||rows.some(row=>(other?row[2]:row[1])!=null)?[0,1]:[1-other];const table=h("table","cx-chg-facts" +(sides.length===1?" cx-chg-onevalue":"")),head=h("tr");for(const text of["",...sides.map(side=>side?labels.changeAfter:labels.changeBefore)]){const th=h("th");th.scope="col";setText(th,text);head.appendChild(th)}
169
+ const thead=h("thead"),tbody=h("tbody");thead.appendChild(head);for(const[label,before,after,tone,mono,note]of rows){const tr=h("tr"),th=h("th");th.scope="row";setText(th,label);tr.appendChild(th);sides.forEach((side,at)=>{const value=side?after:before;const td=h("td",(mono&&value!==null?"cx-chg-mono":"")+(side===1&&sides.length===2&&before!==null&&after!==null&&before!==after?" cx-chg-changed":""));setText(td,value??"—");if((side===1||sides.length===1)&&tone)applyTone(td,td.className,toneOf(tone));if(note&&at===sides.length-1){const small=h("div","cx-chg-factnote");setText(small,note);td.appendChild(small)}
170
+ tr.appendChild(td)});tbody.appendChild(tr)}
171
+ table.append(thead,tbody);box.appendChild(table)}
172
+ if(item.diff){let diff=this.diffs.get(item.id);if(!diff){const holder=h("div","cx-chg-diff");diff=diffCreate(holder,{labels:this.options.labels,baseLabels:this.doc.labels,lang:this.lang},item.diff);diff.holder=holder;this.diffs.set(item.id,diff)}
173
+ box.appendChild(diff.holder)}
174
+ return box},onClick(event){const target=event.target.closest?.("button, a.cx-chg-action");if(!target||!this.root.contains(target))return;if(target._export){this.exportItems(target._export);return}
175
+ if(target.classList.contains("cx-chg-kind")){const kinds=new Set(this.kinds);if(kinds.has(target._kind))kinds.delete(target._kind);else kinds.add(target._kind);this.applyState({kinds:[...kinds]});[...this.bar.querySelectorAll(".cx-chg-kind")].find(button=>button._kind===target._kind)?.focus();return}
176
+ if(target.classList.contains("cx-chg-toggle")){const id=target.closest("[data-cx-item]").getAttribute("data-cx-item");this.toggle(id);this.focusItem(id);return}
177
+ if(target.classList.contains("cx-chg-hashbtn")){this.copyText(target._copy);return}
178
+ if(target._action)this.act(target._action,target._item,target)},async copyText(text){const ok=await changeCopy(text);if(!this.destroyed)setText(this.live,ok?this.labels.changeCopied:this.labels.changeCopyFailed);return ok},onKey(event){if(!event.target.classList?.contains("cx-chg-toggle")||event.key!=="ArrowDown"&&event.key!=="ArrowUp"&&event.key!=="Home"&&event.key!=="End")return;const toggles=[...this.list.querySelectorAll(".cx-chg-toggle")],at=toggles.indexOf(event.target);const next=event.key==="Home"?0:event.key==="End"?toggles.length-1:at +(event.key==="ArrowDown"?1:-1);if(next<0||next>=toggles.length)return;event.preventDefault();toggles[next].focus()}});function changeListCreate(host,options){const view=new ChangeListView(host,options);const impl={ready:Promise.resolve().then(()=>{if(view.destroyed)return impl;host.setAttribute("data-cx-ready","");view.emit("changesReady",{list:view.doc.id},"onReady");return impl}),document:()=>view.source||options.view,getState:()=>view.getState(),setState:state=>view.applyState(state),toggle:(id,open)=>view.toggle(id,open),focusItem:id=>view.focusItem(id),update:next=>view.update(next),destroy:()=>{if(view.destroyed)return;view.destroyed=true;view.lifetime.abort();view.panelLife?.abort();for(const diff of view.diffs.values())diff.destroy();view.root.remove();host.removeAttribute("data-cx-ready");if(view.addedTheme)host.removeAttribute("data-cx-theme")}};return impl}
179
+ const CHANGE_LINK=/^(?:#|\/(?!\/)|\.{1,2}\/|https?:\/\/|mailto:)/i;
180
+ function changeHref(value){if(typeof value!=="string")return null;const href=value.trim();return CHANGE_LINK.test(href)&&!/[\\\u0000-\u001f\u007f]/.test(href) ? href : null;
181
+ }
182
+ function changeDownload(name,type,text){if(typeof Blob!=="function"||!global.URL||typeof URL.createObjectURL!=="function")return false;const url=URL.createObjectURL(new Blob([text],{type})),link=h("a");link.href=url;link.download=name;link.hidden=true;document.body.appendChild(link);link.click();link.remove();setTimeout(()=>URL.revokeObjectURL(url),1000);return true}
183
+ function changeCsvField(value){let text=value==null?"":String(value);if(/^[=+\-@\t\r]/.test(text)) text = "'" + text;
184
+ return /[",\r\n]/.test(text) ? "\"" + text.replace(/"/g, "\"\"") + "\"" : text;
185
+ }
186
+ function changeIso(time){return time==null?null:new Date(time).toISOString()}
187
+ function changeExportDiff(diff){if(!diff)return null;let hunks=diff.hunks;const canCompare=diff.left&&diff.left.length<=DIFF_CLIENT_LINES&&diff.right.length<=DIFF_CLIENT_LINES;if(diff.content==="text"&&!hunks.length&&canCompare)hunks=diffHunks(diff.left,diff.right,diff.context);const unified=diff.content==="text"&&(diff.unified!==null||hunks.length||canCompare)?diffUnified(diff,hunks):null;return{content:diff.content,message:diff.message,unified,truncated:diff.truncated}}
188
+ function changeExportVersion(version){return version?{size:version.size,hash:version.hash,time:changeIso(version.time)}:null}
189
+ function changeExportItem(item){return{id:item.id,kind:item.kind,kindLabel:item.kindLabel,path:item.path,previousPath:item.previousPath,secondary:item.secondary,before:changeExportVersion(item.before),after:changeExportVersion(item.after),flags:item.flags.map(flag=>({id:flag.id,label:flag.label,reason:flag.reason,tone:flag.tone})),facts:item.facts.map(fact=>({label:fact.label,before:fact.before,after:fact.after,note:fact.note})),values:{...item.values},diff:changeExportDiff(item.diff)}}
190
+ function changeCsv(items){const keys=[...new Set(items.flatMap(item=>Object.keys(item.values)))].sort();const head=["kind","kindLabel","path","previousPath","secondary","sizeBefore","sizeAfter","hashBefore","hashAfter","timeBefore","timeAfter","flags","facts"].concat(keys.map(key=>"values."+key));const rows=[head].concat(items.map(item=>[item.kind,item.kindLabel,item.path,item.previousPath,item.secondary,item.before?.size,item.after?.size,item.before?.hash,item.after?.hash,changeIso(item.before?.time),changeIso(item.after?.time),item.flags.map(flag=>flag.label+": "+flag.reason).join(" | "),item.facts.map(fact=>fact.label+": " +(fact.before??"")+(fact.before!=null&&fact.after!=null?" → ":"")+(fact.after??"")+(fact.note?" ("+fact.note+")":"")).join(" | ")].concat(keys.map(key=>item.values[key]))));return rows.map(row=>row.map(changeCsvField).join(",")).join("\r\n")+ "\r\n"}
191
+ Object.assign(ChangeListView.prototype,{actionControl(action,item){if(item&&item.actions.length&&!item.actions.includes(action.id))return null;const values=item?item.values:this.doc.values;const value=key=>key===null?null:item&&key==="path"?item.path:item&&key==="previousPath"?item.previousPath:values[key]??null;if(action.copy!==null&&value(action.copy)===null)return null;const href=action.link===null?null:changeHref(value(action.link));if(action.link!==null&&href===null)return null;if(action.reveal==="diff"&&!item.diff)return null;const control=h(href?"a":"button","cx-chg-action" +(action.icon==="none"||!item?" cx-chg-textaction":""));if(href)control.href=href;else control.type="button";control._action=action;control._item=item;control.setAttribute("data-cx-action",action.id);if(action.icon!=="none")control.appendChild(changeIcon(action.icon));if(action.icon==="none"||!item)control.appendChild(document.createTextNode(action.label));else{control.setAttribute("aria-label",action.label);control.setAttribute("data-cx-tip",action.label)}
192
+ if(action.reason)control.setAttribute("aria-expanded",String(!!(this.pending&&this.pending.mode==="ask"&&this.pending.action===action&&this.pending.item===item)));return control},tools(){const tools=h("div","cx-chg-tools");tools.setAttribute("role","group");tools.setAttribute("aria-label",fill(this.labels.changeActions,{name:this.doc.label}));for(const action of this.doc.toolbar){const control=this.actionControl(action,null);if(control)tools.appendChild(control)}
193
+ for(const format of this.doc.exports){const button=h("button","cx-chg-action cx-chg-textaction cx-chg-export");button.type="button";button._export=format;button.append(changeIcon("download"),document.createTextNode(format==="csv"?this.labels.changeExportCsv:this.labels.changeExportJson));tools.appendChild(button)}
194
+ return tools.childNodes.length?tools:null},detailOf(action,item,extra){return Object.assign({list:this.doc.id,action:action.id,item:item?item.id:null,kind:item?item.kind:null,path:item?item.path:null,previousPath:item?item.previousPath:null,values:{...(item?item.values:this.doc.values)},copied:null},extra)},request(detail){const event=new CustomEvent("canopyx:changeAction",{detail,bubbles:true,cancelable:true});this.host.dispatchEvent(event);let claimed=event.defaultPrevented;if(typeof this.options.onAction==="function"){try{this.options.onAction(detail,event);claimed=true}catch(error){this.host.dispatchEvent(new CustomEvent("canopyx:error",{detail:{error,hook:"onAction"},bubbles:true}))}}
195
+ return claimed},async act(action,item,control){if(action.copy!==null){const key=action.copy,copied=await this.copyText(item&&key==="path"?item.path:item&&key==="previousPath"?item.previousPath:(item?item.values:this.doc.values)[key]);if(!this.destroyed)this.emit("changeAction",this.detailOf(action,item,{copied}),"onAction");return}
196
+ if(action.link!==null){this.emit("changeAction",this.detailOf(action,item,{href:control.getAttribute("href")}),"onAction");return}
197
+ if(action.reveal!=="none"){this.reveal(item.id,action.reveal);this.emit("changeAction",this.detailOf(action,item,{reveal:action.reveal}),"onAction");return}
198
+ if(action.reason){this.pending={mode:"ask",action,item,text:"",error:false};this.render();this.focusPanel();return}
199
+ this.send(action,item,null)},send(action,item,reason){const detail=this.detailOf(action,item,reason===null?{}:{reason});const claimed=this.request(detail);if(this.destroyed)return;if(item?!this.doc.items.includes(item):!this.doc.toolbar.includes(action)){this.pending=null;this.render();return}
200
+ this.pending=claimed?null:{mode:"note",action,item,detail};this.render();if(this.pending)this.focusPanel();else this.focusAction(action,item)},reveal(id,what){this.toggle(id,true);const box=this.list.querySelector("[data-cx-item=\""+CSS.escape(id)+ "\"] > .cx-chg-detail");if(!box)return;const target=what==="diff"?box.querySelector(".cx-chg-diff .cx-diff-scroll")||box:box;if(target===box)box.tabIndex=-1;target.scrollIntoView?.({block:"nearest"});target.focus({preventScroll:true})},panel(){this.panelLife?.abort();this.panelLife=new AbortController();const pending=this.pending,labels=this.labels,signal=this.panelLife.signal,box=h("div","cx-chg-panel");box.setAttribute("role","group");box.setAttribute("aria-label",pending.action.label);const cancel=h("button","cx-chg-action cx-chg-textaction");cancel.type="button";setText(cancel,labels.changeCancel);cancel.addEventListener("click",()=>{const{action,item}=pending;this.pending=null;this.render();this.focusAction(action,item)},{signal});if(pending.mode==="note"){const text=h("p","cx-chg-note"),copy=h("button","cx-chg-action cx-chg-textaction");copy.type="button";setText(text,fill(labels.changeUnclaimed,{action:pending.action.label}));copy.append(changeIcon("copy"),document.createTextNode(labels.changeCopyRequest));copy.addEventListener("click",()=>this.copyText(JSON.stringify(pending.detail,null,2)),{signal});const buttons=h("div","cx-chg-askbtns");buttons.append(copy,cancel);box.append(text,buttons);return box}
201
+ const id=(this.host.id||"cx-changes")+ "-reason-"+this.uid(),field=h("textarea","cx-chg-reasonbox"),label=h("label","cx-chg-asklabel"),hint=h("div","cx-chg-hint");field.id=id;field.rows=2;field.maxLength=1000;field.value=pending.text;field.setAttribute("aria-describedby",id+"-hint");label.htmlFor=id;setText(label,labels.changeReason);hint.id=id+"-hint";setText(hint,fill(labels.changeReasonHint,{action:pending.action.label}));const error=h("div","cx-chg-askerror");error.setAttribute("role","alert");setText(error,pending.error?labels.changeReasonRequired:"");if(pending.error)field.setAttribute("aria-invalid","true");const confirm=h("button","cx-chg-action cx-chg-textaction cx-chg-primary");confirm.type="button";setText(confirm,pending.action.label);const submit=()=>{const reason=field.value.trim();if(!reason){pending.error=true;pending.text=field.value;this.render();this.focusPanel();return}
202
+ this.send(pending.action,pending.item,reason)};confirm.addEventListener("click",submit,{signal});field.addEventListener("input",()=>{pending.text=field.value},{signal});field.addEventListener("keydown",event=>{if(event.key==="Escape"){event.preventDefault();event.stopPropagation();cancel.click()}
203
+ else if(event.key==="Enter"&&(event.ctrlKey||event.metaKey)){event.preventDefault();submit()}},{signal});const buttons=h("div","cx-chg-askbtns");buttons.append(confirm,cancel);box.append(label,field,hint,error,buttons);return box},focusPanel(){const target=this.root.querySelector(".cx-chg-panel :is(textarea, button)");if(target)target.focus()},focusAction(action,item){const scope=item?this.list.querySelector("[data-cx-item=\""+CSS.escape(item.id)+ "\"] > .cx-chg-row"):this.bar;scope?.querySelector("[data-cx-action=\""+CSS.escape(action.id)+ "\"]")?.focus()},exportItems(format){const items=this.folders(this.visible()).flatMap(group=>group[2]),name=(this.doc.id.replace(/[^\w.-]+/g, "-") || "changes") + "." + format;
204
+ const text=format==="csv"?""+changeCsv(items)
205
+ :JSON.stringify({list:this.doc.id,label:this.doc.label,exported:new Date().toISOString(),filters:{kinds:[...this.kinds],flaggedOnly:this.flaggedOnly},items:items.map(changeExportItem)},null,2);const saved=changeDownload(name,format==="csv"?"text/csv;charset=utf-8":"application/json",text);this.emit("changeExport",{list:this.doc.id,format,count:items.length,saved},"onExport");return saved}});const PROPAGATION_RANK={fault:3,propagating:2,unknown:1,converged:0};class PropagationView{constructor(host,options){this.host=host;this.options=options;this.lifetime=new AbortController();this.doc=propagationDocument(options.view);this.labels=changeLabels(this.doc.labels,options.labels);this.lang=host.closest("[lang]")?.getAttribute("lang")||document.documentElement.lang||"en";this.format=numberFormat(this.lang);if(!host.closest("[data-cx-theme]")){host.setAttribute("data-cx-theme","auto");this.addedTheme=true}
206
+ this.root=h("div","cx-prop");host.appendChild(this.root);this.render()}
207
+ emit(name,detail,callback){const event=new CustomEvent("canopyx:"+name,{detail,bubbles:true});this.host.dispatchEvent(event);if(typeof this.options[callback]==="function"){try{this.options[callback](detail,event)}catch(error){this.host.dispatchEvent(new CustomEvent("canopyx:error",{detail:{error,hook:callback},bubbles:true}))}}}
208
+ stateText(state){return this.labels["change"+state[0].toUpperCase()+ state.slice(1)]}
209
+ badge(state,className){const badge=h("span"),[icon,tone]=PROPAGATION_LOOK[state];applyTone(badge,className +(state==="fault"||state==="propagating"?" cx-prop-attn":""),toneOf(tone));const text=h("span");setText(text,this.stateText(state));badge.append(changeIcon(icon),text);return badge}
210
+ render(){const doc=this.doc,labels=this.labels,witnesses=doc.witnesses;const seen=witnesses.map(witness=>witness.firstSeen).filter(time=>time!==null),reports=witnesses.flatMap(witness=>[witness.firstSeen,witness.lastSeen]).filter(time=>time!==null);const start=doc.start??(seen.length?Math.min(...seen):null),end=doc.asOf??(reports.length?Math.max(...reports):null),span=start!==null&&end!==null?Math.max(1,end-start):0;const overall=doc.state||witnesses.reduce((worst,witness)=>PROPAGATION_RANK[witness.state]>PROPAGATION_RANK[worst]?witness.state:worst,"converged");const converged=witnesses.filter(witness=>witness.state==="converged").length;const head=h("div","cx-prop-head"),summary=h("span","cx-prop-summary"),counts=h("ul","cx-prop-counts");setText(summary,doc.summary||fill(labels.changeProgress,{converged:this.format.format(converged),total:this.format.format(witnesses.length)}));head.append(this.badge(overall,"cx-prop-state"),summary);for(const state of PROPAGATION_STATES){const count=witnesses.filter(witness=>witness.state===state).length;if(!count)continue;const item=h("li","cx-prop-count"),badge=this.badge(state,"cx-prop-key"),number=h("span","cx-prop-number");setText(number,this.format.format(count));item.append(badge,number);counts.appendChild(item)}
211
+ counts.setAttribute("aria-label",labels.changeState);const table=h("table","cx-prop-table"),thead=h("thead"),heads=h("tr");table.setAttribute("aria-label",doc.label);for(const text of[doc.title||labels.changeReplica,labels.changeState,labels.changeFirstSeen,labels.changeLastSeen,labels.changeTimeline]){const th=h("th");th.scope="col";setText(th,text);heads.appendChild(th)}
212
+ thead.appendChild(heads);table.appendChild(thead);const groups=new Map();for(const witness of witnesses){if(!groups.has(witness.group))groups.set(witness.group,[]);groups.get(witness.group).push(witness)}
213
+ for(const[group,members]of groups){const body=h("tbody");if(group!==null){const tr=h("tr","cx-prop-group"),th=h("th"),count=h("span","cx-prop-groupcount");th.scope="rowgroup";th.colSpan=5;setText(th,group);setText(count," · "+changeCount(doc.groupCount||"{count}",members.length,this.lang,{count:this.format.format(members.length)}));th.appendChild(count);tr.appendChild(th);body.appendChild(tr)}
214
+ for(const witness of members)body.appendChild(this.row(witness,start,span));table.appendChild(body)}
215
+ this.root.replaceChildren(head,counts,table)}
216
+ row(witness,start,span){const labels=this.labels,tr=h("tr","cx-prop-row cx-prop-"+witness.state),name=h("th"),state=h("td","cx-prop-statecell"),first=h("td","cx-prop-first"),last=h("td","cx-prop-last"),lane=h("td","cx-prop-lanecell");name.scope="row";const label=h("span","cx-prop-name");setText(label,witness.label);name.appendChild(label);if(witness.source){const mark=h("span","cx-prop-source");setText(mark,labels.changeSource);name.appendChild(mark)}
217
+ if(witness.secondary){const sub=h("span","cx-prop-sub");setText(sub,witness.secondary);name.appendChild(sub)}
218
+ state.appendChild(this.badge(witness.state,"cx-prop-badge"));if(witness.note){const note=h("span","cx-prop-sub");setText(note,witness.note);state.appendChild(note)}
219
+ let delay=null;if(witness.firstSeen!==null){const time=h("span");setText(time,changeTimeText(witness.firstSeen,this.doc.local,labels));first.appendChild(time);if(start!==null&&!witness.source){delay=fill(labels.changeDelay,{duration:changeDuration(witness.firstSeen-start,labels,this.format)});const sub=h("span","cx-prop-delay");setText(sub,delay);first.appendChild(sub)}}else{const none=h("span","cx-prop-none");setText(none,labels.changeNotSeen);first.appendChild(none)}
220
+ setText(last,witness.lastSeen!==null?changeTimeText(witness.lastSeen,this.doc.local,labels):"");const track=h("div","cx-prop-lane"),fillBar=h("div","cx-prop-fill");track.setAttribute("aria-hidden","true");const reached=witness.firstSeen!==null&&span?Math.min(1,Math.max(0,(witness.firstSeen-start)/ span)) : witness.state === "unknown" || !span ? 0 : 1;
221
+ fillBar.style.width=(reached*100).toFixed(1)+ "%";applyTone(fillBar,"cx-prop-fill" +(witness.firstSeen===null&&witness.state!=="unknown"?" cx-prop-open":""),toneOf(PROPAGATION_LOOK[witness.state][1]));track.appendChild(fillBar);if(delay)track.title=delay;lane.appendChild(track);tr.append(name,state,first,last,lane);return tr}
222
+ update(view){this.doc=propagationDocument(view);this.labels=changeLabels(this.doc.labels,this.options.labels);this.source=view;this.render()}}
223
+ function propagationCreate(host,options){const view=new PropagationView(host,options);const impl={ready:Promise.resolve().then(()=>{if(view.destroyed)return impl;host.setAttribute("data-cx-ready","");view.emit("propagationReady",{propagation:view.doc.id},"onReady");return impl}),document:()=>view.source||options.view,update:next=>view.update(next),destroy:()=>{if(view.destroyed)return;view.destroyed=true;view.lifetime.abort();view.root.remove();host.removeAttribute("data-cx-ready");if(view.addedTheme)host.removeAttribute("data-cx-theme")}};return impl}
224
+ Object.assign(CHANGES_IMPL,{diff:diffCreate,changes:changeListCreate,propagation:propagationCreate});installed.add(context.CHANGES_IMPL)}};(global.CanopyXParts||=[]).push(["changes",part])})(typeof window!=="undefined"?window:globalThis);
@@ -1,11 +1,11 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"6c96974326d65a4c",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,GRID_ROWS,GRID_REPORTING,h,setText,fill,gridButton,gridFreeze,cellText,formatting,labelsFor,requireIdentifier,GridRowOffsets,TreeGrid,createDataGrid,createTreeGrid,disposeRowHostCells,OWN_ARROWS,hostHtml}=context;const ROWS_BASE={};for(const name of["render","renderPass","trimInactiveCache","syncRowOffsets","setFocus","onKey","fillRow","destroy"])
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"4908d1d2b7f67aa8",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,GRID_ROWS,GRID_REPORTING,h,setText,fill,gridButton,gridFreeze,cellText,formatting,labelsFor,requireIdentifier,GridRowOffsets,TreeGrid,createDataGrid,createTreeGrid,disposeRowHostCells,OWN_ARROWS,hostHtml}=context;const ROWS_BASE={};for(const name of["render","renderPass","trimInactiveCache","syncRowOffsets","setFocus","onKey","fillRow","destroy"])
2
2
  ROWS_BASE[name]=DataGrid.prototype[name];GRID_ROWS.view=ROWS_BASE;function rowsLowerBound(list,value){let low=0,high=list.length;while(low<high){const middle=(low+high)>>>1;if(list[middle]<value)low=middle+1;else high=middle}
3
3
  return low}
4
4
  function rowsAtOrBefore(list,value,field){let low=0,high=list.length;while(low<high){const middle=(low+high)>>>1;if(list[middle][field]<=value)low=middle+1;else high=middle}
5
5
  return low?list[low-1]:null}
6
6
  function rowsResolve(segment,k){const list=segment.details,first=segment.first;let low=0,high=list.length;while(low<high){const middle=(low+high)>>>1;if(list[middle]- first+middle+1<k)low=middle+1;else high=middle}
7
7
  return low<list.length&&list[low]- first+low+1===k?[list[low],true]:[first+k-low,false]}
8
- Object.assign(DataGrid.prototype,{prepareRowProjection(){this.resolveDetails();const bounds=this.rowBounds(),groups=this.projectionGroups?.()||null;const key=[this.snapshot,groups,bounds.first,bounds.last,this.groupCollapseVersion||0,this.detailVersion||0,this.rowH];if(this.rowProjectionKey?.every((value,index)=>value===key[index]))return;const previous=this.rowProjection,sameResult=previous&&this.rowProjectionKey[0]===this.snapshot,top=this.logicalTop();const anchorIndex=sameResult&&previous.rowCount()?previous.rowAt(top):-1;const anchor=anchorIndex>=0?{item:previous.rowItem(anchorIndex),offset:top-previous.rowTop(anchorIndex)}:null;const details=this.detailOrdinals||[],segments=[],runs=[],headers=new Map();let count=0;const run=(first,last)=>{if(first>last)return;const list=details.slice(rowsLowerBound(details,first),rowsLowerBound(details,last+1));const segment={start:count,length:last-first+1+list.length,first,last,details:list};segments.push(segment);runs.push(segment);count +=segment.length};const walk=(list,level,path)=>{for(const group of list){const first=Math.max(bounds.first,group.offset),last=Math.min(bounds.last,group.offset+group.count-1);if(first>last)continue;headers.set(group.key,count);segments.push({start:count,length:1,header:this.groupHeader(group,level,path)});count++;if(this.collapsedGroups?.has(group.key))continue;if(group.groups?.length)walk(group.groups,level+1,[...path,group]);else run(first,last)}};if(groups)walk(groups,1,[]);else run(bounds.first,bounds.last);const recordIndex=ordinal=>{const segment=rowsAtOrBefore(runs,ordinal,"first");return!segment||ordinal>segment.last?-1:segment.start+ordinal-segment.first+rowsLowerBound(segment.details,ordinal)};const offsets=new GridRowOffsets(count,this.rowH);for(const segment of runs)segment.details.forEach((ordinal,index)=>{const height=this.detailHeights?.get(this.detailKeyAt(ordinal));offsets.set(segment.start+ordinal-segment.first+index+1,height??this.detailEstimate())});if(this.autoRows)for(const[ordinal,height]of this.rowHeights){const index=recordIndex(ordinal);if(index>=0)offsets.set(index,height)}
8
+ Object.assign(DataGrid.prototype,{prepareRowProjection(){this.resolveDetails();const bounds=this.rowBounds(),groups=this.projectionGroups?.()||null;const key=[this.snapshot,groups,bounds.first,bounds.last,this.groupCollapseVersion||0,this.detailVersion||0,this.rowH,this.autoRows];if(this.rowProjectionKey?.every((value,index)=>value===key[index]))return;const previous=this.rowProjection,sameResult=previous&&this.rowProjectionKey[0]===this.snapshot,top=this.logicalTop();const anchorIndex=sameResult&&previous.rowCount()?previous.rowAt(top):-1;const anchor=anchorIndex>=0?{item:previous.rowItem(anchorIndex),offset:top-previous.rowTop(anchorIndex)}:null;const details=this.detailOrdinals||[],segments=[],runs=[],headers=new Map();let count=0;const run=(first,last)=>{if(first>last)return;const list=details.slice(rowsLowerBound(details,first),rowsLowerBound(details,last+1));const segment={start:count,length:last-first+1+list.length,first,last,details:list};segments.push(segment);runs.push(segment);count +=segment.length};const walk=(list,level,path)=>{for(const group of list){const first=Math.max(bounds.first,group.offset),last=Math.min(bounds.last,group.offset+group.count-1);if(first>last)continue;headers.set(group.key,count);segments.push({start:count,length:1,header:this.groupHeader(group,level,path)});count++;if(this.collapsedGroups?.has(group.key))continue;if(group.groups?.length)walk(group.groups,level+1,[...path,group]);else run(first,last)}};if(groups)walk(groups,1,[]);else run(bounds.first,bounds.last);const recordIndex=ordinal=>{const segment=rowsAtOrBefore(runs,ordinal,"first");return!segment||ordinal>segment.last?-1:segment.start+ordinal-segment.first+rowsLowerBound(segment.details,ordinal)};const offsets=new GridRowOffsets(count,this.rowH);for(const segment of runs)segment.details.forEach((ordinal,index)=>{const height=this.detailHeights?.get(this.detailKeyAt(ordinal));offsets.set(segment.start+ordinal-segment.first+index+1,height??this.detailEstimate())});if(this.autoRows)for(const[ordinal,height]of this.rowHeights){const index=recordIndex(ordinal);if(index>=0)offsets.set(index,height)}
9
9
  const projection={rowCount:()=>count,rowAt:top=>offsets.at(top),rowTop:index=>offsets.top(index),recordIndex,rowItem:index=>{const segment=rowsAtOrBefore(segments,index,"start");if(!segment||index>=segment.start+segment.length)return null;if(segment.header)return segment.header;const[ordinal,detail]=rowsResolve(segment,index-segment.start);return detail?this.detailItem(ordinal):this.rowItem(ordinal)},indexOf:row=>{if(!row)return -1;if(row.group)return this.groupHeaders?.get(row.group.key)===row?headers.get(row.group.key)??-1:-1;if(row.status==="detail"){const index=this.detailRecords?.get(row.detailOf)===row&&details.includes(row.ordinal)?recordIndex(row.ordinal):-1;return index<0?-1:index+1}
10
10
  return row._ri<0||this.indexOf(row)<0?-1:recordIndex(row._ri)},recordWindow:(first,last)=>{const ranges=[];for(const segment of runs){const start=Math.max(first,segment.start),end=Math.min(last,segment.start+segment.length-1);if(start<=end)ranges.push({first:rowsResolve(segment,start-segment.start)[0],last:rowsResolve(segment,end-segment.start)[0]})}
11
11
  return{first:ranges[0]?.first??bounds.first,last:ranges.at(-1)?.last??-1,ranges}}};this.rowProjection=projection;this.rowProjectionKey=key;this.rowOffsets=offsets;this.rowOffsetOwner=projection;this.rowOffsetFirst=bounds.first;this.relocateFocus?.(projection);this.total=offsets.top(count);this.grid.setAttribute("aria-rowcount",String(count +(this.summaryControls?.parentElement===this.grid?2:1)+(this.headDepth||0)));const index=anchor?projection.indexOf(anchor.item):-1;this.syncScrollRange(index>=0?projection.rowTop(index)+ anchor.offset:top)},syncRowOffsets(){if(!this.rowProjection)ROWS_BASE.syncRowOffsets.call(this)},render(){if(!this.rowsAttached)return ROWS_BASE.render.call(this);if(!this.destroyed&&this.acceptedQuery){this.acceptRows?.();this.prepareRowProjection()}
@@ -1,4 +1,5 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"99cafeec3da59ffa",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,gridButton,gridInputLabel,gridOption,gridScalar,gridDateInputMoment,gridQuery,gridQueryStart,GRID_FILTER_LABELS,gridResultShape,gridQueryRows,gridOrder,gridColumnProjection,gridCellRenderer,cellValue,cellText,foldText,reachable,gridMatchesGroup,gridDecorateButton,gridIcon,numberFormat,numberText,fill,announceMenu,reportingRangeRows,applyTone,toneOf,badgeElement,isCellObject,RECORD_IMPL}=context;const buildFilters=DataGrid.prototype.buildGridFilters;function gridOverlayTheme(grid,element){element.setAttribute("data-cx-theme",grid.host.getAttribute("data-cx-theme"));const theme=getComputedStyle(grid.host);element.style.colorScheme=theme.colorScheme;for(const name of theme)if(name.startsWith("--cx-"))element.style.setProperty(name,theme.getPropertyValue(name))}
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"8a058a87bbba3cbe",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,gridButton,gridInputLabel,gridOption,gridScalar,gridDateInputMoment,gridQuery,gridQueryStart,GRID_FILTER_LABELS,gridResultShape,gridQueryRows,gridOrder,gridColumnProjection,gridCellRenderer,cellValue,cellText,foldText,reachable,gridMatchesGroup,gridDecorateButton,gridIcon,numberFormat,numberText,fill,announceMenu,reportingRangeRows,applyTone,toneOf,badgeElement,isCellObject,RECORD_IMPL}=context;const buildFilters=DataGrid.prototype.buildGridFilters;function gridOverlayTheme(grid,element){for(const name of(grid.opts.overlayClass||"").split(/\s+/).filter(Boolean)) element.classList.add(name);
2
+ element.setAttribute("data-cx-theme",grid.host.getAttribute("data-cx-theme"));const theme=getComputedStyle(grid.host);element.style.colorScheme=theme.colorScheme;for(const name of theme)if(name.startsWith("--cx-"))element.style.setProperty(name,theme.getPropertyValue(name))}
2
3
  function gridRestoreFocus(grid,returnFocus,rowId,column){if(grid.destroyed)return;if(returnFocus?.isConnected&&reachable(returnFocus)){returnFocus.focus({preventScroll:true});return}
3
4
  const row=[...grid.rowCache.values()].find(item=>!item.status&&item.key===rowId);const index=grid.visibleColumns().findIndex(item=>item.id===column?.id);if(row&&index>=0)grid.focusCell(row,index,0);else if(row)grid.setFocus(row,true);else grid.grid.focus({preventScroll:true})}
4
5
  const COLUMNS_MENU={moveColumnChoice(column,target,after){column.pin=target.pin;const ids=this.columns.map(item=>item.id).filter(id=>id!==column.id);ids.splice(ids.indexOf(target.id)+ after,0,column.id);this.setColumnOrder(ids)},renderGridColumns(panel){if(!this.columnList){const find=this.columnFind=h("input","cx-grid-column-find"),actions=h("div","cx-grid-column-actions");find.type="search";find.placeholder=this.labels.gridFindColumn;find.setAttribute("aria-label",find.placeholder);find.hidden=this.columns.length<9;find.oninput=()=>this.syncColumnControls();actions.append(gridButton(this.labels.gridShowAllColumns,()=>this.showAllColumns()),gridButton(this.labels.gridResetColumns,()=>this.resetColumns()));panel.replaceChildren(find,this.columnList=h("div"),actions)}
@@ -1,4 +1,4 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"6f9f3c51b0ad2a0e",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,setClass,fill,gridFreeze,gridDateInputMoment,cellValue,cellText,foldText,toneOf,numberFormat,gridButton,gridOption,gridInputLabel,gridIcon,gridDecorateButton,GRID_REPORTING,loadPart,definePart,announceMenu,boundedPattern,wildcardMatch,svgIcon}=context;const FORMAT_OPERATORS={eq:1,ne:1,gt:1,ge:1,lt:1,le:1,contains:1,notContains:1,startsWith:1,endsWith:1,like:1,notLike:1,matches:1,notMatches:1,in:-1,notIn:-1,between:2,betweenExclusive:2,isNull:0,isNotNull:0,olderThan:1,withinLast:1};const FORMAT_TYPES=["text","number","boolean","date"],FORMAT_DAY=86400000;function formatError(message){throw new Error("CanopyX: "+message)}
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"1a28068597f435de",install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,setClass,fill,gridFreeze,gridDateInputMoment,cellValue,cellText,foldText,toneOf,numberFormat,gridButton,gridOption,gridInputLabel,gridIcon,gridDecorateButton,GRID_REPORTING,loadPart,definePart,announceMenu,boundedPattern,wildcardMatch,svgIcon}=context;const FORMAT_OPERATORS={eq:1,ne:1,gt:1,ge:1,lt:1,le:1,contains:1,notContains:1,startsWith:1,endsWith:1,like:1,notLike:1,matches:1,notMatches:1,in:-1,notIn:-1,between:2,betweenExclusive:2,isNull:0,isNotNull:0,olderThan:1,withinLast:1};const FORMAT_TYPES=["text","number","boolean","date"],FORMAT_DAY=86400000;function formatError(message){throw new Error("CanopyX: "+message)}
2
2
  function formatKeys(value,keys,what){if(!value||typeof value!=="object"||Array.isArray(value)||Object.keys(value).some(key=>!keys.includes(key)))formatError("invalid "+what+".")}
3
3
  const formatCultures=new Map();function formatCulture(culture){let entry=formatCultures.get(culture);if(!entry){let tag;try{tag=Intl.getCanonicalLocales(culture)[0]}catch(_){formatError("unknown culture '"+culture+"'.")}
4
4
  const parts=new Intl.NumberFormat(tag).formatToParts(12345.6),part=type=>(parts.find(item=>item.type===type)||{}).value;entry={tag,group:part("group")||",",decimal:part("decimal")||".",accent:new Intl.Collator(tag,{sensitivity:"accent"}),variant:new Intl.Collator(tag,{sensitivity:"variant"})};if(formatCultures.size<64)formatCultures.set(culture,entry)}
@@ -59,8 +59,9 @@ function formatFinish(target){if(!target)return target;if(target.icon===undefine
59
59
  Object.assign(DataGrid.prototype,{formatResult(data,details=false){const rules=this.formatCompiled;if(!rules||!rules.length)return null;const cache=details?this.formatDetailCache||=new WeakMap():this.formatCache;const stats=formatStats(this),cached=cache.get(data);if(cached&&cached.version===this.formatVersion)return cached.result;let row=null,cells=null;for(const rule of rules){if(!rule.enabled||rule.test&&!rule.test(data))continue;const style=formatResolve(rule,data,stats);if(style&&(!details||rule.details)){if(rule.target==="row")row=formatMerge(row,rule,style);else for(const id of rule.columns)(cells||=new Map()).set(id,formatMerge(cells.get(id),rule,style))}
60
60
  if(rule.stop)break}
61
61
  if(cells)for(const target of cells.values())formatFinish(target);const result=row||cells?{row:formatFinish(row),cells:cells||new Map()}:null;cache.set(data,{version:this.formatVersion,result});return result}});const FORMAT_RANK={danger:5,warning:4,info:3,success:2,neutral:1,muted:1};function formatUndo(fx){for(const node of fx.nodes)node.remove();for(const name of fx.props)fx.element.style.removeProperty(name);for(const name of fx.classes)fx.element.classList.remove(name)}
62
- function formatClear(element){for(const slot of element._cells||[])if(slot&&slot.fx){formatUndo(slot.fx);slot.fx=null}
63
- if(element._fx){formatUndo(element._fx);element._fx=null}}
62
+ function formatClearSlot(slot){if(slot.fx){formatUndo(slot.fx);slot.fx=null}
63
+ for(const child of slot.childSlots||[])formatClearSlot(child)}
64
+ function formatClear(element){for(const slot of element._cells||[])if(slot)formatClearSlot(slot);if(element._fx){formatUndo(element._fx);element._fx=null}}
64
65
  function formatEffects(element){return{element,nodes:[],props:[],classes:[]}}
65
66
  function formatDecorate(element,target,fx,before,emphasis){const add=name=>{if(!element.classList.contains(name)){element.classList.add(name);fx.classes.push(name)}};const prop=(target,name,value)=>{target.style.setProperty(name,value);if(target===element)fx.props.push(name)};const node=(tag,className,text)=>{const item=h(tag,className);if(text)setText(item,text);fx.nodes.push(item);return item};if(target.tone&&emphasis){add("cx-fmt-" +(target.bar||target.scale?"text":target.emphasis||emphasis));prop(element,"--cx-fmt",formatToneVar(target.tone))}
66
67
  if(target.scale){add("cx-fmt-scale");prop(element,"--cx-fmt-scale",target.scale)}
@@ -73,9 +74,9 @@ if(target.cue)element.appendChild(node("span","cx-fmt-cue",target.cue));if(targe
73
74
  const formatCues=target=>({glyph:target.glyph,badge:target.badge,cue:target.cue,tone:target.tone,descriptions:target.descriptions});const FORMAT_PAINT=DataGrid.prototype.paintGridHighlights;DataGrid.prototype.paintGridHighlights=function(element,row){FORMAT_PAINT.call(this,element,row);formatClear(element);const result=!row.status&&this.formatResult&&this.formatResult(row.h);if(!result)return;const slots=element._cells,first=slots[0];if(result.row){const own=result.row;if(own.tone||own.className||own.css){const fx=element._fx=formatEffects(element);setClass(element,element._cls +(own.tone?" cx-fmt-row":"")+(own.className?" "+own.className:""));if(own.tone){element.style.setProperty("--cx-fmt",formatToneVar(own.tone));fx.props.push("--cx-fmt")}
74
75
  if(own.css)for(const name of Object.keys(own.css)){element.style.setProperty(name,own.css[name]);fx.props.push(name)}}
75
76
  if(first){first.fx=formatEffects(first.cell);first.highlighted=true;formatDecorate(first.cell,formatCues(result.row),first.fx,first.textSpan)}}
76
- for(let i=0;i<this.cols.length;i++){const target=result.cells.get(this.cols[i].id),slot=slots[i];if(!target||!slot)continue;if(!slot.fx||slot.fx.element!==slot.cell)slot.fx=formatEffects(slot.cell);slot.highlighted=true;formatDecorate(slot.cell,target,slot.fx,slot.textSpan,"fill")}
77
- if(first&&first.hiddenFields){const details=this.formatResult(row.h,true);if(details)this.formatHiddenFields(first,details)}};const formatFlag=target=>target&&target.details&&(target.tone||target.glyph||target.badge||target.cue);DataGrid.prototype.formatHiddenFields=function(slot,result){const button=slot.hiddenFields,hidden=this.columns.filter(column=>(column.hidden||column.responsiveHidden||column.fitHidden)&&formatFlag(result.cells.get(column.id)));if(!hidden.length)return;let tone=null;for(const column of hidden){const next=result.cells.get(column.id).tone;if(next&&(FORMAT_RANK[next]||3)>(FORMAT_RANK[tone]||0))tone=next}
78
- const fx=slot.fx||(slot.fx=formatEffects(slot.cell)),effects=formatEffects(button),label=button.getAttribute("aria-label");fx.nodes.push({remove(){formatUndo(effects);button.setAttribute("aria-label",label)}});effects.classes.push("cx-fmt-flag");button.classList.add("cx-fmt-flag");button.style.setProperty("--cx-fmt",tone?formatToneVar(tone):"var(--cx-text-2)");effects.props.push("--cx-fmt");const rules=[...new Set(hidden.flatMap(column=>result.cells.get(column.id).descriptions))].join(", ");button.setAttribute("aria-label",label+". "+fill(this.formatLabels.formatHiddenFlagged,{count:hidden.length,rules}))};const FORMAT_MEASURE=DataGrid.prototype.measureGridColumn;DataGrid.prototype.measureGridColumn=function(column){if(column.formatMinW!==undefined){column.minW=column.formatMinW;delete column.formatMinW}
77
+ const columns=this.cols.concat(this.phoneLineColumns()),paintSlots=slots.concat(first?.childSlots||[]);for(let i=0;i<columns.length;i++){const target=result.cells.get(columns[i].id),slot=paintSlots[i];if(!target||!slot)continue;if(!slot.fx||slot.fx.element!==slot.cell)slot.fx=formatEffects(slot.cell);slot.highlighted=true;formatDecorate(slot.cell,target,slot.fx,slot.textSpan,"fill")}
78
+ if(first&&first.hiddenFields){const details=this.formatResult(row.h,true);if(details)this.formatHiddenFields(first,details)}};const formatFlag=target=>target&&target.details&&(target.tone||target.glyph||target.badge||target.cue);DataGrid.prototype.formatHiddenFields=function(slot,result){const button=slot.hiddenFields,hidden=this.phoneRemainingColumns().filter(column=>formatFlag(result.cells.get(column.id)));if(!hidden.length)return;let tone=null;for(const column of hidden){const next=result.cells.get(column.id).tone;if(next&&(FORMAT_RANK[next]||3)>(FORMAT_RANK[tone]||0))tone=next}
79
+ const fx=slot.fx||(slot.fx=formatEffects(slot.cell)),effects=formatEffects(button),label=button.getAttribute("aria-label");let decorated;fx.nodes.push({remove(){formatUndo(effects);if(button.getAttribute("aria-label")===decorated)button.setAttribute("aria-label",label)}});effects.classes.push("cx-fmt-flag");button.classList.add("cx-fmt-flag");button.style.setProperty("--cx-fmt",tone?formatToneVar(tone):"var(--cx-text-2)");effects.props.push("--cx-fmt");const rules=[...new Set(hidden.flatMap(column=>result.cells.get(column.id).descriptions))].join(", ");decorated=label+". "+fill(this.formatLabels.formatHiddenFlagged,{count:hidden.length,rules});button.setAttribute("aria-label",decorated)};const FORMAT_MEASURE=DataGrid.prototype.measureGridColumn;DataGrid.prototype.measureGridColumn=function(column){if(column.formatMinW!==undefined){column.minW=column.formatMinW;delete column.formatMinW}
79
80
  const result=FORMAT_MEASURE.call(this,column);if(!this.formatCompiled||!this.formatCompiled.length||!this.fitContext||!this.fitFont)return result;const context=this.fitContext,[font,small,,pad]=this.fitFont,first=column===this.columns.find(item=>!item.hidden);const measure=(text,face)=>{context.font=face;return context.measureText(String(text).slice(0,512)).width};let need=0,floor=0,sampled=0;for(const row of this.rowCache.values()){if(sampled++===200)break;const formats=this.formatResult(row.h),own=formats&&formats.cells.get(column.id),cues=first&&formats&&formats.row;if(!own&&!cues)continue;let width=measure(cellText(row.h.cells[column.id],column,this.fmt)||"—",font),flag=false;for(const target of[own,cues])if(target){if(target.glyph){width +=20;flag=true}
80
81
  if(target.badge){width +=measure(target.badge,small)+ 20;flag=true}
81
82
  if(target.cue){width +=measure(target.cue,small)+ 6;flag=true}