web-mojo 2.2.57 → 2.2.59

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 (119) 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 -10105
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.es.js +1 -588
  7. package/dist/auth.es.js.map +1 -1
  8. package/dist/charts.cjs.js +1 -1
  9. package/dist/charts.es.js +1 -571
  10. package/dist/charts.es.js.map +1 -1
  11. package/dist/chunks/ChatView-D4A9rIX3.js +2 -0
  12. package/dist/chunks/ChatView-D4A9rIX3.js.map +1 -0
  13. package/dist/chunks/ChatView-nxaq8aIo.js +2 -0
  14. package/dist/chunks/ChatView-nxaq8aIo.js.map +1 -0
  15. package/dist/chunks/Collection-1sPoIFvQ.js +2 -0
  16. package/dist/chunks/{Collection-DaiL0uGl.js.map → Collection-1sPoIFvQ.js.map} +1 -1
  17. package/dist/chunks/{Collection-CxbNKOas.js → Collection-DSBRXpwK.js} +2 -2
  18. package/dist/chunks/{Collection-CxbNKOas.js.map → Collection-DSBRXpwK.js.map} +1 -1
  19. package/dist/chunks/{ContextMenu-ClwHEbbD.js → ContextMenu-BWy7WqF4.js} +2 -2
  20. package/dist/chunks/{ContextMenu-ClwHEbbD.js.map → ContextMenu-BWy7WqF4.js.map} +1 -1
  21. package/dist/chunks/ContextMenu-BvniQz-N.js +3 -0
  22. package/dist/chunks/{ContextMenu-sgvgSACY.js.map → ContextMenu-BvniQz-N.js.map} +1 -1
  23. package/dist/chunks/DataView--nUWtq6r.js +2 -0
  24. package/dist/chunks/{DataView-Dzo0jbs2.js.map → DataView--nUWtq6r.js.map} +1 -1
  25. package/dist/chunks/{DataView-1xh3GFeC.js → DataView-CK3Z0TJH.js} +2 -2
  26. package/dist/chunks/{DataView-1xh3GFeC.js.map → DataView-CK3Z0TJH.js.map} +1 -1
  27. package/dist/chunks/Dialog-BcgSR01Z.js +2 -0
  28. package/dist/chunks/{Dialog-DOGDalUq.js.map → Dialog-BcgSR01Z.js.map} +1 -1
  29. package/dist/chunks/{Dialog-CQlTDhZS.js → Dialog-DwCTFV6O.js} +2 -2
  30. package/dist/chunks/{Dialog-CQlTDhZS.js.map → Dialog-DwCTFV6O.js.map} +1 -1
  31. package/dist/chunks/FormPlugins-DvQ-G5J5.js +2 -0
  32. package/dist/chunks/{FormPlugins-DY6e88YT.js.map → FormPlugins-DvQ-G5J5.js.map} +1 -1
  33. package/dist/chunks/{FormView-DaKA4Sys.js → FormView-CRmEReTC.js} +3 -3
  34. package/dist/chunks/{FormView-DaKA4Sys.js.map → FormView-CRmEReTC.js.map} +1 -1
  35. package/dist/chunks/FormView-OLA7t-yv.js +3 -0
  36. package/dist/chunks/{FormView-Dz3mYasQ.js.map → FormView-OLA7t-yv.js.map} +1 -1
  37. package/dist/chunks/ListView-6JQ6tRXs.js +2 -0
  38. package/dist/chunks/{ListView-X5w5jf51.js.map → ListView-6JQ6tRXs.js.map} +1 -1
  39. package/dist/chunks/{ListView-CDzKIpd8.js → ListView-DVStKiMi.js} +2 -2
  40. package/dist/chunks/{ListView-CDzKIpd8.js.map → ListView-DVStKiMi.js.map} +1 -1
  41. package/dist/chunks/{MetricsCountryMapView-Dx2cw7ya.js → MetricsCountryMapView-CnAEbUw_.js} +2 -2
  42. package/dist/chunks/{MetricsCountryMapView-Dx2cw7ya.js.map → MetricsCountryMapView-CnAEbUw_.js.map} +1 -1
  43. package/dist/chunks/MetricsCountryMapView-J067qrrt.js +2 -0
  44. package/dist/chunks/{MetricsCountryMapView-B2xz6zUw.js.map → MetricsCountryMapView-J067qrrt.js.map} +1 -1
  45. package/dist/chunks/{MetricsMiniChartWidget-CBuso0OE.js → MetricsMiniChartWidget-BeD1slGs.js} +2 -2
  46. package/dist/chunks/{MetricsMiniChartWidget-CBuso0OE.js.map → MetricsMiniChartWidget-BeD1slGs.js.map} +1 -1
  47. package/dist/chunks/MetricsMiniChartWidget-x2gFjHOU.js +2 -0
  48. package/dist/chunks/{MetricsMiniChartWidget-DvKd7Qrk.js.map → MetricsMiniChartWidget-x2gFjHOU.js.map} +1 -1
  49. package/dist/chunks/PDFViewer-CsyKn-gh.js +2 -0
  50. package/dist/chunks/{PDFViewer-EJ9cOfPF.js.map → PDFViewer-CsyKn-gh.js.map} +1 -1
  51. package/dist/chunks/{PDFViewer-ofMGdSaj.js → PDFViewer-DSa4BZCm.js} +2 -2
  52. package/dist/chunks/{PDFViewer-ofMGdSaj.js.map → PDFViewer-DSa4BZCm.js.map} +1 -1
  53. package/dist/chunks/Rest-DHbszkuP.js +2 -0
  54. package/dist/chunks/Rest-DHbszkuP.js.map +1 -0
  55. package/dist/chunks/Rest-Ds9e8tN8.js +2 -0
  56. package/dist/chunks/Rest-Ds9e8tN8.js.map +1 -0
  57. package/dist/chunks/TokenManager-D6SjKgPZ.js +2 -0
  58. package/dist/chunks/{TokenManager-DoN9e6q6.js.map → TokenManager-D6SjKgPZ.js.map} +1 -1
  59. package/dist/chunks/{TokenManager-Gqvj7SDX.js → TokenManager-REbha1Le.js} +2 -2
  60. package/dist/chunks/{TokenManager-Gqvj7SDX.js.map → TokenManager-REbha1Le.js.map} +1 -1
  61. package/dist/chunks/WebApp-CULZpO_0.js +2 -0
  62. package/dist/chunks/{WebApp-6qvqmOts.js.map → WebApp-CULZpO_0.js.map} +1 -1
  63. package/dist/chunks/{WebApp-_dgpwtFw.js → WebApp-DovLtA60.js} +2 -2
  64. package/dist/chunks/{WebApp-_dgpwtFw.js.map → WebApp-DovLtA60.js.map} +1 -1
  65. package/dist/chunks/WebSocketClient-B-wc3mez.js +2 -0
  66. package/dist/chunks/{WebSocketClient-DG2olXpH.js.map → WebSocketClient-B-wc3mez.js.map} +1 -1
  67. package/dist/chunks/{WebSocketClient-MFkFlSue.js → WebSocketClient-BdZ9QYll.js} +2 -2
  68. package/dist/chunks/{WebSocketClient-MFkFlSue.js.map → WebSocketClient-BdZ9QYll.js.map} +1 -1
  69. package/dist/chunks/version-C3dnl1bg.js +2 -0
  70. package/dist/chunks/version-C3dnl1bg.js.map +1 -0
  71. package/dist/chunks/{version-BVADfTA5.js → version-ioN546cp.js} +2 -2
  72. package/dist/chunks/{version-BVADfTA5.js.map → version-ioN546cp.js.map} +1 -1
  73. package/dist/css/web-mojo.css +1 -1
  74. package/dist/docit.cjs.js +1 -1
  75. package/dist/docit.es.js +1 -957
  76. package/dist/docit.es.js.map +1 -1
  77. package/dist/index.cjs.js +1 -1
  78. package/dist/index.es.js +1 -3252
  79. package/dist/index.es.js.map +1 -1
  80. package/dist/lightbox.cjs.js +1 -1
  81. package/dist/lightbox.es.js +1 -3737
  82. package/dist/lightbox.es.js.map +1 -1
  83. package/dist/loader.umd.js +2 -2
  84. package/dist/map.cjs.js +1 -1
  85. package/dist/map.es.js +1 -1032
  86. package/dist/map.es.js.map +1 -1
  87. package/dist/mojo-auth.es.js +338 -0
  88. package/dist/mojo-auth.umd.js +1 -0
  89. package/dist/timeline.cjs.js +1 -1
  90. package/dist/timeline.es.js +1 -224
  91. package/dist/timeline.es.js.map +1 -1
  92. package/dist/web-mojo.lite.iife.js +14 -3
  93. package/dist/web-mojo.lite.iife.js.map +1 -1
  94. package/dist/web-mojo.lite.iife.min.js +6 -6
  95. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  96. package/package.json +2 -2
  97. package/dist/chunks/ChatView-9k6xBWXk.js +0 -7632
  98. package/dist/chunks/ChatView-9k6xBWXk.js.map +0 -1
  99. package/dist/chunks/ChatView-CdtuCDYm.js +0 -2
  100. package/dist/chunks/ChatView-CdtuCDYm.js.map +0 -1
  101. package/dist/chunks/Collection-DaiL0uGl.js +0 -1014
  102. package/dist/chunks/ContextMenu-sgvgSACY.js +0 -1535
  103. package/dist/chunks/DataView-Dzo0jbs2.js +0 -862
  104. package/dist/chunks/Dialog-DOGDalUq.js +0 -1579
  105. package/dist/chunks/FormPlugins-DY6e88YT.js +0 -124
  106. package/dist/chunks/FormView-Dz3mYasQ.js +0 -8636
  107. package/dist/chunks/ListView-X5w5jf51.js +0 -495
  108. package/dist/chunks/MetricsCountryMapView-B2xz6zUw.js +0 -1054
  109. package/dist/chunks/MetricsMiniChartWidget-DvKd7Qrk.js +0 -3283
  110. package/dist/chunks/PDFViewer-EJ9cOfPF.js +0 -946
  111. package/dist/chunks/Rest-CgSjfMaU.js +0 -2
  112. package/dist/chunks/Rest-CgSjfMaU.js.map +0 -1
  113. package/dist/chunks/Rest-W-sPfGh9.js +0 -4375
  114. package/dist/chunks/Rest-W-sPfGh9.js.map +0 -1
  115. package/dist/chunks/TokenManager-DoN9e6q6.js +0 -1423
  116. package/dist/chunks/WebApp-6qvqmOts.js +0 -1386
  117. package/dist/chunks/WebSocketClient-DG2olXpH.js +0 -209
  118. package/dist/chunks/version-OyPGnx30.js +0 -38
  119. package/dist/chunks/version-OyPGnx30.js.map +0 -1
package/dist/map.es.js CHANGED
@@ -1,1033 +1,2 @@
1
- import { M as MapView } from "./chunks/MetricsCountryMapView-B2xz6zUw.js";
2
- import { b, a } from "./chunks/MetricsCountryMapView-B2xz6zUw.js";
3
- import { r as rest, V as View } from "./chunks/Rest-W-sPfGh9.js";
4
- import Dialog from "./chunks/Dialog-DOGDalUq.js";
5
- import { F as FormPlugins } from "./chunks/FormPlugins-DY6e88YT.js";
6
- import { C, M } from "./chunks/Collection-DaiL0uGl.js";
7
- class LocationClient {
8
- /**
9
- * @param {Object} options
10
- * @param {string} [options.basePath='/api'] - API base path prefix (e.g., '/api')
11
- * @param {string|(() => string|null)} [options.authHeader] - Authorization header string or function returning one
12
- * @param {Object} [options.endpoints] - Override endpoint paths
13
- * @param {Function} [options.fetchImpl] - Custom fetch implementation, defaults to global fetch
14
- */
15
- constructor({
16
- basePath = "/api",
17
- endpoints = {}
18
- } = {}) {
19
- this.basePath = String(basePath || "");
20
- this.sessionToken = null;
21
- this.endpoints = {
22
- validate: "/location/address/validate",
23
- autocomplete: "/location/address/suggestions",
24
- details: "/location/address/place-details",
25
- geocode: "/location/address/geocode",
26
- reverse: "/location/address/reverse-geocode",
27
- timezone: "/location/timezone",
28
- ...endpoints
29
- };
30
- }
31
- /**
32
- * Optional helper: supply or change auth header at runtime
33
- * @param {string|(() => string|null)} header
34
- */
35
- setAuthHeader(header) {
36
- this._authHeader = header;
37
- }
38
- /**
39
- * Compute headers for a request.
40
- * @param {Object} [extra]
41
- * @returns {Record<string,string>}
42
- */
43
- headers(extra) {
44
- let auth = null;
45
- if (typeof this._authHeader === "function") {
46
- try {
47
- auth = this._authHeader();
48
- } catch {
49
- auth = null;
50
- }
51
- } else {
52
- auth = this._authHeader;
53
- }
54
- const h = { "Content-Type": "application/json", ...extra || {} };
55
- if (auth) h.Authorization = auth;
56
- return h;
57
- }
58
- /**
59
- * Perform JSON GET with query params
60
- * @param {string} path - relative path starting with '/'
61
- * @param {Record<string, any>} [params]
62
- */
63
- async jsonGet(path, params) {
64
- const resp = await rest.GET(this.fullPath(path), params || {});
65
- return resp && resp.data !== void 0 ? resp.data : resp;
66
- }
67
- /**
68
- * Perform JSON POST
69
- * @param {string} path - relative path starting with '/'
70
- * @param {any} body
71
- */
72
- async jsonPost(path, body) {
73
- const resp = await rest.POST(this.fullPath(path), body ?? {}, {}, {});
74
- return resp && resp.data !== void 0 ? resp.data : resp;
75
- }
76
- fullPath(path) {
77
- return `${this.basePath}${path}`;
78
- }
79
- async _safeJson(res) {
80
- try {
81
- return await res.json();
82
- } catch {
83
- return null;
84
- }
85
- }
86
- // ----------------------------
87
- // API Methods
88
- // ----------------------------
89
- /**
90
- * Address Validation
91
- * @param {Object} address
92
- * @param {string} address.address1
93
- * @param {string} [address.address2]
94
- * @param {string} address.city
95
- * @param {string} address.state
96
- * @param {string} address.postal_code
97
- * @param {string} [address.provider] - e.g., 'usps' (if API supports provider selection)
98
- * @returns {Promise<any>} API response
99
- */
100
- validateAddress(address) {
101
- return this.jsonPost(this.endpoints.validate, address);
102
- }
103
- /**
104
- * Address Autocomplete (GET)
105
- * Maintains a session token for repeated queries to optimize provider cost.
106
- * @param {string} query
107
- * @param {Object} [opts] - e.g., { country, state, ... }
108
- * @returns {Promise<any>} { success, session_token, data: [{ id, place_id, description, ... }], ... }
109
- */
110
- async autocomplete(query, opts = {}) {
111
- if (!query || String(query).trim().length === 0) {
112
- return { success: true, data: [], size: 0, count: 0 };
113
- }
114
- if (!this.sessionToken) {
115
- this.sessionToken = this._createSessionToken();
116
- }
117
- const params = { input: query, session_token: this.sessionToken, ...opts };
118
- const result = await this.jsonGet(this.endpoints.autocomplete, params);
119
- if (result && result.session_token) {
120
- this.sessionToken = result.session_token;
121
- }
122
- return result;
123
- }
124
- /**
125
- * Place Details (GET)
126
- * @param {Object} params
127
- * @param {string} [params.place_id]
128
- * @param {string} [params.id]
129
- * @returns {Promise<any>} { success, address: { formatted_address, latitude, longitude, ... } }
130
- */
131
- placeDetails({ place_id, session_token, id } = {}) {
132
- const q = {};
133
- const pid = place_id || id || null;
134
- if (pid) q.place_id = pid;
135
- if (session_token) q.session_token = session_token;
136
- return this.jsonGet(this.endpoints.details, q);
137
- }
138
- /**
139
- * Geocode (POST)
140
- * @param {string|Object} address - string or object { address1, city, state, postal_code }
141
- * @returns {Promise<any>} { success, latitude, longitude, formatted_address, place_id, address_components... }
142
- */
143
- geocode(address) {
144
- return this.jsonPost(this.endpoints.geocode, { address });
145
- }
146
- /**
147
- * Reverse Geocoding (GET)
148
- * @param {Object} coords
149
- * @param {number|string} coords.lat
150
- * @param {number|string} coords.lng
151
- * @returns {Promise<any>} { success, formatted_address, place_id, address_components... }
152
- */
153
- reverseGeocode({ lat, lng }) {
154
- return this.jsonGet(this.endpoints.reverse, { lat, lng });
155
- }
156
- /**
157
- * Timezone Lookup (GET)
158
- * @param {Object} coords
159
- * @param {number|string} coords.lat
160
- * @param {number|string} coords.lng
161
- * @returns {Promise<any>} { success, timezone_id, timezone_name, raw_offset, dst_offset, total_offset }
162
- */
163
- timezone({ lat, lng }) {
164
- return this.jsonGet(this.endpoints.timezone, { lat, lng });
165
- }
166
- /**
167
- * Reset session token (useful when user starts a new autocomplete flow).
168
- */
169
- resetSessionToken() {
170
- this.sessionToken = null;
171
- }
172
- /**
173
- * Basic parser for suggestion item to extract display info.
174
- * @param {Object} suggestion - item from autocomplete response
175
- */
176
- normalizeSuggestion(suggestion) {
177
- return {
178
- id: suggestion?.id || suggestion?.place_id || null,
179
- place_id: suggestion?.place_id || suggestion?.id || null,
180
- description: suggestion?.description || "",
181
- main_text: suggestion?.main_text || "",
182
- secondary_text: suggestion?.secondary_text || "",
183
- types: suggestion?.types || []
184
- };
185
- }
186
- _createSessionToken() {
187
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
188
- return crypto.randomUUID();
189
- }
190
- return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
191
- }
192
- }
193
- function useLocationAutocomplete(formView, {
194
- client,
195
- field = "address1",
196
- dropdownClass = "loc-suggest",
197
- minChars = 3,
198
- debounceMs = 200,
199
- mapping = {
200
- address1: "address1",
201
- city: "city",
202
- state_code: "state",
203
- postal_code: "postal_code",
204
- country_code: "country",
205
- latitude: "latitude",
206
- longitude: "longitude",
207
- formatted_address: "formatted_address",
208
- place_id: "place_id"
209
- },
210
- onSelect
211
- } = {}) {
212
- if (!formView || !formView.element) {
213
- console.warn("[useLocationAutocomplete] Missing formView or formView.element");
214
- return () => {
215
- };
216
- }
217
- if (!client || typeof client.autocomplete !== "function" || typeof client.placeDetails !== "function") {
218
- console.warn("[useLocationAutocomplete] Missing or invalid client. Provide an object with autocomplete() and placeDetails().");
219
- return () => {
220
- };
221
- }
222
- const inputEl = typeof field === "string" ? formView.element.querySelector(`input[name="${field}"], #${field}`) || null : field instanceof HTMLElement ? field : null;
223
- if (!inputEl) {
224
- return () => {
225
- };
226
- }
227
- const dd = document.createElement("div");
228
- dd.className = dropdownClass || "loc-suggest";
229
- dd.style.position = "absolute";
230
- dd.style.zIndex = "10000";
231
- dd.style.display = "none";
232
- dd.style.background = "#fff";
233
- dd.style.border = "1px solid #e5e7eb";
234
- dd.style.borderRadius = "8px";
235
- dd.style.boxShadow = "0 8px 24px rgba(0,0,0,.08)";
236
- dd.style.padding = "4px 0";
237
- dd.style.maxHeight = "280px";
238
- dd.style.overflowY = "auto";
239
- dd.style.minWidth = "240px";
240
- dd.setAttribute("role", "listbox");
241
- dd.setAttribute("aria-label", "Address suggestions");
242
- let open = false;
243
- let timer = null;
244
- let suppress = false;
245
- function placeDropdown() {
246
- if (!open) return;
247
- const r = inputEl.getBoundingClientRect();
248
- dd.style.minWidth = `${r.width}px`;
249
- dd.style.left = `${r.left + window.scrollX}px`;
250
- dd.style.top = `${r.bottom + window.scrollY + 4}px`;
251
- }
252
- function openDropdown() {
253
- if (!open) {
254
- open = true;
255
- dd.style.display = "block";
256
- document.body.appendChild(dd);
257
- placeDropdown();
258
- }
259
- }
260
- function closeDropdown() {
261
- open = false;
262
- dd.style.display = "none";
263
- dd.innerHTML = "";
264
- if (dd.parentNode) {
265
- dd.parentNode.removeChild(dd);
266
- }
267
- }
268
- function createRow(item, index) {
269
- const row = document.createElement("div");
270
- row.setAttribute("role", "option");
271
- row.setAttribute("tabindex", "-1");
272
- row.style.padding = "8px 12px";
273
- row.style.cursor = "pointer";
274
- row.style.display = "flex";
275
- row.style.flexDirection = "column";
276
- row.dataset.index = String(index);
277
- const main = document.createElement("div");
278
- main.style.fontWeight = "600";
279
- main.style.color = "#111827";
280
- main.textContent = item.main_text || item.description || "";
281
- const sub = document.createElement("div");
282
- sub.style.fontSize = "12px";
283
- sub.style.color = "#6b7280";
284
- sub.textContent = item.secondary_text || "";
285
- row.appendChild(main);
286
- if (sub.textContent) row.appendChild(sub);
287
- row.addEventListener("mouseenter", () => {
288
- row.style.background = "#f3f4f6";
289
- });
290
- row.addEventListener("mouseleave", () => {
291
- row.style.background = "transparent";
292
- });
293
- row.addEventListener("mousedown", (e) => {
294
- e.preventDefault();
295
- selectSuggestion(item);
296
- });
297
- return row;
298
- }
299
- async function renderSuggestions(list) {
300
- dd.innerHTML = "";
301
- if (!list || list.length === 0) {
302
- const empty = document.createElement("div");
303
- empty.style.padding = "8px 12px";
304
- empty.style.color = "#6b7280";
305
- empty.textContent = "No results";
306
- dd.appendChild(empty);
307
- return;
308
- }
309
- list.forEach((item, idx) => dd.appendChild(createRow(item, idx)));
310
- }
311
- async function selectSuggestion(item) {
312
- try {
313
- const id = item.place_id || item.id;
314
- let details = null;
315
- if (id) {
316
- const res = await client.placeDetails({ place_id: id, id, session_token: client.sessionToken });
317
- details = res?.address || null;
318
- }
319
- suppress = true;
320
- clearTimeout(timer);
321
- if (details?.formatted_address) {
322
- inputEl.value = details.formatted_address;
323
- inputEl.dispatchEvent(new Event("input", { bubbles: true }));
324
- } else if (item.description) {
325
- inputEl.value = item.description;
326
- inputEl.dispatchEvent(new Event("input", { bubbles: true }));
327
- }
328
- try {
329
- inputEl.blur();
330
- } catch {
331
- }
332
- closeDropdown();
333
- setTimeout(() => {
334
- suppress = false;
335
- }, debounceMs + 50);
336
- if (details && mapping && typeof mapping === "object") {
337
- Object.entries(mapping).forEach(([srcKey, formField]) => {
338
- if (!formField) return;
339
- const val = details[srcKey];
340
- if (val !== void 0 && val !== null) {
341
- try {
342
- if (typeof formView.setFieldValue === "function") {
343
- formView.setFieldValue(formField, String(val));
344
- }
345
- } catch (err) {
346
- }
347
- const targetEl = formView.element.querySelector(`input[name="${formField}"], #${formField}, textarea[name="${formField}"], select[name="${formField}"]`);
348
- if (targetEl) {
349
- targetEl.value = String(val);
350
- targetEl.dispatchEvent(new Event("input", { bubbles: true }));
351
- targetEl.dispatchEvent(new Event("change", { bubbles: true }));
352
- }
353
- }
354
- });
355
- }
356
- if (typeof onSelect === "function") {
357
- onSelect(details || null);
358
- }
359
- } catch (err) {
360
- console.warn("[useLocationAutocomplete] placeDetails error:", err);
361
- } finally {
362
- closeDropdown();
363
- }
364
- }
365
- async function handleInput() {
366
- const q = inputEl.value.trim();
367
- if (q.length < minChars) {
368
- closeDropdown();
369
- return;
370
- }
371
- try {
372
- const res = await client.autocomplete(q);
373
- const list = Array.isArray(res?.data) ? res.data : [];
374
- const items = list.map((x) => ({
375
- id: x.id,
376
- place_id: x.place_id,
377
- description: x.description,
378
- main_text: x.main_text,
379
- secondary_text: x.secondary_text,
380
- types: x.types
381
- }));
382
- openDropdown();
383
- placeDropdown();
384
- await renderSuggestions(items);
385
- } catch (err) {
386
- console.warn("[useLocationAutocomplete] autocomplete error:", err);
387
- closeDropdown();
388
- }
389
- }
390
- function onInput() {
391
- if (suppress) return;
392
- clearTimeout(timer);
393
- timer = setTimeout(handleInput, debounceMs);
394
- }
395
- function onFocus() {
396
- if (suppress) return;
397
- if (inputEl.value.trim().length >= minChars) {
398
- onInput();
399
- }
400
- }
401
- function onBlur() {
402
- setTimeout(() => {
403
- if (!dd.contains(document.activeElement)) {
404
- closeDropdown();
405
- }
406
- }, 120);
407
- }
408
- function onWindowMove() {
409
- if (open) placeDropdown();
410
- }
411
- function onDocumentClick(e) {
412
- if (!dd.contains(e.target) && e.target !== inputEl) {
413
- closeDropdown();
414
- }
415
- }
416
- inputEl.addEventListener("input", onInput);
417
- inputEl.addEventListener("focus", onFocus);
418
- inputEl.addEventListener("blur", onBlur);
419
- window.addEventListener("resize", onWindowMove);
420
- window.addEventListener("scroll", onWindowMove, true);
421
- document.addEventListener("click", onDocumentClick);
422
- return function dispose() {
423
- clearTimeout(timer);
424
- try {
425
- inputEl.removeEventListener("input", onInput);
426
- } catch (e) {
427
- }
428
- try {
429
- inputEl.removeEventListener("focus", onFocus);
430
- } catch (e) {
431
- }
432
- try {
433
- inputEl.removeEventListener("blur", onBlur);
434
- } catch (e) {
435
- }
436
- try {
437
- window.removeEventListener("resize", onWindowMove);
438
- } catch (e) {
439
- }
440
- try {
441
- window.removeEventListener("scroll", onWindowMove, true);
442
- } catch (e) {
443
- }
444
- try {
445
- document.removeEventListener("click", onDocumentClick);
446
- } catch (e) {
447
- }
448
- try {
449
- closeDropdown();
450
- } catch (e) {
451
- }
452
- };
453
- }
454
- class LocationDetailsView extends View {
455
- /**
456
- * @param {Object} options
457
- * @param {Object} [options.details] - Address details (formatted_address, latitude, longitude, place_id, etc.)
458
- * @param {number} [options.height=260] - Map height (px)
459
- * @param {string} [options.tileLayer='osm'] - Tile layer key for MapView
460
- */
461
- constructor({ details = {}, height = 260, tileLayer = "osm" } = {}) {
462
- super({
463
- className: "location-details-view"
464
- });
465
- this.details = details || {};
466
- this.height = Number.isFinite(height) ? height : 260;
467
- this.tileLayer = tileLayer || "osm";
468
- this.formatted_address = this.details.formatted_address || "";
469
- this.place_id = this.details.place_id || "";
470
- this.latitude = this._toNumber(this.details.latitude ?? this.details.lat ?? null);
471
- this.longitude = this._toNumber(this.details.longitude ?? this.details.lng ?? null);
472
- this.hasCoords = Number.isFinite(this.latitude) && Number.isFinite(this.longitude);
473
- this._mapView = null;
474
- this.template = `
475
- <div class="loc-details">
476
- {{#formatted_address}}
477
- <div class="mb-2 fw-semibold" style="word-break: break-word;">
478
- {{formatted_address}}
479
- </div>
480
- {{/formatted_address}}
481
-
482
- {{#place_id}}
483
- <div class="text-muted small mb-2">Place ID: {{place_id}}</div>
484
- {{/place_id}}
485
-
486
- {{^formatted_address}}
487
- <div class="text-muted small mb-2">No formatted address provided</div>
488
- {{/formatted_address}}
489
-
490
- {{#hasCoords}}
491
- <div data-container="map"></div>
492
- {{/hasCoords}}
493
-
494
- {{^hasCoords}}
495
- <div class="text-muted small" style="border: 1px dashed #e5e7eb; border-radius: 8px; padding: 12px;">
496
- No coordinates available for map preview
497
- </div>
498
- {{/hasCoords}}
499
- </div>
500
- `;
501
- }
502
- async onInit() {
503
- if (this.hasCoords) {
504
- const markers = [{
505
- lat: this.latitude,
506
- lng: this.longitude,
507
- popup: this.formatted_address || ""
508
- }];
509
- this._mapView = new MapView({
510
- markers,
511
- center: [this.latitude, this.longitude],
512
- zoom: 13,
513
- height: this.height,
514
- tileLayer: this.tileLayer,
515
- showLayerControl: true,
516
- containerId: "map"
517
- });
518
- this.addChild(this._mapView);
519
- }
520
- }
521
- _toNumber(v) {
522
- if (v === null || v === void 0 || v === "") return null;
523
- const n = Number(v);
524
- return Number.isFinite(n) ? n : null;
525
- }
526
- }
527
- async function showLocationDetailsDialog({
528
- client = new LocationClient({ basePath: "/api" }),
529
- details = null,
530
- place_id = null,
531
- id = null,
532
- title = "Location Details",
533
- height = 260,
534
- tileLayer = "osm"
535
- } = {}) {
536
- let resolved = details;
537
- if (!resolved && (place_id || id)) {
538
- try {
539
- const res = await client.placeDetails({ place_id, id, session_token: client.sessionToken });
540
- resolved = res?.address || null;
541
- } catch (err) {
542
- resolved = {
543
- formatted_address: "Unable to load place details",
544
- error: err?.message || "Unknown error"
545
- };
546
- }
547
- }
548
- const view = new LocationDetailsView({
549
- details: resolved || {},
550
- height,
551
- tileLayer
552
- });
553
- return Dialog.showDialog({
554
- title,
555
- body: view,
556
- size: "md",
557
- buttons: [{ text: "Close", class: "btn-secondary", dismiss: true, value: "close" }]
558
- });
559
- }
560
- class LocationPickerView extends View {
561
- constructor({
562
- client,
563
- minChars = 3,
564
- debounceMs = 200,
565
- placeholder = "Search address",
566
- height = 240,
567
- tileLayer = "osm"
568
- } = {}) {
569
- super({
570
- className: "location-picker-view",
571
- template: '<div class="location-picker-view-root"></div>'
572
- });
573
- this.client = client;
574
- this.minChars = minChars;
575
- this.debounceMs = debounceMs;
576
- this.placeholder = placeholder;
577
- this.height = height;
578
- this.tileLayer = tileLayer;
579
- this._selected = null;
580
- this._timer = null;
581
- this._suggestions = [];
582
- this._elements = {};
583
- this._previewView = null;
584
- }
585
- getSelected() {
586
- return this._selected;
587
- }
588
- async onAfterRender() {
589
- const root = document.createElement("div");
590
- root.style.display = "grid";
591
- root.style.gap = "10px";
592
- const input = document.createElement("input");
593
- input.type = "text";
594
- input.className = "form-control";
595
- input.placeholder = this.placeholder || "Search address";
596
- input.setAttribute("aria-label", "Address search");
597
- root.appendChild(input);
598
- const dd = document.createElement("div");
599
- dd.style.border = "1px solid #e5e7eb";
600
- dd.style.borderRadius = "8px";
601
- dd.style.background = "#fff";
602
- dd.style.boxShadow = "0 8px 24px rgba(0,0,0,.08)";
603
- dd.style.maxHeight = "260px";
604
- dd.style.overflowY = "auto";
605
- dd.style.display = "none";
606
- root.appendChild(dd);
607
- const previewHost = document.createElement("div");
608
- root.appendChild(previewHost);
609
- this.element.appendChild(root);
610
- this._elements = { root, input, dd, previewHost };
611
- input.addEventListener("input", () => {
612
- clearTimeout(this._timer);
613
- this._timer = setTimeout(() => this._handleInput(), this.debounceMs);
614
- });
615
- input.addEventListener("focus", () => {
616
- if ((input.value || "").trim().length >= this.minChars) {
617
- this._handleInput();
618
- }
619
- });
620
- await this._renderPreview(null);
621
- }
622
- async _handleInput() {
623
- const q = (this._elements.input.value || "").trim();
624
- if (q.length < this.minChars) {
625
- this._elements.dd.style.display = "none";
626
- this._elements.dd.innerHTML = "";
627
- return;
628
- }
629
- try {
630
- const res = await this.client.autocomplete(q);
631
- const items = Array.isArray(res?.data) ? res.data : [];
632
- this._suggestions = items.map((x) => ({
633
- id: x.id,
634
- place_id: x.place_id,
635
- description: x.description,
636
- main_text: x.main_text,
637
- secondary_text: x.secondary_text,
638
- types: x.types
639
- }));
640
- await this._renderSuggestions();
641
- } catch (err) {
642
- this._suggestions = [];
643
- await this._renderSuggestions();
644
- }
645
- }
646
- async _renderSuggestions() {
647
- const dd = this._elements.dd;
648
- dd.innerHTML = "";
649
- if (!this._suggestions.length) {
650
- const empty = document.createElement("div");
651
- empty.style.padding = "8px 12px";
652
- empty.style.color = "#6b7280";
653
- empty.textContent = "No results";
654
- dd.appendChild(empty);
655
- dd.style.display = "block";
656
- return;
657
- }
658
- this._suggestions.forEach((item, idx) => {
659
- const row = document.createElement("div");
660
- row.style.padding = "8px 12px";
661
- row.style.cursor = "pointer";
662
- row.style.display = "flex";
663
- row.style.flexDirection = "column";
664
- row.addEventListener("mouseenter", () => {
665
- row.style.background = "#f3f4f6";
666
- });
667
- row.addEventListener("mouseleave", () => {
668
- row.style.background = "transparent";
669
- });
670
- const main = document.createElement("div");
671
- main.style.fontWeight = "600";
672
- main.style.color = "#111827";
673
- main.textContent = item.main_text || item.description || "";
674
- const sub = document.createElement("div");
675
- sub.style.fontSize = "12px";
676
- sub.style.color = "#6b7280";
677
- sub.textContent = item.secondary_text || "";
678
- row.appendChild(main);
679
- if (sub.textContent) row.appendChild(sub);
680
- row.addEventListener("mousedown", (e) => {
681
- e.preventDefault();
682
- this._selectSuggestion(item);
683
- });
684
- dd.appendChild(row);
685
- });
686
- dd.style.display = "block";
687
- }
688
- async _selectSuggestion(item) {
689
- try {
690
- const id = item.place_id || item.id;
691
- if (!id) return;
692
- const res = await this.client.placeDetails({ place_id: id, id, session_token: this.client.sessionToken });
693
- const details = res?.address || null;
694
- if (details?.formatted_address) {
695
- this._elements.input.value = details.formatted_address;
696
- } else if (item.description) {
697
- this._elements.input.value = item.description;
698
- }
699
- this._selected = details || null;
700
- this._elements.dd.style.display = "none";
701
- this._elements.dd.innerHTML = "";
702
- await this._renderPreview(this._selected);
703
- } catch (err) {
704
- }
705
- }
706
- async _renderPreview(details) {
707
- this._elements.previewHost.innerHTML = "";
708
- if (!details) {
709
- const ph = document.createElement("div");
710
- ph.style.border = "1px dashed #e5e7eb";
711
- ph.style.borderRadius = "8px";
712
- ph.style.padding = "12px";
713
- ph.style.color = "#6b7280";
714
- ph.textContent = "No location selected";
715
- this._elements.previewHost.appendChild(ph);
716
- return;
717
- }
718
- try {
719
- this._previewView = new LocationDetailsView({
720
- details,
721
- height: this.height,
722
- tileLayer: this.tileLayer
723
- });
724
- await this._previewView.render(true, this._elements.previewHost);
725
- } catch {
726
- const fallback = document.createElement("div");
727
- fallback.style.border = "1px dashed #e5e7eb";
728
- fallback.style.borderRadius = "8px";
729
- fallback.style.padding = "12px";
730
- fallback.textContent = details?.formatted_address || "Selected location";
731
- this._elements.previewHost.appendChild(fallback);
732
- }
733
- }
734
- async onBeforeDestroy() {
735
- clearTimeout(this._timer);
736
- await super.onBeforeDestroy();
737
- }
738
- }
739
- async function showLocationPickerDialog({
740
- client = new LocationClient({ basePath: "/api" }),
741
- title = "Pick a Location",
742
- minChars = 3,
743
- debounceMs = 200,
744
- placeholder = "Search address",
745
- confirmText = "Select",
746
- height = 240,
747
- tileLayer = "osm"
748
- } = {}) {
749
- const view = new LocationPickerView({
750
- client,
751
- minChars,
752
- debounceMs,
753
- placeholder,
754
- height,
755
- tileLayer
756
- });
757
- const result = await Dialog.showDialog({
758
- title,
759
- body: view,
760
- size: "md",
761
- buttons: [
762
- { text: "Cancel", class: "btn-secondary", dismiss: true, value: "cancel" },
763
- { text: confirmText, class: "btn-primary", value: "ok" }
764
- ]
765
- });
766
- if (result === "ok") {
767
- return view.getSelected() || null;
768
- }
769
- return null;
770
- }
771
- class LocationFormPlugin {
772
- /**
773
- * @param {Object} options
774
- * @param {string} [options.basePath] - API base path prefix (e.g., '/api') used with core Rest
775
- * @param {Object} [options.mapping] - Mapping from API address keys -> form field names
776
- * @param {boolean} [options.registerFieldType=true] - Register custom field type (address)
777
- * @param {string} [options.fieldTypeName='address'] - Field type name to register
778
- * @param {string} [options.attributeSelector='data-location'] - Attribute to opt-in on any text input (e.g., data-location="address")
779
- * @param {number} [options.minChars=3] - Minimum characters before triggering autocomplete
780
- * @param {number} [options.debounceMs=200] - Typing debounce for autocomplete
781
- */
782
- constructor({
783
- basePath = "/api",
784
- mapping,
785
- registerFieldType = true,
786
- fieldTypeName = "address",
787
- attributeSelector = "data-location",
788
- minChars = 3,
789
- debounceMs = 200,
790
- // Browser autofill/autocomplete suppression (important for suggestion inputs)
791
- // Chrome often ignores autocomplete="off" for address-like fields, so we default
792
- // to a more reliable value.
793
- suppressBrowserAutocomplete = true,
794
- autocompleteValue = "new-password"
795
- } = {}) {
796
- this.id = "location";
797
- this.client = new LocationClient({ basePath });
798
- this.mapping = mapping || {
799
- address1: "address1",
800
- city: "city",
801
- state_code: "state",
802
- postal_code: "postal_code",
803
- country_code: "country",
804
- latitude: "latitude",
805
- longitude: "longitude",
806
- formatted_address: "formatted_address",
807
- place_id: "place_id"
808
- };
809
- this.fieldTypeName = fieldTypeName;
810
- this.attributeSelector = attributeSelector;
811
- this.minChars = minChars;
812
- this.debounceMs = debounceMs;
813
- this.suppressBrowserAutocomplete = suppressBrowserAutocomplete !== false;
814
- this.autocompleteValue = autocompleteValue || "new-password";
815
- if (registerFieldType) {
816
- this.fieldTypes = {
817
- [this.fieldTypeName]: (builder, field) => this.renderAddressField(builder, field)
818
- };
819
- }
820
- }
821
- /**
822
- * Custom renderer for the "address" field type.
823
- * Leverages existing FormBuilder input rendering for consistency (text input).
824
- */
825
- renderAddressField(builder, field) {
826
- const suppressAttrs = this.suppressBrowserAutocomplete ? `autocomplete="${this.autocompleteValue}" autocapitalize="off" autocorrect="off" spellcheck="false" inputmode="search"` : "";
827
- const f = {
828
- ...field,
829
- type: "text",
830
- placeholder: field.placeholder || "Start typing an address",
831
- attrs: this.mergeAttrs(
832
- field.attrs,
833
- `${this.attributeSelector}="address" ${suppressAttrs} aria-autocomplete="list" role="combobox"`
834
- )
835
- };
836
- if (typeof builder.renderTextField === "function") {
837
- return builder.renderTextField(f);
838
- }
839
- if (typeof builder.renderInputField === "function") {
840
- return builder.renderInputField(f, "text");
841
- }
842
- const id = builder.getFieldId?.(f.name) || `field_${f.name}`;
843
- return `
844
- <div class="mojo-form-control">
845
- ${f.label ? `<label for="${id}" class="${builder.options?.labelClass || "form-label"}">${f.label}</label>` : ""}
846
- <input type="text" id="${id}" name="${f.name}" class="${builder.options?.inputClass || "form-control"}"
847
- placeholder="${f.placeholder || ""}" ${this.attributeSelector}="address" ${suppressAttrs} />
848
- </div>
849
- `;
850
- }
851
- /**
852
- * Helper to merge existing attrs with an additional attribute string
853
- */
854
- mergeAttrs(existing, add) {
855
- const base = (existing || "").trim();
856
- const extra = (add || "").trim();
857
- if (!base) return extra;
858
- if (!extra) return base;
859
- return `${base} ${extra}`;
860
- }
861
- /**
862
- * Hook: called when FormView is initialized
863
- * You can read application config here if needed.
864
- */
865
- onFormViewInit(_formView) {
866
- }
867
- /**
868
- * Hook: called after FormView finished rendering and initializing components
869
- * This is a good spot to opt-in based on attributes (e.g., data-location="address") on any text input.
870
- */
871
- onAfterRender(formView) {
872
- if (!formView?.element) return;
873
- try {
874
- const selector = `input[${this.attributeSelector}="address"]`;
875
- const inputs = formView.element.querySelectorAll(selector);
876
- inputs.forEach((inputEl) => {
877
- if (inputEl.dataset && inputEl.dataset._locationBound === "1") return;
878
- if (this.suppressBrowserAutocomplete) {
879
- try {
880
- inputEl.setAttribute("autocomplete", this.autocompleteValue);
881
- inputEl.setAttribute("autocapitalize", "off");
882
- inputEl.setAttribute("autocorrect", "off");
883
- inputEl.setAttribute("spellcheck", "false");
884
- inputEl.setAttribute("inputmode", "search");
885
- } catch (e) {
886
- }
887
- }
888
- let dispose;
889
- const rebind = () => {
890
- dispose = useLocationAutocomplete(formView, {
891
- client: this.client,
892
- field: inputEl,
893
- mapping: this.mapping,
894
- minChars: this.minChars,
895
- debounceMs: this.debounceMs,
896
- onSelect: (_details) => {
897
- try {
898
- inputEl.blur();
899
- } catch (e) {
900
- }
901
- try {
902
- dispose && dispose();
903
- } catch (e) {
904
- }
905
- setTimeout(() => {
906
- rebind();
907
- }, this.debounceMs + 50);
908
- }
909
- });
910
- inputEl.dataset._locationBound = "1";
911
- this._trackDisposer(formView, dispose);
912
- };
913
- rebind();
914
- });
915
- } catch (err) {
916
- }
917
- }
918
- /**
919
- * Hook: called for each field element with its config after FormView initialization
920
- * If the field is our "address" type (or opt-in via attribute), attach autocomplete.
921
- */
922
- onFieldInit(formView, fieldEl, fieldConfig) {
923
- try {
924
- const isAddressType = fieldConfig?.type === this.fieldTypeName;
925
- const hasAttr = fieldEl?.getAttribute?.(this.attributeSelector) === "address";
926
- if (isAddressType || hasAttr) {
927
- if (fieldEl.dataset && fieldEl.dataset._locationBound === "1") return;
928
- if (this.suppressBrowserAutocomplete) {
929
- try {
930
- fieldEl.setAttribute("autocomplete", this.autocompleteValue);
931
- fieldEl.setAttribute("autocapitalize", "off");
932
- fieldEl.setAttribute("autocorrect", "off");
933
- fieldEl.setAttribute("spellcheck", "false");
934
- fieldEl.setAttribute("inputmode", "search");
935
- } catch (e) {
936
- }
937
- }
938
- let dispose;
939
- const rebind = () => {
940
- dispose = useLocationAutocomplete(formView, {
941
- client: this.client,
942
- field: fieldEl,
943
- mapping: this.mapping,
944
- minChars: this.minChars,
945
- debounceMs: this.debounceMs,
946
- onSelect: (_details) => {
947
- try {
948
- fieldEl.blur();
949
- } catch (e) {
950
- }
951
- try {
952
- dispose && dispose();
953
- } catch (e) {
954
- }
955
- setTimeout(() => {
956
- rebind();
957
- }, this.debounceMs + 50);
958
- }
959
- });
960
- fieldEl.dataset._locationBound = "1";
961
- this._trackDisposer(formView, dispose);
962
- };
963
- rebind();
964
- }
965
- } catch (err) {
966
- }
967
- }
968
- /**
969
- * Hook: field change notification (no-op for now)
970
- */
971
- onFieldChange(_formView, _name, _value) {
972
- }
973
- /**
974
- * Track disposers for cleanup. If the FormView provides an event API, attach to destroy/before-destroy.
975
- */
976
- _trackDisposer(formView, dispose) {
977
- if (!dispose || typeof dispose !== "function") return;
978
- if (!formView) return;
979
- if (!formView._locationDisposers) {
980
- Object.defineProperty(formView, "_locationDisposers", {
981
- configurable: true,
982
- enumerable: false,
983
- writable: true,
984
- value: []
985
- });
986
- }
987
- formView._locationDisposers.push(dispose);
988
- const tryBind = (eventName) => {
989
- if (typeof formView.on === "function") {
990
- try {
991
- formView.on(eventName, () => {
992
- try {
993
- formView._locationDisposers?.forEach((fn) => {
994
- try {
995
- fn();
996
- } catch {
997
- }
998
- });
999
- } finally {
1000
- formView._locationDisposers = [];
1001
- }
1002
- });
1003
- return true;
1004
- } catch {
1005
- }
1006
- }
1007
- return false;
1008
- };
1009
- if (!tryBind("before:destroy")) {
1010
- tryBind("destroy");
1011
- }
1012
- }
1013
- }
1014
- function registerLocationPlugin(options = {}) {
1015
- const plugin = new LocationFormPlugin(options);
1016
- return FormPlugins.register(plugin);
1017
- }
1018
- export {
1019
- C as Collection,
1020
- LocationClient,
1021
- LocationDetailsView,
1022
- LocationFormPlugin,
1023
- b as MapLibreView,
1024
- MapView,
1025
- a as MetricsCountryMapView,
1026
- M as Model,
1027
- View,
1028
- registerLocationPlugin,
1029
- showLocationDetailsDialog,
1030
- showLocationPickerDialog,
1031
- useLocationAutocomplete
1032
- };
1
+ import{M as e}from"./chunks/MetricsCountryMapView-J067qrrt.js";import{b as t,a as s}from"./chunks/MetricsCountryMapView-J067qrrt.js";import{r as i,V as n}from"./chunks/Rest-DHbszkuP.js";import o from"./chunks/Dialog-BcgSR01Z.js";import{F as a}from"./chunks/FormPlugins-DvQ-G5J5.js";import{C as r,M as l}from"./chunks/Collection-1sPoIFvQ.js";class LocationClient{constructor({basePath:e="/api",endpoints:t={}}={}){this.basePath=String(e||""),this.sessionToken=null,this.endpoints={validate:"/location/address/validate",autocomplete:"/location/address/suggestions",details:"/location/address/place-details",geocode:"/location/address/geocode",reverse:"/location/address/reverse-geocode",timezone:"/location/timezone",...t}}setAuthHeader(e){this._authHeader=e}headers(e){let t=null;if("function"==typeof this._authHeader)try{t=this._authHeader()}catch{t=null}else t=this._authHeader;const s={"Content-Type":"application/json",...e||{}};return t&&(s.Authorization=t),s}async jsonGet(e,t){const s=await i.GET(this.fullPath(e),t||{});return s&&void 0!==s.data?s.data:s}async jsonPost(e,t){const s=await i.POST(this.fullPath(e),t??{},{},{});return s&&void 0!==s.data?s.data:s}fullPath(e){return`${this.basePath}${e}`}async _safeJson(e){try{return await e.json()}catch{return null}}validateAddress(e){return this.jsonPost(this.endpoints.validate,e)}async autocomplete(e,t={}){if(!e||0===String(e).trim().length)return{success:!0,data:[],size:0,count:0};this.sessionToken||(this.sessionToken=this._createSessionToken());const s={input:e,session_token:this.sessionToken,...t},i=await this.jsonGet(this.endpoints.autocomplete,s);return i&&i.session_token&&(this.sessionToken=i.session_token),i}placeDetails({place_id:e,session_token:t,id:s}={}){const i={},n=e||s||null;return n&&(i.place_id=n),t&&(i.session_token=t),this.jsonGet(this.endpoints.details,i)}geocode(e){return this.jsonPost(this.endpoints.geocode,{address:e})}reverseGeocode({lat:e,lng:t}){return this.jsonGet(this.endpoints.reverse,{lat:e,lng:t})}timezone({lat:e,lng:t}){return this.jsonGet(this.endpoints.timezone,{lat:e,lng:t})}resetSessionToken(){this.sessionToken=null}normalizeSuggestion(e){return{id:e?.id||e?.place_id||null,place_id:e?.place_id||e?.id||null,description:e?.description||"",main_text:e?.main_text||"",secondary_text:e?.secondary_text||"",types:e?.types||[]}}_createSessionToken(){return"undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}}function d(e,{client:t,field:s="address1",dropdownClass:i="loc-suggest",minChars:n=3,debounceMs:o=200,mapping:a={address1:"address1",city:"city",state_code:"state",postal_code:"postal_code",country_code:"country",latitude:"latitude",longitude:"longitude",formatted_address:"formatted_address",place_id:"place_id"},onSelect:r}={}){if(!e||!e.element)return console.warn("[useLocationAutocomplete] Missing formView or formView.element"),()=>{};if(!t||"function"!=typeof t.autocomplete||"function"!=typeof t.placeDetails)return console.warn("[useLocationAutocomplete] Missing or invalid client. Provide an object with autocomplete() and placeDetails()."),()=>{};const l="string"==typeof s?e.element.querySelector(`input[name="${s}"], #${s}`)||null:s instanceof HTMLElement?s:null;if(!l)return()=>{};const d=document.createElement("div");d.className=i||"loc-suggest",d.style.position="absolute",d.style.zIndex="10000",d.style.display="none",d.style.background="#fff",d.style.border="1px solid #e5e7eb",d.style.borderRadius="8px",d.style.boxShadow="0 8px 24px rgba(0,0,0,.08)",d.style.padding="4px 0",d.style.maxHeight="280px",d.style.overflowY="auto",d.style.minWidth="240px",d.setAttribute("role","listbox"),d.setAttribute("aria-label","Address suggestions");let c=!1,u=null,p=!1;function h(){if(!c)return;const e=l.getBoundingClientRect();d.style.minWidth=`${e.width}px`,d.style.left=`${e.left+window.scrollX}px`,d.style.top=`${e.bottom+window.scrollY+4}px`}function m(){c=!1,d.style.display="none",d.innerHTML="",d.parentNode&&d.parentNode.removeChild(d)}async function y(){const s=l.value.trim();if(s.length<n)m();else try{const i=await t.autocomplete(s),n=(Array.isArray(i?.data)?i.data:[]).map(e=>({id:e.id,place_id:e.place_id,description:e.description,main_text:e.main_text,secondary_text:e.secondary_text,types:e.types}));c||(c=!0,d.style.display="block",document.body.appendChild(d),h()),h(),await async function(s){if(d.innerHTML="",!s||0===s.length){const e=document.createElement("div");return e.style.padding="8px 12px",e.style.color="#6b7280",e.textContent="No results",void d.appendChild(e)}s.forEach((s,i)=>d.appendChild(function(s,i){const n=document.createElement("div");n.setAttribute("role","option"),n.setAttribute("tabindex","-1"),n.style.padding="8px 12px",n.style.cursor="pointer",n.style.display="flex",n.style.flexDirection="column",n.dataset.index=String(i);const d=document.createElement("div");d.style.fontWeight="600",d.style.color="#111827",d.textContent=s.main_text||s.description||"";const c=document.createElement("div");return c.style.fontSize="12px",c.style.color="#6b7280",c.textContent=s.secondary_text||"",n.appendChild(d),c.textContent&&n.appendChild(c),n.addEventListener("mouseenter",()=>{n.style.background="#f3f4f6"}),n.addEventListener("mouseleave",()=>{n.style.background="transparent"}),n.addEventListener("mousedown",i=>{i.preventDefault(),async function(s){try{const i=s.place_id||s.id;let n=null;if(i){const e=await t.placeDetails({place_id:i,id:i,session_token:t.sessionToken});n=e?.address||null}p=!0,clearTimeout(u),n?.formatted_address?(l.value=n.formatted_address,l.dispatchEvent(new Event("input",{bubbles:!0}))):s.description&&(l.value=s.description,l.dispatchEvent(new Event("input",{bubbles:!0})));try{l.blur()}catch{}m(),setTimeout(()=>{p=!1},o+50),n&&a&&"object"==typeof a&&Object.entries(a).forEach(([t,s])=>{if(!s)return;const i=n[t];if(null!=i){try{"function"==typeof e.setFieldValue&&e.setFieldValue(s,String(i))}catch(o){}const t=e.element.querySelector(`input[name="${s}"], #${s}, textarea[name="${s}"], select[name="${s}"]`);t&&(t.value=String(i),t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0})))}}),"function"==typeof r&&r(n||null)}catch(i){console.warn("[useLocationAutocomplete] placeDetails error:",i)}finally{m()}}(s)}),n}(s,i)))}(n)}catch(i){console.warn("[useLocationAutocomplete] autocomplete error:",i),m()}}function f(){p||(clearTimeout(u),u=setTimeout(y,o))}function _(){p||l.value.trim().length>=n&&f()}function b(){setTimeout(()=>{d.contains(document.activeElement)||m()},120)}function g(){c&&h()}function v(e){d.contains(e.target)||e.target===l||m()}return l.addEventListener("input",f),l.addEventListener("focus",_),l.addEventListener("blur",b),window.addEventListener("resize",g),window.addEventListener("scroll",g,!0),document.addEventListener("click",v),function(){clearTimeout(u);try{l.removeEventListener("input",f)}catch(e){}try{l.removeEventListener("focus",_)}catch(e){}try{l.removeEventListener("blur",b)}catch(e){}try{window.removeEventListener("resize",g)}catch(e){}try{window.removeEventListener("scroll",g,!0)}catch(e){}try{document.removeEventListener("click",v)}catch(e){}try{m()}catch(e){}}}class LocationDetailsView extends n{constructor({details:e={},height:t=260,tileLayer:s="osm"}={}){super({className:"location-details-view"}),this.details=e||{},this.height=Number.isFinite(t)?t:260,this.tileLayer=s||"osm",this.formatted_address=this.details.formatted_address||"",this.place_id=this.details.place_id||"",this.latitude=this._toNumber(this.details.latitude??this.details.lat??null),this.longitude=this._toNumber(this.details.longitude??this.details.lng??null),this.hasCoords=Number.isFinite(this.latitude)&&Number.isFinite(this.longitude),this._mapView=null,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 '}async onInit(){if(this.hasCoords){const t=[{lat:this.latitude,lng:this.longitude,popup:this.formatted_address||""}];this._mapView=new e({markers:t,center:[this.latitude,this.longitude],zoom:13,height:this.height,tileLayer:this.tileLayer,showLayerControl:!0,containerId:"map"}),this.addChild(this._mapView)}}_toNumber(e){if(null==e||""===e)return null;const t=Number(e);return Number.isFinite(t)?t:null}}async function c({client:e=new LocationClient({basePath:"/api"}),details:t=null,place_id:s=null,id:i=null,title:n="Location Details",height:a=260,tileLayer:r="osm"}={}){let l=t;if(!l&&(s||i))try{const t=await e.placeDetails({place_id:s,id:i,session_token:e.sessionToken});l=t?.address||null}catch(c){l={formatted_address:"Unable to load place details",error:c?.message||"Unknown error"}}const d=new LocationDetailsView({details:l||{},height:a,tileLayer:r});return o.showDialog({title:n,body:d,size:"md",buttons:[{text:"Close",class:"btn-secondary",dismiss:!0,value:"close"}]})}class LocationPickerView extends n{constructor({client:e,minChars:t=3,debounceMs:s=200,placeholder:i="Search address",height:n=240,tileLayer:o="osm"}={}){super({className:"location-picker-view",template:'<div class="location-picker-view-root"></div>'}),this.client=e,this.minChars=t,this.debounceMs=s,this.placeholder=i,this.height=n,this.tileLayer=o,this._selected=null,this._timer=null,this._suggestions=[],this._elements={},this._previewView=null}getSelected(){return this._selected}async onAfterRender(){const e=document.createElement("div");e.style.display="grid",e.style.gap="10px";const t=document.createElement("input");t.type="text",t.className="form-control",t.placeholder=this.placeholder||"Search address",t.setAttribute("aria-label","Address search"),e.appendChild(t);const s=document.createElement("div");s.style.border="1px solid #e5e7eb",s.style.borderRadius="8px",s.style.background="#fff",s.style.boxShadow="0 8px 24px rgba(0,0,0,.08)",s.style.maxHeight="260px",s.style.overflowY="auto",s.style.display="none",e.appendChild(s);const i=document.createElement("div");e.appendChild(i),this.element.appendChild(e),this._elements={root:e,input:t,dd:s,previewHost:i},t.addEventListener("input",()=>{clearTimeout(this._timer),this._timer=setTimeout(()=>this._handleInput(),this.debounceMs)}),t.addEventListener("focus",()=>{(t.value||"").trim().length>=this.minChars&&this._handleInput()}),await this._renderPreview(null)}async _handleInput(){const e=(this._elements.input.value||"").trim();if(e.length<this.minChars)return this._elements.dd.style.display="none",void(this._elements.dd.innerHTML="");try{const t=await this.client.autocomplete(e),s=Array.isArray(t?.data)?t.data:[];this._suggestions=s.map(e=>({id:e.id,place_id:e.place_id,description:e.description,main_text:e.main_text,secondary_text:e.secondary_text,types:e.types})),await this._renderSuggestions()}catch(t){this._suggestions=[],await this._renderSuggestions()}}async _renderSuggestions(){const e=this._elements.dd;if(e.innerHTML="",!this._suggestions.length){const t=document.createElement("div");return t.style.padding="8px 12px",t.style.color="#6b7280",t.textContent="No results",e.appendChild(t),void(e.style.display="block")}this._suggestions.forEach((t,s)=>{const i=document.createElement("div");i.style.padding="8px 12px",i.style.cursor="pointer",i.style.display="flex",i.style.flexDirection="column",i.addEventListener("mouseenter",()=>{i.style.background="#f3f4f6"}),i.addEventListener("mouseleave",()=>{i.style.background="transparent"});const n=document.createElement("div");n.style.fontWeight="600",n.style.color="#111827",n.textContent=t.main_text||t.description||"";const o=document.createElement("div");o.style.fontSize="12px",o.style.color="#6b7280",o.textContent=t.secondary_text||"",i.appendChild(n),o.textContent&&i.appendChild(o),i.addEventListener("mousedown",e=>{e.preventDefault(),this._selectSuggestion(t)}),e.appendChild(i)}),e.style.display="block"}async _selectSuggestion(e){try{const t=e.place_id||e.id;if(!t)return;const s=await this.client.placeDetails({place_id:t,id:t,session_token:this.client.sessionToken}),i=s?.address||null;i?.formatted_address?this._elements.input.value=i.formatted_address:e.description&&(this._elements.input.value=e.description),this._selected=i||null,this._elements.dd.style.display="none",this._elements.dd.innerHTML="",await this._renderPreview(this._selected)}catch(t){}}async _renderPreview(e){if(this._elements.previewHost.innerHTML="",!e){const e=document.createElement("div");return e.style.border="1px dashed #e5e7eb",e.style.borderRadius="8px",e.style.padding="12px",e.style.color="#6b7280",e.textContent="No location selected",void this._elements.previewHost.appendChild(e)}try{this._previewView=new LocationDetailsView({details:e,height:this.height,tileLayer:this.tileLayer}),await this._previewView.render(!0,this._elements.previewHost)}catch{const t=document.createElement("div");t.style.border="1px dashed #e5e7eb",t.style.borderRadius="8px",t.style.padding="12px",t.textContent=e?.formatted_address||"Selected location",this._elements.previewHost.appendChild(t)}}async onBeforeDestroy(){clearTimeout(this._timer),await super.onBeforeDestroy()}}async function u({client:e=new LocationClient({basePath:"/api"}),title:t="Pick a Location",minChars:s=3,debounceMs:i=200,placeholder:n="Search address",confirmText:a="Select",height:r=240,tileLayer:l="osm"}={}){const d=new LocationPickerView({client:e,minChars:s,debounceMs:i,placeholder:n,height:r,tileLayer:l});return"ok"===await o.showDialog({title:t,body:d,size:"md",buttons:[{text:"Cancel",class:"btn-secondary",dismiss:!0,value:"cancel"},{text:a,class:"btn-primary",value:"ok"}]})&&d.getSelected()||null}class LocationFormPlugin{constructor({basePath:e="/api",mapping:t,registerFieldType:s=!0,fieldTypeName:i="address",attributeSelector:n="data-location",minChars:o=3,debounceMs:a=200,suppressBrowserAutocomplete:r=!0,autocompleteValue:l="new-password"}={}){this.id="location",this.client=new LocationClient({basePath:e}),this.mapping=t||{address1:"address1",city:"city",state_code:"state",postal_code:"postal_code",country_code:"country",latitude:"latitude",longitude:"longitude",formatted_address:"formatted_address",place_id:"place_id"},this.fieldTypeName=i,this.attributeSelector=n,this.minChars=o,this.debounceMs=a,this.suppressBrowserAutocomplete=!1!==r,this.autocompleteValue=l||"new-password",s&&(this.fieldTypes={[this.fieldTypeName]:(e,t)=>this.renderAddressField(e,t)})}renderAddressField(e,t){const s=this.suppressBrowserAutocomplete?`autocomplete="${this.autocompleteValue}" autocapitalize="off" autocorrect="off" spellcheck="false" inputmode="search"`:"",i={...t,type:"text",placeholder:t.placeholder||"Start typing an address",attrs:this.mergeAttrs(t.attrs,`${this.attributeSelector}="address" ${s} aria-autocomplete="list" role="combobox"`)};if("function"==typeof e.renderTextField)return e.renderTextField(i);if("function"==typeof e.renderInputField)return e.renderInputField(i,"text");const n=e.getFieldId?.(i.name)||`field_${i.name}`;return`\n <div class="mojo-form-control">\n ${i.label?`<label for="${n}" class="${e.options?.labelClass||"form-label"}">${i.label}</label>`:""}\n <input type="text" id="${n}" name="${i.name}" class="${e.options?.inputClass||"form-control"}"\n placeholder="${i.placeholder||""}" ${this.attributeSelector}="address" ${s} />\n </div>\n `}mergeAttrs(e,t){const s=(e||"").trim(),i=(t||"").trim();return s?i?`${s} ${i}`:s:i}onFormViewInit(e){}onAfterRender(e){if(e?.element)try{const t=`input[${this.attributeSelector}="address"]`;e.element.querySelectorAll(t).forEach(t=>{if(t.dataset&&"1"===t.dataset._locationBound)return;if(this.suppressBrowserAutocomplete)try{t.setAttribute("autocomplete",this.autocompleteValue),t.setAttribute("autocapitalize","off"),t.setAttribute("autocorrect","off"),t.setAttribute("spellcheck","false"),t.setAttribute("inputmode","search")}catch(n){}let s;const i=()=>{s=d(e,{client:this.client,field:t,mapping:this.mapping,minChars:this.minChars,debounceMs:this.debounceMs,onSelect:e=>{try{t.blur()}catch(n){}try{s&&s()}catch(n){}setTimeout(()=>{i()},this.debounceMs+50)}}),t.dataset._locationBound="1",this._trackDisposer(e,s)};i()})}catch(t){}}onFieldInit(e,t,s){try{const n=s?.type===this.fieldTypeName,o="address"===t?.getAttribute?.(this.attributeSelector);if(n||o){if(t.dataset&&"1"===t.dataset._locationBound)return;if(this.suppressBrowserAutocomplete)try{t.setAttribute("autocomplete",this.autocompleteValue),t.setAttribute("autocapitalize","off"),t.setAttribute("autocorrect","off"),t.setAttribute("spellcheck","false"),t.setAttribute("inputmode","search")}catch(i){}let s;const n=()=>{s=d(e,{client:this.client,field:t,mapping:this.mapping,minChars:this.minChars,debounceMs:this.debounceMs,onSelect:e=>{try{t.blur()}catch(i){}try{s&&s()}catch(i){}setTimeout(()=>{n()},this.debounceMs+50)}}),t.dataset._locationBound="1",this._trackDisposer(e,s)};n()}}catch(n){}}onFieldChange(e,t,s){}_trackDisposer(e,t){if(!t||"function"!=typeof t)return;if(!e)return;e._locationDisposers||Object.defineProperty(e,"_locationDisposers",{configurable:!0,enumerable:!1,writable:!0,value:[]}),e._locationDisposers.push(t);const s=t=>{if("function"==typeof e.on)try{return e.on(t,()=>{try{e._locationDisposers?.forEach(e=>{try{e()}catch{}})}finally{e._locationDisposers=[]}}),!0}catch{}return!1};s("before:destroy")||s("destroy")}}function p(e={}){const t=new LocationFormPlugin(e);return a.register(t)}export{r as Collection,LocationClient,LocationDetailsView,LocationFormPlugin,t as MapLibreView,e as MapView,s as MetricsCountryMapView,l as Model,n as View,p as registerLocationPlugin,c as showLocationDetailsDialog,u as showLocationPickerDialog,d as useLocationAutocomplete};
1033
2
  //# sourceMappingURL=map.es.js.map