web-mojo 2.3.2 → 2.3.5

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 (120) hide show
  1. package/CHANGELOG.md +192 -5
  2. package/dist/admin-models.cjs.js +1 -1
  3. package/dist/admin-models.es.js +1 -1
  4. package/dist/admin.cjs.js +1 -1
  5. package/dist/admin.es.js +1 -1
  6. package/dist/auth.cjs.js +1 -1
  7. package/dist/auth.es.js +1 -1
  8. package/dist/charts.cjs.js +1 -1
  9. package/dist/charts.es.js +1 -1
  10. package/dist/chunks/{AssistantPanelView-D-0f5hZS.js → AssistantPanelView-CvjzGGS8.js} +2 -2
  11. package/dist/chunks/{AssistantPanelView-D-0f5hZS.js.map → AssistantPanelView-CvjzGGS8.js.map} +1 -1
  12. package/dist/chunks/{AssistantPanelView-72i9d6rq.js → AssistantPanelView-D3WCWj5q.js} +2 -2
  13. package/dist/chunks/{AssistantPanelView-72i9d6rq.js.map → AssistantPanelView-D3WCWj5q.js.map} +1 -1
  14. package/dist/chunks/{ChatView-ys5MIiBf.js → ChatView-B-2JVhM3.js} +2 -2
  15. package/dist/chunks/{ChatView-ys5MIiBf.js.map → ChatView-B-2JVhM3.js.map} +1 -1
  16. package/dist/chunks/{ChatView-CXykdAV1.js → ChatView-oZ9FggEo.js} +2 -2
  17. package/dist/chunks/{ChatView-CXykdAV1.js.map → ChatView-oZ9FggEo.js.map} +1 -1
  18. package/dist/chunks/{Collection-CY6BblFn.js → Collection-BtSHP_BV.js} +2 -2
  19. package/dist/chunks/{Collection-CY6BblFn.js.map → Collection-BtSHP_BV.js.map} +1 -1
  20. package/dist/chunks/{Collection-BpUmNuDZ.js → Collection-CtSGTegm.js} +2 -2
  21. package/dist/chunks/{Collection-BpUmNuDZ.js.map → Collection-CtSGTegm.js.map} +1 -1
  22. package/dist/chunks/{ContextMenu-XQVFU0mL.js → ContextMenu-0KtfqyQm.js} +2 -2
  23. package/dist/chunks/{ContextMenu-XQVFU0mL.js.map → ContextMenu-0KtfqyQm.js.map} +1 -1
  24. package/dist/chunks/{ContextMenu-PtN50qH2.js → ContextMenu-DvQTJA49.js} +2 -2
  25. package/dist/chunks/{ContextMenu-PtN50qH2.js.map → ContextMenu-DvQTJA49.js.map} +1 -1
  26. package/dist/chunks/{DataView-BTi_BZHx.js → DataView-DJMOmlKN.js} +2 -2
  27. package/dist/chunks/{DataView-BTi_BZHx.js.map → DataView-DJMOmlKN.js.map} +1 -1
  28. package/dist/chunks/{DataView-WHRh1o6E.js → DataView-jHxX-6Gh.js} +2 -2
  29. package/dist/chunks/{DataView-WHRh1o6E.js.map → DataView-jHxX-6Gh.js.map} +1 -1
  30. package/dist/chunks/{FormView-DTD-Zy22.js → FormView-C_7xQuI4.js} +3 -3
  31. package/dist/chunks/{FormView-DTD-Zy22.js.map → FormView-C_7xQuI4.js.map} +1 -1
  32. package/dist/chunks/{FormView-BzaGMf5_.js → FormView-D8YrH2V4.js} +3 -3
  33. package/dist/chunks/{FormView-BzaGMf5_.js.map → FormView-D8YrH2V4.js.map} +1 -1
  34. package/dist/chunks/{ListView-BsnnTcmC.js → ListView-CjJ-4gD7.js} +2 -2
  35. package/dist/chunks/{ListView-BsnnTcmC.js.map → ListView-CjJ-4gD7.js.map} +1 -1
  36. package/dist/chunks/{ListView-Xf7kO6Me.js → ListView-mr1Kmuj-.js} +2 -2
  37. package/dist/chunks/{ListView-Xf7kO6Me.js.map → ListView-mr1Kmuj-.js.map} +1 -1
  38. package/dist/chunks/{MetricsCountryMapView-BzGOi1d2.js → MetricsCountryMapView-BIsfmvE0.js} +2 -2
  39. package/dist/chunks/{MetricsCountryMapView-BzGOi1d2.js.map → MetricsCountryMapView-BIsfmvE0.js.map} +1 -1
  40. package/dist/chunks/{MetricsCountryMapView-C-S00Wiw.js → MetricsCountryMapView-D9J4Gwdi.js} +2 -2
  41. package/dist/chunks/{MetricsCountryMapView-C-S00Wiw.js.map → MetricsCountryMapView-D9J4Gwdi.js.map} +1 -1
  42. package/dist/chunks/Modal-DF98u_sN.js +3 -0
  43. package/dist/chunks/Modal-DF98u_sN.js.map +1 -0
  44. package/dist/chunks/Modal-DYJadSN8.js +3 -0
  45. package/dist/chunks/Modal-DYJadSN8.js.map +1 -0
  46. package/dist/chunks/Passkeys-8ko7Rg8G.js +2 -0
  47. package/dist/chunks/Passkeys-8ko7Rg8G.js.map +1 -0
  48. package/dist/chunks/Passkeys-CD8RKUl8.js +2 -0
  49. package/dist/chunks/Passkeys-CD8RKUl8.js.map +1 -0
  50. package/dist/chunks/TokenManager-B-xR8zPl.js +2 -0
  51. package/dist/chunks/TokenManager-B-xR8zPl.js.map +1 -0
  52. package/dist/chunks/TokenManager-CZTL4OqZ.js +2 -0
  53. package/dist/chunks/TokenManager-CZTL4OqZ.js.map +1 -0
  54. package/dist/chunks/{User-KTBU_5cr.js → User-C6Tbn6vZ.js} +2 -2
  55. package/dist/chunks/User-C6Tbn6vZ.js.map +1 -0
  56. package/dist/chunks/{User-B_Urf7U7.js → User-DRbw-wOB.js} +2 -2
  57. package/dist/chunks/User-DRbw-wOB.js.map +1 -0
  58. package/dist/chunks/{UserProfileView-BmduMJ86.js → UserProfileView-DC70hRer.js} +2 -2
  59. package/dist/chunks/{UserProfileView-COxSyPB0.js.map → UserProfileView-DC70hRer.js.map} +1 -1
  60. package/dist/chunks/{UserProfileView-COxSyPB0.js → UserProfileView-TQ9BqUE2.js} +2 -2
  61. package/dist/chunks/{UserProfileView-BmduMJ86.js.map → UserProfileView-TQ9BqUE2.js.map} +1 -1
  62. package/dist/chunks/{View-C8UWvaSM.js → View-BWOE7WJm.js} +2 -2
  63. package/dist/chunks/{View-C8UWvaSM.js.map → View-BWOE7WJm.js.map} +1 -1
  64. package/dist/chunks/{View-Cvs2TY7b.js → View-D6Ug7M6k.js} +2 -2
  65. package/dist/chunks/{View-Cvs2TY7b.js.map → View-D6Ug7M6k.js.map} +1 -1
  66. package/dist/chunks/{WebApp-kbRq7dM_.js → WebApp-By80XfTK.js} +2 -2
  67. package/dist/chunks/{WebApp-kbRq7dM_.js.map → WebApp-By80XfTK.js.map} +1 -1
  68. package/dist/chunks/{WebApp-DuwanN2O.js → WebApp-CLTFSbto.js} +2 -2
  69. package/dist/chunks/{WebApp-DuwanN2O.js.map → WebApp-CLTFSbto.js.map} +1 -1
  70. package/dist/chunks/{admin-BcJ_hgzn.js → admin-CXA-Vkhf.js} +2 -2
  71. package/dist/chunks/{admin-BcJ_hgzn.js.map → admin-CXA-Vkhf.js.map} +1 -1
  72. package/dist/chunks/{admin-DtjMWe6R.js → admin-DcLy2QC2.js} +2 -2
  73. package/dist/chunks/{admin-DtjMWe6R.js.map → admin-DcLy2QC2.js.map} +1 -1
  74. package/dist/chunks/{exportChart-Dn2pioNl.js → exportChart-BQXkqsxe.js} +2 -2
  75. package/dist/chunks/{exportChart-Dn2pioNl.js.map → exportChart-BQXkqsxe.js.map} +1 -1
  76. package/dist/chunks/{exportChart-Bkxr7mCe.js → exportChart-UQ5nq_mR.js} +2 -2
  77. package/dist/chunks/{exportChart-Bkxr7mCe.js.map → exportChart-UQ5nq_mR.js.map} +1 -1
  78. package/dist/chunks/{index-CJeTVskY.js → index-C9wf7N-j.js} +2 -2
  79. package/dist/chunks/{index-CJeTVskY.js.map → index-C9wf7N-j.js.map} +1 -1
  80. package/dist/chunks/{index-BCWkcyOy.js → index-TqW1pAMX.js} +2 -2
  81. package/dist/chunks/{index-BCWkcyOy.js.map → index-TqW1pAMX.js.map} +1 -1
  82. package/dist/chunks/{version-DkxW4rIi.js → version-Bdrq2emC.js} +2 -2
  83. package/dist/chunks/{version-DkxW4rIi.js.map → version-Bdrq2emC.js.map} +1 -1
  84. package/dist/chunks/{version-CB0Ssm9c.js → version-xpJSWoyH.js} +2 -2
  85. package/dist/chunks/{version-CB0Ssm9c.js.map → version-xpJSWoyH.js.map} +1 -1
  86. package/dist/core.css +68 -209
  87. package/dist/css/web-mojo.css +1 -1
  88. package/dist/docit.cjs.js +1 -1
  89. package/dist/docit.es.js +1 -1
  90. package/dist/index.cjs.js +1 -1
  91. package/dist/index.cjs.js.map +1 -1
  92. package/dist/index.es.js +1 -1
  93. package/dist/index.es.js.map +1 -1
  94. package/dist/lightbox.cjs.js +1 -1
  95. package/dist/lightbox.es.js +1 -1
  96. package/dist/map.cjs.js +1 -1
  97. package/dist/map.es.js +1 -1
  98. package/dist/timeline.cjs.js +1 -1
  99. package/dist/timeline.es.js +1 -1
  100. package/dist/user-profile.cjs.js +1 -1
  101. package/dist/user-profile.es.js +1 -1
  102. package/dist/web-mojo.lite.iife.js +95 -168
  103. package/dist/web-mojo.lite.iife.js.map +1 -1
  104. package/dist/web-mojo.lite.iife.min.js +98 -98
  105. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  106. package/package.json +3 -1
  107. package/dist/chunks/Modal-GWjyfcz5.js +0 -3
  108. package/dist/chunks/Modal-GWjyfcz5.js.map +0 -1
  109. package/dist/chunks/Modal-wrfWfQhv.js +0 -3
  110. package/dist/chunks/Modal-wrfWfQhv.js.map +0 -1
  111. package/dist/chunks/Passkeys-BviQX3_5.js +0 -2
  112. package/dist/chunks/Passkeys-BviQX3_5.js.map +0 -1
  113. package/dist/chunks/Passkeys-gXR1Rc6C.js +0 -2
  114. package/dist/chunks/Passkeys-gXR1Rc6C.js.map +0 -1
  115. package/dist/chunks/TokenManager-C6aXkRaI.js +0 -2
  116. package/dist/chunks/TokenManager-C6aXkRaI.js.map +0 -1
  117. package/dist/chunks/TokenManager-DgvhhTqN.js +0 -2
  118. package/dist/chunks/TokenManager-DgvhhTqN.js.map +0 -1
  119. package/dist/chunks/User-B_Urf7U7.js.map +0 -1
  120. package/dist/chunks/User-KTBU_5cr.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Collection-CY6BblFn.js","sources":["../../src/core/utils/DataFormatter.js","../../src/core/utils/MOJOUtils.js","../../src/core/mixins/EventEmitter.js","../../src/core/Rest.js","../../src/core/Model.js","../../src/core/Collection.js"],"sourcesContent":["/**\n * DataFormatter - Universal data formatting utility for MOJO Framework\n * Provides consistent formatting with clean APIs and pipe syntax support\n * @version 1.0.0\n */\n\n// A generic, gray, person icon SVG, encoded as a Base64 data URI.\n// This is used as a fallback for the avatar formatter when no image URL is provided.\nconst GENERIC_AVATAR_SVG = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2NlZDRkYSI+PHBhdGggZD0iTTEyIDEyYzIuMjEgMCA0LTEuNzkgNC00cy0xLjc5LTQtNC00LTQgMS43OS00IDQgMS43OSA0IDQgNHptMCAyYy0yLjY3IDAtOCAxLjM0LTggNHYyaDE2di0yYzAtMi42Ni01LjMzLTQtOC00eiIvPjwvc3ZnPg==';\n\nclass DataFormatter {\n constructor() {\n this.formatters = new Map();\n this.registerBuiltInFormatters();\n }\n\n escapeHtml(str) {\n if (str === null || str === undefined) {\n return '';\n }\n const map = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#039;'\n };\n return String(str).replace(/[&<>\"']/g, (m) => map[m]);\n }\n\n /**\n * Register all built-in formatters\n */\n registerBuiltInFormatters() {\n // Date/Time formatters\n this.register('date', this.date.bind(this));\n this.register('time', this.time.bind(this));\n this.register('datetime', this.datetime.bind(this));\n this.register('datetime_tz', this.datetime_tz.bind(this));\n this.register('datatime_tz', this.datetime_tz.bind(this)); // Alias for common typo\n this.register('date_range', this.date_range.bind(this));\n this.register('datetime_range', this.datetime_range.bind(this));\n this.register('relative', this.relative.bind(this));\n this.register('fromNow', this.relative.bind(this)); // Alias\n this.register('timeago', this.relative.bind(this)); // Alias used by TimelineViewItem\n this.register('relative_short', this.relative_short.bind(this)); // Alias for short relative time\n this.register('iso', this.iso.bind(this));\n this.register('epoch', (v) => {\n if (v === null || v === undefined || v === '') return v;\n const num = parseFloat(v);\n if (isNaN(num)) return v;\n // Convert seconds to milliseconds\n return num * 1000;\n });\n\n // Number formatters\n this.register('number', this.number.bind(this));\n this.register('currency', this.currency.bind(this));\n this.register('percent', this.percent.bind(this));\n this.register('filesize', this.filesize.bind(this));\n this.register('ordinal', this.ordinal.bind(this));\n this.register('compact', this.compact.bind(this));\n\n // Math formatters\n this.register('add', this.add.bind(this));\n this.register('subtract', this.subtract.bind(this));\n this.register('multiply', this.multiply.bind(this));\n this.register('divide', this.divide.bind(this));\n this.register('sub', this.subtract.bind(this)); // Alias\n this.register('mult', this.multiply.bind(this)); // Alias\n this.register('div', this.divide.bind(this)); // Alias\n\n // String formatters\n this.register('uppercase', (v) => String(v).toUpperCase());\n this.register('lowercase', (v) => String(v).toLowerCase());\n this.register('upper', (v) => String(v).toUpperCase());\n this.register('lower', (v) => String(v).toLowerCase());\n this.register('capitalize', this.capitalize.bind(this));\n this.register('caps', this.capitalize.bind(this));\n this.register('replace', this.replace.bind(this));\n this.register('truncate', this.truncate.bind(this));\n this.register('truncate_middle', this.truncate_middle.bind(this));\n this.register('truncate_front', this.truncate_front.bind(this));\n this.register('slug', this.slug.bind(this));\n this.register('initials', this.initials.bind(this));\n this.register('mask', this.mask.bind(this));\n this.register('hex', this.hex.bind(this));\n this.register('tohex', this.hex.bind(this));\n this.register('unhex', this.unhex.bind(this));\n this.register('fromhex', this.unhex.bind(this));\n\n // HTML/Web formatters\n this.register('email', this.email.bind(this));\n this.register('phone', this.phone.bind(this));\n this.register('url', this.url.bind(this));\n this.register('badge', this.badge.bind(this));\n this.register('badgeClass', this.badgeClass.bind(this));\n this.register('status', this.status.bind(this));\n this.register('status_text', this.status_text.bind(this));\n this.register('status_icon', this.status_icon.bind(this));\n this.register('boolean', this.boolean.bind(this));\n this.register('bool', this.bool.bind(this));\n this.register('yesno', (v) => this.boolean(v, 'Yes', 'No'));\n this.register('yesnoicon', this.yesnoicon.bind(this));\n this.register('icon', this.icon.bind(this));\n this.register('avatar', this.avatar.bind(this));\n this.register('image', this.image.bind(this));\n this.register('tooltip', this.tooltip.bind(this));\n this.register('linkify', this.linkify.bind(this));\n this.register('clipboard', this.clipboard.bind(this));\n\n // Utility formatters\n this.register('default', this.default.bind(this));\n this.register('equals', this.equals.bind(this));\n this.register('json', this.json.bind(this));\n this.register('raw', (v) => v);\n this.register('custom', (v, fn) => typeof fn === 'function' ? fn(v) : v);\n this.register('iter', this.iter.bind(this));\n this.register('keys', (v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.keys(v);\n }\n return null;\n });\n this.register('values', (v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.values(v);\n }\n return null;\n });\n\n // Text/Content formatters\n this.register('plural', this.plural.bind(this));\n this.register('list', this.formatList.bind(this));\n this.register('duration', this.duration.bind(this));\n this.register('hash', this.hash.bind(this));\n this.register('stripHtml', this.stripHtml.bind(this));\n this.register('highlight', this.highlight.bind(this));\n this.register('nl2br', this.nl2br.bind(this));\n this.register('code', this.code.bind(this));\n this.register('pre', (v) => `<pre class=\"bg-light p-2 rounded border\">${this.escapeHtml(String(v))}</pre>`);\n }\n\n relative_short(value) {\n return this.relative(value, true);\n }\n\n linkify(value, options = {}) {\n if (value === null || value === undefined) return '';\n const text = String(value);\n const escaped = this.escapeHtml(text);\n const defaults = { urls: true, emails: true, target: '_blank', rel: 'noopener noreferrer' };\n const opts = (options && typeof options === 'object') ? { ...defaults, ...options } : defaults;\n\n let result = escaped;\n\n // URLs: http(s):// and www.\n if (opts.urls !== false) {\n const urlRegex = /(^|\\s)((?:https?:\\/\\/|www\\.)[^\\s<]+)/gi;\n result = result.replace(urlRegex, (match, prefix, url) => {\n const href = url.startsWith('www.') ? `https://${url}` : url;\n return `${prefix}<a href=\"${href}\" target=\"${opts.target}\" rel=\"${opts.rel}\">${url}</a>`;\n });\n }\n\n // Emails\n if (opts.emails !== false) {\n const emailRegex = /\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b/gi;\n result = result.replace(emailRegex, (email) => `<a href=\"mailto:${email}\">${email}</a>`);\n }\n\n return result;\n }\n\n clipboard(value, mode = 'text') {\n if (value === null || value === undefined) return '';\n const text = String(value);\n const escapedText = this.escapeHtml(text);\n const showText = mode !== 'icon-only';\n\n const buttonHtml = `\n <button type=\"button\"\n class=\"btn btn-sm btn-outline-secondary ms-1 p-0 border-0 bg-transparent\"\n title=\"Copy\"\n data-bs-toggle=\"tooltip\"\n data-action=\"copy-to-clipboard\"\n data-clipboard=\"${escapedText}\">\n <i class=\"bi bi-clipboard\"></i>\n </button>`.trim();\n\n return `\n <span class=\"mojo-clipboard d-inline-flex align-items-center\">\n ${showText ? `<span class=\"font-monospace\">${escapedText}</span>` : ''}\n ${buttonHtml}\n </span>\n `;\n }\n\n nl2br(value) {\n if (value === null || value === undefined) return '';\n return this.escapeHtml(String(value)).replace(/\\r\\n|\\r|\\n/g, '<br>');\n }\n\n code(value, lang = '') {\n if (value === null || value === undefined) return '';\n const language = lang ? `language-${this.escapeHtml(String(lang))}` : '';\n const content = this.escapeHtml(String(value));\n return `<pre class=\"bg-light p-2 rounded border\"><code class=\"${language}\">${content}</code></pre>`;\n }\n\n /**\n * Register a custom formatter\n * @param {string} name - Formatter name\n * @param {Function} formatter - Formatter function\n * @returns {DataFormatter} This instance for chaining\n */\n register(name, formatter) {\n if (typeof formatter !== 'function') {\n throw new Error(`Formatter must be a function, got ${typeof formatter}`);\n }\n this.formatters.set(name.toLowerCase(), formatter);\n return this;\n }\n\n /**\n * Apply a formatter\n * @param {string} name - Formatter name\n * @param {*} value - Value to format\n * @param {...*} args - Additional arguments\n * @returns {*} Formatted value\n */\n apply(name, value, ...args) {\n try {\n const formatter = this.formatters.get(name.toLowerCase());\n if (!formatter) {\n console.warn(`Formatter '${name}' not found`);\n return value;\n }\n return formatter(value, ...args);\n } catch (error) {\n console.error(`Error in formatter '${name}':`, error);\n return value;\n }\n }\n\n /**\n * Process pipe string\n * @param {*} value - Value to format\n * @param {string} pipeString - Pipe string (e.g., \"date('YYYY-MM-DD')|uppercase\")\n * @param {object} context - Optional context for resolving variables in formatter arguments\n * @returns {*} Formatted value\n */\n pipe(value, pipeString, context = null) {\n if (!pipeString) return value;\n\n // Split by pipe and process each formatter\n const pipes = this.parsePipeString(pipeString, context);\n\n return pipes.reduce((currentValue, pipe) => {\n return this.apply(pipe.name, currentValue, ...pipe.args);\n }, value);\n }\n\n /**\n * Parse pipe string into formatter calls\n * @param {string} pipeString - Pipe string\n * @param {object} context - Optional context for resolving variables\n * @returns {Array} Array of {name, args} objects\n */\n parsePipeString(pipeString, context = null) {\n const pipes = [];\n const tokens = pipeString.split('|').map(s => s.trim());\n\n for (const token of tokens) {\n const parsed = this.parseFormatter(token, context);\n if (parsed) {\n pipes.push(parsed);\n }\n }\n\n return pipes;\n }\n\n /**\n * Parse individual formatter with arguments\n * Supports both syntaxes:\n * - Parentheses: formatter('arg1', 'arg2', 3)\n * - Colon: formatter:'arg1':'arg2':3\n *\n * @param {string} token - Formatter token\n * @param {object} context - Optional context for resolving variables\n * @returns {Object} {name, args} object\n */\n parseFormatter(token, context = null) {\n // Try parentheses syntax first: formatter(arg1, arg2)\n const parenMatch = token.match(/^([a-zA-Z_]\\w*)\\s*\\((.*)\\)$/);\n if (parenMatch) {\n const [, name, argsString] = parenMatch;\n const args = argsString ? this.parseArguments(argsString, context) : [];\n return { name, args };\n }\n\n // Try colon syntax: formatter:arg1:arg2\n const colonMatch = token.match(/^([a-zA-Z_]\\w*)(?::(.+))?$/);\n if (colonMatch) {\n const [, name, argsString] = colonMatch;\n const args = argsString ? this.parseColonArguments(argsString, context) : [];\n return { name, args };\n }\n\n return null;\n }\n\n /**\n * Parse formatter arguments (comma-separated, parentheses syntax)\n * @param {string} argsString - Arguments string\n * @param {object} context - Optional context for resolving variables\n * @returns {Array} Parsed arguments\n */\n parseArguments(argsString, context = null) {\n const args = [];\n let current = '';\n let inQuotes = false;\n let quoteChar = null;\n let depth = 0;\n\n for (let i = 0; i < argsString.length; i++) {\n const char = argsString[i];\n\n if (!inQuotes && (char === '\"' || char === \"'\")) {\n inQuotes = true;\n quoteChar = char;\n current += char;\n } else if (inQuotes && char === quoteChar && argsString[i - 1] !== '\\\\') {\n inQuotes = false;\n quoteChar = null;\n current += char;\n } else if (!inQuotes && char === '{') {\n depth++;\n current += char;\n } else if (!inQuotes && char === '}') {\n depth--;\n current += char;\n } else if (!inQuotes && depth === 0 && char === ',') {\n args.push(this.parseValue(current.trim(), context));\n current = '';\n } else {\n current += char;\n }\n }\n\n if (current.trim()) {\n args.push(this.parseValue(current.trim(), context));\n }\n\n return args;\n }\n\n /**\n * Parse formatter arguments (colon-separated syntax)\n * Handles quoted strings with colons inside them\n * @param {string} argsString - Arguments string\n * @param {object} context - Optional context for resolving variables\n * @returns {Array} Parsed arguments\n */\n parseColonArguments(argsString, context = null) {\n const args = [];\n let current = '';\n let inQuotes = false;\n let quoteChar = null;\n\n for (let i = 0; i < argsString.length; i++) {\n const char = argsString[i];\n\n if (!inQuotes && (char === '\"' || char === \"'\")) {\n inQuotes = true;\n quoteChar = char;\n current += char;\n } else if (inQuotes && char === quoteChar && argsString[i - 1] !== '\\\\') {\n inQuotes = false;\n quoteChar = null;\n current += char;\n } else if (!inQuotes && char === ':') {\n args.push(this.parseValue(current.trim(), context));\n current = '';\n } else {\n current += char;\n }\n }\n\n if (current.trim()) {\n args.push(this.parseValue(current.trim(), context));\n }\n\n return args;\n }\n\n /**\n * Parse a single value\n * @param {string} value - Value string\n * @param {object} context - Optional context for resolving variables\n * @returns {*} Parsed value\n */\n parseValue(value, context = null) {\n // Remove quotes if present - quoted strings are always literals\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n return value.slice(1, -1);\n }\n\n // Boolean values\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value === 'null') return null;\n if (value === 'undefined') return undefined;\n\n // Numbers\n if (!isNaN(value) && value !== '') {\n return Number(value);\n }\n\n // Objects\n if (value.startsWith('{') && value.endsWith('}')) {\n try {\n return JSON.parse(value);\n } catch (e) {\n // Not valid JSON, continue\n }\n }\n\n // Try to resolve from context if available\n // This allows: {{value|tooltip:title:'top'}} where title is a context variable\n // Also supports dot notation: {{value|tooltip:model.email}}\n if (context && this.isIdentifier(value)) {\n // For simple identifiers, try direct property access\n if (!value.includes('.')) {\n if (Object.prototype.hasOwnProperty.call(context, value)) {\n return context[value];\n }\n }\n\n // Try get() method (for Models/Views) - handles dot notation\n if (context.get && typeof context.get === 'function') {\n const contextValue = context.get(value);\n if (contextValue !== undefined) {\n return contextValue;\n }\n }\n // Try getContextValue() method - handles dot notation\n if (context.getContextValue && typeof context.getContextValue === 'function') {\n const contextValue = context.getContextValue(value);\n if (contextValue !== undefined) {\n return contextValue;\n }\n }\n\n // For dot notation on plain objects, use MOJOUtils\n if (value.includes('.')) {\n // Import MOJOUtils if needed for nested property access\n const MOJOUtils = window.MOJOUtils || (typeof require !== 'undefined' ? require('./MOJOUtils.js').default : null);\n if (MOJOUtils) {\n const contextValue = MOJOUtils.getNestedValue(context, value);\n if (contextValue !== undefined) {\n return contextValue;\n }\n }\n }\n }\n\n // Fall back to treating as literal string\n return value;\n }\n\n /**\n * Check if a value is a valid identifier (variable name or dot-notation path)\n * @param {string} value - Value to check\n * @returns {boolean} True if valid identifier or path\n */\n isIdentifier(value) {\n // Valid JavaScript identifier or dot notation path\n // Examples: \"email\", \"model.email\", \"user.profile.name\"\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/.test(value);\n }\n\n // ============= Date/Time Formatters =============\n\n /**\n * Format date\n * @param {*} value - Date value\n * @param {string} format - Date format pattern\n * @returns {string} Formatted date\n */\n date(value, format = 'MM/DD/YYYY') {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n let date;\n if (value instanceof Date) {\n date = value;\n } else if (typeof value === 'string') {\n // Handle date strings more carefully to avoid timezone issues\n // If it's a date-only string (YYYY-MM-DD), parse as local time\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n const [year, month, day] = value.split('-').map(Number);\n date = new Date(year, month - 1, day);\n } else {\n date = new Date(value);\n }\n } else {\n date = new Date(value);\n }\n\n if (isNaN(date.getTime())) return String(value);\n\n // Build replacements with placeholders to avoid corruption\n const tokens = {\n 'YYYY': date.getFullYear(),\n 'YY': String(date.getFullYear()).slice(-2),\n 'MMMM': date.toLocaleDateString('en-US', { month: 'long' }),\n 'MMM': date.toLocaleDateString('en-US', { month: 'short' }),\n 'MM': String(date.getMonth() + 1).padStart(2, '0'),\n 'M': date.getMonth() + 1,\n 'dddd': date.toLocaleDateString('en-US', { weekday: 'long' }),\n 'ddd': date.toLocaleDateString('en-US', { weekday: 'short' }),\n 'DD': String(date.getDate()).padStart(2, '0'),\n 'D': date.getDate()\n };\n\n // Replace tokens using a single pass with placeholders\n let result = format;\n const tokenPattern = new RegExp(`(${Object.keys(tokens).join('|')})`, 'g');\n result = result.replace(tokenPattern, (match) => tokens[match] || match);\n\n return result;\n }\n\n /**\n * Format time\n * @param {*} value - Time value\n * @param {string} format - Time format pattern\n * @returns {string} Formatted time\n */\n time(value, format = 'HH:mm:ss') {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n\n const hours = date.getHours();\n const replacements = {\n 'HH': String(hours).padStart(2, '0'),\n 'H': hours,\n 'hh': String(hours % 12 || 12).padStart(2, '0'),\n 'h': hours % 12 || 12,\n 'mm': String(date.getMinutes()).padStart(2, '0'),\n 'm': date.getMinutes(),\n 'ss': String(date.getSeconds()).padStart(2, '0'),\n 's': date.getSeconds(),\n 'A': hours >= 12 ? 'PM' : 'AM',\n 'a': hours >= 12 ? 'pm' : 'am'\n };\n\n let result = format;\n const sortedKeys = Object.keys(replacements).sort((a, b) => b.length - a.length);\n for (const key of sortedKeys) {\n result = result.replace(new RegExp(key, 'g'), replacements[key]);\n }\n\n return result;\n }\n\n /**\n * Format date and time\n * @param {*} value - DateTime value\n * @param {string} dateFormat - Date format\n * @param {string} timeFormat - Time format\n * @returns {string} Formatted datetime\n */\n datetime(value, dateFormat = 'MM/DD/YYYY', timeFormat = 'HH:mm:ss') {\n value = this.normalizeEpoch(value);\n const dateStr = this.date(value, dateFormat);\n const timeStr = this.time(value, timeFormat);\n return dateStr && timeStr ? `${dateStr} ${timeStr}` : '';\n }\n\n /**\n * Format date and time with short timezone abbreviation (e.g., EST, PDT)\n * @param {*} value - DateTime value\n * @param {string} dateFormat - Date format\n * @param {string} timeFormat - Time format\n * @param {Object} options - Options: { timeZone?: string, locale?: string }\n * @returns {string} Formatted datetime with timezone abbreviation\n */\n datetime_tz(value, dateFormat = 'MM/DD/YYYY', timeFormat = 'HH:mm:ss', options = {}) {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n\n const locale = (options && options.locale) || 'en-US';\n const timeZone = options && options.timeZone ? options.timeZone : undefined;\n\n // Helper: build short TZ abbreviation in the requested zone\n const getTzAbbr = () => {\n let abbr = '';\n try {\n const parts = new Intl.DateTimeFormat(locale, {\n hour: '2-digit',\n minute: '2-digit',\n timeZoneName: 'short',\n ...(timeZone ? { timeZone } : {})\n }).formatToParts(date);\n const tzPart = parts.find(p => p.type === 'timeZoneName');\n abbr = tzPart ? tzPart.value : '';\n\n // Try to avoid GMT offsets if browser returns them\n if (abbr && /^GMT[+-]/i.test(abbr)) {\n try {\n const parts2 = new Intl.DateTimeFormat(locale, {\n timeStyle: 'short',\n timeZoneName: 'short',\n ...(timeZone ? { timeZone } : {})\n }).formatToParts(date);\n const tz2 = parts2.find(p => p.type === 'timeZoneName');\n if (tz2 && tz2.value && !/^GMT[+-]/i.test(tz2.value)) {\n abbr = tz2.value;\n }\n } catch (e) { void 0; }\n }\n // Collapse long names like \"Eastern Daylight Time\" to initials \"EDT\"\n if (abbr && /\\s/.test(abbr)) {\n const initials = abbr.split(/\\s+/).map(w => w[0]).join('').toUpperCase();\n if (initials.length >= 2 && initials.length <= 4) {\n abbr = initials;\n }\n }\n } catch (e) {\n abbr = '';\n }\n return abbr;\n };\n\n // If no specific timeZone requested, fall back to existing date/time logic\n if (!timeZone) {\n const dateStr = this.date(date, dateFormat);\n const timeStr = this.time(date, timeFormat);\n const abbr = getTzAbbr();\n return dateStr && timeStr ? `${dateStr} ${timeStr} ${abbr}`.trim() : '';\n }\n\n // With a specific timeZone, generate tokens from Intl parts in that zone\n const parts = new Intl.DateTimeFormat(locale, {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hourCycle: 'h23'\n }).formatToParts(date);\n const get = (type) => {\n const p = parts.find(pt => pt.type === type);\n return p ? p.value : '';\n };\n\n const y4 = get('year');\n const M2 = get('month'); // '01'..'12'\n const D2 = get('day'); // '01'..'31'\n const H2 = get('hour'); // '00'..'23'\n const m2 = get('minute');\n const s2 = get('second');\n\n const M = M2 ? String(parseInt(M2, 10)) : '';\n const D = D2 ? String(parseInt(D2, 10)) : '';\n const H = H2 ? String(parseInt(H2, 10)) : '';\n const hNum = H2 ? ((parseInt(H2, 10) % 12) || 12) : '';\n const A = H2 ? (parseInt(H2, 10) >= 12 ? 'PM' : 'AM') : '';\n const a = A ? A.toLowerCase() : '';\n\n const monthLong = new Intl.DateTimeFormat(locale, { timeZone, month: 'long' }).format(date);\n const monthShort = new Intl.DateTimeFormat(locale, { timeZone, month: 'short' }).format(date);\n const weekdayLong = new Intl.DateTimeFormat(locale, { timeZone, weekday: 'long' }).format(date);\n const weekdayShort = new Intl.DateTimeFormat(locale, { timeZone, weekday: 'short' }).format(date);\n\n const dateTokens = {\n 'YYYY': y4,\n 'YY': y4 ? y4.slice(-2) : '',\n 'MMMM': monthLong,\n 'MMM': monthShort,\n 'MM': M2,\n 'M': M,\n 'dddd': weekdayLong,\n 'ddd': weekdayShort,\n 'DD': D2,\n 'D': D\n };\n const timeTokens = {\n 'HH': H2,\n 'H': H,\n 'hh': hNum !== '' ? String(hNum).padStart(2, '0') : '',\n 'h': hNum !== '' ? String(hNum) : '',\n 'mm': m2,\n 'm': m2 ? String(parseInt(m2, 10)) : '',\n 'ss': s2,\n 's': s2 ? String(parseInt(s2, 10)) : '',\n 'A': A,\n 'a': a\n };\n\n const replaceTokens = (fmt, tokens) => {\n if (!fmt) return '';\n const pattern = new RegExp(`(${Object.keys(tokens).sort((a, b) => b.length - a.length).join('|')})`, 'g');\n return fmt.replace(pattern, (match) => tokens[match] ?? match);\n };\n\n const dateStr = replaceTokens(dateFormat, dateTokens);\n const timeStr = replaceTokens(timeFormat, timeTokens);\n const abbr = getTzAbbr();\n\n return dateStr && timeStr ? `${dateStr} ${timeStr} ${abbr}`.trim() : '';\n }\n\n normalizeEpoch(value) {\n // Pass-through for Date instances — callers wrap with `new Date(value)`.\n if (value instanceof Date) return value;\n\n // ISO-8601 / parseable strings — Number('2026-04-25T...') is NaN and\n // would silently drop to '' below; recover by trying Date.parse first.\n if (typeof value === 'string') {\n const asNum = Number(value);\n if (Number.isFinite(asNum)) {\n value = asNum;\n } else {\n const parsed = Date.parse(value);\n if (Number.isFinite(parsed)) return parsed; // already ms epoch\n return '';\n }\n } else if (typeof value !== 'number') {\n value = Number(value);\n }\n\n // Check if the number is valid\n if (isNaN(value)) return '';\n\n // treat anything smaller than year 2000 in ms as seconds\n if (value < 1e11) { // less than ~Sat Mar 03 1973 09:46:40 GMT\n return value * 1000; // seconds → ms\n } else if (value > 1e12 && value < 1e13) {\n return value; // already ms\n } else {\n throw new Error(\"Value doesn't look like epoch seconds or ms\");\n }\n }\n\n /**\n * Format date range\n * @param {*} startValue - Start date (required)\n * @param {*} endValue - End date (defaults to now)\n * @param {string} format - Date format (defaults to 'MM/DD/YYYY')\n * @returns {string} Formatted date range (e.g., \"01/01/2025 - 01/31/2025\")\n */\n date_range(startValue, endValue = null, format = 'MM/DD/YYYY') {\n if (!startValue) return '';\n\n const endVal = endValue || new Date();\n const startStr = this.date(startValue, format);\n const endStr = this.date(endVal, format);\n\n if (!startStr || !endStr) return '';\n return `${startStr} - ${endStr}`;\n }\n\n /**\n * Format datetime range\n * @param {*} startValue - Start datetime (required)\n * @param {*} endValue - End datetime (defaults to now)\n * @param {string} dateFormat - Date format (defaults to 'MM/DD/YYYY')\n * @param {string} timeFormat - Time format (defaults to 'HH:mm')\n * @returns {string} Formatted datetime range (e.g., \"01/01/2025 14:30 - 01/31/2025 16:45\")\n */\n datetime_range(startValue, endValue = null, dateFormat = 'MM/DD/YYYY', timeFormat = 'HH:mm') {\n if (!startValue) return '';\n\n const endVal = endValue || new Date();\n const startStr = this.datetime(startValue, dateFormat, timeFormat);\n const endStr = this.datetime(endVal, dateFormat, timeFormat);\n\n if (!startStr || !endStr) return '';\n return `${startStr} - ${endStr}`;\n }\n\n /**\n * Format relative time\n * @param {*} value - Date value\n * @param {boolean} short - Use short format\n * @returns {string} Relative time string\n */\n relative(value, short = false) {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n\n const now = new Date();\n const diffMs = date - now; // Changed to support future dates\n const absDiffMs = Math.abs(diffMs);\n const diffSecs = Math.floor(absDiffMs / 1000);\n const diffMins = Math.floor(diffSecs / 60);\n const diffHours = Math.floor(diffMins / 60);\n const diffDays = Math.floor(diffHours / 24);\n const isFuture = diffMs > 0;\n\n if (short) {\n if (diffDays > 365) return Math.floor(diffDays / 365) + 'y';\n if (diffDays > 30) return Math.floor(diffDays / 30) + 'mo';\n if (diffDays > 7) return Math.floor(diffDays / 7) + 'w';\n if (diffDays > 0) return diffDays + 'd';\n if (diffHours > 0) return diffHours + 'h';\n if (diffMins > 0) return diffMins + 'm';\n return 'now';\n }\n\n if (diffDays > 365) {\n const years = Math.floor(diffDays / 365);\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + years + ' year' + (years > 1 ? 's' : '') + suffix;\n }\n if (diffDays > 30) {\n const months = Math.floor(diffDays / 30);\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + months + ' month' + (months > 1 ? 's' : '') + suffix;\n }\n if (diffDays > 7) {\n const weeks = Math.floor(diffDays / 7);\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + weeks + ' week' + (weeks > 1 ? 's' : '') + suffix;\n }\n if (diffDays === 1) return isFuture ? 'tomorrow' : 'yesterday';\n if (diffDays > 0) {\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + diffDays + ' days' + suffix;\n }\n if (diffHours > 0) {\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + diffHours + ' hour' + (diffHours > 1 ? 's' : '') + suffix;\n }\n if (diffMins > 0) {\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + diffMins + ' minute' + (diffMins > 1 ? 's' : '') + suffix;\n }\n if (diffSecs > 30) {\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + diffSecs + ' seconds' + suffix;\n }\n\n return 'just now';\n }\n\n /**\n * Format ISO date\n * @param {*} value - Date value\n * @param {boolean} dateOnly - Return date only\n * @returns {string} ISO date string\n */\n iso(value, dateOnly = false) {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n\n if (dateOnly) {\n return date.toISOString().split('T')[0];\n }\n return date.toISOString();\n }\n\n // ============= Number Formatters =============\n\n /**\n * Format number\n * @param {*} value - Number value\n * @param {number} decimals - Decimal places\n * @param {string} locale - Locale string\n * @returns {string} Formatted number\n */\n number(value, decimals = 2, locale = 'en-US') {\n const num = parseFloat(value);\n if (isNaN(num)) return String(value);\n\n return num.toLocaleString(locale, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format currency\n * @param {*} value - Number value in cents\n * @param {string} symbol - Currency symbol\n * @param {number} decimals - Decimal places\n * @returns {string} Formatted currency\n */\n currency(value, symbol = '$', decimals = 2) {\n const num = parseInt(value);\n if (isNaN(num)) return String(value);\n\n // Convert cents to dollars using string manipulation to avoid floating point issues\n const centsStr = Math.abs(num).toString();\n const sign = num < 0 ? '-' : '';\n\n let dollars, cents;\n if (centsStr.length <= 2) {\n dollars = '0';\n cents = centsStr.padStart(2, '0');\n } else {\n dollars = centsStr.slice(0, -2);\n cents = centsStr.slice(-2);\n }\n\n // Add thousands separators to dollars part\n dollars = dollars.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n\n // Format based on requested decimals\n let formatted;\n if (decimals === 0) {\n // Round to nearest dollar\n const totalCents = parseInt(cents);\n if (totalCents >= 50) {\n dollars = (parseInt(dollars.replace(/,/g, '')) + 1).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n }\n formatted = dollars;\n } else if (decimals === 2) {\n formatted = `${dollars}.${cents}`;\n } else {\n // For other decimal places, truncate or pad cents as needed\n const adjustedCents = cents.slice(0, decimals).padEnd(decimals, '0');\n formatted = `${dollars}.${adjustedCents}`;\n }\n\n return sign + symbol + formatted;\n }\n\n /**\n * Format percentage\n * @param {*} value - Number value\n * @param {number} decimals - Decimal places\n * @param {boolean} multiply - Multiply by 100\n * @returns {string} Formatted percentage\n */\n percent(value, decimals = 0, multiply = true) {\n const num = parseFloat(value);\n if (isNaN(num)) return String(value);\n\n const percent = multiply ? num * 100 : num;\n return this.number(percent, decimals) + '%';\n }\n\n /**\n * Format file size\n * @param {*} value - Size in bytes\n * @param {boolean} binary - Use binary units (1024)\n * @param {number} decimals - Decimal places\n * @returns {string} Formatted file size\n */\n filesize(value, binary = false, decimals = 1) {\n const bytes = parseInt(value);\n if (isNaN(bytes)) return String(value);\n\n const units = binary ?\n ['B', 'KiB', 'MiB', 'GiB', 'TiB'] :\n ['B', 'KB', 'MB', 'GB', 'TB'];\n const divisor = binary ? 1024 : 1000;\n\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= divisor && unitIndex < units.length - 1) {\n size /= divisor;\n unitIndex++;\n }\n\n const decimalPlaces = unitIndex === 0 ? 0 : decimals;\n return `${size.toFixed(decimalPlaces)} ${units[unitIndex]}`;\n }\n\n /**\n * Format ordinal number\n * @param {*} value - Number value\n * @param {boolean} suffixOnly - Return suffix only\n * @returns {string} Ordinal number\n */\n ordinal(value, suffixOnly = false) {\n const num = parseInt(value);\n if (isNaN(num)) return String(value);\n\n const j = num % 10;\n const k = num % 100;\n\n let suffix = 'th';\n if (j === 1 && k !== 11) suffix = 'st';\n else if (j === 2 && k !== 12) suffix = 'nd';\n else if (j === 3 && k !== 13) suffix = 'rd';\n\n return suffixOnly ? suffix : num + suffix;\n }\n\n /**\n * Format compact number\n * @param {*} value - Number value\n * @param {number} decimals - Decimal places\n * @returns {string} Compact number\n */\n compact(value, decimals = 1) {\n const num = parseFloat(value);\n if (isNaN(num)) return String(value);\n\n const abs = Math.abs(num);\n const sign = num < 0 ? '-' : '';\n\n if (abs >= 1e9) {\n return sign + (abs / 1e9).toFixed(decimals) + 'B';\n }\n if (abs >= 1e6) {\n return sign + (abs / 1e6).toFixed(decimals) + 'M';\n }\n if (abs >= 1e3) {\n return sign + (abs / 1e3).toFixed(decimals) + 'K';\n }\n\n return String(num);\n }\n\n /**\n * Add numbers\n * @param {*} value - First number\n * @param {*} addend - Number to add\n * @returns {number} Sum\n */\n add(value, addend) {\n const num1 = parseFloat(value);\n const num2 = parseFloat(addend);\n if (isNaN(num1) || isNaN(num2)) return value;\n return num1 + num2;\n }\n\n /**\n * Subtract numbers\n * @param {*} value - First number\n * @param {*} subtrahend - Number to subtract\n * @returns {number} Difference\n */\n subtract(value, subtrahend) {\n const num1 = parseFloat(value);\n const num2 = parseFloat(subtrahend);\n if (isNaN(num1) || isNaN(num2)) return value;\n return num1 - num2;\n }\n\n /**\n * Multiply numbers\n * @param {*} value - First number\n * @param {*} multiplier - Number to multiply by\n * @returns {number} Product\n */\n multiply(value, multiplier) {\n const num1 = parseFloat(value);\n const num2 = parseFloat(multiplier);\n if (isNaN(num1) || isNaN(num2)) return value;\n return num1 * num2;\n }\n\n /**\n * Divide numbers\n * @param {*} value - Dividend\n * @param {*} divisor - Divisor\n * @returns {number} Quotient\n */\n divide(value, divisor) {\n const num1 = parseFloat(value);\n const num2 = parseFloat(divisor);\n if (isNaN(num1) || isNaN(num2) || num2 === 0) return value;\n return num1 / num2;\n }\n\n // ============= String Formatters =============\n\n /**\n * Capitalize string\n * @param {*} value - String value\n * @param {boolean} all - Capitalize all words (default: true). If false, only capitalizes first letter\n * @returns {string} Capitalized string\n */\n capitalize(value, all = true) {\n const str = String(value);\n if (!str) return '';\n\n if (all) {\n return str.replace(/\\b\\w/g, c => c.toUpperCase());\n }\n return str.charAt(0).toUpperCase() + str.slice(1);\n }\n\n /**\n * Replace occurrences in a string\n * @param {*} value - String value\n * @param {*} search - Search value (string or RegExp-ish string like \"/_/g\")\n * @param {*} replacement - Replacement string\n * @param {string} flags - Optional RegExp flags when search is a plain string\n * @returns {string} Updated string\n *\n * Examples:\n * - {{model.name|replace:'_':''}} // underscores removed (all occurrences)\n * - {{model.name|replace('_', '')}} // parentheses syntax\n * - {{model.name|replace:'_':' ':'g'}} // replace all underscores with spaces\n * - {{model.name|replace:'/[_-]+/g':' '}} // regex form in a string\n */\n replace(value, search, replacement = '', flags = 'g') {\n if (value === null || value === undefined) return '';\n const str = String(value);\n\n if (search === null || search === undefined || search === '') {\n return str;\n }\n\n // If a real RegExp was passed in, use it directly.\n if (search instanceof RegExp) {\n return str.replace(search, String(replacement));\n }\n\n const searchStr = String(search);\n\n // Support \"/pattern/flags\" style passed as a string.\n // Note: this is intentionally simple and doesn't attempt to parse escaped slashes.\n const regexLike = searchStr.match(/^\\/(.+)\\/([a-z]*)$/i);\n if (regexLike) {\n const [, pattern, rxFlags] = regexLike;\n try {\n return str.replace(new RegExp(pattern, rxFlags), String(replacement));\n } catch (e) {\n // Fall back to string replace below if regex construction fails\n }\n }\n\n // Default: string replace. If flags includes 'g', replace all occurrences.\n if (String(flags).includes('g')) {\n return str.split(searchStr).join(String(replacement));\n }\n\n return str.replace(searchStr, String(replacement));\n }\n\n /**\n * Truncate string\n * @param {*} value - String value\n * @param {number} length - Max length\n * @param {string} suffix - Suffix to append\n * @returns {string} Truncated string\n */\n truncate(value, length = 50, suffix = '...') {\n const str = String(value);\n if (str.length <= length) return str;\n return str.substring(0, length) + suffix;\n }\n\n /**\n * Truncate keeping only the end of the string\n * @param {*} value - String value\n * @param {number} length - Characters to keep at the end\n * @param {string} prefix - Text to prepend when truncating\n * @returns {string} Truncated string\n */\n truncate_front(value, length = 8, prefix = '...') {\n const str = String(value);\n if (str.length <= length) {\n return str;\n }\n return `${prefix}${str.slice(-length)}`;\n }\n\n /**\n * Truncate string in the middle\n * @param {*} value - String value\n * @param {number} size - The total number of characters to keep (half for the start, half for the end).\n * @param {string} replace - The character(s) to use for the middle part.\n * @returns {string} Truncated string\n */\n truncate_middle(value, size = 8, replace = '***') {\n const str = String(value);\n if (str.length <= size) {\n return str;\n }\n\n const halfSize = Math.floor(size / 2);\n const front = str.substring(0, halfSize);\n const back = str.substring(str.length - halfSize);\n\n return `${front}${replace}${back}`;\n }\n\n /**\n * Create slug from string\n * @param {*} value - String value\n * @param {string} separator - Word separator\n * @returns {string} Slug\n */\n slug(value, separator = '-') {\n const str = String(value);\n return str\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, separator)\n .replace(new RegExp(`${separator}+`, 'g'), separator)\n .replace(new RegExp(`^${separator}|${separator}$`, 'g'), '');\n }\n\n /**\n * Get initials from string\n * @param {*} value - String value\n * @param {number} count - Number of initials\n * @returns {string} Initials\n */\n initials(value, count = 2) {\n const str = String(value);\n const words = str.split(/\\s+/).filter(w => w.length > 0);\n return words\n .slice(0, count)\n .map(word => word.charAt(0).toUpperCase())\n .join('');\n }\n\n /**\n * Mask string\n * @param {*} value - String value\n * @param {string} char - Mask character\n * @param {number} showLast - Number of chars to show at end\n * @returns {string} Masked string\n */\n mask(value, char = '*', showLast = 4) {\n const str = String(value);\n if (str.length <= showLast) return str;\n\n const masked = char.repeat(Math.max(0, str.length - showLast));\n const visible = str.slice(-showLast);\n return masked + visible;\n }\n\n // ============= HTML/Web Formatters =============\n\n /**\n * Format email\n * @param {*} value - Email value\n * @param {Object} options - Options\n * @returns {string} Formatted email\n */\n email(value, options = {}) {\n const email = String(value).trim();\n if (!email) return '';\n\n if (options.link === false) {\n return email;\n }\n\n const subject = options.subject ? `?subject=${encodeURIComponent(options.subject)}` : '';\n const body = options.body ? `&body=${encodeURIComponent(options.body)}` : '';\n const className = options.class ? ` class=\"${options.class}\"` : '';\n\n return `<a href=\"mailto:${email}${subject}${body}\"${className}>${email}</a>`;\n }\n\n /**\n * Format phone number\n * @param {*} value - Phone value\n * @param {string} format - Format type\n * @param {boolean} link - Create tel link\n * @returns {string} Formatted phone\n */\n phone(value, format = 'US', link = true) {\n let phone = String(value).replace(/\\D/g, '');\n\n let formatted = phone;\n if (format === 'US') {\n if (phone.length === 10) {\n formatted = `(${phone.slice(0, 3)}) ${phone.slice(3, 6)}-${phone.slice(6)}`;\n } else if (phone.length === 11 && phone[0] === '1') {\n formatted = `+1 (${phone.slice(1, 4)}) ${phone.slice(4, 7)}-${phone.slice(7)}`;\n }\n }\n\n if (!link) {\n return formatted;\n }\n\n return `<a href=\"tel:${phone}\">${formatted}</a>`;\n }\n\n /**\n * Format URL\n * @param {*} value - URL value\n * @param {string} text - Link text\n * @param {boolean} newWindow - Open in new window\n * @returns {string} Formatted URL\n */\n url(value, text = null, newWindow = true) {\n let url = String(value).trim();\n if (!url) return '';\n\n // Add protocol if missing\n if (!/^https?:\\/\\//.test(url)) {\n url = 'https://' + url;\n }\n\n const linkText = text || url;\n const target = newWindow ? ' target=\"_blank\"' : '';\n const rel = newWindow ? ' rel=\"noopener noreferrer\"' : '';\n\n return `<a href=\"${url}\"${target}${rel}>${linkText}</a>`;\n }\n\n /**\n * Format as badge\n * @param {*} value - Badge text\n * @param {string} type - Badge type or value=type mapping string\n * Single type: badge:danger\n * Auto-detect: badge (or badge:auto)\n * Value mapping: badge:lock_engaged=danger,lock_released=success\n * @returns {string} Badge HTML\n */\n badge(value, type = 'auto') {\n // If the value is an array, map over it and create a badge for each item.\n if (Array.isArray(value)) {\n return value.map(item => this.badge(item, type)).join(' ');\n }\n\n const text = String(value);\n\n // Check for value=type mapping syntax\n // e.g. badge:lock_engaged=danger,lock_released=success\n // Arrives as a single arg: \"lock_engaged=danger,lock_released=success\"\n if (typeof type === 'string' && type.includes('=')) {\n const mapping = Object.fromEntries(\n type.split(',').map(pair => pair.split('=').map(s => s.trim()))\n );\n const badgeType = mapping[text] || mapping[text.toLowerCase()] || this.inferBadgeType(text);\n const className = badgeType ? `bg-${badgeType}` : 'bg-secondary';\n return `<span class=\"badge ${className}\">${text}</span>`;\n }\n\n const badgeType = type === 'auto' ? this.inferBadgeType(text) : type;\n const className = badgeType ? `bg-${badgeType}` : 'bg-secondary';\n\n return `<span class=\"badge ${className}\">${text}</span>`;\n }\n\n /**\n * Get badge CSS class for a value\n * @param {*} value - Value to get badge class for\n * @param {string} type - Badge type (optional, auto-detected if not specified)\n * @returns {string} Badge CSS class\n */\n badgeClass(value, type = 'auto') {\n const text = String(value);\n const badgeType = type === 'auto' ? this.inferBadgeType(text) : type;\n return badgeType ? `bg-${badgeType}` : 'bg-secondary';\n }\n\n /**\n * Infer badge type from text\n * @param {string} text - Badge text\n * @returns {string} Badge type\n */\n inferBadgeType(text) {\n const lowered = text.toLowerCase();\n if (['active', 'pass', 'success', 'complete', 'completed', 'approved', 'done', 'true', 'on', 'yes'].includes(lowered)) return 'success';\n if (['error', 'failed', 'fail', 'rejected', 'deleted', 'cancelled', 'false', 'off', 'no', 'declined'].includes(lowered)) return 'danger';\n if (['warning', 'pending', 'review', 'processing', 'uploading'].includes(lowered)) return 'warning';\n if (['info', 'new', 'draft'].includes(lowered)) return 'info';\n if (['inactive', 'disabled', 'archived', 'suspended'].includes(lowered)) return 'secondary';\n return 'secondary';\n }\n\n status(value) {\n return this._status(value);\n }\n\n status_icon(value) {\n return this._status(value, {}, {}, false, true);\n }\n\n status_text(value) {\n return this._status(value, {}, {}, true, false);\n }\n\n /**\n * Format status\n * @param {*} value - Status value\n * @param {Object} icons - Icon mapping\n * @param {Object} colors - Color mapping\n * @param {boolean} noIcons - Whether to include icons\n * @param {boolean} noText - Whether to include text\n * @returns {string} Status HTML\n */\n _status(value, icons = {}, colors = {}, noIcons = false, noText = false) {\n const status = String(value).toLowerCase();\n\n const defaultIcons = {\n 'active': 'bi bi-check-circle-fill',\n 'approved': 'bi bi-check-circle-fill',\n 'declined': 'bi bi-x-circle-fill',\n 'inactive': 'bi bi-pause-circle-fill',\n 'pending': 'bi bi-clock-fill',\n 'success': 'bi bi-check-circle-fill',\n 'error': 'bi bi-exclamation-triangle-fill',\n 'warning': 'bi bi-exclamation-triangle-fill'\n };\n\n const defaultColors = {\n 'active': 'success',\n \"approved\": \"success\",\n \"declined\": \"danger\",\n 'inactive': 'secondary',\n 'pending': 'warning',\n 'success': 'success',\n 'error': 'danger',\n 'warning': 'warning'\n };\n\n const iconClass = icons[status] || defaultIcons[status] || '';\n const color = colors[status] || defaultColors[status] || 'secondary';\n\n let icon = '';\n if (!noIcons && iconClass) {\n icon = `<i class=\"${iconClass}\"></i>`;\n }\n let text = '';\n if (!noText) {\n text = value;\n }\n\n return `<span class=\"text-${color}\">${icon}${icon ? ' ' : ''}${text}</span>`;\n }\n\n /**\n * Format boolean\n * @param {*} value - Boolean value\n * @param {string} trueText - Text for true\n * @param {string} falseText - Text for false\n * @returns {string} Boolean text\n */\n boolean(value, trueText = 'True', falseText = 'False', colored = false) {\n const text = value ? trueText : falseText;\n return colored ? `<span class=\"text-${value ? 'success' : 'danger'}\">${text}</span>` : text;\n }\n\n bool(value) {\n // Return false for null, undefined, 0, empty string\n if (value === null || value === undefined || value === 0 || value === '') {\n return false;\n }\n\n if (value === false || value === 'false') {\n return false;\n }\n\n if (value === true || value === 'true') {\n return true;\n }\n\n // Return false for empty arrays\n if (Array.isArray(value) && value.length === 0) {\n return false;\n }\n\n // Return false for empty objects (but not for other object types like Date, etc.)\n if (value && typeof value === 'object' && value.constructor === Object && Object.keys(value).length === 0) {\n return false;\n }\n\n // Return true for everything else\n return true;\n }\n\n /**\n * Format icon\n * @param {*} value - Icon key\n * @param {Object} mapping - Icon mapping\n * @returns {string} Icon HTML\n */\n icon(value, mapping = {}) {\n const key = String(value).toLowerCase();\n const icon = mapping[key] || '';\n return icon ? `<i class=\"${icon}\"></i>` : '';\n }\n\n /**\n * Format boolean as a yes/no icon\n * @param {*} value - Boolean value\n * @returns {string} Icon HTML\n */\n yesnoicon(value, yesIcon = 'bi bi-check-circle-fill text-success', noIcon = 'bi bi-x-circle-fill text-danger') {\n if (value) { // Handles true, 1, \"true\", \"on\", etc.\n return `<i class=\"${yesIcon}\"></i>`;\n }\n // Handles false, 0, \"\", null, undefined\n return `<i class=\"${noIcon}\"></i>`;\n }\n\n /**\n * Format value as Bootstrap 5 image with optional rendition support\n * @param {string|object} value - URL string or file object with renditions\n * @param {string} rendition - Desired rendition (thumbnail, thumbnail_sm, etc.)\n * @param {string} classes - Additional CSS classes\n * @param {string} alt - Alt text for the image\n * @returns {string} Bootstrap image HTML\n */\n image(value, rendition = 'thumbnail', classes = 'img-fluid', alt = '') {\n const url = this._extractImageUrl(value, rendition);\n if (!url) return '';\n\n return `<img src=\"${url}\" class=\"${classes}\" alt=\"${alt}\" />`;\n }\n\n /**\n * Format value as Bootstrap 5 avatar (circular image)\n * @param {string|object} value - URL string or file object with renditions\n * @param {string} size - Avatar size (xs, sm, md, lg, xl)\n * @param {string} classes - Additional CSS classes\n * @param {string} alt - Alt text for the avatar\n * @returns {string} Bootstrap avatar HTML\n */\n avatar(value, size = 'md', classes = 'rounded-circle', alt = '') {\n const url = this._extractImageUrl(value, 'square_sm') || GENERIC_AVATAR_SVG;\n\n // Bootstrap avatar sizing\n const sizeClasses = {\n 'xs': 'width: 1.5rem; height: 1.5rem;',\n 'sm': 'width: 2rem; height: 2rem;',\n 'md': 'width: 3rem; height: 3rem;',\n 'lg': 'width: 4rem; height: 4rem;',\n 'xl': 'width: 5rem; height: 5rem;'\n };\n\n const sizeStyle = sizeClasses[size] || sizeClasses['md'];\n const baseClasses = 'object-fit-cover';\n const allClasses = `${baseClasses} ${classes}`.trim();\n\n return `<img src=\"${url}\" class=\"${allClasses}\" style=\"${sizeStyle}\" alt=\"${alt}\" />`;\n }\n\n /**\n * Tooltip formatter - wraps value with Bootstrap tooltip\n * Usage:\n * {{value|tooltip:'Tooltip text'}}\n * {{value|tooltip:'Help text':top}}\n * {{value|tooltip:'Info':bottom:html}}\n *\n * @param {*} value - Value to display (not escaped, works with formatter chains)\n * @param {string} text - Tooltip text content\n * @param {string} placement - Tooltip placement: top, bottom, left, right (default: top)\n * @param {string} html - 'html' to allow HTML in tooltip (default: text only)\n * @returns {string} HTML with tooltip\n */\n tooltip(value, text = '', placement = 'top', html = '') {\n if (value === null || value === undefined) return '';\n\n // Don't escape value - it may be HTML from previous formatters in the chain\n const displayValue = String(value);\n const tooltipText = html === 'html' ? text : this.escapeHtml(text);\n const dataAttr = html === 'html' ? 'data-bs-html=\"true\"' : '';\n\n return `<span data-bs-toggle=\"tooltip\" data-bs-placement=\"${placement}\" ${dataAttr} data-bs-title=\"${tooltipText}\">${displayValue}</span>`;\n }\n\n /**\n * Helper method to extract image URL from string or file object\n * @param {string|object} value - URL string or file object with renditions\n * @param {string} preferredRendition - Preferred rendition name\n * @returns {string|null} Image URL or null if not found\n */\n _extractImageUrl(value, preferredRendition = 'thumbnail') {\n // Handle null/undefined\n if (!value) return null;\n\n // Handle string URL directly\n if (typeof value === 'string') {\n return value;\n }\n\n // Handle file object with renditions\n if (typeof value === 'object') {\n\n if (value.attributes) value = value.attributes;\n if (preferredRendition === \"thumbnail\" && value.thumbnail && typeof value.thumbnail === 'string') {\n return value.thumbnail;\n }\n // Check if it has renditions\n if (value.renditions && typeof value.renditions === 'object') {\n // Try to get preferred rendition\n const rendition = value.renditions[preferredRendition];\n if (rendition && rendition.url) {\n return rendition.url;\n }\n\n // Fallback to any available rendition\n const availableRenditions = Object.values(value.renditions);\n if (availableRenditions.length > 0 && availableRenditions[0].url) {\n return availableRenditions[0].url;\n }\n }\n\n // Fallback to original file URL\n if (value.url) {\n return value.url;\n }\n }\n\n return null;\n }\n\n // ============= Utility Formatters =============\n\n /**\n * Apply default value\n * @param {*} value - Value\n * @param {*} defaultValue - Default value\n * @returns {*} Value or default\n */\n default(value, defaultValue = '') {\n return value === null || value === undefined || value === '' ? defaultValue : value;\n }\n\n /**\n * Compare value and return one of two results based on equality\n * Useful for conditional CSS classes, text, or any conditional output\n *\n * @param {*} value - Value to compare\n * @param {*} compareValue - Value to compare against\n * @param {*} trueResult - Result if values are equal\n * @param {*} falseResult - Result if values are not equal (optional, defaults to empty string)\n * @returns {*} trueResult or falseResult\n *\n * @example\n * // CSS classes\n * {{status|equals:1:'text-success':'text-secondary'}}\n * {{model.state|equals:'active':'badge-success':'badge-secondary'}}\n *\n * // Text output\n * {{role|equals:'admin':'Administrator':'User'}}\n *\n * // Numbers\n * {{count|equals:0:'No items':'Has items'}}\n */\n equals(value, compareValue, trueResult, falseResult = '') {\n // Handle loose equality for common cases (1 == '1', true == 'true', etc.)\n // eslint-disable-next-line eqeqeq\n return value == compareValue ? trueResult : falseResult;\n }\n\n /**\n * Format as JSON\n * @param {*} value - Value to stringify\n * @param {number} indent - Indentation\n * @returns {string} JSON string\n */\n /**\n * Format pluralization based on count\n * @param {number} count - The count value\n * @param {string} singular - Singular form of the word\n * @param {string|null} plural - Plural form (defaults to singular + 's')\n * @param {boolean} includeCount - Whether to include the count in output\n * @returns {string} Formatted plural string\n */\n plural(count, singular, plural = null, includeCount = true) {\n if (count === null || count === undefined || singular === null || singular === undefined) {\n return includeCount ? `${count} ${singular}` : (singular || '');\n }\n\n const num = parseInt(count);\n if (isNaN(num)) {\n return includeCount ? `${count} ${singular}` : (singular || '');\n }\n\n const word = Math.abs(num) === 1 ? singular : (plural || singular + 's');\n return includeCount ? `${num} ${word}` : word;\n }\n\n /**\n * Format array as a human-readable list\n * @param {Array} array - Array to format\n * @param {Object} options - Formatting options\n * @returns {string} Formatted list string\n */\n formatList(array, options = {}) {\n if (!Array.isArray(array)) {\n return String(array);\n }\n\n const { conjunction = 'and', limit = null, moreText = 'others' } = options;\n\n if (array.length === 0) return '';\n if (array.length === 1) return String(array[0]);\n\n let items = array.slice();\n let hasMore = false;\n\n if (limit && array.length > limit) {\n items = array.slice(0, limit);\n hasMore = true;\n }\n\n if (hasMore) {\n const remaining = array.length - limit;\n return `${items.join(', ')}, ${conjunction} ${remaining} ${moreText}`;\n }\n\n if (items.length === 2) {\n return `${items[0]} ${conjunction} ${items[1]}`;\n }\n\n return `${items.slice(0, -1).join(', ')}, ${conjunction} ${items[items.length - 1]}`;\n }\n\n /**\n * Format duration to human-readable format\n * @param {number} value - Duration value\n * @param {string} unit - Input unit: 'ms', 's', 'm', 'h', 'd' (defaults to 'ms')\n * @param {boolean} short - Use short format (e.g., '1h30m' vs '1 hour 30 minutes')\n * @param {number} precision - Max number of units to show (defaults to 2)\n * @returns {string} Formatted duration string\n */\n duration(value, unit = 'ms', short = false, precision = 2) {\n if (value === null || value === undefined) return '';\n\n const num = parseFloat(value);\n if (isNaN(num)) return String(value);\n\n // Convert input to milliseconds\n let ms;\n switch (unit) {\n case 's':\n case 'sec':\n case 'seconds':\n ms = num * 1000;\n break;\n case 'm':\n case 'min':\n case 'minutes':\n ms = num * 60000;\n break;\n case 'h':\n case 'hr':\n case 'hours':\n ms = num * 3600000;\n break;\n case 'd':\n case 'day':\n case 'days':\n ms = num * 86400000;\n break;\n case 'ms':\n case 'milliseconds':\n default:\n ms = num;\n }\n\n const units = [\n { name: 'day', short: 'd', value: 86400000 },\n { name: 'hour', short: 'h', value: 3600000 },\n { name: 'minute', short: 'm', value: 60000 },\n { name: 'second', short: 's', value: 1000 }\n ];\n\n if (ms === 0) return short ? '0s' : '0 seconds';\n\n const absMs = Math.abs(ms);\n const sign = ms < 0 ? '-' : '';\n const parts = [];\n let remaining = absMs;\n\n for (const u of units) {\n if (remaining >= u.value) {\n const count = Math.floor(remaining / u.value);\n remaining = remaining % u.value;\n\n const unitName = short ? u.short : (count === 1 ? u.name : u.name + 's');\n parts.push(short ? `${count}${unitName}` : `${count} ${unitName}`);\n\n if (parts.length >= precision) break;\n }\n }\n\n if (parts.length === 0) {\n return short ? `${Math.round(absMs)}ms` : `${Math.round(absMs)} milliseconds`;\n }\n\n return sign + (short ? parts.join('') : parts.join(' '));\n }\n\n /**\n * Format long strings/IDs with truncation\n * @param {string} value - Value to format\n * @param {number} length - Maximum length before truncation\n * @param {string} prefix - Prefix to add\n * @param {string} suffix - Suffix for truncated strings\n * @returns {string} Formatted hash string\n */\n hash(value, length = 8, prefix = '', suffix = '...') {\n if (value === null || value === undefined) return '';\n\n const str = String(value);\n if (str.length <= length) return prefix + str;\n return prefix + str.substring(0, length) + suffix;\n }\n\n /**\n * Strip HTML tags from text\n * @param {string} html - HTML string to strip\n * @returns {string} Plain text without HTML tags\n */\n stripHtml(html) {\n if (html === null || html === undefined) return '';\n return String(html).replace(/<[^>]*>/g, '');\n }\n\n /**\n * Highlight search terms in text\n * @param {string} text - Text to search in\n * @param {string} searchTerm - Term to highlight\n * @param {string} className - CSS class for highlighting\n * @returns {string} Text with highlighted terms\n */\n highlight(text, searchTerm, className = 'highlight') {\n if (text === null || text === undefined || !searchTerm) {\n return String(text || '');\n }\n\n const escapedTerm = String(searchTerm).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp(`(${escapedTerm})`, 'gi');\n return String(text).replace(regex, `<mark class=\"${className}\">$1</mark>`);\n }\n\n /**\n * Encode a value as a hex string.\n * - Strings are encoded as UTF-8 bytes, then hex-encoded\n * - Numbers are converted to base-16 (padded to even length)\n * - Uint8Array/ArrayBuffer/number[] are treated as bytes\n *\n * @param {*} value - The value to encode\n * @param {boolean} uppercase - Uppercase hex letters (A-F)\n * @param {boolean} withPrefix - Prefix with '0x'\n * @returns {string} Hex string\n */\n hex(value, uppercase = false, withPrefix = false) {\n if (value === null || value === undefined) return '';\n\n let hexStr = '';\n\n const toHexFromBytes = (bytes) =>\n Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');\n\n if (typeof value === 'number') {\n let hex = Math.abs(Math.trunc(value)).toString(16);\n if (hex.length % 2) hex = '0' + hex;\n hexStr = hex;\n } else if (value instanceof Uint8Array) {\n hexStr = toHexFromBytes(value);\n } else if (value instanceof ArrayBuffer) {\n hexStr = toHexFromBytes(new Uint8Array(value));\n } else if (Array.isArray(value) && value.every(n => typeof n === 'number')) {\n hexStr = toHexFromBytes(Uint8Array.from(value.map(n => n & 0xFF)));\n } else {\n // Treat everything else as string and encode to UTF-8\n const enc = new TextEncoder();\n const bytes = enc.encode(String(value));\n hexStr = toHexFromBytes(bytes);\n }\n\n if (uppercase) hexStr = hexStr.toUpperCase();\n return (withPrefix ? '0x' : '') + hexStr;\n }\n\n /**\n * Decode a hex string into UTF-8 text.\n * Accepts optional '0x' prefix and ignores whitespace.\n *\n * @param {string} value - Hex string\n * @returns {string} Decoded UTF-8 string (or original value on parse error)\n */\n unhex(value) {\n if (value === null || value === undefined) return '';\n\n let str = String(value).trim();\n if (str.startsWith('0x') || str.startsWith('0X')) str = str.slice(2);\n str = str.replace(/\\s+/g, '');\n\n if (str.length === 0) return '';\n\n // If odd length, pad with leading zero\n if (str.length % 2 !== 0) str = '0' + str;\n\n const bytes = new Uint8Array(str.length / 2);\n for (let i = 0; i < str.length; i += 2) {\n const byte = parseInt(str.slice(i, i + 2), 16);\n if (Number.isNaN(byte)) {\n return String(value);\n }\n bytes[i / 2] = byte;\n }\n\n try {\n const dec = new TextDecoder();\n return dec.decode(bytes);\n } catch (e) {\n // Fallback if TextDecoder is unavailable\n let text = '';\n for (const b of bytes) text += String.fromCharCode(b);\n return text;\n }\n }\n\n json(value, indent = 2) {\n try {\n return JSON.stringify(value, null, indent);\n } catch (e) {\n return String(value);\n }\n }\n\n /**\n * Check if formatter exists\n * @param {string} name - Formatter name\n * @returns {boolean} True if exists\n */\n has(name) {\n return this.formatters.has(name.toLowerCase());\n }\n\n /**\n * Remove a formatter\n * @param {string} name - Formatter name\n * @returns {boolean} True if removed\n */\n unregister(name) {\n return this.formatters.delete(name.toLowerCase());\n }\n\n /**\n * Get all formatter names\n * @returns {Array} Formatter names\n */\n listFormatters() {\n return Array.from(this.formatters.keys()).sort();\n }\n\n iter(v) {\n if (v === null || v === undefined) {\n return [];\n }\n\n // If it's already an array, return as-is\n if (Array.isArray(v)) {\n return v;\n }\n\n // If it's an object, convert to key-value pairs\n if (typeof v === 'object') {\n return Object.entries(v).map(([key, value]) => ({\n key: key,\n value: value\n }));\n }\n\n // For primitive values, wrap in array with single item\n return [{ key: '0', value: v }];\n }\n}\n\n// Create singleton instance\nconst dataFormatter = new DataFormatter();\nwindow.dataFormatter = dataFormatter;\n\n// Export both class and instance\nexport { DataFormatter };\nexport default dataFormatter;\n","/**\n * MOJOUtils - Core utility functions for MOJO Framework\n * Provides centralized data access and formatting utilities\n */\n\nimport dataFormatter from './DataFormatter.js';\n\nclass MOJOUtils {\n /**\n * Get data from context with support for:\n * - Dot notation (e.g., \"user.name\")\n * - Pipe formatting (e.g., \"name|uppercase\")\n * - Combined (e.g., \"user.name|uppercase|truncate(10)\")\n *\n * @param {object} context - The data context to search in\n * @param {string} key - The key path with optional pipes\n * @returns {*} The value, possibly formatted\n */\n static getContextData(context, key) {\n if (!key || context == null) {\n return undefined;\n }\n\n // Check for pipe syntax - split on first pipe outside of parentheses\n let field = key;\n let pipes = '';\n\n // Find the first pipe that's not inside parentheses\n let parenDepth = 0;\n let pipeIndex = -1;\n\n for (let i = 0; i < key.length; i++) {\n const char = key[i];\n if (char === '(') parenDepth++;\n else if (char === ')') parenDepth--;\n else if (char === '|' && parenDepth === 0) {\n pipeIndex = i;\n break;\n }\n }\n\n if (pipeIndex > -1) {\n field = key.substring(0, pipeIndex).trim();\n pipes = key.substring(pipeIndex + 1).trim();\n }\n\n // Get the raw value\n const value = this.getNestedValue(context, field);\n\n // Apply pipes if present, passing context for variable resolution\n if (pipes) {\n return dataFormatter.pipe(value, pipes, context);\n }\n\n return value;\n }\n\n /**\n * Get nested value from object using dot notation\n * IMPORTANT: Never calls get() on the top-level context to avoid recursion\n * But DOES call get() on nested objects if they have that method\n *\n * @param {object} context - The object to search in\n * @param {string} path - Dot notation path\n * @returns {*} The value at the path\n */\n static getNestedValue(context, path) {\n if (!path || context == null) {\n return undefined;\n }\n\n // If no dots, simple property lookup\n if (!path.includes('.')) {\n // Direct property access - never call get() on top level\n // Check if property exists (including prototype chain for methods)\n if (path in context) {\n const value = context[path];\n // Check if it's a method (like getStatus, getButtonClass)\n if (typeof value === 'function') {\n return value.call(context);\n }\n return value;\n }\n\n return undefined;\n }\n\n // Handle dot notation\n const keys = path.split('.');\n let current = context;\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n\n if (current == null) {\n return undefined;\n }\n\n // For the first key, never use get() (it's the top-level context)\n if (i === 0) {\n // Direct property access\n if (current.hasOwnProperty(key)) {\n const value = current[key];\n // Check if it's a method and call it\n if (typeof value === 'function') {\n current = value.call(context);\n } else {\n current = value;\n }\n } else {\n return undefined;\n }\n } else {\n // For nested objects, check if they have a get() method\n if (current && typeof current.getContextValue === 'function') {\n // Use get() for the remaining path\n const remainingPath = keys.slice(i).join('.');\n return current.getContextValue(remainingPath);\n }\n\n // Standard property access\n if (Array.isArray(current) && !isNaN(key)) {\n // Array index access\n current = current[parseInt(key)];\n } else if (current.hasOwnProperty(key)) {\n current = current[key];\n } else if (typeof current[key] === 'function') {\n current = current[key].call(current);\n } else {\n return undefined;\n }\n }\n }\n\n return current;\n }\n\n /**\n * Check if a value is null or undefined\n * @param {*} value - Value to check\n * @returns {boolean} True if null or undefined\n */\n static isNullOrUndefined(value) {\n return value === null || value === undefined;\n }\n\n /**\n * Deep clone an object\n * @param {*} obj - Object to clone\n * @returns {*} Cloned object\n */\n static deepClone(obj) {\n if (obj === null || typeof obj !== 'object') return obj;\n if (obj instanceof Date) return new Date(obj.getTime());\n if (obj instanceof Array) return obj.map(item => this.deepClone(item));\n if (obj instanceof Object) {\n const clonedObj = {};\n for (const key in obj) {\n if (obj.hasOwnProperty(key)) {\n clonedObj[key] = this.deepClone(obj[key]);\n }\n }\n return clonedObj;\n }\n }\n\n /**\n * Merge objects deeply\n * @param {object} target - Target object\n * @param {...object} sources - Source objects to merge\n * @returns {object} Merged object\n */\n static deepMerge(target, ...sources) {\n if (!sources.length) return target;\n const source = sources.shift();\n\n if (this.isObject(target) && this.isObject(source)) {\n for (const key in source) {\n if (this.isObject(source[key])) {\n if (!target[key]) Object.assign(target, { [key]: {} });\n this.deepMerge(target[key], source[key]);\n } else {\n Object.assign(target, { [key]: source[key] });\n }\n }\n }\n\n return this.deepMerge(target, ...sources);\n }\n\n /**\n * Check if value is a plain object\n * @param {*} item - Value to check\n * @returns {boolean} True if plain object\n */\n static isObject(item) {\n return item && typeof item === 'object' && !Array.isArray(item);\n }\n\n /**\n * Debounce function calls\n * @param {function} func - Function to debounce\n * @param {number} wait - Wait time in milliseconds\n * @returns {function} Debounced function\n */\n static debounce(func, wait) {\n let timeout;\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func(...args);\n };\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n }\n\n /**\n * Throttle function calls\n * @param {function} func - Function to throttle\n * @param {number} limit - Time limit in milliseconds\n * @returns {function} Throttled function\n */\n static throttle(func, limit) {\n let inThrottle;\n return function(...args) {\n if (!inThrottle) {\n func.apply(this, args);\n inThrottle = true;\n setTimeout(() => inThrottle = false, limit);\n }\n };\n }\n\n /**\n * Generate a unique ID\n * @param {string} prefix - Optional prefix for the ID\n * @returns {string} Unique ID\n */\n static generateId(prefix = '') {\n const timestamp = Date.now().toString(36);\n const randomStr = Math.random().toString(36).substr(2, 9);\n return prefix ? `${prefix}_${timestamp}_${randomStr}` : `${timestamp}_${randomStr}`;\n }\n\n /**\n * Escape HTML special characters\n * @param {string} str - String to escape\n * @returns {string} Escaped string\n */\n static escapeHtml(str) {\n const entityMap = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;'\n };\n\n return String(str).replace(/[&<>\"'`=\\/]/g, s => entityMap[s]);\n }\n\n /**\n * Check password strength and provide detailed feedback\n * @param {string} password - Password to check\n * @returns {object} Password strength analysis\n */\n static checkPasswordStrength(password) {\n if (!password || typeof password !== 'string') {\n return {\n score: 0,\n strength: 'invalid',\n feedback: ['Password must be a non-empty string'],\n details: {\n length: 0,\n hasLowercase: false,\n hasUppercase: false,\n hasNumbers: false,\n hasSpecialChars: false,\n hasCommonPatterns: false,\n isCommonPassword: false\n }\n };\n }\n\n const feedback = [];\n const details = {\n length: password.length,\n hasLowercase: /[a-z]/.test(password),\n hasUppercase: /[A-Z]/.test(password),\n hasNumbers: /[0-9]/.test(password),\n hasSpecialChars: /[^a-zA-Z0-9]/.test(password),\n hasCommonPatterns: false,\n isCommonPassword: false\n };\n\n let score = 0;\n\n // Length scoring\n if (password.length < 6) {\n feedback.push('Password should be at least 6 characters long');\n } else if (password.length < 8) {\n score += 1;\n feedback.push('Consider using at least 8 characters for better security');\n } else if (password.length < 12) {\n score += 3;\n } else {\n score += 4;\n }\n\n // Character variety scoring\n if (details.hasLowercase) score += 1;\n else feedback.push('Include lowercase letters');\n\n if (details.hasUppercase) score += 1;\n else feedback.push('Include uppercase letters');\n\n if (details.hasNumbers) score += 1;\n else feedback.push('Include numbers');\n\n if (details.hasSpecialChars) score += 2;\n else feedback.push('Include special characters (!@#$%^&* etc.)');\n\n // Check for common patterns\n const commonPatterns = [\n /123/, // Sequential numbers\n /abc/i, // Sequential letters\n /qwerty/i, // Keyboard patterns\n /asdf/i, // Keyboard patterns\n /(.)\\1{2,}/, // Repeated characters (aaa, 111)\n /password/i, // Contains \"password\"\n /admin/i, // Contains \"admin\"\n /user/i, // Contains \"user\"\n /login/i // Contains \"login\"\n ];\n\n for (const pattern of commonPatterns) {\n if (pattern.test(password)) {\n details.hasCommonPatterns = true;\n score -= 1;\n feedback.push('Avoid common patterns and dictionary words');\n break;\n }\n }\n\n // Check against very common passwords\n const commonPasswords = [\n '123456', 'password', '123456789', '12345678', '12345',\n '1234567', '1234567890', 'qwerty', 'abc123', '111111',\n '123123', 'admin', 'letmein', 'welcome', 'monkey',\n 'password123', '123qwe', 'qwerty123', '000000', 'dragon',\n 'sunshine', 'princess', 'azerty', '1234', 'iloveyou',\n 'trustno1', 'superman', 'shadow', 'master', 'jennifer'\n ];\n\n if (commonPasswords.includes(password.toLowerCase())) {\n details.isCommonPassword = true;\n score = Math.max(0, score - 3);\n feedback.push('This password is too common and easily guessed');\n }\n\n // Calculate final strength\n let strength;\n if (score < 2) {\n strength = 'very-weak';\n } else if (score < 4) {\n strength = 'weak';\n } else if (score < 6) {\n strength = 'fair';\n } else if (score < 8) {\n strength = 'good';\n } else {\n strength = 'strong';\n }\n\n // Add positive feedback for strong passwords\n if (score >= 7 && feedback.length === 0) {\n feedback.push('Strong password! Consider using a password manager.');\n } else if (score >= 5 && feedback.length <= 1) {\n feedback.push('Good password strength. Consider adding more variety.');\n }\n\n return {\n score: Math.max(0, score),\n strength,\n feedback,\n details\n };\n }\n\n /**\n * Generate a secure password with customizable options\n * @param {object} options - Password generation options\n * @param {number} options.length - Password length (default: 12)\n * @param {boolean} options.includeLowercase - Include lowercase letters (default: true)\n * @param {boolean} options.includeUppercase - Include uppercase letters (default: true)\n * @param {boolean} options.includeNumbers - Include numbers (default: true)\n * @param {boolean} options.includeSpecialChars - Include special characters (default: true)\n * @param {string} options.customChars - Custom character set to use\n * @param {boolean} options.excludeAmbiguous - Exclude ambiguous characters like 0, O, l, I (default: false)\n * @returns {string} Generated password\n */\n static generatePassword(options = {}) {\n const defaults = {\n length: 12,\n includeLowercase: true,\n includeUppercase: true,\n includeNumbers: true,\n includeSpecialChars: true,\n customChars: '',\n excludeAmbiguous: false\n };\n\n const config = { ...defaults, ...options };\n\n if (config.length < 4) {\n throw new Error('Password length must be at least 4 characters');\n }\n\n // Build character sets\n let lowercase = 'abcdefghijklmnopqrstuvwxyz';\n let uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\n let numbers = '0123456789';\n let specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?';\n\n // Remove ambiguous characters if requested\n if (config.excludeAmbiguous) {\n lowercase = lowercase.replace(/[il]/g, '');\n uppercase = uppercase.replace(/[IOL]/g, '');\n numbers = numbers.replace(/[01]/g, '');\n specialChars = specialChars.replace(/[|]/g, '');\n }\n\n // Build character pool\n let charPool = '';\n const requiredChars = [];\n\n if (config.customChars) {\n charPool = config.customChars;\n } else {\n if (config.includeLowercase) {\n charPool += lowercase;\n requiredChars.push(lowercase[Math.floor(Math.random() * lowercase.length)]);\n }\n if (config.includeUppercase) {\n charPool += uppercase;\n requiredChars.push(uppercase[Math.floor(Math.random() * uppercase.length)]);\n }\n if (config.includeNumbers) {\n charPool += numbers;\n requiredChars.push(numbers[Math.floor(Math.random() * numbers.length)]);\n }\n if (config.includeSpecialChars) {\n charPool += specialChars;\n requiredChars.push(specialChars[Math.floor(Math.random() * specialChars.length)]);\n }\n }\n\n if (!charPool) {\n throw new Error('No character types selected for password generation');\n }\n\n // Generate password\n let password = '';\n\n // Add required characters first to ensure variety\n for (const char of requiredChars) {\n password += char;\n }\n\n // Fill remaining length with random characters\n for (let i = password.length; i < config.length; i++) {\n password += charPool[Math.floor(Math.random() * charPool.length)];\n }\n\n // Shuffle the password to avoid predictable patterns\n return password.split('').sort(() => Math.random() - 0.5).join('');\n }\n\n /**\n * Parse query string into object\n * @param {string} queryString - Query string to parse\n * @returns {object} Parsed query parameters\n */\n static parseQueryString(queryString) {\n const params = {};\n const searchParams = new URLSearchParams(queryString);\n for (const [key, value] of searchParams.entries()) {\n params[key] = value;\n }\n return params;\n }\n\n /**\n * Convert object to query string\n * @param {object} params - Parameters object\n * @returns {string} Query string\n */\n static toQueryString(params) {\n return new URLSearchParams(params).toString();\n }\n\n /**\n * Wrap data objects to provide get() method support\n * This ensures pipe formatting works in all contexts\n * @param {*} data - Data to wrap\n * @param {object} rootContext - Optional root context for nested access\n * @returns {*} Wrapped data if object/array, otherwise original\n */\n static wrapData(data, rootContext = null, depth = 3) {\n if (!data || typeof data !== 'object') {\n return data;\n }\n\n // Don't wrap built-in types (Date, RegExp, etc.)\n if (data instanceof Date || data instanceof RegExp || data instanceof Error) {\n return data;\n }\n\n // Stop wrapping at max depth to prevent infinite recursion\n if (depth <= 0) {\n return data;\n }\n\n // Don't wrap if already has get method\n if (typeof data.getContextValue === 'function') {\n return data;\n }\n\n // Handle arrays specially - wrap each element but keep as array\n if (Array.isArray(data)) {\n return data.map(item => {\n if (item && typeof item === 'object' && !item.getContextValue) {\n return new DataWrapper(item, rootContext);\n }\n return item;\n });\n }\n\n // Use DataWrapper for objects\n return new DataWrapper(data, rootContext);\n }\n}\n\n/**\n * DataWrapper - Wraps plain objects to provide get() method with pipe support\n * Used internally by View to ensure all data objects support formatting\n */\nclass DataWrapper {\n constructor(data, rootContext = null) {\n // Store the wrapped data as non-enumerable to avoid JSDOM serialization issues\n Object.defineProperty(this, '_data', {\n value: data,\n writable: false,\n enumerable: false,\n configurable: false\n });\n\n Object.defineProperty(this, '_rootContext', {\n value: rootContext,\n writable: false,\n enumerable: false,\n configurable: false\n });\n\n // Copy all properties from data to this wrapper\n // This allows direct property access\n if (data && typeof data === 'object') {\n for (const key in data) {\n if (data.hasOwnProperty(key)) {\n const value = data[key];\n // Wrap nested values using wrapData for consistency\n this[key] = MOJOUtils.wrapData(value, rootContext);\n }\n }\n }\n }\n\n /**\n * Get value with pipe support\n * @param {string} key - Key with optional pipes\n * @returns {*} Value, possibly formatted\n */\n getContextValue(key) {\n // Check if key has pipes\n let field = key;\n let pipes = '';\n\n // Find the first pipe that's not inside parentheses\n let parenDepth = 0;\n let pipeIndex = -1;\n\n for (let i = 0; i < key.length; i++) {\n const char = key[i];\n if (char === '(') parenDepth++;\n else if (char === ')') parenDepth--;\n else if (char === '|' && parenDepth === 0) {\n pipeIndex = i;\n break;\n }\n }\n\n if (pipeIndex > -1) {\n field = key.substring(0, pipeIndex).trim();\n pipes = key.substring(pipeIndex + 1).trim();\n }\n\n // Get value - supports both direct properties and dot-notation paths\n let value;\n \n // First check if it's a direct property on the wrapper (already wrapped)\n if (field in this && field !== '_data' && field !== '_rootContext') {\n value = this[field];\n } else {\n // Try to get nested value using dot-notation path\n value = MOJOUtils.getNestedValue(this._data, field);\n }\n\n // Apply pipes if present, passing root context for variable resolution\n if (pipes && value !== undefined) {\n return dataFormatter.pipe(value, pipes, this._rootContext || this._data);\n }\n\n return value;\n }\n\n /**\n * Check if wrapper has a property\n * @param {string} key - Property key\n * @returns {boolean} True if property exists\n */\n has(key) {\n return this._data && this._data.hasOwnProperty(key);\n }\n\n /**\n * Get the raw wrapped data\n * @returns {object} The original data object\n */\n toJSON() {\n return this._data;\n }\n}\n\n// Attach DataWrapper to MOJOUtils for easy access\nMOJOUtils.DataWrapper = DataWrapper;\n\n// Export as both class and singleton for flexibility\nexport default MOJOUtils;\nexport { MOJOUtils, DataWrapper };\n\n// Also attach to window for global access if needed\nif (typeof window !== 'undefined') {\n // window.MOJO = window.MOJO || {};\n // window.MOJO.Utils = MOJOUtils;\n // window.MOJO.DataWrapper = DataWrapper;\n window.utils = MOJOUtils;\n}\n","/**\n * EventEmitter - Lightweight event system for instance-level events.\n *\n * Provides a simple, consistent event API for Models, Collections, Views, and Pages.\n * Events are scoped to individual instances - use the global EventBus for cross-component communication.\n *\n * Usage as a mixin:\n * Object.assign(MyClass.prototype, EventEmitter);\n *\n * API:\n * on(event, callback, context) - Add event listener with optional context\n * off(event, callback, context) - Remove event listener\n * once(event, callback, context) - Add one-time event listener with optional context\n * emit(event, ...args) - Emit event to all listeners\n *\n * @example\n * // Clean context binding\n * model.on('change', this.handleChange, this);\n * model.off('change', this.handleChange, this); // Easy cleanup!\n * \n * // Traditional usage still works\n * model.on('change', (data) => console.log(data));\n */\n\nconst EventEmitter = {\n /**\n * Add an event listener\n * @param {string} event - Event name to listen for\n * @param {Function} callback - Function to call when event is emitted\n * @param {Object} [context] - Context to bind the callback to (optional)\n * @returns {Object} This instance for method chaining\n *\n * @example\n * // With context binding\n * model.on('change', this.handleChange, this);\n * \n * // Without context (traditional)\n * model.on('change', (data) => console.log(data));\n */\n on(event, callback, context) {\n if (!this._listeners) this._listeners = {};\n if (!this._listeners[event]) this._listeners[event] = [];\n \n const listener = {\n callback,\n context,\n fn: context ? callback.bind(context) : callback\n };\n \n this._listeners[event].push(listener);\n return this;\n },\n\n /**\n * Remove an event listener\n * @param {string} event - Event name\n * @param {Function} [callback] - Specific callback to remove. If omitted, removes all listeners for the event\n * @param {Object} [context] - Context that was used when adding the listener\n * @returns {Object} This instance for method chaining\n *\n * @example\n * // Remove specific listener with context\n * model.off('change', this.handleChange, this);\n *\n * // Remove specific callback (any context)\n * model.off('change', myHandler);\n *\n * // Remove all listeners for an event\n * model.off('change');\n */\n off(event, callback, context) {\n if (!this._listeners || !this._listeners[event]) return this;\n\n if (!callback) {\n // Remove all listeners for event\n delete this._listeners[event];\n } else {\n // Remove specific listener(s)\n this._listeners[event] = this._listeners[event].filter(listener => {\n // Match callback\n if (listener.callback !== callback) return true;\n \n // If context specified, must match context too\n if (arguments.length === 3 && listener.context !== context) return true;\n \n // This listener should be removed\n return false;\n });\n \n if (this._listeners[event].length === 0) {\n delete this._listeners[event];\n }\n }\n return this;\n },\n\n /**\n * Add a one-time event listener that automatically removes itself after being called\n * @param {string} event - Event name to listen for\n * @param {Function} callback - Function to call when event is emitted (called only once)\n * @param {Object} [context] - Context to bind the callback to (optional)\n * @returns {Object} This instance for method chaining\n *\n * @example\n * model.once('ready', this.handleReady, this);\n */\n once(event, callback, context) {\n const onceWrapper = (...args) => {\n this.off(event, onceWrapper);\n const fn = context ? callback.bind(context) : callback;\n fn.apply(context || this, args);\n };\n \n this.on(event, onceWrapper);\n return this;\n },\n\n /**\n * Emit an event to all registered listeners\n * @param {string} event - Event name to emit\n * @param {...*} args - Arguments to pass to event listeners\n * @returns {Object} This instance for method chaining\n *\n * @example\n * // Emit with single argument\n * model.emit('change', { field: 'name', value: 'John' });\n *\n * // Emit with multiple arguments\n * model.emit('update', oldValue, newValue, timestamp);\n *\n * // Emit without arguments\n * model.emit('ready');\n */\n emit(event, ...args) {\n if (!this._listeners || !this._listeners[event]) return this;\n \n // Copy the listeners in case one removes itself during emit\n const listeners = this._listeners[event].slice();\n \n for (const listener of listeners) {\n try {\n listener.fn.apply(listener.context || this, args);\n } catch (error) {\n // Don't allow one bad handler to block other listeners\n if (console && console.error) {\n console.error(`Error in ${event} event handler:`, error);\n }\n }\n }\n return this;\n }\n};\n\nexport default EventEmitter;","/**\n * Rest - HTTP client for API communication\n * Provides methods for making REST API calls with interceptors and error handling\n */\n\nclass Rest {\n constructor() {\n this.config = {\n baseURL: '',\n timeout: 30000,\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json'\n },\n trackDevice: true, // New setting to control DUID tracking\n duidHeader: 'X-Mojo-UID', // Header name for the DUID\n duidTransport: 'header' // How to send the DUID: 'payload' or 'header'\n };\n\n this.interceptors = {\n request: [],\n response: []\n };\n\n this.duid = null;\n if (this.config.trackDevice) {\n this._initializeDuid();\n }\n }\n\n /**\n * Initialize or generate the Device Unique ID (DUID)\n * @private\n */\n _initializeDuid() {\n const storageKey = 'mojo_device_uid';\n try {\n let storedDuid = localStorage.getItem(storageKey);\n if (storedDuid) {\n this.duid = storedDuid;\n } else {\n this.duid = this._generateDuid();\n localStorage.setItem(storageKey, this.duid);\n }\n } catch (e) {\n console.error(\"Could not access localStorage to get/set DUID.\", e);\n // Use a non-persistent DUID as a fallback\n this.duid = this._generateDuid();\n }\n }\n\n /**\n * Generate a new DUID (UUID v4)\n * @private\n * @returns {string} A new UUID\n */\n _generateDuid() {\n if (crypto && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n\n /**\n * Configure the REST client\n * @param {object} config - Configuration object\n */\n configure(config) {\n if (config.baseUrl) config.baseURL = config.baseUrl;\n const oldTrackDevice = this.config.trackDevice;\n\n this.config = {\n ...this.config,\n ...config,\n headers: {\n ...this.config.headers,\n ...config.headers\n }\n };\n\n // Initialize DUID if tracking is newly enabled\n if (this.config.trackDevice && !oldTrackDevice) {\n this._initializeDuid();\n }\n }\n\n /**\n * Add request or response interceptor\n * @param {string} type - 'request' or 'response'\n * @param {function} interceptor - Interceptor function\n */\n addInterceptor(type, interceptor) {\n if (this.interceptors[type]) {\n this.interceptors[type].push(interceptor);\n }\n }\n\n /**\n * Build complete URL\n * @param {string} url - Endpoint URL\n * @returns {string} Complete URL\n */\n buildUrl(url) {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n return url;\n }\n\n\n const baseURL = this.config.baseURL.endsWith('/')\n ? this.config.baseURL.slice(0, -1)\n : this.config.baseURL;\n\n const endpoint = url.startsWith('/') ? url : `/${url}`;\n\n return `${baseURL}${endpoint}`;\n }\n\n /**\n * Categorize error into common reason codes\n * @param {Error} error - The error object\n * @param {number} status - HTTP status code (if available)\n * @returns {object} Object with reason code and user-friendly message\n */\n categorizeError(error, status = 0) {\n // Network/connection errors\n if (error.name === 'TypeError' && error.message.includes('fetch')) {\n return {\n reason: 'not_reachable',\n message: 'Service is not reachable - please check your connection'\n };\n }\n\n if (error.name === 'AbortError') {\n return {\n reason: 'cancelled',\n message: 'Request was cancelled'\n };\n }\n\n if (error.name === 'TimeoutError' || error.message.includes('timeout')) {\n return {\n reason: 'timed_out',\n message: 'Request timed out - please try again'\n };\n }\n\n // HTTP status-based categorization\n if (status >= 400) {\n if (status === 400) {\n return {\n reason: 'bad_request',\n message: 'Invalid request data'\n };\n }\n if (status === 401) {\n return {\n reason: 'unauthorized',\n message: 'Authentication required'\n };\n }\n if (status === 403) {\n return {\n reason: 'forbidden',\n message: 'Access denied'\n };\n }\n if (status === 404) {\n return {\n reason: 'not_found',\n message: 'Resource not found'\n };\n }\n if (status === 409) {\n return {\n reason: 'conflict',\n message: 'Resource conflict'\n };\n }\n if (status === 422) {\n return {\n reason: 'validation_error',\n message: 'Validation failed'\n };\n }\n if (status === 429) {\n return {\n reason: 'rate_limited',\n message: 'Too many requests - please wait'\n };\n }\n if (status >= 500) {\n return {\n reason: 'server_error',\n message: 'Server error - please try again later'\n };\n }\n if (status >= 400) {\n return {\n reason: 'client_error',\n message: 'Request error'\n };\n }\n }\n\n // Generic network errors\n if (error.message.includes('CORS')) {\n return {\n reason: 'cors_error',\n message: 'Cross-origin request blocked'\n };\n }\n\n if (error.message.includes('DNS') || error.message.includes('ENOTFOUND')) {\n return {\n reason: 'dns_error',\n message: 'Unable to resolve server address'\n };\n }\n\n // Default fallback\n return {\n reason: 'unknown_error',\n message: `Network error: ${error.message}`\n };\n }\n\n /**\n * Build query string from parameters\n * @param {object} params - Query parameters\n * @returns {string} Query string\n */\n buildQueryString(params = {}) {\n const searchParams = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value !== null && value !== undefined) {\n if (Array.isArray(value)) {\n value.forEach(v => searchParams.append(`${key}[]`, v));\n } else {\n searchParams.append(key, value);\n }\n }\n });\n\n const queryString = searchParams.toString();\n return queryString ? `?${queryString}` : '';\n }\n\n /**\n * Process request through interceptors\n * @param {object} request - Request configuration\n * @returns {object} Processed request configuration\n */\n async processRequestInterceptors(request) {\n let processedRequest = { ...request };\n\n for (const interceptor of this.interceptors.request) {\n try {\n processedRequest = await interceptor(processedRequest);\n } catch (error) {\n console.error('Request interceptor error:', error);\n throw error;\n }\n }\n\n return processedRequest;\n }\n\n /**\n * Process response through interceptors\n * @param {Response} response - Fetch response object\n * @param {object} request - Original request configuration\n * @returns {object} Processed response data\n */\n async processResponseInterceptors(response, request) {\n let responseData = {\n success: response.ok,\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n data: null,\n errors: null,\n message: null,\n reason: null\n };\n\n // Parse response body\n try {\n const contentType = response.headers.get('content-type');\n\n if (contentType && contentType.includes('application/json')) {\n const jsonData = await response.json();\n responseData.data = jsonData;\n\n // Handle API error responses\n if (!response.ok) {\n const errorInfo = this.categorizeError(new Error('HTTP Error'), response.status);\n responseData.errors = jsonData.errors || {};\n responseData.message = jsonData.message || errorInfo.message;\n responseData.reason = errorInfo.reason;\n }\n } else {\n responseData.data = await response.text();\n\n if (!response.ok) {\n const errorInfo = this.categorizeError(new Error('HTTP Error'), response.status);\n responseData.message = errorInfo.message;\n responseData.reason = errorInfo.reason;\n }\n }\n } catch (error) {\n responseData.errors = { parse: 'Failed to parse response' };\n responseData.message = 'Invalid response format';\n }\n\n // Process through response interceptors\n for (const interceptor of this.interceptors.response) {\n try {\n responseData = await interceptor(responseData, request);\n } catch (error) {\n console.error('Response interceptor error:', error);\n }\n }\n\n return responseData;\n }\n\n /**\n * Make HTTP request\n * @param {string} method - HTTP method\n * @param {string} url - Request URL\n * @param {object} data - Request body data\n * @param {object} params - Query parameters\n * @param {object} options - Additional request options\n * @returns {Promise} Promise that resolves with response data\n */\n async request(method, url, data = null, params = {}, options = {}) {\n // Build request configuration\n let request = {\n method: method.toUpperCase(),\n url: this.buildUrl(url) + this.buildQueryString(params),\n headers: {\n ...this.config.headers,\n ...options.headers\n },\n data,\n options: {\n timeout: this.config.timeout,\n ...options\n }\n };\n\n // Process request interceptors. If an interceptor throws\n // AuthRequiredError (auth gate refused to refresh), short-circuit to a\n // 401 response without hitting the network.\n try {\n request = await this.processRequestInterceptors(request);\n } catch (error) {\n if (error.name === 'AuthRequiredError') {\n return {\n success: false,\n status: 401,\n statusText: 'Unauthorized',\n headers: {},\n data: null,\n errors: { auth: error.message },\n message: 'Authentication required',\n reason: 'unauthorized'\n };\n }\n throw error;\n }\n\n // Add DUID if tracking is enabled\n if (this.config.trackDevice && this.duid) {\n if (this.config.duidTransport === 'header') {\n // Always add as a header\n request.headers[this.config.duidHeader] = this.duid;\n } else { // 'payload' transport (default)\n if (request.method === 'GET') {\n // For GET requests, add as a query parameter\n const url = new URL(request.url);\n url.searchParams.append('duid', this.duid);\n request.url = url.toString();\n } else if (request.data && typeof request.data === 'object' && !(request.data instanceof FormData)) {\n // For POST/PUT/PATCH with JSON body, add to the data payload\n request.data.duid = this.duid;\n }\n // Note: For other request types like FormData, the duid is not sent in 'payload' mode.\n }\n }\n\n // Prepare fetch options\n const fetchOptions = {\n method: request.method,\n headers: request.headers\n };\n\n // Handle abort signals - combine timeout and external signal if provided\n const signals = [];\n\n // Add timeout signal\n if (request.options.timeout) {\n signals.push(AbortSignal.timeout(request.options.timeout));\n }\n\n // Add external signal if provided\n if (request.options.signal) {\n signals.push(request.options.signal);\n }\n\n // Combine signals or use single signal\n if (signals.length > 1) {\n fetchOptions.signal = AbortSignal.any ? AbortSignal.any(signals) : signals[0];\n } else if (signals.length === 1) {\n fetchOptions.signal = signals[0];\n }\n\n // Add body for methods that support it\n if (request.data && ['POST', 'PUT', 'PATCH'].includes(request.method)) {\n if (request.data instanceof FormData) {\n fetchOptions.body = request.data;\n // Remove Content-Type header for FormData (browser sets it with boundary)\n delete fetchOptions.headers['Content-Type'];\n } else if (typeof request.data === 'object') {\n fetchOptions.body = JSON.stringify(request.data);\n } else {\n fetchOptions.body = request.data;\n }\n }\n\n try {\n // Make the request\n const response = await fetch(request.url, fetchOptions);\n\n // Process response through interceptors\n const responseData = await this.processResponseInterceptors(response, request);\n\n // Unwrap server envelope: { status, data, message } → data only\n if (options.dataOnly && responseData.data && typeof responseData.data === 'object' && 'data' in responseData.data) {\n responseData.message = responseData.message || responseData.data.message;\n responseData.data = responseData.data.data;\n }\n\n return responseData;\n\n } catch (error) {\n // Handle AbortError (cancellation) - re-throw to be handled by caller\n if (error.name === 'AbortError') {\n throw error;\n }\n\n // Categorize the error\n const errorInfo = this.categorizeError(error);\n\n // Handle network and timeout errors\n const errorResponse = {\n success: false,\n status: 0,\n statusText: 'Network Error',\n headers: {},\n data: null,\n errors: { network: error.message },\n message: errorInfo.message,\n reason: errorInfo.reason\n };\n\n // Create mock response for interceptor processing\n const mockResponse = {\n ok: false,\n status: 0,\n statusText: 'Network Error',\n headers: new Headers(),\n json: async () => ({}),\n text: async () => ''\n };\n\n // Process through interceptors and return the categorized error\n await this.processResponseInterceptors(mockResponse, request);\n return errorResponse;\n }\n }\n\n /**\n * GET request\n * @param {string} url - Request URL\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async GET(url, params = {}, options = {}) {\n return this.request('GET', url, null, params, options);\n }\n\n /**\n * POST request\n * @param {string} url - Request URL\n * @param {object} data - Request body data\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async POST(url, data = {}, params = {}, options = {}) {\n return this.request('POST', url, data, params, options);\n }\n\n /**\n * PUT request\n * @param {string} url - Request URL\n * @param {object} data - Request body data\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async PUT(url, data = {}, params = {}, options = {}) {\n return this.request('PUT', url, data, params, options);\n }\n\n /**\n * PATCH request\n * @param {string} url - Request URL\n * @param {object} data - Request body data\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async PATCH(url, data = {}, params = {}, options = {}) {\n return this.request('PATCH', url, data, params, options);\n }\n\n /**\n * DELETE request\n * @param {string} url - Request URL\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async DELETE(url, params = {}, options = {}) {\n return this.request('DELETE', url, null, params, options);\n }\n\n // Lowercase aliases\n get(...args) { return this.GET(...args); }\n post(...args) { return this.POST(...args); }\n put(...args) { return this.PUT(...args); }\n patch(...args) { return this.PATCH(...args); }\n delete(...args) { return this.DELETE(...args); }\n\n /**\n * Download a file from a URL\n * @param {string} url - Request URL\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves when download is initiated\n */\n async download(url, params = {}, options = {}) {\n const requestUrl = this.buildUrl(url) + this.buildQueryString(params);\n const request = {\n method: 'GET',\n url: requestUrl,\n headers: {\n ...this.config.headers,\n 'Accept': '*/*', // Default, can be overridden by options\n ...options.headers\n },\n options: {\n ...options\n }\n };\n // Remove content-type for GET request\n delete request.headers['Content-Type'];\n\n try {\n const response = await fetch(request.url, {\n method: request.method,\n headers: request.headers,\n signal: request.options.signal\n });\n\n if (!response.ok) {\n throw new Error(`Download failed: ${response.status} ${response.statusText}`);\n }\n\n const contentDisposition = response.headers.get('content-disposition');\n let filename = options.filename || 'download';\n\n if (contentDisposition) {\n const filenameMatch = contentDisposition.match(/filename=\"?(.+)\"?/);\n if (filenameMatch && filenameMatch.length > 1) {\n filename = filenameMatch[1];\n }\n }\n\n // Create download without loading entire file into memory\n const reader = response.body.getReader();\n const stream = new ReadableStream({\n start(controller) {\n function pump() {\n return reader.read().then(({ done, value }) => {\n if (done) {\n controller.close();\n return;\n }\n controller.enqueue(value);\n return pump();\n });\n }\n return pump();\n }\n });\n\n const blob = await new Response(stream).blob();\n const downloadUrl = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.style.display = 'none';\n a.href = downloadUrl;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n window.URL.revokeObjectURL(downloadUrl);\n a.remove();\n\n return { success: true, message: 'Download initiated' };\n\n } catch (error) {\n console.error('Download error:', error);\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Download a file from a URL by fetching the entire content into a Blob.\n * @param {string} url - Request URL\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves when download is initiated\n */\n async downloadBlob(url, params = {}, options = {}) {\n const requestUrl = this.buildUrl(url) + this.buildQueryString(params);\n const request = {\n method: 'GET',\n url: requestUrl,\n headers: {\n ...this.config.headers,\n 'Accept': '*/*', // Default, can be overridden by options\n ...options.headers\n },\n options: {\n ...options\n }\n };\n // Remove content-type for GET request\n delete request.headers['Content-Type'];\n\n try {\n const response = await fetch(request.url, {\n method: request.method,\n headers: request.headers,\n signal: request.options.signal\n });\n\n if (!response.ok) {\n throw new Error(`Download failed: ${response.status} ${response.statusText}`);\n }\n\n const blob = await response.blob();\n const contentDisposition = response.headers.get('content-disposition');\n let filename = options.filename || 'download';\n\n if (contentDisposition) {\n const filenameMatch = contentDisposition.match(/filename=\"?(.+)\"?/);\n if (filenameMatch && filenameMatch.length > 1) {\n filename = filenameMatch[1];\n }\n }\n\n const downloadUrl = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.style.display = 'none';\n a.href = downloadUrl;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n window.URL.revokeObjectURL(downloadUrl);\n a.remove();\n\n return { success: true, message: 'Download initiated' };\n\n } catch (error) {\n console.error('Download error:', error);\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Upload file with raw PUT request (compatible with legacy backend)\n * @param {string} url - Upload URL\n * @param {File} file - Single file to upload\n * @param {object} options - Request options\n * @param {function} options.onProgress - Progress callback function(event)\n * @returns {Promise} Promise that resolves with response data\n */\n async upload(url, file, options = {}) {\n return new Promise((resolve, reject) => {\n // Validate input - only accept single File objects\n if (!(file instanceof File)) {\n reject(new Error('Only single File objects are supported for legacy backend compatibility'));\n return;\n }\n\n const xhr = new XMLHttpRequest();\n\n // Set up progress tracking if callback provided\n if (options.onProgress && typeof options.onProgress === 'function') {\n xhr.upload.onprogress = options.onProgress;\n }\n\n // Set up response handlers\n xhr.onload = function() {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({\n data: xhr.response,\n status: xhr.status,\n statusText: xhr.statusText,\n xhr: xhr\n });\n } else {\n reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));\n }\n };\n\n xhr.onerror = function() {\n reject(new Error('Upload failed: Network error'));\n };\n\n xhr.ontimeout = function() {\n reject(new Error('Upload failed: Timeout'));\n };\n\n // Configure request - use PUT method with raw file data\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', file.type);\n\n // Set timeout if specified\n if (options.timeout) {\n xhr.timeout = options.timeout;\n }\n\n // Send the raw file data\n xhr.send(file);\n });\n }\n\n /**\n * Upload multiple files with multipart/form-data (for modern backends)\n * @param {string} url - Upload URL\n * @param {File|FileList|FormData} files - File(s) to upload\n * @param {object} additionalData - Additional form fields\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async uploadMultipart(url, files, additionalData = {}, options = {}) {\n const formData = new FormData();\n\n // Add files to form data\n if (files instanceof FileList) {\n Array.from(files).forEach((file, index) => {\n formData.append(`file_${index}`, file);\n });\n } else if (files instanceof File) {\n formData.append('file', files);\n } else if (files instanceof FormData) {\n // Use provided FormData directly\n return this.POST(url, files, {}, options);\n }\n\n // Add additional data\n Object.entries(additionalData).forEach(([key, value]) => {\n formData.append(key, value);\n });\n\n return this.POST(url, formData, {}, options);\n }\n\n /**\n * Set authentication token\n * @param {string} token - JWT or API token\n * @param {string} type - Token type ('Bearer', 'Token', etc.)\n */\n setAuthToken(token, type = 'Bearer') {\n if (token) {\n this.config.headers['Authorization'] = `${type} ${token}`;\n } else {\n delete this.config.headers['Authorization'];\n }\n }\n\n /**\n * Clear authentication\n */\n clearAuth() {\n delete this.config.headers['Authorization'];\n }\n\n /**\n * Check if an error is retryable (network issues that might resolve)\n * @param {object} response - Response object with reason field\n * @returns {boolean} True if error can be retried\n */\n isRetryableError(response) {\n const retryableReasons = [\n 'not_reachable',\n 'timed_out',\n 'server_error',\n 'dns_error'\n ];\n return retryableReasons.includes(response.reason);\n }\n\n /**\n * Check if error requires authentication\n * @param {object} response - Response object with reason field\n * @returns {boolean} True if authentication is required\n */\n requiresAuth(response) {\n return response.reason === 'unauthorized';\n }\n\n /**\n * Check if error is network-related\n * @param {object} response - Response object with reason field\n * @returns {boolean} True if it's a network error\n */\n isNetworkError(response) {\n const networkReasons = [\n 'not_reachable',\n 'timed_out',\n 'cancelled',\n 'cors_error',\n 'dns_error'\n ];\n return networkReasons.includes(response.reason);\n }\n\n /**\n * Get user-friendly error message based on reason\n * @param {object} response - Response object with reason field\n * @returns {string} User-friendly error message\n */\n getUserMessage(response) {\n if (response.message) {\n return response.message;\n }\n\n const messages = {\n 'not_reachable': 'Unable to connect to the server. Please check your internet connection.',\n 'timed_out': 'The request took too long. Please try again.',\n 'cancelled': 'The request was cancelled.',\n 'unauthorized': 'Please log in to continue.',\n 'forbidden': 'You don\\'t have permission to perform this action.',\n 'not_found': 'The requested resource was not found.',\n 'validation_error': 'Please check your input and try again.',\n 'rate_limited': 'Too many requests. Please wait a moment before trying again.',\n 'server_error': 'Server error. Please try again later.',\n 'cors_error': 'Access blocked by security policy.',\n 'dns_error': 'Unable to reach the server.',\n 'unknown_error': 'An unexpected error occurred.'\n };\n\n return messages[response.reason] || 'An error occurred. Please try again.';\n }\n}\n\n// Create singleton instance\nconst rest = new Rest();\n\nexport { Rest };\nexport default rest;\n","/**\n * Model - Base class for models with REST API support\n * Provides CRUD operations for API resources with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits 'change' events when data is modified via set()\n * Emits 'change:attributeName' for specific attribute changes\n *\n * Standard Events:\n * - 'change' - Emitted when any model data changes\n * - 'change:fieldName' - Emitted when specific field changes\n *\n * @example\n * const user = new User({ name: 'John', email: 'john@example.com' });\n *\n * // Listen for any changes\n * user.on('change', (model) => {\n * console.log('User model changed');\n * view.render();\n * });\n *\n * // Listen for specific field changes\n * user.on('change:name', (newName, model) => {\n * console.log('Name changed to:', newName);\n * });\n *\n * // Trigger events by changing data\n * user.set('name', 'Jane'); // Emits 'change' and 'change:name'\n * user.set({ name: 'Bob', email: 'bob@example.com' }); // Emits 'change' and individual field events\n */\n\nimport MOJOUtils from '@core/utils/MOJOUtils.js';\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport rest from '@core/Rest.js';\n\nclass Model {\n constructor(data = {}, options = {}) {\n this.endpoint = options.endpoint || this.constructor.endpoint || '';\n this.id = data.id || null;\n this.attributes = { ...data };\n this._ = this.attributes;\n this.originalAttributes = { ...data };\n this.errors = {};\n this.loading = false;\n this.rest = rest;\n\n // Event system via EventEmitter mixin (applied to prototype)\n\n // Configuration options\n this.options = {\n idAttribute: 'id',\n timestamps: true,\n ...options\n };\n }\n\n getContextValue(key) {\n return this.get(key);\n }\n\n /**\n * Get attribute value with support for dot notation and pipe formatting\n * @param {string} key - Attribute key with optional pipes (e.g., \"name|uppercase\")\n * @returns {*} Attribute value, possibly formatted\n */\n get(key) {\n // Check if key exists as an instance field first (for 'id', 'endpoint', etc.)\n if (!key.includes('.') && !key.includes('|') && this[key] !== undefined) {\n // If it's a function, call it and return the result\n if (typeof this[key] === 'function') {\n return this[key]();\n }\n return this[key];\n }\n\n // Use MOJOUtils for all attribute access with pipes and dot notation\n return MOJOUtils.getContextData(this.attributes, key);\n }\n\n /**\n * Set attribute value(s)\n * @param {string|object} key - Attribute key or object of key-value pairs\n * @param {*} value - Attribute value (if key is string)\n * @param {object} options - Options (silent: true to not trigger change event)\n */\n set(key, value, options = {}) {\n const previousAttributes = JSON.parse(JSON.stringify(this.attributes)); // Deep copy\n let hasChanged = false;\n if (key === undefined || key === null) return;\n\n if (typeof key === 'object') {\n // Set multiple attributes\n for (const [attrKey, attrValue] of Object.entries(key)) {\n hasChanged = this._setNestedAttribute(attrKey, attrValue) || hasChanged;\n }\n if (key.id !== undefined) {\n this.id = key.id;\n }\n } else {\n // Set single attribute\n if (key === 'id') {\n this.id = value;\n hasChanged = true;\n } else {\n hasChanged = this._setNestedAttribute(key, value);\n }\n }\n\n // Trigger change event if data changed and not silent\n if (hasChanged && !options.silent) {\n this.emit('change', this);\n\n // Trigger specific attribute change events\n if (typeof key === 'string') {\n this.emit(`change:${key}`, value, this);\n } else {\n for (const [attr, val] of Object.entries(key)) {\n // Get the final value that was actually set (after nested expansion)\n const finalValue = this._getNestedValue(attr);\n if (JSON.stringify(this._getNestedValue(attr, previousAttributes)) !== JSON.stringify(finalValue)) {\n this.emit(`change:${attr}`, finalValue, this);\n }\n }\n }\n }\n }\n\n /**\n * Set a nested attribute using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {*} value - Value to set\n * @returns {boolean} - Whether the value changed\n */\n _setNestedAttribute(key, value) {\n if (!key.includes('.')) {\n // Simple attribute\n const oldValue = this.attributes[key];\n this.attributes[key] = value;\n this[key] = value;\n return oldValue !== value;\n }\n\n // Nested attribute with dot notation\n const keys = key.split('.');\n const topLevelKey = keys[0];\n\n // Ensure the top-level object exists\n if (!this.attributes[topLevelKey] || typeof this.attributes[topLevelKey] !== 'object') {\n this.attributes[topLevelKey] = {};\n }\n if (!this[topLevelKey] || typeof this[topLevelKey] !== 'object') {\n this[topLevelKey] = {};\n }\n\n // Get the old value for comparison\n const oldValue = this._getNestedValue(key);\n\n // Navigate to the nested location and set the value\n let attrTarget = this.attributes[topLevelKey];\n let instanceTarget = this[topLevelKey];\n\n for (let i = 1; i < keys.length - 1; i++) {\n const currentKey = keys[i];\n\n if (!attrTarget[currentKey] || typeof attrTarget[currentKey] !== 'object') {\n attrTarget[currentKey] = {};\n }\n if (!instanceTarget[currentKey] || typeof instanceTarget[currentKey] !== 'object') {\n instanceTarget[currentKey] = {};\n }\n\n attrTarget = attrTarget[currentKey];\n instanceTarget = instanceTarget[currentKey];\n }\n\n // Set the final value\n const finalKey = keys[keys.length - 1];\n attrTarget[finalKey] = value;\n instanceTarget[finalKey] = value;\n\n return JSON.stringify(oldValue) !== JSON.stringify(value);\n }\n\n /**\n * Get a nested value using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {object} source - Source object (defaults to this.attributes)\n * @returns {*} - The nested value\n */\n _getNestedValue(key, source = this.attributes) {\n if (!key.includes('.')) {\n return source[key];\n }\n\n const keys = key.split('.');\n let current = source;\n\n for (const k of keys) {\n if (current == null || typeof current !== 'object') {\n return undefined;\n }\n current = current[k];\n }\n\n return current;\n }\n\n getData() {\n return this.attributes;\n }\n\n getId() {\n return this.id;\n }\n\n /**\n * Fetch model data from API with request deduplication and cancellation\n * @param {object} options - Request options\n * @param {number} options.debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(options = {}) {\n let url = options.url;\n if (!url) {\n const id = options.id || this.getId();\n if (!id && this.options.requiresId !== false) {\n throw new Error('Model: ID is required for fetching');\n }\n url = this.buildUrl(id);\n }\n const requestKey = JSON.stringify({url, params: options.params});\n\n // Handle debounced fetch\n if (options.debounceMs && options.debounceMs > 0) {\n return this._debouncedFetch(requestKey, options);\n }\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Model: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Model: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Model: Rate limited, skipping fetch');\n return this;\n }\n\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, options, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Model: Request was cancelled');\n return this;\n }\n throw error;\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Handle debounced fetch requests\n * @param {string} requestKey - Unique key for this request\n * @param {object} options - Fetch options\n * @returns {Promise} Promise that resolves with REST response\n */\n async _debouncedFetch(requestKey, options) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch({ ...options, debounceMs: 0 });\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, options.debounceMs);\n });\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} options - Request options\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, options, abortController) {\n try {\n if (options.graph && (!options.params || !options.params.graph)) {\n if (!options.params) options.params = {};\n options.params.graph = options.graph;\n }\n const response = await this.rest.GET(url, options.params, {\n signal: abortController.signal\n });\n\n if (response.success) {\n if (response.data.status) {\n this.originalAttributes = { ...this.attributes };\n if (response.data.data) this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Model: Fetch was cancelled');\n throw error;\n }\n\n this.errors = { fetch: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Save model to API (create or update)\n * @param {object} data - Data to save to the model\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async save(data, options = {}) {\n const isNew = !this.id;\n const method = isNew ? 'POST' : 'PUT';\n const url = isNew ? this.buildUrl() : this.buildUrl(this.id);\n\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest[method](url, data, options.params);\n\n if (response.success) {\n if (response.data.status) {\n // Update model on success\n this.originalAttributes = { ...this.attributes };\n this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response; // Always return the full response\n\n } catch (error) {\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n\n /**\n * Delete model from API\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async destroy(options = {}) {\n if (!this.id) {\n this.errors = { destroy: 'Cannot destroy model without ID' };\n return {\n success: false,\n error: 'Cannot destroy model without ID',\n status: 400\n };\n }\n\n const url = this.buildUrl(this.id);\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest.DELETE(url, options.params);\n\n if (response.success) {\n // Clear model data on success\n this.attributes = {};\n this.originalAttributes = {};\n this.id = null;\n this.errors = {};\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n\n } catch (error) {\n this.errors = { destroy: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Check if model has been modified\n * @returns {boolean} True if model has unsaved changes\n */\n isDirty() {\n return JSON.stringify(this.attributes) !== JSON.stringify(this.originalAttributes);\n }\n\n /**\n * Get attributes that have changed since last save\n * @returns {object} Object containing only changed attributes\n */\n getChangedAttributes() {\n const changed = {};\n\n for (const [key, value] of Object.entries(this.attributes)) {\n if (this.originalAttributes[key] !== value) {\n changed[key] = value;\n }\n }\n\n return changed;\n }\n\n /**\n * Reset model to original state\n */\n reset() {\n // _setNestedAttribute mirrors writes onto instance props (this[key]) for\n // fast access. Clear those so `reset()` actually restores original state\n // — otherwise `model.get(key)` (which reads this[key] first) returns the\n // dirty value even though `this.attributes` has been reverted.\n for (const key of Object.keys(this.attributes)) {\n if (!(key in this.originalAttributes)) delete this[key];\n }\n for (const [key, value] of Object.entries(this.originalAttributes)) {\n this[key] = value;\n }\n this.attributes = { ...this.originalAttributes };\n this._ = this.attributes;\n this.errors = {};\n }\n\n /**\n * Build URL for API requests\n * @param {string|number} id - Optional ID to append to URL\n * @returns {string} Complete API URL\n */\n buildUrl(id = null) {\n let url = this.endpoint;\n if (id) {\n url = url.endsWith('/') ? `${url}${id}` : `${url}/${id}`;\n }\n return url;\n }\n\n /**\n * Convert model to JSON\n * @returns {object} Model attributes as plain object\n */\n toJSON() {\n return {\n id: this.id,\n ...this.attributes\n };\n }\n\n /**\n * Validate model attributes\n * @returns {boolean} True if valid, false if validation errors exist\n */\n validate() {\n this.errors = {};\n\n // Override in subclasses for custom validation\n if (this.constructor.validations) {\n for (const [field, rules] of Object.entries(this.constructor.validations)) {\n this.validateField(field, rules);\n }\n }\n\n return Object.keys(this.errors).length === 0;\n }\n\n /**\n * Validate a single field\n * @param {string} field - Field name\n * @param {object|array} rules - Validation rules\n */\n validateField(field, rules) {\n const value = this.get(field);\n const rulesArray = Array.isArray(rules) ? rules : [rules];\n\n for (const rule of rulesArray) {\n if (typeof rule === 'function') {\n const result = rule(value, this);\n if (result !== true) {\n this.errors[field] = result || `${field} is invalid`;\n break;\n }\n } else if (typeof rule === 'object') {\n if (rule.required && (value === undefined || value === null || value === '')) {\n this.errors[field] = rule.message || `${field} is required`;\n break;\n }\n if (rule.minLength && value && value.length < rule.minLength) {\n this.errors[field] = rule.message || `${field} must be at least ${rule.minLength} characters`;\n break;\n }\n if (rule.maxLength && value && value.length > rule.maxLength) {\n this.errors[field] = rule.message || `${field} must be no more than ${rule.maxLength} characters`;\n break;\n }\n if (rule.pattern && value && !rule.pattern.test(value)) {\n this.errors[field] = rule.message || `${field} format is invalid`;\n break;\n }\n }\n }\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Static method to create and fetch a model by ID\n * @param {string|number} id - Model ID\n * @param {object} options - Options\n * @returns {Promise<RestModel>} Promise that resolves with fetched model\n */\n static async find(id, options = {}) {\n const model = new this({}, options);\n await model.fetch({ id, ...options });\n return model;\n }\n\n /**\n * Static method to create a new model with data\n * @param {object} data - Model data\n * @param {object} options - Options\n * @returns {RestModel} New model instance\n */\n static create(data = {}, options = {}) {\n return new this(data, options);\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Model: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n\n // Cancel debounced fetch if exists\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n this.debouncedFetchTimeout = null;\n return true;\n }\n\n return false;\n }\n\n /**\n * Check if model has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n async showError(message) {\n const Modal = (await import('@core/views/feedback/Modal.js')).default;\n await Modal.alert(message, 'Error', {\n size: 'md',\n class: 'text-danger'\n });\n }\n}\n\nObject.assign(Model.prototype, EventEmitter);\n\nexport default Model;\n","/**\n * Collection - Class for managing arrays of Model instances\n * Provides methods for fetching and managing collections of models with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits events when collection is modified\n *\n * Standard Events:\n * - 'add' - Emitted when models are added to the collection\n * - 'remove' - Emitted when models are removed from the collection\n * - 'update' - Emitted when collection is modified (after add/remove)\n * - 'reset' - Emitted when collection is reset (all models replaced)\n *\n * @example\n * const users = new UserCollection();\n *\n * // Listen for collection changes\n * users.on('add', ({ models, collection }) => {\n * console.log('Added', models.length, 'users');\n * updateUI();\n * });\n *\n * users.on('remove', ({ models, collection }) => {\n * console.log('Removed', models.length, 'users');\n * updateUI();\n * });\n *\n * // Add models - triggers 'add' and 'update' events\n * users.add([\n * new User({ name: 'John' }),\n * new User({ name: 'Jane' })\n * ]);\n *\n * Usage Examples:\n *\n * // Preloaded Data (no REST fetching)\n * const collection = new MyCollection({ preloaded: true });\n * collection.add(new MyModel({...}));\n * // collection.fetch() will be skipped if data already exists\n *\n * // REST Data (fetch from API)\n * const collection = new MyCollection({ preloaded: false }); // default\n * await collection.fetch(); // Will make API call\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\nclass Collection {\n constructor(options = {}, data = null) {\n // Handle case where first argument is data instead of ModelClass\n if (Array.isArray(options)) {\n // First argument is an array, treat it as data\n data = options;\n options = data || {};\n } else {\n data = data || options.data || [];\n }\n this.ModelClass = options.ModelClass || Model;\n this.models = [];\n this.loading = false;\n this.errors = {};\n this.meta = {};\n this.rest = rest;\n if (data) {\n this.add(data);\n }\n\n // Initialize params with defaults - single source of truth for query state\n this.params = {\n start: 0,\n size: options.size || 10,\n ...options.params\n };\n\n // Set up endpoint\n this.endpoint = options.endpoint || this.ModelClass.endpoint || '';\n if (!this.endpoint) {\n let tmp = new this.ModelClass();\n this.endpoint = tmp.endpoint;\n }\n\n // Automatic REST detection based on endpoint\n this.restEnabled = this.endpoint ? true : false;\n\n // Allow explicit override\n if (options.restEnabled !== undefined) {\n this.restEnabled = options.restEnabled;\n }\n\n // Configuration\n this.options = {\n parse: true,\n reset: true,\n preloaded: false,\n ...options\n };\n\n // Event system via EventEmitter mixin (applied to prototype)\n }\n\n getModelName() {\n return this.ModelClass.name;\n }\n\n /**\n * Fetch collection data from API\n * @param {object} additionalParams - Additional parameters to merge for this fetch only\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(additionalParams = {}) {\n const requestKey = JSON.stringify({ ...this.params, ...additionalParams });\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Collection: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Collection: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.options.rateLimiting && this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Collection: Rate limited, skipping fetch');\n return { success: true, message: 'Rate limited, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if not REST enabled\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, skipping fetch');\n return { success: true, message: 'REST disabled, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if preloaded is true and we already have data\n if (this.options.preloaded && this.models.length > 0) {\n console.info('Collection: Using preloaded data, skipping fetch');\n return { success: true, message: 'Using preloaded data, skipping fetch', data: { data: this.toJSON() } };\n }\n\n const url = this.buildUrl();\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, additionalParams, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Collection: Request was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} additionalParams - Additional parameters\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, additionalParams, abortController) {\n const fetchParams = { ...this.params, ...additionalParams };\n console.log('Fetching collection data from', url, fetchParams);\n try {\n this.emit(\"fetch:start\");\n const response = await this.rest.GET(url, fetchParams, {\n signal: abortController.signal\n });\n\n if (response.success && response.data.status) {\n const data = this.options.parse ? this.parse(response) : response.data;\n\n if (this.options.reset || additionalParams.reset !== false) {\n this.reset();\n }\n\n this.add(data, { silent: additionalParams.silent });\n this.errors = {};\n this.emit(\"fetch:success\");\n } else {\n if (response.data && response.data.error) {\n this.errors = response.data;\n this.emit(\"fetch:error\", { message: response.data.error, error: response.data });\n } else {\n this.errors = response.errors || {};\n this.emit(\"fetch:error\", { error: response.errors });\n }\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Collection: Fetch was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n\n this.errors = { fetch: error.message };\n this.emit(\"fetch:error\", { message: error.message, error: error });\n\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n this.emit(\"fetch:end\");\n }\n }\n\n /**\n * Update collection parameters and optionally fetch new data\n * @param {object} newParams - Parameters to update\n * @param {boolean} autoFetch - Whether to automatically fetch after updating params\n * @param {number} debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response if autoFetch=true, or collection if autoFetch=false\n */\n async updateParams(newParams, autoFetch = false, debounceMs = 0) {\n return await this.setParams({ ...this.params, ...newParams }, autoFetch, debounceMs);\n }\n\n async setParams(newParams, autoFetch = false, debounceMs = 0) {\n this.params = newParams;\n if (autoFetch && this.restEnabled) {\n if (debounceMs > 0) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch();\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, debounceMs);\n });\n } else {\n // For immediate fetches, the fetch method will handle cancellation\n return this.fetch();\n }\n }\n\n return Promise.resolve(this);\n }\n\n /**\n * Fetch a single model by ID\n * @param {string|number} id - Model ID to fetch\n * @param {object} options - Additional fetch options\n * @returns {Promise<Model|null>} Promise that resolves with model instance or null if not found\n */\n async fetchOne(id, options = {}) {\n if (!id) {\n console.warn('Collection: fetchOne requires an ID');\n return null;\n }\n\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, cannot fetch single item');\n return null;\n }\n\n try {\n // Create model instance with the ID and use its fetch method\n const model = new this.ModelClass({ id }, {\n endpoint: this.endpoint,\n collection: this\n });\n\n const response = await model.fetch(options);\n\n if (response.success) {\n // Optionally add to collection if not already present\n if (options.addToCollection === true) {\n const existingModel = this.get(model.id);\n if (!existingModel) {\n this.add(model, { silent: options.silent });\n } else if (options.merge !== false) {\n existingModel.set(model.attributes);\n }\n }\n\n return model;\n } else {\n console.warn('Collection: fetchOne failed -', response.error || 'Unknown error');\n return null;\n }\n } catch (error) {\n console.error('Collection: fetchOne error -', error.message);\n return null;\n }\n }\n\n /**\n * Download collection data in a specified format\n * @param {string} format - The format for the download (e.g., 'csv', 'json')\n * @param {object} options - Download options\n * @returns {Promise} Promise that resolves with the download result\n */\n async download(format = 'json', options = {}) {\n if (!this.restEnabled) {\n console.warn('Collection: REST is not enabled, cannot download from remote.');\n // Here we could implement local data export in the future.\n return { success: false, message: 'Remote downloads are not enabled for this collection.' };\n }\n\n const url = this.buildUrl();\n const downloadParams = { ...this.params };\n\n // Remove pagination params for full export\n delete downloadParams.start;\n delete downloadParams.size;\n\n // Add format param\n downloadParams.download_format = format;\n\n // Provide a default filename and content type\n const baseName = `export-${this.getModelName().toLowerCase()}`;\n const rangeSuffix = this._buildDateRangeSuffix(downloadParams);\n const filename = `${baseName}${rangeSuffix}.${format}`;\n const contentTypes = {\n json: 'application/json',\n csv: 'text/csv'\n };\n const acceptHeader = contentTypes[format] || '*/*';\n downloadParams.filename = filename;\n\n return this.rest.download(url, downloadParams, {\n ...options,\n filename,\n headers: { 'Accept': acceptHeader }\n });\n }\n\n _buildDateRangeSuffix(params = {}) {\n const hasStart = params.dr_start;\n const hasEnd = params.dr_end;\n\n if (!hasStart && !hasEnd) {\n return '';\n }\n\n const sanitize = (value) => {\n if (!value) return '';\n return String(value).replace(/[^\\dA-Za-z_-]/g, '-');\n };\n\n const parts = [];\n const field = params.dr_field || 'daterange';\n parts.push(sanitize(field));\n\n if (hasStart) {\n parts.push(`from-${sanitize(params.dr_start)}`);\n }\n if (hasEnd) {\n parts.push(`to-${sanitize(params.dr_end)}`);\n }\n\n return `-${parts.filter(Boolean).join('-')}`;\n }\n\n /**\n * Parse response data - override in subclasses for custom parsing\n * @param {object} response - API response\n * @returns {array} Array of model data objects\n */\n parse(response) {\n // Handle standard paginated responses with size/start/count\n if (response.data && Array.isArray(response.data.data)) {\n this.meta = {\n size: response.data.size || 10,\n start: response.data.start || 0,\n count: response.data.count || 0,\n status: response.data.status,\n graph: response.data.graph,\n ...response.meta\n };\n return response.data.data;\n }\n\n // Handle direct array responses\n if (Array.isArray(response.data)) {\n return response.data;\n }\n\n // Fallback - assume response itself is the data array\n return Array.isArray(response) ? response : [response];\n }\n\n /**\n * Add model(s) to the collection\n * @param {object|array} data - Model data or array of model data\n * @param {object} options - Options for adding models\n */\n add(data, options = {}) {\n const modelsData = Array.isArray(data) ? data : [data];\n const addedModels = [];\n\n for (const modelData of modelsData) {\n let model;\n\n if (modelData instanceof this.ModelClass) {\n model = modelData;\n } else {\n model = new this.ModelClass(modelData, {\n endpoint: this.endpoint,\n collection: this\n });\n }\n\n // Check for duplicates\n const existingIndex = this.models.findIndex(m => m.id === model.id);\n if (existingIndex !== -1) {\n if (options.merge !== false) {\n // Update existing model\n this.models[existingIndex].set(model.attributes);\n }\n } else {\n // Add new model\n this.models.push(model);\n addedModels.push(model);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && addedModels.length > 0) {\n this.emit('add', { models: addedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return addedModels;\n }\n\n /**\n * Remove model(s) from the collection\n * @param {Model|array|string|number} models - Model(s) to remove or ID(s)\n * @param {object} options - Options\n */\n remove(models, options = {}) {\n const modelsToRemove = Array.isArray(models) ? models : [models];\n const removedModels = [];\n\n for (const model of modelsToRemove) {\n let index = -1;\n\n if (typeof model === 'string' || typeof model === 'number') {\n // Remove by ID\n index = this.models.findIndex(m => m.id == model);\n } else {\n // Remove by model instance\n index = this.models.indexOf(model);\n }\n\n if (index !== -1) {\n const removedModel = this.models.splice(index, 1)[0];\n removedModels.push(removedModel);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && removedModels.length > 0) {\n this.emit('remove', { models: removedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return removedModels;\n }\n\n /**\n * Reset the collection (remove all models)\n * @param {array} models - Optional new models to set\n * @param {object} options - Options\n */\n reset(models = null, options = {}) {\n const previousModels = [...this.models];\n this.models = [];\n\n if (models) {\n this.add(models, { silent: true, ...options });\n }\n\n if (!options.silent) {\n this.emit('reset', {\n collection: this,\n previousModels\n });\n }\n\n return this;\n }\n\n /**\n * Get model by ID\n * @param {string|number} id - Model ID\n * @returns {Model|undefined} Model instance or undefined\n */\n get(id) {\n return this.models.find(model => model.id == id);\n }\n\n /**\n * Get model by index\n * @param {number} index - Model index\n * @returns {Model|undefined} Model instance or undefined\n */\n at(index) {\n return this.models[index];\n }\n\n /**\n * Get collection length\n * @returns {number} Number of models in collection\n */\n length() {\n return this.models.length;\n }\n\n /**\n * Check if collection is empty\n * @returns {boolean} True if collection has no models\n */\n isEmpty() {\n return this.models.length === 0;\n }\n\n /**\n * Find models matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {array} Array of matching models\n */\n where(criteria) {\n if (typeof criteria === 'function') {\n return this.models.filter(criteria);\n }\n\n if (typeof criteria === 'object') {\n return this.models.filter(model => {\n return Object.entries(criteria).every(([key, value]) => {\n return model.get(key) === value;\n });\n });\n }\n\n return [];\n }\n\n /**\n * Find first model matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {Model|undefined} First matching model or undefined\n */\n findWhere(criteria) {\n const results = this.where(criteria);\n return results.length > 0 ? results[0] : undefined;\n }\n\n /**\n * Iterate over each model in the collection\n * @param {function} callback - Function to execute for each model (model, index, collection)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {Collection} Returns the collection for chaining\n */\n forEach(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n this.models.forEach((model, index) => {\n callback.call(thisArg, model, index, this);\n });\n\n return this;\n }\n\n /**\n * Sort collection by comparator function\n * @param {function|string} comparator - Comparison function or attribute name\n * @param {object} options - Sort options\n */\n sort(comparator, options = {}) {\n if (typeof comparator === 'string') {\n const attr = comparator;\n comparator = (a, b) => {\n const aVal = a.get(attr);\n const bVal = b.get(attr);\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n };\n }\n\n this.models.sort(comparator);\n\n if (!options.silent) {\n this.emit('sort', { collection: this });\n }\n\n return this;\n }\n\n /**\n * Convert collection to JSON array\n * @returns {array} Array of model JSON representations\n */\n toJSON() {\n return this.models.map(model => model.toJSON());\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Collection: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n return false;\n }\n\n /**\n * Check if collection has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n /**\n * Build URL for collection endpoint\n * @returns {string} Collection API URL\n */\n buildUrl() {\n return this.endpoint;\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Iterator support for for...of loops\n */\n *[Symbol.iterator]() {\n for (const model of this.models) {\n yield model;\n }\n }\n\n /**\n * Static method to create collection from array data\n * @param {function} ModelClass - Model class constructor\n * @param {array} data - Array of model data\n * @param {object} options - Collection options\n * @returns {Collection} New collection instance\n */\n static fromArray(ModelClass, data = [], options = {}) {\n const collection = new this({ ModelClass, ...options });\n collection.add(data, { silent: true });\n return collection;\n }\n}\n\nObject.assign(Collection.prototype, EventEmitter);\n\nexport default Collection;\n"],"names":["dataFormatter","constructor","this","formatters","Map","registerBuiltInFormatters","escapeHtml","str","map","String","replace","m","register","date","bind","time","datetime","datetime_tz","date_range","datetime_range","relative","relative_short","iso","v","num","parseFloat","isNaN","number","currency","percent","filesize","ordinal","compact","add","subtract","multiply","divide","toUpperCase","toLowerCase","capitalize","truncate","truncate_middle","truncate_front","slug","initials","mask","hex","unhex","email","phone","url","badge","badgeClass","status","status_text","status_icon","boolean","bool","yesnoicon","icon","avatar","image","tooltip","linkify","clipboard","default","equals","json","fn","iter","Array","isArray","Object","keys","values","plural","formatList","duration","hash","stripHtml","highlight","nl2br","code","value","options","text","escaped","defaults","urls","emails","target","rel","opts","result","urlRegex","match","prefix","startsWith","emailRegex","mode","escapedText","trim","lang","name","formatter","Error","set","apply","args","get","console","warn","error","pipe","pipeString","context","parsePipeString","reduce","currentValue","pipes","tokens","split","s","token","parsed","parseFormatter","push","parenMatch","argsString","parseArguments","colonMatch","parseColonArguments","current","inQuotes","quoteChar","depth","i","length","char","parseValue","endsWith","slice","Number","JSON","parse","e","isIdentifier","includes","prototype","hasOwnProperty","call","contextValue","getContextValue","MOJOUtils","window","require","getNestedValue","test","format","normalizeEpoch","Date","year","month","day","getTime","YYYY","getFullYear","YY","MMMM","toLocaleDateString","MMM","MM","getMonth","padStart","M","dddd","weekday","ddd","DD","getDate","D","tokenPattern","RegExp","join","hours","getHours","replacements","HH","H","hh","h","mm","getMinutes","ss","getSeconds","A","a","sortedKeys","sort","b","key","dateFormat","timeFormat","dateStr","timeStr","locale","timeZone","getTzAbbr","abbr","tzPart","Intl","DateTimeFormat","hour","minute","timeZoneName","formatToParts","find","p","type","tz2","timeStyle","w","parts","second","hourCycle","pt","y4","M2","D2","H2","m2","s2","parseInt","hNum","monthLong","monthShort","weekdayLong","weekdayShort","dateTokens","timeTokens","replaceTokens","fmt","pattern","asNum","isFinite","startValue","endValue","endVal","startStr","endStr","short","diffMs","absDiffMs","Math","abs","diffSecs","floor","diffMins","diffHours","diffDays","isFuture","years","months","weeks","dateOnly","toISOString","decimals","toLocaleString","minimumFractionDigits","maximumFractionDigits","symbol","centsStr","toString","sign","dollars","cents","formatted","padEnd","binary","bytes","units","divisor","size","unitIndex","decimalPlaces","toFixed","suffixOnly","j","k","suffix","addend","num1","num2","subtrahend","multiplier","all","c","charAt","search","replacement","flags","searchStr","regexLike","rxFlags","substring","halfSize","separator","count","filter","word","showLast","repeat","max","link","subject","encodeURIComponent","body","class","newWindow","item","mapping","fromEntries","pair","badgeType","inferBadgeType","lowered","_status","icons","colors","noIcons","noText","iconClass","active","approved","declined","inactive","pending","success","warning","trueText","falseText","colored","yesIcon","noIcon","rendition","classes","alt","_extractImageUrl","sizeClasses","xs","sm","md","lg","xl","sizeStyle","placement","html","displayValue","preferredRendition","attributes","thumbnail","renditions","availableRenditions","defaultValue","compareValue","trueResult","falseResult","singular","includeCount","array","conjunction","limit","moreText","items","hasMore","remaining","unit","precision","ms","absMs","u","unitName","round","searchTerm","className","escapedTerm","regex","uppercase","withPrefix","hexStr","toHexFromBytes","from","trunc","Uint8Array","ArrayBuffer","every","n","TextEncoder","encode","byte","TextDecoder","decode","fromCharCode","indent","stringify","has","unregister","delete","listFormatters","entries","getContextData","field","parenDepth","pipeIndex","path","remainingPath","isNullOrUndefined","deepClone","obj","clonedObj","deepMerge","sources","source","shift","isObject","assign","debounce","func","wait","timeout","clearTimeout","setTimeout","throttle","inThrottle","generateId","timestamp","now","randomStr","random","substr","entityMap","checkPasswordStrength","password","score","strength","feedback","details","hasLowercase","hasUppercase","hasNumbers","hasSpecialChars","hasCommonPatterns","isCommonPassword","commonPatterns","generatePassword","config","includeLowercase","includeUppercase","includeNumbers","includeSpecialChars","customChars","excludeAmbiguous","lowercase","numbers","specialChars","charPool","requiredChars","parseQueryString","queryString","params","searchParams","URLSearchParams","toQueryString","wrapData","data","rootContext","DataWrapper","defineProperty","writable","enumerable","configurable","_data","_rootContext","toJSON","utils","EventEmitter","on","event","callback","_listeners","listener","off","arguments","once","onceWrapper","emit","listeners","rest","baseURL","headers","Accept","trackDevice","duidHeader","duidTransport","interceptors","request","response","duid","_initializeDuid","storageKey","storedDuid","localStorage","getItem","_generateDuid","setItem","crypto","randomUUID","r","configure","baseUrl","oldTrackDevice","addInterceptor","interceptor","buildUrl","categorizeError","message","reason","buildQueryString","forEach","append","processRequestInterceptors","processedRequest","processResponseInterceptors","responseData","ok","statusText","errors","contentType","jsonData","errorInfo","method","auth","URL","FormData","fetchOptions","signals","AbortSignal","signal","any","fetch","dataOnly","errorResponse","network","mockResponse","Headers","async","GET","POST","PUT","PATCH","DELETE","post","put","patch","download","contentDisposition","filename","filenameMatch","reader","getReader","stream","ReadableStream","start","controller","pump","read","then","done","enqueue","close","blob","Response","downloadUrl","createObjectURL","document","createElement","style","display","href","appendChild","click","revokeObjectURL","remove","downloadBlob","upload","file","Promise","resolve","reject","File","xhr","XMLHttpRequest","onProgress","onprogress","onload","onerror","ontimeout","open","setRequestHeader","send","uploadMultipart","files","additionalData","formData","FileList","index","setAuthToken","clearAuth","isRetryableError","requiresAuth","isNetworkError","getUserMessage","not_reachable","timed_out","cancelled","unauthorized","forbidden","not_found","validation_error","rate_limited","server_error","cors_error","dns_error","unknown_error","Model","endpoint","id","_","originalAttributes","loading","idAttribute","timestamps","previousAttributes","hasChanged","attrKey","attrValue","_setNestedAttribute","silent","attr","val","finalValue","_getNestedValue","oldValue","topLevelKey","attrTarget","instanceTarget","currentKey","finalKey","getData","getId","requiresId","requestKey","debounceMs","_debouncedFetch","currentRequest","currentRequestKey","abortController","abort","lastFetchTime","AbortController","_performFetch","debouncedFetchTimeout","cancel","graph","save","isNew","destroy","isDirty","getChangedAttributes","changed","reset","validate","validations","rules","validateField","rulesArray","rule","required","minLength","maxLength","model","create","isFetching","showError","Modal","import","alert","Collection","ModelClass","models","meta","tmp","restEnabled","preloaded","getModelName","additionalParams","rateLimiting","fetchParams","updateParams","newParams","autoFetch","setParams","fetchOne","collection","addToCollection","existingModel","merge","downloadParams","download_format","_buildDateRangeSuffix","acceptHeader","csv","hasStart","dr_start","hasEnd","dr_end","sanitize","dr_field","Boolean","modelsData","addedModels","modelData","existingIndex","findIndex","modelsToRemove","removedModels","indexOf","removedModel","splice","previousModels","at","isEmpty","where","criteria","findWhere","results","thisArg","TypeError","comparator","aVal","bVal","Symbol","iterator","fromArray"],"mappings":"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"}
1
+ {"version":3,"file":"Collection-BtSHP_BV.js","sources":["../../src/core/utils/DataFormatter.js","../../src/core/utils/MOJOUtils.js","../../src/core/mixins/EventEmitter.js","../../src/core/Rest.js","../../src/core/Model.js","../../src/core/Collection.js"],"sourcesContent":["/**\n * DataFormatter - Universal data formatting utility for MOJO Framework\n * Provides consistent formatting with clean APIs and pipe syntax support\n * @version 1.0.0\n */\n\n// A generic, gray, person icon SVG, encoded as a Base64 data URI.\n// This is used as a fallback for the avatar formatter when no image URL is provided.\nconst GENERIC_AVATAR_SVG = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2NlZDRkYSI+PHBhdGggZD0iTTEyIDEyYzIuMjEgMCA0LTEuNzkgNC00cy0xLjc5LTQtNC00LTQgMS43OS00IDQgMS43OSA0IDQgNHptMCAyYy0yLjY3IDAtOCAxLjM0LTggNHYyaDE2di0yYzAtMi42Ni01LjMzLTQtOC00eiIvPjwvc3ZnPg==';\n\nclass DataFormatter {\n constructor() {\n this.formatters = new Map();\n this.registerBuiltInFormatters();\n }\n\n escapeHtml(str) {\n if (str === null || str === undefined) {\n return '';\n }\n const map = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#039;'\n };\n return String(str).replace(/[&<>\"']/g, (m) => map[m]);\n }\n\n /**\n * Register all built-in formatters\n */\n registerBuiltInFormatters() {\n // Date/Time formatters\n this.register('date', this.date.bind(this));\n this.register('time', this.time.bind(this));\n this.register('datetime', this.datetime.bind(this));\n this.register('datetime_tz', this.datetime_tz.bind(this));\n this.register('datatime_tz', this.datetime_tz.bind(this)); // Alias for common typo\n this.register('date_range', this.date_range.bind(this));\n this.register('datetime_range', this.datetime_range.bind(this));\n this.register('relative', this.relative.bind(this));\n this.register('fromNow', this.relative.bind(this)); // Alias\n this.register('timeago', this.relative.bind(this)); // Alias used by TimelineViewItem\n this.register('relative_short', this.relative_short.bind(this)); // Alias for short relative time\n this.register('iso', this.iso.bind(this));\n this.register('epoch', (v) => {\n if (v === null || v === undefined || v === '') return v;\n const num = parseFloat(v);\n if (isNaN(num)) return v;\n // Convert seconds to milliseconds\n return num * 1000;\n });\n\n // Number formatters\n this.register('number', this.number.bind(this));\n this.register('currency', this.currency.bind(this));\n this.register('percent', this.percent.bind(this));\n this.register('filesize', this.filesize.bind(this));\n this.register('ordinal', this.ordinal.bind(this));\n this.register('compact', this.compact.bind(this));\n\n // Math formatters\n this.register('add', this.add.bind(this));\n this.register('subtract', this.subtract.bind(this));\n this.register('multiply', this.multiply.bind(this));\n this.register('divide', this.divide.bind(this));\n this.register('sub', this.subtract.bind(this)); // Alias\n this.register('mult', this.multiply.bind(this)); // Alias\n this.register('div', this.divide.bind(this)); // Alias\n\n // String formatters\n this.register('uppercase', (v) => String(v).toUpperCase());\n this.register('lowercase', (v) => String(v).toLowerCase());\n this.register('upper', (v) => String(v).toUpperCase());\n this.register('lower', (v) => String(v).toLowerCase());\n this.register('capitalize', this.capitalize.bind(this));\n this.register('caps', this.capitalize.bind(this));\n this.register('replace', this.replace.bind(this));\n this.register('truncate', this.truncate.bind(this));\n this.register('truncate_middle', this.truncate_middle.bind(this));\n this.register('truncate_front', this.truncate_front.bind(this));\n this.register('slug', this.slug.bind(this));\n this.register('initials', this.initials.bind(this));\n this.register('mask', this.mask.bind(this));\n this.register('hex', this.hex.bind(this));\n this.register('tohex', this.hex.bind(this));\n this.register('unhex', this.unhex.bind(this));\n this.register('fromhex', this.unhex.bind(this));\n\n // HTML/Web formatters\n this.register('email', this.email.bind(this));\n this.register('phone', this.phone.bind(this));\n this.register('url', this.url.bind(this));\n this.register('badge', this.badge.bind(this));\n this.register('badgeClass', this.badgeClass.bind(this));\n this.register('status', this.status.bind(this));\n this.register('status_text', this.status_text.bind(this));\n this.register('status_icon', this.status_icon.bind(this));\n this.register('boolean', this.boolean.bind(this));\n this.register('bool', this.bool.bind(this));\n this.register('yesno', (v) => this.boolean(v, 'Yes', 'No'));\n this.register('yesnoicon', this.yesnoicon.bind(this));\n this.register('icon', this.icon.bind(this));\n this.register('avatar', this.avatar.bind(this));\n this.register('image', this.image.bind(this));\n this.register('tooltip', this.tooltip.bind(this));\n this.register('linkify', this.linkify.bind(this));\n this.register('clipboard', this.clipboard.bind(this));\n\n // Utility formatters\n this.register('default', this.default.bind(this));\n this.register('equals', this.equals.bind(this));\n this.register('json', this.json.bind(this));\n this.register('raw', (v) => v);\n this.register('custom', (v, fn) => typeof fn === 'function' ? fn(v) : v);\n this.register('iter', this.iter.bind(this));\n this.register('keys', (v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.keys(v);\n }\n return null;\n });\n this.register('values', (v) => {\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n return Object.values(v);\n }\n return null;\n });\n\n // Text/Content formatters\n this.register('plural', this.plural.bind(this));\n this.register('list', this.formatList.bind(this));\n this.register('duration', this.duration.bind(this));\n this.register('hash', this.hash.bind(this));\n this.register('stripHtml', this.stripHtml.bind(this));\n this.register('highlight', this.highlight.bind(this));\n this.register('nl2br', this.nl2br.bind(this));\n this.register('code', this.code.bind(this));\n this.register('pre', (v) => `<pre class=\"bg-light p-2 rounded border\">${this.escapeHtml(String(v))}</pre>`);\n }\n\n relative_short(value) {\n return this.relative(value, true);\n }\n\n linkify(value, options = {}) {\n if (value === null || value === undefined) return '';\n const text = String(value);\n const escaped = this.escapeHtml(text);\n const defaults = { urls: true, emails: true, target: '_blank', rel: 'noopener noreferrer' };\n const opts = (options && typeof options === 'object') ? { ...defaults, ...options } : defaults;\n\n let result = escaped;\n\n // URLs: http(s):// and www.\n if (opts.urls !== false) {\n const urlRegex = /(^|\\s)((?:https?:\\/\\/|www\\.)[^\\s<]+)/gi;\n result = result.replace(urlRegex, (match, prefix, url) => {\n const href = url.startsWith('www.') ? `https://${url}` : url;\n return `${prefix}<a href=\"${href}\" target=\"${opts.target}\" rel=\"${opts.rel}\">${url}</a>`;\n });\n }\n\n // Emails\n if (opts.emails !== false) {\n const emailRegex = /\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b/gi;\n result = result.replace(emailRegex, (email) => `<a href=\"mailto:${email}\">${email}</a>`);\n }\n\n return result;\n }\n\n clipboard(value, mode = 'text') {\n if (value === null || value === undefined) return '';\n const text = String(value);\n const escapedText = this.escapeHtml(text);\n const showText = mode !== 'icon-only';\n\n const buttonHtml = `\n <button type=\"button\"\n class=\"btn btn-sm btn-outline-secondary ms-1 p-0 border-0 bg-transparent\"\n title=\"Copy\"\n data-bs-toggle=\"tooltip\"\n data-action=\"copy-to-clipboard\"\n data-clipboard=\"${escapedText}\">\n <i class=\"bi bi-clipboard\"></i>\n </button>`.trim();\n\n return `\n <span class=\"mojo-clipboard d-inline-flex align-items-center\">\n ${showText ? `<span class=\"font-monospace\">${escapedText}</span>` : ''}\n ${buttonHtml}\n </span>\n `;\n }\n\n nl2br(value) {\n if (value === null || value === undefined) return '';\n return this.escapeHtml(String(value)).replace(/\\r\\n|\\r|\\n/g, '<br>');\n }\n\n code(value, lang = '') {\n if (value === null || value === undefined) return '';\n const language = lang ? `language-${this.escapeHtml(String(lang))}` : '';\n const content = this.escapeHtml(String(value));\n return `<pre class=\"bg-light p-2 rounded border\"><code class=\"${language}\">${content}</code></pre>`;\n }\n\n /**\n * Register a custom formatter\n * @param {string} name - Formatter name\n * @param {Function} formatter - Formatter function\n * @returns {DataFormatter} This instance for chaining\n */\n register(name, formatter) {\n if (typeof formatter !== 'function') {\n throw new Error(`Formatter must be a function, got ${typeof formatter}`);\n }\n this.formatters.set(name.toLowerCase(), formatter);\n return this;\n }\n\n /**\n * Apply a formatter\n * @param {string} name - Formatter name\n * @param {*} value - Value to format\n * @param {...*} args - Additional arguments\n * @returns {*} Formatted value\n */\n apply(name, value, ...args) {\n try {\n const formatter = this.formatters.get(name.toLowerCase());\n if (!formatter) {\n console.warn(`Formatter '${name}' not found`);\n return value;\n }\n return formatter(value, ...args);\n } catch (error) {\n console.error(`Error in formatter '${name}':`, error);\n return value;\n }\n }\n\n /**\n * Process pipe string\n * @param {*} value - Value to format\n * @param {string} pipeString - Pipe string (e.g., \"date('YYYY-MM-DD')|uppercase\")\n * @param {object} context - Optional context for resolving variables in formatter arguments\n * @returns {*} Formatted value\n */\n pipe(value, pipeString, context = null) {\n if (!pipeString) return value;\n\n // Split by pipe and process each formatter\n const pipes = this.parsePipeString(pipeString, context);\n\n return pipes.reduce((currentValue, pipe) => {\n return this.apply(pipe.name, currentValue, ...pipe.args);\n }, value);\n }\n\n /**\n * Parse pipe string into formatter calls\n * @param {string} pipeString - Pipe string\n * @param {object} context - Optional context for resolving variables\n * @returns {Array} Array of {name, args} objects\n */\n parsePipeString(pipeString, context = null) {\n const pipes = [];\n const tokens = pipeString.split('|').map(s => s.trim());\n\n for (const token of tokens) {\n const parsed = this.parseFormatter(token, context);\n if (parsed) {\n pipes.push(parsed);\n }\n }\n\n return pipes;\n }\n\n /**\n * Parse individual formatter with arguments\n * Supports both syntaxes:\n * - Parentheses: formatter('arg1', 'arg2', 3)\n * - Colon: formatter:'arg1':'arg2':3\n *\n * @param {string} token - Formatter token\n * @param {object} context - Optional context for resolving variables\n * @returns {Object} {name, args} object\n */\n parseFormatter(token, context = null) {\n // Try parentheses syntax first: formatter(arg1, arg2)\n const parenMatch = token.match(/^([a-zA-Z_]\\w*)\\s*\\((.*)\\)$/);\n if (parenMatch) {\n const [, name, argsString] = parenMatch;\n const args = argsString ? this.parseArguments(argsString, context) : [];\n return { name, args };\n }\n\n // Try colon syntax: formatter:arg1:arg2\n const colonMatch = token.match(/^([a-zA-Z_]\\w*)(?::(.+))?$/);\n if (colonMatch) {\n const [, name, argsString] = colonMatch;\n const args = argsString ? this.parseColonArguments(argsString, context) : [];\n return { name, args };\n }\n\n return null;\n }\n\n /**\n * Parse formatter arguments (comma-separated, parentheses syntax)\n * @param {string} argsString - Arguments string\n * @param {object} context - Optional context for resolving variables\n * @returns {Array} Parsed arguments\n */\n parseArguments(argsString, context = null) {\n const args = [];\n let current = '';\n let inQuotes = false;\n let quoteChar = null;\n let depth = 0;\n\n for (let i = 0; i < argsString.length; i++) {\n const char = argsString[i];\n\n if (!inQuotes && (char === '\"' || char === \"'\")) {\n inQuotes = true;\n quoteChar = char;\n current += char;\n } else if (inQuotes && char === quoteChar && argsString[i - 1] !== '\\\\') {\n inQuotes = false;\n quoteChar = null;\n current += char;\n } else if (!inQuotes && char === '{') {\n depth++;\n current += char;\n } else if (!inQuotes && char === '}') {\n depth--;\n current += char;\n } else if (!inQuotes && depth === 0 && char === ',') {\n args.push(this.parseValue(current.trim(), context));\n current = '';\n } else {\n current += char;\n }\n }\n\n if (current.trim()) {\n args.push(this.parseValue(current.trim(), context));\n }\n\n return args;\n }\n\n /**\n * Parse formatter arguments (colon-separated syntax)\n * Handles quoted strings with colons inside them\n * @param {string} argsString - Arguments string\n * @param {object} context - Optional context for resolving variables\n * @returns {Array} Parsed arguments\n */\n parseColonArguments(argsString, context = null) {\n const args = [];\n let current = '';\n let inQuotes = false;\n let quoteChar = null;\n\n for (let i = 0; i < argsString.length; i++) {\n const char = argsString[i];\n\n if (!inQuotes && (char === '\"' || char === \"'\")) {\n inQuotes = true;\n quoteChar = char;\n current += char;\n } else if (inQuotes && char === quoteChar && argsString[i - 1] !== '\\\\') {\n inQuotes = false;\n quoteChar = null;\n current += char;\n } else if (!inQuotes && char === ':') {\n args.push(this.parseValue(current.trim(), context));\n current = '';\n } else {\n current += char;\n }\n }\n\n if (current.trim()) {\n args.push(this.parseValue(current.trim(), context));\n }\n\n return args;\n }\n\n /**\n * Parse a single value\n * @param {string} value - Value string\n * @param {object} context - Optional context for resolving variables\n * @returns {*} Parsed value\n */\n parseValue(value, context = null) {\n // Remove quotes if present - quoted strings are always literals\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n return value.slice(1, -1);\n }\n\n // Boolean values\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value === 'null') return null;\n if (value === 'undefined') return undefined;\n\n // Numbers\n if (!isNaN(value) && value !== '') {\n return Number(value);\n }\n\n // Objects\n if (value.startsWith('{') && value.endsWith('}')) {\n try {\n return JSON.parse(value);\n } catch (e) {\n // Not valid JSON, continue\n }\n }\n\n // Try to resolve from context if available\n // This allows: {{value|tooltip:title:'top'}} where title is a context variable\n // Also supports dot notation: {{value|tooltip:model.email}}\n if (context && this.isIdentifier(value)) {\n // For simple identifiers, try direct property access\n if (!value.includes('.')) {\n if (Object.prototype.hasOwnProperty.call(context, value)) {\n return context[value];\n }\n }\n\n // Try get() method (for Models/Views) - handles dot notation\n if (context.get && typeof context.get === 'function') {\n const contextValue = context.get(value);\n if (contextValue !== undefined) {\n return contextValue;\n }\n }\n // Try getContextValue() method - handles dot notation\n if (context.getContextValue && typeof context.getContextValue === 'function') {\n const contextValue = context.getContextValue(value);\n if (contextValue !== undefined) {\n return contextValue;\n }\n }\n\n // For dot notation on plain objects, use MOJOUtils\n if (value.includes('.')) {\n // Import MOJOUtils if needed for nested property access\n const MOJOUtils = window.MOJOUtils || (typeof require !== 'undefined' ? require('./MOJOUtils.js').default : null);\n if (MOJOUtils) {\n const contextValue = MOJOUtils.getNestedValue(context, value);\n if (contextValue !== undefined) {\n return contextValue;\n }\n }\n }\n }\n\n // Fall back to treating as literal string\n return value;\n }\n\n /**\n * Check if a value is a valid identifier (variable name or dot-notation path)\n * @param {string} value - Value to check\n * @returns {boolean} True if valid identifier or path\n */\n isIdentifier(value) {\n // Valid JavaScript identifier or dot notation path\n // Examples: \"email\", \"model.email\", \"user.profile.name\"\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*(\\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/.test(value);\n }\n\n // ============= Date/Time Formatters =============\n\n /**\n * Format date\n * @param {*} value - Date value\n * @param {string} format - Date format pattern\n * @returns {string} Formatted date\n */\n date(value, format = 'MM/DD/YYYY') {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n let date;\n if (value instanceof Date) {\n date = value;\n } else if (typeof value === 'string') {\n // Handle date strings more carefully to avoid timezone issues\n // If it's a date-only string (YYYY-MM-DD), parse as local time\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n const [year, month, day] = value.split('-').map(Number);\n date = new Date(year, month - 1, day);\n } else {\n date = new Date(value);\n }\n } else {\n date = new Date(value);\n }\n\n if (isNaN(date.getTime())) return String(value);\n\n // Build replacements with placeholders to avoid corruption\n const tokens = {\n 'YYYY': date.getFullYear(),\n 'YY': String(date.getFullYear()).slice(-2),\n 'MMMM': date.toLocaleDateString('en-US', { month: 'long' }),\n 'MMM': date.toLocaleDateString('en-US', { month: 'short' }),\n 'MM': String(date.getMonth() + 1).padStart(2, '0'),\n 'M': date.getMonth() + 1,\n 'dddd': date.toLocaleDateString('en-US', { weekday: 'long' }),\n 'ddd': date.toLocaleDateString('en-US', { weekday: 'short' }),\n 'DD': String(date.getDate()).padStart(2, '0'),\n 'D': date.getDate()\n };\n\n // Replace tokens using a single pass with placeholders\n let result = format;\n const tokenPattern = new RegExp(`(${Object.keys(tokens).join('|')})`, 'g');\n result = result.replace(tokenPattern, (match) => tokens[match] || match);\n\n return result;\n }\n\n /**\n * Format time\n * @param {*} value - Time value\n * @param {string} format - Time format pattern\n * @returns {string} Formatted time\n */\n time(value, format = 'HH:mm:ss') {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n\n const hours = date.getHours();\n const replacements = {\n 'HH': String(hours).padStart(2, '0'),\n 'H': hours,\n 'hh': String(hours % 12 || 12).padStart(2, '0'),\n 'h': hours % 12 || 12,\n 'mm': String(date.getMinutes()).padStart(2, '0'),\n 'm': date.getMinutes(),\n 'ss': String(date.getSeconds()).padStart(2, '0'),\n 's': date.getSeconds(),\n 'A': hours >= 12 ? 'PM' : 'AM',\n 'a': hours >= 12 ? 'pm' : 'am'\n };\n\n let result = format;\n const sortedKeys = Object.keys(replacements).sort((a, b) => b.length - a.length);\n for (const key of sortedKeys) {\n result = result.replace(new RegExp(key, 'g'), replacements[key]);\n }\n\n return result;\n }\n\n /**\n * Format date and time\n * @param {*} value - DateTime value\n * @param {string} dateFormat - Date format\n * @param {string} timeFormat - Time format\n * @returns {string} Formatted datetime\n */\n datetime(value, dateFormat = 'MM/DD/YYYY', timeFormat = 'HH:mm:ss') {\n value = this.normalizeEpoch(value);\n const dateStr = this.date(value, dateFormat);\n const timeStr = this.time(value, timeFormat);\n return dateStr && timeStr ? `${dateStr} ${timeStr}` : '';\n }\n\n /**\n * Format date and time with short timezone abbreviation (e.g., EST, PDT)\n * @param {*} value - DateTime value\n * @param {string} dateFormat - Date format\n * @param {string} timeFormat - Time format\n * @param {Object} options - Options: { timeZone?: string, locale?: string }\n * @returns {string} Formatted datetime with timezone abbreviation\n */\n datetime_tz(value, dateFormat = 'MM/DD/YYYY', timeFormat = 'HH:mm:ss', options = {}) {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n\n const locale = (options && options.locale) || 'en-US';\n const timeZone = options && options.timeZone ? options.timeZone : undefined;\n\n // Helper: build short TZ abbreviation in the requested zone\n const getTzAbbr = () => {\n let abbr = '';\n try {\n const parts = new Intl.DateTimeFormat(locale, {\n hour: '2-digit',\n minute: '2-digit',\n timeZoneName: 'short',\n ...(timeZone ? { timeZone } : {})\n }).formatToParts(date);\n const tzPart = parts.find(p => p.type === 'timeZoneName');\n abbr = tzPart ? tzPart.value : '';\n\n // Try to avoid GMT offsets if browser returns them\n if (abbr && /^GMT[+-]/i.test(abbr)) {\n try {\n const parts2 = new Intl.DateTimeFormat(locale, {\n timeStyle: 'short',\n timeZoneName: 'short',\n ...(timeZone ? { timeZone } : {})\n }).formatToParts(date);\n const tz2 = parts2.find(p => p.type === 'timeZoneName');\n if (tz2 && tz2.value && !/^GMT[+-]/i.test(tz2.value)) {\n abbr = tz2.value;\n }\n } catch (e) { void 0; }\n }\n // Collapse long names like \"Eastern Daylight Time\" to initials \"EDT\"\n if (abbr && /\\s/.test(abbr)) {\n const initials = abbr.split(/\\s+/).map(w => w[0]).join('').toUpperCase();\n if (initials.length >= 2 && initials.length <= 4) {\n abbr = initials;\n }\n }\n } catch (e) {\n abbr = '';\n }\n return abbr;\n };\n\n // If no specific timeZone requested, fall back to existing date/time logic\n if (!timeZone) {\n const dateStr = this.date(date, dateFormat);\n const timeStr = this.time(date, timeFormat);\n const abbr = getTzAbbr();\n return dateStr && timeStr ? `${dateStr} ${timeStr} ${abbr}`.trim() : '';\n }\n\n // With a specific timeZone, generate tokens from Intl parts in that zone\n const parts = new Intl.DateTimeFormat(locale, {\n timeZone,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hourCycle: 'h23'\n }).formatToParts(date);\n const get = (type) => {\n const p = parts.find(pt => pt.type === type);\n return p ? p.value : '';\n };\n\n const y4 = get('year');\n const M2 = get('month'); // '01'..'12'\n const D2 = get('day'); // '01'..'31'\n const H2 = get('hour'); // '00'..'23'\n const m2 = get('minute');\n const s2 = get('second');\n\n const M = M2 ? String(parseInt(M2, 10)) : '';\n const D = D2 ? String(parseInt(D2, 10)) : '';\n const H = H2 ? String(parseInt(H2, 10)) : '';\n const hNum = H2 ? ((parseInt(H2, 10) % 12) || 12) : '';\n const A = H2 ? (parseInt(H2, 10) >= 12 ? 'PM' : 'AM') : '';\n const a = A ? A.toLowerCase() : '';\n\n const monthLong = new Intl.DateTimeFormat(locale, { timeZone, month: 'long' }).format(date);\n const monthShort = new Intl.DateTimeFormat(locale, { timeZone, month: 'short' }).format(date);\n const weekdayLong = new Intl.DateTimeFormat(locale, { timeZone, weekday: 'long' }).format(date);\n const weekdayShort = new Intl.DateTimeFormat(locale, { timeZone, weekday: 'short' }).format(date);\n\n const dateTokens = {\n 'YYYY': y4,\n 'YY': y4 ? y4.slice(-2) : '',\n 'MMMM': monthLong,\n 'MMM': monthShort,\n 'MM': M2,\n 'M': M,\n 'dddd': weekdayLong,\n 'ddd': weekdayShort,\n 'DD': D2,\n 'D': D\n };\n const timeTokens = {\n 'HH': H2,\n 'H': H,\n 'hh': hNum !== '' ? String(hNum).padStart(2, '0') : '',\n 'h': hNum !== '' ? String(hNum) : '',\n 'mm': m2,\n 'm': m2 ? String(parseInt(m2, 10)) : '',\n 'ss': s2,\n 's': s2 ? String(parseInt(s2, 10)) : '',\n 'A': A,\n 'a': a\n };\n\n const replaceTokens = (fmt, tokens) => {\n if (!fmt) return '';\n const pattern = new RegExp(`(${Object.keys(tokens).sort((a, b) => b.length - a.length).join('|')})`, 'g');\n return fmt.replace(pattern, (match) => tokens[match] ?? match);\n };\n\n const dateStr = replaceTokens(dateFormat, dateTokens);\n const timeStr = replaceTokens(timeFormat, timeTokens);\n const abbr = getTzAbbr();\n\n return dateStr && timeStr ? `${dateStr} ${timeStr} ${abbr}`.trim() : '';\n }\n\n normalizeEpoch(value) {\n // Pass-through for Date instances — callers wrap with `new Date(value)`.\n if (value instanceof Date) return value;\n\n // ISO-8601 / parseable strings — Number('2026-04-25T...') is NaN and\n // would silently drop to '' below; recover by trying Date.parse first.\n if (typeof value === 'string') {\n const asNum = Number(value);\n if (Number.isFinite(asNum)) {\n value = asNum;\n } else {\n const parsed = Date.parse(value);\n if (Number.isFinite(parsed)) return parsed; // already ms epoch\n return '';\n }\n } else if (typeof value !== 'number') {\n value = Number(value);\n }\n\n // Check if the number is valid\n if (isNaN(value)) return '';\n\n // treat anything smaller than year 2000 in ms as seconds\n if (value < 1e11) { // less than ~Sat Mar 03 1973 09:46:40 GMT\n return value * 1000; // seconds → ms\n } else if (value > 1e12 && value < 1e13) {\n return value; // already ms\n } else {\n throw new Error(\"Value doesn't look like epoch seconds or ms\");\n }\n }\n\n /**\n * Format date range\n * @param {*} startValue - Start date (required)\n * @param {*} endValue - End date (defaults to now)\n * @param {string} format - Date format (defaults to 'MM/DD/YYYY')\n * @returns {string} Formatted date range (e.g., \"01/01/2025 - 01/31/2025\")\n */\n date_range(startValue, endValue = null, format = 'MM/DD/YYYY') {\n if (!startValue) return '';\n\n const endVal = endValue || new Date();\n const startStr = this.date(startValue, format);\n const endStr = this.date(endVal, format);\n\n if (!startStr || !endStr) return '';\n return `${startStr} - ${endStr}`;\n }\n\n /**\n * Format datetime range\n * @param {*} startValue - Start datetime (required)\n * @param {*} endValue - End datetime (defaults to now)\n * @param {string} dateFormat - Date format (defaults to 'MM/DD/YYYY')\n * @param {string} timeFormat - Time format (defaults to 'HH:mm')\n * @returns {string} Formatted datetime range (e.g., \"01/01/2025 14:30 - 01/31/2025 16:45\")\n */\n datetime_range(startValue, endValue = null, dateFormat = 'MM/DD/YYYY', timeFormat = 'HH:mm') {\n if (!startValue) return '';\n\n const endVal = endValue || new Date();\n const startStr = this.datetime(startValue, dateFormat, timeFormat);\n const endStr = this.datetime(endVal, dateFormat, timeFormat);\n\n if (!startStr || !endStr) return '';\n return `${startStr} - ${endStr}`;\n }\n\n /**\n * Format relative time\n * @param {*} value - Date value\n * @param {boolean} short - Use short format\n * @returns {string} Relative time string\n */\n relative(value, short = false) {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n\n const now = new Date();\n const diffMs = date - now; // Changed to support future dates\n const absDiffMs = Math.abs(diffMs);\n const diffSecs = Math.floor(absDiffMs / 1000);\n const diffMins = Math.floor(diffSecs / 60);\n const diffHours = Math.floor(diffMins / 60);\n const diffDays = Math.floor(diffHours / 24);\n const isFuture = diffMs > 0;\n\n if (short) {\n if (diffDays > 365) return Math.floor(diffDays / 365) + 'y';\n if (diffDays > 30) return Math.floor(diffDays / 30) + 'mo';\n if (diffDays > 7) return Math.floor(diffDays / 7) + 'w';\n if (diffDays > 0) return diffDays + 'd';\n if (diffHours > 0) return diffHours + 'h';\n if (diffMins > 0) return diffMins + 'm';\n return 'now';\n }\n\n if (diffDays > 365) {\n const years = Math.floor(diffDays / 365);\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + years + ' year' + (years > 1 ? 's' : '') + suffix;\n }\n if (diffDays > 30) {\n const months = Math.floor(diffDays / 30);\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + months + ' month' + (months > 1 ? 's' : '') + suffix;\n }\n if (diffDays > 7) {\n const weeks = Math.floor(diffDays / 7);\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + weeks + ' week' + (weeks > 1 ? 's' : '') + suffix;\n }\n if (diffDays === 1) return isFuture ? 'tomorrow' : 'yesterday';\n if (diffDays > 0) {\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + diffDays + ' days' + suffix;\n }\n if (diffHours > 0) {\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + diffHours + ' hour' + (diffHours > 1 ? 's' : '') + suffix;\n }\n if (diffMins > 0) {\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + diffMins + ' minute' + (diffMins > 1 ? 's' : '') + suffix;\n }\n if (diffSecs > 30) {\n const prefix = isFuture ? 'in ' : '';\n const suffix = isFuture ? '' : ' ago';\n return prefix + diffSecs + ' seconds' + suffix;\n }\n\n return 'just now';\n }\n\n /**\n * Format ISO date\n * @param {*} value - Date value\n * @param {boolean} dateOnly - Return date only\n * @returns {string} ISO date string\n */\n iso(value, dateOnly = false) {\n if (!value) return '';\n value = this.normalizeEpoch(value);\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n\n if (dateOnly) {\n return date.toISOString().split('T')[0];\n }\n return date.toISOString();\n }\n\n // ============= Number Formatters =============\n\n /**\n * Format number\n * @param {*} value - Number value\n * @param {number} decimals - Decimal places\n * @param {string} locale - Locale string\n * @returns {string} Formatted number\n */\n number(value, decimals = 2, locale = 'en-US') {\n const num = parseFloat(value);\n if (isNaN(num)) return String(value);\n\n return num.toLocaleString(locale, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format currency\n * @param {*} value - Number value in cents\n * @param {string} symbol - Currency symbol\n * @param {number} decimals - Decimal places\n * @returns {string} Formatted currency\n */\n currency(value, symbol = '$', decimals = 2) {\n const num = parseInt(value);\n if (isNaN(num)) return String(value);\n\n // Convert cents to dollars using string manipulation to avoid floating point issues\n const centsStr = Math.abs(num).toString();\n const sign = num < 0 ? '-' : '';\n\n let dollars, cents;\n if (centsStr.length <= 2) {\n dollars = '0';\n cents = centsStr.padStart(2, '0');\n } else {\n dollars = centsStr.slice(0, -2);\n cents = centsStr.slice(-2);\n }\n\n // Add thousands separators to dollars part\n dollars = dollars.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n\n // Format based on requested decimals\n let formatted;\n if (decimals === 0) {\n // Round to nearest dollar\n const totalCents = parseInt(cents);\n if (totalCents >= 50) {\n dollars = (parseInt(dollars.replace(/,/g, '')) + 1).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n }\n formatted = dollars;\n } else if (decimals === 2) {\n formatted = `${dollars}.${cents}`;\n } else {\n // For other decimal places, truncate or pad cents as needed\n const adjustedCents = cents.slice(0, decimals).padEnd(decimals, '0');\n formatted = `${dollars}.${adjustedCents}`;\n }\n\n return sign + symbol + formatted;\n }\n\n /**\n * Format percentage\n * @param {*} value - Number value\n * @param {number} decimals - Decimal places\n * @param {boolean} multiply - Multiply by 100\n * @returns {string} Formatted percentage\n */\n percent(value, decimals = 0, multiply = true) {\n const num = parseFloat(value);\n if (isNaN(num)) return String(value);\n\n const percent = multiply ? num * 100 : num;\n return this.number(percent, decimals) + '%';\n }\n\n /**\n * Format file size\n * @param {*} value - Size in bytes\n * @param {boolean} binary - Use binary units (1024)\n * @param {number} decimals - Decimal places\n * @returns {string} Formatted file size\n */\n filesize(value, binary = false, decimals = 1) {\n const bytes = parseInt(value);\n if (isNaN(bytes)) return String(value);\n\n const units = binary ?\n ['B', 'KiB', 'MiB', 'GiB', 'TiB'] :\n ['B', 'KB', 'MB', 'GB', 'TB'];\n const divisor = binary ? 1024 : 1000;\n\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= divisor && unitIndex < units.length - 1) {\n size /= divisor;\n unitIndex++;\n }\n\n const decimalPlaces = unitIndex === 0 ? 0 : decimals;\n return `${size.toFixed(decimalPlaces)} ${units[unitIndex]}`;\n }\n\n /**\n * Format ordinal number\n * @param {*} value - Number value\n * @param {boolean} suffixOnly - Return suffix only\n * @returns {string} Ordinal number\n */\n ordinal(value, suffixOnly = false) {\n const num = parseInt(value);\n if (isNaN(num)) return String(value);\n\n const j = num % 10;\n const k = num % 100;\n\n let suffix = 'th';\n if (j === 1 && k !== 11) suffix = 'st';\n else if (j === 2 && k !== 12) suffix = 'nd';\n else if (j === 3 && k !== 13) suffix = 'rd';\n\n return suffixOnly ? suffix : num + suffix;\n }\n\n /**\n * Format compact number\n * @param {*} value - Number value\n * @param {number} decimals - Decimal places\n * @returns {string} Compact number\n */\n compact(value, decimals = 1) {\n const num = parseFloat(value);\n if (isNaN(num)) return String(value);\n\n const abs = Math.abs(num);\n const sign = num < 0 ? '-' : '';\n\n if (abs >= 1e9) {\n return sign + (abs / 1e9).toFixed(decimals) + 'B';\n }\n if (abs >= 1e6) {\n return sign + (abs / 1e6).toFixed(decimals) + 'M';\n }\n if (abs >= 1e3) {\n return sign + (abs / 1e3).toFixed(decimals) + 'K';\n }\n\n return String(num);\n }\n\n /**\n * Add numbers\n * @param {*} value - First number\n * @param {*} addend - Number to add\n * @returns {number} Sum\n */\n add(value, addend) {\n const num1 = parseFloat(value);\n const num2 = parseFloat(addend);\n if (isNaN(num1) || isNaN(num2)) return value;\n return num1 + num2;\n }\n\n /**\n * Subtract numbers\n * @param {*} value - First number\n * @param {*} subtrahend - Number to subtract\n * @returns {number} Difference\n */\n subtract(value, subtrahend) {\n const num1 = parseFloat(value);\n const num2 = parseFloat(subtrahend);\n if (isNaN(num1) || isNaN(num2)) return value;\n return num1 - num2;\n }\n\n /**\n * Multiply numbers\n * @param {*} value - First number\n * @param {*} multiplier - Number to multiply by\n * @returns {number} Product\n */\n multiply(value, multiplier) {\n const num1 = parseFloat(value);\n const num2 = parseFloat(multiplier);\n if (isNaN(num1) || isNaN(num2)) return value;\n return num1 * num2;\n }\n\n /**\n * Divide numbers\n * @param {*} value - Dividend\n * @param {*} divisor - Divisor\n * @returns {number} Quotient\n */\n divide(value, divisor) {\n const num1 = parseFloat(value);\n const num2 = parseFloat(divisor);\n if (isNaN(num1) || isNaN(num2) || num2 === 0) return value;\n return num1 / num2;\n }\n\n // ============= String Formatters =============\n\n /**\n * Capitalize string\n * @param {*} value - String value\n * @param {boolean} all - Capitalize all words (default: true). If false, only capitalizes first letter\n * @returns {string} Capitalized string\n */\n capitalize(value, all = true) {\n const str = String(value);\n if (!str) return '';\n\n if (all) {\n return str.replace(/\\b\\w/g, c => c.toUpperCase());\n }\n return str.charAt(0).toUpperCase() + str.slice(1);\n }\n\n /**\n * Replace occurrences in a string\n * @param {*} value - String value\n * @param {*} search - Search value (string or RegExp-ish string like \"/_/g\")\n * @param {*} replacement - Replacement string\n * @param {string} flags - Optional RegExp flags when search is a plain string\n * @returns {string} Updated string\n *\n * Examples:\n * - {{model.name|replace:'_':''}} // underscores removed (all occurrences)\n * - {{model.name|replace('_', '')}} // parentheses syntax\n * - {{model.name|replace:'_':' ':'g'}} // replace all underscores with spaces\n * - {{model.name|replace:'/[_-]+/g':' '}} // regex form in a string\n */\n replace(value, search, replacement = '', flags = 'g') {\n if (value === null || value === undefined) return '';\n const str = String(value);\n\n if (search === null || search === undefined || search === '') {\n return str;\n }\n\n // If a real RegExp was passed in, use it directly.\n if (search instanceof RegExp) {\n return str.replace(search, String(replacement));\n }\n\n const searchStr = String(search);\n\n // Support \"/pattern/flags\" style passed as a string.\n // Note: this is intentionally simple and doesn't attempt to parse escaped slashes.\n const regexLike = searchStr.match(/^\\/(.+)\\/([a-z]*)$/i);\n if (regexLike) {\n const [, pattern, rxFlags] = regexLike;\n try {\n return str.replace(new RegExp(pattern, rxFlags), String(replacement));\n } catch (e) {\n // Fall back to string replace below if regex construction fails\n }\n }\n\n // Default: string replace. If flags includes 'g', replace all occurrences.\n if (String(flags).includes('g')) {\n return str.split(searchStr).join(String(replacement));\n }\n\n return str.replace(searchStr, String(replacement));\n }\n\n /**\n * Truncate string\n * @param {*} value - String value\n * @param {number} length - Max length\n * @param {string} suffix - Suffix to append\n * @returns {string} Truncated string\n */\n truncate(value, length = 50, suffix = '...') {\n const str = String(value);\n if (str.length <= length) return str;\n return str.substring(0, length) + suffix;\n }\n\n /**\n * Truncate keeping only the end of the string\n * @param {*} value - String value\n * @param {number} length - Characters to keep at the end\n * @param {string} prefix - Text to prepend when truncating\n * @returns {string} Truncated string\n */\n truncate_front(value, length = 8, prefix = '...') {\n const str = String(value);\n if (str.length <= length) {\n return str;\n }\n return `${prefix}${str.slice(-length)}`;\n }\n\n /**\n * Truncate string in the middle\n * @param {*} value - String value\n * @param {number} size - The total number of characters to keep (half for the start, half for the end).\n * @param {string} replace - The character(s) to use for the middle part.\n * @returns {string} Truncated string\n */\n truncate_middle(value, size = 8, replace = '***') {\n const str = String(value);\n if (str.length <= size) {\n return str;\n }\n\n const halfSize = Math.floor(size / 2);\n const front = str.substring(0, halfSize);\n const back = str.substring(str.length - halfSize);\n\n return `${front}${replace}${back}`;\n }\n\n /**\n * Create slug from string\n * @param {*} value - String value\n * @param {string} separator - Word separator\n * @returns {string} Slug\n */\n slug(value, separator = '-') {\n const str = String(value);\n return str\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, separator)\n .replace(new RegExp(`${separator}+`, 'g'), separator)\n .replace(new RegExp(`^${separator}|${separator}$`, 'g'), '');\n }\n\n /**\n * Get initials from string\n * @param {*} value - String value\n * @param {number} count - Number of initials\n * @returns {string} Initials\n */\n initials(value, count = 2) {\n const str = String(value);\n const words = str.split(/\\s+/).filter(w => w.length > 0);\n return words\n .slice(0, count)\n .map(word => word.charAt(0).toUpperCase())\n .join('');\n }\n\n /**\n * Mask string\n * @param {*} value - String value\n * @param {string} char - Mask character\n * @param {number} showLast - Number of chars to show at end\n * @returns {string} Masked string\n */\n mask(value, char = '*', showLast = 4) {\n const str = String(value);\n if (str.length <= showLast) return str;\n\n const masked = char.repeat(Math.max(0, str.length - showLast));\n const visible = str.slice(-showLast);\n return masked + visible;\n }\n\n // ============= HTML/Web Formatters =============\n\n /**\n * Format email\n * @param {*} value - Email value\n * @param {Object} options - Options\n * @returns {string} Formatted email\n */\n email(value, options = {}) {\n const email = String(value).trim();\n if (!email) return '';\n\n if (options.link === false) {\n return email;\n }\n\n const subject = options.subject ? `?subject=${encodeURIComponent(options.subject)}` : '';\n const body = options.body ? `&body=${encodeURIComponent(options.body)}` : '';\n const className = options.class ? ` class=\"${options.class}\"` : '';\n\n return `<a href=\"mailto:${email}${subject}${body}\"${className}>${email}</a>`;\n }\n\n /**\n * Format phone number\n * @param {*} value - Phone value\n * @param {string} format - Format type\n * @param {boolean} link - Create tel link\n * @returns {string} Formatted phone\n */\n phone(value, format = 'US', link = true) {\n let phone = String(value).replace(/\\D/g, '');\n\n let formatted = phone;\n if (format === 'US') {\n if (phone.length === 10) {\n formatted = `(${phone.slice(0, 3)}) ${phone.slice(3, 6)}-${phone.slice(6)}`;\n } else if (phone.length === 11 && phone[0] === '1') {\n formatted = `+1 (${phone.slice(1, 4)}) ${phone.slice(4, 7)}-${phone.slice(7)}`;\n }\n }\n\n if (!link) {\n return formatted;\n }\n\n return `<a href=\"tel:${phone}\">${formatted}</a>`;\n }\n\n /**\n * Format URL\n * @param {*} value - URL value\n * @param {string} text - Link text\n * @param {boolean} newWindow - Open in new window\n * @returns {string} Formatted URL\n */\n url(value, text = null, newWindow = true) {\n let url = String(value).trim();\n if (!url) return '';\n\n // Add protocol if missing\n if (!/^https?:\\/\\//.test(url)) {\n url = 'https://' + url;\n }\n\n const linkText = text || url;\n const target = newWindow ? ' target=\"_blank\"' : '';\n const rel = newWindow ? ' rel=\"noopener noreferrer\"' : '';\n\n return `<a href=\"${url}\"${target}${rel}>${linkText}</a>`;\n }\n\n /**\n * Format as badge\n * @param {*} value - Badge text\n * @param {string} type - Badge type or value=type mapping string\n * Single type: badge:danger\n * Auto-detect: badge (or badge:auto)\n * Value mapping: badge:lock_engaged=danger,lock_released=success\n * @returns {string} Badge HTML\n */\n badge(value, type = 'auto') {\n // If the value is an array, map over it and create a badge for each item.\n if (Array.isArray(value)) {\n return value.map(item => this.badge(item, type)).join(' ');\n }\n\n const text = String(value);\n\n // Check for value=type mapping syntax\n // e.g. badge:lock_engaged=danger,lock_released=success\n // Arrives as a single arg: \"lock_engaged=danger,lock_released=success\"\n if (typeof type === 'string' && type.includes('=')) {\n const mapping = Object.fromEntries(\n type.split(',').map(pair => pair.split('=').map(s => s.trim()))\n );\n const badgeType = mapping[text] || mapping[text.toLowerCase()] || this.inferBadgeType(text);\n const className = badgeType ? `bg-${badgeType}` : 'bg-secondary';\n return `<span class=\"badge ${className}\">${text}</span>`;\n }\n\n const badgeType = type === 'auto' ? this.inferBadgeType(text) : type;\n const className = badgeType ? `bg-${badgeType}` : 'bg-secondary';\n\n return `<span class=\"badge ${className}\">${text}</span>`;\n }\n\n /**\n * Get badge CSS class for a value\n * @param {*} value - Value to get badge class for\n * @param {string} type - Badge type (optional, auto-detected if not specified)\n * @returns {string} Badge CSS class\n */\n badgeClass(value, type = 'auto') {\n const text = String(value);\n const badgeType = type === 'auto' ? this.inferBadgeType(text) : type;\n return badgeType ? `bg-${badgeType}` : 'bg-secondary';\n }\n\n /**\n * Infer badge type from text\n * @param {string} text - Badge text\n * @returns {string} Badge type\n */\n inferBadgeType(text) {\n const lowered = text.toLowerCase();\n if (['active', 'pass', 'success', 'complete', 'completed', 'approved', 'done', 'true', 'on', 'yes'].includes(lowered)) return 'success';\n if (['error', 'failed', 'fail', 'rejected', 'deleted', 'cancelled', 'false', 'off', 'no', 'declined'].includes(lowered)) return 'danger';\n if (['warning', 'pending', 'review', 'processing', 'uploading'].includes(lowered)) return 'warning';\n if (['info', 'new', 'draft'].includes(lowered)) return 'info';\n if (['inactive', 'disabled', 'archived', 'suspended'].includes(lowered)) return 'secondary';\n return 'secondary';\n }\n\n status(value) {\n return this._status(value);\n }\n\n status_icon(value) {\n return this._status(value, {}, {}, false, true);\n }\n\n status_text(value) {\n return this._status(value, {}, {}, true, false);\n }\n\n /**\n * Format status\n * @param {*} value - Status value\n * @param {Object} icons - Icon mapping\n * @param {Object} colors - Color mapping\n * @param {boolean} noIcons - Whether to include icons\n * @param {boolean} noText - Whether to include text\n * @returns {string} Status HTML\n */\n _status(value, icons = {}, colors = {}, noIcons = false, noText = false) {\n const status = String(value).toLowerCase();\n\n const defaultIcons = {\n 'active': 'bi bi-check-circle-fill',\n 'approved': 'bi bi-check-circle-fill',\n 'declined': 'bi bi-x-circle-fill',\n 'inactive': 'bi bi-pause-circle-fill',\n 'pending': 'bi bi-clock-fill',\n 'success': 'bi bi-check-circle-fill',\n 'error': 'bi bi-exclamation-triangle-fill',\n 'warning': 'bi bi-exclamation-triangle-fill'\n };\n\n const defaultColors = {\n 'active': 'success',\n \"approved\": \"success\",\n \"declined\": \"danger\",\n 'inactive': 'secondary',\n 'pending': 'warning',\n 'success': 'success',\n 'error': 'danger',\n 'warning': 'warning'\n };\n\n const iconClass = icons[status] || defaultIcons[status] || '';\n const color = colors[status] || defaultColors[status] || 'secondary';\n\n let icon = '';\n if (!noIcons && iconClass) {\n icon = `<i class=\"${iconClass}\"></i>`;\n }\n let text = '';\n if (!noText) {\n text = value;\n }\n\n return `<span class=\"text-${color}\">${icon}${icon ? ' ' : ''}${text}</span>`;\n }\n\n /**\n * Format boolean\n * @param {*} value - Boolean value\n * @param {string} trueText - Text for true\n * @param {string} falseText - Text for false\n * @returns {string} Boolean text\n */\n boolean(value, trueText = 'True', falseText = 'False', colored = false) {\n const text = value ? trueText : falseText;\n return colored ? `<span class=\"text-${value ? 'success' : 'danger'}\">${text}</span>` : text;\n }\n\n bool(value) {\n // Return false for null, undefined, 0, empty string\n if (value === null || value === undefined || value === 0 || value === '') {\n return false;\n }\n\n if (value === false || value === 'false') {\n return false;\n }\n\n if (value === true || value === 'true') {\n return true;\n }\n\n // Return false for empty arrays\n if (Array.isArray(value) && value.length === 0) {\n return false;\n }\n\n // Return false for empty objects (but not for other object types like Date, etc.)\n if (value && typeof value === 'object' && value.constructor === Object && Object.keys(value).length === 0) {\n return false;\n }\n\n // Return true for everything else\n return true;\n }\n\n /**\n * Format icon\n * @param {*} value - Icon key\n * @param {Object} mapping - Icon mapping\n * @returns {string} Icon HTML\n */\n icon(value, mapping = {}) {\n const key = String(value).toLowerCase();\n const icon = mapping[key] || '';\n return icon ? `<i class=\"${icon}\"></i>` : '';\n }\n\n /**\n * Format boolean as a yes/no icon\n * @param {*} value - Boolean value\n * @returns {string} Icon HTML\n */\n yesnoicon(value, yesIcon = 'bi bi-check-circle-fill text-success', noIcon = 'bi bi-x-circle-fill text-danger') {\n if (value) { // Handles true, 1, \"true\", \"on\", etc.\n return `<i class=\"${yesIcon}\"></i>`;\n }\n // Handles false, 0, \"\", null, undefined\n return `<i class=\"${noIcon}\"></i>`;\n }\n\n /**\n * Format value as Bootstrap 5 image with optional rendition support\n * @param {string|object} value - URL string or file object with renditions\n * @param {string} rendition - Desired rendition (thumbnail, thumbnail_sm, etc.)\n * @param {string} classes - Additional CSS classes\n * @param {string} alt - Alt text for the image\n * @returns {string} Bootstrap image HTML\n */\n image(value, rendition = 'thumbnail', classes = 'img-fluid', alt = '') {\n const url = this._extractImageUrl(value, rendition);\n if (!url) return '';\n\n return `<img src=\"${url}\" class=\"${classes}\" alt=\"${alt}\" />`;\n }\n\n /**\n * Format value as Bootstrap 5 avatar (circular image)\n * @param {string|object} value - URL string or file object with renditions\n * @param {string} size - Avatar size (xs, sm, md, lg, xl)\n * @param {string} classes - Additional CSS classes\n * @param {string} alt - Alt text for the avatar\n * @returns {string} Bootstrap avatar HTML\n */\n avatar(value, size = 'md', classes = 'rounded-circle', alt = '') {\n const url = this._extractImageUrl(value, 'square_sm') || GENERIC_AVATAR_SVG;\n\n // Bootstrap avatar sizing\n const sizeClasses = {\n 'xs': 'width: 1.5rem; height: 1.5rem;',\n 'sm': 'width: 2rem; height: 2rem;',\n 'md': 'width: 3rem; height: 3rem;',\n 'lg': 'width: 4rem; height: 4rem;',\n 'xl': 'width: 5rem; height: 5rem;'\n };\n\n const sizeStyle = sizeClasses[size] || sizeClasses['md'];\n const baseClasses = 'object-fit-cover';\n const allClasses = `${baseClasses} ${classes}`.trim();\n\n return `<img src=\"${url}\" class=\"${allClasses}\" style=\"${sizeStyle}\" alt=\"${alt}\" />`;\n }\n\n /**\n * Tooltip formatter - wraps value with Bootstrap tooltip\n * Usage:\n * {{value|tooltip:'Tooltip text'}}\n * {{value|tooltip:'Help text':top}}\n * {{value|tooltip:'Info':bottom:html}}\n *\n * @param {*} value - Value to display (not escaped, works with formatter chains)\n * @param {string} text - Tooltip text content\n * @param {string} placement - Tooltip placement: top, bottom, left, right (default: top)\n * @param {string} html - 'html' to allow HTML in tooltip (default: text only)\n * @returns {string} HTML with tooltip\n */\n tooltip(value, text = '', placement = 'top', html = '') {\n if (value === null || value === undefined) return '';\n\n // Don't escape value - it may be HTML from previous formatters in the chain\n const displayValue = String(value);\n const tooltipText = html === 'html' ? text : this.escapeHtml(text);\n const dataAttr = html === 'html' ? 'data-bs-html=\"true\"' : '';\n\n return `<span data-bs-toggle=\"tooltip\" data-bs-placement=\"${placement}\" ${dataAttr} data-bs-title=\"${tooltipText}\">${displayValue}</span>`;\n }\n\n /**\n * Helper method to extract image URL from string or file object\n * @param {string|object} value - URL string or file object with renditions\n * @param {string} preferredRendition - Preferred rendition name\n * @returns {string|null} Image URL or null if not found\n */\n _extractImageUrl(value, preferredRendition = 'thumbnail') {\n // Handle null/undefined\n if (!value) return null;\n\n // Handle string URL directly\n if (typeof value === 'string') {\n return value;\n }\n\n // Handle file object with renditions\n if (typeof value === 'object') {\n\n if (value.attributes) value = value.attributes;\n if (preferredRendition === \"thumbnail\" && value.thumbnail && typeof value.thumbnail === 'string') {\n return value.thumbnail;\n }\n // Check if it has renditions\n if (value.renditions && typeof value.renditions === 'object') {\n // Try to get preferred rendition\n const rendition = value.renditions[preferredRendition];\n if (rendition && rendition.url) {\n return rendition.url;\n }\n\n // Fallback to any available rendition\n const availableRenditions = Object.values(value.renditions);\n if (availableRenditions.length > 0 && availableRenditions[0].url) {\n return availableRenditions[0].url;\n }\n }\n\n // Fallback to original file URL\n if (value.url) {\n return value.url;\n }\n }\n\n return null;\n }\n\n // ============= Utility Formatters =============\n\n /**\n * Apply default value\n * @param {*} value - Value\n * @param {*} defaultValue - Default value\n * @returns {*} Value or default\n */\n default(value, defaultValue = '') {\n return value === null || value === undefined || value === '' ? defaultValue : value;\n }\n\n /**\n * Compare value and return one of two results based on equality\n * Useful for conditional CSS classes, text, or any conditional output\n *\n * @param {*} value - Value to compare\n * @param {*} compareValue - Value to compare against\n * @param {*} trueResult - Result if values are equal\n * @param {*} falseResult - Result if values are not equal (optional, defaults to empty string)\n * @returns {*} trueResult or falseResult\n *\n * @example\n * // CSS classes\n * {{status|equals:1:'text-success':'text-secondary'}}\n * {{model.state|equals:'active':'badge-success':'badge-secondary'}}\n *\n * // Text output\n * {{role|equals:'admin':'Administrator':'User'}}\n *\n * // Numbers\n * {{count|equals:0:'No items':'Has items'}}\n */\n equals(value, compareValue, trueResult, falseResult = '') {\n // Handle loose equality for common cases (1 == '1', true == 'true', etc.)\n // eslint-disable-next-line eqeqeq\n return value == compareValue ? trueResult : falseResult;\n }\n\n /**\n * Format as JSON\n * @param {*} value - Value to stringify\n * @param {number} indent - Indentation\n * @returns {string} JSON string\n */\n /**\n * Format pluralization based on count\n * @param {number} count - The count value\n * @param {string} singular - Singular form of the word\n * @param {string|null} plural - Plural form (defaults to singular + 's')\n * @param {boolean} includeCount - Whether to include the count in output\n * @returns {string} Formatted plural string\n */\n plural(count, singular, plural = null, includeCount = true) {\n if (count === null || count === undefined || singular === null || singular === undefined) {\n return includeCount ? `${count} ${singular}` : (singular || '');\n }\n\n const num = parseInt(count);\n if (isNaN(num)) {\n return includeCount ? `${count} ${singular}` : (singular || '');\n }\n\n const word = Math.abs(num) === 1 ? singular : (plural || singular + 's');\n return includeCount ? `${num} ${word}` : word;\n }\n\n /**\n * Format array as a human-readable list\n * @param {Array} array - Array to format\n * @param {Object} options - Formatting options\n * @returns {string} Formatted list string\n */\n formatList(array, options = {}) {\n if (!Array.isArray(array)) {\n return String(array);\n }\n\n const { conjunction = 'and', limit = null, moreText = 'others' } = options;\n\n if (array.length === 0) return '';\n if (array.length === 1) return String(array[0]);\n\n let items = array.slice();\n let hasMore = false;\n\n if (limit && array.length > limit) {\n items = array.slice(0, limit);\n hasMore = true;\n }\n\n if (hasMore) {\n const remaining = array.length - limit;\n return `${items.join(', ')}, ${conjunction} ${remaining} ${moreText}`;\n }\n\n if (items.length === 2) {\n return `${items[0]} ${conjunction} ${items[1]}`;\n }\n\n return `${items.slice(0, -1).join(', ')}, ${conjunction} ${items[items.length - 1]}`;\n }\n\n /**\n * Format duration to human-readable format\n * @param {number} value - Duration value\n * @param {string} unit - Input unit: 'ms', 's', 'm', 'h', 'd' (defaults to 'ms')\n * @param {boolean} short - Use short format (e.g., '1h30m' vs '1 hour 30 minutes')\n * @param {number} precision - Max number of units to show (defaults to 2)\n * @returns {string} Formatted duration string\n */\n duration(value, unit = 'ms', short = false, precision = 2) {\n if (value === null || value === undefined) return '';\n\n const num = parseFloat(value);\n if (isNaN(num)) return String(value);\n\n // Convert input to milliseconds\n let ms;\n switch (unit) {\n case 's':\n case 'sec':\n case 'seconds':\n ms = num * 1000;\n break;\n case 'm':\n case 'min':\n case 'minutes':\n ms = num * 60000;\n break;\n case 'h':\n case 'hr':\n case 'hours':\n ms = num * 3600000;\n break;\n case 'd':\n case 'day':\n case 'days':\n ms = num * 86400000;\n break;\n case 'ms':\n case 'milliseconds':\n default:\n ms = num;\n }\n\n const units = [\n { name: 'day', short: 'd', value: 86400000 },\n { name: 'hour', short: 'h', value: 3600000 },\n { name: 'minute', short: 'm', value: 60000 },\n { name: 'second', short: 's', value: 1000 }\n ];\n\n if (ms === 0) return short ? '0s' : '0 seconds';\n\n const absMs = Math.abs(ms);\n const sign = ms < 0 ? '-' : '';\n const parts = [];\n let remaining = absMs;\n\n for (const u of units) {\n if (remaining >= u.value) {\n const count = Math.floor(remaining / u.value);\n remaining = remaining % u.value;\n\n const unitName = short ? u.short : (count === 1 ? u.name : u.name + 's');\n parts.push(short ? `${count}${unitName}` : `${count} ${unitName}`);\n\n if (parts.length >= precision) break;\n }\n }\n\n if (parts.length === 0) {\n return short ? `${Math.round(absMs)}ms` : `${Math.round(absMs)} milliseconds`;\n }\n\n return sign + (short ? parts.join('') : parts.join(' '));\n }\n\n /**\n * Format long strings/IDs with truncation\n * @param {string} value - Value to format\n * @param {number} length - Maximum length before truncation\n * @param {string} prefix - Prefix to add\n * @param {string} suffix - Suffix for truncated strings\n * @returns {string} Formatted hash string\n */\n hash(value, length = 8, prefix = '', suffix = '...') {\n if (value === null || value === undefined) return '';\n\n const str = String(value);\n if (str.length <= length) return prefix + str;\n return prefix + str.substring(0, length) + suffix;\n }\n\n /**\n * Strip HTML tags from text\n * @param {string} html - HTML string to strip\n * @returns {string} Plain text without HTML tags\n */\n stripHtml(html) {\n if (html === null || html === undefined) return '';\n return String(html).replace(/<[^>]*>/g, '');\n }\n\n /**\n * Highlight search terms in text\n * @param {string} text - Text to search in\n * @param {string} searchTerm - Term to highlight\n * @param {string} className - CSS class for highlighting\n * @returns {string} Text with highlighted terms\n */\n highlight(text, searchTerm, className = 'highlight') {\n if (text === null || text === undefined || !searchTerm) {\n return String(text || '');\n }\n\n const escapedTerm = String(searchTerm).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp(`(${escapedTerm})`, 'gi');\n return String(text).replace(regex, `<mark class=\"${className}\">$1</mark>`);\n }\n\n /**\n * Encode a value as a hex string.\n * - Strings are encoded as UTF-8 bytes, then hex-encoded\n * - Numbers are converted to base-16 (padded to even length)\n * - Uint8Array/ArrayBuffer/number[] are treated as bytes\n *\n * @param {*} value - The value to encode\n * @param {boolean} uppercase - Uppercase hex letters (A-F)\n * @param {boolean} withPrefix - Prefix with '0x'\n * @returns {string} Hex string\n */\n hex(value, uppercase = false, withPrefix = false) {\n if (value === null || value === undefined) return '';\n\n let hexStr = '';\n\n const toHexFromBytes = (bytes) =>\n Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');\n\n if (typeof value === 'number') {\n let hex = Math.abs(Math.trunc(value)).toString(16);\n if (hex.length % 2) hex = '0' + hex;\n hexStr = hex;\n } else if (value instanceof Uint8Array) {\n hexStr = toHexFromBytes(value);\n } else if (value instanceof ArrayBuffer) {\n hexStr = toHexFromBytes(new Uint8Array(value));\n } else if (Array.isArray(value) && value.every(n => typeof n === 'number')) {\n hexStr = toHexFromBytes(Uint8Array.from(value.map(n => n & 0xFF)));\n } else {\n // Treat everything else as string and encode to UTF-8\n const enc = new TextEncoder();\n const bytes = enc.encode(String(value));\n hexStr = toHexFromBytes(bytes);\n }\n\n if (uppercase) hexStr = hexStr.toUpperCase();\n return (withPrefix ? '0x' : '') + hexStr;\n }\n\n /**\n * Decode a hex string into UTF-8 text.\n * Accepts optional '0x' prefix and ignores whitespace.\n *\n * @param {string} value - Hex string\n * @returns {string} Decoded UTF-8 string (or original value on parse error)\n */\n unhex(value) {\n if (value === null || value === undefined) return '';\n\n let str = String(value).trim();\n if (str.startsWith('0x') || str.startsWith('0X')) str = str.slice(2);\n str = str.replace(/\\s+/g, '');\n\n if (str.length === 0) return '';\n\n // If odd length, pad with leading zero\n if (str.length % 2 !== 0) str = '0' + str;\n\n const bytes = new Uint8Array(str.length / 2);\n for (let i = 0; i < str.length; i += 2) {\n const byte = parseInt(str.slice(i, i + 2), 16);\n if (Number.isNaN(byte)) {\n return String(value);\n }\n bytes[i / 2] = byte;\n }\n\n try {\n const dec = new TextDecoder();\n return dec.decode(bytes);\n } catch (e) {\n // Fallback if TextDecoder is unavailable\n let text = '';\n for (const b of bytes) text += String.fromCharCode(b);\n return text;\n }\n }\n\n json(value, indent = 2) {\n try {\n return JSON.stringify(value, null, indent);\n } catch (e) {\n return String(value);\n }\n }\n\n /**\n * Check if formatter exists\n * @param {string} name - Formatter name\n * @returns {boolean} True if exists\n */\n has(name) {\n return this.formatters.has(name.toLowerCase());\n }\n\n /**\n * Remove a formatter\n * @param {string} name - Formatter name\n * @returns {boolean} True if removed\n */\n unregister(name) {\n return this.formatters.delete(name.toLowerCase());\n }\n\n /**\n * Get all formatter names\n * @returns {Array} Formatter names\n */\n listFormatters() {\n return Array.from(this.formatters.keys()).sort();\n }\n\n iter(v) {\n if (v === null || v === undefined) {\n return [];\n }\n\n // If it's already an array, return as-is\n if (Array.isArray(v)) {\n return v;\n }\n\n // If it's an object, convert to key-value pairs\n if (typeof v === 'object') {\n return Object.entries(v).map(([key, value]) => ({\n key: key,\n value: value\n }));\n }\n\n // For primitive values, wrap in array with single item\n return [{ key: '0', value: v }];\n }\n}\n\n// Create singleton instance\nconst dataFormatter = new DataFormatter();\nwindow.dataFormatter = dataFormatter;\n\n// Export both class and instance\nexport { DataFormatter };\nexport default dataFormatter;\n","/**\n * MOJOUtils - Core utility functions for MOJO Framework\n * Provides centralized data access and formatting utilities\n */\n\nimport dataFormatter from './DataFormatter.js';\n\nclass MOJOUtils {\n /**\n * Get data from context with support for:\n * - Dot notation (e.g., \"user.name\")\n * - Pipe formatting (e.g., \"name|uppercase\")\n * - Combined (e.g., \"user.name|uppercase|truncate(10)\")\n *\n * @param {object} context - The data context to search in\n * @param {string} key - The key path with optional pipes\n * @returns {*} The value, possibly formatted\n */\n static getContextData(context, key) {\n if (!key || context == null) {\n return undefined;\n }\n\n // Check for pipe syntax - split on first pipe outside of parentheses\n let field = key;\n let pipes = '';\n\n // Find the first pipe that's not inside parentheses\n let parenDepth = 0;\n let pipeIndex = -1;\n\n for (let i = 0; i < key.length; i++) {\n const char = key[i];\n if (char === '(') parenDepth++;\n else if (char === ')') parenDepth--;\n else if (char === '|' && parenDepth === 0) {\n pipeIndex = i;\n break;\n }\n }\n\n if (pipeIndex > -1) {\n field = key.substring(0, pipeIndex).trim();\n pipes = key.substring(pipeIndex + 1).trim();\n }\n\n // Get the raw value\n const value = this.getNestedValue(context, field);\n\n // Apply pipes if present, passing context for variable resolution\n if (pipes) {\n return dataFormatter.pipe(value, pipes, context);\n }\n\n return value;\n }\n\n /**\n * Get nested value from object using dot notation\n * IMPORTANT: Never calls get() on the top-level context to avoid recursion\n * But DOES call get() on nested objects if they have that method\n *\n * @param {object} context - The object to search in\n * @param {string} path - Dot notation path\n * @returns {*} The value at the path\n */\n static getNestedValue(context, path) {\n if (!path || context == null) {\n return undefined;\n }\n\n // If no dots, simple property lookup\n if (!path.includes('.')) {\n // Direct property access - never call get() on top level\n // Check if property exists (including prototype chain for methods)\n if (path in context) {\n const value = context[path];\n // Check if it's a method (like getStatus, getButtonClass)\n if (typeof value === 'function') {\n return value.call(context);\n }\n return value;\n }\n\n return undefined;\n }\n\n // Handle dot notation\n const keys = path.split('.');\n let current = context;\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n\n if (current == null) {\n return undefined;\n }\n\n // For the first key, never use get() (it's the top-level context)\n if (i === 0) {\n // Direct property access\n if (current.hasOwnProperty(key)) {\n const value = current[key];\n // Check if it's a method and call it\n if (typeof value === 'function') {\n current = value.call(context);\n } else {\n current = value;\n }\n } else {\n return undefined;\n }\n } else {\n // For nested objects, check if they have a get() method\n if (current && typeof current.getContextValue === 'function') {\n // Use get() for the remaining path\n const remainingPath = keys.slice(i).join('.');\n return current.getContextValue(remainingPath);\n }\n\n // Standard property access\n if (Array.isArray(current) && !isNaN(key)) {\n // Array index access\n current = current[parseInt(key)];\n } else if (current.hasOwnProperty(key)) {\n current = current[key];\n } else if (typeof current[key] === 'function') {\n current = current[key].call(current);\n } else {\n return undefined;\n }\n }\n }\n\n return current;\n }\n\n /**\n * Check if a value is null or undefined\n * @param {*} value - Value to check\n * @returns {boolean} True if null or undefined\n */\n static isNullOrUndefined(value) {\n return value === null || value === undefined;\n }\n\n /**\n * Deep clone an object\n * @param {*} obj - Object to clone\n * @returns {*} Cloned object\n */\n static deepClone(obj) {\n if (obj === null || typeof obj !== 'object') return obj;\n if (obj instanceof Date) return new Date(obj.getTime());\n if (obj instanceof Array) return obj.map(item => this.deepClone(item));\n if (obj instanceof Object) {\n const clonedObj = {};\n for (const key in obj) {\n if (obj.hasOwnProperty(key)) {\n clonedObj[key] = this.deepClone(obj[key]);\n }\n }\n return clonedObj;\n }\n }\n\n /**\n * Merge objects deeply\n * @param {object} target - Target object\n * @param {...object} sources - Source objects to merge\n * @returns {object} Merged object\n */\n static deepMerge(target, ...sources) {\n if (!sources.length) return target;\n const source = sources.shift();\n\n if (this.isObject(target) && this.isObject(source)) {\n for (const key in source) {\n if (this.isObject(source[key])) {\n if (!target[key]) Object.assign(target, { [key]: {} });\n this.deepMerge(target[key], source[key]);\n } else {\n Object.assign(target, { [key]: source[key] });\n }\n }\n }\n\n return this.deepMerge(target, ...sources);\n }\n\n /**\n * Check if value is a plain object\n * @param {*} item - Value to check\n * @returns {boolean} True if plain object\n */\n static isObject(item) {\n return item && typeof item === 'object' && !Array.isArray(item);\n }\n\n /**\n * Debounce function calls\n * @param {function} func - Function to debounce\n * @param {number} wait - Wait time in milliseconds\n * @returns {function} Debounced function\n */\n static debounce(func, wait) {\n let timeout;\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func(...args);\n };\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n }\n\n /**\n * Throttle function calls\n * @param {function} func - Function to throttle\n * @param {number} limit - Time limit in milliseconds\n * @returns {function} Throttled function\n */\n static throttle(func, limit) {\n let inThrottle;\n return function(...args) {\n if (!inThrottle) {\n func.apply(this, args);\n inThrottle = true;\n setTimeout(() => inThrottle = false, limit);\n }\n };\n }\n\n /**\n * Generate a unique ID\n * @param {string} prefix - Optional prefix for the ID\n * @returns {string} Unique ID\n */\n static generateId(prefix = '') {\n const timestamp = Date.now().toString(36);\n const randomStr = Math.random().toString(36).substr(2, 9);\n return prefix ? `${prefix}_${timestamp}_${randomStr}` : `${timestamp}_${randomStr}`;\n }\n\n /**\n * Escape HTML special characters\n * @param {string} str - String to escape\n * @returns {string} Escaped string\n */\n static escapeHtml(str) {\n const entityMap = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;'\n };\n\n return String(str).replace(/[&<>\"'`=\\/]/g, s => entityMap[s]);\n }\n\n /**\n * Check password strength and provide detailed feedback\n * @param {string} password - Password to check\n * @returns {object} Password strength analysis\n */\n static checkPasswordStrength(password) {\n if (!password || typeof password !== 'string') {\n return {\n score: 0,\n strength: 'invalid',\n feedback: ['Password must be a non-empty string'],\n details: {\n length: 0,\n hasLowercase: false,\n hasUppercase: false,\n hasNumbers: false,\n hasSpecialChars: false,\n hasCommonPatterns: false,\n isCommonPassword: false\n }\n };\n }\n\n const feedback = [];\n const details = {\n length: password.length,\n hasLowercase: /[a-z]/.test(password),\n hasUppercase: /[A-Z]/.test(password),\n hasNumbers: /[0-9]/.test(password),\n hasSpecialChars: /[^a-zA-Z0-9]/.test(password),\n hasCommonPatterns: false,\n isCommonPassword: false\n };\n\n let score = 0;\n\n // Length scoring\n if (password.length < 6) {\n feedback.push('Password should be at least 6 characters long');\n } else if (password.length < 8) {\n score += 1;\n feedback.push('Consider using at least 8 characters for better security');\n } else if (password.length < 12) {\n score += 3;\n } else {\n score += 4;\n }\n\n // Character variety scoring\n if (details.hasLowercase) score += 1;\n else feedback.push('Include lowercase letters');\n\n if (details.hasUppercase) score += 1;\n else feedback.push('Include uppercase letters');\n\n if (details.hasNumbers) score += 1;\n else feedback.push('Include numbers');\n\n if (details.hasSpecialChars) score += 2;\n else feedback.push('Include special characters (!@#$%^&* etc.)');\n\n // Check for common patterns\n const commonPatterns = [\n /123/, // Sequential numbers\n /abc/i, // Sequential letters\n /qwerty/i, // Keyboard patterns\n /asdf/i, // Keyboard patterns\n /(.)\\1{2,}/, // Repeated characters (aaa, 111)\n /password/i, // Contains \"password\"\n /admin/i, // Contains \"admin\"\n /user/i, // Contains \"user\"\n /login/i // Contains \"login\"\n ];\n\n for (const pattern of commonPatterns) {\n if (pattern.test(password)) {\n details.hasCommonPatterns = true;\n score -= 1;\n feedback.push('Avoid common patterns and dictionary words');\n break;\n }\n }\n\n // Check against very common passwords\n const commonPasswords = [\n '123456', 'password', '123456789', '12345678', '12345',\n '1234567', '1234567890', 'qwerty', 'abc123', '111111',\n '123123', 'admin', 'letmein', 'welcome', 'monkey',\n 'password123', '123qwe', 'qwerty123', '000000', 'dragon',\n 'sunshine', 'princess', 'azerty', '1234', 'iloveyou',\n 'trustno1', 'superman', 'shadow', 'master', 'jennifer'\n ];\n\n if (commonPasswords.includes(password.toLowerCase())) {\n details.isCommonPassword = true;\n score = Math.max(0, score - 3);\n feedback.push('This password is too common and easily guessed');\n }\n\n // Calculate final strength\n let strength;\n if (score < 2) {\n strength = 'very-weak';\n } else if (score < 4) {\n strength = 'weak';\n } else if (score < 6) {\n strength = 'fair';\n } else if (score < 8) {\n strength = 'good';\n } else {\n strength = 'strong';\n }\n\n // Add positive feedback for strong passwords\n if (score >= 7 && feedback.length === 0) {\n feedback.push('Strong password! Consider using a password manager.');\n } else if (score >= 5 && feedback.length <= 1) {\n feedback.push('Good password strength. Consider adding more variety.');\n }\n\n return {\n score: Math.max(0, score),\n strength,\n feedback,\n details\n };\n }\n\n /**\n * Generate a secure password with customizable options\n * @param {object} options - Password generation options\n * @param {number} options.length - Password length (default: 12)\n * @param {boolean} options.includeLowercase - Include lowercase letters (default: true)\n * @param {boolean} options.includeUppercase - Include uppercase letters (default: true)\n * @param {boolean} options.includeNumbers - Include numbers (default: true)\n * @param {boolean} options.includeSpecialChars - Include special characters (default: true)\n * @param {string} options.customChars - Custom character set to use\n * @param {boolean} options.excludeAmbiguous - Exclude ambiguous characters like 0, O, l, I (default: false)\n * @returns {string} Generated password\n */\n static generatePassword(options = {}) {\n const defaults = {\n length: 12,\n includeLowercase: true,\n includeUppercase: true,\n includeNumbers: true,\n includeSpecialChars: true,\n customChars: '',\n excludeAmbiguous: false\n };\n\n const config = { ...defaults, ...options };\n\n if (config.length < 4) {\n throw new Error('Password length must be at least 4 characters');\n }\n\n // Build character sets\n let lowercase = 'abcdefghijklmnopqrstuvwxyz';\n let uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\n let numbers = '0123456789';\n let specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?';\n\n // Remove ambiguous characters if requested\n if (config.excludeAmbiguous) {\n lowercase = lowercase.replace(/[il]/g, '');\n uppercase = uppercase.replace(/[IOL]/g, '');\n numbers = numbers.replace(/[01]/g, '');\n specialChars = specialChars.replace(/[|]/g, '');\n }\n\n // Build character pool\n let charPool = '';\n const requiredChars = [];\n\n if (config.customChars) {\n charPool = config.customChars;\n } else {\n if (config.includeLowercase) {\n charPool += lowercase;\n requiredChars.push(lowercase[Math.floor(Math.random() * lowercase.length)]);\n }\n if (config.includeUppercase) {\n charPool += uppercase;\n requiredChars.push(uppercase[Math.floor(Math.random() * uppercase.length)]);\n }\n if (config.includeNumbers) {\n charPool += numbers;\n requiredChars.push(numbers[Math.floor(Math.random() * numbers.length)]);\n }\n if (config.includeSpecialChars) {\n charPool += specialChars;\n requiredChars.push(specialChars[Math.floor(Math.random() * specialChars.length)]);\n }\n }\n\n if (!charPool) {\n throw new Error('No character types selected for password generation');\n }\n\n // Generate password\n let password = '';\n\n // Add required characters first to ensure variety\n for (const char of requiredChars) {\n password += char;\n }\n\n // Fill remaining length with random characters\n for (let i = password.length; i < config.length; i++) {\n password += charPool[Math.floor(Math.random() * charPool.length)];\n }\n\n // Shuffle the password to avoid predictable patterns\n return password.split('').sort(() => Math.random() - 0.5).join('');\n }\n\n /**\n * Parse query string into object\n * @param {string} queryString - Query string to parse\n * @returns {object} Parsed query parameters\n */\n static parseQueryString(queryString) {\n const params = {};\n const searchParams = new URLSearchParams(queryString);\n for (const [key, value] of searchParams.entries()) {\n params[key] = value;\n }\n return params;\n }\n\n /**\n * Convert object to query string\n * @param {object} params - Parameters object\n * @returns {string} Query string\n */\n static toQueryString(params) {\n return new URLSearchParams(params).toString();\n }\n\n /**\n * Wrap data objects to provide get() method support\n * This ensures pipe formatting works in all contexts\n * @param {*} data - Data to wrap\n * @param {object} rootContext - Optional root context for nested access\n * @returns {*} Wrapped data if object/array, otherwise original\n */\n static wrapData(data, rootContext = null, depth = 3) {\n if (!data || typeof data !== 'object') {\n return data;\n }\n\n // Don't wrap built-in types (Date, RegExp, etc.)\n if (data instanceof Date || data instanceof RegExp || data instanceof Error) {\n return data;\n }\n\n // Stop wrapping at max depth to prevent infinite recursion\n if (depth <= 0) {\n return data;\n }\n\n // Don't wrap if already has get method\n if (typeof data.getContextValue === 'function') {\n return data;\n }\n\n // Handle arrays specially - wrap each element but keep as array\n if (Array.isArray(data)) {\n return data.map(item => {\n if (item && typeof item === 'object' && !item.getContextValue) {\n return new DataWrapper(item, rootContext);\n }\n return item;\n });\n }\n\n // Use DataWrapper for objects\n return new DataWrapper(data, rootContext);\n }\n}\n\n/**\n * DataWrapper - Wraps plain objects to provide get() method with pipe support\n * Used internally by View to ensure all data objects support formatting\n */\nclass DataWrapper {\n constructor(data, rootContext = null) {\n // Store the wrapped data as non-enumerable to avoid JSDOM serialization issues\n Object.defineProperty(this, '_data', {\n value: data,\n writable: false,\n enumerable: false,\n configurable: false\n });\n\n Object.defineProperty(this, '_rootContext', {\n value: rootContext,\n writable: false,\n enumerable: false,\n configurable: false\n });\n\n // Copy all properties from data to this wrapper\n // This allows direct property access\n if (data && typeof data === 'object') {\n for (const key in data) {\n if (data.hasOwnProperty(key)) {\n const value = data[key];\n // Wrap nested values using wrapData for consistency\n this[key] = MOJOUtils.wrapData(value, rootContext);\n }\n }\n }\n }\n\n /**\n * Get value with pipe support\n * @param {string} key - Key with optional pipes\n * @returns {*} Value, possibly formatted\n */\n getContextValue(key) {\n // Check if key has pipes\n let field = key;\n let pipes = '';\n\n // Find the first pipe that's not inside parentheses\n let parenDepth = 0;\n let pipeIndex = -1;\n\n for (let i = 0; i < key.length; i++) {\n const char = key[i];\n if (char === '(') parenDepth++;\n else if (char === ')') parenDepth--;\n else if (char === '|' && parenDepth === 0) {\n pipeIndex = i;\n break;\n }\n }\n\n if (pipeIndex > -1) {\n field = key.substring(0, pipeIndex).trim();\n pipes = key.substring(pipeIndex + 1).trim();\n }\n\n // Get value - supports both direct properties and dot-notation paths\n let value;\n \n // First check if it's a direct property on the wrapper (already wrapped)\n if (field in this && field !== '_data' && field !== '_rootContext') {\n value = this[field];\n } else {\n // Try to get nested value using dot-notation path\n value = MOJOUtils.getNestedValue(this._data, field);\n }\n\n // Apply pipes if present, passing root context for variable resolution\n if (pipes && value !== undefined) {\n return dataFormatter.pipe(value, pipes, this._rootContext || this._data);\n }\n\n return value;\n }\n\n /**\n * Check if wrapper has a property\n * @param {string} key - Property key\n * @returns {boolean} True if property exists\n */\n has(key) {\n return this._data && this._data.hasOwnProperty(key);\n }\n\n /**\n * Get the raw wrapped data\n * @returns {object} The original data object\n */\n toJSON() {\n return this._data;\n }\n}\n\n// Attach DataWrapper to MOJOUtils for easy access\nMOJOUtils.DataWrapper = DataWrapper;\n\n// Export as both class and singleton for flexibility\nexport default MOJOUtils;\nexport { MOJOUtils, DataWrapper };\n\n// Also attach to window for global access if needed\nif (typeof window !== 'undefined') {\n // window.MOJO = window.MOJO || {};\n // window.MOJO.Utils = MOJOUtils;\n // window.MOJO.DataWrapper = DataWrapper;\n window.utils = MOJOUtils;\n}\n","/**\n * EventEmitter - Lightweight event system for instance-level events.\n *\n * Provides a simple, consistent event API for Models, Collections, Views, and Pages.\n * Events are scoped to individual instances - use the global EventBus for cross-component communication.\n *\n * Usage as a mixin:\n * Object.assign(MyClass.prototype, EventEmitter);\n *\n * API:\n * on(event, callback, context) - Add event listener with optional context\n * off(event, callback, context) - Remove event listener\n * once(event, callback, context) - Add one-time event listener with optional context\n * emit(event, ...args) - Emit event to all listeners\n *\n * @example\n * // Clean context binding\n * model.on('change', this.handleChange, this);\n * model.off('change', this.handleChange, this); // Easy cleanup!\n * \n * // Traditional usage still works\n * model.on('change', (data) => console.log(data));\n */\n\nconst EventEmitter = {\n /**\n * Add an event listener\n * @param {string} event - Event name to listen for\n * @param {Function} callback - Function to call when event is emitted\n * @param {Object} [context] - Context to bind the callback to (optional)\n * @returns {Object} This instance for method chaining\n *\n * @example\n * // With context binding\n * model.on('change', this.handleChange, this);\n * \n * // Without context (traditional)\n * model.on('change', (data) => console.log(data));\n */\n on(event, callback, context) {\n if (!this._listeners) this._listeners = {};\n if (!this._listeners[event]) this._listeners[event] = [];\n \n const listener = {\n callback,\n context,\n fn: context ? callback.bind(context) : callback\n };\n \n this._listeners[event].push(listener);\n return this;\n },\n\n /**\n * Remove an event listener\n * @param {string} event - Event name\n * @param {Function} [callback] - Specific callback to remove. If omitted, removes all listeners for the event\n * @param {Object} [context] - Context that was used when adding the listener\n * @returns {Object} This instance for method chaining\n *\n * @example\n * // Remove specific listener with context\n * model.off('change', this.handleChange, this);\n *\n * // Remove specific callback (any context)\n * model.off('change', myHandler);\n *\n * // Remove all listeners for an event\n * model.off('change');\n */\n off(event, callback, context) {\n if (!this._listeners || !this._listeners[event]) return this;\n\n if (!callback) {\n // Remove all listeners for event\n delete this._listeners[event];\n } else {\n // Remove specific listener(s)\n this._listeners[event] = this._listeners[event].filter(listener => {\n // Match callback\n if (listener.callback !== callback) return true;\n \n // If context specified, must match context too\n if (arguments.length === 3 && listener.context !== context) return true;\n \n // This listener should be removed\n return false;\n });\n \n if (this._listeners[event].length === 0) {\n delete this._listeners[event];\n }\n }\n return this;\n },\n\n /**\n * Add a one-time event listener that automatically removes itself after being called\n * @param {string} event - Event name to listen for\n * @param {Function} callback - Function to call when event is emitted (called only once)\n * @param {Object} [context] - Context to bind the callback to (optional)\n * @returns {Object} This instance for method chaining\n *\n * @example\n * model.once('ready', this.handleReady, this);\n */\n once(event, callback, context) {\n const onceWrapper = (...args) => {\n this.off(event, onceWrapper);\n const fn = context ? callback.bind(context) : callback;\n fn.apply(context || this, args);\n };\n \n this.on(event, onceWrapper);\n return this;\n },\n\n /**\n * Emit an event to all registered listeners\n * @param {string} event - Event name to emit\n * @param {...*} args - Arguments to pass to event listeners\n * @returns {Object} This instance for method chaining\n *\n * @example\n * // Emit with single argument\n * model.emit('change', { field: 'name', value: 'John' });\n *\n * // Emit with multiple arguments\n * model.emit('update', oldValue, newValue, timestamp);\n *\n * // Emit without arguments\n * model.emit('ready');\n */\n emit(event, ...args) {\n if (!this._listeners || !this._listeners[event]) return this;\n \n // Copy the listeners in case one removes itself during emit\n const listeners = this._listeners[event].slice();\n \n for (const listener of listeners) {\n try {\n listener.fn.apply(listener.context || this, args);\n } catch (error) {\n // Don't allow one bad handler to block other listeners\n if (console && console.error) {\n console.error(`Error in ${event} event handler:`, error);\n }\n }\n }\n return this;\n }\n};\n\nexport default EventEmitter;","/**\n * Rest - HTTP client for API communication\n * Provides methods for making REST API calls with interceptors and error handling\n */\n\nclass Rest {\n constructor() {\n this.config = {\n baseURL: '',\n timeout: 30000,\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json'\n },\n trackDevice: true, // New setting to control DUID tracking\n duidHeader: 'X-Mojo-UID', // Header name for the DUID\n duidTransport: 'header' // How to send the DUID: 'payload' or 'header'\n };\n\n this.interceptors = {\n request: [],\n response: []\n };\n\n this.duid = null;\n if (this.config.trackDevice) {\n this._initializeDuid();\n }\n }\n\n /**\n * Initialize or generate the Device Unique ID (DUID)\n * @private\n */\n _initializeDuid() {\n const storageKey = 'mojo_device_uid';\n try {\n let storedDuid = localStorage.getItem(storageKey);\n if (storedDuid) {\n this.duid = storedDuid;\n } else {\n this.duid = this._generateDuid();\n localStorage.setItem(storageKey, this.duid);\n }\n } catch (e) {\n console.error(\"Could not access localStorage to get/set DUID.\", e);\n // Use a non-persistent DUID as a fallback\n this.duid = this._generateDuid();\n }\n }\n\n /**\n * Generate a new DUID (UUID v4)\n * @private\n * @returns {string} A new UUID\n */\n _generateDuid() {\n if (crypto && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n }\n\n /**\n * Configure the REST client\n * @param {object} config - Configuration object\n */\n configure(config) {\n if (config.baseUrl) config.baseURL = config.baseUrl;\n const oldTrackDevice = this.config.trackDevice;\n\n this.config = {\n ...this.config,\n ...config,\n headers: {\n ...this.config.headers,\n ...config.headers\n }\n };\n\n // Initialize DUID if tracking is newly enabled\n if (this.config.trackDevice && !oldTrackDevice) {\n this._initializeDuid();\n }\n }\n\n /**\n * Add request or response interceptor\n * @param {string} type - 'request' or 'response'\n * @param {function} interceptor - Interceptor function\n */\n addInterceptor(type, interceptor) {\n if (this.interceptors[type]) {\n this.interceptors[type].push(interceptor);\n }\n }\n\n /**\n * Build complete URL\n * @param {string} url - Endpoint URL\n * @returns {string} Complete URL\n */\n buildUrl(url) {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n return url;\n }\n\n\n const baseURL = this.config.baseURL.endsWith('/')\n ? this.config.baseURL.slice(0, -1)\n : this.config.baseURL;\n\n const endpoint = url.startsWith('/') ? url : `/${url}`;\n\n return `${baseURL}${endpoint}`;\n }\n\n /**\n * Categorize error into common reason codes\n * @param {Error} error - The error object\n * @param {number} status - HTTP status code (if available)\n * @returns {object} Object with reason code and user-friendly message\n */\n categorizeError(error, status = 0) {\n // Network/connection errors\n if (error.name === 'TypeError' && error.message.includes('fetch')) {\n return {\n reason: 'not_reachable',\n message: 'Service is not reachable - please check your connection'\n };\n }\n\n if (error.name === 'AbortError') {\n return {\n reason: 'cancelled',\n message: 'Request was cancelled'\n };\n }\n\n if (error.name === 'TimeoutError' || error.message.includes('timeout')) {\n return {\n reason: 'timed_out',\n message: 'Request timed out - please try again'\n };\n }\n\n // HTTP status-based categorization\n if (status >= 400) {\n if (status === 400) {\n return {\n reason: 'bad_request',\n message: 'Invalid request data'\n };\n }\n if (status === 401) {\n return {\n reason: 'unauthorized',\n message: 'Authentication required'\n };\n }\n if (status === 403) {\n return {\n reason: 'forbidden',\n message: 'Access denied'\n };\n }\n if (status === 404) {\n return {\n reason: 'not_found',\n message: 'Resource not found'\n };\n }\n if (status === 409) {\n return {\n reason: 'conflict',\n message: 'Resource conflict'\n };\n }\n if (status === 422) {\n return {\n reason: 'validation_error',\n message: 'Validation failed'\n };\n }\n if (status === 429) {\n return {\n reason: 'rate_limited',\n message: 'Too many requests - please wait'\n };\n }\n if (status >= 500) {\n return {\n reason: 'server_error',\n message: 'Server error - please try again later'\n };\n }\n if (status >= 400) {\n return {\n reason: 'client_error',\n message: 'Request error'\n };\n }\n }\n\n // Generic network errors\n if (error.message.includes('CORS')) {\n return {\n reason: 'cors_error',\n message: 'Cross-origin request blocked'\n };\n }\n\n if (error.message.includes('DNS') || error.message.includes('ENOTFOUND')) {\n return {\n reason: 'dns_error',\n message: 'Unable to resolve server address'\n };\n }\n\n // Default fallback\n return {\n reason: 'unknown_error',\n message: `Network error: ${error.message}`\n };\n }\n\n /**\n * Build query string from parameters\n * @param {object} params - Query parameters\n * @returns {string} Query string\n */\n buildQueryString(params = {}) {\n const searchParams = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value !== null && value !== undefined) {\n if (Array.isArray(value)) {\n value.forEach(v => searchParams.append(`${key}[]`, v));\n } else {\n searchParams.append(key, value);\n }\n }\n });\n\n const queryString = searchParams.toString();\n return queryString ? `?${queryString}` : '';\n }\n\n /**\n * Process request through interceptors\n * @param {object} request - Request configuration\n * @returns {object} Processed request configuration\n */\n async processRequestInterceptors(request) {\n let processedRequest = { ...request };\n\n for (const interceptor of this.interceptors.request) {\n try {\n processedRequest = await interceptor(processedRequest);\n } catch (error) {\n console.error('Request interceptor error:', error);\n throw error;\n }\n }\n\n return processedRequest;\n }\n\n /**\n * Process response through interceptors\n * @param {Response} response - Fetch response object\n * @param {object} request - Original request configuration\n * @returns {object} Processed response data\n */\n async processResponseInterceptors(response, request) {\n let responseData = {\n success: response.ok,\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n data: null,\n errors: null,\n message: null,\n reason: null\n };\n\n // Parse response body\n try {\n const contentType = response.headers.get('content-type');\n\n if (contentType && contentType.includes('application/json')) {\n const jsonData = await response.json();\n responseData.data = jsonData;\n\n // Handle API error responses\n if (!response.ok) {\n const errorInfo = this.categorizeError(new Error('HTTP Error'), response.status);\n responseData.errors = jsonData.errors || {};\n responseData.message = jsonData.message || errorInfo.message;\n responseData.reason = errorInfo.reason;\n }\n } else {\n responseData.data = await response.text();\n\n if (!response.ok) {\n const errorInfo = this.categorizeError(new Error('HTTP Error'), response.status);\n responseData.message = errorInfo.message;\n responseData.reason = errorInfo.reason;\n }\n }\n } catch (error) {\n responseData.errors = { parse: 'Failed to parse response' };\n responseData.message = 'Invalid response format';\n }\n\n // Process through response interceptors\n for (const interceptor of this.interceptors.response) {\n try {\n responseData = await interceptor(responseData, request);\n } catch (error) {\n console.error('Response interceptor error:', error);\n }\n }\n\n return responseData;\n }\n\n /**\n * Make HTTP request\n * @param {string} method - HTTP method\n * @param {string} url - Request URL\n * @param {object} data - Request body data\n * @param {object} params - Query parameters\n * @param {object} options - Additional request options\n * @returns {Promise} Promise that resolves with response data\n */\n async request(method, url, data = null, params = {}, options = {}) {\n // Build request configuration\n let request = {\n method: method.toUpperCase(),\n url: this.buildUrl(url) + this.buildQueryString(params),\n headers: {\n ...this.config.headers,\n ...options.headers\n },\n data,\n options: {\n timeout: this.config.timeout,\n ...options\n }\n };\n\n // Process request interceptors. If an interceptor throws\n // AuthRequiredError (auth gate refused to refresh), short-circuit to a\n // 401 response without hitting the network.\n try {\n request = await this.processRequestInterceptors(request);\n } catch (error) {\n if (error.name === 'AuthRequiredError') {\n return {\n success: false,\n status: 401,\n statusText: 'Unauthorized',\n headers: {},\n data: null,\n errors: { auth: error.message },\n message: 'Authentication required',\n reason: 'unauthorized'\n };\n }\n throw error;\n }\n\n // Add DUID if tracking is enabled\n if (this.config.trackDevice && this.duid) {\n if (this.config.duidTransport === 'header') {\n // Always add as a header\n request.headers[this.config.duidHeader] = this.duid;\n } else { // 'payload' transport (default)\n if (request.method === 'GET') {\n // For GET requests, add as a query parameter\n const url = new URL(request.url);\n url.searchParams.append('duid', this.duid);\n request.url = url.toString();\n } else if (request.data && typeof request.data === 'object' && !(request.data instanceof FormData)) {\n // For POST/PUT/PATCH with JSON body, add to the data payload\n request.data.duid = this.duid;\n }\n // Note: For other request types like FormData, the duid is not sent in 'payload' mode.\n }\n }\n\n // Prepare fetch options\n const fetchOptions = {\n method: request.method,\n headers: request.headers\n };\n\n // Handle abort signals - combine timeout and external signal if provided\n const signals = [];\n\n // Add timeout signal\n if (request.options.timeout) {\n signals.push(AbortSignal.timeout(request.options.timeout));\n }\n\n // Add external signal if provided\n if (request.options.signal) {\n signals.push(request.options.signal);\n }\n\n // Combine signals or use single signal\n if (signals.length > 1) {\n fetchOptions.signal = AbortSignal.any ? AbortSignal.any(signals) : signals[0];\n } else if (signals.length === 1) {\n fetchOptions.signal = signals[0];\n }\n\n // Add body for methods that support it\n if (request.data && ['POST', 'PUT', 'PATCH'].includes(request.method)) {\n if (request.data instanceof FormData) {\n fetchOptions.body = request.data;\n // Remove Content-Type header for FormData (browser sets it with boundary)\n delete fetchOptions.headers['Content-Type'];\n } else if (typeof request.data === 'object') {\n fetchOptions.body = JSON.stringify(request.data);\n } else {\n fetchOptions.body = request.data;\n }\n }\n\n try {\n // Make the request\n const response = await fetch(request.url, fetchOptions);\n\n // Process response through interceptors\n const responseData = await this.processResponseInterceptors(response, request);\n\n // Unwrap server envelope: { status, data, message } → data only\n if (options.dataOnly && responseData.data && typeof responseData.data === 'object' && 'data' in responseData.data) {\n responseData.message = responseData.message || responseData.data.message;\n responseData.data = responseData.data.data;\n }\n\n return responseData;\n\n } catch (error) {\n // Handle AbortError (cancellation) - re-throw to be handled by caller\n if (error.name === 'AbortError') {\n throw error;\n }\n\n // Categorize the error\n const errorInfo = this.categorizeError(error);\n\n // Handle network and timeout errors\n const errorResponse = {\n success: false,\n status: 0,\n statusText: 'Network Error',\n headers: {},\n data: null,\n errors: { network: error.message },\n message: errorInfo.message,\n reason: errorInfo.reason\n };\n\n // Create mock response for interceptor processing\n const mockResponse = {\n ok: false,\n status: 0,\n statusText: 'Network Error',\n headers: new Headers(),\n json: async () => ({}),\n text: async () => ''\n };\n\n // Process through interceptors and return the categorized error\n await this.processResponseInterceptors(mockResponse, request);\n return errorResponse;\n }\n }\n\n /**\n * GET request\n * @param {string} url - Request URL\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async GET(url, params = {}, options = {}) {\n return this.request('GET', url, null, params, options);\n }\n\n /**\n * POST request\n * @param {string} url - Request URL\n * @param {object} data - Request body data\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async POST(url, data = {}, params = {}, options = {}) {\n return this.request('POST', url, data, params, options);\n }\n\n /**\n * PUT request\n * @param {string} url - Request URL\n * @param {object} data - Request body data\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async PUT(url, data = {}, params = {}, options = {}) {\n return this.request('PUT', url, data, params, options);\n }\n\n /**\n * PATCH request\n * @param {string} url - Request URL\n * @param {object} data - Request body data\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async PATCH(url, data = {}, params = {}, options = {}) {\n return this.request('PATCH', url, data, params, options);\n }\n\n /**\n * DELETE request\n * @param {string} url - Request URL\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async DELETE(url, params = {}, options = {}) {\n return this.request('DELETE', url, null, params, options);\n }\n\n // Lowercase aliases\n get(...args) { return this.GET(...args); }\n post(...args) { return this.POST(...args); }\n put(...args) { return this.PUT(...args); }\n patch(...args) { return this.PATCH(...args); }\n delete(...args) { return this.DELETE(...args); }\n\n /**\n * Download a file from a URL\n * @param {string} url - Request URL\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves when download is initiated\n */\n async download(url, params = {}, options = {}) {\n const requestUrl = this.buildUrl(url) + this.buildQueryString(params);\n const request = {\n method: 'GET',\n url: requestUrl,\n headers: {\n ...this.config.headers,\n 'Accept': '*/*', // Default, can be overridden by options\n ...options.headers\n },\n options: {\n ...options\n }\n };\n // Remove content-type for GET request\n delete request.headers['Content-Type'];\n\n try {\n const response = await fetch(request.url, {\n method: request.method,\n headers: request.headers,\n signal: request.options.signal\n });\n\n if (!response.ok) {\n throw new Error(`Download failed: ${response.status} ${response.statusText}`);\n }\n\n const contentDisposition = response.headers.get('content-disposition');\n let filename = options.filename || 'download';\n\n if (contentDisposition) {\n const filenameMatch = contentDisposition.match(/filename=\"?(.+)\"?/);\n if (filenameMatch && filenameMatch.length > 1) {\n filename = filenameMatch[1];\n }\n }\n\n // Create download without loading entire file into memory\n const reader = response.body.getReader();\n const stream = new ReadableStream({\n start(controller) {\n function pump() {\n return reader.read().then(({ done, value }) => {\n if (done) {\n controller.close();\n return;\n }\n controller.enqueue(value);\n return pump();\n });\n }\n return pump();\n }\n });\n\n const blob = await new Response(stream).blob();\n const downloadUrl = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.style.display = 'none';\n a.href = downloadUrl;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n window.URL.revokeObjectURL(downloadUrl);\n a.remove();\n\n return { success: true, message: 'Download initiated' };\n\n } catch (error) {\n console.error('Download error:', error);\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Download a file from a URL by fetching the entire content into a Blob.\n * @param {string} url - Request URL\n * @param {object} params - Query parameters\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves when download is initiated\n */\n async downloadBlob(url, params = {}, options = {}) {\n const requestUrl = this.buildUrl(url) + this.buildQueryString(params);\n const request = {\n method: 'GET',\n url: requestUrl,\n headers: {\n ...this.config.headers,\n 'Accept': '*/*', // Default, can be overridden by options\n ...options.headers\n },\n options: {\n ...options\n }\n };\n // Remove content-type for GET request\n delete request.headers['Content-Type'];\n\n try {\n const response = await fetch(request.url, {\n method: request.method,\n headers: request.headers,\n signal: request.options.signal\n });\n\n if (!response.ok) {\n throw new Error(`Download failed: ${response.status} ${response.statusText}`);\n }\n\n const blob = await response.blob();\n const contentDisposition = response.headers.get('content-disposition');\n let filename = options.filename || 'download';\n\n if (contentDisposition) {\n const filenameMatch = contentDisposition.match(/filename=\"?(.+)\"?/);\n if (filenameMatch && filenameMatch.length > 1) {\n filename = filenameMatch[1];\n }\n }\n\n const downloadUrl = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.style.display = 'none';\n a.href = downloadUrl;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n window.URL.revokeObjectURL(downloadUrl);\n a.remove();\n\n return { success: true, message: 'Download initiated' };\n\n } catch (error) {\n console.error('Download error:', error);\n return { success: false, message: error.message };\n }\n }\n\n /**\n * Upload file with raw PUT request (compatible with legacy backend)\n * @param {string} url - Upload URL\n * @param {File} file - Single file to upload\n * @param {object} options - Request options\n * @param {function} options.onProgress - Progress callback function(event)\n * @returns {Promise} Promise that resolves with response data\n */\n async upload(url, file, options = {}) {\n return new Promise((resolve, reject) => {\n // Validate input - only accept single File objects\n if (!(file instanceof File)) {\n reject(new Error('Only single File objects are supported for legacy backend compatibility'));\n return;\n }\n\n const xhr = new XMLHttpRequest();\n\n // Set up progress tracking if callback provided\n if (options.onProgress && typeof options.onProgress === 'function') {\n xhr.upload.onprogress = options.onProgress;\n }\n\n // Set up response handlers\n xhr.onload = function() {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({\n data: xhr.response,\n status: xhr.status,\n statusText: xhr.statusText,\n xhr: xhr\n });\n } else {\n reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));\n }\n };\n\n xhr.onerror = function() {\n reject(new Error('Upload failed: Network error'));\n };\n\n xhr.ontimeout = function() {\n reject(new Error('Upload failed: Timeout'));\n };\n\n // Configure request - use PUT method with raw file data\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', file.type);\n\n // Set timeout if specified\n if (options.timeout) {\n xhr.timeout = options.timeout;\n }\n\n // Send the raw file data\n xhr.send(file);\n });\n }\n\n /**\n * Upload multiple files with multipart/form-data (for modern backends)\n * @param {string} url - Upload URL\n * @param {File|FileList|FormData} files - File(s) to upload\n * @param {object} additionalData - Additional form fields\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with response data\n */\n async uploadMultipart(url, files, additionalData = {}, options = {}) {\n const formData = new FormData();\n\n // Add files to form data\n if (files instanceof FileList) {\n Array.from(files).forEach((file, index) => {\n formData.append(`file_${index}`, file);\n });\n } else if (files instanceof File) {\n formData.append('file', files);\n } else if (files instanceof FormData) {\n // Use provided FormData directly\n return this.POST(url, files, {}, options);\n }\n\n // Add additional data\n Object.entries(additionalData).forEach(([key, value]) => {\n formData.append(key, value);\n });\n\n return this.POST(url, formData, {}, options);\n }\n\n /**\n * Set authentication token\n * @param {string} token - JWT or API token\n * @param {string} type - Token type ('Bearer', 'Token', etc.)\n */\n setAuthToken(token, type = 'Bearer') {\n if (token) {\n this.config.headers['Authorization'] = `${type} ${token}`;\n } else {\n delete this.config.headers['Authorization'];\n }\n }\n\n /**\n * Clear authentication\n */\n clearAuth() {\n delete this.config.headers['Authorization'];\n }\n\n /**\n * Check if an error is retryable (network issues that might resolve)\n * @param {object} response - Response object with reason field\n * @returns {boolean} True if error can be retried\n */\n isRetryableError(response) {\n const retryableReasons = [\n 'not_reachable',\n 'timed_out',\n 'server_error',\n 'dns_error'\n ];\n return retryableReasons.includes(response.reason);\n }\n\n /**\n * Check if error requires authentication\n * @param {object} response - Response object with reason field\n * @returns {boolean} True if authentication is required\n */\n requiresAuth(response) {\n return response.reason === 'unauthorized';\n }\n\n /**\n * Check if error is network-related\n * @param {object} response - Response object with reason field\n * @returns {boolean} True if it's a network error\n */\n isNetworkError(response) {\n const networkReasons = [\n 'not_reachable',\n 'timed_out',\n 'cancelled',\n 'cors_error',\n 'dns_error'\n ];\n return networkReasons.includes(response.reason);\n }\n\n /**\n * Get user-friendly error message based on reason\n * @param {object} response - Response object with reason field\n * @returns {string} User-friendly error message\n */\n getUserMessage(response) {\n if (response.message) {\n return response.message;\n }\n\n const messages = {\n 'not_reachable': 'Unable to connect to the server. Please check your internet connection.',\n 'timed_out': 'The request took too long. Please try again.',\n 'cancelled': 'The request was cancelled.',\n 'unauthorized': 'Please log in to continue.',\n 'forbidden': 'You don\\'t have permission to perform this action.',\n 'not_found': 'The requested resource was not found.',\n 'validation_error': 'Please check your input and try again.',\n 'rate_limited': 'Too many requests. Please wait a moment before trying again.',\n 'server_error': 'Server error. Please try again later.',\n 'cors_error': 'Access blocked by security policy.',\n 'dns_error': 'Unable to reach the server.',\n 'unknown_error': 'An unexpected error occurred.'\n };\n\n return messages[response.reason] || 'An error occurred. Please try again.';\n }\n}\n\n// Create singleton instance\nconst rest = new Rest();\n\nexport { Rest };\nexport default rest;\n","/**\n * Model - Base class for models with REST API support\n * Provides CRUD operations for API resources with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits 'change' events when data is modified via set()\n * Emits 'change:attributeName' for specific attribute changes\n *\n * Standard Events:\n * - 'change' - Emitted when any model data changes\n * - 'change:fieldName' - Emitted when specific field changes\n *\n * @example\n * const user = new User({ name: 'John', email: 'john@example.com' });\n *\n * // Listen for any changes\n * user.on('change', (model) => {\n * console.log('User model changed');\n * view.render();\n * });\n *\n * // Listen for specific field changes\n * user.on('change:name', (newName, model) => {\n * console.log('Name changed to:', newName);\n * });\n *\n * // Trigger events by changing data\n * user.set('name', 'Jane'); // Emits 'change' and 'change:name'\n * user.set({ name: 'Bob', email: 'bob@example.com' }); // Emits 'change' and individual field events\n */\n\nimport MOJOUtils from '@core/utils/MOJOUtils.js';\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport rest from '@core/Rest.js';\n\nclass Model {\n constructor(data = {}, options = {}) {\n this.endpoint = options.endpoint || this.constructor.endpoint || '';\n this.id = data.id || null;\n this.attributes = { ...data };\n this._ = this.attributes;\n this.originalAttributes = { ...data };\n this.errors = {};\n this.loading = false;\n this.rest = rest;\n\n // Event system via EventEmitter mixin (applied to prototype)\n\n // Configuration options\n this.options = {\n idAttribute: 'id',\n timestamps: true,\n ...options\n };\n }\n\n getContextValue(key) {\n return this.get(key);\n }\n\n /**\n * Get attribute value with support for dot notation and pipe formatting\n * @param {string} key - Attribute key with optional pipes (e.g., \"name|uppercase\")\n * @returns {*} Attribute value, possibly formatted\n */\n get(key) {\n // Check if key exists as an instance field first (for 'id', 'endpoint', etc.)\n if (!key.includes('.') && !key.includes('|') && this[key] !== undefined) {\n // If it's a function, call it and return the result\n if (typeof this[key] === 'function') {\n return this[key]();\n }\n return this[key];\n }\n\n // Use MOJOUtils for all attribute access with pipes and dot notation\n return MOJOUtils.getContextData(this.attributes, key);\n }\n\n /**\n * Set attribute value(s)\n * @param {string|object} key - Attribute key or object of key-value pairs\n * @param {*} value - Attribute value (if key is string)\n * @param {object} options - Options (silent: true to not trigger change event)\n */\n set(key, value, options = {}) {\n const previousAttributes = JSON.parse(JSON.stringify(this.attributes)); // Deep copy\n let hasChanged = false;\n if (key === undefined || key === null) return;\n\n if (typeof key === 'object') {\n // Set multiple attributes\n for (const [attrKey, attrValue] of Object.entries(key)) {\n hasChanged = this._setNestedAttribute(attrKey, attrValue) || hasChanged;\n }\n if (key.id !== undefined) {\n this.id = key.id;\n }\n } else {\n // Set single attribute\n if (key === 'id') {\n this.id = value;\n hasChanged = true;\n } else {\n hasChanged = this._setNestedAttribute(key, value);\n }\n }\n\n // Trigger change event if data changed and not silent\n if (hasChanged && !options.silent) {\n this.emit('change', this);\n\n // Trigger specific attribute change events\n if (typeof key === 'string') {\n this.emit(`change:${key}`, value, this);\n } else {\n for (const [attr, val] of Object.entries(key)) {\n // Get the final value that was actually set (after nested expansion)\n const finalValue = this._getNestedValue(attr);\n if (JSON.stringify(this._getNestedValue(attr, previousAttributes)) !== JSON.stringify(finalValue)) {\n this.emit(`change:${attr}`, finalValue, this);\n }\n }\n }\n }\n }\n\n /**\n * Set a nested attribute using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {*} value - Value to set\n * @returns {boolean} - Whether the value changed\n */\n _setNestedAttribute(key, value) {\n if (!key.includes('.')) {\n // Simple attribute\n const oldValue = this.attributes[key];\n this.attributes[key] = value;\n this[key] = value;\n return oldValue !== value;\n }\n\n // Nested attribute with dot notation\n const keys = key.split('.');\n const topLevelKey = keys[0];\n\n // Ensure the top-level object exists\n if (!this.attributes[topLevelKey] || typeof this.attributes[topLevelKey] !== 'object') {\n this.attributes[topLevelKey] = {};\n }\n if (!this[topLevelKey] || typeof this[topLevelKey] !== 'object') {\n this[topLevelKey] = {};\n }\n\n // Get the old value for comparison\n const oldValue = this._getNestedValue(key);\n\n // Navigate to the nested location and set the value\n let attrTarget = this.attributes[topLevelKey];\n let instanceTarget = this[topLevelKey];\n\n for (let i = 1; i < keys.length - 1; i++) {\n const currentKey = keys[i];\n\n if (!attrTarget[currentKey] || typeof attrTarget[currentKey] !== 'object') {\n attrTarget[currentKey] = {};\n }\n if (!instanceTarget[currentKey] || typeof instanceTarget[currentKey] !== 'object') {\n instanceTarget[currentKey] = {};\n }\n\n attrTarget = attrTarget[currentKey];\n instanceTarget = instanceTarget[currentKey];\n }\n\n // Set the final value\n const finalKey = keys[keys.length - 1];\n attrTarget[finalKey] = value;\n instanceTarget[finalKey] = value;\n\n return JSON.stringify(oldValue) !== JSON.stringify(value);\n }\n\n /**\n * Get a nested value using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {object} source - Source object (defaults to this.attributes)\n * @returns {*} - The nested value\n */\n _getNestedValue(key, source = this.attributes) {\n if (!key.includes('.')) {\n return source[key];\n }\n\n const keys = key.split('.');\n let current = source;\n\n for (const k of keys) {\n if (current == null || typeof current !== 'object') {\n return undefined;\n }\n current = current[k];\n }\n\n return current;\n }\n\n getData() {\n return this.attributes;\n }\n\n getId() {\n return this.id;\n }\n\n /**\n * Fetch model data from API with request deduplication and cancellation\n * @param {object} options - Request options\n * @param {number} options.debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(options = {}) {\n let url = options.url;\n if (!url) {\n const id = options.id || this.getId();\n if (!id && this.options.requiresId !== false) {\n throw new Error('Model: ID is required for fetching');\n }\n url = this.buildUrl(id);\n }\n const requestKey = JSON.stringify({url, params: options.params});\n\n // Handle debounced fetch\n if (options.debounceMs && options.debounceMs > 0) {\n return this._debouncedFetch(requestKey, options);\n }\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Model: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Model: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Model: Rate limited, skipping fetch');\n return this;\n }\n\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, options, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Model: Request was cancelled');\n return this;\n }\n throw error;\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Handle debounced fetch requests\n * @param {string} requestKey - Unique key for this request\n * @param {object} options - Fetch options\n * @returns {Promise} Promise that resolves with REST response\n */\n async _debouncedFetch(requestKey, options) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch({ ...options, debounceMs: 0 });\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, options.debounceMs);\n });\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} options - Request options\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, options, abortController) {\n try {\n if (options.graph && (!options.params || !options.params.graph)) {\n if (!options.params) options.params = {};\n options.params.graph = options.graph;\n }\n const response = await this.rest.GET(url, options.params, {\n signal: abortController.signal\n });\n\n if (response.success) {\n if (response.data.status) {\n this.originalAttributes = { ...this.attributes };\n if (response.data.data) this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Model: Fetch was cancelled');\n throw error;\n }\n\n this.errors = { fetch: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Save model to API (create or update)\n * @param {object} data - Data to save to the model\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async save(data, options = {}) {\n const isNew = !this.id;\n const method = isNew ? 'POST' : 'PUT';\n const url = isNew ? this.buildUrl() : this.buildUrl(this.id);\n\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest[method](url, data, options.params);\n\n if (response.success) {\n if (response.data.status) {\n // Update model on success\n this.originalAttributes = { ...this.attributes };\n this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response; // Always return the full response\n\n } catch (error) {\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n\n /**\n * Delete model from API\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async destroy(options = {}) {\n if (!this.id) {\n this.errors = { destroy: 'Cannot destroy model without ID' };\n return {\n success: false,\n error: 'Cannot destroy model without ID',\n status: 400\n };\n }\n\n const url = this.buildUrl(this.id);\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest.DELETE(url, options.params);\n\n if (response.success) {\n // Clear model data on success\n this.attributes = {};\n this.originalAttributes = {};\n this.id = null;\n this.errors = {};\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n\n } catch (error) {\n this.errors = { destroy: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Check if model has been modified\n * @returns {boolean} True if model has unsaved changes\n */\n isDirty() {\n return JSON.stringify(this.attributes) !== JSON.stringify(this.originalAttributes);\n }\n\n /**\n * Get attributes that have changed since last save\n * @returns {object} Object containing only changed attributes\n */\n getChangedAttributes() {\n const changed = {};\n\n for (const [key, value] of Object.entries(this.attributes)) {\n if (this.originalAttributes[key] !== value) {\n changed[key] = value;\n }\n }\n\n return changed;\n }\n\n /**\n * Reset model to original state\n */\n reset() {\n // _setNestedAttribute mirrors writes onto instance props (this[key]) for\n // fast access. Clear those so `reset()` actually restores original state\n // — otherwise `model.get(key)` (which reads this[key] first) returns the\n // dirty value even though `this.attributes` has been reverted.\n for (const key of Object.keys(this.attributes)) {\n if (!(key in this.originalAttributes)) delete this[key];\n }\n for (const [key, value] of Object.entries(this.originalAttributes)) {\n this[key] = value;\n }\n this.attributes = { ...this.originalAttributes };\n this._ = this.attributes;\n this.errors = {};\n }\n\n /**\n * Build URL for API requests\n * @param {string|number} id - Optional ID to append to URL\n * @returns {string} Complete API URL\n */\n buildUrl(id = null) {\n let url = this.endpoint;\n if (id) {\n url = url.endsWith('/') ? `${url}${id}` : `${url}/${id}`;\n }\n return url;\n }\n\n /**\n * Convert model to JSON\n * @returns {object} Model attributes as plain object\n */\n toJSON() {\n return {\n id: this.id,\n ...this.attributes\n };\n }\n\n /**\n * Validate model attributes\n * @returns {boolean} True if valid, false if validation errors exist\n */\n validate() {\n this.errors = {};\n\n // Override in subclasses for custom validation\n if (this.constructor.validations) {\n for (const [field, rules] of Object.entries(this.constructor.validations)) {\n this.validateField(field, rules);\n }\n }\n\n return Object.keys(this.errors).length === 0;\n }\n\n /**\n * Validate a single field\n * @param {string} field - Field name\n * @param {object|array} rules - Validation rules\n */\n validateField(field, rules) {\n const value = this.get(field);\n const rulesArray = Array.isArray(rules) ? rules : [rules];\n\n for (const rule of rulesArray) {\n if (typeof rule === 'function') {\n const result = rule(value, this);\n if (result !== true) {\n this.errors[field] = result || `${field} is invalid`;\n break;\n }\n } else if (typeof rule === 'object') {\n if (rule.required && (value === undefined || value === null || value === '')) {\n this.errors[field] = rule.message || `${field} is required`;\n break;\n }\n if (rule.minLength && value && value.length < rule.minLength) {\n this.errors[field] = rule.message || `${field} must be at least ${rule.minLength} characters`;\n break;\n }\n if (rule.maxLength && value && value.length > rule.maxLength) {\n this.errors[field] = rule.message || `${field} must be no more than ${rule.maxLength} characters`;\n break;\n }\n if (rule.pattern && value && !rule.pattern.test(value)) {\n this.errors[field] = rule.message || `${field} format is invalid`;\n break;\n }\n }\n }\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Static method to create and fetch a model by ID\n * @param {string|number} id - Model ID\n * @param {object} options - Options\n * @returns {Promise<RestModel>} Promise that resolves with fetched model\n */\n static async find(id, options = {}) {\n const model = new this({}, options);\n await model.fetch({ id, ...options });\n return model;\n }\n\n /**\n * Static method to create a new model with data\n * @param {object} data - Model data\n * @param {object} options - Options\n * @returns {RestModel} New model instance\n */\n static create(data = {}, options = {}) {\n return new this(data, options);\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Model: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n\n // Cancel debounced fetch if exists\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n this.debouncedFetchTimeout = null;\n return true;\n }\n\n return false;\n }\n\n /**\n * Check if model has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n async showError(message) {\n const Modal = (await import('@core/views/feedback/Modal.js')).default;\n await Modal.alert(message, 'Error', {\n size: 'md',\n class: 'text-danger'\n });\n }\n}\n\nObject.assign(Model.prototype, EventEmitter);\n\nexport default Model;\n","/**\n * Collection - Class for managing arrays of Model instances\n * Provides methods for fetching and managing collections of models with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits events when collection is modified\n *\n * Standard Events:\n * - 'add' - Emitted when models are added to the collection\n * - 'remove' - Emitted when models are removed from the collection\n * - 'update' - Emitted when collection is modified (after add/remove)\n * - 'reset' - Emitted when collection is reset (all models replaced)\n *\n * @example\n * const users = new UserCollection();\n *\n * // Listen for collection changes\n * users.on('add', ({ models, collection }) => {\n * console.log('Added', models.length, 'users');\n * updateUI();\n * });\n *\n * users.on('remove', ({ models, collection }) => {\n * console.log('Removed', models.length, 'users');\n * updateUI();\n * });\n *\n * // Add models - triggers 'add' and 'update' events\n * users.add([\n * new User({ name: 'John' }),\n * new User({ name: 'Jane' })\n * ]);\n *\n * Usage Examples:\n *\n * // Preloaded Data (no REST fetching)\n * const collection = new MyCollection({ preloaded: true });\n * collection.add(new MyModel({...}));\n * // collection.fetch() will be skipped if data already exists\n *\n * // REST Data (fetch from API)\n * const collection = new MyCollection({ preloaded: false }); // default\n * await collection.fetch(); // Will make API call\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\nclass Collection {\n constructor(options = {}, data = null) {\n // Handle case where first argument is data instead of ModelClass\n if (Array.isArray(options)) {\n // First argument is an array, treat it as data\n data = options;\n options = data || {};\n } else {\n data = data || options.data || [];\n }\n this.ModelClass = options.ModelClass || Model;\n this.models = [];\n this.loading = false;\n this.errors = {};\n this.meta = {};\n this.rest = rest;\n if (data) {\n this.add(data);\n }\n\n // Initialize params with defaults - single source of truth for query state\n this.params = {\n start: 0,\n size: options.size || 10,\n ...options.params\n };\n\n // Set up endpoint\n this.endpoint = options.endpoint || this.ModelClass.endpoint || '';\n if (!this.endpoint) {\n let tmp = new this.ModelClass();\n this.endpoint = tmp.endpoint;\n }\n\n // Automatic REST detection based on endpoint\n this.restEnabled = this.endpoint ? true : false;\n\n // Allow explicit override\n if (options.restEnabled !== undefined) {\n this.restEnabled = options.restEnabled;\n }\n\n // Configuration\n this.options = {\n parse: true,\n reset: true,\n preloaded: false,\n ...options\n };\n\n // Event system via EventEmitter mixin (applied to prototype)\n }\n\n getModelName() {\n return this.ModelClass.name;\n }\n\n /**\n * Fetch collection data from API\n * @param {object} additionalParams - Additional parameters to merge for this fetch only\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(additionalParams = {}) {\n const requestKey = JSON.stringify({ ...this.params, ...additionalParams });\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Collection: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Collection: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.options.rateLimiting && this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Collection: Rate limited, skipping fetch');\n return { success: true, message: 'Rate limited, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if not REST enabled\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, skipping fetch');\n return { success: true, message: 'REST disabled, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if preloaded is true and we already have data\n if (this.options.preloaded && this.models.length > 0) {\n console.info('Collection: Using preloaded data, skipping fetch');\n return { success: true, message: 'Using preloaded data, skipping fetch', data: { data: this.toJSON() } };\n }\n\n const url = this.buildUrl();\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, additionalParams, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Collection: Request was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} additionalParams - Additional parameters\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, additionalParams, abortController) {\n const fetchParams = { ...this.params, ...additionalParams };\n console.log('Fetching collection data from', url, fetchParams);\n try {\n this.emit(\"fetch:start\");\n const response = await this.rest.GET(url, fetchParams, {\n signal: abortController.signal\n });\n\n if (response.success && response.data.status) {\n const data = this.options.parse ? this.parse(response) : response.data;\n\n if (this.options.reset || additionalParams.reset !== false) {\n this.reset();\n }\n\n this.add(data, { silent: additionalParams.silent });\n this.errors = {};\n this.emit(\"fetch:success\");\n } else {\n if (response.data && response.data.error) {\n this.errors = response.data;\n this.emit(\"fetch:error\", { message: response.data.error, error: response.data });\n } else {\n this.errors = response.errors || {};\n this.emit(\"fetch:error\", { error: response.errors });\n }\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Collection: Fetch was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n\n this.errors = { fetch: error.message };\n this.emit(\"fetch:error\", { message: error.message, error: error });\n\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n this.emit(\"fetch:end\");\n }\n }\n\n /**\n * Update collection parameters and optionally fetch new data\n * @param {object} newParams - Parameters to update\n * @param {boolean} autoFetch - Whether to automatically fetch after updating params\n * @param {number} debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response if autoFetch=true, or collection if autoFetch=false\n */\n async updateParams(newParams, autoFetch = false, debounceMs = 0) {\n return await this.setParams({ ...this.params, ...newParams }, autoFetch, debounceMs);\n }\n\n async setParams(newParams, autoFetch = false, debounceMs = 0) {\n this.params = newParams;\n if (autoFetch && this.restEnabled) {\n if (debounceMs > 0) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch();\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, debounceMs);\n });\n } else {\n // For immediate fetches, the fetch method will handle cancellation\n return this.fetch();\n }\n }\n\n return Promise.resolve(this);\n }\n\n /**\n * Fetch a single model by ID\n * @param {string|number} id - Model ID to fetch\n * @param {object} options - Additional fetch options\n * @returns {Promise<Model|null>} Promise that resolves with model instance or null if not found\n */\n async fetchOne(id, options = {}) {\n if (!id) {\n console.warn('Collection: fetchOne requires an ID');\n return null;\n }\n\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, cannot fetch single item');\n return null;\n }\n\n try {\n // Create model instance with the ID and use its fetch method\n const model = new this.ModelClass({ id }, {\n endpoint: this.endpoint,\n collection: this\n });\n\n const response = await model.fetch(options);\n\n if (response.success) {\n // Optionally add to collection if not already present\n if (options.addToCollection === true) {\n const existingModel = this.get(model.id);\n if (!existingModel) {\n this.add(model, { silent: options.silent });\n } else if (options.merge !== false) {\n existingModel.set(model.attributes);\n }\n }\n\n return model;\n } else {\n console.warn('Collection: fetchOne failed -', response.error || 'Unknown error');\n return null;\n }\n } catch (error) {\n console.error('Collection: fetchOne error -', error.message);\n return null;\n }\n }\n\n /**\n * Download collection data in a specified format\n * @param {string} format - The format for the download (e.g., 'csv', 'json')\n * @param {object} options - Download options\n * @returns {Promise} Promise that resolves with the download result\n */\n async download(format = 'json', options = {}) {\n if (!this.restEnabled) {\n console.warn('Collection: REST is not enabled, cannot download from remote.');\n // Here we could implement local data export in the future.\n return { success: false, message: 'Remote downloads are not enabled for this collection.' };\n }\n\n const url = this.buildUrl();\n const downloadParams = { ...this.params };\n\n // Remove pagination params for full export\n delete downloadParams.start;\n delete downloadParams.size;\n\n // Add format param\n downloadParams.download_format = format;\n\n // Provide a default filename and content type\n const baseName = `export-${this.getModelName().toLowerCase()}`;\n const rangeSuffix = this._buildDateRangeSuffix(downloadParams);\n const filename = `${baseName}${rangeSuffix}.${format}`;\n const contentTypes = {\n json: 'application/json',\n csv: 'text/csv'\n };\n const acceptHeader = contentTypes[format] || '*/*';\n downloadParams.filename = filename;\n\n return this.rest.download(url, downloadParams, {\n ...options,\n filename,\n headers: { 'Accept': acceptHeader }\n });\n }\n\n _buildDateRangeSuffix(params = {}) {\n const hasStart = params.dr_start;\n const hasEnd = params.dr_end;\n\n if (!hasStart && !hasEnd) {\n return '';\n }\n\n const sanitize = (value) => {\n if (!value) return '';\n return String(value).replace(/[^\\dA-Za-z_-]/g, '-');\n };\n\n const parts = [];\n const field = params.dr_field || 'daterange';\n parts.push(sanitize(field));\n\n if (hasStart) {\n parts.push(`from-${sanitize(params.dr_start)}`);\n }\n if (hasEnd) {\n parts.push(`to-${sanitize(params.dr_end)}`);\n }\n\n return `-${parts.filter(Boolean).join('-')}`;\n }\n\n /**\n * Parse response data - override in subclasses for custom parsing\n * @param {object} response - API response\n * @returns {array} Array of model data objects\n */\n parse(response) {\n // Handle standard paginated responses with size/start/count\n if (response.data && Array.isArray(response.data.data)) {\n this.meta = {\n size: response.data.size || 10,\n start: response.data.start || 0,\n count: response.data.count || 0,\n status: response.data.status,\n graph: response.data.graph,\n ...response.meta\n };\n return response.data.data;\n }\n\n // Handle direct array responses\n if (Array.isArray(response.data)) {\n return response.data;\n }\n\n // Fallback - assume response itself is the data array\n return Array.isArray(response) ? response : [response];\n }\n\n /**\n * Add model(s) to the collection\n * @param {object|array} data - Model data or array of model data\n * @param {object} options - Options for adding models\n */\n add(data, options = {}) {\n const modelsData = Array.isArray(data) ? data : [data];\n const addedModels = [];\n\n for (const modelData of modelsData) {\n let model;\n\n if (modelData instanceof this.ModelClass) {\n model = modelData;\n } else {\n model = new this.ModelClass(modelData, {\n endpoint: this.endpoint,\n collection: this\n });\n }\n\n // Check for duplicates\n const existingIndex = this.models.findIndex(m => m.id === model.id);\n if (existingIndex !== -1) {\n if (options.merge !== false) {\n // Update existing model\n this.models[existingIndex].set(model.attributes);\n }\n } else {\n // Add new model\n this.models.push(model);\n addedModels.push(model);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && addedModels.length > 0) {\n this.emit('add', { models: addedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return addedModels;\n }\n\n /**\n * Remove model(s) from the collection\n * @param {Model|array|string|number} models - Model(s) to remove or ID(s)\n * @param {object} options - Options\n */\n remove(models, options = {}) {\n const modelsToRemove = Array.isArray(models) ? models : [models];\n const removedModels = [];\n\n for (const model of modelsToRemove) {\n let index = -1;\n\n if (typeof model === 'string' || typeof model === 'number') {\n // Remove by ID\n index = this.models.findIndex(m => m.id == model);\n } else {\n // Remove by model instance\n index = this.models.indexOf(model);\n }\n\n if (index !== -1) {\n const removedModel = this.models.splice(index, 1)[0];\n removedModels.push(removedModel);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && removedModels.length > 0) {\n this.emit('remove', { models: removedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return removedModels;\n }\n\n /**\n * Reset the collection (remove all models)\n * @param {array} models - Optional new models to set\n * @param {object} options - Options\n */\n reset(models = null, options = {}) {\n const previousModels = [...this.models];\n this.models = [];\n\n if (models) {\n this.add(models, { silent: true, ...options });\n }\n\n if (!options.silent) {\n this.emit('reset', {\n collection: this,\n previousModels\n });\n }\n\n return this;\n }\n\n /**\n * Get model by ID\n * @param {string|number} id - Model ID\n * @returns {Model|undefined} Model instance or undefined\n */\n get(id) {\n return this.models.find(model => model.id == id);\n }\n\n /**\n * Get model by index\n * @param {number} index - Model index\n * @returns {Model|undefined} Model instance or undefined\n */\n at(index) {\n return this.models[index];\n }\n\n /**\n * Get collection length\n * @returns {number} Number of models in collection\n */\n length() {\n return this.models.length;\n }\n\n /**\n * Check if collection is empty\n * @returns {boolean} True if collection has no models\n */\n isEmpty() {\n return this.models.length === 0;\n }\n\n /**\n * Find models matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {array} Array of matching models\n */\n where(criteria) {\n if (typeof criteria === 'function') {\n return this.models.filter(criteria);\n }\n\n if (typeof criteria === 'object') {\n return this.models.filter(model => {\n return Object.entries(criteria).every(([key, value]) => {\n return model.get(key) === value;\n });\n });\n }\n\n return [];\n }\n\n /**\n * Find first model matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {Model|undefined} First matching model or undefined\n */\n findWhere(criteria) {\n const results = this.where(criteria);\n return results.length > 0 ? results[0] : undefined;\n }\n\n /**\n * Iterate over each model in the collection\n * @param {function} callback - Function to execute for each model (model, index, collection)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {Collection} Returns the collection for chaining\n */\n forEach(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n this.models.forEach((model, index) => {\n callback.call(thisArg, model, index, this);\n });\n\n return this;\n }\n\n /**\n * Sort collection by comparator function\n * @param {function|string} comparator - Comparison function or attribute name\n * @param {object} options - Sort options\n */\n sort(comparator, options = {}) {\n if (typeof comparator === 'string') {\n const attr = comparator;\n comparator = (a, b) => {\n const aVal = a.get(attr);\n const bVal = b.get(attr);\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n };\n }\n\n this.models.sort(comparator);\n\n if (!options.silent) {\n this.emit('sort', { collection: this });\n }\n\n return this;\n }\n\n /**\n * Convert collection to JSON array\n * @returns {array} Array of model JSON representations\n */\n toJSON() {\n return this.models.map(model => model.toJSON());\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Collection: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n return false;\n }\n\n /**\n * Check if collection has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n /**\n * Build URL for collection endpoint\n * @returns {string} Collection API URL\n */\n buildUrl() {\n return this.endpoint;\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Iterator support for for...of loops\n */\n *[Symbol.iterator]() {\n for (const model of this.models) {\n yield model;\n }\n }\n\n /**\n * Static method to create collection from array data\n * @param {function} ModelClass - Model class constructor\n * @param {array} data - Array of model data\n * @param {object} options - Collection options\n * @returns {Collection} New collection instance\n */\n static fromArray(ModelClass, data = [], options = {}) {\n const collection = new this({ ModelClass, ...options });\n collection.add(data, { silent: true });\n return collection;\n }\n}\n\nObject.assign(Collection.prototype, EventEmitter);\n\nexport default Collection;\n"],"names":["dataFormatter","constructor","this","formatters","Map","registerBuiltInFormatters","escapeHtml","str","map","String","replace","m","register","date","bind","time","datetime","datetime_tz","date_range","datetime_range","relative","relative_short","iso","v","num","parseFloat","isNaN","number","currency","percent","filesize","ordinal","compact","add","subtract","multiply","divide","toUpperCase","toLowerCase","capitalize","truncate","truncate_middle","truncate_front","slug","initials","mask","hex","unhex","email","phone","url","badge","badgeClass","status","status_text","status_icon","boolean","bool","yesnoicon","icon","avatar","image","tooltip","linkify","clipboard","default","equals","json","fn","iter","Array","isArray","Object","keys","values","plural","formatList","duration","hash","stripHtml","highlight","nl2br","code","value","options","text","escaped","defaults","urls","emails","target","rel","opts","result","urlRegex","match","prefix","startsWith","emailRegex","mode","escapedText","trim","lang","name","formatter","Error","set","apply","args","get","console","warn","error","pipe","pipeString","context","parsePipeString","reduce","currentValue","pipes","tokens","split","s","token","parsed","parseFormatter","push","parenMatch","argsString","parseArguments","colonMatch","parseColonArguments","current","inQuotes","quoteChar","depth","i","length","char","parseValue","endsWith","slice","Number","JSON","parse","e","isIdentifier","includes","prototype","hasOwnProperty","call","contextValue","getContextValue","MOJOUtils","window","require","getNestedValue","test","format","normalizeEpoch","Date","year","month","day","getTime","YYYY","getFullYear","YY","MMMM","toLocaleDateString","MMM","MM","getMonth","padStart","M","dddd","weekday","ddd","DD","getDate","D","tokenPattern","RegExp","join","hours","getHours","replacements","HH","H","hh","h","mm","getMinutes","ss","getSeconds","A","a","sortedKeys","sort","b","key","dateFormat","timeFormat","dateStr","timeStr","locale","timeZone","getTzAbbr","abbr","tzPart","Intl","DateTimeFormat","hour","minute","timeZoneName","formatToParts","find","p","type","tz2","timeStyle","w","parts","second","hourCycle","pt","y4","M2","D2","H2","m2","s2","parseInt","hNum","monthLong","monthShort","weekdayLong","weekdayShort","dateTokens","timeTokens","replaceTokens","fmt","pattern","asNum","isFinite","startValue","endValue","endVal","startStr","endStr","short","diffMs","absDiffMs","Math","abs","diffSecs","floor","diffMins","diffHours","diffDays","isFuture","years","months","weeks","dateOnly","toISOString","decimals","toLocaleString","minimumFractionDigits","maximumFractionDigits","symbol","centsStr","toString","sign","dollars","cents","formatted","padEnd","binary","bytes","units","divisor","size","unitIndex","decimalPlaces","toFixed","suffixOnly","j","k","suffix","addend","num1","num2","subtrahend","multiplier","all","c","charAt","search","replacement","flags","searchStr","regexLike","rxFlags","substring","halfSize","separator","count","filter","word","showLast","repeat","max","link","subject","encodeURIComponent","body","class","newWindow","item","mapping","fromEntries","pair","badgeType","inferBadgeType","lowered","_status","icons","colors","noIcons","noText","iconClass","active","approved","declined","inactive","pending","success","warning","trueText","falseText","colored","yesIcon","noIcon","rendition","classes","alt","_extractImageUrl","sizeClasses","xs","sm","md","lg","xl","sizeStyle","placement","html","displayValue","preferredRendition","attributes","thumbnail","renditions","availableRenditions","defaultValue","compareValue","trueResult","falseResult","singular","includeCount","array","conjunction","limit","moreText","items","hasMore","remaining","unit","precision","ms","absMs","u","unitName","round","searchTerm","className","escapedTerm","regex","uppercase","withPrefix","hexStr","toHexFromBytes","from","trunc","Uint8Array","ArrayBuffer","every","n","TextEncoder","encode","byte","TextDecoder","decode","fromCharCode","indent","stringify","has","unregister","delete","listFormatters","entries","getContextData","field","parenDepth","pipeIndex","path","remainingPath","isNullOrUndefined","deepClone","obj","clonedObj","deepMerge","sources","source","shift","isObject","assign","debounce","func","wait","timeout","clearTimeout","setTimeout","throttle","inThrottle","generateId","timestamp","now","randomStr","random","substr","entityMap","checkPasswordStrength","password","score","strength","feedback","details","hasLowercase","hasUppercase","hasNumbers","hasSpecialChars","hasCommonPatterns","isCommonPassword","commonPatterns","generatePassword","config","includeLowercase","includeUppercase","includeNumbers","includeSpecialChars","customChars","excludeAmbiguous","lowercase","numbers","specialChars","charPool","requiredChars","parseQueryString","queryString","params","searchParams","URLSearchParams","toQueryString","wrapData","data","rootContext","DataWrapper","defineProperty","writable","enumerable","configurable","_data","_rootContext","toJSON","utils","EventEmitter","on","event","callback","_listeners","listener","off","arguments","once","onceWrapper","emit","listeners","rest","baseURL","headers","Accept","trackDevice","duidHeader","duidTransport","interceptors","request","response","duid","_initializeDuid","storageKey","storedDuid","localStorage","getItem","_generateDuid","setItem","crypto","randomUUID","r","configure","baseUrl","oldTrackDevice","addInterceptor","interceptor","buildUrl","categorizeError","message","reason","buildQueryString","forEach","append","processRequestInterceptors","processedRequest","processResponseInterceptors","responseData","ok","statusText","errors","contentType","jsonData","errorInfo","method","auth","URL","FormData","fetchOptions","signals","AbortSignal","signal","any","fetch","dataOnly","errorResponse","network","mockResponse","Headers","async","GET","POST","PUT","PATCH","DELETE","post","put","patch","download","contentDisposition","filename","filenameMatch","reader","getReader","stream","ReadableStream","start","controller","pump","read","then","done","enqueue","close","blob","Response","downloadUrl","createObjectURL","document","createElement","style","display","href","appendChild","click","revokeObjectURL","remove","downloadBlob","upload","file","Promise","resolve","reject","File","xhr","XMLHttpRequest","onProgress","onprogress","onload","onerror","ontimeout","open","setRequestHeader","send","uploadMultipart","files","additionalData","formData","FileList","index","setAuthToken","clearAuth","isRetryableError","requiresAuth","isNetworkError","getUserMessage","not_reachable","timed_out","cancelled","unauthorized","forbidden","not_found","validation_error","rate_limited","server_error","cors_error","dns_error","unknown_error","Model","endpoint","id","_","originalAttributes","loading","idAttribute","timestamps","previousAttributes","hasChanged","attrKey","attrValue","_setNestedAttribute","silent","attr","val","finalValue","_getNestedValue","oldValue","topLevelKey","attrTarget","instanceTarget","currentKey","finalKey","getData","getId","requiresId","requestKey","debounceMs","_debouncedFetch","currentRequest","currentRequestKey","abortController","abort","lastFetchTime","AbortController","_performFetch","debouncedFetchTimeout","cancel","graph","save","isNew","destroy","isDirty","getChangedAttributes","changed","reset","validate","validations","rules","validateField","rulesArray","rule","required","minLength","maxLength","model","create","isFetching","showError","Modal","import","alert","Collection","ModelClass","models","meta","tmp","restEnabled","preloaded","getModelName","additionalParams","rateLimiting","fetchParams","updateParams","newParams","autoFetch","setParams","fetchOne","collection","addToCollection","existingModel","merge","downloadParams","download_format","_buildDateRangeSuffix","acceptHeader","csv","hasStart","dr_start","hasEnd","dr_end","sanitize","dr_field","Boolean","modelsData","addedModels","modelData","existingIndex","findIndex","modelsToRemove","removedModels","indexOf","removedModel","splice","previousModels","at","isEmpty","where","criteria","findWhere","results","thisArg","TypeError","comparator","aVal","bVal","Symbol","iterator","fromArray"],"mappings":"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"}