htmx.org 1.9.9 → 1.9.11

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/LICENSE CHANGED
@@ -1,25 +1,13 @@
1
- BSD 2-Clause License
2
-
3
- Copyright (c) 2020, Big Sky Software
4
- All rights reserved.
5
-
6
- Redistribution and use in source and binary forms, with or without
7
- modification, are permitted provided that the following conditions are met:
8
-
9
- 1. Redistributions of source code must retain the above copyright notice, this
10
- list of conditions and the following disclaimer.
11
-
12
- 2. Redistributions in binary form must reproduce the above copyright notice,
13
- this list of conditions and the following disclaimer in the documentation
14
- and/or other materials provided with the distribution.
15
-
16
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1
+ Zero-Clause BSD
2
+ =============
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for
5
+ any purpose with or without fee is hereby granted.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
8
+ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
9
+ OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
10
+ FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
11
+ DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
12
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
13
+ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -33,7 +33,7 @@ By removing these arbitrary constraints htmx completes HTML as a
33
33
  ## quick start
34
34
 
35
35
  ```html
36
- <script src="https://unpkg.com/htmx.org@1.9.9"></script>
36
+ <script src="https://unpkg.com/htmx.org@1.9.11"></script>
37
37
  <!-- have a button POST a click via AJAX -->
38
38
  <button hx-post="/clicked" hx-swap="outerHTML">
39
39
  Click Me
@@ -0,0 +1,11 @@
1
+ htmx.defineExtension('path-params', {
2
+ onEvent: function(name, evt) {
3
+ if (name === "htmx:configRequest") {
4
+ evt.detail.path = evt.detail.path.replace(/{([^}]+)}/g, function (_, param) {
5
+ var val = evt.detail.parameters[param];
6
+ delete evt.detail.parameters[param];
7
+ return val === undefined ? "{" + param + "}" : encodeURIComponent(val);
8
+ })
9
+ }
10
+ }
11
+ });
package/dist/ext/sse.js CHANGED
@@ -5,7 +5,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
5
5
 
6
6
  */
7
7
 
8
- (function(){
8
+ (function() {
9
9
 
10
10
  /** @type {import("../htmx").HtmxInternalApi} */
11
11
  var api;
@@ -37,19 +37,21 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
37
37
  */
38
38
  onEvent: function(name, evt) {
39
39
 
40
+ var parent = evt.target || evt.detail.elt;
40
41
  switch (name) {
41
42
 
42
- // Try to remove remove an EventSource when elements are removed
43
- case "htmx:beforeCleanupElement":
44
- var internalData = api.getInternalData(evt.target)
45
- if (internalData.sseEventSource) {
46
- internalData.sseEventSource.close();
47
- }
48
- return;
43
+ case "htmx:beforeCleanupElement":
44
+ var internalData = api.getInternalData(parent)
45
+ // Try to remove remove an EventSource when elements are removed
46
+ if (internalData.sseEventSource) {
47
+ internalData.sseEventSource.close();
48
+ }
49
49
 
50
- // Try to create EventSources when elements are processed
51
- case "htmx:afterProcessNode":
52
- createEventSourceOnElement(evt.target);
50
+ return;
51
+
52
+ // Try to create EventSources when elements are processed
53
+ case "htmx:afterProcessNode":
54
+ ensureEventSourceOnElement(parent);
53
55
  }
54
56
  }
55
57
  });
@@ -66,8 +68,8 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
66
68
  * @param {string} url
67
69
  * @returns EventSource
68
70
  */
69
- function createEventSource(url) {
70
- return new EventSource(url, {withCredentials:true});
71
+ function createEventSource(url) {
72
+ return new EventSource(url, { withCredentials: true });
71
73
  }
72
74
 
73
75
  function splitOnWhitespace(trigger) {
@@ -90,7 +92,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
90
92
  function getLegacySSESwaps(elt) {
91
93
  var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
92
94
  var returnArr = [];
93
- if (legacySSEValue) {
95
+ if (legacySSEValue != null) {
94
96
  var values = splitOnWhitespace(legacySSEValue);
95
97
  for (var i = 0; i < values.length; i++) {
96
98
  var value = values[i].split(/:(.+)/);
@@ -103,65 +105,25 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
103
105
  }
104
106
 
105
107
  /**
106
- * createEventSourceOnElement creates a new EventSource connection on the provided element.
107
- * If a usable EventSource already exists, then it is returned. If not, then a new EventSource
108
- * is created and stored in the element's internalData.
108
+ * registerSSE looks for attributes that can contain sse events, right
109
+ * now hx-trigger and sse-swap and adds listeners based on these attributes too
110
+ * the closest event source
111
+ *
109
112
  * @param {HTMLElement} elt
110
- * @param {number} retryCount
111
- * @returns {EventSource | null}
112
113
  */
113
- function createEventSourceOnElement(elt, retryCount) {
114
-
115
- if (elt == null) {
116
- return null;
117
- }
118
-
119
- var internalData = api.getInternalData(elt);
120
-
121
- // get URL from element's attribute
122
- var sseURL = api.getAttributeValue(elt, "sse-connect");
123
-
124
-
125
- if (sseURL == undefined) {
126
- var legacyURL = getLegacySSEURL(elt)
127
- if (legacyURL) {
128
- sseURL = legacyURL;
129
- } else {
130
- return null;
131
- }
132
- }
133
-
134
- // Connect to the EventSource
135
- var source = htmx.createEventSource(sseURL);
136
- internalData.sseEventSource = source;
137
-
138
- // Create event handlers
139
- source.onerror = function (err) {
140
-
141
- // Log an error event
142
- api.triggerErrorEvent(elt, "htmx:sseError", {error:err, source:source});
143
-
144
- // If parent no longer exists in the document, then clean up this EventSource
145
- if (maybeCloseSSESource(elt)) {
146
- return;
114
+ function registerSSE(elt) {
115
+ // Add message handlers for every `sse-swap` attribute
116
+ queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function (child) {
117
+ // Find closest existing event source
118
+ var sourceElement = api.getClosestMatch(child, hasEventSource);
119
+ if (sourceElement == null) {
120
+ // api.triggerErrorEvent(elt, "htmx:noSSESourceError")
121
+ return null; // no eventsource in parentage, orphaned element
147
122
  }
148
123
 
149
- // Otherwise, try to reconnect the EventSource
150
- if (source.readyState === EventSource.CLOSED) {
151
- retryCount = retryCount || 0;
152
- var timeout = Math.random() * (2 ^ retryCount) * 500;
153
- window.setTimeout(function() {
154
- createEventSourceOnElement(elt, Math.min(7, retryCount+1));
155
- }, timeout);
156
- }
157
- };
158
-
159
- source.onopen = function (evt) {
160
- api.triggerEvent(elt, "htmx:sseOpen", {source: source});
161
- }
162
-
163
- // Add message handlers for every `sse-swap` attribute
164
- queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
124
+ // Set internalData and source
125
+ var internalData = api.getInternalData(sourceElement);
126
+ var source = internalData.sseEventSource;
165
127
 
166
128
  var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
167
129
  if (sseSwapAttr) {
@@ -170,29 +132,47 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
170
132
  var sseEventNames = getLegacySSESwaps(child);
171
133
  }
172
134
 
173
- for (var i = 0 ; i < sseEventNames.length ; i++) {
135
+ for (var i = 0; i < sseEventNames.length; i++) {
174
136
  var sseEventName = sseEventNames[i].trim();
175
137
  var listener = function(event) {
176
138
 
177
- // If the parent is missing then close SSE and remove listener
178
- if (maybeCloseSSESource(elt)) {
139
+ // If the source is missing then close SSE
140
+ if (maybeCloseSSESource(sourceElement)) {
141
+ return;
142
+ }
143
+
144
+ // If the body no longer contains the element, remove the listener
145
+ if (!api.bodyContains(child)) {
179
146
  source.removeEventListener(sseEventName, listener);
180
147
  return;
181
148
  }
182
149
 
183
150
  // swap the response into the DOM and trigger a notification
151
+ if(!api.triggerEvent(elt, "htmx:sseBeforeMessage", event)) {
152
+ return;
153
+ }
184
154
  swap(child, event.data);
185
155
  api.triggerEvent(elt, "htmx:sseMessage", event);
186
156
  };
187
157
 
188
158
  // Register the new listener
189
- api.getInternalData(elt).sseEventListener = listener;
159
+ api.getInternalData(child).sseEventListener = listener;
190
160
  source.addEventListener(sseEventName, listener);
191
161
  }
192
162
  });
193
163
 
194
164
  // Add message handlers for every `hx-trigger="sse:*"` attribute
195
165
  queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
166
+ // Find closest existing event source
167
+ var sourceElement = api.getClosestMatch(child, hasEventSource);
168
+ if (sourceElement == null) {
169
+ // api.triggerErrorEvent(elt, "htmx:noSSESourceError")
170
+ return null; // no eventsource in parentage, orphaned element
171
+ }
172
+
173
+ // Set internalData and source
174
+ var internalData = api.getInternalData(sourceElement);
175
+ var source = internalData.sseEventSource;
196
176
 
197
177
  var sseEventName = api.getAttributeValue(child, "hx-trigger");
198
178
  if (sseEventName == null) {
@@ -203,24 +183,87 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
203
183
  if (sseEventName.slice(0, 4) != "sse:") {
204
184
  return;
205
185
  }
186
+
187
+ // remove the sse: prefix from here on out
188
+ sseEventName = sseEventName.substr(4);
206
189
 
207
- var listener = function(event) {
190
+ var listener = function() {
191
+ if (maybeCloseSSESource(sourceElement)) {
192
+ return
193
+ }
208
194
 
209
- // If parent is missing, then close SSE and remove listener
210
- if (maybeCloseSSESource(elt)) {
195
+ if (!api.bodyContains(child)) {
211
196
  source.removeEventListener(sseEventName, listener);
212
- return;
213
197
  }
198
+ }
199
+ });
200
+ }
201
+
202
+ /**
203
+ * ensureEventSourceOnElement creates a new EventSource connection on the provided element.
204
+ * If a usable EventSource already exists, then it is returned. If not, then a new EventSource
205
+ * is created and stored in the element's internalData.
206
+ * @param {HTMLElement} elt
207
+ * @param {number} retryCount
208
+ * @returns {EventSource | null}
209
+ */
210
+ function ensureEventSourceOnElement(elt, retryCount) {
214
211
 
215
- // Trigger events to be handled by the rest of htmx
216
- htmx.trigger(child, sseEventName, event);
217
- htmx.trigger(child, "htmx:sseMessage", event);
212
+ if (elt == null) {
213
+ return null;
214
+ }
215
+
216
+ // handle extension source creation attribute
217
+ queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) {
218
+ var sseURL = api.getAttributeValue(child, "sse-connect");
219
+ if (sseURL == null) {
220
+ return;
221
+ }
222
+
223
+ ensureEventSource(child, sseURL, retryCount);
224
+ });
225
+
226
+ // handle legacy sse, remove for HTMX2
227
+ queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) {
228
+ var sseURL = getLegacySSEURL(child);
229
+ if (sseURL == null) {
230
+ return;
218
231
  }
219
232
 
220
- // Register the new listener
221
- api.getInternalData(elt).sseEventListener = listener;
222
- source.addEventListener(sseEventName.slice(4), listener);
233
+ ensureEventSource(child, sseURL, retryCount);
223
234
  });
235
+
236
+ registerSSE(elt);
237
+ }
238
+
239
+ function ensureEventSource(elt, url, retryCount) {
240
+ var source = htmx.createEventSource(url);
241
+
242
+ source.onerror = function(err) {
243
+
244
+ // Log an error event
245
+ api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source });
246
+
247
+ // If parent no longer exists in the document, then clean up this EventSource
248
+ if (maybeCloseSSESource(elt)) {
249
+ return;
250
+ }
251
+
252
+ // Otherwise, try to reconnect the EventSource
253
+ if (source.readyState === EventSource.CLOSED) {
254
+ retryCount = retryCount || 0;
255
+ var timeout = Math.random() * (2 ^ retryCount) * 500;
256
+ window.setTimeout(function() {
257
+ ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1));
258
+ }, timeout);
259
+ }
260
+ };
261
+
262
+ source.onopen = function(evt) {
263
+ api.triggerEvent(elt, "htmx:sseOpen", { source: source });
264
+ }
265
+
266
+ api.getInternalData(elt).sseEventSource = source;
224
267
  }
225
268
 
226
269
  /**
@@ -253,12 +296,12 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
253
296
  var result = [];
254
297
 
255
298
  // If the parent element also contains the requested attribute, then add it to the results too.
256
- if (api.hasAttribute(elt, attributeName) || api.hasAttribute(elt, "hx-sse")) {
299
+ if (api.hasAttribute(elt, attributeName)) {
257
300
  result.push(elt);
258
301
  }
259
302
 
260
303
  // Search all child nodes that match the requested attribute
261
- elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "], [hx-sse], [data-hx-sse]").forEach(function(node) {
304
+ elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) {
262
305
  result.push(node);
263
306
  });
264
307
 
@@ -281,7 +324,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
281
324
 
282
325
  api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
283
326
 
284
- settleInfo.elts.forEach(function (elt) {
327
+ settleInfo.elts.forEach(function(elt) {
285
328
  if (elt.classList) {
286
329
  elt.classList.add(htmx.config.settlingClass);
287
330
  }
@@ -306,11 +349,11 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
306
349
  function doSettle(settleInfo) {
307
350
 
308
351
  return function() {
309
- settleInfo.tasks.forEach(function (task) {
352
+ settleInfo.tasks.forEach(function(task) {
310
353
  task.call();
311
354
  });
312
355
 
313
- settleInfo.elts.forEach(function (elt) {
356
+ settleInfo.elts.forEach(function(elt) {
314
357
  if (elt.classList) {
315
358
  elt.classList.remove(htmx.config.settlingClass);
316
359
  }
@@ -319,4 +362,8 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
319
362
  }
320
363
  }
321
364
 
322
- })();
365
+ function hasEventSource(node) {
366
+ return api.getInternalData(node).sseEventSource != null;
367
+ }
368
+
369
+ })();
package/dist/ext/ws.js CHANGED
@@ -38,13 +38,14 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
38
38
  * @param {Event} evt
39
39
  */
40
40
  onEvent: function (name, evt) {
41
+ var parent = evt.target || evt.detail.elt;
41
42
 
42
43
  switch (name) {
43
44
 
44
45
  // Try to close the socket when elements are removed
45
46
  case "htmx:beforeCleanupElement":
46
47
 
47
- var internalData = api.getInternalData(evt.target)
48
+ var internalData = api.getInternalData(parent)
48
49
 
49
50
  if (internalData.webSocket) {
50
51
  internalData.webSocket.close();
@@ -53,8 +54,6 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
53
54
 
54
55
  // Try to create websockets when elements are processed
55
56
  case "htmx:beforeProcessNode":
56
- var parent = evt.target;
57
-
58
57
  forEach(queryAttributeOnThisOrChildren(parent, "ws-connect"), function (child) {
59
58
  ensureWebSocket(child)
60
59
  });
package/dist/htmx.d.ts CHANGED
@@ -21,7 +21,7 @@ export function addClass(elt: Element, clazz: string, delay?: number): void;
21
21
  * @param element the element to target (defaults to the **body**)
22
22
  * @returns Promise that resolves immediately if no request is sent, or when the request is complete
23
23
  */
24
- export function ajax(verb: string, path: string, element: Element): Promise<void>;
24
+ export function ajax(verb: string, path: string, element?: Element): Promise<void>;
25
25
 
26
26
  /**
27
27
  * Issues an htmx-style AJAX request
@@ -400,13 +400,86 @@ export interface HtmxConfig {
400
400
  * @default true
401
401
  */
402
402
  scrollIntoViewOnBoost?: boolean;
403
+ /**
404
+ * If set, the nonce will be added to inline scripts.
405
+ * @default ''
406
+ */
407
+ inlineScriptNonce?: string;
408
+ /**
409
+ * The type of binary data being received over the WebSocket connection
410
+ * @default 'blob'
411
+ */
412
+ wsBinaryType?: 'blob' | 'arraybuffer';
413
+ /**
414
+ * If set to true htmx will include a cache-busting parameter in GET requests to avoid caching partial responses by the browser
415
+ * @default false
416
+ */
417
+ getCacheBusterParam?: boolean;
418
+ /**
419
+ * If set to true, htmx will use the View Transition API when swapping in new content.
420
+ * @default false
421
+ */
422
+ globalViewTransitions?: boolean;
423
+ /**
424
+ * htmx will format requests with these methods by encoding their parameters in the URL, not the request body
425
+ * @default ["get"]
426
+ */
427
+ methodsThatUseUrlParams?: ('get' | 'head' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace' | 'patch' )[];
428
+ /**
429
+ * If set to true htmx will not update the title of the document when a title tag is found in new content
430
+ * @default false
431
+ */
432
+ ignoreTitle?: boolean;
403
433
  }
404
434
 
435
+ export type HtmxEvent = "htmx:abort"
436
+ | "htmx:afterOnLoad"
437
+ | "htmx:afterProcessNode"
438
+ | "htmx:afterRequest"
439
+ | "htmx:afterSettle"
440
+ | "htmx:afterSwap"
441
+ | "htmx:beforeCleanupElement"
442
+ | "htmx:beforeOnLoad"
443
+ | "htmx:beforeProcessNode"
444
+ | "htmx:beforeRequest"
445
+ | "htmx:beforeSwap"
446
+ | "htmx:beforeSend"
447
+ | "htmx:configRequest"
448
+ | "htmx:confirm"
449
+ | "htmx:historyCacheError"
450
+ | "htmx:historyCacheMiss"
451
+ | "htmx:historyCacheMissError"
452
+ | "htmx:historyCacheMissLoad"
453
+ | "htmx:historyRestore"
454
+ | "htmx:load"
455
+ | "htmx:noSSESourceError"
456
+ | "htmx:onLoadError"
457
+ | "htmx:oobAfterSwap"
458
+ | "htmx:oobBeforeSwap"
459
+ | "htmx:oobErrorNoTarget"
460
+ | "htmx:prompt"
461
+ | "htmx:pushedIntoHistory"
462
+ | "htmx:responseError"
463
+ | "htmx:sendError"
464
+ | "htmx:sseError"
465
+ | "htmx:sseOpen"
466
+ | "htmx:swapError"
467
+ | "htmx:targetError"
468
+ | "htmx:timeout"
469
+ | "htmx:validation:validate"
470
+ | "htmx:validation:failed"
471
+ | "htmx:validation:halted"
472
+ | "htmx:xhr:abort"
473
+ | "htmx:xhr:loadend"
474
+ | "htmx:xhr:loadstart"
475
+ | "htmx:xhr:progress"
476
+ ;
477
+
405
478
  /**
406
479
  * https://htmx.org/extensions/#defining
407
480
  */
408
481
  export interface HtmxExtension {
409
- onEvent?: (name: string, evt: CustomEvent) => any;
482
+ onEvent?: (name: HtmxEvent, evt: CustomEvent) => any;
410
483
  transformResponse?: (text: any, xhr: XMLHttpRequest, elt: any) => any;
411
484
  isInlineSwap?: (swapStyle: any) => any;
412
485
  handleSwap?: (swapStyle: any, target: any, fragment: any, settleInfo: any) => any;