@evotecit/canopyx 0.3.2 → 0.3.3

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 (32) hide show
  1. package/CanopyX.Html/Assets/canopyx-editing.css +1 -6
  2. package/CanopyX.Html/Assets/canopyx-editing.js +4 -39
  3. package/CanopyX.Html/Assets/canopyx-flow-entry.css +178 -0
  4. package/CanopyX.Html/Assets/canopyx-flow-entry.js +78 -0
  5. package/CanopyX.Html/Assets/canopyx-flow-module.mjs +80 -0
  6. package/CanopyX.Html/Assets/canopyx-flow.css +180 -0
  7. package/CanopyX.Html/Assets/canopyx-flow.js +214 -0
  8. package/CanopyX.Html/Assets/canopyx-forms.css +8 -0
  9. package/CanopyX.Html/Assets/canopyx-forms.js +39 -0
  10. package/CanopyX.Html/Assets/canopyx-grid-module.mjs +15 -12
  11. package/CanopyX.Html/Assets/canopyx-grid.css +2 -0
  12. package/CanopyX.Html/Assets/canopyx-grid.js +15 -12
  13. package/CanopyX.Html/Assets/canopyx-interactive.js +6 -6
  14. package/CanopyX.Html/Assets/canopyx-labels-de.js +72 -0
  15. package/CanopyX.Html/Assets/canopyx-labels-es.js +72 -0
  16. package/CanopyX.Html/Assets/canopyx-labels-fr.js +72 -0
  17. package/CanopyX.Html/Assets/canopyx-labels-it.js +72 -0
  18. package/CanopyX.Html/Assets/canopyx-labels-pl.js +72 -0
  19. package/CanopyX.Html/Assets/canopyx-module.mjs +6 -6
  20. package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +15 -12
  21. package/CanopyX.Html/Assets/canopyx-reporting.css +2 -0
  22. package/CanopyX.Html/Assets/canopyx-reporting.js +15 -12
  23. package/CanopyX.Html/Assets/canopyx.js +6 -6
  24. package/JavaScript/flow.d.ts +232 -0
  25. package/JavaScript/grid.d.ts +8 -5
  26. package/JavaScript/locales/de.mjs +72 -0
  27. package/JavaScript/locales/es.mjs +72 -0
  28. package/JavaScript/locales/fr.mjs +72 -0
  29. package/JavaScript/locales/it.mjs +72 -0
  30. package/JavaScript/locales/pl.mjs +72 -0
  31. package/README.md +3 -2
  32. package/package.json +20 -1
@@ -12,12 +12,7 @@
12
12
  .cx-editor[aria-invalid="true"]{border-color:var(--cx-tone-danger);}
13
13
  .cx-kind-number .cx-editor{text-align:right;font-variant-numeric:tabular-nums;}
14
14
  .cx-editor.cx-editor-toggle{flex:none;width:var(--cx-size-18px);height:var(--cx-size-18px);margin:0 auto 0 var(--cx-size-6px);accent-color:var(--cx-accent);}
15
- .cx-editor-list,.cx-edit-tip{position:fixed;inset:auto;margin:0;box-sizing:border-box;background:var(--cx-surface);color:var(--cx-text);border:var(--cx-size-1px) solid var(--cx-line-strong);border-radius:var(--cx-radius);box-shadow:var(--cx-panel-shadow);font:var(--cx-size-13d5px)/1.4 var(--cx-font);}
16
- .cx-editor-list{max-height:var(--cx-size-240px);overflow:auto;padding:var(--cx-size-4px);}
17
- .cx-editor-option,.cx-editor-note{padding:var(--cx-size-6px) var(--cx-size-8px);border-radius:var(--cx-radius-sm);white-space:nowrap;}
18
- .cx-editor-option{cursor:pointer;}
19
- .cx-editor-option[aria-selected="true"],.cx-editor-option:hover{background:var(--cx-selected-bg);}
20
- .cx-editor-note{color:var(--cx-muted);}
15
+ .cx-edit-tip{position:fixed;inset:auto;margin:0;box-sizing:border-box;background:var(--cx-surface);color:var(--cx-text);border:var(--cx-size-1px) solid var(--cx-line-strong);border-radius:var(--cx-radius);box-shadow:var(--cx-panel-shadow);font:var(--cx-size-13d5px)/1.4 var(--cx-font);}
21
16
  .cx-edit-tip{max-width:var(--cx-size-280px);padding:var(--cx-size-6px) var(--cx-size-10px);border-left:var(--cx-size-3px) solid var(--cx-tone-danger);white-space:normal;}
22
17
  .cx-editbar{display:flex;align-items:center;gap:var(--cx-size-4px);height:var(--cx-control-h);padding:0 var(--cx-size-4px) 0 var(--cx-size-10px);border-radius:var(--cx-radius);background:var(--cx-head-bg);font-size:var(--cx-size-13d5px);}
23
18
  .cx-editbar[hidden]{display:none;}
@@ -1,11 +1,6 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"01965774e28f70bf",requires:["filter-editor"],install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,h,setText,fill,gridButton,gridDecorateButton,GRID_ICONS,cellValue,cellText,isCellObject,gridFreeze,gridMomentFromParts,numberFormat,gridQuery,foldText,requireIdentifier,checkDocument,validateGridRows,inlineGridSource,PROTOCOL,boundedPattern,validateGridFacetPage,RECORD_IMPL}=context;const EDIT_LABELS={gridEditCell:"Edit {column}",gridEditSave:"Save",gridEditCancel:"Cancel",gridEditDiscard:"Discard",gridEditUndo:"Undo",gridEditRedo:"Redo",gridEditEdited:"Edited",gridEditChanges:"Unsaved changes: {count}",gridEditErrors:"Errors: {count}",gridEditSaving:"Saving…",gridEditSaved:"Saved rows: {count}",gridEditFailed:"Rows not saved: {count}",gridEditRequired:"{column} is required.",gridEditNumber:"{column} must be a number.",gridEditDate:"{column} must be a valid date.",gridEditMin:"{column} must be at least {min}.",gridEditMax:"{column} must be at most {max}.",gridEditStep:"{column} must be a multiple of {step}.",gridEditMinLength:"{column} needs at least {min} characters.",gridEditMaxLength:"{column} allows at most {max} characters.",gridEditPattern:"{column} does not have the expected format.",gridEditChoice:"Choose {column} from the list.",gridEditConflict:"Someone else changed this row. Reload it and try again.",gridEditRejected:"Not saved: {error}",gridEditNotSaved:"Not saved.",gridEditNoMatches:"No matches",gridEditSearching:"Searching…",gridEditUndone:"Undone: {column}",gridEditRedone:"Redone: {column}",gridEditNone:"(none)"};const editCopy=value=>gridFreeze(JSON.parse(JSON.stringify(value===undefined?null:value)));const editSame=(left,right)=>left===right||left==null&&right==null;const editScalar=value=>value===null||typeof value==="string"||typeof value==="boolean"||Number.isFinite(value);const editBad=(id,problem)=>{throw new Error("CanopyX: column "+id+" "+problem+".")};function editOptions(list,id){if(list===undefined)return null;if(!Array.isArray(list)||list.length>1000)editBad(id,"needs an array of at most 1000 editor options");return list.map(item=>{const option=item!==null&&typeof item==="object"?item:{value:item};if(!["string","number","boolean"].includes(typeof option.value)||option.label!==undefined&&typeof option.label!=="string")
2
- editBad(id,"has an invalid editor option");return Object.freeze({value:option.value,label:option.label??String(option.value)})})}
3
- function editSpec(column,editors,columns){const id=column.id,raw=column.editor===undefined?{}:typeof column.editor==="string"?{type:column.editor}:column.editor;const rules=column.validation===undefined?{}:column.validation,object=value=>value&&typeof value==="object"&&!Array.isArray(value);if(!object(raw)||!object(rules))editBad(id,"has an invalid editor or validation");const type=raw.type??column.kind;if(typeof type!=="string"||!Object.hasOwn(editors,type))editBad(id,"names an editor nobody registered");const fits={number:["number"],text:["text"],toggle:["text"],date:["datetime","text"],datetime:["datetime","text"],time:["datetime","text"]}[type];if(fits&&!fits.includes(column.kind))editBad(id,"cannot use a "+type+" editor for its kind");const bound=value=>value===undefined||Number.isFinite(value)||typeof value==="string"&&!Number.isNaN(Date.parse(value));const count=value=>value===undefined||Number.isInteger(value)&&value>=0&&value<=1000000;if(raw.step!==undefined&&!(Number.isFinite(raw.step)&&raw.step>0)||![raw.min,raw.max,rules.min,rules.max].every(bound)||
4
- !count(rules.minLength)||!count(rules.maxLength)||rules.required!==undefined&&typeof rules.required!=="boolean"||
5
- [rules.pattern,rules.message,raw.placeholder].some(text=>text!==undefined&&typeof text!=="string"))editBad(id,"has invalid editor or validation settings");let pattern=null;if(rules.pattern!==undefined)try{pattern=boundedPattern(rules.pattern,true,true)}catch(error){editBad(id,"has an invalid or unsafe pattern")}
6
- const lookup=raw.lookup??(type==="lookup"?{}:null);if(lookup&&(!object(lookup)||lookup.column!==undefined&&!columns.some(item=>item.id===lookup.column)||
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"c82648dfaa7e5ee5",requires:["filter-editor","forms"],install(context){if(installed.has(context.DataGrid.prototype))return;const{DataGrid,FORMS_IMPL,h,setText,fill,gridButton,gridDecorateButton,GRID_ICONS,cellValue,cellText,isCellObject,gridFreeze,gridMomentFromParts,numberFormat,gridQuery,foldText,requireIdentifier,checkDocument,validateGridRows,inlineGridSource,PROTOCOL,boundedPattern,validateGridFacetPage,RECORD_IMPL}=context;const{EDIT_EDITORS,editOptions,formRuleSpec,formCheck,FORM_LABELS}=FORMS_IMPL;const EDIT_LABELS={...FORM_LABELS,gridEditCell:"Edit {column}",gridEditSave:"Save",gridEditCancel:"Cancel",gridEditDiscard:"Discard",gridEditUndo:"Undo",gridEditRedo:"Redo",gridEditEdited:"Edited",gridEditChanges:"Unsaved changes: {count}",gridEditErrors:"Errors: {count}",gridEditSaving:"Saving…",gridEditSaved:"Saved rows: {count}",gridEditFailed:"Rows not saved: {count}",gridEditConflict:"Someone else changed this row. Reload it and try again.",gridEditRejected:"Not saved: {error}",gridEditNotSaved:"Not saved.",gridEditUndone:"Undone: {column}",gridEditRedone:"Redone: {column}"};const editCopy=value=>gridFreeze(JSON.parse(JSON.stringify(value===undefined?null:value)));const editSame=(left,right)=>left===right||left==null&&right==null;const editScalar=value=>value===null||typeof value==="string"||typeof value==="boolean"||Number.isFinite(value);const editBad=(id,problem)=>{throw new Error("CanopyX: column "+id+" "+problem+".")};function editSpec(column,editors,columns){const id=column.id,raw=column.editor===undefined?{}:typeof column.editor==="string"?{type:column.editor}:column.editor;const rules=column.validation===undefined?{}:column.validation,object=value=>value&&typeof value==="object"&&!Array.isArray(value);if(!object(raw)||!object(rules))editBad(id,"has an invalid editor or validation");const type=raw.type??column.kind;if(typeof type!=="string"||!Object.hasOwn(editors,type))editBad(id,"names an editor nobody registered");const fits={number:["number"],text:["text"],toggle:["text"],date:["datetime","text"],datetime:["datetime","text"],time:["datetime","text"]}[type];if(fits&&!fits.includes(column.kind))editBad(id,"cannot use a "+type+" editor for its kind");const{pattern,rules:checks}=formRuleSpec(raw,rules,problem=>editBad(id,problem));const lookup=raw.lookup??(type==="lookup"?{}:null);if(lookup&&(!object(lookup)||lookup.column!==undefined&&!columns.some(item=>item.id===lookup.column)||
7
2
  lookup.limit!==undefined&&!(Number.isInteger(lookup.limit)&&lookup.limit>=1&&lookup.limit<=50)||
8
- lookup.minLength!==undefined&&!(Number.isInteger(lookup.minLength)&&lookup.minLength>=0&&lookup.minLength<=16)))editBad(id,"has invalid lookup settings");return Object.freeze({id,column,type,editor:gridFreeze({...raw,type}),options:editOptions(raw.options,id),lookup:lookup&&Object.freeze({column:lookup.column??id,limit:lookup.limit??20,minLength:lookup.minLength??1}),pattern,rules:Object.freeze({required:!!rules.required,min:rules.min??raw.min,max:rules.max??raw.max,step:raw.step,minLength:rules.minLength,maxLength:rules.maxLength,message:rules.message})})}
3
+ lookup.minLength!==undefined&&!(Number.isInteger(lookup.minLength)&&lookup.minLength>=0&&lookup.minLength<=16)))editBad(id,"has invalid lookup settings");return Object.freeze({id,column,type,editor:gridFreeze({...raw,type}),options:editOptions(raw.options,id),lookup:lookup&&Object.freeze({column:lookup.column??id,limit:lookup.limit??20,minLength:lookup.minLength??1}),pattern,rules:checks})}
9
4
  function editCell(original,value,text){if(!isCellObject(original)&&text===undefined)return value;const cell=isCellObject(original)?{...original}:{};delete cell.text;delete cell.searchText;delete cell.sort;delete cell.secondary;cell.value=value;if(text!==undefined)cell.text=text;return cell}
10
5
  Object.assign(DataGrid.prototype,{startEditing(){if(this.edit||this.destroyed)return;const options=this.opts.editing??{},view=this.view.editing??{},object=value=>Object(value)===value;const mode=options.mode??view.mode??"cell",drawer=options.drawer??view.drawer??"phone";if(!object(options)||!object(view)||["isEditable","validateRow","commit"].some(name=>options[name]!=null&&typeof options[name]!=="function")||
11
6
  ["validators","lookups","editors","formatters"].some(name=>options[name]!=null&&!object(options[name]))||
@@ -19,32 +14,7 @@ if(entry)for(const column of Object.keys(entry.values))
19
14
  this.editApply(id,this.edit.specs.get(column),cellValue(entry.original[column]),"revert",entry.row)}})},canUndo(){return this.edit.undo.length>0},canRedo(){return this.edit.redo.length>0},undo(){return this.editHistory(this.edit.undo,this.edit.redo,"before","undo")},redo(){return this.editHistory(this.edit.redo,this.edit.undo,"after","redo")},editHistory(from,to,key,source){if(!from.length||this.destroyed)return false;if(key==="before"&&from[from.length-1].some(step=>step.insert&&this.edit.changes.get(step.id)?.status==="saving"))return false;this.cancelEdit();const steps=from.pop(),order=key==="before"?[...steps].reverse():steps;for(const step of order){if(step.insert){if(key==="before")this.editRemoveInserted(step.id);else this.editInsert(step.insert,source);continue}
20
15
  const record=this.editRecord(step.id),spec=this.edit.specs.get(step.column);if(record&&spec)this.editApply(step.id,spec,step[key],source,record)}
21
16
  to.push(steps);const last=this.edit.specs.get(order.findLast(step=>!step.insert)?.column);this.editAnnounce(fill(this.edit.labels[source==="undo"?"gridEditUndone":"gridEditRedone"],{column:last?last.column.title:""}));this.syncEditBar();return true},setReadOnly(value){if(typeof value!=="boolean")throw new Error("CanopyX: setReadOnly takes a boolean.");if(value)this.cancelEdit();this.editReadOnly=value;this.version++;this.scheduleRender();this.syncEditBar()}});const EDIT_STORE_PAGE=DataGrid.prototype.storePage;DataGrid.prototype.storePage=function(page){const changes=this.edit?.changes;if(changes?.size)for(const item of page.items){const entry=changes.get(item.id);if(entry)for(const[column,value]of Object.entries(entry.values)){item.cells[column]=editCell(item.cells[column],value,this.editText(this.edit.specs.get(column),value,item))}}
22
- const result=EDIT_STORE_PAGE.call(this,page);if(changes?.size)for(const row of this.pages.get(page.offset)?.rows||[])if(changes.has(row.key))changes.get(row.key).row=row.h;const session=this.edit?.session;if(session&&this.rowCache.get(session.wrapper._ri)!==session.wrapper)this.editClose(true,true);return result};const editPad=(number,width=2)=>String(number).padStart(width,"0");function editInput(context,type="text"){const input=h("input","cx-editor");input.type=type;input.autocomplete="off";if(typeof context.editor.placeholder==="string")input.placeholder=context.editor.placeholder;return input}
23
- const EDIT_NUMBER_LOCALES=new Map();function editParseNumber(text,locale){let format=EDIT_NUMBER_LOCALES.get(locale);if(!format){let number;try{number=new Intl.NumberFormat(locale||undefined)}catch(error){number=new Intl.NumberFormat()}
24
- const digits=new Map();for(let digit=0;digit<10;digit++)digits.set(number.formatToParts(digit).find(part=>part.type==="integer").value,String(digit));format={parts:number.formatToParts(-12345.6),digits};if(EDIT_NUMBER_LOCALES.size>=32)EDIT_NUMBER_LOCALES.delete(EDIT_NUMBER_LOCALES.keys().next().value);EDIT_NUMBER_LOCALES.set(locale,format)}
25
- const parts=format.parts;const group=parts.find(part=>part.type==="group")?.value??",",decimal=parts.find(part=>part.type==="decimal")?.value??".";let source=Array.from(text.trim(),digit=>format.digits.get(digit)??digit).join("").replace(/[\u061c\u200e\u200f]/g, "").replace(/\u2212/g, "-");
26
- const pieces=source.split(decimal),whole=pieces[0],spaced=!group.trim();if(pieces.length>2)return NaN;const groups=whole.replace(/^[-+]/, "").split(spaced ? /[\s\u00a0\u202f]/ : group);
27
- if(groups.length>1&&(!/^\d{1,3}$/.test(groups[0]) || groups.slice(1).some(digits => !/^\d{3}$/.test(digits)))) return NaN;
28
- source=(/^[-+]/.test(whole) ? whole[0] : "") + groups.join("") + (pieces.length > 1 ? "." + pieces[1] : "");
29
- return /^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?$/i.test(source) ? Number(source) : NaN;
30
- }
31
- function editMoment(value,local){const date=typeof value==="string"?new Date(value):null;if(!date||Number.isNaN(date.getTime()))return null;const get=name=>date[(local?"get":"getUTC")+ name]();const seconds=get("Seconds"),milliseconds=get("Milliseconds");const time=editPad(get("Hours"))+ ":"+editPad(get("Minutes"))+
32
- (seconds||milliseconds?":"+editPad(seconds)+(milliseconds?"."+editPad(milliseconds,3):""):"");return{date:editPad(get("FullYear"),4)+ "-"+editPad(get("Month")+ 1)+ "-"+editPad(get("Date")),time}}
33
- function editFromParts(date,time,local){const[clock,fraction=""]=time.split("."),milliseconds=Number(fraction.padEnd(3,"0"));const[year,month,day]=date.split("-").map(Number),[hours,minutes,seconds=0]=clock.split(":").map(Number);const moment=gridMomentFromParts(year,month,day,hours,minutes,seconds,local);if(!moment||!Number.isInteger(milliseconds)||milliseconds<0||milliseconds>999)return null;moment.setUTCMilliseconds(milliseconds);return moment.toISOString().replace(".000Z","Z")}
34
- function editDateEditor(context,withDate,withTime){const local=context.timeZone==="local",moment=context.column.kind==="datetime";const input=editInput(context,withDate&&withTime?"datetime-local":withDate?"date":"time");if(moment&&withTime)input.step="0.001";const parts=moment?editMoment(context.value,local):null;const start=!moment?typeof context.value==="string"?context.value:"":!parts?"":
35
- withDate&&withTime?parts.date+"T"+parts.time:withDate?parts.date:parts.time;input.value=start;const displayed=input.value;let changed=false;input.addEventListener("input",()=>{changed=true});const read=()=>{if(!changed&&input.value===displayed||input.value===start)return context.value??null;if(!input.value)return null;if(!moment)return input.value;const[date,time]=withDate&&withTime?input.value.split("T"):withDate?[input.value,parts?parts.time:"00:00"]:
36
- [(parts||editMoment(new Date().toISOString(),local)).date,input.value];return editFromParts(date,time,local)};return{element:input,getValue:read,error:()=>input.validity?.badInput||moment&&input.value&&read()===null?fill(context.labels.gridEditDate,{column:context.column.title}):null}}
37
- function editNumberPlaces(value){const[coefficient,exponent="0"]=String(value).split("e");return Math.max(0,(coefficient.split(".")[1]||"").length-Number(exponent))}
38
- const EDIT_EDITORS={text:{create(context){const input=editInput(context);input.value=context.initial??(context.value==null?"":String(context.value));return{element:input,getValue:()=>input.value===""&&context.value==null?null:input.value}}},number:{create(context){const input=editInput(context),step=context.editor.step||1,rules=context.rules;const decimals=Math.max(editNumberPlaces(step),editNumberPlaces(rules.min??0));let plain;try{plain=new Intl.NumberFormat(context.locale||undefined,{useGrouping:false,maximumSignificantDigits:21})}
39
- catch(error){plain=new Intl.NumberFormat(undefined,{useGrouping:false,maximumSignificantDigits:21})}
40
- input.inputMode="decimal";input.value=context.initial??(typeof context.value==="number"?plain.format(context.value):"");const read=()=>input.value.trim()===""?null:editParseNumber(input.value,context.locale);input.addEventListener("keydown",event=>{if(event.key!=="ArrowUp"&&event.key!=="ArrowDown"||event.altKey)return;const value=read();if(!Number.isNaN(value)){let next=(value??0)+(event.key==="ArrowUp"?step:-step);if(typeof rules.min==="number")next=Math.max(rules.min,next);if(typeof rules.max==="number")next=Math.min(rules.max,next);input.value=plain.format(decimals<=100?Number(next.toFixed(decimals)):next)}
41
- event.preventDefault()});return{element:input,getValue:read,error:()=>Number.isNaN(read())?fill(context.labels.gridEditNumber,{column:context.column.title}):null}}},datetime:{create:context=>editDateEditor(context,true,true)},date:{create:context=>editDateEditor(context,true,false)},time:{create:context=>editDateEditor(context,false,true)},select:{create(context){const select=h("select","cx-editor"),options=(context.options||[]).slice();if(context.value!=null&&!options.some(option=>option.value===context.value))
42
- options.unshift({value:context.value,label:context.text||String(context.value)});if(!context.rules.required||context.value==null)select.appendChild(new Option(context.labels.gridEditNone,""));options.forEach((option,index)=>select.appendChild(new Option(option.label,String(index))));const at=options.findIndex(option=>option.value===context.value);select.value=at<0?"":String(at);return{element:select,getValue:()=>select.value===""?null:options[Number(select.value)].value}}},toggle:{create(context){const box=h("input","cx-editor cx-editor-toggle");box.type="checkbox";box.checked=context.value===true;return{element:box,getValue:()=>box.checked}}},lookup:{create:context=>editLookupEditor(context)}};function editLookupEditor(context){const input=editInput(context),popup=h("div","cx-editor-list"),note=h("div","cx-editor-note"),list=h("div"),id=context.id+"-list",labels=context.labels;input.setAttribute("role","combobox");input.setAttribute("aria-autocomplete","list");input.setAttribute("aria-expanded","false");input.setAttribute("aria-controls",id);list.id=id;list.setAttribute("role","listbox");list.setAttribute("aria-label",context.column.title);popup.popover="manual";popup.append(note,list);popup.addEventListener("pointerdown",event=>event.preventDefault());let options=[],active=-1,timer=0,request=null;let chosen=context.initial!==undefined||context.value==null?null:{value:context.value,label:context.text||String(context.value)};input.value=context.initial??(chosen?chosen.label:"");const paint=()=>{[...list.querySelectorAll("[role=option]")].forEach((option,index)=>option.setAttribute("aria-selected",String(index===active)));if(active>=0){input.setAttribute("aria-activedescendant",id+"-"+active);list.querySelector("#"+CSS.escape(id+"-"+active))?.scrollIntoView({block:"nearest"})}
43
- else input.removeAttribute("aria-activedescendant")};const close=()=>{clearTimeout(timer);request?.abort();input.setAttribute("aria-expanded","false");input.removeAttribute("aria-activedescendant");if(popup.matches(":popover-open"))popup.hidePopover()};const show=(items,message)=>{options=items;active=items.length?0:-1;list.replaceChildren();list.hidden=!items.length;setText(note,message||"");note.hidden=!message;items.forEach((item,index)=>{const option=h("div","cx-editor-option");option.id=id+"-"+index;option.setAttribute("role","option");setText(option,item.label);option.addEventListener("click",()=>pick(index));list.appendChild(option)});if(!input.isConnected)return;const box=input.getBoundingClientRect();popup.style.left=box.left+"px";popup.style.top=box.bottom+2+"px";popup.style.minWidth=box.width+"px";if(!popup.matches(":popover-open"))popup.showPopover();input.setAttribute("aria-expanded","true");paint()};const pick=index=>{chosen=options[index];input.value=chosen.label;context.remember(chosen);close()};const search=()=>{clearTimeout(timer);request?.abort();const text=input.value;if(text.length<context.lookupMinLength){close();return}
44
- show([],labels.gridEditSearching);timer=setTimeout(()=>{const controller=request=new AbortController();Promise.resolve(context.lookup(text,controller.signal)).then(items=>{if(!controller.signal.aborted)show(items,items.length?null:labels.gridEditNoMatches)},error=>{if(!controller.signal.aborted&&error.name!=="AbortError")show([],error.message)})},200)};input.addEventListener("input",()=>{chosen=null;search()});input.addEventListener("keydown",event=>{const open=input.getAttribute("aria-expanded")==="true";if(event.key==="ArrowDown"||event.key==="ArrowUp"){if(!open)search();else if(options.length){active=Math.max(0,Math.min(options.length-1,active +(event.key==="ArrowDown"?1:-1)));paint()}
45
- event.preventDefault()}else if(event.key==="Enter"&&open&&active>=0){pick(active);event.preventDefault()}
46
- else if(event.key==="Escape"&&open){close();event.preventDefault();event.stopPropagation()}});const typed=()=>{const text=foldText(input.value.trim());return options.find(option=>foldText(option.label)===text)||(chosen&&foldText(chosen.label)===text?chosen:null)};const element=h("span","cx-editor-combo");element.append(input,popup);if(typeof context.initial==="string")queueMicrotask(search);return{element,control:input,getValue:()=>!input.value.trim()?null:(chosen||typed())?.value??null,error:()=>input.value.trim()&&!chosen&&!typed()?fill(labels.gridEditChoice,{column:context.column.title}):null,destroy(){clearTimeout(timer);request?.abort();close();popup.remove()}}}
47
- Object.assign(DataGrid.prototype,{editColumnIndex(id){return this.visibleColumns().findIndex(column=>column.id===id)},editCreate(record,spec,mode,initial){const state=this.edit,abort=new AbortController(),cell=record.cells[spec.id];const context=Object.freeze({id:this.id+"-ed" + ++state.sequence,value:cellValue(cell),text:cellText(cell,spec.column,this.fmt),row:editCopy(record),column:Object.freeze({id:spec.id,title:spec.column.title,kind:spec.column.kind}),editor:spec.editor,options:spec.options,rules:spec.rules,labels:Object.freeze({...this.labels,...state.labels}),locale:this.lang,timeZone:this.local?"local":"utc",mode,initial,signal:abort.signal,lookupMinLength:spec.lookup?spec.lookup.minLength:1,commit:()=>{(mode==="form"?this.editFormSave():this.endEdit()).catch(error=>this.emit("error",{error}))},cancel:()=>mode==="form"?state.form?.dialog.close():this.cancelEdit(),lookup:(search,signal)=>this.editLookup(spec,search,record,signal),remember:option=>this.editRemember(spec.id,option)});const instance=state.editors[spec.type].create(context);if(!instance||!(instance.element instanceof Element)||typeof instance.getValue!=="function")
17
+ const result=EDIT_STORE_PAGE.call(this,page);if(changes?.size)for(const row of this.pages.get(page.offset)?.rows||[])if(changes.has(row.key))changes.get(row.key).row=row.h;const session=this.edit?.session;if(session&&this.rowCache.get(session.wrapper._ri)!==session.wrapper)this.editClose(true,true);return result};Object.assign(DataGrid.prototype,{editColumnIndex(id){return this.visibleColumns().findIndex(column=>column.id===id)},editCreate(record,spec,mode,initial){const state=this.edit,abort=new AbortController(),cell=record.cells[spec.id];const context=Object.freeze({id:this.id+"-ed" + ++state.sequence,value:cellValue(cell),text:cellText(cell,spec.column,this.fmt),row:editCopy(record),column:Object.freeze({id:spec.id,title:spec.column.title,kind:spec.column.kind}),editor:spec.editor,options:spec.options,rules:spec.rules,labels:Object.freeze({...this.labels,...state.labels}),locale:this.lang,timeZone:this.local?"local":"utc",mode,initial,signal:abort.signal,lookupMinLength:spec.lookup?spec.lookup.minLength:1,commit:()=>{(mode==="form"?this.editFormSave():this.endEdit()).catch(error=>this.emit("error",{error}))},cancel:()=>mode==="form"?state.form?.dialog.close():this.cancelEdit(),lookup:(search,signal)=>this.editLookup(spec,search,record,signal),remember:option=>this.editRemember(spec.id,option)});const instance=state.editors[spec.type].create(context);if(!instance||!(instance.element instanceof Element)||typeof instance.getValue!=="function")
48
18
  throw new Error("CanopyX: the editor "+spec.type+" must return an element and getValue.");const host=h("div","cx-editor-host"),control=instance.control||instance.element;host.appendChild(instance.element);control.setAttribute("aria-label",fill(state.labels.gridEditCell,{column:spec.column.title}));return{spec,instance,host,control,abort,record}},editDispose(field){field.abort.abort();try{field.instance.destroy?.()}catch(error){this.emit("error",{error})}
49
19
  field.host.remove();field.cell?.classList.remove("cx-editing")},editPreview(record,values){const preview={...record,cells:Object.assign(Object.create(null),record.cells)};for(const[spec,value]of values)if(!editSame(cellValue(record.cells[spec.id]),value))
50
20
  preview.cells[spec.id]=editCell(record.cells[spec.id],value,this.editText(spec,value,record));return preview},editStart(wrapper,column,initial){const state=this.edit,open=state.session;if(open?.wrapper===wrapper&&open.fields.has(column)){open.fields.get(column).control.focus({preventScroll:true});return true}
@@ -61,12 +31,7 @@ if(this.canEditCell(row.h,ids[at])){this.setFocus(row,false);if(this.editStart(r
61
31
  this.editFocusCell(wrapper,column)},editBlur(event){const session=this.edit.session,next=event.relatedTarget;if(!session||next&&[...session.fields.values()].some(field=>field.host.contains(next)))return;setTimeout(()=>{if(this.edit.session!==session||!document.hasFocus()||[...session.fields.values()].some(field=>field.host.contains(document.activeElement)))return;this.editClose(true,true);if(session.mode==="row")this.commitRow(session.id).catch(error=>this.emit("error",{error}))})}});const editErrors=entry=>Object.assign(Object.create(null),entry.rowErrors,entry.errors);const editInvalid=entry=>!!(entry.error||entry.rowError||Object.keys(editErrors(entry)).length);const editMessage=value=>typeof value==="string"&&value.trim()?value:null;const EDIT_VALIDATIONS=new WeakMap();function editCurrentValidation(grid,result){const state=grid.edit,entry=state.changes.get(result.id),stamp=EDIT_VALIDATIONS.get(result);if(!entry)return{id:result.id,valid:!grid.destroyed,errors:{}};const columns=Object.keys(entry.values),fresh=stamp&&stamp.entry===entry&&
62
32
  columns.length===Object.keys(stamp.values).length&&columns.every(column=>editSame(entry.values[column],stamp.values[column]))&&
63
33
  JSON.stringify(entry.row.cells)===stamp.row&&columns.every(column=>!state.checks.has(result.id+"\u0000"+column));return{id:result.id,valid:!!result.valid&&!!fresh&&!grid.destroyed&&!editInvalid({...entry,error:null}),errors:editErrors(entry),error:entry.rowError||null}}
64
- Object.assign(DataGrid.prototype,{editRules(spec,value,record){const rules=spec.rules,column=spec.column,labels=this.edit.labels;const say=(name,values)=>rules.message||fill(labels[name],{column:column.title,...values});const shown=bound=>column.kind==="datetime"?cellText(bound,column,this.fmt):typeof bound==="number"?numberFormat(this.lang).format(bound):String(bound);if(value==null||value===""){if(rules.required)return say("gridEditRequired")}
65
- else{if(column.kind==="number"&&!(typeof value==="number"&&Number.isFinite(value)&&Math.abs(value)<=Number.MAX_SAFE_INTEGER))return say("gridEditNumber");if(column.kind==="datetime"&&!(typeof value==="string"&&/^\d{4}-\d\d-\d\dT.*(?:Z|[+-]\d\d:\d\d)$/.test(value) && Number.isFinite(Date.parse(value)))) return say("gridEditDate");
66
- if(typeof value==="number"||column.kind==="datetime"){const at=column.kind==="datetime"?Date.parse(value):value,of=bound=>typeof bound==="string"?Date.parse(bound):bound;if(rules.min!==undefined&&at<of(rules.min))return say("gridEditMin",{min:shown(rules.min)});if(rules.max!==undefined&&at>of(rules.max))return say("gridEditMax",{max:shown(rules.max)});const base=typeof rules.min==="number"?rules.min:0,steps=rules.step&&(value-base)/ rules.step;
67
- if(typeof value==="number"&&rules.step&&Math.abs(steps-Math.round(steps))>1e-9)return say("gridEditStep",{step:shown(rules.step)})}
68
- if(typeof value==="string"&&column.kind!=="datetime"){if(rules.minLength!==undefined&&value.length<rules.minLength)return say("gridEditMinLength",{min:rules.minLength});if(rules.maxLength!==undefined&&value.length>rules.maxLength)return say("gridEditMaxLength",{max:rules.maxLength});if(spec.pattern&&!spec.pattern.test(value))return say("gridEditPattern")}}
69
- const validators=this.edit.options.validators,validator=validators&&Object.hasOwn(validators,spec.id)?validators[spec.id]:null;if(typeof validator!=="function")return null;let result;try{result=validator(value,editCopy(record))}catch(error){return error.message||String(error)}
34
+ Object.assign(DataGrid.prototype,{editRules(spec,value,record){const column=spec.column;const shown=bound=>column.kind==="datetime"?cellText(bound,column,this.fmt):typeof bound==="number"?numberFormat(this.lang).format(bound):String(bound);const failed=formCheck(spec,value,column.kind,column.title,this.edit.labels,shown);if(failed)return failed;const validators=this.edit.options.validators,validator=validators&&Object.hasOwn(validators,spec.id)?validators[spec.id]:null;if(typeof validator!=="function")return null;let result;try{result=validator(value,editCopy(record))}catch(error){return error.message||String(error)}
70
35
  if(!result||typeof result.then!=="function")return editMessage(result);return Promise.resolve(result).then(editMessage,error=>error?.message||String(error))},editCheck(id,spec,checked){const state=this.edit,entry=state.changes.get(id);if(!entry||!Object.hasOwn(entry.values,spec.id))return Promise.resolve(null);const value=entry.values[spec.id],key=id+"\u0000"+spec.id;const token={value,row:checked?.row??JSON.stringify(entry.row.cells)};state.checks.set(key,token);(entry.checked||=new Map()).set(spec.id,token);const settle=message=>{if(state.checks.get(key)!==token)return message;state.checks.delete(key);const current=state.changes.get(id);if(current&&editSame(current.values[spec.id],value)){if(message)current.errors[spec.id]=message;else delete current.errors[spec.id];this.editChanged();this.editRefreshMessage()}
71
36
  return message};const result=checked?checked.result:this.editRules(spec,value,entry.row);if(result&&typeof result.then==="function")return token.promise=result.then(settle);return token.promise=Promise.resolve(settle(result||null))},async editValidateRow(id,refresh=false){const state=this.edit,same=(left,right)=>Object.keys(left).length===Object.keys(right).length&&Object.keys(left).every(column=>editSame(left[column],right[column]));let entry,stable=false;for(let round=0;round<5;round++){entry=state.changes.get(id);if(!entry)return{id,valid:true,errors:{}};if(this.destroyed)break;const values={...entry.values},shape=JSON.stringify(entry.row.cells),used=new Map();const unchanged=()=>!this.destroyed&&state.changes.get(id)===entry&&same(values,entry.values)&&JSON.stringify(entry.row.cells)===shape;await Promise.all(Object.keys(values).map(column=>{const pending=state.checks.get(id+"\u0000"+column),checked=entry.checked?.get(column);const matches=token=>token&&token.row===shape&&editSame(token.value,values[column]);const kept=!refresh&&matches(checked)?checked:matches(pending)?pending:null;const result=kept?.promise??this.editCheck(id,state.specs.get(column));used.set(column,entry.checked?.get(column));return result}));for(const[column,token]of used)if(entry.checked?.get(column)===token)entry.checked.delete(column);const validator=state.options.validateRow;if(validator&&unchanged()){let result;try{result=await validator(editCopy(entry.row),this.editChangeOf(entry))}catch(error){result=error.message||String(error)}
72
37
  if(unchanged()){entry.rowError=editMessage(result);entry.rowErrors=Object.create(null);if(result&&typeof result==="object")for(const[column,message]of Object.entries(result))
@@ -0,0 +1,178 @@
1
+ /* CanopyX theme. */
2
+ .cx-host,.cx-static{
3
+ --cx-surface:#ffffff;
4
+ --cx-text:#16181c;
5
+ --cx-accent:#11776f;
6
+ --cx-line:#e2e4e7;
7
+ --cx-tone-info:#0a728b;
8
+ --cx-tone-success:#177345;
9
+ --cx-tone-warning:#8c5c05;
10
+ --cx-tone-danger:#b8292a;
11
+ --cx-font:Calibri,Carlito,"Segoe UI",system-ui,sans-serif;
12
+ --cx-font-mono:"IBM Plex Mono","Cascadia Mono",Consolas,monospace;
13
+ --cx-font-size:14px;
14
+ --cx-radius:6px;
15
+ --cx-radius-sm:4px;
16
+ --cx-control-h:32px;
17
+ --cx-row-h:28px;
18
+ --cx-head-h:36px;
19
+ --cx-cell-px:12px;
20
+ --cx-on-accent:#ffffff;
21
+ --cx-shadow-ink:#000;
22
+ --cx-size-d04em:.04em;
23
+ --cx-size-46ch:46ch;
24
+ --cx-size-440px:440px;
25
+ --cx-size-140px:140px;
26
+ --cx-size-d06em:.06em;
27
+ --cx-size-210px:210px;
28
+ --cx-size-neg-4px:-4px;
29
+ --cx-size-56px:56px;
30
+ --cx-size-64px:64px;
31
+ --cx-size-160px:160px;
32
+ --cx-size-12d5px:12.5px;
33
+ --cx-size-640px:640px;
34
+ --cx-size-17px:17px;
35
+ --cx-size-60vh:60vh;
36
+ --cx-size-11d5px:11.5px;
37
+ --cx-size-13d5px:13.5px;
38
+ --cx-size-38px:38px;
39
+ --cx-size-26px:26px;
40
+ --cx-size-52px:52px;
41
+ --cx-size-30px:30px;
42
+ --cx-size-72px:72px;
43
+ --cx-size-244px:244px;
44
+ --cx-size-44px:44px;
45
+ --cx-size-85vh:85vh;
46
+ --cx-size-480px:480px;
47
+ --cx-size-14px:14px;
48
+ --cx-size-1px:1px;
49
+ --cx-size-neg-1px:-1px;
50
+ --cx-size-8px:8px;
51
+ --cx-size-12px:12px;
52
+ --cx-size-4px:4px;
53
+ --cx-size-10px:10px;
54
+ --cx-size-11px:11px;
55
+ --cx-size-1d5px:1.5px;
56
+ --cx-size-21px:21px;
57
+ --cx-size-5px:5px;
58
+ --cx-size-280px:280px;
59
+ --cx-size-34px:34px;
60
+ --cx-size-6px:6px;
61
+ --cx-size-32px:32px;
62
+ --cx-size-7px:7px;
63
+ --cx-size-420px:420px;
64
+ --cx-size-100vw:100vw;
65
+ --cx-size-16px:16px;
66
+ --cx-size-320px:320px;
67
+ --cx-size-2px:2px;
68
+ --cx-size-d8em:.8em;
69
+ --cx-size-d85em:.85em;
70
+ --cx-size-neg-2px:-2px;
71
+ --cx-size-180px:180px;
72
+ --cx-size-d92em:.92em;
73
+ --cx-size-9px:9px;
74
+ --cx-size-neg-3px:-3px;
75
+ --cx-size-18px:18px;
76
+ --cx-size-1d6px:1.6px;
77
+ --cx-size-999px:999px;
78
+ --cx-size-12em:12em;
79
+ --cx-size-100vh:100vh;
80
+ --cx-size-340px:340px;
81
+ --cx-size-20px:20px;
82
+ --cx-size-36px:36px;
83
+ --cx-size-3px:3px;
84
+ --cx-size-13px:13px;
85
+ --cx-size-360px:360px;
86
+ --cx-size-22px:22px;
87
+ --cx-size-24px:24px;
88
+ --cx-size-80vh:80vh;
89
+ --cx-size-240px:240px;
90
+ --cx-size-d875em:.875em;
91
+ --cx-size-92px:92px;
92
+ --cx-size-28px:28px;
93
+ --cx-text-2:color-mix(in srgb,var(--cx-text) 78%,var(--cx-surface));
94
+ --cx-muted:color-mix(in srgb,var(--cx-text) 66%,var(--cx-surface));
95
+ --cx-control-border:color-mix(in srgb,var(--cx-text) 54%,var(--cx-surface));
96
+ --cx-head-bg:color-mix(in srgb,var(--cx-text) 3.5%,var(--cx-surface));
97
+ --cx-group-bg:color-mix(in srgb,var(--cx-text) 2.5%,var(--cx-surface));
98
+ --cx-alt-bg:var(--cx-group-bg);
99
+ --cx-hover-bg:color-mix(in srgb,var(--cx-text) 4%,var(--cx-surface));
100
+ --cx-selected-bg:color-mix(in srgb,var(--cx-accent) 11%,var(--cx-surface));
101
+ --cx-accent-text:color-mix(in srgb,var(--cx-accent) 98%,var(--cx-text));
102
+ --cx-accent-soft:color-mix(in srgb,var(--cx-accent) 13%,var(--cx-surface));
103
+ --cx-line-strong:color-mix(in srgb,var(--cx-text) 22%,var(--cx-surface));
104
+ --cx-row-line:var(--cx-line);
105
+ --cx-panel-shadow:0 8px 28px color-mix(in srgb,var(--cx-shadow-ink) 14%,transparent),0 1px 3px color-mix(in srgb,var(--cx-shadow-ink) 8%,transparent);
106
+ --cx-tone-danger-bg:color-mix(in srgb,var(--cx-tone-danger) 11%,var(--cx-surface));
107
+ --cx-tone-warning-bg:color-mix(in srgb,var(--cx-tone-warning) 13%,var(--cx-surface));
108
+ --cx-tone-success-bg:color-mix(in srgb,var(--cx-tone-success) 11%,var(--cx-surface));
109
+ --cx-tone-info-bg:color-mix(in srgb,var(--cx-tone-info) 11%,var(--cx-surface));
110
+ --cx-quiet:color-mix(in srgb,var(--cx-tone-success) 55%,var(--cx-surface));
111
+ --cx-scrollbar-thumb:color-mix(in srgb,var(--cx-text) 28%,transparent);
112
+ --cx-scrollbar-thumb-hover:color-mix(in srgb,var(--cx-text) 46%,transparent);
113
+ --cx-scrollbar-track:transparent;
114
+ --cx-scrollbar-size:10px;
115
+ color-scheme:light;
116
+ }
117
+ .cx-host[data-cx-theme="dark"],.cx-static[data-cx-theme="dark"]{
118
+ color-scheme:dark;
119
+ --cx-surface:#16181c;
120
+ --cx-text:#f2f3f5;
121
+ --cx-accent:#37c9b8;
122
+ --cx-line:#272a30;
123
+ --cx-tone-info:#3ec3dd;
124
+ --cx-tone-success:#4cc78a;
125
+ --cx-tone-warning:#eeb640;
126
+ --cx-tone-danger:#f47171;
127
+ --cx-on-accent:#0c0d10;
128
+ --cx-head-bg:color-mix(in srgb,var(--cx-text) 4.5%,var(--cx-surface));
129
+ --cx-panel-shadow:0 10px 32px color-mix(in srgb,var(--cx-shadow-ink) 55%,transparent),0 0 0 1px var(--cx-line);
130
+ }
131
+ @media (prefers-color-scheme:dark){
132
+ .cx-host[data-cx-theme="auto"],.cx-static[data-cx-theme="auto"]{
133
+ color-scheme:dark;
134
+ --cx-surface:#16181c;
135
+ --cx-text:#f2f3f5;
136
+ --cx-accent:#37c9b8;
137
+ --cx-line:#272a30;
138
+ --cx-tone-info:#3ec3dd;
139
+ --cx-tone-success:#4cc78a;
140
+ --cx-tone-warning:#eeb640;
141
+ --cx-tone-danger:#f47171;
142
+ --cx-on-accent:#0c0d10;
143
+ --cx-head-bg:color-mix(in srgb,var(--cx-text) 4.5%,var(--cx-surface));
144
+ --cx-panel-shadow:0 10px 32px color-mix(in srgb,var(--cx-shadow-ink) 55%,transparent),0 0 0 1px var(--cx-line);
145
+ }
146
+ }
147
+ .cx-host{
148
+ box-sizing:border-box;
149
+ display:flex;
150
+ flex-direction:column;
151
+ height:var(--cx-size-480px);
152
+ min-height:0;
153
+ color:var(--cx-text);
154
+ background:var(--cx-surface);
155
+ font-family:var(--cx-font,inherit);
156
+ font-size:var(--cx-font-size,var(--cx-size-14px));
157
+ line-height:1.4;
158
+ }
159
+ .cx-host *,.cx-host *::before,.cx-host *::after{box-sizing:border-box;}
160
+ .cx-host .cx-button.cx-filter-chip{display:inline-flex;align-items:center;justify-content:flex-start;gap:var(--cx-size-6px);min-width:0;}
161
+ .cx-filter-chip > .cx-chip-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
162
+ .cx-filter-chip > .cx-chip-remove{flex:none;}
163
+ @media print{.cx-filter-chip > .cx-chip-label{white-space:normal;overflow:visible;text-overflow:clip;}}
164
+ @media not (forced-colors:active){
165
+ :where(.cx-host,.cx-host *){scrollbar-color:var(--cx-scrollbar-thumb) var(--cx-scrollbar-track);scrollbar-width:thin;}
166
+ .cx-host::-webkit-scrollbar,.cx-host ::-webkit-scrollbar{width:var(--cx-scrollbar-size);height:var(--cx-scrollbar-size);}
167
+ .cx-host::-webkit-scrollbar-track,.cx-host ::-webkit-scrollbar-track,.cx-host ::-webkit-scrollbar-corner{background:var(--cx-scrollbar-track);}
168
+ .cx-host::-webkit-scrollbar-thumb,.cx-host ::-webkit-scrollbar-thumb{background:var(--cx-scrollbar-thumb) padding-box;border:var(--cx-size-2px) solid transparent;border-radius:var(--cx-size-999px);}
169
+ .cx-host ::-webkit-scrollbar-thumb:hover{background-color:var(--cx-scrollbar-thumb-hover);}
170
+ }
171
+ .cx-sr-only{
172
+ --cx-size-1px:1px;--cx-size-neg-1px:-1px;
173
+ position:absolute !important;
174
+ width:var(--cx-size-1px);height:var(--cx-size-1px);padding:0;margin:var(--cx-size-neg-1px);
175
+ overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
176
+ }
177
+ .cx-host.cx-flow-host{min-height:0;display:block;background:transparent;}
178
+ .cx-host:where(.cx-flow-host:not([data-cx-height])){height:auto;}
@@ -0,0 +1,78 @@
1
+ /* CanopyX flow entry: forms and guided flows. */
2
+ (function(global){"use strict";if(global.CanopyX){return}
3
+ function h(tag,className){const element=document.createElement(tag);if(className)element.className=className;return element}
4
+ function setText(element,value){element.textContent=value==null?"":String(value)}
5
+ const PLUGINS=new Map();function fill(template,values){return template.replace(/\{(\w+)\}/g, (match, name) => (values && Object.hasOwn(values, name) ? String(values[name]) : match));
6
+ }
7
+ const pluralRules=new Map();function plural(template,count,lang){if(!template.startsWith("["))return template;const forms={};for(const match of template.matchAll(/\[(zero|one|two|few|many|other)\]([^[]*)/g)) forms[match[1]] = match[2];
8
+ let rules=pluralRules.get(lang);if(!rules){try{rules=new Intl.PluralRules(lang||"en")}catch(_){rules=new Intl.PluralRules("en")}pluralRules.set(lang,rules)}
9
+ return forms[rules.select(Number(count))]??forms.other??template}
10
+ const numberFormats=new Map();function numberFormat(lang,minimum=0,maximum=3){const key=(lang||"")+ "|"+minimum+"|"+maximum;let format=numberFormats.get(key);if(!format){const digits={minimumFractionDigits:minimum,maximumFractionDigits:maximum};try{format=new Intl.NumberFormat(lang||undefined,digits)}catch(e){format=new Intl.NumberFormat(undefined,digits)}
11
+ if(numberFormats.size>=16)numberFormats.delete(numberFormats.keys().next().value);numberFormats.set(key,format)}
12
+ return format}
13
+ function numberText(value,lang,format){if(!format)return numberFormat(lang).format(value);const minimum=format.minimumFractionDigits??0,maximum=format.maximumFractionDigits??3;const magnitude=Math.abs(value),fixed=magnitude>=1e21?BigInt(magnitude).toString():magnitude.toFixed(maximum);const signed=value<0&&Number(fixed)!==0?"-"+fixed:fixed;const parts=numberFormat(lang,minimum,maximum).formatToParts(signed);const at=parts.findIndex(part=>!["minusSign","literal"].includes(part.type));return parts.map((part,index)=>(index===at&&format.prefix||"")+ part.value).join("")+(format.suffix||"")}
14
+ let pageNonce=typeof document!=="undefined"&&document.currentScript?.nonce||"",trustPolicy;function setNonce(nonce){if(typeof nonce!=="string"||!nonce)throw new Error("CanopyX: invalid nonce.");pageNonce=nonce}
15
+ function withNonce(element){const nonce=pageNonce||document.querySelector("script[nonce]")?.nonce;if(nonce)element.nonce=nonce;return element}
16
+ function trusted(kind,value){if(trustPolicy===undefined){try{trustPolicy=global.trustedTypes?.createPolicy("canopyx",{createHTML:text=>text,createScript:text=>text,createScriptURL:text=>text})||null}
17
+ catch(_){trustPolicy=null}}
18
+ return trustPolicy?trustPolicy["create"+kind](value):value}
19
+ function hostHtml(element,html,sanitize,kind){const text=String(html??""),value=sanitize?sanitize(text,{kind}):text;if(value?.nodeType)element.replaceChildren(value);else if(typeof value==="string")element.innerHTML=trusted("HTML",value);else if(global.trustedTypes?.isHTML?.(value))element.innerHTML=value;else throw new Error("CanopyX: invalid sanitizeHtml result.")}
20
+ function boundedPattern(source,exact=true,whole=false){const bad=()=>{throw new Error("CanopyX: pattern requires literals, character classes and at most one variable repetition anchored at the start; groups, alternatives and backreferences are unsupported.")};if(typeof source!=="string"||source.length>256)bad();let width=0,variable=0;for(let i=0;i<source.length;){const unit=source[i++];if(unit==="^"&&i===1||unit==="$"&&i===source.length)continue;if(unit==="\\"){const escape=source[i++];if(!escape||!/^[dDsSwWbBnrtfv\\.^$*+?{}\[\]()|/]$/.test(escape)) bad();
21
+ }else if(unit==="["){let closed=false;while(i<source.length){const c=source[i++];if(c==="\\"){const escape=source[i++];if(!escape||!/^[dDsSwWbnrtfv\\.^$*+?{}\[\]()|/-]$/.test(escape)) bad(); }
22
+ else if(c==="]"){closed=true;break}}
23
+ if(!closed)bad()}else if("()|^$*+?{}]".includes(unit))bad();let count=1;if(i<source.length&&"*+?".includes(source[i])){variable++;i++}
24
+ else if(source[i]==="{"){const match=/^\{(\d+)(?:,(\d*))?\}/.exec(source.slice(i));
25
+ if(!match)bad();const low=Number(match[1]),high=match[2]===undefined?low:match[2]===""?Infinity:Number(match[2]);if(low>256||high<low||high!==Infinity&&high>256)bad();if(low!==high)variable++;count=low===high?low:1;i +=match[0].length}
26
+ width +=count;if(width>256||variable>1)bad()}
27
+ if(variable&&!whole&&source[0]!=="^")bad();return new RegExp(whole?"^(?:"+source+")$":source,exact?"u":"iu")}
28
+ function wildcardMatch(pattern,value,exact){if(!exact){pattern=pattern.toLowerCase();value=value.toLowerCase()}
29
+ let p=0,v=0,star=-1,retry=0;while(v<value.length){if(p<pattern.length&&(pattern[p]==="?"||pattern[p]===value[v])){p++;v++}
30
+ else if(pattern[p]==="*"){star=p++;retry=v}
31
+ else if(star>=0){p=star+1;v=++retry}
32
+ else return false}
33
+ while(pattern[p]==="*")p++;return p===pattern.length}
34
+ const EXTRA_FOLD_PAIRS="1t.w.q.1,2u.w.n.1,2.w.7.1,y.1.o.2,4.1.3.2,3.1.8.2,3.1.n.2,2.-3d,1.1.3.2,4.5u,1.1,2.1,2.5q,1.1,2.5p,1.5p,1.1,3.27,1.5m,1.5n,1.1,2.5p,1.5r,2.5v,1.5t,1.1,4.5v,1.5x,2.5y,1.1.3.2,2.62,1.1,2.62,3.1,2.62,1.1,2.61,1.61,1.1,2.1,2.63,1.1,4.1,8.2,1.1,2.2,1.1,2.2,1.1.9.2,3.1.9.2,3.2,1.1,2.1,2.-2p,1.-1k,1.1.k.2,2.-3m,2.1.9.2,8.8bv,1.1,2.-4j,1.8bs,3.1,2.-5f,1.1x,1.1z,1.1.5.2,82.1,2.1,4.1,9.38,7.12,2.11.3.1,2.1s,2.1r,1.1r,2.w.h.1,2.w.9.1,10.8,9.1.c.2,6.-1o,3.1,2.-7,1.1,3.-3m.3.1,1.28.g.1,1.w.w.1,1d.1.h.2,a.1.r.2,2.f,1.1.7.2,3.1.1c.2,3.1c.12.1,28a.5ls.12.1,2.5ls,6.5ls,k3.tzk.28.1,1.8.6.1,1p0.1,7.-2bk.17.1,3.-2bk.3.1,8x.1.23.2,a.-5vj,2.1.1c.2,a.-8.8.1,9.-8.6.1,b.-8.8.1,9.-8.8.1,9.-8.6.1,c.-8.4.2,9.-8.8.1,p.-8.8.1,9.-8.8.1,9.-8.8.1,9.-8,1.-8,1.-22,1.-22,1.-9,c.-2e.4.1,1.-9,c.-8,1.-8,1.-2s,1.-2s,d.-8,1.-8,1.-34,1.-34,1.-7,c.-3k,1.-3k,1.-3i,1.-3i,1.-9,8a.-5st,4.-6gv,1.-6di,7.s,1a.g.g.1,k.1,mr.q.q.1,1f5.1c.1c.1,1d.1,2.-8af,1.-2xy,1.-89z,3.1.3.2,2.-8bg,1.-8al,1.-8bj,1.-8bi,2.1,3.1,9.-8cf,1.-8cf,1.1.1e.2,9.1,2.1,5.1,nym.1.n.2,k.1.e.2,3s.1.7.2,4.1.v.2,b.1,2.1,2.-r9g,1.1.5.2,5.1,2.-wmg,3.1,2.1,4.1.a.2,2.-wn8,1.-wnj,1.-wnf,1.-wn5,1.-wn8,2.-wlu,1.-wmi,1.-wlx,1.ps,1.1.8.2,2.-1c,1.-wn7,1.-raw,1.1,2.1,2.-wo7,1.1,4.1,6.1.3.2,2.-wu9,p.1,h7w.w.q.1";const EXTRA_LOWERCASE=new Map();let extraUnit=0;for(const record of EXTRA_FOLD_PAIRS.split(",")){const[gap,delta,count=1,step=0]=record.split(".").map(part=>parseInt(part,36));extraUnit +=gap;for(let i=0;i<count;i++)EXTRA_LOWERCASE.set(extraUnit+i*step,extraUnit+i*step+delta);extraUnit +=(count-1)*step}
35
+ function foldCode(c){if(c<0x80){return c>=65&&c<=90?c+32:c}
36
+ return EXTRA_LOWERCASE.get(c)||c}
37
+ function foldUnit(unit){return String.fromCharCode(foldCode(unit.charCodeAt(0)))}
38
+ function isSeparator(code){return code<128&&!((code>=48&&code<=57)||(code>=65&&code<=90)||(code>=97&&code<=122))}
39
+ function foldText(text){const source=String(text==null?"":text);if(!/[^\0-\x7f]/.test(source)) return source.toLowerCase();
40
+ let out="";for(let i=0;i<source.length;i++){out +=foldUnit(source.charAt(i))}
41
+ return out}
42
+ function words(text){const folded=foldText(text);const out=[];let current="";for(let i=0;i<folded.length;i++){if(isSeparator(folded.charCodeAt(i))){if(current){out.push(current);current=""}}else{current +=folded.charAt(i)}}
43
+ if(current){out.push(current)}
44
+ return out}
45
+ function matchesWords(labelWords,queryWords){return queryWords.every((q)=>labelWords.some((w)=>w.startsWith(q)))}
46
+ function trimSeparators(text){let start=0,end=text.length;while(start<end&&isSeparator(text.charCodeAt(start)))start++;while(end>start&&isSeparator(text.charCodeAt(end-1)))end--;return text.slice(start,end)}
47
+ function rankLabel(label,foldedQuery){const l=trimSeparators(foldText(label));return l===foldedQuery?0:l.startsWith(foldedQuery)?1:2}
48
+ function shardKey(prefix){let out="";for(let i=0;i<prefix.length;i++){const code=prefix.charCodeAt(i);out +=(code>=97&&code<=122)||(code>=48&&code<=57)?prefix.charAt(i):"_" +("000"+code.toString(16)).slice(-4)}
49
+ return out}
50
+ function gridMomentFromParts(year,month,day,hours,minutes,seconds,local){const parts=[year,month,day,hours,minutes,seconds];if(!parts.every(Number.isInteger))return null;const moment=new Date(0),zone=local?"":"UTC";moment["set"+zone+"FullYear"](year,month-1,day);moment["set"+zone+"Hours"](hours,minutes,seconds,0);const actual=["FullYear","Month","Date","Hours","Minutes","Seconds"].map(name=>moment["get"+zone+name]());actual[1]++;return parts.every((value,index)=>value===actual[index])?moment:null}
51
+ function gridDateInputMoment(text,local){const match=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?$/.exec(text);
52
+ if(!match)return null;const moment=gridMomentFromParts(Number(match[1]),Number(match[2]),Number(match[3]),Number(match[4]),Number(match[5]),Number(match[6]||0),local);if(moment)moment.setTime(moment.getTime()+ Number((match[7]||"").padEnd(3,"0")));return moment}
53
+ function gridFreeze(value){if(value&&typeof value==="object"&&!Object.isFrozen(value)){Object.values(value).forEach(gridFreeze);Object.freeze(value)}
54
+ return value}
55
+ const PART_URL=typeof document!=="undefined"&&document.currentScript?.src;const PARTS={"forms":["a8c60f205d9babca","canopyx-forms.16987de1cdf9bda9.js","canopyx-forms.29058ce3c88af96e.css"],"flow":["75b9643b282dddb7","canopyx-flow.52d8d6168378df0c.js","canopyx-flow.3152e616c5557452.css"]},PART_CODE=new Map(),partName=name=>typeof name==="string"&&/^[a-z][a-z\d-]{0,31}$/.test(name);
56
+ function registerPart(name,part){if(!partName(name)||!part||typeof part.version!=="string"||typeof part.install!=="function"||
57
+ part.requires!==undefined&&!(Array.isArray(part.requires)&&part.requires.every(partName)))throw new Error("CanopyX: invalid part.");PART_CODE.set(name,part)}
58
+ function definePart(View,name,label,context){const parts=View.prototype.parts||={};return parts[name]={name,label,context,parts}}
59
+ function loadPart(part){return part.ready||=Promise.resolve().then(async()=>{const[version,scriptFile,styleFile]=Object.hasOwn(PARTS,part.name)?PARTS[part.name]:[];part.version=version;if(!version)throw new Error("CanopyX: this runtime has no "+part.label+".");if(PART_CODE.get(part.name)?.version!==part.version||part.styleReady===null){const find=type=>[...document.querySelectorAll('script[type="application/x-canopyx-'+type+'"][data-cx-version="'+part.version+'"]')].find(item=>!pageNonce||item.nonce===pageNonce);const payload=find(part.name),script=h("script"),embeddedStyle=styleFile&&find(part.name+"-style");if(payload){if(embeddedStyle){const style=h("style");style.nonce=payload.nonce;setText(style,embeddedStyle.textContent);document.head.appendChild(style)}
60
+ script.textContent=trusted("Script",payload.textContent);script.nonce=payload.nonce;document.head.appendChild(script);script.remove()}else{const base=global.CanopyXFilterEditorBase,runtime=PART_URL||typeof base==="string"&&base||document.baseURI,hashed=PART_URL&&/\.[a-f\d]{16}\.js(?:[?#]|$)/.test(PART_URL);
61
+ const load=(element,attribute,file,kind)=>new Promise((resolve,reject)=>{const url=new URL(file,runtime).href;element[attribute]=attribute==="src"?trusted("ScriptURL",url):url;element.onload=resolve;element.onerror=()=>{element.remove();reject(new Error("CanopyX: "+part.label+" "+kind+" unavailable."))};document.head.appendChild(withNonce(element))});const link=h("link");link.rel="stylesheet";const style=styleFile&&(part.styleReady||=load(link,"href",hashed?styleFile:"canopyx-"+part.name+".css","stylesheet")
62
+ .catch(error=>{part.styleReady=null;throw error}));const code=PART_CODE.get(part.name)?.version===part.version||(part.scriptReady||=load(script,"src",hashed?scriptFile:"canopyx-"+part.name+".js","script")
63
+ .finally(()=>script.remove()).catch(error=>{part.scriptReady=null;throw error}));await Promise.all([style,code])}}
64
+ const code=PART_CODE.get(part.name);if(code?.version!==part.version)throw new Error("CanopyX: "+part.label+" and runtime must come from the same build.");for(const name of code.requires||[])await loadPart(part.parts[name]);code.install(part.context())}).catch(error=>{part.ready=null;throw error})}
65
+ const partQueue=global.CanopyXParts=global.CanopyXParts||[];partQueue.forEach(entry=>registerPart(...entry));partQueue.length=0;partQueue.push=(...entries)=>{entries.forEach(entry=>registerPart(...entry));return 0};class FlowHost{}
66
+ const FORMS_IMPL={},FLOW_IMPL={},activeFlows=new WeakMap();const FORMS_PART=definePart(FlowHost,"forms","forms",()=>({FORMS_IMPL,h,setText,fill,gridFreeze,gridMomentFromParts,boundedPattern,foldText}));const FLOW_PART=definePart(FlowHost,"flow","flow view",()=>({FLOW_IMPL,FORMS_IMPL,h,setText,fill,plural,foldText,boundedPattern,numberFormat,gridFreeze}));if(typeof DataGrid==="function")DataGrid.prototype.parts.forms=FORMS_PART;const FLOW_ASYNC_METHODS=["next","back","goTo","setState","validate","submit"];const FLOW_SYNC_METHODS=["getStep","getValues","setValues","getState","isDirty","reset","setFieldError","setProgress","complete"];function createFlow(host,options){if(!host||!options||!options.flow||typeof options.flow!=="object")throw new Error("CanopyX: createFlow needs a host element and a flow document.");const existing=activeFlows.get(host);if(existing)return existing;let impl=null,destroyed=false,mountError=null;const destroyedError=()=>new Error("CanopyX: this flow has been destroyed.");const requireImpl=name=>{if(destroyed)throw destroyedError();if(!impl)throw new Error("CanopyX: flow is not ready; await api.ready before calling "+name+"().");return impl};const editors=Object.assign({},...[...PLUGINS.values()].map(item=>item.editors||{}));const ready=loadPart(FLOW_PART).then(()=>{if(destroyed)throw destroyedError();impl=FLOW_IMPL.create(host,options,editors);return impl.ready});const api={element:host,flow:options.flow,view:options.flow};for(const name of FLOW_SYNC_METHODS)api[name]=(...args)=>requireImpl(name)[name](...args);for(const name of FLOW_ASYNC_METHODS)api[name]=(...args)=>{if(destroyed)return Promise.reject(destroyedError());return ready.then(()=>requireImpl(name)[name](...args))};api.ready=ready.then(()=>{if(destroyed)throw destroyedError();return api});api.ready.catch(()=>{});api.destroy=()=>{if(destroyed)return;destroyed=true;try{impl?.destroy()}
67
+ finally{impl=null;mountError?.remove();if(activeFlows.get(host)===api){activeFlows.delete(host);if(host.canopyx===api)delete host.canopyx;host.removeAttribute("data-canopyx-mounted");host.removeAttribute("data-cx-ready");host.removeAttribute("data-cx-error");host.classList.remove("cx-flow-host")}}};ready.catch(error=>{if(destroyed)return;const notice=mountError=h("div","cx-mount-error");notice.setAttribute("role","alert");setText(notice,error&&error.message?error.message:String(error));host.appendChild(notice);host.setAttribute("data-cx-error",notice.textContent);host.dispatchEvent(new CustomEvent("canopyx:error",{detail:{error},bubbles:true}))});Object.freeze(api);activeFlows.set(host,api);host.classList.add("cx-host","cx-flow-host");host.setAttribute("data-canopyx-mounted","true");host.canopyx=api;return api}
68
+ const FLOW_API={createFlow};const configured=new Map();const flowId=value=>typeof value==="string"&&/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(value);
69
+ function configure(id,options){if(!flowId(id)||!options||typeof options!=="object")throw new Error("CanopyX: configure needs a flow id and an options object.");if([...document.querySelectorAll("[data-canopyx-mounted]")].some(host=>host.canopyx?.view?.id===id))
70
+ throw new Error("CanopyX: flow '"+id+"' is already mounted; configure it before the page has loaded, or mark its fragment data-cx-mount=\"manual\" and call CanopyX.mount.");const registration=Object.assign({},configured.get(id),options,{flow:undefined});configured.set(id,registration);return()=>{if(configured.get(id)===registration)configured.delete(id)}}
71
+ function unconfigure(id){if(!flowId(id))throw new Error("CanopyX: unconfigure needs a flow id.");return configured.delete(id)}
72
+ function use(value){const item=Object.freeze({...value});if(!value||!partName(item.name)||Object.hasOwn(PARTS,item.name)||item.editors!==undefined&&Object(item.editors)!==item.editors||
73
+ Object.values(item.editors||{}).some(editor=>!editor||typeof editor.create!=="function"))
74
+ throw new Error("CanopyX: invalid plugin '"+item.name+"'.");if(PLUGINS.has(item.name))throw new Error("CanopyX: plugin '"+item.name+"' is already registered.");PLUGINS.set(item.name,item);return()=>{if(PLUGINS.get(item.name)===item)PLUGINS.delete(item.name)}}
75
+ let watcher=null;function mountWhenVisible(host){watcher||=new IntersectionObserver(entries=>entries.forEach(entry=>entry.isIntersecting&&mount(entry.target)),{rootMargin:Math.ceil(global.innerHeight / 2) + "px 0px" });
76
+ watcher.observe(host)}
77
+ function mount(root,automatic){const scope=root||document,created=[],selector="[data-canopyx-host]:not([data-canopyx-mounted])";(scope.matches?.(selector)?[scope]:scope.querySelectorAll(selector)).forEach(host=>{const config=host.querySelector("script.cx-config"),mode=host.getAttribute("data-cx-mount");if(!config||automatic===true&&mode==="manual")return;if(mode==="visible"&&host!==scope&&global.IntersectionObserver)return mountWhenVisible(host);watcher?.unobserve(host);host.setAttribute("data-canopyx-mounted","true");try{const flow=JSON.parse(config.textContent);if(!flow||flow.kind!=="flow")throw new Error("CanopyX: the flow entry point mounts flow documents only.");created.push(createFlow(host,Object.assign({},configured.get(flow.id),{flow})))}catch(error){const notice=h("div","cx-mount-error");notice.setAttribute("role","alert");setText(notice,error.message||String(error));host.classList.add("cx-host");host.setAttribute("data-cx-error",notice.textContent);host.appendChild(notice);if(global.console)global.console.error(error)}});return created}
78
+ global.CanopyX=Object.freeze({createFlow,configure,unconfigure,mount:root=>mount(root,false),use,registerPart,setNonce});const navigation=global.performance&&performance.getEntriesByType?performance.getEntriesByType("navigation")[0]:null;if(document.readyState==="loading"||(document.readyState==="interactive"&&navigation&&navigation.domContentLoadedEventStart===0)){document.addEventListener("DOMContentLoaded",()=>mount(document,true))}else{mount(document,true)}})(typeof window!=="undefined"?window:this);
@@ -0,0 +1,80 @@
1
+ const runtimeHost=typeof window==="undefined"?{}:window;if(runtimeHost.CanopyX&&!runtimeHost.CanopyX.createFlow){throw new Error("CanopyX: load the flow or grid runtime, or this module, before the core script.")}
2
+ if(typeof document!=="undefined")runtimeHost.CanopyXFilterEditorBase=new URL("./",import.meta.url).href;
3
+ /* CanopyX flow entry: forms and guided flows. */
4
+ (function(global){"use strict";if(global.CanopyX){return}
5
+ function h(tag,className){const element=document.createElement(tag);if(className)element.className=className;return element}
6
+ function setText(element,value){element.textContent=value==null?"":String(value)}
7
+ const PLUGINS=new Map();function fill(template,values){return template.replace(/\{(\w+)\}/g, (match, name) => (values && Object.hasOwn(values, name) ? String(values[name]) : match));
8
+ }
9
+ const pluralRules=new Map();function plural(template,count,lang){if(!template.startsWith("["))return template;const forms={};for(const match of template.matchAll(/\[(zero|one|two|few|many|other)\]([^[]*)/g)) forms[match[1]] = match[2];
10
+ let rules=pluralRules.get(lang);if(!rules){try{rules=new Intl.PluralRules(lang||"en")}catch(_){rules=new Intl.PluralRules("en")}pluralRules.set(lang,rules)}
11
+ return forms[rules.select(Number(count))]??forms.other??template}
12
+ const numberFormats=new Map();function numberFormat(lang,minimum=0,maximum=3){const key=(lang||"")+ "|"+minimum+"|"+maximum;let format=numberFormats.get(key);if(!format){const digits={minimumFractionDigits:minimum,maximumFractionDigits:maximum};try{format=new Intl.NumberFormat(lang||undefined,digits)}catch(e){format=new Intl.NumberFormat(undefined,digits)}
13
+ if(numberFormats.size>=16)numberFormats.delete(numberFormats.keys().next().value);numberFormats.set(key,format)}
14
+ return format}
15
+ function numberText(value,lang,format){if(!format)return numberFormat(lang).format(value);const minimum=format.minimumFractionDigits??0,maximum=format.maximumFractionDigits??3;const magnitude=Math.abs(value),fixed=magnitude>=1e21?BigInt(magnitude).toString():magnitude.toFixed(maximum);const signed=value<0&&Number(fixed)!==0?"-"+fixed:fixed;const parts=numberFormat(lang,minimum,maximum).formatToParts(signed);const at=parts.findIndex(part=>!["minusSign","literal"].includes(part.type));return parts.map((part,index)=>(index===at&&format.prefix||"")+ part.value).join("")+(format.suffix||"")}
16
+ let pageNonce=typeof document!=="undefined"&&document.currentScript?.nonce||"",trustPolicy;function setNonce(nonce){if(typeof nonce!=="string"||!nonce)throw new Error("CanopyX: invalid nonce.");pageNonce=nonce}
17
+ function withNonce(element){const nonce=pageNonce||document.querySelector("script[nonce]")?.nonce;if(nonce)element.nonce=nonce;return element}
18
+ function trusted(kind,value){if(trustPolicy===undefined){try{trustPolicy=global.trustedTypes?.createPolicy("canopyx",{createHTML:text=>text,createScript:text=>text,createScriptURL:text=>text})||null}
19
+ catch(_){trustPolicy=null}}
20
+ return trustPolicy?trustPolicy["create"+kind](value):value}
21
+ function hostHtml(element,html,sanitize,kind){const text=String(html??""),value=sanitize?sanitize(text,{kind}):text;if(value?.nodeType)element.replaceChildren(value);else if(typeof value==="string")element.innerHTML=trusted("HTML",value);else if(global.trustedTypes?.isHTML?.(value))element.innerHTML=value;else throw new Error("CanopyX: invalid sanitizeHtml result.")}
22
+ function boundedPattern(source,exact=true,whole=false){const bad=()=>{throw new Error("CanopyX: pattern requires literals, character classes and at most one variable repetition anchored at the start; groups, alternatives and backreferences are unsupported.")};if(typeof source!=="string"||source.length>256)bad();let width=0,variable=0;for(let i=0;i<source.length;){const unit=source[i++];if(unit==="^"&&i===1||unit==="$"&&i===source.length)continue;if(unit==="\\"){const escape=source[i++];if(!escape||!/^[dDsSwWbBnrtfv\\.^$*+?{}\[\]()|/]$/.test(escape)) bad();
23
+ }else if(unit==="["){let closed=false;while(i<source.length){const c=source[i++];if(c==="\\"){const escape=source[i++];if(!escape||!/^[dDsSwWbnrtfv\\.^$*+?{}\[\]()|/-]$/.test(escape)) bad(); }
24
+ else if(c==="]"){closed=true;break}}
25
+ if(!closed)bad()}else if("()|^$*+?{}]".includes(unit))bad();let count=1;if(i<source.length&&"*+?".includes(source[i])){variable++;i++}
26
+ else if(source[i]==="{"){const match=/^\{(\d+)(?:,(\d*))?\}/.exec(source.slice(i));
27
+ if(!match)bad();const low=Number(match[1]),high=match[2]===undefined?low:match[2]===""?Infinity:Number(match[2]);if(low>256||high<low||high!==Infinity&&high>256)bad();if(low!==high)variable++;count=low===high?low:1;i +=match[0].length}
28
+ width +=count;if(width>256||variable>1)bad()}
29
+ if(variable&&!whole&&source[0]!=="^")bad();return new RegExp(whole?"^(?:"+source+")$":source,exact?"u":"iu")}
30
+ function wildcardMatch(pattern,value,exact){if(!exact){pattern=pattern.toLowerCase();value=value.toLowerCase()}
31
+ let p=0,v=0,star=-1,retry=0;while(v<value.length){if(p<pattern.length&&(pattern[p]==="?"||pattern[p]===value[v])){p++;v++}
32
+ else if(pattern[p]==="*"){star=p++;retry=v}
33
+ else if(star>=0){p=star+1;v=++retry}
34
+ else return false}
35
+ while(pattern[p]==="*")p++;return p===pattern.length}
36
+ const EXTRA_FOLD_PAIRS="1t.w.q.1,2u.w.n.1,2.w.7.1,y.1.o.2,4.1.3.2,3.1.8.2,3.1.n.2,2.-3d,1.1.3.2,4.5u,1.1,2.1,2.5q,1.1,2.5p,1.5p,1.1,3.27,1.5m,1.5n,1.1,2.5p,1.5r,2.5v,1.5t,1.1,4.5v,1.5x,2.5y,1.1.3.2,2.62,1.1,2.62,3.1,2.62,1.1,2.61,1.61,1.1,2.1,2.63,1.1,4.1,8.2,1.1,2.2,1.1,2.2,1.1.9.2,3.1.9.2,3.2,1.1,2.1,2.-2p,1.-1k,1.1.k.2,2.-3m,2.1.9.2,8.8bv,1.1,2.-4j,1.8bs,3.1,2.-5f,1.1x,1.1z,1.1.5.2,82.1,2.1,4.1,9.38,7.12,2.11.3.1,2.1s,2.1r,1.1r,2.w.h.1,2.w.9.1,10.8,9.1.c.2,6.-1o,3.1,2.-7,1.1,3.-3m.3.1,1.28.g.1,1.w.w.1,1d.1.h.2,a.1.r.2,2.f,1.1.7.2,3.1.1c.2,3.1c.12.1,28a.5ls.12.1,2.5ls,6.5ls,k3.tzk.28.1,1.8.6.1,1p0.1,7.-2bk.17.1,3.-2bk.3.1,8x.1.23.2,a.-5vj,2.1.1c.2,a.-8.8.1,9.-8.6.1,b.-8.8.1,9.-8.8.1,9.-8.6.1,c.-8.4.2,9.-8.8.1,p.-8.8.1,9.-8.8.1,9.-8.8.1,9.-8,1.-8,1.-22,1.-22,1.-9,c.-2e.4.1,1.-9,c.-8,1.-8,1.-2s,1.-2s,d.-8,1.-8,1.-34,1.-34,1.-7,c.-3k,1.-3k,1.-3i,1.-3i,1.-9,8a.-5st,4.-6gv,1.-6di,7.s,1a.g.g.1,k.1,mr.q.q.1,1f5.1c.1c.1,1d.1,2.-8af,1.-2xy,1.-89z,3.1.3.2,2.-8bg,1.-8al,1.-8bj,1.-8bi,2.1,3.1,9.-8cf,1.-8cf,1.1.1e.2,9.1,2.1,5.1,nym.1.n.2,k.1.e.2,3s.1.7.2,4.1.v.2,b.1,2.1,2.-r9g,1.1.5.2,5.1,2.-wmg,3.1,2.1,4.1.a.2,2.-wn8,1.-wnj,1.-wnf,1.-wn5,1.-wn8,2.-wlu,1.-wmi,1.-wlx,1.ps,1.1.8.2,2.-1c,1.-wn7,1.-raw,1.1,2.1,2.-wo7,1.1,4.1,6.1.3.2,2.-wu9,p.1,h7w.w.q.1";const EXTRA_LOWERCASE=new Map();let extraUnit=0;for(const record of EXTRA_FOLD_PAIRS.split(",")){const[gap,delta,count=1,step=0]=record.split(".").map(part=>parseInt(part,36));extraUnit +=gap;for(let i=0;i<count;i++)EXTRA_LOWERCASE.set(extraUnit+i*step,extraUnit+i*step+delta);extraUnit +=(count-1)*step}
37
+ function foldCode(c){if(c<0x80){return c>=65&&c<=90?c+32:c}
38
+ return EXTRA_LOWERCASE.get(c)||c}
39
+ function foldUnit(unit){return String.fromCharCode(foldCode(unit.charCodeAt(0)))}
40
+ function isSeparator(code){return code<128&&!((code>=48&&code<=57)||(code>=65&&code<=90)||(code>=97&&code<=122))}
41
+ function foldText(text){const source=String(text==null?"":text);if(!/[^\0-\x7f]/.test(source)) return source.toLowerCase();
42
+ let out="";for(let i=0;i<source.length;i++){out +=foldUnit(source.charAt(i))}
43
+ return out}
44
+ function words(text){const folded=foldText(text);const out=[];let current="";for(let i=0;i<folded.length;i++){if(isSeparator(folded.charCodeAt(i))){if(current){out.push(current);current=""}}else{current +=folded.charAt(i)}}
45
+ if(current){out.push(current)}
46
+ return out}
47
+ function matchesWords(labelWords,queryWords){return queryWords.every((q)=>labelWords.some((w)=>w.startsWith(q)))}
48
+ function trimSeparators(text){let start=0,end=text.length;while(start<end&&isSeparator(text.charCodeAt(start)))start++;while(end>start&&isSeparator(text.charCodeAt(end-1)))end--;return text.slice(start,end)}
49
+ function rankLabel(label,foldedQuery){const l=trimSeparators(foldText(label));return l===foldedQuery?0:l.startsWith(foldedQuery)?1:2}
50
+ function shardKey(prefix){let out="";for(let i=0;i<prefix.length;i++){const code=prefix.charCodeAt(i);out +=(code>=97&&code<=122)||(code>=48&&code<=57)?prefix.charAt(i):"_" +("000"+code.toString(16)).slice(-4)}
51
+ return out}
52
+ function gridMomentFromParts(year,month,day,hours,minutes,seconds,local){const parts=[year,month,day,hours,minutes,seconds];if(!parts.every(Number.isInteger))return null;const moment=new Date(0),zone=local?"":"UTC";moment["set"+zone+"FullYear"](year,month-1,day);moment["set"+zone+"Hours"](hours,minutes,seconds,0);const actual=["FullYear","Month","Date","Hours","Minutes","Seconds"].map(name=>moment["get"+zone+name]());actual[1]++;return parts.every((value,index)=>value===actual[index])?moment:null}
53
+ function gridDateInputMoment(text,local){const match=/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?$/.exec(text);
54
+ if(!match)return null;const moment=gridMomentFromParts(Number(match[1]),Number(match[2]),Number(match[3]),Number(match[4]),Number(match[5]),Number(match[6]||0),local);if(moment)moment.setTime(moment.getTime()+ Number((match[7]||"").padEnd(3,"0")));return moment}
55
+ function gridFreeze(value){if(value&&typeof value==="object"&&!Object.isFrozen(value)){Object.values(value).forEach(gridFreeze);Object.freeze(value)}
56
+ return value}
57
+ const PART_URL=typeof document!=="undefined"&&document.currentScript?.src;const PARTS={"forms":["a8c60f205d9babca","canopyx-forms.16987de1cdf9bda9.js","canopyx-forms.29058ce3c88af96e.css"],"flow":["75b9643b282dddb7","canopyx-flow.52d8d6168378df0c.js","canopyx-flow.3152e616c5557452.css"]},PART_CODE=new Map(),partName=name=>typeof name==="string"&&/^[a-z][a-z\d-]{0,31}$/.test(name);
58
+ function registerPart(name,part){if(!partName(name)||!part||typeof part.version!=="string"||typeof part.install!=="function"||
59
+ part.requires!==undefined&&!(Array.isArray(part.requires)&&part.requires.every(partName)))throw new Error("CanopyX: invalid part.");PART_CODE.set(name,part)}
60
+ function definePart(View,name,label,context){const parts=View.prototype.parts||={};return parts[name]={name,label,context,parts}}
61
+ function loadPart(part){return part.ready||=Promise.resolve().then(async()=>{const[version,scriptFile,styleFile]=Object.hasOwn(PARTS,part.name)?PARTS[part.name]:[];part.version=version;if(!version)throw new Error("CanopyX: this runtime has no "+part.label+".");if(PART_CODE.get(part.name)?.version!==part.version||part.styleReady===null){const find=type=>[...document.querySelectorAll('script[type="application/x-canopyx-'+type+'"][data-cx-version="'+part.version+'"]')].find(item=>!pageNonce||item.nonce===pageNonce);const payload=find(part.name),script=h("script"),embeddedStyle=styleFile&&find(part.name+"-style");if(payload){if(embeddedStyle){const style=h("style");style.nonce=payload.nonce;setText(style,embeddedStyle.textContent);document.head.appendChild(style)}
62
+ script.textContent=trusted("Script",payload.textContent);script.nonce=payload.nonce;document.head.appendChild(script);script.remove()}else{const base=global.CanopyXFilterEditorBase,runtime=PART_URL||typeof base==="string"&&base||document.baseURI,hashed=PART_URL&&/\.[a-f\d]{16}\.js(?:[?#]|$)/.test(PART_URL);
63
+ const load=(element,attribute,file,kind)=>new Promise((resolve,reject)=>{const url=new URL(file,runtime).href;element[attribute]=attribute==="src"?trusted("ScriptURL",url):url;element.onload=resolve;element.onerror=()=>{element.remove();reject(new Error("CanopyX: "+part.label+" "+kind+" unavailable."))};document.head.appendChild(withNonce(element))});const link=h("link");link.rel="stylesheet";const style=styleFile&&(part.styleReady||=load(link,"href",hashed?styleFile:"canopyx-"+part.name+".css","stylesheet")
64
+ .catch(error=>{part.styleReady=null;throw error}));const code=PART_CODE.get(part.name)?.version===part.version||(part.scriptReady||=load(script,"src",hashed?scriptFile:"canopyx-"+part.name+".js","script")
65
+ .finally(()=>script.remove()).catch(error=>{part.scriptReady=null;throw error}));await Promise.all([style,code])}}
66
+ const code=PART_CODE.get(part.name);if(code?.version!==part.version)throw new Error("CanopyX: "+part.label+" and runtime must come from the same build.");for(const name of code.requires||[])await loadPart(part.parts[name]);code.install(part.context())}).catch(error=>{part.ready=null;throw error})}
67
+ const partQueue=global.CanopyXParts=global.CanopyXParts||[];partQueue.forEach(entry=>registerPart(...entry));partQueue.length=0;partQueue.push=(...entries)=>{entries.forEach(entry=>registerPart(...entry));return 0};class FlowHost{}
68
+ const FORMS_IMPL={},FLOW_IMPL={},activeFlows=new WeakMap();const FORMS_PART=definePart(FlowHost,"forms","forms",()=>({FORMS_IMPL,h,setText,fill,gridFreeze,gridMomentFromParts,boundedPattern,foldText}));const FLOW_PART=definePart(FlowHost,"flow","flow view",()=>({FLOW_IMPL,FORMS_IMPL,h,setText,fill,plural,foldText,boundedPattern,numberFormat,gridFreeze}));if(typeof DataGrid==="function")DataGrid.prototype.parts.forms=FORMS_PART;const FLOW_ASYNC_METHODS=["next","back","goTo","setState","validate","submit"];const FLOW_SYNC_METHODS=["getStep","getValues","setValues","getState","isDirty","reset","setFieldError","setProgress","complete"];function createFlow(host,options){if(!host||!options||!options.flow||typeof options.flow!=="object")throw new Error("CanopyX: createFlow needs a host element and a flow document.");const existing=activeFlows.get(host);if(existing)return existing;let impl=null,destroyed=false,mountError=null;const destroyedError=()=>new Error("CanopyX: this flow has been destroyed.");const requireImpl=name=>{if(destroyed)throw destroyedError();if(!impl)throw new Error("CanopyX: flow is not ready; await api.ready before calling "+name+"().");return impl};const editors=Object.assign({},...[...PLUGINS.values()].map(item=>item.editors||{}));const ready=loadPart(FLOW_PART).then(()=>{if(destroyed)throw destroyedError();impl=FLOW_IMPL.create(host,options,editors);return impl.ready});const api={element:host,flow:options.flow,view:options.flow};for(const name of FLOW_SYNC_METHODS)api[name]=(...args)=>requireImpl(name)[name](...args);for(const name of FLOW_ASYNC_METHODS)api[name]=(...args)=>{if(destroyed)return Promise.reject(destroyedError());return ready.then(()=>requireImpl(name)[name](...args))};api.ready=ready.then(()=>{if(destroyed)throw destroyedError();return api});api.ready.catch(()=>{});api.destroy=()=>{if(destroyed)return;destroyed=true;try{impl?.destroy()}
69
+ finally{impl=null;mountError?.remove();if(activeFlows.get(host)===api){activeFlows.delete(host);if(host.canopyx===api)delete host.canopyx;host.removeAttribute("data-canopyx-mounted");host.removeAttribute("data-cx-ready");host.removeAttribute("data-cx-error");host.classList.remove("cx-flow-host")}}};ready.catch(error=>{if(destroyed)return;const notice=mountError=h("div","cx-mount-error");notice.setAttribute("role","alert");setText(notice,error&&error.message?error.message:String(error));host.appendChild(notice);host.setAttribute("data-cx-error",notice.textContent);host.dispatchEvent(new CustomEvent("canopyx:error",{detail:{error},bubbles:true}))});Object.freeze(api);activeFlows.set(host,api);host.classList.add("cx-host","cx-flow-host");host.setAttribute("data-canopyx-mounted","true");host.canopyx=api;return api}
70
+ const FLOW_API={createFlow};const configured=new Map();const flowId=value=>typeof value==="string"&&/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(value);
71
+ function configure(id,options){if(!flowId(id)||!options||typeof options!=="object")throw new Error("CanopyX: configure needs a flow id and an options object.");if([...document.querySelectorAll("[data-canopyx-mounted]")].some(host=>host.canopyx?.view?.id===id))
72
+ throw new Error("CanopyX: flow '"+id+"' is already mounted; configure it before the page has loaded, or mark its fragment data-cx-mount=\"manual\" and call CanopyX.mount.");const registration=Object.assign({},configured.get(id),options,{flow:undefined});configured.set(id,registration);return()=>{if(configured.get(id)===registration)configured.delete(id)}}
73
+ function unconfigure(id){if(!flowId(id))throw new Error("CanopyX: unconfigure needs a flow id.");return configured.delete(id)}
74
+ function use(value){const item=Object.freeze({...value});if(!value||!partName(item.name)||Object.hasOwn(PARTS,item.name)||item.editors!==undefined&&Object(item.editors)!==item.editors||
75
+ Object.values(item.editors||{}).some(editor=>!editor||typeof editor.create!=="function"))
76
+ throw new Error("CanopyX: invalid plugin '"+item.name+"'.");if(PLUGINS.has(item.name))throw new Error("CanopyX: plugin '"+item.name+"' is already registered.");PLUGINS.set(item.name,item);return()=>{if(PLUGINS.get(item.name)===item)PLUGINS.delete(item.name)}}
77
+ let watcher=null;function mountWhenVisible(host){watcher||=new IntersectionObserver(entries=>entries.forEach(entry=>entry.isIntersecting&&mount(entry.target)),{rootMargin:Math.ceil(global.innerHeight / 2) + "px 0px" });
78
+ watcher.observe(host)}
79
+ function mount(root,automatic){const scope=root||document,created=[],selector="[data-canopyx-host]:not([data-canopyx-mounted])";(scope.matches?.(selector)?[scope]:scope.querySelectorAll(selector)).forEach(host=>{const config=host.querySelector("script.cx-config"),mode=host.getAttribute("data-cx-mount");if(!config||automatic===true&&mode==="manual")return;if(mode==="visible"&&host!==scope&&global.IntersectionObserver)return mountWhenVisible(host);watcher?.unobserve(host);host.setAttribute("data-canopyx-mounted","true");try{const flow=JSON.parse(config.textContent);if(!flow||flow.kind!=="flow")throw new Error("CanopyX: the flow entry point mounts flow documents only.");created.push(createFlow(host,Object.assign({},configured.get(flow.id),{flow})))}catch(error){const notice=h("div","cx-mount-error");notice.setAttribute("role","alert");setText(notice,error.message||String(error));host.classList.add("cx-host");host.setAttribute("data-cx-error",notice.textContent);host.appendChild(notice);if(global.console)global.console.error(error)}});return created}
80
+ global.CanopyX=Object.freeze({createFlow,configure,unconfigure,mount:root=>mount(root,false),use,registerPart,setNonce});if(typeof document!=="undefined"){const navigation=global.performance&&performance.getEntriesByType?performance.getEntriesByType("navigation")[0]:null;if(document.readyState==="loading"||(document.readyState==="interactive"&&navigation&&navigation.domContentLoadedEventStart===0)){document.addEventListener("DOMContentLoaded",()=>mount(document,true))}else{mount(document,true)}}})(runtimeHost);const CanopyX=runtimeHost.CanopyX;const{createFlow,configure,unconfigure,mount,use,registerPart,setNonce}=CanopyX;export{createFlow,configure,unconfigure,mount,use,registerPart,setNonce};export default CanopyX;