@ourlu/assistant-sdk 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,458 @@
1
+ (function() {
2
+ "use strict";
3
+ var runtime = window.__OurluWidgetRuntimeV1 || (window.__OurluWidgetRuntimeV1 = {});
4
+
5
+ var BAN_API_URL = "https://api-adresse.data.gouv.fr/search/";
6
+ var BAN_DEBOUNCE_MS = 300;
7
+ var BAN_MAX_RESULTS = 5;
8
+
9
+ function PluCssBuilder() {}
10
+
11
+ PluCssBuilder.prototype.build = function() {
12
+ return [
13
+ this.buildContent(), this.buildAddressInput(), this.buildSuggestions(),
14
+ this.buildSearchButton(), this.buildLoading(), this.buildResult(),
15
+ this.buildError(), this.buildPluButton(), this.buildMobileOverrides()
16
+ ].join("\n");
17
+ };
18
+
19
+ PluCssBuilder.prototype.buildContent = function() {
20
+ return [
21
+ ".cm-tab[data-tab='plu'].hidden{display:none}",
22
+ "#cm-plu-content{display:none;flex-direction:column;flex:1;overflow:hidden}",
23
+ "#cm-plu-content.active{display:flex}",
24
+ ".cm-plu-scroll{flex:1;overflow-y:auto;overflow-x:hidden;padding:20px 16px;display:flex;flex-direction:column;gap:16px;min-width:0;width:100%}",
25
+ ".cm-plu-scroll::-webkit-scrollbar{width:6px}",
26
+ ".cm-plu-scroll::-webkit-scrollbar-thumb{background:rgba(0,0,0,.1);border-radius:3px}",
27
+ ".cm-plu-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600;color:#1e293b;margin:0}",
28
+ ".cm-plu-title svg{width:20px;height:20px;flex-shrink:0}",
29
+ ".cm-plu-desc{font-size:14px;color:#64748b;line-height:1.5;margin:0}"
30
+ ].join("\n");
31
+ };
32
+
33
+ PluCssBuilder.prototype.buildAddressInput = function() {
34
+ return [
35
+ ".cm-plu-input-wrap{position:relative}",
36
+ ".cm-plu-input{width:100%;padding:12px 14px;border:1.5px solid #e2e8f0;border-radius:12px;font-size:14px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s;background:#fff;color:#1e293b;box-sizing:border-box}",
37
+ ".cm-plu-input:focus{border-color:#0066ff;box-shadow:0 0 0 3px rgba(0,102,255,.15)}",
38
+ ".cm-plu-input::placeholder{color:#9ca3af}"
39
+ ].join("\n");
40
+ };
41
+
42
+ PluCssBuilder.prototype.buildSuggestions = function() {
43
+ return [
44
+ ".cm-plu-suggestions{position:absolute;top:100%;left:0;right:0;margin-top:4px;background:#fff;border:1.5px solid #e2e8f0;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.12);z-index:10;overflow:hidden;display:none}",
45
+ ".cm-plu-suggestions.visible{display:block}",
46
+ ".cm-plu-suggestion{padding:10px 14px;font-size:14px;color:#334155;cursor:pointer;transition:background .1s;border-bottom:1px solid #f1f5f9;font-family:inherit}",
47
+ ".cm-plu-suggestion:last-child{border-bottom:none}",
48
+ ".cm-plu-suggestion:hover,.cm-plu-suggestion.highlighted{background:#eff6ff;color:#1d4ed8}"
49
+ ].join("\n");
50
+ };
51
+
52
+ PluCssBuilder.prototype.buildSearchButton = function() {
53
+ return [
54
+ ".cm-plu-search-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:12px 20px;border-radius:12px;border:none;background:#0066ff;color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .15s;font-family:inherit}",
55
+ ".cm-plu-search-btn svg{width:18px;height:18px;flex-shrink:0}",
56
+ ".cm-plu-search-btn:hover:not(:disabled){background:#0052cc;transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,102,255,.3)}",
57
+ ".cm-plu-search-btn:active:not(:disabled){transform:scale(0.97)}",
58
+ ".cm-plu-search-btn:disabled{opacity:.5;cursor:default}"
59
+ ].join("\n");
60
+ };
61
+
62
+ PluCssBuilder.prototype.buildLoading = function() {
63
+ return [
64
+ ".cm-plu-loading{display:none;flex-direction:column;align-items:center;gap:12px;padding:24px 16px;text-align:center}",
65
+ ".cm-plu-loading.visible{display:flex}",
66
+ ".cm-plu-spinner{width:36px;height:36px;border:3px solid #e2e8f0;border-top-color:#0066ff;border-radius:50%;animation:cm-plu-spin 1s linear infinite}",
67
+ "@keyframes cm-plu-spin{to{transform:rotate(360deg)}}",
68
+ ".cm-plu-loading-text{font-size:14px;color:#64748b;font-weight:500}",
69
+ ".cm-plu-loading-sub{font-size:13px;color:#94a3b8}"
70
+ ].join("\n");
71
+ };
72
+
73
+ PluCssBuilder.prototype.buildResult = function() {
74
+ return [
75
+ ".cm-plu-result{display:none;flex-direction:column;gap:16px}",
76
+ ".cm-plu-result.visible{display:flex}",
77
+ ".cm-plu-result-addr{display:flex;align-items:flex-start;gap:8px;font-size:14px;color:#334155;line-height:1.5}",
78
+ ".cm-plu-result-addr svg{width:18px;height:18px;flex-shrink:0;color:#0066ff;margin-top:2px}",
79
+ ".cm-plu-zone-card{padding:14px 16px;border-radius:12px;background:#eff6ff;border:1.5px solid #bfdbfe;display:flex;flex-direction:column;gap:4px}",
80
+ ".cm-plu-zone-code{font-size:18px;font-weight:700;color:#1d4ed8}",
81
+ ".cm-plu-zone-label{font-size:14px;color:#3b82f6;font-weight:500}",
82
+ ".cm-plu-zone-doc{font-size:12px;color:#64748b;margin-top:2px}",
83
+ ".cm-plu-rules-title{font-size:13px;font-weight:600;color:#475569;text-transform:uppercase;letter-spacing:.04em;margin-bottom:4px}",
84
+ ".cm-plu-rules-list{list-style:disc;padding-left:20px;margin:0;display:flex;flex-direction:column;gap:6px}",
85
+ ".cm-plu-rules-list li{font-size:14px;color:#334155;line-height:1.5}",
86
+ ".cm-plu-sources{font-size:12px;color:#94a3b8;margin-top:4px}",
87
+ ".cm-plu-disclaimer{display:flex;align-items:flex-start;gap:8px;padding:12px 14px;border-radius:10px;background:#fffbeb;border:1px solid #fde68a;font-size:13px;color:#92400e;line-height:1.5}",
88
+ ".cm-plu-disclaimer svg{width:16px;height:16px;flex-shrink:0;margin-top:2px}",
89
+ ".cm-plu-new-search-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:10px 20px;border-radius:12px;border:1.5px solid #e2e8f0;background:#fff;color:#475569;font-size:14px;font-weight:500;cursor:pointer;transition:all .15s;font-family:inherit}",
90
+ ".cm-plu-new-search-btn:hover{border-color:#94a3b8;background:#f8fafc;color:#334155}"
91
+ ].join("\n");
92
+ };
93
+
94
+ PluCssBuilder.prototype.buildError = function() {
95
+ return [
96
+ ".cm-plu-error{display:none;flex-direction:column;align-items:center;gap:12px;padding:20px 16px;text-align:center}",
97
+ ".cm-plu-error.visible{display:flex}",
98
+ ".cm-plu-error-icon{width:40px;height:40px;border-radius:50%;background:#fef2f2;display:flex;align-items:center;justify-content:center}",
99
+ ".cm-plu-error-icon svg{width:22px;height:22px;color:#dc2626}",
100
+ ".cm-plu-error-msg{font-size:14px;color:#991b1b;font-weight:500;line-height:1.5}",
101
+ ".cm-plu-error-code{font-size:12px;color:#94a3b8;font-family:ui-monospace,monospace}",
102
+ ".cm-plu-retry-btn{display:flex;align-items:center;justify-content:center;gap:6px;padding:10px 20px;border-radius:12px;border:1.5px solid #fecaca;background:#fef2f2;color:#dc2626;font-size:14px;font-weight:500;cursor:pointer;transition:all .15s;font-family:inherit}",
103
+ ".cm-plu-retry-btn:hover{background:#dc2626;color:#fff;border-color:#dc2626}"
104
+ ].join("\n");
105
+ };
106
+
107
+ PluCssBuilder.prototype.buildPluButton = function() {
108
+ return [
109
+ ".cm-plu-open-btn{display:inline-flex;align-items:center;gap:8px;margin-top:10px;padding:10px 18px;border-radius:12px;border:1.5px solid #0066ff;background:#eff6ff;color:#0066ff;font-size:14px;font-weight:600;cursor:pointer;transition:all .15s;font-family:inherit}",
110
+ ".cm-plu-open-btn svg{width:18px;height:18px;flex-shrink:0}",
111
+ ".cm-plu-open-btn:hover{background:#0066ff;color:#fff;transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,102,255,.3)}",
112
+ ".cm-plu-open-btn:active{transform:scale(0.97)}"
113
+ ].join("\n");
114
+ };
115
+
116
+ PluCssBuilder.prototype.buildMobileOverrides = function() {
117
+ return "@media(max-width:600px){.cm-plu-scroll{padding:16px 12px}.cm-plu-zone-card{padding:12px 14px}}";
118
+ };
119
+
120
+ function BanAutocompleteManager(inputEl, suggestionsEl) {
121
+ this.inputEl = inputEl;
122
+ this.suggestionsEl = suggestionsEl;
123
+ this.debounceTimer = null;
124
+ this.highlightedIndex = -1;
125
+ this.results = [];
126
+ this.selectedAddress = null;
127
+ this.onSelect = null;
128
+ this.bindEvents();
129
+ }
130
+
131
+ BanAutocompleteManager.prototype.bindEvents = function() {
132
+ var self = this;
133
+ this.inputEl.addEventListener("input", function() {
134
+ self.selectedAddress = null;
135
+ self.scheduleSearch(self.inputEl.value.trim());
136
+ });
137
+ this.inputEl.addEventListener("keydown", function(e) {
138
+ if (!self.results.length) return;
139
+ if (e.key === "ArrowDown") {
140
+ e.preventDefault();
141
+ self.highlightedIndex = Math.min(self.highlightedIndex + 1, self.results.length - 1);
142
+ self.renderHighlight();
143
+ } else if (e.key === "ArrowUp") {
144
+ e.preventDefault();
145
+ self.highlightedIndex = Math.max(self.highlightedIndex - 1, 0);
146
+ self.renderHighlight();
147
+ } else if (e.key === "Enter") {
148
+ e.preventDefault();
149
+ if (self.highlightedIndex >= 0) self.selectResult(self.results[self.highlightedIndex]);
150
+ } else if (e.key === "Escape") {
151
+ self.hideSuggestions();
152
+ }
153
+ });
154
+ document.addEventListener("click", function(e) {
155
+ if (!self.inputEl.contains(e.target) && !self.suggestionsEl.contains(e.target)) self.hideSuggestions();
156
+ });
157
+ };
158
+
159
+ BanAutocompleteManager.prototype.scheduleSearch = function(query) {
160
+ var self = this;
161
+ clearTimeout(this.debounceTimer);
162
+ if (query.length < 3) { this.hideSuggestions(); return; }
163
+ this.debounceTimer = setTimeout(function() { self.search(query); }, BAN_DEBOUNCE_MS);
164
+ };
165
+
166
+ BanAutocompleteManager.prototype.search = async function(query) {
167
+ var url = BAN_API_URL + "?q=" + encodeURIComponent(query) + "&limit=" + BAN_MAX_RESULTS;
168
+ var response = await fetch(url);
169
+ if (!response.ok) throw new Error("BAN API erreur HTTP " + response.status);
170
+ var data = await response.json();
171
+ this.results = (data.features || []).map(function(f) {
172
+ var props = f.properties || {};
173
+ var coords = f.geometry && f.geometry.coordinates ? f.geometry.coordinates : [0, 0];
174
+ return { label: props.label || "", city: props.city || "", postcode: props.postcode || "", citycode: props.citycode || "", longitude: coords[0], latitude: coords[1] };
175
+ });
176
+ this.highlightedIndex = -1;
177
+ this.renderSuggestions();
178
+ };
179
+
180
+ BanAutocompleteManager.prototype.renderSuggestions = function() {
181
+ var self = this;
182
+ if (!this.results.length) { this.hideSuggestions(); return; }
183
+ this.suggestionsEl.innerHTML = this.results.map(function(r, idx) {
184
+ return '<div class="cm-plu-suggestion" data-idx="' + idx + '">' + self.escapeHtml(r.label) + '</div>';
185
+ }).join("");
186
+ this.suggestionsEl.classList.add("visible");
187
+ this.suggestionsEl.querySelectorAll(".cm-plu-suggestion").forEach(function(el) {
188
+ el.addEventListener("click", function() { self.selectResult(self.results[Number(el.getAttribute("data-idx"))]); });
189
+ });
190
+ };
191
+
192
+ BanAutocompleteManager.prototype.renderHighlight = function() {
193
+ var self = this;
194
+ this.suggestionsEl.querySelectorAll(".cm-plu-suggestion").forEach(function(el, idx) {
195
+ el.classList.toggle("highlighted", idx === self.highlightedIndex);
196
+ });
197
+ };
198
+
199
+ BanAutocompleteManager.prototype.selectResult = function(result) {
200
+ if (!result) return;
201
+ this.selectedAddress = result;
202
+ this.inputEl.value = result.label;
203
+ this.hideSuggestions();
204
+ if (typeof this.onSelect === "function") this.onSelect(result);
205
+ };
206
+
207
+ BanAutocompleteManager.prototype.hideSuggestions = function() {
208
+ this.suggestionsEl.classList.remove("visible");
209
+ this.results = [];
210
+ this.highlightedIndex = -1;
211
+ };
212
+
213
+ BanAutocompleteManager.prototype.escapeHtml = function(str) {
214
+ var div = document.createElement("div");
215
+ div.textContent = str;
216
+ return div.innerHTML;
217
+ };
218
+
219
+ BanAutocompleteManager.prototype.reset = function() {
220
+ this.inputEl.value = "";
221
+ this.selectedAddress = null;
222
+ this.hideSuggestions();
223
+ };
224
+
225
+ var ICON_HOUSE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>';
226
+ var ICON_PIN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>';
227
+ var ICON_SEARCH = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>';
228
+ var ICON_WARN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>';
229
+ var ICON_X = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
230
+
231
+ function PluModuleManager(rootEl, events, apiManager, uiManager) {
232
+ this.root = rootEl;
233
+ this.events = events;
234
+ this.api = apiManager;
235
+ this.ui = uiManager;
236
+ this.autocomplete = null;
237
+ this.contentEl = null;
238
+ this.stateInputEl = null;
239
+ this.stateLoadingEl = null;
240
+ this.stateResultEl = null;
241
+ this.stateErrorEl = null;
242
+ this.searchBtnEl = null;
243
+ this.mounted = false;
244
+ }
245
+
246
+ PluModuleManager.prototype.injectCSS = function() {
247
+ var styleEl = document.createElement("style");
248
+ styleEl.id = "cm-plu-css";
249
+ styleEl.textContent = new PluCssBuilder().build();
250
+ this.root.appendChild(styleEl);
251
+ };
252
+
253
+ PluModuleManager.prototype.mount = function() {
254
+ if (this.mounted) return;
255
+ this.injectCSS();
256
+ var tabsBar = this.root.querySelector("#cm-tabs-bar");
257
+ if (tabsBar) {
258
+ var pluTab = document.createElement("button");
259
+ pluTab.className = "cm-tab hidden";
260
+ pluTab.setAttribute("data-tab", "plu");
261
+ pluTab.type = "button";
262
+ pluTab.innerHTML = ICON_HOUSE + 'Urbanisme<button class="cm-tab-close" type="button" aria-label="Fermer l\'onglet urbanisme">' + ICON_X + '</button>';
263
+ tabsBar.appendChild(pluTab);
264
+ var self = this;
265
+ pluTab.addEventListener("click", function() {
266
+ if (!pluTab.classList.contains("hidden")) self.switchTab("plu");
267
+ });
268
+ var closeBtn = pluTab.querySelector(".cm-tab-close");
269
+ if (closeBtn) closeBtn.addEventListener("click", function(e) { e.stopPropagation(); self.close(); });
270
+ }
271
+ var content = document.createElement("div");
272
+ content.id = "cm-plu-content";
273
+ content.innerHTML = this.buildHTML();
274
+ var panel = this.ui.getPanel();
275
+ var formEl = panel.querySelector("#cm-form");
276
+ if (formEl) { panel.insertBefore(content, formEl); } else { panel.appendChild(content); }
277
+ this.contentEl = content;
278
+ this.stateInputEl = content.querySelector(".cm-plu-state-input");
279
+ this.stateLoadingEl = content.querySelector(".cm-plu-loading");
280
+ this.stateResultEl = content.querySelector(".cm-plu-result");
281
+ this.stateErrorEl = content.querySelector(".cm-plu-error");
282
+ this.searchBtnEl = content.querySelector(".cm-plu-search-btn");
283
+ this.autocomplete = new BanAutocompleteManager(content.querySelector(".cm-plu-input"), content.querySelector(".cm-plu-suggestions"));
284
+ this.bindEvents();
285
+ this.mounted = true;
286
+ };
287
+
288
+ PluModuleManager.prototype.buildHTML = function() {
289
+ return [
290
+ '<div class="cm-plu-scroll">',
291
+ '<div class="cm-plu-state-input" style="display:flex;flex-direction:column;gap:16px">',
292
+ '<h3 class="cm-plu-title">' + ICON_HOUSE + ' Recherche PLU</h3>',
293
+ '<p class="cm-plu-desc">Saisissez votre adresse pour conna\u00eetre les r\u00e8gles d\'urbanisme applicables \u00e0 votre parcelle.</p>',
294
+ '<div class="cm-plu-input-wrap"><input class="cm-plu-input" type="text" placeholder="Tapez votre adresse\u2026" autocomplete="off"/><div class="cm-plu-suggestions"></div></div>',
295
+ '<button class="cm-plu-search-btn" type="button" disabled>' + ICON_SEARCH + ' Rechercher</button>',
296
+ '</div>',
297
+ '<div class="cm-plu-loading"><div class="cm-plu-spinner"></div><div class="cm-plu-loading-text">Recherche en cours\u2026</div><div class="cm-plu-loading-sub">Identification de votre parcelle et r\u00e9cup\u00e9ration des r\u00e8gles PLU\u2026</div></div>',
298
+ '<div class="cm-plu-result"></div>',
299
+ '<div class="cm-plu-error"></div>',
300
+ '</div>'
301
+ ].join("");
302
+ };
303
+
304
+ PluModuleManager.prototype.bindEvents = function() {
305
+ var self = this;
306
+ this.autocomplete.onSelect = function() { self.searchBtnEl.disabled = false; };
307
+ this.searchBtnEl.addEventListener("click", function() { self.submitQuery(); });
308
+ };
309
+
310
+ PluModuleManager.prototype.switchTab = function(tabName) {
311
+ var isPlu = tabName === "plu";
312
+ var tabsBar = this.root.querySelector("#cm-tabs-bar");
313
+ if (tabsBar) {
314
+ tabsBar.querySelectorAll(".cm-tab").forEach(function(t) {
315
+ t.classList.toggle("active", t.getAttribute("data-tab") === tabName);
316
+ });
317
+ }
318
+ this.root.querySelectorAll("#cm-messages, #cm-typing, #cm-error, #cm-disclaimer, #cm-transparency").forEach(function(el) { el.style.display = isPlu ? "none" : ""; });
319
+ var sigContent = this.root.querySelector("#cm-signalement-content");
320
+ if (sigContent) { sigContent.style.display = "none"; sigContent.classList.remove("active"); }
321
+ var sigFooter = this.root.querySelector(".cm-sig-footer");
322
+ if (sigFooter) sigFooter.classList.remove("active");
323
+ var chatForm = this.root.querySelector("#cm-form");
324
+ if (chatForm) chatForm.style.display = isPlu ? "none" : "";
325
+ this.contentEl.classList.toggle("active", isPlu);
326
+ this.contentEl.style.display = isPlu ? "flex" : "none";
327
+ };
328
+
329
+ PluModuleManager.prototype.open = function() {
330
+ var tabsBar = this.root.querySelector("#cm-tabs-bar");
331
+ if (tabsBar) {
332
+ var pluTab = tabsBar.querySelector('[data-tab="plu"]');
333
+ if (pluTab) pluTab.classList.remove("hidden");
334
+ tabsBar.classList.add("visible");
335
+ }
336
+ this.switchTab("plu");
337
+ this.showState("input");
338
+ this.events.emit("module:plu:open", {});
339
+ };
340
+
341
+ PluModuleManager.prototype.close = function() {
342
+ var tabsBar = this.root.querySelector("#cm-tabs-bar");
343
+ if (tabsBar) {
344
+ var pluTab = tabsBar.querySelector('[data-tab="plu"]');
345
+ if (pluTab) pluTab.classList.add("hidden");
346
+ }
347
+ this.switchTab("chat");
348
+ this.events.emit("module:plu:closed", {});
349
+ };
350
+
351
+ PluModuleManager.prototype.showState = function(state) {
352
+ this.stateInputEl.style.display = state === "input" ? "flex" : "none";
353
+ this.stateLoadingEl.classList.toggle("visible", state === "loading");
354
+ this.stateResultEl.classList.toggle("visible", state === "result");
355
+ this.stateErrorEl.classList.toggle("visible", state === "error");
356
+ };
357
+
358
+ PluModuleManager.prototype.submitQuery = async function() {
359
+ var address = this.autocomplete.selectedAddress;
360
+ if (!address) return;
361
+ this.showState("loading");
362
+ this.events.emit("module:plu:submitted", address);
363
+ try {
364
+ await this.api.ensureSession();
365
+ var url = this.api.resolveRuntimeUrl("/v1/modules/plu/query");
366
+ var headers = this.api.buildHeaders({ "Content-Type": "application/json" });
367
+ var body = JSON.stringify({
368
+ session_id: this.api.state.sessionId || "",
369
+ address_label: address.label,
370
+ latitude: address.latitude,
371
+ longitude: address.longitude,
372
+ city_insee_code: address.citycode
373
+ });
374
+ var response = await fetch(url, { method: "POST", headers: headers, body: body });
375
+ if (!response.ok) {
376
+ var errText = await response.text().catch(function() { return ""; });
377
+ var errObj = {};
378
+ try { errObj = JSON.parse(errText); } catch (_) {}
379
+ throw { message: errObj.message || errObj.error || "Erreur HTTP " + response.status, code: errObj.error_code || "PLU_HTTP_" + response.status };
380
+ }
381
+ var result = await response.json();
382
+ this.showResult(address, result);
383
+ this.events.emit("module:plu:resolved", result);
384
+ } catch (error) {
385
+ this.showError(
386
+ (error && error.message) || "Impossible de résoudre le PLU pour cette adresse.",
387
+ (error && error.code) || "PLU_UNKNOWN_ERROR"
388
+ );
389
+ this.events.emit("module:plu:error", { message: error && error.message, code: error && error.code });
390
+ }
391
+ };
392
+
393
+ PluModuleManager.prototype.showResult = function(address, data) {
394
+ var rulesHtml = "";
395
+ if (data.rules && data.rules.length) {
396
+ rulesHtml = '<div class="cm-plu-rules-title">Règles applicables :</div><ul class="cm-plu-rules-list">' +
397
+ data.rules.map(function(r) { return "<li>" + escapeHtml(typeof r === "string" ? r : (r.label || r.description || "")) + "</li>"; }).join("") + '</ul>';
398
+ }
399
+ var sourcesText = "";
400
+ if (data.sources && data.sources.length) {
401
+ sourcesText = "Sources : " + data.sources.map(function(s) { return escapeHtml(typeof s === "string" ? s : (s.name || "")); }).join(", ");
402
+ }
403
+ this.stateResultEl.innerHTML = [
404
+ '<h3 class="cm-plu-title">' + ICON_HOUSE + ' Résultat PLU</h3>',
405
+ '<div class="cm-plu-result-addr">' + ICON_PIN + '<div>' + escapeHtml(address.label) + '</div></div>',
406
+ '<div class="cm-plu-zone-card"><div class="cm-plu-zone-code">Zone : ' + escapeHtml(data.zone_code || "N/A") + '</div>',
407
+ '<div class="cm-plu-zone-label">' + escapeHtml(data.zone_label || "") + '</div>',
408
+ data.document_type ? '<div class="cm-plu-zone-doc">Document : ' + escapeHtml(data.document_type) + '</div>' : '',
409
+ '</div>', rulesHtml,
410
+ sourcesText ? '<div class="cm-plu-sources">' + sourcesText + '</div>' : '',
411
+ '<div class="cm-plu-disclaimer">' + ICON_WARN + '<div>' + escapeHtml(data.disclaimer || "Information indicative. Consultez le service urbanisme de votre mairie pour confirmation.") + '</div></div>',
412
+ '<button class="cm-plu-new-search-btn" type="button">' + ICON_SEARCH + ' Nouvelle recherche</button>'
413
+ ].join("");
414
+ var self = this;
415
+ var newBtn = this.stateResultEl.querySelector(".cm-plu-new-search-btn");
416
+ if (newBtn) newBtn.addEventListener("click", function() { self.resetToInput(); });
417
+ this.showState("result");
418
+ };
419
+
420
+ PluModuleManager.prototype.showError = function(message, code) {
421
+ this.stateErrorEl.innerHTML = [
422
+ '<div class="cm-plu-error-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg></div>',
423
+ '<div class="cm-plu-error-msg">' + escapeHtml(message) + '</div>',
424
+ code ? '<div class="cm-plu-error-code">' + escapeHtml(code) + '</div>' : '',
425
+ '<button class="cm-plu-retry-btn" type="button">' + ICON_SEARCH + ' Réessayer</button>'
426
+ ].join("");
427
+ var self = this;
428
+ var retryBtn = this.stateErrorEl.querySelector(".cm-plu-retry-btn");
429
+ if (retryBtn) retryBtn.addEventListener("click", function() { self.resetToInput(); });
430
+ this.showState("error");
431
+ };
432
+
433
+ PluModuleManager.prototype.resetToInput = function() {
434
+ this.autocomplete.reset();
435
+ this.searchBtnEl.disabled = true;
436
+ this.showState("input");
437
+ };
438
+
439
+ PluModuleManager.prototype.createOpenButton = function() {
440
+ var btn = document.createElement("button");
441
+ btn.className = "cm-plu-open-btn";
442
+ btn.type = "button";
443
+ btn.innerHTML = ICON_HOUSE + 'Urbanisme';
444
+ var self = this;
445
+ btn.addEventListener("click", function() { self.open(); });
446
+ return btn;
447
+ };
448
+
449
+ function escapeHtml(str) {
450
+ var div = document.createElement("div");
451
+ div.textContent = String(str || "");
452
+ return div.innerHTML;
453
+ }
454
+
455
+ runtime.PluModuleManager = PluModuleManager;
456
+ runtime.PluCssBuilder = PluCssBuilder;
457
+ runtime.BanAutocompleteManager = BanAutocompleteManager;
458
+ })();