@evotecit/canopyx 0.3.5 → 0.3.7

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 (58) hide show
  1. package/CanopyX.Html/Assets/canopyx-calendar.js +4 -3
  2. package/CanopyX.Html/Assets/canopyx-detail-rows.js +2 -2
  3. package/CanopyX.Html/Assets/canopyx-filter-editor.js +2 -1
  4. package/CanopyX.Html/Assets/canopyx-flow-checks.css +24 -0
  5. package/CanopyX.Html/Assets/canopyx-flow-checks.js +13 -0
  6. package/CanopyX.Html/Assets/canopyx-flow-command.css +14 -0
  7. package/CanopyX.Html/Assets/canopyx-flow-command.js +19 -0
  8. package/CanopyX.Html/Assets/canopyx-flow-consent.css +6 -0
  9. package/CanopyX.Html/Assets/canopyx-flow-consent.js +8 -0
  10. package/CanopyX.Html/Assets/canopyx-flow-dialog.css +14 -0
  11. package/CanopyX.Html/Assets/canopyx-flow-dialog.js +3 -0
  12. package/CanopyX.Html/Assets/canopyx-flow-drafts.css +17 -0
  13. package/CanopyX.Html/Assets/canopyx-flow-drafts.js +21 -0
  14. package/CanopyX.Html/Assets/canopyx-flow-endpoint.js +6 -0
  15. package/CanopyX.Html/Assets/canopyx-flow-entry.css +1 -0
  16. package/CanopyX.Html/Assets/canopyx-flow-entry.js +6 -2
  17. package/CanopyX.Html/Assets/canopyx-flow-host.css +5 -0
  18. package/CanopyX.Html/Assets/canopyx-flow-host.js +4 -0
  19. package/CanopyX.Html/Assets/canopyx-flow-links.js +6 -0
  20. package/CanopyX.Html/Assets/canopyx-flow-module.mjs +6 -2
  21. package/CanopyX.Html/Assets/canopyx-flow-options.js +25 -0
  22. package/CanopyX.Html/Assets/canopyx-flow-pick.css +19 -0
  23. package/CanopyX.Html/Assets/canopyx-flow-pick.js +9 -0
  24. package/CanopyX.Html/Assets/canopyx-flow-preview.css +11 -0
  25. package/CanopyX.Html/Assets/canopyx-flow-preview.js +9 -0
  26. package/CanopyX.Html/Assets/canopyx-flow-records.css +9 -0
  27. package/CanopyX.Html/Assets/canopyx-flow-records.js +12 -0
  28. package/CanopyX.Html/Assets/canopyx-flow-tasks.css +29 -0
  29. package/CanopyX.Html/Assets/canopyx-flow-tasks.js +20 -0
  30. package/CanopyX.Html/Assets/canopyx-flow.js +48 -45
  31. package/CanopyX.Html/Assets/canopyx-formatting.js +7 -6
  32. package/CanopyX.Html/Assets/canopyx-grid-module.mjs +69 -42
  33. package/CanopyX.Html/Assets/canopyx-grid.css +23 -0
  34. package/CanopyX.Html/Assets/canopyx-grid.js +69 -42
  35. package/CanopyX.Html/Assets/canopyx-interactive.js +4 -4
  36. package/CanopyX.Html/Assets/canopyx-labels-de.js +77 -1
  37. package/CanopyX.Html/Assets/canopyx-labels-es.js +77 -1
  38. package/CanopyX.Html/Assets/canopyx-labels-fr.js +77 -1
  39. package/CanopyX.Html/Assets/canopyx-labels-it.js +77 -1
  40. package/CanopyX.Html/Assets/canopyx-labels-pl.js +77 -1
  41. package/CanopyX.Html/Assets/canopyx-live.js +4 -3
  42. package/CanopyX.Html/Assets/canopyx-matrix.js +3 -3
  43. package/CanopyX.Html/Assets/canopyx-module.mjs +4 -4
  44. package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +71 -44
  45. package/CanopyX.Html/Assets/canopyx-reporting.css +23 -0
  46. package/CanopyX.Html/Assets/canopyx-reporting.js +71 -44
  47. package/CanopyX.Html/Assets/canopyx-tree-tools.js +10 -8
  48. package/CanopyX.Html/Assets/canopyx.js +4 -4
  49. package/JavaScript/flow.d.ts +180 -9
  50. package/JavaScript/grid.d.ts +8 -0
  51. package/JavaScript/locales/de.mjs +77 -1
  52. package/JavaScript/locales/es.mjs +77 -1
  53. package/JavaScript/locales/fr.mjs +77 -1
  54. package/JavaScript/locales/it.mjs +77 -1
  55. package/JavaScript/locales/pl.mjs +77 -1
  56. package/JavaScript/protocol.d.ts +1 -1
  57. package/README.md +6 -2
  58. package/package.json +3 -1
@@ -1,4 +1,4 @@
1
- (function(global){"use strict";const installed=new WeakSet();const part={version:"dbc7cd1bfd91ce63",install(context){if(installed.has(context.TreeGrid.prototype))return;const{TreeGrid,h,setText,fill,numberFormat,watchFlyout,columnWidth,prepareWidth,cellText,cellValue,isCellObject,foldText,formatting,sortKey,compareKeys,EXPORT_FORMATS,exportFormat,exportTimeout,exportFormatsOf,INDENT,MAX_PROTOCOL_INTEGER,hostControl,detailCell,HostCellContext,disposeHostSlot,loadPart,definePart,pluginItems,applyTone,toneOf,badgeElement,RECORD_IMPL,svgIcon,gridExportField}=context;const EXPORT_IMPL={formatsOf:exportFormatsOf};const GRID_ICONS={search:'<circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>',filter:'<path d="M4 5h16l-6 7.5V19l-4-2v-4.5z"/>',columns:'<rect x="3.5" y="4.5" width="17" height="15" rx="2"/><path d="M9.5 4.5v15M14.5 4.5v15"/>',views:'<path d="M6 4h12v16l-6-4-6 4z"/>',export:'<path d="M12 4v11M7.5 10.5 12 15l4.5-4.5M5 19h14"/>',more:'<circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/>',close:'<path d="M6 6l12 12M18 6 6 18"/>',check:'<path d="m5 12.5 4.5 4.5L19 7.5"/>',chart:'<path d="M4 19h16M7 16V10M12 16V6M17 16v-4"/>',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"/>',up:'<path d="M12 19V5M6 11l6-6 6 6"/>',down:'<path d="M12 5v14M6 13l6 6 6-6"/>',retry:'<path d="M4 12a8 8 0 1 0 2.4-5.7M4 4v4.5h4.5"/>',previous:'<path d="m14 7-5 5 5 5"/>',next:'<path d="m10 7 5 5-5 5"/>',first:'<path d="m13 7-5 5 5 5M17 7l-5 5 5 5"/>',last:'<path d="m11 7 5 5-5 5M7 7l5 5-5 5"/>'};function gridIcon(name,shapes){return svgIcon(shapes||GRID_ICONS[name]||GRID_ICONS.more)}
1
+ (function(global){"use strict";const installed=new WeakSet();const part={version:"4c6c4101089c4444",install(context){if(installed.has(context.TreeGrid.prototype))return;const{TreeGrid,h,setText,fill,numberFormat,watchFlyout,columnWidth,prepareWidth,cellText,cellValue,isCellObject,foldText,formatting,sortKey,compareKeys,EXPORT_FORMATS,exportFormat,exportTimeout,exportFormatsOf,INDENT,MAX_PROTOCOL_INTEGER,hostControl,detailCell,HostCellContext,disposeHostSlot,loadPart,definePart,pluginItems,applyTone,toneOf,badgeElement,RECORD_IMPL,svgIcon,gridExportField}=context;const EXPORT_IMPL={formatsOf:exportFormatsOf};const GRID_ICONS={search:'<circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>',filter:'<path d="M4 5h16l-6 7.5V19l-4-2v-4.5z"/>',columns:'<rect x="3.5" y="4.5" width="17" height="15" rx="2"/><path d="M9.5 4.5v15M14.5 4.5v15"/>',views:'<path d="M6 4h12v16l-6-4-6 4z"/>',export:'<path d="M12 4v11M7.5 10.5 12 15l4.5-4.5M5 19h14"/>',more:'<circle cx="5" cy="12" r="1.2"/><circle cx="12" cy="12" r="1.2"/><circle cx="19" cy="12" r="1.2"/>',close:'<path d="M6 6l12 12M18 6 6 18"/>',check:'<path d="m5 12.5 4.5 4.5L19 7.5"/>',chart:'<path d="M4 19h16M7 16V10M12 16V6M17 16v-4"/>',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"/>',up:'<path d="M12 19V5M6 11l6-6 6 6"/>',down:'<path d="M12 5v14M6 13l6 6 6-6"/>',retry:'<path d="M4 12a8 8 0 1 0 2.4-5.7M4 4v4.5h4.5"/>',previous:'<path d="m14 7-5 5 5 5"/>',next:'<path d="m10 7 5 5-5 5"/>',first:'<path d="m13 7-5 5 5 5M17 7l-5 5 5 5"/>',last:'<path d="m11 7 5 5-5 5M7 7l5 5-5 5"/>'};function gridIcon(name,shapes){return svgIcon(shapes||GRID_ICONS[name]||GRID_ICONS.more)}
2
2
  function gridDecorateButton(button,icon,ghost){button.classList.add("cx-btn");if(ghost)button.classList.add("ghost");if(icon)button.prepend(gridIcon(icon));return button}
3
3
  function gridFilterCount(query){const count=group=>group?group.filters.length+group.groups.reduce((sum,child)=>sum+count(child),0):0;return(query?.filters?.length||0)+ count(query?.filterGroup)}
4
4
  function gridPriority(value){return["essential","normal","optional"].includes(value)||Number.isInteger(value)&&value>=0&&value<=MAX_PROTOCOL_INTEGER}
@@ -8,7 +8,8 @@ function gridOptions(column){return column.presenter||{}}
8
8
  const COLUMN_DIGITS=new Map(),COLUMN_NUMERIC_CHARS="0123456789 .,:;+-−/()[]\u00a0\u202f",COLUMN_HAS_NUMERIC=/[0-9\s.,:;+\-−/()\[\]]/;
9
9
  function gridNumericVariant(grid){let sample=grid.querySelector(".cx-cell"),owned=!sample;if(owned){sample=document.createElement("span");sample.className="cx-cell";sample.style.display="none";grid.appendChild(sample)}
10
10
  const variant=getComputedStyle(sample).fontVariantNumeric;if(owned)sample.remove();return variant}
11
- function gridClassFont(grid,className){const probe=document.createElement("span");probe.className=className;probe.style.display="none";grid.appendChild(probe);const style=getComputedStyle(probe),font=style.fontWeight+" "+style.fontSize+" "+style.fontFamily;probe.remove();return font}
11
+ function gridClassFont(grid,className,chrome){const probe=h("span",className);probe.style.cssText="position:absolute;visibility:hidden";grid.appendChild(probe);const style=getComputedStyle(probe),font=[style.fontWeight,style.fontSize,style.fontFamily].join(" ");if(chrome){chrome.extra=probe.getBoundingClientRect().width;chrome.spacing=parseFloat(style.letterSpacing)||0}
12
+ probe.remove();return font}
12
13
  function gridMeasureText(context,value,variant="normal"){const text=String(value).slice(0,512),measured=context.measureText(text).width;if(!variant.includes("tabular-nums")||!COLUMN_HAS_NUMERIC.test(text))return measured;const font=context.font;let variants=COLUMN_DIGITS.get(font);if(!variants){if(COLUMN_DIGITS.size===32)COLUMN_DIGITS.delete(COLUMN_DIGITS.keys().next().value);COLUMN_DIGITS.set(font,variants=new Map())}
13
14
  if(!variants.has(variant)){const probe=document.createElement("span"),glyphs=[];probe.style.cssText="all:initial;position:absolute;visibility:hidden;display:flex;white-space:pre;padding:0;border:0;max-width:none;";probe.style.font=font;probe.style.fontVariantNumeric=variant;for(const character of COLUMN_NUMERIC_CHARS)for(const numeric of[true,false]){const glyph=document.createElement("span");glyph.textContent=(numeric?"00"+character+"00":character).repeat(10);glyph.style.cssText="flex:none;white-space:pre;";probe.appendChild(glyph);glyphs.push(glyph)}
14
15
  document.body.appendChild(probe);const zero=glyphs[0].getBoundingClientRect().width / 50, numeric = new Map(), plain = new Map();
@@ -30,10 +31,10 @@ return excess},fitSpare(columns,spare){let target,flex=columns.filter(column=>co
30
31
  flex=flex.filter(column=>column.maxW-column.renderWidth>0.5)}
31
32
  const identity=columns.find(column=>column.identity),widest=columns.filter(column=>!column.identity&&
32
33
  gridTextual(column)&&gridLevel(column)===1).sort((a,b)=>b.need-a.need)[0];grow(identity,420);if(identity&&widest)grow(widest,widest.renderWidth+spare*widest.need / (widest.need + identity.need));
33
- grow(identity,Infinity);grow(widest,Infinity);return target},fitHide(shown,width,phone){let total=0,remaining=shown.length;const order=(a,b)=>gridLevel(b)- gridLevel(a)||gridRank(b)- gridRank(a)||shown.indexOf(b)- shown.indexOf(a),identity=phone&&shown.find(column=>column.identity);const hide=(column,pinned)=>!column.sized&&!column.alwaysVisible&&(column.pin==="none")!==pinned&&!(phone&&column.identity)&&gridLevel(column)>!phone;for(const column of this.columns)column.fitHidden=false;if(this.layoutMode&&this.layoutMode!=="fit")return;for(const column of shown)total +=this.fitSize(column,1);for(const column of shown.filter(column=>hide(column,false)).sort(order))
34
- if(total>width&&remaining>1){column.fitHidden=true;total -=this.fitSize(column,1);remaining--}
35
- if(identity&&!identity.fixed&&!identity.sized)total +=Math.min(identity.need??identity.width,(identity.indentNeed||0)+ 2*(this.fitCh||88))+(identity.reserve||0)- this.fitSize(identity,1);if(identity)for(const column of shown.filter(column=>hide(column,true)).sort(order))
36
- if(total>width){column.fitHidden=true;total -=this.fitSize(column,1)}}};function gridButton(text,run){const button=h("button","cx-button");button.type="button";setText(button,text);button.onclick=event=>{button.focus({preventScroll:true});run(event)};return button}
34
+ grow(identity,Infinity);grow(widest,Infinity);return target},fitHide(shown,width,phone,size=column=>this.fitSize(column,1)){let total=0,remaining=shown.length;const order=(a,b)=>gridLevel(b)- gridLevel(a)||gridRank(b)- gridRank(a)||shown.indexOf(b)- shown.indexOf(a),identity=phone&&shown.find(column=>column.identity);const hide=(column,pinned)=>!column.sized&&!column.alwaysVisible&&(column.pin==="none")!==pinned&&!(phone&&column.identity)&&gridLevel(column)>!phone;for(const column of this.columns)column.fitHidden=false;if(this.layoutMode&&this.layoutMode!=="fit")return;for(const column of shown)total +=size(column);for(const column of shown.filter(column=>hide(column,false)).sort(order))
35
+ if(total>width&&remaining>1){column.fitHidden=true;total -=size(column);remaining--}
36
+ if(identity&&!identity.fixed&&!identity.sized)total +=Math.min(identity.need??identity.width,(identity.indentNeed||0)+ 2*(this.fitCh||88))+(identity.reserve||0)- size(identity);if(identity)for(const column of shown.filter(column=>hide(column,true)).sort(order))
37
+ if(total>width){column.fitHidden=true;total -=size(column)}}};function gridButton(text,run){const button=h("button","cx-button");button.type="button";setText(button,text);button.onclick=event=>{button.focus({preventScroll:true});run(event)};return button}
37
38
  let keyboardInput=false;if(typeof document!=="undefined"){document.addEventListener("keydown",()=>{keyboardInput=true},true);document.addEventListener("pointerdown",()=>{keyboardInput=false},true)}
38
39
  const focusDrawerTitle=title=>{if(keyboardInput&&title===document.activeElement&&!title.matches(":focus-visible"))title.blur();title.toggleAttribute("data-cx-pointer-focus",!keyboardInput);if(!keyboardInput)title.addEventListener("blur",()=>title.removeAttribute("data-cx-pointer-focus"),{once:true});title.focus({preventScroll:true,focusVisible:keyboardInput})};function gridInputLabel(text,input){const label=h("label","cx-grid-field");label.appendChild(document.createTextNode(text));label.appendChild(input);return label}
39
40
  function gridOption(value,text){const option=h("option");option.value=value;setText(option,text);return option}
@@ -64,8 +65,9 @@ this.groupPins();this.restoredPin=true}
64
65
  return TREE_FIT.applyStatePresentation.call(this,state)}});const TREE_HEIGHT_DELAY=60;Object.assign(TreeGrid.prototype,{watchHeight(){if(!this.fitReady||this.view.autoHeight===false||this.total===this.heightTotal&&
65
66
  (this.heightFitted||!this.rows.some(row=>!row.status)))return;const growing=this.total>this.heightTotal;this.heightTotal=this.total;clearTimeout(this.heightTimer);if(!this.heightFitted||growing){this.fitHeight();return}
66
67
  this.heightTimer=setTimeout(()=>this.destroyed||this.fitHeight(),TREE_HEIGHT_DELAY)},fitHeight(){const grid=this.grid,style=this.host.style;if(!grid.clientWidth){this.heightTotal=undefined;return}
67
- if(this.heightInline===undefined){this.heightInline=style.maxHeight;this.heightMax=parseFloat(getComputedStyle(this.host).maxHeight)||Infinity;this.lifetime.signal.addEventListener("abort",()=>{clearTimeout(this.heightTimer);style.maxHeight=this.heightInline},{once:true})}
68
- const content=[...grid.children].reduce((sum,child)=>sum+child.offsetHeight,grid.offsetHeight-grid.clientHeight);const value=this.rows.length?Math.min(this.heightMax,Math.ceil(this.host.offsetHeight-grid.offsetHeight+content))+ "px":this.heightInline;if(style.maxHeight!==value){style.maxHeight=value;const anchor=this.interactionPanel&&this.rendered.get(this.panelNode);if(anchor?.isConnected)this.placeInteractionPanel(anchor)}
68
+ if(this.heightInline===undefined){const own=getComputedStyle(this.host).maxHeight;this.heightInline=style.maxHeight;this.heightMax=own==="none"||!own?Infinity:/^-?[\d.]+px$/.test(own) ? parseFloat(own) : own;
69
+ this.lifetime.signal.addEventListener("abort",()=>{clearTimeout(this.heightTimer);style.maxHeight=this.heightInline},{once:true})}
70
+ const content=[...grid.children].reduce((sum,child)=>sum+child.offsetHeight,grid.offsetHeight-grid.clientHeight);const height=Math.ceil(this.host.offsetHeight-grid.offsetHeight+content),bound=this.heightMax;const value=!this.rows.length?this.heightInline:typeof bound==="number"?Math.min(bound,height)+ "px":"min("+height+"px, "+bound+")";if(style.maxHeight!==value){style.maxHeight=value;const anchor=this.interactionPanel&&this.rendered.get(this.panelNode);if(anchor?.isConnected)this.placeInteractionPanel(anchor)}
69
71
  if(this.rows.some(row=>!row.status))this.heightFitted=true}});Object.assign(TreeGrid.prototype,{prepareSavedViews(){this.savedViews=new Map();if(this.stateKey())try{const stored=JSON.parse(sessionStorage.getItem(this.stateKey()+ ":views")||"[]");for(const entry of Array.isArray(stored)?stored.slice(0,20):[])if(this.readState(entry&&entry.state))this.savedViews.set(this.viewName(entry.name),entry.state)}catch(error){/* Unavailable storage or an invalid entry leaves the views that could be read. */ }
70
72
  },writeSavedViews(){if(this.stateKey())try{sessionStorage.setItem(this.stateKey()+ ":views",JSON.stringify([...this.savedViews].map(([name,state])=>({name,state}))))}
71
73
  catch(error){/* A disabled or full storage leaves the current session usable. */ }
@@ -60,7 +60,7 @@ function fill(template,values){return template.replace(/\{(\w+)\}/g, (match, nam
60
60
  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];
61
61
  let rules=pluralRules.get(lang);if(!rules){try{rules=new Intl.PluralRules(lang||"en")}catch(_){rules=new Intl.PluralRules("en")}pluralRules.set(lang,rules)}
62
62
  return forms[rules.select(Number(count))]??forms.other??template}
63
- let DEFAULT_LABELS=Object.freeze({searchPlaceholder:"Search",searchResults:"Search results",searchResultCount:"{count} results",searchResultCountOfTotal:"{count} of {total} results",searchResultCountOfMany:"{count} of many results",searchNoResults:"No results",searchTooShort:"Type at least {min} characters",searchWordTooShort:"Type a word of at least {min} characters",searchFailed:"Search failed: {error}",columns:"Columns",visibleColumns:"Visible columns",resizeColumn:"Resize {column}",columnWidth:"{width} pixels",loading:"Loading…",loadError:"Could not load: {error} (Enter to retry)",queryFailed:"Could not update the view: {error}",showMore:"Show more ({loaded} of {total} loaded)",showMoreUnknownTotal:"Show more ({loaded} loaded)",pageLoaded:"{loaded} of {total} loaded",itemsLoaded:"{count} items",yes:"Yes",no:"No",utcTime:"{time} UTC",localTime:"{time} local time",mountError:"This view could not be displayed: {error}"});function labelsFor(...sources){const labels=Object.assign({},DEFAULT_LABELS);for(const source of sources){if(source==null)continue;if(typeof source!=="object"||Array.isArray(source))throw new Error("CanopyX: labels must be an object of strings.");Object.keys(source).forEach((name)=>{if(!Object.hasOwn(DEFAULT_LABELS,name)&&!/^(?:gridEdit|format|calendar|flow|matrix)[A-Z]\w{0,63}$/.test(name)) throw new Error("CanopyX: there is no label '" + name + "'.");
63
+ let DEFAULT_LABELS=Object.freeze({searchPlaceholder:"Search",searchResults:"Search results",searchResultCount:"{count} results",searchResultCountOfTotal:"{count} of {total} results",searchResultCountOfMany:"{count} of many results",searchNoResults:"No results",searchTooShort:"Type at least {min} characters",searchWordTooShort:"Type a word of at least {min} characters",searchFailed:"Search failed: {error}",columns:"Columns",hiddenColumns:"[one]+{count} column[other]+{count} columns",visibleColumns:"Visible columns",resizeColumn:"Resize {column}",columnWidth:"{width} pixels",loading:"Loading…",loadError:"Could not load: {error} (Enter to retry)",queryFailed:"Could not update the view: {error}",showMore:"Show more ({loaded} of {total} loaded)",showMoreUnknownTotal:"Show more ({loaded} loaded)",pageLoaded:"{loaded} of {total} loaded",itemsLoaded:"{count} items",yes:"Yes",no:"No",utcTime:"{time} UTC",localTime:"{time} local time",mountError:"This view could not be displayed: {error}"});function labelsFor(...sources){const labels=Object.assign({},DEFAULT_LABELS);for(const source of sources){if(source==null)continue;if(typeof source!=="object"||Array.isArray(source))throw new Error("CanopyX: labels must be an object of strings.");Object.keys(source).forEach((name)=>{if(!Object.hasOwn(DEFAULT_LABELS,name)&&!/^(?:gridEdit|format|calendar|flow|matrix)[A-Z]\w{0,63}$/.test(name)) throw new Error("CanopyX: there is no label '" + name + "'.");
64
64
  if(typeof source[name]!=="string"||!/\S/.test(source[name])) throw new Error("CanopyX: label '" + name + "' must be a string with visible text.");
65
65
  if((name==="utcTime"||name==="localTime")&&source[name].indexOf("{time}")<0)throw new Error("CanopyX: label '"+name+"' must hold {time}.");labels[name]=source[name]})}
66
66
  return Object.freeze(labels)}
@@ -74,7 +74,7 @@ function trusted(kind,value){if(trustPolicy===undefined){try{trustPolicy=global.
74
74
  catch(_){trustPolicy=null}}
75
75
  return trustPolicy?trustPolicy["create"+kind](value):value}
76
76
  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.")}
77
- DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{gridViews:"Views",gridViewName:"View name",gridSaveView:"Save view",gridRemoveView:"Remove view",gridResetView:"Reset view",gridChooseView:"Choose a saved view",gridApply:"Apply",gridFindColumn:"Find a column…",gridShowAllColumns:"Show all",gridResetColumns:"Reset columns",gridReorderColumn:"{column}: drag, or press Alt+Up or Alt+Down to move",gridPinnedLeft:"Pinned left",gridPinnedRight:"Pinned right",gridResponsiveHidden:"Hidden at this width",gridExport:"Export",gridVisibleRows:"Visible rows",gridSelectedRows:"Selected rows",gridAllRows:"All rows",gridDownloadCsv:"Download CSV",gridCopy:"Copy",gridExportProgress:"{completed} of {total} records",gridExportReading:"{completed} records",gridExportCancelled:"Export cancelled",gridCancelExport:"Cancel export",gridMore:"More",gridPrint:"Print",gridDetails:"Record details",gridDetailsSection:"Details",gridPreviousRecord:"Previous record",gridNextRecord:"Next record",gridCloseRecord:"Close record details"}));DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{recordBack:"Back",recordBackTo:"Back to {record}",recordTrail:"Record trail",recordShowing:"Showing {record}",recordTabs:"Record views",recordSections:"Jump to a section",recordOtherFields:"Other fields",recordShowEmpty:"Show empty fields ({count})",recordHideEmpty:"Hide empty fields",recordActions:"Record actions",recordCopyValues:"Copy values",recordCopyJson:"Copy as JSON",recordCopyLink:"Copy link",recordCopied:"Copied to the clipboard",recordPrint:"Print record",recordExportCsv:"Export CSV",recordExportJson:"Export JSON",recordOpenAll:"Open all in grid",recordLoadMore:"Load more",recordCount:"{shown} of {total}",recordItems:"{count} items",recordMore:"+{count}",recordShowAll:"Show all {count}",recordRetry:"Retry",recordLoadFailed:"Could not load the record details: {error}",recordUnavailable:"Record {id} is not available here.",recordField:"Field",recordValue:"Value",recordSection:"Section",recordBefore:"Before",recordAfter:"After",recordAdded:"Added",recordRemoved:"Removed",recordChanged:"{count} changed fields"}));function boundedPattern(source,exact=true,whole=false){const bad=()=>{throw new Error("CanopyX: pattern requires literals, character classes and at most one variable repetition anchored at the start; groups, alternatives and backreferences are unsupported.")};if(typeof source!=="string"||source.length>256)bad();let width=0,variable=0;for(let i=0;i<source.length;){const unit=source[i++];if(unit==="^"&&i===1||unit==="$"&&i===source.length)continue;if(unit==="\\"){const escape=source[i++];if(!escape||!/^[dDsSwWbBnrtfv\\.^$*+?{}\[\]()|/]$/.test(escape)) bad();
77
+ DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{gridViews:"Views",gridViewName:"View name",gridSaveView:"Save view",gridRemoveView:"Remove view",gridResetView:"Reset view",gridChooseView:"Choose a saved view",gridApply:"Apply",gridFindColumn:"Find a column…",gridShowAllColumns:"Show all",gridResetColumns:"Reset columns",gridReorderColumn:"{column}: drag, or press Alt+Up or Alt+Down to move",gridPinnedLeft:"Pinned left",gridPinnedRight:"Pinned right",gridResponsiveHidden:"Hidden at this width",gridExport:"Export",gridVisibleRows:"Visible rows",gridSelectedRows:"Selected rows",gridAllRows:"All rows",gridDownloadCsv:"Download CSV",gridCopy:"Copy",gridExportProgress:"{completed} of {total} records",gridExportReading:"{completed} records",gridExportCancelled:"Export cancelled",gridCancelExport:"Cancel export",gridMore:"More",gridPrint:"Print",gridDetails:"Record details",gridDetailsSection:"Details",gridPreviousRecord:"Previous record",gridNextRecord:"Next record",gridCloseRecord:"Close"}));DEFAULT_LABELS=Object.freeze(Object.assign({},DEFAULT_LABELS,{recordBack:"Back",recordBackTo:"Back to {record}",recordTrail:"Record trail",recordShowing:"Showing {record}",recordTabs:"Record views",recordSections:"Jump to a section",recordOtherFields:"Other fields",recordShowEmpty:"Show empty fields ({count})",recordHideEmpty:"Hide empty fields",recordActions:"Record actions",recordCopyValues:"Copy values",recordCopyJson:"Copy as JSON",recordCopyLink:"Copy link",recordCopied:"Copied to the clipboard",recordPrint:"Print record",recordExportCsv:"Export CSV",recordExportJson:"Export JSON",recordOpenAll:"Open all in grid",recordLoadMore:"Load more",recordCount:"{shown} of {total}",recordItems:"{count} items",recordMore:"+{count}",recordShowAll:"Show all {count}",recordRetry:"Retry",recordLoadFailed:"Could not load the record details: {error}",recordUnavailable:"Record {id} is not available here.",recordField:"Field",recordValue:"Value",recordSection:"Section",recordBefore:"Before",recordAfter:"After",recordAdded:"Added",recordRemoved:"Removed",recordChanged:"{count} changed fields"}));function boundedPattern(source,exact=true,whole=false){const bad=()=>{throw new Error("CanopyX: pattern requires literals, character classes and at most one variable repetition anchored at the start; groups, alternatives and backreferences are unsupported.")};if(typeof source!=="string"||source.length>256)bad();let width=0,variable=0;for(let i=0;i<source.length;){const unit=source[i++];if(unit==="^"&&i===1||unit==="$"&&i===source.length)continue;if(unit==="\\"){const escape=source[i++];if(!escape||!/^[dDsSwWbBnrtfv\\.^$*+?{}\[\]()|/]$/.test(escape)) bad();
78
78
  }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(); }
79
79
  else if(c==="]"){closed=true;break}}
80
80
  if(!closed)bad()}else if("()|^$*+?{}]".includes(unit))bad();let count=1;if(i<source.length&&"*+?".includes(source[i])){variable++;i++}
@@ -334,7 +334,7 @@ function hostRenderers(renderers,columns,plugins){if(![Object.prototype,null].in
334
334
  Object.keys(renderers).some(id=>typeof renderers[id]!=="function"||!columns.some(column=>column.id===id)))
335
335
  throw new Error("CanopyX: invalid column renderers.");const named=Object.assign(Object.create(null),...plugins.map(item=>item.cellRenderers));return Object.assign(Object.create(null),...columns.filter(column=>typeof column.renderer==="string"&&named[column.renderer])
336
336
  .map(column=>({[column.id]:named[column.renderer]})),renderers)}
337
- function disposeHostSlot(slot){const lifetime=slot.hostLifetime;if(!lifetime)return;delete slot.hostLifetime;lifetime.dispose()}
337
+ function disposeHostSlot(slot){slot.childSlots?.forEach(disposeHostSlot);const lifetime=slot.hostLifetime;if(!lifetime)return;delete slot.hostLifetime;lifetime.dispose()}
338
338
  function disposeRowHostCells(row){row._cells?.forEach(disposeHostSlot)}
339
339
  class HostCellContext{#controller;#cleanups;constructor(slot,column,level,expanded,index){slot.hostLifetime=this;this.column=column;this.level=level;this.expanded=expanded;this.index=index}
340
340
  get signal(){this.#controller||=new AbortController();if(this.#cleanups===null)this.#controller.abort();return this.#controller.signal}
@@ -504,7 +504,7 @@ if(active>=0){list.children[active].setAttribute("aria-selected","false")}
504
504
  active=active<0?(delta<0?hits.length-1:0):(active+delta+hits.length)%hits.length;const item=list.children[active];item.setAttribute("aria-selected","true");input.setAttribute("aria-activedescendant",item.id);item.scrollIntoView({block:"nearest"})};input.addEventListener("input",()=>{cancelNavigation();sequence++;clearTimeout(timer);hits=[];close();timer=setTimeout(run,150)});input.addEventListener("keydown",(e)=>{if(e.defaultPrevented||e.isComposing)return;if(e.key==="ArrowDown"||e.key==="ArrowUp"){move(e.key==="ArrowDown"?1:-1)}else if(e.key==="Enter"){if(!list.hidden){choose(active>=0?active:0)}}else if(e.key==="Escape"){cancelNavigation();sequence++;clearTimeout(timer);if(list.hidden){input.value=""}
505
505
  close()}else{return}
506
506
  e.preventDefault()});input.addEventListener("blur",()=>{cancelNavigation();sequence++;clearTimeout(timer);clearTimeout(blurTimer);blurTimer=setTimeout(close,150)});this.cancelSearch=()=>{cancelNavigation();sequence++;clearTimeout(timer);clearTimeout(blurTimer);hits=[];close()};box.appendChild(input);box.appendChild(list);box.appendChild(status);bar.appendChild(box);this.searchInput=input}});function watchFlyout(anchorElement,panel,isOpen){const position=()=>{if(!isOpen())return;const anchor=anchorElement.getBoundingClientRect(),fixed=getComputedStyle(panel).position==="fixed";const below=Math.max(0,global.innerHeight-anchor.bottom-8);const above=Math.max(0,anchor.top-8);const opensAbove=below<120&&above>below,room=opensAbove?above:below;panel.style.maxHeight=(panel.classList.contains("cx-fb")?room:Math.min(320,room))+ "px";panel.style.top=opensAbove?"auto":fixed?anchor.bottom+"px":"100%";panel.style.bottom=opensAbove?fixed?global.innerHeight-anchor.top+"px":"100%":"auto";const width=panel.getBoundingClientRect().width;panel.style.left=Math.max(8,Math.min(anchor.left,global.innerWidth-width-8))-(fixed?0:anchor.left)+ "px"};global.addEventListener("resize",position);global.addEventListener("scroll",position,true);return{position,dispose:()=>{global.removeEventListener("resize",position);global.removeEventListener("scroll",position,true)}}}
507
- const PART_URL=typeof document!=="undefined"&&document.currentScript?.src;const PARTS={"tree-tools":["dbc7cd1bfd91ce63","canopyx-tree-tools.879d4297cd1760d6.js","canopyx-tree-tools.ef7a3fb9c9115a82.css"],"export":["8b99c0b7b0ec37c5","canopyx-export.6989ea36f5d6da7b.js"],"links":["f1ab7e32b501ce37","canopyx-links.d96addeefb3eccf5.js"],"search":["521eebd6e1bbbd35","canopyx-search.14c69f4551c69f23.js"],"link":["fa0c9e888463553b","canopyx-link.4ec39bc39fa6cf50.js","canopyx-link.8435c892613926ac.css"],"highlight":["f39423961654c1e2","canopyx-highlight.a6f93893d36ab343.js","canopyx-highlight.5b6d2e0d979f9f9b.css"],"layouts":["fa8c686f21626884","canopyx-layouts.e7250c82a6abf52e.js","canopyx-layouts.0b75738924533ee8.css"],"record-panel":["bcc06276c7933af6","canopyx-record-panel.a374fb4254f9115c.js","canopyx-record-panel.f3691d54cefaab04.css"]},PART_CODE=new Map(),partName=name=>typeof name==="string"&&/^[a-z][a-z\d-]{0,31}$/.test(name);
507
+ const PART_URL=typeof document!=="undefined"&&document.currentScript?.src;const PARTS={"tree-tools":["4c6c4101089c4444","canopyx-tree-tools.510207e8f47014fd.js","canopyx-tree-tools.ef7a3fb9c9115a82.css"],"export":["8b99c0b7b0ec37c5","canopyx-export.6989ea36f5d6da7b.js"],"links":["f1ab7e32b501ce37","canopyx-links.d96addeefb3eccf5.js"],"search":["521eebd6e1bbbd35","canopyx-search.14c69f4551c69f23.js"],"link":["fa0c9e888463553b","canopyx-link.4ec39bc39fa6cf50.js","canopyx-link.8435c892613926ac.css"],"highlight":["f39423961654c1e2","canopyx-highlight.a6f93893d36ab343.js","canopyx-highlight.5b6d2e0d979f9f9b.css"],"layouts":["fa8c686f21626884","canopyx-layouts.e7250c82a6abf52e.js","canopyx-layouts.0b75738924533ee8.css"],"record-panel":["bcc06276c7933af6","canopyx-record-panel.a374fb4254f9115c.js","canopyx-record-panel.f3691d54cefaab04.css"]},PART_CODE=new Map(),partName=name=>typeof name==="string"&&/^[a-z][a-z\d-]{0,31}$/.test(name);
508
508
  function registerPart(name,part){if(!partName(name)||!part||typeof part.version!=="string"||typeof part.install!=="function"||
509
509
  part.requires!==undefined&&!(Array.isArray(part.requires)&&part.requires.every(partName)))throw new Error("CanopyX: invalid part.");PART_CODE.set(name,part)}
510
510
  function definePart(View,name,label,context){const parts=View.prototype.parts||={};return parts[name]={name,label,context,parts}}
@@ -2,12 +2,18 @@
2
2
  // proposal to the host. The flow entry point (@evotecit/canopyx/flow) and the grid entry points create them.
3
3
  import type { CanopyPlugin } from "./plugins.js";
4
4
 
5
- /** A value a field holds: text, a number, a boolean, a list of values, or nothing. Secrets never have one. */
6
- export type FlowValue = string | number | boolean | null | readonly (string | number | boolean)[];
5
+ /** A value a field holds: text, a number, a boolean, a list of values, rows of a records field, picked records, or nothing. Secrets never have one. */
6
+ export type FlowValue = string | number | boolean | null | readonly (string | number | boolean)[] | readonly FlowRecordRow[] | readonly FlowPickedRecord[];
7
+
8
+ /** A row of a records field: a cell per column. */
9
+ export type FlowRecordRow = Readonly<Record<string, string | number | boolean | null>>;
10
+
11
+ /** A record a pick step chose: its id and the label the tray and the review page show. */
12
+ export interface FlowPickedRecord { readonly id: string; readonly label: string }
7
13
 
8
14
  /** The field types: the shared grid editors, the types flows add, and custom editors. */
9
15
  export type FlowFieldType = "text" | "number" | "date" | "datetime" | "time" | "select" | "toggle" | "lookup" | "choice" | "multiChoice"
10
- | "textarea" | "list" | "secret" | "duration" | "custom";
16
+ | "textarea" | "list" | "secret" | "duration" | "records" | "custom";
11
17
 
12
18
  /** A condition operator, from the filter builder's grammar. */
13
19
  export type FlowOperator = "eq" | "ne" | "gt" | "ge" | "lt" | "le" | "contains" | "notContains" | "startsWith" | "endsWith" | "matches" | "notMatches"
@@ -77,17 +83,73 @@ export interface FlowField {
77
83
  units?: readonly ("seconds" | "minutes" | "hours" | "days")[];
78
84
  /** Custom fields: the registered editor's name. */
79
85
  editor?: string;
86
+ /** Select, choice, multiple-choice and lookup fields: "host" takes the options from hooks.options (or an endpoint). */
87
+ source?: "document" | "host";
88
+ /** The fields (at most 16, never secrets) whose change reloads host options, or empties this answer. */
89
+ dependsOn?: readonly string[];
90
+ /** Records fields: the columns of each row (1 to 16). */
91
+ columns?: readonly FlowRecordColumn[];
92
+ }
93
+
94
+ /** A column of a records field. */
95
+ export interface FlowRecordColumn {
96
+ id: string;
97
+ title: string;
98
+ type?: "text" | "number" | "date" | "time" | "select" | "toggle";
99
+ required?: boolean;
100
+ options?: readonly FlowOption[];
101
+ validation?: { min?: number; max?: number; minLength?: number; maxLength?: number; pattern?: string; message?: string };
102
+ width?: number;
103
+ }
104
+
105
+ /** A server check of some answers, such as "Send a test message", run through hooks.check (or an endpoint). */
106
+ export interface FlowCheck {
107
+ id: string;
108
+ label: string;
109
+ description?: string;
110
+ /** The fields it tests (1 to 64); a change marks its result stale. Secrets among them reach the hook through secret(id). */
111
+ fields: readonly string[];
112
+ /** "pass" blocks Next until it passes on the current answers; "run" until it ran on them without failing. */
113
+ required?: "pass" | "run";
114
+ }
115
+
116
+ /** A pick step's picker: a grid or tree view whose selection is the step's answer. */
117
+ export interface FlowPick {
118
+ field: {
119
+ id: string; label?: string; help?: string; required?: boolean; when?: FlowCondition; parameter?: string; path?: string; sensitive?: boolean;
120
+ validation?: Pick<FlowValidation, "minItems" | "maxItems" | "message">; default?: readonly FlowPickedRecord[];
121
+ };
122
+ /** The grid or tree view document (canopyx/2). */
123
+ view: Readonly<Record<string, unknown>>;
124
+ /** The view column whose value labels a picked record. */
125
+ label: string;
126
+ /** Whether the tray of picked records shows (default true). */
127
+ tray?: boolean;
128
+ height?: number;
80
129
  }
81
130
 
82
- /** A section of a step: a heading over some of its fields. */
83
- export interface FlowSection { id: string; label: string; description?: string; fields: readonly FlowField[]; when?: FlowCondition }
131
+ /** A preset the reader can start from. */
132
+ export interface FlowPreset { id: string; label: string; description?: string; values: Readonly<Record<string, FlowValue>> }
84
133
 
85
- /** A step: a form, a page of text, or the review page (which comes last). */
134
+ /** A section of a step: a heading over some of its fields, or over fields the host supplies. */
135
+ export interface FlowSection {
136
+ id: string;
137
+ label: string;
138
+ description?: string;
139
+ /** The section's own fields; none when its fields come from the host. */
140
+ fields?: readonly FlowField[];
141
+ when?: FlowCondition;
142
+ /** "host": hooks.fields (or an endpoint) supplies the fields as the step opens, and again when a field in dependsOn changes. */
143
+ source?: "host";
144
+ dependsOn?: readonly string[];
145
+ }
146
+
147
+ /** A step: a form, a page of text, a pick step, or the review page (which comes last). */
86
148
  export interface FlowStep {
87
149
  id: string;
88
150
  title?: string;
89
151
  description?: string;
90
- type?: "form" | "info" | "review";
152
+ type?: "form" | "info" | "pick" | "review";
91
153
  fields?: readonly FlowField[];
92
154
  sections?: readonly FlowSection[];
93
155
  when?: FlowCondition;
@@ -96,6 +158,19 @@ export interface FlowStep {
96
158
  declaration?: string;
97
159
  /** Review steps in edit mode: whether only changed answers show first (default true). */
98
160
  changesOnly?: boolean;
161
+ /** Form steps: server checks (at most 8). */
162
+ checks?: readonly FlowCheck[];
163
+ /** Task lists: the heading the task sits under. */
164
+ group?: string;
165
+ /** Pick steps: the picker. */
166
+ pick?: FlowPick;
167
+ /** Review steps: a text the reader types before submitting, or a field whose answer they type. */
168
+ confirm?: { label: string; text?: string; field?: string };
169
+ /** Review steps: a box the reader ticks before submitting, while the condition holds. */
170
+ acknowledge?: { label: string; when?: FlowCondition };
171
+ /** Review steps: true says the review page shows the host's preview (hooks.preview), so the page loads the preview part with
172
+ * the flow; without it, a preview hook loads the part on its own. */
173
+ preview?: boolean;
99
174
  }
100
175
 
101
176
  /** A flow document, the contract C# writes with CanopyFlow and any host can map. */
@@ -105,8 +180,9 @@ export interface FlowDocument {
105
180
  id: string;
106
181
  title: string;
107
182
  description?: string;
108
- mode?: "linear" | "free";
109
- layout?: { orientation?: "auto" | "horizontal" | "vertical" };
183
+ mode?: "linear" | "free" | "tasks";
184
+ /** Container "dialog" or "drawer" opens the flow from a launch button. */
185
+ layout?: { orientation?: "auto" | "horizontal" | "vertical"; container?: "inline" | "dialog" | "drawer"; launch?: string };
110
186
  density?: "compact" | "normal" | "comfortable";
111
187
  timeZone?: "utc" | "local";
112
188
  steps: readonly FlowStep[];
@@ -117,6 +193,15 @@ export interface FlowDocument {
117
193
  baseline?: string;
118
194
  submit?: { label?: string };
119
195
  labels?: Readonly<Record<string, string>>;
196
+ /** The equivalent command the review page shows; variants come from hooks.commands. */
197
+ command?: { name: string; style?: "powershell" | "posix" | "windows"; variants?: readonly { id: string; label: string }[] };
198
+ presets?: readonly FlowPreset[];
199
+ /** Where the draft lives: nowhere (the default), the session, local storage, or hooks.draftStore. */
200
+ draft?: "none" | "session" | "local" | "host";
201
+ /** Whether the page's address names the open step (#flow-id/step-id). */
202
+ deepLinks?: boolean;
203
+ /** Routes on the page's own origin that answer the hooks the host left unset (CanopyX.AspNetCore's MapCanopyFlow). */
204
+ endpoint?: { url: string; routes: readonly ("options" | "fields" | "check" | "validate" | "preview" | "status" | "draft" | "submit")[] };
120
205
  }
121
206
 
122
207
  /** The proposal flowSubmit carries: visible answers, secret states, the changes and a JSON patch. Never a secret value. */
@@ -130,8 +215,50 @@ export interface FlowSubmission {
130
215
  secrets: Record<string, { state: "set" | "keep" }>;
131
216
  changes: { field: string; path?: string; from: FlowValue; to: FlowValue }[];
132
217
  patch: ({ op: "add" | "replace"; path: string; value: FlowValue } | { op: "remove"; path: string })[];
218
+ /** The checks that ran on these answers, as the browser saw them: a claim the server runs again before relying on it. */
219
+ checks?: Record<string, { status: FlowCheckStatus; at: string }>;
220
+ /** The equivalent command of these answers; typed secrets read <secret>. */
221
+ command?: string;
222
+ /** The preset the answers started from. */
223
+ preset?: string;
224
+ }
225
+
226
+ /** A check's outcome. */
227
+ export type FlowCheckStatus = "pass" | "warning" | "fail";
228
+
229
+ /** What a check answers. */
230
+ export interface FlowCheckResult {
231
+ status: FlowCheckStatus;
232
+ message?: string;
233
+ details?: { label: string; value: string | number | boolean }[];
234
+ fieldErrors?: Record<string, string>;
133
235
  }
134
236
 
237
+ /** A task's state on a task-list flow, as the host sees it. */
238
+ export interface FlowStepState { state: "complete" | "needsAction" | "blocked" | "notStarted"; summary?: string; blockers?: string[] }
239
+
240
+ /** A row of the review page's preview. */
241
+ export interface FlowPreviewRow { label: string; value: string | number | boolean; tone?: "info" | "success" | "warning" | "danger" }
242
+
243
+ /** The review page's preview: rows of what submitting does, or a title, rows and notes. Text only. */
244
+ export type FlowPreview = readonly FlowPreviewRow[] | { title?: string; rows?: readonly FlowPreviewRow[]; notes?: readonly string[] };
245
+
246
+ /** A draft: the answers (never secrets or sensitive fields), the open step, the completed steps, the preset and the baseline. */
247
+ export interface FlowDraft {
248
+ kind: "flowDraft";
249
+ version: 1;
250
+ flow: string;
251
+ step: string;
252
+ completed: string[];
253
+ values: Record<string, FlowValue>;
254
+ saved: string;
255
+ baseline?: string;
256
+ preset?: string;
257
+ }
258
+
259
+ /** What an asynchronous hook receives: a signal that a newer request, Cancel, the timeout or destroy aborts. */
260
+ export interface FlowHookContext { signal: AbortSignal }
261
+
135
262
  /** The submit event's detail: the proposal, how to answer it, and each secret once. */
136
263
  export interface FlowSubmitDetail extends Readonly<FlowSubmission> {
137
264
  respondWith(answer: FlowAnswer | Promise<FlowAnswer>): void;
@@ -159,6 +286,24 @@ export interface FlowHooks {
159
286
  validateStep?(stepId: string, values: Record<string, FlowValue>): FlowRuleResult | Promise<FlowRuleResult>;
160
287
  validateFlow?(values: Record<string, FlowValue>): FlowRuleResult | Promise<FlowRuleResult>;
161
288
  submit?(detail: FlowSubmitDetail): FlowAnswer | Promise<FlowAnswer>;
289
+ /** The options of a field whose source is "host". */
290
+ options?(fieldId: string, values: Record<string, FlowValue>, context: FlowHookContext): readonly FlowOption[] | Promise<readonly FlowOption[]>;
291
+ /** The fields of a host section: at most 64, of the plain types, without path, source or dependsOn. */
292
+ fields?(sectionId: string, values: Record<string, FlowValue>, context: FlowHookContext): readonly FlowField[] | Promise<readonly FlowField[]>;
293
+ /** Runs a check; secret(id) reads, once, a secret among the fields it tests. */
294
+ check?(checkId: string, values: Record<string, FlowValue>, context: FlowHookContext & { secret(fieldId: string): string | null }): FlowCheckResult | Promise<FlowCheckResult>;
295
+ /** The review page's preview of what submitting does. */
296
+ preview?(values: Record<string, FlowValue>, context: FlowHookContext): FlowPreview | Promise<FlowPreview>;
297
+ /** A task list's step states, by step id. */
298
+ status?(values: Record<string, FlowValue>, context: FlowHookContext): Record<string, FlowStepState> | Promise<Record<string, FlowStepState>>;
299
+ /** The command's variants, by variant id: each writes the command line of the answers. */
300
+ commands?: Record<string, (values: Record<string, FlowValue>) => string | Promise<string>>;
301
+ /** The store of draft "host". */
302
+ draftStore?: { load(flowId: string): FlowDraft | null | Promise<FlowDraft | null>; save(flowId: string, draft: FlowDraft): void | Promise<void>; clear(flowId: string): void | Promise<void> };
303
+ /** Presets the host keeps; save adds "Save as preset". */
304
+ presetStore?: { list(flowId: string): readonly FlowPreset[] | Promise<readonly FlowPreset[]>; save?(flowId: string, preset: FlowPreset): void | Promise<void> };
305
+ /** Grid or tree options of a pick step's view, by step id, such as a remote data source. */
306
+ pickers?: Record<string, Readonly<Record<string, unknown>>>;
162
307
  }
163
308
 
164
309
  /** Options of createFlow and CanopyX.configure for a flow fragment. */
@@ -176,6 +321,17 @@ export interface FlowOptions {
176
321
  onSubmit?(detail: FlowSubmitDetail): FlowAnswer | Promise<FlowAnswer> | void;
177
322
  onSubmitted?(answer: FlowAnswer): void;
178
323
  onReload?(detail: { baseline: string | null }): void;
324
+ onCheck?(detail: { check: string; result: { status: FlowCheckStatus; message: string | null; details: { label: string; value: string }[]; at: string } }): void;
325
+ /** A dialog or drawer flow closes with answers; false (or cancelling flowLeaving) keeps it open. */
326
+ onLeaving?(detail: { reason: "close" }): boolean | void;
327
+ /** Deep links: a step's fragment, in place of "flow-id/step-id". */
328
+ formatLink?(stepId: string): string | null;
329
+ /** Deep links: the step a fragment names, or null. */
330
+ parseLink?(fragment: string): string | null;
331
+ /** How long an asynchronous hook may take, in milliseconds: 100 to 600000, default 30000. */
332
+ timeout?: number;
333
+ /** Headers an endpoint's requests carry, such as an antiforgery token. */
334
+ headers?: Record<string, string>;
179
335
  }
180
336
 
181
337
  /** A validation result: the errors and warnings of a step or the whole flow. */
@@ -212,6 +368,21 @@ export interface FlowApi {
212
368
  submit(): Promise<FlowAnswer | null>;
213
369
  setProgress(progress: { done?: number; total?: number; message?: string }): boolean;
214
370
  complete(answer: FlowAnswer): boolean;
371
+ /** Runs a check now; resolves with its result as the flow keeps it (field messages show in place), or null when it was cancelled.
372
+ * Rejects for a check the flow does not have. */
373
+ runCheck(checkId: string): Promise<{ status: FlowCheckStatus; message: string | null; details: { label: string; value: string }[]; at: string } | null>;
374
+ /** Loads a host-option field's options again. Rejects for any other field. */
375
+ refreshOptions(fieldId: string): Promise<void>;
376
+ /** Sets a task's state as the host sees it; null returns it to what the answers say. Throws for a flow that is no task list. */
377
+ setStepState(stepId: string, state: FlowStepState | null): void;
378
+ /** Saves the draft now; resolves with whether it was saved (false for a flow without a draft store). */
379
+ saveDraft(): Promise<boolean>;
380
+ /** Starts from a preset, or from the plain start with null. Rejects for a preset the flow does not have. */
381
+ setPreset(presetId: string | null): Promise<void>;
382
+ /** Dialog and drawer flows: opens the container; true once it is open, false for an inline flow. */
383
+ open(): boolean;
384
+ /** Dialog and drawer flows: closes the container, asking first when answers would be lost; false when it stays open. */
385
+ close(): boolean;
215
386
  /** Releases the flow and its host's mount state, even before ready or after mount failure. Repeated calls do nothing. */
216
387
  destroy(): void;
217
388
  }
@@ -354,6 +354,8 @@ interface GridDefinition<Q extends AcceptedGridQuery = GridQuery, Row extends Gr
354
354
  /** Pager settings of a paged view, read by the pager part (canopyx-pager.js); absent keeps the built-in pager. */
355
355
  pager?: GridPager;
356
356
  selection: "none" | "single" | "multiple";
357
+ /** Ordinal IDs are source positions in one snapshot; saved selection replay and keyed live updates are unavailable. Default stableKey. */
358
+ rowIdentity?: "stableKey" | "ordinal";
357
359
  search: boolean;
358
360
  /** Search-input debounce (0..5000). Absent uses 0 for inline and 300 for host sources. */
359
361
  searchDelayMs?: number;
@@ -378,6 +380,8 @@ interface GridDefinition<Q extends AcceptedGridQuery = GridQuery, Row extends Gr
378
380
  dragRows?: boolean;
379
381
  stateKey?: string;
380
382
  timeZone?: TimeZone;
383
+ /** Space-separated host styling classes copied onto body-appended record, text and column overlays when opened. */
384
+ overlayClass?: string;
381
385
  labels?: Partial<CanopyLabels>;
382
386
  /** Sanitizes trusted host HTML details before insertion; shares the tree host's sanitizer contract. */
383
387
  sanitizeHtml?: HostOptions["sanitizeHtml"];
@@ -695,6 +699,8 @@ interface GridHostBehavior<Q extends AcceptedGridQuery = GridQuery, Row extends
695
699
  /** Overrides the view's search-input debounce (0..5000); Enter always applies immediately. */
696
700
  searchDelayMs?: number;
697
701
  source?: Q extends GridCursorQuery ? GridCursorSource<Row> : GridSource<Row>;
702
+ /** Space-separated host styling classes copied onto body-appended record, text and column overlays when opened. */
703
+ overlayClass?: string;
698
704
  labels?: Partial<CanopyLabels>;
699
705
  renderers?: Record<string, (row: Row, element: HTMLElement, context: GridCellContext) => boolean | void>;
700
706
  cachePageLimit?: number;
@@ -887,6 +893,8 @@ export interface DataGrid<Q extends AcceptedGridQuery = GridQuery, Row extends G
887
893
  getSelection(): string[];
888
894
  setSelection(ids: string[]): void;
889
895
  clearSelection(): void;
896
+ /** The loaded records among ids, as copies of { id, cells }; a record that is not loaded now is left out. Never fetches. */
897
+ getLoadedRows(ids: readonly string[]): { id: string; cells: Record<string, Cell> }[];
890
898
  prepareExport(options: GridExportOptions): GridExport<Q>;
891
899
  toCsv(options: GridExportOptions): Promise<string>;
892
900
  copy(options: GridExportOptions): Promise<string>;
@@ -11,6 +11,7 @@ const labels = Object.freeze({
11
11
  searchWordTooShort: "Ein Wort mit mindestens {min} Zeichen eingeben",
12
12
  searchFailed: "Suche fehlgeschlagen: {error}",
13
13
  columns: "Spalten",
14
+ hiddenColumns: "[one]+{count} Spalte[other]+{count} Spalten",
14
15
  gridFilters: "Filter",
15
16
  gridFitContent: "An Inhalt anpassen",
16
17
  gridUnpin: "Fixierung aufheben",
@@ -243,7 +244,7 @@ const labels = Object.freeze({
243
244
  gridExportCancelled: "Export abgebrochen",
244
245
  gridCancelExport: "Export abbrechen",
245
246
  gridPrint: "Drucken",
246
- gridCloseRecord: "Datensatzdetails schließen",
247
+ gridCloseRecord: "Schließen",
247
248
  gridContains: "enthält",
248
249
  gridStartsWith: "beginnt mit",
249
250
  gridIn: "ist einer der Werte",
@@ -588,6 +589,80 @@ const labels = Object.freeze({
588
589
  flowCopied: "Kopiert",
589
590
  flowSavedIt: "Ich habe es gespeichert",
590
591
  flowShownOnce: "Wird nur einmal angezeigt. Jetzt kopieren.",
592
+ flowTimedOut: "Der Host hat nicht rechtzeitig geantwortet.",
593
+ flowNoHostHook: "Der Host beantwortet das nicht.",
594
+ flowOptionsLoading: "Optionen werden geladen…",
595
+ flowOptionsFailed: "Die Optionen konnten nicht geladen werden: {message}",
596
+ flowRetry: "Erneut versuchen",
597
+ flowRunAgain: "Erneut ausführen",
598
+ flowRunCheckAgain: "„{check}“ erneut ausführen",
599
+ flowFieldsLoading: "Felder werden geladen…",
600
+ flowFieldsFailed: "Die Felder konnten nicht geladen werden: {message}",
601
+ flowChangedMark: "Geändert",
602
+ flowRecordActions: "Aktionen",
603
+ flowRecordCell: "{column}, Zeile {row}",
604
+ flowRecordRemove: "Zeile {row} entfernen",
605
+ flowRecordAdd: "Zeile hinzufügen",
606
+ flowRecordsEmpty: "Noch keine Zeilen.",
607
+ flowRecordProblem: "Zeile {row}: {message}",
608
+ flowRecordsCount: "[one]{count} Zeile[other]{count} Zeilen",
609
+ flowPickMore: "und {count} weitere",
610
+ flowPickCount: "[one]{count} ausgewählt[other]{count} ausgewählt",
611
+ flowPickChosen: "Ausgewählt",
612
+ flowPickNone: "Noch nichts ausgewählt.",
613
+ flowChecks: "Prüfungen",
614
+ flowCancel: "Abbrechen",
615
+ flowCheckAcknowledge: "Trotz der Warnung fortfahren",
616
+ flowCheckRunning: "Wird geprüft…",
617
+ flowCheckNotRun: "Nicht ausgeführt",
618
+ flowCheckStale: "Die Antworten haben sich seit der Prüfung geändert",
619
+ flowCheckPassed: "Bestanden um {time}",
620
+ flowCheckWarning: "Warnung um {time}",
621
+ flowCheckFailed: "Fehlgeschlagen um {time}",
622
+ flowCheckInvalid: "Die Prüfung lieferte keine verwertbare Antwort.",
623
+ flowCheckNeeded: "Führen Sie „{check}“ mit diesen Antworten aus.",
624
+ flowCheckNotPassed: "„{check}“ wurde nicht bestanden.",
625
+ flowCheckConfirmWarning: "Bestätigen Sie die Warnung von „{check}“.",
626
+ flowTasks: "Aufgaben",
627
+ flowTasksProgress: "{done} von {count} Aufgaben erledigt",
628
+ flowTaskNeedsAction: "Handlungsbedarf",
629
+ flowTaskBlocked: "Kann noch nicht beginnen",
630
+ flowTasksReview: "Prüfen und absenden",
631
+ flowTasksIncomplete: "Erledigen Sie zuerst die erforderlichen Aufgaben.",
632
+ flowBackToTasks: "Zurück zu den Aufgaben",
633
+ flowSaveAndReturn: "Speichern und zurück",
634
+ flowSaveDraft: "Entwurf speichern",
635
+ flowClose: "Schließen",
636
+ flowDiscardQuestion: "Ihre Antworten verwerfen?",
637
+ flowDiscard: "Verwerfen",
638
+ flowKeepEditing: "Weiter bearbeiten",
639
+ flowDraftSaved: "Entwurf gespeichert",
640
+ flowDraftFailed: "Der Entwurf wurde nicht gespeichert.",
641
+ flowDraftFound: "Gespeicherter Entwurf",
642
+ flowDraftFrom: "Sie haben einen Entwurf vom {time}.",
643
+ flowDraftStale: "Die Einstellungen haben sich nach dem Speichern dieses Entwurfs geändert.",
644
+ flowDraftResume: "Dort weitermachen, wo Sie aufgehört haben",
645
+ flowDraftRestart: "Neu beginnen",
646
+ flowStartFrom: "Ausgangspunkt",
647
+ flowPresetSave: "Als Vorlage speichern",
648
+ flowPresetNone: "Standardwerte",
649
+ flowPresetsSaved: "Gespeicherte Vorlagen",
650
+ flowPresetApplied: "Ausgehend von {preset}",
651
+ flowPresetName: "Name der Vorlage",
652
+ flowPresetFailed: "Die Vorlage wurde nicht gespeichert.",
653
+ flowPresetSaved: "Vorlage {preset} gespeichert",
654
+ flowPresetChanges: "[one]Basierend auf {preset}, {count} Änderung[other]Basierend auf {preset}, {count} Änderungen",
655
+ flowCommandTab: "Befehl",
656
+ flowPatchTab: "JSON-Patch",
657
+ flowCommandTitle: "Entsprechender Befehl",
658
+ flowPreview: "Vorschau",
659
+ flowPreviewLoading: "Vorschau wird geladen…",
660
+ flowPreviewFailed: "Die Vorschau konnte nicht geladen werden: {message}",
661
+ flowPreviewNone: "Keine Vorschau.",
662
+ flowConfirmMismatch: "Geben Sie genau {text} ein, um fortzufahren.",
663
+ flowAcknowledgeNeeded: "Bestätigen Sie dies vor dem Absenden.",
664
+ flowPresetCurrent: "Aktuelle Einstellungen",
665
+ flowPickOneByOne: "Wählen Sie die Datensätze einzeln oder seitenweise aus.",
591
666
  matrixRows: "Zeilen",
592
667
  matrixColumns: "Spalten",
593
668
  matrixSearch: "Zeilen und Spalten suchen",
@@ -614,6 +689,7 @@ const labels = Object.freeze({
614
689
  matrixItemTotal: "{item}: {count}",
615
690
  matrixExpandGroup: "{group} aufklappen",
616
691
  matrixCollapseGroup: "{group} zuklappen",
692
+ matrixGroupHeadingCount: "{count}",
617
693
  matrixGroupCount: "Elemente: {count}",
618
694
  matrixDetails: "Details",
619
695
  matrixClose: "Schließen",