htmx.org 1.9.9 → 1.9.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.9.10] - 2023-12-21
4
+
5
+ * `hx-on*` attributes now support the form `hx-on-`, with a trailing dash, to better support template systems (such as EJS)
6
+ that do not like double colons in HTML attributes.
7
+ * Added an `htmx.config.triggerSpecsCache` configuration property that can be set to an object to cache the trigger spec parsing
8
+ * Added a `path-params.js` extension for populating request paths with variable values
9
+ * Many smaller bug fixes & improvements
10
+
3
11
  ## [1.9.9] - 2023-11-21
4
12
 
5
13
  * Allow CSS selectors with whitespace in attributes like `hx-target` by using parens or curly-braces
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.10"></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;
@@ -39,17 +39,19 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
39
39
 
40
40
  switch (name) {
41
41
 
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;
42
+ case "htmx:beforeCleanupElement":
43
+ var internalData = api.getInternalData(evt.target)
44
+ // Try to remove remove an EventSource when elements are removed
45
+ if (internalData.sseEventSource) {
46
+ internalData.sseEventSource.close();
47
+ }
49
48
 
50
- // Try to create EventSources when elements are processed
51
- case "htmx:afterProcessNode":
52
- createEventSourceOnElement(evt.target);
49
+ return;
50
+
51
+ // Try to create EventSources when elements are processed
52
+ case "htmx:afterProcessNode":
53
+ ensureEventSourceOnElement(evt.target);
54
+ registerSSE(evt.target);
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,63 +105,24 @@ 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;
114
+ function registerSSE(elt) {
115
+ // Find closest existing event source
116
+ var sourceElement = api.getClosestMatch(elt, hasEventSource);
117
+ if (sourceElement == null) {
118
+ // api.triggerErrorEvent(elt, "htmx:noSSESourceError")
119
+ return null; // no eventsource in parentage, orphaned element
117
120
  }
118
121
 
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});
122
+ // Set internalData and source
123
+ var internalData = api.getInternalData(sourceElement);
124
+ var source = internalData.sseEventSource;
143
125
 
144
- // If parent no longer exists in the document, then clean up this EventSource
145
- if (maybeCloseSSESource(elt)) {
146
- return;
147
- }
148
-
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
126
  // Add message handlers for every `sse-swap` attribute
164
127
  queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
165
128
 
@@ -170,23 +133,27 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
170
133
  var sseEventNames = getLegacySSESwaps(child);
171
134
  }
172
135
 
173
- for (var i = 0 ; i < sseEventNames.length ; i++) {
136
+ for (var i = 0; i < sseEventNames.length; i++) {
174
137
  var sseEventName = sseEventNames[i].trim();
175
138
  var listener = function(event) {
176
139
 
177
- // If the parent is missing then close SSE and remove listener
178
- if (maybeCloseSSESource(elt)) {
179
- source.removeEventListener(sseEventName, listener);
140
+ // If the source is missing then close SSE
141
+ if (maybeCloseSSESource(sourceElement)) {
180
142
  return;
181
143
  }
182
144
 
145
+ // If the body no longer contains the element, remove the listener
146
+ if (!api.bodyContains(child)) {
147
+ source.removeEventListener(sseEventName, listener);
148
+ }
149
+
183
150
  // swap the response into the DOM and trigger a notification
184
151
  swap(child, event.data);
185
152
  api.triggerEvent(elt, "htmx:sseMessage", event);
186
153
  };
187
154
 
188
155
  // Register the new listener
189
- api.getInternalData(elt).sseEventListener = listener;
156
+ api.getInternalData(child).sseEventListener = listener;
190
157
  source.addEventListener(sseEventName, listener);
191
158
  }
192
159
  });
@@ -203,24 +170,86 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
203
170
  if (sseEventName.slice(0, 4) != "sse:") {
204
171
  return;
205
172
  }
173
+
174
+ // remove the sse: prefix from here on out
175
+ sseEventName = sseEventName.substr(4);
206
176
 
207
- var listener = function(event) {
177
+ var listener = function() {
178
+ if (maybeCloseSSESource(sourceElement)) {
179
+ return
180
+ }
208
181
 
209
- // If parent is missing, then close SSE and remove listener
210
- if (maybeCloseSSESource(elt)) {
182
+ if (!api.bodyContains(child)) {
211
183
  source.removeEventListener(sseEventName, listener);
212
- return;
213
184
  }
185
+ }
186
+ });
187
+ }
188
+
189
+ /**
190
+ * ensureEventSourceOnElement creates a new EventSource connection on the provided element.
191
+ * If a usable EventSource already exists, then it is returned. If not, then a new EventSource
192
+ * is created and stored in the element's internalData.
193
+ * @param {HTMLElement} elt
194
+ * @param {number} retryCount
195
+ * @returns {EventSource | null}
196
+ */
197
+ function ensureEventSourceOnElement(elt, retryCount) {
198
+
199
+ if (elt == null) {
200
+ return null;
201
+ }
214
202
 
215
- // Trigger events to be handled by the rest of htmx
216
- htmx.trigger(child, sseEventName, event);
217
- htmx.trigger(child, "htmx:sseMessage", event);
203
+ // handle extension source creation attribute
204
+ queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) {
205
+ var sseURL = api.getAttributeValue(child, "sse-connect");
206
+ if (sseURL == null) {
207
+ return;
218
208
  }
219
209
 
220
- // Register the new listener
221
- api.getInternalData(elt).sseEventListener = listener;
222
- source.addEventListener(sseEventName.slice(4), listener);
210
+ ensureEventSource(child, sseURL, retryCount);
223
211
  });
212
+
213
+ // handle legacy sse, remove for HTMX2
214
+ queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) {
215
+ var sseURL = getLegacySSEURL(child);
216
+ if (sseURL == null) {
217
+ return;
218
+ }
219
+
220
+ ensureEventSource(child, sseURL, retryCount);
221
+ });
222
+
223
+ }
224
+
225
+ function ensureEventSource(elt, url, retryCount) {
226
+ var source = htmx.createEventSource(url);
227
+
228
+ source.onerror = function(err) {
229
+
230
+ // Log an error event
231
+ api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source });
232
+
233
+ // If parent no longer exists in the document, then clean up this EventSource
234
+ if (maybeCloseSSESource(elt)) {
235
+ return;
236
+ }
237
+
238
+ // Otherwise, try to reconnect the EventSource
239
+ if (source.readyState === EventSource.CLOSED) {
240
+ retryCount = retryCount || 0;
241
+ var timeout = Math.random() * (2 ^ retryCount) * 500;
242
+ window.setTimeout(function() {
243
+ ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1));
244
+ }, timeout);
245
+ }
246
+ };
247
+
248
+ source.onopen = function(evt) {
249
+ api.triggerEvent(elt, "htmx:sseOpen", { source: source });
250
+ }
251
+
252
+ api.getInternalData(elt).sseEventSource = source;
224
253
  }
225
254
 
226
255
  /**
@@ -253,12 +282,12 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
253
282
  var result = [];
254
283
 
255
284
  // 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")) {
285
+ if (api.hasAttribute(elt, attributeName)) {
257
286
  result.push(elt);
258
287
  }
259
288
 
260
289
  // Search all child nodes that match the requested attribute
261
- elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "], [hx-sse], [data-hx-sse]").forEach(function(node) {
290
+ elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) {
262
291
  result.push(node);
263
292
  });
264
293
 
@@ -281,7 +310,7 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
281
310
 
282
311
  api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
283
312
 
284
- settleInfo.elts.forEach(function (elt) {
313
+ settleInfo.elts.forEach(function(elt) {
285
314
  if (elt.classList) {
286
315
  elt.classList.add(htmx.config.settlingClass);
287
316
  }
@@ -306,11 +335,11 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
306
335
  function doSettle(settleInfo) {
307
336
 
308
337
  return function() {
309
- settleInfo.tasks.forEach(function (task) {
338
+ settleInfo.tasks.forEach(function(task) {
310
339
  task.call();
311
340
  });
312
341
 
313
- settleInfo.elts.forEach(function (elt) {
342
+ settleInfo.elts.forEach(function(elt) {
314
343
  if (elt.classList) {
315
344
  elt.classList.remove(htmx.config.settlingClass);
316
345
  }
@@ -319,4 +348,8 @@ This extension adds support for Server Sent Events to htmx. See /www/extensions
319
348
  }
320
349
  }
321
350
 
322
- })();
351
+ function hasEventSource(node) {
352
+ return api.getInternalData(node).sseEventSource != null;
353
+ }
354
+
355
+ })();
package/dist/htmx.d.ts CHANGED
@@ -400,6 +400,42 @@ 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;
433
+ /**
434
+ * The cache to store evaluated trigger specifications into.
435
+ * You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
436
+ * @default null
437
+ */
438
+ triggerSpecsCache?: {[trigger: string]: HtmxTriggerSpecification[]};
403
439
  }
404
440
 
405
441
  /**