web-mojo 2.2.89 → 2.2.91

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 (81) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/admin.cjs.js +1 -1
  3. package/dist/admin.cjs.js.map +1 -1
  4. package/dist/admin.css +48 -2
  5. package/dist/admin.es.js +1 -1
  6. package/dist/admin.es.js.map +1 -1
  7. package/dist/auth.cjs.js +1 -1
  8. package/dist/auth.es.js +1 -1
  9. package/dist/charts.cjs.js +1 -1
  10. package/dist/charts.es.js +1 -1
  11. package/dist/chunks/{ChatView-C9VR3pfa.js → ChatView-BeJiXws6.js} +2 -2
  12. package/dist/chunks/{ChatView-C9VR3pfa.js.map → ChatView-BeJiXws6.js.map} +1 -1
  13. package/dist/chunks/{ChatView-wuMHOvns.js → ChatView-DhV0Ycul.js} +2 -2
  14. package/dist/chunks/{ChatView-wuMHOvns.js.map → ChatView-DhV0Ycul.js.map} +1 -1
  15. package/dist/chunks/{Dialog-EZ9XD2AO.js → Dialog-DZuk-4Ck.js} +2 -2
  16. package/dist/chunks/{Dialog-EZ9XD2AO.js.map → Dialog-DZuk-4Ck.js.map} +1 -1
  17. package/dist/chunks/{FormView-BqqtbPVb.js → FormView-C0oytbfM.js} +2 -2
  18. package/dist/chunks/{FormView-BqqtbPVb.js.map → FormView-C0oytbfM.js.map} +1 -1
  19. package/dist/chunks/GroupView-DAzE4A-4.js +2 -0
  20. package/dist/chunks/GroupView-DAzE4A-4.js.map +1 -0
  21. package/dist/chunks/GroupView-FfwdNW9H.js +2 -0
  22. package/dist/chunks/GroupView-FfwdNW9H.js.map +1 -0
  23. package/dist/chunks/{ListView-DekXjgMV.js → ListView-BZTAh7jk.js} +2 -2
  24. package/dist/chunks/ListView-BZTAh7jk.js.map +1 -0
  25. package/dist/chunks/{ListView-DqZ6BQKk.js → ListView-DnjpwUSD.js} +2 -2
  26. package/dist/chunks/ListView-DnjpwUSD.js.map +1 -0
  27. package/dist/chunks/{MetricsMiniChartWidget-B4CcNlv-.js → MetricsMiniChartWidget-BVbBD-su.js} +2 -2
  28. package/dist/chunks/{MetricsMiniChartWidget-B4CcNlv-.js.map → MetricsMiniChartWidget-BVbBD-su.js.map} +1 -1
  29. package/dist/chunks/{MetricsMiniChartWidget-y-KklF3c.js → MetricsMiniChartWidget-DnIxJqxS.js} +2 -2
  30. package/dist/chunks/{MetricsMiniChartWidget-y-KklF3c.js.map → MetricsMiniChartWidget-DnIxJqxS.js.map} +1 -1
  31. package/dist/chunks/{Modal-DoIYUAKi.js → Modal-RvUBle9X.js} +2 -2
  32. package/dist/chunks/{Modal-DoIYUAKi.js.map → Modal-RvUBle9X.js.map} +1 -1
  33. package/dist/chunks/{PDFViewer-DYAJqRAV.js → PDFViewer-XPhqOvSX.js} +2 -2
  34. package/dist/chunks/{PDFViewer-DYAJqRAV.js.map → PDFViewer-XPhqOvSX.js.map} +1 -1
  35. package/dist/chunks/{Passkeys-DNpner4L.js → Passkeys-C7-z9-v5.js} +2 -2
  36. package/dist/chunks/{Passkeys-DNpner4L.js.map → Passkeys-C7-z9-v5.js.map} +1 -1
  37. package/dist/chunks/{Passkeys-DapTiqNW.js → Passkeys-ZNOvvdIC.js} +2 -2
  38. package/dist/chunks/{Passkeys-DapTiqNW.js.map → Passkeys-ZNOvvdIC.js.map} +1 -1
  39. package/dist/chunks/{TokenManager-BLd64x-1.js → TokenManager-D2YDvzww.js} +2 -2
  40. package/dist/chunks/{TokenManager-BLd64x-1.js.map → TokenManager-D2YDvzww.js.map} +1 -1
  41. package/dist/chunks/{UserProfileView-q29bxm0T.js → UserProfileView-CxH58ZvK.js} +2 -2
  42. package/dist/chunks/{UserProfileView-q29bxm0T.js.map → UserProfileView-CxH58ZvK.js.map} +1 -1
  43. package/dist/chunks/{UserProfileView-BDD3WOjd.js → UserProfileView-gTSICPvR.js} +2 -2
  44. package/dist/chunks/{UserProfileView-BDD3WOjd.js.map → UserProfileView-gTSICPvR.js.map} +1 -1
  45. package/dist/chunks/{WebApp-DgmB8vpR.js → WebApp-hSbcg7WF.js} +2 -2
  46. package/dist/chunks/{WebApp-DgmB8vpR.js.map → WebApp-hSbcg7WF.js.map} +1 -1
  47. package/dist/chunks/{WebSocketClient-C9VS1m8v.js → WebSocketClient-BRxSW8O4.js} +2 -2
  48. package/dist/chunks/WebSocketClient-BRxSW8O4.js.map +1 -0
  49. package/dist/chunks/{WebSocketClient-CMV76kSt.js → WebSocketClient-Ctjkjn45.js} +2 -2
  50. package/dist/chunks/WebSocketClient-Ctjkjn45.js.map +1 -0
  51. package/dist/chunks/{index-iPINfbSy.js → index-BXBnnQiG.js} +2 -2
  52. package/dist/chunks/{index-iPINfbSy.js.map → index-BXBnnQiG.js.map} +1 -1
  53. package/dist/chunks/{index-DXlIy6bc.js → index-C1Qa4dbR.js} +2 -2
  54. package/dist/chunks/{index-DXlIy6bc.js.map → index-C1Qa4dbR.js.map} +1 -1
  55. package/dist/chunks/{version-BvwIbhO-.js → version-CmEMzl3n.js} +2 -2
  56. package/dist/chunks/{version-BvwIbhO-.js.map → version-CmEMzl3n.js.map} +1 -1
  57. package/dist/chunks/{version-CToejY47.js → version-fsZmab6w.js} +2 -2
  58. package/dist/chunks/{version-CToejY47.js.map → version-fsZmab6w.js.map} +1 -1
  59. package/dist/css/web-mojo.css +1 -1
  60. package/dist/docit.cjs.js +1 -1
  61. package/dist/docit.es.js +1 -1
  62. package/dist/index.cjs.js +1 -1
  63. package/dist/index.cjs.js.map +1 -1
  64. package/dist/index.es.js +1 -1
  65. package/dist/index.es.js.map +1 -1
  66. package/dist/lightbox.cjs.js +1 -1
  67. package/dist/lightbox.es.js +1 -1
  68. package/dist/map.es.js +1 -1
  69. package/dist/timeline.cjs.js +1 -1
  70. package/dist/timeline.es.js +1 -1
  71. package/dist/user-profile.cjs.js +1 -1
  72. package/dist/user-profile.es.js +1 -1
  73. package/dist/web-mojo.lite.iife.js +5 -1
  74. package/dist/web-mojo.lite.iife.js.map +1 -1
  75. package/dist/web-mojo.lite.iife.min.js +1 -1
  76. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  77. package/package.json +1 -1
  78. package/dist/chunks/ListView-DekXjgMV.js.map +0 -1
  79. package/dist/chunks/ListView-DqZ6BQKk.js.map +0 -1
  80. package/dist/chunks/WebSocketClient-C9VS1m8v.js.map +0 -1
  81. package/dist/chunks/WebSocketClient-CMV76kSt.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-mojo",
3
- "version": "2.2.89",
3
+ "version": "2.2.91",
4
4
  "description": "WEB-MOJO - A lightweight JavaScript framework for building data-driven web applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs.js",
@@ -1 +0,0 @@
1
- {"version":3,"file":"ListView-DekXjgMV.js","sources":["../../src/core/views/list/ListViewItem.js","../../src/core/views/list/ListView.js"],"sourcesContent":["/**\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\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 * 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 * 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 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 * 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 * 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 *\n * @example\n * // Basic usage with custom item template\n * const listView = new ListView({\n * collection: userCollection,\n * itemTemplate: '<div class=\"user-item\">{{name}} - {{email}}</div>',\n * selectionMode: 'single'\n * });\n *\n * // Custom template with model fields\n * const productList = new ListView({\n * collection: productCollection,\n * itemTemplate: `\n * <div class=\"product-card\" data-action=\"select\">\n * <h4>{{name}}</h4>\n * <p class=\"price\">{{price|currency}}</p>\n * <p>{{description|truncate(100)}}</p>\n * </div>\n * `,\n * selectionMode: 'multiple'\n * });\n *\n * // Using custom item class with template\n * const customList = new ListView({\n * collection: myCollection,\n * itemClass: CustomListItem, // Your custom ListViewItem subclass\n * itemTemplate: '<div>{{title}}</div>', // Passed as 'template' to itemClass constructor\n * selectionMode: 'none'\n * });\n *\n * // Dynamic template update\n * listView.setItemTemplate('<div class=\"compact\">{{name}}</div>', true);\n */\n\nimport View from '@core/View.js';\nimport Collection from '@core/Collection.js';\nimport ListViewItem from './ListViewItem.js';\n\nclass ListView extends View {\n constructor(options = {}) {\n super({\n className: 'list-view',\n template: `\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 ...options\n });\n\n // ListView specific properties\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 // Configuration\n this.itemTemplate = options.itemTemplate || null; // Template passed to each item's view\n this.itemClass = options.itemClass || ListViewItem; // Class for creating item views\n this.selectionMode = options.selectionMode || 'none'; // none, single, multiple\n this.emptyMessage = options.emptyMessage || 'No items to display';\n this.loading = false;\n this.isEmpty = true;\n\n }\n\n /**\n * Override onInit to set up initial state\n */\n async onInit() {\n // Initial render will happen automatically\n this._initCollection(this.options.collection || this.options.Collection);\n }\n\n\n /**\n * Initialize the collection\n */\n _initCollection(collectionOrClass) {\n if (!collectionOrClass) {\n console.log('Collection not provided');\n return;\n };\n\n // Check if it's already a Collection instance\n if (collectionOrClass instanceof Collection) {\n this.setCollection(collectionOrClass);\n }\n // Check if it's a Collection class\n else if (typeof collectionOrClass === 'function') {\n const collection = new collectionOrClass();\n this.setCollection(collection);\n }\n // Check if it's an array of data\n else if (Array.isArray(collectionOrClass)) {\n const collection = new Collection(null, {}, collectionOrClass);\n this.setCollection(collection);\n }\n }\n\n /**\n * Set the collection for this list view\n */\n setCollection(collection) {\n if (this.collection === collection) return this;\n\n // Clean up old collection listeners\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\n if (this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.collectionParams };\n }\n\n // Set up new collection listeners\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 // Build items for existing models\n this._buildItems();\n }\n\n return this;\n }\n\n async _renderChildren() {\n await super._renderChildren();\n const itemsContainer = this.getChildElement(\"items\");\n if (!itemsContainer) {\n // console.warn('ListView: items container not found');\n return;\n }\n this.forEachItem((item, index) => {\n itemsContainer.appendChild(item.element);\n item.render(false);\n });\n }\n\n /**\n * Build item views for all models in collection\n */\n _buildItems() {\n // Clear existing items\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\n // Create item views for each model\n this.collection.forEach((model, index) => {\n this._createItemView(model, index);\n });\n\n this.emit('list:loaded', { count: this.collection.length() });\n\n // Render if already mounted\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Create an item view for a model\n * The itemTemplate is passed as the template option to the itemClass constructor\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate, // Pass the itemTemplate to the item view\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', this._onItemSelect.bind(this));\n itemView.on('item:deselect', this._onItemDeselect.bind(this));\n\n return itemView;\n }\n\n /**\n * Clear all item views\n */\n _clearItems() {\n this.forEachItem(itemView => {\n this.removeChild(itemView.id);\n });\n this.itemViews.clear();\n this.selectedItems.clear();\n }\n\n /**\n * Handle models added to collection\n */\n _onModelsAdded(event) {\n const { models } = event;\n\n models.forEach(model => {\n const index = this.collection.models.indexOf(model);\n this._createItemView(model, index);\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to show new items\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle models removed from collection\n */\n _onModelsRemoved(event) {\n const { models } = event;\n\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\n // Re-render to update display\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n\n if (this.isEmpty) {\n this.emit('list:empty');\n }\n }\n\n /**\n * Handle collection reset\n */\n _onCollectionReset(_event) {\n this._buildItems();\n }\n\n /**\n * Handle fetch start\n */\n _onFetchStart() {\n this.loading = true;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle fetch end\n */\n _onFetchEnd() {\n this.loading = false;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle item selection\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 // Deselect all other items\n this.itemViews.forEach((view, id) => {\n if (id !== model.id && view.selected) {\n view.deselect();\n }\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: item,\n model: model\n });\n }\n\n /**\n * Handle item deselection\n */\n _onItemDeselect(event) {\n const { model } = event;\n\n this.selectedItems.delete(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: event.item,\n model: model\n });\n }\n\n /**\n * Get selected items\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 /**\n * Iterate over each item view in the list\n * @param {function} callback - Function to execute for each item (itemView, model, index)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {ListView} Returns the ListView for chaining\n */\n forEachItem(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n let index = 0;\n this.itemViews.forEach((itemView, modelId) => {\n callback.call(thisArg, itemView, itemView.model, index++);\n });\n\n return this;\n }\n\n /**\n * Clear selection\n */\n clearSelection() {\n this.forEachItem(itemView => {\n if (itemView.selected) {\n itemView.deselect();\n }\n });\n this.selectedItems.clear();\n\n this.emit('selection:change', {\n selected: []\n });\n }\n\n /**\n * Select item by model ID\n */\n selectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.select();\n }\n return this;\n }\n\n /**\n * Deselect item by model ID\n */\n deselectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.deselect();\n }\n return this;\n }\n\n /**\n * Set or update the item template\n * @param {string} template - New template string for items\n * @param {boolean} rerender - Whether to re-render existing items with new template\n * @returns {ListView} Returns the ListView for chaining\n */\n setItemTemplate(template, rerender = false) {\n this.itemTemplate = template;\n\n if (rerender && this.itemViews.size > 0) {\n // Update template for all existing item views\n this.forEachItem((itemView) => {\n itemView.setTemplate(template);\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n }\n\n return this;\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 /**\n * Refresh the list (re-fetch if collection supports it)\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 * Override destroy to clean up\n */\n async destroy() {\n // Clean up collection listeners\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 // Clear items\n this._clearItems();\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListView;\n"],"names":["ListViewItem","View","constructor","options","super","className","this","selected","index","listView","template","onActionSelect","event","_element","stopPropagation","deselect","select","addClass","emit","item","model","data","toJSON","removeClass","onActionDefault","action","_event","setIndex","element","setAttribute","setSelected","destroy","ListView","collection","itemViews","Map","selectedItems","Set","itemTemplate","itemClass","selectionMode","emptyMessage","loading","isEmpty","onInit","_initCollection","Collection","collectionOrClass","setCollection","Array","isArray","off","_onModelsAdded","_onModelsRemoved","_onCollectionReset","_onFetchStart","_onFetchEnd","defaultQuery","collectionParams","params","on","_buildItems","_renderChildren","itemsContainer","getChildElement","forEachItem","appendChild","render","_clearItems","forEach","_createItemView","count","length","isMounted","has","id","itemView","set","_onItemSelect","bind","_onItemDeselect","removeChild","clear","models","indexOf","get","delete","view","add","from","getSelectedItems","push","callback","thisArg","TypeError","modelId","call","clearSelection","selectItem","deselectItem","setItemTemplate","rerender","size","setTemplate","onAfterMount","fetchOnMount","lastFetchTime","fetch","refresh","restEnabled"],"mappings":"oDAoBA,MAAMA,qBAAqBC,EACzB,WAAAC,CAAYC,EAAU,IACpBC,MAAM,CACJC,UAAW,oBACRF,IAILG,KAAKC,UAAW,EAChBD,KAAKE,MAAQL,EAAQK,OAAS,EAC9BF,KAAKG,SAAWN,EAAQM,UAAY,KAG/BH,KAAKI,WACRJ,KAAKI,SAAW,+lBAepB,CAKA,oBAAMC,CAAeC,EAAOC,GAC1BD,EAAME,kBAEFR,KAAKC,SACPD,KAAKS,WAELT,KAAKU,QAET,CAKA,MAAAA,GACMV,KAAKC,WAETD,KAAKC,UAAW,EAChBD,KAAKW,SAAS,YAGdX,KAAKY,KAAK,cAAe,CACvBC,KAAMb,KACNc,MAAOd,KAAKc,MACZZ,MAAOF,KAAKE,MACZa,KAAMf,KAAKc,OAAOE,OAAShB,KAAKc,MAAME,SAAWhB,KAAKc,QAIpDd,KAAKG,UACPH,KAAKG,SAASS,KAAK,cAAe,CAChCC,KAAMb,KACNc,MAAOd,KAAKc,MACZZ,MAAOF,KAAKE,MACZa,KAAMf,KAAKc,OAAOE,OAAShB,KAAKc,MAAME,SAAWhB,KAAKc,QAG5D,CAKA,QAAAL,GACOT,KAAKC,WAEVD,KAAKC,UAAW,EAChBD,KAAKiB,YAAY,YAGjBjB,KAAKY,KAAK,gBAAiB,CACzBC,KAAMb,KACNc,MAAOd,KAAKc,MACZZ,MAAOF,KAAKE,MACZa,KAAMf,KAAKc,OAAOE,OAAShB,KAAKc,MAAME,SAAWhB,KAAKc,QAIpDd,KAAKG,UACPH,KAAKG,SAASS,KAAK,gBAAiB,CAClCC,KAAMb,KACNc,MAAOd,KAAKc,MACZZ,MAAOF,KAAKE,MACZa,KAAMf,KAAKc,OAAOE,OAAShB,KAAKc,MAAME,SAAWhB,KAAKc,QAG5D,CAKA,qBAAMI,CAAgBC,EAAQC,EAAQb,GAEpCP,KAAKY,KAAK,aAAc,CACtBC,KAAMb,KACNc,MAAOd,KAAKc,MACZZ,MAAOF,KAAKE,MACZiB,SACAJ,KAAMf,KAAKc,OAAOE,OAAShB,KAAKc,MAAME,SAAWhB,KAAKc,QAIpDd,KAAKG,UACPH,KAAKG,SAASS,KAAK,aAAc,CAC/BC,KAAMb,KACNc,MAAOd,KAAKc,MACZZ,MAAOF,KAAKE,MACZiB,SACAJ,KAAMf,KAAKc,OAAOE,OAAShB,KAAKc,MAAME,SAAWhB,KAAKc,OAG5D,CAKA,QAAAO,CAASnB,GAGP,OAFAF,KAAKE,MAAQA,EACbF,KAAKsB,QAAQC,aAAa,aAAcrB,GACjCF,IACT,CAKA,WAAAwB,CAAYvB,GAMV,OALIA,EACFD,KAAKU,SAELV,KAAKS,WAEAT,IACT,CAKA,aAAMyB,GAEJzB,KAAKG,SAAW,WAGVL,MAAM2B,SACd,EC5HF,MAAMC,iBAAiB/B,EACrB,WAAAC,CAAYC,EAAU,IACpBC,MAAM,CACJC,UAAW,YACXK,SAAU,+qBAsBPP,IAILG,KAAK2B,WAAa,KAClB3B,KAAK4B,6BAAgBC,IACrB7B,KAAK8B,iCAAoBC,IAGzB/B,KAAKgC,aAAenC,EAAQmC,cAAgB,KAC5ChC,KAAKiC,UAAYpC,EAAQoC,WAAavC,aACtCM,KAAKkC,cAAgBrC,EAAQqC,eAAiB,OAC9ClC,KAAKmC,aAAetC,EAAQsC,cAAgB,sBAC5CnC,KAAKoC,SAAU,EACfpC,KAAKqC,SAAU,CAEjB,CAKA,YAAMC,GAEJtC,KAAKuC,gBAAgBvC,KAAKH,QAAQ8B,YAAc3B,KAAKH,QAAQ2C,WAC/D,CAMA,eAAAD,CAAgBE,GACd,GAAKA,EAML,GAAIA,aAA6BD,EAC/BxC,KAAK0C,cAAcD,QACrB,GAEsC,mBAAtBA,EAAkC,CAChD,MAAMd,EAAa,IAAIc,EACvBzC,KAAK0C,cAAcf,EACrB,MAAA,GAESgB,MAAMC,QAAQH,GAAoB,CACzC,MAAMd,EAAa,IAAIa,EAAW,KAAM,CAAA,EAAIC,GAC5CzC,KAAK0C,cAAcf,EACrB,CACF,CAKA,aAAAe,CAAcf,GACZ,OAAI3B,KAAK2B,aAAeA,IAGpB3B,KAAK2B,aACP3B,KAAK2B,WAAWkB,IAAI,MAAO7C,KAAK8C,eAAgB9C,MAChDA,KAAK2B,WAAWkB,IAAI,SAAU7C,KAAK+C,iBAAkB/C,MACrDA,KAAK2B,WAAWkB,IAAI,QAAS7C,KAAKgD,mBAAoBhD,MACtDA,KAAK2B,WAAWkB,IAAI,cAAe7C,KAAKiD,cAAejD,MACvDA,KAAK2B,WAAWkB,IAAI,YAAa7C,KAAKkD,YAAalD,OAGrDA,KAAK2B,WAAaA,EAEd3B,KAAKH,QAAQsD,eAAiBnD,KAAKH,QAAQuD,mBAC3CpD,KAAK2B,WAAW0B,OAAS,IAAKrD,KAAK2B,WAAW0B,UAAWrD,KAAKH,QAAQsD,eAGtEnD,KAAKH,QAAQuD,mBACbpD,KAAK2B,WAAW0B,OAAU,IAAKrD,KAAK2B,WAAW0B,UAAWrD,KAAKH,QAAQuD,mBAIvEpD,KAAK2B,aACP3B,KAAK2B,WAAW2B,GAAG,MAAOtD,KAAK8C,eAAgB9C,MAC/CA,KAAK2B,WAAW2B,GAAG,SAAUtD,KAAK+C,iBAAkB/C,MACpDA,KAAK2B,WAAW2B,GAAG,QAAStD,KAAKgD,mBAAoBhD,MACrDA,KAAK2B,WAAW2B,GAAG,cAAetD,KAAKiD,cAAejD,MACtDA,KAAK2B,WAAW2B,GAAG,YAAatD,KAAKkD,YAAalD,MAGlDA,KAAKuD,gBA9BoCvD,IAkC7C,CAEA,qBAAMwD,SACI1D,MAAM0D,kBACZ,MAAMC,EAAiBzD,KAAK0D,gBAAgB,SACvCD,GAILzD,KAAK2D,YAAY,CAAC9C,EAAMX,KACtBuD,EAAeG,YAAY/C,EAAKS,SAChCT,EAAKgD,QAAO,IAElB,CAKA,WAAAN,GAIE,GAFAvD,KAAK8D,eAEA9D,KAAK2B,YAAc3B,KAAK2B,WAAWU,UAGtC,OAFArC,KAAKqC,SAAU,OACfrC,KAAKY,KAAK,cAIZZ,KAAKqC,SAAU,EAGfrC,KAAK2B,WAAWoC,QAAQ,CAACjD,EAAOZ,KAC9BF,KAAKgE,gBAAgBlD,EAAOZ,KAG9BF,KAAKY,KAAK,cAAe,CAAEqD,MAAOjE,KAAK2B,WAAWuC,WAG9ClE,KAAKmE,aACPnE,KAAK6D,QAET,CAMA,eAAAG,CAAgBlD,EAAOZ,GAErB,GAAIF,KAAK4B,UAAUwC,IAAItD,EAAMuD,IAAK,OAElC,MAAMC,EAAW,IAAItE,KAAKiC,UAAU,CAClCnB,QACAZ,QACAC,SAAUH,KACVI,SAAUJ,KAAKgC,eAUjB,OANAhC,KAAK4B,UAAU2C,IAAIzD,EAAMuD,GAAIC,GAG7BA,EAAShB,GAAG,cAAetD,KAAKwE,cAAcC,KAAKzE,OACnDsE,EAAShB,GAAG,gBAAiBtD,KAAK0E,gBAAgBD,KAAKzE,OAEhDsE,CACT,CAKA,WAAAR,GACE9D,KAAK2D,YAAYW,IACftE,KAAK2E,YAAYL,EAASD,MAE5BrE,KAAK4B,UAAUgD,QACf5E,KAAK8B,cAAc8C,OACrB,CAKA,cAAA9B,CAAexC,GACb,MAAMuE,OAAEA,GAAWvE,EAEnBuE,EAAOd,QAAQjD,IACb,MAAMZ,EAAQF,KAAK2B,WAAWkD,OAAOC,QAAQhE,GAC7Cd,KAAKgE,gBAAgBlD,EAAOZ,KAG9BF,KAAKqC,QAAUrC,KAAK2B,WAAWU,WAG1BrC,KAAKoC,SAAWpC,KAAKmE,aACxBnE,KAAK6D,QAET,CAKA,gBAAAd,CAAiBzC,GACf,MAAMuE,OAAEA,GAAWvE,EAEnBuE,EAAOd,QAAQjD,IACb,MAAMwD,EAAWtE,KAAK4B,UAAUmD,IAAIjE,EAAMuD,IACtCC,IACFtE,KAAK2E,YAAYL,EAASD,IAC1BrE,KAAK4B,UAAUoD,OAAOlE,EAAMuD,IAC5BrE,KAAK8B,cAAckD,OAAOlE,EAAMuD,OAIpCrE,KAAKqC,QAAUrC,KAAK2B,WAAWU,WAG1BrC,KAAKoC,SAAWpC,KAAKmE,aACxBnE,KAAK6D,SAGH7D,KAAKqC,SACPrC,KAAKY,KAAK,aAEd,CAKA,kBAAAoC,CAAmB5B,GACjBpB,KAAKuD,aACP,CAKA,aAAAN,GACEjD,KAAKoC,SAAU,EACXpC,KAAKmE,aACPnE,KAAK6D,QAET,CAKA,WAAAX,GACElD,KAAKoC,SAAU,EACXpC,KAAKmE,aACPnE,KAAK6D,QAET,CAKA,aAAAW,CAAclE,GACZ,MAAMQ,MAAEA,EAAAD,KAAOA,GAASP,EAEG,SAAvBN,KAAKkC,eAKkB,WAAvBlC,KAAKkC,gBAEPlC,KAAK4B,UAAUmC,QAAQ,CAACkB,EAAMZ,KACxBA,IAAOvD,EAAMuD,IAAMY,EAAKhF,UAC1BgF,EAAKxE,aAGTT,KAAK8B,cAAc8C,SAGrB5E,KAAK8B,cAAcoD,IAAIpE,EAAMuD,IAE7BrE,KAAKY,KAAK,mBAAoB,CAC5BX,SAAU0C,MAAMwC,KAAKnF,KAAK8B,eAC1BjB,OACAC,WAnBAD,EAAKJ,UAqBT,CAKA,eAAAiE,CAAgBpE,GACd,MAAMQ,MAAEA,GAAUR,EAElBN,KAAK8B,cAAckD,OAAOlE,EAAMuD,IAEhCrE,KAAKY,KAAK,mBAAoB,CAC5BX,SAAU0C,MAAMwC,KAAKnF,KAAK8B,eAC1BjB,KAAMP,EAAMO,KACZC,SAEJ,CAKA,gBAAAsE,GACE,MAAMnF,EAAW,GAWjB,OAVAD,KAAK8B,cAAciC,QAAQM,IACzB,MAAMC,EAAWtE,KAAK4B,UAAUmD,IAAIV,GAChCC,GACFrE,EAASoF,KAAK,CACZJ,KAAMX,EACNxD,MAAOwD,EAASxD,MAChBC,KAAMuD,EAASxD,OAAOE,OAASsD,EAASxD,MAAME,SAAWsD,EAASxD,UAIjEb,CACT,CAQA,WAAA0D,CAAY2B,EAAUC,GACpB,GAAwB,mBAAbD,EACT,MAAM,IAAIE,UAAU,+BAGtB,IAAItF,EAAQ,EAKZ,OAJAF,KAAK4B,UAAUmC,QAAQ,CAACO,EAAUmB,KAChCH,EAASI,KAAKH,EAASjB,EAAUA,EAASxD,MAAOZ,OAG5CF,IACT,CAKA,cAAA2F,GACE3F,KAAK2D,YAAYW,IACXA,EAASrE,UACXqE,EAAS7D,aAGbT,KAAK8B,cAAc8C,QAEnB5E,KAAKY,KAAK,mBAAoB,CAC5BX,SAAU,IAEd,CAKA,UAAA2F,CAAWH,GACT,MAAMnB,EAAWtE,KAAK4B,UAAUmD,IAAIU,GAIpC,OAHInB,GACFA,EAAS5D,SAEJV,IACT,CAKA,YAAA6F,CAAaJ,GACX,MAAMnB,EAAWtE,KAAK4B,UAAUmD,IAAIU,GAIpC,OAHInB,GACFA,EAAS7D,WAEJT,IACT,CAQA,eAAA8F,CAAgB1F,EAAU2F,GAAW,GAanC,OAZA/F,KAAKgC,aAAe5B,EAEhB2F,GAAY/F,KAAK4B,UAAUoE,KAAO,GAEpChG,KAAK2D,YAAaW,IAChBA,EAAS2B,YAAY7F,GACjBkE,EAASH,aACXG,EAAST,WAKR7D,IACT,CAEA,kBAAMkG,SACIpG,MAAMoG,gBACRlG,KAAK2B,aAAe3B,KAAKH,QAAQsG,cAAiBnG,KAAK2B,WAAWyE,eAClEpG,KAAK2B,WAAW0E,OAExB,CAKA,aAAMC,GACJ,GAAItG,KAAK2B,YAAc3B,KAAK2B,WAAW4E,YACrC,aAAavG,KAAK2B,WAAW0E,QAE/BrG,KAAKuD,aACP,CAKA,aAAM9B,GAEAzB,KAAK2B,aACP3B,KAAK2B,WAAWkB,IAAI,MAAO7C,KAAK8C,eAAgB9C,MAChDA,KAAK2B,WAAWkB,IAAI,SAAU7C,KAAK+C,iBAAkB/C,MACrDA,KAAK2B,WAAWkB,IAAI,QAAS7C,KAAKgD,mBAAoBhD,MACtDA,KAAK2B,WAAWkB,IAAI,cAAe7C,KAAKiD,cAAejD,MACvDA,KAAK2B,WAAWkB,IAAI,YAAa7C,KAAKkD,YAAalD,OAIrDA,KAAK8D,oBAGChE,MAAM2B,SACd"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ListView-DqZ6BQKk.js","sources":["../../src/core/views/list/ListViewItem.js","../../src/core/views/list/ListView.js"],"sourcesContent":["/**\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\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 * 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 * 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 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 * 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 * 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 *\n * @example\n * // Basic usage with custom item template\n * const listView = new ListView({\n * collection: userCollection,\n * itemTemplate: '<div class=\"user-item\">{{name}} - {{email}}</div>',\n * selectionMode: 'single'\n * });\n *\n * // Custom template with model fields\n * const productList = new ListView({\n * collection: productCollection,\n * itemTemplate: `\n * <div class=\"product-card\" data-action=\"select\">\n * <h4>{{name}}</h4>\n * <p class=\"price\">{{price|currency}}</p>\n * <p>{{description|truncate(100)}}</p>\n * </div>\n * `,\n * selectionMode: 'multiple'\n * });\n *\n * // Using custom item class with template\n * const customList = new ListView({\n * collection: myCollection,\n * itemClass: CustomListItem, // Your custom ListViewItem subclass\n * itemTemplate: '<div>{{title}}</div>', // Passed as 'template' to itemClass constructor\n * selectionMode: 'none'\n * });\n *\n * // Dynamic template update\n * listView.setItemTemplate('<div class=\"compact\">{{name}}</div>', true);\n */\n\nimport View from '@core/View.js';\nimport Collection from '@core/Collection.js';\nimport ListViewItem from './ListViewItem.js';\n\nclass ListView extends View {\n constructor(options = {}) {\n super({\n className: 'list-view',\n template: `\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 ...options\n });\n\n // ListView specific properties\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 // Configuration\n this.itemTemplate = options.itemTemplate || null; // Template passed to each item's view\n this.itemClass = options.itemClass || ListViewItem; // Class for creating item views\n this.selectionMode = options.selectionMode || 'none'; // none, single, multiple\n this.emptyMessage = options.emptyMessage || 'No items to display';\n this.loading = false;\n this.isEmpty = true;\n\n }\n\n /**\n * Override onInit to set up initial state\n */\n async onInit() {\n // Initial render will happen automatically\n this._initCollection(this.options.collection || this.options.Collection);\n }\n\n\n /**\n * Initialize the collection\n */\n _initCollection(collectionOrClass) {\n if (!collectionOrClass) {\n console.log('Collection not provided');\n return;\n };\n\n // Check if it's already a Collection instance\n if (collectionOrClass instanceof Collection) {\n this.setCollection(collectionOrClass);\n }\n // Check if it's a Collection class\n else if (typeof collectionOrClass === 'function') {\n const collection = new collectionOrClass();\n this.setCollection(collection);\n }\n // Check if it's an array of data\n else if (Array.isArray(collectionOrClass)) {\n const collection = new Collection(null, {}, collectionOrClass);\n this.setCollection(collection);\n }\n }\n\n /**\n * Set the collection for this list view\n */\n setCollection(collection) {\n if (this.collection === collection) return this;\n\n // Clean up old collection listeners\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\n if (this.options.collectionParams) {\n this.collection.params = { ...this.collection.params, ...this.options.collectionParams };\n }\n\n // Set up new collection listeners\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 // Build items for existing models\n this._buildItems();\n }\n\n return this;\n }\n\n async _renderChildren() {\n await super._renderChildren();\n const itemsContainer = this.getChildElement(\"items\");\n if (!itemsContainer) {\n // console.warn('ListView: items container not found');\n return;\n }\n this.forEachItem((item, index) => {\n itemsContainer.appendChild(item.element);\n item.render(false);\n });\n }\n\n /**\n * Build item views for all models in collection\n */\n _buildItems() {\n // Clear existing items\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\n // Create item views for each model\n this.collection.forEach((model, index) => {\n this._createItemView(model, index);\n });\n\n this.emit('list:loaded', { count: this.collection.length() });\n\n // Render if already mounted\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Create an item view for a model\n * The itemTemplate is passed as the template option to the itemClass constructor\n */\n _createItemView(model, index) {\n // Don't create duplicate views\n if (this.itemViews.has(model.id)) return;\n\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n template: this.itemTemplate, // Pass the itemTemplate to the item view\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', this._onItemSelect.bind(this));\n itemView.on('item:deselect', this._onItemDeselect.bind(this));\n\n return itemView;\n }\n\n /**\n * Clear all item views\n */\n _clearItems() {\n this.forEachItem(itemView => {\n this.removeChild(itemView.id);\n });\n this.itemViews.clear();\n this.selectedItems.clear();\n }\n\n /**\n * Handle models added to collection\n */\n _onModelsAdded(event) {\n const { models } = event;\n\n models.forEach(model => {\n const index = this.collection.models.indexOf(model);\n this._createItemView(model, index);\n });\n\n this.isEmpty = this.collection.isEmpty();\n\n // Re-render to show new items\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle models removed from collection\n */\n _onModelsRemoved(event) {\n const { models } = event;\n\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\n // Re-render to update display\n if (!this.loading && this.isMounted()) {\n this.render();\n }\n\n if (this.isEmpty) {\n this.emit('list:empty');\n }\n }\n\n /**\n * Handle collection reset\n */\n _onCollectionReset(_event) {\n this._buildItems();\n }\n\n /**\n * Handle fetch start\n */\n _onFetchStart() {\n this.loading = true;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle fetch end\n */\n _onFetchEnd() {\n this.loading = false;\n if (this.isMounted()) {\n this.render();\n }\n }\n\n /**\n * Handle item selection\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 // Deselect all other items\n this.itemViews.forEach((view, id) => {\n if (id !== model.id && view.selected) {\n view.deselect();\n }\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: item,\n model: model\n });\n }\n\n /**\n * Handle item deselection\n */\n _onItemDeselect(event) {\n const { model } = event;\n\n this.selectedItems.delete(model.id);\n\n this.emit('selection:change', {\n selected: Array.from(this.selectedItems),\n item: event.item,\n model: model\n });\n }\n\n /**\n * Get selected items\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 /**\n * Iterate over each item view in the list\n * @param {function} callback - Function to execute for each item (itemView, model, index)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {ListView} Returns the ListView for chaining\n */\n forEachItem(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n let index = 0;\n this.itemViews.forEach((itemView, modelId) => {\n callback.call(thisArg, itemView, itemView.model, index++);\n });\n\n return this;\n }\n\n /**\n * Clear selection\n */\n clearSelection() {\n this.forEachItem(itemView => {\n if (itemView.selected) {\n itemView.deselect();\n }\n });\n this.selectedItems.clear();\n\n this.emit('selection:change', {\n selected: []\n });\n }\n\n /**\n * Select item by model ID\n */\n selectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.select();\n }\n return this;\n }\n\n /**\n * Deselect item by model ID\n */\n deselectItem(modelId) {\n const itemView = this.itemViews.get(modelId);\n if (itemView) {\n itemView.deselect();\n }\n return this;\n }\n\n /**\n * Set or update the item template\n * @param {string} template - New template string for items\n * @param {boolean} rerender - Whether to re-render existing items with new template\n * @returns {ListView} Returns the ListView for chaining\n */\n setItemTemplate(template, rerender = false) {\n this.itemTemplate = template;\n\n if (rerender && this.itemViews.size > 0) {\n // Update template for all existing item views\n this.forEachItem((itemView) => {\n itemView.setTemplate(template);\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n }\n\n return this;\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 /**\n * Refresh the list (re-fetch if collection supports it)\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 * Override destroy to clean up\n */\n async destroy() {\n // Clean up collection listeners\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 // Clear items\n this._clearItems();\n\n // Call parent destroy\n await super.destroy();\n }\n}\n\nexport default ListView;\n"],"names":["ListViewItem","View","constructor","options","super","className","this","selected","index","listView","template","onActionSelect","event","_element","stopPropagation","deselect","select","addClass","emit","item","model","data","toJSON","removeClass","onActionDefault","action","_event","setIndex","element","setAttribute","setSelected","destroy","ListView","collection","itemViews","Map","selectedItems","Set","itemTemplate","itemClass","selectionMode","emptyMessage","loading","isEmpty","onInit","_initCollection","Collection","collectionOrClass","setCollection","Array","isArray","off","_onModelsAdded","_onModelsRemoved","_onCollectionReset","_onFetchStart","_onFetchEnd","defaultQuery","collectionParams","params","on","_buildItems","_renderChildren","itemsContainer","getChildElement","forEachItem","appendChild","render","_clearItems","forEach","_createItemView","count","length","isMounted","has","id","itemView","set","_onItemSelect","bind","_onItemDeselect","removeChild","clear","models","indexOf","get","delete","view","add","from","getSelectedItems","push","callback","thisArg","TypeError","modelId","call","clearSelection","selectItem","deselectItem","setItemTemplate","rerender","size","setTemplate","onAfterMount","fetchOnMount","lastFetchTime","fetch","refresh","restEnabled"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"WebSocketClient-C9VS1m8v.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocketClient - Robust, infinitely-resilient WebSocket client\n *\n * Features:\n * - Infinite auto-reconnect with capped exponential backoff + jitter (never gives up)\n * - Separate \"intentional disconnect\" flag so clean closes still reconnect\n * - Heartbeat ping/pong with timeout-driven force-reconnect\n * - Immediate reconnect nudge on browser visibility / window focus restore\n * - Optional integration with WebApp's browser:focus event bus\n * - Event-driven architecture using EventEmitter\n * - Token-based authentication\n *\n * Usage:\n * const ws = new WebSocketClient({\n * url: \"wss://api.example.com/ws/realtime\",\n * tokenPrefix: \"bearer\",\n * getToken: () => app.tokenManager.getToken(),\n * app, // optional – hooks into app.events 'browser:focus'\n * });\n *\n * ws.on('connected', () => console.log('Connected!'));\n * ws.on('message', (data) => console.log('Received:', data));\n * ws.on('reconnecting', ({attempt, delay}) => console.log(`Retry #${attempt} in ${delay}ms`));\n * ws.connect();\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\n\nclass WebSocketClient {\n constructor(options = {}) {\n // ── Connection ──────────────────────────────────────────────────────────\n this.url = options.url;\n this.socket = null;\n this.isConnected = false;\n this.isConnecting = false;\n\n // ── Auth ────────────────────────────────────────────────────────────────\n this.getToken = options.getToken || null;\n this.tokenPrefix = options.tokenPrefix || 'bearer';\n\n // ── Reconnection ────────────────────────────────────────────────────────\n // `autoReconnect` – master switch (default true)\n // `_intentionalDisconnect` – set only when the caller explicitly calls\n // disconnect(), so that clean server closes\n // (code 1000) are still retried automatically.\n this.autoReconnect = options.autoReconnect !== false;\n this._intentionalDisconnect = false;\n\n this.reconnectInterval = options.reconnectInterval || 2000; // base delay ms\n this.reconnectBackoff = options.reconnectBackoff || 1.5; // multiplier\n this.maxReconnectDelay = options.maxReconnectDelay || 30000; // hard cap ms\n this.reconnectJitter = options.reconnectJitter !== false; // ±20 % jitter\n this.reconnectAttempts = 0;\n this.reconnectTimer = null;\n\n // ── Heartbeat ────────────────────────────────────────────────────────────\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 10000;\n this.pingTimer = null;\n this.pongTimer = null;\n\n // ── Optional WebApp integration ──────────────────────────────────────────\n // Pass `app` to also hook into app.events 'browser:focus' in addition to\n // the native visibility / window focus events we listen to below.\n this._app = options.app || null;\n\n // ── Debug ────────────────────────────────────────────────────────────────\n this.debug = options.debug || false;\n\n // Bind handlers so they can be cleanly removed later\n this._onVisibilityChange = this._handleVisibilityChange.bind(this);\n this._onWindowFocus = this._handleWindowFocus.bind(this);\n\n this._setupVisibilityHandlers();\n this._setupAppFocusHandler();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Public API\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Connect to the WebSocket server.\n * Safe to call multiple times – extra calls are no-ops when already\n * connected or connecting.\n */\n async connect(url = null) {\n if (url) this.url = url;\n if (!this.url) throw new Error('WebSocket URL is required');\n\n // Any explicit call to connect() resets the intentional-disconnect flag.\n this._intentionalDisconnect = false;\n\n if (this.isConnected || this.isConnecting) return;\n\n return this._doConnect();\n }\n\n /**\n * Permanently stop the client. No further reconnect attempts are made\n * until connect() is called again.\n */\n disconnect() {\n this._intentionalDisconnect = true;\n this._clearTimers();\n\n if (this.socket) {\n this._log('Disconnecting (intentional)');\n this.socket.close(1000, 'Client disconnect');\n this.socket = null;\n }\n\n this.isConnected = false;\n this.isConnecting = false;\n }\n\n /**\n * Send data. Objects are auto-serialised to JSON.\n */\n send(data) {\n if (!this.isConnected || !this.socket) {\n throw new Error('WebSocket not connected');\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n this._log('Sent:', message);\n }\n\n /**\n * Fully tear down the client, removing all DOM/app listeners.\n * Call this when the owning view/page is destroyed.\n */\n destroy() {\n this.disconnect();\n this._teardownVisibilityHandlers();\n this._teardownAppFocusHandler();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Connection internals\n // ═══════════════════════════════════════════════════════════════════════════\n\n _doConnect() {\n if (this.isConnected || this.isConnecting) return Promise.resolve();\n\n this.isConnecting = true;\n this._log('Connecting to:', this.url);\n\n return new Promise((resolve, reject) => {\n // `settled` ensures the promise is only resolved/rejected once even\n // though onerror and onclose can both fire on a failed connection.\n let settled = false;\n const settle = (fn, value) => {\n if (settled) return;\n settled = true;\n fn(value);\n };\n\n try {\n const socket = new WebSocket(this.url);\n this.socket = socket;\n\n socket.onopen = () => {\n this._log('Connected');\n this.isConnected = true;\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n\n this._authenticate();\n this._startHeartbeat();\n\n this.emit('connected');\n settle(resolve, undefined);\n };\n\n socket.onmessage = (event) => this._handleMessage(event);\n\n // onerror in browsers is always followed by onclose, so we let\n // _handleClose drive the reconnect to avoid double-scheduling.\n // We only use the error event to emit and to reject the initial\n // connect() promise if we haven't successfully opened yet.\n socket.onerror = (event) => {\n this._log('Socket error:', event);\n this.emit('error', event);\n\n if (!this.isConnected) {\n settle(reject, new Error('WebSocket connection failed'));\n }\n };\n\n // Pass the settle-reject so _handleClose can reject the promise when\n // the socket closes before we ever reached onopen.\n socket.onclose = (event) => this._handleClose(event, settle.bind(null, reject));\n\n } catch (error) {\n // Synchronous WebSocket constructor failure (bad URL, etc.)\n this.isConnecting = false;\n this.socket = null;\n settle(reject, error);\n this._scheduleReconnect();\n }\n });\n }\n\n _authenticate() {\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token available — skipping authentication');\n return;\n }\n\n this.send({ type: 'authenticate', token, prefix: this.tokenPrefix });\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Event handlers\n // ═══════════════════════════════════════════════════════════════════════════\n\n _handleMessage(event) {\n this._log('Received:', event.data);\n\n let data;\n try {\n data = JSON.parse(event.data);\n } catch {\n data = event.data;\n }\n\n // Absorb pong heartbeat response\n if (data?.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n\n if (data?.type) {\n this.emit(`message:${data.type}`, data);\n }\n this.emit('message', data);\n }\n\n _handleClose(event, initialRejectFn) {\n this._log('Closed:', event.code, event.reason);\n\n const wasConnecting = this.isConnecting;\n\n this.isConnected = false;\n this.isConnecting = false;\n this._clearTimers();\n this.socket = null;\n\n this.emit('disconnected', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean\n });\n\n // Reject the caller's connect() promise if we closed before onopen fired\n if (wasConnecting && typeof initialRejectFn === 'function') {\n initialRejectFn(new Error(`WebSocket closed before connecting (code ${event.code})`));\n }\n\n // Always reconnect unless the user explicitly called disconnect()\n if (!this._intentionalDisconnect && this.autoReconnect) {\n this._scheduleReconnect();\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Reconnect\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Schedule the next reconnect with capped exponential backoff + optional\n * jitter. This is the single code path through which all retries are queued.\n */\n _scheduleReconnect() {\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n\n // Guard against queuing a second timer while one is already pending\n if (this.reconnectTimer !== null) return;\n\n this.reconnectAttempts++;\n\n // Exponential backoff, hard-capped\n let delay = Math.min(\n this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1),\n this.maxReconnectDelay\n );\n\n // ±20 % jitter to avoid thundering-herd problems after server restarts\n if (this.reconnectJitter) {\n delay = delay * (0.8 + Math.random() * 0.4);\n }\n\n delay = Math.round(delay);\n\n this._log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n this.emit('reconnecting', { attempt: this.reconnectAttempts, delay });\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n\n this._doConnect().catch((err) => {\n this._log('Reconnect attempt failed:', err.message);\n // _handleClose already called _scheduleReconnect for us; if somehow it\n // didn't (e.g. synchronous throw path), make sure we still retry.\n if (this.reconnectTimer === null && !this._intentionalDisconnect) {\n this._scheduleReconnect();\n }\n });\n }, delay);\n }\n\n /**\n * Skip the remaining backoff wait and attempt a reconnect right away.\n * Called when the browser regains visibility or focus so we don't leave\n * the user sitting at the max-delay cap after waking a laptop or switching\n * back to the tab.\n */\n _nudgeReconnect() {\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n if (this.isConnected || this.isConnecting) return;\n\n this._log('Focus/visibility restored — nudging reconnect immediately');\n\n // Cancel the pending backoff timer; we're going now\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n\n // Small grace period (200 ms) to let the network stack wake up before\n // we try to open the socket.\n setTimeout(() => {\n if (this._intentionalDisconnect || this.isConnected || this.isConnecting) return;\n\n // Don't inflate the attempts counter for a nudge — treat it as\n // retrying the current attempt rather than starting a new one.\n const savedAttempts = this.reconnectAttempts;\n this._doConnect().catch((err) => {\n this._log('Nudge reconnect failed:', err.message);\n // Restore attempt count so backoff calculation stays consistent\n this.reconnectAttempts = savedAttempts;\n });\n }, 200);\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Heartbeat\n // ═══════════════════════════════════════════════════════════════════════════\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n\n this.pingTimer = setInterval(() => {\n if (!this.isConnected) return;\n try {\n this.send({ action: 'ping' });\n this._startPongTimeout();\n } catch (err) {\n this._log('Ping send failed:', err.message);\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout — forcing reconnect');\n this.emit('pong-timeout');\n if (this.socket) {\n // Non-1000 code so _handleClose knows this was not an intentional close\n this.socket.close(4001, 'Pong timeout');\n }\n }, this.pongTimeout);\n }\n\n _clearPongTimeout() {\n if (this.pongTimer !== null) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Timer cleanup\n // ═══════════════════════════════════════════════════════════════════════════\n\n _clearTimers() {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.pingTimer !== null) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this._clearPongTimeout();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Visibility / focus integration\n // ═══════════════════════════════════════════════════════════════════════════\n\n _setupVisibilityHandlers() {\n if (typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n if (typeof window !== 'undefined') {\n window.addEventListener('focus', this._onWindowFocus);\n }\n }\n\n _teardownVisibilityHandlers() {\n if (typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n if (typeof window !== 'undefined') {\n window.removeEventListener('focus', this._onWindowFocus);\n }\n }\n\n _handleVisibilityChange() {\n if (!document.hidden) {\n this._nudgeReconnect();\n }\n }\n\n _handleWindowFocus() {\n this._nudgeReconnect();\n }\n\n /**\n * Hook into a WebApp instance's unified event bus so that PortalApp's\n * existing browser:focus handling also triggers a reconnect nudge.\n * This is intentionally additive — the native DOM listeners above already\n * cover the common cases; the app bus is a belt-and-suspenders extra.\n */\n _setupAppFocusHandler() {\n if (!this._app?.events) return;\n this._appFocusHandler = () => this._nudgeReconnect();\n this._app.events.on('browser:focus', this._appFocusHandler);\n }\n\n _teardownAppFocusHandler() {\n if (!this._app?.events || !this._appFocusHandler) return;\n this._app.events.off('browser:focus', this._appFocusHandler);\n this._appFocusHandler = null;\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Logging\n // ═══════════════════════════════════════════════════════════════════════════\n\n _log(...args) {\n if (this.debug) {\n console.log('[WebSocket]', ...args);\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Static helpers\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Convert a REST API base URL to a WebSocket URL.\n *\n * @param {string} baseURL - REST base URL (http / https)\n * @param {string} path - WebSocket path (default: '/ws/realtime/')\n * @returns {string} WebSocket URL (ws / wss)\n *\n * @example\n * WebSocketClient.deriveURL('https://api.example.com', '/ws/realtime')\n * // → 'wss://api.example.com/ws/realtime'\n *\n * WebSocketClient.deriveURL('http://localhost:3000')\n * // → 'ws://localhost:3000/ws/realtime/'\n */\n static deriveURL(baseURL, path = '/ws/realtime/') {\n if (!baseURL) throw new Error('baseURL is required');\n const url = new URL(baseURL);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = path.startsWith('/') ? path : `/${path}`;\n return url.toString();\n }\n}\n\n// Mix in EventEmitter (adds on / off / emit / once)\nObject.assign(WebSocketClient.prototype, EventEmitter);\n\nexport default WebSocketClient;"],"names":["WebSocketClient","constructor","options","this","url","socket","isConnected","isConnecting","getToken","tokenPrefix","autoReconnect","_intentionalDisconnect","reconnectInterval","reconnectBackoff","maxReconnectDelay","reconnectJitter","reconnectAttempts","reconnectTimer","pingInterval","pongTimeout","pingTimer","pongTimer","_app","app","debug","_onVisibilityChange","_handleVisibilityChange","bind","_onWindowFocus","_handleWindowFocus","_setupVisibilityHandlers","_setupAppFocusHandler","connect","Error","_doConnect","disconnect","_clearTimers","_log","close","send","data","message","JSON","stringify","destroy","_teardownVisibilityHandlers","_teardownAppFocusHandler","Promise","resolve","reject","settled","settle","fn","value","WebSocket","onopen","_authenticate","_startHeartbeat","emit","onmessage","event","_handleMessage","onerror","onclose","_handleClose","error","_scheduleReconnect","token","type","prefix","console","warn","parse","_clearPongTimeout","initialRejectFn","code","reason","wasConnecting","wasClean","delay","Math","min","pow","random","round","attempt","setTimeout","catch","err","_nudgeReconnect","clearTimeout","savedAttempts","setInterval","action","_startPongTimeout","clearInterval","document","addEventListener","window","removeEventListener","hidden","events","_appFocusHandler","on","off","args","deriveURL","baseURL","path","URL","protocol","pathname","startsWith","toString","Object","assign","prototype","EventEmitter"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"WebSocketClient-CMV76kSt.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocketClient - Robust, infinitely-resilient WebSocket client\n *\n * Features:\n * - Infinite auto-reconnect with capped exponential backoff + jitter (never gives up)\n * - Separate \"intentional disconnect\" flag so clean closes still reconnect\n * - Heartbeat ping/pong with timeout-driven force-reconnect\n * - Immediate reconnect nudge on browser visibility / window focus restore\n * - Optional integration with WebApp's browser:focus event bus\n * - Event-driven architecture using EventEmitter\n * - Token-based authentication\n *\n * Usage:\n * const ws = new WebSocketClient({\n * url: \"wss://api.example.com/ws/realtime\",\n * tokenPrefix: \"bearer\",\n * getToken: () => app.tokenManager.getToken(),\n * app, // optional – hooks into app.events 'browser:focus'\n * });\n *\n * ws.on('connected', () => console.log('Connected!'));\n * ws.on('message', (data) => console.log('Received:', data));\n * ws.on('reconnecting', ({attempt, delay}) => console.log(`Retry #${attempt} in ${delay}ms`));\n * ws.connect();\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\n\nclass WebSocketClient {\n constructor(options = {}) {\n // ── Connection ──────────────────────────────────────────────────────────\n this.url = options.url;\n this.socket = null;\n this.isConnected = false;\n this.isConnecting = false;\n\n // ── Auth ────────────────────────────────────────────────────────────────\n this.getToken = options.getToken || null;\n this.tokenPrefix = options.tokenPrefix || 'bearer';\n\n // ── Reconnection ────────────────────────────────────────────────────────\n // `autoReconnect` – master switch (default true)\n // `_intentionalDisconnect` – set only when the caller explicitly calls\n // disconnect(), so that clean server closes\n // (code 1000) are still retried automatically.\n this.autoReconnect = options.autoReconnect !== false;\n this._intentionalDisconnect = false;\n\n this.reconnectInterval = options.reconnectInterval || 2000; // base delay ms\n this.reconnectBackoff = options.reconnectBackoff || 1.5; // multiplier\n this.maxReconnectDelay = options.maxReconnectDelay || 30000; // hard cap ms\n this.reconnectJitter = options.reconnectJitter !== false; // ±20 % jitter\n this.reconnectAttempts = 0;\n this.reconnectTimer = null;\n\n // ── Heartbeat ────────────────────────────────────────────────────────────\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 10000;\n this.pingTimer = null;\n this.pongTimer = null;\n\n // ── Optional WebApp integration ──────────────────────────────────────────\n // Pass `app` to also hook into app.events 'browser:focus' in addition to\n // the native visibility / window focus events we listen to below.\n this._app = options.app || null;\n\n // ── Debug ────────────────────────────────────────────────────────────────\n this.debug = options.debug || false;\n\n // Bind handlers so they can be cleanly removed later\n this._onVisibilityChange = this._handleVisibilityChange.bind(this);\n this._onWindowFocus = this._handleWindowFocus.bind(this);\n\n this._setupVisibilityHandlers();\n this._setupAppFocusHandler();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Public API\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Connect to the WebSocket server.\n * Safe to call multiple times – extra calls are no-ops when already\n * connected or connecting.\n */\n async connect(url = null) {\n if (url) this.url = url;\n if (!this.url) throw new Error('WebSocket URL is required');\n\n // Any explicit call to connect() resets the intentional-disconnect flag.\n this._intentionalDisconnect = false;\n\n if (this.isConnected || this.isConnecting) return;\n\n return this._doConnect();\n }\n\n /**\n * Permanently stop the client. No further reconnect attempts are made\n * until connect() is called again.\n */\n disconnect() {\n this._intentionalDisconnect = true;\n this._clearTimers();\n\n if (this.socket) {\n this._log('Disconnecting (intentional)');\n this.socket.close(1000, 'Client disconnect');\n this.socket = null;\n }\n\n this.isConnected = false;\n this.isConnecting = false;\n }\n\n /**\n * Send data. Objects are auto-serialised to JSON.\n */\n send(data) {\n if (!this.isConnected || !this.socket) {\n throw new Error('WebSocket not connected');\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n this._log('Sent:', message);\n }\n\n /**\n * Fully tear down the client, removing all DOM/app listeners.\n * Call this when the owning view/page is destroyed.\n */\n destroy() {\n this.disconnect();\n this._teardownVisibilityHandlers();\n this._teardownAppFocusHandler();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Connection internals\n // ═══════════════════════════════════════════════════════════════════════════\n\n _doConnect() {\n if (this.isConnected || this.isConnecting) return Promise.resolve();\n\n this.isConnecting = true;\n this._log('Connecting to:', this.url);\n\n return new Promise((resolve, reject) => {\n // `settled` ensures the promise is only resolved/rejected once even\n // though onerror and onclose can both fire on a failed connection.\n let settled = false;\n const settle = (fn, value) => {\n if (settled) return;\n settled = true;\n fn(value);\n };\n\n try {\n const socket = new WebSocket(this.url);\n this.socket = socket;\n\n socket.onopen = () => {\n this._log('Connected');\n this.isConnected = true;\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n\n this._authenticate();\n this._startHeartbeat();\n\n this.emit('connected');\n settle(resolve, undefined);\n };\n\n socket.onmessage = (event) => this._handleMessage(event);\n\n // onerror in browsers is always followed by onclose, so we let\n // _handleClose drive the reconnect to avoid double-scheduling.\n // We only use the error event to emit and to reject the initial\n // connect() promise if we haven't successfully opened yet.\n socket.onerror = (event) => {\n this._log('Socket error:', event);\n this.emit('error', event);\n\n if (!this.isConnected) {\n settle(reject, new Error('WebSocket connection failed'));\n }\n };\n\n // Pass the settle-reject so _handleClose can reject the promise when\n // the socket closes before we ever reached onopen.\n socket.onclose = (event) => this._handleClose(event, settle.bind(null, reject));\n\n } catch (error) {\n // Synchronous WebSocket constructor failure (bad URL, etc.)\n this.isConnecting = false;\n this.socket = null;\n settle(reject, error);\n this._scheduleReconnect();\n }\n });\n }\n\n _authenticate() {\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token available — skipping authentication');\n return;\n }\n\n this.send({ type: 'authenticate', token, prefix: this.tokenPrefix });\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Event handlers\n // ═══════════════════════════════════════════════════════════════════════════\n\n _handleMessage(event) {\n this._log('Received:', event.data);\n\n let data;\n try {\n data = JSON.parse(event.data);\n } catch {\n data = event.data;\n }\n\n // Absorb pong heartbeat response\n if (data?.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n\n if (data?.type) {\n this.emit(`message:${data.type}`, data);\n }\n this.emit('message', data);\n }\n\n _handleClose(event, initialRejectFn) {\n this._log('Closed:', event.code, event.reason);\n\n const wasConnecting = this.isConnecting;\n\n this.isConnected = false;\n this.isConnecting = false;\n this._clearTimers();\n this.socket = null;\n\n this.emit('disconnected', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean\n });\n\n // Reject the caller's connect() promise if we closed before onopen fired\n if (wasConnecting && typeof initialRejectFn === 'function') {\n initialRejectFn(new Error(`WebSocket closed before connecting (code ${event.code})`));\n }\n\n // Always reconnect unless the user explicitly called disconnect()\n if (!this._intentionalDisconnect && this.autoReconnect) {\n this._scheduleReconnect();\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Reconnect\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Schedule the next reconnect with capped exponential backoff + optional\n * jitter. This is the single code path through which all retries are queued.\n */\n _scheduleReconnect() {\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n\n // Guard against queuing a second timer while one is already pending\n if (this.reconnectTimer !== null) return;\n\n this.reconnectAttempts++;\n\n // Exponential backoff, hard-capped\n let delay = Math.min(\n this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1),\n this.maxReconnectDelay\n );\n\n // ±20 % jitter to avoid thundering-herd problems after server restarts\n if (this.reconnectJitter) {\n delay = delay * (0.8 + Math.random() * 0.4);\n }\n\n delay = Math.round(delay);\n\n this._log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n this.emit('reconnecting', { attempt: this.reconnectAttempts, delay });\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n\n this._doConnect().catch((err) => {\n this._log('Reconnect attempt failed:', err.message);\n // _handleClose already called _scheduleReconnect for us; if somehow it\n // didn't (e.g. synchronous throw path), make sure we still retry.\n if (this.reconnectTimer === null && !this._intentionalDisconnect) {\n this._scheduleReconnect();\n }\n });\n }, delay);\n }\n\n /**\n * Skip the remaining backoff wait and attempt a reconnect right away.\n * Called when the browser regains visibility or focus so we don't leave\n * the user sitting at the max-delay cap after waking a laptop or switching\n * back to the tab.\n */\n _nudgeReconnect() {\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n if (this.isConnected || this.isConnecting) return;\n\n this._log('Focus/visibility restored — nudging reconnect immediately');\n\n // Cancel the pending backoff timer; we're going now\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n\n // Small grace period (200 ms) to let the network stack wake up before\n // we try to open the socket.\n setTimeout(() => {\n if (this._intentionalDisconnect || this.isConnected || this.isConnecting) return;\n\n // Don't inflate the attempts counter for a nudge — treat it as\n // retrying the current attempt rather than starting a new one.\n const savedAttempts = this.reconnectAttempts;\n this._doConnect().catch((err) => {\n this._log('Nudge reconnect failed:', err.message);\n // Restore attempt count so backoff calculation stays consistent\n this.reconnectAttempts = savedAttempts;\n });\n }, 200);\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Heartbeat\n // ═══════════════════════════════════════════════════════════════════════════\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n\n this.pingTimer = setInterval(() => {\n if (!this.isConnected) return;\n try {\n this.send({ action: 'ping' });\n this._startPongTimeout();\n } catch (err) {\n this._log('Ping send failed:', err.message);\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout — forcing reconnect');\n this.emit('pong-timeout');\n if (this.socket) {\n // Non-1000 code so _handleClose knows this was not an intentional close\n this.socket.close(4001, 'Pong timeout');\n }\n }, this.pongTimeout);\n }\n\n _clearPongTimeout() {\n if (this.pongTimer !== null) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Timer cleanup\n // ═══════════════════════════════════════════════════════════════════════════\n\n _clearTimers() {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.pingTimer !== null) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this._clearPongTimeout();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Visibility / focus integration\n // ═══════════════════════════════════════════════════════════════════════════\n\n _setupVisibilityHandlers() {\n if (typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n if (typeof window !== 'undefined') {\n window.addEventListener('focus', this._onWindowFocus);\n }\n }\n\n _teardownVisibilityHandlers() {\n if (typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n if (typeof window !== 'undefined') {\n window.removeEventListener('focus', this._onWindowFocus);\n }\n }\n\n _handleVisibilityChange() {\n if (!document.hidden) {\n this._nudgeReconnect();\n }\n }\n\n _handleWindowFocus() {\n this._nudgeReconnect();\n }\n\n /**\n * Hook into a WebApp instance's unified event bus so that PortalApp's\n * existing browser:focus handling also triggers a reconnect nudge.\n * This is intentionally additive — the native DOM listeners above already\n * cover the common cases; the app bus is a belt-and-suspenders extra.\n */\n _setupAppFocusHandler() {\n if (!this._app?.events) return;\n this._appFocusHandler = () => this._nudgeReconnect();\n this._app.events.on('browser:focus', this._appFocusHandler);\n }\n\n _teardownAppFocusHandler() {\n if (!this._app?.events || !this._appFocusHandler) return;\n this._app.events.off('browser:focus', this._appFocusHandler);\n this._appFocusHandler = null;\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Logging\n // ═══════════════════════════════════════════════════════════════════════════\n\n _log(...args) {\n if (this.debug) {\n console.log('[WebSocket]', ...args);\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Static helpers\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Convert a REST API base URL to a WebSocket URL.\n *\n * @param {string} baseURL - REST base URL (http / https)\n * @param {string} path - WebSocket path (default: '/ws/realtime/')\n * @returns {string} WebSocket URL (ws / wss)\n *\n * @example\n * WebSocketClient.deriveURL('https://api.example.com', '/ws/realtime')\n * // → 'wss://api.example.com/ws/realtime'\n *\n * WebSocketClient.deriveURL('http://localhost:3000')\n * // → 'ws://localhost:3000/ws/realtime/'\n */\n static deriveURL(baseURL, path = '/ws/realtime/') {\n if (!baseURL) throw new Error('baseURL is required');\n const url = new URL(baseURL);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = path.startsWith('/') ? path : `/${path}`;\n return url.toString();\n }\n}\n\n// Mix in EventEmitter (adds on / off / emit / once)\nObject.assign(WebSocketClient.prototype, EventEmitter);\n\nexport default WebSocketClient;"],"names":["WebSocketClient","constructor","options","this","url","socket","isConnected","isConnecting","getToken","tokenPrefix","autoReconnect","_intentionalDisconnect","reconnectInterval","reconnectBackoff","maxReconnectDelay","reconnectJitter","reconnectAttempts","reconnectTimer","pingInterval","pongTimeout","pingTimer","pongTimer","_app","app","debug","_onVisibilityChange","_handleVisibilityChange","bind","_onWindowFocus","_handleWindowFocus","_setupVisibilityHandlers","_setupAppFocusHandler","connect","Error","_doConnect","disconnect","_clearTimers","_log","close","send","data","message","JSON","stringify","destroy","_teardownVisibilityHandlers","_teardownAppFocusHandler","Promise","resolve","reject","settled","settle","fn","value","WebSocket","onopen","_authenticate","_startHeartbeat","emit","onmessage","event","_handleMessage","onerror","onclose","_handleClose","error","_scheduleReconnect","token","type","prefix","console","warn","parse","_clearPongTimeout","initialRejectFn","code","reason","wasConnecting","wasClean","delay","Math","min","pow","random","round","attempt","setTimeout","catch","err","_nudgeReconnect","clearTimeout","savedAttempts","setInterval","action","_startPongTimeout","clearInterval","document","addEventListener","window","removeEventListener","hidden","events","_appFocusHandler","on","off","args","deriveURL","baseURL","path","URL","protocol","pathname","startsWith","toString","Object","assign","prototype","EventEmitter"],"mappings":"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"}