@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.
- package/CanopyX.Html/Assets/canopyx-calendar.js +4 -3
- package/CanopyX.Html/Assets/canopyx-detail-rows.js +2 -2
- package/CanopyX.Html/Assets/canopyx-filter-editor.js +2 -1
- package/CanopyX.Html/Assets/canopyx-flow-checks.css +24 -0
- package/CanopyX.Html/Assets/canopyx-flow-checks.js +13 -0
- package/CanopyX.Html/Assets/canopyx-flow-command.css +14 -0
- package/CanopyX.Html/Assets/canopyx-flow-command.js +19 -0
- package/CanopyX.Html/Assets/canopyx-flow-consent.css +6 -0
- package/CanopyX.Html/Assets/canopyx-flow-consent.js +8 -0
- package/CanopyX.Html/Assets/canopyx-flow-dialog.css +14 -0
- package/CanopyX.Html/Assets/canopyx-flow-dialog.js +3 -0
- package/CanopyX.Html/Assets/canopyx-flow-drafts.css +17 -0
- package/CanopyX.Html/Assets/canopyx-flow-drafts.js +21 -0
- package/CanopyX.Html/Assets/canopyx-flow-endpoint.js +6 -0
- package/CanopyX.Html/Assets/canopyx-flow-entry.css +1 -0
- package/CanopyX.Html/Assets/canopyx-flow-entry.js +6 -2
- package/CanopyX.Html/Assets/canopyx-flow-host.css +5 -0
- package/CanopyX.Html/Assets/canopyx-flow-host.js +4 -0
- package/CanopyX.Html/Assets/canopyx-flow-links.js +6 -0
- package/CanopyX.Html/Assets/canopyx-flow-module.mjs +6 -2
- package/CanopyX.Html/Assets/canopyx-flow-options.js +25 -0
- package/CanopyX.Html/Assets/canopyx-flow-pick.css +19 -0
- package/CanopyX.Html/Assets/canopyx-flow-pick.js +9 -0
- package/CanopyX.Html/Assets/canopyx-flow-preview.css +11 -0
- package/CanopyX.Html/Assets/canopyx-flow-preview.js +9 -0
- package/CanopyX.Html/Assets/canopyx-flow-records.css +9 -0
- package/CanopyX.Html/Assets/canopyx-flow-records.js +12 -0
- package/CanopyX.Html/Assets/canopyx-flow-tasks.css +29 -0
- package/CanopyX.Html/Assets/canopyx-flow-tasks.js +20 -0
- package/CanopyX.Html/Assets/canopyx-flow.js +48 -45
- package/CanopyX.Html/Assets/canopyx-formatting.js +7 -6
- package/CanopyX.Html/Assets/canopyx-grid-module.mjs +69 -42
- package/CanopyX.Html/Assets/canopyx-grid.css +23 -0
- package/CanopyX.Html/Assets/canopyx-grid.js +69 -42
- package/CanopyX.Html/Assets/canopyx-interactive.js +4 -4
- package/CanopyX.Html/Assets/canopyx-labels-de.js +77 -1
- package/CanopyX.Html/Assets/canopyx-labels-es.js +77 -1
- package/CanopyX.Html/Assets/canopyx-labels-fr.js +77 -1
- package/CanopyX.Html/Assets/canopyx-labels-it.js +77 -1
- package/CanopyX.Html/Assets/canopyx-labels-pl.js +77 -1
- package/CanopyX.Html/Assets/canopyx-live.js +4 -3
- package/CanopyX.Html/Assets/canopyx-matrix.js +3 -3
- package/CanopyX.Html/Assets/canopyx-module.mjs +4 -4
- package/CanopyX.Html/Assets/canopyx-reporting-module.mjs +71 -44
- package/CanopyX.Html/Assets/canopyx-reporting.css +23 -0
- package/CanopyX.Html/Assets/canopyx-reporting.js +71 -44
- package/CanopyX.Html/Assets/canopyx-tree-tools.js +10 -8
- package/CanopyX.Html/Assets/canopyx.js +4 -4
- package/JavaScript/flow.d.ts +180 -9
- package/JavaScript/grid.d.ts +8 -0
- package/JavaScript/locales/de.mjs +77 -1
- package/JavaScript/locales/es.mjs +77 -1
- package/JavaScript/locales/fr.mjs +77 -1
- package/JavaScript/locales/it.mjs +77 -1
- package/JavaScript/locales/pl.mjs +77 -1
- package/JavaScript/protocol.d.ts +1 -1
- package/README.md +6 -2
- package/package.json +3 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(global){"use strict";const installed=new WeakSet();const part={version:"
|
|
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=
|
|
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 +=
|
|
34
|
-
if(total>width&&remaining>1){column.fitHidden=true;total -=
|
|
35
|
-
if(identity&&!identity.fixed&&!identity.sized)total +=Math.min(identity.need??identity.width,(identity.indentNeed||0)+ 2*(this.fitCh||88))+(identity.reserve||0)-
|
|
36
|
-
if(total>width){column.fitHidden=true;total -=
|
|
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){
|
|
68
|
-
|
|
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
|
|
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":["
|
|
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}}
|
package/JavaScript/flow.d.ts
CHANGED
|
@@ -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
|
|
83
|
-
export interface
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/JavaScript/grid.d.ts
CHANGED
|
@@ -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: "
|
|
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",
|