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.
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +1 -10105
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -588
- package/dist/auth.es.js.map +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -571
- package/dist/charts.es.js.map +1 -1
- package/dist/chunks/ChatView-D4A9rIX3.js +2 -0
- package/dist/chunks/ChatView-D4A9rIX3.js.map +1 -0
- package/dist/chunks/ChatView-nxaq8aIo.js +2 -0
- package/dist/chunks/ChatView-nxaq8aIo.js.map +1 -0
- package/dist/chunks/Collection-1sPoIFvQ.js +2 -0
- package/dist/chunks/{Collection-DaiL0uGl.js.map → Collection-1sPoIFvQ.js.map} +1 -1
- package/dist/chunks/{Collection-CxbNKOas.js → Collection-DSBRXpwK.js} +2 -2
- package/dist/chunks/{Collection-CxbNKOas.js.map → Collection-DSBRXpwK.js.map} +1 -1
- package/dist/chunks/{ContextMenu-ClwHEbbD.js → ContextMenu-BWy7WqF4.js} +2 -2
- package/dist/chunks/{ContextMenu-ClwHEbbD.js.map → ContextMenu-BWy7WqF4.js.map} +1 -1
- package/dist/chunks/ContextMenu-BvniQz-N.js +3 -0
- package/dist/chunks/{ContextMenu-sgvgSACY.js.map → ContextMenu-BvniQz-N.js.map} +1 -1
- package/dist/chunks/DataView--nUWtq6r.js +2 -0
- package/dist/chunks/{DataView-Dzo0jbs2.js.map → DataView--nUWtq6r.js.map} +1 -1
- package/dist/chunks/{DataView-1xh3GFeC.js → DataView-CK3Z0TJH.js} +2 -2
- package/dist/chunks/{DataView-1xh3GFeC.js.map → DataView-CK3Z0TJH.js.map} +1 -1
- package/dist/chunks/Dialog-BcgSR01Z.js +2 -0
- package/dist/chunks/{Dialog-DOGDalUq.js.map → Dialog-BcgSR01Z.js.map} +1 -1
- package/dist/chunks/{Dialog-CQlTDhZS.js → Dialog-DwCTFV6O.js} +2 -2
- package/dist/chunks/{Dialog-CQlTDhZS.js.map → Dialog-DwCTFV6O.js.map} +1 -1
- package/dist/chunks/FormPlugins-DvQ-G5J5.js +2 -0
- package/dist/chunks/{FormPlugins-DY6e88YT.js.map → FormPlugins-DvQ-G5J5.js.map} +1 -1
- package/dist/chunks/{FormView-DaKA4Sys.js → FormView-CRmEReTC.js} +3 -3
- package/dist/chunks/{FormView-DaKA4Sys.js.map → FormView-CRmEReTC.js.map} +1 -1
- package/dist/chunks/FormView-OLA7t-yv.js +3 -0
- package/dist/chunks/{FormView-Dz3mYasQ.js.map → FormView-OLA7t-yv.js.map} +1 -1
- package/dist/chunks/ListView-6JQ6tRXs.js +2 -0
- package/dist/chunks/{ListView-X5w5jf51.js.map → ListView-6JQ6tRXs.js.map} +1 -1
- package/dist/chunks/{ListView-CDzKIpd8.js → ListView-DVStKiMi.js} +2 -2
- package/dist/chunks/{ListView-CDzKIpd8.js.map → ListView-DVStKiMi.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-Dx2cw7ya.js → MetricsCountryMapView-CnAEbUw_.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-Dx2cw7ya.js.map → MetricsCountryMapView-CnAEbUw_.js.map} +1 -1
- package/dist/chunks/MetricsCountryMapView-J067qrrt.js +2 -0
- package/dist/chunks/{MetricsCountryMapView-B2xz6zUw.js.map → MetricsCountryMapView-J067qrrt.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-CBuso0OE.js → MetricsMiniChartWidget-BeD1slGs.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-CBuso0OE.js.map → MetricsMiniChartWidget-BeD1slGs.js.map} +1 -1
- package/dist/chunks/MetricsMiniChartWidget-x2gFjHOU.js +2 -0
- package/dist/chunks/{MetricsMiniChartWidget-DvKd7Qrk.js.map → MetricsMiniChartWidget-x2gFjHOU.js.map} +1 -1
- package/dist/chunks/PDFViewer-CsyKn-gh.js +2 -0
- package/dist/chunks/{PDFViewer-EJ9cOfPF.js.map → PDFViewer-CsyKn-gh.js.map} +1 -1
- package/dist/chunks/{PDFViewer-ofMGdSaj.js → PDFViewer-DSa4BZCm.js} +2 -2
- package/dist/chunks/{PDFViewer-ofMGdSaj.js.map → PDFViewer-DSa4BZCm.js.map} +1 -1
- package/dist/chunks/Rest-DHbszkuP.js +2 -0
- package/dist/chunks/Rest-DHbszkuP.js.map +1 -0
- package/dist/chunks/Rest-Ds9e8tN8.js +2 -0
- package/dist/chunks/Rest-Ds9e8tN8.js.map +1 -0
- package/dist/chunks/TokenManager-D6SjKgPZ.js +2 -0
- package/dist/chunks/{TokenManager-DoN9e6q6.js.map → TokenManager-D6SjKgPZ.js.map} +1 -1
- package/dist/chunks/{TokenManager-Gqvj7SDX.js → TokenManager-REbha1Le.js} +2 -2
- package/dist/chunks/{TokenManager-Gqvj7SDX.js.map → TokenManager-REbha1Le.js.map} +1 -1
- package/dist/chunks/WebApp-CULZpO_0.js +2 -0
- package/dist/chunks/{WebApp-6qvqmOts.js.map → WebApp-CULZpO_0.js.map} +1 -1
- package/dist/chunks/{WebApp-_dgpwtFw.js → WebApp-DovLtA60.js} +2 -2
- package/dist/chunks/{WebApp-_dgpwtFw.js.map → WebApp-DovLtA60.js.map} +1 -1
- package/dist/chunks/WebSocketClient-B-wc3mez.js +2 -0
- package/dist/chunks/{WebSocketClient-DG2olXpH.js.map → WebSocketClient-B-wc3mez.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-MFkFlSue.js → WebSocketClient-BdZ9QYll.js} +2 -2
- package/dist/chunks/{WebSocketClient-MFkFlSue.js.map → WebSocketClient-BdZ9QYll.js.map} +1 -1
- package/dist/chunks/version-C3dnl1bg.js +2 -0
- package/dist/chunks/version-C3dnl1bg.js.map +1 -0
- package/dist/chunks/{version-BVADfTA5.js → version-ioN546cp.js} +2 -2
- package/dist/chunks/{version-BVADfTA5.js.map → version-ioN546cp.js.map} +1 -1
- package/dist/css/web-mojo.css +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +1 -957
- package/dist/docit.es.js.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -3252
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +1 -3737
- package/dist/lightbox.es.js.map +1 -1
- package/dist/loader.umd.js +2 -2
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +1 -1032
- package/dist/map.es.js.map +1 -1
- package/dist/mojo-auth.es.js +338 -0
- package/dist/mojo-auth.umd.js +1 -0
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +1 -224
- package/dist/timeline.es.js.map +1 -1
- package/dist/web-mojo.lite.iife.js +14 -3
- package/dist/web-mojo.lite.iife.js.map +1 -1
- package/dist/web-mojo.lite.iife.min.js +6 -6
- package/dist/web-mojo.lite.iife.min.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunks/ChatView-9k6xBWXk.js +0 -7632
- package/dist/chunks/ChatView-9k6xBWXk.js.map +0 -1
- package/dist/chunks/ChatView-CdtuCDYm.js +0 -2
- package/dist/chunks/ChatView-CdtuCDYm.js.map +0 -1
- package/dist/chunks/Collection-DaiL0uGl.js +0 -1014
- package/dist/chunks/ContextMenu-sgvgSACY.js +0 -1535
- package/dist/chunks/DataView-Dzo0jbs2.js +0 -862
- package/dist/chunks/Dialog-DOGDalUq.js +0 -1579
- package/dist/chunks/FormPlugins-DY6e88YT.js +0 -124
- package/dist/chunks/FormView-Dz3mYasQ.js +0 -8636
- package/dist/chunks/ListView-X5w5jf51.js +0 -495
- package/dist/chunks/MetricsCountryMapView-B2xz6zUw.js +0 -1054
- package/dist/chunks/MetricsMiniChartWidget-DvKd7Qrk.js +0 -3283
- package/dist/chunks/PDFViewer-EJ9cOfPF.js +0 -946
- package/dist/chunks/Rest-CgSjfMaU.js +0 -2
- package/dist/chunks/Rest-CgSjfMaU.js.map +0 -1
- package/dist/chunks/Rest-W-sPfGh9.js +0 -4375
- package/dist/chunks/Rest-W-sPfGh9.js.map +0 -1
- package/dist/chunks/TokenManager-DoN9e6q6.js +0 -1423
- package/dist/chunks/WebApp-6qvqmOts.js +0 -1386
- package/dist/chunks/WebSocketClient-DG2olXpH.js +0 -209
- package/dist/chunks/version-OyPGnx30.js +0 -38
- 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
|