pict-section-form 1.1.8 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/docs/Configuration.md +1 -0
  3. package/docs/Layouts.md +60 -0
  4. package/docs/_version.json +3 -3
  5. package/docs/examples/README.md +1 -1
  6. package/docs/examples/change_tracking/change_tracking.js +322 -20
  7. package/docs/examples/diagram_form/diagram_form_example.min.js +2 -2
  8. package/docs/examples/dynamic_analysis/dynamic_analysis_application.js +301 -20
  9. package/docs/examples/gradebook/README.md +43 -1
  10. package/docs/examples/gradebook/gradebook_application.min.js +2 -2
  11. package/docs/examples/ndt_field_test/ndt_field_test.js +322 -20
  12. package/docs/examples/richtext_form/richtext_form_example.min.js +2 -3
  13. package/docs/examples/scope_mathematics/scope_mathematics.js +322 -20
  14. package/docs/examples/simple_table/README.md +1 -0
  15. package/docs/examples/simple_table/simple_tabular_application.min.js +2 -2
  16. package/docs/examples/superhero_studio/superhero_studio_example.min.js +2 -3
  17. package/docs/index.html +2 -2
  18. package/docs/retold-catalog.json +1 -1
  19. package/docs/retold-keyword-index.json +10252 -9612
  20. package/example_applications/complex_table/Complex-Tabular-Application.js +5 -0
  21. package/example_applications/gradebook/Gradebook-Application.js +19 -4
  22. package/example_applications/gradebook/package.json +2 -2
  23. package/example_applications/simple_table/README-SimpleTable.md +9 -0
  24. package/example_applications/simple_table/Simple-Tabular-Application.js +6 -1
  25. package/package.json +4 -4
  26. package/source/global.d.ts +1 -0
  27. package/source/providers/inputs/Pict-Provider-Input-AutofillTriggerGroup.js +30 -0
  28. package/source/providers/inputs/Pict-Provider-Input-Diagram.js +87 -0
  29. package/source/providers/inputs/Pict-Provider-Input-EntityBundleRequest.js +11 -1
  30. package/source/providers/layouts/Pict-Layout-Tabular.js +639 -1
  31. package/source/views/Pict-View-Form-Metacontroller.js +59 -0
  32. package/test/Pict-View-Form-Metacontroller-Marshal_tests.js +100 -0
  33. package/test/PictSectionForm-Tabular-Features_tests.js +293 -0
  34. package/types/source/Pict-Section-Form.d.ts +3 -0
  35. package/types/source/providers/Pict-Provider-DynamicFormSolverBehaviors.d.ts +115 -0
  36. package/types/source/providers/Pict-Provider-DynamicFormSolverBehaviors.d.ts.map +1 -1
  37. package/types/source/providers/Pict-Provider-DynamicTabularData.d.ts +24 -0
  38. package/types/source/providers/Pict-Provider-DynamicTabularData.d.ts.map +1 -1
  39. package/types/source/providers/Pict-Provider-DynamicTemplates.d.ts.map +1 -1
  40. package/types/source/providers/inputs/Pict-Provider-Input-AutofillTriggerGroup.d.ts.map +1 -1
  41. package/types/source/providers/inputs/Pict-Provider-Input-Diagram-CSS.d.ts +3 -0
  42. package/types/source/providers/inputs/Pict-Provider-Input-Diagram-CSS.d.ts.map +1 -0
  43. package/types/source/providers/inputs/Pict-Provider-Input-Diagram.d.ts +125 -0
  44. package/types/source/providers/inputs/Pict-Provider-Input-Diagram.d.ts.map +1 -0
  45. package/types/source/providers/inputs/Pict-Provider-Input-EntityBundleRequest.d.ts.map +1 -1
  46. package/types/source/providers/inputs/Pict-Provider-Input-RichText-CSS.d.ts +3 -0
  47. package/types/source/providers/inputs/Pict-Provider-Input-RichText-CSS.d.ts.map +1 -0
  48. package/types/source/providers/inputs/Pict-Provider-Input-RichText.d.ts +41 -0
  49. package/types/source/providers/inputs/Pict-Provider-Input-RichText.d.ts.map +1 -0
  50. package/types/source/providers/inputs/util/Themeify-SVG.d.ts +48 -0
  51. package/types/source/providers/inputs/util/Themeify-SVG.d.ts.map +1 -0
  52. package/types/source/providers/layouts/Pict-Layout-Tabular.d.ts +200 -0
  53. package/types/source/providers/layouts/Pict-Layout-Tabular.d.ts.map +1 -1
  54. package/types/source/services/ManifestFactory.d.ts +29 -0
  55. package/types/source/services/ManifestFactory.d.ts.map +1 -1
  56. package/types/source/templates/Pict-Template-TabularEditingControls.d.ts +26 -0
  57. package/types/source/templates/Pict-Template-TabularEditingControls.d.ts.map +1 -0
  58. package/types/source/templates/Pict-Template-TabularRowLabels.d.ts +28 -0
  59. package/types/source/templates/Pict-Template-TabularRowLabels.d.ts.map +1 -0
  60. package/types/source/views/Pict-View-Form-Metacontroller.d.ts +22 -0
  61. package/types/source/views/Pict-View-Form-Metacontroller.d.ts.map +1 -1
@@ -4342,7 +4342,7 @@ if(tmpIsRootRenderable&&pRenderable&&pRenderable.TransactionHash){this.pict.Tran
4342
4342
  * Lifecycle hook that triggers after data is marshaled into the view (async flow).
4343
4343
  *
4344
4344
  * @param {ErrorCallback} fCallback - The callback to call when the async operation is complete.
4345
- */onAfterMarshalToViewAsync(fCallback){this.onAfterMarshalToView();return fCallback();}/** @return {boolean} - True if the object is a PictView. */get isPictView(){return true;}}module.exports=PictView;},{"../package.json":38,"fable-serviceproviderbase":4}],40:[function(require,module,exports){module.exports={"name":"pict-section-form","version":"1.1.3","description":"Pict dynamic form sections","main":"source/Pict-Section-Form.js","directories":{"test":"test"},"repository":{"type":"git","url":"git+https://github.com/fable-retold/pict-section-form.git"},"bugs":{"url":"https://github.com/fable-retold/pict-section-form/issues"},"homepage":"https://github.com/fable-retold/pict-section-form#readme","scripts":{"start":"node source/Pict-Section-Form.js","tests":"npx quack test -g","coverage":"npx quack coverage","build":"npx quack build","test":"npx quack test","lint":"eslint source/**","types":"tsc -p .","docker-dev-build":"docker build ./ -f Dockerfile_LUXURYCode -t pict-section-form-image:local","docker-dev-run":"docker run -it -d --name pict-section-form-dev -p 48888:8080 -p 49999:9999 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/pict-section-form\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" pict-section-form-image:local","docker-dev-shell":"docker exec -it pict-section-form-dev /bin/bash"},"types":"types/source/Pict-Section-Form.d.ts","author":"steven velozo <steven@velozo.com>","license":"MIT","devDependencies":{"@eslint/js":"^9.39.2","browser-env":"^3.3.0","eslint":"^9.39.2","jquery":"^4.0.0","pict":"^1.0.372","pict-application":"^1.0.34","pict-docuserve":"^1.4.4","pict-service-commandlineutility":"^1.0.19","quackage":"^1.3.0","tui-grid":"^4.21.22","typescript":"^5.9.3"},"dependencies":{"chart.js":"^4.5.1","fable-serviceproviderbase":"^3.0.19","marked":"^4.3.0","pict-provider":"^1.0.13","pict-section-excalidraw":"^1.0.2","pict-section-markdowneditor":"^1.0.17","pict-section-tuigrid":"^1.0.31","pict-template":"^1.0.15","pict-view":"^1.0.68"},"mocha":{"diff":true,"extension":["js"],"package":"./package.json","reporter":"spec","slow":"75","timeout":"5000","ui":"tdd","watch-files":["source/**/*.js","test/**/*.js"],"watch-ignore":["lib/vendor"]}};},{}],41:[function(require,module,exports){// The container for all the Pict-Section-Form related code.
4345
+ */onAfterMarshalToViewAsync(fCallback){this.onAfterMarshalToView();return fCallback();}/** @return {boolean} - True if the object is a PictView. */get isPictView(){return true;}}module.exports=PictView;},{"../package.json":38,"fable-serviceproviderbase":4}],40:[function(require,module,exports){module.exports={"name":"pict-section-form","version":"1.2.0","description":"Pict dynamic form sections","main":"source/Pict-Section-Form.js","directories":{"test":"test"},"repository":{"type":"git","url":"git+https://github.com/fable-retold/pict-section-form.git"},"bugs":{"url":"https://github.com/fable-retold/pict-section-form/issues"},"homepage":"https://github.com/fable-retold/pict-section-form#readme","scripts":{"start":"node source/Pict-Section-Form.js","tests":"npx quack test -g","coverage":"npx quack coverage","build":"npx quack build","test":"npx quack test","lint":"eslint source/**","types":"tsc -p .","docker-dev-build":"docker build ./ -f Dockerfile_LUXURYCode -t pict-section-form-image:local","docker-dev-run":"docker run -it -d --name pict-section-form-dev -p 48888:8080 -p 49999:9999 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/pict-section-form\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" pict-section-form-image:local","docker-dev-shell":"docker exec -it pict-section-form-dev /bin/bash"},"types":"types/source/Pict-Section-Form.d.ts","author":"steven velozo <steven@velozo.com>","license":"MIT","devDependencies":{"@eslint/js":"^9.39.2","browser-env":"^3.3.0","eslint":"^9.39.2","jquery":"^4.0.0","pict":"^1.0.373","pict-application":"^1.0.34","pict-docuserve":"^1.4.19","pict-service-commandlineutility":"^1.0.19","quackage":"^1.3.0","tui-grid":"^4.21.22","typescript":"^5.9.3"},"dependencies":{"chart.js":"^4.5.1","fable-serviceproviderbase":"^3.0.19","marked":"^4.3.0","pict-provider":"^1.0.13","pict-section-excalidraw":"^1.0.3","pict-section-markdowneditor":"^1.0.17","pict-section-tuigrid":"^1.0.31","pict-template":"^1.0.15","pict-view":"^1.0.68"},"mocha":{"diff":true,"extension":["js"],"package":"./package.json","reporter":"spec","slow":"75","timeout":"5000","ui":"tdd","watch-files":["source/**/*.js","test/**/*.js"],"watch-ignore":["lib/vendor"]}};},{}],41:[function(require,module,exports){// The container for all the Pict-Section-Form related code.
4346
4346
  // The main dynamic view class
4347
4347
  module.exports=require('./views/Pict-View-DynamicForm.js');//module.exports.default_configuration = require('./views/Pict-View-DynamicForm-DefaultConfiguration.json');
4348
4348
  // The dynamic application dependencies
@@ -6625,7 +6625,7 @@ pView.setDataTabularByHash(pInput.PictForm.GroupIndex,pInput.Hash,pRowIndex,tmpV
6625
6625
  * @param {string} pHTMLSelector - The HTML selector of the input.
6626
6626
  * @param {string} pTransactionGUID - The transaction GUID, if any.
6627
6627
  * @returns {any} - The result of the super.onDataChange method.
6628
- */onDataChange(pView,pInput,pValue,pHTMLSelector,pTransactionGUID){let tmpTriggerGroupConfigurations=this.getTriggerGroupConfigurationArray(pInput);if(Array.isArray(tmpTriggerGroupConfigurations)&&this.pict.views.PictFormMetacontroller){for(let i=0;i<tmpTriggerGroupConfigurations.length;i++){const tmpGroupConfig=tmpTriggerGroupConfigurations[i];if(tmpGroupConfig.TriggerAllInputs){if(Array.isArray(tmpGroupConfig.PreSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PreSolvers,`AutofillTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} pre-trigger`);}pView.registerOnTransactionCompleteCallback(pTransactionGUID,()=>{if(Array.isArray(tmpGroupConfig.PostSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PostSolvers,`AutofillTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} post-trigger`);}});this.pict.views.PictFormMetacontroller.triggerGlobalInputEvent(`TriggerGroup:${tmpGroupConfig.TriggerGroupHash}:DataChange:${pInput.Hash||pInput.DataAddress}:${this.pict.getUUID()}`,pTransactionGUID);}}}return super.onDataChange(pView,pInput,pValue,pHTMLSelector,pTransactionGUID);}/**
6628
+ */onDataChange(pView,pInput,pValue,pHTMLSelector,pTransactionGUID){let tmpTriggerGroupConfigurations=this.getTriggerGroupConfigurationArray(pInput);if(Array.isArray(tmpTriggerGroupConfigurations)&&this.pict.views.PictFormMetacontroller){for(let i=0;i<tmpTriggerGroupConfigurations.length;i++){const tmpGroupConfig=tmpTriggerGroupConfigurations[i];if(tmpGroupConfig.TriggerAllInputs){if(Array.isArray(tmpGroupConfig.PreSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PreSolvers,`AutofillTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} pre-trigger`);}pView.registerOnTransactionCompleteCallback(pTransactionGUID,()=>{if(Array.isArray(tmpGroupConfig.PostSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PostSolvers,`AutofillTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} post-trigger`);const tmpMarshalScope=tmpGroupConfig.MarshalOnComplete;if(tmpMarshalScope===true){this.pict.views.PictFormMetacontroller.marshalToView();}else if(tmpMarshalScope){this.pict.views.PictFormMetacontroller.marshalSectionToView(tmpMarshalScope.Section);this.pict.views.PictFormMetacontroller.marshalInputToView(tmpMarshalScope.Input);}}});this.pict.views.PictFormMetacontroller.triggerGlobalInputEvent(`TriggerGroup:${tmpGroupConfig.TriggerGroupHash}:DataChange:${pInput.Hash||pInput.DataAddress}:${this.pict.getUUID()}`,pTransactionGUID);}}}return super.onDataChange(pView,pInput,pValue,pHTMLSelector,pTransactionGUID);}/**
6629
6629
  * Handles the change event for tabular data.
6630
6630
  *
6631
6631
  * @param {Object} pView - The view object.
@@ -6635,7 +6635,7 @@ pView.setDataTabularByHash(pInput.PictForm.GroupIndex,pInput.Hash,pRowIndex,tmpV
6635
6635
  * @param {number} pRowIndex - The index of the row.
6636
6636
  * @param {string} pTransactionGUID - The transaction GUID, if any.
6637
6637
  * @returns {any} - The result of the super method.
6638
- */onDataChangeTabular(pView,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID){let tmpTriggerGroupConfigurations=this.getTriggerGroupConfigurationArray(pInput);if(Array.isArray(tmpTriggerGroupConfigurations)&&this.pict.views.PictFormMetacontroller){for(let i=0;i<tmpTriggerGroupConfigurations.length;i++){const tmpGroupConfig=tmpTriggerGroupConfigurations[i];if(tmpGroupConfig.TriggerAllInputs){if(Array.isArray(tmpGroupConfig.PreSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PreSolvers,`AutofillTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} tabular pre-trigger`);}pView.registerOnTransactionCompleteCallback(pTransactionGUID,()=>{if(Array.isArray(tmpGroupConfig.PostSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PostSolvers,`AutofillTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} tabular post-trigger`);}});this.pict.views.PictFormMetacontroller.triggerGlobalInputEvent(`TriggerGroup:${tmpGroupConfig.TriggerGroupHash}:DataChange:${pInput.Hash||pInput.DataAddress}:${this.pict.getUUID()}`,pTransactionGUID);}}}return super.onDataChangeTabular(pView,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID);}/**
6638
+ */onDataChangeTabular(pView,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID){let tmpTriggerGroupConfigurations=this.getTriggerGroupConfigurationArray(pInput);if(Array.isArray(tmpTriggerGroupConfigurations)&&this.pict.views.PictFormMetacontroller){for(let i=0;i<tmpTriggerGroupConfigurations.length;i++){const tmpGroupConfig=tmpTriggerGroupConfigurations[i];if(tmpGroupConfig.TriggerAllInputs){if(Array.isArray(tmpGroupConfig.PreSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PreSolvers,`AutofillTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} tabular pre-trigger`);}pView.registerOnTransactionCompleteCallback(pTransactionGUID,()=>{if(Array.isArray(tmpGroupConfig.PostSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PostSolvers,`AutofillTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} tabular post-trigger`);const tmpMarshalScope=tmpGroupConfig.MarshalOnComplete;if(tmpMarshalScope===true){this.pict.views.PictFormMetacontroller.marshalToView();}else if(tmpMarshalScope){this.pict.views.PictFormMetacontroller.marshalSectionToView(tmpMarshalScope.Section);this.pict.views.PictFormMetacontroller.marshalInputToView(tmpMarshalScope.Input);}}});this.pict.views.PictFormMetacontroller.triggerGlobalInputEvent(`TriggerGroup:${tmpGroupConfig.TriggerGroupHash}:DataChange:${pInput.Hash||pInput.DataAddress}:${this.pict.getUUID()}`,pTransactionGUID);}}}return super.onDataChangeTabular(pView,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID);}/**
6639
6639
  * This input extension only responds to events
6640
6640
  *
6641
6641
  * @param {Object} pView - The view object.
@@ -6649,7 +6649,7 @@ pView.setDataTabularByHash(pInput.PictForm.GroupIndex,pInput.Hash,pRowIndex,tmpV
6649
6649
  if('TriggerAddress'in tmpAutoFillTriggerGroup){// Autofill based on the address list as it isn't a select option
6650
6650
  this.autoFillFromAddressList(pView,pInput,tmpAutoFillTriggerGroup,pHTMLSelector);}if(tmpAutoFillTriggerGroup.SelectOptionsRefresh){// Regenerate the picklist
6651
6651
  // Because the pick lists are view specific, we need to lookup the view the input is in
6652
- let tmpInputView=this.pict.views[pInput.PictForm.ViewHash];this.pict.providers.DynamicMetaLists.rebuildListByHash(pInput.PictForm.SelectOptionsPickList);this.pict.providers['Pict-Input-Select'].refreshSelectList(tmpInputView,tmpInputView.getGroup(pInput.PictForm.GroupIndex),tmpInputView.getRow(pInput.PictForm.GroupIndex,pInput.PictForm.Row),pInput,pValue,pHTMLSelector);tmpInputView.manualMarshalDataToViewByInput(pInput,tmpEventGUID);}if(Array.isArray(tmpAutoFillTriggerGroup.PostSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpAutoFillTriggerGroup.PostSolvers,`AutofillTriggerGroup hash ${tmpAutoFillTriggerGroup.TriggerGroupHash} post-autofill`);}}return super.onAfterEventCompletion(pView,pInput,pValue,pHTMLSelector,pEvent,pTransactionGUID);}/**
6652
+ let tmpInputView=this.pict.views[pInput.PictForm.ViewHash];this.pict.providers.DynamicMetaLists.rebuildListByHash(pInput.PictForm.SelectOptionsPickList);this.pict.providers['Pict-Input-Select'].refreshSelectList(tmpInputView,tmpInputView.getGroup(pInput.PictForm.GroupIndex),tmpInputView.getRow(pInput.PictForm.GroupIndex,pInput.PictForm.Row),pInput,pValue,pHTMLSelector);tmpInputView.manualMarshalDataToViewByInput(pInput,tmpEventGUID);}if(Array.isArray(tmpAutoFillTriggerGroup.PostSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpAutoFillTriggerGroup.PostSolvers,`AutofillTriggerGroup hash ${tmpAutoFillTriggerGroup.TriggerGroupHash} post-autofill`);const tmpMarshalScope=tmpAutoFillTriggerGroup.MarshalOnComplete;if(tmpMarshalScope===true){this.pict.views.PictFormMetacontroller.marshalToView();}else if(tmpMarshalScope){this.pict.views.PictFormMetacontroller.marshalSectionToView(tmpMarshalScope.Section);this.pict.views.PictFormMetacontroller.marshalInputToView(tmpMarshalScope.Input);}}}return super.onAfterEventCompletion(pView,pInput,pValue,pHTMLSelector,pEvent,pTransactionGUID);}/**
6653
6653
  * Handles events for the Pict-Provider-InputExtension.
6654
6654
  *
6655
6655
  * @param {Object} pView - The view object.
@@ -7046,15 +7046,63 @@ let tmpInputSelectValue;try{tmpInputSelectValue=this.pict.ContentAssignment.read
7046
7046
  * provider.commit(inputHash, fCallback)
7047
7047
  */const libPictSectionInputExtension=require('../Pict-Provider-InputExtension.js');const libPictSectionExcalidraw=require('pict-section-excalidraw');// Form templates live in pict-section-form's default-template set, so no
7048
7048
  // runtime template injection is needed here. CSS still needs registering.
7049
- const libCSS=require('./Pict-Provider-Input-Diagram-CSS.js');const libThemeify=require('./util/Themeify-SVG.js');const _DefaultProviderConfiguration={ProviderIdentifier:'Pict-Input-Diagram',AutoInitialize:true,AutoInitializeOrdinal:0,AutoSolveWithApp:false};class PictInputDiagram extends libPictSectionInputExtension{constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},JSON.parse(JSON.stringify(_DefaultProviderConfiguration)),pOptions);super(pFable,tmpOptions,pServiceHash);/** @type {import('pict')} */this.pict;/** @type {any} */this.log;// inputHash → { mode, slotID, lastValue (SVG string), viewInstance?, viewHash?, input }
7050
- this._instances={};// Register the scoped CSS.
7049
+ const libCSS=require('./Pict-Provider-Input-Diagram-CSS.js');const libThemeify=require('./util/Themeify-SVG.js');const _DefaultProviderConfiguration={ProviderIdentifier:'Pict-Input-Diagram',AutoInitialize:true,AutoInitializeOrdinal:0,AutoSolveWithApp:false};/**
7050
+ * @typedef {Object} Instance
7051
+ * @property {string} mode - 'edit' or 'view'
7052
+ * @property {string} slotID - The HTML ID of the content slot for this input
7053
+ * @property {string} lastValue - The last known value (SVG string)
7054
+ * @property {Object} viewInstance - The editor view instance (if in edit mode)
7055
+ * @property {string} viewHash - The hash of the editor view (if in edit mode)
7056
+ * @property {Object} input - The input definition object
7057
+ */class PictInputDiagram extends libPictSectionInputExtension{/**
7058
+ * Creates an instance of the PictInputExtensionProvider class.
7059
+ *
7060
+ * @param {import('pict')} pFable - The Pict instance.
7061
+ * @param {Record<string, any>} [pOptions] - The options for the provider.
7062
+ * @param {string} [pServiceHash] - The service hash for the provider.
7063
+ */constructor(pFable,pOptions,pServiceHash){let tmpOptions=Object.assign({},JSON.parse(JSON.stringify(_DefaultProviderConfiguration)),pOptions);super(pFable,tmpOptions,pServiceHash);/** @type {import('pict')} */this.pict;/** @type {any} */this.log;// inputHash → { mode, slotID, lastValue (SVG string), viewInstance?, viewHash?, input }
7064
+ /** @type {Record<String, Instance>} */this._instances={};// Register the scoped CSS.
7051
7065
  if(this.pict&&this.pict.CSSMap&&typeof this.pict.CSSMap.addCSS==='function'){this.pict.CSSMap.addCSS('Pict-Input-Diagram-CSS',libCSS,500);}}// ----------------------------------------------------------------------------
7052
7066
  // Helpers
7053
7067
  // ----------------------------------------------------------------------------
7054
- getContentDisplayHTMLID(pInputHTMLID){return`#DISPLAY-FOR-${pInputHTMLID}`;}getTabularContentDisplayInputID(pInputHTMLID,pRowIndex){return`#DISPLAY-FOR-TABULAR-${pInputHTMLID}-${pRowIndex}`;}_resolveValue(pInput,pValue){if(typeof pValue==='string'&&pValue.length>0)return pValue;if(pInput&&pInput.Content&&typeof pInput.Content==='string')return pInput.Content;if(pInput&&pInput.Default&&typeof pInput.Default==='string')return pInput.Default;return'';}_isLikelySvg(pValue){return typeof pValue==='string'&&/<svg[\s>]/i.test(pValue);}_assignSlotContent(pSlotID,pHTML){if(this.pict&&this.pict.ContentAssignment&&typeof this.pict.ContentAssignment.assignContent==='function'){this.pict.ContentAssignment.assignContent(pSlotID,pHTML);return true;}return false;}_writeHiddenInputValue(pInputHTMLID,pValue){let tmpEl=typeof document!=='undefined'?document.getElementById(pInputHTMLID):null;if(!tmpEl)return false;tmpEl.value=pValue==null?'':String(pValue);try{tmpEl.dispatchEvent(new Event('change',{bubbles:true}));}catch(pErr){/* jsdom may lack Event */}return true;}_resolveVendor(){if(typeof window==='undefined')return null;return window.PictSectionExcalidrawVendor||null;}/**
7068
+ /**
7069
+ * @param {string} pInputHTMLID - The RawHTMLID of the input.
7070
+ * @returns {string} The HTML ID selector for the content display slot corresponding to the input.
7071
+ */getContentDisplayHTMLID(pInputHTMLID){return`#DISPLAY-FOR-${pInputHTMLID}`;}/**
7072
+ * @param {string} pInputHTMLID - The RawHTMLID of the input.
7073
+ * @param {number} pRowIndex - The row index for tabular inputs.
7074
+ * @returns {string} The HTML ID selector for the content display slot corresponding to the tabular input.
7075
+ */getTabularContentDisplayInputID(pInputHTMLID,pRowIndex){return`#DISPLAY-FOR-TABULAR-${pInputHTMLID}-${pRowIndex}`;}/**
7076
+ * Resolve the value to use for display/editing, following this precedence:
7077
+ * 1. The provided pValue (if a non-empty string)
7078
+ * 2. The input's Content property (if a non-empty string)
7079
+ * 3. The input's Default property (if a non-empty string)
7080
+ * 4. An empty string if none of the above are valid
7081
+ *
7082
+ * @param {Object} pInput - The input definition object.
7083
+ * @param {any} pValue - The value provided for the input.
7084
+ * @returns {string} The resolved value to use for display/editing.
7085
+ */_resolveValue(pInput,pValue){if(typeof pValue==='string'&&pValue.length>0)return pValue;if(pInput&&pInput.Content&&typeof pInput.Content==='string')return pInput.Content;if(pInput&&pInput.Default&&typeof pInput.Default==='string')return pInput.Default;return'';}/**
7086
+ * @param {any} pValue - The value to check.
7087
+ * @return {boolean} True if the value is a string that appears to contain an <svg> element, false otherwise.
7088
+ */_isLikelySvg(pValue){return typeof pValue==='string'&&/<svg[\s>]/i.test(pValue);}/**
7089
+ * @param {string} pSlotID - The HTML ID of the content slot to assign.
7090
+ * @param {string} pHTML - The HTML string to assign to the slot.
7091
+ * @returns {boolean} True if the assignment was successful, false otherwise.
7092
+ */_assignSlotContent(pSlotID,pHTML){if(this.pict&&this.pict.ContentAssignment&&typeof this.pict.ContentAssignment.assignContent==='function'){this.pict.ContentAssignment.assignContent(pSlotID,pHTML);return true;}return false;}/**
7093
+ * @param {string} pInputHTMLID - The RawHTMLID of the input whose hidden value field should be updated.
7094
+ * @param {string} pValue - The SVG string to set as the value of the hidden input field.
7095
+ * @returns {boolean} True if the value was successfully written and a change event dispatched, false otherwise.
7096
+ */_writeHiddenInputValue(pInputHTMLID,pValue){let tmpEl=typeof document!=='undefined'?document.getElementById(pInputHTMLID):null;if(!tmpEl)return false;tmpEl.value=pValue==null?'':String(pValue);try{tmpEl.dispatchEvent(new Event('change',{bubbles:true}));}catch(pErr){/* jsdom may lack Event */}return true;}_resolveVendor(){if(typeof window==='undefined')return null;return window.PictSectionExcalidrawVendor||null;}/**
7055
7097
  * Wrap an SVG string in a thin <div> for the view-mode slot. If the value
7056
7098
  * is empty or not an SVG, show an "(empty)" placeholder.
7057
- */_buildViewHTML(pValue){if(this._isLikelySvg(pValue)){return`<div class="pict-section-form-diagram-view">${pValue}</div>`;}return`<div class="pict-section-form-diagram-view is-empty">(empty diagram)</div>`;}_setSlotModeClass(pInput,pMode){if(typeof document==='undefined')return;let tmpRawHTMLID=pInput.Macro.RawHTMLID;let tmpOuter=document.getElementById(tmpRawHTMLID)||document.querySelector(this.getContentDisplayHTMLID(tmpRawHTMLID));if(!tmpOuter||!tmpOuter.classList)return;tmpOuter.classList.remove('mode-edit','mode-view','pict-section-form-diagram-edit');if(pMode==='edit'){tmpOuter.classList.add('mode-edit','pict-section-form-diagram-edit');}else{tmpOuter.classList.add('mode-view');}}// ----------------------------------------------------------------------------
7099
+ *
7100
+ * @param {string} pValue - The SVG string to wrap for display.
7101
+ * @returns {string} The HTML string to assign to the view slot.
7102
+ */_buildViewHTML(pValue){if(this._isLikelySvg(pValue)){return`<div class="pict-section-form-diagram-view">${pValue}</div>`;}return`<div class="pict-section-form-diagram-view is-empty">(empty diagram)</div>`;}/**
7103
+ * @param {Object} pInput - The input definition object.
7104
+ * @param {string} pMode - The mode to set ('edit' or 'view').
7105
+ */_setSlotModeClass(pInput,pMode){if(typeof document==='undefined')return;let tmpRawHTMLID=pInput.Macro.RawHTMLID;let tmpOuter=document.getElementById(tmpRawHTMLID)||document.querySelector(this.getContentDisplayHTMLID(tmpRawHTMLID));if(!tmpOuter||!tmpOuter.classList)return;tmpOuter.classList.remove('mode-edit','mode-view','pict-section-form-diagram-edit');if(pMode==='edit'){tmpOuter.classList.add('mode-edit','pict-section-form-diagram-edit');}else{tmpOuter.classList.add('mode-view');}}// ----------------------------------------------------------------------------
7058
7106
  // View mode
7059
7107
  // ----------------------------------------------------------------------------
7060
7108
  _mountView(pView,pInput,pValue){let tmpRawHTMLID=pInput.Macro.RawHTMLID;let tmpSlotID=this.getContentDisplayHTMLID(tmpRawHTMLID);let tmpValue=this._resolveValue(pInput,pValue);this._assignSlotContent(tmpSlotID,this._buildViewHTML(tmpValue));this._setSlotModeClass(pInput,'view');let tmpInst=this._instances[pInput.Hash]||{};tmpInst.mode='view';tmpInst.slotID=tmpSlotID;tmpInst.lastValue=tmpValue;tmpInst.input=pInput;tmpInst.viewInstance=null;this._instances[pInput.Hash]=tmpInst;}// ----------------------------------------------------------------------------
@@ -7081,7 +7129,32 @@ getMode(pInputHash){let tmpInst=this._instances[pInputHash];return tmpInst?tmpIn
7081
7129
  let tmpThemeColors=tmpInst.themeColors!==false;let tmpProvider=this;let tmpRawHTMLID=tmpInst.input.Macro.RawHTMLID;try{let tmpExportPromise=tmpInst.viewInstance.exportSvg({exportEmbedScene:true,exportBackground:false});Promise.resolve(tmpExportPromise).then(pSvgEl=>{let tmpSvgStr=pSvgEl&&typeof pSvgEl.outerHTML==='string'?pSvgEl.outerHTML:null;if(tmpSvgStr){if(tmpThemeColors)tmpSvgStr=libThemeify.themeifySVG(tmpSvgStr);tmpProvider._writeHiddenInputValue(tmpRawHTMLID,tmpSvgStr);tmpInst.lastValue=tmpSvgStr;}if(typeof fCallback==='function')fCallback(null);},pErr=>{if(typeof fCallback==='function')fCallback(pErr);});}catch(pErr){if(typeof fCallback==='function')fCallback(pErr);}}// ----------------------------------------------------------------------------
7082
7130
  // Lifecycle hooks
7083
7131
  // ----------------------------------------------------------------------------
7084
- onInputInitialize(pView,pGroup,pRow,pInput,pValue,pHTMLSelector,pTransactionGUID){this._mountView(pView,pInput,pValue);return super.onInputInitialize(pView,pGroup,pRow,pInput,pValue,pHTMLSelector,pTransactionGUID);}onInputInitializeTabular(pView,pGroup,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID){let tmpErr=new Error('Diagram InputType is not supported inside Tabular rows in Phase 1.');if(this.log)this.log.warn('[Pict-Input-Diagram] tabular not supported',{inputHash:pInput&&pInput.Hash});throw tmpErr;}onDataMarshalToForm(pView,pGroup,pRow,pInput,pValue,pHTMLSelector,pTransactionGUID){let tmpInst=this._instances[pInput.Hash];if(!tmpInst){this._mountView(pView,pInput,pValue);}else if(tmpInst.mode==='view'){this._mountView(pView,pInput,pValue);}else{// Edit mode: hot-replace the editor's scene.
7132
+ onInputInitialize(pView,pGroup,pRow,pInput,pValue,pHTMLSelector,pTransactionGUID){this._mountView(pView,pInput,pValue);return super.onInputInitialize(pView,pGroup,pRow,pInput,pValue,pHTMLSelector,pTransactionGUID);}/**
7133
+ * A tabular input has been initialized (rendered into the DOM)
7134
+ *
7135
+ * Called when an input has this Provider hash in its 'Providers' array.
7136
+ *
7137
+ * @param {Object} pView - The view object.
7138
+ * @param {Object} pGroup - The group definition object.
7139
+ * @param {Object} pInput - The input object.
7140
+ * @param {any} pValue - The value of the input object
7141
+ * @param {string} pHTMLSelector - The HTML selector for the input object (it will return an array).
7142
+ * @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
7143
+ * @param {number} pRowIndex - The row index of the tabular data
7144
+ *
7145
+ * @return {boolean}
7146
+ */onInputInitializeTabular(pView,pGroup,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID){super.onInputInitializeTabular(pView,pGroup,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID);let tmpErr=new Error('Diagram InputType is not supported inside Tabular rows in Phase 1.');if(this.log)this.log.warn('[Pict-Input-Diagram] tabular not supported',{inputHash:pInput&&pInput.Hash});throw tmpErr;}/**
7147
+ * Fires when data is marshaled to the form for this input.
7148
+ *
7149
+ * @param {Object} pView - The view object.
7150
+ * @param {Object} pGroup - The group definition object.
7151
+ * @param {number} pRow - The Row index.
7152
+ * @param {Object} pInput - The input object.
7153
+ * @param {any} pValue - The value to marshal.
7154
+ * @param {string} pHTMLSelector - The HTML selector.
7155
+ * @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
7156
+ * @returns {boolean} - Returns true if the data was successfully marshaled to the form.
7157
+ */onDataMarshalToForm(pView,pGroup,pRow,pInput,pValue,pHTMLSelector,pTransactionGUID){let tmpInst=this._instances[pInput.Hash];if(!tmpInst){this._mountView(pView,pInput,pValue);}else if(tmpInst.mode==='view'){this._mountView(pView,pInput,pValue);}else{// Edit mode: hot-replace the editor's scene.
7085
7158
  tmpInst.lastValue=typeof pValue==='string'?pValue:tmpInst.lastValue;this._extractSceneFromSvg(tmpInst.lastValue,(pErr,pScene)=>{if(pErr||!pScene)return;if(tmpInst.viewInstance&&typeof tmpInst.viewInstance.setScene==='function'){try{tmpInst.viewInstance.setScene(pScene);}catch(pSetErr){if(this.log)this.log.warn('[Pict-Input-Diagram] setScene threw after marshal',{error:pSetErr.message});}}});}return super.onDataMarshalToForm(pView,pGroup,pRow,pInput,pValue,pHTMLSelector,pTransactionGUID);}onDataMarshalToFormTabular(pView,pGroup,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID){return super.onDataMarshalToFormTabular(pView,pGroup,pInput,pValue,pHTMLSelector,pRowIndex,pTransactionGUID);}}module.exports=PictInputDiagram;module.exports.default_configuration=_DefaultProviderConfiguration;},{"../Pict-Provider-InputExtension.js":53,"./Pict-Provider-Input-Diagram-CSS.js":63,"./util/Themeify-SVG.js":78,"pict-section-excalidraw":20}],65:[function(require,module,exports){const libPictSectionInputExtension=require('../Pict-Provider-InputExtension.js');/**
7086
7159
  * CustomInputHandler class for Entity Bundle Requests.
7087
7160
  *
@@ -7148,7 +7221,7 @@ if(pCustomRequestInformation.Destination){this.pict.manifest.setValueByHash(this
7148
7221
  if(typeof pInput!=='object'||!('PictForm'in pInput)||!('EntitiesBundle'in pInput.PictForm)||!Array.isArray(pInput.PictForm.EntitiesBundle)){this.log.warn(`Input at ${pHTMLSelector} is set as an EntityBundleRequest input but no array of entity requests was found`);return null;}const tmpLoadGUID=`BundleLoad-${this.pict.getUUID()}`;if(pTransactionGUID){pView.registerEventTransactionAsyncOperation(pTransactionGUID,tmpLoadGUID);}let tmpInput=pInput;let tmpValue=pValue;let tmpAnticipate=this.fable.newAnticipate();if(tmpInput.PictForm.EntitiesBundle.length>0&&tmpInput.PictForm.EntitiesBundle[0].PictMode){tmpAnticipate.anticipate(fNext=>{this.pict.EntityProvider.gatherDataFromServer(tmpInput.PictForm.EntitiesBundle,pError=>{// in case of an empty array, or all tasks being synchronous, wait for the next tick so we don't get event ordering problems
7149
7222
  setTimeout(()=>fNext(pError),0);});});}else{const tmpStateStack=[];/** @type {Record<string, any>} */let tmpState={Value:tmpValue,Input:tmpInput,View:pView};for(let i=0;i<tmpInput.PictForm.EntitiesBundle.length;i++){let tmpEntityBundleEntry=tmpInput.PictForm.EntitiesBundle[i];tmpAnticipate.anticipate(fNext=>{try{switch(tmpEntityBundleEntry.Type){case'Custom':return this.gatherCustomDataSet(fNext,tmpEntityBundleEntry,pView,tmpInput,tmpValue);case'SetStateAddress':tmpStateStack.push(tmpState);tmpState=this.fable.manifest.getValueByHash(this.fable,tmpEntityBundleEntry.StateAddress);if(typeof tmpState==='undefined'){tmpState={};this.fable.manifest.setValueByHash(this.fable,tmpEntityBundleEntry.StateAddress,tmpState);}break;case'PopState':if(tmpStateStack.length>0){tmpState=tmpStateStack.pop();}else{this.log.warn(`EntityBundleRequest encountered a PopState without a matching SetStateAddress.`);}break;case'MapJoin':this.pict.EntityProvider.mapJoin(tmpEntityBundleEntry,this.pict.EntityProvider.prepareState(tmpState,tmpEntityBundleEntry));break;case'ProjectDataset':this.pict.EntityProvider.projectDataset(tmpEntityBundleEntry,this.pict.EntityProvider.prepareState(tmpState,tmpEntityBundleEntry));break;// This is the default case, for a meadow entity set or single entity
7150
7223
  case'MeadowEntity':default:return this.gatherEntitySet(fNext,tmpEntityBundleEntry,pView,tmpInput,tmpValue);}}catch(pError){this.log.error(`EntityBundleRequest error gathering entity set: ${pError}`,pError);}return fNext();});}// in case of an empty array, or all tasks being synchronous, wait for the next tick so we don't get event ordering problems
7151
- tmpAnticipate.anticipate(fNext=>setTimeout(fNext,0));}tmpAnticipate.anticipate(fNext=>{if(tmpInput.PictForm.EntityBundleTriggerGroup&&this.pict.views.PictFormMetacontroller){const tmpGroupConfig=this.getTriggerGroupConfigurationArray(tmpInput,tmpInput.PictForm.EntityBundleTriggerGroup)[0];if(tmpGroupConfig&&Array.isArray(tmpGroupConfig.PreSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PreSolvers,`EntityBundleTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} pre-trigger`);}let tmpTransactionGUID;if(tmpGroupConfig&&Array.isArray(tmpGroupConfig.PostSolvers)){tmpTransactionGUID=pTransactionGUID||this.pict.getUUID();if(tmpTransactionGUID!==pTransactionGUID){this.pict.TransactionTracking.registerTransaction(tmpTransactionGUID);}pView.registerOnTransactionCompleteCallback(tmpTransactionGUID,()=>{if(Array.isArray(tmpGroupConfig.PostSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PostSolvers,`EntityBundleTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} tabular post-trigger`);}});}// Trigger the autofill global event
7224
+ tmpAnticipate.anticipate(fNext=>setTimeout(fNext,0));}tmpAnticipate.anticipate(fNext=>{if(tmpInput.PictForm.EntityBundleTriggerGroup&&this.pict.views.PictFormMetacontroller){const tmpGroupConfig=this.getTriggerGroupConfigurationArray(tmpInput,tmpInput.PictForm.EntityBundleTriggerGroup)[0];if(tmpGroupConfig&&Array.isArray(tmpGroupConfig.PreSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PreSolvers,`EntityBundleTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} pre-trigger`);}let tmpTransactionGUID;if(tmpGroupConfig&&Array.isArray(tmpGroupConfig.PostSolvers)){tmpTransactionGUID=pTransactionGUID||this.pict.getUUID();if(tmpTransactionGUID!==pTransactionGUID){this.pict.TransactionTracking.registerTransaction(tmpTransactionGUID);}pView.registerOnTransactionCompleteCallback(tmpTransactionGUID,()=>{if(Array.isArray(tmpGroupConfig.PostSolvers)){this.pict.providers.DynamicSolver.executeSolvers(pView,tmpGroupConfig.PostSolvers,`EntityBundleTriggerGroup hash ${tmpGroupConfig.TriggerGroupHash} post-trigger`);const tmpMarshalScope=tmpGroupConfig.MarshalOnComplete;if(tmpMarshalScope===true){this.pict.views.PictFormMetacontroller.marshalToView();}else if(tmpMarshalScope){this.pict.views.PictFormMetacontroller.marshalSectionToView(tmpMarshalScope.Section);this.pict.views.PictFormMetacontroller.marshalInputToView(tmpMarshalScope.Input);}}});}// Trigger the autofill global event
7152
7225
  this.pict.views.PictFormMetacontroller.triggerGlobalInputEvent(`TriggerGroup:${tmpInput.PictForm.EntityBundleTriggerGroup}:BundleLoad:${pInput.Hash||pInput.DataAddress}:${this.pict.getUUID()}`,pTransactionGUID);if(tmpTransactionGUID&&tmpTransactionGUID!==pTransactionGUID){pView.finalizeTransaction(tmpTransactionGUID);}}if(tmpInput.PictForm.EntityBundleTriggerMetacontrollerSolve&&this.pict.views.PictFormMetacontroller){// Trigger the solve global event
7153
7226
  this.pict.views.PictFormMetacontroller.solve();}if(tmpInput.PictForm.EntityBundleTriggerMetacontrollerRender&&this.pict.views.PictFormMetacontroller){// Trigger the render
7154
7227
  this.pict.views.PictFormMetacontroller.render();}fNext();});return new Promise((pResolve,pReject)=>{// Now fire the "autofilldata" event for the groups.
@@ -7608,7 +7681,13 @@ tmpProvider._writeHiddenInputValue(tmpRawHTMLID,pContent);let tmpLatest=tmpProvi
7608
7681
  // its default base64 inline; we *don't* want that when text-only,
7609
7682
  // so we eat the file by calling back with an error.
7610
7683
  fImgCallback('Image uploads are disabled for this field.');return true;}let tmpUploaderName=tmpRichTextOpts.ImageUploader;if(tmpUploaderName&&tmpProvider.pict&&tmpProvider.pict.PictApplication&&typeof tmpProvider.pict.PictApplication[tmpUploaderName]==='function'){try{return tmpProvider.pict.PictApplication[tmpUploaderName](pFile,pInput,fImgCallback);}catch(pErr){if(tmpProvider.log){tmpProvider.log.warn('[Pict-Input-RichText] ImageUploader threw',{error:pErr.message,inputHash:pInput.Hash});}fImgCallback(pErr.message);return true;}}// No uploader configured + AllowImages=true → default base64 inline.
7611
- return false;}}this.pict.addView(tmpViewHash,tmpEditorOpts,RichTextFormEditor);let tmpEditorView=this.pict.views[tmpViewHash];if(!tmpEditorView){let tmpErr=new Error('Failed to instantiate RichText editor view '+tmpViewHash);if(this.log)this.log.error('[Pict-Input-RichText] addView returned nothing',{viewHash:tmpViewHash});if(typeof fCallback==='function')fCallback(tmpErr);return;}// Surface mode on the outer slot for the optional toggle-chip pseudo.
7684
+ return false;}/**
7685
+ * Push CodeMirror extensions before the editor mounts. We enable
7686
+ * line wrapping by default so long markdown paragraphs flow to
7687
+ * the next visual line instead of horizontally scrolling — the
7688
+ * almost-always-correct default for prose. Opt out via
7689
+ * `PictForm.RichText.WordWrap: false` in the descriptor.
7690
+ */customConfigureExtensions(pExtensions,pSegmentIndex){let tmpWantWrap=tmpRichTextOpts.WordWrap!==false;if(tmpWantWrap){let tmpCM=this._codeMirrorModules;if(tmpCM&&tmpCM.EditorView&&tmpCM.EditorView.lineWrapping){pExtensions.push(tmpCM.EditorView.lineWrapping);}}return pExtensions;}}this.pict.addView(tmpViewHash,tmpEditorOpts,RichTextFormEditor);let tmpEditorView=this.pict.views[tmpViewHash];if(!tmpEditorView){let tmpErr=new Error('Failed to instantiate RichText editor view '+tmpViewHash);if(this.log)this.log.error('[Pict-Input-RichText] addView returned nothing',{viewHash:tmpViewHash});if(typeof fCallback==='function')fCallback(tmpErr);return;}// Surface mode on the outer slot for the optional toggle-chip pseudo.
7612
7691
  if(typeof document!=='undefined'){let tmpOuter=document.querySelector(tmpSlotID);if(tmpOuter&&tmpOuter.classList){tmpOuter.classList.remove('mode-view');tmpOuter.classList.add('mode-edit','pict-section-form-richtext-edit');}}tmpInst.mode='edit';tmpInst.slotID=tmpSlotID;tmpInst.lastValue=tmpContent;tmpInst.viewInstance=tmpEditorView;tmpInst.viewHash=tmpViewHash;this._instances[pInput.Hash]=tmpInst;// Render the editor. The view's render() resolves TargetElementAddress
7613
7692
  // against ContentAssignment and paints into the slot.
7614
7693
  try{let tmpResult=tmpEditorView.render();// Some renders are async (Promise-returning) and some are sync.
@@ -8293,7 +8372,32 @@ this.pict.TemplateProvider.addTemplate(pGroup.SectionTabularRowVirtualTemplateHa
8293
8372
  // pict's built-in icon registry (so they theme + scale like every other glyph).
8294
8373
  if(this.pict&&this.pict.providers&&this.pict.providers.Icon&&typeof this.pict.providers.Icon.registerSet==='function'){this.pict.providers.Icon.registerSet({Outline:{Sort:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 20V5"/><path d="M5.5 8.5L9 5L12.5 8.5"/><path d="M15 4V19"/><path d="M11.5 15.5L15 19L18.5 15.5"/></svg>',SortAscending:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20V5"/><path d="M6 11L12 5L18 11"/></svg>',SortDescending:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4V19"/><path d="M6 13L12 19L18 13"/></svg>'}});}// CSS for the clickable sort control. Inactive glyphs are faded; the
8295
8374
  // actively-sorted column's glyph is full opacity.
8296
- if(this.pict&&this.pict.CSSMap&&typeof this.pict.CSSMap.addCSS==='function'){this.pict.CSSMap.addCSS('Pict-Layout-Tabular-Sort-CSS','.pict-tabular-sort-control { cursor: pointer; display: inline-flex; vertical-align: middle; margin-left: 0.3em; opacity: 0.4; }'+' .pict-tabular-sort-control:hover { opacity: 0.75; }'+' .pict-tabular-sort-control.pict-tabular-sort-asc, .pict-tabular-sort-control.pict-tabular-sort-desc { opacity: 1; }',500);}}/**
8375
+ if(this.pict&&this.pict.CSSMap&&typeof this.pict.CSSMap.addCSS==='function'){this.pict.CSSMap.addCSS('Pict-Layout-Tabular-Sort-CSS','.pict-tabular-sort-control { cursor: pointer; display: inline-flex; vertical-align: middle; margin-left: 0.3em; opacity: 0.4; }'+' .pict-tabular-sort-control:hover { opacity: 0.75; }'+' .pict-tabular-sort-control.pict-tabular-sort-asc, .pict-tabular-sort-control.pict-tabular-sort-desc { opacity: 1; }',500);// CSS for the optional ColumnChooser feature: a right-aligned trigger button
8376
+ // above the table and a fixed-position popover of checkbox rows. Fixed +
8377
+ // JS-positioned (like the recordset's column chooser) so no ancestor
8378
+ // overflow can clip it; the transparent backdrop catches outside clicks.
8379
+ this.pict.CSSMap.addCSS('Pict-Layout-Tabular-ColumnChooser-CSS',/*css*/`
8380
+ .pict-tabular-colchooser-bar { display: flex; justify-content: flex-end; margin: 0 0 0.35rem; }
8381
+ .pict-tabular-colchooser-trigger { display: inline-flex; align-items: center; gap: 0.35rem; cursor: pointer; font: inherit; font-size: 0.88rem;
8382
+ padding: 0.3rem 0.65rem; border: 1px solid var(--theme-color-border-default, #d7dce3); border-radius: 7px;
8383
+ background: var(--theme-color-background-panel, #fff); color: var(--theme-color-text-secondary, #45505f); }
8384
+ .pict-tabular-colchooser-trigger:hover { background: var(--theme-color-background-tertiary, #eceef2); color: var(--theme-color-text-primary, #1f2733); }
8385
+ .pict-tabular-colchooser-count { font-size: 0.8em; color: var(--theme-color-text-muted, #6b7686); }
8386
+ .pict-tabular-colchooser-pop { position: fixed; z-index: 30; min-width: 220px; max-width: 320px; display: none; }
8387
+ .pict-tabular-colchooser-pop.open { display: block; }
8388
+ .pict-tabular-colchooser-backdrop { position: fixed; inset: 0; z-index: 0; }
8389
+ .pict-tabular-colchooser-panel { position: relative; z-index: 1; display: flex; flex-direction: column; max-height: min(70vh, 420px);
8390
+ background: var(--theme-color-background-panel, #fff); border: 1px solid var(--theme-color-border-default, #d7dce3);
8391
+ border-radius: 10px; box-shadow: 0 10px 28px rgba(17, 24, 39, 0.14); overflow: hidden; }
8392
+ .pict-tabular-colchooser-list { flex: 1 1 auto; overflow-y: auto; padding: 0.25rem 0; }
8393
+ .pict-tabular-colchooser-row { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; cursor: pointer;
8394
+ padding: 0.32rem 0.8rem; color: var(--theme-color-text-primary, #1f2733); }
8395
+ .pict-tabular-colchooser-row:hover { background: var(--theme-color-background-tertiary, #eceef2); }
8396
+ .pict-tabular-colchooser-row input { flex: 0 0 auto; margin: 0; cursor: pointer; }
8397
+ .pict-tabular-colchooser-footer { flex: 0 0 auto; display: flex; justify-content: flex-end; padding: 0.45rem 0.7rem; border-top: 1px solid var(--theme-color-border-light, #e8ebf0); }
8398
+ .pict-tabular-colchooser-reset { font: inherit; font-size: 0.84rem; cursor: pointer; border: none; background: transparent; color: var(--theme-color-text-muted, #6b7686); padding: 0.15rem 0.3rem; border-radius: 5px; }
8399
+ .pict-tabular-colchooser-reset:hover { color: var(--theme-color-text-primary, #1f2733); background: var(--theme-color-background-tertiary, #eceef2); }
8400
+ `,500);}}/**
8297
8401
  * Builds one prime-header `<th>` with an injected, clickable sort control
8298
8402
  * `<span>` carrying the sort SVG glyph. Used when the group has
8299
8403
  * `ColumnSorting: true`. The glyph reflects the current sort state of the
@@ -8351,14 +8455,23 @@ tmpView.rebuildCustomTemplate();tmpView.render();if(this.pict.views.PictFormMeta
8351
8455
  * @param {Object} pView
8352
8456
  * @param {Object} pGroup
8353
8457
  * @returns {Array<Array<{Label: string, ColumnSpan: number, CSSClass: string}>>}
8354
- */_buildExpandedHeadersConfig(pView,pGroup){let tmpExpanded=[];// Render user-provided Headers config first (at the top of the table).
8355
- if(Array.isArray(pGroup.Headers)){for(let r=0;r<pGroup.Headers.length;r++){let tmpRowConfig=pGroup.Headers[r];if(!Array.isArray(tmpRowConfig)){continue;}let tmpNormalizedRow=[];for(let c=0;c<tmpRowConfig.length;c++){let tmpCell=tmpRowConfig[c];if(!tmpCell||typeof tmpCell!=='object'){continue;}tmpNormalizedRow.push({Label:typeof tmpCell.Label==='string'?tmpCell.Label:'',ColumnSpan:Number(tmpCell.ColumnSpan)>0?Number(tmpCell.ColumnSpan):1,CSSClass:typeof tmpCell.CSSClass==='string'?tmpCell.CSSClass:''});}tmpExpanded.push(tmpNormalizedRow);}}// Then synthesize a clustered super-header row from any DynamicColumns generator
8458
+ */_buildExpandedHeadersConfig(pView,pGroup){let tmpExpanded=[];// When the column chooser has hidden columns, each user-provided header
8459
+ // cell's ColumnSpan shrinks by the hidden columns it covers (cells reduced
8460
+ // to zero drop out), so the stacked headers stay aligned with the columns
8461
+ // that actually render. The spans partition the configured (non-
8462
+ // TabularHidden) data columns in manifest order — same contract the
8463
+ // span-total warning in generateGroupLayoutTemplate enforces.
8464
+ let tmpChooserHiddenSet=this._getTabularColumnChooserHiddenSet(pView,pGroup);let tmpConfiguredColumnVisibility=[];if(tmpChooserHiddenSet&&pGroup.supportingManifest&&Array.isArray(pGroup.supportingManifest.elementAddresses)){for(let k=0;k<pGroup.supportingManifest.elementAddresses.length;k++){let tmpDescriptor=pGroup.supportingManifest.elementDescriptors[pGroup.supportingManifest.elementAddresses[k]];if(!tmpDescriptor||tmpDescriptor.PictForm&&tmpDescriptor.PictForm.TabularHidden){continue;}tmpConfiguredColumnVisibility.push(!tmpChooserHiddenSet.has(String(pGroup.supportingManifest.elementAddresses[k])));}}// Render user-provided Headers config first (at the top of the table).
8465
+ if(Array.isArray(pGroup.Headers)){for(let r=0;r<pGroup.Headers.length;r++){let tmpRowConfig=pGroup.Headers[r];if(!Array.isArray(tmpRowConfig)){continue;}let tmpNormalizedRow=[];// Walks the configured-column visibility list as spans consume columns.
8466
+ let tmpColumnCursor=0;for(let c=0;c<tmpRowConfig.length;c++){let tmpCell=tmpRowConfig[c];if(!tmpCell||typeof tmpCell!=='object'){continue;}let tmpColumnSpan=Number(tmpCell.ColumnSpan)>0?Number(tmpCell.ColumnSpan):1;if(tmpChooserHiddenSet){let tmpVisibleSpan=0;for(let s=0;s<tmpColumnSpan;s++){// Columns past the configured set keep their span (the
8467
+ // misalignment warning will already have fired for that).
8468
+ if(tmpColumnCursor>=tmpConfiguredColumnVisibility.length||tmpConfiguredColumnVisibility[tmpColumnCursor]){tmpVisibleSpan++;}tmpColumnCursor++;}if(tmpVisibleSpan===0){continue;}tmpColumnSpan=tmpVisibleSpan;}tmpNormalizedRow.push({Label:typeof tmpCell.Label==='string'?tmpCell.Label:'',ColumnSpan:tmpColumnSpan,CSSClass:typeof tmpCell.CSSClass==='string'?tmpCell.CSSClass:''});}tmpExpanded.push(tmpNormalizedRow);}}// Then synthesize a clustered super-header row from any DynamicColumns generator
8356
8469
  // that declared a HeaderGroupTemplate. This row sits JUST ABOVE the default
8357
8470
  // column-name row so the visual cluster lines up with the columns it groups.
8358
8471
  // Walk the supportingManifest in order and group adjacent descriptors that
8359
8472
  // share both _DynamicColumnGeneratorIndex and _DynamicColumnHeaderGroup; static
8360
8473
  // descriptors get blank "spacer" cells.
8361
- let tmpHasHeaderGroups=false;if(Array.isArray(pGroup.DynamicColumns)){for(let g=0;g<pGroup.DynamicColumns.length;g++){if(pGroup.DynamicColumns[g]&&pGroup.DynamicColumns[g].HeaderGroupTemplate){tmpHasHeaderGroups=true;break;}}}if(tmpHasHeaderGroups&&pGroup.supportingManifest){let tmpSynthRow=[];let tmpCurrentRun=null;let tmpAddresses=pGroup.supportingManifest.elementAddresses;for(let k=0;k<tmpAddresses.length;k++){let tmpDescriptor=pGroup.supportingManifest.elementDescriptors[tmpAddresses[k]];if(!tmpDescriptor){continue;}if(tmpDescriptor.PictForm&&tmpDescriptor.PictForm.TabularHidden){continue;}let tmpIsDynamic=typeof tmpDescriptor._DynamicColumnGeneratorIndex==='number';let tmpGroupLabel=tmpIsDynamic&&typeof tmpDescriptor._DynamicColumnHeaderGroup==='string'?tmpDescriptor._DynamicColumnHeaderGroup:'';let tmpRunKey=tmpIsDynamic?`D${tmpDescriptor._DynamicColumnGeneratorIndex}|${tmpGroupLabel}`:`S|${k}`;if(tmpCurrentRun&&tmpCurrentRun.RunKey===tmpRunKey){tmpCurrentRun.Cell.ColumnSpan+=1;}else{tmpCurrentRun={RunKey:tmpRunKey,Cell:{Label:tmpIsDynamic?tmpGroupLabel:'',ColumnSpan:1,CSSClass:tmpIsDynamic?'pict-tabular-dynamic-header-group':'pict-tabular-static-header-spacer'}};tmpSynthRow.push(tmpCurrentRun.Cell);}}tmpExpanded.push(tmpSynthRow);}return tmpExpanded;}/**
8474
+ let tmpHasHeaderGroups=false;if(Array.isArray(pGroup.DynamicColumns)){for(let g=0;g<pGroup.DynamicColumns.length;g++){if(pGroup.DynamicColumns[g]&&pGroup.DynamicColumns[g].HeaderGroupTemplate){tmpHasHeaderGroups=true;break;}}}if(tmpHasHeaderGroups&&pGroup.supportingManifest){let tmpSynthRow=[];let tmpCurrentRun=null;let tmpAddresses=pGroup.supportingManifest.elementAddresses;for(let k=0;k<tmpAddresses.length;k++){let tmpDescriptor=pGroup.supportingManifest.elementDescriptors[tmpAddresses[k]];if(!tmpDescriptor){continue;}if(tmpDescriptor.PictForm&&tmpDescriptor.PictForm.TabularHidden){continue;}if(tmpChooserHiddenSet&&tmpChooserHiddenSet.has(String(tmpAddresses[k]))){continue;}let tmpIsDynamic=typeof tmpDescriptor._DynamicColumnGeneratorIndex==='number';let tmpGroupLabel=tmpIsDynamic&&typeof tmpDescriptor._DynamicColumnHeaderGroup==='string'?tmpDescriptor._DynamicColumnHeaderGroup:'';let tmpRunKey=tmpIsDynamic?`D${tmpDescriptor._DynamicColumnGeneratorIndex}|${tmpGroupLabel}`:`S|${k}`;if(tmpCurrentRun&&tmpCurrentRun.RunKey===tmpRunKey){tmpCurrentRun.Cell.ColumnSpan+=1;}else{tmpCurrentRun={RunKey:tmpRunKey,Cell:{Label:tmpIsDynamic?tmpGroupLabel:'',ColumnSpan:1,CSSClass:tmpIsDynamic?'pict-tabular-dynamic-header-group':'pict-tabular-static-header-spacer'}};tmpSynthRow.push(tmpCurrentRun.Cell);}}tmpExpanded.push(tmpSynthRow);}return tmpExpanded;}/**
8362
8475
  * Compute per-row label metadata for a tabular group with RowLabels config.
8363
8476
  *
8364
8477
  * Walks the current RecordSetAddress array. For each row, resolves the
@@ -8465,6 +8578,163 @@ if(Array.isArray(tmpGroup.RowLabelMetadata)&&!isNaN(tmpRowKeyNum)&&tmpRowKeyNum>
8465
8578
  * @param {Object} pView
8466
8579
  * @param {Object} pGroup
8467
8580
  */_reapplyTabularSelectionHighlights(pView,pGroup){let tmpBehaviors=this.pict.providers.DynamicFormSolverBehaviors;if(!tmpBehaviors||!pView.sectionDefinition){return;}let tmpSectionHash=pView.sectionDefinition.Hash;if(pGroup._RowSelectionConfig&&pGroup._RowSelectionConfig.HighlightClass){let tmpRows=this._getTabularSelectionArray(pView,pGroup._RowSelectionConfig);for(let i=0;i<tmpRows.length;i++){tmpBehaviors.highlightTabularRow(tmpSectionHash,pGroup.Hash,i,tmpRows[i]?1:0,pGroup._RowSelectionConfig.HighlightClass);}}if(pGroup._ColumnSelectionConfig&&pGroup._ColumnSelectionConfig.HighlightClass){let tmpColumns=this._getTabularSelectionArray(pView,pGroup._ColumnSelectionConfig);for(let i=0;i<tmpColumns.length;i++){tmpBehaviors.highlightTabularColumn(tmpSectionHash,pGroup.Hash,i,tmpColumns[i]?1:0,pGroup._ColumnSelectionConfig.HighlightClass);}}}/**
8581
+ * Normalize a Group.ColumnChooser config value.
8582
+ *
8583
+ * Accepts `true` (use all defaults) or an object
8584
+ * `{ Enabled, DataAddress, ButtonLabel, DefaultHiddenColumns }`. Returns null
8585
+ * when the chooser is not enabled (the feature is strictly opt-in).
8586
+ *
8587
+ * - `DataAddress` — address (relative to the form's marshal destination) where
8588
+ * the array of hidden column hashes is stored, so it persists with the form data.
8589
+ * - `ButtonLabel` — text for the trigger button above the table.
8590
+ * - `DefaultHiddenColumns` — column hashes hidden until the user changes them
8591
+ * (merged with any descriptor-level `PictForm.TabularDefaultHidden` flags).
8592
+ *
8593
+ * @param {boolean|Object} pConfigValue
8594
+ * @param {string} pDefaultDataAddress
8595
+ * @returns {{DataAddress: string, ButtonLabel: string, DefaultHiddenColumns: Array<string>}|null}
8596
+ */_normalizeColumnChooserConfig(pConfigValue,pDefaultDataAddress){if(pConfigValue!==true&&(typeof pConfigValue!=='object'||pConfigValue===null)){return null;}let tmpConfig=typeof pConfigValue==='object'?pConfigValue:{};if(tmpConfig.Enabled===false){return null;}return{DataAddress:typeof tmpConfig.DataAddress==='string'&&tmpConfig.DataAddress.length>0?tmpConfig.DataAddress:pDefaultDataAddress,ButtonLabel:typeof tmpConfig.ButtonLabel==='string'&&tmpConfig.ButtonLabel.length>0?tmpConfig.ButtonLabel:'Columns',DefaultHiddenColumns:Array.isArray(tmpConfig.DefaultHiddenColumns)?tmpConfig.DefaultHiddenColumns.map(pHash=>String(pHash)):[]};}/**
8597
+ * Lazily normalize (and cache on the group) the ColumnChooser config. The
8598
+ * template bake re-normalizes each pass; this accessor covers code paths
8599
+ * (marshal hooks, inline handlers) that may run against a group whose
8600
+ * template hasn't been baked yet.
8601
+ *
8602
+ * @param {Object} pGroup
8603
+ * @returns {{DataAddress: string, ButtonLabel: string, DefaultHiddenColumns: Array<string>}|null}
8604
+ */_ensureTabularColumnChooserConfig(pGroup){if(pGroup._ColumnChooserConfig===undefined){pGroup._ColumnChooserConfig=this._normalizeColumnChooserConfig(pGroup.ColumnChooser,`${pGroup.Hash}_HiddenColumns`);}return pGroup._ColumnChooserConfig;}/**
8605
+ * The absolute address (within the form's marshal destination) of the
8606
+ * chooser's hidden-column-hash array.
8607
+ *
8608
+ * @param {Object} pView
8609
+ * @param {{DataAddress: string}} pChooserConfig
8610
+ * @returns {string}
8611
+ */_getTabularHiddenColumnsAddress(pView,pChooserConfig){return`${pView.getMarshalDestinationAddress()}.${pChooserConfig.DataAddress}`;}/**
8612
+ * The set of column hashes hidden BY DEFAULT for a group: the chooser
8613
+ * config's DefaultHiddenColumns plus every descriptor flagged
8614
+ * `PictForm.TabularDefaultHidden`. These apply only until the user changes
8615
+ * column visibility (which writes an explicit array into the form data).
8616
+ *
8617
+ * @param {Object} pGroup
8618
+ * @returns {Array<string>}
8619
+ */_getTabularColumnChooserDefaultHidden(pGroup){let tmpConfig=pGroup._ColumnChooserConfig;if(!tmpConfig){return[];}let tmpDefaultHidden={};for(let i=0;i<tmpConfig.DefaultHiddenColumns.length;i++){tmpDefaultHidden[tmpConfig.DefaultHiddenColumns[i]]=true;}if(pGroup.supportingManifest&&Array.isArray(pGroup.supportingManifest.elementAddresses)){for(let k=0;k<pGroup.supportingManifest.elementAddresses.length;k++){let tmpHash=pGroup.supportingManifest.elementAddresses[k];let tmpDescriptor=pGroup.supportingManifest.elementDescriptors[tmpHash];if(tmpDescriptor&&tmpDescriptor.PictForm&&!tmpDescriptor.PictForm.TabularHidden&&tmpDescriptor.PictForm.TabularDefaultHidden===true){tmpDefaultHidden[String(tmpHash)]=true;}}}return Object.keys(tmpDefaultHidden);}/**
8620
+ * The EFFECTIVE set of chooser-hidden column hashes for a group: the array
8621
+ * stored in the form data when the user has made choices, otherwise the
8622
+ * configured defaults. Returns null when the chooser is not enabled, so
8623
+ * callers can use a single falsy check to keep the legacy code path intact.
8624
+ *
8625
+ * @param {Object} pView
8626
+ * @param {Object} pGroup
8627
+ * @returns {Set<string>|null}
8628
+ */_getTabularColumnChooserHiddenSet(pView,pGroup){let tmpConfig=this._ensureTabularColumnChooserConfig(pGroup);if(!tmpConfig){return null;}let tmpStored=this.pict.resolveStateFromAddress(this._getTabularHiddenColumnsAddress(pView,tmpConfig));let tmpHiddenList=Array.isArray(tmpStored)?tmpStored:this._getTabularColumnChooserDefaultHidden(pGroup);let tmpHiddenSet=new Set();for(let i=0;i<tmpHiddenList.length;i++){tmpHiddenSet.add(String(tmpHiddenList[i]));}return tmpHiddenSet;}/**
8629
+ * A canonical string for the group's effective hidden-column set, used to
8630
+ * detect (on marshal) that loaded form data carries different column
8631
+ * visibility than the table template was baked with.
8632
+ *
8633
+ * @param {Object} pView
8634
+ * @param {Object} pGroup
8635
+ * @returns {string}
8636
+ */_getTabularColumnChooserStateKey(pView,pGroup){let tmpHiddenSet=this._getTabularColumnChooserHiddenSet(pView,pGroup);if(!tmpHiddenSet){return'';}return Array.from(tmpHiddenSet).sort().join('|');}/**
8637
+ * The columns the chooser can manage, in manifest order. Statically hidden
8638
+ * descriptors (`PictForm.TabularHidden`) are never choosable and never
8639
+ * listed. Each entry carries the descriptor's manifest index so inline
8640
+ * handlers can address it without string-escaping concerns.
8641
+ *
8642
+ * @param {Object} pView
8643
+ * @param {Object} pGroup
8644
+ * @returns {Array<{Key: string, Name: string, ColumnIndex: number, Visible: boolean}>}
8645
+ */_getTabularChoosableColumns(pView,pGroup){let tmpColumns=[];if(!pGroup.supportingManifest||!Array.isArray(pGroup.supportingManifest.elementAddresses)){return tmpColumns;}let tmpHiddenSet=this._getTabularColumnChooserHiddenSet(pView,pGroup);for(let k=0;k<pGroup.supportingManifest.elementAddresses.length;k++){let tmpHash=String(pGroup.supportingManifest.elementAddresses[k]);let tmpDescriptor=pGroup.supportingManifest.elementDescriptors[tmpHash];if(!tmpDescriptor||tmpDescriptor.PictForm&&tmpDescriptor.PictForm.TabularHidden){continue;}tmpColumns.push({Key:tmpHash,Name:tmpDescriptor.Name!=null&&String(tmpDescriptor.Name).length>0?String(tmpDescriptor.Name):tmpHash,ColumnIndex:k,Visible:!(tmpHiddenSet&&tmpHiddenSet.has(tmpHash))});}return tmpColumns;}/**
8646
+ * DOM element id for one of the chooser's baked elements (TRIGGER / POPOVER),
8647
+ * namespaced by form and group so multiple tabular groups can each carry
8648
+ * their own chooser.
8649
+ *
8650
+ * @param {Object} pView
8651
+ * @param {Object} pGroup
8652
+ * @param {string} pElement
8653
+ * @returns {string}
8654
+ */_getTabularColumnChooserElementId(pView,pGroup,pElement){return`PICTFORM-COLCHOOSER-${pElement}-${pView.formID}-${pGroup.Hash}`;}/**
8655
+ * Builds the chooser bar baked above the table: a right-aligned trigger
8656
+ * button (with a "n hidden" hint when columns are hidden) plus the empty
8657
+ * popover container the open action renders into.
8658
+ *
8659
+ * @param {Object} pView
8660
+ * @param {Object} pGroup
8661
+ * @returns {string}
8662
+ */_buildTabularColumnChooserBarHTML(pView,pGroup){let tmpColumns=this._getTabularChoosableColumns(pView,pGroup);let tmpHiddenCount=tmpColumns.filter(pColumn=>!pColumn.Visible).length;let tmpGlyph=typeof this.pict.icon==='function'?this.pict.icon('Settings'):'';let tmpCountHint=tmpHiddenCount>0?` <span class="pict-tabular-colchooser-count">(${tmpHiddenCount} hidden)</span>`:'';return`<div class="pict-tabular-colchooser-bar">`+`<button type="button" class="pict-tabular-colchooser-trigger" id="${this._getTabularColumnChooserElementId(pView,pGroup,'TRIGGER')}" `+`title="Choose which columns to show" `+`onclick="_Pict.providers['Pict-Layout-Tabular'].toggleTabularColumnChooser('${pView.Hash}', ${pGroup.GroupIndex})">`+`${tmpGlyph} ${this._escapeHTML(pGroup._ColumnChooserConfig.ButtonLabel)}${tmpCountHint}</button>`+`<div class="pict-tabular-colchooser-pop" id="${this._getTabularColumnChooserElementId(pView,pGroup,'POPOVER')}"></div>`+`</div>`;}/**
8663
+ * Renders the chooser popover's content (backdrop + panel of checkbox rows +
8664
+ * reset footer) into its baked container. Runs on open and after each toggle
8665
+ * (the table re-render replaces the popover element, so its content must be
8666
+ * repainted to keep the menu open across toggles).
8667
+ *
8668
+ * @param {Object} pView
8669
+ * @param {Object} pGroup
8670
+ */_renderTabularColumnChooserPopover(pView,pGroup){let tmpColumns=this._getTabularChoosableColumns(pView,pGroup);let tmpRowsHTML='';for(let i=0;i<tmpColumns.length;i++){let tmpColumn=tmpColumns[i];tmpRowsHTML+=`<label class="pict-tabular-colchooser-row">`+`<input type="checkbox"${tmpColumn.Visible?' checked="checked"':''} `+`onchange="_Pict.providers['Pict-Layout-Tabular'].toggleTabularColumnVisibility('${pView.Hash}', ${pGroup.GroupIndex}, ${tmpColumn.ColumnIndex}, this.checked)">`+`<span>${this._escapeHTML(tmpColumn.Name)}</span>`+`</label>`;}let tmpPopoverHTML=`<div class="pict-tabular-colchooser-backdrop" onclick="_Pict.providers['Pict-Layout-Tabular'].closeTabularColumnChooser('${pView.Hash}', ${pGroup.GroupIndex})"></div>`+`<div class="pict-tabular-colchooser-panel">`+`<div class="pict-tabular-colchooser-list">${tmpRowsHTML}</div>`+`<div class="pict-tabular-colchooser-footer">`+`<button type="button" class="pict-tabular-colchooser-reset" onclick="_Pict.providers['Pict-Layout-Tabular'].resetTabularColumnVisibility('${pView.Hash}', ${pGroup.GroupIndex})">Reset to defaults</button>`+`</div>`+`</div>`;this.pict.ContentAssignment.assignContent(`#${this._getTabularColumnChooserElementId(pView,pGroup,'POPOVER')}`,tmpPopoverHTML);}/**
8671
+ * Reflect the chooser popover's open/closed state on its container element,
8672
+ * positioning it against the trigger when opening.
8673
+ *
8674
+ * @param {Object} pView
8675
+ * @param {Object} pGroup
8676
+ * @param {boolean} pOpen
8677
+ */_paintTabularColumnChooserOpenState(pView,pGroup,pOpen){let tmpPopoverElements=this.pict.ContentAssignment.getElement(`#${this._getTabularColumnChooserElementId(pView,pGroup,'POPOVER')}`);if(!tmpPopoverElements.length){return;}tmpPopoverElements[0].classList.toggle('open',!!pOpen);if(pOpen){this._positionTabularColumnChooserPopover(pView,pGroup,tmpPopoverElements[0]);}}/**
8678
+ * Position the (fixed) chooser popover against its trigger button, flipping
8679
+ * above when the room below is genuinely cramped — same approach as the
8680
+ * recordset's column chooser, so no ancestor overflow can clip it.
8681
+ *
8682
+ * @param {Object} pView
8683
+ * @param {Object} pGroup
8684
+ * @param {HTMLElement} pPopover
8685
+ */_positionTabularColumnChooserPopover(pView,pGroup,pPopover){let tmpTriggerElements=this.pict.ContentAssignment.getElement(`#${this._getTabularColumnChooserElementId(pView,pGroup,'TRIGGER')}`);if(!tmpTriggerElements.length||typeof window==='undefined'){return;}let tmpPanel=pPopover.querySelector('.pict-tabular-colchooser-panel');let tmpRect=tmpTriggerElements[0].getBoundingClientRect();let tmpGap=6;let tmpMargin=8;let tmpViewportHeight=window.innerHeight;let tmpViewportWidth=window.innerWidth;let tmpWidth=pPopover.offsetWidth||240;// Right-align the popover to the (right-aligned) trigger, clamped into the viewport.
8686
+ pPopover.style.left=`${Math.round(Math.max(tmpMargin,Math.min(tmpRect.right-tmpWidth,tmpViewportWidth-tmpWidth-tmpMargin)))}px`;pPopover.style.right='auto';let tmpSpaceBelow=tmpViewportHeight-tmpRect.bottom-tmpGap-tmpMargin;let tmpSpaceAbove=tmpRect.top-tmpGap-tmpMargin;if(tmpSpaceBelow>=220||tmpSpaceBelow>=tmpSpaceAbove){pPopover.style.top=`${Math.round(tmpRect.bottom+tmpGap)}px`;pPopover.style.bottom='auto';if(tmpPanel){tmpPanel.style.maxHeight=`${Math.max(160,Math.min(tmpSpaceBelow,420))}px`;}}else{pPopover.style.top='auto';pPopover.style.bottom=`${Math.round(tmpViewportHeight-tmpRect.top+tmpGap)}px`;if(tmpPanel){tmpPanel.style.maxHeight=`${Math.max(160,Math.min(tmpSpaceAbove,420))}px`;}}}/**
8687
+ * Rebuild + re-render a tabular view and re-marshal the form data into it.
8688
+ * Same tail as sortTabularColumn: the rebuild re-bakes the table template
8689
+ * (column set, headers, chooser bar), the render repaints, the marshal
8690
+ * pushes current values back into the freshly built inputs.
8691
+ *
8692
+ * @param {Object} pView
8693
+ */_rebuildTabularGroupView(pView){pView.rebuildCustomTemplate();pView.render();if(this.pict.views.PictFormMetacontroller){this.pict.views.PictFormMetacontroller.marshalFormSections();}else{pView.marshalToView();}}/**
8694
+ * Inline-handler entry point: opens/closes a group's column chooser popover
8695
+ * (the trigger button's handler). Open/closed is derived from the popover's
8696
+ * DOM class, not an instance flag — a re-render replaces the popover element
8697
+ * (visually closed), so a flag would go stale and demand a double-click.
8698
+ *
8699
+ * @param {string} pViewHash
8700
+ * @param {number|string} pGroupIndex
8701
+ * @returns {boolean}
8702
+ */toggleTabularColumnChooser(pViewHash,pGroupIndex){let tmpView=this.pict.views[pViewHash];if(!tmpView||!tmpView.sectionDefinition||!Array.isArray(tmpView.sectionDefinition.Groups)){return false;}let tmpGroup=tmpView.sectionDefinition.Groups[Number(pGroupIndex)];if(!tmpGroup||!this._ensureTabularColumnChooserConfig(tmpGroup)){return false;}let tmpPopoverElements=this.pict.ContentAssignment.getElement(`#${this._getTabularColumnChooserElementId(tmpView,tmpGroup,'POPOVER')}`);if(tmpPopoverElements.length&&tmpPopoverElements[0].classList.contains('open')){this._paintTabularColumnChooserOpenState(tmpView,tmpGroup,false);return true;}this._renderTabularColumnChooserPopover(tmpView,tmpGroup);this._paintTabularColumnChooserOpenState(tmpView,tmpGroup,true);return true;}/**
8703
+ * Inline-handler entry point: closes a group's column chooser popover (the
8704
+ * backdrop's handler).
8705
+ *
8706
+ * @param {string} pViewHash
8707
+ * @param {number|string} pGroupIndex
8708
+ * @returns {boolean}
8709
+ */closeTabularColumnChooser(pViewHash,pGroupIndex){let tmpView=this.pict.views[pViewHash];if(!tmpView||!tmpView.sectionDefinition||!Array.isArray(tmpView.sectionDefinition.Groups)){return false;}let tmpGroup=tmpView.sectionDefinition.Groups[Number(pGroupIndex)];if(!tmpGroup){return false;}this._paintTabularColumnChooserOpenState(tmpView,tmpGroup,false);return true;}/**
8710
+ * Inline-handler entry point: shows/hides one column (a chooser checkbox's
8711
+ * handler). Writes the updated hidden-hash array into the form data (so it
8712
+ * persists with a save), rebuilds the table template without the column,
8713
+ * re-renders, re-marshals, then re-opens the popover the re-render closed.
8714
+ *
8715
+ * Hiding never touches the underlying record data — the column's values
8716
+ * stay in the record set and reappear when the column is shown again.
8717
+ *
8718
+ * Refuses to hide the last visible column (the checkbox snaps back).
8719
+ *
8720
+ * @param {string} pViewHash
8721
+ * @param {number|string} pGroupIndex
8722
+ * @param {number|string} pColumnIndex - The column's manifest index (stable within a bake).
8723
+ * @param {boolean} pVisible - true to show the column, false to hide it.
8724
+ * @returns {boolean}
8725
+ */toggleTabularColumnVisibility(pViewHash,pGroupIndex,pColumnIndex,pVisible){let tmpView=this.pict.views[pViewHash];if(!tmpView||!tmpView.sectionDefinition||!Array.isArray(tmpView.sectionDefinition.Groups)){return false;}let tmpGroup=tmpView.sectionDefinition.Groups[Number(pGroupIndex)];if(!tmpGroup){return false;}let tmpConfig=this._ensureTabularColumnChooserConfig(tmpGroup);if(!tmpConfig){return false;}let tmpColumns=this._getTabularChoosableColumns(tmpView,tmpGroup);let tmpColumn=tmpColumns.find(pCandidate=>pCandidate.ColumnIndex===Number(pColumnIndex));if(!tmpColumn){return false;}let tmpHiddenSet=this._getTabularColumnChooserHiddenSet(tmpView,tmpGroup);if(pVisible){tmpHiddenSet.delete(tmpColumn.Key);}else{let tmpVisibleCount=tmpColumns.filter(pCandidate=>pCandidate.Visible).length;if(tmpVisibleCount<=1&&tmpColumn.Visible){this.log.warn(`PICT Form Tabular column chooser on group [${tmpGroup.Hash}] refused to hide the last visible column [${tmpColumn.Key}].`);// Repaint the popover so the refused checkbox snaps back to checked.
8726
+ this._renderTabularColumnChooserPopover(tmpView,tmpGroup);this._paintTabularColumnChooserOpenState(tmpView,tmpGroup,true);return false;}tmpHiddenSet.add(tmpColumn.Key);}this.pict.setStateValueAtAddress(this._getTabularHiddenColumnsAddress(tmpView,tmpConfig),null,Array.from(tmpHiddenSet));this._rebuildTabularGroupView(tmpView);// The re-render replaced the popover element (closed); keep the menu open
8727
+ // so the user can toggle several columns in one visit.
8728
+ this._renderTabularColumnChooserPopover(tmpView,tmpGroup);this._paintTabularColumnChooserOpenState(tmpView,tmpGroup,true);return true;}/**
8729
+ * Inline-handler entry point: resets a group's column visibility to its
8730
+ * configured defaults (the reset footer button's handler). Writes the
8731
+ * default hidden set into the form data explicitly — the user interacted,
8732
+ * so the state should serialize deterministically with a save.
8733
+ *
8734
+ * @param {string} pViewHash
8735
+ * @param {number|string} pGroupIndex
8736
+ * @returns {boolean}
8737
+ */resetTabularColumnVisibility(pViewHash,pGroupIndex){let tmpView=this.pict.views[pViewHash];if(!tmpView||!tmpView.sectionDefinition||!Array.isArray(tmpView.sectionDefinition.Groups)){return false;}let tmpGroup=tmpView.sectionDefinition.Groups[Number(pGroupIndex)];if(!tmpGroup){return false;}let tmpConfig=this._ensureTabularColumnChooserConfig(tmpGroup);if(!tmpConfig){return false;}this.pict.setStateValueAtAddress(this._getTabularHiddenColumnsAddress(tmpView,tmpConfig),null,this._getTabularColumnChooserDefaultHidden(tmpGroup));this._rebuildTabularGroupView(tmpView);this._renderTabularColumnChooserPopover(tmpView,tmpGroup);this._paintTabularColumnChooserOpenState(tmpView,tmpGroup,true);return true;}/**
8468
8738
  * Generate a group layout template for a single-record dynamically generated group view.
8469
8739
  *
8470
8740
  * This is the standard name / field entry form that you're used to filling out for addresses
@@ -8477,7 +8747,12 @@ if(Array.isArray(tmpGroup.RowLabelMetadata)&&!isNaN(tmpRowKeyNum)&&tmpRowKeyNum>
8477
8747
  // In this case we are going to load the descriptors from the supportingManifests
8478
8748
  if(!pGroup.supportingManifest){this.log.error(`PICT Form [${pView.UUID}]::[${pView.Hash}] error generating tabular metatemplate: missing group manifest ${pGroup.RecordManifest} from supportingManifests.`);return'';}let tmpMetatemplateGenerator=this.pict.providers.MetatemplateGenerator;let tmpTemplate='';let tmpTemplateSetRecordRowTemplate='';// Read new optional configuration.
8479
8749
  let tmpEditingControlsPosition=typeof pGroup.EditingControlsPosition==='string'?pGroup.EditingControlsPosition:'right';let tmpRowLabels=Array.isArray(pGroup.RowLabels)?pGroup.RowLabels:[];let tmpSuppressDefaultColumnHeaderRow=pGroup.SuppressDefaultColumnHeaderRow===true;// ColumnSorting (off by default): injects a clickable sort-glyph span into every prime header cell.
8480
- let tmpColumnSortingEnabled=pGroup.ColumnSorting===true;if(tmpColumnSortingEnabled&&!pGroup._SortState){pGroup._SortState={ColumnIndex:-1,Direction:'none'};}let tmpExpandedHeaders=this._buildExpandedHeadersConfig(pView,pGroup);// Stash the structures referenced by the templates below.
8750
+ let tmpColumnSortingEnabled=pGroup.ColumnSorting===true;if(tmpColumnSortingEnabled&&!pGroup._SortState){pGroup._SortState={ColumnIndex:-1,Direction:'none'};}// ColumnChooser (off by default): a menu of checkboxes above the table that
8751
+ // shows/hides columns, with the hidden set stored in the form data. Normalized
8752
+ // BEFORE the headers expand so chooser-hidden columns adjust header spans.
8753
+ pGroup._ColumnChooserConfig=this._normalizeColumnChooserConfig(pGroup.ColumnChooser,`${pGroup.Hash}_HiddenColumns`);let tmpChooserHiddenSet=this._getTabularColumnChooserHiddenSet(pView,pGroup);// Remember what visibility this bake used, so a marshal carrying different
8754
+ // (e.g. freshly loaded) state knows to trigger a rebuild.
8755
+ pGroup._ColumnChooserBakedStateKey=tmpChooserHiddenSet?Array.from(tmpChooserHiddenSet).sort().join('|'):'';let tmpExpandedHeaders=this._buildExpandedHeadersConfig(pView,pGroup);// Stash the structures referenced by the templates below.
8481
8756
  pGroup.ExpandedHeaders=tmpExpandedHeaders;pGroup.RowLabelHeaderCells=tmpRowLabels.map(pLabel=>({Name:pLabel.Name||'',CSSClass:typeof pLabel.CSSClass==='string'?pLabel.CSSClass:''}));// Selectable rows / columns. When enabled, a checkbox column / header row is
8482
8757
  // rendered and the boolean selection state is stored in the form data so it
8483
8758
  // persists with a save. Solvers can read that state; selection optionally
@@ -8487,19 +8762,24 @@ let tmpLeadingColumnCount=(tmpRowSelectionEnabled?1:0)+tmpRowLabels.length+(tmpE
8487
8762
  // extra-postfix slots by registering empty view-specific overrides. This keeps the
8488
8763
  // existing template machinery intact for backwards compatibility -- consumers that
8489
8764
  // don't opt in to the new behavior see identical output.
8490
- if(tmpEditingControlsPosition==='left'||tmpEditingControlsPosition==='hidden'){this.pict.TemplateProvider.addTemplate(pView.getViewSpecificTemplateHash('-TabularTemplate-Row-ExtraPostfix'),'');this.pict.TemplateProvider.addTemplate(pView.getViewSpecificTemplateHash('-TabularTemplate-RowHeader-ExtraPostfix'),'');}tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-Group-Prefix`,`getGroup("${pGroup.GroupIndex}")`);// Header section
8765
+ if(tmpEditingControlsPosition==='left'||tmpEditingControlsPosition==='hidden'){this.pict.TemplateProvider.addTemplate(pView.getViewSpecificTemplateHash('-TabularTemplate-Row-ExtraPostfix'),'');this.pict.TemplateProvider.addTemplate(pView.getViewSpecificTemplateHash('-TabularTemplate-RowHeader-ExtraPostfix'),'');}// The chooser bar sits ABOVE the group's table container (a div can't live
8766
+ // inside <table> without the browser foster-parenting it out anyway).
8767
+ if(pGroup._ColumnChooserConfig){tmpTemplate+=this._buildTabularColumnChooserBarHTML(pView,pGroup);}tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-Group-Prefix`,`getGroup("${pGroup.GroupIndex}")`);// Header section
8491
8768
  tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-RowHeader-Prefix`,`getGroup("${pGroup.GroupIndex}")`);// Emit additional header rows (above the default column-name row).
8492
8769
  // Each row gets its own <tr>; leading non-data columns get blank <th>s
8493
8770
  // prepended (and a trailing one for right-side editing controls) for alignment.
8494
8771
  for(let r=0;r<tmpExpandedHeaders.length;r++){let tmpHeaderRow=tmpExpandedHeaders[r];tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-ExtraHeaderRow-Prefix`,`getGroup("${pGroup.GroupIndex}")`);for(let l=0;l<tmpLeadingColumnCount;l++){tmpTemplate+=`<th class="pict-row-label-spacer"></th>`;}for(let c=0;c<tmpHeaderRow.length;c++){let tmpCellAddress=`getGroup("${pGroup.GroupIndex}").ExpandedHeaders.${r}.${c}`;tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-ExtraHeaderCell`,tmpCellAddress);}if(tmpEditingRight){tmpTemplate+=`<th class="pict-row-label-spacer"></th>`;}tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-ExtraHeaderRow-Postfix`,`getGroup("${pGroup.GroupIndex}")`);}// Column-selection header row -- a <tr> of checkboxes, one per visible data
8495
8772
  // column, plus leading/trailing spacers to line up with the data columns.
8496
- if(tmpColumnSelectionEnabled){let tmpColumnSelectionState=this._getTabularSelectionArray(pView,pGroup._ColumnSelectionConfig);let tmpColumnSelectRow=`<tr class="pict-tabular-column-select-row">`;for(let l=0;l<tmpLeadingColumnCount;l++){tmpColumnSelectRow+=`<th class="pict-row-label-spacer"></th>`;}for(let k=0;k<pGroup.supportingManifest.elementAddresses.length;k++){let tmpColumnDescriptor=pGroup.supportingManifest.elementDescriptors[pGroup.supportingManifest.elementAddresses[k]];if(!tmpColumnDescriptor||tmpColumnDescriptor.PictForm&&tmpColumnDescriptor.PictForm.TabularHidden){continue;}let tmpColumnCheckedAttr=tmpColumnSelectionState[k]?' checked="checked"':'';tmpColumnSelectRow+=`<th class="pict-tabular-column-select">`+`<input type="checkbox"${tmpColumnCheckedAttr} onchange="_Pict.providers['Pict-Layout-Tabular'].toggleTabularColumnSelection('${pView.Hash}', ${pGroup.GroupIndex}, ${k}, this.checked)">`+`</th>`;}if(tmpEditingRight){tmpColumnSelectRow+=`<th class="pict-row-label-spacer"></th>`;}tmpColumnSelectRow+=`</tr>`;tmpTemplate+=tmpColumnSelectRow;}// Existing ExtraPrefix slot (preserved for host overrides)
8773
+ if(tmpColumnSelectionEnabled){let tmpColumnSelectionState=this._getTabularSelectionArray(pView,pGroup._ColumnSelectionConfig);let tmpColumnSelectRow=`<tr class="pict-tabular-column-select-row">`;for(let l=0;l<tmpLeadingColumnCount;l++){tmpColumnSelectRow+=`<th class="pict-row-label-spacer"></th>`;}for(let k=0;k<pGroup.supportingManifest.elementAddresses.length;k++){let tmpColumnDescriptor=pGroup.supportingManifest.elementDescriptors[pGroup.supportingManifest.elementAddresses[k]];if(!tmpColumnDescriptor||tmpColumnDescriptor.PictForm&&tmpColumnDescriptor.PictForm.TabularHidden){continue;}if(tmpChooserHiddenSet&&tmpChooserHiddenSet.has(String(pGroup.supportingManifest.elementAddresses[k]))){continue;}let tmpColumnCheckedAttr=tmpColumnSelectionState[k]?' checked="checked"':'';tmpColumnSelectRow+=`<th class="pict-tabular-column-select">`+`<input type="checkbox"${tmpColumnCheckedAttr} onchange="_Pict.providers['Pict-Layout-Tabular'].toggleTabularColumnSelection('${pView.Hash}', ${pGroup.GroupIndex}, ${k}, this.checked)">`+`</th>`;}if(tmpEditingRight){tmpColumnSelectRow+=`<th class="pict-row-label-spacer"></th>`;}tmpColumnSelectRow+=`</tr>`;tmpTemplate+=tmpColumnSelectRow;}// Existing ExtraPrefix slot (preserved for host overrides)
8497
8774
  tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-RowHeader-ExtraPrefix`,`getGroup("${pGroup.GroupIndex}")`);// Row-selection header cell.
8498
8775
  if(tmpRowSelectionEnabled){tmpTemplate+=`<th class="pict-row-label-header pict-tabular-row-select-header">${this._escapeHTML(pGroup._RowSelectionConfig.HeaderLabel)}</th>`;}// Row label header cells in the default header row.
8499
8776
  for(let l=0;l<tmpRowLabels.length;l++){let tmpLabelHeaderAddress=`getGroup("${pGroup.GroupIndex}").RowLabelHeaderCells.${l}`;tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-RowLabel-HeaderCell`,tmpLabelHeaderAddress);}// Header cell matching the left-side editing controls column, if configured.
8500
8777
  if(tmpEditingLeft){tmpTemplate+=`<th class="pict-row-label-header pict-tabular-editing-controls-header"></th>`;}// Per-descriptor header cells + the per-descriptor row body template.
8501
8778
  let tmpDataColumnCount=0;for(let k=0;k<pGroup.supportingManifest.elementAddresses.length;k++){let tmpSupportingManifestHash=pGroup.supportingManifest.elementAddresses[k];let tmpInput=pGroup.supportingManifest.elementDescriptors[tmpSupportingManifestHash];// Update the InputIndex to match the current render config
8502
- if(!('PictForm'in tmpInput)){tmpInput.PictForm={};}if(tmpInput.PictForm.TabularHidden){continue;}tmpInput.PictForm.InputIndex=k;tmpInput.PictForm.GroupIndex=pGroup.GroupIndex;tmpInput.PictForm.RowIndex=0;if(!tmpSuppressDefaultColumnHeaderRow){if(tmpColumnSortingEnabled){tmpTemplate+=this._buildSortableHeaderCell(pView,pGroup,tmpInput,k);}else{tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-HeaderCell`,`getTabularRecordInput("${pGroup.GroupIndex}","${k}")`);}}tmpTemplateSetRecordRowTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-Cell-Prefix`,`getTabularRecordInput("${pGroup.GroupIndex}","${k}")`);let tmpInputType='PictForm'in tmpInput&&tmpInput.PictForm.InputType?tmpInput.PictForm.InputType:'Default';tmpTemplateSetRecordRowTemplate+=tmpMetatemplateGenerator.getTabularInputMetatemplateTemplateReference(pView,tmpInput.DataType,tmpInputType,`getTabularRecordInput("${pGroup.GroupIndex}","${k}")`,pGroup.GroupIndex,k);tmpTemplateSetRecordRowTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-Cell-Postfix`,`getTabularRecordInput("${pGroup.GroupIndex}","${k}")`);tmpDataColumnCount++;}tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-RowHeader-ExtraPostfix`,`getGroup("${pGroup.GroupIndex}")`);tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-RowHeader-Postfix`,`getGroup("${pGroup.GroupIndex}")`);// Warn on header alignment mismatches (data-column count must equal each extra-header
8779
+ if(!('PictForm'in tmpInput)){tmpInput.PictForm={};}if(tmpInput.PictForm.TabularHidden){continue;}// Chooser-hidden columns are skipped at bake time (neither header nor row
8780
+ // cells render) but their record data is never touched — showing the
8781
+ // column again restores it intact, same invariant as DynamicColumns.
8782
+ if(tmpChooserHiddenSet&&tmpChooserHiddenSet.has(String(tmpSupportingManifestHash))){continue;}tmpInput.PictForm.InputIndex=k;tmpInput.PictForm.GroupIndex=pGroup.GroupIndex;tmpInput.PictForm.RowIndex=0;if(!tmpSuppressDefaultColumnHeaderRow){if(tmpColumnSortingEnabled){tmpTemplate+=this._buildSortableHeaderCell(pView,pGroup,tmpInput,k);}else{tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-HeaderCell`,`getTabularRecordInput("${pGroup.GroupIndex}","${k}")`);}}tmpTemplateSetRecordRowTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-Cell-Prefix`,`getTabularRecordInput("${pGroup.GroupIndex}","${k}")`);let tmpInputType='PictForm'in tmpInput&&tmpInput.PictForm.InputType?tmpInput.PictForm.InputType:'Default';tmpTemplateSetRecordRowTemplate+=tmpMetatemplateGenerator.getTabularInputMetatemplateTemplateReference(pView,tmpInput.DataType,tmpInputType,`getTabularRecordInput("${pGroup.GroupIndex}","${k}")`,pGroup.GroupIndex,k);tmpTemplateSetRecordRowTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-Cell-Postfix`,`getTabularRecordInput("${pGroup.GroupIndex}","${k}")`);tmpDataColumnCount++;}tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-RowHeader-ExtraPostfix`,`getGroup("${pGroup.GroupIndex}")`);tmpTemplate+=tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView,`-TabularTemplate-RowHeader-Postfix`,`getGroup("${pGroup.GroupIndex}")`);// Warn on header alignment mismatches (data-column count must equal each extra-header
8503
8783
  // row's total ColumnSpan).
8504
8784
  for(let r=0;r<tmpExpandedHeaders.length;r++){let tmpSpanTotal=0;for(let c=0;c<tmpExpandedHeaders[r].length;c++){tmpSpanTotal+=tmpExpandedHeaders[r][c].ColumnSpan||1;}if(tmpSpanTotal!==tmpDataColumnCount){this.log.warn(`PICT Form [${pView.UUID}]::[${pView.Hash}] tabular group [${pGroup.Hash}] header row ${r} total ColumnSpan (${tmpSpanTotal}) does not match data column count (${tmpDataColumnCount}). Header will visually misalign.`);}}// Compute initial row label metadata; recomputed on every marshal in onDataMarshalToForm.
8505
8785
  this._computeRowLabelMetadata(pView,pGroup);// This is the template by which the tabular template includes the rows.
@@ -8538,7 +8818,12 @@ this.pict.TemplateProvider.addTemplate(pGroup.SectionTabularRowVirtualTemplateHa
8538
8818
  * @returns {boolean}
8539
8819
  */onDataMarshalToForm(pView,pGroup){if(!pGroup){return true;}// Avoid recursion when this hook itself triggers a render -> marshal cycle.
8540
8820
  if(pGroup._RebuildInProgress){return true;}let tmpHasDynamicColumns=Array.isArray(pGroup.DynamicColumns)&&pGroup.DynamicColumns.length>0;if(tmpHasDynamicColumns&&this.fable.ManifestFactory&&typeof this.fable.ManifestFactory._resolveDynamicColumns==='function'){let tmpResult=this.fable.ManifestFactory._resolveDynamicColumns(pView,pGroup);if(tmpResult&&tmpResult.changed){pGroup._RebuildInProgress=true;try{pView.rebuildCustomTemplate();pView.render();}finally{pGroup._RebuildInProgress=false;}// The re-render rebuilt the table DOM -- restore selection highlights.
8541
- this._reapplyTabularSelectionHighlights(pView,pGroup);return true;}}this._computeRowLabelMetadata(pView,pGroup);// Keep selection highlights in sync with the (possibly reloaded) selection data.
8821
+ this._reapplyTabularSelectionHighlights(pView,pGroup);return true;}}// When the chooser's effective hidden-column set differs from the one the
8822
+ // current table template was baked with (e.g. the host just loaded saved
8823
+ // form data carrying a <GroupHash>_HiddenColumns array), rebuild so the
8824
+ // table reflects the loaded visibility. Steady state is a no-op — the
8825
+ // bake stamped _ColumnChooserBakedStateKey from the same form data.
8826
+ if(this._ensureTabularColumnChooserConfig(pGroup)){let tmpCurrentStateKey=this._getTabularColumnChooserStateKey(pView,pGroup);if(tmpCurrentStateKey!==pGroup._ColumnChooserBakedStateKey){pGroup._RebuildInProgress=true;try{pView.rebuildCustomTemplate();pView.render();}finally{pGroup._RebuildInProgress=false;}this._reapplyTabularSelectionHighlights(pView,pGroup);return true;}}this._computeRowLabelMetadata(pView,pGroup);// Keep selection highlights in sync with the (possibly reloaded) selection data.
8542
8827
  this._reapplyTabularSelectionHighlights(pView,pGroup);return true;}}module.exports=TabularLayout;},{"../Pict-Provider-DynamicLayout.js":46}],84:[function(require,module,exports){const libPictSectionGroupLayout=require('../Pict-Provider-DynamicLayout.js');const libPictSectionTuiGridLayout=require('./Pict-Layout-TuiGrid/Pict-Section-TuiGrid.js');class TuiGridLayout extends libPictSectionGroupLayout{/**
8543
8828
  * @param {import('pict')} pFable - The Fable instance.
8544
8829
  * @param {any} [pOptions={}] - The options for the TuiGrid layout.
@@ -9906,6 +10191,23 @@ this.pict.ContentAssignment.appendContent(`#Pict-${this.UUID}-FormContainer`,tmp
9906
10191
  tmpSectionDefinition.Name=tmpSectionDefinition.Hash;}if(!('Description'in tmpSectionDefinition)){// If there isn't a description, use the name
9907
10192
  tmpSectionDefinition.Description=`PICT Section [${tmpSectionDefinition.Name}].`;}if(!('Groups'in tmpSectionDefinition)){// If there isn't a groups array, create an empty one
9908
10193
  tmpSectionDefinition.Groups=[];}return tmpSectionDefinition;}catch(pError){this.log.error(`getSectionDefinition() failed to parse the section object with error: ${pError.message||pError}`);return false;}}getSectionViewFromHash(pSectionHash){let tmpSectionHash=typeof pSectionHash==='string'?pSectionHash:false;if(!tmpSectionHash){this.log.error('getSectionViewFromHash() called without a valid section hash.');return false;}let tmpViewHash=`PictSectionForm-${tmpSectionHash}`;if(tmpViewHash in this.fable.views){return this.fable.views[tmpViewHash];}this.log.error(`getSectionViewFromHash() could not find a view for section hash [${tmpSectionHash}].`);return false;}/**
10194
+ * Marshal one or more section views' data to the DOM.
10195
+ *
10196
+ * For values written outside the normal solve -> marshal cycle (e.g. a trigger group's `PostSolvers`
10197
+ * running in an EntityBundleRequest transaction-complete callback). The global equivalent is
10198
+ * `marshalToView()`; this is the section-scoped, cheaper option.
10199
+ *
10200
+ * @param {string|string[]} pSectionHashes - a section hash, or an array of section hashes
10201
+ * @returns {void}
10202
+ */marshalSectionToView(pSectionHashes){const tmpSectionHashes=(Array.isArray(pSectionHashes)?pSectionHashes:[pSectionHashes]).filter(pHash=>typeof pHash==='string'&&pHash.length>0);for(const tmpSectionHash of tmpSectionHashes){const tmpSectionView=this.getSectionViewFromHash(tmpSectionHash);if(tmpSectionView){tmpSectionView.marshalToView();}}}/**
10203
+ * Marshal one or more inputs' data to the DOM, wherever they live (finds the owning section view).
10204
+ *
10205
+ * The single-input equivalent of {@link marshalSectionToView}; use it when only specific fields
10206
+ * changed (e.g. a trigger group that computed one read-only attribute).
10207
+ *
10208
+ * @param {string|string[]} pInputHashes - an input hash, or an array of input hashes
10209
+ * @returns {void}
10210
+ */marshalInputToView(pInputHashes){const tmpInputHashes=(Array.isArray(pInputHashes)?pInputHashes:[pInputHashes]).filter(pHash=>typeof pHash==='string'&&pHash.length>0);if(tmpInputHashes.length<1){return;}const tmpSectionViews=this.filterViews();for(const tmpInputHash of tmpInputHashes){for(let i=0;i<tmpSectionViews.length;i++){const tmpSectionView=tmpSectionViews[i];if(tmpSectionView===this||typeof tmpSectionView.getInputFromHash!=='function'){continue;}const tmpInput=tmpSectionView.getInputFromHash(tmpInputHash);if(tmpInput){tmpSectionView.manualMarshalDataToViewByInput(tmpInput);break;}}}}/**
9909
10211
  * Clears out the manifest description set on the meta controller.
9910
10212
  */clearManifestDescription(){this.manifestDescription=null;}/**
9911
10213
  * Bootstraps Pict DynamicForm views from a Manyfest description JSON object.
@@ -25,6 +25,7 @@ case study: the same `Tabular` layout, stripped to its essentials.
25
25
  | Reference manifests | `FruitEditor` defines the table's columns |
26
26
  | Dot-notation data addresses | The Calories column reads `nutritions.calories` |
27
27
  | Per-field types & defaults | Each column declares a `DataType` and optional `Default` |
28
+ | Column chooser | `ColumnChooser: true` adds the **Columns** menu; hidden columns persist in the form data at `FruitGrid_HiddenColumns` |
28
29
 
29
30
  ## Key files
30
31