web-mojo 2.2.69 → 2.2.70

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 (157) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +1 -1
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.es.js +1 -1
  7. package/dist/charts.cjs.js +1 -1
  8. package/dist/charts.cjs.js.map +1 -1
  9. package/dist/charts.es.js +1 -1
  10. package/dist/charts.es.js.map +1 -1
  11. package/dist/chunks/ChatView-DH42WXgV.js +2 -0
  12. package/dist/chunks/{ChatView-Dw-iVmht.js.map → ChatView-DH42WXgV.js.map} +1 -1
  13. package/dist/chunks/ChatView-_8eQTETQ.js +2 -0
  14. package/dist/chunks/{ChatView-CZ3Key2k.js.map → ChatView-_8eQTETQ.js.map} +1 -1
  15. package/dist/chunks/Collection-BUv4E9op.js +2 -0
  16. package/dist/chunks/Collection-BUv4E9op.js.map +1 -0
  17. package/dist/chunks/Collection-r1ACzUeh.js +2 -0
  18. package/dist/chunks/Collection-r1ACzUeh.js.map +1 -0
  19. package/dist/chunks/ContextMenu-BFxliZ03.js +2 -0
  20. package/dist/chunks/{ContextMenu-8vTiZZQV.js.map → ContextMenu-BFxliZ03.js.map} +1 -1
  21. package/dist/chunks/ContextMenu-BwJJ4QJE.js +2 -0
  22. package/dist/chunks/{ContextMenu-DBw0WMTO.js.map → ContextMenu-BwJJ4QJE.js.map} +1 -1
  23. package/dist/chunks/DataView-DMpNXerv.js +2 -0
  24. package/dist/chunks/{DataView-DyJKgOn3.js.map → DataView-DMpNXerv.js.map} +1 -1
  25. package/dist/chunks/DataView-_CACqzRt.js +2 -0
  26. package/dist/chunks/{DataView-BEovBggn.js.map → DataView-_CACqzRt.js.map} +1 -1
  27. package/dist/chunks/Dialog-BVCCpLPw.js +3 -0
  28. package/dist/chunks/Dialog-BVCCpLPw.js.map +1 -0
  29. package/dist/chunks/Dialog-BYiynSW-.js +3 -0
  30. package/dist/chunks/Dialog-BYiynSW-.js.map +1 -0
  31. package/dist/chunks/FormView-Dw7HDwzy.js +3 -0
  32. package/dist/chunks/{FormView-BRHAIawp.js.map → FormView-Dw7HDwzy.js.map} +1 -1
  33. package/dist/chunks/FormView-OgrZ7x0z.js +3 -0
  34. package/dist/chunks/{FormView-B1CXO2t8.js.map → FormView-OgrZ7x0z.js.map} +1 -1
  35. package/dist/chunks/ListView-2M4I8KHF.js +2 -0
  36. package/dist/chunks/{ListView-CMZpwyyC.js.map → ListView-2M4I8KHF.js.map} +1 -1
  37. package/dist/chunks/ListView-B0QbqSPv.js +2 -0
  38. package/dist/chunks/{ListView-BLFFK_Ir.js.map → ListView-B0QbqSPv.js.map} +1 -1
  39. package/dist/chunks/MetricsCountryMapView-DDdDJQFA.js +2 -0
  40. package/dist/chunks/{MetricsCountryMapView-B0kWK-Js.js.map → MetricsCountryMapView-DDdDJQFA.js.map} +1 -1
  41. package/dist/chunks/MetricsCountryMapView-DIlezla0.js +2 -0
  42. package/dist/chunks/{MetricsCountryMapView-DuBKO7gz.js.map → MetricsCountryMapView-DIlezla0.js.map} +1 -1
  43. package/dist/chunks/MetricsMiniChartWidget-Dt2V0eXP.js +2 -0
  44. package/dist/chunks/{MetricsMiniChartWidget-Dg1e6EQJ.js.map → MetricsMiniChartWidget-Dt2V0eXP.js.map} +1 -1
  45. package/dist/chunks/MetricsMiniChartWidget-_N4kzNY_.js +2 -0
  46. package/dist/chunks/{MetricsMiniChartWidget-D1w608Jy.js.map → MetricsMiniChartWidget-_N4kzNY_.js.map} +1 -1
  47. package/dist/chunks/PDFViewer-BruR1RFn.js +2 -0
  48. package/dist/chunks/{PDFViewer-D_3V8QJe.js.map → PDFViewer-BruR1RFn.js.map} +1 -1
  49. package/dist/chunks/PDFViewer-CyGFVcvX.js +2 -0
  50. package/dist/chunks/{PDFViewer-CDeV9OBs.js.map → PDFViewer-CyGFVcvX.js.map} +1 -1
  51. package/dist/chunks/TableView-CxYpxZvr.js +2 -0
  52. package/dist/chunks/{TableView-CI_7a-kD.js.map → TableView-CxYpxZvr.js.map} +1 -1
  53. package/dist/chunks/TableView-DemRVhnX.js +2 -0
  54. package/dist/chunks/{TableView-CWk5k4LQ.js.map → TableView-DemRVhnX.js.map} +1 -1
  55. package/dist/chunks/TokenManager-BFaxNsXO.js +2 -0
  56. package/dist/chunks/{TokenManager-sZgt--C9.js.map → TokenManager-BFaxNsXO.js.map} +1 -1
  57. package/dist/chunks/TokenManager-IlBEFXqZ.js +2 -0
  58. package/dist/chunks/{TokenManager-ien2XzwO.js.map → TokenManager-IlBEFXqZ.js.map} +1 -1
  59. package/dist/chunks/UserProfileView-9vkfCPsp.js +2 -0
  60. package/dist/chunks/{UserProfileView-kupeq2rN.js.map → UserProfileView-9vkfCPsp.js.map} +1 -1
  61. package/dist/chunks/UserProfileView-tcBT6XcE.js +2 -0
  62. package/dist/chunks/{UserProfileView-DnVMHcLH.js.map → UserProfileView-tcBT6XcE.js.map} +1 -1
  63. package/dist/chunks/WebApp-BFR1zozS.js +2 -0
  64. package/dist/chunks/{WebApp-CcVF73yg.js.map → WebApp-BFR1zozS.js.map} +1 -1
  65. package/dist/chunks/WebApp-C82womPC.js +2 -0
  66. package/dist/chunks/{WebApp-Bti0Gqqo.js.map → WebApp-C82womPC.js.map} +1 -1
  67. package/dist/chunks/WebSocketClient-Ibi7mLQu.js +2 -0
  68. package/dist/chunks/{WebSocketClient-Bh0Mmtje.js.map → WebSocketClient-Ibi7mLQu.js.map} +1 -1
  69. package/dist/chunks/WebSocketClient-QaCUN3EQ.js +2 -0
  70. package/dist/chunks/{WebSocketClient-CLgYPxWX.js.map → WebSocketClient-QaCUN3EQ.js.map} +1 -1
  71. package/dist/chunks/index-BaPQHxbL.js +2 -0
  72. package/dist/chunks/{index-Da9sT-tE.js.map → index-BaPQHxbL.js.map} +1 -1
  73. package/dist/chunks/index-BdfwxVMZ.js +2 -0
  74. package/dist/chunks/{index-Aq9ke4vg.js.map → index-BdfwxVMZ.js.map} +1 -1
  75. package/dist/chunks/{version-XmirKYWA.js → version-C2yYRyPn.js} +2 -2
  76. package/dist/chunks/{version-XmirKYWA.js.map → version-C2yYRyPn.js.map} +1 -1
  77. package/dist/chunks/{version-D8JjsPW0.js → version-CaiqhdME.js} +2 -2
  78. package/dist/chunks/{version-D8JjsPW0.js.map → version-CaiqhdME.js.map} +1 -1
  79. package/dist/docit.cjs.js +1 -1
  80. package/dist/docit.cjs.js.map +1 -1
  81. package/dist/docit.es.js +1 -1
  82. package/dist/docit.es.js.map +1 -1
  83. package/dist/index.cjs.js +1 -1
  84. package/dist/index.cjs.js.map +1 -1
  85. package/dist/index.es.js +1 -1
  86. package/dist/index.es.js.map +1 -1
  87. package/dist/lightbox.cjs.js +1 -1
  88. package/dist/lightbox.cjs.js.map +1 -1
  89. package/dist/lightbox.es.js +1 -1
  90. package/dist/lightbox.es.js.map +1 -1
  91. package/dist/map.cjs.js +1 -1
  92. package/dist/map.cjs.js.map +1 -1
  93. package/dist/map.es.js +1 -1
  94. package/dist/map.es.js.map +1 -1
  95. package/dist/timeline.cjs.js +1 -1
  96. package/dist/timeline.cjs.js.map +1 -1
  97. package/dist/timeline.es.js +1 -1
  98. package/dist/timeline.es.js.map +1 -1
  99. package/dist/user-profile.cjs.js +1 -1
  100. package/dist/user-profile.es.js +1 -1
  101. package/dist/web-mojo.lite.iife.js +5435 -5435
  102. package/dist/web-mojo.lite.iife.js.map +1 -1
  103. package/dist/web-mojo.lite.iife.min.js +68 -68
  104. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  105. package/package.json +1 -1
  106. package/dist/chunks/ChatView-CZ3Key2k.js +0 -2
  107. package/dist/chunks/ChatView-Dw-iVmht.js +0 -2
  108. package/dist/chunks/Collection-BWKmydl5.js +0 -2
  109. package/dist/chunks/Collection-BWKmydl5.js.map +0 -1
  110. package/dist/chunks/Collection-CmjTsmrP.js +0 -2
  111. package/dist/chunks/Collection-CmjTsmrP.js.map +0 -1
  112. package/dist/chunks/ContextMenu-8vTiZZQV.js +0 -2
  113. package/dist/chunks/ContextMenu-DBw0WMTO.js +0 -2
  114. package/dist/chunks/DataView-BEovBggn.js +0 -2
  115. package/dist/chunks/DataView-DyJKgOn3.js +0 -2
  116. package/dist/chunks/Dialog-Dhqtd9Yz.js +0 -2
  117. package/dist/chunks/Dialog-Dhqtd9Yz.js.map +0 -1
  118. package/dist/chunks/Dialog-t_9l2Mou.js +0 -2
  119. package/dist/chunks/Dialog-t_9l2Mou.js.map +0 -1
  120. package/dist/chunks/Files-6eRT5k3r.js +0 -2
  121. package/dist/chunks/Files-6eRT5k3r.js.map +0 -1
  122. package/dist/chunks/Files-Dh_5PFBn.js +0 -2
  123. package/dist/chunks/Files-Dh_5PFBn.js.map +0 -1
  124. package/dist/chunks/FormView-B1CXO2t8.js +0 -3
  125. package/dist/chunks/FormView-BRHAIawp.js +0 -3
  126. package/dist/chunks/ListView-BLFFK_Ir.js +0 -2
  127. package/dist/chunks/ListView-CMZpwyyC.js +0 -2
  128. package/dist/chunks/MetricsCountryMapView-B0kWK-Js.js +0 -2
  129. package/dist/chunks/MetricsCountryMapView-DuBKO7gz.js +0 -2
  130. package/dist/chunks/MetricsMiniChartWidget-D1w608Jy.js +0 -2
  131. package/dist/chunks/MetricsMiniChartWidget-Dg1e6EQJ.js +0 -2
  132. package/dist/chunks/PDFViewer-CDeV9OBs.js +0 -2
  133. package/dist/chunks/PDFViewer-D_3V8QJe.js +0 -2
  134. package/dist/chunks/Rest-B1eUyLX5.js +0 -2
  135. package/dist/chunks/Rest-B1eUyLX5.js.map +0 -1
  136. package/dist/chunks/Rest-BJ3Mvx1L.js +0 -2
  137. package/dist/chunks/Rest-BJ3Mvx1L.js.map +0 -1
  138. package/dist/chunks/TableView-CI_7a-kD.js +0 -2
  139. package/dist/chunks/TableView-CWk5k4LQ.js +0 -2
  140. package/dist/chunks/ToastService-C2tTooFn.js +0 -3
  141. package/dist/chunks/ToastService-C2tTooFn.js.map +0 -1
  142. package/dist/chunks/ToastService-nUaGVpSl.js +0 -3
  143. package/dist/chunks/ToastService-nUaGVpSl.js.map +0 -1
  144. package/dist/chunks/TokenManager-ien2XzwO.js +0 -2
  145. package/dist/chunks/TokenManager-sZgt--C9.js +0 -2
  146. package/dist/chunks/User-BL9M_PWB.js +0 -2
  147. package/dist/chunks/User-BL9M_PWB.js.map +0 -1
  148. package/dist/chunks/User-DqHG5Gr1.js +0 -2
  149. package/dist/chunks/User-DqHG5Gr1.js.map +0 -1
  150. package/dist/chunks/UserProfileView-DnVMHcLH.js +0 -2
  151. package/dist/chunks/UserProfileView-kupeq2rN.js +0 -2
  152. package/dist/chunks/WebApp-Bti0Gqqo.js +0 -2
  153. package/dist/chunks/WebApp-CcVF73yg.js +0 -2
  154. package/dist/chunks/WebSocketClient-Bh0Mmtje.js +0 -2
  155. package/dist/chunks/WebSocketClient-CLgYPxWX.js +0 -2
  156. package/dist/chunks/index-Aq9ke4vg.js +0 -2
  157. package/dist/chunks/index-Da9sT-tE.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"map.es.js","sources":["../src/extensions/map/location/LocationClient.js","../src/extensions/map/location/useLocationAutocomplete.js","../src/extensions/map/location/LocationDetailsView.js","../src/extensions/map/location/LocationDialogs.js","../src/extensions/map/location/LocationPlugin.js"],"sourcesContent":["/**\n * LocationClient - minimal client for Location & Address API\n *\n * Endpoints (default paths):\n * - POST /location/address/validate\n * - GET /location/address/suggestions ?input=...&session_token=...\n * - GET /location/address/place-details ?place_id=...&session_token=...\n * - POST /location/address/geocode\n * - GET /location/address/reverse-geocode ?lat=...&lng=...\n * - GET /location/timezone ?lat=...&lng=...\n *\n * This client is framework-friendly and can be used anywhere in a MOJO app.\n *\n * Examples:\n *\n * // Zero-config defaults (uses basePath '/api')\n * import LocationClient from '@ext/map/location/LocationClient.js';\n * const loc = new LocationClient();\n * const res = await loc.autocomplete('1600 Amphitheatre');\n * const details = await loc.placeDetails({ place_id: res?.data?.[0]?.place_id });\n *\n * // Custom base path (still works with core Rest base URL configuration)\n * const loc2 = new LocationClient({ basePath: '/api' });\n *\n * // Optional auth header (if your API requires auth)\n * loc2.setAuthHeader(() => `Bearer ${token}`);\n */\nimport rest from '@core/Rest.js';\nexport default class LocationClient {\n /**\n * @param {Object} options\n * @param {string} [options.basePath='/api'] - API base path prefix (e.g., '/api')\n * @param {string|(() => string|null)} [options.authHeader] - Authorization header string or function returning one\n * @param {Object} [options.endpoints] - Override endpoint paths\n * @param {Function} [options.fetchImpl] - Custom fetch implementation, defaults to global fetch\n */\n constructor({\n basePath = '/api',\n endpoints = {}\n } = {}) {\n this.basePath = String(basePath || '');\n\n this.sessionToken = null; // used for autocomplete session cost optimization\n\n // Default endpoint paths (relative to base path)\n this.endpoints = {\n validate: '/location/address/validate',\n autocomplete: '/location/address/suggestions',\n details: '/location/address/place-details',\n geocode: '/location/address/geocode',\n reverse: '/location/address/reverse-geocode',\n timezone: '/location/timezone',\n ...endpoints\n };\n\n\n }\n\n /**\n * Optional helper: supply or change auth header at runtime\n * @param {string|(() => string|null)} header\n */\n setAuthHeader(header) {\n this._authHeader = header;\n }\n\n /**\n * Compute headers for a request.\n * @param {Object} [extra]\n * @returns {Record<string,string>}\n */\n headers(extra) {\n let auth = null;\n if (typeof this._authHeader === 'function') {\n try { auth = this._authHeader(); } catch { auth = null; }\n } else {\n auth = this._authHeader;\n }\n const h = { 'Content-Type': 'application/json', ...(extra || {}) };\n if (auth) h.Authorization = auth;\n return h;\n }\n\n /**\n * Perform JSON GET with query params\n * @param {string} path - relative path starting with '/'\n * @param {Record<string, any>} [params]\n */\n async jsonGet(path, params) {\n const resp = await rest.GET(this.fullPath(path), params || {});\n // Unwrap Rest wrapper to return server JSON body (spec shape)\n return (resp && resp.data !== undefined) ? resp.data : resp;\n }\n\n /**\n * Perform JSON POST\n * @param {string} path - relative path starting with '/'\n * @param {any} body\n */\n async jsonPost(path, body) {\n const resp = await rest.POST(this.fullPath(path), body ?? {}, {}, {});\n // Unwrap Rest wrapper to return server JSON body (spec shape)\n return (resp && resp.data !== undefined) ? resp.data : resp;\n }\n\n fullPath(path) {\n return `${this.basePath}${path}`;\n }\n\n async _safeJson(res) {\n try {\n return await res.json();\n } catch {\n return null;\n }\n }\n\n // ----------------------------\n // API Methods\n // ----------------------------\n\n /**\n * Address Validation\n * @param {Object} address\n * @param {string} address.address1\n * @param {string} [address.address2]\n * @param {string} address.city\n * @param {string} address.state\n * @param {string} address.postal_code\n * @param {string} [address.provider] - e.g., 'usps' (if API supports provider selection)\n * @returns {Promise<any>} API response\n */\n validateAddress(address) {\n return this.jsonPost(this.endpoints.validate, address);\n }\n\n /**\n * Address Autocomplete (GET)\n * Maintains a session token for repeated queries to optimize provider cost.\n * @param {string} query\n * @param {Object} [opts] - e.g., { country, state, ... }\n * @returns {Promise<any>} { success, session_token, data: [{ id, place_id, description, ... }], ... }\n */\n async autocomplete(query, opts = {}) {\n if (!query || String(query).trim().length === 0) {\n return { success: true, data: [], size: 0, count: 0 };\n }\n if (!this.sessionToken) {\n this.sessionToken = this._createSessionToken();\n }\n const params = { input: query, session_token: this.sessionToken, ...opts };\n const result = await this.jsonGet(this.endpoints.autocomplete, params);\n // Persist returned session_token for subsequent calls if provided by API\n if (result && result.session_token) {\n this.sessionToken = result.session_token;\n }\n return result;\n }\n\n /**\n * Place Details (GET)\n * @param {Object} params\n * @param {string} [params.place_id]\n * @param {string} [params.id]\n * @returns {Promise<any>} { success, address: { formatted_address, latitude, longitude, ... } }\n */\n placeDetails({ place_id, session_token, id } = {}) {\n const q = {};\n const pid = place_id || id || null;\n if (pid) q.place_id = pid;\n if (session_token) q.session_token = session_token;\n return this.jsonGet(this.endpoints.details, q);\n }\n\n /**\n * Geocode (POST)\n * @param {string|Object} address - string or object { address1, city, state, postal_code }\n * @returns {Promise<any>} { success, latitude, longitude, formatted_address, place_id, address_components... }\n */\n geocode(address) {\n return this.jsonPost(this.endpoints.geocode, { address });\n }\n\n /**\n * Reverse Geocoding (GET)\n * @param {Object} coords\n * @param {number|string} coords.lat\n * @param {number|string} coords.lng\n * @returns {Promise<any>} { success, formatted_address, place_id, address_components... }\n */\n reverseGeocode({ lat, lng }) {\n return this.jsonGet(this.endpoints.reverse, { lat, lng });\n }\n\n /**\n * Timezone Lookup (GET)\n * @param {Object} coords\n * @param {number|string} coords.lat\n * @param {number|string} coords.lng\n * @returns {Promise<any>} { success, timezone_id, timezone_name, raw_offset, dst_offset, total_offset }\n */\n timezone({ lat, lng }) {\n return this.jsonGet(this.endpoints.timezone, { lat, lng });\n }\n\n /**\n * Reset session token (useful when user starts a new autocomplete flow).\n */\n resetSessionToken() {\n this.sessionToken = null;\n }\n\n /**\n * Basic parser for suggestion item to extract display info.\n * @param {Object} suggestion - item from autocomplete response\n */\n normalizeSuggestion(suggestion) {\n return {\n id: suggestion?.id || suggestion?.place_id || null,\n place_id: suggestion?.place_id || suggestion?.id || null,\n description: suggestion?.description || '',\n main_text: suggestion?.main_text || '',\n secondary_text: suggestion?.secondary_text || '',\n types: suggestion?.types || []\n };\n }\n\n _createSessionToken() {\n // Prefer crypto.randomUUID if available\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`;\n }\n}\n\nexport { LocationClient };","/**\n * useLocationAutocomplete - attach address autocomplete and details population to a FormView field.\n *\n * Minimal, framework-agnostic. Expects a LocationClient-like object with:\n * - autocomplete(input, opts?) -> Promise<{ success: boolean, session_token?: string, data: Array<{ id, place_id, description, main_text, secondary_text, types }> }>\n * - placeDetails({ place_id, session_token? }) -> Promise<{ success: boolean, address: { formatted_address, latitude, longitude, ... } }>\n *\n * Usage:\n * import { useLocationAutocomplete } from '@ext/map/location/useLocationAutocomplete.js';\n * import LocationClient from '@ext/map/location/LocationClient.js';\n *\n * const client = new LocationClient({ basePath: '/api' });\n * const dispose = useLocationAutocomplete(this, {\n * client,\n * field: 'address1',\n * mapping: {\n * address1: 'address1',\n * city: 'city',\n * state_code: 'state',\n * postal_code: 'postal_code',\n * country_code: 'country',\n * latitude: 'lat',\n * longitude: 'lng',\n * formatted_address: 'formatted_address',\n * place_id: 'place_id'\n * }\n * });\n *\n * @param {Object} formView - Instance of FormView (must expose element and optionally setFieldValue(name, value))\n * @param {Object} options\n * @param {Object} options.client - Location API client with autocomplete and placeDetails methods\n * @param {string|HTMLElement} options.field - Field name or input element to attach to (default: 'address1')\n * @param {string} [options.dropdownClass='loc-suggest'] - CSS class for the suggestions dropdown\n * @param {number} [options.minChars=3] - Minimum characters before triggering suggestions\n * @param {number} [options.debounceMs=200] - Debounce in ms for typing\n * @param {Object} [options.mapping] - Map of sourceKey(from API address) -> formFieldName\n * @param {Function} [options.onSelect] - Callback(details) when a suggestion is selected\n * @returns {Function} dispose() - Cleanup handler\n */\nexport function useLocationAutocomplete(formView, {\n client,\n field = 'address1',\n dropdownClass = 'loc-suggest',\n minChars = 3,\n debounceMs = 200,\n mapping = {\n address1: 'address1',\n city: 'city',\n state_code: 'state',\n postal_code: 'postal_code',\n country_code: 'country',\n latitude: 'latitude',\n longitude: 'longitude',\n formatted_address: 'formatted_address',\n place_id: 'place_id'\n },\n onSelect\n} = {}) {\n if (!formView || !formView.element) {\n console.warn('[useLocationAutocomplete] Missing formView or formView.element');\n return () => {};\n }\n if (!client || typeof client.autocomplete !== 'function' || typeof client.placeDetails !== 'function') {\n console.warn('[useLocationAutocomplete] Missing or invalid client. Provide an object with autocomplete() and placeDetails().');\n return () => {};\n }\n\n // Resolve target input\n const inputEl = (typeof field === 'string')\n ? (formView.element.querySelector(`input[name=\"${field}\"], #${field}`) || null)\n : (field instanceof HTMLElement ? field : null);\n\n if (!inputEl) {\n // Quietly no-op if field isn't present\n return () => {};\n }\n\n // Create dropdown\n const dd = document.createElement('div');\n dd.className = dropdownClass || 'loc-suggest';\n dd.style.position = 'absolute';\n dd.style.zIndex = '10000';\n dd.style.display = 'none';\n dd.style.background = '#fff';\n dd.style.border = '1px solid #e5e7eb';\n dd.style.borderRadius = '8px';\n dd.style.boxShadow = '0 8px 24px rgba(0,0,0,.08)';\n dd.style.padding = '4px 0';\n dd.style.maxHeight = '280px';\n dd.style.overflowY = 'auto';\n dd.style.minWidth = '240px';\n dd.setAttribute('role', 'listbox');\n dd.setAttribute('aria-label', 'Address suggestions');\n\n let open = false;\n let timer = null;\n let suppress = false;\n\n function placeDropdown() {\n if (!open) return;\n const r = inputEl.getBoundingClientRect();\n dd.style.minWidth = `${r.width}px`;\n dd.style.left = `${r.left + window.scrollX}px`;\n dd.style.top = `${r.bottom + window.scrollY + 4}px`;\n }\n\n function openDropdown() {\n if (!open) {\n open = true;\n dd.style.display = 'block';\n document.body.appendChild(dd);\n placeDropdown();\n }\n }\n\n function closeDropdown() {\n open = false;\n dd.style.display = 'none';\n dd.innerHTML = '';\n if (dd.parentNode) {\n dd.parentNode.removeChild(dd);\n }\n }\n\n function createRow(item, index) {\n const row = document.createElement('div');\n row.setAttribute('role', 'option');\n row.setAttribute('tabindex', '-1');\n row.style.padding = '8px 12px';\n row.style.cursor = 'pointer';\n row.style.display = 'flex';\n row.style.flexDirection = 'column';\n row.dataset.index = String(index);\n\n const main = document.createElement('div');\n main.style.fontWeight = '600';\n main.style.color = '#111827';\n main.textContent = item.main_text || item.description || '';\n\n const sub = document.createElement('div');\n sub.style.fontSize = '12px';\n sub.style.color = '#6b7280';\n sub.textContent = item.secondary_text || '';\n\n row.appendChild(main);\n if (sub.textContent) row.appendChild(sub);\n\n row.addEventListener('mouseenter', () => { row.style.background = '#f3f4f6'; });\n row.addEventListener('mouseleave', () => { row.style.background = 'transparent'; });\n\n row.addEventListener('mousedown', (e) => {\n // Use mousedown to select without losing focus before click\n e.preventDefault();\n selectSuggestion(item);\n });\n\n return row;\n }\n\n async function renderSuggestions(list) {\n dd.innerHTML = '';\n if (!list || list.length === 0) {\n const empty = document.createElement('div');\n empty.style.padding = '8px 12px';\n empty.style.color = '#6b7280';\n empty.textContent = 'No results';\n dd.appendChild(empty);\n return;\n }\n list.forEach((item, idx) => dd.appendChild(createRow(item, idx)));\n }\n\n async function selectSuggestion(item) {\n try {\n const id = item.place_id || item.id;\n let details = null;\n if (id) {\n const res = await client.placeDetails({ place_id: id, id, session_token: client.sessionToken });\n details = res?.address || null;\n }\n\n // Set input to readable formatted address\n // Suppress immediate re-query from dispatched input/focus\n suppress = true;\n clearTimeout(timer);\n if (details?.formatted_address) {\n inputEl.value = details.formatted_address;\n // Trigger a native input event if consumers rely on it\n inputEl.dispatchEvent(new Event('input', { bubbles: true }));\n } else if (item.description) {\n inputEl.value = item.description;\n inputEl.dispatchEvent(new Event('input', { bubbles: true }));\n }\n // Hide dropdown and blur to avoid onFocus reopen\n try { inputEl.blur(); } catch {}\n closeDropdown();\n // Lift suppression after debounce window\n setTimeout(() => { suppress = false; }, debounceMs + 50);\n\n // Apply mapping to populate other fields\n if (details && mapping && typeof mapping === 'object') {\n Object.entries(mapping).forEach(([srcKey, formField]) => {\n if (!formField) return;\n const val = details[srcKey];\n if (val !== undefined && val !== null) {\n // Update FormView model and DOM field\n try {\n if (typeof formView.setFieldValue === 'function') {\n formView.setFieldValue(formField, String(val));\n }\n } catch (err) {\n // setFieldValue is optional; ignore errors\n }\n const targetEl = formView.element.querySelector(`input[name=\"${formField}\"], #${formField}, textarea[name=\"${formField}\"], select[name=\"${formField}\"]`);\n if (targetEl) {\n targetEl.value = String(val);\n targetEl.dispatchEvent(new Event('input', { bubbles: true }));\n targetEl.dispatchEvent(new Event('change', { bubbles: true }));\n }\n }\n });\n }\n\n if (typeof onSelect === 'function') {\n onSelect(details || null);\n }\n } catch (err) {\n console.warn('[useLocationAutocomplete] placeDetails error:', err);\n } finally {\n closeDropdown();\n }\n }\n\n async function handleInput() {\n const q = inputEl.value.trim();\n if (q.length < minChars) {\n closeDropdown();\n return;\n }\n\n try {\n const res = await client.autocomplete(q);\n const list = Array.isArray(res?.data) ? res.data : [];\n const items = list.map(x => ({\n id: x.id,\n place_id: x.place_id,\n description: x.description,\n main_text: x.main_text,\n secondary_text: x.secondary_text,\n types: x.types\n }));\n openDropdown();\n placeDropdown();\n await renderSuggestions(items);\n } catch (err) {\n console.warn('[useLocationAutocomplete] autocomplete error:', err);\n closeDropdown();\n }\n }\n\n function onInput() {\n if (suppress) return;\n clearTimeout(timer);\n timer = setTimeout(handleInput, debounceMs);\n }\n\n function onFocus() {\n if (suppress) return;\n if (inputEl.value.trim().length >= minChars) {\n onInput();\n }\n }\n\n function onBlur() {\n // defer close to allow mousedown on dropdown items to register\n setTimeout(() => {\n if (!dd.contains(document.activeElement)) {\n closeDropdown();\n }\n }, 120);\n }\n\n function onWindowMove() {\n if (open) placeDropdown();\n }\n\n function onDocumentClick(e) {\n if (!dd.contains(e.target) && e.target !== inputEl) {\n closeDropdown();\n }\n }\n\n // Attach listeners\n inputEl.addEventListener('input', onInput);\n inputEl.addEventListener('focus', onFocus);\n inputEl.addEventListener('blur', onBlur);\n window.addEventListener('resize', onWindowMove);\n window.addEventListener('scroll', onWindowMove, true);\n document.addEventListener('click', onDocumentClick);\n\n // Return disposer for cleanup\n return function dispose() {\n clearTimeout(timer);\n try { inputEl.removeEventListener('input', onInput); } catch (e) { /* ignore */ }\n try { inputEl.removeEventListener('focus', onFocus); } catch (e) { /* ignore */ }\n try { inputEl.removeEventListener('blur', onBlur); } catch (e) { /* ignore */ }\n try { window.removeEventListener('resize', onWindowMove); } catch (e) { /* ignore */ }\n try { window.removeEventListener('scroll', onWindowMove, true); } catch (e) { /* ignore */ }\n try { document.removeEventListener('click', onDocumentClick); } catch (e) { /* ignore */ }\n try { closeDropdown(); } catch (e) { /* ignore */ }\n };\n}\n\nexport default useLocationAutocomplete;","/**\n * LocationDetailsView - Displays a formatted address and an optional map preview.\n *\n * Usage:\n * new LocationDetailsView({\n * details: {\n * formatted_address: '1600 Amphitheatre Pkwy...',\n * latitude: 37.422,\n * longitude: -122.084,\n * place_id: '...'\n * },\n * height: 260,\n * tileLayer: 'osm'\n * });\n */\n\nimport View from '@core/View.js';\nimport MapView from '../MapView.js';\n\nexport default class LocationDetailsView extends View {\n /**\n * @param {Object} options\n * @param {Object} [options.details] - Address details (formatted_address, latitude, longitude, place_id, etc.)\n * @param {number} [options.height=260] - Map height (px)\n * @param {string} [options.tileLayer='osm'] - Tile layer key for MapView\n */\n constructor({ details = {}, height = 260, tileLayer = 'osm' } = {}) {\n super({\n className: 'location-details-view'\n });\n\n this.details = details || {};\n this.height = Number.isFinite(height) ? height : 260;\n this.tileLayer = tileLayer || 'osm';\n\n // Expose flattened fields for Mustache (view is the template context)\n this.formatted_address = this.details.formatted_address || '';\n this.place_id = this.details.place_id || '';\n this.latitude =\n this._toNumber(this.details.latitude ?? this.details.lat ?? null);\n this.longitude =\n this._toNumber(this.details.longitude ?? this.details.lng ?? null);\n\n this.hasCoords = Number.isFinite(this.latitude) && Number.isFinite(this.longitude);\n\n // Keep a reference to child map view (if created)\n this._mapView = null;\n\n // Simple, framework-friendly template with a child container for the map\n this.template = `\n <div class=\"loc-details\">\n {{#formatted_address}}\n <div class=\"mb-2 fw-semibold\" style=\"word-break: break-word;\">\n {{formatted_address}}\n </div>\n {{/formatted_address}}\n\n {{#place_id}}\n <div class=\"text-muted small mb-2\">Place ID: {{place_id}}</div>\n {{/place_id}}\n\n {{^formatted_address}}\n <div class=\"text-muted small mb-2\">No formatted address provided</div>\n {{/formatted_address}}\n\n {{#hasCoords}}\n <div data-container=\"map\"></div>\n {{/hasCoords}}\n\n {{^hasCoords}}\n <div class=\"text-muted small\" style=\"border: 1px dashed #e5e7eb; border-radius: 8px; padding: 12px;\">\n No coordinates available for map preview\n </div>\n {{/hasCoords}}\n </div>\n `;\n }\n\n async onInit() {\n // Create child map view only if we have coordinates\n if (this.hasCoords) {\n const markers = [{\n lat: this.latitude,\n lng: this.longitude,\n popup: this.formatted_address || ''\n }];\n\n this._mapView = new MapView({\n markers,\n center: [this.latitude, this.longitude],\n zoom: 13,\n height: this.height,\n tileLayer: this.tileLayer,\n showLayerControl: true,\n containerId: 'map'\n });\n\n this.addChild(this._mapView);\n }\n }\n\n _toNumber(v) {\n if (v === null || v === undefined || v === '') return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n }\n}","/**\n * LocationDialogs - helpers for showing location details and a picker dialog.\n *\n * Exports:\n * - showLocationDetailsDialog({ client?, details?, place_id?, id?, title?, height?, tileLayer? })\n * -> Shows a dialog with a formatted address + map. If details not provided, fetches via place_id/id.\n * - showLocationPickerDialog({ client?, title?, minChars?, debounceMs?, placeholder?, confirmText?, height?, tileLayer? })\n * -> Opens a dialog with an address search box, suggestion list, and live preview map. Returns the chosen details or null.\n */\n\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport View from '@core/View.js';\nimport LocationClient from './LocationClient.js';\nimport LocationDetailsView from './LocationDetailsView.js';\n\n/**\n * Show a dialog with location details + map\n * @param {Object} options\n * @param {LocationClient} [options.client] - Optional LocationClient instance\n * @param {Object} [options.details] - If provided, dialog renders immediately without fetching\n * @param {string} [options.place_id] - Place id to fetch details\n * @param {string} [options.id] - Alternative id to fetch details\n * @param {string} [options.title='Location Details'] - Dialog title\n * @param {number} [options.height=260] - Map height\n * @param {string} [options.tileLayer='osm'] - Tile layer\n * @returns {Promise<any>} Dialog result (from Dialog.showView), primarily useful for awaiting close\n */\nexport async function showLocationDetailsDialog({\n client = new LocationClient({ basePath: '/api' }),\n details = null,\n place_id = null,\n id = null,\n title = 'Location Details',\n height = 260,\n tileLayer = 'osm'\n} = {}) {\n let resolved = details;\n\n if (!resolved && (place_id || id)) {\n try {\n const res = await client.placeDetails({ place_id, id, session_token: client.sessionToken });\n resolved = res?.address || null;\n } catch (err) {\n // Fallback to an error placeholder\n resolved = {\n formatted_address: 'Unable to load place details',\n error: err?.message || 'Unknown error'\n };\n }\n }\n\n const view = new LocationDetailsView({\n details: resolved || {},\n height,\n tileLayer\n });\n\n return Dialog.showDialog({\n title,\n body: view,\n size: 'md',\n buttons: [{ text: 'Close', class: 'btn-secondary', dismiss: true, value: 'close' }]\n });\n}\n\n/**\n * Internal view class to implement a picker UI:\n * - Search input with debounced autocomplete\n * - Suggestions dropdown\n * - Live details preview with map\n */\nclass LocationPickerView extends View {\n constructor({\n client,\n minChars = 3,\n debounceMs = 200,\n placeholder = 'Search address',\n height = 240,\n tileLayer = 'osm'\n } = {}) {\n super({\n className: 'location-picker-view',\n template: '<div class=\"location-picker-view-root\"></div>'\n });\n this.client = client;\n this.minChars = minChars;\n this.debounceMs = debounceMs;\n this.placeholder = placeholder;\n this.height = height;\n this.tileLayer = tileLayer;\n\n this._selected = null; // Selected details object\n this._timer = null; // Debounce timer\n this._suggestions = []; // Cached suggestions\n this._elements = {}; // refs to elements\n this._previewView = null; // LocationDetailsView instance for live preview\n }\n\n getSelected() {\n return this._selected;\n }\n\n async onAfterRender() {\n // Build UI\n const root = document.createElement('div');\n root.style.display = 'grid';\n root.style.gap = '10px';\n\n // Search input\n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'form-control';\n input.placeholder = this.placeholder || 'Search address';\n input.setAttribute('aria-label', 'Address search');\n root.appendChild(input);\n\n // Suggestions container\n const dd = document.createElement('div');\n dd.style.border = '1px solid #e5e7eb';\n dd.style.borderRadius = '8px';\n dd.style.background = '#fff';\n dd.style.boxShadow = '0 8px 24px rgba(0,0,0,.08)';\n dd.style.maxHeight = '260px';\n dd.style.overflowY = 'auto';\n dd.style.display = 'none';\n root.appendChild(dd);\n\n // Preview container for details + map\n const previewHost = document.createElement('div');\n root.appendChild(previewHost);\n\n // Mount tree\n this.element.appendChild(root);\n\n // Store refs\n this._elements = { root, input, dd, previewHost };\n\n // Wire events\n input.addEventListener('input', () => {\n clearTimeout(this._timer);\n this._timer = setTimeout(() => this._handleInput(), this.debounceMs);\n });\n\n input.addEventListener('focus', () => {\n if ((input.value || '').trim().length >= this.minChars) {\n this._handleInput();\n }\n });\n\n // Render empty preview on init\n await this._renderPreview(null);\n }\n\n async _handleInput() {\n const q = (this._elements.input.value || '').trim();\n if (q.length < this.minChars) {\n this._elements.dd.style.display = 'none';\n this._elements.dd.innerHTML = '';\n return;\n }\n\n try {\n const res = await this.client.autocomplete(q);\n const items = Array.isArray(res?.data) ? res.data : [];\n this._suggestions = items.map(x => ({\n id: x.id,\n place_id: x.place_id,\n description: x.description,\n main_text: x.main_text,\n secondary_text: x.secondary_text,\n types: x.types\n }));\n await this._renderSuggestions();\n } catch (err) {\n // show empty state\n this._suggestions = [];\n await this._renderSuggestions();\n }\n }\n\n async _renderSuggestions() {\n const dd = this._elements.dd;\n dd.innerHTML = '';\n\n if (!this._suggestions.length) {\n const empty = document.createElement('div');\n empty.style.padding = '8px 12px';\n empty.style.color = '#6b7280';\n empty.textContent = 'No results';\n dd.appendChild(empty);\n dd.style.display = 'block';\n return;\n }\n\n this._suggestions.forEach((item, idx) => {\n const row = document.createElement('div');\n row.style.padding = '8px 12px';\n row.style.cursor = 'pointer';\n row.style.display = 'flex';\n row.style.flexDirection = 'column';\n row.addEventListener('mouseenter', () => { row.style.background = '#f3f4f6'; });\n row.addEventListener('mouseleave', () => { row.style.background = 'transparent'; });\n\n const main = document.createElement('div');\n main.style.fontWeight = '600';\n main.style.color = '#111827';\n main.textContent = item.main_text || item.description || '';\n\n const sub = document.createElement('div');\n sub.style.fontSize = '12px';\n sub.style.color = '#6b7280';\n sub.textContent = item.secondary_text || '';\n\n row.appendChild(main);\n if (sub.textContent) row.appendChild(sub);\n\n row.addEventListener('mousedown', (e) => {\n e.preventDefault(); // select without losing focus\n this._selectSuggestion(item);\n });\n\n dd.appendChild(row);\n });\n\n dd.style.display = 'block';\n }\n\n async _selectSuggestion(item) {\n try {\n const id = item.place_id || item.id;\n if (!id) return;\n\n const res = await this.client.placeDetails({ place_id: id, id, session_token: this.client.sessionToken });\n const details = res?.address || null;\n\n // Update input to readable value and keep selected\n if (details?.formatted_address) {\n this._elements.input.value = details.formatted_address;\n } else if (item.description) {\n this._elements.input.value = item.description;\n }\n\n this._selected = details || null;\n\n // Hide suggestions after selection\n this._elements.dd.style.display = 'none';\n this._elements.dd.innerHTML = '';\n\n // Update preview\n await this._renderPreview(this._selected);\n } catch (err) {\n // keep selection unchanged on error\n }\n }\n\n async _renderPreview(details) {\n // Clear previous preview\n this._elements.previewHost.innerHTML = '';\n\n // If nothing selected, render a small placeholder\n if (!details) {\n const ph = document.createElement('div');\n ph.style.border = '1px dashed #e5e7eb';\n ph.style.borderRadius = '8px';\n ph.style.padding = '12px';\n ph.style.color = '#6b7280';\n ph.textContent = 'No location selected';\n this._elements.previewHost.appendChild(ph);\n return;\n }\n\n // Render a LocationDetailsView\n try {\n this._previewView = new LocationDetailsView({\n details,\n height: this.height,\n tileLayer: this.tileLayer\n });\n await this._previewView.render(true, this._elements.previewHost);\n } catch {\n const fallback = document.createElement('div');\n fallback.style.border = '1px dashed #e5e7eb';\n fallback.style.borderRadius = '8px';\n fallback.style.padding = '12px';\n fallback.textContent = details?.formatted_address || 'Selected location';\n this._elements.previewHost.appendChild(fallback);\n }\n }\n\n async onBeforeDestroy() {\n clearTimeout(this._timer);\n await super.onBeforeDestroy();\n }\n}\n\n/**\n * Show a picker dialog for choosing a location.\n * Returns the chosen location details or null if canceled.\n *\n * @param {Object} options\n * @param {LocationClient} [options.client] - Optional client instance\n * @param {string} [options.title='Pick a Location'] - Dialog title\n * @param {number} [options.minChars=3] - Minimum characters before suggestions\n * @param {number} [options.debounceMs=200] - Typing debounce\n * @param {string} [options.placeholder='Search address'] - Input placeholder\n * @param {string} [options.confirmText='Select'] - Confirm button text\n * @param {number} [options.height=240] - Preview map height\n * @param {string} [options.tileLayer='osm'] - Tile layer\n * @returns {Promise<Object|null>} Selected details or null\n */\nexport async function showLocationPickerDialog({\n client = new LocationClient({ basePath: '/api' }),\n title = 'Pick a Location',\n minChars = 3,\n debounceMs = 200,\n placeholder = 'Search address',\n confirmText = 'Select',\n height = 240,\n tileLayer = 'osm'\n} = {}) {\n const view = new LocationPickerView({\n client,\n minChars,\n debounceMs,\n placeholder,\n height,\n tileLayer\n });\n\n const result = await Dialog.showDialog({\n title,\n body: view,\n size: 'md',\n buttons: [\n { text: 'Cancel', class: 'btn-secondary', dismiss: true, value: 'cancel' },\n { text: confirmText, class: 'btn-primary', value: 'ok' }\n ]\n });\n\n // If user confirmed, return the selected details; otherwise null\n if (result === 'ok') {\n return view.getSelected() || null;\n }\n return null;\n}\n\nexport default {\n showLocationDetailsDialog,\n showLocationPickerDialog\n};","/**\n * LocationPlugin - Form extension for address autocomplete and place details\n *\n * This plugin integrates with the core FormPlugins registry to:\n * - Optionally add a new field type \"address\" (rendered as a text input)\n * - Auto-wire autocomplete and details population for address fields\n *\n * Nothing is active unless you explicitly register this plugin.\n *\n * Usage:\n * import { registerLocationPlugin } from '@ext/map/location/LocationPlugin.js';\n *\n * // Register once at app startup\n * const unregister = registerLocationPlugin({\n * basePath: '/api', // optional prefix for location endpoints (works with Rest baseURL)\n * fieldTypeName: 'address',\n * registerFieldType: true,\n * mapping: {\n * address1: 'address1',\n * city: 'city',\n * state_code: 'state',\n * postal_code: 'postal_code',\n * country_code: 'country',\n * latitude: 'lat',\n * longitude: 'lng',\n * formatted_address: 'formatted_address',\n * place_id: 'place_id'\n * }\n * });\n *\n * // Later (optional):\n * // unregister();\n */\n\nimport { FormPlugins } from '@core/forms/FormPlugins.js';\nimport LocationClient from './LocationClient.js';\nimport { useLocationAutocomplete } from './useLocationAutocomplete.js';\n\nexport class LocationFormPlugin {\n /**\n * @param {Object} options\n * @param {string} [options.basePath] - API base path prefix (e.g., '/api') used with core Rest\n * @param {Object} [options.mapping] - Mapping from API address keys -> form field names\n * @param {boolean} [options.registerFieldType=true] - Register custom field type (address)\n * @param {string} [options.fieldTypeName='address'] - Field type name to register\n * @param {string} [options.attributeSelector='data-location'] - Attribute to opt-in on any text input (e.g., data-location=\"address\")\n * @param {number} [options.minChars=3] - Minimum characters before triggering autocomplete\n * @param {number} [options.debounceMs=200] - Typing debounce for autocomplete\n */\n constructor({\n basePath = '/api',\n mapping,\n registerFieldType = true,\n fieldTypeName = 'address',\n attributeSelector = 'data-location',\n minChars = 3,\n debounceMs = 200,\n\n // Browser autofill/autocomplete suppression (important for suggestion inputs)\n // Chrome often ignores autocomplete=\"off\" for address-like fields, so we default\n // to a more reliable value.\n suppressBrowserAutocomplete = true,\n autocompleteValue = 'new-password'\n } = {}) {\n this.id = 'location';\n this.client = new LocationClient({ basePath });\n\n this.mapping = mapping || {\n address1: 'address1',\n city: 'city',\n state_code: 'state',\n postal_code: 'postal_code',\n country_code: 'country',\n latitude: 'latitude',\n longitude: 'longitude',\n formatted_address: 'formatted_address',\n place_id: 'place_id'\n };\n\n this.fieldTypeName = fieldTypeName;\n this.attributeSelector = attributeSelector;\n this.minChars = minChars;\n this.debounceMs = debounceMs;\n\n this.suppressBrowserAutocomplete = suppressBrowserAutocomplete !== false;\n this.autocompleteValue = autocompleteValue || 'new-password';\n\n // Optional field type registration\n if (registerFieldType) {\n this.fieldTypes = {\n [this.fieldTypeName]: (builder, field) => this.renderAddressField(builder, field)\n };\n }\n }\n\n /**\n * Custom renderer for the \"address\" field type.\n * Leverages existing FormBuilder input rendering for consistency (text input).\n */\n renderAddressField(builder, field) {\n const suppressAttrs = this.suppressBrowserAutocomplete\n ? `autocomplete=\"${this.autocompleteValue}\" autocapitalize=\"off\" autocorrect=\"off\" spellcheck=\"false\" inputmode=\"search\"`\n : '';\n\n const f = {\n ...field,\n type: 'text',\n placeholder: field.placeholder || 'Start typing an address',\n attrs: this.mergeAttrs(\n field.attrs,\n `${this.attributeSelector}=\"address\" ${suppressAttrs} aria-autocomplete=\"list\" role=\"combobox\"`\n )\n };\n // Prefer builder.renderTextField if available, otherwise fallback to generic input\n if (typeof builder.renderTextField === 'function') {\n return builder.renderTextField(f);\n }\n if (typeof builder.renderInputField === 'function') {\n return builder.renderInputField(f, 'text');\n }\n // Minimal fallback if neither method exists (should not happen)\n const id = builder.getFieldId?.(f.name) || `field_${f.name}`;\n return `\n <div class=\"mojo-form-control\">\n ${f.label ? `<label for=\"${id}\" class=\"${builder.options?.labelClass || 'form-label'}\">${f.label}</label>` : ''}\n <input type=\"text\" id=\"${id}\" name=\"${f.name}\" class=\"${builder.options?.inputClass || 'form-control'}\"\n placeholder=\"${f.placeholder || ''}\" ${this.attributeSelector}=\"address\" ${suppressAttrs} />\n </div>\n `;\n }\n\n /**\n * Helper to merge existing attrs with an additional attribute string\n */\n mergeAttrs(existing, add) {\n const base = (existing || '').trim();\n const extra = (add || '').trim();\n if (!base) return extra;\n if (!extra) return base;\n return `${base} ${extra}`;\n }\n\n /**\n * Hook: called when FormView is initialized\n * You can read application config here if needed.\n */\n onFormViewInit(_formView) {\n // no-op by default\n }\n\n /**\n * Hook: called after FormView finished rendering and initializing components\n * This is a good spot to opt-in based on attributes (e.g., data-location=\"address\") on any text input.\n */\n onAfterRender(formView) {\n if (!formView?.element) return;\n\n try {\n const selector = `input[${this.attributeSelector}=\"address\"]`;\n const inputs = formView.element.querySelectorAll(selector);\n inputs.forEach((inputEl) => {\n // Ensure we don't double-bind if a field-level init already handled it\n if (inputEl.dataset && inputEl.dataset._locationBound === '1') return;\n\n // Enforce suppression on any opt-in input (not just our field type renderer)\n if (this.suppressBrowserAutocomplete) {\n try {\n inputEl.setAttribute('autocomplete', this.autocompleteValue);\n inputEl.setAttribute('autocapitalize', 'off');\n inputEl.setAttribute('autocorrect', 'off');\n inputEl.setAttribute('spellcheck', 'false');\n inputEl.setAttribute('inputmode', 'search');\n } catch (e) {\n // best-effort: some environments may block setting attributes\n }\n }\n\n let dispose;\n const rebind = () => {\n dispose = useLocationAutocomplete(formView, {\n client: this.client,\n field: inputEl,\n mapping: this.mapping,\n minChars: this.minChars,\n debounceMs: this.debounceMs,\n onSelect: (_details) => {\n // Prevent immediate re-open by removing listeners during debounce window\n try { inputEl.blur(); } catch (e) { /* best-effort */ }\n try { dispose && dispose(); } catch (e) { /* best-effort */ }\n setTimeout(() => {\n rebind();\n }, this.debounceMs + 50);\n }\n });\n inputEl.dataset._locationBound = '1';\n this._trackDisposer(formView, dispose);\n };\n rebind();\n });\n } catch (err) {\n // best-effort\n }\n }\n\n /**\n * Hook: called for each field element with its config after FormView initialization\n * If the field is our \"address\" type (or opt-in via attribute), attach autocomplete.\n */\n onFieldInit(formView, fieldEl, fieldConfig) {\n try {\n const isAddressType = fieldConfig?.type === this.fieldTypeName;\n const hasAttr = fieldEl?.getAttribute?.(this.attributeSelector) === 'address';\n\n if (isAddressType || hasAttr) {\n // Avoid double-binding\n if (fieldEl.dataset && fieldEl.dataset._locationBound === '1') return;\n\n // Enforce suppression on field-type and attribute-bound inputs\n if (this.suppressBrowserAutocomplete) {\n try {\n fieldEl.setAttribute('autocomplete', this.autocompleteValue);\n fieldEl.setAttribute('autocapitalize', 'off');\n fieldEl.setAttribute('autocorrect', 'off');\n fieldEl.setAttribute('spellcheck', 'false');\n fieldEl.setAttribute('inputmode', 'search');\n } catch (e) {\n // best-effort: some environments may block setting attributes\n }\n }\n\n let dispose;\n const rebind = () => {\n dispose = useLocationAutocomplete(formView, {\n client: this.client,\n field: fieldEl,\n mapping: this.mapping,\n minChars: this.minChars,\n debounceMs: this.debounceMs,\n onSelect: (_details) => {\n // Prevent immediate re-open by removing listeners during debounce window\n try { fieldEl.blur(); } catch (e) { /* best-effort */ }\n try { dispose && dispose(); } catch (e) { /* best-effort */ }\n setTimeout(() => {\n rebind();\n }, this.debounceMs + 50);\n }\n });\n\n fieldEl.dataset._locationBound = '1';\n this._trackDisposer(formView, dispose);\n };\n rebind();\n }\n } catch (err) {\n // best-effort\n }\n }\n\n /**\n * Hook: field change notification (no-op for now)\n */\n onFieldChange(_formView, _name, _value) {\n // Could add logic to validate or re-resolve coordinates based on user edits\n }\n\n /**\n * Track disposers for cleanup. If the FormView provides an event API, attach to destroy/before-destroy.\n */\n _trackDisposer(formView, dispose) {\n if (!dispose || typeof dispose !== 'function') return;\n if (!formView) return;\n\n if (!formView._locationDisposers) {\n Object.defineProperty(formView, '_locationDisposers', {\n configurable: true,\n enumerable: false,\n writable: true,\n value: []\n });\n }\n formView._locationDisposers.push(dispose);\n\n // Attempt to hook into a lifecycle if available\n // Many MOJO View classes expose an event emitter; if not, this is still fine (garbage-collected on page change).\n const tryBind = (eventName) => {\n if (typeof formView.on === 'function') {\n try {\n formView.on(eventName, () => {\n try {\n formView._locationDisposers?.forEach(fn => {\n try { fn(); } catch { /* ignore dispose errors */ }\n });\n } finally {\n formView._locationDisposers = [];\n }\n });\n return true;\n } catch { /* ignore */ }\n }\n return false;\n };\n\n // Bind to a best-effort lifecycle event if present\n if (!tryBind('before:destroy')) {\n tryBind('destroy');\n }\n }\n}\n\n/**\n * Register the location plugin with the core FormPlugins registry.\n * @param {ConstructorParameters<typeof LocationFormPlugin>[0]} options\n * @returns {Function} unregister function\n */\nexport function registerLocationPlugin(options = {}) {\n const plugin = new LocationFormPlugin(options);\n return FormPlugins.register(plugin);\n}\n\nexport default LocationFormPlugin;"],"names":["LocationClient","constructor","basePath","endpoints","this","String","sessionToken","validate","autocomplete","details","geocode","reverse","timezone","setAuthHeader","header","_authHeader","headers","extra","auth","h","Authorization","jsonGet","path","params","resp","rest","GET","fullPath","data","jsonPost","body","POST","_safeJson","res","json","validateAddress","address","query","opts","trim","length","success","size","count","_createSessionToken","input","session_token","result","placeDetails","place_id","id","q","pid","reverseGeocode","lat","lng","resetSessionToken","normalizeSuggestion","suggestion","description","main_text","secondary_text","types","crypto","randomUUID","Date","now","Math","random","toString","slice","useLocationAutocomplete","formView","client","field","dropdownClass","minChars","debounceMs","mapping","address1","city","state_code","postal_code","country_code","latitude","longitude","formatted_address","onSelect","element","console","warn","inputEl","querySelector","HTMLElement","dd","document","createElement","className","style","position","zIndex","display","background","border","borderRadius","boxShadow","padding","maxHeight","overflowY","minWidth","setAttribute","open","timer","suppress","placeDropdown","r","getBoundingClientRect","width","left","window","scrollX","top","bottom","scrollY","closeDropdown","innerHTML","parentNode","removeChild","async","handleInput","value","items","Array","isArray","map","x","appendChild","list","empty","color","textContent","forEach","item","idx","index","row","cursor","flexDirection","dataset","main","fontWeight","sub","fontSize","addEventListener","e","preventDefault","clearTimeout","dispatchEvent","Event","bubbles","blur","setTimeout","Object","entries","srcKey","formField","val","setFieldValue","err","targetEl","selectSuggestion","createRow","renderSuggestions","onInput","onFocus","onBlur","contains","activeElement","onWindowMove","onDocumentClick","target","removeEventListener","LocationDetailsView","View","height","tileLayer","super","Number","isFinite","_toNumber","hasCoords","_mapView","template","onInit","markers","popup","MapView","center","zoom","showLayerControl","containerId","addChild","v","n","showLocationDetailsDialog","title","resolved","error","message","view","Dialog","showDialog","buttons","text","class","dismiss","LocationPickerView","placeholder","_selected","_timer","_suggestions","_elements","_previewView","getSelected","onAfterRender","root","gap","type","previewHost","_handleInput","_renderPreview","_renderSuggestions","_selectSuggestion","ph","render","fallback","onBeforeDestroy","showLocationPickerDialog","confirmText","LocationFormPlugin","registerFieldType","fieldTypeName","attributeSelector","suppressBrowserAutocomplete","autocompleteValue","fieldTypes","builder","renderAddressField","suppressAttrs","f","attrs","mergeAttrs","renderTextField","renderInputField","getFieldId","name","label","options","labelClass","inputClass","existing","add","base","onFormViewInit","_formView","selector","querySelectorAll","_locationBound","dispose","rebind","_details","_trackDisposer","onFieldInit","fieldEl","fieldConfig","isAddressType","hasAttr","getAttribute","onFieldChange","_name","_value","_locationDisposers","defineProperty","configurable","enumerable","writable","push","tryBind","eventName","on","fn","registerLocationPlugin","plugin","FormPlugins","register"],"mappings":"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"}
1
+ {"version":3,"file":"map.es.js","sources":["../src/extensions/map/location/LocationClient.js","../src/extensions/map/location/useLocationAutocomplete.js","../src/extensions/map/location/LocationDetailsView.js","../src/extensions/map/location/LocationDialogs.js","../src/extensions/map/location/LocationPlugin.js"],"sourcesContent":["/**\n * LocationClient - minimal client for Location & Address API\n *\n * Endpoints (default paths):\n * - POST /location/address/validate\n * - GET /location/address/suggestions ?input=...&session_token=...\n * - GET /location/address/place-details ?place_id=...&session_token=...\n * - POST /location/address/geocode\n * - GET /location/address/reverse-geocode ?lat=...&lng=...\n * - GET /location/timezone ?lat=...&lng=...\n *\n * This client is framework-friendly and can be used anywhere in a MOJO app.\n *\n * Examples:\n *\n * // Zero-config defaults (uses basePath '/api')\n * import LocationClient from '@ext/map/location/LocationClient.js';\n * const loc = new LocationClient();\n * const res = await loc.autocomplete('1600 Amphitheatre');\n * const details = await loc.placeDetails({ place_id: res?.data?.[0]?.place_id });\n *\n * // Custom base path (still works with core Rest base URL configuration)\n * const loc2 = new LocationClient({ basePath: '/api' });\n *\n * // Optional auth header (if your API requires auth)\n * loc2.setAuthHeader(() => `Bearer ${token}`);\n */\nimport rest from '@core/Rest.js';\nexport default class LocationClient {\n /**\n * @param {Object} options\n * @param {string} [options.basePath='/api'] - API base path prefix (e.g., '/api')\n * @param {string|(() => string|null)} [options.authHeader] - Authorization header string or function returning one\n * @param {Object} [options.endpoints] - Override endpoint paths\n * @param {Function} [options.fetchImpl] - Custom fetch implementation, defaults to global fetch\n */\n constructor({\n basePath = '/api',\n endpoints = {}\n } = {}) {\n this.basePath = String(basePath || '');\n\n this.sessionToken = null; // used for autocomplete session cost optimization\n\n // Default endpoint paths (relative to base path)\n this.endpoints = {\n validate: '/location/address/validate',\n autocomplete: '/location/address/suggestions',\n details: '/location/address/place-details',\n geocode: '/location/address/geocode',\n reverse: '/location/address/reverse-geocode',\n timezone: '/location/timezone',\n ...endpoints\n };\n\n\n }\n\n /**\n * Optional helper: supply or change auth header at runtime\n * @param {string|(() => string|null)} header\n */\n setAuthHeader(header) {\n this._authHeader = header;\n }\n\n /**\n * Compute headers for a request.\n * @param {Object} [extra]\n * @returns {Record<string,string>}\n */\n headers(extra) {\n let auth = null;\n if (typeof this._authHeader === 'function') {\n try { auth = this._authHeader(); } catch { auth = null; }\n } else {\n auth = this._authHeader;\n }\n const h = { 'Content-Type': 'application/json', ...(extra || {}) };\n if (auth) h.Authorization = auth;\n return h;\n }\n\n /**\n * Perform JSON GET with query params\n * @param {string} path - relative path starting with '/'\n * @param {Record<string, any>} [params]\n */\n async jsonGet(path, params) {\n const resp = await rest.GET(this.fullPath(path), params || {});\n // Unwrap Rest wrapper to return server JSON body (spec shape)\n return (resp && resp.data !== undefined) ? resp.data : resp;\n }\n\n /**\n * Perform JSON POST\n * @param {string} path - relative path starting with '/'\n * @param {any} body\n */\n async jsonPost(path, body) {\n const resp = await rest.POST(this.fullPath(path), body ?? {}, {}, {});\n // Unwrap Rest wrapper to return server JSON body (spec shape)\n return (resp && resp.data !== undefined) ? resp.data : resp;\n }\n\n fullPath(path) {\n return `${this.basePath}${path}`;\n }\n\n async _safeJson(res) {\n try {\n return await res.json();\n } catch {\n return null;\n }\n }\n\n // ----------------------------\n // API Methods\n // ----------------------------\n\n /**\n * Address Validation\n * @param {Object} address\n * @param {string} address.address1\n * @param {string} [address.address2]\n * @param {string} address.city\n * @param {string} address.state\n * @param {string} address.postal_code\n * @param {string} [address.provider] - e.g., 'usps' (if API supports provider selection)\n * @returns {Promise<any>} API response\n */\n validateAddress(address) {\n return this.jsonPost(this.endpoints.validate, address);\n }\n\n /**\n * Address Autocomplete (GET)\n * Maintains a session token for repeated queries to optimize provider cost.\n * @param {string} query\n * @param {Object} [opts] - e.g., { country, state, ... }\n * @returns {Promise<any>} { success, session_token, data: [{ id, place_id, description, ... }], ... }\n */\n async autocomplete(query, opts = {}) {\n if (!query || String(query).trim().length === 0) {\n return { success: true, data: [], size: 0, count: 0 };\n }\n if (!this.sessionToken) {\n this.sessionToken = this._createSessionToken();\n }\n const params = { input: query, session_token: this.sessionToken, ...opts };\n const result = await this.jsonGet(this.endpoints.autocomplete, params);\n // Persist returned session_token for subsequent calls if provided by API\n if (result && result.session_token) {\n this.sessionToken = result.session_token;\n }\n return result;\n }\n\n /**\n * Place Details (GET)\n * @param {Object} params\n * @param {string} [params.place_id]\n * @param {string} [params.id]\n * @returns {Promise<any>} { success, address: { formatted_address, latitude, longitude, ... } }\n */\n placeDetails({ place_id, session_token, id } = {}) {\n const q = {};\n const pid = place_id || id || null;\n if (pid) q.place_id = pid;\n if (session_token) q.session_token = session_token;\n return this.jsonGet(this.endpoints.details, q);\n }\n\n /**\n * Geocode (POST)\n * @param {string|Object} address - string or object { address1, city, state, postal_code }\n * @returns {Promise<any>} { success, latitude, longitude, formatted_address, place_id, address_components... }\n */\n geocode(address) {\n return this.jsonPost(this.endpoints.geocode, { address });\n }\n\n /**\n * Reverse Geocoding (GET)\n * @param {Object} coords\n * @param {number|string} coords.lat\n * @param {number|string} coords.lng\n * @returns {Promise<any>} { success, formatted_address, place_id, address_components... }\n */\n reverseGeocode({ lat, lng }) {\n return this.jsonGet(this.endpoints.reverse, { lat, lng });\n }\n\n /**\n * Timezone Lookup (GET)\n * @param {Object} coords\n * @param {number|string} coords.lat\n * @param {number|string} coords.lng\n * @returns {Promise<any>} { success, timezone_id, timezone_name, raw_offset, dst_offset, total_offset }\n */\n timezone({ lat, lng }) {\n return this.jsonGet(this.endpoints.timezone, { lat, lng });\n }\n\n /**\n * Reset session token (useful when user starts a new autocomplete flow).\n */\n resetSessionToken() {\n this.sessionToken = null;\n }\n\n /**\n * Basic parser for suggestion item to extract display info.\n * @param {Object} suggestion - item from autocomplete response\n */\n normalizeSuggestion(suggestion) {\n return {\n id: suggestion?.id || suggestion?.place_id || null,\n place_id: suggestion?.place_id || suggestion?.id || null,\n description: suggestion?.description || '',\n main_text: suggestion?.main_text || '',\n secondary_text: suggestion?.secondary_text || '',\n types: suggestion?.types || []\n };\n }\n\n _createSessionToken() {\n // Prefer crypto.randomUUID if available\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`;\n }\n}\n\nexport { LocationClient };","/**\n * useLocationAutocomplete - attach address autocomplete and details population to a FormView field.\n *\n * Minimal, framework-agnostic. Expects a LocationClient-like object with:\n * - autocomplete(input, opts?) -> Promise<{ success: boolean, session_token?: string, data: Array<{ id, place_id, description, main_text, secondary_text, types }> }>\n * - placeDetails({ place_id, session_token? }) -> Promise<{ success: boolean, address: { formatted_address, latitude, longitude, ... } }>\n *\n * Usage:\n * import { useLocationAutocomplete } from '@ext/map/location/useLocationAutocomplete.js';\n * import LocationClient from '@ext/map/location/LocationClient.js';\n *\n * const client = new LocationClient({ basePath: '/api' });\n * const dispose = useLocationAutocomplete(this, {\n * client,\n * field: 'address1',\n * mapping: {\n * address1: 'address1',\n * city: 'city',\n * state_code: 'state',\n * postal_code: 'postal_code',\n * country_code: 'country',\n * latitude: 'lat',\n * longitude: 'lng',\n * formatted_address: 'formatted_address',\n * place_id: 'place_id'\n * }\n * });\n *\n * @param {Object} formView - Instance of FormView (must expose element and optionally setFieldValue(name, value))\n * @param {Object} options\n * @param {Object} options.client - Location API client with autocomplete and placeDetails methods\n * @param {string|HTMLElement} options.field - Field name or input element to attach to (default: 'address1')\n * @param {string} [options.dropdownClass='loc-suggest'] - CSS class for the suggestions dropdown\n * @param {number} [options.minChars=3] - Minimum characters before triggering suggestions\n * @param {number} [options.debounceMs=200] - Debounce in ms for typing\n * @param {Object} [options.mapping] - Map of sourceKey(from API address) -> formFieldName\n * @param {Function} [options.onSelect] - Callback(details) when a suggestion is selected\n * @returns {Function} dispose() - Cleanup handler\n */\nexport function useLocationAutocomplete(formView, {\n client,\n field = 'address1',\n dropdownClass = 'loc-suggest',\n minChars = 3,\n debounceMs = 200,\n mapping = {\n address1: 'address1',\n city: 'city',\n state_code: 'state',\n postal_code: 'postal_code',\n country_code: 'country',\n latitude: 'latitude',\n longitude: 'longitude',\n formatted_address: 'formatted_address',\n place_id: 'place_id'\n },\n onSelect\n} = {}) {\n if (!formView || !formView.element) {\n console.warn('[useLocationAutocomplete] Missing formView or formView.element');\n return () => {};\n }\n if (!client || typeof client.autocomplete !== 'function' || typeof client.placeDetails !== 'function') {\n console.warn('[useLocationAutocomplete] Missing or invalid client. Provide an object with autocomplete() and placeDetails().');\n return () => {};\n }\n\n // Resolve target input\n const inputEl = (typeof field === 'string')\n ? (formView.element.querySelector(`input[name=\"${field}\"], #${field}`) || null)\n : (field instanceof HTMLElement ? field : null);\n\n if (!inputEl) {\n // Quietly no-op if field isn't present\n return () => {};\n }\n\n // Create dropdown\n const dd = document.createElement('div');\n dd.className = dropdownClass || 'loc-suggest';\n dd.style.position = 'absolute';\n dd.style.zIndex = '10000';\n dd.style.display = 'none';\n dd.style.background = '#fff';\n dd.style.border = '1px solid #e5e7eb';\n dd.style.borderRadius = '8px';\n dd.style.boxShadow = '0 8px 24px rgba(0,0,0,.08)';\n dd.style.padding = '4px 0';\n dd.style.maxHeight = '280px';\n dd.style.overflowY = 'auto';\n dd.style.minWidth = '240px';\n dd.setAttribute('role', 'listbox');\n dd.setAttribute('aria-label', 'Address suggestions');\n\n let open = false;\n let timer = null;\n let suppress = false;\n\n function placeDropdown() {\n if (!open) return;\n const r = inputEl.getBoundingClientRect();\n dd.style.minWidth = `${r.width}px`;\n dd.style.left = `${r.left + window.scrollX}px`;\n dd.style.top = `${r.bottom + window.scrollY + 4}px`;\n }\n\n function openDropdown() {\n if (!open) {\n open = true;\n dd.style.display = 'block';\n document.body.appendChild(dd);\n placeDropdown();\n }\n }\n\n function closeDropdown() {\n open = false;\n dd.style.display = 'none';\n dd.innerHTML = '';\n if (dd.parentNode) {\n dd.parentNode.removeChild(dd);\n }\n }\n\n function createRow(item, index) {\n const row = document.createElement('div');\n row.setAttribute('role', 'option');\n row.setAttribute('tabindex', '-1');\n row.style.padding = '8px 12px';\n row.style.cursor = 'pointer';\n row.style.display = 'flex';\n row.style.flexDirection = 'column';\n row.dataset.index = String(index);\n\n const main = document.createElement('div');\n main.style.fontWeight = '600';\n main.style.color = '#111827';\n main.textContent = item.main_text || item.description || '';\n\n const sub = document.createElement('div');\n sub.style.fontSize = '12px';\n sub.style.color = '#6b7280';\n sub.textContent = item.secondary_text || '';\n\n row.appendChild(main);\n if (sub.textContent) row.appendChild(sub);\n\n row.addEventListener('mouseenter', () => { row.style.background = '#f3f4f6'; });\n row.addEventListener('mouseleave', () => { row.style.background = 'transparent'; });\n\n row.addEventListener('mousedown', (e) => {\n // Use mousedown to select without losing focus before click\n e.preventDefault();\n selectSuggestion(item);\n });\n\n return row;\n }\n\n async function renderSuggestions(list) {\n dd.innerHTML = '';\n if (!list || list.length === 0) {\n const empty = document.createElement('div');\n empty.style.padding = '8px 12px';\n empty.style.color = '#6b7280';\n empty.textContent = 'No results';\n dd.appendChild(empty);\n return;\n }\n list.forEach((item, idx) => dd.appendChild(createRow(item, idx)));\n }\n\n async function selectSuggestion(item) {\n try {\n const id = item.place_id || item.id;\n let details = null;\n if (id) {\n const res = await client.placeDetails({ place_id: id, id, session_token: client.sessionToken });\n details = res?.address || null;\n }\n\n // Set input to readable formatted address\n // Suppress immediate re-query from dispatched input/focus\n suppress = true;\n clearTimeout(timer);\n if (details?.formatted_address) {\n inputEl.value = details.formatted_address;\n // Trigger a native input event if consumers rely on it\n inputEl.dispatchEvent(new Event('input', { bubbles: true }));\n } else if (item.description) {\n inputEl.value = item.description;\n inputEl.dispatchEvent(new Event('input', { bubbles: true }));\n }\n // Hide dropdown and blur to avoid onFocus reopen\n try { inputEl.blur(); } catch {}\n closeDropdown();\n // Lift suppression after debounce window\n setTimeout(() => { suppress = false; }, debounceMs + 50);\n\n // Apply mapping to populate other fields\n if (details && mapping && typeof mapping === 'object') {\n Object.entries(mapping).forEach(([srcKey, formField]) => {\n if (!formField) return;\n const val = details[srcKey];\n if (val !== undefined && val !== null) {\n // Update FormView model and DOM field\n try {\n if (typeof formView.setFieldValue === 'function') {\n formView.setFieldValue(formField, String(val));\n }\n } catch (err) {\n // setFieldValue is optional; ignore errors\n }\n const targetEl = formView.element.querySelector(`input[name=\"${formField}\"], #${formField}, textarea[name=\"${formField}\"], select[name=\"${formField}\"]`);\n if (targetEl) {\n targetEl.value = String(val);\n targetEl.dispatchEvent(new Event('input', { bubbles: true }));\n targetEl.dispatchEvent(new Event('change', { bubbles: true }));\n }\n }\n });\n }\n\n if (typeof onSelect === 'function') {\n onSelect(details || null);\n }\n } catch (err) {\n console.warn('[useLocationAutocomplete] placeDetails error:', err);\n } finally {\n closeDropdown();\n }\n }\n\n async function handleInput() {\n const q = inputEl.value.trim();\n if (q.length < minChars) {\n closeDropdown();\n return;\n }\n\n try {\n const res = await client.autocomplete(q);\n const list = Array.isArray(res?.data) ? res.data : [];\n const items = list.map(x => ({\n id: x.id,\n place_id: x.place_id,\n description: x.description,\n main_text: x.main_text,\n secondary_text: x.secondary_text,\n types: x.types\n }));\n openDropdown();\n placeDropdown();\n await renderSuggestions(items);\n } catch (err) {\n console.warn('[useLocationAutocomplete] autocomplete error:', err);\n closeDropdown();\n }\n }\n\n function onInput() {\n if (suppress) return;\n clearTimeout(timer);\n timer = setTimeout(handleInput, debounceMs);\n }\n\n function onFocus() {\n if (suppress) return;\n if (inputEl.value.trim().length >= minChars) {\n onInput();\n }\n }\n\n function onBlur() {\n // defer close to allow mousedown on dropdown items to register\n setTimeout(() => {\n if (!dd.contains(document.activeElement)) {\n closeDropdown();\n }\n }, 120);\n }\n\n function onWindowMove() {\n if (open) placeDropdown();\n }\n\n function onDocumentClick(e) {\n if (!dd.contains(e.target) && e.target !== inputEl) {\n closeDropdown();\n }\n }\n\n // Attach listeners\n inputEl.addEventListener('input', onInput);\n inputEl.addEventListener('focus', onFocus);\n inputEl.addEventListener('blur', onBlur);\n window.addEventListener('resize', onWindowMove);\n window.addEventListener('scroll', onWindowMove, true);\n document.addEventListener('click', onDocumentClick);\n\n // Return disposer for cleanup\n return function dispose() {\n clearTimeout(timer);\n try { inputEl.removeEventListener('input', onInput); } catch (e) { /* ignore */ }\n try { inputEl.removeEventListener('focus', onFocus); } catch (e) { /* ignore */ }\n try { inputEl.removeEventListener('blur', onBlur); } catch (e) { /* ignore */ }\n try { window.removeEventListener('resize', onWindowMove); } catch (e) { /* ignore */ }\n try { window.removeEventListener('scroll', onWindowMove, true); } catch (e) { /* ignore */ }\n try { document.removeEventListener('click', onDocumentClick); } catch (e) { /* ignore */ }\n try { closeDropdown(); } catch (e) { /* ignore */ }\n };\n}\n\nexport default useLocationAutocomplete;","/**\n * LocationDetailsView - Displays a formatted address and an optional map preview.\n *\n * Usage:\n * new LocationDetailsView({\n * details: {\n * formatted_address: '1600 Amphitheatre Pkwy...',\n * latitude: 37.422,\n * longitude: -122.084,\n * place_id: '...'\n * },\n * height: 260,\n * tileLayer: 'osm'\n * });\n */\n\nimport View from '@core/View.js';\nimport MapView from '../MapView.js';\n\nexport default class LocationDetailsView extends View {\n /**\n * @param {Object} options\n * @param {Object} [options.details] - Address details (formatted_address, latitude, longitude, place_id, etc.)\n * @param {number} [options.height=260] - Map height (px)\n * @param {string} [options.tileLayer='osm'] - Tile layer key for MapView\n */\n constructor({ details = {}, height = 260, tileLayer = 'osm' } = {}) {\n super({\n className: 'location-details-view'\n });\n\n this.details = details || {};\n this.height = Number.isFinite(height) ? height : 260;\n this.tileLayer = tileLayer || 'osm';\n\n // Expose flattened fields for Mustache (view is the template context)\n this.formatted_address = this.details.formatted_address || '';\n this.place_id = this.details.place_id || '';\n this.latitude =\n this._toNumber(this.details.latitude ?? this.details.lat ?? null);\n this.longitude =\n this._toNumber(this.details.longitude ?? this.details.lng ?? null);\n\n this.hasCoords = Number.isFinite(this.latitude) && Number.isFinite(this.longitude);\n\n // Keep a reference to child map view (if created)\n this._mapView = null;\n\n // Simple, framework-friendly template with a child container for the map\n this.template = `\n <div class=\"loc-details\">\n {{#formatted_address}}\n <div class=\"mb-2 fw-semibold\" style=\"word-break: break-word;\">\n {{formatted_address}}\n </div>\n {{/formatted_address}}\n\n {{#place_id}}\n <div class=\"text-muted small mb-2\">Place ID: {{place_id}}</div>\n {{/place_id}}\n\n {{^formatted_address}}\n <div class=\"text-muted small mb-2\">No formatted address provided</div>\n {{/formatted_address}}\n\n {{#hasCoords}}\n <div data-container=\"map\"></div>\n {{/hasCoords}}\n\n {{^hasCoords}}\n <div class=\"text-muted small\" style=\"border: 1px dashed #e5e7eb; border-radius: 8px; padding: 12px;\">\n No coordinates available for map preview\n </div>\n {{/hasCoords}}\n </div>\n `;\n }\n\n async onInit() {\n // Create child map view only if we have coordinates\n if (this.hasCoords) {\n const markers = [{\n lat: this.latitude,\n lng: this.longitude,\n popup: this.formatted_address || ''\n }];\n\n this._mapView = new MapView({\n markers,\n center: [this.latitude, this.longitude],\n zoom: 13,\n height: this.height,\n tileLayer: this.tileLayer,\n showLayerControl: true,\n containerId: 'map'\n });\n\n this.addChild(this._mapView);\n }\n }\n\n _toNumber(v) {\n if (v === null || v === undefined || v === '') return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n }\n}","/**\n * LocationDialogs - helpers for showing location details and a picker dialog.\n *\n * Exports:\n * - showLocationDetailsDialog({ client?, details?, place_id?, id?, title?, height?, tileLayer? })\n * -> Shows a dialog with a formatted address + map. If details not provided, fetches via place_id/id.\n * - showLocationPickerDialog({ client?, title?, minChars?, debounceMs?, placeholder?, confirmText?, height?, tileLayer? })\n * -> Opens a dialog with an address search box, suggestion list, and live preview map. Returns the chosen details or null.\n */\n\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport View from '@core/View.js';\nimport LocationClient from './LocationClient.js';\nimport LocationDetailsView from './LocationDetailsView.js';\n\n/**\n * Show a dialog with location details + map\n * @param {Object} options\n * @param {LocationClient} [options.client] - Optional LocationClient instance\n * @param {Object} [options.details] - If provided, dialog renders immediately without fetching\n * @param {string} [options.place_id] - Place id to fetch details\n * @param {string} [options.id] - Alternative id to fetch details\n * @param {string} [options.title='Location Details'] - Dialog title\n * @param {number} [options.height=260] - Map height\n * @param {string} [options.tileLayer='osm'] - Tile layer\n * @returns {Promise<any>} Dialog result (from Dialog.showView), primarily useful for awaiting close\n */\nexport async function showLocationDetailsDialog({\n client = new LocationClient({ basePath: '/api' }),\n details = null,\n place_id = null,\n id = null,\n title = 'Location Details',\n height = 260,\n tileLayer = 'osm'\n} = {}) {\n let resolved = details;\n\n if (!resolved && (place_id || id)) {\n try {\n const res = await client.placeDetails({ place_id, id, session_token: client.sessionToken });\n resolved = res?.address || null;\n } catch (err) {\n // Fallback to an error placeholder\n resolved = {\n formatted_address: 'Unable to load place details',\n error: err?.message || 'Unknown error'\n };\n }\n }\n\n const view = new LocationDetailsView({\n details: resolved || {},\n height,\n tileLayer\n });\n\n return Dialog.showDialog({\n title,\n body: view,\n size: 'md',\n buttons: [{ text: 'Close', class: 'btn-secondary', dismiss: true, value: 'close' }]\n });\n}\n\n/**\n * Internal view class to implement a picker UI:\n * - Search input with debounced autocomplete\n * - Suggestions dropdown\n * - Live details preview with map\n */\nclass LocationPickerView extends View {\n constructor({\n client,\n minChars = 3,\n debounceMs = 200,\n placeholder = 'Search address',\n height = 240,\n tileLayer = 'osm'\n } = {}) {\n super({\n className: 'location-picker-view',\n template: '<div class=\"location-picker-view-root\"></div>'\n });\n this.client = client;\n this.minChars = minChars;\n this.debounceMs = debounceMs;\n this.placeholder = placeholder;\n this.height = height;\n this.tileLayer = tileLayer;\n\n this._selected = null; // Selected details object\n this._timer = null; // Debounce timer\n this._suggestions = []; // Cached suggestions\n this._elements = {}; // refs to elements\n this._previewView = null; // LocationDetailsView instance for live preview\n }\n\n getSelected() {\n return this._selected;\n }\n\n async onAfterRender() {\n // Build UI\n const root = document.createElement('div');\n root.style.display = 'grid';\n root.style.gap = '10px';\n\n // Search input\n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'form-control';\n input.placeholder = this.placeholder || 'Search address';\n input.setAttribute('aria-label', 'Address search');\n root.appendChild(input);\n\n // Suggestions container\n const dd = document.createElement('div');\n dd.style.border = '1px solid #e5e7eb';\n dd.style.borderRadius = '8px';\n dd.style.background = '#fff';\n dd.style.boxShadow = '0 8px 24px rgba(0,0,0,.08)';\n dd.style.maxHeight = '260px';\n dd.style.overflowY = 'auto';\n dd.style.display = 'none';\n root.appendChild(dd);\n\n // Preview container for details + map\n const previewHost = document.createElement('div');\n root.appendChild(previewHost);\n\n // Mount tree\n this.element.appendChild(root);\n\n // Store refs\n this._elements = { root, input, dd, previewHost };\n\n // Wire events\n input.addEventListener('input', () => {\n clearTimeout(this._timer);\n this._timer = setTimeout(() => this._handleInput(), this.debounceMs);\n });\n\n input.addEventListener('focus', () => {\n if ((input.value || '').trim().length >= this.minChars) {\n this._handleInput();\n }\n });\n\n // Render empty preview on init\n await this._renderPreview(null);\n }\n\n async _handleInput() {\n const q = (this._elements.input.value || '').trim();\n if (q.length < this.minChars) {\n this._elements.dd.style.display = 'none';\n this._elements.dd.innerHTML = '';\n return;\n }\n\n try {\n const res = await this.client.autocomplete(q);\n const items = Array.isArray(res?.data) ? res.data : [];\n this._suggestions = items.map(x => ({\n id: x.id,\n place_id: x.place_id,\n description: x.description,\n main_text: x.main_text,\n secondary_text: x.secondary_text,\n types: x.types\n }));\n await this._renderSuggestions();\n } catch (err) {\n // show empty state\n this._suggestions = [];\n await this._renderSuggestions();\n }\n }\n\n async _renderSuggestions() {\n const dd = this._elements.dd;\n dd.innerHTML = '';\n\n if (!this._suggestions.length) {\n const empty = document.createElement('div');\n empty.style.padding = '8px 12px';\n empty.style.color = '#6b7280';\n empty.textContent = 'No results';\n dd.appendChild(empty);\n dd.style.display = 'block';\n return;\n }\n\n this._suggestions.forEach((item, idx) => {\n const row = document.createElement('div');\n row.style.padding = '8px 12px';\n row.style.cursor = 'pointer';\n row.style.display = 'flex';\n row.style.flexDirection = 'column';\n row.addEventListener('mouseenter', () => { row.style.background = '#f3f4f6'; });\n row.addEventListener('mouseleave', () => { row.style.background = 'transparent'; });\n\n const main = document.createElement('div');\n main.style.fontWeight = '600';\n main.style.color = '#111827';\n main.textContent = item.main_text || item.description || '';\n\n const sub = document.createElement('div');\n sub.style.fontSize = '12px';\n sub.style.color = '#6b7280';\n sub.textContent = item.secondary_text || '';\n\n row.appendChild(main);\n if (sub.textContent) row.appendChild(sub);\n\n row.addEventListener('mousedown', (e) => {\n e.preventDefault(); // select without losing focus\n this._selectSuggestion(item);\n });\n\n dd.appendChild(row);\n });\n\n dd.style.display = 'block';\n }\n\n async _selectSuggestion(item) {\n try {\n const id = item.place_id || item.id;\n if (!id) return;\n\n const res = await this.client.placeDetails({ place_id: id, id, session_token: this.client.sessionToken });\n const details = res?.address || null;\n\n // Update input to readable value and keep selected\n if (details?.formatted_address) {\n this._elements.input.value = details.formatted_address;\n } else if (item.description) {\n this._elements.input.value = item.description;\n }\n\n this._selected = details || null;\n\n // Hide suggestions after selection\n this._elements.dd.style.display = 'none';\n this._elements.dd.innerHTML = '';\n\n // Update preview\n await this._renderPreview(this._selected);\n } catch (err) {\n // keep selection unchanged on error\n }\n }\n\n async _renderPreview(details) {\n // Clear previous preview\n this._elements.previewHost.innerHTML = '';\n\n // If nothing selected, render a small placeholder\n if (!details) {\n const ph = document.createElement('div');\n ph.style.border = '1px dashed #e5e7eb';\n ph.style.borderRadius = '8px';\n ph.style.padding = '12px';\n ph.style.color = '#6b7280';\n ph.textContent = 'No location selected';\n this._elements.previewHost.appendChild(ph);\n return;\n }\n\n // Render a LocationDetailsView\n try {\n this._previewView = new LocationDetailsView({\n details,\n height: this.height,\n tileLayer: this.tileLayer\n });\n await this._previewView.render(true, this._elements.previewHost);\n } catch {\n const fallback = document.createElement('div');\n fallback.style.border = '1px dashed #e5e7eb';\n fallback.style.borderRadius = '8px';\n fallback.style.padding = '12px';\n fallback.textContent = details?.formatted_address || 'Selected location';\n this._elements.previewHost.appendChild(fallback);\n }\n }\n\n async onBeforeDestroy() {\n clearTimeout(this._timer);\n await super.onBeforeDestroy();\n }\n}\n\n/**\n * Show a picker dialog for choosing a location.\n * Returns the chosen location details or null if canceled.\n *\n * @param {Object} options\n * @param {LocationClient} [options.client] - Optional client instance\n * @param {string} [options.title='Pick a Location'] - Dialog title\n * @param {number} [options.minChars=3] - Minimum characters before suggestions\n * @param {number} [options.debounceMs=200] - Typing debounce\n * @param {string} [options.placeholder='Search address'] - Input placeholder\n * @param {string} [options.confirmText='Select'] - Confirm button text\n * @param {number} [options.height=240] - Preview map height\n * @param {string} [options.tileLayer='osm'] - Tile layer\n * @returns {Promise<Object|null>} Selected details or null\n */\nexport async function showLocationPickerDialog({\n client = new LocationClient({ basePath: '/api' }),\n title = 'Pick a Location',\n minChars = 3,\n debounceMs = 200,\n placeholder = 'Search address',\n confirmText = 'Select',\n height = 240,\n tileLayer = 'osm'\n} = {}) {\n const view = new LocationPickerView({\n client,\n minChars,\n debounceMs,\n placeholder,\n height,\n tileLayer\n });\n\n const result = await Dialog.showDialog({\n title,\n body: view,\n size: 'md',\n buttons: [\n { text: 'Cancel', class: 'btn-secondary', dismiss: true, value: 'cancel' },\n { text: confirmText, class: 'btn-primary', value: 'ok' }\n ]\n });\n\n // If user confirmed, return the selected details; otherwise null\n if (result === 'ok') {\n return view.getSelected() || null;\n }\n return null;\n}\n\nexport default {\n showLocationDetailsDialog,\n showLocationPickerDialog\n};","/**\n * LocationPlugin - Form extension for address autocomplete and place details\n *\n * This plugin integrates with the core FormPlugins registry to:\n * - Optionally add a new field type \"address\" (rendered as a text input)\n * - Auto-wire autocomplete and details population for address fields\n *\n * Nothing is active unless you explicitly register this plugin.\n *\n * Usage:\n * import { registerLocationPlugin } from '@ext/map/location/LocationPlugin.js';\n *\n * // Register once at app startup\n * const unregister = registerLocationPlugin({\n * basePath: '/api', // optional prefix for location endpoints (works with Rest baseURL)\n * fieldTypeName: 'address',\n * registerFieldType: true,\n * mapping: {\n * address1: 'address1',\n * city: 'city',\n * state_code: 'state',\n * postal_code: 'postal_code',\n * country_code: 'country',\n * latitude: 'lat',\n * longitude: 'lng',\n * formatted_address: 'formatted_address',\n * place_id: 'place_id'\n * }\n * });\n *\n * // Later (optional):\n * // unregister();\n */\n\nimport { FormPlugins } from '@core/forms/FormPlugins.js';\nimport LocationClient from './LocationClient.js';\nimport { useLocationAutocomplete } from './useLocationAutocomplete.js';\n\nexport class LocationFormPlugin {\n /**\n * @param {Object} options\n * @param {string} [options.basePath] - API base path prefix (e.g., '/api') used with core Rest\n * @param {Object} [options.mapping] - Mapping from API address keys -> form field names\n * @param {boolean} [options.registerFieldType=true] - Register custom field type (address)\n * @param {string} [options.fieldTypeName='address'] - Field type name to register\n * @param {string} [options.attributeSelector='data-location'] - Attribute to opt-in on any text input (e.g., data-location=\"address\")\n * @param {number} [options.minChars=3] - Minimum characters before triggering autocomplete\n * @param {number} [options.debounceMs=200] - Typing debounce for autocomplete\n */\n constructor({\n basePath = '/api',\n mapping,\n registerFieldType = true,\n fieldTypeName = 'address',\n attributeSelector = 'data-location',\n minChars = 3,\n debounceMs = 200,\n\n // Browser autofill/autocomplete suppression (important for suggestion inputs)\n // Chrome often ignores autocomplete=\"off\" for address-like fields, so we default\n // to a more reliable value.\n suppressBrowserAutocomplete = true,\n autocompleteValue = 'new-password'\n } = {}) {\n this.id = 'location';\n this.client = new LocationClient({ basePath });\n\n this.mapping = mapping || {\n address1: 'address1',\n city: 'city',\n state_code: 'state',\n postal_code: 'postal_code',\n country_code: 'country',\n latitude: 'latitude',\n longitude: 'longitude',\n formatted_address: 'formatted_address',\n place_id: 'place_id'\n };\n\n this.fieldTypeName = fieldTypeName;\n this.attributeSelector = attributeSelector;\n this.minChars = minChars;\n this.debounceMs = debounceMs;\n\n this.suppressBrowserAutocomplete = suppressBrowserAutocomplete !== false;\n this.autocompleteValue = autocompleteValue || 'new-password';\n\n // Optional field type registration\n if (registerFieldType) {\n this.fieldTypes = {\n [this.fieldTypeName]: (builder, field) => this.renderAddressField(builder, field)\n };\n }\n }\n\n /**\n * Custom renderer for the \"address\" field type.\n * Leverages existing FormBuilder input rendering for consistency (text input).\n */\n renderAddressField(builder, field) {\n const suppressAttrs = this.suppressBrowserAutocomplete\n ? `autocomplete=\"${this.autocompleteValue}\" autocapitalize=\"off\" autocorrect=\"off\" spellcheck=\"false\" inputmode=\"search\"`\n : '';\n\n const f = {\n ...field,\n type: 'text',\n placeholder: field.placeholder || 'Start typing an address',\n attrs: this.mergeAttrs(\n field.attrs,\n `${this.attributeSelector}=\"address\" ${suppressAttrs} aria-autocomplete=\"list\" role=\"combobox\"`\n )\n };\n // Prefer builder.renderTextField if available, otherwise fallback to generic input\n if (typeof builder.renderTextField === 'function') {\n return builder.renderTextField(f);\n }\n if (typeof builder.renderInputField === 'function') {\n return builder.renderInputField(f, 'text');\n }\n // Minimal fallback if neither method exists (should not happen)\n const id = builder.getFieldId?.(f.name) || `field_${f.name}`;\n return `\n <div class=\"mojo-form-control\">\n ${f.label ? `<label for=\"${id}\" class=\"${builder.options?.labelClass || 'form-label'}\">${f.label}</label>` : ''}\n <input type=\"text\" id=\"${id}\" name=\"${f.name}\" class=\"${builder.options?.inputClass || 'form-control'}\"\n placeholder=\"${f.placeholder || ''}\" ${this.attributeSelector}=\"address\" ${suppressAttrs} />\n </div>\n `;\n }\n\n /**\n * Helper to merge existing attrs with an additional attribute string\n */\n mergeAttrs(existing, add) {\n const base = (existing || '').trim();\n const extra = (add || '').trim();\n if (!base) return extra;\n if (!extra) return base;\n return `${base} ${extra}`;\n }\n\n /**\n * Hook: called when FormView is initialized\n * You can read application config here if needed.\n */\n onFormViewInit(_formView) {\n // no-op by default\n }\n\n /**\n * Hook: called after FormView finished rendering and initializing components\n * This is a good spot to opt-in based on attributes (e.g., data-location=\"address\") on any text input.\n */\n onAfterRender(formView) {\n if (!formView?.element) return;\n\n try {\n const selector = `input[${this.attributeSelector}=\"address\"]`;\n const inputs = formView.element.querySelectorAll(selector);\n inputs.forEach((inputEl) => {\n // Ensure we don't double-bind if a field-level init already handled it\n if (inputEl.dataset && inputEl.dataset._locationBound === '1') return;\n\n // Enforce suppression on any opt-in input (not just our field type renderer)\n if (this.suppressBrowserAutocomplete) {\n try {\n inputEl.setAttribute('autocomplete', this.autocompleteValue);\n inputEl.setAttribute('autocapitalize', 'off');\n inputEl.setAttribute('autocorrect', 'off');\n inputEl.setAttribute('spellcheck', 'false');\n inputEl.setAttribute('inputmode', 'search');\n } catch (e) {\n // best-effort: some environments may block setting attributes\n }\n }\n\n let dispose;\n const rebind = () => {\n dispose = useLocationAutocomplete(formView, {\n client: this.client,\n field: inputEl,\n mapping: this.mapping,\n minChars: this.minChars,\n debounceMs: this.debounceMs,\n onSelect: (_details) => {\n // Prevent immediate re-open by removing listeners during debounce window\n try { inputEl.blur(); } catch (e) { /* best-effort */ }\n try { dispose && dispose(); } catch (e) { /* best-effort */ }\n setTimeout(() => {\n rebind();\n }, this.debounceMs + 50);\n }\n });\n inputEl.dataset._locationBound = '1';\n this._trackDisposer(formView, dispose);\n };\n rebind();\n });\n } catch (err) {\n // best-effort\n }\n }\n\n /**\n * Hook: called for each field element with its config after FormView initialization\n * If the field is our \"address\" type (or opt-in via attribute), attach autocomplete.\n */\n onFieldInit(formView, fieldEl, fieldConfig) {\n try {\n const isAddressType = fieldConfig?.type === this.fieldTypeName;\n const hasAttr = fieldEl?.getAttribute?.(this.attributeSelector) === 'address';\n\n if (isAddressType || hasAttr) {\n // Avoid double-binding\n if (fieldEl.dataset && fieldEl.dataset._locationBound === '1') return;\n\n // Enforce suppression on field-type and attribute-bound inputs\n if (this.suppressBrowserAutocomplete) {\n try {\n fieldEl.setAttribute('autocomplete', this.autocompleteValue);\n fieldEl.setAttribute('autocapitalize', 'off');\n fieldEl.setAttribute('autocorrect', 'off');\n fieldEl.setAttribute('spellcheck', 'false');\n fieldEl.setAttribute('inputmode', 'search');\n } catch (e) {\n // best-effort: some environments may block setting attributes\n }\n }\n\n let dispose;\n const rebind = () => {\n dispose = useLocationAutocomplete(formView, {\n client: this.client,\n field: fieldEl,\n mapping: this.mapping,\n minChars: this.minChars,\n debounceMs: this.debounceMs,\n onSelect: (_details) => {\n // Prevent immediate re-open by removing listeners during debounce window\n try { fieldEl.blur(); } catch (e) { /* best-effort */ }\n try { dispose && dispose(); } catch (e) { /* best-effort */ }\n setTimeout(() => {\n rebind();\n }, this.debounceMs + 50);\n }\n });\n\n fieldEl.dataset._locationBound = '1';\n this._trackDisposer(formView, dispose);\n };\n rebind();\n }\n } catch (err) {\n // best-effort\n }\n }\n\n /**\n * Hook: field change notification (no-op for now)\n */\n onFieldChange(_formView, _name, _value) {\n // Could add logic to validate or re-resolve coordinates based on user edits\n }\n\n /**\n * Track disposers for cleanup. If the FormView provides an event API, attach to destroy/before-destroy.\n */\n _trackDisposer(formView, dispose) {\n if (!dispose || typeof dispose !== 'function') return;\n if (!formView) return;\n\n if (!formView._locationDisposers) {\n Object.defineProperty(formView, '_locationDisposers', {\n configurable: true,\n enumerable: false,\n writable: true,\n value: []\n });\n }\n formView._locationDisposers.push(dispose);\n\n // Attempt to hook into a lifecycle if available\n // Many MOJO View classes expose an event emitter; if not, this is still fine (garbage-collected on page change).\n const tryBind = (eventName) => {\n if (typeof formView.on === 'function') {\n try {\n formView.on(eventName, () => {\n try {\n formView._locationDisposers?.forEach(fn => {\n try { fn(); } catch { /* ignore dispose errors */ }\n });\n } finally {\n formView._locationDisposers = [];\n }\n });\n return true;\n } catch { /* ignore */ }\n }\n return false;\n };\n\n // Bind to a best-effort lifecycle event if present\n if (!tryBind('before:destroy')) {\n tryBind('destroy');\n }\n }\n}\n\n/**\n * Register the location plugin with the core FormPlugins registry.\n * @param {ConstructorParameters<typeof LocationFormPlugin>[0]} options\n * @returns {Function} unregister function\n */\nexport function registerLocationPlugin(options = {}) {\n const plugin = new LocationFormPlugin(options);\n return FormPlugins.register(plugin);\n}\n\nexport default LocationFormPlugin;"],"names":["LocationClient","constructor","basePath","endpoints","this","String","sessionToken","validate","autocomplete","details","geocode","reverse","timezone","setAuthHeader","header","_authHeader","headers","extra","auth","h","Authorization","jsonGet","path","params","resp","rest","GET","fullPath","data","jsonPost","body","POST","_safeJson","res","json","validateAddress","address","query","opts","trim","length","success","size","count","_createSessionToken","input","session_token","result","placeDetails","place_id","id","q","pid","reverseGeocode","lat","lng","resetSessionToken","normalizeSuggestion","suggestion","description","main_text","secondary_text","types","crypto","randomUUID","Date","now","Math","random","toString","slice","useLocationAutocomplete","formView","client","field","dropdownClass","minChars","debounceMs","mapping","address1","city","state_code","postal_code","country_code","latitude","longitude","formatted_address","onSelect","element","console","warn","inputEl","querySelector","HTMLElement","dd","document","createElement","className","style","position","zIndex","display","background","border","borderRadius","boxShadow","padding","maxHeight","overflowY","minWidth","setAttribute","open","timer","suppress","placeDropdown","r","getBoundingClientRect","width","left","window","scrollX","top","bottom","scrollY","closeDropdown","innerHTML","parentNode","removeChild","async","handleInput","value","items","Array","isArray","map","x","appendChild","list","empty","color","textContent","forEach","item","idx","index","row","cursor","flexDirection","dataset","main","fontWeight","sub","fontSize","addEventListener","e","preventDefault","clearTimeout","dispatchEvent","Event","bubbles","blur","setTimeout","Object","entries","srcKey","formField","val","setFieldValue","err","targetEl","selectSuggestion","createRow","renderSuggestions","onInput","onFocus","onBlur","contains","activeElement","onWindowMove","onDocumentClick","target","removeEventListener","LocationDetailsView","View","height","tileLayer","super","Number","isFinite","_toNumber","hasCoords","_mapView","template","onInit","markers","popup","MapView","center","zoom","showLayerControl","containerId","addChild","v","n","showLocationDetailsDialog","title","resolved","error","message","view","Dialog","showDialog","buttons","text","class","dismiss","LocationPickerView","placeholder","_selected","_timer","_suggestions","_elements","_previewView","getSelected","onAfterRender","root","gap","type","previewHost","_handleInput","_renderPreview","_renderSuggestions","_selectSuggestion","ph","render","fallback","onBeforeDestroy","showLocationPickerDialog","confirmText","LocationFormPlugin","registerFieldType","fieldTypeName","attributeSelector","suppressBrowserAutocomplete","autocompleteValue","fieldTypes","builder","renderAddressField","suppressAttrs","f","attrs","mergeAttrs","renderTextField","renderInputField","getFieldId","name","label","options","labelClass","inputClass","existing","add","base","onFormViewInit","_formView","selector","querySelectorAll","_locationBound","dispose","rebind","_details","_trackDisposer","onFieldInit","fieldEl","fieldConfig","isAddressType","hasAttr","getAttribute","onFieldChange","_name","_value","_locationDisposers","defineProperty","configurable","enumerable","writable","push","tryBind","eventName","on","fn","registerLocationPlugin","plugin","FormPlugins","register"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/ListView-CMZpwyyC.js"),e=require("./chunks/Rest-B1eUyLX5.js"),i=require("./chunks/Collection-CmjTsmrP.js");class TimelineViewItem extends t.ListViewItem{constructor(t={}){super({className:"timeline-item",...t}),this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.template||(this.template='\n <div class="timeline-marker timeline-marker-{{markerType}}">\n {{#hasIcon}}\n <i class="bi {{model.icon}} text-{{displayColor}}"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class="timeline-dot bg-{{displayColor}}"></div>\n {{/hasIcon}}\n </div>\n \n <div class="timeline-content">\n {{#showDate}}\n <div class="timeline-date text-muted small">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class="timeline-card">\n {{#model.title}}\n <h6 class="timeline-title mb-1">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class="timeline-description mb-0">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class="timeline-meta mt-2 text-muted small">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n ')}async onInit(){await super.onInit(),this.processItemData()}processItemData(){this.displayColor=this.model?.get?.("color")||this.model?.color||this.theme;const t=!(!this.model?.get?.("icon")&&!this.model?.icon)&&"icon"===this.dotStyle;this.hasIcon=t,this.markerType=t?"icon":this.dotStyle;const e=this.model?.get?.("date")||this.model?.date;this.formattedDate=this.formatDate(e)}formatDate(t){if(!t)return"";switch(this.dateFormat){case"datetime":return e.dataFormatter.pipe(t,"datetime");case"relative":return e.dataFormatter.pipe(t,"timeago");default:return e.dataFormatter.pipe(t,"date")}}async onActionSelect(t,e){t.stopPropagation(),this.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model})}}class TimelineView extends t.ListView{constructor(t={}){super({className:"timeline-view",itemClass:t.itemClass||TimelineViewItem,selectionMode:"none",emptyMessage:t.emptyMessage||"No timeline events to display",template:'\n <div class="timeline-container timeline-{{position}}">\n {{#loading}}\n <div class="timeline-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 <span class="ms-2 text-muted">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class="timeline-empty text-center text-muted py-4">\n <i class="bi bi-clock-history fs-1 d-block mb-2"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class="timeline" data-container="items"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ',...t}),this.position=t.position||"left",this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.groupBy=t.groupBy||"none"}_createItemView(t,e){if(this.itemViews.has(t.id))return;const i=new this.itemClass({model:t,index:e,listView:this,template:this.itemTemplate,dateFormat:this.dateFormat,dotStyle:this.dotStyle,showDate:this.showDate,theme:this.theme});return this.itemViews.set(t.id,i),i.on("item:click",this._onItemClick.bind(this)),i}_onItemClick(t){this.emit("item:click",t)}setPosition(t){return"left"!==t&&"center"!==t?(console.warn('Invalid position. Use "left" or "center"'),this):(this.position=t,this.isMounted()&&this.render(),this)}setDateFormat(t){return this.dateFormat=t,this.forEachItem(e=>{e.dateFormat=t,e.processItemData(),e.isMounted()&&e.render()}),this}setDotStyle(t){return this.dotStyle=t,this.forEachItem(e=>{e.dotStyle=t,e.processItemData(),e.isMounted()&&e.render()}),this}toggleDates(t=null){return this.showDate=null!==t?t:!this.showDate,this.forEachItem(t=>{t.showDate=this.showDate,t.isMounted()&&t.render()}),this}}exports.ListView=t.ListView,exports.ListViewItem=t.ListViewItem,exports.View=e.View,exports.Collection=i.Collection,exports.Model=i.Model,exports.TimelineView=TimelineView,exports.TimelineViewItem=TimelineViewItem;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/ListView-2M4I8KHF.js"),e=require("./chunks/Collection-BUv4E9op.js");class TimelineViewItem extends t.ListViewItem{constructor(t={}){super({className:"timeline-item",...t}),this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.template||(this.template='\n <div class="timeline-marker timeline-marker-{{markerType}}">\n {{#hasIcon}}\n <i class="bi {{model.icon}} text-{{displayColor}}"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class="timeline-dot bg-{{displayColor}}"></div>\n {{/hasIcon}}\n </div>\n \n <div class="timeline-content">\n {{#showDate}}\n <div class="timeline-date text-muted small">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class="timeline-card">\n {{#model.title}}\n <h6 class="timeline-title mb-1">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class="timeline-description mb-0">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class="timeline-meta mt-2 text-muted small">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n ')}async onInit(){await super.onInit(),this.processItemData()}processItemData(){this.displayColor=this.model?.get?.("color")||this.model?.color||this.theme;const t=!(!this.model?.get?.("icon")&&!this.model?.icon)&&"icon"===this.dotStyle;this.hasIcon=t,this.markerType=t?"icon":this.dotStyle;const e=this.model?.get?.("date")||this.model?.date;this.formattedDate=this.formatDate(e)}formatDate(t){if(!t)return"";switch(this.dateFormat){case"datetime":return e.dataFormatter.pipe(t,"datetime");case"relative":return e.dataFormatter.pipe(t,"timeago");default:return e.dataFormatter.pipe(t,"date")}}async onActionSelect(t,e){t.stopPropagation(),this.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model})}}class TimelineView extends t.ListView{constructor(t={}){super({className:"timeline-view",itemClass:t.itemClass||TimelineViewItem,selectionMode:"none",emptyMessage:t.emptyMessage||"No timeline events to display",template:'\n <div class="timeline-container timeline-{{position}}">\n {{#loading}}\n <div class="timeline-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 <span class="ms-2 text-muted">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class="timeline-empty text-center text-muted py-4">\n <i class="bi bi-clock-history fs-1 d-block mb-2"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class="timeline" data-container="items"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ',...t}),this.position=t.position||"left",this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.groupBy=t.groupBy||"none"}_createItemView(t,e){if(this.itemViews.has(t.id))return;const i=new this.itemClass({model:t,index:e,listView:this,template:this.itemTemplate,dateFormat:this.dateFormat,dotStyle:this.dotStyle,showDate:this.showDate,theme:this.theme});return this.itemViews.set(t.id,i),i.on("item:click",this._onItemClick.bind(this)),i}_onItemClick(t){this.emit("item:click",t)}setPosition(t){return"left"!==t&&"center"!==t?(console.warn('Invalid position. Use "left" or "center"'),this):(this.position=t,this.isMounted()&&this.render(),this)}setDateFormat(t){return this.dateFormat=t,this.forEachItem(e=>{e.dateFormat=t,e.processItemData(),e.isMounted()&&e.render()}),this}setDotStyle(t){return this.dotStyle=t,this.forEachItem(e=>{e.dotStyle=t,e.processItemData(),e.isMounted()&&e.render()}),this}toggleDates(t=null){return this.showDate=null!==t?t:!this.showDate,this.forEachItem(t=>{t.showDate=this.showDate,t.isMounted()&&t.render()}),this}}exports.ListView=t.ListView,exports.ListViewItem=t.ListViewItem,exports.Collection=e.Collection,exports.Model=e.Model,exports.View=e.View,exports.TimelineView=TimelineView,exports.TimelineViewItem=TimelineViewItem;
2
2
  //# sourceMappingURL=timeline.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"timeline.cjs.js","sources":["../src/extensions/timeline/TimelineViewItem.js","../src/extensions/timeline/TimelineView.js"],"sourcesContent":["/**\n * TimelineViewItem - Individual timeline item view\n * \n * Extends ListViewItem to provide timeline-specific rendering.\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n * \n * Expected model attributes:\n * - date: Date string or Date object\n * - title: Main heading (optional)\n * - description: Body text (optional)\n * - icon: Bootstrap icon class (optional)\n * - color: Bootstrap color variant (optional)\n * - meta: Additional metadata (optional)\n * \n * @example\n * const item = new TimelineViewItem({\n * model: eventModel,\n * dateFormat: 'relative',\n * dotStyle: 'icon'\n * });\n */\n\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TimelineViewItem extends ListViewItem {\n constructor(options = {}) {\n super({\n className: 'timeline-item',\n ...options\n });\n\n // Timeline-specific options\n this.dateFormat = options.dateFormat || 'date';\n this.dotStyle = options.dotStyle || 'solid';\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n\n // Default timeline item template\n if (!this.template) {\n this.template = `\n <div class=\"timeline-marker timeline-marker-{{markerType}}\">\n {{#hasIcon}}\n <i class=\"bi {{model.icon}} text-{{displayColor}}\"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class=\"timeline-dot bg-{{displayColor}}\"></div>\n {{/hasIcon}}\n </div>\n \n <div class=\"timeline-content\">\n {{#showDate}}\n <div class=\"timeline-date text-muted small\">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class=\"timeline-card\">\n {{#model.title}}\n <h6 class=\"timeline-title mb-1\">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class=\"timeline-description mb-0\">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class=\"timeline-meta mt-2 text-muted small\">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n `;\n }\n }\n\n async onInit() {\n await super.onInit();\n this.processItemData();\n }\n\n processItemData() {\n // Get color from model or use theme default\n this.displayColor = this.model?.get?.('color') || this.model?.color || this.theme;\n \n // Determine marker type\n const hasIcon = !!(this.model?.get?.('icon') || this.model?.icon) && this.dotStyle === 'icon';\n this.hasIcon = hasIcon;\n this.markerType = hasIcon ? 'icon' : this.dotStyle;\n \n // Format date\n const dateValue = this.model?.get?.('date') || this.model?.date;\n this.formattedDate = this.formatDate(dateValue);\n }\n\n formatDate(date) {\n if (!date) return '';\n \n switch (this.dateFormat) {\n case 'datetime':\n return dataFormatter.pipe(date, 'datetime');\n case 'relative':\n return dataFormatter.pipe(date, 'timeago');\n default:\n return dataFormatter.pipe(date, 'date');\n }\n }\n\n // Override to disable selection behavior in timeline\n async onActionSelect(event, _element) {\n event.stopPropagation();\n \n // Emit click event instead of selection\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n if (this.listView) {\n this.listView.emit('item:click', {\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\nexport default TimelineViewItem;\n","/**\n * TimelineView - Timeline component extending ListView\n * \n * Displays chronological events from a Collection with clean, modern styling.\n * Each timeline item is managed as a separate view that updates independently\n * when its model changes.\n * \n * Features:\n * - Collection-based data management\n * - Left-aligned or center-aligned layout\n * - Customizable markers (dots, icons)\n * - Date formatting and grouping\n * - Individual item re-rendering\n * - Responsive Bootstrap 5 styling\n * \n * @example\n * const timeline = new TimelineView({\n * collection: eventCollection,\n * position: 'left',\n * dotStyle: 'icon',\n * dateFormat: 'relative'\n * });\n * \n * @example\n * // Custom item template\n * const timeline = new TimelineView({\n * collection: historyCollection,\n * itemTemplate: `\n * <div class=\"custom-timeline-item\">\n * <strong>{{model.title}}</strong>\n * <p>{{model.description}}</p>\n * </div>\n * `\n * });\n */\n\nimport ListView from '@core/views/list/ListView.js';\nimport TimelineViewItem from './TimelineViewItem.js';\n\nclass TimelineView extends ListView {\n constructor(options = {}) {\n // Override ListView defaults with timeline-specific settings\n super({\n className: 'timeline-view',\n itemClass: options.itemClass || TimelineViewItem,\n selectionMode: 'none', // Timelines typically don't use selection\n emptyMessage: options.emptyMessage || 'No timeline events to display',\n template: `\n <div class=\"timeline-container timeline-{{position}}\">\n {{#loading}}\n <div class=\"timeline-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 <span class=\"ms-2 text-muted\">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"timeline-empty text-center text-muted py-4\">\n <i class=\"bi bi-clock-history fs-1 d-block mb-2\"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"timeline\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // Timeline-specific options\n this.position = options.position || 'left'; // 'left' or 'center'\n this.dateFormat = options.dateFormat || 'date'; // 'date', 'datetime', 'relative'\n this.dotStyle = options.dotStyle || 'solid'; // 'solid', 'hollow', 'icon'\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n this.groupBy = options.groupBy || 'none'; // Future: 'none', 'day', 'month', 'year'\n }\n\n /**\n * Override _createItemView to pass timeline-specific options\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,\n // Pass timeline-specific options to items\n dateFormat: this.dateFormat,\n dotStyle: this.dotStyle,\n showDate: this.showDate,\n theme: this.theme\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:click', this._onItemClick.bind(this));\n\n return itemView;\n }\n\n /**\n * Handle item clicks (replaces selection behavior)\n */\n _onItemClick(event) {\n this.emit('item:click', event);\n }\n\n /**\n * Update timeline position\n */\n setPosition(position) {\n if (position !== 'left' && position !== 'center') {\n console.warn('Invalid position. Use \"left\" or \"center\"');\n return this;\n }\n \n this.position = position;\n if (this.isMounted()) {\n this.render();\n }\n return this;\n }\n\n /**\n * Update date format for all items\n */\n setDateFormat(format) {\n this.dateFormat = format;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dateFormat = format;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Update dot style for all items\n */\n setDotStyle(style) {\n this.dotStyle = style;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dotStyle = style;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Toggle date display\n */\n toggleDates(show = null) {\n this.showDate = show !== null ? show : !this.showDate;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.showDate = this.showDate;\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n}\n\nexport default TimelineView;\n"],"names":["TimelineViewItem","ListViewItem","constructor","options","super","className","this","dateFormat","dotStyle","showDate","theme","template","onInit","processItemData","displayColor","model","get","color","hasIcon","icon","markerType","dateValue","date","formattedDate","formatDate","dataFormatter","pipe","onActionSelect","event","_element","stopPropagation","emit","item","index","data","toJSON","listView","TimelineView","ListView","itemClass","selectionMode","emptyMessage","position","groupBy","_createItemView","itemViews","has","id","itemView","itemTemplate","set","on","_onItemClick","bind","setPosition","console","warn","isMounted","render","setDateFormat","format","forEachItem","setDotStyle","style","toggleDates","show"],"mappings":"qNA0BA,MAAMA,yBAAyBC,EAAAA,aAC3B,WAAAC,CAAYC,EAAU,IAClBC,MAAM,CACFC,UAAW,mBACRF,IAIPG,KAAKC,WAAaJ,EAAQI,YAAc,OACxCD,KAAKE,SAAWL,EAAQK,UAAY,QACpCF,KAAKG,UAAgC,IAArBN,EAAQM,SACxBH,KAAKI,MAAQP,EAAQO,OAAS,UAGzBJ,KAAKK,WACNL,KAAKK,SAAW,y5CAmCxB,CAEA,YAAMC,SACIR,MAAMQ,SACZN,KAAKO,iBACT,CAEA,eAAAA,GAEIP,KAAKQ,aAAeR,KAAKS,OAAOC,MAAM,UAAYV,KAAKS,OAAOE,OAASX,KAAKI,MAG5E,MAAMQ,KAAaZ,KAAKS,OAAOC,MAAM,UAAWV,KAAKS,OAAOI,OAA2B,SAAlBb,KAAKE,SAC1EF,KAAKY,QAAUA,EACfZ,KAAKc,WAAaF,EAAU,OAASZ,KAAKE,SAG1C,MAAMa,EAAYf,KAAKS,OAAOC,MAAM,SAAWV,KAAKS,OAAOO,KAC3DhB,KAAKiB,cAAgBjB,KAAKkB,WAAWH,EACzC,CAEA,UAAAG,CAAWF,GACP,IAAKA,EAAM,MAAO,GAElB,OAAQhB,KAAKC,YACT,IAAK,WACD,OAAOkB,gBAAcC,KAAKJ,EAAM,YACpC,IAAK,WACD,OAAOG,gBAAcC,KAAKJ,EAAM,WACpC,QACI,OAAOG,gBAAcC,KAAKJ,EAAM,QAE5C,CAGA,oBAAMK,CAAeC,EAAOC,GACxBD,EAAME,kBAGNxB,KAAKyB,KAAK,aAAc,CACpBC,KAAM1B,KACNS,MAAOT,KAAKS,MACZkB,MAAO3B,KAAK2B,MACZC,KAAM5B,KAAKS,OAAOoB,OAAS7B,KAAKS,MAAMoB,SAAW7B,KAAKS,QAGtDT,KAAK8B,UACL9B,KAAK8B,SAASL,KAAK,aAAc,CAC7BC,KAAM1B,KACNS,MAAOT,KAAKS,MACZkB,MAAO3B,KAAK2B,MACZC,KAAM5B,KAAKS,OAAOoB,OAAS7B,KAAKS,MAAMoB,SAAW7B,KAAKS,OAGlE,EC3FJ,MAAMsB,qBAAqBC,EAAAA,SACvB,WAAApC,CAAYC,EAAU,IAElBC,MAAM,CACFC,UAAW,gBACXkC,UAAWpC,EAAQoC,WAAavC,iBAChCwC,cAAe,OACfC,aAActC,EAAQsC,cAAgB,gCACtC9B,SAAU,6nCAuBPR,IAIPG,KAAKoC,SAAWvC,EAAQuC,UAAY,OACpCpC,KAAKC,WAAaJ,EAAQI,YAAc,OACxCD,KAAKE,SAAWL,EAAQK,UAAY,QACpCF,KAAKG,UAAgC,IAArBN,EAAQM,SACxBH,KAAKI,MAAQP,EAAQO,OAAS,UAC9BJ,KAAKqC,QAAUxC,EAAQwC,SAAW,MACtC,CAKA,eAAAC,CAAgB7B,EAAOkB,GAEnB,GAAI3B,KAAKuC,UAAUC,IAAI/B,EAAMgC,IAAK,OAElC,MAAMC,EAAW,IAAI1C,KAAKiC,UAAU,CAChCxB,QACAkB,QACAG,SAAU9B,KACVK,SAAUL,KAAK2C,aAEf1C,WAAYD,KAAKC,WACjBC,SAAUF,KAAKE,SACfC,SAAUH,KAAKG,SACfC,MAAOJ,KAAKI,QAShB,OALAJ,KAAKuC,UAAUK,IAAInC,EAAMgC,GAAIC,GAG7BA,EAASG,GAAG,aAAc7C,KAAK8C,aAAaC,KAAK/C,OAE1C0C,CACX,CAKA,YAAAI,CAAaxB,GACTtB,KAAKyB,KAAK,aAAcH,EAC5B,CAKA,WAAA0B,CAAYZ,GACR,MAAiB,SAAbA,GAAoC,WAAbA,GACvBa,QAAQC,KAAK,4CACNlD,OAGXA,KAAKoC,SAAWA,EACZpC,KAAKmD,aACLnD,KAAKoD,SAEFpD,KACX,CAKA,aAAAqD,CAAcC,GAYV,OAXAtD,KAAKC,WAAaqD,EAGlBtD,KAAKuD,YAAYb,IACbA,EAASzC,WAAaqD,EACtBZ,EAASnC,kBACLmC,EAASS,aACTT,EAASU,WAIVpD,IACX,CAKA,WAAAwD,CAAYC,GAYR,OAXAzD,KAAKE,SAAWuD,EAGhBzD,KAAKuD,YAAYb,IACbA,EAASxC,SAAWuD,EACpBf,EAASnC,kBACLmC,EAASS,aACTT,EAASU,WAIVpD,IACX,CAKA,WAAA0D,CAAYC,EAAO,MAWf,OAVA3D,KAAKG,SAAoB,OAATwD,EAAgBA,GAAQ3D,KAAKG,SAG7CH,KAAKuD,YAAYb,IACbA,EAASvC,SAAWH,KAAKG,SACrBuC,EAASS,aACTT,EAASU,WAIVpD,IACX"}
1
+ {"version":3,"file":"timeline.cjs.js","sources":["../src/extensions/timeline/TimelineViewItem.js","../src/extensions/timeline/TimelineView.js"],"sourcesContent":["/**\n * TimelineViewItem - Individual timeline item view\n * \n * Extends ListViewItem to provide timeline-specific rendering.\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n * \n * Expected model attributes:\n * - date: Date string or Date object\n * - title: Main heading (optional)\n * - description: Body text (optional)\n * - icon: Bootstrap icon class (optional)\n * - color: Bootstrap color variant (optional)\n * - meta: Additional metadata (optional)\n * \n * @example\n * const item = new TimelineViewItem({\n * model: eventModel,\n * dateFormat: 'relative',\n * dotStyle: 'icon'\n * });\n */\n\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TimelineViewItem extends ListViewItem {\n constructor(options = {}) {\n super({\n className: 'timeline-item',\n ...options\n });\n\n // Timeline-specific options\n this.dateFormat = options.dateFormat || 'date';\n this.dotStyle = options.dotStyle || 'solid';\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n\n // Default timeline item template\n if (!this.template) {\n this.template = `\n <div class=\"timeline-marker timeline-marker-{{markerType}}\">\n {{#hasIcon}}\n <i class=\"bi {{model.icon}} text-{{displayColor}}\"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class=\"timeline-dot bg-{{displayColor}}\"></div>\n {{/hasIcon}}\n </div>\n \n <div class=\"timeline-content\">\n {{#showDate}}\n <div class=\"timeline-date text-muted small\">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class=\"timeline-card\">\n {{#model.title}}\n <h6 class=\"timeline-title mb-1\">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class=\"timeline-description mb-0\">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class=\"timeline-meta mt-2 text-muted small\">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n `;\n }\n }\n\n async onInit() {\n await super.onInit();\n this.processItemData();\n }\n\n processItemData() {\n // Get color from model or use theme default\n this.displayColor = this.model?.get?.('color') || this.model?.color || this.theme;\n \n // Determine marker type\n const hasIcon = !!(this.model?.get?.('icon') || this.model?.icon) && this.dotStyle === 'icon';\n this.hasIcon = hasIcon;\n this.markerType = hasIcon ? 'icon' : this.dotStyle;\n \n // Format date\n const dateValue = this.model?.get?.('date') || this.model?.date;\n this.formattedDate = this.formatDate(dateValue);\n }\n\n formatDate(date) {\n if (!date) return '';\n \n switch (this.dateFormat) {\n case 'datetime':\n return dataFormatter.pipe(date, 'datetime');\n case 'relative':\n return dataFormatter.pipe(date, 'timeago');\n default:\n return dataFormatter.pipe(date, 'date');\n }\n }\n\n // Override to disable selection behavior in timeline\n async onActionSelect(event, _element) {\n event.stopPropagation();\n \n // Emit click event instead of selection\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n if (this.listView) {\n this.listView.emit('item:click', {\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\nexport default TimelineViewItem;\n","/**\n * TimelineView - Timeline component extending ListView\n * \n * Displays chronological events from a Collection with clean, modern styling.\n * Each timeline item is managed as a separate view that updates independently\n * when its model changes.\n * \n * Features:\n * - Collection-based data management\n * - Left-aligned or center-aligned layout\n * - Customizable markers (dots, icons)\n * - Date formatting and grouping\n * - Individual item re-rendering\n * - Responsive Bootstrap 5 styling\n * \n * @example\n * const timeline = new TimelineView({\n * collection: eventCollection,\n * position: 'left',\n * dotStyle: 'icon',\n * dateFormat: 'relative'\n * });\n * \n * @example\n * // Custom item template\n * const timeline = new TimelineView({\n * collection: historyCollection,\n * itemTemplate: `\n * <div class=\"custom-timeline-item\">\n * <strong>{{model.title}}</strong>\n * <p>{{model.description}}</p>\n * </div>\n * `\n * });\n */\n\nimport ListView from '@core/views/list/ListView.js';\nimport TimelineViewItem from './TimelineViewItem.js';\n\nclass TimelineView extends ListView {\n constructor(options = {}) {\n // Override ListView defaults with timeline-specific settings\n super({\n className: 'timeline-view',\n itemClass: options.itemClass || TimelineViewItem,\n selectionMode: 'none', // Timelines typically don't use selection\n emptyMessage: options.emptyMessage || 'No timeline events to display',\n template: `\n <div class=\"timeline-container timeline-{{position}}\">\n {{#loading}}\n <div class=\"timeline-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 <span class=\"ms-2 text-muted\">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"timeline-empty text-center text-muted py-4\">\n <i class=\"bi bi-clock-history fs-1 d-block mb-2\"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"timeline\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // Timeline-specific options\n this.position = options.position || 'left'; // 'left' or 'center'\n this.dateFormat = options.dateFormat || 'date'; // 'date', 'datetime', 'relative'\n this.dotStyle = options.dotStyle || 'solid'; // 'solid', 'hollow', 'icon'\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n this.groupBy = options.groupBy || 'none'; // Future: 'none', 'day', 'month', 'year'\n }\n\n /**\n * Override _createItemView to pass timeline-specific options\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,\n // Pass timeline-specific options to items\n dateFormat: this.dateFormat,\n dotStyle: this.dotStyle,\n showDate: this.showDate,\n theme: this.theme\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:click', this._onItemClick.bind(this));\n\n return itemView;\n }\n\n /**\n * Handle item clicks (replaces selection behavior)\n */\n _onItemClick(event) {\n this.emit('item:click', event);\n }\n\n /**\n * Update timeline position\n */\n setPosition(position) {\n if (position !== 'left' && position !== 'center') {\n console.warn('Invalid position. Use \"left\" or \"center\"');\n return this;\n }\n \n this.position = position;\n if (this.isMounted()) {\n this.render();\n }\n return this;\n }\n\n /**\n * Update date format for all items\n */\n setDateFormat(format) {\n this.dateFormat = format;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dateFormat = format;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Update dot style for all items\n */\n setDotStyle(style) {\n this.dotStyle = style;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dotStyle = style;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Toggle date display\n */\n toggleDates(show = null) {\n this.showDate = show !== null ? show : !this.showDate;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.showDate = this.showDate;\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n}\n\nexport default TimelineView;\n"],"names":["TimelineViewItem","ListViewItem","constructor","options","super","className","this","dateFormat","dotStyle","showDate","theme","template","onInit","processItemData","displayColor","model","get","color","hasIcon","icon","markerType","dateValue","date","formattedDate","formatDate","dataFormatter","pipe","onActionSelect","event","_element","stopPropagation","emit","item","index","data","toJSON","listView","TimelineView","ListView","itemClass","selectionMode","emptyMessage","position","groupBy","_createItemView","itemViews","has","id","itemView","itemTemplate","set","on","_onItemClick","bind","setPosition","console","warn","isMounted","render","setDateFormat","format","forEachItem","setDotStyle","style","toggleDates","show"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{L as t,a as e}from"./chunks/ListView-BLFFK_Ir.js";import{d as i}from"./chunks/Rest-BJ3Mvx1L.js";import{V as s}from"./chunks/Rest-BJ3Mvx1L.js";import{C as n,M as o}from"./chunks/Collection-BWKmydl5.js";class TimelineViewItem extends t{constructor(t={}){super({className:"timeline-item",...t}),this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.template||(this.template='\n <div class="timeline-marker timeline-marker-{{markerType}}">\n {{#hasIcon}}\n <i class="bi {{model.icon}} text-{{displayColor}}"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class="timeline-dot bg-{{displayColor}}"></div>\n {{/hasIcon}}\n </div>\n \n <div class="timeline-content">\n {{#showDate}}\n <div class="timeline-date text-muted small">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class="timeline-card">\n {{#model.title}}\n <h6 class="timeline-title mb-1">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class="timeline-description mb-0">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class="timeline-meta mt-2 text-muted small">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n ')}async onInit(){await super.onInit(),this.processItemData()}processItemData(){this.displayColor=this.model?.get?.("color")||this.model?.color||this.theme;const t=!(!this.model?.get?.("icon")&&!this.model?.icon)&&"icon"===this.dotStyle;this.hasIcon=t,this.markerType=t?"icon":this.dotStyle;const e=this.model?.get?.("date")||this.model?.date;this.formattedDate=this.formatDate(e)}formatDate(t){if(!t)return"";switch(this.dateFormat){case"datetime":return i.pipe(t,"datetime");case"relative":return i.pipe(t,"timeago");default:return i.pipe(t,"date")}}async onActionSelect(t,e){t.stopPropagation(),this.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model})}}class TimelineView extends e{constructor(t={}){super({className:"timeline-view",itemClass:t.itemClass||TimelineViewItem,selectionMode:"none",emptyMessage:t.emptyMessage||"No timeline events to display",template:'\n <div class="timeline-container timeline-{{position}}">\n {{#loading}}\n <div class="timeline-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 <span class="ms-2 text-muted">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class="timeline-empty text-center text-muted py-4">\n <i class="bi bi-clock-history fs-1 d-block mb-2"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class="timeline" data-container="items"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ',...t}),this.position=t.position||"left",this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.groupBy=t.groupBy||"none"}_createItemView(t,e){if(this.itemViews.has(t.id))return;const i=new this.itemClass({model:t,index:e,listView:this,template:this.itemTemplate,dateFormat:this.dateFormat,dotStyle:this.dotStyle,showDate:this.showDate,theme:this.theme});return this.itemViews.set(t.id,i),i.on("item:click",this._onItemClick.bind(this)),i}_onItemClick(t){this.emit("item:click",t)}setPosition(t){return"left"!==t&&"center"!==t?(console.warn('Invalid position. Use "left" or "center"'),this):(this.position=t,this.isMounted()&&this.render(),this)}setDateFormat(t){return this.dateFormat=t,this.forEachItem(e=>{e.dateFormat=t,e.processItemData(),e.isMounted()&&e.render()}),this}setDotStyle(t){return this.dotStyle=t,this.forEachItem(e=>{e.dotStyle=t,e.processItemData(),e.isMounted()&&e.render()}),this}toggleDates(t=null){return this.showDate=null!==t?t:!this.showDate,this.forEachItem(t=>{t.showDate=this.showDate,t.isMounted()&&t.render()}),this}}export{n as Collection,e as ListView,t as ListViewItem,o as Model,TimelineView,TimelineViewItem,s as View};
1
+ import{L as t,a as e}from"./chunks/ListView-B0QbqSPv.js";import{d as i}from"./chunks/Collection-r1ACzUeh.js";import{C as s,M as n,V as o}from"./chunks/Collection-r1ACzUeh.js";class TimelineViewItem extends t{constructor(t={}){super({className:"timeline-item",...t}),this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.template||(this.template='\n <div class="timeline-marker timeline-marker-{{markerType}}">\n {{#hasIcon}}\n <i class="bi {{model.icon}} text-{{displayColor}}"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class="timeline-dot bg-{{displayColor}}"></div>\n {{/hasIcon}}\n </div>\n \n <div class="timeline-content">\n {{#showDate}}\n <div class="timeline-date text-muted small">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class="timeline-card">\n {{#model.title}}\n <h6 class="timeline-title mb-1">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class="timeline-description mb-0">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class="timeline-meta mt-2 text-muted small">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n ')}async onInit(){await super.onInit(),this.processItemData()}processItemData(){this.displayColor=this.model?.get?.("color")||this.model?.color||this.theme;const t=!(!this.model?.get?.("icon")&&!this.model?.icon)&&"icon"===this.dotStyle;this.hasIcon=t,this.markerType=t?"icon":this.dotStyle;const e=this.model?.get?.("date")||this.model?.date;this.formattedDate=this.formatDate(e)}formatDate(t){if(!t)return"";switch(this.dateFormat){case"datetime":return i.pipe(t,"datetime");case"relative":return i.pipe(t,"timeago");default:return i.pipe(t,"date")}}async onActionSelect(t,e){t.stopPropagation(),this.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:click",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model})}}class TimelineView extends e{constructor(t={}){super({className:"timeline-view",itemClass:t.itemClass||TimelineViewItem,selectionMode:"none",emptyMessage:t.emptyMessage||"No timeline events to display",template:'\n <div class="timeline-container timeline-{{position}}">\n {{#loading}}\n <div class="timeline-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 <span class="ms-2 text-muted">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class="timeline-empty text-center text-muted py-4">\n <i class="bi bi-clock-history fs-1 d-block mb-2"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class="timeline" data-container="items"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ',...t}),this.position=t.position||"left",this.dateFormat=t.dateFormat||"date",this.dotStyle=t.dotStyle||"solid",this.showDate=!1!==t.showDate,this.theme=t.theme||"primary",this.groupBy=t.groupBy||"none"}_createItemView(t,e){if(this.itemViews.has(t.id))return;const i=new this.itemClass({model:t,index:e,listView:this,template:this.itemTemplate,dateFormat:this.dateFormat,dotStyle:this.dotStyle,showDate:this.showDate,theme:this.theme});return this.itemViews.set(t.id,i),i.on("item:click",this._onItemClick.bind(this)),i}_onItemClick(t){this.emit("item:click",t)}setPosition(t){return"left"!==t&&"center"!==t?(console.warn('Invalid position. Use "left" or "center"'),this):(this.position=t,this.isMounted()&&this.render(),this)}setDateFormat(t){return this.dateFormat=t,this.forEachItem(e=>{e.dateFormat=t,e.processItemData(),e.isMounted()&&e.render()}),this}setDotStyle(t){return this.dotStyle=t,this.forEachItem(e=>{e.dotStyle=t,e.processItemData(),e.isMounted()&&e.render()}),this}toggleDates(t=null){return this.showDate=null!==t?t:!this.showDate,this.forEachItem(t=>{t.showDate=this.showDate,t.isMounted()&&t.render()}),this}}export{s as Collection,e as ListView,t as ListViewItem,n as Model,TimelineView,TimelineViewItem,o as View};
2
2
  //# sourceMappingURL=timeline.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"timeline.es.js","sources":["../src/extensions/timeline/TimelineViewItem.js","../src/extensions/timeline/TimelineView.js"],"sourcesContent":["/**\n * TimelineViewItem - Individual timeline item view\n * \n * Extends ListViewItem to provide timeline-specific rendering.\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n * \n * Expected model attributes:\n * - date: Date string or Date object\n * - title: Main heading (optional)\n * - description: Body text (optional)\n * - icon: Bootstrap icon class (optional)\n * - color: Bootstrap color variant (optional)\n * - meta: Additional metadata (optional)\n * \n * @example\n * const item = new TimelineViewItem({\n * model: eventModel,\n * dateFormat: 'relative',\n * dotStyle: 'icon'\n * });\n */\n\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TimelineViewItem extends ListViewItem {\n constructor(options = {}) {\n super({\n className: 'timeline-item',\n ...options\n });\n\n // Timeline-specific options\n this.dateFormat = options.dateFormat || 'date';\n this.dotStyle = options.dotStyle || 'solid';\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n\n // Default timeline item template\n if (!this.template) {\n this.template = `\n <div class=\"timeline-marker timeline-marker-{{markerType}}\">\n {{#hasIcon}}\n <i class=\"bi {{model.icon}} text-{{displayColor}}\"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class=\"timeline-dot bg-{{displayColor}}\"></div>\n {{/hasIcon}}\n </div>\n \n <div class=\"timeline-content\">\n {{#showDate}}\n <div class=\"timeline-date text-muted small\">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class=\"timeline-card\">\n {{#model.title}}\n <h6 class=\"timeline-title mb-1\">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class=\"timeline-description mb-0\">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class=\"timeline-meta mt-2 text-muted small\">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n `;\n }\n }\n\n async onInit() {\n await super.onInit();\n this.processItemData();\n }\n\n processItemData() {\n // Get color from model or use theme default\n this.displayColor = this.model?.get?.('color') || this.model?.color || this.theme;\n \n // Determine marker type\n const hasIcon = !!(this.model?.get?.('icon') || this.model?.icon) && this.dotStyle === 'icon';\n this.hasIcon = hasIcon;\n this.markerType = hasIcon ? 'icon' : this.dotStyle;\n \n // Format date\n const dateValue = this.model?.get?.('date') || this.model?.date;\n this.formattedDate = this.formatDate(dateValue);\n }\n\n formatDate(date) {\n if (!date) return '';\n \n switch (this.dateFormat) {\n case 'datetime':\n return dataFormatter.pipe(date, 'datetime');\n case 'relative':\n return dataFormatter.pipe(date, 'timeago');\n default:\n return dataFormatter.pipe(date, 'date');\n }\n }\n\n // Override to disable selection behavior in timeline\n async onActionSelect(event, _element) {\n event.stopPropagation();\n \n // Emit click event instead of selection\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n if (this.listView) {\n this.listView.emit('item:click', {\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\nexport default TimelineViewItem;\n","/**\n * TimelineView - Timeline component extending ListView\n * \n * Displays chronological events from a Collection with clean, modern styling.\n * Each timeline item is managed as a separate view that updates independently\n * when its model changes.\n * \n * Features:\n * - Collection-based data management\n * - Left-aligned or center-aligned layout\n * - Customizable markers (dots, icons)\n * - Date formatting and grouping\n * - Individual item re-rendering\n * - Responsive Bootstrap 5 styling\n * \n * @example\n * const timeline = new TimelineView({\n * collection: eventCollection,\n * position: 'left',\n * dotStyle: 'icon',\n * dateFormat: 'relative'\n * });\n * \n * @example\n * // Custom item template\n * const timeline = new TimelineView({\n * collection: historyCollection,\n * itemTemplate: `\n * <div class=\"custom-timeline-item\">\n * <strong>{{model.title}}</strong>\n * <p>{{model.description}}</p>\n * </div>\n * `\n * });\n */\n\nimport ListView from '@core/views/list/ListView.js';\nimport TimelineViewItem from './TimelineViewItem.js';\n\nclass TimelineView extends ListView {\n constructor(options = {}) {\n // Override ListView defaults with timeline-specific settings\n super({\n className: 'timeline-view',\n itemClass: options.itemClass || TimelineViewItem,\n selectionMode: 'none', // Timelines typically don't use selection\n emptyMessage: options.emptyMessage || 'No timeline events to display',\n template: `\n <div class=\"timeline-container timeline-{{position}}\">\n {{#loading}}\n <div class=\"timeline-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 <span class=\"ms-2 text-muted\">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"timeline-empty text-center text-muted py-4\">\n <i class=\"bi bi-clock-history fs-1 d-block mb-2\"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"timeline\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // Timeline-specific options\n this.position = options.position || 'left'; // 'left' or 'center'\n this.dateFormat = options.dateFormat || 'date'; // 'date', 'datetime', 'relative'\n this.dotStyle = options.dotStyle || 'solid'; // 'solid', 'hollow', 'icon'\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n this.groupBy = options.groupBy || 'none'; // Future: 'none', 'day', 'month', 'year'\n }\n\n /**\n * Override _createItemView to pass timeline-specific options\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,\n // Pass timeline-specific options to items\n dateFormat: this.dateFormat,\n dotStyle: this.dotStyle,\n showDate: this.showDate,\n theme: this.theme\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:click', this._onItemClick.bind(this));\n\n return itemView;\n }\n\n /**\n * Handle item clicks (replaces selection behavior)\n */\n _onItemClick(event) {\n this.emit('item:click', event);\n }\n\n /**\n * Update timeline position\n */\n setPosition(position) {\n if (position !== 'left' && position !== 'center') {\n console.warn('Invalid position. Use \"left\" or \"center\"');\n return this;\n }\n \n this.position = position;\n if (this.isMounted()) {\n this.render();\n }\n return this;\n }\n\n /**\n * Update date format for all items\n */\n setDateFormat(format) {\n this.dateFormat = format;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dateFormat = format;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Update dot style for all items\n */\n setDotStyle(style) {\n this.dotStyle = style;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dotStyle = style;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Toggle date display\n */\n toggleDates(show = null) {\n this.showDate = show !== null ? show : !this.showDate;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.showDate = this.showDate;\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n}\n\nexport default TimelineView;\n"],"names":["TimelineViewItem","ListViewItem","constructor","options","super","className","this","dateFormat","dotStyle","showDate","theme","template","onInit","processItemData","displayColor","model","get","color","hasIcon","icon","markerType","dateValue","date","formattedDate","formatDate","dataFormatter","pipe","onActionSelect","event","_element","stopPropagation","emit","item","index","data","toJSON","listView","TimelineView","ListView","itemClass","selectionMode","emptyMessage","position","groupBy","_createItemView","itemViews","has","id","itemView","itemTemplate","set","on","_onItemClick","bind","setPosition","console","warn","isMounted","render","setDateFormat","format","forEachItem","setDotStyle","style","toggleDates","show"],"mappings":"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"}
1
+ {"version":3,"file":"timeline.es.js","sources":["../src/extensions/timeline/TimelineViewItem.js","../src/extensions/timeline/TimelineView.js"],"sourcesContent":["/**\n * TimelineViewItem - Individual timeline item view\n * \n * Extends ListViewItem to provide timeline-specific rendering.\n * Each item is its own View with its own model, allowing for\n * independent re-rendering when the model changes.\n * \n * Expected model attributes:\n * - date: Date string or Date object\n * - title: Main heading (optional)\n * - description: Body text (optional)\n * - icon: Bootstrap icon class (optional)\n * - color: Bootstrap color variant (optional)\n * - meta: Additional metadata (optional)\n * \n * @example\n * const item = new TimelineViewItem({\n * model: eventModel,\n * dateFormat: 'relative',\n * dotStyle: 'icon'\n * });\n */\n\nimport ListViewItem from '@core/views/list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TimelineViewItem extends ListViewItem {\n constructor(options = {}) {\n super({\n className: 'timeline-item',\n ...options\n });\n\n // Timeline-specific options\n this.dateFormat = options.dateFormat || 'date';\n this.dotStyle = options.dotStyle || 'solid';\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n\n // Default timeline item template\n if (!this.template) {\n this.template = `\n <div class=\"timeline-marker timeline-marker-{{markerType}}\">\n {{#hasIcon}}\n <i class=\"bi {{model.icon}} text-{{displayColor}}\"></i>\n {{/hasIcon}}\n {{^hasIcon}}\n <div class=\"timeline-dot bg-{{displayColor}}\"></div>\n {{/hasIcon}}\n </div>\n \n <div class=\"timeline-content\">\n {{#showDate}}\n <div class=\"timeline-date text-muted small\">\n {{formattedDate}}\n </div>\n {{/showDate}}\n \n <div class=\"timeline-card\">\n {{#model.title}}\n <h6 class=\"timeline-title mb-1\">{{model.title}}</h6>\n {{/model.title}}\n \n {{#model.description}}\n <p class=\"timeline-description mb-0\">{{model.description}}</p>\n {{/model.description}}\n \n {{#model.meta}}\n <div class=\"timeline-meta mt-2 text-muted small\">\n {{model.meta}}\n </div>\n {{/model.meta}}\n </div>\n </div>\n `;\n }\n }\n\n async onInit() {\n await super.onInit();\n this.processItemData();\n }\n\n processItemData() {\n // Get color from model or use theme default\n this.displayColor = this.model?.get?.('color') || this.model?.color || this.theme;\n \n // Determine marker type\n const hasIcon = !!(this.model?.get?.('icon') || this.model?.icon) && this.dotStyle === 'icon';\n this.hasIcon = hasIcon;\n this.markerType = hasIcon ? 'icon' : this.dotStyle;\n \n // Format date\n const dateValue = this.model?.get?.('date') || this.model?.date;\n this.formattedDate = this.formatDate(dateValue);\n }\n\n formatDate(date) {\n if (!date) return '';\n \n switch (this.dateFormat) {\n case 'datetime':\n return dataFormatter.pipe(date, 'datetime');\n case 'relative':\n return dataFormatter.pipe(date, 'timeago');\n default:\n return dataFormatter.pipe(date, 'date');\n }\n }\n\n // Override to disable selection behavior in timeline\n async onActionSelect(event, _element) {\n event.stopPropagation();\n \n // Emit click event instead of selection\n this.emit('item:click', {\n item: this,\n model: this.model,\n index: this.index,\n data: this.model?.toJSON ? this.model.toJSON() : this.model\n });\n\n if (this.listView) {\n this.listView.emit('item:click', {\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\nexport default TimelineViewItem;\n","/**\n * TimelineView - Timeline component extending ListView\n * \n * Displays chronological events from a Collection with clean, modern styling.\n * Each timeline item is managed as a separate view that updates independently\n * when its model changes.\n * \n * Features:\n * - Collection-based data management\n * - Left-aligned or center-aligned layout\n * - Customizable markers (dots, icons)\n * - Date formatting and grouping\n * - Individual item re-rendering\n * - Responsive Bootstrap 5 styling\n * \n * @example\n * const timeline = new TimelineView({\n * collection: eventCollection,\n * position: 'left',\n * dotStyle: 'icon',\n * dateFormat: 'relative'\n * });\n * \n * @example\n * // Custom item template\n * const timeline = new TimelineView({\n * collection: historyCollection,\n * itemTemplate: `\n * <div class=\"custom-timeline-item\">\n * <strong>{{model.title}}</strong>\n * <p>{{model.description}}</p>\n * </div>\n * `\n * });\n */\n\nimport ListView from '@core/views/list/ListView.js';\nimport TimelineViewItem from './TimelineViewItem.js';\n\nclass TimelineView extends ListView {\n constructor(options = {}) {\n // Override ListView defaults with timeline-specific settings\n super({\n className: 'timeline-view',\n itemClass: options.itemClass || TimelineViewItem,\n selectionMode: 'none', // Timelines typically don't use selection\n emptyMessage: options.emptyMessage || 'No timeline events to display',\n template: `\n <div class=\"timeline-container timeline-{{position}}\">\n {{#loading}}\n <div class=\"timeline-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 <span class=\"ms-2 text-muted\">Loading timeline...</span>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"timeline-empty text-center text-muted py-4\">\n <i class=\"bi bi-clock-history fs-1 d-block mb-2\"></i>\n <p>{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <div class=\"timeline\" data-container=\"items\"></div>\n {{/isEmpty}}\n {{/loading}}\n </div>\n `,\n ...options\n });\n\n // Timeline-specific options\n this.position = options.position || 'left'; // 'left' or 'center'\n this.dateFormat = options.dateFormat || 'date'; // 'date', 'datetime', 'relative'\n this.dotStyle = options.dotStyle || 'solid'; // 'solid', 'hollow', 'icon'\n this.showDate = options.showDate !== false;\n this.theme = options.theme || 'primary';\n this.groupBy = options.groupBy || 'none'; // Future: 'none', 'day', 'month', 'year'\n }\n\n /**\n * Override _createItemView to pass timeline-specific options\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,\n // Pass timeline-specific options to items\n dateFormat: this.dateFormat,\n dotStyle: this.dotStyle,\n showDate: this.showDate,\n theme: this.theme\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:click', this._onItemClick.bind(this));\n\n return itemView;\n }\n\n /**\n * Handle item clicks (replaces selection behavior)\n */\n _onItemClick(event) {\n this.emit('item:click', event);\n }\n\n /**\n * Update timeline position\n */\n setPosition(position) {\n if (position !== 'left' && position !== 'center') {\n console.warn('Invalid position. Use \"left\" or \"center\"');\n return this;\n }\n \n this.position = position;\n if (this.isMounted()) {\n this.render();\n }\n return this;\n }\n\n /**\n * Update date format for all items\n */\n setDateFormat(format) {\n this.dateFormat = format;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dateFormat = format;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Update dot style for all items\n */\n setDotStyle(style) {\n this.dotStyle = style;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.dotStyle = style;\n itemView.processItemData();\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n\n /**\n * Toggle date display\n */\n toggleDates(show = null) {\n this.showDate = show !== null ? show : !this.showDate;\n \n // Update all items\n this.forEachItem(itemView => {\n itemView.showDate = this.showDate;\n if (itemView.isMounted()) {\n itemView.render();\n }\n });\n \n return this;\n }\n}\n\nexport default TimelineView;\n"],"names":["TimelineViewItem","ListViewItem","constructor","options","super","className","this","dateFormat","dotStyle","showDate","theme","template","onInit","processItemData","displayColor","model","get","color","hasIcon","icon","markerType","dateValue","date","formattedDate","formatDate","dataFormatter","pipe","onActionSelect","event","_element","stopPropagation","emit","item","index","data","toJSON","listView","TimelineView","ListView","itemClass","selectionMode","emptyMessage","position","groupBy","_createItemView","itemViews","has","id","itemView","itemTemplate","set","on","_onItemClick","bind","setPosition","console","warn","isMounted","render","setDateFormat","format","forEachItem","setDotStyle","style","toggleDates","show"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/UserProfileView-kupeq2rN.js"),i=require("./chunks/index-Da9sT-tE.js");exports.PasskeySetupView=e.PasskeySetupView,exports.ProfileApiKeysSection=e.ProfileApiKeysSection,exports.ProfileConnectedSection=e.ProfileConnectedSection,exports.ProfileDevicesSection=e.ProfileDevicesSection,exports.ProfileGroupsSection=e.ProfileGroupsSection,exports.ProfileNotificationsSection=e.ProfileNotificationsSection,exports.ProfileOverviewSection=e.ProfileOverviewSection,exports.ProfilePermissionsSection=e.ProfilePermissionsSection,exports.ProfilePersonalSection=e.ProfilePersonalSection,exports.ProfileSecurityEventsSection=e.ProfileSecurityEventsSection,exports.ProfileSecuritySection=e.ProfileSecuritySection,exports.ProfileSessionsSection=e.ProfileSessionsSection,exports.UserProfileView=e.UserProfileView,exports.ProfileActivitySection=i.ProfileActivitySection;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/UserProfileView-9vkfCPsp.js"),i=require("./chunks/index-BaPQHxbL.js");exports.PasskeySetupView=e.PasskeySetupView,exports.ProfileApiKeysSection=e.ProfileApiKeysSection,exports.ProfileConnectedSection=e.ProfileConnectedSection,exports.ProfileDevicesSection=e.ProfileDevicesSection,exports.ProfileGroupsSection=e.ProfileGroupsSection,exports.ProfileNotificationsSection=e.ProfileNotificationsSection,exports.ProfileOverviewSection=e.ProfileOverviewSection,exports.ProfilePermissionsSection=e.ProfilePermissionsSection,exports.ProfilePersonalSection=e.ProfilePersonalSection,exports.ProfileSecurityEventsSection=e.ProfileSecurityEventsSection,exports.ProfileSecuritySection=e.ProfileSecuritySection,exports.ProfileSessionsSection=e.ProfileSessionsSection,exports.UserProfileView=e.UserProfileView,exports.ProfileActivitySection=i.ProfileActivitySection;
2
2
  //# sourceMappingURL=user-profile.cjs.js.map
@@ -1,2 +1,2 @@
1
- import{P as e,a as i,b as o,c as s,d as r,e as n,f as t,g as c,h as a,i as f,j as P,k as l,U as S}from"./chunks/UserProfileView-DnVMHcLH.js";import{ProfileActivitySection as p}from"./chunks/index-Aq9ke4vg.js";export{e as PasskeySetupView,p as ProfileActivitySection,i as ProfileApiKeysSection,o as ProfileConnectedSection,s as ProfileDevicesSection,r as ProfileGroupsSection,n as ProfileNotificationsSection,t as ProfileOverviewSection,c as ProfilePermissionsSection,a as ProfilePersonalSection,f as ProfileSecurityEventsSection,P as ProfileSecuritySection,l as ProfileSessionsSection,S as UserProfileView};
1
+ import{P as e,a as i,b as o,c as s,d as r,e as n,f as t,g as c,h as a,i as f,j as P,k as S,U as l}from"./chunks/UserProfileView-tcBT6XcE.js";import{ProfileActivitySection as p}from"./chunks/index-BdfwxVMZ.js";export{e as PasskeySetupView,p as ProfileActivitySection,i as ProfileApiKeysSection,o as ProfileConnectedSection,s as ProfileDevicesSection,r as ProfileGroupsSection,n as ProfileNotificationsSection,t as ProfileOverviewSection,c as ProfilePermissionsSection,a as ProfilePersonalSection,f as ProfileSecurityEventsSection,P as ProfileSecuritySection,S as ProfileSessionsSection,l as UserProfileView};
2
2
  //# sourceMappingURL=user-profile.es.js.map