web-mojo 2.5.5 → 2.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/admin.cjs.js +1 -1
  3. package/dist/admin.cjs.js.map +1 -1
  4. package/dist/admin.es.js +1 -1
  5. package/dist/admin.es.js.map +1 -1
  6. package/dist/auth.cjs.js +1 -1
  7. package/dist/auth.es.js +1 -1
  8. package/dist/charts.cjs.js +1 -1
  9. package/dist/charts.es.js +1 -1
  10. package/dist/chunks/ChatView-3d50GW02.js +2 -0
  11. package/dist/chunks/ChatView-3d50GW02.js.map +1 -0
  12. package/dist/chunks/ChatView-C27ckVwL.js +2 -0
  13. package/dist/chunks/ChatView-C27ckVwL.js.map +1 -0
  14. package/dist/chunks/{ListView-BxxGqz7q.js → ListView-C-jiqALE.js} +2 -2
  15. package/dist/chunks/{ListView-Wpby1PCA.js.map → ListView-C-jiqALE.js.map} +1 -1
  16. package/dist/chunks/{ListView-Wpby1PCA.js → ListView-zpCxyyjq.js} +2 -2
  17. package/dist/chunks/{ListView-BxxGqz7q.js.map → ListView-zpCxyyjq.js.map} +1 -1
  18. package/dist/chunks/{Passkeys-C2VoBjDn.js → Passkeys-B4bndv5b.js} +2 -2
  19. package/dist/chunks/{Passkeys-C2VoBjDn.js.map → Passkeys-B4bndv5b.js.map} +1 -1
  20. package/dist/chunks/{Passkeys-eZrcSyHX.js → Passkeys-CIhIxwb2.js} +2 -2
  21. package/dist/chunks/{Passkeys-eZrcSyHX.js.map → Passkeys-CIhIxwb2.js.map} +1 -1
  22. package/dist/chunks/{UserProfileView-CXJKMGy6.js → UserProfileView-B_HnFtsf.js} +2 -2
  23. package/dist/chunks/{UserProfileView-CXJKMGy6.js.map → UserProfileView-B_HnFtsf.js.map} +1 -1
  24. package/dist/chunks/{UserProfileView-D3MGkVVN.js → UserProfileView-DugtA_qG.js} +2 -2
  25. package/dist/chunks/{UserProfileView-D3MGkVVN.js.map → UserProfileView-DugtA_qG.js.map} +1 -1
  26. package/dist/chunks/{index-Dc0tdf4C.js → index-D-bZ5zeg.js} +2 -2
  27. package/dist/chunks/{index-Dc0tdf4C.js.map → index-D-bZ5zeg.js.map} +1 -1
  28. package/dist/chunks/{index-51V5UL7N.js → index-D-gO-M9M.js} +2 -2
  29. package/dist/chunks/{index-51V5UL7N.js.map → index-D-gO-M9M.js.map} +1 -1
  30. package/dist/chunks/{version-b3ZqvsDw.js → version-B1TH_fkK.js} +2 -2
  31. package/dist/chunks/{version-b3ZqvsDw.js.map → version-B1TH_fkK.js.map} +1 -1
  32. package/dist/chunks/{version-CcV-NTY8.js → version-DlfxFCfQ.js} +2 -2
  33. package/dist/chunks/{version-CcV-NTY8.js.map → version-DlfxFCfQ.js.map} +1 -1
  34. package/dist/docit.cjs.js +1 -1
  35. package/dist/docit.es.js +1 -1
  36. package/dist/index.cjs.js +1 -1
  37. package/dist/index.es.js +1 -1
  38. package/dist/lightbox.cjs.js +1 -1
  39. package/dist/lightbox.es.js +1 -1
  40. package/dist/timeline.cjs.js +1 -1
  41. package/dist/timeline.es.js +1 -1
  42. package/dist/user-profile.cjs.js +1 -1
  43. package/dist/user-profile.es.js +1 -1
  44. package/dist/web-mojo.lite.iife.js +8 -0
  45. package/dist/web-mojo.lite.iife.js.map +1 -1
  46. package/dist/web-mojo.lite.iife.min.js +1 -1
  47. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  48. package/package.json +1 -1
  49. package/dist/chunks/ChatView-Ctjijnsd.js +0 -2
  50. package/dist/chunks/ChatView-Ctjijnsd.js.map +0 -1
  51. package/dist/chunks/ChatView-cPwjzX7r.js +0 -2
  52. package/dist/chunks/ChatView-cPwjzX7r.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ListView-Wpby1PCA.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 *\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 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 // 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 // \"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 this._initCollection(this.options.collection || this.options.Collection);\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 }\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\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.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 <div class=\"list-view-container\">\n {{#loading}}\n <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 <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 ${toolbar}\n <div class=\"list-view-container\">\n {{#loading}}\n <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 <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.toolbarButtons && this.toolbarButtons.length > 0) ||\n this.options.showAdd ||\n this.options.showExport\n );\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 const rightGroup = `\n <div class=\"d-flex align-items-center gap-2 flex-wrap ${titleBlock ? 'ms-auto' : ''}\">\n ${this.buildActionButtonsTemplate()}\n ${sortDropdown}\n ${this.filterable ? this.buildFilterDropdownTemplate() : ''}\n ${this.searchable && this.searchPlacement === 'toolbar' ? this.buildSearchTemplate() : ''}\n ${dayRangeSlot}\n ${rightSlot}\n </div>\n `;\n\n return `\n <div class=\"table-action-buttons mb-3\">\n <div class=\"d-flex align-items-center gap-3 flex-wrap\">\n ${titleBlock}\n ${rightGroup}\n </div>\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 if (this.hideActivePills) return '';\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) return '';\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 ${pills}\n ${clearAllButton}\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 // 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 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\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 ...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 return ModelClass.MODEL_NAME ||\n ModelClass.name.replace(/Model$/, '') ||\n '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 /**\n * Permission check used by `toolbarButtons[].permissions`. Default: allow.\n * TableView and apps can override to integrate with their own ACL.\n */\n checkPermissions(_permissions) {\n return true;\n }\n\n /** Light HTML-escape for inline template strings. */\n escapeHtml(value) {\n if (value == null) return '';\n return String(value)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n\n // ============================================================\n // Cleanup\n // ============================================================\n\n async destroy() {\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","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","exportOptions","exportSource","persistSelection","onItemClick","clickAction","itemView","addForm","editForm","deleteTemplate","formDialogConfig","viewDialogOptions","fetchOnView","groupBy","groupHeaderTemplate","groupHeaderLabel","groupHeaderClass","groupHeaderStyle","GROUP_HEADER_STYLES","includes","groupHeaderViews","_renderOrder","loadingMore","_isToolbarEnabled","buildListTemplate","_defaultBareTemplate","onInit","_initCollection","Collection","_seedDayRangeParams","containerId","on","_onDayRangeChange","addChild","onAfterMount","fetchOnMount","lastFetchTime","fetch","onBeforeRender","searchValue","getActiveFilters","search","hasMore","_computeHasMore","render","total","meta","count","params","Math","min","startEl","querySelector","endEl","totalEl","textContent","pageSizeSelect","renderPagination","updateFilterPills","setupSearchClearListener","buildToolbarTemplate","buildShowMoreTemplate","buildPaginationTemplate","_hasAnyFilters","showAdd","showExport","titleBlock","_buildTitleBlockTemplate","rightSlot","dayRangeSlot","sortDropdown","buildSortDropdownTemplate","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","hasSearch","toString","filterEntries","entries","getFilterLabel","displayText","safeParamKey","collectionOrClass","setCollection","raw","defaults","_dayRangeDays","m","exec","parseInt","days","epoch","floor","Date","now","restEnabled","err","console","error","getRange","setRange","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","id","clear","resolver","get","prevKey","prevKeySet","rawKey","warn","_createGroupHeaderView","_rebuildGroupHeaders","displayKey","_defaultGroupHeaderTemplate","_groupHeaderViewOptions","set","_model","_key","_index","has","_wireItemViewListeners","_onItemSelect","bind","_onItemDeselect","_dispatchRowClick","_onRowView","_onRowEdit","_onRowDelete","onRowClick","deselectItem","selectItem","models","indexOf","delete","add","from","getSelectedItems","callback","thisArg","TypeError","clearSelection","modelId","setItemTemplate","rerender","setTemplate","refresh","_stripeClassFor","ROW_STRIPE_TOKENS","classes","startsWith","remove","refreshStripes","onItemView","Modal","hideLoading","showError","message","ViewClass","getItemViewClass","viewInstance","dialog","header","body","centered","getFormDialogConfig","getModelClass","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","getApp","activeGroup","addRequiresActiveUser","user","activeUser","addFormDefaults","assign","onActionExport","getAttribute","source","download","onExport","MODEL_NAME","replace","VIEW_CLASS","ADD_FORM","EDIT_FORM","FORM_DIALOG_CONFIG","Mustache","onActionRefresh","onActionApplySearch","searchTerm","setFilter","onActionClearSearch","input","container","innerHTML","updateSearchInputs","inputs","paginationContainer","currentPage","totalPages","ceil","prevPage","nextPage","pages","visibleSet","i","visible","a","b","last","p","onActionPage","preventDefault","rawPage","max","page","isNaN","setParams","onChangePageSize","newSize","onActionShowMore","fetchMore","response","onActionSortOption","onActionCustomToolbarButton","idx","onActionAddFilter","filterKey","filterConfig","getFilterConfig","currentValue","buildFilterDialogField","newFilterValue","extractFilterValue","applyFilters","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","test","num","Number","ms","startDate","begin","endDate","to","finish","valueArray","initial","defaultValue","stringValue","trueLabel","falseLabel","formResult","setTitle","el","setEyebrow","_permissions"],"mappings":"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"}
1
+ {"version":3,"file":"ListView-C-jiqALE.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 *\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 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 // 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 // \"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 this._initCollection(this.options.collection || this.options.Collection);\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 }\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\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.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 <div class=\"list-view-container\">\n {{#loading}}\n <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 <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 ${toolbar}\n <div class=\"list-view-container\">\n {{#loading}}\n <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 <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.toolbarButtons && this.toolbarButtons.length > 0) ||\n this.options.showAdd ||\n this.options.showExport\n );\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 const rightGroup = `\n <div class=\"d-flex align-items-center gap-2 flex-wrap ${titleBlock ? 'ms-auto' : ''}\">\n ${this.buildActionButtonsTemplate()}\n ${sortDropdown}\n ${this.filterable ? this.buildFilterDropdownTemplate() : ''}\n ${this.searchable && this.searchPlacement === 'toolbar' ? this.buildSearchTemplate() : ''}\n ${dayRangeSlot}\n ${rightSlot}\n </div>\n `;\n\n return `\n <div class=\"table-action-buttons mb-3\">\n <div class=\"d-flex align-items-center gap-3 flex-wrap\">\n ${titleBlock}\n ${rightGroup}\n </div>\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 if (this.hideActivePills) return '';\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) return '';\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 ${pills}\n ${clearAllButton}\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 // 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 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 return ModelClass.MODEL_NAME ||\n ModelClass.name.replace(/Model$/, '') ||\n '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 /**\n * Permission check used by `toolbarButtons[].permissions`. Default: allow.\n * TableView and apps can override to integrate with their own ACL.\n */\n checkPermissions(_permissions) {\n return true;\n }\n\n /** Light HTML-escape for inline template strings. */\n escapeHtml(value) {\n if (value == null) return '';\n return String(value)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n\n // ============================================================\n // Cleanup\n // ============================================================\n\n async destroy() {\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","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","exportOptions","exportSource","persistSelection","onItemClick","clickAction","itemView","addForm","editForm","deleteTemplate","formDialogConfig","viewDialogOptions","fetchOnView","groupBy","groupHeaderTemplate","groupHeaderLabel","groupHeaderClass","groupHeaderStyle","GROUP_HEADER_STYLES","includes","groupHeaderViews","_renderOrder","loadingMore","_isToolbarEnabled","buildListTemplate","_defaultBareTemplate","onInit","_initCollection","Collection","_seedDayRangeParams","containerId","on","_onDayRangeChange","addChild","onAfterMount","fetchOnMount","lastFetchTime","fetch","onBeforeRender","searchValue","getActiveFilters","search","hasMore","_computeHasMore","render","total","meta","count","params","Math","min","startEl","querySelector","endEl","totalEl","textContent","pageSizeSelect","renderPagination","updateFilterPills","setupSearchClearListener","buildToolbarTemplate","buildShowMoreTemplate","buildPaginationTemplate","_hasAnyFilters","showAdd","showExport","titleBlock","_buildTitleBlockTemplate","rightSlot","dayRangeSlot","sortDropdown","buildSortDropdownTemplate","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","hasSearch","toString","filterEntries","entries","getFilterLabel","displayText","safeParamKey","collectionOrClass","setCollection","raw","defaults","_dayRangeDays","m","exec","parseInt","days","epoch","floor","Date","now","restEnabled","err","console","error","getRange","setRange","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","id","clear","resolver","get","prevKey","prevKeySet","rawKey","warn","_createGroupHeaderView","_rebuildGroupHeaders","displayKey","_defaultGroupHeaderTemplate","_groupHeaderViewOptions","set","_model","_key","_index","has","_wireItemViewListeners","_onItemSelect","bind","_onItemDeselect","_dispatchRowClick","_onRowView","_onRowEdit","_onRowDelete","onRowClick","deselectItem","selectItem","models","indexOf","delete","add","from","getSelectedItems","callback","thisArg","TypeError","clearSelection","modelId","setItemTemplate","rerender","setTemplate","refresh","_stripeClassFor","ROW_STRIPE_TOKENS","classes","startsWith","remove","refreshStripes","onItemView","Modal","hideLoading","showError","message","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","getApp","activeGroup","addRequiresActiveUser","user","activeUser","addFormDefaults","assign","onActionExport","getAttribute","source","download","onExport","MODEL_NAME","replace","VIEW_CLASS","ADD_FORM","EDIT_FORM","FORM_DIALOG_CONFIG","Mustache","onActionRefresh","onActionApplySearch","searchTerm","setFilter","onActionClearSearch","input","container","innerHTML","updateSearchInputs","inputs","paginationContainer","currentPage","totalPages","ceil","prevPage","nextPage","pages","visibleSet","i","visible","a","b","last","p","onActionPage","preventDefault","rawPage","max","page","isNaN","setParams","onChangePageSize","newSize","onActionShowMore","fetchMore","response","onActionSortOption","onActionCustomToolbarButton","idx","onActionAddFilter","filterKey","filterConfig","getFilterConfig","currentValue","buildFilterDialogField","newFilterValue","extractFilterValue","applyFilters","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","test","num","Number","ms","startDate","begin","endDate","to","finish","valueArray","initial","defaultValue","stringValue","trueLabel","falseLabel","formResult","setTitle","el","setEyebrow","_permissions"],"mappings":"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"}