web-mojo 2.4.7 → 2.4.10

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 (125) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/admin-models.cjs.js +1 -1
  3. package/dist/admin-models.cjs.js.map +1 -1
  4. package/dist/admin-models.es.js +1 -1
  5. package/dist/admin-models.es.js.map +1 -1
  6. package/dist/admin.cjs.js +1 -1
  7. package/dist/admin.cjs.js.map +1 -1
  8. package/dist/admin.es.js +1 -1
  9. package/dist/admin.es.js.map +1 -1
  10. package/dist/auth.cjs.js +1 -1
  11. package/dist/auth.es.js +1 -1
  12. package/dist/charts.cjs.js +1 -1
  13. package/dist/charts.es.js +1 -1
  14. package/dist/chunks/{ChatView-D2WOSxPu.js → ChatView-C_2dRLAY.js} +2 -2
  15. package/dist/chunks/{ChatView-D2WOSxPu.js.map → ChatView-C_2dRLAY.js.map} +1 -1
  16. package/dist/chunks/{ChatView-kWguc444.js → ChatView-DVb2Ufq_.js} +2 -2
  17. package/dist/chunks/{ChatView-kWguc444.js.map → ChatView-DVb2Ufq_.js.map} +1 -1
  18. package/dist/chunks/{Collection-DNmr743A.js → Collection-Bq_EMAqK.js} +2 -2
  19. package/dist/chunks/{Collection-DNmr743A.js.map → Collection-Bq_EMAqK.js.map} +1 -1
  20. package/dist/chunks/{Collection-C0pHSKDH.js → Collection-tgvDhyz_.js} +2 -2
  21. package/dist/chunks/{Collection-C0pHSKDH.js.map → Collection-tgvDhyz_.js.map} +1 -1
  22. package/dist/chunks/{ContextMenu-T3yDdsIe.js → ContextMenu-BL_sdgIw.js} +2 -2
  23. package/dist/chunks/{ContextMenu-T3yDdsIe.js.map → ContextMenu-BL_sdgIw.js.map} +1 -1
  24. package/dist/chunks/{ContextMenu-BeveGkJr.js → ContextMenu-fIy3upEy.js} +2 -2
  25. package/dist/chunks/{ContextMenu-BeveGkJr.js.map → ContextMenu-fIy3upEy.js.map} +1 -1
  26. package/dist/chunks/{DataView-VZIXSsZa.js → DataView-DRQ2yFRz.js} +2 -2
  27. package/dist/chunks/{DataView-VZIXSsZa.js.map → DataView-DRQ2yFRz.js.map} +1 -1
  28. package/dist/chunks/{DataView-z2rxXk4L.js → DataView-DW_ufpQo.js} +2 -2
  29. package/dist/chunks/{DataView-z2rxXk4L.js.map → DataView-DW_ufpQo.js.map} +1 -1
  30. package/dist/chunks/{FormView-COIPtbrd.js → FormView-eWUWw8oL.js} +2 -2
  31. package/dist/chunks/{FormView-COIPtbrd.js.map → FormView-eWUWw8oL.js.map} +1 -1
  32. package/dist/chunks/{FormView-DUXQruUZ.js → FormView-xl_w506t.js} +2 -2
  33. package/dist/chunks/{FormView-DUXQruUZ.js.map → FormView-xl_w506t.js.map} +1 -1
  34. package/dist/chunks/{ListView-BjsNHuZ1.js → ListView-Bj4ONQFG.js} +2 -2
  35. package/dist/chunks/{ListView-BjsNHuZ1.js.map → ListView-Bj4ONQFG.js.map} +1 -1
  36. package/dist/chunks/{ListView-BxcxIwC3.js → ListView-jyIVhCN3.js} +2 -2
  37. package/dist/chunks/{ListView-BxcxIwC3.js.map → ListView-jyIVhCN3.js.map} +1 -1
  38. package/dist/chunks/MetricsCountryMapView-D_666qlW.js +2 -0
  39. package/dist/chunks/MetricsCountryMapView-D_666qlW.js.map +1 -0
  40. package/dist/chunks/MetricsCountryMapView-U3xr0QXD.js +2 -0
  41. package/dist/chunks/MetricsCountryMapView-U3xr0QXD.js.map +1 -0
  42. package/dist/chunks/Modal-CCVJefEX.js +3 -0
  43. package/dist/chunks/{Modal-Bm1OQ8Ou.js.map → Modal-CCVJefEX.js.map} +1 -1
  44. package/dist/chunks/Modal-Uwt-GBgJ.js +3 -0
  45. package/dist/chunks/{Modal-KnJhNZ1E.js.map → Modal-Uwt-GBgJ.js.map} +1 -1
  46. package/dist/chunks/Passkeys-DScGUF6p.js +2 -0
  47. package/dist/chunks/{Passkeys-BlHx11-5.js.map → Passkeys-DScGUF6p.js.map} +1 -1
  48. package/dist/chunks/{Passkeys-B1-Z4-16.js → Passkeys-c4amXO03.js} +2 -2
  49. package/dist/chunks/{Passkeys-B1-Z4-16.js.map → Passkeys-c4amXO03.js.map} +1 -1
  50. package/dist/chunks/{TokenManager-CiNtJclo.js → TokenManager-BRx2U5w4.js} +2 -2
  51. package/dist/chunks/{TokenManager-CiNtJclo.js.map → TokenManager-BRx2U5w4.js.map} +1 -1
  52. package/dist/chunks/{TokenManager-6atX9uKB.js → TokenManager-DZP2Lcnz.js} +2 -2
  53. package/dist/chunks/{TokenManager-6atX9uKB.js.map → TokenManager-DZP2Lcnz.js.map} +1 -1
  54. package/dist/chunks/{User-9qvKV7G6.js → User-DR-2VJcv.js} +2 -2
  55. package/dist/chunks/{User-9qvKV7G6.js.map → User-DR-2VJcv.js.map} +1 -1
  56. package/dist/chunks/{User-BM76Ughk.js → User-e9aOjdHv.js} +2 -2
  57. package/dist/chunks/{User-BM76Ughk.js.map → User-e9aOjdHv.js.map} +1 -1
  58. package/dist/chunks/{UserProfileView-DDflzpTa.js → UserProfileView-CCNcNT-M.js} +2 -2
  59. package/dist/chunks/{UserProfileView-DDflzpTa.js.map → UserProfileView-CCNcNT-M.js.map} +1 -1
  60. package/dist/chunks/{UserProfileView-cUF8ED9n.js → UserProfileView-PmerV2tC.js} +2 -2
  61. package/dist/chunks/{UserProfileView-cUF8ED9n.js.map → UserProfileView-PmerV2tC.js.map} +1 -1
  62. package/dist/chunks/{View-CPWwS19u.js → View-CLWMWXbO.js} +2 -2
  63. package/dist/chunks/{View-CPWwS19u.js.map → View-CLWMWXbO.js.map} +1 -1
  64. package/dist/chunks/{View-BxlKR1IW.js → View-DADtcBcb.js} +2 -2
  65. package/dist/chunks/{View-BxlKR1IW.js.map → View-DADtcBcb.js.map} +1 -1
  66. package/dist/chunks/{WebApp-BdxhRnnT.js → WebApp-BBrvjnnH.js} +2 -2
  67. package/dist/chunks/{WebApp-BdxhRnnT.js.map → WebApp-BBrvjnnH.js.map} +1 -1
  68. package/dist/chunks/{WebApp-Bo_egO0c.js → WebApp-wMWf0qkm.js} +2 -2
  69. package/dist/chunks/{WebApp-Bo_egO0c.js.map → WebApp-wMWf0qkm.js.map} +1 -1
  70. package/dist/chunks/exportChart-DVnTgKP0.js +2 -0
  71. package/dist/chunks/exportChart-DVnTgKP0.js.map +1 -0
  72. package/dist/chunks/exportChart-DYWi1WFJ.js +2 -0
  73. package/dist/chunks/exportChart-DYWi1WFJ.js.map +1 -0
  74. package/dist/chunks/{index-DsID1QpB.js → index-B312g4f-.js} +2 -2
  75. package/dist/chunks/{index-DsID1QpB.js.map → index-B312g4f-.js.map} +1 -1
  76. package/dist/chunks/{index-Cxffar1o.js → index-D7ITRVln.js} +2 -2
  77. package/dist/chunks/{index-Cxffar1o.js.map → index-D7ITRVln.js.map} +1 -1
  78. package/dist/chunks/version-B1_7ymBC.js +2 -0
  79. package/dist/chunks/version-B1_7ymBC.js.map +1 -0
  80. package/dist/chunks/version-CQGpN0qh.js +2 -0
  81. package/dist/chunks/version-CQGpN0qh.js.map +1 -0
  82. package/dist/docit.cjs.js +1 -1
  83. package/dist/docit.es.js +1 -1
  84. package/dist/index.cjs.js +1 -1
  85. package/dist/index.es.js +1 -1
  86. package/dist/lightbox.cjs.js +1 -1
  87. package/dist/lightbox.es.js +1 -1
  88. package/dist/map.cjs.js +1 -1
  89. package/dist/map.es.js +1 -1
  90. package/dist/timeline.cjs.js +1 -1
  91. package/dist/timeline.es.js +1 -1
  92. package/dist/user-profile.cjs.js +1 -1
  93. package/dist/user-profile.es.js +1 -1
  94. package/package.json +1 -1
  95. package/dist/chunks/AssistantPanelView-DmlaVMvK.js +0 -2
  96. package/dist/chunks/AssistantPanelView-DmlaVMvK.js.map +0 -1
  97. package/dist/chunks/AssistantPanelView-rkVX6wky.js +0 -2
  98. package/dist/chunks/AssistantPanelView-rkVX6wky.js.map +0 -1
  99. package/dist/chunks/MetricsCountryMapView-Bp3qoVHp.js +0 -2
  100. package/dist/chunks/MetricsCountryMapView-Bp3qoVHp.js.map +0 -1
  101. package/dist/chunks/MetricsCountryMapView-CWjIEBJB.js +0 -2
  102. package/dist/chunks/MetricsCountryMapView-CWjIEBJB.js.map +0 -1
  103. package/dist/chunks/Modal-Bm1OQ8Ou.js +0 -3
  104. package/dist/chunks/Modal-KnJhNZ1E.js +0 -3
  105. package/dist/chunks/Passkeys-BlHx11-5.js +0 -2
  106. package/dist/chunks/TicketPanelView-BGQZ6q2B.js +0 -2
  107. package/dist/chunks/TicketPanelView-BGQZ6q2B.js.map +0 -1
  108. package/dist/chunks/TicketPanelView-BZ8e66o1.js +0 -2
  109. package/dist/chunks/TicketPanelView-BZ8e66o1.js.map +0 -1
  110. package/dist/chunks/admin-CmyeTstQ.js +0 -2
  111. package/dist/chunks/admin-CmyeTstQ.js.map +0 -1
  112. package/dist/chunks/admin-D6gHOojO.js +0 -2
  113. package/dist/chunks/admin-D6gHOojO.js.map +0 -1
  114. package/dist/chunks/admin-models-CkHjtMHf.js +0 -2
  115. package/dist/chunks/admin-models-CkHjtMHf.js.map +0 -1
  116. package/dist/chunks/admin-models-DLtFboxy.js +0 -2
  117. package/dist/chunks/admin-models-DLtFboxy.js.map +0 -1
  118. package/dist/chunks/exportChart-BTrEOM9j.js +0 -2
  119. package/dist/chunks/exportChart-BTrEOM9j.js.map +0 -1
  120. package/dist/chunks/exportChart-BfzZUb1j.js +0 -2
  121. package/dist/chunks/exportChart-BfzZUb1j.js.map +0 -1
  122. package/dist/chunks/version-DdqQDDOW.js +0 -2
  123. package/dist/chunks/version-DdqQDDOW.js.map +0 -1
  124. package/dist/chunks/version-NEBZhkhG.js +0 -2
  125. package/dist/chunks/version-NEBZhkhG.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{V as e}from"./View-BxlKR1IW.js";class Page extends e{constructor(e={}){e.tagName=e.tagName||"main",e.className=e.className||"mojo-page";const t=e.pageName||"";t&&!e.id&&(e.id="page_"+t.toLowerCase().replace(/\s+/g,"_")),super(e),this.pageName=e.pageName||this.constructor.pageName||"",this.route=e.route||this.constructor.route||"",this.title=e.title||this.pageName||"",this.id||!this.constructor.pageName||e.pageName||(this.id="page_"+this.constructor.pageName.toLowerCase().replace(/\s+/g,"_")),this.pageIcon=e.icon||e.pageIcon||this.constructor.pageIcon||"bi bi-file-text",this.displayName=e.displayName||this.constructor.displayName||this.pageName||"",this.pageDescription=e.pageDescription||this.constructor.pageDescription||"",this.params={},this.query={},this.matched=!1,this.isActive=!1,this.pageOptions={title:e.title||this.pageName||"Untitled Page",description:e.description||"",requiresAuth:e.requiresAuth||!1,...e.pageOptions},this.savedState=null,this.pageName,this.route}async onParams(e={},t={}){this.params=e,this.query=t}canEnter(){if(this.options.permissions){const e=this.getApp().activeUser;if(!e||!e.hasPermission(this.options.permissions))return!1}return!(this.options.requiresGroup&&!this.getApp().activeGroup)}async onEnter(){this.isActive=!0,this._wasExited=!1,await this.onInitView(),this.savedState&&(this.restoreState(this.savedState),this.savedState=null),this.pageOptions&&this.pageOptions.title&&"undefined"!=typeof document&&(document.title=this.pageOptions.title),this.emit("activated",{page:this.getMetadata()}),this.pageName}async onExit(){this.savedState=this.captureState(),this.isActive=!1,this._wasExited=!0,this._clearScheduledRefreshes(),this.emit("deactivated",{page:this.getMetadata()}),this.pageName}scheduleRefresh(e,t,s={}){if(this._scheduledRefreshes||(this._scheduledRefreshes=[]),"function"!=typeof e||!(t>0))return null;const i=async()=>{try{await e()}catch(t){console.warn(`[Page ${this.pageName}] scheduleRefresh handler error:`,t)}};s.immediate&&i();const n=setInterval(i,t),a={id:n,tier:s.tier||null,handler:i,intervalMs:t};return this._scheduledRefreshes.push(a),{cancel:()=>{clearInterval(n),this._scheduledRefreshes=(this._scheduledRefreshes||[]).filter(e=>e!==a)}}}async runScheduledRefreshes(e=null){const t=this._scheduledRefreshes||[],s=e?t.filter(t=>t.tier===e):t;await Promise.allSettled(s.map(e=>e.handler()))}_clearScheduledRefreshes(){if(this._scheduledRefreshes){for(const e of this._scheduledRefreshes)clearInterval(e.id);this._scheduledRefreshes=[]}}async render(e=!0,t=null){return this._wasExited&&!this.isActive?this:super.render(e,t)}async onGroupChange(e){}getMetadata(){return{name:this.pageName,displayName:this.displayName||this.pageName,icon:this.pageIcon,description:this.pageDescription,route:this.route,isActive:this.isActive}}async onActionDefault(e){this.pageName}async makeActive(){this.getApp().showPage(this)}async onActionNavigate(e,t){e.preventDefault();const s=t.dataset.page;this.getApp().showPage(s)}captureState(){return this.element?{scrollTop:this.element.scrollTop,formData:this.captureFormData(),custom:this.captureCustomState()}:null}restoreState(e){e&&this.element&&(this.element.scrollTop=e.scrollTop||0,this.restoreFormData(e.formData),e.custom&&this.restoreCustomState(e.custom))}captureFormData(){const e={};return this.element?(this.element.querySelectorAll("input, select, textarea").forEach(t=>{t.name&&("checkbox"===t.type?e[t.name]=t.checked:"radio"===t.type?t.checked&&(e[t.name]=t.value):e[t.name]=t.value)}),e):e}restoreFormData(e){e&&this.element&&Object.entries(e).forEach(([e,t])=>{const s=this.element.querySelector(`[name="${e}"]`);if(s)if("checkbox"===s.type)s.checked=t;else if("radio"===s.type){const s=this.element.querySelector(`[name="${e}"][value="${t}"]`);s&&(s.checked=!0)}else s.value=t})}captureCustomState(){return{}}restoreCustomState(e){}setMeta(e={}){if("undefined"!=typeof document){if(e.title&&(document.title=e.title,this.pageOptions.title=e.title),e.description){let t=document.querySelector('meta[name="description"]');t||(t=document.createElement("meta"),t.name="description",document.head.appendChild(t)),t.content=e.description,this.pageOptions.description=e.description}Object.entries(e).forEach(([e,t])=>{if("title"!==e&&"description"!==e){let s=document.querySelector(`meta[name="${e}"]`);s||(s=document.createElement("meta"),s.name=e,document.head.appendChild(s)),s.content=t}})}}showError(e){if(super.showError(e),this.element){const t=document.createElement("div");t.className="alert alert-danger alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},5e3)}}showSuccess(e){if(super.showSuccess(e),this.element){const t=document.createElement("div");t.className="alert alert-success alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},3e3)}}async onBeforeRender(){await super.onBeforeRender(),this.setMeta({title:this.pageOptions.title,description:this.pageOptions.description})}async onAfterMount(){await super.onAfterMount(),"undefined"!=typeof document&&this.pageName&&document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}async onBeforeDestroy(){await super.onBeforeDestroy(),"undefined"!=typeof document&&this.pageName&&document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}navigate(e,t={},s={}){return this.app&&this.app.router?this.app.router.navigate(e,s):"undefined"!=typeof window&&window.MOJO?.router?window.MOJO.router.navigate(e,s):void console.error("No router available for navigation")}getRoute(){if(this.route){let e=this.route;return"string"==typeof e&&e.startsWith("/")&&(e=e.substring(1)),e}return this.pageName}syncUrl(e=!0){this.updateBrowserUrl(this.query,!1,!1)}updateBrowserUrl(e=null,t=!1,s=!1){this.getApp(),this.app.router.updateBrowserUrl(this.getRoute(),e,t,s)}static define(e){class DefinedPage extends Page{constructor(t={}){super({...e,...t})}}return DefinedPage.template=e.template,DefinedPage.pageName=e.pageName,DefinedPage.route=e.route,DefinedPage}}class ContextMenu extends e{static DEBUG=!1;constructor(e={}){super({tagName:"div",className:"context-menu-view dropdown",...e}),this.config=e.contextMenu||e.config||{},this.context=e.context||{}}async onAfterRender(){if(await super.onAfterRender(),!this.element)return;const e=window.bootstrap?.Dropdown;if(!e)return;const t=this.element.querySelector('[data-bs-toggle="dropdown"]');t&&e.getOrCreateInstance(t)}async renderTemplate(){const e=this.config.items||[];if(0===e.length)return"";const t=this.config.icon||"bi-three-dots",s=this.config.buttonClass||"btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1",i=`context-menu-${this.id}`;return`\n <button class="${s}" type="button" id="${i}" data-bs-toggle="dropdown" aria-expanded="false">\n <i class="${t}"></i>\n </button>\n <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="${i}">\n ${e.map(e=>this.buildMenuItemHTML(e)).join("")}\n </ul>\n `}buildMenuItemHTML(e){if("divider"===e.type||e.separator)return'<li><hr class="dropdown-divider"></li>';const t=e.icon?`<i class="${e.icon} me-2"></i>`:"",s=e.label||"",i=`dropdown-item ${e.danger?"text-danger":""} ${e.disabled?"disabled":""}`,n=e.action||"";return e.href?`<li><a class="${i}" href="${e.href}" target="${e.target||"_self"}">${t}${s}</a></li>`:`<li><a class="${i}" href="#" data-action="menu-item-click" data-item-action="${n}">${t}${s}</a></li>`}async onActionMenuItemClick(e,t){e.preventDefault();const s=t.getAttribute("data-item-action"),i=ContextMenu.DEBUG;if(i&&(this.parent,this.parent),!s)return;const n=this.config.items.find(e=>e.action===s);n&&!n.disabled&&("function"==typeof n.handler?n.handler(this.context,e,t):this.parent&&this.parent.events?(i&&this.parent.constructor,this.parent.events.dispatch(s,e,t)):i&&console.warn("[ContextMenu] no handler and no parent.events — action lost",{action:s}),this.closeDropdown())}closeDropdown(){const e=this.element?.querySelector(".dropdown-menu")||this._menuOrigParent?.querySelector?.(".dropdown-menu");e&&e.classList.contains("show")&&e.parentElement===document.body&&(e.classList.remove("show"),e.style.position="",e.style.left="",e.style.top="",e.style.transform="",e.style.inset="",e.style.margin="",e.style.zIndex="",this._menuOrigParent&&(this._menuOrigNextSibling&&this._menuOrigNextSibling.parentElement===this._menuOrigParent?this._menuOrigParent.insertBefore(e,this._menuOrigNextSibling):this._menuOrigParent.appendChild(e))),this._outsideHandler&&(document.removeEventListener("mousedown",this._outsideHandler,!0),this._outsideHandler=null);const t=this.element?.querySelector('[data-bs-toggle="dropdown"]');if(t){const e=window.bootstrap?.Dropdown.getInstance(t);e?.hide()}}async openAt(e,t,s){void 0!==s&&(this.context=s),this.isMounted()||(this.parent||this.containerId||this.container||(this.options.allowAppendToBody=!0),await this.render());const i=this.element?.querySelector(".dropdown-menu");return i?(i.parentElement!==document.body&&(this._menuOrigParent=i.parentElement,this._menuOrigNextSibling=i.nextSibling,document.body.appendChild(i)),i.style.transform="none",i.style.inset="auto",i.style.position="fixed",i.style.left=`${e}px`,i.style.top=`${t}px`,i.style.margin="0",i.style.zIndex="1080",i.classList.add("show"),this._menuClickHandler&&i.removeEventListener("click",this._menuClickHandler),this._menuClickHandler=e=>{const t=e.target.closest('[data-action="menu-item-click"]');t&&this.onActionMenuItemClick(e,t)},i.addEventListener("click",this._menuClickHandler),this._outsideHandler&&document.removeEventListener("mousedown",this._outsideHandler,!0),this._outsideHandler=e=>{i.contains(e.target)||this.closeDropdown()},setTimeout(()=>{document.addEventListener("mousedown",this._outsideHandler,!0)},0),this):this}static attachToRightClick(e,t,s={}){if(!e)throw new Error("ContextMenu.attachToRightClick: element is required");const i=s.menu instanceof ContextMenu?s.menu:new ContextMenu(s),n=e=>{e.preventDefault();const s="function"==typeof t?t(e):t;i.openAt(e.clientX,e.clientY,s)};return e.addEventListener("contextmenu",n),i._rightClickHandler=n,i._rightClickElement=e,i}detachRightClick(){return this._rightClickElement&&this._rightClickHandler&&(this._rightClickElement.removeEventListener("contextmenu",this._rightClickHandler),this._rightClickElement=null,this._rightClickHandler=null),this}}export{ContextMenu as C,Page as P};
2
- //# sourceMappingURL=ContextMenu-T3yDdsIe.js.map
1
+ import{V as e}from"./View-DADtcBcb.js";class Page extends e{constructor(e={}){e.tagName=e.tagName||"main",e.className=e.className||"mojo-page";const t=e.pageName||"";t&&!e.id&&(e.id="page_"+t.toLowerCase().replace(/\s+/g,"_")),super(e),this.pageName=e.pageName||this.constructor.pageName||"",this.route=e.route||this.constructor.route||"",this.title=e.title||this.pageName||"",this.id||!this.constructor.pageName||e.pageName||(this.id="page_"+this.constructor.pageName.toLowerCase().replace(/\s+/g,"_")),this.pageIcon=e.icon||e.pageIcon||this.constructor.pageIcon||"bi bi-file-text",this.displayName=e.displayName||this.constructor.displayName||this.pageName||"",this.pageDescription=e.pageDescription||this.constructor.pageDescription||"",this.params={},this.query={},this.matched=!1,this.isActive=!1,this.pageOptions={title:e.title||this.pageName||"Untitled Page",description:e.description||"",requiresAuth:e.requiresAuth||!1,...e.pageOptions},this.savedState=null,this.pageName,this.route}async onParams(e={},t={}){this.params=e,this.query=t}canEnter(){if(this.options.permissions){const e=this.getApp().activeUser;if(!e||!e.hasPermission(this.options.permissions))return!1}return!(this.options.requiresGroup&&!this.getApp().activeGroup)}async onEnter(){this.isActive=!0,this._wasExited=!1,await this.onInitView(),this.savedState&&(this.restoreState(this.savedState),this.savedState=null),this.pageOptions&&this.pageOptions.title&&"undefined"!=typeof document&&(document.title=this.pageOptions.title),this.emit("activated",{page:this.getMetadata()}),this.pageName}async onExit(){this.savedState=this.captureState(),this.isActive=!1,this._wasExited=!0,this._clearScheduledRefreshes(),this.emit("deactivated",{page:this.getMetadata()}),this.pageName}scheduleRefresh(e,t,s={}){if(this._scheduledRefreshes||(this._scheduledRefreshes=[]),"function"!=typeof e||!(t>0))return null;const i=async()=>{try{await e()}catch(t){console.warn(`[Page ${this.pageName}] scheduleRefresh handler error:`,t)}};s.immediate&&i();const n=setInterval(i,t),a={id:n,tier:s.tier||null,handler:i,intervalMs:t};return this._scheduledRefreshes.push(a),{cancel:()=>{clearInterval(n),this._scheduledRefreshes=(this._scheduledRefreshes||[]).filter(e=>e!==a)}}}async runScheduledRefreshes(e=null){const t=this._scheduledRefreshes||[],s=e?t.filter(t=>t.tier===e):t;await Promise.allSettled(s.map(e=>e.handler()))}_clearScheduledRefreshes(){if(this._scheduledRefreshes){for(const e of this._scheduledRefreshes)clearInterval(e.id);this._scheduledRefreshes=[]}}async render(e=!0,t=null){return this._wasExited&&!this.isActive?this:super.render(e,t)}async onGroupChange(e){}getMetadata(){return{name:this.pageName,displayName:this.displayName||this.pageName,icon:this.pageIcon,description:this.pageDescription,route:this.route,isActive:this.isActive}}async onActionDefault(e){this.pageName}async makeActive(){this.getApp().showPage(this)}async onActionNavigate(e,t){e.preventDefault();const s=t.dataset.page;this.getApp().showPage(s)}captureState(){return this.element?{scrollTop:this.element.scrollTop,formData:this.captureFormData(),custom:this.captureCustomState()}:null}restoreState(e){e&&this.element&&(this.element.scrollTop=e.scrollTop||0,this.restoreFormData(e.formData),e.custom&&this.restoreCustomState(e.custom))}captureFormData(){const e={};return this.element?(this.element.querySelectorAll("input, select, textarea").forEach(t=>{t.name&&("checkbox"===t.type?e[t.name]=t.checked:"radio"===t.type?t.checked&&(e[t.name]=t.value):e[t.name]=t.value)}),e):e}restoreFormData(e){e&&this.element&&Object.entries(e).forEach(([e,t])=>{const s=this.element.querySelector(`[name="${e}"]`);if(s)if("checkbox"===s.type)s.checked=t;else if("radio"===s.type){const s=this.element.querySelector(`[name="${e}"][value="${t}"]`);s&&(s.checked=!0)}else s.value=t})}captureCustomState(){return{}}restoreCustomState(e){}setMeta(e={}){if("undefined"!=typeof document){if(e.title&&(document.title=e.title,this.pageOptions.title=e.title),e.description){let t=document.querySelector('meta[name="description"]');t||(t=document.createElement("meta"),t.name="description",document.head.appendChild(t)),t.content=e.description,this.pageOptions.description=e.description}Object.entries(e).forEach(([e,t])=>{if("title"!==e&&"description"!==e){let s=document.querySelector(`meta[name="${e}"]`);s||(s=document.createElement("meta"),s.name=e,document.head.appendChild(s)),s.content=t}})}}showError(e){if(super.showError(e),this.element){const t=document.createElement("div");t.className="alert alert-danger alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},5e3)}}showSuccess(e){if(super.showSuccess(e),this.element){const t=document.createElement("div");t.className="alert alert-success alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},3e3)}}async onBeforeRender(){await super.onBeforeRender(),this.setMeta({title:this.pageOptions.title,description:this.pageOptions.description})}async onAfterMount(){await super.onAfterMount(),"undefined"!=typeof document&&this.pageName&&document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}async onBeforeDestroy(){await super.onBeforeDestroy(),"undefined"!=typeof document&&this.pageName&&document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}navigate(e,t={},s={}){return this.app&&this.app.router?this.app.router.navigate(e,s):"undefined"!=typeof window&&window.MOJO?.router?window.MOJO.router.navigate(e,s):void console.error("No router available for navigation")}getRoute(){if(this.route){let e=this.route;return"string"==typeof e&&e.startsWith("/")&&(e=e.substring(1)),e}return this.pageName}syncUrl(e=!0){this.updateBrowserUrl(this.query,!1,!1)}updateBrowserUrl(e=null,t=!1,s=!1){this.getApp(),this.app.router.updateBrowserUrl(this.getRoute(),e,t,s)}static define(e){class DefinedPage extends Page{constructor(t={}){super({...e,...t})}}return DefinedPage.template=e.template,DefinedPage.pageName=e.pageName,DefinedPage.route=e.route,DefinedPage}}class ContextMenu extends e{static DEBUG=!1;constructor(e={}){super({tagName:"div",className:"context-menu-view dropdown",...e}),this.config=e.contextMenu||e.config||{},this.context=e.context||{}}async onAfterRender(){if(await super.onAfterRender(),!this.element)return;const e=window.bootstrap?.Dropdown;if(!e)return;const t=this.element.querySelector('[data-bs-toggle="dropdown"]');t&&e.getOrCreateInstance(t)}async renderTemplate(){const e=this.config.items||[];if(0===e.length)return"";const t=this.config.icon||"bi-three-dots",s=this.config.buttonClass||"btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1",i=`context-menu-${this.id}`;return`\n <button class="${s}" type="button" id="${i}" data-bs-toggle="dropdown" aria-expanded="false">\n <i class="${t}"></i>\n </button>\n <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="${i}">\n ${e.map(e=>this.buildMenuItemHTML(e)).join("")}\n </ul>\n `}buildMenuItemHTML(e){if("divider"===e.type||e.separator)return'<li><hr class="dropdown-divider"></li>';const t=e.icon?`<i class="${e.icon} me-2"></i>`:"",s=e.label||"",i=`dropdown-item ${e.danger?"text-danger":""} ${e.disabled?"disabled":""}`,n=e.action||"";return e.href?`<li><a class="${i}" href="${e.href}" target="${e.target||"_self"}">${t}${s}</a></li>`:`<li><a class="${i}" href="#" data-action="menu-item-click" data-item-action="${n}">${t}${s}</a></li>`}async onActionMenuItemClick(e,t){e.preventDefault();const s=t.getAttribute("data-item-action"),i=ContextMenu.DEBUG;if(i&&(this.parent,this.parent),!s)return;const n=this.config.items.find(e=>e.action===s);n&&!n.disabled&&("function"==typeof n.handler?n.handler(this.context,e,t):this.parent&&this.parent.events?(i&&this.parent.constructor,this.parent.events.dispatch(s,e,t)):i&&console.warn("[ContextMenu] no handler and no parent.events — action lost",{action:s}),this.closeDropdown())}closeDropdown(){const e=this.element?.querySelector(".dropdown-menu")||this._menuOrigParent?.querySelector?.(".dropdown-menu");e&&e.classList.contains("show")&&e.parentElement===document.body&&(e.classList.remove("show"),e.style.position="",e.style.left="",e.style.top="",e.style.transform="",e.style.inset="",e.style.margin="",e.style.zIndex="",this._menuOrigParent&&(this._menuOrigNextSibling&&this._menuOrigNextSibling.parentElement===this._menuOrigParent?this._menuOrigParent.insertBefore(e,this._menuOrigNextSibling):this._menuOrigParent.appendChild(e))),this._outsideHandler&&(document.removeEventListener("mousedown",this._outsideHandler,!0),this._outsideHandler=null);const t=this.element?.querySelector('[data-bs-toggle="dropdown"]');if(t){const e=window.bootstrap?.Dropdown.getInstance(t);e?.hide()}}async openAt(e,t,s){void 0!==s&&(this.context=s),this.isMounted()||(this.parent||this.containerId||this.container||(this.options.allowAppendToBody=!0),await this.render());const i=this.element?.querySelector(".dropdown-menu");return i?(i.parentElement!==document.body&&(this._menuOrigParent=i.parentElement,this._menuOrigNextSibling=i.nextSibling,document.body.appendChild(i)),i.style.transform="none",i.style.inset="auto",i.style.position="fixed",i.style.left=`${e}px`,i.style.top=`${t}px`,i.style.margin="0",i.style.zIndex="1080",i.classList.add("show"),this._menuClickHandler&&i.removeEventListener("click",this._menuClickHandler),this._menuClickHandler=e=>{const t=e.target.closest('[data-action="menu-item-click"]');t&&this.onActionMenuItemClick(e,t)},i.addEventListener("click",this._menuClickHandler),this._outsideHandler&&document.removeEventListener("mousedown",this._outsideHandler,!0),this._outsideHandler=e=>{i.contains(e.target)||this.closeDropdown()},setTimeout(()=>{document.addEventListener("mousedown",this._outsideHandler,!0)},0),this):this}static attachToRightClick(e,t,s={}){if(!e)throw new Error("ContextMenu.attachToRightClick: element is required");const i=s.menu instanceof ContextMenu?s.menu:new ContextMenu(s),n=e=>{e.preventDefault();const s="function"==typeof t?t(e):t;i.openAt(e.clientX,e.clientY,s)};return e.addEventListener("contextmenu",n),i._rightClickHandler=n,i._rightClickElement=e,i}detachRightClick(){return this._rightClickElement&&this._rightClickHandler&&(this._rightClickElement.removeEventListener("contextmenu",this._rightClickHandler),this._rightClickElement=null,this._rightClickHandler=null),this}}export{ContextMenu as C,Page as P};
2
+ //# sourceMappingURL=ContextMenu-BL_sdgIw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContextMenu-T3yDdsIe.js","sources":["../../src/core/Page.js","../../src/core/views/feedback/ContextMenu.js"],"sourcesContent":["/**\n * Page - Extends View with routing capabilities for MOJO framework\n * Handles URL routing, parameters, and page-specific actions\n *\n * Event Emitter notes:\n * - Uses EventEmitter via View base class.\n * - Use .emit/.on/.off/.once for all custom events.\n */\n\nimport View from '@core/View.js';\n\nclass Page extends View {\n constructor(options = {}) {\n // Set default tag name for pages\n options.tagName = options.tagName || 'main';\n options.className = options.className || 'mojo-page';\n\n // Set page ID based on page name\n const pageName = options.pageName || '';\n if (pageName && !options.id) {\n options.id = 'page_' + pageName.toLowerCase().replace(/\\s+/g, '_');\n }\n\n super(options);\n\n // Core page properties from design doc\n this.pageName = options.pageName || this.constructor.pageName || '';\n this.route = options.route || this.constructor.route || '';\n this.title = options.title || this.pageName || '';\n\n // Set page ID if not already set and we have a page_name from constructor\n if (!this.id && this.constructor.pageName && !options.pageName) {\n this.id = 'page_' + this.constructor.pageName.toLowerCase().replace(/\\s+/g, '_');\n }\n\n // Page metadata for event system\n this.pageIcon = options.icon || options.pageIcon || this.constructor.pageIcon || 'bi bi-file-text';\n this.displayName = options.displayName || this.constructor.displayName || this.pageName || '';\n this.pageDescription = options.pageDescription || this.constructor.pageDescription || '';\n\n // Routing state\n this.params = {};\n this.query = {};\n this.matched = false;\n this.isActive = false;\n\n // Page-specific options\n this.pageOptions = {\n title: options.title || this.pageName || 'Untitled Page',\n description: options.description || '',\n requiresAuth: options.requiresAuth || false,\n ...options.pageOptions\n };\n\n // State preservation\n this.savedState = null;\n\n console.log(`Page ${this.pageName} constructed with route: ${this.route}`);\n }\n\n /**\n * Handle route parameters - from design doc\n * @param {object} params - Route parameters\n * @param {object} query - Query string parameters\n */\n async onParams(params = {}, query = {}) {\n // const paramsChanged = JSON.stringify(params) !== JSON.stringify(this.params);\n // const queryChanged = JSON.stringify(query) !== JSON.stringify(this.query);\n\n this.params = params;\n this.query = query;\n\n // Only re-render if params actually changed and page is active\n // if (this.isActive && (paramsChanged || queryChanged)) {\n // console.log(`Page ${this.pageName} params changed, re-rendering`);\n // await this.render();\n // }\n }\n\n canEnter() {\n if (this.options.permissions) {\n const user = this.getApp().activeUser;\n if (!user || !user.hasPermission(this.options.permissions)) {\n return false;\n }\n }\n if (this.options.requiresGroup && !this.getApp().activeGroup) {\n return false;\n }\n return true;\n }\n\n /**\n * Called when entering this page (before render)\n * Override this method for initialization logic\n */\n async onEnter() {\n this.isActive = true;\n this._wasExited = false;\n await this.onInitView();\n\n // Restore saved state if exists\n if (this.savedState) {\n this.restoreState(this.savedState);\n this.savedState = null;\n }\n\n // Set page title if provided\n if (this.pageOptions && this.pageOptions.title && typeof document !== 'undefined') {\n document.title = this.pageOptions.title;\n }\n\n // Emit activation event\n this.emit('activated', {\n page: this.getMetadata()\n });\n\n console.log(`Page ${this.pageName} entered`);\n }\n\n /**\n * Called when leaving this page (before cleanup)\n * Override this method for cleanup logic like removing listeners, clearing timers, etc.\n */\n async onExit() {\n // Save state before exit\n this.savedState = this.captureState();\n this.isActive = false;\n this._wasExited = true;\n\n // Auto-clear any intervals registered via scheduleRefresh()\n this._clearScheduledRefreshes();\n\n // Emit deactivation event\n this.emit('deactivated', {\n page: this.getMetadata()\n });\n console.log(`Page ${this.pageName} exiting`);\n }\n\n /**\n * Register a recurring handler that auto-clears on page exit.\n * Use this instead of hand-rolling setInterval / clearInterval pairs\n * across every dashboard page.\n *\n * Multiple cadences are supported by calling scheduleRefresh multiple\n * times with different intervals. The optional `tier` is a label used\n * by `runScheduledRefreshes(tier)` to fire only handlers tagged with\n * that tier (handy for a manual \"refresh all\" button that wants to\n * call only the slow tier without waiting for it to tick).\n *\n * @param {Function} handler - Called every `intervalMs`. May return a Promise.\n * @param {number} intervalMs - Interval in milliseconds.\n * @param {object} [options]\n * @param {string} [options.tier] - Label e.g. 'fast' / 'slow'\n * @param {boolean} [options.immediate=false] - Fire once now, then start interval\n * @returns {object} A handle with `cancel()` to clear before exit if needed.\n */\n scheduleRefresh(handler, intervalMs, options = {}) {\n if (!this._scheduledRefreshes) this._scheduledRefreshes = [];\n if (typeof handler !== 'function' || !(intervalMs > 0)) return null;\n\n const safe = async () => {\n try { await handler(); }\n catch (err) { console.warn(`[Page ${this.pageName}] scheduleRefresh handler error:`, err); }\n };\n\n if (options.immediate) safe();\n const id = setInterval(safe, intervalMs);\n const entry = { id, tier: options.tier || null, handler: safe, intervalMs };\n this._scheduledRefreshes.push(entry);\n\n return {\n cancel: () => {\n clearInterval(id);\n this._scheduledRefreshes = (this._scheduledRefreshes || []).filter(e => e !== entry);\n }\n };\n }\n\n /**\n * Fire all scheduled handlers immediately. If a `tier` is given, only\n * handlers registered with that tier label run.\n */\n async runScheduledRefreshes(tier = null) {\n const list = this._scheduledRefreshes || [];\n const targets = tier ? list.filter(e => e.tier === tier) : list;\n await Promise.allSettled(targets.map(e => e.handler()));\n }\n\n _clearScheduledRefreshes() {\n if (!this._scheduledRefreshes) return;\n for (const entry of this._scheduledRefreshes) {\n clearInterval(entry.id);\n }\n this._scheduledRefreshes = [];\n }\n\n /**\n * Render guard: once a Page has been exited (onExit ran, isActive went\n * false), subsequent render() calls become no-ops until the page is\n * re-entered. Without this guard, async sources still holding a\n * reference to the page (timers, WebSocket reconnect attempts, lingering\n * promises) can call this.render() from a hidden page and overwrite\n * whatever page is currently visible in the page-container.\n *\n * The flag flips back on in onEnter via savedState restoration. The very\n * first render (before any onEnter) is allowed because savedState is null\n * and isActive is false but onExit has never set the _wasExited flag.\n */\n async render(allowMount = true, container = null) {\n if (this._wasExited && !this.isActive) {\n return this;\n }\n return super.render(allowMount, container);\n }\n\n\n /**\n * Called by PortalApp.setActiveGroup() whenever the user switches to a different group.\n *\n * Override this on any page that displays group-scoped data so the page\n * stays in sync when the active group changes. Always call super first.\n *\n * This is an MVC framework — your override should tell your Models and\n * Collections to re-fetch for the new group, then call this.render().\n * There is no framework-provided loadData() method; organise your own\n * fetch logic however you like.\n *\n * @param {Model} group - The newly active Group model.\n * this.getApp().activeGroup is already set to this value.\n *\n * @example\n * async onGroupChange(group) {\n * await super.onGroupChange(group);\n * if (!group) return;\n * await this.myCollection.fetch({ group_id: group.id });\n * await this.render();\n * }\n */\n async onGroupChange(group) {\n // Empty stub — override in subclasses that display group-scoped data.\n }\n\n /**\n * Get page metadata for display and events\n * @returns {object} Page metadata\n */\n getMetadata() {\n return {\n name: this.pageName,\n displayName: this.displayName || this.pageName,\n icon: this.pageIcon,\n description: this.pageDescription,\n route: this.route,\n isActive: this.isActive\n };\n }\n\n /**\n * Handle default action - fallback from design doc\n */\n async onActionDefault(action) {\n console.log(`Default action '${action}' triggered on page: ${this.pageName}`);\n }\n\n async makeActive() {\n this.getApp().showPage(this);\n }\n\n async onActionNavigate(event, element) {\n event.preventDefault();\n const page = element.dataset.page;\n this.getApp().showPage(page);\n }\n\n /**\n * Capture current page state for preservation\n * @returns {object|null} Captured state\n */\n captureState() {\n if (!this.element) return null;\n\n return {\n scrollTop: this.element.scrollTop,\n formData: this.captureFormData(),\n custom: this.captureCustomState()\n };\n }\n\n /**\n * Restore saved state\n * @param {object} state - State to restore\n */\n restoreState(state) {\n if (!state || !this.element) return;\n\n this.element.scrollTop = state.scrollTop || 0;\n this.restoreFormData(state.formData);\n if (state.custom) {\n this.restoreCustomState(state.custom);\n }\n }\n\n /**\n * Capture form data from page\n * @returns {object} Form data\n */\n captureFormData() {\n const data = {};\n if (!this.element) return data;\n\n this.element.querySelectorAll('input, select, textarea').forEach(field => {\n if (field.name) {\n if (field.type === 'checkbox') {\n data[field.name] = field.checked;\n } else if (field.type === 'radio') {\n if (field.checked) {\n data[field.name] = field.value;\n }\n } else {\n data[field.name] = field.value;\n }\n }\n });\n\n return data;\n }\n\n /**\n * Restore form data to page\n * @param {object} formData - Form data to restore\n */\n restoreFormData(formData) {\n if (!formData || !this.element) return;\n\n Object.entries(formData).forEach(([name, value]) => {\n const field = this.element.querySelector(`[name=\"${name}\"]`);\n if (field) {\n if (field.type === 'checkbox') {\n field.checked = value;\n } else if (field.type === 'radio') {\n const radio = this.element.querySelector(`[name=\"${name}\"][value=\"${value}\"]`);\n if (radio) radio.checked = true;\n } else {\n field.value = value;\n }\n }\n });\n }\n\n /**\n * Capture custom state - override in subclasses\n * @returns {object} Custom state\n */\n captureCustomState() {\n return {};\n }\n\n /**\n * Restore custom state - override in subclasses\n * @param {object} state - Custom state to restore\n */\n restoreCustomState(state) {\n // Override in subclasses\n }\n\n\n\n /**\n * Set page metadata\n * @param {object} meta - Metadata object\n */\n setMeta(meta = {}) {\n if (typeof document === 'undefined') {\n return;\n }\n\n // Set title\n if (meta.title) {\n document.title = meta.title;\n this.pageOptions.title = meta.title;\n }\n\n // Set description\n if (meta.description) {\n let descMeta = document.querySelector('meta[name=\"description\"]');\n if (!descMeta) {\n descMeta = document.createElement('meta');\n descMeta.name = 'description';\n document.head.appendChild(descMeta);\n }\n descMeta.content = meta.description;\n this.pageOptions.description = meta.description;\n }\n\n // Set other meta tags\n Object.entries(meta).forEach(([key, value]) => {\n if (key !== 'title' && key !== 'description') {\n let metaEl = document.querySelector(`meta[name=\"${key}\"]`);\n if (!metaEl) {\n metaEl = document.createElement('meta');\n metaEl.name = key;\n document.head.appendChild(metaEl);\n }\n metaEl.content = value;\n }\n });\n }\n\n\n /**\n * Show error message with page context\n * @param {string} message - Error message\n */\n showError(message) {\n super.showError(message);\n\n // Page-specific error display can be implemented here\n if (this.element) {\n // Example: Add error to page\n const errorDiv = document.createElement('div');\n errorDiv.className = 'alert alert-danger alert-dismissible fade show';\n errorDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n `;\n\n // Insert at top of page\n this.element.insertBefore(errorDiv, this.element.firstChild);\n\n // Auto-remove after 5 seconds\n setTimeout(() => {\n if (errorDiv.parentNode) {\n errorDiv.parentNode.removeChild(errorDiv);\n }\n }, 5000);\n }\n }\n\n /**\n * Show success message with page context\n * @param {string} message - Success message\n */\n showSuccess(message) {\n super.showSuccess(message);\n\n // Page-specific success display\n if (this.element) {\n const successDiv = document.createElement('div');\n successDiv.className = 'alert alert-success alert-dismissible fade show';\n successDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n `;\n\n // Insert at top of page\n this.element.insertBefore(successDiv, this.element.firstChild);\n\n // Auto-remove after 3 seconds\n setTimeout(() => {\n if (successDiv.parentNode) {\n successDiv.parentNode.removeChild(successDiv);\n }\n }, 3000);\n }\n }\n\n /**\n * Page-specific before render hook\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n\n // Set page metadata before rendering\n this.setMeta({\n title: this.pageOptions.title,\n description: this.pageOptions.description\n });\n }\n\n /**\n * Page-specific after mount hook\n */\n async onAfterMount() {\n await super.onAfterMount();\n\n // Add page-specific class to body\n if (typeof document !== 'undefined' && this.pageName) {\n document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\\s+/g, '-')}`);\n }\n }\n\n /**\n * Page-specific before destroy hook\n */\n async onBeforeDestroy() {\n await super.onBeforeDestroy();\n\n // Remove page-specific class from body\n if (typeof document !== 'undefined' && this.pageName) {\n document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\\s+/g, '-')}`);\n }\n }\n\n /**\n * Navigate to another page using the app's router\n * @param {string} route - Route to navigate to\n * @param {object} params - Route parameters\n * @param {object} options - Navigation options\n */\n navigate(route, params = {}, options = {}) {\n // Delegate to app's router\n if (this.app && this.app.router) {\n return this.app.router.navigate(route, options);\n }\n\n // Fallback to MOJO global router\n if (typeof window !== 'undefined' && window.MOJO?.router) {\n return window.MOJO.router.navigate(route, options);\n }\n\n console.error('No router available for navigation');\n }\n\n getRoute() {\n if (this.route) {\n let route = this.route;\n if (typeof route === 'string' && route.startsWith('/')) {\n route = route.substring(1);\n }\n return route;\n }\n return this.pageName;\n }\n\n syncUrl(force = true) {\n this.updateBrowserUrl(this.query, false, false);\n }\n\n updateBrowserUrl(query = null, replace = false, trigger = false) {\n this.getApp();\n // we need to do this to normalize the URL\n // const targetPath = this.app.buildPagePath(this, this.params, query);\n // const { pageName, queryParams } = this.app.router.parseInput(targetPath);\n this.app.router.updateBrowserUrl(this.getRoute(), query, replace, trigger);\n }\n\n /**\n * Static method to define a page class with metadata\n * @param {object} definition - Page class definition\n * @returns {class} Page class\n */\n static define(definition) {\n class DefinedPage extends Page {\n constructor(options = {}) {\n super({\n ...definition,\n ...options\n });\n }\n }\n\n // Copy static properties\n DefinedPage.template = definition.template;\n DefinedPage.pageName = definition.pageName;\n DefinedPage.route = definition.route;\n\n return DefinedPage;\n }\n}\n\nexport default Page;\n","/**\n * ContextMenu - A reusable context menu component for MOJO\n *\n * Renders a Bootstrap 5 dropdown menu based on a configuration object.\n * This component is designed to be easily embedded in any other View.\n * It supports the same configuration syntax as the Dialog's contextMenu.\n *\n * Features:\n * - Renders a dropdown button with a configurable icon.\n * - Generates menu items from a configuration array.\n * - Supports dividers, icons, labels, and links.\n * - Handles actions via inline handlers or by emitting an 'action' event.\n * - Supports right-click usage via `openAt(x, y, contextItem)` and the\n * `ContextMenu.attachToRightClick()` static helper.\n *\n * @example\n * const contextMenu = new ContextMenu({\n * config: {\n * icon: 'bi-gear', // Optional: custom trigger icon\n * items: [\n * { label: 'Edit', action: 'edit', icon: 'bi-pencil' },\n * { label: 'Delete', action: 'delete', icon: 'bi-trash', danger: true },\n * { type: 'divider' },\n * {\n * label: 'Custom Action',\n * action: 'custom',\n * icon: 'bi-star',\n * handler: (context) => {\n * console.log('Inline handler called with context:', context);\n * }\n * }\n * ]\n * },\n * context: { id: 123, name: 'My Item' } // Optional data to pass to handlers/events\n * });\n *\n * // Listen for actions from the parent view\n * contextMenu.on('action', (data) => {\n * console.log(`Action '${data.action}' triggered for context:`, data.context);\n * if (data.action === 'edit') {\n * // handle edit\n * }\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ContextMenu extends View {\n /**\n * Set `ContextMenu.DEBUG = true` from the browser console (or in a\n * downstream app's bootstrap) to trace menu-item clicks, parent\n * dispatch, and Bootstrap auto-attach. Off by default.\n */\n static DEBUG = false;\n\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'context-menu-view dropdown',\n ...options\n });\n\n this.config = options.contextMenu || options.config || {};\n this.context = options.context || {}; // Optional data to pass to handlers/events\n }\n\n /**\n * After every render, ensure the Bootstrap Dropdown instance is wired\n * to our trigger button. Bootstrap's data-API is supposed to handle\n * this via document delegation, but doesn't reliably attach to\n * dynamically rendered View markup — without this hook, clicking the\n * three-dots trigger button silently does nothing.\n */\n async onAfterRender() {\n await super.onAfterRender();\n if (!this.element) return;\n const Dropdown = window.bootstrap?.Dropdown;\n if (!Dropdown) return;\n const trigger = this.element.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (trigger) Dropdown.getOrCreateInstance(trigger);\n }\n\n /**\n * Build the dropdown menu HTML from the configuration.\n */\n async renderTemplate() {\n const menuItems = this.config.items || [];\n if (menuItems.length === 0) {\n return ''; // Don't render anything if there are no items\n }\n\n const triggerIcon = this.config.icon || 'bi-three-dots';\n const buttonClass = this.config.buttonClass || 'btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1';\n const dropdownId = `context-menu-${this.id}`;\n\n const menuItemsHtml = menuItems.map(item => this.buildMenuItemHTML(item)).join('');\n\n return `\n <button class=\"${buttonClass}\" type=\"button\" id=\"${dropdownId}\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"${triggerIcon}\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\" aria-labelledby=\"${dropdownId}\">\n ${menuItemsHtml}\n </ul>\n `;\n }\n\n /**\n * Build the HTML for a single menu item.\n * @param {object} item - The menu item configuration.\n * @returns {string} The HTML string for the list item.\n */\n buildMenuItemHTML(item) {\n if (item.type === 'divider' || item.separator) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n const icon = item.icon ? `<i class=\"${item.icon} me-2\"></i>` : '';\n const label = item.label || '';\n const itemClass = `dropdown-item ${item.danger ? 'text-danger' : ''} ${item.disabled ? 'disabled' : ''}`;\n const action = item.action || '';\n\n if (item.href) {\n return `<li><a class=\"${itemClass}\" href=\"${item.href}\" target=\"${item.target || '_self'}\">${icon}${label}</a></li>`;\n }\n\n return `<li><a class=\"${itemClass}\" href=\"#\" data-action=\"menu-item-click\" data-item-action=\"${action}\">${icon}${label}</a></li>`;\n }\n\n /**\n * Handle clicks on menu items.\n * @param {Event} event - The click event.\n * @param {HTMLElement} element - The clicked anchor element.\n */\n async onActionMenuItemClick(event, element) {\n event.preventDefault();\n const action = element.getAttribute('data-item-action');\n const debug = ContextMenu.DEBUG;\n if (debug) console.log('[ContextMenu] menu-item-click', { action, hasParent: !!this.parent, parentClass: this.parent?.constructor?.name });\n if (!action) return;\n\n const menuItem = this.config.items.find(item => item.action === action);\n if (!menuItem || menuItem.disabled) {\n if (debug) console.log('[ContextMenu] no matching item or disabled', { action, found: !!menuItem, disabled: menuItem?.disabled });\n return;\n }\n\n // Support for inline handlers\n if (typeof menuItem.handler === 'function') {\n if (debug) console.log('[ContextMenu] inline handler', action);\n menuItem.handler(this.context, event, element);\n } else if (this.parent && this.parent.events) {\n if (debug) console.log('[ContextMenu] dispatching to parent', { action, parentClass: this.parent.constructor?.name });\n this.parent.events.dispatch(action, event, element);\n } else if (debug) {\n console.warn('[ContextMenu] no handler and no parent.events — action lost', { action });\n }\n this.closeDropdown();\n }\n\n closeDropdown() {\n // Manual-mode close: undo what openAt did (re-parent, strip\n // .show, drop the outside-click listener). Falls through to the\n // Bootstrap path so the trigger-button case keeps working.\n const menuEl = this.element?.querySelector('.dropdown-menu')\n || this._menuOrigParent?.querySelector?.('.dropdown-menu');\n if (menuEl && menuEl.classList.contains('show') && menuEl.parentElement === document.body) {\n menuEl.classList.remove('show');\n menuEl.style.position = '';\n menuEl.style.left = '';\n menuEl.style.top = '';\n menuEl.style.transform = '';\n menuEl.style.inset = '';\n menuEl.style.margin = '';\n menuEl.style.zIndex = '';\n if (this._menuOrigParent) {\n if (this._menuOrigNextSibling && this._menuOrigNextSibling.parentElement === this._menuOrigParent) {\n this._menuOrigParent.insertBefore(menuEl, this._menuOrigNextSibling);\n } else {\n this._menuOrigParent.appendChild(menuEl);\n }\n }\n }\n if (this._outsideHandler) {\n document.removeEventListener('mousedown', this._outsideHandler, true);\n this._outsideHandler = null;\n }\n\n // Bootstrap-trigger case (visible three-dots button).\n const dropdownTrigger = this.element?.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (dropdownTrigger) {\n const dropdownInstance = window.bootstrap?.Dropdown.getInstance(dropdownTrigger);\n dropdownInstance?.hide();\n }\n }\n\n /**\n * Open the menu at viewport coordinates (x, y), without needing a\n * visible trigger button. Used for right-click / programmatic patterns.\n *\n * Implementation note — we deliberately do NOT delegate positioning to\n * Bootstrap's Dropdown / Popper here. Two reasons:\n * 1. Hosts often mount the ContextMenu inside a wrapper that hides\n * the (otherwise visible) trigger button. Wrappers like\n * `.visually-hidden` apply `clip: rect(0,0,0,0)` and\n * `overflow: hidden`, which clip the popped-out menu's paint even\n * though Popper places it correctly.\n * 2. Portal layouts paint sidebars/offcanvas at z-index 1050 above\n * the dropdown's default 1000.\n * Re-parenting the menu to `document.body` and pinning it with\n * `position: fixed` + a high z-index sidesteps both.\n *\n * @param {number} x - Viewport X coordinate (e.g. event.clientX)\n * @param {number} y - Viewport Y coordinate (e.g. event.clientY)\n * @param {*} [contextItem] - Optional context to attach for the handler/dispatch path\n * @returns {Promise<this>}\n */\n async openAt(x, y, contextItem) {\n if (typeof contextItem !== 'undefined') {\n this.context = contextItem;\n }\n\n // Make sure the menu is rendered and in the DOM.\n if (!this.isMounted()) {\n if (!this.parent && !this.containerId && !this.container) {\n this.options.allowAppendToBody = true;\n }\n await this.render();\n }\n\n const menuEl = this.element?.querySelector('.dropdown-menu');\n if (!menuEl) return this;\n\n // Re-parent the menu to <body> on first openAt so no host wrapper\n // can clip or stack above it. Stash the original parent so we can\n // re-attach on close (keeps the DOM tidy and the View's children\n // model coherent).\n if (menuEl.parentElement !== document.body) {\n this._menuOrigParent = menuEl.parentElement;\n this._menuOrigNextSibling = menuEl.nextSibling;\n document.body.appendChild(menuEl);\n }\n\n // Manual positioning + visibility — no Popper involvement.\n // NOTE: `inset` is shorthand for top/right/bottom/left, so set it\n // FIRST and then override left/top — otherwise inset:auto would\n // clobber the coordinates we just assigned.\n menuEl.style.transform = 'none';\n menuEl.style.inset = 'auto';\n menuEl.style.position = 'fixed';\n menuEl.style.left = `${x}px`;\n menuEl.style.top = `${y}px`;\n menuEl.style.margin = '0';\n menuEl.style.zIndex = '1080';\n menuEl.classList.add('show');\n\n // The menu is now a document.body child, so its `data-action`\n // clicks no longer bubble up to `this.element` where the View's\n // EventDelegate listens. Wire menu-item dispatch directly.\n if (this._menuClickHandler) {\n menuEl.removeEventListener('click', this._menuClickHandler);\n }\n this._menuClickHandler = (ev) => {\n const item = ev.target.closest('[data-action=\"menu-item-click\"]');\n if (!item) return;\n this.onActionMenuItemClick(ev, item);\n };\n menuEl.addEventListener('click', this._menuClickHandler);\n\n // Click/contextmenu outside closes the menu. We listen on the next\n // tick so the contextmenu event that opened us doesn't immediately\n // close us.\n if (this._outsideHandler) {\n document.removeEventListener('mousedown', this._outsideHandler, true);\n }\n this._outsideHandler = (ev) => {\n if (!menuEl.contains(ev.target)) this.closeDropdown();\n };\n setTimeout(() => {\n document.addEventListener('mousedown', this._outsideHandler, true);\n }, 0);\n\n return this;\n }\n\n /**\n * Wire a `contextmenu` (right-click) event on `element` to open a\n * ContextMenu at the cursor. Returns the ContextMenu instance so\n * callers can keep a handle for cleanup or further configuration.\n *\n * Two ways to supply the menu:\n * 1. Pass a pre-built ContextMenu via `menuOptions.menu`.\n * 2. Pass a plain options object (config / context / etc.) and a\n * fresh ContextMenu will be constructed.\n *\n * @param {HTMLElement} element - The element that should respond to right-click\n * @param {Function} getContextItem - Callback invoked with the contextmenu event;\n * the return value is stored on `menu.context` for the dispatch path.\n * @param {object} [menuOptions] - Either a ContextMenu options object\n * (`{ config, context, ... }`) or `{ menu: existingContextMenuInstance }`.\n * @returns {ContextMenu} The ContextMenu instance bound to the element.\n */\n static attachToRightClick(element, getContextItem, menuOptions = {}) {\n if (!element) {\n throw new Error('ContextMenu.attachToRightClick: element is required');\n }\n\n const menu = menuOptions.menu instanceof ContextMenu\n ? menuOptions.menu\n : new ContextMenu(menuOptions);\n\n const handler = (event) => {\n event.preventDefault();\n const contextItem = typeof getContextItem === 'function'\n ? getContextItem(event)\n : getContextItem;\n menu.openAt(event.clientX, event.clientY, contextItem);\n };\n\n element.addEventListener('contextmenu', handler);\n\n // Stash the handler so callers can remove it if they need to.\n menu._rightClickHandler = handler;\n menu._rightClickElement = element;\n\n return menu;\n }\n\n /**\n * Detach a previously attached right-click handler. Safe to call\n * multiple times. Does not destroy the ContextMenu itself.\n */\n detachRightClick() {\n if (this._rightClickElement && this._rightClickHandler) {\n this._rightClickElement.removeEventListener('contextmenu', this._rightClickHandler);\n this._rightClickElement = null;\n this._rightClickHandler = null;\n }\n return this;\n }\n}\n\nexport default ContextMenu;\n"],"names":["Page","View","constructor","options","tagName","className","pageName","id","toLowerCase","replace","super","this","route","title","pageIcon","icon","displayName","pageDescription","params","query","matched","isActive","pageOptions","description","requiresAuth","savedState","onParams","canEnter","permissions","user","getApp","activeUser","hasPermission","requiresGroup","activeGroup","onEnter","_wasExited","onInitView","restoreState","document","emit","page","getMetadata","onExit","captureState","_clearScheduledRefreshes","scheduleRefresh","handler","intervalMs","_scheduledRefreshes","safe","async","err","console","warn","immediate","setInterval","entry","tier","push","cancel","clearInterval","filter","e","runScheduledRefreshes","list","targets","Promise","allSettled","map","render","allowMount","container","onGroupChange","group","name","onActionDefault","action","makeActive","showPage","onActionNavigate","event","element","preventDefault","dataset","scrollTop","formData","captureFormData","custom","captureCustomState","state","restoreFormData","restoreCustomState","data","querySelectorAll","forEach","field","type","checked","value","Object","entries","querySelector","radio","setMeta","meta","descMeta","createElement","head","appendChild","content","key","metaEl","showError","message","errorDiv","innerHTML","insertBefore","firstChild","setTimeout","parentNode","removeChild","showSuccess","successDiv","onBeforeRender","onAfterMount","body","classList","add","onBeforeDestroy","remove","navigate","app","router","window","MOJO","error","getRoute","startsWith","substring","syncUrl","force","updateBrowserUrl","trigger","define","definition","DefinedPage","template","ContextMenu","static","config","contextMenu","context","onAfterRender","Dropdown","bootstrap","getOrCreateInstance","renderTemplate","menuItems","items","length","triggerIcon","buttonClass","dropdownId","item","buildMenuItemHTML","join","separator","label","itemClass","danger","disabled","href","target","onActionMenuItemClick","getAttribute","debug","DEBUG","parent","menuItem","find","events","dispatch","closeDropdown","menuEl","_menuOrigParent","contains","parentElement","style","position","left","top","transform","inset","margin","zIndex","_menuOrigNextSibling","_outsideHandler","removeEventListener","dropdownTrigger","dropdownInstance","getInstance","hide","openAt","x","y","contextItem","isMounted","containerId","allowAppendToBody","nextSibling","_menuClickHandler","ev","closest","addEventListener","attachToRightClick","getContextItem","menuOptions","Error","menu","clientX","clientY","_rightClickHandler","_rightClickElement","detachRightClick"],"mappings":"uCAWA,MAAMA,aAAaC,EACjB,WAAAC,CAAYC,EAAU,IAEpBA,EAAQC,QAAUD,EAAQC,SAAW,OACrCD,EAAQE,UAAYF,EAAQE,WAAa,YAGzC,MAAMC,EAAWH,EAAQG,UAAY,GACjCA,IAAaH,EAAQI,KACvBJ,EAAQI,GAAK,QAAUD,EAASE,cAAcC,QAAQ,OAAQ,MAGhEC,MAAMP,GAGNQ,KAAKL,SAAWH,EAAQG,UAAYK,KAAKT,YAAYI,UAAY,GACjEK,KAAKC,MAAQT,EAAQS,OAASD,KAAKT,YAAYU,OAAS,GACxDD,KAAKE,MAAQV,EAAQU,OAASF,KAAKL,UAAY,GAG1CK,KAAKJ,KAAMI,KAAKT,YAAYI,UAAaH,EAAQG,WACpDK,KAAKJ,GAAK,QAAUI,KAAKT,YAAYI,SAASE,cAAcC,QAAQ,OAAQ,MAI9EE,KAAKG,SAAWX,EAAQY,MAAQZ,EAAQW,UAAYH,KAAKT,YAAYY,UAAY,kBACjFH,KAAKK,YAAcb,EAAQa,aAAeL,KAAKT,YAAYc,aAAeL,KAAKL,UAAY,GAC3FK,KAAKM,gBAAkBd,EAAQc,iBAAmBN,KAAKT,YAAYe,iBAAmB,GAGtFN,KAAKO,OAAS,CAAA,EACdP,KAAKQ,MAAQ,CAAA,EACbR,KAAKS,SAAU,EACfT,KAAKU,UAAW,EAGhBV,KAAKW,YAAc,CACjBT,MAAOV,EAAQU,OAASF,KAAKL,UAAY,gBACzCiB,YAAapB,EAAQoB,aAAe,GACpCC,aAAcrB,EAAQqB,eAAgB,KACnCrB,EAAQmB,aAIbX,KAAKc,WAAa,KAEEd,KAAKL,SAAoCK,KAAKC,KACpE,CAOA,cAAMc,CAASR,EAAS,GAAIC,EAAQ,CAAA,GAIlCR,KAAKO,OAASA,EACdP,KAAKQ,MAAQA,CAOf,CAEA,QAAAQ,GACE,GAAIhB,KAAKR,QAAQyB,YAAa,CAC5B,MAAMC,EAAOlB,KAAKmB,SAASC,WAC3B,IAAKF,IAASA,EAAKG,cAAcrB,KAAKR,QAAQyB,aAC5C,OAAO,CAEX,CACA,QAAIjB,KAAKR,QAAQ8B,gBAAkBtB,KAAKmB,SAASI,YAInD,CAMA,aAAMC,GACJxB,KAAKU,UAAW,EAChBV,KAAKyB,YAAa,QACZzB,KAAK0B,aAGP1B,KAAKc,aACPd,KAAK2B,aAAa3B,KAAKc,YACvBd,KAAKc,WAAa,MAIhBd,KAAKW,aAAeX,KAAKW,YAAYT,OAA6B,oBAAb0B,WACvDA,SAAS1B,MAAQF,KAAKW,YAAYT,OAIpCF,KAAK6B,KAAK,YAAa,CACrBC,KAAM9B,KAAK+B,gBAGO/B,KAAKL,QAC3B,CAMA,YAAMqC,GAEJhC,KAAKc,WAAad,KAAKiC,eACvBjC,KAAKU,UAAW,EAChBV,KAAKyB,YAAa,EAGlBzB,KAAKkC,2BAGLlC,KAAK6B,KAAK,cAAe,CACvBC,KAAM9B,KAAK+B,gBAEO/B,KAAKL,QAC3B,CAoBA,eAAAwC,CAAgBC,EAASC,EAAY7C,EAAU,CAAA,GAE7C,GADKQ,KAAKsC,sBAAqBtC,KAAKsC,oBAAsB,IACnC,mBAAZF,KAA4BC,EAAa,GAAI,OAAO,KAE/D,MAAME,EAAOC,UACX,UAAYJ,GAAW,OAChBK,GAAOC,QAAQC,KAAK,SAAS3C,KAAKL,2CAA4C8C,EAAM,GAGzFjD,EAAQoD,WAAWL,IACvB,MAAM3C,EAAKiD,YAAYN,EAAMF,GACvBS,EAAQ,CAAElD,KAAImD,KAAMvD,EAAQuD,MAAQ,KAAMX,QAASG,EAAMF,cAG/D,OAFArC,KAAKsC,oBAAoBU,KAAKF,GAEvB,CACLG,OAAQ,KACNC,cAActD,GACdI,KAAKsC,qBAAuBtC,KAAKsC,qBAAuB,IAAIa,OAAOC,GAAKA,IAAMN,IAGpF,CAMA,2BAAMO,CAAsBN,EAAO,MACjC,MAAMO,EAAOtD,KAAKsC,qBAAuB,GACnCiB,EAAUR,EAAOO,EAAKH,UAAYC,EAAEL,OAASA,GAAQO,QACrDE,QAAQC,WAAWF,EAAQG,OAASN,EAAEhB,WAC9C,CAEA,wBAAAF,GACE,GAAKlC,KAAKsC,oBAAV,CACA,IAAA,MAAWQ,KAAS9C,KAAKsC,oBACvBY,cAAcJ,EAAMlD,IAEtBI,KAAKsC,oBAAsB,EAJI,CAKjC,CAcA,YAAMqB,CAAOC,GAAa,EAAMC,EAAY,MAC1C,OAAI7D,KAAKyB,aAAezB,KAAKU,SACpBV,KAEFD,MAAM4D,OAAOC,EAAYC,EAClC,CAyBA,mBAAMC,CAAcC,GAEpB,CAMA,WAAAhC,GACE,MAAO,CACLiC,KAAMhE,KAAKL,SACXU,YAAaL,KAAKK,aAAeL,KAAKL,SACtCS,KAAMJ,KAAKG,SACXS,YAAaZ,KAAKM,gBAClBL,MAAOD,KAAKC,MACZS,SAAUV,KAAKU,SAEnB,CAKA,qBAAMuD,CAAgBC,GACyClE,KAAKL,QACpE,CAEA,gBAAMwE,GACFnE,KAAKmB,SAASiD,SAASpE,KAC3B,CAEA,sBAAMqE,CAAiBC,EAAOC,GAC1BD,EAAME,iBACN,MAAM1C,EAAOyC,EAAQE,QAAQ3C,KAC7B9B,KAAKmB,SAASiD,SAAStC,EAC3B,CAMA,YAAAG,GACE,OAAKjC,KAAKuE,QAEH,CACLG,UAAW1E,KAAKuE,QAAQG,UACxBC,SAAU3E,KAAK4E,kBACfC,OAAQ7E,KAAK8E,sBALW,IAO5B,CAMA,YAAAnD,CAAaoD,GACNA,GAAU/E,KAAKuE,UAEpBvE,KAAKuE,QAAQG,UAAYK,EAAML,WAAa,EAC5C1E,KAAKgF,gBAAgBD,EAAMJ,UACvBI,EAAMF,QACR7E,KAAKiF,mBAAmBF,EAAMF,QAElC,CAMA,eAAAD,GACE,MAAMM,EAAO,CAAA,EACb,OAAKlF,KAAKuE,SAEVvE,KAAKuE,QAAQY,iBAAiB,2BAA2BC,QAAQC,IAC3DA,EAAMrB,OACW,aAAfqB,EAAMC,KACRJ,EAAKG,EAAMrB,MAAQqB,EAAME,QACD,UAAfF,EAAMC,KACXD,EAAME,UACRL,EAAKG,EAAMrB,MAAQqB,EAAMG,OAG3BN,EAAKG,EAAMrB,MAAQqB,EAAMG,SAKxBN,GAhBmBA,CAiB5B,CAMA,eAAAF,CAAgBL,GACTA,GAAa3E,KAAKuE,SAEvBkB,OAAOC,QAAQf,GAAUS,QAAQ,EAAEpB,EAAMwB,MACvC,MAAMH,EAAQrF,KAAKuE,QAAQoB,cAAc,UAAU3B,OACnD,GAAIqB,EACF,GAAmB,aAAfA,EAAMC,KACRD,EAAME,QAAUC,OAClB,GAA0B,UAAfH,EAAMC,KAAkB,CACjC,MAAMM,EAAQ5F,KAAKuE,QAAQoB,cAAc,UAAU3B,cAAiBwB,OAChEI,MAAaL,SAAU,EAC7B,MACEF,EAAMG,MAAQA,GAItB,CAMA,kBAAAV,GACE,MAAO,CAAA,CACT,CAMA,kBAAAG,CAAmBF,GAEnB,CAQA,OAAAc,CAAQC,EAAO,IACb,GAAwB,oBAAblE,SAAX,CAWA,GANIkE,EAAK5F,QACP0B,SAAS1B,MAAQ4F,EAAK5F,MACtBF,KAAKW,YAAYT,MAAQ4F,EAAK5F,OAI5B4F,EAAKlF,YAAa,CACpB,IAAImF,EAAWnE,SAAS+D,cAAc,4BACjCI,IACHA,EAAWnE,SAASoE,cAAc,QAClCD,EAAS/B,KAAO,cAChBpC,SAASqE,KAAKC,YAAYH,IAE5BA,EAASI,QAAUL,EAAKlF,YACxBZ,KAAKW,YAAYC,YAAckF,EAAKlF,WACtC,CAGA6E,OAAOC,QAAQI,GAAMV,QAAQ,EAAEgB,EAAKZ,MAClC,GAAY,UAARY,GAA2B,gBAARA,EAAuB,CAC5C,IAAIC,EAASzE,SAAS+D,cAAc,cAAcS,OAC7CC,IACHA,EAASzE,SAASoE,cAAc,QAChCK,EAAOrC,KAAOoC,EACdxE,SAASqE,KAAKC,YAAYG,IAE5BA,EAAOF,QAAUX,CACnB,GA9BF,CAgCF,CAOA,SAAAc,CAAUC,GAIR,GAHAxG,MAAMuG,UAAUC,GAGZvG,KAAKuE,QAAS,CAEhB,MAAMiC,EAAW5E,SAASoE,cAAc,OACxCQ,EAAS9G,UAAY,iDACrB8G,EAASC,UAAY,aACjBF,kHAKJvG,KAAKuE,QAAQmC,aAAaF,EAAUxG,KAAKuE,QAAQoC,YAGjDC,WAAW,KACLJ,EAASK,YACXL,EAASK,WAAWC,YAAYN,IAEjC,IACL,CACF,CAMA,WAAAO,CAAYR,GAIV,GAHAxG,MAAMgH,YAAYR,GAGdvG,KAAKuE,QAAS,CAChB,MAAMyC,EAAapF,SAASoE,cAAc,OAC1CgB,EAAWtH,UAAY,kDACvBsH,EAAWP,UAAY,aACnBF,kHAKJvG,KAAKuE,QAAQmC,aAAaM,EAAYhH,KAAKuE,QAAQoC,YAGnDC,WAAW,KACLI,EAAWH,YACbG,EAAWH,WAAWC,YAAYE,IAEnC,IACL,CACF,CAKA,oBAAMC,SACElH,MAAMkH,iBAGZjH,KAAK6F,QAAQ,CACX3F,MAAOF,KAAKW,YAAYT,MACxBU,YAAaZ,KAAKW,YAAYC,aAElC,CAKA,kBAAMsG,SACEnH,MAAMmH,eAGY,oBAAbtF,UAA4B5B,KAAKL,UAC1CiC,SAASuF,KAAKC,UAAUC,IAAI,QAAQrH,KAAKL,SAASE,cAAcC,QAAQ,OAAQ,OAEpF,CAKA,qBAAMwH,SACEvH,MAAMuH,kBAGY,oBAAb1F,UAA4B5B,KAAKL,UAC1CiC,SAASuF,KAAKC,UAAUG,OAAO,QAAQvH,KAAKL,SAASE,cAAcC,QAAQ,OAAQ,OAEvF,CAQA,QAAA0H,CAASvH,EAAOM,EAAS,CAAA,EAAIf,EAAU,CAAA,GAErC,OAAIQ,KAAKyH,KAAOzH,KAAKyH,IAAIC,OAChB1H,KAAKyH,IAAIC,OAAOF,SAASvH,EAAOT,GAInB,oBAAXmI,QAA0BA,OAAOC,MAAMF,OACzCC,OAAOC,KAAKF,OAAOF,SAASvH,EAAOT,QAG5CkD,QAAQmF,MAAM,qCAChB,CAEA,QAAAC,GACI,GAAI9H,KAAKC,MAAO,CACZ,IAAIA,EAAQD,KAAKC,MAIjB,MAHqB,iBAAVA,GAAsBA,EAAM8H,WAAW,OAC9C9H,EAAQA,EAAM+H,UAAU,IAErB/H,CACX,CACA,OAAOD,KAAKL,QAChB,CAEA,OAAAsI,CAAQC,GAAQ,GACZlI,KAAKmI,iBAAiBnI,KAAKQ,OAAO,GAAO,EAC7C,CAEA,gBAAA2H,CAAiB3H,EAAQ,KAAMV,GAAU,EAAOsI,GAAU,GACxDpI,KAAKmB,SAILnB,KAAKyH,IAAIC,OAAOS,iBAAiBnI,KAAK8H,WAAYtH,EAAOV,EAASsI,EACpE,CAOA,aAAOC,CAAOC,GACZ,MAAMC,oBAAoBlJ,KACxB,WAAAE,CAAYC,EAAU,IACpBO,MAAM,IACDuI,KACA9I,GAEP,EAQF,OAJA+I,YAAYC,SAAWF,EAAWE,SAClCD,YAAY5I,SAAW2I,EAAW3I,SAClC4I,YAAYtI,MAAQqI,EAAWrI,MAExBsI,WACT,EC1gBF,MAAME,oBAAoBnJ,EAMtBoJ,cAAe,EAEf,WAAAnJ,CAAYC,EAAU,IAClBO,MAAM,CACFN,QAAS,MACTC,UAAW,gCACRF,IAGPQ,KAAK2I,OAASnJ,EAAQoJ,aAAepJ,EAAQmJ,QAAU,CAAA,EACvD3I,KAAK6I,QAAUrJ,EAAQqJ,SAAW,CAAA,CACtC,CASA,mBAAMC,GAEF,SADM/I,MAAM+I,iBACP9I,KAAKuE,QAAS,OACnB,MAAMwE,EAAWpB,OAAOqB,WAAWD,SACnC,IAAKA,EAAU,OACf,MAAMX,EAAUpI,KAAKuE,QAAQoB,cAAc,+BACvCyC,GAASW,EAASE,oBAAoBb,EAC9C,CAKA,oBAAMc,GACF,MAAMC,EAAYnJ,KAAK2I,OAAOS,OAAS,GACvC,GAAyB,IAArBD,EAAUE,OACV,MAAO,GAGX,MAAMC,EAActJ,KAAK2I,OAAOvI,MAAQ,gBAClCmJ,EAAcvJ,KAAK2I,OAAOY,aAAe,kDACzCC,EAAa,gBAAgBxJ,KAAKJ,KAIxC,MAAO,gCACc2J,wBAAkCC,kFACnCF,4GAE+CE,wBAN7CL,EAAUzF,IAAI+F,GAAQzJ,KAAK0J,kBAAkBD,IAAOE,KAAK,kCAUnF,CAOA,iBAAAD,CAAkBD,GACd,GAAkB,YAAdA,EAAKnE,MAAsBmE,EAAKG,UAChC,MAAO,yCAGX,MAAMxJ,EAAOqJ,EAAKrJ,KAAO,aAAaqJ,EAAKrJ,kBAAoB,GACzDyJ,EAAQJ,EAAKI,OAAS,GACtBC,EAAY,iBAAiBL,EAAKM,OAAS,cAAgB,MAAMN,EAAKO,SAAW,WAAa,KAC9F9F,EAASuF,EAAKvF,QAAU,GAE9B,OAAIuF,EAAKQ,KACE,iBAAiBH,YAAoBL,EAAKQ,iBAAiBR,EAAKS,QAAU,YAAY9J,IAAOyJ,aAGjG,iBAAiBC,+DAAuE5F,MAAW9D,IAAOyJ,YACrH,CAOA,2BAAMM,CAAsB7F,EAAOC,GAC/BD,EAAME,iBACN,MAAMN,EAASK,EAAQ6F,aAAa,oBAC9BC,EAAQ5B,YAAY6B,MAE1B,GADID,IAA2ErK,KAAKuK,OAAqBvK,KAAKuK,SACzGrG,EAAQ,OAEb,MAAMsG,EAAWxK,KAAK2I,OAAOS,MAAMqB,KAAKhB,GAAQA,EAAKvF,SAAWA,GAC3DsG,IAAYA,EAASR,WAMM,mBAArBQ,EAASpI,QAEhBoI,EAASpI,QAAQpC,KAAK6I,QAASvE,EAAOC,GAC/BvE,KAAKuK,QAAUvK,KAAKuK,OAAOG,QAC9BL,GAAiFrK,KAAKuK,OAAOhL,YACjGS,KAAKuK,OAAOG,OAAOC,SAASzG,EAAQI,EAAOC,IACpC8F,GACP3H,QAAQC,KAAK,8DAA+D,CAAEuB,WAElFlE,KAAK4K,gBACT,CAEA,aAAAA,GAII,MAAMC,EAAS7K,KAAKuE,SAASoB,cAAc,mBACpC3F,KAAK8K,iBAAiBnF,gBAAgB,kBACzCkF,GAAUA,EAAOzD,UAAU2D,SAAS,SAAWF,EAAOG,gBAAkBpJ,SAASuF,OACjF0D,EAAOzD,UAAUG,OAAO,QACxBsD,EAAOI,MAAMC,SAAW,GACxBL,EAAOI,MAAME,KAAO,GACpBN,EAAOI,MAAMG,IAAM,GACnBP,EAAOI,MAAMI,UAAY,GACzBR,EAAOI,MAAMK,MAAQ,GACrBT,EAAOI,MAAMM,OAAS,GACtBV,EAAOI,MAAMO,OAAS,GAClBxL,KAAK8K,kBACD9K,KAAKyL,sBAAwBzL,KAAKyL,qBAAqBT,gBAAkBhL,KAAK8K,gBAC9E9K,KAAK8K,gBAAgBpE,aAAamE,EAAQ7K,KAAKyL,sBAE/CzL,KAAK8K,gBAAgB5E,YAAY2E,KAIzC7K,KAAK0L,kBACL9J,SAAS+J,oBAAoB,YAAa3L,KAAK0L,iBAAiB,GAChE1L,KAAK0L,gBAAkB,MAI3B,MAAME,EAAkB5L,KAAKuE,SAASoB,cAAc,+BACpD,GAAIiG,EAAiB,CACjB,MAAMC,EAAmBlE,OAAOqB,WAAWD,SAAS+C,YAAYF,GAChEC,GAAkBE,MACtB,CACJ,CAuBA,YAAMC,CAAOC,EAAGC,EAAGC,QACY,IAAhBA,IACPnM,KAAK6I,QAAUsD,GAIdnM,KAAKoM,cACDpM,KAAKuK,QAAWvK,KAAKqM,aAAgBrM,KAAK6D,YAC3C7D,KAAKR,QAAQ8M,mBAAoB,SAE/BtM,KAAK2D,UAGf,MAAMkH,EAAS7K,KAAKuE,SAASoB,cAAc,kBAC3C,OAAKkF,GAMDA,EAAOG,gBAAkBpJ,SAASuF,OAClCnH,KAAK8K,gBAAkBD,EAAOG,cAC9BhL,KAAKyL,qBAAuBZ,EAAO0B,YACnC3K,SAASuF,KAAKjB,YAAY2E,IAO9BA,EAAOI,MAAMI,UAAY,OACzBR,EAAOI,MAAMK,MAAQ,OACrBT,EAAOI,MAAMC,SAAW,QACxBL,EAAOI,MAAME,KAAO,GAAGc,MACvBpB,EAAOI,MAAMG,IAAM,GAAGc,MACtBrB,EAAOI,MAAMM,OAAS,IACtBV,EAAOI,MAAMO,OAAS,OACtBX,EAAOzD,UAAUC,IAAI,QAKjBrH,KAAKwM,mBACL3B,EAAOc,oBAAoB,QAAS3L,KAAKwM,mBAE7CxM,KAAKwM,kBAAqBC,IACtB,MAAMhD,EAAOgD,EAAGvC,OAAOwC,QAAQ,mCAC1BjD,GACLzJ,KAAKmK,sBAAsBsC,EAAIhD,IAEnCoB,EAAO8B,iBAAiB,QAAS3M,KAAKwM,mBAKlCxM,KAAK0L,iBACL9J,SAAS+J,oBAAoB,YAAa3L,KAAK0L,iBAAiB,GAEpE1L,KAAK0L,gBAAmBe,IACf5B,EAAOE,SAAS0B,EAAGvC,cAAcU,iBAE1ChE,WAAW,KACPhF,SAAS+K,iBAAiB,YAAa3M,KAAK0L,iBAAiB,IAC9D,GAEI1L,MAnDaA,IAoDxB,CAmBA,yBAAO4M,CAAmBrI,EAASsI,EAAgBC,EAAc,CAAA,GAC7D,IAAKvI,EACD,MAAM,IAAIwI,MAAM,uDAGpB,MAAMC,EAAOF,EAAYE,gBAAgBvE,YACnCqE,EAAYE,KACZ,IAAIvE,YAAYqE,GAEhB1K,EAAWkC,IACbA,EAAME,iBACN,MAAM2H,EAAwC,mBAAnBU,EACrBA,EAAevI,GACfuI,EACNG,EAAKhB,OAAO1H,EAAM2I,QAAS3I,EAAM4I,QAASf,IAS9C,OANA5H,EAAQoI,iBAAiB,cAAevK,GAGxC4K,EAAKG,mBAAqB/K,EAC1B4K,EAAKI,mBAAqB7I,EAEnByI,CACX,CAMA,gBAAAK,GAMI,OALIrN,KAAKoN,oBAAsBpN,KAAKmN,qBAChCnN,KAAKoN,mBAAmBzB,oBAAoB,cAAe3L,KAAKmN,oBAChEnN,KAAKoN,mBAAqB,KAC1BpN,KAAKmN,mBAAqB,MAEvBnN,IACX"}
1
+ {"version":3,"file":"ContextMenu-BL_sdgIw.js","sources":["../../src/core/Page.js","../../src/core/views/feedback/ContextMenu.js"],"sourcesContent":["/**\n * Page - Extends View with routing capabilities for MOJO framework\n * Handles URL routing, parameters, and page-specific actions\n *\n * Event Emitter notes:\n * - Uses EventEmitter via View base class.\n * - Use .emit/.on/.off/.once for all custom events.\n */\n\nimport View from '@core/View.js';\n\nclass Page extends View {\n constructor(options = {}) {\n // Set default tag name for pages\n options.tagName = options.tagName || 'main';\n options.className = options.className || 'mojo-page';\n\n // Set page ID based on page name\n const pageName = options.pageName || '';\n if (pageName && !options.id) {\n options.id = 'page_' + pageName.toLowerCase().replace(/\\s+/g, '_');\n }\n\n super(options);\n\n // Core page properties from design doc\n this.pageName = options.pageName || this.constructor.pageName || '';\n this.route = options.route || this.constructor.route || '';\n this.title = options.title || this.pageName || '';\n\n // Set page ID if not already set and we have a page_name from constructor\n if (!this.id && this.constructor.pageName && !options.pageName) {\n this.id = 'page_' + this.constructor.pageName.toLowerCase().replace(/\\s+/g, '_');\n }\n\n // Page metadata for event system\n this.pageIcon = options.icon || options.pageIcon || this.constructor.pageIcon || 'bi bi-file-text';\n this.displayName = options.displayName || this.constructor.displayName || this.pageName || '';\n this.pageDescription = options.pageDescription || this.constructor.pageDescription || '';\n\n // Routing state\n this.params = {};\n this.query = {};\n this.matched = false;\n this.isActive = false;\n\n // Page-specific options\n this.pageOptions = {\n title: options.title || this.pageName || 'Untitled Page',\n description: options.description || '',\n requiresAuth: options.requiresAuth || false,\n ...options.pageOptions\n };\n\n // State preservation\n this.savedState = null;\n\n console.log(`Page ${this.pageName} constructed with route: ${this.route}`);\n }\n\n /**\n * Handle route parameters - from design doc\n * @param {object} params - Route parameters\n * @param {object} query - Query string parameters\n */\n async onParams(params = {}, query = {}) {\n // const paramsChanged = JSON.stringify(params) !== JSON.stringify(this.params);\n // const queryChanged = JSON.stringify(query) !== JSON.stringify(this.query);\n\n this.params = params;\n this.query = query;\n\n // Only re-render if params actually changed and page is active\n // if (this.isActive && (paramsChanged || queryChanged)) {\n // console.log(`Page ${this.pageName} params changed, re-rendering`);\n // await this.render();\n // }\n }\n\n canEnter() {\n if (this.options.permissions) {\n const user = this.getApp().activeUser;\n if (!user || !user.hasPermission(this.options.permissions)) {\n return false;\n }\n }\n if (this.options.requiresGroup && !this.getApp().activeGroup) {\n return false;\n }\n return true;\n }\n\n /**\n * Called when entering this page (before render)\n * Override this method for initialization logic\n */\n async onEnter() {\n this.isActive = true;\n this._wasExited = false;\n await this.onInitView();\n\n // Restore saved state if exists\n if (this.savedState) {\n this.restoreState(this.savedState);\n this.savedState = null;\n }\n\n // Set page title if provided\n if (this.pageOptions && this.pageOptions.title && typeof document !== 'undefined') {\n document.title = this.pageOptions.title;\n }\n\n // Emit activation event\n this.emit('activated', {\n page: this.getMetadata()\n });\n\n console.log(`Page ${this.pageName} entered`);\n }\n\n /**\n * Called when leaving this page (before cleanup)\n * Override this method for cleanup logic like removing listeners, clearing timers, etc.\n */\n async onExit() {\n // Save state before exit\n this.savedState = this.captureState();\n this.isActive = false;\n this._wasExited = true;\n\n // Auto-clear any intervals registered via scheduleRefresh()\n this._clearScheduledRefreshes();\n\n // Emit deactivation event\n this.emit('deactivated', {\n page: this.getMetadata()\n });\n console.log(`Page ${this.pageName} exiting`);\n }\n\n /**\n * Register a recurring handler that auto-clears on page exit.\n * Use this instead of hand-rolling setInterval / clearInterval pairs\n * across every dashboard page.\n *\n * Multiple cadences are supported by calling scheduleRefresh multiple\n * times with different intervals. The optional `tier` is a label used\n * by `runScheduledRefreshes(tier)` to fire only handlers tagged with\n * that tier (handy for a manual \"refresh all\" button that wants to\n * call only the slow tier without waiting for it to tick).\n *\n * @param {Function} handler - Called every `intervalMs`. May return a Promise.\n * @param {number} intervalMs - Interval in milliseconds.\n * @param {object} [options]\n * @param {string} [options.tier] - Label e.g. 'fast' / 'slow'\n * @param {boolean} [options.immediate=false] - Fire once now, then start interval\n * @returns {object} A handle with `cancel()` to clear before exit if needed.\n */\n scheduleRefresh(handler, intervalMs, options = {}) {\n if (!this._scheduledRefreshes) this._scheduledRefreshes = [];\n if (typeof handler !== 'function' || !(intervalMs > 0)) return null;\n\n const safe = async () => {\n try { await handler(); }\n catch (err) { console.warn(`[Page ${this.pageName}] scheduleRefresh handler error:`, err); }\n };\n\n if (options.immediate) safe();\n const id = setInterval(safe, intervalMs);\n const entry = { id, tier: options.tier || null, handler: safe, intervalMs };\n this._scheduledRefreshes.push(entry);\n\n return {\n cancel: () => {\n clearInterval(id);\n this._scheduledRefreshes = (this._scheduledRefreshes || []).filter(e => e !== entry);\n }\n };\n }\n\n /**\n * Fire all scheduled handlers immediately. If a `tier` is given, only\n * handlers registered with that tier label run.\n */\n async runScheduledRefreshes(tier = null) {\n const list = this._scheduledRefreshes || [];\n const targets = tier ? list.filter(e => e.tier === tier) : list;\n await Promise.allSettled(targets.map(e => e.handler()));\n }\n\n _clearScheduledRefreshes() {\n if (!this._scheduledRefreshes) return;\n for (const entry of this._scheduledRefreshes) {\n clearInterval(entry.id);\n }\n this._scheduledRefreshes = [];\n }\n\n /**\n * Render guard: once a Page has been exited (onExit ran, isActive went\n * false), subsequent render() calls become no-ops until the page is\n * re-entered. Without this guard, async sources still holding a\n * reference to the page (timers, WebSocket reconnect attempts, lingering\n * promises) can call this.render() from a hidden page and overwrite\n * whatever page is currently visible in the page-container.\n *\n * The flag flips back on in onEnter via savedState restoration. The very\n * first render (before any onEnter) is allowed because savedState is null\n * and isActive is false but onExit has never set the _wasExited flag.\n */\n async render(allowMount = true, container = null) {\n if (this._wasExited && !this.isActive) {\n return this;\n }\n return super.render(allowMount, container);\n }\n\n\n /**\n * Called by PortalApp.setActiveGroup() whenever the user switches to a different group.\n *\n * Override this on any page that displays group-scoped data so the page\n * stays in sync when the active group changes. Always call super first.\n *\n * This is an MVC framework — your override should tell your Models and\n * Collections to re-fetch for the new group, then call this.render().\n * There is no framework-provided loadData() method; organise your own\n * fetch logic however you like.\n *\n * @param {Model} group - The newly active Group model.\n * this.getApp().activeGroup is already set to this value.\n *\n * @example\n * async onGroupChange(group) {\n * await super.onGroupChange(group);\n * if (!group) return;\n * await this.myCollection.fetch({ group_id: group.id });\n * await this.render();\n * }\n */\n async onGroupChange(group) {\n // Empty stub — override in subclasses that display group-scoped data.\n }\n\n /**\n * Get page metadata for display and events\n * @returns {object} Page metadata\n */\n getMetadata() {\n return {\n name: this.pageName,\n displayName: this.displayName || this.pageName,\n icon: this.pageIcon,\n description: this.pageDescription,\n route: this.route,\n isActive: this.isActive\n };\n }\n\n /**\n * Handle default action - fallback from design doc\n */\n async onActionDefault(action) {\n console.log(`Default action '${action}' triggered on page: ${this.pageName}`);\n }\n\n async makeActive() {\n this.getApp().showPage(this);\n }\n\n async onActionNavigate(event, element) {\n event.preventDefault();\n const page = element.dataset.page;\n this.getApp().showPage(page);\n }\n\n /**\n * Capture current page state for preservation\n * @returns {object|null} Captured state\n */\n captureState() {\n if (!this.element) return null;\n\n return {\n scrollTop: this.element.scrollTop,\n formData: this.captureFormData(),\n custom: this.captureCustomState()\n };\n }\n\n /**\n * Restore saved state\n * @param {object} state - State to restore\n */\n restoreState(state) {\n if (!state || !this.element) return;\n\n this.element.scrollTop = state.scrollTop || 0;\n this.restoreFormData(state.formData);\n if (state.custom) {\n this.restoreCustomState(state.custom);\n }\n }\n\n /**\n * Capture form data from page\n * @returns {object} Form data\n */\n captureFormData() {\n const data = {};\n if (!this.element) return data;\n\n this.element.querySelectorAll('input, select, textarea').forEach(field => {\n if (field.name) {\n if (field.type === 'checkbox') {\n data[field.name] = field.checked;\n } else if (field.type === 'radio') {\n if (field.checked) {\n data[field.name] = field.value;\n }\n } else {\n data[field.name] = field.value;\n }\n }\n });\n\n return data;\n }\n\n /**\n * Restore form data to page\n * @param {object} formData - Form data to restore\n */\n restoreFormData(formData) {\n if (!formData || !this.element) return;\n\n Object.entries(formData).forEach(([name, value]) => {\n const field = this.element.querySelector(`[name=\"${name}\"]`);\n if (field) {\n if (field.type === 'checkbox') {\n field.checked = value;\n } else if (field.type === 'radio') {\n const radio = this.element.querySelector(`[name=\"${name}\"][value=\"${value}\"]`);\n if (radio) radio.checked = true;\n } else {\n field.value = value;\n }\n }\n });\n }\n\n /**\n * Capture custom state - override in subclasses\n * @returns {object} Custom state\n */\n captureCustomState() {\n return {};\n }\n\n /**\n * Restore custom state - override in subclasses\n * @param {object} state - Custom state to restore\n */\n restoreCustomState(state) {\n // Override in subclasses\n }\n\n\n\n /**\n * Set page metadata\n * @param {object} meta - Metadata object\n */\n setMeta(meta = {}) {\n if (typeof document === 'undefined') {\n return;\n }\n\n // Set title\n if (meta.title) {\n document.title = meta.title;\n this.pageOptions.title = meta.title;\n }\n\n // Set description\n if (meta.description) {\n let descMeta = document.querySelector('meta[name=\"description\"]');\n if (!descMeta) {\n descMeta = document.createElement('meta');\n descMeta.name = 'description';\n document.head.appendChild(descMeta);\n }\n descMeta.content = meta.description;\n this.pageOptions.description = meta.description;\n }\n\n // Set other meta tags\n Object.entries(meta).forEach(([key, value]) => {\n if (key !== 'title' && key !== 'description') {\n let metaEl = document.querySelector(`meta[name=\"${key}\"]`);\n if (!metaEl) {\n metaEl = document.createElement('meta');\n metaEl.name = key;\n document.head.appendChild(metaEl);\n }\n metaEl.content = value;\n }\n });\n }\n\n\n /**\n * Show error message with page context\n * @param {string} message - Error message\n */\n showError(message) {\n super.showError(message);\n\n // Page-specific error display can be implemented here\n if (this.element) {\n // Example: Add error to page\n const errorDiv = document.createElement('div');\n errorDiv.className = 'alert alert-danger alert-dismissible fade show';\n errorDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n `;\n\n // Insert at top of page\n this.element.insertBefore(errorDiv, this.element.firstChild);\n\n // Auto-remove after 5 seconds\n setTimeout(() => {\n if (errorDiv.parentNode) {\n errorDiv.parentNode.removeChild(errorDiv);\n }\n }, 5000);\n }\n }\n\n /**\n * Show success message with page context\n * @param {string} message - Success message\n */\n showSuccess(message) {\n super.showSuccess(message);\n\n // Page-specific success display\n if (this.element) {\n const successDiv = document.createElement('div');\n successDiv.className = 'alert alert-success alert-dismissible fade show';\n successDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n `;\n\n // Insert at top of page\n this.element.insertBefore(successDiv, this.element.firstChild);\n\n // Auto-remove after 3 seconds\n setTimeout(() => {\n if (successDiv.parentNode) {\n successDiv.parentNode.removeChild(successDiv);\n }\n }, 3000);\n }\n }\n\n /**\n * Page-specific before render hook\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n\n // Set page metadata before rendering\n this.setMeta({\n title: this.pageOptions.title,\n description: this.pageOptions.description\n });\n }\n\n /**\n * Page-specific after mount hook\n */\n async onAfterMount() {\n await super.onAfterMount();\n\n // Add page-specific class to body\n if (typeof document !== 'undefined' && this.pageName) {\n document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\\s+/g, '-')}`);\n }\n }\n\n /**\n * Page-specific before destroy hook\n */\n async onBeforeDestroy() {\n await super.onBeforeDestroy();\n\n // Remove page-specific class from body\n if (typeof document !== 'undefined' && this.pageName) {\n document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\\s+/g, '-')}`);\n }\n }\n\n /**\n * Navigate to another page using the app's router\n * @param {string} route - Route to navigate to\n * @param {object} params - Route parameters\n * @param {object} options - Navigation options\n */\n navigate(route, params = {}, options = {}) {\n // Delegate to app's router\n if (this.app && this.app.router) {\n return this.app.router.navigate(route, options);\n }\n\n // Fallback to MOJO global router\n if (typeof window !== 'undefined' && window.MOJO?.router) {\n return window.MOJO.router.navigate(route, options);\n }\n\n console.error('No router available for navigation');\n }\n\n getRoute() {\n if (this.route) {\n let route = this.route;\n if (typeof route === 'string' && route.startsWith('/')) {\n route = route.substring(1);\n }\n return route;\n }\n return this.pageName;\n }\n\n syncUrl(force = true) {\n this.updateBrowserUrl(this.query, false, false);\n }\n\n updateBrowserUrl(query = null, replace = false, trigger = false) {\n this.getApp();\n // we need to do this to normalize the URL\n // const targetPath = this.app.buildPagePath(this, this.params, query);\n // const { pageName, queryParams } = this.app.router.parseInput(targetPath);\n this.app.router.updateBrowserUrl(this.getRoute(), query, replace, trigger);\n }\n\n /**\n * Static method to define a page class with metadata\n * @param {object} definition - Page class definition\n * @returns {class} Page class\n */\n static define(definition) {\n class DefinedPage extends Page {\n constructor(options = {}) {\n super({\n ...definition,\n ...options\n });\n }\n }\n\n // Copy static properties\n DefinedPage.template = definition.template;\n DefinedPage.pageName = definition.pageName;\n DefinedPage.route = definition.route;\n\n return DefinedPage;\n }\n}\n\nexport default Page;\n","/**\n * ContextMenu - A reusable context menu component for MOJO\n *\n * Renders a Bootstrap 5 dropdown menu based on a configuration object.\n * This component is designed to be easily embedded in any other View.\n * It supports the same configuration syntax as the Dialog's contextMenu.\n *\n * Features:\n * - Renders a dropdown button with a configurable icon.\n * - Generates menu items from a configuration array.\n * - Supports dividers, icons, labels, and links.\n * - Handles actions via inline handlers or by emitting an 'action' event.\n * - Supports right-click usage via `openAt(x, y, contextItem)` and the\n * `ContextMenu.attachToRightClick()` static helper.\n *\n * @example\n * const contextMenu = new ContextMenu({\n * config: {\n * icon: 'bi-gear', // Optional: custom trigger icon\n * items: [\n * { label: 'Edit', action: 'edit', icon: 'bi-pencil' },\n * { label: 'Delete', action: 'delete', icon: 'bi-trash', danger: true },\n * { type: 'divider' },\n * {\n * label: 'Custom Action',\n * action: 'custom',\n * icon: 'bi-star',\n * handler: (context) => {\n * console.log('Inline handler called with context:', context);\n * }\n * }\n * ]\n * },\n * context: { id: 123, name: 'My Item' } // Optional data to pass to handlers/events\n * });\n *\n * // Listen for actions from the parent view\n * contextMenu.on('action', (data) => {\n * console.log(`Action '${data.action}' triggered for context:`, data.context);\n * if (data.action === 'edit') {\n * // handle edit\n * }\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ContextMenu extends View {\n /**\n * Set `ContextMenu.DEBUG = true` from the browser console (or in a\n * downstream app's bootstrap) to trace menu-item clicks, parent\n * dispatch, and Bootstrap auto-attach. Off by default.\n */\n static DEBUG = false;\n\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'context-menu-view dropdown',\n ...options\n });\n\n this.config = options.contextMenu || options.config || {};\n this.context = options.context || {}; // Optional data to pass to handlers/events\n }\n\n /**\n * After every render, ensure the Bootstrap Dropdown instance is wired\n * to our trigger button. Bootstrap's data-API is supposed to handle\n * this via document delegation, but doesn't reliably attach to\n * dynamically rendered View markup — without this hook, clicking the\n * three-dots trigger button silently does nothing.\n */\n async onAfterRender() {\n await super.onAfterRender();\n if (!this.element) return;\n const Dropdown = window.bootstrap?.Dropdown;\n if (!Dropdown) return;\n const trigger = this.element.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (trigger) Dropdown.getOrCreateInstance(trigger);\n }\n\n /**\n * Build the dropdown menu HTML from the configuration.\n */\n async renderTemplate() {\n const menuItems = this.config.items || [];\n if (menuItems.length === 0) {\n return ''; // Don't render anything if there are no items\n }\n\n const triggerIcon = this.config.icon || 'bi-three-dots';\n const buttonClass = this.config.buttonClass || 'btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1';\n const dropdownId = `context-menu-${this.id}`;\n\n const menuItemsHtml = menuItems.map(item => this.buildMenuItemHTML(item)).join('');\n\n return `\n <button class=\"${buttonClass}\" type=\"button\" id=\"${dropdownId}\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"${triggerIcon}\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\" aria-labelledby=\"${dropdownId}\">\n ${menuItemsHtml}\n </ul>\n `;\n }\n\n /**\n * Build the HTML for a single menu item.\n * @param {object} item - The menu item configuration.\n * @returns {string} The HTML string for the list item.\n */\n buildMenuItemHTML(item) {\n if (item.type === 'divider' || item.separator) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n const icon = item.icon ? `<i class=\"${item.icon} me-2\"></i>` : '';\n const label = item.label || '';\n const itemClass = `dropdown-item ${item.danger ? 'text-danger' : ''} ${item.disabled ? 'disabled' : ''}`;\n const action = item.action || '';\n\n if (item.href) {\n return `<li><a class=\"${itemClass}\" href=\"${item.href}\" target=\"${item.target || '_self'}\">${icon}${label}</a></li>`;\n }\n\n return `<li><a class=\"${itemClass}\" href=\"#\" data-action=\"menu-item-click\" data-item-action=\"${action}\">${icon}${label}</a></li>`;\n }\n\n /**\n * Handle clicks on menu items.\n * @param {Event} event - The click event.\n * @param {HTMLElement} element - The clicked anchor element.\n */\n async onActionMenuItemClick(event, element) {\n event.preventDefault();\n const action = element.getAttribute('data-item-action');\n const debug = ContextMenu.DEBUG;\n if (debug) console.log('[ContextMenu] menu-item-click', { action, hasParent: !!this.parent, parentClass: this.parent?.constructor?.name });\n if (!action) return;\n\n const menuItem = this.config.items.find(item => item.action === action);\n if (!menuItem || menuItem.disabled) {\n if (debug) console.log('[ContextMenu] no matching item or disabled', { action, found: !!menuItem, disabled: menuItem?.disabled });\n return;\n }\n\n // Support for inline handlers\n if (typeof menuItem.handler === 'function') {\n if (debug) console.log('[ContextMenu] inline handler', action);\n menuItem.handler(this.context, event, element);\n } else if (this.parent && this.parent.events) {\n if (debug) console.log('[ContextMenu] dispatching to parent', { action, parentClass: this.parent.constructor?.name });\n this.parent.events.dispatch(action, event, element);\n } else if (debug) {\n console.warn('[ContextMenu] no handler and no parent.events — action lost', { action });\n }\n this.closeDropdown();\n }\n\n closeDropdown() {\n // Manual-mode close: undo what openAt did (re-parent, strip\n // .show, drop the outside-click listener). Falls through to the\n // Bootstrap path so the trigger-button case keeps working.\n const menuEl = this.element?.querySelector('.dropdown-menu')\n || this._menuOrigParent?.querySelector?.('.dropdown-menu');\n if (menuEl && menuEl.classList.contains('show') && menuEl.parentElement === document.body) {\n menuEl.classList.remove('show');\n menuEl.style.position = '';\n menuEl.style.left = '';\n menuEl.style.top = '';\n menuEl.style.transform = '';\n menuEl.style.inset = '';\n menuEl.style.margin = '';\n menuEl.style.zIndex = '';\n if (this._menuOrigParent) {\n if (this._menuOrigNextSibling && this._menuOrigNextSibling.parentElement === this._menuOrigParent) {\n this._menuOrigParent.insertBefore(menuEl, this._menuOrigNextSibling);\n } else {\n this._menuOrigParent.appendChild(menuEl);\n }\n }\n }\n if (this._outsideHandler) {\n document.removeEventListener('mousedown', this._outsideHandler, true);\n this._outsideHandler = null;\n }\n\n // Bootstrap-trigger case (visible three-dots button).\n const dropdownTrigger = this.element?.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (dropdownTrigger) {\n const dropdownInstance = window.bootstrap?.Dropdown.getInstance(dropdownTrigger);\n dropdownInstance?.hide();\n }\n }\n\n /**\n * Open the menu at viewport coordinates (x, y), without needing a\n * visible trigger button. Used for right-click / programmatic patterns.\n *\n * Implementation note — we deliberately do NOT delegate positioning to\n * Bootstrap's Dropdown / Popper here. Two reasons:\n * 1. Hosts often mount the ContextMenu inside a wrapper that hides\n * the (otherwise visible) trigger button. Wrappers like\n * `.visually-hidden` apply `clip: rect(0,0,0,0)` and\n * `overflow: hidden`, which clip the popped-out menu's paint even\n * though Popper places it correctly.\n * 2. Portal layouts paint sidebars/offcanvas at z-index 1050 above\n * the dropdown's default 1000.\n * Re-parenting the menu to `document.body` and pinning it with\n * `position: fixed` + a high z-index sidesteps both.\n *\n * @param {number} x - Viewport X coordinate (e.g. event.clientX)\n * @param {number} y - Viewport Y coordinate (e.g. event.clientY)\n * @param {*} [contextItem] - Optional context to attach for the handler/dispatch path\n * @returns {Promise<this>}\n */\n async openAt(x, y, contextItem) {\n if (typeof contextItem !== 'undefined') {\n this.context = contextItem;\n }\n\n // Make sure the menu is rendered and in the DOM.\n if (!this.isMounted()) {\n if (!this.parent && !this.containerId && !this.container) {\n this.options.allowAppendToBody = true;\n }\n await this.render();\n }\n\n const menuEl = this.element?.querySelector('.dropdown-menu');\n if (!menuEl) return this;\n\n // Re-parent the menu to <body> on first openAt so no host wrapper\n // can clip or stack above it. Stash the original parent so we can\n // re-attach on close (keeps the DOM tidy and the View's children\n // model coherent).\n if (menuEl.parentElement !== document.body) {\n this._menuOrigParent = menuEl.parentElement;\n this._menuOrigNextSibling = menuEl.nextSibling;\n document.body.appendChild(menuEl);\n }\n\n // Manual positioning + visibility — no Popper involvement.\n // NOTE: `inset` is shorthand for top/right/bottom/left, so set it\n // FIRST and then override left/top — otherwise inset:auto would\n // clobber the coordinates we just assigned.\n menuEl.style.transform = 'none';\n menuEl.style.inset = 'auto';\n menuEl.style.position = 'fixed';\n menuEl.style.left = `${x}px`;\n menuEl.style.top = `${y}px`;\n menuEl.style.margin = '0';\n menuEl.style.zIndex = '1080';\n menuEl.classList.add('show');\n\n // The menu is now a document.body child, so its `data-action`\n // clicks no longer bubble up to `this.element` where the View's\n // EventDelegate listens. Wire menu-item dispatch directly.\n if (this._menuClickHandler) {\n menuEl.removeEventListener('click', this._menuClickHandler);\n }\n this._menuClickHandler = (ev) => {\n const item = ev.target.closest('[data-action=\"menu-item-click\"]');\n if (!item) return;\n this.onActionMenuItemClick(ev, item);\n };\n menuEl.addEventListener('click', this._menuClickHandler);\n\n // Click/contextmenu outside closes the menu. We listen on the next\n // tick so the contextmenu event that opened us doesn't immediately\n // close us.\n if (this._outsideHandler) {\n document.removeEventListener('mousedown', this._outsideHandler, true);\n }\n this._outsideHandler = (ev) => {\n if (!menuEl.contains(ev.target)) this.closeDropdown();\n };\n setTimeout(() => {\n document.addEventListener('mousedown', this._outsideHandler, true);\n }, 0);\n\n return this;\n }\n\n /**\n * Wire a `contextmenu` (right-click) event on `element` to open a\n * ContextMenu at the cursor. Returns the ContextMenu instance so\n * callers can keep a handle for cleanup or further configuration.\n *\n * Two ways to supply the menu:\n * 1. Pass a pre-built ContextMenu via `menuOptions.menu`.\n * 2. Pass a plain options object (config / context / etc.) and a\n * fresh ContextMenu will be constructed.\n *\n * @param {HTMLElement} element - The element that should respond to right-click\n * @param {Function} getContextItem - Callback invoked with the contextmenu event;\n * the return value is stored on `menu.context` for the dispatch path.\n * @param {object} [menuOptions] - Either a ContextMenu options object\n * (`{ config, context, ... }`) or `{ menu: existingContextMenuInstance }`.\n * @returns {ContextMenu} The ContextMenu instance bound to the element.\n */\n static attachToRightClick(element, getContextItem, menuOptions = {}) {\n if (!element) {\n throw new Error('ContextMenu.attachToRightClick: element is required');\n }\n\n const menu = menuOptions.menu instanceof ContextMenu\n ? menuOptions.menu\n : new ContextMenu(menuOptions);\n\n const handler = (event) => {\n event.preventDefault();\n const contextItem = typeof getContextItem === 'function'\n ? getContextItem(event)\n : getContextItem;\n menu.openAt(event.clientX, event.clientY, contextItem);\n };\n\n element.addEventListener('contextmenu', handler);\n\n // Stash the handler so callers can remove it if they need to.\n menu._rightClickHandler = handler;\n menu._rightClickElement = element;\n\n return menu;\n }\n\n /**\n * Detach a previously attached right-click handler. Safe to call\n * multiple times. Does not destroy the ContextMenu itself.\n */\n detachRightClick() {\n if (this._rightClickElement && this._rightClickHandler) {\n this._rightClickElement.removeEventListener('contextmenu', this._rightClickHandler);\n this._rightClickElement = null;\n this._rightClickHandler = null;\n }\n return this;\n }\n}\n\nexport default ContextMenu;\n"],"names":["Page","View","constructor","options","tagName","className","pageName","id","toLowerCase","replace","super","this","route","title","pageIcon","icon","displayName","pageDescription","params","query","matched","isActive","pageOptions","description","requiresAuth","savedState","onParams","canEnter","permissions","user","getApp","activeUser","hasPermission","requiresGroup","activeGroup","onEnter","_wasExited","onInitView","restoreState","document","emit","page","getMetadata","onExit","captureState","_clearScheduledRefreshes","scheduleRefresh","handler","intervalMs","_scheduledRefreshes","safe","async","err","console","warn","immediate","setInterval","entry","tier","push","cancel","clearInterval","filter","e","runScheduledRefreshes","list","targets","Promise","allSettled","map","render","allowMount","container","onGroupChange","group","name","onActionDefault","action","makeActive","showPage","onActionNavigate","event","element","preventDefault","dataset","scrollTop","formData","captureFormData","custom","captureCustomState","state","restoreFormData","restoreCustomState","data","querySelectorAll","forEach","field","type","checked","value","Object","entries","querySelector","radio","setMeta","meta","descMeta","createElement","head","appendChild","content","key","metaEl","showError","message","errorDiv","innerHTML","insertBefore","firstChild","setTimeout","parentNode","removeChild","showSuccess","successDiv","onBeforeRender","onAfterMount","body","classList","add","onBeforeDestroy","remove","navigate","app","router","window","MOJO","error","getRoute","startsWith","substring","syncUrl","force","updateBrowserUrl","trigger","define","definition","DefinedPage","template","ContextMenu","static","config","contextMenu","context","onAfterRender","Dropdown","bootstrap","getOrCreateInstance","renderTemplate","menuItems","items","length","triggerIcon","buttonClass","dropdownId","item","buildMenuItemHTML","join","separator","label","itemClass","danger","disabled","href","target","onActionMenuItemClick","getAttribute","debug","DEBUG","parent","menuItem","find","events","dispatch","closeDropdown","menuEl","_menuOrigParent","contains","parentElement","style","position","left","top","transform","inset","margin","zIndex","_menuOrigNextSibling","_outsideHandler","removeEventListener","dropdownTrigger","dropdownInstance","getInstance","hide","openAt","x","y","contextItem","isMounted","containerId","allowAppendToBody","nextSibling","_menuClickHandler","ev","closest","addEventListener","attachToRightClick","getContextItem","menuOptions","Error","menu","clientX","clientY","_rightClickHandler","_rightClickElement","detachRightClick"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";const e=require("./View-CPWwS19u.js");class Page extends e.View{constructor(e={}){e.tagName=e.tagName||"main",e.className=e.className||"mojo-page";const t=e.pageName||"";t&&!e.id&&(e.id="page_"+t.toLowerCase().replace(/\s+/g,"_")),super(e),this.pageName=e.pageName||this.constructor.pageName||"",this.route=e.route||this.constructor.route||"",this.title=e.title||this.pageName||"",this.id||!this.constructor.pageName||e.pageName||(this.id="page_"+this.constructor.pageName.toLowerCase().replace(/\s+/g,"_")),this.pageIcon=e.icon||e.pageIcon||this.constructor.pageIcon||"bi bi-file-text",this.displayName=e.displayName||this.constructor.displayName||this.pageName||"",this.pageDescription=e.pageDescription||this.constructor.pageDescription||"",this.params={},this.query={},this.matched=!1,this.isActive=!1,this.pageOptions={title:e.title||this.pageName||"Untitled Page",description:e.description||"",requiresAuth:e.requiresAuth||!1,...e.pageOptions},this.savedState=null,this.pageName,this.route}async onParams(e={},t={}){this.params=e,this.query=t}canEnter(){if(this.options.permissions){const e=this.getApp().activeUser;if(!e||!e.hasPermission(this.options.permissions))return!1}return!(this.options.requiresGroup&&!this.getApp().activeGroup)}async onEnter(){this.isActive=!0,this._wasExited=!1,await this.onInitView(),this.savedState&&(this.restoreState(this.savedState),this.savedState=null),this.pageOptions&&this.pageOptions.title&&"undefined"!=typeof document&&(document.title=this.pageOptions.title),this.emit("activated",{page:this.getMetadata()}),this.pageName}async onExit(){this.savedState=this.captureState(),this.isActive=!1,this._wasExited=!0,this._clearScheduledRefreshes(),this.emit("deactivated",{page:this.getMetadata()}),this.pageName}scheduleRefresh(e,t,s={}){if(this._scheduledRefreshes||(this._scheduledRefreshes=[]),"function"!=typeof e||!(t>0))return null;const i=async()=>{try{await e()}catch(t){console.warn(`[Page ${this.pageName}] scheduleRefresh handler error:`,t)}};s.immediate&&i();const n=setInterval(i,t),a={id:n,tier:s.tier||null,handler:i,intervalMs:t};return this._scheduledRefreshes.push(a),{cancel:()=>{clearInterval(n),this._scheduledRefreshes=(this._scheduledRefreshes||[]).filter(e=>e!==a)}}}async runScheduledRefreshes(e=null){const t=this._scheduledRefreshes||[],s=e?t.filter(t=>t.tier===e):t;await Promise.allSettled(s.map(e=>e.handler()))}_clearScheduledRefreshes(){if(this._scheduledRefreshes){for(const e of this._scheduledRefreshes)clearInterval(e.id);this._scheduledRefreshes=[]}}async render(e=!0,t=null){return this._wasExited&&!this.isActive?this:super.render(e,t)}async onGroupChange(e){}getMetadata(){return{name:this.pageName,displayName:this.displayName||this.pageName,icon:this.pageIcon,description:this.pageDescription,route:this.route,isActive:this.isActive}}async onActionDefault(e){this.pageName}async makeActive(){this.getApp().showPage(this)}async onActionNavigate(e,t){e.preventDefault();const s=t.dataset.page;this.getApp().showPage(s)}captureState(){return this.element?{scrollTop:this.element.scrollTop,formData:this.captureFormData(),custom:this.captureCustomState()}:null}restoreState(e){e&&this.element&&(this.element.scrollTop=e.scrollTop||0,this.restoreFormData(e.formData),e.custom&&this.restoreCustomState(e.custom))}captureFormData(){const e={};return this.element?(this.element.querySelectorAll("input, select, textarea").forEach(t=>{t.name&&("checkbox"===t.type?e[t.name]=t.checked:"radio"===t.type?t.checked&&(e[t.name]=t.value):e[t.name]=t.value)}),e):e}restoreFormData(e){e&&this.element&&Object.entries(e).forEach(([e,t])=>{const s=this.element.querySelector(`[name="${e}"]`);if(s)if("checkbox"===s.type)s.checked=t;else if("radio"===s.type){const s=this.element.querySelector(`[name="${e}"][value="${t}"]`);s&&(s.checked=!0)}else s.value=t})}captureCustomState(){return{}}restoreCustomState(e){}setMeta(e={}){if("undefined"!=typeof document){if(e.title&&(document.title=e.title,this.pageOptions.title=e.title),e.description){let t=document.querySelector('meta[name="description"]');t||(t=document.createElement("meta"),t.name="description",document.head.appendChild(t)),t.content=e.description,this.pageOptions.description=e.description}Object.entries(e).forEach(([e,t])=>{if("title"!==e&&"description"!==e){let s=document.querySelector(`meta[name="${e}"]`);s||(s=document.createElement("meta"),s.name=e,document.head.appendChild(s)),s.content=t}})}}showError(e){if(super.showError(e),this.element){const t=document.createElement("div");t.className="alert alert-danger alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},5e3)}}showSuccess(e){if(super.showSuccess(e),this.element){const t=document.createElement("div");t.className="alert alert-success alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},3e3)}}async onBeforeRender(){await super.onBeforeRender(),this.setMeta({title:this.pageOptions.title,description:this.pageOptions.description})}async onAfterMount(){await super.onAfterMount(),"undefined"!=typeof document&&this.pageName&&document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}async onBeforeDestroy(){await super.onBeforeDestroy(),"undefined"!=typeof document&&this.pageName&&document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}navigate(e,t={},s={}){return this.app&&this.app.router?this.app.router.navigate(e,s):"undefined"!=typeof window&&window.MOJO?.router?window.MOJO.router.navigate(e,s):void console.error("No router available for navigation")}getRoute(){if(this.route){let e=this.route;return"string"==typeof e&&e.startsWith("/")&&(e=e.substring(1)),e}return this.pageName}syncUrl(e=!0){this.updateBrowserUrl(this.query,!1,!1)}updateBrowserUrl(e=null,t=!1,s=!1){this.getApp(),this.app.router.updateBrowserUrl(this.getRoute(),e,t,s)}static define(e){class DefinedPage extends Page{constructor(t={}){super({...e,...t})}}return DefinedPage.template=e.template,DefinedPage.pageName=e.pageName,DefinedPage.route=e.route,DefinedPage}}class ContextMenu extends e.View{static DEBUG=!1;constructor(e={}){super({tagName:"div",className:"context-menu-view dropdown",...e}),this.config=e.contextMenu||e.config||{},this.context=e.context||{}}async onAfterRender(){if(await super.onAfterRender(),!this.element)return;const e=window.bootstrap?.Dropdown;if(!e)return;const t=this.element.querySelector('[data-bs-toggle="dropdown"]');t&&e.getOrCreateInstance(t)}async renderTemplate(){const e=this.config.items||[];if(0===e.length)return"";const t=this.config.icon||"bi-three-dots",s=this.config.buttonClass||"btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1",i=`context-menu-${this.id}`;return`\n <button class="${s}" type="button" id="${i}" data-bs-toggle="dropdown" aria-expanded="false">\n <i class="${t}"></i>\n </button>\n <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="${i}">\n ${e.map(e=>this.buildMenuItemHTML(e)).join("")}\n </ul>\n `}buildMenuItemHTML(e){if("divider"===e.type||e.separator)return'<li><hr class="dropdown-divider"></li>';const t=e.icon?`<i class="${e.icon} me-2"></i>`:"",s=e.label||"",i=`dropdown-item ${e.danger?"text-danger":""} ${e.disabled?"disabled":""}`,n=e.action||"";return e.href?`<li><a class="${i}" href="${e.href}" target="${e.target||"_self"}">${t}${s}</a></li>`:`<li><a class="${i}" href="#" data-action="menu-item-click" data-item-action="${n}">${t}${s}</a></li>`}async onActionMenuItemClick(e,t){e.preventDefault();const s=t.getAttribute("data-item-action"),i=ContextMenu.DEBUG;if(i&&(this.parent,this.parent),!s)return;const n=this.config.items.find(e=>e.action===s);n&&!n.disabled&&("function"==typeof n.handler?n.handler(this.context,e,t):this.parent&&this.parent.events?(i&&this.parent.constructor,this.parent.events.dispatch(s,e,t)):i&&console.warn("[ContextMenu] no handler and no parent.events — action lost",{action:s}),this.closeDropdown())}closeDropdown(){const e=this.element?.querySelector(".dropdown-menu")||this._menuOrigParent?.querySelector?.(".dropdown-menu");e&&e.classList.contains("show")&&e.parentElement===document.body&&(e.classList.remove("show"),e.style.position="",e.style.left="",e.style.top="",e.style.transform="",e.style.inset="",e.style.margin="",e.style.zIndex="",this._menuOrigParent&&(this._menuOrigNextSibling&&this._menuOrigNextSibling.parentElement===this._menuOrigParent?this._menuOrigParent.insertBefore(e,this._menuOrigNextSibling):this._menuOrigParent.appendChild(e))),this._outsideHandler&&(document.removeEventListener("mousedown",this._outsideHandler,!0),this._outsideHandler=null);const t=this.element?.querySelector('[data-bs-toggle="dropdown"]');if(t){const e=window.bootstrap?.Dropdown.getInstance(t);e?.hide()}}async openAt(e,t,s){void 0!==s&&(this.context=s),this.isMounted()||(this.parent||this.containerId||this.container||(this.options.allowAppendToBody=!0),await this.render());const i=this.element?.querySelector(".dropdown-menu");return i?(i.parentElement!==document.body&&(this._menuOrigParent=i.parentElement,this._menuOrigNextSibling=i.nextSibling,document.body.appendChild(i)),i.style.transform="none",i.style.inset="auto",i.style.position="fixed",i.style.left=`${e}px`,i.style.top=`${t}px`,i.style.margin="0",i.style.zIndex="1080",i.classList.add("show"),this._menuClickHandler&&i.removeEventListener("click",this._menuClickHandler),this._menuClickHandler=e=>{const t=e.target.closest('[data-action="menu-item-click"]');t&&this.onActionMenuItemClick(e,t)},i.addEventListener("click",this._menuClickHandler),this._outsideHandler&&document.removeEventListener("mousedown",this._outsideHandler,!0),this._outsideHandler=e=>{i.contains(e.target)||this.closeDropdown()},setTimeout(()=>{document.addEventListener("mousedown",this._outsideHandler,!0)},0),this):this}static attachToRightClick(e,t,s={}){if(!e)throw new Error("ContextMenu.attachToRightClick: element is required");const i=s.menu instanceof ContextMenu?s.menu:new ContextMenu(s),n=e=>{e.preventDefault();const s="function"==typeof t?t(e):t;i.openAt(e.clientX,e.clientY,s)};return e.addEventListener("contextmenu",n),i._rightClickHandler=n,i._rightClickElement=e,i}detachRightClick(){return this._rightClickElement&&this._rightClickHandler&&(this._rightClickElement.removeEventListener("contextmenu",this._rightClickHandler),this._rightClickElement=null,this._rightClickHandler=null),this}}exports.ContextMenu=ContextMenu,exports.Page=Page;
2
- //# sourceMappingURL=ContextMenu-BeveGkJr.js.map
1
+ "use strict";const e=require("./View-CLWMWXbO.js");class Page extends e.View{constructor(e={}){e.tagName=e.tagName||"main",e.className=e.className||"mojo-page";const t=e.pageName||"";t&&!e.id&&(e.id="page_"+t.toLowerCase().replace(/\s+/g,"_")),super(e),this.pageName=e.pageName||this.constructor.pageName||"",this.route=e.route||this.constructor.route||"",this.title=e.title||this.pageName||"",this.id||!this.constructor.pageName||e.pageName||(this.id="page_"+this.constructor.pageName.toLowerCase().replace(/\s+/g,"_")),this.pageIcon=e.icon||e.pageIcon||this.constructor.pageIcon||"bi bi-file-text",this.displayName=e.displayName||this.constructor.displayName||this.pageName||"",this.pageDescription=e.pageDescription||this.constructor.pageDescription||"",this.params={},this.query={},this.matched=!1,this.isActive=!1,this.pageOptions={title:e.title||this.pageName||"Untitled Page",description:e.description||"",requiresAuth:e.requiresAuth||!1,...e.pageOptions},this.savedState=null,this.pageName,this.route}async onParams(e={},t={}){this.params=e,this.query=t}canEnter(){if(this.options.permissions){const e=this.getApp().activeUser;if(!e||!e.hasPermission(this.options.permissions))return!1}return!(this.options.requiresGroup&&!this.getApp().activeGroup)}async onEnter(){this.isActive=!0,this._wasExited=!1,await this.onInitView(),this.savedState&&(this.restoreState(this.savedState),this.savedState=null),this.pageOptions&&this.pageOptions.title&&"undefined"!=typeof document&&(document.title=this.pageOptions.title),this.emit("activated",{page:this.getMetadata()}),this.pageName}async onExit(){this.savedState=this.captureState(),this.isActive=!1,this._wasExited=!0,this._clearScheduledRefreshes(),this.emit("deactivated",{page:this.getMetadata()}),this.pageName}scheduleRefresh(e,t,s={}){if(this._scheduledRefreshes||(this._scheduledRefreshes=[]),"function"!=typeof e||!(t>0))return null;const i=async()=>{try{await e()}catch(t){console.warn(`[Page ${this.pageName}] scheduleRefresh handler error:`,t)}};s.immediate&&i();const n=setInterval(i,t),a={id:n,tier:s.tier||null,handler:i,intervalMs:t};return this._scheduledRefreshes.push(a),{cancel:()=>{clearInterval(n),this._scheduledRefreshes=(this._scheduledRefreshes||[]).filter(e=>e!==a)}}}async runScheduledRefreshes(e=null){const t=this._scheduledRefreshes||[],s=e?t.filter(t=>t.tier===e):t;await Promise.allSettled(s.map(e=>e.handler()))}_clearScheduledRefreshes(){if(this._scheduledRefreshes){for(const e of this._scheduledRefreshes)clearInterval(e.id);this._scheduledRefreshes=[]}}async render(e=!0,t=null){return this._wasExited&&!this.isActive?this:super.render(e,t)}async onGroupChange(e){}getMetadata(){return{name:this.pageName,displayName:this.displayName||this.pageName,icon:this.pageIcon,description:this.pageDescription,route:this.route,isActive:this.isActive}}async onActionDefault(e){this.pageName}async makeActive(){this.getApp().showPage(this)}async onActionNavigate(e,t){e.preventDefault();const s=t.dataset.page;this.getApp().showPage(s)}captureState(){return this.element?{scrollTop:this.element.scrollTop,formData:this.captureFormData(),custom:this.captureCustomState()}:null}restoreState(e){e&&this.element&&(this.element.scrollTop=e.scrollTop||0,this.restoreFormData(e.formData),e.custom&&this.restoreCustomState(e.custom))}captureFormData(){const e={};return this.element?(this.element.querySelectorAll("input, select, textarea").forEach(t=>{t.name&&("checkbox"===t.type?e[t.name]=t.checked:"radio"===t.type?t.checked&&(e[t.name]=t.value):e[t.name]=t.value)}),e):e}restoreFormData(e){e&&this.element&&Object.entries(e).forEach(([e,t])=>{const s=this.element.querySelector(`[name="${e}"]`);if(s)if("checkbox"===s.type)s.checked=t;else if("radio"===s.type){const s=this.element.querySelector(`[name="${e}"][value="${t}"]`);s&&(s.checked=!0)}else s.value=t})}captureCustomState(){return{}}restoreCustomState(e){}setMeta(e={}){if("undefined"!=typeof document){if(e.title&&(document.title=e.title,this.pageOptions.title=e.title),e.description){let t=document.querySelector('meta[name="description"]');t||(t=document.createElement("meta"),t.name="description",document.head.appendChild(t)),t.content=e.description,this.pageOptions.description=e.description}Object.entries(e).forEach(([e,t])=>{if("title"!==e&&"description"!==e){let s=document.querySelector(`meta[name="${e}"]`);s||(s=document.createElement("meta"),s.name=e,document.head.appendChild(s)),s.content=t}})}}showError(e){if(super.showError(e),this.element){const t=document.createElement("div");t.className="alert alert-danger alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},5e3)}}showSuccess(e){if(super.showSuccess(e),this.element){const t=document.createElement("div");t.className="alert alert-success alert-dismissible fade show",t.innerHTML=`\n ${e}\n <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>\n `,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},3e3)}}async onBeforeRender(){await super.onBeforeRender(),this.setMeta({title:this.pageOptions.title,description:this.pageOptions.description})}async onAfterMount(){await super.onAfterMount(),"undefined"!=typeof document&&this.pageName&&document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}async onBeforeDestroy(){await super.onBeforeDestroy(),"undefined"!=typeof document&&this.pageName&&document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}navigate(e,t={},s={}){return this.app&&this.app.router?this.app.router.navigate(e,s):"undefined"!=typeof window&&window.MOJO?.router?window.MOJO.router.navigate(e,s):void console.error("No router available for navigation")}getRoute(){if(this.route){let e=this.route;return"string"==typeof e&&e.startsWith("/")&&(e=e.substring(1)),e}return this.pageName}syncUrl(e=!0){this.updateBrowserUrl(this.query,!1,!1)}updateBrowserUrl(e=null,t=!1,s=!1){this.getApp(),this.app.router.updateBrowserUrl(this.getRoute(),e,t,s)}static define(e){class DefinedPage extends Page{constructor(t={}){super({...e,...t})}}return DefinedPage.template=e.template,DefinedPage.pageName=e.pageName,DefinedPage.route=e.route,DefinedPage}}class ContextMenu extends e.View{static DEBUG=!1;constructor(e={}){super({tagName:"div",className:"context-menu-view dropdown",...e}),this.config=e.contextMenu||e.config||{},this.context=e.context||{}}async onAfterRender(){if(await super.onAfterRender(),!this.element)return;const e=window.bootstrap?.Dropdown;if(!e)return;const t=this.element.querySelector('[data-bs-toggle="dropdown"]');t&&e.getOrCreateInstance(t)}async renderTemplate(){const e=this.config.items||[];if(0===e.length)return"";const t=this.config.icon||"bi-three-dots",s=this.config.buttonClass||"btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1",i=`context-menu-${this.id}`;return`\n <button class="${s}" type="button" id="${i}" data-bs-toggle="dropdown" aria-expanded="false">\n <i class="${t}"></i>\n </button>\n <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="${i}">\n ${e.map(e=>this.buildMenuItemHTML(e)).join("")}\n </ul>\n `}buildMenuItemHTML(e){if("divider"===e.type||e.separator)return'<li><hr class="dropdown-divider"></li>';const t=e.icon?`<i class="${e.icon} me-2"></i>`:"",s=e.label||"",i=`dropdown-item ${e.danger?"text-danger":""} ${e.disabled?"disabled":""}`,n=e.action||"";return e.href?`<li><a class="${i}" href="${e.href}" target="${e.target||"_self"}">${t}${s}</a></li>`:`<li><a class="${i}" href="#" data-action="menu-item-click" data-item-action="${n}">${t}${s}</a></li>`}async onActionMenuItemClick(e,t){e.preventDefault();const s=t.getAttribute("data-item-action"),i=ContextMenu.DEBUG;if(i&&(this.parent,this.parent),!s)return;const n=this.config.items.find(e=>e.action===s);n&&!n.disabled&&("function"==typeof n.handler?n.handler(this.context,e,t):this.parent&&this.parent.events?(i&&this.parent.constructor,this.parent.events.dispatch(s,e,t)):i&&console.warn("[ContextMenu] no handler and no parent.events — action lost",{action:s}),this.closeDropdown())}closeDropdown(){const e=this.element?.querySelector(".dropdown-menu")||this._menuOrigParent?.querySelector?.(".dropdown-menu");e&&e.classList.contains("show")&&e.parentElement===document.body&&(e.classList.remove("show"),e.style.position="",e.style.left="",e.style.top="",e.style.transform="",e.style.inset="",e.style.margin="",e.style.zIndex="",this._menuOrigParent&&(this._menuOrigNextSibling&&this._menuOrigNextSibling.parentElement===this._menuOrigParent?this._menuOrigParent.insertBefore(e,this._menuOrigNextSibling):this._menuOrigParent.appendChild(e))),this._outsideHandler&&(document.removeEventListener("mousedown",this._outsideHandler,!0),this._outsideHandler=null);const t=this.element?.querySelector('[data-bs-toggle="dropdown"]');if(t){const e=window.bootstrap?.Dropdown.getInstance(t);e?.hide()}}async openAt(e,t,s){void 0!==s&&(this.context=s),this.isMounted()||(this.parent||this.containerId||this.container||(this.options.allowAppendToBody=!0),await this.render());const i=this.element?.querySelector(".dropdown-menu");return i?(i.parentElement!==document.body&&(this._menuOrigParent=i.parentElement,this._menuOrigNextSibling=i.nextSibling,document.body.appendChild(i)),i.style.transform="none",i.style.inset="auto",i.style.position="fixed",i.style.left=`${e}px`,i.style.top=`${t}px`,i.style.margin="0",i.style.zIndex="1080",i.classList.add("show"),this._menuClickHandler&&i.removeEventListener("click",this._menuClickHandler),this._menuClickHandler=e=>{const t=e.target.closest('[data-action="menu-item-click"]');t&&this.onActionMenuItemClick(e,t)},i.addEventListener("click",this._menuClickHandler),this._outsideHandler&&document.removeEventListener("mousedown",this._outsideHandler,!0),this._outsideHandler=e=>{i.contains(e.target)||this.closeDropdown()},setTimeout(()=>{document.addEventListener("mousedown",this._outsideHandler,!0)},0),this):this}static attachToRightClick(e,t,s={}){if(!e)throw new Error("ContextMenu.attachToRightClick: element is required");const i=s.menu instanceof ContextMenu?s.menu:new ContextMenu(s),n=e=>{e.preventDefault();const s="function"==typeof t?t(e):t;i.openAt(e.clientX,e.clientY,s)};return e.addEventListener("contextmenu",n),i._rightClickHandler=n,i._rightClickElement=e,i}detachRightClick(){return this._rightClickElement&&this._rightClickHandler&&(this._rightClickElement.removeEventListener("contextmenu",this._rightClickHandler),this._rightClickElement=null,this._rightClickHandler=null),this}}exports.ContextMenu=ContextMenu,exports.Page=Page;
2
+ //# sourceMappingURL=ContextMenu-fIy3upEy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContextMenu-BeveGkJr.js","sources":["../../src/core/Page.js","../../src/core/views/feedback/ContextMenu.js"],"sourcesContent":["/**\n * Page - Extends View with routing capabilities for MOJO framework\n * Handles URL routing, parameters, and page-specific actions\n *\n * Event Emitter notes:\n * - Uses EventEmitter via View base class.\n * - Use .emit/.on/.off/.once for all custom events.\n */\n\nimport View from '@core/View.js';\n\nclass Page extends View {\n constructor(options = {}) {\n // Set default tag name for pages\n options.tagName = options.tagName || 'main';\n options.className = options.className || 'mojo-page';\n\n // Set page ID based on page name\n const pageName = options.pageName || '';\n if (pageName && !options.id) {\n options.id = 'page_' + pageName.toLowerCase().replace(/\\s+/g, '_');\n }\n\n super(options);\n\n // Core page properties from design doc\n this.pageName = options.pageName || this.constructor.pageName || '';\n this.route = options.route || this.constructor.route || '';\n this.title = options.title || this.pageName || '';\n\n // Set page ID if not already set and we have a page_name from constructor\n if (!this.id && this.constructor.pageName && !options.pageName) {\n this.id = 'page_' + this.constructor.pageName.toLowerCase().replace(/\\s+/g, '_');\n }\n\n // Page metadata for event system\n this.pageIcon = options.icon || options.pageIcon || this.constructor.pageIcon || 'bi bi-file-text';\n this.displayName = options.displayName || this.constructor.displayName || this.pageName || '';\n this.pageDescription = options.pageDescription || this.constructor.pageDescription || '';\n\n // Routing state\n this.params = {};\n this.query = {};\n this.matched = false;\n this.isActive = false;\n\n // Page-specific options\n this.pageOptions = {\n title: options.title || this.pageName || 'Untitled Page',\n description: options.description || '',\n requiresAuth: options.requiresAuth || false,\n ...options.pageOptions\n };\n\n // State preservation\n this.savedState = null;\n\n console.log(`Page ${this.pageName} constructed with route: ${this.route}`);\n }\n\n /**\n * Handle route parameters - from design doc\n * @param {object} params - Route parameters\n * @param {object} query - Query string parameters\n */\n async onParams(params = {}, query = {}) {\n // const paramsChanged = JSON.stringify(params) !== JSON.stringify(this.params);\n // const queryChanged = JSON.stringify(query) !== JSON.stringify(this.query);\n\n this.params = params;\n this.query = query;\n\n // Only re-render if params actually changed and page is active\n // if (this.isActive && (paramsChanged || queryChanged)) {\n // console.log(`Page ${this.pageName} params changed, re-rendering`);\n // await this.render();\n // }\n }\n\n canEnter() {\n if (this.options.permissions) {\n const user = this.getApp().activeUser;\n if (!user || !user.hasPermission(this.options.permissions)) {\n return false;\n }\n }\n if (this.options.requiresGroup && !this.getApp().activeGroup) {\n return false;\n }\n return true;\n }\n\n /**\n * Called when entering this page (before render)\n * Override this method for initialization logic\n */\n async onEnter() {\n this.isActive = true;\n this._wasExited = false;\n await this.onInitView();\n\n // Restore saved state if exists\n if (this.savedState) {\n this.restoreState(this.savedState);\n this.savedState = null;\n }\n\n // Set page title if provided\n if (this.pageOptions && this.pageOptions.title && typeof document !== 'undefined') {\n document.title = this.pageOptions.title;\n }\n\n // Emit activation event\n this.emit('activated', {\n page: this.getMetadata()\n });\n\n console.log(`Page ${this.pageName} entered`);\n }\n\n /**\n * Called when leaving this page (before cleanup)\n * Override this method for cleanup logic like removing listeners, clearing timers, etc.\n */\n async onExit() {\n // Save state before exit\n this.savedState = this.captureState();\n this.isActive = false;\n this._wasExited = true;\n\n // Auto-clear any intervals registered via scheduleRefresh()\n this._clearScheduledRefreshes();\n\n // Emit deactivation event\n this.emit('deactivated', {\n page: this.getMetadata()\n });\n console.log(`Page ${this.pageName} exiting`);\n }\n\n /**\n * Register a recurring handler that auto-clears on page exit.\n * Use this instead of hand-rolling setInterval / clearInterval pairs\n * across every dashboard page.\n *\n * Multiple cadences are supported by calling scheduleRefresh multiple\n * times with different intervals. The optional `tier` is a label used\n * by `runScheduledRefreshes(tier)` to fire only handlers tagged with\n * that tier (handy for a manual \"refresh all\" button that wants to\n * call only the slow tier without waiting for it to tick).\n *\n * @param {Function} handler - Called every `intervalMs`. May return a Promise.\n * @param {number} intervalMs - Interval in milliseconds.\n * @param {object} [options]\n * @param {string} [options.tier] - Label e.g. 'fast' / 'slow'\n * @param {boolean} [options.immediate=false] - Fire once now, then start interval\n * @returns {object} A handle with `cancel()` to clear before exit if needed.\n */\n scheduleRefresh(handler, intervalMs, options = {}) {\n if (!this._scheduledRefreshes) this._scheduledRefreshes = [];\n if (typeof handler !== 'function' || !(intervalMs > 0)) return null;\n\n const safe = async () => {\n try { await handler(); }\n catch (err) { console.warn(`[Page ${this.pageName}] scheduleRefresh handler error:`, err); }\n };\n\n if (options.immediate) safe();\n const id = setInterval(safe, intervalMs);\n const entry = { id, tier: options.tier || null, handler: safe, intervalMs };\n this._scheduledRefreshes.push(entry);\n\n return {\n cancel: () => {\n clearInterval(id);\n this._scheduledRefreshes = (this._scheduledRefreshes || []).filter(e => e !== entry);\n }\n };\n }\n\n /**\n * Fire all scheduled handlers immediately. If a `tier` is given, only\n * handlers registered with that tier label run.\n */\n async runScheduledRefreshes(tier = null) {\n const list = this._scheduledRefreshes || [];\n const targets = tier ? list.filter(e => e.tier === tier) : list;\n await Promise.allSettled(targets.map(e => e.handler()));\n }\n\n _clearScheduledRefreshes() {\n if (!this._scheduledRefreshes) return;\n for (const entry of this._scheduledRefreshes) {\n clearInterval(entry.id);\n }\n this._scheduledRefreshes = [];\n }\n\n /**\n * Render guard: once a Page has been exited (onExit ran, isActive went\n * false), subsequent render() calls become no-ops until the page is\n * re-entered. Without this guard, async sources still holding a\n * reference to the page (timers, WebSocket reconnect attempts, lingering\n * promises) can call this.render() from a hidden page and overwrite\n * whatever page is currently visible in the page-container.\n *\n * The flag flips back on in onEnter via savedState restoration. The very\n * first render (before any onEnter) is allowed because savedState is null\n * and isActive is false but onExit has never set the _wasExited flag.\n */\n async render(allowMount = true, container = null) {\n if (this._wasExited && !this.isActive) {\n return this;\n }\n return super.render(allowMount, container);\n }\n\n\n /**\n * Called by PortalApp.setActiveGroup() whenever the user switches to a different group.\n *\n * Override this on any page that displays group-scoped data so the page\n * stays in sync when the active group changes. Always call super first.\n *\n * This is an MVC framework — your override should tell your Models and\n * Collections to re-fetch for the new group, then call this.render().\n * There is no framework-provided loadData() method; organise your own\n * fetch logic however you like.\n *\n * @param {Model} group - The newly active Group model.\n * this.getApp().activeGroup is already set to this value.\n *\n * @example\n * async onGroupChange(group) {\n * await super.onGroupChange(group);\n * if (!group) return;\n * await this.myCollection.fetch({ group_id: group.id });\n * await this.render();\n * }\n */\n async onGroupChange(group) {\n // Empty stub — override in subclasses that display group-scoped data.\n }\n\n /**\n * Get page metadata for display and events\n * @returns {object} Page metadata\n */\n getMetadata() {\n return {\n name: this.pageName,\n displayName: this.displayName || this.pageName,\n icon: this.pageIcon,\n description: this.pageDescription,\n route: this.route,\n isActive: this.isActive\n };\n }\n\n /**\n * Handle default action - fallback from design doc\n */\n async onActionDefault(action) {\n console.log(`Default action '${action}' triggered on page: ${this.pageName}`);\n }\n\n async makeActive() {\n this.getApp().showPage(this);\n }\n\n async onActionNavigate(event, element) {\n event.preventDefault();\n const page = element.dataset.page;\n this.getApp().showPage(page);\n }\n\n /**\n * Capture current page state for preservation\n * @returns {object|null} Captured state\n */\n captureState() {\n if (!this.element) return null;\n\n return {\n scrollTop: this.element.scrollTop,\n formData: this.captureFormData(),\n custom: this.captureCustomState()\n };\n }\n\n /**\n * Restore saved state\n * @param {object} state - State to restore\n */\n restoreState(state) {\n if (!state || !this.element) return;\n\n this.element.scrollTop = state.scrollTop || 0;\n this.restoreFormData(state.formData);\n if (state.custom) {\n this.restoreCustomState(state.custom);\n }\n }\n\n /**\n * Capture form data from page\n * @returns {object} Form data\n */\n captureFormData() {\n const data = {};\n if (!this.element) return data;\n\n this.element.querySelectorAll('input, select, textarea').forEach(field => {\n if (field.name) {\n if (field.type === 'checkbox') {\n data[field.name] = field.checked;\n } else if (field.type === 'radio') {\n if (field.checked) {\n data[field.name] = field.value;\n }\n } else {\n data[field.name] = field.value;\n }\n }\n });\n\n return data;\n }\n\n /**\n * Restore form data to page\n * @param {object} formData - Form data to restore\n */\n restoreFormData(formData) {\n if (!formData || !this.element) return;\n\n Object.entries(formData).forEach(([name, value]) => {\n const field = this.element.querySelector(`[name=\"${name}\"]`);\n if (field) {\n if (field.type === 'checkbox') {\n field.checked = value;\n } else if (field.type === 'radio') {\n const radio = this.element.querySelector(`[name=\"${name}\"][value=\"${value}\"]`);\n if (radio) radio.checked = true;\n } else {\n field.value = value;\n }\n }\n });\n }\n\n /**\n * Capture custom state - override in subclasses\n * @returns {object} Custom state\n */\n captureCustomState() {\n return {};\n }\n\n /**\n * Restore custom state - override in subclasses\n * @param {object} state - Custom state to restore\n */\n restoreCustomState(state) {\n // Override in subclasses\n }\n\n\n\n /**\n * Set page metadata\n * @param {object} meta - Metadata object\n */\n setMeta(meta = {}) {\n if (typeof document === 'undefined') {\n return;\n }\n\n // Set title\n if (meta.title) {\n document.title = meta.title;\n this.pageOptions.title = meta.title;\n }\n\n // Set description\n if (meta.description) {\n let descMeta = document.querySelector('meta[name=\"description\"]');\n if (!descMeta) {\n descMeta = document.createElement('meta');\n descMeta.name = 'description';\n document.head.appendChild(descMeta);\n }\n descMeta.content = meta.description;\n this.pageOptions.description = meta.description;\n }\n\n // Set other meta tags\n Object.entries(meta).forEach(([key, value]) => {\n if (key !== 'title' && key !== 'description') {\n let metaEl = document.querySelector(`meta[name=\"${key}\"]`);\n if (!metaEl) {\n metaEl = document.createElement('meta');\n metaEl.name = key;\n document.head.appendChild(metaEl);\n }\n metaEl.content = value;\n }\n });\n }\n\n\n /**\n * Show error message with page context\n * @param {string} message - Error message\n */\n showError(message) {\n super.showError(message);\n\n // Page-specific error display can be implemented here\n if (this.element) {\n // Example: Add error to page\n const errorDiv = document.createElement('div');\n errorDiv.className = 'alert alert-danger alert-dismissible fade show';\n errorDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n `;\n\n // Insert at top of page\n this.element.insertBefore(errorDiv, this.element.firstChild);\n\n // Auto-remove after 5 seconds\n setTimeout(() => {\n if (errorDiv.parentNode) {\n errorDiv.parentNode.removeChild(errorDiv);\n }\n }, 5000);\n }\n }\n\n /**\n * Show success message with page context\n * @param {string} message - Success message\n */\n showSuccess(message) {\n super.showSuccess(message);\n\n // Page-specific success display\n if (this.element) {\n const successDiv = document.createElement('div');\n successDiv.className = 'alert alert-success alert-dismissible fade show';\n successDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n `;\n\n // Insert at top of page\n this.element.insertBefore(successDiv, this.element.firstChild);\n\n // Auto-remove after 3 seconds\n setTimeout(() => {\n if (successDiv.parentNode) {\n successDiv.parentNode.removeChild(successDiv);\n }\n }, 3000);\n }\n }\n\n /**\n * Page-specific before render hook\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n\n // Set page metadata before rendering\n this.setMeta({\n title: this.pageOptions.title,\n description: this.pageOptions.description\n });\n }\n\n /**\n * Page-specific after mount hook\n */\n async onAfterMount() {\n await super.onAfterMount();\n\n // Add page-specific class to body\n if (typeof document !== 'undefined' && this.pageName) {\n document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\\s+/g, '-')}`);\n }\n }\n\n /**\n * Page-specific before destroy hook\n */\n async onBeforeDestroy() {\n await super.onBeforeDestroy();\n\n // Remove page-specific class from body\n if (typeof document !== 'undefined' && this.pageName) {\n document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\\s+/g, '-')}`);\n }\n }\n\n /**\n * Navigate to another page using the app's router\n * @param {string} route - Route to navigate to\n * @param {object} params - Route parameters\n * @param {object} options - Navigation options\n */\n navigate(route, params = {}, options = {}) {\n // Delegate to app's router\n if (this.app && this.app.router) {\n return this.app.router.navigate(route, options);\n }\n\n // Fallback to MOJO global router\n if (typeof window !== 'undefined' && window.MOJO?.router) {\n return window.MOJO.router.navigate(route, options);\n }\n\n console.error('No router available for navigation');\n }\n\n getRoute() {\n if (this.route) {\n let route = this.route;\n if (typeof route === 'string' && route.startsWith('/')) {\n route = route.substring(1);\n }\n return route;\n }\n return this.pageName;\n }\n\n syncUrl(force = true) {\n this.updateBrowserUrl(this.query, false, false);\n }\n\n updateBrowserUrl(query = null, replace = false, trigger = false) {\n this.getApp();\n // we need to do this to normalize the URL\n // const targetPath = this.app.buildPagePath(this, this.params, query);\n // const { pageName, queryParams } = this.app.router.parseInput(targetPath);\n this.app.router.updateBrowserUrl(this.getRoute(), query, replace, trigger);\n }\n\n /**\n * Static method to define a page class with metadata\n * @param {object} definition - Page class definition\n * @returns {class} Page class\n */\n static define(definition) {\n class DefinedPage extends Page {\n constructor(options = {}) {\n super({\n ...definition,\n ...options\n });\n }\n }\n\n // Copy static properties\n DefinedPage.template = definition.template;\n DefinedPage.pageName = definition.pageName;\n DefinedPage.route = definition.route;\n\n return DefinedPage;\n }\n}\n\nexport default Page;\n","/**\n * ContextMenu - A reusable context menu component for MOJO\n *\n * Renders a Bootstrap 5 dropdown menu based on a configuration object.\n * This component is designed to be easily embedded in any other View.\n * It supports the same configuration syntax as the Dialog's contextMenu.\n *\n * Features:\n * - Renders a dropdown button with a configurable icon.\n * - Generates menu items from a configuration array.\n * - Supports dividers, icons, labels, and links.\n * - Handles actions via inline handlers or by emitting an 'action' event.\n * - Supports right-click usage via `openAt(x, y, contextItem)` and the\n * `ContextMenu.attachToRightClick()` static helper.\n *\n * @example\n * const contextMenu = new ContextMenu({\n * config: {\n * icon: 'bi-gear', // Optional: custom trigger icon\n * items: [\n * { label: 'Edit', action: 'edit', icon: 'bi-pencil' },\n * { label: 'Delete', action: 'delete', icon: 'bi-trash', danger: true },\n * { type: 'divider' },\n * {\n * label: 'Custom Action',\n * action: 'custom',\n * icon: 'bi-star',\n * handler: (context) => {\n * console.log('Inline handler called with context:', context);\n * }\n * }\n * ]\n * },\n * context: { id: 123, name: 'My Item' } // Optional data to pass to handlers/events\n * });\n *\n * // Listen for actions from the parent view\n * contextMenu.on('action', (data) => {\n * console.log(`Action '${data.action}' triggered for context:`, data.context);\n * if (data.action === 'edit') {\n * // handle edit\n * }\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ContextMenu extends View {\n /**\n * Set `ContextMenu.DEBUG = true` from the browser console (or in a\n * downstream app's bootstrap) to trace menu-item clicks, parent\n * dispatch, and Bootstrap auto-attach. Off by default.\n */\n static DEBUG = false;\n\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'context-menu-view dropdown',\n ...options\n });\n\n this.config = options.contextMenu || options.config || {};\n this.context = options.context || {}; // Optional data to pass to handlers/events\n }\n\n /**\n * After every render, ensure the Bootstrap Dropdown instance is wired\n * to our trigger button. Bootstrap's data-API is supposed to handle\n * this via document delegation, but doesn't reliably attach to\n * dynamically rendered View markup — without this hook, clicking the\n * three-dots trigger button silently does nothing.\n */\n async onAfterRender() {\n await super.onAfterRender();\n if (!this.element) return;\n const Dropdown = window.bootstrap?.Dropdown;\n if (!Dropdown) return;\n const trigger = this.element.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (trigger) Dropdown.getOrCreateInstance(trigger);\n }\n\n /**\n * Build the dropdown menu HTML from the configuration.\n */\n async renderTemplate() {\n const menuItems = this.config.items || [];\n if (menuItems.length === 0) {\n return ''; // Don't render anything if there are no items\n }\n\n const triggerIcon = this.config.icon || 'bi-three-dots';\n const buttonClass = this.config.buttonClass || 'btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1';\n const dropdownId = `context-menu-${this.id}`;\n\n const menuItemsHtml = menuItems.map(item => this.buildMenuItemHTML(item)).join('');\n\n return `\n <button class=\"${buttonClass}\" type=\"button\" id=\"${dropdownId}\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"${triggerIcon}\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\" aria-labelledby=\"${dropdownId}\">\n ${menuItemsHtml}\n </ul>\n `;\n }\n\n /**\n * Build the HTML for a single menu item.\n * @param {object} item - The menu item configuration.\n * @returns {string} The HTML string for the list item.\n */\n buildMenuItemHTML(item) {\n if (item.type === 'divider' || item.separator) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n const icon = item.icon ? `<i class=\"${item.icon} me-2\"></i>` : '';\n const label = item.label || '';\n const itemClass = `dropdown-item ${item.danger ? 'text-danger' : ''} ${item.disabled ? 'disabled' : ''}`;\n const action = item.action || '';\n\n if (item.href) {\n return `<li><a class=\"${itemClass}\" href=\"${item.href}\" target=\"${item.target || '_self'}\">${icon}${label}</a></li>`;\n }\n\n return `<li><a class=\"${itemClass}\" href=\"#\" data-action=\"menu-item-click\" data-item-action=\"${action}\">${icon}${label}</a></li>`;\n }\n\n /**\n * Handle clicks on menu items.\n * @param {Event} event - The click event.\n * @param {HTMLElement} element - The clicked anchor element.\n */\n async onActionMenuItemClick(event, element) {\n event.preventDefault();\n const action = element.getAttribute('data-item-action');\n const debug = ContextMenu.DEBUG;\n if (debug) console.log('[ContextMenu] menu-item-click', { action, hasParent: !!this.parent, parentClass: this.parent?.constructor?.name });\n if (!action) return;\n\n const menuItem = this.config.items.find(item => item.action === action);\n if (!menuItem || menuItem.disabled) {\n if (debug) console.log('[ContextMenu] no matching item or disabled', { action, found: !!menuItem, disabled: menuItem?.disabled });\n return;\n }\n\n // Support for inline handlers\n if (typeof menuItem.handler === 'function') {\n if (debug) console.log('[ContextMenu] inline handler', action);\n menuItem.handler(this.context, event, element);\n } else if (this.parent && this.parent.events) {\n if (debug) console.log('[ContextMenu] dispatching to parent', { action, parentClass: this.parent.constructor?.name });\n this.parent.events.dispatch(action, event, element);\n } else if (debug) {\n console.warn('[ContextMenu] no handler and no parent.events — action lost', { action });\n }\n this.closeDropdown();\n }\n\n closeDropdown() {\n // Manual-mode close: undo what openAt did (re-parent, strip\n // .show, drop the outside-click listener). Falls through to the\n // Bootstrap path so the trigger-button case keeps working.\n const menuEl = this.element?.querySelector('.dropdown-menu')\n || this._menuOrigParent?.querySelector?.('.dropdown-menu');\n if (menuEl && menuEl.classList.contains('show') && menuEl.parentElement === document.body) {\n menuEl.classList.remove('show');\n menuEl.style.position = '';\n menuEl.style.left = '';\n menuEl.style.top = '';\n menuEl.style.transform = '';\n menuEl.style.inset = '';\n menuEl.style.margin = '';\n menuEl.style.zIndex = '';\n if (this._menuOrigParent) {\n if (this._menuOrigNextSibling && this._menuOrigNextSibling.parentElement === this._menuOrigParent) {\n this._menuOrigParent.insertBefore(menuEl, this._menuOrigNextSibling);\n } else {\n this._menuOrigParent.appendChild(menuEl);\n }\n }\n }\n if (this._outsideHandler) {\n document.removeEventListener('mousedown', this._outsideHandler, true);\n this._outsideHandler = null;\n }\n\n // Bootstrap-trigger case (visible three-dots button).\n const dropdownTrigger = this.element?.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (dropdownTrigger) {\n const dropdownInstance = window.bootstrap?.Dropdown.getInstance(dropdownTrigger);\n dropdownInstance?.hide();\n }\n }\n\n /**\n * Open the menu at viewport coordinates (x, y), without needing a\n * visible trigger button. Used for right-click / programmatic patterns.\n *\n * Implementation note — we deliberately do NOT delegate positioning to\n * Bootstrap's Dropdown / Popper here. Two reasons:\n * 1. Hosts often mount the ContextMenu inside a wrapper that hides\n * the (otherwise visible) trigger button. Wrappers like\n * `.visually-hidden` apply `clip: rect(0,0,0,0)` and\n * `overflow: hidden`, which clip the popped-out menu's paint even\n * though Popper places it correctly.\n * 2. Portal layouts paint sidebars/offcanvas at z-index 1050 above\n * the dropdown's default 1000.\n * Re-parenting the menu to `document.body` and pinning it with\n * `position: fixed` + a high z-index sidesteps both.\n *\n * @param {number} x - Viewport X coordinate (e.g. event.clientX)\n * @param {number} y - Viewport Y coordinate (e.g. event.clientY)\n * @param {*} [contextItem] - Optional context to attach for the handler/dispatch path\n * @returns {Promise<this>}\n */\n async openAt(x, y, contextItem) {\n if (typeof contextItem !== 'undefined') {\n this.context = contextItem;\n }\n\n // Make sure the menu is rendered and in the DOM.\n if (!this.isMounted()) {\n if (!this.parent && !this.containerId && !this.container) {\n this.options.allowAppendToBody = true;\n }\n await this.render();\n }\n\n const menuEl = this.element?.querySelector('.dropdown-menu');\n if (!menuEl) return this;\n\n // Re-parent the menu to <body> on first openAt so no host wrapper\n // can clip or stack above it. Stash the original parent so we can\n // re-attach on close (keeps the DOM tidy and the View's children\n // model coherent).\n if (menuEl.parentElement !== document.body) {\n this._menuOrigParent = menuEl.parentElement;\n this._menuOrigNextSibling = menuEl.nextSibling;\n document.body.appendChild(menuEl);\n }\n\n // Manual positioning + visibility — no Popper involvement.\n // NOTE: `inset` is shorthand for top/right/bottom/left, so set it\n // FIRST and then override left/top — otherwise inset:auto would\n // clobber the coordinates we just assigned.\n menuEl.style.transform = 'none';\n menuEl.style.inset = 'auto';\n menuEl.style.position = 'fixed';\n menuEl.style.left = `${x}px`;\n menuEl.style.top = `${y}px`;\n menuEl.style.margin = '0';\n menuEl.style.zIndex = '1080';\n menuEl.classList.add('show');\n\n // The menu is now a document.body child, so its `data-action`\n // clicks no longer bubble up to `this.element` where the View's\n // EventDelegate listens. Wire menu-item dispatch directly.\n if (this._menuClickHandler) {\n menuEl.removeEventListener('click', this._menuClickHandler);\n }\n this._menuClickHandler = (ev) => {\n const item = ev.target.closest('[data-action=\"menu-item-click\"]');\n if (!item) return;\n this.onActionMenuItemClick(ev, item);\n };\n menuEl.addEventListener('click', this._menuClickHandler);\n\n // Click/contextmenu outside closes the menu. We listen on the next\n // tick so the contextmenu event that opened us doesn't immediately\n // close us.\n if (this._outsideHandler) {\n document.removeEventListener('mousedown', this._outsideHandler, true);\n }\n this._outsideHandler = (ev) => {\n if (!menuEl.contains(ev.target)) this.closeDropdown();\n };\n setTimeout(() => {\n document.addEventListener('mousedown', this._outsideHandler, true);\n }, 0);\n\n return this;\n }\n\n /**\n * Wire a `contextmenu` (right-click) event on `element` to open a\n * ContextMenu at the cursor. Returns the ContextMenu instance so\n * callers can keep a handle for cleanup or further configuration.\n *\n * Two ways to supply the menu:\n * 1. Pass a pre-built ContextMenu via `menuOptions.menu`.\n * 2. Pass a plain options object (config / context / etc.) and a\n * fresh ContextMenu will be constructed.\n *\n * @param {HTMLElement} element - The element that should respond to right-click\n * @param {Function} getContextItem - Callback invoked with the contextmenu event;\n * the return value is stored on `menu.context` for the dispatch path.\n * @param {object} [menuOptions] - Either a ContextMenu options object\n * (`{ config, context, ... }`) or `{ menu: existingContextMenuInstance }`.\n * @returns {ContextMenu} The ContextMenu instance bound to the element.\n */\n static attachToRightClick(element, getContextItem, menuOptions = {}) {\n if (!element) {\n throw new Error('ContextMenu.attachToRightClick: element is required');\n }\n\n const menu = menuOptions.menu instanceof ContextMenu\n ? menuOptions.menu\n : new ContextMenu(menuOptions);\n\n const handler = (event) => {\n event.preventDefault();\n const contextItem = typeof getContextItem === 'function'\n ? getContextItem(event)\n : getContextItem;\n menu.openAt(event.clientX, event.clientY, contextItem);\n };\n\n element.addEventListener('contextmenu', handler);\n\n // Stash the handler so callers can remove it if they need to.\n menu._rightClickHandler = handler;\n menu._rightClickElement = element;\n\n return menu;\n }\n\n /**\n * Detach a previously attached right-click handler. Safe to call\n * multiple times. Does not destroy the ContextMenu itself.\n */\n detachRightClick() {\n if (this._rightClickElement && this._rightClickHandler) {\n this._rightClickElement.removeEventListener('contextmenu', this._rightClickHandler);\n this._rightClickElement = null;\n this._rightClickHandler = null;\n }\n return this;\n }\n}\n\nexport default ContextMenu;\n"],"names":["Page","View","constructor","options","tagName","className","pageName","id","toLowerCase","replace","super","this","route","title","pageIcon","icon","displayName","pageDescription","params","query","matched","isActive","pageOptions","description","requiresAuth","savedState","onParams","canEnter","permissions","user","getApp","activeUser","hasPermission","requiresGroup","activeGroup","onEnter","_wasExited","onInitView","restoreState","document","emit","page","getMetadata","onExit","captureState","_clearScheduledRefreshes","scheduleRefresh","handler","intervalMs","_scheduledRefreshes","safe","async","err","console","warn","immediate","setInterval","entry","tier","push","cancel","clearInterval","filter","e","runScheduledRefreshes","list","targets","Promise","allSettled","map","render","allowMount","container","onGroupChange","group","name","onActionDefault","action","makeActive","showPage","onActionNavigate","event","element","preventDefault","dataset","scrollTop","formData","captureFormData","custom","captureCustomState","state","restoreFormData","restoreCustomState","data","querySelectorAll","forEach","field","type","checked","value","Object","entries","querySelector","radio","setMeta","meta","descMeta","createElement","head","appendChild","content","key","metaEl","showError","message","errorDiv","innerHTML","insertBefore","firstChild","setTimeout","parentNode","removeChild","showSuccess","successDiv","onBeforeRender","onAfterMount","body","classList","add","onBeforeDestroy","remove","navigate","app","router","window","MOJO","error","getRoute","startsWith","substring","syncUrl","force","updateBrowserUrl","trigger","define","definition","DefinedPage","template","ContextMenu","static","config","contextMenu","context","onAfterRender","Dropdown","bootstrap","getOrCreateInstance","renderTemplate","menuItems","items","length","triggerIcon","buttonClass","dropdownId","item","buildMenuItemHTML","join","separator","label","itemClass","danger","disabled","href","target","onActionMenuItemClick","getAttribute","debug","DEBUG","parent","menuItem","find","events","dispatch","closeDropdown","menuEl","_menuOrigParent","contains","parentElement","style","position","left","top","transform","inset","margin","zIndex","_menuOrigNextSibling","_outsideHandler","removeEventListener","dropdownTrigger","dropdownInstance","getInstance","hide","openAt","x","y","contextItem","isMounted","containerId","allowAppendToBody","nextSibling","_menuClickHandler","ev","closest","addEventListener","attachToRightClick","getContextItem","menuOptions","Error","menu","clientX","clientY","_rightClickHandler","_rightClickElement","detachRightClick"],"mappings":"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"}
1
+ {"version":3,"file":"ContextMenu-fIy3upEy.js","sources":["../../src/core/Page.js","../../src/core/views/feedback/ContextMenu.js"],"sourcesContent":["/**\n * Page - Extends View with routing capabilities for MOJO framework\n * Handles URL routing, parameters, and page-specific actions\n *\n * Event Emitter notes:\n * - Uses EventEmitter via View base class.\n * - Use .emit/.on/.off/.once for all custom events.\n */\n\nimport View from '@core/View.js';\n\nclass Page extends View {\n constructor(options = {}) {\n // Set default tag name for pages\n options.tagName = options.tagName || 'main';\n options.className = options.className || 'mojo-page';\n\n // Set page ID based on page name\n const pageName = options.pageName || '';\n if (pageName && !options.id) {\n options.id = 'page_' + pageName.toLowerCase().replace(/\\s+/g, '_');\n }\n\n super(options);\n\n // Core page properties from design doc\n this.pageName = options.pageName || this.constructor.pageName || '';\n this.route = options.route || this.constructor.route || '';\n this.title = options.title || this.pageName || '';\n\n // Set page ID if not already set and we have a page_name from constructor\n if (!this.id && this.constructor.pageName && !options.pageName) {\n this.id = 'page_' + this.constructor.pageName.toLowerCase().replace(/\\s+/g, '_');\n }\n\n // Page metadata for event system\n this.pageIcon = options.icon || options.pageIcon || this.constructor.pageIcon || 'bi bi-file-text';\n this.displayName = options.displayName || this.constructor.displayName || this.pageName || '';\n this.pageDescription = options.pageDescription || this.constructor.pageDescription || '';\n\n // Routing state\n this.params = {};\n this.query = {};\n this.matched = false;\n this.isActive = false;\n\n // Page-specific options\n this.pageOptions = {\n title: options.title || this.pageName || 'Untitled Page',\n description: options.description || '',\n requiresAuth: options.requiresAuth || false,\n ...options.pageOptions\n };\n\n // State preservation\n this.savedState = null;\n\n console.log(`Page ${this.pageName} constructed with route: ${this.route}`);\n }\n\n /**\n * Handle route parameters - from design doc\n * @param {object} params - Route parameters\n * @param {object} query - Query string parameters\n */\n async onParams(params = {}, query = {}) {\n // const paramsChanged = JSON.stringify(params) !== JSON.stringify(this.params);\n // const queryChanged = JSON.stringify(query) !== JSON.stringify(this.query);\n\n this.params = params;\n this.query = query;\n\n // Only re-render if params actually changed and page is active\n // if (this.isActive && (paramsChanged || queryChanged)) {\n // console.log(`Page ${this.pageName} params changed, re-rendering`);\n // await this.render();\n // }\n }\n\n canEnter() {\n if (this.options.permissions) {\n const user = this.getApp().activeUser;\n if (!user || !user.hasPermission(this.options.permissions)) {\n return false;\n }\n }\n if (this.options.requiresGroup && !this.getApp().activeGroup) {\n return false;\n }\n return true;\n }\n\n /**\n * Called when entering this page (before render)\n * Override this method for initialization logic\n */\n async onEnter() {\n this.isActive = true;\n this._wasExited = false;\n await this.onInitView();\n\n // Restore saved state if exists\n if (this.savedState) {\n this.restoreState(this.savedState);\n this.savedState = null;\n }\n\n // Set page title if provided\n if (this.pageOptions && this.pageOptions.title && typeof document !== 'undefined') {\n document.title = this.pageOptions.title;\n }\n\n // Emit activation event\n this.emit('activated', {\n page: this.getMetadata()\n });\n\n console.log(`Page ${this.pageName} entered`);\n }\n\n /**\n * Called when leaving this page (before cleanup)\n * Override this method for cleanup logic like removing listeners, clearing timers, etc.\n */\n async onExit() {\n // Save state before exit\n this.savedState = this.captureState();\n this.isActive = false;\n this._wasExited = true;\n\n // Auto-clear any intervals registered via scheduleRefresh()\n this._clearScheduledRefreshes();\n\n // Emit deactivation event\n this.emit('deactivated', {\n page: this.getMetadata()\n });\n console.log(`Page ${this.pageName} exiting`);\n }\n\n /**\n * Register a recurring handler that auto-clears on page exit.\n * Use this instead of hand-rolling setInterval / clearInterval pairs\n * across every dashboard page.\n *\n * Multiple cadences are supported by calling scheduleRefresh multiple\n * times with different intervals. The optional `tier` is a label used\n * by `runScheduledRefreshes(tier)` to fire only handlers tagged with\n * that tier (handy for a manual \"refresh all\" button that wants to\n * call only the slow tier without waiting for it to tick).\n *\n * @param {Function} handler - Called every `intervalMs`. May return a Promise.\n * @param {number} intervalMs - Interval in milliseconds.\n * @param {object} [options]\n * @param {string} [options.tier] - Label e.g. 'fast' / 'slow'\n * @param {boolean} [options.immediate=false] - Fire once now, then start interval\n * @returns {object} A handle with `cancel()` to clear before exit if needed.\n */\n scheduleRefresh(handler, intervalMs, options = {}) {\n if (!this._scheduledRefreshes) this._scheduledRefreshes = [];\n if (typeof handler !== 'function' || !(intervalMs > 0)) return null;\n\n const safe = async () => {\n try { await handler(); }\n catch (err) { console.warn(`[Page ${this.pageName}] scheduleRefresh handler error:`, err); }\n };\n\n if (options.immediate) safe();\n const id = setInterval(safe, intervalMs);\n const entry = { id, tier: options.tier || null, handler: safe, intervalMs };\n this._scheduledRefreshes.push(entry);\n\n return {\n cancel: () => {\n clearInterval(id);\n this._scheduledRefreshes = (this._scheduledRefreshes || []).filter(e => e !== entry);\n }\n };\n }\n\n /**\n * Fire all scheduled handlers immediately. If a `tier` is given, only\n * handlers registered with that tier label run.\n */\n async runScheduledRefreshes(tier = null) {\n const list = this._scheduledRefreshes || [];\n const targets = tier ? list.filter(e => e.tier === tier) : list;\n await Promise.allSettled(targets.map(e => e.handler()));\n }\n\n _clearScheduledRefreshes() {\n if (!this._scheduledRefreshes) return;\n for (const entry of this._scheduledRefreshes) {\n clearInterval(entry.id);\n }\n this._scheduledRefreshes = [];\n }\n\n /**\n * Render guard: once a Page has been exited (onExit ran, isActive went\n * false), subsequent render() calls become no-ops until the page is\n * re-entered. Without this guard, async sources still holding a\n * reference to the page (timers, WebSocket reconnect attempts, lingering\n * promises) can call this.render() from a hidden page and overwrite\n * whatever page is currently visible in the page-container.\n *\n * The flag flips back on in onEnter via savedState restoration. The very\n * first render (before any onEnter) is allowed because savedState is null\n * and isActive is false but onExit has never set the _wasExited flag.\n */\n async render(allowMount = true, container = null) {\n if (this._wasExited && !this.isActive) {\n return this;\n }\n return super.render(allowMount, container);\n }\n\n\n /**\n * Called by PortalApp.setActiveGroup() whenever the user switches to a different group.\n *\n * Override this on any page that displays group-scoped data so the page\n * stays in sync when the active group changes. Always call super first.\n *\n * This is an MVC framework — your override should tell your Models and\n * Collections to re-fetch for the new group, then call this.render().\n * There is no framework-provided loadData() method; organise your own\n * fetch logic however you like.\n *\n * @param {Model} group - The newly active Group model.\n * this.getApp().activeGroup is already set to this value.\n *\n * @example\n * async onGroupChange(group) {\n * await super.onGroupChange(group);\n * if (!group) return;\n * await this.myCollection.fetch({ group_id: group.id });\n * await this.render();\n * }\n */\n async onGroupChange(group) {\n // Empty stub — override in subclasses that display group-scoped data.\n }\n\n /**\n * Get page metadata for display and events\n * @returns {object} Page metadata\n */\n getMetadata() {\n return {\n name: this.pageName,\n displayName: this.displayName || this.pageName,\n icon: this.pageIcon,\n description: this.pageDescription,\n route: this.route,\n isActive: this.isActive\n };\n }\n\n /**\n * Handle default action - fallback from design doc\n */\n async onActionDefault(action) {\n console.log(`Default action '${action}' triggered on page: ${this.pageName}`);\n }\n\n async makeActive() {\n this.getApp().showPage(this);\n }\n\n async onActionNavigate(event, element) {\n event.preventDefault();\n const page = element.dataset.page;\n this.getApp().showPage(page);\n }\n\n /**\n * Capture current page state for preservation\n * @returns {object|null} Captured state\n */\n captureState() {\n if (!this.element) return null;\n\n return {\n scrollTop: this.element.scrollTop,\n formData: this.captureFormData(),\n custom: this.captureCustomState()\n };\n }\n\n /**\n * Restore saved state\n * @param {object} state - State to restore\n */\n restoreState(state) {\n if (!state || !this.element) return;\n\n this.element.scrollTop = state.scrollTop || 0;\n this.restoreFormData(state.formData);\n if (state.custom) {\n this.restoreCustomState(state.custom);\n }\n }\n\n /**\n * Capture form data from page\n * @returns {object} Form data\n */\n captureFormData() {\n const data = {};\n if (!this.element) return data;\n\n this.element.querySelectorAll('input, select, textarea').forEach(field => {\n if (field.name) {\n if (field.type === 'checkbox') {\n data[field.name] = field.checked;\n } else if (field.type === 'radio') {\n if (field.checked) {\n data[field.name] = field.value;\n }\n } else {\n data[field.name] = field.value;\n }\n }\n });\n\n return data;\n }\n\n /**\n * Restore form data to page\n * @param {object} formData - Form data to restore\n */\n restoreFormData(formData) {\n if (!formData || !this.element) return;\n\n Object.entries(formData).forEach(([name, value]) => {\n const field = this.element.querySelector(`[name=\"${name}\"]`);\n if (field) {\n if (field.type === 'checkbox') {\n field.checked = value;\n } else if (field.type === 'radio') {\n const radio = this.element.querySelector(`[name=\"${name}\"][value=\"${value}\"]`);\n if (radio) radio.checked = true;\n } else {\n field.value = value;\n }\n }\n });\n }\n\n /**\n * Capture custom state - override in subclasses\n * @returns {object} Custom state\n */\n captureCustomState() {\n return {};\n }\n\n /**\n * Restore custom state - override in subclasses\n * @param {object} state - Custom state to restore\n */\n restoreCustomState(state) {\n // Override in subclasses\n }\n\n\n\n /**\n * Set page metadata\n * @param {object} meta - Metadata object\n */\n setMeta(meta = {}) {\n if (typeof document === 'undefined') {\n return;\n }\n\n // Set title\n if (meta.title) {\n document.title = meta.title;\n this.pageOptions.title = meta.title;\n }\n\n // Set description\n if (meta.description) {\n let descMeta = document.querySelector('meta[name=\"description\"]');\n if (!descMeta) {\n descMeta = document.createElement('meta');\n descMeta.name = 'description';\n document.head.appendChild(descMeta);\n }\n descMeta.content = meta.description;\n this.pageOptions.description = meta.description;\n }\n\n // Set other meta tags\n Object.entries(meta).forEach(([key, value]) => {\n if (key !== 'title' && key !== 'description') {\n let metaEl = document.querySelector(`meta[name=\"${key}\"]`);\n if (!metaEl) {\n metaEl = document.createElement('meta');\n metaEl.name = key;\n document.head.appendChild(metaEl);\n }\n metaEl.content = value;\n }\n });\n }\n\n\n /**\n * Show error message with page context\n * @param {string} message - Error message\n */\n showError(message) {\n super.showError(message);\n\n // Page-specific error display can be implemented here\n if (this.element) {\n // Example: Add error to page\n const errorDiv = document.createElement('div');\n errorDiv.className = 'alert alert-danger alert-dismissible fade show';\n errorDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n `;\n\n // Insert at top of page\n this.element.insertBefore(errorDiv, this.element.firstChild);\n\n // Auto-remove after 5 seconds\n setTimeout(() => {\n if (errorDiv.parentNode) {\n errorDiv.parentNode.removeChild(errorDiv);\n }\n }, 5000);\n }\n }\n\n /**\n * Show success message with page context\n * @param {string} message - Success message\n */\n showSuccess(message) {\n super.showSuccess(message);\n\n // Page-specific success display\n if (this.element) {\n const successDiv = document.createElement('div');\n successDiv.className = 'alert alert-success alert-dismissible fade show';\n successDiv.innerHTML = `\n ${message}\n <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>\n `;\n\n // Insert at top of page\n this.element.insertBefore(successDiv, this.element.firstChild);\n\n // Auto-remove after 3 seconds\n setTimeout(() => {\n if (successDiv.parentNode) {\n successDiv.parentNode.removeChild(successDiv);\n }\n }, 3000);\n }\n }\n\n /**\n * Page-specific before render hook\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n\n // Set page metadata before rendering\n this.setMeta({\n title: this.pageOptions.title,\n description: this.pageOptions.description\n });\n }\n\n /**\n * Page-specific after mount hook\n */\n async onAfterMount() {\n await super.onAfterMount();\n\n // Add page-specific class to body\n if (typeof document !== 'undefined' && this.pageName) {\n document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\\s+/g, '-')}`);\n }\n }\n\n /**\n * Page-specific before destroy hook\n */\n async onBeforeDestroy() {\n await super.onBeforeDestroy();\n\n // Remove page-specific class from body\n if (typeof document !== 'undefined' && this.pageName) {\n document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\\s+/g, '-')}`);\n }\n }\n\n /**\n * Navigate to another page using the app's router\n * @param {string} route - Route to navigate to\n * @param {object} params - Route parameters\n * @param {object} options - Navigation options\n */\n navigate(route, params = {}, options = {}) {\n // Delegate to app's router\n if (this.app && this.app.router) {\n return this.app.router.navigate(route, options);\n }\n\n // Fallback to MOJO global router\n if (typeof window !== 'undefined' && window.MOJO?.router) {\n return window.MOJO.router.navigate(route, options);\n }\n\n console.error('No router available for navigation');\n }\n\n getRoute() {\n if (this.route) {\n let route = this.route;\n if (typeof route === 'string' && route.startsWith('/')) {\n route = route.substring(1);\n }\n return route;\n }\n return this.pageName;\n }\n\n syncUrl(force = true) {\n this.updateBrowserUrl(this.query, false, false);\n }\n\n updateBrowserUrl(query = null, replace = false, trigger = false) {\n this.getApp();\n // we need to do this to normalize the URL\n // const targetPath = this.app.buildPagePath(this, this.params, query);\n // const { pageName, queryParams } = this.app.router.parseInput(targetPath);\n this.app.router.updateBrowserUrl(this.getRoute(), query, replace, trigger);\n }\n\n /**\n * Static method to define a page class with metadata\n * @param {object} definition - Page class definition\n * @returns {class} Page class\n */\n static define(definition) {\n class DefinedPage extends Page {\n constructor(options = {}) {\n super({\n ...definition,\n ...options\n });\n }\n }\n\n // Copy static properties\n DefinedPage.template = definition.template;\n DefinedPage.pageName = definition.pageName;\n DefinedPage.route = definition.route;\n\n return DefinedPage;\n }\n}\n\nexport default Page;\n","/**\n * ContextMenu - A reusable context menu component for MOJO\n *\n * Renders a Bootstrap 5 dropdown menu based on a configuration object.\n * This component is designed to be easily embedded in any other View.\n * It supports the same configuration syntax as the Dialog's contextMenu.\n *\n * Features:\n * - Renders a dropdown button with a configurable icon.\n * - Generates menu items from a configuration array.\n * - Supports dividers, icons, labels, and links.\n * - Handles actions via inline handlers or by emitting an 'action' event.\n * - Supports right-click usage via `openAt(x, y, contextItem)` and the\n * `ContextMenu.attachToRightClick()` static helper.\n *\n * @example\n * const contextMenu = new ContextMenu({\n * config: {\n * icon: 'bi-gear', // Optional: custom trigger icon\n * items: [\n * { label: 'Edit', action: 'edit', icon: 'bi-pencil' },\n * { label: 'Delete', action: 'delete', icon: 'bi-trash', danger: true },\n * { type: 'divider' },\n * {\n * label: 'Custom Action',\n * action: 'custom',\n * icon: 'bi-star',\n * handler: (context) => {\n * console.log('Inline handler called with context:', context);\n * }\n * }\n * ]\n * },\n * context: { id: 123, name: 'My Item' } // Optional data to pass to handlers/events\n * });\n *\n * // Listen for actions from the parent view\n * contextMenu.on('action', (data) => {\n * console.log(`Action '${data.action}' triggered for context:`, data.context);\n * if (data.action === 'edit') {\n * // handle edit\n * }\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ContextMenu extends View {\n /**\n * Set `ContextMenu.DEBUG = true` from the browser console (or in a\n * downstream app's bootstrap) to trace menu-item clicks, parent\n * dispatch, and Bootstrap auto-attach. Off by default.\n */\n static DEBUG = false;\n\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'context-menu-view dropdown',\n ...options\n });\n\n this.config = options.contextMenu || options.config || {};\n this.context = options.context || {}; // Optional data to pass to handlers/events\n }\n\n /**\n * After every render, ensure the Bootstrap Dropdown instance is wired\n * to our trigger button. Bootstrap's data-API is supposed to handle\n * this via document delegation, but doesn't reliably attach to\n * dynamically rendered View markup — without this hook, clicking the\n * three-dots trigger button silently does nothing.\n */\n async onAfterRender() {\n await super.onAfterRender();\n if (!this.element) return;\n const Dropdown = window.bootstrap?.Dropdown;\n if (!Dropdown) return;\n const trigger = this.element.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (trigger) Dropdown.getOrCreateInstance(trigger);\n }\n\n /**\n * Build the dropdown menu HTML from the configuration.\n */\n async renderTemplate() {\n const menuItems = this.config.items || [];\n if (menuItems.length === 0) {\n return ''; // Don't render anything if there are no items\n }\n\n const triggerIcon = this.config.icon || 'bi-three-dots';\n const buttonClass = this.config.buttonClass || 'btn btn-link text-secondary ps-3 pe-0 pt-0 pb-1';\n const dropdownId = `context-menu-${this.id}`;\n\n const menuItemsHtml = menuItems.map(item => this.buildMenuItemHTML(item)).join('');\n\n return `\n <button class=\"${buttonClass}\" type=\"button\" id=\"${dropdownId}\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"${triggerIcon}\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\" aria-labelledby=\"${dropdownId}\">\n ${menuItemsHtml}\n </ul>\n `;\n }\n\n /**\n * Build the HTML for a single menu item.\n * @param {object} item - The menu item configuration.\n * @returns {string} The HTML string for the list item.\n */\n buildMenuItemHTML(item) {\n if (item.type === 'divider' || item.separator) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n const icon = item.icon ? `<i class=\"${item.icon} me-2\"></i>` : '';\n const label = item.label || '';\n const itemClass = `dropdown-item ${item.danger ? 'text-danger' : ''} ${item.disabled ? 'disabled' : ''}`;\n const action = item.action || '';\n\n if (item.href) {\n return `<li><a class=\"${itemClass}\" href=\"${item.href}\" target=\"${item.target || '_self'}\">${icon}${label}</a></li>`;\n }\n\n return `<li><a class=\"${itemClass}\" href=\"#\" data-action=\"menu-item-click\" data-item-action=\"${action}\">${icon}${label}</a></li>`;\n }\n\n /**\n * Handle clicks on menu items.\n * @param {Event} event - The click event.\n * @param {HTMLElement} element - The clicked anchor element.\n */\n async onActionMenuItemClick(event, element) {\n event.preventDefault();\n const action = element.getAttribute('data-item-action');\n const debug = ContextMenu.DEBUG;\n if (debug) console.log('[ContextMenu] menu-item-click', { action, hasParent: !!this.parent, parentClass: this.parent?.constructor?.name });\n if (!action) return;\n\n const menuItem = this.config.items.find(item => item.action === action);\n if (!menuItem || menuItem.disabled) {\n if (debug) console.log('[ContextMenu] no matching item or disabled', { action, found: !!menuItem, disabled: menuItem?.disabled });\n return;\n }\n\n // Support for inline handlers\n if (typeof menuItem.handler === 'function') {\n if (debug) console.log('[ContextMenu] inline handler', action);\n menuItem.handler(this.context, event, element);\n } else if (this.parent && this.parent.events) {\n if (debug) console.log('[ContextMenu] dispatching to parent', { action, parentClass: this.parent.constructor?.name });\n this.parent.events.dispatch(action, event, element);\n } else if (debug) {\n console.warn('[ContextMenu] no handler and no parent.events — action lost', { action });\n }\n this.closeDropdown();\n }\n\n closeDropdown() {\n // Manual-mode close: undo what openAt did (re-parent, strip\n // .show, drop the outside-click listener). Falls through to the\n // Bootstrap path so the trigger-button case keeps working.\n const menuEl = this.element?.querySelector('.dropdown-menu')\n || this._menuOrigParent?.querySelector?.('.dropdown-menu');\n if (menuEl && menuEl.classList.contains('show') && menuEl.parentElement === document.body) {\n menuEl.classList.remove('show');\n menuEl.style.position = '';\n menuEl.style.left = '';\n menuEl.style.top = '';\n menuEl.style.transform = '';\n menuEl.style.inset = '';\n menuEl.style.margin = '';\n menuEl.style.zIndex = '';\n if (this._menuOrigParent) {\n if (this._menuOrigNextSibling && this._menuOrigNextSibling.parentElement === this._menuOrigParent) {\n this._menuOrigParent.insertBefore(menuEl, this._menuOrigNextSibling);\n } else {\n this._menuOrigParent.appendChild(menuEl);\n }\n }\n }\n if (this._outsideHandler) {\n document.removeEventListener('mousedown', this._outsideHandler, true);\n this._outsideHandler = null;\n }\n\n // Bootstrap-trigger case (visible three-dots button).\n const dropdownTrigger = this.element?.querySelector('[data-bs-toggle=\"dropdown\"]');\n if (dropdownTrigger) {\n const dropdownInstance = window.bootstrap?.Dropdown.getInstance(dropdownTrigger);\n dropdownInstance?.hide();\n }\n }\n\n /**\n * Open the menu at viewport coordinates (x, y), without needing a\n * visible trigger button. Used for right-click / programmatic patterns.\n *\n * Implementation note — we deliberately do NOT delegate positioning to\n * Bootstrap's Dropdown / Popper here. Two reasons:\n * 1. Hosts often mount the ContextMenu inside a wrapper that hides\n * the (otherwise visible) trigger button. Wrappers like\n * `.visually-hidden` apply `clip: rect(0,0,0,0)` and\n * `overflow: hidden`, which clip the popped-out menu's paint even\n * though Popper places it correctly.\n * 2. Portal layouts paint sidebars/offcanvas at z-index 1050 above\n * the dropdown's default 1000.\n * Re-parenting the menu to `document.body` and pinning it with\n * `position: fixed` + a high z-index sidesteps both.\n *\n * @param {number} x - Viewport X coordinate (e.g. event.clientX)\n * @param {number} y - Viewport Y coordinate (e.g. event.clientY)\n * @param {*} [contextItem] - Optional context to attach for the handler/dispatch path\n * @returns {Promise<this>}\n */\n async openAt(x, y, contextItem) {\n if (typeof contextItem !== 'undefined') {\n this.context = contextItem;\n }\n\n // Make sure the menu is rendered and in the DOM.\n if (!this.isMounted()) {\n if (!this.parent && !this.containerId && !this.container) {\n this.options.allowAppendToBody = true;\n }\n await this.render();\n }\n\n const menuEl = this.element?.querySelector('.dropdown-menu');\n if (!menuEl) return this;\n\n // Re-parent the menu to <body> on first openAt so no host wrapper\n // can clip or stack above it. Stash the original parent so we can\n // re-attach on close (keeps the DOM tidy and the View's children\n // model coherent).\n if (menuEl.parentElement !== document.body) {\n this._menuOrigParent = menuEl.parentElement;\n this._menuOrigNextSibling = menuEl.nextSibling;\n document.body.appendChild(menuEl);\n }\n\n // Manual positioning + visibility — no Popper involvement.\n // NOTE: `inset` is shorthand for top/right/bottom/left, so set it\n // FIRST and then override left/top — otherwise inset:auto would\n // clobber the coordinates we just assigned.\n menuEl.style.transform = 'none';\n menuEl.style.inset = 'auto';\n menuEl.style.position = 'fixed';\n menuEl.style.left = `${x}px`;\n menuEl.style.top = `${y}px`;\n menuEl.style.margin = '0';\n menuEl.style.zIndex = '1080';\n menuEl.classList.add('show');\n\n // The menu is now a document.body child, so its `data-action`\n // clicks no longer bubble up to `this.element` where the View's\n // EventDelegate listens. Wire menu-item dispatch directly.\n if (this._menuClickHandler) {\n menuEl.removeEventListener('click', this._menuClickHandler);\n }\n this._menuClickHandler = (ev) => {\n const item = ev.target.closest('[data-action=\"menu-item-click\"]');\n if (!item) return;\n this.onActionMenuItemClick(ev, item);\n };\n menuEl.addEventListener('click', this._menuClickHandler);\n\n // Click/contextmenu outside closes the menu. We listen on the next\n // tick so the contextmenu event that opened us doesn't immediately\n // close us.\n if (this._outsideHandler) {\n document.removeEventListener('mousedown', this._outsideHandler, true);\n }\n this._outsideHandler = (ev) => {\n if (!menuEl.contains(ev.target)) this.closeDropdown();\n };\n setTimeout(() => {\n document.addEventListener('mousedown', this._outsideHandler, true);\n }, 0);\n\n return this;\n }\n\n /**\n * Wire a `contextmenu` (right-click) event on `element` to open a\n * ContextMenu at the cursor. Returns the ContextMenu instance so\n * callers can keep a handle for cleanup or further configuration.\n *\n * Two ways to supply the menu:\n * 1. Pass a pre-built ContextMenu via `menuOptions.menu`.\n * 2. Pass a plain options object (config / context / etc.) and a\n * fresh ContextMenu will be constructed.\n *\n * @param {HTMLElement} element - The element that should respond to right-click\n * @param {Function} getContextItem - Callback invoked with the contextmenu event;\n * the return value is stored on `menu.context` for the dispatch path.\n * @param {object} [menuOptions] - Either a ContextMenu options object\n * (`{ config, context, ... }`) or `{ menu: existingContextMenuInstance }`.\n * @returns {ContextMenu} The ContextMenu instance bound to the element.\n */\n static attachToRightClick(element, getContextItem, menuOptions = {}) {\n if (!element) {\n throw new Error('ContextMenu.attachToRightClick: element is required');\n }\n\n const menu = menuOptions.menu instanceof ContextMenu\n ? menuOptions.menu\n : new ContextMenu(menuOptions);\n\n const handler = (event) => {\n event.preventDefault();\n const contextItem = typeof getContextItem === 'function'\n ? getContextItem(event)\n : getContextItem;\n menu.openAt(event.clientX, event.clientY, contextItem);\n };\n\n element.addEventListener('contextmenu', handler);\n\n // Stash the handler so callers can remove it if they need to.\n menu._rightClickHandler = handler;\n menu._rightClickElement = element;\n\n return menu;\n }\n\n /**\n * Detach a previously attached right-click handler. Safe to call\n * multiple times. Does not destroy the ContextMenu itself.\n */\n detachRightClick() {\n if (this._rightClickElement && this._rightClickHandler) {\n this._rightClickElement.removeEventListener('contextmenu', this._rightClickHandler);\n this._rightClickElement = null;\n this._rightClickHandler = null;\n }\n return this;\n }\n}\n\nexport default ContextMenu;\n"],"names":["Page","View","constructor","options","tagName","className","pageName","id","toLowerCase","replace","super","this","route","title","pageIcon","icon","displayName","pageDescription","params","query","matched","isActive","pageOptions","description","requiresAuth","savedState","onParams","canEnter","permissions","user","getApp","activeUser","hasPermission","requiresGroup","activeGroup","onEnter","_wasExited","onInitView","restoreState","document","emit","page","getMetadata","onExit","captureState","_clearScheduledRefreshes","scheduleRefresh","handler","intervalMs","_scheduledRefreshes","safe","async","err","console","warn","immediate","setInterval","entry","tier","push","cancel","clearInterval","filter","e","runScheduledRefreshes","list","targets","Promise","allSettled","map","render","allowMount","container","onGroupChange","group","name","onActionDefault","action","makeActive","showPage","onActionNavigate","event","element","preventDefault","dataset","scrollTop","formData","captureFormData","custom","captureCustomState","state","restoreFormData","restoreCustomState","data","querySelectorAll","forEach","field","type","checked","value","Object","entries","querySelector","radio","setMeta","meta","descMeta","createElement","head","appendChild","content","key","metaEl","showError","message","errorDiv","innerHTML","insertBefore","firstChild","setTimeout","parentNode","removeChild","showSuccess","successDiv","onBeforeRender","onAfterMount","body","classList","add","onBeforeDestroy","remove","navigate","app","router","window","MOJO","error","getRoute","startsWith","substring","syncUrl","force","updateBrowserUrl","trigger","define","definition","DefinedPage","template","ContextMenu","static","config","contextMenu","context","onAfterRender","Dropdown","bootstrap","getOrCreateInstance","renderTemplate","menuItems","items","length","triggerIcon","buttonClass","dropdownId","item","buildMenuItemHTML","join","separator","label","itemClass","danger","disabled","href","target","onActionMenuItemClick","getAttribute","debug","DEBUG","parent","menuItem","find","events","dispatch","closeDropdown","menuEl","_menuOrigParent","contains","parentElement","style","position","left","top","transform","inset","margin","zIndex","_menuOrigNextSibling","_outsideHandler","removeEventListener","dropdownTrigger","dropdownInstance","getInstance","hide","openAt","x","y","contextItem","isMounted","containerId","allowAppendToBody","nextSibling","_menuClickHandler","ev","closest","addEventListener","attachToRightClick","getContextItem","menuOptions","Error","menu","clientX","clientY","_rightClickHandler","_rightClickElement","detachRightClick"],"mappings":"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"}