web-mojo 2.6.4 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/admin-models.cjs.js +1 -1
- package/dist/admin-models.es.js +1 -1
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunks/ChatView-B76p_4u8.js +2 -0
- package/dist/chunks/ChatView-B76p_4u8.js.map +1 -0
- package/dist/chunks/ChatView-CC4SaAGA.js +2 -0
- package/dist/chunks/ChatView-CC4SaAGA.js.map +1 -0
- package/dist/chunks/{ContextMenu-3_bOvSwa.js → ContextMenu-B6u-y5X8.js} +2 -2
- package/dist/chunks/ContextMenu-B6u-y5X8.js.map +1 -0
- package/dist/chunks/{ContextMenu-xjj8Quls.js → ContextMenu-DkIc9twn.js} +2 -2
- package/dist/chunks/ContextMenu-DkIc9twn.js.map +1 -0
- package/dist/chunks/{DataView-CdyrhKrD.js → DataView-CnbbvE5S.js} +2 -2
- package/dist/chunks/{DataView-CdyrhKrD.js.map → DataView-CnbbvE5S.js.map} +1 -1
- package/dist/chunks/{DataView-B7XoPBZd.js → DataView-CvXW1Ewe.js} +2 -2
- package/dist/chunks/{DataView-B7XoPBZd.js.map → DataView-CvXW1Ewe.js.map} +1 -1
- package/dist/chunks/FormView-BMjKplJy.js +2 -0
- package/dist/chunks/FormView-BMjKplJy.js.map +1 -0
- package/dist/chunks/FormView-geCsNsn1.js +2 -0
- package/dist/chunks/FormView-geCsNsn1.js.map +1 -0
- package/dist/chunks/ListView-B6cE3WKB.js +2 -0
- package/dist/chunks/ListView-B6cE3WKB.js.map +1 -0
- package/dist/chunks/ListView-DXfD53sU.js +2 -0
- package/dist/chunks/ListView-DXfD53sU.js.map +1 -0
- package/dist/chunks/{MetricsCountryMapView-9e2t5sfH.js → MetricsCountryMapView-BuFNN1Ey.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-9e2t5sfH.js.map → MetricsCountryMapView-BuFNN1Ey.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-ki6auFBe.js → MetricsCountryMapView-Dse3HsUv.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-ki6auFBe.js.map → MetricsCountryMapView-Dse3HsUv.js.map} +1 -1
- package/dist/chunks/{Modal-Bbn-54-6.js → Modal-BmXvplWi.js} +3 -3
- package/dist/chunks/Modal-BmXvplWi.js.map +1 -0
- package/dist/chunks/{Modal-BRmaTaWa.js → Modal-DRrfsnNP.js} +3 -3
- package/dist/chunks/Modal-DRrfsnNP.js.map +1 -0
- package/dist/chunks/Passkeys-D2LFSJpQ.js +2 -0
- package/dist/chunks/Passkeys-D2LFSJpQ.js.map +1 -0
- package/dist/chunks/Passkeys-vneS9ywK.js +2 -0
- package/dist/chunks/Passkeys-vneS9ywK.js.map +1 -0
- package/dist/chunks/{TokenManager-qlmZ7eW3.js → TokenManager-DBwv-cF4.js} +2 -2
- package/dist/chunks/{TokenManager-qlmZ7eW3.js.map → TokenManager-DBwv-cF4.js.map} +1 -1
- package/dist/chunks/{TokenManager-BkW1MQ1m.js → TokenManager-DXtiygmr.js} +2 -2
- package/dist/chunks/{TokenManager-BkW1MQ1m.js.map → TokenManager-DXtiygmr.js.map} +1 -1
- package/dist/chunks/User-DJHaSCL9.js +2 -0
- package/dist/chunks/User-DJHaSCL9.js.map +1 -0
- package/dist/chunks/User-ao-iRfPc.js +2 -0
- package/dist/chunks/User-ao-iRfPc.js.map +1 -0
- package/dist/chunks/{UserProfileView-Dbei45vn.js → UserProfileView-DRpHVJtE.js} +2 -2
- package/dist/chunks/{UserProfileView-Dbei45vn.js.map → UserProfileView-DRpHVJtE.js.map} +1 -1
- package/dist/chunks/{UserProfileView-OcqSynHt.js → UserProfileView-DtIBSw3O.js} +2 -2
- package/dist/chunks/{UserProfileView-OcqSynHt.js.map → UserProfileView-DtIBSw3O.js.map} +1 -1
- package/dist/chunks/{WebApp-CIhrSvuV.js → WebApp-7COAyECf.js} +2 -2
- package/dist/chunks/{WebApp-CIhrSvuV.js.map → WebApp-7COAyECf.js.map} +1 -1
- package/dist/chunks/{WebApp-DAviV-_c.js → WebApp-CSvy-GUm.js} +2 -2
- package/dist/chunks/{WebApp-DAviV-_c.js.map → WebApp-CSvy-GUm.js.map} +1 -1
- package/dist/chunks/{admin-models-BBmATRhV.js → admin-models-DHXtrq87.js} +2 -2
- package/dist/chunks/{admin-models-BBmATRhV.js.map → admin-models-DHXtrq87.js.map} +1 -1
- package/dist/chunks/{admin-models-C-SHgZGn.js → admin-models-vhiH86Tq.js} +2 -2
- package/dist/chunks/{admin-models-C-SHgZGn.js.map → admin-models-vhiH86Tq.js.map} +1 -1
- package/dist/chunks/{exportChart-2I2hxr7t.js → exportChart-B1pjkCdm.js} +2 -2
- package/dist/chunks/{exportChart-2I2hxr7t.js.map → exportChart-B1pjkCdm.js.map} +1 -1
- package/dist/chunks/{exportChart-B5optjiT.js → exportChart-Vq4_f06o.js} +2 -2
- package/dist/chunks/{exportChart-B5optjiT.js.map → exportChart-Vq4_f06o.js.map} +1 -1
- package/dist/chunks/{index-B543iMCu.js → index-Bj5AxsEG.js} +2 -2
- package/dist/chunks/{index-B543iMCu.js.map → index-Bj5AxsEG.js.map} +1 -1
- package/dist/chunks/{index-C6reyIyu.js → index-DrmdnYWO.js} +2 -2
- package/dist/chunks/{index-C6reyIyu.js.map → index-DrmdnYWO.js.map} +1 -1
- package/dist/chunks/{version-D_d_sDRM.js → version-CSb8B3nS.js} +2 -2
- package/dist/chunks/{version-D_d_sDRM.js.map → version-CSb8B3nS.js.map} +1 -1
- package/dist/chunks/{version-DWWDfVB9.js → version-_kAV0pF7.js} +2 -2
- package/dist/chunks/{version-DWWDfVB9.js.map → version-_kAV0pF7.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +1 -1
- package/dist/user-profile.cjs.js +1 -1
- package/dist/user-profile.es.js +1 -1
- package/dist/web-mojo.lite.iife.js +1406 -43
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +336 -95
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/ChatView-C1Vc-P_O.js +0 -2
- package/dist/chunks/ChatView-C1Vc-P_O.js.map +0 -1
- package/dist/chunks/ChatView-Cv6kH3MO.js +0 -2
- package/dist/chunks/ChatView-Cv6kH3MO.js.map +0 -1
- package/dist/chunks/ContextMenu-3_bOvSwa.js.map +0 -1
- package/dist/chunks/ContextMenu-xjj8Quls.js.map +0 -1
- package/dist/chunks/FormView-BE4qZvNA.js +0 -2
- package/dist/chunks/FormView-BE4qZvNA.js.map +0 -1
- package/dist/chunks/FormView-BqR4x5nz.js +0 -2
- package/dist/chunks/FormView-BqR4x5nz.js.map +0 -1
- package/dist/chunks/ListView-BF442PdR.js +0 -2
- package/dist/chunks/ListView-BF442PdR.js.map +0 -1
- package/dist/chunks/ListView-DqfZmgXS.js +0 -2
- package/dist/chunks/ListView-DqfZmgXS.js.map +0 -1
- package/dist/chunks/Modal-BRmaTaWa.js.map +0 -1
- package/dist/chunks/Modal-Bbn-54-6.js.map +0 -1
- package/dist/chunks/Passkeys-B_bxdIVa.js +0 -2
- package/dist/chunks/Passkeys-B_bxdIVa.js.map +0 -1
- package/dist/chunks/Passkeys-LRaiCx3H.js +0 -2
- package/dist/chunks/Passkeys-LRaiCx3H.js.map +0 -1
- package/dist/chunks/User-B7jwnBx2.js +0 -2
- package/dist/chunks/User-B7jwnBx2.js.map +0 -1
- package/dist/chunks/User-Dbw_wZAz.js +0 -2
- package/dist/chunks/User-Dbw_wZAz.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListView-B6cE3WKB.js","sources":["../../src/core/views/navigation/SegmentControl.js","../../src/core/utils/DjangoLookups.js","../../src/core/views/list/ListViewItem.js","../../src/core/views/list/ListGroupHeaderView.js","../../src/core/views/list/ListView.js"],"sourcesContent":["/**\n * SegmentControl - Horizontal pill-button group for one-of-N selection.\n *\n * A small standalone view that renders a Bootstrap btn-group and emits a\n * `change` event when the user picks a different option. Use for\n * range pickers (7d / 30d / 90d), view modes, or any compact toggle.\n *\n * Not a FormView input — caller is responsible for applying the value\n * (e.g., updating a Collection's params and re-fetching).\n *\n * Example:\n * const segments = new SegmentControl({\n * options: [\n * { value: '7d', label: '7d' },\n * { value: '30d', label: '30d' },\n * { value: '90d', label: '90d' },\n * ],\n * value: '30d',\n * size: 'sm',\n * ariaLabel: 'Time range'\n * });\n * segments.on('change', ({ value }) => collection.fetch({ range: value }));\n * this.addChild(segments, { containerId: 'range' });\n */\n\nimport View from '@core/View.js';\n\nclass SegmentControl extends View {\n constructor(options = {}) {\n const {\n options: items = [],\n value,\n size = 'sm',\n ariaLabel = 'Segment control',\n ...viewOptions\n } = options;\n\n super({\n tagName: 'div',\n className: 'segment-control',\n ...viewOptions\n });\n\n this.items = items;\n this.value = value !== undefined ? value : (items[0] && items[0].value);\n this.size = size === 'md' ? '' : 'sm';\n this.ariaLabel = ariaLabel;\n\n this.template = () => this._buildTemplate();\n }\n\n _buildTemplate() {\n const sizeClass = this.size ? `btn-group-${this.size}` : '';\n const buttons = this.items.map(item => {\n const isActive = item.value === this.value;\n const cls = isActive ? 'btn btn-primary' : 'btn btn-outline-secondary';\n const icon = item.icon ? `<i class=\"bi ${this.escapeHtml(item.icon)} me-1\"></i>` : '';\n return `<button type=\"button\"\n class=\"${cls}\"\n data-action=\"select\"\n data-value=\"${this.escapeHtml(String(item.value))}\"\n ${isActive ? 'aria-pressed=\"true\"' : 'aria-pressed=\"false\"'}>${icon}${this.escapeHtml(item.label)}</button>`;\n }).join('');\n\n return `<div class=\"btn-group ${sizeClass}\" role=\"group\" aria-label=\"${this.escapeHtml(this.ariaLabel)}\">${buttons}</div>`;\n }\n\n async onActionSelect(event, element) {\n const next = element.dataset.value;\n if (next === this.value) return;\n const previous = this.value;\n this.value = next;\n this._paintActive();\n this.emit('change', { value: next, previous });\n }\n\n /**\n * Update the active button styling without a full re-render.\n * @private\n */\n _paintActive() {\n if (!this.element) return;\n this.element.querySelectorAll('button[data-value]').forEach(btn => {\n const isActive = btn.dataset.value === String(this.value);\n btn.classList.toggle('btn-primary', isActive);\n btn.classList.toggle('btn-outline-secondary', !isActive);\n btn.setAttribute('aria-pressed', isActive ? 'true' : 'false');\n });\n }\n\n /**\n * Programmatically set the value.\n * @param {*} value - The new value to select\n * @param {object} opts - { silent: boolean } — suppress the change event\n * @returns {boolean} true if the value matched a known option and was applied\n */\n setValue(value, { silent = false } = {}) {\n const match = this.items.find(item => String(item.value) === String(value));\n if (!match) return false;\n const previous = this.value;\n if (match.value === previous) return true;\n this.value = match.value;\n this._paintActive();\n if (!silent) this.emit('change', { value: this.value, previous });\n return true;\n }\n\n getValue() {\n return this.value;\n }\n}\n\nexport default SegmentControl;\n","/**\n * DjangoLookups - Utility for Django-style filter lookup parsing and formatting\n * \n * Provides utilities to parse filter keys like \"status__in\" or \"created__gte\"\n * and format them into human-readable display text for filter pills.\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * formatFilterDisplay('status__in', 'new,open', 'Status') // \"Status in 'new', 'open'\"\n */\n\n/**\n * Supported Django-style lookups with display configurations\n * Only includes commonly used lookups (KISS principle)\n */\nexport const LOOKUPS = {\n // Comparison\n 'exact': { \n display: 'is',\n description: 'Exact match'\n },\n 'in': { \n display: 'in',\n description: 'Match any of the values (comma-separated)'\n },\n 'not': { \n display: 'is not',\n description: 'Does not match'\n },\n 'not_in': { \n display: 'not in',\n description: 'Does not match any of the values'\n },\n 'gt': { \n display: '>',\n description: 'Greater than'\n },\n 'gte': { \n display: '>=',\n description: 'Greater than or equal to'\n },\n 'lt': { \n display: '<',\n description: 'Less than'\n },\n 'lte': { \n display: '<=',\n description: 'Less than or equal to'\n },\n \n // String operations\n 'contains': { \n display: 'contains',\n description: 'Contains substring (case-sensitive)'\n },\n 'icontains': { \n display: 'contains',\n description: 'Contains substring (case-insensitive)'\n },\n 'startswith': { \n display: 'starts with',\n description: 'Starts with substring (case-sensitive)'\n },\n 'istartswith': { \n display: 'starts with',\n description: 'Starts with substring (case-insensitive)'\n },\n 'endswith': { \n display: 'ends with',\n description: 'Ends with substring (case-sensitive)'\n },\n 'iendswith': { \n display: 'ends with',\n description: 'Ends with substring (case-insensitive)'\n },\n \n // Null checks\n 'isnull': { \n display: (val) => val === 'true' || val === true ? 'is null' : 'is not null',\n description: 'Check if value is null or not'\n },\n \n // Range operations\n 'range': { \n display: 'between',\n description: 'Between two values (comma-separated)'\n }\n};\n\n/**\n * Parse a filter key into field name and lookup operator\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\", \"created__gte\")\n * @returns {Object} Object with field and lookup properties\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * parseFilterKey('status') // { field: 'status', lookup: null }\n * parseFilterKey('user__profile__name__icontains') // { field: 'user__profile__name', lookup: 'icontains' }\n */\nexport function parseFilterKey(paramKey) {\n if (!paramKey || typeof paramKey !== 'string') {\n return { field: paramKey, lookup: null };\n }\n\n const parts = paramKey.split('__');\n \n // Single part, no lookup\n if (parts.length === 1) {\n return { field: paramKey, lookup: null };\n }\n \n // Check if last part is a valid lookup\n const possibleLookup = parts[parts.length - 1];\n if (LOOKUPS[possibleLookup]) {\n return { \n field: parts.slice(0, -1).join('__'), \n lookup: possibleLookup \n };\n }\n \n // No valid lookup found, treat entire string as field name\n return { field: paramKey, lookup: null };\n}\n\n/**\n * Format a filter key and value into human-readable display text\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\")\n * @param {string|Array} value - Filter value(s)\n * @param {string} label - Human-readable field label\n * @returns {string} Formatted display text\n * \n * @example\n * formatFilterDisplay('status__in', 'new,open', 'Status') \n * // \"Status in 'new', 'open'\"\n * \n * formatFilterDisplay('created__gte', '2025-01-01', 'Created') \n * // \"Created >= '2025-01-01'\"\n * \n * formatFilterDisplay('name__icontains', 'john', 'Name') \n * // \"Name contains 'john'\"\n */\nexport function formatFilterDisplay(paramKey, value, label) {\n if (!paramKey || value === null || value === undefined) {\n return '';\n }\n\n const { field, lookup } = parseFilterKey(paramKey);\n const lookupDef = LOOKUPS[lookup];\n \n // Handle object-based values (e.g., daterange payloads)\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const hasStart = value.start !== undefined && value.start !== null && value.start !== '';\n const hasEnd = value.end !== undefined && value.end !== null && value.end !== '';\n\n if (hasStart || hasEnd) {\n if (hasStart && hasEnd) {\n return `${label} between '${value.start}' and '${value.end}'`;\n }\n if (hasStart) {\n return `${label} from '${value.start}'`;\n }\n return `${label} until '${value.end}'`;\n }\n\n // Fallback to JSON if it's some other object shape\n return `${label} is '${JSON.stringify(value)}'`;\n }\n\n // Convert array to comma-separated string if needed\n const valueStr = Array.isArray(value) ? value.join(',') : String(value);\n \n // No lookup or exact lookup - simple \"is\" format\n if (!lookup || lookup === 'exact') {\n return `${label} is '${valueStr}'`;\n }\n \n // Multi-value lookups (in, not_in)\n if (lookup === 'in' || lookup === 'not_in') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 0) {\n return `${label} ${lookupDef.display}`;\n }\n const formattedValues = values.map(v => `'${v}'`).join(', ');\n return `${label} ${lookupDef.display} ${formattedValues}`;\n }\n \n // Range lookup - special formatting\n if (lookup === 'range') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 2) {\n return `${label} between '${values[0]}' and '${values[1]}'`;\n }\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Null check - dynamic display based on value\n if (lookup === 'isnull') {\n const displayText = typeof lookupDef.display === 'function' \n ? lookupDef.display(valueStr) \n : lookupDef.display;\n return `${label} ${displayText}`;\n }\n \n // Standard lookup with operator\n if (lookupDef) {\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Fallback for unknown lookups\n return `${label} is '${valueStr}'`;\n}\n\n/**\n * Get a user-friendly description of a lookup operator\n * \n * @param {string} lookup - Lookup operator (e.g., \"in\", \"gte\", \"icontains\")\n * @returns {string} Human-readable description\n * \n * @example\n * getLookupDescription('in') // \"Match any of the values (comma-separated)\"\n * getLookupDescription('gte') // \"Greater than or equal to\"\n */\nexport function getLookupDescription(lookup) {\n const lookupDef = LOOKUPS[lookup];\n return lookupDef ? lookupDef.description : 'Exact match';\n}\n\n/**\n * Check if a string is a valid lookup operator\n * \n * @param {string} lookup - Potential lookup operator\n * @returns {boolean} True if valid lookup\n * \n * @example\n * isValidLookup('in') // true\n * isValidLookup('foo') // false\n */\nexport function isValidLookup(lookup) {\n return lookup && LOOKUPS.hasOwnProperty(lookup);\n}\n\n/**\n * Get all available lookup operators\n * \n * @returns {Array<string>} Array of lookup operator names\n * \n * @example\n * getAvailableLookups() // ['exact', 'in', 'not', 'not_in', 'gt', ...]\n */\nexport function getAvailableLookups() {\n return Object.keys(LOOKUPS);\n}\n\n/**\n * Build a filter key from field name and lookup operator\n * \n * @param {string} field - Field name\n * @param {string} lookup - Lookup operator (optional)\n * @returns {string} Combined filter key\n * \n * @example\n * buildFilterKey('status', 'in') // \"status__in\"\n * buildFilterKey('status') // \"status\"\n */\nexport function buildFilterKey(field, lookup = null) {\n if (!field) return '';\n if (!lookup) return field;\n return `${field}__${lookup}`;\n}\n\nexport default {\n LOOKUPS,\n parseFilterKey,\n formatFilterDisplay,\n getLookupDescription,\n isValidLookup,\n getAvailableLookups,\n buildFilterKey\n};\n","/**\n * ListViewItem - Individual item view for ListView\n *\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n *\n * Events:\n * - 'item:click' - Emitted when item is clicked\n * - 'item:select' - Emitted when item is selected\n * - 'item:deselect' - Emitted when item is deselected\n *\n * @example\n * const item = new ListViewItem({\n * model: userModel,\n * template: '<div class=\"user-item\">{{name}} - {{email}}</div>'\n * });\n */\n\nimport View from '@core/View.js';\n\nclass ListViewItem extends View {\n constructor(options = {}) {\n super({\n className: 'list-view-item',\n ...options\n });\n\n // Item-specific properties\n this.selected = false;\n this.index = options.index ?? 0;\n this.listView = options.listView ?? null;\n this.clickable = options.clickable === true;\n if (this.clickable && this.element) {\n this.addClass('clickable');\n }\n\n // Default template if none provided\n if (!this.template) {\n this.template = `\n <div class=\"list-item-content\" data-action=\"select\">\n {{#model}}\n {{#id}}<span class=\"item-id\">{{id}}</span>{{/id}}\n {{#name}}<span class=\"item-name\">{{name}}</span>{{/name}}\n {{#title}}<span class=\"item-title\">{{title}}</span>{{/title}}\n {{#label}}<span class=\"item-label\">{{label}}</span>{{/label}}\n {{#description}}<p class=\"item-description\">{{description}}</p>{{/description}}\n {{/model}}\n {{^model}}\n <span class=\"item-empty\">No data</span>\n {{/model}}\n </div>\n `;\n }\n }\n\n /**\n * Handle item selection action\n */\n async onActionSelect(event, _element) {\n event.stopPropagation();\n\n if (this.selected) {\n this.deselect();\n } else {\n this.select();\n }\n }\n\n /**\n * Handle the standard View / Edit / Delete actions when the item\n * template includes `data-action=\"view\"` / `\"edit\"` / `\"delete\"` buttons.\n * Each emits a `row:view` / `row:edit` / `row:delete` event with the\n * model and event payload — ListView listens for these and runs the\n * standard Modal dialog flow (or a custom override).\n */\n async onActionView(event, _element) {\n event.stopPropagation();\n this._emitRowEvent('row:view', event);\n }\n\n async onActionEdit(event, _element) {\n event.stopPropagation();\n this._emitRowEvent('row:edit', event);\n }\n\n async onActionDelete(event, _element) {\n event.stopPropagation();\n this._emitRowEvent('row:delete', event);\n }\n\n /** @private */\n _emitRowEvent(name, event) {\n const payload = {\n item: this,\n model: this.model,\n index: this.index,\n event,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n };\n this.emit(name, payload);\n if (this.listView) this.listView.emit(name, payload);\n }\n\n /**\n * Select this item\n */\n select() {\n if (this.selected) return;\n\n this.selected = true;\n this.addClass('selected');\n\n // Emit selection event with item data\n this.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:select', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Deselect this item\n */\n deselect() {\n if (!this.selected) return;\n\n this.selected = false;\n this.removeClass('selected');\n\n // Emit deselection event\n this.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:deselect', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * onAfterRender — wire up the whole-row click handler when `clickable` is\n * set. Inner elements with their own `data-action` are NOT intercepted: the\n * EventDelegate on the parent View runs first and dispatches the inner\n * action, while this listener checks `event.defaultPrevented` and bails\n * out so we don't double-fire. The click only registers as a \"row click\"\n * when the user clicked the card body, not a button/link inside it.\n */\n async onAfterRender() {\n await super.onAfterRender();\n if (this.clickable && this.element) {\n this.addClass('clickable');\n this._wireClickableHandler();\n }\n // Parent ListView owns the row-stripe mapping; the row just signals\n // \"I rendered, refresh me\". Piggybacks on View's automatic\n // `model:change → render()` so stripes auto-update on model change.\n if (this.listView?.rowStripe && typeof this.listView._applyRowStripe === 'function') {\n this.listView._applyRowStripe(this);\n }\n }\n\n _wireClickableHandler() {\n if (this._clickableHandler || !this.element) return;\n this._clickableHandler = (event) => {\n // If the click landed on (or inside) an element with a `data-action`,\n // the inner action handler owns it — don't treat it as a row click.\n if (event.target?.closest?.('[data-action]')) return;\n // Skip native form-control interactions (typing in an input inside the card).\n const tag = event.target?.tagName;\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;\n\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: 'row-click',\n event,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: 'row-click',\n event,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n };\n this.element.addEventListener('click', this._clickableHandler);\n }\n\n /**\n * Handle click events on the item\n */\n async onActionDefault(action, _event, _element) {\n // Emit click event for any action not specifically handled\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n // Notify parent ListView if available\n if (this.listView) {\n this.listView.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n action: action,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n }\n }\n\n /**\n * Set the item's index in the list\n */\n setIndex(index) {\n this.index = index;\n this.element.setAttribute('data-index', index);\n return this;\n }\n\n /**\n * Update the item's selection state\n */\n setSelected(selected) {\n if (selected) {\n this.select();\n } else {\n this.deselect();\n }\n return this;\n }\n\n /**\n * Override destroy to clean up references\n */\n async destroy() {\n // Remove the row-click handler we attached imperatively.\n if (this._clickableHandler && this.element) {\n this.element.removeEventListener('click', this._clickableHandler);\n this._clickableHandler = null;\n }\n // Remove reference to parent ListView\n this.listView = null;\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListViewItem;\n","/**\n * ListGroupHeaderView - Synthetic header row inserted between groups of items\n * in a ListView when `groupBy` is configured.\n *\n * Extends `View` directly (NOT `ListViewItem`) so it does not inherit the\n * row-click / `onActionDefault` / `_wireClickableHandler` machinery — header\n * rows must NEVER fire `item:click`, `row:click`, or `clickAction: 'view'`.\n *\n * The view's Mustache context exposes:\n * - `{{key}}` — the resolved + label-formatted display key\n * - `{{model.*}}` — the trigger model (first model of the group)\n * - `{{colspan}}` — set by TableView's override so a `<th colspan=\"N\">`\n * header spans the full row.\n */\n\nimport View from '@core/View.js';\n\nclass ListGroupHeaderView extends View {\n constructor(options = {}) {\n super({\n tagName: options.tagName || 'div',\n className: options.className || 'list-group-header',\n ...options\n });\n\n this.key = options.key ?? '';\n this.index = options.index ?? 0;\n this.colspan = options.colspan ?? 1;\n\n if (!this.template) {\n this.template = '{{key}}';\n }\n }\n}\n\nexport default ListGroupHeaderView;\n","/**\n * ListView - Visual list component for Collections\n *\n * Manages a collection of ListViewItem views, each with its own model.\n * When a model changes, only its corresponding ListViewItem re-renders.\n *\n * As of the toolbar / filters / pagination upgrade, ListView also hosts an\n * optional toolbar (search, filter dropdown + active pills, refresh,\n * custom buttons, title/eyebrow, right-slot view), an optional sort\n * dropdown, and optional pagination — either numbered pages\n * (`paginationMode: 'pages'`) or \"Show more\" load-more\n * (`paginationMode: 'more'`). All toolbar features are opt-in; a plain\n * ListView with just `collection` + `itemTemplate` renders exactly as\n * before. TableView extends ListView and inherits the same toolbar /\n * filter / pagination machinery.\n *\n * Events:\n * - 'item:click' - Emitted when any item is clicked\n * - 'item:select' - Emitted when an item is selected\n * - 'item:deselect' - Emitted when an item is deselected\n * - 'selection:change' - Emitted when selection changes\n * - 'list:empty' - Emitted when list becomes empty\n * - 'list:loaded' - Emitted when list is populated\n * - 'list:search' - Emitted when toolbar search applied\n * - 'list:sort' - Emitted when toolbar sort changed\n * - 'list:page' - Emitted when pagination page changed\n * - 'list:pagesize' - Emitted when pagination page size changed\n * - 'list:show-more' - Emitted when \"Show more\" button clicked\n * - 'filter:edit' - Emitted when a filter pill is clicked to edit\n * - 'filter:remove' - Emitted when a filter pill is removed\n * - 'filters:clear' - Emitted when \"Clear All\" is clicked\n * - 'params-changed' - Emitted whenever sort/page/filter/search changes\n * - 'preset:change' - Emitted when a filter preset is applied ({ key, params }) or cleared (null)\n *\n * @example\n * // Plain list, unchanged from prior behavior.\n * const listView = new ListView({\n * collection: userCollection,\n * itemTemplate: '<div class=\"user-item\">{{name}} - {{email}}</div>',\n * selectionMode: 'single'\n * });\n *\n * @example\n * // Visual list with search, a filter, and \"Show more\" paging.\n * const listView = new ListView({\n * collection: new ArticleCollection(),\n * itemTemplate: '<div class=\"card\">{{title}} — {{author}}</div>',\n * searchable: true,\n * filterable: true,\n * filters: [{ name: 'topic', label: 'Topic', type: 'select', options: ['ops', 'patterns'] }],\n * paginated: true,\n * paginationMode: 'more'\n * });\n */\n\nimport View from '@core/View.js';\nimport Collection from '@core/Collection.js';\nimport Modal from '@core/views/feedback/Modal.js';\nimport Mustache from '@core/utils/mustache.js';\nimport FormView from '@core/forms/FormView.js';\nimport SegmentControl from '@core/views/navigation/SegmentControl.js';\nimport { parseFilterKey, formatFilterDisplay } from '@core/utils/DjangoLookups.js';\nimport ListViewItem from './ListViewItem.js';\nimport ListGroupHeaderView from './ListGroupHeaderView.js';\n\nclass ListView extends View {\n /**\n * Valid values for the `groupHeaderStyle` constructor option. Each maps\n * to a CSS modifier class — see `src/core/css/list-view.css`.\n */\n static GROUP_HEADER_STYLES = ['banner', 'mark', 'band', 'rule'];\n\n /**\n * Bootstrap variant tokens accepted by the `rowStripe` callback. A return\n * matching one of these maps to the canonical `list-row-stripe-<token>`\n * class (defined in list-view.css / table.css). Any other non-empty\n * string returned by the callback is treated as a consumer-defined\n * class name and passed through verbatim.\n */\n static ROW_STRIPE_TOKENS = ['danger', 'warning', 'success', 'info', 'primary', 'secondary'];\n\n /**\n * Track Model classes already warned about a minified `.name` so the\n * console doesn't spam — one warning per offending class per session.\n * @private\n */\n static _warnedMinifiedClasses = new WeakSet();\n\n constructor(options = {}) {\n super({\n className: options.className || 'list-view',\n ...options\n });\n\n // -------- Core list properties (unchanged) --------\n this.collection = null;\n this.itemViews = new Map(); // Map of model.id -> ListViewItem\n this.selectedItems = new Set(); // Set of selected item IDs\n\n this.itemTemplate = options.itemTemplate || null;\n this.itemClass = options.itemClass || ListViewItem;\n this.selectionMode = options.selectionMode || 'none';\n this.emptyMessage = options.emptyMessage || 'No items to display';\n this.loading = false;\n this.isEmpty = true;\n\n // -------- Toolbar / filters / pagination — all opt-in --------\n this.searchable = options.searchable === true;\n this.filterable = options.filterable === true;\n this.paginated = options.paginated === true;\n\n // 'pages' = numbered pagination; 'more' = load-more button; 'none' = off.\n // When `paginated: true` is set without an explicit mode, default to 'more'\n // (the convention for visual lists). Subclasses (TableView) override this.\n let mode = options.paginationMode;\n if (!mode) {\n mode = this.paginated ? 'more' : 'none';\n }\n this.paginationMode = mode;\n\n // Search\n this.searchPlacement = options.searchPlacement || 'toolbar'; // 'toolbar' | 'dropdown'\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n\n // Sort dropdown — list-style alternative to TableView's column-header sort.\n // Each option: { key: 'created', label: 'Newest', dir: 'desc' }\n this.sortOptions = Array.isArray(options.sortOptions) ? options.sortOptions : [];\n\n // Filter configuration\n this.filters = {}; // populated from columns by TableView; empty for plain ListView\n this.additionalFilters = options.filters || [];\n this.hideActivePills = options.hideActivePills === true;\n this.hideActivePillNames = options.hideActivePillNames || [];\n\n // Toolbar chrome\n this.title = options.title || null;\n this.eyebrow = options.eyebrow || null;\n this.showRefresh = options.showRefresh !== false;\n this.toolbarButtons = options.toolbarButtons || [];\n this.toolbarRight = options.toolbarRight || null;\n this._toolbarRightMounted = false;\n\n // Day-range filter helper: opt-in `1d / 7d / 30d / 90d` SegmentControl\n // mounted to the left of `toolbarRight`. When enabled, ListView writes\n // `${field}__gte = nowEpoch - days*86400` to `collection.params` on each\n // change and refetches — same contract as `applyFilters`. Caller can\n // listen to the `range:change` event for side effects (eyebrow labels,\n // etc.). Boolean true → defaults; object form merges over defaults.\n this.dayRangeFilter = this._normalizeDayRangeFilter(options.dayRangeFilter);\n this.dayRangeControl = null;\n\n // Filter presets: opt-in `filterPresets:` array of author-defined\n // \"common queries\" rendered as a compact joined btn-group segment.\n // Each preset bundles a set of filter params applied in one click.\n // Mutual-exclusion + toggle-off only (no additive mode). Active state\n // is *derived* — a preset is active when every one of its resolved\n // params strictly matches getActiveFilters(). Empty / absent → the\n // whole feature is inert (byte-identical rendering & behavior).\n // See `docs/web-mojo/components/ListView.md` (Filter presets).\n this.filterPresets = this._normalizeFilterPresets(options.filterPresets);\n\n // Export configuration (gated by `showAdd` / `showExport` toolbar\n // options, which default to off on ListView). exportSource is 'remote'\n // (download from server via collection.download) or 'local' (pass\n // current data to options.onExport).\n this.exportOptions = options.exportOptions || null;\n this.exportSource = options.exportSource || 'remote';\n\n // Selection persistence across rebuilds (page change / fetchMore append).\n // Default: persist when in 'more' mode (rows aren't torn down anyway, and\n // users expect their selection to stay), clear when in 'pages' mode\n // (preserves existing TableView behavior unless caller opts in).\n this.persistSelection = options.persistSelection === undefined\n ? this.paginationMode === 'more'\n : options.persistSelection === true;\n\n // Click-anywhere-on-the-row pattern (parity with TableView's clickAction).\n // - `onItemClick(model, event)` — fired when the item's root element is\n // clicked anywhere not handled by an inner `data-action` element.\n // - `clickable` — applies `.clickable` to each item (cursor: pointer +\n // hover treatment). Implied true when `onItemClick` is set OR when\n // `clickAction` is set to a non-'none' / non-'select' value.\n this.onItemClick = typeof options.onItemClick === 'function' ? options.onItemClick : null;\n\n // Model lifecycle (view / edit / delete / add) — same options\n // TableView ships. ListView defaults `clickAction: 'none'` (no\n // automatic dialog on row click) so existing usage is unchanged;\n // users opt in by setting clickAction + providing itemView / editForm\n // / etc. Subclasses override the default — TableView sets 'view'.\n this.clickAction = options.clickAction || 'none';\n this.itemView = options.itemView;\n this.addForm = options.addForm;\n this.editForm = options.editForm;\n this.deleteTemplate = options.deleteTemplate;\n this.formDialogConfig = options.formDialogConfig || {};\n this.viewDialogOptions = options.viewDialogOptions || {};\n this.fetchOnView = options.fetchOnView !== false;\n\n this.clickable = options.clickable === true\n || !!this.onItemClick\n || (this.clickAction && this.clickAction !== 'none');\n\n // -------- Row stripe (severity-coded left-edge color) --------\n // Optional per-row callback (model) => token | className | null.\n // Tokens in `ROW_STRIPE_TOKENS` map to `list-row-stripe-<token>`;\n // any other non-empty string is treated as a consumer-defined class\n // name and passed through. Null/undefined/empty = no stripe.\n //\n // The stripe re-applies automatically on every row render — and\n // since View binds `model:change → render()` in the base class,\n // a `model.set()` that flips the callback's input drives a stripe\n // refresh with no extra wiring. For stripes that depend on\n // external (non-model) state, call `refreshStripes()` from the\n // consumer's own event hook.\n this.rowStripe = typeof options.rowStripe === 'function' ? options.rowStripe : null;\n\n // -------- Grouped rows — opt-in via groupBy --------\n // `groupBy` is a function (model) => key OR a string (model field name).\n // When set, ListView inserts a synthetic header row before the first\n // item of each new group key (computed in render order). Headers are\n // ListGroupHeaderView instances — separate from the click-routing\n // machinery so they cannot fire item:click / row:click.\n //\n // Any falsy resolver return (null, undefined, '', 0, false) = no header\n // for that item (\"ungrouped tail\" — prior group's section continues\n // visually). If you genuinely want `0` or `''` as a group, return a\n // string ('zero', '__empty__', etc.) and format it in groupHeaderLabel.\n //\n // Note: `data-action` attributes inside a custom `groupHeaderTemplate`\n // will bubble to the parent ListView's EventDelegate and trigger the\n // matching `onAction*` handler. The default template emits non-interactive\n // markup (with `pointer-events: none` CSS), but if you supply a custom\n // template with buttons / links that have `data-action`, treat those as\n // first-class ListView actions and define handlers accordingly.\n this.groupBy = (typeof options.groupBy === 'function' || typeof options.groupBy === 'string')\n ? options.groupBy\n : null;\n this.groupHeaderTemplate = options.groupHeaderTemplate || null;\n this.groupHeaderLabel = typeof options.groupHeaderLabel === 'function' ? options.groupHeaderLabel : null;\n this.groupHeaderClass = options.groupHeaderClass || ListGroupHeaderView;\n // Visual style — selects the CSS modifier class on each header view.\n // Valid values: 'banner' (default — neutral full-width tint, label centered),\n // 'mark' (small accent square + bold label + fading hairline), 'band'\n // (neutral full-width tint, label left-aligned), 'rule' (editorial\n // fieldset-legend, label centered between rules). See list-view.css.\n this.groupHeaderStyle = ListView.GROUP_HEADER_STYLES.includes(options.groupHeaderStyle)\n ? options.groupHeaderStyle\n : 'banner';\n this.groupHeaderViews = new Map();\n this._renderOrder = [];\n\n // -------- WM-033 feedback states — all opt-in --------\n // Three independent upgrades that share the list body render path:\n // 1. `emptyState: {icon, title, message, action:{label, action, icon}}`\n // — a rich empty panel (icon chip + title + message + optional CTA)\n // replacing the bare `emptyMessage` string. A truly-empty list shows\n // the configured content; a *filtered*-empty list (getActiveFilters()\n // non-empty) auto-swaps to a \"No results match your filters\" panel\n // with a Clear filters button (reuses onActionClearAllFilters).\n // `emptyMessage` stays the untouched fallback when this is absent.\n // 2. `loadingStyle` — the loading visual during fetch. Skeleton shimmer\n // rows/cards are the DEFAULT; pass `loadingStyle: 'spinner'` (or the\n // `'default'` alias) to opt back into the classic spinner. This is the\n // one feedback default that is NOT off — the skeleton is a deliberate,\n // owner-approved upgrade to the loading visual only; non-loading\n // renders are unchanged.\n // 3. `showResultCount: true` — a \"Showing N of M\" line rendered with the\n // active-filter pills, \"· filtered\" when any filter is active.\n // `emptyState` and `showResultCount` default off; a page that sets neither\n // (and doesn't touch `loadingStyle`) renders unchanged apart from the\n // loading visual. See `docs/web-mojo/components/ListView.md` (Feedback states).\n this.emptyState = this._normalizeEmptyState(options.emptyState);\n this.loadingStyle = (options.loadingStyle === 'spinner' || options.loadingStyle === 'default')\n ? 'spinner'\n : 'skeleton';\n this.showResultCount = options.showResultCount === true;\n\n // -------- WM-034 auto-refresh — opt-in interval refetch --------\n // `autoRefresh: <seconds>` silently refetches the collection on an\n // interval (clamped to a 5s minimum), preserving the current\n // `collection.params` (filters / sort / paging). The tick is guarded:\n // it skips (without clearing the timer) while the tab is hidden or the\n // window is blurred, while a selection is active, and — for TableView —\n // while an inline cell edit or a row context menu is open. On regaining\n // focus the timer fires an immediate refetch then resumes normal cadence.\n // Absent → `_autoRefreshMs === 0`, so no timer and no listeners are ever\n // created (byte-identical behavior for pages that don't opt in).\n // See `docs/web-mojo/components/ListView.md` (Auto-refresh).\n this._autoRefreshMs = this._normalizeAutoRefresh(options.autoRefresh);\n this._autoRefreshTimer = null;\n this._autoRefreshPaused = false;\n this._autoRefreshHandlers = null;\n // A quiet toolbar indicator (muted `bi-arrow-repeat`) is shown whenever\n // auto-refresh is active; pass `autoRefreshIndicator: false` to hide it.\n // Off when auto-refresh is off, so it never adds toolbar markup by itself.\n this.autoRefreshIndicator = options.autoRefreshIndicator !== false;\n this._autoRefreshPulseTimer = null;\n\n // -------- WM-035 view persistence — opt-in per-table saved view --------\n // `persistState: true` remembers how each user likes this list/table —\n // sort, page size, day-range value, and active filter params — in\n // localStorage under a stable identity (`persistKey`, or the page route +\n // collection endpoint). Restored on the next visit, with precedence\n // URL > saved > configured defaults (saved state only fills params the\n // current query didn't already set). Strictly opt-in: the storage layer\n // is lazily touched ONLY when this flag is on (privacy + zero side effects\n // otherwise). The persisted blob is versioned (`{ v: 1, ... }`); corrupt\n // or stale entries are discarded silently. TableView layers column\n // visibility onto the same entry.\n // See `docs/web-mojo/components/ListView.md` (View persistence).\n this.persistState = options.persistState === true;\n this.persistKey = options.persistKey || null;\n\n // \"Show more\" state\n this.loadingMore = false;\n\n // Build the template if any toolbar / pagination feature is enabled.\n // Plain ListView (no flags) keeps the lean default template untouched.\n if (this._isToolbarEnabled() || this.paginationMode !== 'none') {\n this.template = this.buildListTemplate();\n } else if (!this.template) {\n this.template = this._defaultBareTemplate();\n }\n }\n\n // ============================================================\n // Lifecycle\n // ============================================================\n\n async onInit() {\n // Snapshot the params already on the passed-in collection BEFORE\n // `_initCollection` layers on configured defaults (pageSize / defaultQuery /\n // collectionParams). These pre-existing keys are \"the query\" — for a\n // TablePage they're the URL params it applied before constructing this\n // view. Saved state (WM-035) fills only the slots the query didn't set,\n // giving the precedence URL > saved > configured defaults.\n const queryKeys = (this.persistState && this.options.collection && this.options.collection.params)\n ? Object.keys(this.options.collection.params)\n : [];\n\n this._initCollection(this.options.collection || this.options.Collection);\n\n // WM-035 — rehydrate the saved view before the day-range control is built\n // and before the first fetch, so restored sort/size/filters/day-range feed\n // that initial request. No-op (and zero storage access) unless persistState\n // is set. Also attach the save-on-change listener.\n if (this.persistState) {\n this._restorePersistedState(queryKeys);\n this.on('params-changed', () => this._savePersistedState());\n }\n\n if (this.dayRangeFilter) {\n this._seedDayRangeParams();\n this.dayRangeControl = new SegmentControl({\n containerId: 'toolbar-day-range',\n options: this.dayRangeFilter.options,\n value: this.dayRangeFilter.value,\n ariaLabel: this.dayRangeFilter.ariaLabel\n });\n this.dayRangeControl.on('change', this._onDayRangeChange, this);\n this.addChild(this.dayRangeControl);\n }\n }\n\n async onAfterMount() {\n await super.onAfterMount();\n if (this.collection && (this.options.fetchOnMount || !this.collection.lastFetchTime)) {\n this.collection.fetch();\n }\n // Start the opt-in auto-refresh timer now the view is live. Fires on\n // first mount and on every cached-page re-entry (the page re-mounts its\n // children). No-op when `autoRefresh` wasn't set, and idempotent so a\n // re-entry never stacks a second interval.\n this._startAutoRefresh();\n }\n\n async onBeforeUnmount() {\n await super.onBeforeUnmount();\n // Tear the timer + listeners down when a standalone view unmounts. (For\n // a TableView hosted inside a cached Page this hook doesn't fire on the\n // child — the parent only unbinds child events — so `_autoRefreshTick`\n // self-terminates on its next tick when it finds itself detached.)\n this._stopAutoRefresh();\n }\n\n async onBeforeRender() {\n // Surface the live search value into the template context so the input\n // re-renders with the current value after a fetch.\n this.searchValue = this.getActiveFilters().search || '';\n // Show-more visibility derives from collection state at render time.\n this.hasMore = this._computeHasMore();\n\n // WM-033 feedback states — build the skeleton / rich-empty markup for\n // the current render pass (unescaped `{{{…}}}` template slots). Both are\n // empty strings on the default paths, so no-option pages keep the\n // spinner + `emptyMessage` markup exactly.\n this.skeletonHtml = (this.loadingStyle === 'skeleton' && this.loading)\n ? this._buildSkeletonHtml()\n : '';\n this.emptyStateHtml = (this.emptyState && this.isEmpty && !this.loading)\n ? this._buildEmptyStateHtml()\n : '';\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n\n // Mount the optional right-side toolbar slot (e.g. range picker).\n if (this.toolbarRight && !this._toolbarRightMounted) {\n this.toolbarRight.containerId = 'toolbar-right';\n this.addChild(this.toolbarRight);\n await this.toolbarRight.render();\n this._toolbarRightMounted = true;\n }\n // A render() may have replaced the toolbar markup — reset the flag if\n // the previous slot element is gone, so the next render re-mounts.\n if (this._toolbarRightMounted && this.toolbarRight && !this.element?.contains(this.toolbarRight.element)) {\n this._toolbarRightMounted = false;\n }\n\n // Numbered pagination: update status text + re-render the page list.\n if (this.paginated && this.paginationMode === 'pages' && this.collection) {\n const total = this.collection.meta?.count || this.collection.length();\n const start = this.collection.params?.start || 0;\n const size = this.collection.params?.size || 10;\n const end = Math.min(start + size, total);\n\n const startEl = this.element.querySelector('[data-value=\"start\"]');\n const endEl = this.element.querySelector('[data-value=\"end\"]');\n const totalEl = this.element.querySelector('[data-value=\"total\"]');\n if (startEl) startEl.textContent = total === 0 ? 0 : start + 1;\n if (endEl) endEl.textContent = end;\n if (totalEl) totalEl.textContent = total;\n\n const pageSizeSelect = this.element.querySelector('[data-change-action=\"page-size\"]');\n if (pageSizeSelect) pageSizeSelect.value = size;\n\n this.renderPagination();\n }\n\n // Filter pills + native search-clear listener wiring.\n if (this._isToolbarEnabled()) {\n this.updateFilterPills();\n this.renderFilterPresets();\n this.setupSearchClearListener();\n }\n }\n\n // ============================================================\n // Templates\n // ============================================================\n\n /**\n * Bare \"loading | empty | items\" template used when no toolbar / pagination\n * features are enabled. Matches the historical ListView layout exactly so\n * the simple-case API is unchanged.\n * @private\n */\n _defaultBareTemplate() {\n return `\n ${this._hasFeedbackFeature() ? this._buildFeedbackStyles() : ''}\n <div class=\"list-view-container\">\n {{#loading}}\n ${this._loadingContent(`<div class=\"list-loading\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n Loading...\n </div>`)}\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n ${this._emptyContent(`<div class=\"list-empty\">\n {{emptyMessage}}\n </div>`)}\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"list-items\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `;\n }\n\n /**\n * Toolbar / pagination template wrapper. Composes:\n * [ toolbar? ]\n * [ list body (loading | empty | items) ]\n * [ show-more? ]\n * [ pagination? ]\n *\n * Subclasses (TableView) override this to wrap the body in `<table>` markup.\n */\n buildListTemplate() {\n const toolbar = this.buildToolbarTemplate();\n const showMore = this.paginationMode === 'more' ? this.buildShowMoreTemplate() : '';\n const pagination = this.paginationMode === 'pages' ? this.buildPaginationTemplate() : '';\n\n return `\n <div class=\"list-view-wrapper\">\n ${this._hasFeedbackFeature() ? this._buildFeedbackStyles() : ''}\n ${toolbar}\n <div class=\"list-view-container\">\n {{#loading}}\n ${this._loadingContent(`<div class=\"list-loading text-center py-4\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n Loading...\n </div>`)}\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n ${this._emptyContent(`<div class=\"list-empty text-center py-4 text-muted\">\n {{emptyMessage}}\n </div>`)}\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"list-items\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ${showMore}\n ${pagination}\n </div>\n `;\n }\n\n /**\n * @returns {boolean} true when any toolbar feature is enabled.\n * @private\n */\n _isToolbarEnabled() {\n return !!(\n this.title ||\n this.eyebrow ||\n this.searchable ||\n (this.filterable && this._hasAnyFilters()) ||\n (this.sortOptions && this.sortOptions.length > 0) ||\n this.toolbarRight ||\n this.dayRangeFilter ||\n (this.filterPresets && this.filterPresets.length > 0) ||\n (this.toolbarButtons && this.toolbarButtons.length > 0) ||\n this._autoRefreshIndicatorEnabled() ||\n this.options.showAdd ||\n this.options.showExport\n );\n }\n\n /**\n * @returns {boolean} true when the auto-refresh toolbar indicator should\n * render — auto-refresh is active and it wasn't opted out. Gates both the\n * toolbar slot and its style block (off → zero markup, byte-identical).\n * @private\n */\n _autoRefreshIndicatorEnabled() {\n return this._autoRefreshMs > 0 && this.autoRefreshIndicator !== false;\n }\n\n _hasAnyFilters() {\n return (\n (this.filters && Object.keys(this.filters).length > 0) ||\n (this.additionalFilters && this.additionalFilters.length > 0)\n );\n }\n\n /**\n * Render the toolbar shell. Subclasses can override `buildActionButtonsTemplate`\n * to add Add/Export buttons (TableView does this).\n */\n buildToolbarTemplate() {\n if (!this._isToolbarEnabled()) return '';\n\n const titleBlock = this._buildTitleBlockTemplate();\n const rightSlot = this.toolbarRight ? `<div data-container=\"toolbar-right\"></div>` : '';\n const dayRangeSlot = this.dayRangeFilter ? `<div data-container=\"toolbar-day-range\"></div>` : '';\n const sortDropdown = (this.sortOptions && this.sortOptions.length > 0) ? this.buildSortDropdownTemplate() : '';\n\n // Filter presets: ≤4 → inline in the right-group (sibling of day-range);\n // 5+ → its own scrollable row above the filter pills. The container is\n // filled at render time by `renderFilterPresets()` so the derived active\n // state repaints on every fetch (like `updateFilterPills`).\n const hasPresets = this.filterPresets && this.filterPresets.length > 0;\n const presetsInline = hasPresets && this._presetsInline();\n const presetInlineSlot = presetsInline ? `<div data-container=\"filter-presets\" class=\"preset-segment-slot\"></div>` : '';\n const presetRowSlot = (hasPresets && !presetsInline) ? `<div data-container=\"filter-presets\" class=\"preset-scroll mt-3\"></div>` : '';\n const presetStyle = hasPresets ? this._buildPresetStyle() : '';\n\n // Auto-refresh indicator: a quiet muted icon near the action buttons,\n // gated on the option (off → no slot, no style — byte-identical render).\n const showIndicator = this._autoRefreshIndicatorEnabled();\n const indicatorSlot = showIndicator ? this._buildAutoRefreshIndicatorSlot() : '';\n const indicatorStyle = showIndicator ? this._buildAutoRefreshIndicatorStyle() : '';\n\n const rightGroup = `\n <div class=\"d-flex align-items-center gap-2 flex-wrap ${titleBlock ? 'ms-auto' : ''}\">\n ${this.buildActionButtonsTemplate()}\n ${indicatorSlot}\n ${sortDropdown}\n ${this.filterable ? this.buildFilterDropdownTemplate() : ''}\n ${this.searchable && this.searchPlacement === 'toolbar' ? this.buildSearchTemplate() : ''}\n ${presetInlineSlot}\n ${dayRangeSlot}\n ${rightSlot}\n </div>\n `;\n\n return `\n <div class=\"table-action-buttons mb-3\">\n ${presetStyle}\n ${indicatorStyle}\n <div class=\"d-flex align-items-center gap-3 flex-wrap\">\n ${titleBlock}\n ${rightGroup}\n </div>\n ${presetRowSlot}\n <div data-container=\"filter-pills\"></div>\n </div>\n `;\n }\n\n _buildTitleBlockTemplate() {\n if (!this.title && !this.eyebrow) return '';\n // Use Mustache `{{eyebrow}}` / `{{title}}` so setEyebrow / setTitle\n // (and any other code path that mutates these props) survive a\n // re-render — the value is read from the view context at render\n // time, not baked at construction time.\n const eyebrow = `{{#eyebrow}}<div class=\"text-body-secondary text-uppercase small fw-semibold rs-table-eyebrow\" style=\"letter-spacing: 0.05em; line-height: 1.2;\">{{eyebrow}}</div>{{/eyebrow}}`;\n const title = `{{#title}}<h5 class=\"mb-0 rs-table-title\">{{title}}</h5>{{/title}}`;\n return `<div class=\"rs-table-title-block\">${eyebrow}${title}</div>`;\n }\n\n /**\n * Default action buttons: refresh + Add + Export + custom toolbarButtons.\n * Subclasses (TableView) override to inject Fullscreen.\n *\n * Add/Export are gated by `showAdd` / `showExport` and the Add button\n * uses `addForm` / Model.ADD_FORM via `onActionAdd`. Export calls\n * `collection.download(format)` which works on any REST-backed Collection.\n */\n buildActionButtonsTemplate() {\n const buttons = [];\n\n if (this.showRefresh) {\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-refresh\"\n data-action=\"refresh\"\n title=\"Refresh\">\n <i class=\"bi bi-arrow-clockwise\"></i>\n </button>\n `);\n }\n\n if (this.options.showAdd) {\n const addLabel = this.escapeHtml(this.options.addButtonLabel || 'Add');\n const addIcon = this.escapeHtml(this.options.addButtonIcon || 'bi bi-plus-circle');\n buttons.push(`\n <button class=\"btn btn-sm btn-success btn-add\"\n data-action=\"add\"\n title=\"${addLabel}\">\n <i class=\"${addIcon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${addLabel}</span>\n </button>\n `);\n }\n\n if (this.options.showExport) {\n const exportOptions = this.exportOptions || [\n { format: 'csv', label: 'Export as CSV', icon: 'bi bi-file-earmark-spreadsheet' },\n { format: 'json', label: 'Export as JSON', icon: 'bi bi-file-earmark-code' }\n ];\n if (exportOptions.length > 1) {\n const dropdownItems = exportOptions.map((opt) => `\n <li>\n <a class=\"dropdown-item\" href=\"#\" data-action=\"export\"\n data-format=\"${this.escapeHtml(opt.format)}\">\n <i class=\"${this.escapeHtml(opt.icon || 'bi bi-file-earmark-arrow-down')} me-2\"></i>\n ${this.escapeHtml(opt.label)}\n </a>\n </li>\n `).join('');\n buttons.push(`\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\" title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n <ul class=\"dropdown-menu\">${dropdownItems}</ul>\n </div>\n `);\n } else {\n const format = exportOptions.length === 1 ? exportOptions[0].format : 'json';\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-export\"\n data-action=\"export\"\n data-format=\"${this.escapeHtml(format)}\"\n title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n `);\n }\n }\n\n if (this.toolbarButtons && this.toolbarButtons.length > 0) {\n this.toolbarButtons.forEach((button, index) => {\n const {\n label = 'Button',\n icon = '',\n action = '',\n handler = null,\n variant = 'outline-secondary',\n title = label,\n className = '',\n permissions = null\n } = button;\n\n if (permissions && !this.checkPermissions(permissions)) return;\n\n // Escape every dev-supplied field that flows into HTML / attributes —\n // defense in depth. `label` and `title` may legitimately contain\n // text, but never markup; the icon/variant/className/action fields\n // similarly should never carry an attribute-escaping vector.\n const safeIcon = this.escapeHtml(icon);\n const safeLabel = this.escapeHtml(label);\n const safeTitle = this.escapeHtml(title);\n const safeVariant = this.escapeHtml(variant);\n const safeClassName = this.escapeHtml(className);\n const safeAction = this.escapeHtml(action);\n\n const iconHtml = icon ? `<i class=\"${safeIcon} me-1\"></i>` : '';\n const labelHtml = `<span class=\"d-none d-lg-inline\">${safeLabel}</span>`;\n\n let dataAttrs = '';\n if (handler) {\n dataAttrs = `data-action=\"custom-toolbar-button\" data-button-index=\"${index}\"`;\n } else if (action) {\n dataAttrs = `data-action=\"${safeAction}\"`;\n }\n\n const btnClass = `btn btn-sm btn-${safeVariant} ${safeClassName}`.trim();\n\n buttons.push(`\n <button class=\"${btnClass}\" ${dataAttrs} title=\"${safeTitle}\">\n ${iconHtml}${labelHtml}\n </button>\n `);\n });\n }\n\n return buttons.join('');\n }\n\n buildSearchTemplate() {\n return `\n <div class=\"flex-grow-1\" style=\"max-width: 400px;\">\n <div class=\"input-group input-group-sm\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input type=\"search\"\n class=\"form-control\"\n placeholder=\"${this.escapeHtml(this.searchPlaceholder)}\"\n data-filter=\"search\"\n data-change-action=\"apply-search\"\n value=\"{{searchValue}}\"\n aria-label=\"Search\">\n {{#searchValue}}\n <button class=\"btn btn-outline-secondary\" type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\">\n <i class=\"bi bi-x\"></i>\n </button>\n {{/searchValue}}\n </div>\n </div>\n `;\n }\n\n buildSortDropdownTemplate() {\n const currentSort = this.collection?.params?.sort || '';\n const items = this.sortOptions.map(opt => {\n const dir = opt.dir === 'desc' ? '-' : '';\n const value = `${dir}${opt.key}`;\n const isActive = currentSort === value;\n return `\n <li><a class=\"dropdown-item ${isActive ? 'active' : ''}\" href=\"#\"\n data-action=\"sort-option\" data-sort=\"${this.escapeHtml(value)}\">\n ${this.escapeHtml(opt.label || opt.key)}\n </a></li>\n `;\n }).join('');\n\n const clearItem = currentSort ? `\n <li><hr class=\"dropdown-divider\"></li>\n <li><a class=\"dropdown-item\" href=\"#\" data-action=\"sort-option\" data-sort=\"\">\n <i class=\"bi bi-x-circle me-1\"></i>Clear sort\n </a></li>\n ` : '';\n\n return `\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"bi bi-sort-down me-1\"></i>\n <span class=\"d-none d-lg-inline\">Sort</span>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n ${items}\n ${clearItem}\n </ul>\n </div>\n `;\n }\n\n buildFilterDropdownTemplate() {\n if (!this._hasAnyFilters()) return '';\n return `\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"bi bi-filter me-1\"></i>\n <span class=\"d-none d-lg-inline\">Add Filter</span>\n </button>\n <div class=\"dropdown-menu\" style=\"min-width: 250px;\">\n ${this.buildFilterList()}\n </div>\n </div>\n `;\n }\n\n buildFilterList() {\n const allFilters = this.getAllAvailableFilters();\n const activeFilters = this.getActiveFilters();\n\n if (allFilters.length === 0) {\n return '<div class=\"dropdown-item-text text-muted\">No filters available</div>';\n }\n\n const filterItems = allFilters.map(filter => {\n const isActive = Object.prototype.hasOwnProperty.call(activeFilters, filter.key);\n const activeClass = isActive ? 'active' : '';\n const icon = this.getFilterIcon(filter.type || filter.config?.type);\n\n return `\n <button class=\"dropdown-item ${activeClass}\"\n data-action=\"add-filter\"\n data-filter-key=\"${this.escapeHtml(filter.key)}\">\n <i class=\"bi bi-${this.escapeHtml(icon)} me-2\"></i>\n ${this.escapeHtml(filter.label || filter.key)}\n ${isActive ? '<i class=\"bi bi-check-circle ms-auto\"></i>' : ''}\n </button>\n `;\n }).join('');\n\n return `\n ${filterItems}\n ${Object.keys(activeFilters).length > 0 ? `\n <div class=\"dropdown-divider\"></div>\n <button class=\"dropdown-item text-danger\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-2\"></i>Clear All Filters\n </button>\n ` : ''}\n `;\n }\n\n buildActivePills() {\n // WM-033: the result-count summary rides in the filter-pills row, before\n // the pills. It's '' when showResultCount is off, so the pills markup is\n // byte-identical for pages that don't opt in.\n const countHtml = this.showResultCount ? this._buildResultCountSummary() : '';\n\n if (this.hideActivePills) {\n return countHtml ? this._wrapPillRow(countHtml) : '';\n }\n\n const activeFilters = this.getActiveFilters();\n const hasSearch = activeFilters.search && activeFilters.search.toString().trim() !== '';\n let filterEntries = Object.entries(activeFilters).filter(([key, value]) =>\n value && value.toString().trim() !== '' && key !== 'search'\n );\n\n if (this.hideActivePillNames && this.hideActivePillNames.length > 0) {\n filterEntries = filterEntries.filter(([key]) => !this.hideActivePillNames.includes(key));\n }\n\n if (filterEntries.length === 0 && !hasSearch) {\n return countHtml ? this._wrapPillRow(countHtml) : '';\n }\n\n const pills = filterEntries.map(([paramKey, value]) => {\n const { field } = parseFilterKey(paramKey);\n const label = this.getFilterLabel(field);\n // formatFilterDisplay interpolates the user's raw filter value, so\n // escape its return before injecting into innerHTML. paramKey is\n // attribute-escaped because it ends up in `data-filter`.\n const displayText = this.escapeHtml(formatFilterDisplay(paramKey, value, label));\n const safeParamKey = this.escapeHtml(paramKey);\n const icon = 'filter';\n\n // The framework's `.badge.bg-primary` is a subtle pill (light bg\n // + emphasis-text color) — `text-white` and `btn-close-white`\n // would render invisible. Use plain `btn-link` which inherits the\n // badge's own foreground color, and the default `.btn-close`\n // (no -white modifier) which renders dark on the light pill.\n return `\n <span class=\"badge bg-primary me-1 mb-1 py-1 px-2 position-relative\" style=\"font-size: 0.75rem;\">\n <i class=\"bi bi-${icon} me-1\" style=\"font-size: 0.65rem;\"></i>\n\n <button type=\"button\" class=\"btn btn-link p-0 ms-1 list-filter-pill-text\"\n style=\"font-size: 0.65rem; line-height: 1; text-decoration: none;\"\n data-action=\"edit-filter\"\n data-filter=\"${safeParamKey}\"\n title=\"Edit filter\">\n ${displayText}\n </button>\n\n <button type=\"button\" class=\"btn-close ms-1\"\n style=\"font-size: 0.6rem; width: 0.5rem; height: 0.5rem;\"\n data-action=\"remove-filter\"\n data-filter=\"${safeParamKey}\"\n title=\"Remove filter\">\n </button>\n </span>\n `;\n }).join('');\n\n const showClearAll = filterEntries.length > 1 || (filterEntries.length > 0 && hasSearch) || (filterEntries.length === 0 && hasSearch);\n const clearAllButton = showClearAll ? `\n <button class=\"btn btn-sm btn-outline-secondary mb-1 py-0 px-2\" style=\"font-size: 0.75rem;\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-1\" style=\"font-size: 0.7rem;\"></i>\n <small>Clear All</small>\n </button>\n ` : '';\n\n return `\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"d-flex flex-wrap align-items-center\">\n ${countHtml}${pills}\n ${clearAllButton}\n </div>\n </div>\n </div>\n `;\n }\n\n /**\n * Wrap arbitrary inner markup in the standard active-pills row shell. Used\n * for the count-only case (WM-033) where the result-count summary renders\n * without any filter pills.\n * @private\n */\n _wrapPillRow(inner) {\n return `\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"d-flex flex-wrap align-items-center\">\n ${inner}\n </div>\n </div>\n </div>\n `;\n }\n\n buildPaginationTemplate() {\n return `\n <div class=\"table-status-bar mt-3\">\n <div class=\"d-flex flex-column flex-lg-row justify-content-center justify-content-lg-between align-items-center gap-3\">\n <div class=\"d-flex flex-column flex-sm-row align-items-center gap-2 gap-sm-3 text-center text-lg-start\">\n <span class=\"text-muted\">\n Showing <span data-value=\"start\">0</span> to <span data-value=\"end\">0</span>\n of <span data-value=\"total\">0</span> entries\n </span>\n <div class=\"d-flex align-items-center\">\n <label class=\"form-label me-2 mb-0\">Show:</label>\n <select class=\"form-select form-select-sm\" style=\"width: auto;\" data-change-action=\"page-size\">\n <option value=\"5\">5</option>\n <option value=\"10\">10</option>\n <option value=\"25\">25</option>\n <option value=\"50\">50</option>\n <option value=\"100\">100</option>\n </select>\n </div>\n </div>\n <nav aria-label=\"List pagination\">\n <ul class=\"pagination pagination-sm mb-0 justify-content-center\" data-container=\"pagination\"></ul>\n </nav>\n </div>\n </div>\n `;\n }\n\n buildShowMoreTemplate() {\n return `\n {{#hasMore}}\n <div class=\"list-show-more-row text-center mt-3\">\n <button class=\"btn btn-outline-secondary btn-sm\" data-action=\"show-more\"\n {{#loadingMore}}disabled{{/loadingMore}}>\n {{#loadingMore}}\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\" aria-hidden=\"true\"></span>\n Loading…\n {{/loadingMore}}\n {{^loadingMore}}\n <i class=\"bi bi-arrow-down-circle me-1\"></i>Show more\n {{/loadingMore}}\n </button>\n </div>\n {{/hasMore}}\n `;\n }\n\n // ============================================================\n // Feedback states (WM-033) — rich empty, skeletons, result count\n // ============================================================\n\n /**\n * Normalize the `emptyState:` option into `{ icon, title, message, action }`\n * or null when absent. `action` is `{ label, action, icon }` where `action`\n * is a kebab-case `data-action` name (routed like any toolbar action, e.g.\n * `'add'` → onActionAdd). Returns null for a non-object so the feature stays\n * inert and `emptyMessage` remains the fallback.\n * @private\n */\n _normalizeEmptyState(raw) {\n if (!raw || typeof raw !== 'object') return null;\n let action = null;\n if (raw.action && typeof raw.action === 'object' && raw.action.action) {\n action = {\n label: raw.action.label || 'Action',\n action: raw.action.action,\n icon: raw.action.icon || null\n };\n }\n return {\n icon: raw.icon || 'inbox',\n title: raw.title || null,\n message: raw.message || null,\n action\n };\n }\n\n /**\n * True when any of the three opt-in feedback features is enabled. Gates the\n * one-time `<style>` block emission so opt-out pages carry zero extra CSS.\n * @private\n */\n _hasFeedbackFeature() {\n return !!this.emptyState || this.loadingStyle === 'skeleton' || this.showResultCount;\n }\n\n /**\n * Loading-block content for the body template. Returns the unescaped\n * skeleton slot when `loadingStyle: 'skeleton'`, otherwise the caller's\n * default spinner markup verbatim (byte-identical opt-out).\n * @private\n */\n _loadingContent(defaultMarkup) {\n return this.loadingStyle === 'skeleton' ? '{{{skeletonHtml}}}' : defaultMarkup;\n }\n\n /**\n * Empty-block content for the body template. Returns the unescaped rich\n * empty-state slot when `emptyState` is configured, otherwise the caller's\n * default `emptyMessage` markup verbatim (byte-identical opt-out).\n * @private\n */\n _emptyContent(defaultMarkup) {\n return this.emptyState ? '{{{emptyStateHtml}}}' : defaultMarkup;\n }\n\n /**\n * Skeleton row count: `collection.params.size` capped at 8, defaulting to 5\n * when size isn't set. Shared by the ListView (card) and TableView (row)\n * skeleton builders.\n * @private\n */\n _skeletonRowCount() {\n const size = this.collection?.params?.size;\n const n = (typeof size === 'number' && size > 0) ? size : 5;\n return Math.min(n, 8);\n }\n\n /**\n * ListView skeleton — stacked card silhouettes. TableView overrides this to\n * emit a column-matched `<table>`. Called from onBeforeRender only while\n * loading + `loadingStyle: 'skeleton'`.\n * @private\n */\n _buildSkeletonHtml() {\n const rows = this._skeletonRowCount();\n let cards = '';\n for (let i = 0; i < rows; i++) {\n cards += '<div class=\"list-skeleton-card\">'\n + '<span class=\"skeleton-line w-40\"></span>'\n + '<span class=\"skeleton-line w-90\"></span>'\n + '<span class=\"skeleton-line w-60\"></span>'\n + '</div>';\n }\n return `<div class=\"list-skeleton\" aria-hidden=\"true\">${cards}</div>`;\n }\n\n /**\n * Build the rich empty-state panel. Branches on `getActiveFilters()`:\n * non-empty → the filtered variant (\"No results match your filters\" +\n * Clear filters button reusing onActionClearAllFilters); empty → the\n * configured truly-empty content with its optional CTA. Search counts as\n * an active filter. Returns '' when `emptyState` isn't configured.\n * @private\n */\n _buildEmptyStateHtml() {\n const es = this.emptyState;\n if (!es) return '';\n\n const activeCount = Object.keys(this.getActiveFilters()).length;\n const isFiltered = activeCount > 0;\n\n let icon;\n let title;\n let message;\n let buttonHtml;\n\n if (isFiltered) {\n icon = 'funnel';\n title = 'No results match your filters';\n const plural = activeCount === 1 ? 'filter' : 'filters';\n message = `No items match the ${activeCount} active ${plural}. Try widening or removing a filter.`;\n buttonHtml = '<button class=\"btn btn-outline-primary\" data-action=\"clear-all-filters\">'\n + '<i class=\"bi bi-x-circle me-1\"></i>Clear filters</button>';\n } else {\n icon = es.icon || 'inbox';\n title = es.title || this.emptyMessage;\n message = es.message || '';\n if (es.action) {\n const iconHtml = es.action.icon\n ? `<i class=\"bi ${this.escapeHtml(es.action.icon)} me-1\"></i>`\n : '';\n buttonHtml = `<button class=\"btn btn-primary\" data-action=\"${this.escapeHtml(es.action.action)}\">`\n + `${iconHtml}${this.escapeHtml(es.action.label)}</button>`;\n } else {\n buttonHtml = '';\n }\n }\n\n const titleHtml = title ? `<div class=\"empty-state-title h6\">${this.escapeHtml(title)}</div>` : '';\n const messageHtml = message ? `<p class=\"empty-state-message\">${this.escapeHtml(message)}</p>` : '';\n\n return `\n <div class=\"table-empty-state\">\n <span class=\"empty-state-icon\"><i class=\"bi bi-${this.escapeHtml(icon)}\"></i></span>\n ${titleHtml}\n ${messageHtml}\n ${buttonHtml}\n </div>\n `;\n }\n\n /**\n * Build the \"Showing N of M\" result-count summary from `collection.meta`\n * (`count`) and the current loaded length. Appends a \"· filtered\" marker\n * when any filter is active. Returns '' when `showResultCount` is off or the\n * total count isn't known yet. Rendered inside the filter-pills row by\n * `buildActivePills`.\n * @private\n */\n _buildResultCountSummary() {\n if (!this.showResultCount || !this.collection) return '';\n const meta = this.collection.meta || {};\n const total = typeof meta.count === 'number' ? meta.count : null;\n if (total === null) return '';\n\n const shown = this.collection.length();\n const isFiltered = Object.keys(this.getActiveFilters()).length > 0;\n const fmt = (n) => Number(n).toLocaleString();\n const filteredSuffix = isFiltered\n ? ' <span class=\"count-filtered\">· filtered</span>'\n : '';\n\n return `<span class=\"result-count-summary me-2 mb-1\">`\n + `Showing <strong>${fmt(shown)}</strong> of <strong>${fmt(total)}</strong>${filteredSuffix}</span>`;\n }\n\n /**\n * One-time `<style>` block for the three feedback features (skeleton\n * shimmer, empty-state chip, result-count summary). Token-based so both\n * themes track automatically; the shimmer's moving band is a component tint\n * (black-on-light) with its rgba(255,255,255) dark companion, and the\n * animation is disabled under prefers-reduced-motion. Emitted once per\n * template, only when `_hasFeedbackFeature()`.\n * @private\n */\n _buildFeedbackStyles() {\n return `\n <style>\n .table-empty-state { text-align: center; padding: 3rem 1.5rem; }\n .table-empty-state .empty-state-icon {\n display: inline-flex; align-items: center; justify-content: center;\n width: 4rem; height: 4rem; border-radius: 50%;\n margin-bottom: 1rem; font-size: 1.75rem;\n background: var(--bs-secondary-bg);\n color: var(--bs-secondary-color);\n border: 1px solid var(--bs-border-color);\n }\n .table-empty-state .empty-state-title {\n font-weight: 600; color: var(--bs-emphasis-color); margin-bottom: 0.35rem;\n }\n .table-empty-state .empty-state-message {\n color: var(--bs-secondary-color); max-width: 26rem; margin: 0 auto 1.25rem;\n }\n\n .skeleton-line {\n display: block; height: 0.85rem; border-radius: 0.35rem;\n background-color: var(--bs-secondary-bg);\n background-image: linear-gradient(90deg,\n rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.06) 50%, rgba(0, 0, 0, 0) 100%);\n background-size: 200% 100%; background-repeat: no-repeat;\n animation: mojo-skeleton-shimmer 1.4s ease-in-out infinite;\n }\n [data-bs-theme=\"dark\"] .skeleton-line {\n background-image: linear-gradient(90deg,\n rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0) 100%);\n }\n .skeleton-line.w-90 { width: 90%; }\n .skeleton-line.w-75 { width: 75%; }\n .skeleton-line.w-60 { width: 60%; }\n .skeleton-line.w-40 { width: 40%; }\n .skeleton-line.w-25 { width: 25%; }\n .skeleton-line.skeleton-pill { height: 1.35rem; width: 4rem; border-radius: 999px; }\n .list-skeleton-card {\n display: flex; flex-direction: column; gap: 0.5rem;\n padding: 0.85rem 1rem; margin-bottom: 0.5rem;\n border: 1px solid var(--bs-border-color-translucent);\n border-radius: 0.5rem;\n }\n @keyframes mojo-skeleton-shimmer {\n 0% { background-position: 150% 0; }\n 100% { background-position: -50% 0; }\n }\n @media (prefers-reduced-motion: reduce) {\n .skeleton-line { animation: none; }\n }\n\n .result-count-summary { font-size: 0.8rem; color: var(--bs-secondary-color); }\n .result-count-summary strong { color: var(--bs-emphasis-color); font-weight: 600; }\n .result-count-summary .count-filtered { color: var(--bs-primary); }\n </style>\n `;\n }\n\n // ============================================================\n // Collection wiring (existing + persistSelection-aware)\n // ============================================================\n\n _initCollection(collectionOrClass) {\n if (!collectionOrClass) {\n console.log('Collection not provided');\n return;\n }\n if (collectionOrClass instanceof Collection) {\n this.setCollection(collectionOrClass);\n } else if (typeof collectionOrClass === 'function') {\n const collection = new collectionOrClass();\n this.setCollection(collection);\n } else if (Array.isArray(collectionOrClass)) {\n const collection = new Collection(collectionOrClass);\n this.setCollection(collection);\n }\n }\n\n // ============================================================\n // Day-range filter helper\n // ============================================================\n\n _normalizeDayRangeFilter(raw) {\n if (!raw) return null;\n const defaults = {\n field: 'created',\n value: '7d',\n options: [\n { value: '1d', label: '1d' },\n { value: '7d', label: '7d' },\n { value: '30d', label: '30d' },\n { value: '90d', label: '90d' }\n ],\n ariaLabel: 'Time range'\n };\n if (raw === true) return defaults;\n return { ...defaults, ...raw };\n }\n\n _dayRangeDays(value) {\n const m = /^(\\d+)d$/.exec(String(value || ''));\n return m ? parseInt(m[1], 10) : null;\n }\n\n _seedDayRangeParams() {\n if (!this.dayRangeFilter || !this.collection) return;\n const days = this._dayRangeDays(this.dayRangeFilter.value);\n if (days == null) return;\n const epoch = Math.floor(Date.now() / 1000) - days * 86400;\n this.collection.params[`${this.dayRangeFilter.field}__gte`] = epoch;\n }\n\n async _onDayRangeChange({ value, previous }) {\n const field = this.dayRangeFilter?.field || 'created';\n const days = this._dayRangeDays(value);\n let params = {};\n\n if (days != null && this.collection) {\n const epoch = Math.floor(Date.now() / 1000) - days * 86400;\n this.collection.params[`${field}__gte`] = epoch;\n this.collection.params.start = 0;\n params = { [`${field}__gte`]: epoch };\n }\n\n this.emit('range:change', { field, value, previous, params });\n this.emit('params-changed');\n\n if (this.collection?.restEnabled) {\n try {\n // fetch:end already triggers a render via _onFetchEnd, so no\n // trailing render() here — calling one would revert any\n // setEyebrow / setTitle updates a `range:change` listener made\n // (the toolbar template is baked at construction time).\n await this.collection.fetch();\n } catch (err) {\n console.error('Failed to fetch day-range data:', err);\n await this.render();\n }\n } else {\n await this.render();\n }\n }\n\n /**\n * Get the currently-selected day-range value, or null when the helper\n * is disabled / not yet initialized.\n * @returns {string|null}\n */\n getRange() {\n return this.dayRangeControl?.getValue() ?? null;\n }\n\n /**\n * Programmatically set the day-range value. Returns false when the helper\n * is disabled or the value isn't a known option. Pass `silent: true` to\n * suppress the `range:change` event and the refetch.\n * @param {string} value\n * @param {{ silent?: boolean }} opts\n * @returns {boolean}\n */\n setRange(value, { silent = false } = {}) {\n if (!this.dayRangeControl) return false;\n const previous = this.dayRangeControl.getValue();\n const ok = this.dayRangeControl.setValue(value, { silent: true });\n if (!ok) return false;\n if (!silent) this._onDayRangeChange({ value, previous });\n return true;\n }\n\n // ============================================================\n // Filter presets (compact joined btn-group segment)\n //\n // Author-defined \"common queries\" bundled behind a toolbar segment.\n // Mutual-exclusion + toggle-off only; active state is derived from\n // strict param matching against getActiveFilters(). The `'@me'` token\n // in a preset's params resolves to the active user's id at apply AND\n // match time (skipped when no active user).\n // ============================================================\n\n /**\n * Normalize the `filterPresets:` option into a clean array of\n * `{ key, label, icon, params, description }`. Drops entries without a\n * `key`, warns on (and drops) duplicate keys. Returns `[]` when the\n * option is absent / empty so the whole feature stays inert.\n * @private\n */\n _normalizeFilterPresets(raw) {\n if (!Array.isArray(raw) || raw.length === 0) return [];\n const seen = new Set();\n const presets = [];\n raw.forEach((p) => {\n if (!p || !p.key) return;\n if (seen.has(p.key)) {\n console.warn(`ListView: duplicate filterPresets key '${p.key}' — ignoring the later one.`);\n return;\n }\n seen.add(p.key);\n presets.push({\n key: p.key,\n label: p.label || p.key,\n icon: p.icon || null,\n params: (p.params && typeof p.params === 'object') ? p.params : {},\n description: p.description || null\n });\n });\n return presets;\n }\n\n /**\n * Placement branch: ≤4 presets render inline (sibling of the day-range\n * segment); 5+ get their own scrollable row above the filter pills.\n * @private\n */\n _presetsInline() {\n return this.filterPresets.length <= 4;\n }\n\n _getPreset(key) {\n return this.filterPresets.find((p) => p.key === key) || null;\n }\n\n /**\n * Resolve a preset's raw params into concrete filter values. The `'@me'`\n * token resolves to the active user's id; when no active user can be\n * resolved that param is skipped (both at apply and match time so the\n * derived active state stays consistent).\n * @private\n */\n _resolvePresetParams(preset) {\n const out = {};\n const raw = preset?.params || {};\n Object.keys(raw).forEach((key) => {\n const value = raw[key];\n if (value === '@me') {\n const uid = this.getApp()?.activeUser?.id;\n if (uid === undefined || uid === null) return; // skip — no active user\n out[key] = uid;\n } else {\n out[key] = value;\n }\n });\n return out;\n }\n\n /**\n * A preset is active when every one of its resolved params strictly\n * matches the current active filters. Matching goes through\n * getActiveFilters() (not raw collection.params) so `__in` collapsing is\n * honored. A preset whose params all resolve away (e.g. only `'@me'`\n * with no active user) never matches.\n * @private\n */\n _presetMatches(preset) {\n const resolved = this._resolvePresetParams(preset);\n const keys = Object.keys(resolved);\n if (keys.length === 0) return false;\n const active = this.getActiveFilters();\n return keys.every((key) => {\n const value = resolved[key];\n if (Array.isArray(value)) {\n if (value.length === 0) return true;\n const { field } = parseFilterKey(key);\n if (value.length === 1) {\n return active[field] !== undefined && String(active[field]) === String(value[0]);\n }\n const inVal = active[`${field}__in`];\n return inVal !== undefined && String(inVal) === value.join(',');\n }\n return active[key] !== undefined && String(active[key]) === String(value);\n });\n }\n\n /**\n * Null out every resolved param key belonging to a preset (used by\n * toggle-off and by mutual-exclusion when switching presets).\n * @private\n */\n _clearPresetParams(preset) {\n const resolved = this._resolvePresetParams(preset);\n Object.keys(resolved).forEach((key) => this.setFilter(key, null));\n }\n\n /**\n * Get the currently-active preset's full config object, or null. Active\n * state is derived — no stored \"active preset\" field to drift.\n *\n * Most-specific-match-wins: when several presets match at once (e.g. one\n * preset's params are a strict subset of another's — Errors `{level__gte:4}`\n * ⊂ Auth `{path__icontains:'/auth', level__gte:4}`), the one with the MOST\n * resolved param keys is returned so the correct (narrower) chip highlights\n * and mutual-exclusion/toggle logic reads it. Ties resolve to the first\n * matching preset in array order.\n * @returns {object|null}\n */\n getActivePreset() {\n if (!this.filterPresets || this.filterPresets.length === 0) return null;\n let best = null;\n let bestCount = -1;\n for (const preset of this.filterPresets) {\n if (!this._presetMatches(preset)) continue;\n const count = Object.keys(this._resolvePresetParams(preset)).length;\n if (count > bestCount) { // strict `>` → ties keep the earlier preset\n best = preset;\n bestCount = count;\n }\n }\n return best;\n }\n\n /**\n * Apply a preset by key. Toggles off if it's already the active preset;\n * otherwise clears the previously-active preset's params (mutual\n * exclusion), applies the new bundle, refetches, and repaints.\n * @param {string} key\n * @returns {Promise<boolean>} false when the key is unknown.\n */\n async applyPreset(key) {\n const preset = this._getPreset(key);\n if (!preset) return false;\n\n const active = this.getActivePreset();\n\n // Toggle-off: re-clicking the active preset clears it.\n if (active && active.key === preset.key) {\n await this.clearPreset();\n return true;\n }\n\n // Mutual exclusion: drop the previously-active preset's params first.\n if (active) this._clearPresetParams(active);\n\n const resolved = this._resolvePresetParams(preset);\n Object.keys(resolved).forEach((k) => this.setFilter(k, resolved[k]));\n\n await this.applyFilters(); // start = 0 + fetch + pills + params-changed\n this.renderFilterPresets(); // repaint derived active state\n this.emit('preset:change', { key: preset.key, params: resolved });\n return true;\n }\n\n /**\n * Clear the active preset (removes its params). No-op when nothing is\n * active, but still repaints and emits `preset:change` with null.\n * @returns {Promise<void>}\n */\n async clearPreset() {\n const active = this.getActivePreset();\n if (active) {\n this._clearPresetParams(active);\n await this.applyFilters();\n }\n this.renderFilterPresets();\n this.emit('preset:change', null);\n }\n\n /**\n * Toolbar action — apply/toggle a preset from its segment button.\n */\n async onActionApplyPreset(event, element) {\n if (event && typeof event.preventDefault === 'function') event.preventDefault();\n const key = element?.getAttribute('data-preset-key');\n if (!key) return;\n await this.applyPreset(key);\n }\n\n /**\n * Fill the `filter-presets` container with the joined segment, painting\n * the derived-active button as `btn-primary`. innerHTML re-render pattern\n * mirroring `updateFilterPills`. No-op when presets aren't configured or\n * the container isn't in the DOM yet.\n */\n renderFilterPresets() {\n if (!this.filterPresets || this.filterPresets.length === 0) return;\n const container = this.element?.querySelector('[data-container=\"filter-presets\"]');\n if (!container) return;\n container.innerHTML = this._buildPresetSegment();\n }\n\n /**\n * Build the joined `btn-group btn-sm preset-segment` markup with the\n * active button styled `btn-primary` (derived from param matching).\n * @private\n */\n _buildPresetSegment() {\n const active = this.getActivePreset();\n const activeKey = active ? active.key : null;\n\n const buttons = this.filterPresets.map((preset) => {\n const isActive = preset.key === activeKey;\n const cls = isActive ? 'btn btn-primary' : 'btn btn-outline-secondary';\n const icon = preset.icon ? `<i class=\"bi ${this.escapeHtml(preset.icon)}\"></i> ` : '';\n const titleAttr = preset.description ? ` title=\"${this.escapeHtml(preset.description)}\"` : '';\n return `<button type=\"button\"\n class=\"${cls}\"\n data-action=\"apply-preset\"\n data-preset-key=\"${this.escapeHtml(preset.key)}\"\n aria-pressed=\"${isActive ? 'true' : 'false'}\"${titleAttr}>${icon}${this.escapeHtml(preset.label)}</button>`;\n }).join('');\n\n return `<div class=\"btn-group btn-group-sm preset-segment\" role=\"group\" aria-label=\"Filter presets\">${buttons}</div>`;\n }\n\n /**\n * Inline `<style>` for the preset segment. Token-based (auto-tracks both\n * themes); the single scrollbar-thumb literal carries its dark companion.\n * Emitted once from the toolbar template (only when presets exist).\n * @private\n */\n _buildPresetStyle() {\n return `\n <style>\n .preset-segment .btn {\n --bs-btn-padding-y: 0.25rem;\n --bs-btn-padding-x: 0.6rem;\n font-size: 0.8125rem;\n font-weight: 500;\n }\n .preset-segment .btn .bi { font-size: 0.8rem; }\n\n /* Touch: bump the tap target only on coarse pointers — desktop stays compact. */\n @media (pointer: coarse) {\n .preset-segment .btn {\n --bs-btn-padding-y: 0.5rem;\n --bs-btn-padding-x: 0.85rem;\n }\n }\n\n /* 5+ presets: own row, scroll horizontally instead of wrapping. */\n .preset-scroll {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n padding-bottom: 0.2rem;\n }\n .preset-scroll .btn-group { flex-wrap: nowrap; }\n .preset-scroll .btn { white-space: nowrap; }\n .preset-scroll::-webkit-scrollbar { height: 6px; }\n .preset-scroll::-webkit-scrollbar-thumb {\n background: rgba(0, 0, 0, 0.15);\n border-radius: 999px;\n }\n [data-bs-theme=\"dark\"] .preset-scroll::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.12);\n }\n </style>\n `;\n }\n\n /**\n * Compact, quiet auto-refresh indicator for the toolbar right-group: a\n * muted `bi-arrow-repeat` icon with no button chrome and no text label. The\n * icon is `aria-hidden`; a visually-hidden span carries the text\n * alternative for assistive tech. `_pulseAutoRefreshIndicator()` adds the\n * `is-refreshing` class on each successful tick to fire a brief spin.\n * @private\n */\n _buildAutoRefreshIndicatorSlot() {\n const secs = Math.round(this._autoRefreshMs / 1000);\n const label = `Auto-refresh: ${secs}s`;\n return `<span class=\"mojo-autorefresh-indicator\" data-autorefresh-indicator title=\"${this.escapeHtml(label)}\">\n <i class=\"bi bi-arrow-repeat\" aria-hidden=\"true\"></i>\n <span class=\"visually-hidden\">${this.escapeHtml(label)}</span>\n </span>`;\n }\n\n /**\n * Inline `<style>` for the auto-refresh indicator. Muted via\n * `--bs-secondary-color` (auto-tracks both themes — no literals). The spin\n * animation only plays while `is-refreshing` is set and is suppressed under\n * `prefers-reduced-motion`. Emitted once, only when the indicator renders.\n * @private\n */\n _buildAutoRefreshIndicatorStyle() {\n return `\n <style>\n .mojo-autorefresh-indicator {\n display: inline-flex;\n align-items: center;\n color: var(--bs-secondary-color);\n font-size: 0.9rem;\n line-height: 1;\n }\n .mojo-autorefresh-indicator .bi { display: block; }\n .mojo-autorefresh-indicator.is-refreshing .bi {\n animation: mojo-autorefresh-spin 0.6s ease;\n }\n @keyframes mojo-autorefresh-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n @media (prefers-reduced-motion: reduce) {\n .mojo-autorefresh-indicator.is-refreshing .bi { animation: none; }\n }\n </style>\n `;\n }\n\n /**\n * Flash the toolbar auto-refresh indicator: add the `is-refreshing` class\n * (fires the CSS spin) and strip it after ~600ms. Coalesces rapid ticks by\n * resetting its own timer; a no-op when the indicator isn't in the DOM.\n * @private\n */\n _pulseAutoRefreshIndicator() {\n const el = this.element?.querySelector('[data-autorefresh-indicator]');\n if (!el) return;\n el.classList.add('is-refreshing');\n if (this._autoRefreshPulseTimer) clearTimeout(this._autoRefreshPulseTimer);\n this._autoRefreshPulseTimer = setTimeout(() => {\n el.classList.remove('is-refreshing');\n this._autoRefreshPulseTimer = null;\n }, 600);\n }\n\n setCollection(collection) {\n if (this.collection === collection) return this;\n\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n\n this.collection = collection;\n\n if (this.options.defaultQuery && !this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.defaultQuery };\n }\n if (this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.collectionParams };\n }\n if (this.options.pageSize && this.collection) {\n this.collection.params = { ...this.collection.params, size: this.options.pageSize };\n }\n\n if (this.collection) {\n this.collection.on('add', this._onModelsAdded, this);\n this.collection.on('remove', this._onModelsRemoved, this);\n this.collection.on('reset', this._onCollectionReset, this);\n this.collection.on('fetch:start', this._onFetchStart, this);\n this.collection.on('fetch:end', this._onFetchEnd, this);\n\n if (this.collection.restEnabled && !this.collection.lastFetchTime && !this.collection.options?.preloaded) {\n this.loading = true;\n } else {\n this._buildItems();\n }\n }\n\n return this;\n }\n\n async _renderChildren() {\n await super._renderChildren();\n const itemsContainer = this.getChildElement('items');\n if (!itemsContainer) return;\n // Render each item synchronously so its onAfterRender (which wires\n // the clickable handler, etc.) has fully run by the time the parent's\n // render() resolves. Items are kept in the `itemViews` Map, not the\n // standard child registry, so we drive their render directly.\n //\n // When grouping is configured, walk the precomputed `_renderOrder` to\n // interleave header views with item views in the correct DOM order.\n // Otherwise, fall through to the lighter forEachItem walk (zero\n // behavior change for non-grouped consumers).\n const renders = [];\n if (this._renderOrder.length > 0) {\n for (const entry of this._renderOrder) {\n itemsContainer.appendChild(entry.view.element);\n renders.push(Promise.resolve(entry.view.render(false)).catch(() => {}));\n }\n } else {\n this.forEachItem((item) => {\n itemsContainer.appendChild(item.element);\n renders.push(Promise.resolve(item.render(false)).catch(() => {}));\n });\n }\n await Promise.all(renders);\n }\n\n _buildItems() {\n this._clearItems();\n\n if (!this.collection || this.collection.isEmpty()) {\n this.isEmpty = true;\n this.emit('list:empty');\n return;\n }\n\n this.isEmpty = false;\n this.collection.forEach((model, index) => {\n this._createItemView(model, index);\n });\n this._applyPersistedSelections();\n this._buildGroupHeaders();\n\n this.emit('list:loaded', { count: this.collection.length() });\n\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Walk the collection in render order, run `groupBy`, and build the\n * `_renderOrder` array of `{ type: 'item' | 'header', view }` entries.\n *\n * No-op when `groupBy` isn't configured — `_renderChildren` falls back to\n * the lighter `forEachItem` walk for non-grouped consumers.\n *\n * Falsy resolver returns are treated as \"ungrouped tail\" — the item is\n * pushed without emitting a header, so the prior group's section\n * continues visually.\n *\n * @private\n */\n _buildGroupHeaders() {\n this.groupHeaderViews.forEach((headerView) => this.removeChild(headerView.id));\n this.groupHeaderViews.clear();\n this._renderOrder.length = 0;\n\n if (!this.groupBy || !this.collection || this.collection.isEmpty()) return;\n\n const resolver = typeof this.groupBy === 'function'\n ? this.groupBy\n : (model) => model.get(this.groupBy);\n\n let prevKey;\n let prevKeySet = false;\n\n this.collection.forEach((model, index) => {\n const itemView = this.itemViews.get(model.id);\n if (!itemView) return;\n\n let rawKey;\n try {\n rawKey = resolver(model);\n } catch (err) {\n console.warn('ListView: groupBy resolver threw — treating as ungrouped tail', err);\n rawKey = null;\n }\n\n if (rawKey && (!prevKeySet || rawKey !== prevKey)) {\n const headerView = this._createGroupHeaderView(model, rawKey, index);\n this._renderOrder.push({ type: 'header', view: headerView });\n prevKey = rawKey;\n prevKeySet = true;\n }\n this._renderOrder.push({ type: 'item', view: itemView });\n });\n }\n\n /**\n * Build (or rebuild) the group-header markers. Public-ish hook called by\n * `_onModelsAdded` after Show More appends, so the appended page's first\n * item gets (or doesn't get) a header against the prior tail correctly.\n * @private\n */\n _rebuildGroupHeaders() {\n this._buildGroupHeaders();\n }\n\n /**\n * Create a ListGroupHeaderView for the given trigger model and raw key.\n * Subclasses (TableView) override to inject `tagName: 'tr'` + `colspan`.\n * @private\n */\n _createGroupHeaderView(model, key, index) {\n const displayKey = this.groupHeaderLabel ? this.groupHeaderLabel(key) : key;\n const headerView = new this.groupHeaderClass({\n template: this.groupHeaderTemplate || this._defaultGroupHeaderTemplate(),\n model,\n key: displayKey,\n index,\n ...this._groupHeaderViewOptions(model, key, index)\n });\n this.groupHeaderViews.set(headerView.id, headerView);\n return headerView;\n }\n\n /**\n * Subclass hook for extra constructor options on the header view (TableView\n * uses this to set `tagName: 'tr'`, `colspan`, and the table-shape modifier\n * className). Default: applies the `.list-group-header--<style>` modifier\n * for the configured `groupHeaderStyle`.\n * @protected\n */\n _groupHeaderViewOptions(_model, _key, _index) {\n return {\n className: `list-group-header list-group-header--${this.groupHeaderStyle}`\n };\n }\n\n /**\n * Default Mustache template used when the consumer doesn't pass\n * `groupHeaderTemplate`. ListView default is the bare display key — the\n * outer `<div class=\"list-group-header\">` wrapper comes from\n * ListGroupHeaderView's tagName + className. TableView overrides to emit\n * a `<th colspan=\"...\">` cell so the framework-provided `<tr>` is full-width.\n * @protected\n */\n _defaultGroupHeaderTemplate() {\n return '{{key}}';\n }\n\n _createItemView(model, index) {\n if (this.itemViews.has(model.id)) return this.itemViews.get(model.id);\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate,\n clickable: this.clickable\n });\n\n this.itemViews.set(model.id, itemView);\n this._wireItemViewListeners(itemView);\n\n return itemView;\n }\n\n /**\n * Wire the standard event listeners (select, click, view/edit/delete)\n * on a freshly-created item view. Factored out so subclasses (TableView)\n * can call this from their own _createItemView override.\n *\n * Two event names funnel into the same routing logic:\n * - `item:click` with action 'row-click' (from ListViewItem's\n * whole-row click handler) — re-emitted as `row:click` on the\n * parent ListView.\n * - `row:click` (from TableRow's `data-action=\"row-click\"` handler,\n * which already emits the event on `this` directly) — NOT\n * re-emitted here, just routed.\n *\n * @protected\n */\n _wireItemViewListeners(itemView) {\n itemView.on('item:select', this._onItemSelect.bind(this));\n itemView.on('item:deselect', this._onItemDeselect.bind(this));\n\n itemView.on('item:click', (event) => {\n // ListViewItem.onActionDefault also emits `item:click` for any\n // unhandled `data-action`. Ignore those — only the whole-row click\n // flagged with action 'row-click' should route to the click flow.\n if (event.action !== 'row-click') return;\n this.emit('row:click', event);\n this._dispatchRowClick(event);\n });\n\n itemView.on('row:click', (event) => {\n // TableRow already emits row:click on `this` (the listView/tableView)\n // directly — don't double-emit, just route.\n this._dispatchRowClick(event);\n });\n\n // Model-lifecycle row events fired from data-action=\"view|edit|delete\"\n // buttons inside the item template (and TableRow's own action buttons).\n itemView.on('row:view', this._onRowView.bind(this));\n itemView.on('row:edit', this._onRowEdit.bind(this));\n itemView.on('row:delete', this._onRowDelete.bind(this));\n }\n\n /**\n * Routing logic for a whole-row click. Order:\n * 1. options.onRowClick(model, event) — full override\n * 2. clickAction is a function — call it\n * 3. onItemClick(model, event) — callback shorthand\n * 4. clickAction === 'view' — open view dialog\n * 5. clickAction === 'edit' — open edit dialog\n * 6. clickAction === 'select' — toggle selection\n * 7. 'none' (default) — no-op (event was already emitted by caller)\n * @private\n */\n _dispatchRowClick(event) {\n if (typeof this.options.onRowClick === 'function') {\n return this.options.onRowClick(event.model, event.event);\n }\n\n if (typeof this.clickAction === 'function') {\n return this.clickAction(event.model, event.event);\n }\n\n if (this.onItemClick) {\n return this.onItemClick(event.model, event.event);\n }\n\n if (this.clickAction === 'view') {\n return this._onRowView(event);\n }\n if (this.clickAction === 'edit') {\n return this._onRowEdit(event);\n }\n if (this.clickAction === 'select') {\n if (this.selectedItems.has(event.model.id)) {\n this.deselectItem(event.model.id);\n } else {\n this.selectItem(event.model.id);\n }\n }\n }\n\n /**\n * If `persistSelection` is on, restore the `selected` flag on item views\n * whose model.id is in the `selectedItems` Set. Used after rebuilds (page\n * change) and after `_onModelsAdded` (Show More appends).\n * @private\n */\n _applyPersistedSelections() {\n if (!this.persistSelection || this.selectedItems.size === 0) return;\n this.itemViews.forEach((itemView, id) => {\n if (this.selectedItems.has(id) && !itemView.selected) {\n itemView.selected = true;\n if (itemView.element) itemView.addClass('selected');\n }\n });\n }\n\n _clearItems() {\n this.forEachItem((itemView) => {\n this.removeChild(itemView.id);\n });\n this.itemViews.clear();\n this.groupHeaderViews.forEach((headerView) => this.removeChild(headerView.id));\n this.groupHeaderViews.clear();\n this._renderOrder.length = 0;\n if (!this.persistSelection) {\n this.selectedItems.clear();\n }\n }\n\n _onModelsAdded(event) {\n const { models } = event;\n models.forEach((model) => {\n const index = this.collection.models.indexOf(model);\n this._createItemView(model, index);\n });\n this._applyPersistedSelections();\n // Rebuild the grouping pass against the now-larger collection so the\n // appended page's first item gets (or doesn't get) a header against\n // the prior tail correctly. No-op for non-grouped consumers.\n if (this.groupBy) this._rebuildGroupHeaders();\n\n this.isEmpty = this.collection.isEmpty();\n\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n }\n\n _onModelsRemoved(event) {\n const { models } = event;\n models.forEach((model) => {\n const itemView = this.itemViews.get(model.id);\n if (itemView) {\n this.removeChild(itemView.id);\n this.itemViews.delete(model.id);\n this.selectedItems.delete(model.id);\n }\n });\n\n this.isEmpty = this.collection.isEmpty();\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n if (this.isEmpty) this.emit('list:empty');\n }\n\n _onCollectionReset(_event) {\n this._buildItems();\n }\n\n _onFetchStart() {\n this.loading = true;\n // During a Show More fetch, onActionShowMore owns the render lifecycle\n // (it renders to show \"Loading…\" on the button and again after the\n // fetch resolves). Rendering here would replace the existing items\n // with the full-list spinner AND race with the post-fetch render —\n // canRender() drops the second one as a no-op, leaving the button\n // stuck on \"Loading…\".\n if (this.loadingMore) return;\n if (this.isMounted()) this.render();\n }\n\n _onFetchEnd() {\n this.loading = false;\n if (this.loadingMore) return;\n if (this.isMounted()) this.render();\n }\n\n _onItemSelect(event) {\n const { model, item } = event;\n\n if (this.selectionMode === 'none') {\n item.deselect();\n return;\n }\n\n if (this.selectionMode === 'single') {\n this.itemViews.forEach((view, id) => {\n if (id !== model.id && view.selected) view.deselect();\n });\n this.selectedItems.clear();\n }\n\n this.selectedItems.add(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item, model\n });\n }\n\n _onItemDeselect(event) {\n const { model } = event;\n this.selectedItems.delete(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: event.item, model\n });\n }\n\n // ============================================================\n // Selection API (unchanged)\n // ============================================================\n\n getSelectedItems() {\n const selected = [];\n this.selectedItems.forEach((id) => {\n const itemView = this.itemViews.get(id);\n if (itemView) {\n selected.push({\n view: itemView,\n model: itemView.model,\n data: itemView.model?.toJSON ? itemView.model.toJSON() : itemView.model\n });\n }\n });\n return selected;\n }\n\n forEachItem(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n let index = 0;\n this.itemViews.forEach((itemView) => {\n callback.call(thisArg, itemView, itemView.model, index++);\n });\n return this;\n }\n\n clearSelection() {\n this.forEachItem((itemView) => {\n if (itemView.selected) itemView.deselect();\n });\n this.selectedItems.clear();\n this.emit('selection:change', { selected: [] });\n }\n\n selectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) itemView.select();\n return this;\n }\n\n deselectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) itemView.deselect();\n return this;\n }\n\n setItemTemplate(template, rerender = false) {\n this.itemTemplate = template;\n if (rerender && this.itemViews.size > 0) {\n this.forEachItem((itemView) => {\n itemView.setTemplate(template);\n if (itemView.isMounted()) itemView.render();\n });\n }\n return this;\n }\n\n async refresh() {\n if (this.collection && this.collection.restEnabled) {\n return await this.collection.fetch();\n }\n this._buildItems();\n }\n\n // ============================================================\n // Row stripe (severity-coded left-edge color)\n // ============================================================\n\n /**\n * Resolve the row-stripe class for a given model. Returns null when no\n * `rowStripe` callback is set, when the callback returns falsy/empty,\n * or when it throws. Bootstrap variant tokens in\n * `ListView.ROW_STRIPE_TOKENS` map to `list-row-stripe-<token>`; any\n * other non-empty string passes through as a class name.\n *\n * @private\n */\n _stripeClassFor(model) {\n if (!this.rowStripe || !model) return null;\n let raw;\n try {\n raw = this.rowStripe(model);\n } catch (err) {\n console.warn('ListView: rowStripe callback threw — treating as no stripe', err);\n return null;\n }\n if (raw === null || raw === undefined || raw === '') return null;\n if (typeof raw !== 'string') return null;\n if (ListView.ROW_STRIPE_TOKENS.includes(raw)) {\n return `list-row-stripe-${raw}`;\n }\n return raw;\n }\n\n /**\n * Strip any existing `list-row-stripe*` class from the itemView's\n * element, then apply the freshly-resolved class (if any). Called from\n * ListViewItem.onAfterRender so the stripe re-evaluates on every row\n * render — including the implicit re-renders View triggers when the\n * model emits `change`.\n *\n * @private\n */\n _applyRowStripe(itemView) {\n if (!itemView || !itemView.element) return;\n const classes = itemView.element.classList;\n // Remove any prior stripe class so the row reflects current state\n // even when the level/decision downgrades.\n for (const cls of Array.from(classes)) {\n if (cls.startsWith('list-row-stripe')) classes.remove(cls);\n }\n const next = this._stripeClassFor(itemView.model);\n if (next) {\n try {\n classes.add(next);\n } catch (err) {\n // DOMException for invalid class names (e.g. strings containing\n // whitespace). Don't break the render — just skip the stripe.\n console.warn('ListView: rowStripe returned an invalid class name, skipping', next, err);\n }\n }\n }\n\n /**\n * Re-evaluate the row stripe for every current itemView. Useful when\n * the callback depends on state outside the model (parent filter,\n * threshold, etc.) — call this from the consumer's own event hook\n * after the external state changes.\n *\n * No-op when no `rowStripe` callback is configured.\n */\n refreshStripes() {\n if (!this.rowStripe) return this;\n this.forEachItem((itemView) => this._applyRowStripe(itemView));\n return this;\n }\n\n // ============================================================\n // Model lifecycle (view / edit / delete / add)\n //\n // Inherited by TableView. Generic across list and table — none of\n // these methods touch columns, cells, or rows. They open the\n // standard MOJO Modal dialogs based on the model class's static\n // VIEW_CLASS / ADD_FORM / EDIT_FORM / DELETE_TEMPLATE / FORM_DIALOG_CONFIG\n // (with per-instance overrides via this.itemView / this.addForm /\n // this.editForm / this.deleteTemplate / this.formDialogConfig).\n //\n // The view dialog also honors a static DIALOG_OPTIONS on the\n // VIEW_CLASS itself, so a detail View can declare its own modal\n // size/presentation once instead of every page repeating it.\n // ============================================================\n\n /**\n * Handle the view action for a row/item.\n * Fires `row:view` event then opens the view dialog (or runs a custom\n * `onItemView(model, event)` override). Wired automatically when\n * clickAction: 'view' or when the item template has data-action=\"view\".\n */\n async _onRowView(event) {\n this.emit('row:view', event);\n\n if (this.options.onItemView) {\n await this.options.onItemView(event.model, event.event);\n return;\n }\n\n if (this.fetchOnView) {\n try {\n Modal.loading();\n await event.model.fetch();\n } catch (error) {\n Modal.hideLoading(true);\n Modal.showError(error?.data?.error || error?.message || 'Failed to load item details');\n return;\n } finally {\n Modal.hideLoading(true);\n }\n }\n\n const ViewClass = this.getItemViewClass(event.model);\n\n if (ViewClass) {\n const viewInstance = new ViewClass({ model: event.model, collection: this.collection });\n await Modal.dialog({\n header: false,\n body: viewInstance,\n size: 'lg',\n centered: false,\n ...this.getFormDialogConfig(this.getModelClass(event.model)),\n // View class declares its own modal presentation (size, centered,\n // scrollable, …) via a static DIALOG_OPTIONS. Page-level\n // viewDialogOptions still wins as the final override.\n ...ViewClass.DIALOG_OPTIONS,\n ...this.viewDialogOptions\n });\n } else {\n await Modal.data({\n title: `View ${this.getModelName(event.model)} #${event.model.id}`,\n model: event.model\n });\n }\n }\n\n /**\n * Handle the edit action for a row/item.\n */\n async _onRowEdit(event) {\n this.emit('row:edit', event);\n\n if (this.options.onItemEdit) {\n await this.options.onItemEdit(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n let formConfig = this.getEditFormConfig(ModelClass);\n\n if (formConfig) {\n if (!formConfig.fields) {\n formConfig = { title: `Edit ${this.getModelName(event.model)}`, fields: formConfig };\n }\n\n const result = await Modal.modelForm({\n model: event.model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (!result) return;\n\n if (!result.success || !result?.result?.data.status) {\n Modal.showError(result?.result?.data?.error || result?.result?.message || 'An error occurred');\n return;\n }\n } else {\n const result = await Modal.dialog({\n title: `Edit ${this.getModelName(event.model)} #${event.model.id}`,\n body: new FormView({\n model: event.model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await event.model.save(result);\n if (!resp.data?.status) {\n Modal.showError(resp.data.error || 'An error occurred');\n return;\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle the delete action for a row/item.\n */\n async _onRowDelete(event) {\n this.emit('row:delete', event);\n\n if (this.options.onItemDelete) {\n await this.options.onItemDelete(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n const template = this.deleteTemplate ||\n ModelClass?.DELETE_TEMPLATE ||\n 'Are you sure you want to delete this {{name||\"item\"}}?';\n\n const message = this.renderTemplateString(template, event.model);\n\n const confirmed = await Modal.confirm({\n message: message || 'Are you sure you want to delete this item?',\n title: 'Confirm Delete',\n confirmText: 'Delete',\n confirmClass: 'btn-danger'\n });\n\n if (confirmed) {\n await event.model.destroy();\n if (this.collection?.restEnabled) {\n this.collection.fetch();\n } else {\n this._buildItems();\n }\n }\n }\n\n /**\n * Handle the Add toolbar action — opens an Add dialog using addForm /\n * Model.ADD_FORM and saves the new model into the collection.\n */\n async onActionAdd(event, _element) {\n if (this.options.onAdd) {\n this.emit('list:add', { event });\n await this.options.onAdd(event);\n return;\n }\n\n this.emit('list:add', { event });\n\n const ModelClass = this.getModelClass();\n if (!ModelClass) {\n console.warn('Cannot determine Model class for add operation');\n return;\n }\n\n let formConfig = this.getAddFormConfig(ModelClass);\n\n if (formConfig) {\n const model = new ModelClass();\n if (!formConfig.fields) {\n formConfig = { title: `Add ${this.getModelName()}`, fields: formConfig };\n }\n\n const result = await Modal.form({\n model: model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (result) {\n if (this.options.addRequiresActiveGroup) {\n result.group = this.getApp().activeGroup.id;\n }\n if (this.options.addRequiresActiveUser) {\n result.user = this.getApp().activeUser.id;\n }\n if (this.options.addFormDefaults) {\n Object.assign(result, this.options.addFormDefaults);\n }\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Modal.showError(resp?.data.error || 'An error occurred');\n return;\n }\n if (this.collection) this.collection.add(model);\n await this.refresh();\n }\n } else {\n const model = new ModelClass();\n const result = await Modal.dialog({\n title: `Add ${this.getModelName()}`,\n body: new FormView({\n model: model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Modal.showError(resp.data.error || 'An error occurred');\n return;\n }\n if (this.collection) this.collection.add(model);\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle Export — emits `list:export` and either downloads from the\n * server (`exportSource: 'remote'`) or passes the current data to\n * `options.onExport(data, format)` (`exportSource: 'local'`).\n */\n async onActionExport(event, element) {\n const format = element.getAttribute('data-format') || 'json';\n\n this.emit('list:export', {\n format,\n source: this.exportSource,\n event\n });\n\n if (this.exportSource === 'remote') {\n if (this.collection) {\n await this.collection.download(format);\n } else {\n console.warn('ListView: Cannot export from remote without a collection.');\n }\n } else {\n if (this.options.onExport) {\n await this.options.onExport(this.collection?.toJSON() || [], format);\n } else {\n console.warn('ListView: onExport handler not implemented for local export.');\n }\n }\n }\n\n // -------- Model-resolution helpers --------\n\n getModelClass(model) {\n if (this.collection?.ModelClass) return this.collection.ModelClass;\n if (this.collection?.model) return this.collection.model;\n if (model?.constructor) return model.constructor;\n return null;\n }\n\n getModelName(model) {\n const ModelClass = this.getModelClass(model);\n if (!ModelClass) return 'Item';\n if (ModelClass.MODEL_NAME) return ModelClass.MODEL_NAME;\n // Production minifiers (Vite/esbuild) mangle class identifiers into short\n // strings like 'ys' or 'a'. `ModelClass.name` is therefore unsafe as a\n // user-facing label without an explicit MODEL_NAME. Only accept names\n // that look like a hand-written PascalCase identifier; otherwise fall\n // back to 'Item' and warn once per class so the dev knows to set\n // MODEL_NAME.\n const rawName = (ModelClass.name || '').replace(/Model$/, '');\n if (/^[A-Z][A-Za-z0-9]{2,}$/.test(rawName)) return rawName;\n if (!ListView._warnedMinifiedClasses.has(ModelClass)) {\n ListView._warnedMinifiedClasses.add(ModelClass);\n console.warn(\n `ListView.getModelName: class name '${rawName}' looks minified — ` +\n `set a static MODEL_NAME on the model class for user-facing dialog ` +\n `titles. Falling back to 'Item'.`\n );\n }\n return 'Item';\n }\n\n getItemViewClass(model) {\n if (this.itemView) return this.itemView;\n const ModelClass = this.getModelClass(model);\n if (ModelClass?.VIEW_CLASS) return ModelClass.VIEW_CLASS;\n return null;\n }\n\n getAddFormConfig(ModelClass) {\n return this.addForm ||\n ModelClass?.ADD_FORM ||\n this.editForm ||\n ModelClass?.EDIT_FORM;\n }\n\n getEditFormConfig(ModelClass) {\n return this.editForm ||\n ModelClass?.EDIT_FORM ||\n this.addForm ||\n ModelClass?.ADD_FORM;\n }\n\n getFormDialogConfig(ModelClass) {\n return {\n ...ModelClass?.FORM_DIALOG_CONFIG,\n ...this.formDialogConfig\n };\n }\n\n renderTemplateString(template, model) {\n if (!template) return '';\n return Mustache.render(template, model);\n }\n\n // ============================================================\n // Toolbar action handlers\n // ============================================================\n\n async onActionRefresh(_event, _element) {\n await this.refresh();\n }\n\n async onActionApplySearch(_event, element) {\n const searchTerm = element.value.trim();\n if (this.collection) {\n this.setFilter('search', searchTerm);\n this.collection.params.start = 0;\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n await this.render();\n }\n }\n this.updateFilterPills();\n this.emit('list:search', { searchTerm });\n this.emit('params-changed');\n }\n\n async onActionClearSearch(_event, _element) {\n this.setFilter('search', null);\n if (this.collection) {\n this.collection.params.start = 0;\n if (this.collection.restEnabled) await this.collection.fetch();\n }\n await this.render();\n this.updateFilterPills();\n this.emit('list:search', { searchTerm: '' });\n this.emit('params-changed');\n }\n\n setupSearchClearListener() {\n if (!this.element) return;\n const searchInputs = this.element.querySelectorAll('input[type=\"search\"][data-filter=\"search\"]');\n searchInputs.forEach((input) => {\n input.addEventListener('input', (event) => {\n if (event.target.value === '' && this.getActiveFilters().search) {\n this.onActionClearSearch(event, event.target);\n }\n });\n });\n }\n\n updateFilterPills() {\n const container = this.element?.querySelector('[data-container=\"filter-pills\"]');\n if (!container) return;\n container.innerHTML = this.buildActivePills();\n }\n\n updateSearchInputs(value) {\n const inputs = this.element?.querySelectorAll('[data-filter=\"search\"]');\n if (inputs) inputs.forEach((input) => { input.value = value || ''; });\n }\n\n // -------- Pagination handlers --------\n renderPagination() {\n const paginationContainer = this.element.querySelector('[data-container=\"pagination\"]');\n if (!paginationContainer || !this.collection) return;\n\n const total = this.collection.meta?.count || this.collection.length();\n const size = this.collection.params?.size || 10;\n const start = this.collection.params?.start || 0;\n const currentPage = Math.floor(start / size) + 1;\n const totalPages = Math.ceil(total / size);\n\n if (totalPages <= 1) {\n paginationContainer.innerHTML = '';\n return;\n }\n\n const prevPage = currentPage > 1 ? currentPage - 1 : totalPages;\n const nextPage = currentPage < totalPages ? currentPage + 1 : 1;\n\n const pages = [];\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${prevPage}\">\n <i class=\"bi bi-chevron-left\"></i>\n </a>\n </li>\n `);\n\n const neighbors = 1;\n const visibleSet = new Set([1, totalPages]);\n for (let i = currentPage - neighbors; i <= currentPage + neighbors; i++) {\n if (i >= 1 && i <= totalPages) visibleSet.add(i);\n }\n const visible = Array.from(visibleSet).sort((a, b) => a - b);\n\n let last = 0;\n for (const p of visible) {\n if (last && p - last > 1) {\n pages.push('<li class=\"page-item disabled\"><span class=\"page-link\">…</span></li>');\n }\n pages.push(`\n <li class=\"page-item ${p === currentPage ? 'active' : ''}\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${p}\">${p}</a>\n </li>\n `);\n last = p;\n }\n\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${nextPage}\">\n <i class=\"bi bi-chevron-right\"></i>\n </a>\n </li>\n `);\n\n paginationContainer.innerHTML = pages.join('');\n }\n\n async onActionPage(event, element) {\n event.preventDefault();\n const rawPage = parseInt(element.getAttribute('data-page'), 10);\n const size = this.collection.params?.size || 10;\n const total = this.collection.meta?.count || this.collection.length();\n const totalPages = Math.max(1, Math.ceil(total / size));\n\n let page = isNaN(rawPage) ? 1 : rawPage;\n if (page < 1) page = totalPages;\n if (page > totalPages) page = 1;\n\n this.collection.setParams({\n ...this.collection.params,\n start: (page - 1) * size\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n this.render();\n }\n\n this.emit('list:page', { page, event });\n this.emit('params-changed');\n }\n\n async onChangePageSize(_event, element) {\n const newSize = parseInt(element.value, 10);\n if (this.collection) {\n this.collection.setParams({\n ...this.collection.params,\n start: 0,\n size: newSize\n });\n if (this.collection.restEnabled) await this.collection.fetch();\n this.render();\n }\n this.emit('list:pagesize', { size: newSize });\n this.emit('params-changed');\n }\n\n // -------- Show more --------\n _computeHasMore() {\n if (this.paginationMode !== 'more') return false;\n if (!this.collection) return false;\n const total = this.collection.meta?.count;\n if (typeof total !== 'number') return false;\n return this.collection.length() < total;\n }\n\n async onActionShowMore(_event, _element) {\n if (this.loadingMore) return;\n if (!this.collection || typeof this.collection.fetchMore !== 'function') {\n console.warn('ListView: collection does not support fetchMore()');\n return;\n }\n this.loadingMore = true;\n if (this.isMounted()) await this.render();\n try {\n const response = await this.collection.fetchMore();\n this.emit('list:show-more', { response });\n } catch (err) {\n console.error('ListView: fetchMore failed', err);\n } finally {\n this.loadingMore = false;\n if (this.isMounted()) await this.render();\n }\n }\n\n // -------- Sort dropdown handler --------\n async onActionSortOption(event, element) {\n event.preventDefault();\n const sort = element.getAttribute('data-sort');\n\n if (this.collection) {\n this.collection.setParams({\n ...this.collection.params,\n sort: sort || undefined,\n start: 0\n });\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n this.render();\n }\n }\n this.emit('list:sort', { sort });\n this.emit('params-changed');\n }\n\n // -------- Custom toolbar button --------\n async onActionCustomToolbarButton(event, element) {\n const idx = parseInt(element.getAttribute('data-button-index'), 10);\n const button = this.toolbarButtons[idx];\n if (button && typeof button.handler === 'function') {\n await button.handler.call(this, event, element);\n }\n }\n\n // -------- Filter handlers --------\n async onActionAddFilter(_event, element) {\n const filterKey = element.getAttribute('data-filter-key');\n const filterConfig = this.getFilterConfig(filterKey);\n const currentValue = this.getActiveFilters()[filterKey];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey);\n return;\n }\n\n const result = await Modal.form({\n title: `${currentValue !== undefined && currentValue !== '' ? 'Edit' : 'Add'} ${this.getFilterLabel(filterKey)} Filter`,\n size: 'md',\n fields: [this.buildFilterDialogField(filterConfig, currentValue, filterKey)]\n });\n\n if (result) {\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n async onActionEditFilter(_event, element) {\n const filterKey = element.getAttribute('data-filter');\n const { field } = parseFilterKey(filterKey);\n\n const filterConfig = this.getFilterConfig(field) || this.getFilterConfig(filterKey);\n\n const activeFilters = this.getActiveFilters();\n const currentValue = activeFilters[filterKey] || activeFilters[field];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey, 'or field:', field);\n return;\n }\n\n const formData = { filter_value: currentValue };\n if (filterConfig.type === 'daterange' && currentValue && typeof currentValue === 'object') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n formData[startName] = currentValue.start || '';\n formData[endName] = currentValue.end || '';\n }\n\n // NOTE: do NOT emit `filter:edit` here. ListView owns the edit dialog\n // flow end-to-end — emitting would also cause TablePage's legacy\n // handleFilterEdit handler to open a SECOND modal racing this one.\n // External listeners that want to know a filter was edited can listen\n // for `params-changed` after applyFilters() runs.\n\n const result = await Modal.form({\n title: `Edit ${this.getFilterLabel(field)} Filter`,\n size: 'md',\n data: formData,\n fields: [this.buildFilterDialogField(filterConfig, currentValue, field)]\n });\n\n if (result) {\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n async onActionRemoveFilter(_event, element) {\n const filterKey = element.getAttribute('data-filter');\n const { field } = parseFilterKey(filterKey);\n\n this.setFilter(filterKey, null);\n if (filterKey === 'search') this.updateSearchInputs('');\n\n if (this.collection?.restEnabled) await this.collection.fetch();\n this.render();\n this.updateFilterPills();\n\n this.emit('filter:remove', { key: filterKey, field });\n this.emit('params-changed');\n }\n\n async onActionClearAllFilters(_event, _element) {\n if (!this.collection) return;\n\n const { start, size, sort } = this.collection.params;\n const preserved = { start, size };\n if (sort) preserved.sort = sort;\n\n if (Array.isArray(this.hideActivePillNames) && this.hideActivePillNames.length > 0) {\n this.hideActivePillNames.forEach((key) => {\n if (this.collection.params[key] !== undefined) preserved[key] = this.collection.params[key];\n\n const filterConfig = this.getFilterConfig(key);\n if (filterConfig && filterConfig.type === 'daterange') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n const fieldName = filterConfig.fieldName || 'dr_field';\n if (this.collection.params[startName] !== undefined) preserved[startName] = this.collection.params[startName];\n if (this.collection.params[endName] !== undefined) preserved[endName] = this.collection.params[endName];\n if (this.collection.params[fieldName] !== undefined) preserved[fieldName] = this.collection.params[fieldName];\n }\n });\n }\n\n this.collection.params = preserved;\n this.updateSearchInputs('');\n\n if (this.collection.restEnabled) await this.collection.fetch();\n this.render();\n this.updateFilterPills();\n\n this.emit('filters:clear');\n this.emit('params-changed');\n }\n\n async applyFilters() {\n if (this.collection) this.collection.params.start = 0;\n\n if (this.collection?.restEnabled) {\n try {\n await this.collection.fetch();\n await this.render();\n } catch (err) {\n console.error('Failed to fetch filtered data:', err);\n await this.render();\n }\n } else {\n await this.render();\n }\n\n this.updateFilterPills();\n this.emit('params-changed');\n }\n\n // ============================================================\n // Filter API (params-driven, identical semantics to TableView)\n // ============================================================\n\n getActiveFilters() {\n if (!this.collection?.params) return {};\n const { start: _start, size: _size, sort: _sort, ...allParams } = this.collection.params;\n const filters = {};\n\n const processedKeys = new Set();\n const allFilterConfigs = this.getAllAvailableFilters();\n\n allFilterConfigs.forEach((filterDef) => {\n if (filterDef.config?.type === 'daterange') {\n const key = filterDef.key;\n const startName = filterDef.config.startName || 'dr_start';\n const endName = filterDef.config.endName || 'dr_end';\n const fieldName = filterDef.config.fieldName || 'dr_field';\n\n if (allParams[fieldName] === key && (allParams[startName] || allParams[endName])) {\n filters[key] = {\n start: allParams[startName] || '',\n end: allParams[endName] || ''\n };\n processedKeys.add(startName);\n processedKeys.add(endName);\n processedKeys.add(fieldName);\n }\n }\n });\n\n Object.keys(allParams).forEach((paramKey) => {\n if (!processedKeys.has(paramKey)) filters[paramKey] = allParams[paramKey];\n });\n\n Object.keys(filters).forEach((key) => {\n const inKey = `${key}__in`;\n if (Object.prototype.hasOwnProperty.call(filters, inKey)) {\n delete filters[key];\n }\n });\n\n return filters;\n }\n\n setFilter(key, value) {\n if (!this.collection) return;\n\n const filterConfig = this.getFilterConfig(key);\n\n if (filterConfig && filterConfig.type === 'daterange') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n const fieldName = filterConfig.fieldName || 'dr_field';\n\n delete this.collection.params[startName];\n delete this.collection.params[endName];\n delete this.collection.params[fieldName];\n\n if (value && typeof value === 'object' && (value.start || value.end)) {\n if (value.start) this.collection.params[startName] = value.start;\n if (value.end) this.collection.params[endName] = value.end;\n this.collection.params[fieldName] = key;\n }\n } else {\n const { field } = parseFilterKey(key);\n\n delete this.collection.params[key];\n delete this.collection.params[field];\n delete this.collection.params[`${field}__in`];\n\n if (!value || (Array.isArray(value) && value.length === 0)) return;\n\n if (Array.isArray(value)) {\n if (value.length === 1) {\n this.collection.params[field] = value[0];\n } else {\n this.collection.params[`${field}__in`] = value.join(',');\n }\n } else {\n this.collection.params[key] = value;\n }\n }\n }\n\n getAllAvailableFilters() {\n const filters = [];\n\n // Column-based filters (populated by TableView via extractColumnFilters).\n Object.entries(this.filters || {}).forEach(([fieldKey, config]) => {\n filters.push({\n key: fieldKey,\n label: config.label || fieldKey,\n type: config.type,\n config\n });\n });\n\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n this.additionalFilters.forEach((filter) => {\n filters.push({\n key: filter.name || filter.key,\n label: filter.label,\n type: filter.type,\n config: filter\n });\n });\n }\n\n return filters;\n }\n\n getFilterConfig(filterKey) {\n if (this.filters && this.filters[filterKey]) return this.filters[filterKey];\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n const filter = this.additionalFilters.find((f) => (f.name || f.key) === filterKey);\n if (filter) return filter;\n }\n return null;\n }\n\n getFilterLabel(key) {\n if (key === 'search') return 'Search';\n const f = this.filters?.[key];\n if (f && f.label) return f.label;\n const af = this.additionalFilters?.find((x) => (x.name || x.key) === key);\n if (af && af.label) return af.label;\n return key.charAt(0).toUpperCase() + key.slice(1);\n }\n\n getFilterIcon(type) {\n const icons = {\n 'text': 'search',\n 'select': 'funnel',\n 'date': 'calendar',\n 'daterange': 'calendar-range',\n 'number': '123',\n 'boolean': 'toggle-on'\n };\n return icons[type] || 'filter';\n }\n\n buildFilterDialogField(filterConfig, currentValue, _filterKey) {\n const { name: _filterName, value: _filterValue, ...rest } = filterConfig;\n const field = {\n ...rest,\n name: 'filter_value',\n label: rest.label,\n value: currentValue,\n placeholder: rest.placeholder || rest.placeHolder\n };\n\n if (filterConfig.type === 'daterange') {\n field.startName = field.startName || 'dr_start';\n field.endName = field.endName || 'dr_end';\n field.fieldName = field.fieldName || 'dr_field';\n field.format = field.format || 'YYYY-MM-DD';\n field.displayFormat = field.displayFormat || 'MMM DD, YYYY';\n field.separator = field.separator || ' to ';\n field.label = field.label || 'Date Range';\n\n if (currentValue && typeof currentValue === 'object') {\n const normalizeDateValue = (val) => {\n if (!val && val !== 0) return '';\n if (val instanceof Date && !isNaN(val)) return val.toISOString().slice(0, 10);\n const str = String(val).trim();\n if (!str) return '';\n if (/^-?\\d+$/.test(str)) {\n const num = Number(str);\n const ms = str.length <= 10 ? num * 1000 : num;\n const date = new Date(ms);\n if (!isNaN(date)) return date.toISOString().slice(0, 10);\n }\n const date = new Date(str);\n if (!isNaN(date)) return date.toISOString().slice(0, 10);\n return str;\n };\n\n field.startDate = normalizeDateValue(currentValue.start || currentValue.from || currentValue.begin || '');\n field.endDate = normalizeDateValue(currentValue.end || currentValue.to || currentValue.finish || '');\n }\n } else if (filterConfig.type === 'multiselect') {\n let valueArray = [];\n if (currentValue) {\n if (Array.isArray(currentValue)) {\n valueArray = currentValue;\n } else if (typeof currentValue === 'string') {\n valueArray = currentValue.split(',').map((v) => v.trim()).filter((v) => v);\n }\n }\n field.value = valueArray;\n if (!field.placeholder && !field.placeHolder) {\n if (filterConfig.placeholder || filterConfig.placeHolder) {\n field.placeholder = filterConfig.placeholder || filterConfig.placeHolder;\n } else if (filterConfig.label) {\n field.placeholder = `Select ${filterConfig.label}...`;\n }\n }\n } else if (\n filterConfig.type === 'boolean' ||\n filterConfig.type === 'switch' ||\n filterConfig.type === 'toggle'\n ) {\n // Boolean filters render as a True / False select in the edit dialog.\n // FormBuilder doesn't have a `boolean` field type, and a switch/toggle\n // is awkward in a one-field filter dialog (no clear \"submit\" affordance\n // and ambiguous off-state). Select with two options is URL-friendly,\n // explicit, and easy to read in the active-pill bar.\n field.type = 'select';\n // Stringify the (current OR default) value so the <option value=\"...\">\n // match works. Filter params are strings on the wire —\n // Collection.params['foo'] = 'true'. When the dialog is opened fresh\n // (Add Filter) and the config has `defaultValue`, honor it as the\n // pre-selected option.\n const initial = currentValue !== undefined && currentValue !== null && currentValue !== ''\n ? currentValue\n : filterConfig.defaultValue;\n const stringValue = initial === true ? 'true'\n : initial === false ? 'false'\n : initial == null ? '' : String(initial);\n field.value = stringValue;\n // Allow caller to override the labels (e.g. `trueLabel: 'Active'`,\n // `falseLabel: 'Inactive'`); fall back to plain True / False.\n const trueLabel = filterConfig.trueLabel || 'True';\n const falseLabel = filterConfig.falseLabel || 'False';\n field.options = [\n { value: 'true', text: trueLabel },\n { value: 'false', text: falseLabel }\n ];\n if (!field.placeholder && !field.placeHolder) {\n field.placeholder = filterConfig.label\n ? `Filter by ${filterConfig.label}…`\n : 'Select…';\n }\n }\n\n return field;\n }\n\n extractFilterValue(filterConfig, formResult) {\n if (filterConfig.type === 'daterange') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n return { start: formResult[startName], end: formResult[endName] };\n }\n if (filterConfig.type === 'multiselect') return formResult.filter_value;\n return formResult.filter_value;\n }\n\n // -------- Toolbar text mutators --------\n // `_buildTitleBlockTemplate` emits `{{title}}` / `{{eyebrow}}` Mustache\n // vars (resolved at render time from `this.title` / `this.eyebrow`),\n // so setters update the instance prop and patch the live DOM node;\n // the next render() picks up the value automatically.\n setTitle(value) {\n this.title = value || null;\n const el = this.element?.querySelector('.rs-table-title');\n if (el) el.textContent = this.title || '';\n }\n\n setEyebrow(value) {\n this.eyebrow = value || null;\n const el = this.element?.querySelector('.rs-table-eyebrow');\n if (el) el.textContent = this.eyebrow || '';\n }\n\n // -------- Hooks called from action handlers (subclassable) --------\n\n // `toolbarButtons[].permissions` gating uses View#checkPermissions\n // (fail-closed against app.activeUser). Subclasses/apps can still\n // override to integrate their own ACL.\n\n /** Light HTML-escape for inline template strings. */\n escapeHtml(value) {\n if (value == null) return '';\n return String(value)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n\n // ============================================================\n // Auto-refresh (WM-034) — opt-in interval refetch with smart pause\n // ============================================================\n\n /**\n * Normalize the `autoRefresh:` option into an interval in milliseconds.\n * Accepts a positive number of seconds, clamped to a 5s floor to prevent\n * hammering the API. Anything else (absent / non-number / ≤0) → 0, which\n * disables the feature entirely (no timer, no listeners).\n * @private\n */\n _normalizeAutoRefresh(raw) {\n if (typeof raw !== 'number' || !isFinite(raw) || raw <= 0) return 0;\n return Math.max(5, raw) * 1000;\n }\n\n /**\n * Start the auto-refresh interval + focus/visibility listeners. No-op when\n * `autoRefresh` wasn't configured, and idempotent — a second call while a\n * timer is already live returns immediately, so cached-page re-entries\n * (which re-fire onAfterMount) never stack a second interval.\n * @private\n */\n _startAutoRefresh() {\n if (!this._autoRefreshMs) return;\n if (this._autoRefreshTimer) return;\n this._autoRefreshPaused = this._autoRefreshDocHidden();\n this._bindAutoRefreshListeners();\n this._autoRefreshTimer = setInterval(() => this._autoRefreshTick(), this._autoRefreshMs);\n }\n\n /**\n * Fully tear down the timer and its focus/visibility listeners. Safe to\n * call when nothing is running.\n * @private\n */\n _stopAutoRefresh() {\n if (this._autoRefreshTimer) {\n clearInterval(this._autoRefreshTimer);\n this._autoRefreshTimer = null;\n }\n if (this._autoRefreshPulseTimer) {\n clearTimeout(this._autoRefreshPulseTimer);\n this._autoRefreshPulseTimer = null;\n }\n this._unbindAutoRefreshListeners();\n this._autoRefreshPaused = false;\n }\n\n /**\n * Bind window focus/blur + document visibilitychange listeners so the\n * timer can pause while the tab is backgrounded and resume (with an\n * immediate refetch) when the user returns. Idempotent.\n * @private\n */\n _bindAutoRefreshListeners() {\n if (this._autoRefreshHandlers) return;\n const onBlur = () => { this._autoRefreshPaused = true; };\n const onFocus = () => { this._autoRefreshPaused = false; this._autoRefreshResume(); };\n const onVisibility = () => {\n const hidden = this._autoRefreshDocHidden();\n this._autoRefreshPaused = hidden;\n if (!hidden) this._autoRefreshResume();\n };\n this._autoRefreshHandlers = { onBlur, onFocus, onVisibility };\n if (typeof window !== 'undefined') {\n window.addEventListener('blur', onBlur);\n window.addEventListener('focus', onFocus);\n }\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', onVisibility);\n }\n }\n\n /** @private */\n _unbindAutoRefreshListeners() {\n const h = this._autoRefreshHandlers;\n if (!h) return;\n if (typeof window !== 'undefined') {\n window.removeEventListener('blur', h.onBlur);\n window.removeEventListener('focus', h.onFocus);\n }\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', h.onVisibility);\n }\n this._autoRefreshHandlers = null;\n }\n\n /** @private */\n _autoRefreshDocHidden() {\n return typeof document !== 'undefined'\n && (document.visibilityState === 'hidden' || document.hidden === true);\n }\n\n /**\n * Regaining focus / visibility fires an immediate guarded refetch, then the\n * existing interval carries on at its normal cadence. No-op when no timer\n * is live (so a stray focus event after teardown can't refetch).\n * @private\n */\n _autoRefreshResume() {\n if (!this._autoRefreshTimer) return;\n this._autoRefreshTick();\n }\n\n /**\n * One interval tick. Self-terminates if the view is no longer mounted\n * (covers a cached Page that exited without an unmount hook reaching this\n * child). Otherwise skips silently while any pause condition holds, then\n * issues a silent `collection.fetch()` that preserves current params —\n * Collection's own dedup/cancel machinery handles overlap.\n * @private\n */\n _autoRefreshTick() {\n if (!this.isMounted()) { this._stopAutoRefresh(); return; }\n if (this._autoRefreshShouldSkip()) return;\n if (!this.collection || !this.collection.restEnabled) return;\n Promise.resolve(this.collection.fetch())\n .then(() => this._pulseAutoRefreshIndicator())\n .catch((err) => {\n console.warn('ListView autoRefresh: fetch failed', err);\n });\n }\n\n /**\n * Pause predicate evaluated at tick time. Base ListView pauses while the\n * tab is hidden/blurred or a selection is active. TableView overrides this\n * to also pause during an inline cell edit or an open row context menu.\n * @protected\n * @returns {boolean} true → skip this tick (do not clear the timer).\n */\n _autoRefreshShouldSkip() {\n if (this._autoRefreshPaused) return true;\n if (this._autoRefreshDocHidden()) return true;\n if (this.selectedItems && this.selectedItems.size > 0) return true;\n return false;\n }\n\n // ============================================================\n // View persistence (WM-035) — opt-in per-table saved view\n //\n // Persisted blob shape (versioned): `{ v: 1, sort?, size?, dayRange?,\n // filters?: {…raw params…}, hidden?: [columnKey] }`. `filters` is the raw\n // `collection.params` set MINUS start/size/sort and the day-range field\n // param (which is re-derived from `dayRange`), so `field__in` collapsed keys\n // and `dr_*` daterange triplets round-trip verbatim (preset matching depends\n // on it). `hidden` is written by TableView's column chooser.\n // ============================================================\n\n /**\n * The localStorage key for this view's saved state. Uses the explicit\n * `persistKey` when supplied, else a stable identity of page route +\n * collection endpoint (`<pathname>::<endpoint>`), namespaced under\n * `mojo:tableview:`.\n * @private\n */\n _persistStorageKey() {\n const id = this.persistKey || this._defaultPersistKey();\n return `mojo:tableview:${id}`;\n }\n\n /** @private */\n _defaultPersistKey() {\n const route = (typeof window !== 'undefined' && window.location && window.location.pathname) || '';\n const endpoint = (this.collection && this.collection.endpoint) || '';\n return `${route}::${endpoint}`;\n }\n\n /**\n * Resolve the localStorage backing object, or null when it's unavailable\n * (SSR / privacy mode / access throws). All storage access funnels through\n * here so the feature degrades to a no-op rather than throwing.\n * @private\n */\n _persistStorage() {\n try {\n const ls = (typeof globalThis !== 'undefined') ? globalThis.localStorage : null;\n if (ls && typeof ls.getItem === 'function') return ls;\n } catch (e) { /* access denied */ }\n return null;\n }\n\n /**\n * Read + validate the saved blob. Discards (and clears) anything that isn't\n * this schema version or won't parse, returning null.\n * @private\n */\n _readPersistedState() {\n const store = this._persistStorage();\n if (!store) return null;\n let raw;\n try { raw = store.getItem(this._persistStorageKey()); } catch (e) { return null; }\n if (!raw) return null;\n let parsed;\n try { parsed = JSON.parse(raw); } catch (e) { this._clearPersistedStorage(); return null; }\n if (!parsed || typeof parsed !== 'object' || parsed.v !== 1) {\n this._clearPersistedStorage();\n return null;\n }\n return parsed;\n }\n\n /** @private */\n _writePersistedState(state) {\n const store = this._persistStorage();\n if (!store) return;\n try { store.setItem(this._persistStorageKey(), JSON.stringify(state)); } catch (e) { /* quota / denied */ }\n }\n\n /** @private */\n _clearPersistedStorage() {\n const store = this._persistStorage();\n if (!store) return;\n try { store.removeItem(this._persistStorageKey()); } catch (e) { /* denied */ }\n }\n\n /**\n * Rehydrate the collection params from the saved blob. Fills only slots the\n * current query didn't already set (URL > saved), so a shared link always\n * wins. The day-range value is restored onto `this.dayRangeFilter` so the\n * subsequent seed/control build uses it. Column visibility is delegated to\n * the TableView hook. No-op unless persistState is on.\n * @private\n */\n _restorePersistedState(queryKeys = []) {\n if (!this.persistState || !this.collection) return;\n const saved = this._readPersistedState();\n if (!saved) return;\n\n const params = this.collection.params || (this.collection.params = {});\n // `start`/`size` are structural paging defaults every Collection carries\n // from construction, so they can't signal a real URL query. Drop them from\n // the query set: `sort`, filters, and the day-range field strictly honor\n // URL > saved (they only appear when a query set them), while page `size`\n // is treated as a per-user viewing preference — saved size is restored over\n // the paging default (and a URL-provided size, since TablePage always\n // round-trips size through the URL). Documented in TableView.md.\n const fromQuery = new Set(queryKeys);\n fromQuery.delete('start');\n fromQuery.delete('size');\n\n if (saved.sort !== undefined && !fromQuery.has('sort')) params.sort = saved.sort;\n if (saved.size !== undefined) params.size = saved.size;\n\n // Day-range: restore the selected value (re-seeded to a fresh epoch by the\n // day-range block) unless the query already carried the field param.\n if (saved.dayRange !== undefined && this.dayRangeFilter) {\n const field = this.dayRangeFilter.field || 'created';\n if (!fromQuery.has(`${field}__gte`)) {\n this.dayRangeFilter.value = saved.dayRange;\n }\n }\n\n // Filters — restore each key the query didn't set, verbatim (field__in /\n // dr_* triplets preserved so preset matching still works).\n if (saved.filters && typeof saved.filters === 'object') {\n Object.keys(saved.filters).forEach((key) => {\n if (!fromQuery.has(key)) params[key] = saved.filters[key];\n });\n }\n\n // Column visibility (TableView only).\n if (typeof this._restoreHiddenColumns === 'function') {\n this._restoreHiddenColumns(saved.hidden);\n }\n }\n\n /**\n * Serialize the current view state to localStorage. Called on every\n * `params-changed` (and by TableView on a column toggle). No-op unless\n * persistState is on.\n * @private\n */\n _savePersistedState() {\n if (!this.persistState) return;\n const params = (this.collection && this.collection.params) || {};\n const dayRangeGteKey = this.dayRangeFilter ? `${this.dayRangeFilter.field || 'created'}__gte` : null;\n\n const filters = {};\n Object.keys(params).forEach((key) => {\n if (key === 'start' || key === 'size' || key === 'sort') return;\n if (dayRangeGteKey && key === dayRangeGteKey) return; // re-derived from dayRange\n const value = params[key];\n if (value === undefined || value === null || value === '') return;\n filters[key] = value;\n });\n\n const state = { v: 1 };\n if (params.sort !== undefined) state.sort = params.sort;\n if (params.size !== undefined) state.size = params.size;\n if (this.dayRangeControl && typeof this.dayRangeControl.getValue === 'function') {\n state.dayRange = this.dayRangeControl.getValue();\n } else if (this.dayRangeFilter) {\n state.dayRange = this.dayRangeFilter.value;\n }\n if (Object.keys(filters).length > 0) state.filters = filters;\n if (typeof this._getHiddenColumnKeys === 'function') {\n const hidden = this._getHiddenColumnKeys();\n if (hidden && hidden.length > 0) state.hidden = hidden;\n }\n\n this._writePersistedState(state);\n }\n\n /**\n * Public escape hatch — forget this table's saved view (both the view state\n * and, via the TableView hook, any hidden columns), reverting to defaults.\n */\n clearPersistedState() {\n this._clearPersistedStorage();\n if (typeof this._onClearPersistedState === 'function') this._onClearPersistedState();\n }\n\n // ============================================================\n // Cleanup\n // ============================================================\n\n async destroy() {\n this._stopAutoRefresh();\n if (this.collection) {\n this.collection.off('add', this._onModelsAdded, this);\n this.collection.off('remove', this._onModelsRemoved, this);\n this.collection.off('reset', this._onCollectionReset, this);\n this.collection.off('fetch:start', this._onFetchStart, this);\n this.collection.off('fetch:end', this._onFetchEnd, this);\n }\n this._clearItems();\n await super.destroy();\n }\n}\n\nexport default ListView;\n"],"names":["SegmentControl","View","constructor","options","items","value","size","ariaLabel","viewOptions","super","tagName","className","this","template","_buildTemplate","sizeClass","buttons","map","item","isActive","cls","icon","escapeHtml","String","label","join","onActionSelect","event","element","next","dataset","previous","_paintActive","emit","querySelectorAll","forEach","btn","classList","toggle","setAttribute","setValue","silent","match","find","getValue","LOOKUPS","exact","display","description","in","not","not_in","gt","gte","lt","lte","contains","icontains","startswith","istartswith","endswith","iendswith","isnull","val","range","parseFilterKey","paramKey","field","lookup","parts","split","length","possibleLookup","slice","formatFilterDisplay","lookupDef","Array","isArray","hasStart","start","hasEnd","end","JSON","stringify","valueStr","values","v","trim","filter","formattedValues","DjangoLookups","getLookupDescription","isValidLookup","hasOwnProperty","getAvailableLookups","Object","keys","buildFilterKey","ListViewItem","selected","index","listView","clickable","addClass","_element","stopPropagation","deselect","select","onActionView","_emitRowEvent","onActionEdit","onActionDelete","name","payload","model","data","toJSON","removeClass","onAfterRender","_wireClickableHandler","rowStripe","_applyRowStripe","_clickableHandler","target","closest","tag","action","addEventListener","onActionDefault","_event","setIndex","setSelected","destroy","removeEventListener","ListGroupHeaderView","key","colspan","ListView","static","WeakSet","collection","itemViews","Map","selectedItems","Set","itemTemplate","itemClass","selectionMode","emptyMessage","loading","isEmpty","searchable","filterable","paginated","mode","paginationMode","searchPlacement","searchPlaceholder","sortOptions","filters","additionalFilters","hideActivePills","hideActivePillNames","title","eyebrow","showRefresh","toolbarButtons","toolbarRight","_toolbarRightMounted","dayRangeFilter","_normalizeDayRangeFilter","dayRangeControl","filterPresets","_normalizeFilterPresets","exportOptions","exportSource","persistSelection","onItemClick","clickAction","itemView","addForm","editForm","deleteTemplate","formDialogConfig","viewDialogOptions","fetchOnView","groupBy","groupHeaderTemplate","groupHeaderLabel","groupHeaderClass","groupHeaderStyle","GROUP_HEADER_STYLES","includes","groupHeaderViews","_renderOrder","emptyState","_normalizeEmptyState","loadingStyle","showResultCount","_autoRefreshMs","_normalizeAutoRefresh","autoRefresh","_autoRefreshTimer","_autoRefreshPaused","_autoRefreshHandlers","autoRefreshIndicator","_autoRefreshPulseTimer","persistState","persistKey","loadingMore","_isToolbarEnabled","buildListTemplate","_defaultBareTemplate","onInit","queryKeys","params","_initCollection","Collection","_restorePersistedState","on","_savePersistedState","_seedDayRangeParams","containerId","_onDayRangeChange","addChild","onAfterMount","fetchOnMount","lastFetchTime","fetch","_startAutoRefresh","onBeforeUnmount","_stopAutoRefresh","onBeforeRender","searchValue","getActiveFilters","search","hasMore","_computeHasMore","skeletonHtml","_buildSkeletonHtml","emptyStateHtml","_buildEmptyStateHtml","render","total","meta","count","Math","min","startEl","querySelector","endEl","totalEl","textContent","pageSizeSelect","renderPagination","updateFilterPills","renderFilterPresets","setupSearchClearListener","_hasFeedbackFeature","_buildFeedbackStyles","_loadingContent","_emptyContent","toolbar","buildToolbarTemplate","showMore","buildShowMoreTemplate","pagination","buildPaginationTemplate","_hasAnyFilters","_autoRefreshIndicatorEnabled","showAdd","showExport","titleBlock","_buildTitleBlockTemplate","rightSlot","dayRangeSlot","sortDropdown","buildSortDropdownTemplate","hasPresets","presetsInline","_presetsInline","presetInlineSlot","presetRowSlot","presetStyle","_buildPresetStyle","showIndicator","indicatorSlot","_buildAutoRefreshIndicatorSlot","_buildAutoRefreshIndicatorStyle","buildActionButtonsTemplate","buildFilterDropdownTemplate","buildSearchTemplate","push","addLabel","addButtonLabel","addIcon","addButtonIcon","format","dropdownItems","opt","button","handler","variant","permissions","checkPermissions","safeIcon","safeLabel","safeTitle","safeVariant","safeClassName","safeAction","iconHtml","labelHtml","dataAttrs","btnClass","currentSort","sort","dir","buildFilterList","allFilters","getAllAvailableFilters","activeFilters","prototype","call","activeClass","getFilterIcon","type","config","buildActivePills","countHtml","_buildResultCountSummary","_wrapPillRow","hasSearch","toString","filterEntries","entries","getFilterLabel","displayText","safeParamKey","inner","raw","message","defaultMarkup","_skeletonRowCount","n","rows","cards","i","es","activeCount","buttonHtml","titleHtml","messageHtml","shown","fmt","Number","toLocaleString","filteredSuffix","collectionOrClass","setCollection","defaults","_dayRangeDays","m","exec","parseInt","days","epoch","floor","Date","now","restEnabled","err","console","error","getRange","setRange","seen","presets","p","has","warn","add","_getPreset","_resolvePresetParams","preset","out","uid","getApp","activeUser","id","_presetMatches","resolved","active","every","inVal","_clearPresetParams","setFilter","getActivePreset","best","bestCount","applyPreset","clearPreset","k","applyFilters","onActionApplyPreset","preventDefault","getAttribute","container","innerHTML","_buildPresetSegment","activeKey","titleAttr","round","_pulseAutoRefreshIndicator","el","clearTimeout","setTimeout","remove","off","_onModelsAdded","_onModelsRemoved","_onCollectionReset","_onFetchStart","_onFetchEnd","defaultQuery","collectionParams","pageSize","preloaded","_buildItems","_renderChildren","itemsContainer","getChildElement","renders","entry","appendChild","view","Promise","resolve","catch","forEachItem","all","_clearItems","_createItemView","_applyPersistedSelections","_buildGroupHeaders","isMounted","headerView","removeChild","clear","resolver","get","prevKey","prevKeySet","rawKey","_createGroupHeaderView","_rebuildGroupHeaders","displayKey","_defaultGroupHeaderTemplate","_groupHeaderViewOptions","set","_model","_key","_index","_wireItemViewListeners","_onItemSelect","bind","_onItemDeselect","_dispatchRowClick","_onRowView","_onRowEdit","_onRowDelete","onRowClick","deselectItem","selectItem","models","indexOf","delete","from","getSelectedItems","callback","thisArg","TypeError","clearSelection","modelId","setItemTemplate","rerender","setTemplate","refresh","_stripeClassFor","ROW_STRIPE_TOKENS","classes","startsWith","refreshStripes","onItemView","Modal","hideLoading","showError","ViewClass","getItemViewClass","viewInstance","dialog","header","body","centered","getFormDialogConfig","getModelClass","DIALOG_OPTIONS","getModelName","onItemEdit","ModelClass","formConfig","getEditFormConfig","fields","result","modelForm","success","status","FormView","formFields","resp","save","onItemDelete","DELETE_TEMPLATE","renderTemplateString","confirm","confirmText","confirmClass","onActionAdd","onAdd","getAddFormConfig","form","addRequiresActiveGroup","group","activeGroup","addRequiresActiveUser","user","addFormDefaults","assign","onActionExport","source","download","onExport","MODEL_NAME","rawName","replace","test","_warnedMinifiedClasses","VIEW_CLASS","ADD_FORM","EDIT_FORM","FORM_DIALOG_CONFIG","Mustache","onActionRefresh","onActionApplySearch","searchTerm","onActionClearSearch","input","updateSearchInputs","inputs","paginationContainer","currentPage","totalPages","ceil","prevPage","nextPage","pages","visibleSet","visible","a","b","last","onActionPage","rawPage","max","page","isNaN","setParams","onChangePageSize","newSize","onActionShowMore","fetchMore","response","onActionSortOption","onActionCustomToolbarButton","idx","onActionAddFilter","filterKey","filterConfig","getFilterConfig","currentValue","buildFilterDialogField","newFilterValue","extractFilterValue","onActionEditFilter","formData","filter_value","startName","endName","onActionRemoveFilter","onActionClearAllFilters","preserved","fieldName","_start","_size","_sort","allParams","processedKeys","filterDef","inKey","fieldKey","f","af","x","charAt","toUpperCase","text","date","daterange","number","boolean","_filterKey","_filterName","_filterValue","rest","placeholder","placeHolder","displayFormat","separator","normalizeDateValue","toISOString","str","num","ms","startDate","begin","endDate","to","finish","valueArray","initial","defaultValue","stringValue","trueLabel","falseLabel","formResult","setTitle","setEyebrow","isFinite","_autoRefreshDocHidden","_bindAutoRefreshListeners","setInterval","_autoRefreshTick","clearInterval","_unbindAutoRefreshListeners","onBlur","onFocus","_autoRefreshResume","onVisibility","hidden","window","document","h","visibilityState","_autoRefreshShouldSkip","then","_persistStorageKey","_defaultPersistKey","location","pathname","endpoint","_persistStorage","ls","globalThis","localStorage","getItem","e","_readPersistedState","store","parsed","parse","_clearPersistedStorage","_writePersistedState","state","setItem","removeItem","saved","fromQuery","dayRange","_restoreHiddenColumns","dayRangeGteKey","_getHiddenColumnKeys","clearPersistedState","_onClearPersistedState"],"mappings":"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"}
|