htmx.org 4.0.0-alpha3 → 4.0.0-alpha5
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/ext/hx-compat.js +91 -0
- package/dist/ext/hx-compat.min.js +1 -0
- package/dist/ext/hx-compat.min.js.map +1 -0
- package/dist/ext/hx-head.js +129 -0
- package/dist/ext/hx-head.min.js +1 -0
- package/dist/ext/hx-head.min.js.map +1 -0
- package/dist/ext/hx-optimistic.js +1 -1
- package/dist/ext/hx-optimistic.min.js +1 -1
- package/dist/ext/hx-optimistic.min.js.map +1 -1
- package/dist/ext/hx-preload.js +24 -11
- package/dist/ext/hx-preload.min.js +1 -1
- package/dist/ext/hx-preload.min.js.map +1 -1
- package/dist/ext/hx-ws.js +614 -0
- package/dist/ext/hx-ws.min.js +1 -0
- package/dist/ext/hx-ws.min.js.map +1 -0
- package/dist/htmx.d.ts +52 -0
- package/dist/htmx.esm.js +260 -174
- package/dist/htmx.esm.min.js +1 -1
- package/dist/htmx.esm.min.js.map +1 -1
- package/dist/htmx.js +258 -173
- package/dist/htmx.min.js +1 -1
- package/dist/htmx.min.js.map +1 -1
- package/package.json +11 -7
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
let api;
|
|
3
|
+
|
|
4
|
+
// Helper to get attribute value, checking colon, hyphen, and plain variants
|
|
5
|
+
function getWsAttribute(element, attrName) {
|
|
6
|
+
// Try colon variant first (hx-ws:connect)
|
|
7
|
+
let colonValue = api.attributeValue(element, 'hx-ws:' + attrName);
|
|
8
|
+
if (colonValue !== null && colonValue !== undefined) return colonValue;
|
|
9
|
+
|
|
10
|
+
// Try hyphen variant for JSX (hx-ws-connect)
|
|
11
|
+
let hyphenValue = api.attributeValue(element, 'hx-ws-' + attrName);
|
|
12
|
+
if (hyphenValue !== null && hyphenValue !== undefined) return hyphenValue;
|
|
13
|
+
|
|
14
|
+
// For 'send', also check plain 'hx-ws' (marker attribute)
|
|
15
|
+
if (attrName === 'send') {
|
|
16
|
+
let plainValue = api.attributeValue(element, 'hx-ws');
|
|
17
|
+
if (plainValue !== null && plainValue !== undefined) return plainValue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Helper to check if element has WebSocket attribute (any variant)
|
|
24
|
+
function hasWsAttribute(element, attrName) {
|
|
25
|
+
let value = getWsAttribute(element, attrName);
|
|
26
|
+
return value !== null && value !== undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ========================================
|
|
30
|
+
// CONFIGURATION
|
|
31
|
+
// ========================================
|
|
32
|
+
|
|
33
|
+
function getConfig() {
|
|
34
|
+
const defaults = {
|
|
35
|
+
reconnect: true,
|
|
36
|
+
reconnectDelay: 1000,
|
|
37
|
+
reconnectMaxDelay: 30000,
|
|
38
|
+
reconnectJitter: true,
|
|
39
|
+
autoConnect: false,
|
|
40
|
+
pauseInBackground: true
|
|
41
|
+
};
|
|
42
|
+
return { ...defaults, ...(htmx.config.websockets || {}) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ========================================
|
|
46
|
+
// CONNECTION REGISTRY
|
|
47
|
+
// ========================================
|
|
48
|
+
|
|
49
|
+
const connectionRegistry = new Map();
|
|
50
|
+
|
|
51
|
+
function getOrCreateConnection(url, element) {
|
|
52
|
+
if (connectionRegistry.has(url)) {
|
|
53
|
+
let entry = connectionRegistry.get(url);
|
|
54
|
+
entry.refCount++;
|
|
55
|
+
entry.elements.add(element);
|
|
56
|
+
return entry;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let entry = {
|
|
60
|
+
socket: null,
|
|
61
|
+
refCount: 1,
|
|
62
|
+
elements: new Set([element]),
|
|
63
|
+
reconnectAttempts: 0,
|
|
64
|
+
reconnectTimer: null,
|
|
65
|
+
pendingRequests: new Map()
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
connectionRegistry.set(url, entry);
|
|
69
|
+
createWebSocket(url, entry);
|
|
70
|
+
return entry;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createWebSocket(url, entry) {
|
|
74
|
+
let firstElement = entry.elements.values().next().value;
|
|
75
|
+
if (firstElement) {
|
|
76
|
+
if (!triggerEvent(firstElement, 'htmx:before:ws:connect', { url })) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Close and remove listeners from old socket
|
|
82
|
+
if (entry.socket) {
|
|
83
|
+
let oldSocket = entry.socket;
|
|
84
|
+
entry.socket = null;
|
|
85
|
+
|
|
86
|
+
oldSocket.onopen = null;
|
|
87
|
+
oldSocket.onmessage = null;
|
|
88
|
+
oldSocket.onclose = null;
|
|
89
|
+
oldSocket.onerror = null;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
if (oldSocket.readyState === WebSocket.OPEN || oldSocket.readyState === WebSocket.CONNECTING) {
|
|
93
|
+
oldSocket.close();
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
entry.socket = new WebSocket(url);
|
|
100
|
+
|
|
101
|
+
entry.socket.addEventListener('open', () => {
|
|
102
|
+
// Don't reset reconnectAttempts immediately - allow backoff to persist across quick reconnections
|
|
103
|
+
// It will naturally decrease as the connection remains stable
|
|
104
|
+
if (firstElement) {
|
|
105
|
+
triggerEvent(firstElement, 'htmx:after:ws:connect', { url, socket: entry.socket });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
entry.socket.addEventListener('message', (event) => {
|
|
110
|
+
handleMessage(entry, event);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
entry.socket.addEventListener('close', (event) => {
|
|
114
|
+
// Check if this socket is still the active one
|
|
115
|
+
if (event.target !== entry.socket) return;
|
|
116
|
+
|
|
117
|
+
if (firstElement) {
|
|
118
|
+
triggerEvent(firstElement, 'htmx:ws:close', { url });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if entry is still valid (not cleared)
|
|
122
|
+
if (!connectionRegistry.has(url)) return;
|
|
123
|
+
|
|
124
|
+
let config = getConfig();
|
|
125
|
+
if (config.reconnect && entry.refCount > 0) {
|
|
126
|
+
scheduleReconnect(url, entry);
|
|
127
|
+
} else {
|
|
128
|
+
connectionRegistry.delete(url);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
entry.socket.addEventListener('error', (error) => {
|
|
133
|
+
if (firstElement) {
|
|
134
|
+
triggerEvent(firstElement, 'htmx:ws:error', { url, error });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (firstElement) {
|
|
139
|
+
triggerEvent(firstElement, 'htmx:ws:error', { url, error });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function scheduleReconnect(url, entry) {
|
|
145
|
+
let config = getConfig();
|
|
146
|
+
|
|
147
|
+
// Increment attempts before calculating delay for proper exponential backoff
|
|
148
|
+
let attempts = entry.reconnectAttempts;
|
|
149
|
+
entry.reconnectAttempts++;
|
|
150
|
+
|
|
151
|
+
let delay = Math.min(
|
|
152
|
+
(config.reconnectDelay || 1000) * Math.pow(2, attempts),
|
|
153
|
+
config.reconnectMaxDelay || 30000
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (config.reconnectJitter) {
|
|
157
|
+
delay = delay * (0.75 + Math.random() * 0.5);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
entry.reconnectTimer = setTimeout(() => {
|
|
161
|
+
if (entry.refCount > 0) {
|
|
162
|
+
let firstElement = entry.elements.values().next().value;
|
|
163
|
+
if (firstElement) {
|
|
164
|
+
triggerEvent(firstElement, 'htmx:ws:reconnect', { url, attempts });
|
|
165
|
+
}
|
|
166
|
+
createWebSocket(url, entry);
|
|
167
|
+
}
|
|
168
|
+
}, delay);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function decrementRef(url, element) {
|
|
172
|
+
if (!connectionRegistry.has(url)) return;
|
|
173
|
+
|
|
174
|
+
let entry = connectionRegistry.get(url);
|
|
175
|
+
entry.elements.delete(element);
|
|
176
|
+
entry.refCount--;
|
|
177
|
+
|
|
178
|
+
if (entry.refCount <= 0) {
|
|
179
|
+
if (entry.reconnectTimer) {
|
|
180
|
+
clearTimeout(entry.reconnectTimer);
|
|
181
|
+
}
|
|
182
|
+
if (entry.socket && entry.socket.readyState === WebSocket.OPEN) {
|
|
183
|
+
entry.socket.close();
|
|
184
|
+
}
|
|
185
|
+
connectionRegistry.delete(url);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ========================================
|
|
190
|
+
// MESSAGE SENDING
|
|
191
|
+
// ========================================
|
|
192
|
+
|
|
193
|
+
function sendMessage(element, event) {
|
|
194
|
+
// Find connection URL
|
|
195
|
+
let url = getWsAttribute(element, 'send');
|
|
196
|
+
if (!url) {
|
|
197
|
+
// Look for nearest ancestor with hx-ws:connect or hx-ws-connect
|
|
198
|
+
let prefix = htmx.config.prefix || '';
|
|
199
|
+
let ancestor = element.closest('[' + prefix + 'hx-ws\\:connect],[' + prefix + 'hx-ws-connect]');
|
|
200
|
+
if (ancestor) {
|
|
201
|
+
url = getWsAttribute(ancestor, 'connect');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!url) {
|
|
206
|
+
console.error('No WebSocket connection found for hx-ws:send element', element);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let entry = connectionRegistry.get(url);
|
|
211
|
+
if (!entry || !entry.socket || entry.socket.readyState !== WebSocket.OPEN) {
|
|
212
|
+
triggerEvent(element, 'htmx:wsSendError', { url, error: 'Connection not open' });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Build message
|
|
217
|
+
let form = element.form || element.closest('form');
|
|
218
|
+
let body = api.collectFormData(element, form, event.submitter);
|
|
219
|
+
api.handleHxVals(element, body);
|
|
220
|
+
|
|
221
|
+
let values = {};
|
|
222
|
+
for (let [key, value] of body) {
|
|
223
|
+
values[key] = value;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let requestId = generateUUID();
|
|
227
|
+
let message = {
|
|
228
|
+
type: 'request',
|
|
229
|
+
request_id: requestId,
|
|
230
|
+
event: event.type,
|
|
231
|
+
values: values,
|
|
232
|
+
path: url
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
if (element.id) {
|
|
236
|
+
message.id = element.id;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Allow modification via event
|
|
240
|
+
let detail = { message, element, url };
|
|
241
|
+
if (!triggerEvent(element, 'htmx:before:ws:send', detail)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
entry.socket.send(JSON.stringify(detail.message));
|
|
247
|
+
|
|
248
|
+
// Store pending request for response matching
|
|
249
|
+
entry.pendingRequests.set(requestId, { element, timestamp: Date.now() });
|
|
250
|
+
|
|
251
|
+
triggerEvent(element, 'htmx:after:ws:send', { message: detail.message, url });
|
|
252
|
+
} catch (error) {
|
|
253
|
+
triggerEvent(element, 'htmx:wsSendError', { url, error });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function generateUUID() {
|
|
258
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
259
|
+
let r = Math.random() * 16 | 0;
|
|
260
|
+
let v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
261
|
+
return v.toString(16);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ========================================
|
|
266
|
+
// MESSAGE RECEIVING & ROUTING
|
|
267
|
+
// ========================================
|
|
268
|
+
|
|
269
|
+
function handleMessage(entry, event) {
|
|
270
|
+
let envelope;
|
|
271
|
+
try {
|
|
272
|
+
envelope = JSON.parse(event.data);
|
|
273
|
+
} catch (e) {
|
|
274
|
+
// Not JSON, emit unknown message event
|
|
275
|
+
let firstElement = entry.elements.values().next().value;
|
|
276
|
+
if (firstElement) {
|
|
277
|
+
triggerEvent(firstElement, 'htmx:wsUnknownMessage', { data: event.data });
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Apply defaults for channel and format
|
|
283
|
+
envelope.channel = envelope.channel || 'ui';
|
|
284
|
+
envelope.format = envelope.format || 'html';
|
|
285
|
+
|
|
286
|
+
// Find target element for this message
|
|
287
|
+
let targetElement = null;
|
|
288
|
+
if (envelope.request_id && entry.pendingRequests.has(envelope.request_id)) {
|
|
289
|
+
targetElement = entry.pendingRequests.get(envelope.request_id).element;
|
|
290
|
+
entry.pendingRequests.delete(envelope.request_id);
|
|
291
|
+
} else {
|
|
292
|
+
// Use first element in the connection
|
|
293
|
+
targetElement = entry.elements.values().next().value;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Emit before:message event (cancelable)
|
|
297
|
+
if (!triggerEvent(targetElement, 'htmx:before:ws:message', { envelope, element: targetElement })) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Route based on channel
|
|
302
|
+
if (envelope.channel === 'ui' && envelope.format === 'html') {
|
|
303
|
+
handleHtmlMessage(targetElement, envelope);
|
|
304
|
+
} else if (envelope.channel && (envelope.channel === 'audio' || envelope.channel === 'json' || envelope.channel === 'binary')) {
|
|
305
|
+
// Known custom channel - emit event for extensions to handle
|
|
306
|
+
triggerEvent(targetElement, 'htmx:wsMessage', { ...envelope, element: targetElement });
|
|
307
|
+
} else {
|
|
308
|
+
// Unknown channel/format - emit unknown message event
|
|
309
|
+
triggerEvent(targetElement, 'htmx:wsUnknownMessage', { ...envelope, element: targetElement });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
triggerEvent(targetElement, 'htmx:after:ws:message', { envelope, element: targetElement });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ========================================
|
|
316
|
+
// HTML PARTIAL HANDLING
|
|
317
|
+
// ========================================
|
|
318
|
+
|
|
319
|
+
function handleHtmlMessage(element, envelope) {
|
|
320
|
+
let parser = new DOMParser();
|
|
321
|
+
let doc = parser.parseFromString(envelope.payload, 'text/html');
|
|
322
|
+
|
|
323
|
+
// Find all hx-partial elements
|
|
324
|
+
let partials = doc.querySelectorAll('hx-partial');
|
|
325
|
+
|
|
326
|
+
if (partials.length === 0) {
|
|
327
|
+
// No partials, treat entire payload as content for element's target
|
|
328
|
+
let target = resolveTarget(element, envelope.target);
|
|
329
|
+
if (target) {
|
|
330
|
+
swapContent(target, envelope.payload, element, envelope.swap);
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
partials.forEach(partial => {
|
|
336
|
+
let targetId = partial.getAttribute('id');
|
|
337
|
+
if (!targetId) return;
|
|
338
|
+
|
|
339
|
+
let target = document.getElementById(targetId);
|
|
340
|
+
if (!target) return;
|
|
341
|
+
|
|
342
|
+
swapContent(target, partial.innerHTML, element);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function resolveTarget(element, envelopeTarget) {
|
|
347
|
+
if (envelopeTarget) {
|
|
348
|
+
if (envelopeTarget === 'this') {
|
|
349
|
+
return element;
|
|
350
|
+
}
|
|
351
|
+
return document.querySelector(envelopeTarget);
|
|
352
|
+
}
|
|
353
|
+
let targetSelector = api.attributeValue(element, 'hx-target');
|
|
354
|
+
if (targetSelector) {
|
|
355
|
+
if (targetSelector === 'this') {
|
|
356
|
+
return element;
|
|
357
|
+
}
|
|
358
|
+
return document.querySelector(targetSelector);
|
|
359
|
+
}
|
|
360
|
+
return element;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function swapContent(target, content, sourceElement, envelopeSwap) {
|
|
364
|
+
let swapStyle = envelopeSwap || api.attributeValue(sourceElement, 'hx-swap') || htmx.config.defaultSwap;
|
|
365
|
+
|
|
366
|
+
// Parse swap style (just get the main style, ignore modifiers for now)
|
|
367
|
+
let style = swapStyle.split(' ')[0];
|
|
368
|
+
|
|
369
|
+
// Normalize swap style
|
|
370
|
+
style = normalizeSwapStyle(style);
|
|
371
|
+
|
|
372
|
+
// Perform swap
|
|
373
|
+
switch (style) {
|
|
374
|
+
case 'innerHTML':
|
|
375
|
+
target.innerHTML = content;
|
|
376
|
+
break;
|
|
377
|
+
case 'outerHTML':
|
|
378
|
+
target.outerHTML = content;
|
|
379
|
+
break;
|
|
380
|
+
case 'beforebegin':
|
|
381
|
+
target.insertAdjacentHTML('beforebegin', content);
|
|
382
|
+
break;
|
|
383
|
+
case 'afterbegin':
|
|
384
|
+
target.insertAdjacentHTML('afterbegin', content);
|
|
385
|
+
break;
|
|
386
|
+
case 'beforeend':
|
|
387
|
+
target.insertAdjacentHTML('beforeend', content);
|
|
388
|
+
break;
|
|
389
|
+
case 'afterend':
|
|
390
|
+
target.insertAdjacentHTML('afterend', content);
|
|
391
|
+
break;
|
|
392
|
+
case 'delete':
|
|
393
|
+
target.remove();
|
|
394
|
+
break;
|
|
395
|
+
case 'none':
|
|
396
|
+
// Do nothing
|
|
397
|
+
break;
|
|
398
|
+
default:
|
|
399
|
+
target.innerHTML = content;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Process new content with HTMX
|
|
403
|
+
htmx.process(target);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function normalizeSwapStyle(style) {
|
|
407
|
+
return style === 'before' ? 'beforebegin' :
|
|
408
|
+
style === 'after' ? 'afterend' :
|
|
409
|
+
style === 'prepend' ? 'afterbegin' :
|
|
410
|
+
style === 'append' ? 'beforeend' : style;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ========================================
|
|
414
|
+
// EVENT HELPERS
|
|
415
|
+
// ========================================
|
|
416
|
+
|
|
417
|
+
function triggerEvent(element, eventName, detail = {}) {
|
|
418
|
+
if (!element) return true;
|
|
419
|
+
return htmx.trigger(element, eventName, detail);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ========================================
|
|
423
|
+
// ELEMENT LIFECYCLE
|
|
424
|
+
// ========================================
|
|
425
|
+
|
|
426
|
+
function initializeElement(element) {
|
|
427
|
+
if (element._htmx?.wsInitialized) return;
|
|
428
|
+
|
|
429
|
+
let connectUrl = getWsAttribute(element, 'connect');
|
|
430
|
+
if (!connectUrl) return;
|
|
431
|
+
|
|
432
|
+
element._htmx = element._htmx || {};
|
|
433
|
+
element._htmx.wsInitialized = true;
|
|
434
|
+
|
|
435
|
+
let config = getConfig();
|
|
436
|
+
let triggerSpec = api.attributeValue(element, 'hx-trigger');
|
|
437
|
+
|
|
438
|
+
if (!triggerSpec && config.autoConnect === true) {
|
|
439
|
+
// Auto-connect on element initialization
|
|
440
|
+
getOrCreateConnection(connectUrl, element);
|
|
441
|
+
element._htmx = element._htmx || {};
|
|
442
|
+
element._htmx.wsUrl = connectUrl;
|
|
443
|
+
} else if (triggerSpec) {
|
|
444
|
+
// Connect based on trigger
|
|
445
|
+
let specs = api.parseTriggerSpecs(triggerSpec);
|
|
446
|
+
if (specs.length > 0) {
|
|
447
|
+
let spec = specs[0];
|
|
448
|
+
if (spec.name === 'load') {
|
|
449
|
+
getOrCreateConnection(connectUrl, element);
|
|
450
|
+
element._htmx = element._htmx || {};
|
|
451
|
+
element._htmx.wsUrl = connectUrl;
|
|
452
|
+
} else {
|
|
453
|
+
// Set up event listener for other triggers
|
|
454
|
+
element.addEventListener(spec.name, () => {
|
|
455
|
+
if (!element._htmx?.wsUrl) {
|
|
456
|
+
getOrCreateConnection(connectUrl, element);
|
|
457
|
+
element._htmx = element._htmx || {};
|
|
458
|
+
element._htmx.wsUrl = connectUrl;
|
|
459
|
+
}
|
|
460
|
+
}, { once: true });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function initializeSendElement(element) {
|
|
467
|
+
if (element._htmx?.wsSendInitialized) return;
|
|
468
|
+
|
|
469
|
+
let sendUrl = getWsAttribute(element, 'send');
|
|
470
|
+
let triggerSpec = api.attributeValue(element, 'hx-trigger');
|
|
471
|
+
|
|
472
|
+
if (!triggerSpec) {
|
|
473
|
+
// Default trigger based on element type
|
|
474
|
+
triggerSpec = element.matches('form') ? 'submit' :
|
|
475
|
+
element.matches('input:not([type=button]),select,textarea') ? 'change' :
|
|
476
|
+
'click';
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
let specs = api.parseTriggerSpecs(triggerSpec);
|
|
480
|
+
if (specs.length > 0) {
|
|
481
|
+
let spec = specs[0];
|
|
482
|
+
|
|
483
|
+
let handler = (evt) => {
|
|
484
|
+
// Prevent default for forms
|
|
485
|
+
if (element.matches('form') && evt.type === 'submit') {
|
|
486
|
+
evt.preventDefault();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// If this element has its own URL, ensure connection exists
|
|
490
|
+
if (sendUrl) {
|
|
491
|
+
if (!element._htmx?.wsUrl) {
|
|
492
|
+
getOrCreateConnection(sendUrl, element);
|
|
493
|
+
element._htmx = element._htmx || {};
|
|
494
|
+
element._htmx.wsUrl = sendUrl;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
sendMessage(element, evt);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
element.addEventListener(spec.name, handler);
|
|
502
|
+
element._htmx = element._htmx || {};
|
|
503
|
+
element._htmx.wsSendInitialized = true;
|
|
504
|
+
element._htmx.wsSendHandler = handler;
|
|
505
|
+
element._htmx.wsSendEvent = spec.name;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function cleanupElement(element) {
|
|
510
|
+
if (element._htmx?.wsUrl) {
|
|
511
|
+
decrementRef(element._htmx.wsUrl, element);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (element._htmx?.wsSendHandler) {
|
|
515
|
+
element.removeEventListener(element._htmx.wsSendEvent, element._htmx.wsSendHandler);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ========================================
|
|
520
|
+
// BACKWARD COMPATIBILITY
|
|
521
|
+
// ========================================
|
|
522
|
+
|
|
523
|
+
function checkLegacyAttributes(element) {
|
|
524
|
+
// Check for old ws-connect / ws-send attributes
|
|
525
|
+
if (element.hasAttribute('ws-connect') || element.hasAttribute('ws-send')) {
|
|
526
|
+
console.warn('HTMX WebSocket: Legacy attributes ws-connect and ws-send are deprecated. Please use hx-ws:connect/hx-ws-connect and hx-ws:send/hx-ws-send instead.');
|
|
527
|
+
|
|
528
|
+
// Map legacy attributes to new ones (prefer hyphen variant for broader compatibility)
|
|
529
|
+
if (element.hasAttribute('ws-connect')) {
|
|
530
|
+
let url = element.getAttribute('ws-connect');
|
|
531
|
+
if (!element.hasAttribute('hx-ws-connect')) {
|
|
532
|
+
element.setAttribute('hx-ws-connect', url);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (element.hasAttribute('ws-send')) {
|
|
537
|
+
if (!element.hasAttribute('hx-ws-send')) {
|
|
538
|
+
element.setAttribute('hx-ws-send', '');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ========================================
|
|
545
|
+
// EXTENSION REGISTRATION
|
|
546
|
+
// ========================================
|
|
547
|
+
|
|
548
|
+
htmx.registerExtension('ws', {
|
|
549
|
+
init: (internalAPI) => {
|
|
550
|
+
api = internalAPI;
|
|
551
|
+
|
|
552
|
+
// Initialize default config if not set
|
|
553
|
+
if (!htmx.config.websockets) {
|
|
554
|
+
htmx.config.websockets = {};
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
htmx_after_process: (element) => {
|
|
559
|
+
const processNode = (node) => {
|
|
560
|
+
// Check for legacy attributes
|
|
561
|
+
checkLegacyAttributes(node);
|
|
562
|
+
|
|
563
|
+
// Initialize WebSocket connection elements (check both variants)
|
|
564
|
+
if (hasWsAttribute(node, 'connect')) {
|
|
565
|
+
initializeElement(node);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Initialize send elements (check both variants)
|
|
569
|
+
if (hasWsAttribute(node, 'send')) {
|
|
570
|
+
initializeSendElement(node);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// Process the element itself
|
|
575
|
+
processNode(element);
|
|
576
|
+
|
|
577
|
+
// Process descendants
|
|
578
|
+
element.querySelectorAll('[hx-ws\\:connect], [hx-ws-connect], [hx-ws\\:send], [hx-ws-send], [hx-ws], [ws-connect], [ws-send]').forEach(processNode);
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
htmx_before_cleanup: (element) => {
|
|
582
|
+
cleanupElement(element);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Expose registry for testing
|
|
587
|
+
if (typeof window !== 'undefined' && window.htmx) {
|
|
588
|
+
window.htmx.ext = window.htmx.ext || {};
|
|
589
|
+
window.htmx.ext.ws = {
|
|
590
|
+
getRegistry: () => ({
|
|
591
|
+
clear: () => {
|
|
592
|
+
let entries = Array.from(connectionRegistry.values());
|
|
593
|
+
connectionRegistry.clear(); // Clear first to prevent reconnects
|
|
594
|
+
|
|
595
|
+
entries.forEach(entry => {
|
|
596
|
+
entry.refCount = 0; // Prevent pending timeouts from reconnecting
|
|
597
|
+
if (entry.reconnectTimer) {
|
|
598
|
+
clearTimeout(entry.reconnectTimer);
|
|
599
|
+
}
|
|
600
|
+
if (entry.socket) {
|
|
601
|
+
// Remove listeners if possible or just close
|
|
602
|
+
entry.socket.close();
|
|
603
|
+
}
|
|
604
|
+
entry.elements.clear();
|
|
605
|
+
entry.pendingRequests.clear();
|
|
606
|
+
});
|
|
607
|
+
},
|
|
608
|
+
get: (key) => connectionRegistry.get(key),
|
|
609
|
+
has: (key) => connectionRegistry.has(key),
|
|
610
|
+
size: connectionRegistry.size
|
|
611
|
+
})
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{let e;function t(t,n){let r=e.attributeValue(t,"hx-ws:"+n);if(null!=r)return r;let s=e.attributeValue(t,"hx-ws-"+n);if(null!=s)return s;if("send"===n){let n=e.attributeValue(t,"hx-ws");if(null!=n)return n}return null}function n(e,n){let r=t(e,n);return null!=r}function r(){return{reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectJitter:!0,autoConnect:!1,pauseInBackground:!0,...htmx.config.websockets||{}}}const s=new Map;function o(e,t){if(s.has(e)){let n=s.get(e);return n.refCount++,n.elements.add(t),n}let n={socket:null,refCount:1,elements:new Set([t]),reconnectAttempts:0,reconnectTimer:null,pendingRequests:new Map};return s.set(e,n),c(e,n),n}function c(t,n){let o=n.elements.values().next().value;if(!o||i(o,"htmx:before:ws:connect",{url:t})){if(n.socket){let e=n.socket;n.socket=null,e.onopen=null,e.onmessage=null,e.onclose=null,e.onerror=null;try{e.readyState!==WebSocket.OPEN&&e.readyState!==WebSocket.CONNECTING||e.close()}catch(e){}}try{n.socket=new WebSocket(t),n.socket.addEventListener("open",()=>{o&&i(o,"htmx:after:ws:connect",{url:t,socket:n.socket})}),n.socket.addEventListener("message",t=>{!function(t,n){let r;try{r=JSON.parse(n.data)}catch(e){let r=t.elements.values().next().value;return void(r&&i(r,"htmx:wsUnknownMessage",{data:n.data}))}r.channel=r.channel||"ui",r.format=r.format||"html";let s=null;r.request_id&&t.pendingRequests.has(r.request_id)?(s=t.pendingRequests.get(r.request_id).element,t.pendingRequests.delete(r.request_id)):s=t.elements.values().next().value;if(!i(s,"htmx:before:ws:message",{envelope:r,element:s}))return;"ui"===r.channel&&"html"===r.format?function(t,n){let r=(new DOMParser).parseFromString(n.payload,"text/html"),s=r.querySelectorAll("hx-partial");if(0===s.length){let r=function(t,n){if(n)return"this"===n?t:document.querySelector(n);let r=e.attributeValue(t,"hx-target");if(r)return"this"===r?t:document.querySelector(r);return t}(t,n.target);return void(r&&l(r,n.payload,t,n.swap))}s.forEach(e=>{let n=e.getAttribute("id");if(!n)return;let r=document.getElementById(n);r&&l(r,e.innerHTML,t)})}(s,r):!r.channel||"audio"!==r.channel&&"json"!==r.channel&&"binary"!==r.channel?i(s,"htmx:wsUnknownMessage",{...r,element:s}):i(s,"htmx:wsMessage",{...r,element:s});i(s,"htmx:after:ws:message",{envelope:r,element:s})}(n,t)}),n.socket.addEventListener("close",e=>{if(e.target!==n.socket)return;if(o&&i(o,"htmx:ws:close",{url:t}),!s.has(t))return;r().reconnect&&n.refCount>0?function(e,t){let n=r(),s=t.reconnectAttempts;t.reconnectAttempts++;let o=Math.min((n.reconnectDelay||1e3)*Math.pow(2,s),n.reconnectMaxDelay||3e4);n.reconnectJitter&&(o*=.75+.5*Math.random());t.reconnectTimer=setTimeout(()=>{if(t.refCount>0){let n=t.elements.values().next().value;n&&i(n,"htmx:ws:reconnect",{url:e,attempts:s}),c(e,t)}},o)}(t,n):s.delete(t)}),n.socket.addEventListener("error",e=>{o&&i(o,"htmx:ws:error",{url:t,error:e})})}catch(e){o&&i(o,"htmx:ws:error",{url:t,error:e})}}}function a(n,r){let o=t(n,"send");if(!o){let e=htmx.config.prefix||"",r=n.closest("["+e+"hx-ws\\:connect],["+e+"hx-ws-connect]");r&&(o=t(r,"connect"))}if(!o)return void console.error("No WebSocket connection found for hx-ws:send element",n);let c=s.get(o);if(!c||!c.socket||c.socket.readyState!==WebSocket.OPEN)return void i(n,"htmx:wsSendError",{url:o,error:"Connection not open"});let a=n.form||n.closest("form"),l=e.collectFormData(n,a,r.submitter);e.handleHxVals(n,l);let u={};for(let[e,t]of l)u[e]=t;let m="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){let t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)}),x={type:"request",request_id:m,event:r.type,values:u,path:o};n.id&&(x.id=n.id);let h={message:x,element:n,url:o};if(i(n,"htmx:before:ws:send",h))try{c.socket.send(JSON.stringify(h.message)),c.pendingRequests.set(m,{element:n,timestamp:Date.now()}),i(n,"htmx:after:ws:send",{message:h.message,url:o})}catch(e){i(n,"htmx:wsSendError",{url:o,error:e})}}function l(t,n,r,s){let o=(s||e.attributeValue(r,"hx-swap")||htmx.config.defaultSwap).split(" ")[0];switch(o=function(e){return"before"===e?"beforebegin":"after"===e?"afterend":"prepend"===e?"afterbegin":"append"===e?"beforeend":e}(o),o){case"innerHTML":default:t.innerHTML=n;break;case"outerHTML":t.outerHTML=n;break;case"beforebegin":t.insertAdjacentHTML("beforebegin",n);break;case"afterbegin":t.insertAdjacentHTML("afterbegin",n);break;case"beforeend":t.insertAdjacentHTML("beforeend",n);break;case"afterend":t.insertAdjacentHTML("afterend",n);break;case"delete":t.remove();case"none":}htmx.process(t)}function i(e,t,n={}){return!e||htmx.trigger(e,t,n)}function u(e){e._htmx?.wsUrl&&function(e,t){if(!s.has(e))return;let n=s.get(e);n.elements.delete(t),n.refCount--,n.refCount<=0&&(n.reconnectTimer&&clearTimeout(n.reconnectTimer),n.socket&&n.socket.readyState===WebSocket.OPEN&&n.socket.close(),s.delete(e))}(e._htmx.wsUrl,e),e._htmx?.wsSendHandler&&e.removeEventListener(e._htmx.wsSendEvent,e._htmx.wsSendHandler)}htmx.registerExtension("ws",{init:t=>{e=t,htmx.config.websockets||(htmx.config.websockets={})},htmx_after_process:s=>{const c=s=>{!function(e){if(e.hasAttribute("ws-connect")||e.hasAttribute("ws-send")){if(console.warn("HTMX WebSocket: Legacy attributes ws-connect and ws-send are deprecated. Please use hx-ws:connect/hx-ws-connect and hx-ws:send/hx-ws-send instead."),e.hasAttribute("ws-connect")){let t=e.getAttribute("ws-connect");e.hasAttribute("hx-ws-connect")||e.setAttribute("hx-ws-connect",t)}e.hasAttribute("ws-send")&&(e.hasAttribute("hx-ws-send")||e.setAttribute("hx-ws-send",""))}}(s),n(s,"connect")&&function(n){if(n._htmx?.wsInitialized)return;let s=t(n,"connect");if(!s)return;n._htmx=n._htmx||{},n._htmx.wsInitialized=!0;let c=r(),a=e.attributeValue(n,"hx-trigger");if(a||!0!==c.autoConnect){if(a){let t=e.parseTriggerSpecs(a);if(t.length>0){let e=t[0];"load"===e.name?(o(s,n),n._htmx=n._htmx||{},n._htmx.wsUrl=s):n.addEventListener(e.name,()=>{n._htmx?.wsUrl||(o(s,n),n._htmx=n._htmx||{},n._htmx.wsUrl=s)},{once:!0})}}}else o(s,n),n._htmx=n._htmx||{},n._htmx.wsUrl=s}(s),n(s,"send")&&function(n){if(n._htmx?.wsSendInitialized)return;let r=t(n,"send"),s=e.attributeValue(n,"hx-trigger");s||(s=n.matches("form")?"submit":n.matches("input:not([type=button]),select,textarea")?"change":"click");let c=e.parseTriggerSpecs(s);if(c.length>0){let e=c[0],t=e=>{n.matches("form")&&"submit"===e.type&&e.preventDefault(),r&&(n._htmx?.wsUrl||(o(r,n),n._htmx=n._htmx||{},n._htmx.wsUrl=r)),a(n,e)};n.addEventListener(e.name,t),n._htmx=n._htmx||{},n._htmx.wsSendInitialized=!0,n._htmx.wsSendHandler=t,n._htmx.wsSendEvent=e.name}}(s)};c(s),s.querySelectorAll("[hx-ws\\:connect], [hx-ws-connect], [hx-ws\\:send], [hx-ws-send], [hx-ws], [ws-connect], [ws-send]").forEach(c)},htmx_before_cleanup:e=>{u(e)}}),"undefined"!=typeof window&&window.htmx&&(window.htmx.ext=window.htmx.ext||{},window.htmx.ext.ws={getRegistry:()=>({clear:()=>{let e=Array.from(s.values());s.clear(),e.forEach(e=>{e.refCount=0,e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.socket&&e.socket.close(),e.elements.clear(),e.pendingRequests.clear()})},get:e=>s.get(e),has:e=>s.has(e),size:s.size})})})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["api","getWsAttribute","element","attrName","colonValue","attributeValue","hyphenValue","plainValue","hasWsAttribute","value","getConfig","reconnect","reconnectDelay","reconnectMaxDelay","reconnectJitter","autoConnect","pauseInBackground","htmx","config","websockets","connectionRegistry","Map","getOrCreateConnection","url","has","entry","get","refCount","elements","add","socket","Set","reconnectAttempts","reconnectTimer","pendingRequests","set","createWebSocket","firstElement","values","next","triggerEvent","oldSocket","onopen","onmessage","onclose","onerror","readyState","WebSocket","OPEN","CONNECTING","close","e","addEventListener","event","envelope","JSON","parse","data","channel","format","targetElement","request_id","delete","doc","DOMParser","parseFromString","payload","partials","querySelectorAll","length","target","envelopeTarget","document","querySelector","targetSelector","resolveTarget","swapContent","swap","forEach","partial","targetId","getAttribute","getElementById","innerHTML","handleHtmlMessage","handleMessage","attempts","delay","Math","min","pow","random","setTimeout","scheduleReconnect","error","sendMessage","prefix","ancestor","closest","console","form","body","collectFormData","submitter","handleHxVals","key","requestId","replace","c","r","toString","message","type","path","id","detail","send","stringify","timestamp","Date","now","content","sourceElement","envelopeSwap","style","defaultSwap","split","normalizeSwapStyle","outerHTML","insertAdjacentHTML","remove","process","eventName","trigger","cleanupElement","_htmx","wsUrl","clearTimeout","decrementRef","wsSendHandler","removeEventListener","wsSendEvent","registerExtension","init","internalAPI","htmx_after_process","processNode","node","hasAttribute","warn","setAttribute","checkLegacyAttributes","wsInitialized","connectUrl","triggerSpec","specs","parseTriggerSpecs","spec","name","once","initializeElement","wsSendInitialized","sendUrl","matches","handler","evt","preventDefault","initializeSendElement","htmx_before_cleanup","window","ext","ws","getRegistry","clear","entries","Array","from","size"],"sources":["dist/ext/hx-ws.js"],"mappings":"AAAA,MACI,IAAIA,EAGJ,SAASC,EAAeC,EAASC,GAE7B,IAAIC,EAAaJ,EAAIK,eAAeH,EAAS,SAAWC,GACxD,GAAIC,QAAiD,OAAOA,EAG5D,IAAIE,EAAcN,EAAIK,eAAeH,EAAS,SAAWC,GACzD,GAAIG,QAAmD,OAAOA,EAG9D,GAAiB,SAAbH,EAAqB,CACrB,IAAII,EAAaP,EAAIK,eAAeH,EAAS,SAC7C,GAAIK,QAAiD,OAAOA,CAChE,CAEA,OAAO,IACX,CAGA,SAASC,EAAeN,EAASC,GAC7B,IAAIM,EAAQR,EAAeC,EAASC,GACpC,OAAOM,OACX,CAMA,SAASC,IASL,MAAO,CAPHC,WAAW,EACXC,eAAgB,IAChBC,kBAAmB,IACnBC,iBAAiB,EACjBC,aAAa,EACbC,mBAAmB,KAEGC,KAAKC,OAAOC,YAAc,CAAC,EACzD,CAMA,MAAMC,EAAqB,IAAIC,IAE/B,SAASC,EAAsBC,EAAKrB,GAChC,GAAIkB,EAAmBI,IAAID,GAAM,CAC7B,IAAIE,EAAQL,EAAmBM,IAAIH,GAGnC,OAFAE,EAAME,WACNF,EAAMG,SAASC,IAAI3B,GACZuB,CACX,CAEA,IAAIA,EAAQ,CACRK,OAAQ,KACRH,SAAU,EACVC,SAAU,IAAIG,IAAI,CAAC7B,IACnB8B,kBAAmB,EACnBC,eAAgB,KAChBC,gBAAiB,IAAIb,KAKzB,OAFAD,EAAmBe,IAAIZ,EAAKE,GAC5BW,EAAgBb,EAAKE,GACdA,CACX,CAEA,SAASW,EAAgBb,EAAKE,GAC1B,IAAIY,EAAeZ,EAAMG,SAASU,SAASC,OAAO9B,MAClD,IAAI4B,GACKG,EAAaH,EAAc,yBAA0B,CAAEd,QADhE,CAOA,GAAIE,EAAMK,OAAQ,CACd,IAAIW,EAAYhB,EAAMK,OACtBL,EAAMK,OAAS,KAEfW,EAAUC,OAAS,KACnBD,EAAUE,UAAY,KACtBF,EAAUG,QAAU,KACpBH,EAAUI,QAAU,KAEpB,IACQJ,EAAUK,aAAeC,UAAUC,MAAQP,EAAUK,aAAeC,UAAUE,YAC9ER,EAAUS,OAElB,CAAE,MAAOC,GAAI,CACjB,CAEA,IACI1B,EAAMK,OAAS,IAAIiB,UAAUxB,GAE7BE,EAAMK,OAAOsB,iBAAiB,OAAQ,KAG9Bf,GACAG,EAAaH,EAAc,wBAAyB,CAAEd,MAAKO,OAAQL,EAAMK,WAIjFL,EAAMK,OAAOsB,iBAAiB,UAAYC,KAgKlD,SAAuB5B,EAAO4B,GAC1B,IAAIC,EACJ,IACIA,EAAWC,KAAKC,MAAMH,EAAMI,KAChC,CAAE,MAAON,GAEL,IAAId,EAAeZ,EAAMG,SAASU,SAASC,OAAO9B,MAIlD,YAHI4B,GACAG,EAAaH,EAAc,wBAAyB,CAAEoB,KAAMJ,EAAMI,OAG1E,CAGAH,EAASI,QAAUJ,EAASI,SAAW,KACvCJ,EAASK,OAASL,EAASK,QAAU,OAGrC,IAAIC,EAAgB,KAChBN,EAASO,YAAcpC,EAAMS,gBAAgBV,IAAI8B,EAASO,aAC1DD,EAAgBnC,EAAMS,gBAAgBR,IAAI4B,EAASO,YAAY3D,QAC/DuB,EAAMS,gBAAgB4B,OAAOR,EAASO,aAGtCD,EAAgBnC,EAAMG,SAASU,SAASC,OAAO9B,MAInD,IAAK+B,EAAaoB,EAAe,yBAA0B,CAAEN,WAAUpD,QAAS0D,IAC5E,OAIqB,OAArBN,EAASI,SAAwC,SAApBJ,EAASK,OAiB9C,SAA2BzD,EAASoD,GAChC,IACIS,GADS,IAAIC,WACAC,gBAAgBX,EAASY,QAAS,aAG/CC,EAAWJ,EAAIK,iBAAiB,cAEpC,GAAwB,IAApBD,EAASE,OAAc,CAEvB,IAAIC,EAkBZ,SAAuBpE,EAASqE,GAC5B,GAAIA,EACA,MAAuB,SAAnBA,EACOrE,EAEJsE,SAASC,cAAcF,GAElC,IAAIG,EAAiB1E,EAAIK,eAAeH,EAAS,aACjD,GAAIwE,EACA,MAAuB,SAAnBA,EACOxE,EAEJsE,SAASC,cAAcC,GAElC,OAAOxE,CACX,CAjCqByE,CAAczE,EAASoD,EAASgB,QAI7C,YAHIA,GACAM,EAAYN,EAAQhB,EAASY,QAAShE,EAASoD,EAASuB,MAGhE,CAEAV,EAASW,QAAQC,IACb,IAAIC,EAAWD,EAAQE,aAAa,MACpC,IAAKD,EAAU,OAEf,IAAIV,EAASE,SAASU,eAAeF,GAChCV,GAELM,EAAYN,EAAQS,EAAQI,UAAWjF,IAE/C,CAzCQkF,CAAkBxB,EAAeN,IAC1BA,EAASI,SAAiC,UAArBJ,EAASI,SAA4C,SAArBJ,EAASI,SAA2C,WAArBJ,EAASI,QAKpGlB,EAAaoB,EAAe,wBAAyB,IAAKN,EAAUpD,QAAS0D,IAH7EpB,EAAaoB,EAAe,iBAAkB,IAAKN,EAAUpD,QAAS0D,IAM1EpB,EAAaoB,EAAe,wBAAyB,CAAEN,WAAUpD,QAAS0D,GAC9E,CA3MYyB,CAAc5D,EAAO4B,KAGzB5B,EAAMK,OAAOsB,iBAAiB,QAAUC,IAEpC,GAAIA,EAAMiB,SAAW7C,EAAMK,OAAQ,OAOnC,GALIO,GACAG,EAAaH,EAAc,gBAAiB,CAAEd,SAI7CH,EAAmBI,IAAID,GAAM,OAErBb,IACFC,WAAac,EAAME,SAAW,EAmBrD,SAA2BJ,EAAKE,GAC5B,IAAIP,EAASR,IAGT4E,EAAW7D,EAAMO,kBACrBP,EAAMO,oBAEN,IAAIuD,EAAQC,KAAKC,KACZvE,EAAON,gBAAkB,KAAQ4E,KAAKE,IAAI,EAAGJ,GAC9CpE,EAAOL,mBAAqB,KAG5BK,EAAOJ,kBACPyE,GAAiB,IAAuB,GAAhBC,KAAKG,UAGjClE,EAAMQ,eAAiB2D,WAAW,KAC9B,GAAInE,EAAME,SAAW,EAAG,CACpB,IAAIU,EAAeZ,EAAMG,SAASU,SAASC,OAAO9B,MAC9C4B,GACAG,EAAaH,EAAc,oBAAqB,CAAEd,MAAK+D,aAE3DlD,EAAgBb,EAAKE,EACzB,GACD8D,EACP,CA3CgBM,CAAkBtE,EAAKE,GAEvBL,EAAmB0C,OAAOvC,KAIlCE,EAAMK,OAAOsB,iBAAiB,QAAU0C,IAChCzD,GACAG,EAAaH,EAAc,gBAAiB,CAAEd,MAAKuE,WAG/D,CAAE,MAAOA,GACDzD,GACAG,EAAaH,EAAc,gBAAiB,CAAEd,MAAKuE,SAE3D,CA9DA,CA+DJ,CAmDA,SAASC,EAAY7F,EAASmD,GAE1B,IAAI9B,EAAMtB,EAAeC,EAAS,QAClC,IAAKqB,EAAK,CAEN,IAAIyE,EAAS/E,KAAKC,OAAO8E,QAAU,GAC/BC,EAAW/F,EAAQgG,QAAQ,IAAMF,EAAS,qBAAuBA,EAAS,kBAC1EC,IACA1E,EAAMtB,EAAegG,EAAU,WAEvC,CAEA,IAAK1E,EAED,YADA4E,QAAQL,MAAM,uDAAwD5F,GAI1E,IAAIuB,EAAQL,EAAmBM,IAAIH,GACnC,IAAKE,IAAUA,EAAMK,QAAUL,EAAMK,OAAOgB,aAAeC,UAAUC,KAEjE,YADAR,EAAatC,EAAS,mBAAoB,CAAEqB,MAAKuE,MAAO,wBAK5D,IAAIM,EAAOlG,EAAQkG,MAAQlG,EAAQgG,QAAQ,QACvCG,EAAOrG,EAAIsG,gBAAgBpG,EAASkG,EAAM/C,EAAMkD,WACpDvG,EAAIwG,aAAatG,EAASmG,GAE1B,IAAI/D,EAAS,CAAC,EACd,IAAK,IAAKmE,EAAKhG,KAAU4F,EACrB/D,EAAOmE,GAAOhG,EAGlB,IAAIiG,EAgCG,uCAAuCC,QAAQ,QAAS,SAASC,GACpE,IAAIC,EAAoB,GAAhBrB,KAAKG,SAAgB,EAE7B,OADc,MAANiB,EAAYC,EAAS,EAAJA,EAAU,GAC1BC,SAAS,GACtB,GAnCIC,EAAU,CACVC,KAAM,UACNnD,WAAY6C,EACZrD,MAAOA,EAAM2D,KACb1E,OAAQA,EACR2E,KAAM1F,GAGNrB,EAAQgH,KACRH,EAAQG,GAAKhH,EAAQgH,IAIzB,IAAIC,EAAS,CAAEJ,UAAS7G,UAASqB,OACjC,GAAKiB,EAAatC,EAAS,sBAAuBiH,GAIlD,IACI1F,EAAMK,OAAOsF,KAAK7D,KAAK8D,UAAUF,EAAOJ,UAGxCtF,EAAMS,gBAAgBC,IAAIuE,EAAW,CAAExG,UAASoH,UAAWC,KAAKC,QAEhEhF,EAAatC,EAAS,qBAAsB,CAAE6G,QAASI,EAAOJ,QAASxF,OAC3E,CAAE,MAAOuE,GACLtD,EAAatC,EAAS,mBAAoB,CAAEqB,MAAKuE,SACrD,CACJ,CA4GA,SAASlB,EAAYN,EAAQmD,EAASC,EAAeC,GACjD,IAGIC,GAHYD,GAAgB3H,EAAIK,eAAeqH,EAAe,YAAczG,KAAKC,OAAO2G,aAGtEC,MAAM,KAAK,GAMjC,OAHAF,EAoCJ,SAA4BA,GACxB,MAAiB,WAAVA,EAAqB,cACX,UAAVA,EAAoB,WACV,YAAVA,EAAsB,aACZ,WAAVA,EAAqB,YAAcA,CAC9C,CAzCYG,CAAmBH,GAGnBA,GACJ,IAAK,YAwBL,QACItD,EAAOa,UAAYsC,QAtBvB,IAAK,YACDnD,EAAO0D,UAAYP,EACnB,MACJ,IAAK,cACDnD,EAAO2D,mBAAmB,cAAeR,GACzC,MACJ,IAAK,aACDnD,EAAO2D,mBAAmB,aAAcR,GACxC,MACJ,IAAK,YACDnD,EAAO2D,mBAAmB,YAAaR,GACvC,MACJ,IAAK,WACDnD,EAAO2D,mBAAmB,WAAYR,GACtC,MACJ,IAAK,SACDnD,EAAO4D,SAEX,IAAK,QAQTjH,KAAKkH,QAAQ7D,EACjB,CAaA,SAAS9B,EAAatC,EAASkI,EAAWjB,EAAS,CAAC,GAChD,OAAKjH,GACEe,KAAKoH,QAAQnI,EAASkI,EAAWjB,EAC5C,CAyFA,SAASmB,EAAepI,GAChBA,EAAQqI,OAAOC,OAnVvB,SAAsBjH,EAAKrB,GACvB,IAAKkB,EAAmBI,IAAID,GAAM,OAElC,IAAIE,EAAQL,EAAmBM,IAAIH,GACnCE,EAAMG,SAASkC,OAAO5D,GACtBuB,EAAME,WAEFF,EAAME,UAAY,IACdF,EAAMQ,gBACNwG,aAAahH,EAAMQ,gBAEnBR,EAAMK,QAAUL,EAAMK,OAAOgB,aAAeC,UAAUC,MACtDvB,EAAMK,OAAOoB,QAEjB9B,EAAmB0C,OAAOvC,GAElC,CAoUQmH,CAAaxI,EAAQqI,MAAMC,MAAOtI,GAGlCA,EAAQqI,OAAOI,eACfzI,EAAQ0I,oBAAoB1I,EAAQqI,MAAMM,YAAa3I,EAAQqI,MAAMI,cAE7E,CA+BA1H,KAAK6H,kBAAkB,KAAM,CACzBC,KAAOC,IACHhJ,EAAMgJ,EAGD/H,KAAKC,OAAOC,aACbF,KAAKC,OAAOC,WAAa,CAAC,IAIlC8H,mBAAqB/I,IACjB,MAAMgJ,EAAeC,KApC7B,SAA+BjJ,GAE3B,GAAIA,EAAQkJ,aAAa,eAAiBlJ,EAAQkJ,aAAa,WAAY,CAIvE,GAHAjD,QAAQkD,KAAK,sJAGTnJ,EAAQkJ,aAAa,cAAe,CACpC,IAAI7H,EAAMrB,EAAQ+E,aAAa,cAC1B/E,EAAQkJ,aAAa,kBACtBlJ,EAAQoJ,aAAa,gBAAiB/H,EAE9C,CAEIrB,EAAQkJ,aAAa,aAChBlJ,EAAQkJ,aAAa,eACtBlJ,EAAQoJ,aAAa,aAAc,IAG/C,CACJ,CAmBYC,CAAsBJ,GAGlB3I,EAAe2I,EAAM,YA1IrC,SAA2BjJ,GACvB,GAAIA,EAAQqI,OAAOiB,cAAe,OAElC,IAAIC,EAAaxJ,EAAeC,EAAS,WACzC,IAAKuJ,EAAY,OAEjBvJ,EAAQqI,MAAQrI,EAAQqI,OAAS,CAAC,EAClCrI,EAAQqI,MAAMiB,eAAgB,EAE9B,IAAItI,EAASR,IACTgJ,EAAc1J,EAAIK,eAAeH,EAAS,cAE9C,GAAKwJ,IAAsC,IAAvBxI,EAAOH,aAKpB,GAAI2I,EAAa,CAEpB,IAAIC,EAAQ3J,EAAI4J,kBAAkBF,GAClC,GAAIC,EAAMtF,OAAS,EAAG,CAClB,IAAIwF,EAAOF,EAAM,GACC,SAAdE,EAAKC,MACLxI,EAAsBmI,EAAYvJ,GAClCA,EAAQqI,MAAQrI,EAAQqI,OAAS,CAAC,EAClCrI,EAAQqI,MAAMC,MAAQiB,GAGtBvJ,EAAQkD,iBAAiByG,EAAKC,KAAM,KAC3B5J,EAAQqI,OAAOC,QAChBlH,EAAsBmI,EAAYvJ,GAClCA,EAAQqI,MAAQrI,EAAQqI,OAAS,CAAC,EAClCrI,EAAQqI,MAAMC,MAAQiB,IAE3B,CAAEM,MAAM,GAEnB,CACJ,OAvBIzI,EAAsBmI,EAAYvJ,GAClCA,EAAQqI,MAAQrI,EAAQqI,OAAS,CAAC,EAClCrI,EAAQqI,MAAMC,MAAQiB,CAsB9B,CAqGgBO,CAAkBb,GAIlB3I,EAAe2I,EAAM,SAvGrC,SAA+BjJ,GAC3B,GAAIA,EAAQqI,OAAO0B,kBAAmB,OAEtC,IAAIC,EAAUjK,EAAeC,EAAS,QAClCwJ,EAAc1J,EAAIK,eAAeH,EAAS,cAEzCwJ,IAEDA,EAAcxJ,EAAQiK,QAAQ,QAAU,SAC3BjK,EAAQiK,QAAQ,4CAA8C,SAC9D,SAGjB,IAAIR,EAAQ3J,EAAI4J,kBAAkBF,GAClC,GAAIC,EAAMtF,OAAS,EAAG,CAClB,IAAIwF,EAAOF,EAAM,GAEbS,EAAWC,IAEPnK,EAAQiK,QAAQ,SAAwB,WAAbE,EAAIrD,MAC/BqD,EAAIC,iBAIJJ,IACKhK,EAAQqI,OAAOC,QAChBlH,EAAsB4I,EAAShK,GAC/BA,EAAQqI,MAAQrI,EAAQqI,OAAS,CAAC,EAClCrI,EAAQqI,MAAMC,MAAQ0B,IAI9BnE,EAAY7F,EAASmK,IAGzBnK,EAAQkD,iBAAiByG,EAAKC,KAAMM,GACpClK,EAAQqI,MAAQrI,EAAQqI,OAAS,CAAC,EAClCrI,EAAQqI,MAAM0B,mBAAoB,EAClC/J,EAAQqI,MAAMI,cAAgByB,EAC9BlK,EAAQqI,MAAMM,YAAcgB,EAAKC,IACrC,CACJ,CA+DgBS,CAAsBpB,IAK9BD,EAAYhJ,GAGRA,EAAQkE,iBAAiB,sGAAsGU,QAAQoE,IAG/IsB,oBAAsBtK,IAClBoI,EAAepI,MAKD,oBAAXuK,QAA0BA,OAAOxJ,OACxCwJ,OAAOxJ,KAAKyJ,IAAMD,OAAOxJ,KAAKyJ,KAAO,CAAC,EACtCD,OAAOxJ,KAAKyJ,IAAIC,GAAK,CACjBC,YAAa,KAAM,CACfC,MAAO,KACH,IAAIC,EAAUC,MAAMC,KAAK5J,EAAmBkB,UAC5ClB,EAAmByJ,QAEnBC,EAAQhG,QAAQrD,IACZA,EAAME,SAAW,EACbF,EAAMQ,gBACNwG,aAAahH,EAAMQ,gBAEnBR,EAAMK,QAENL,EAAMK,OAAOoB,QAEjBzB,EAAMG,SAASiJ,QACfpJ,EAAMS,gBAAgB2I,WAG9BnJ,IAAM+E,GAAQrF,EAAmBM,IAAI+E,GACrCjF,IAAMiF,GAAQrF,EAAmBI,IAAIiF,GACrCwE,KAAM7J,EAAmB6J,QAIxC,EArmBD","ignoreList":[]}
|