htmx.org 1.7.0 → 1.8.1
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 +44 -1
- package/README.md +1 -2
- package/dist/ext/disable-element.js +16 -0
- package/dist/ext/loading-states.js +15 -1
- package/dist/ext/ws.js +115 -55
- package/dist/htmx.js +338 -118
- package/dist/htmx.min.js +1 -1
- package/dist/htmx.min.js.gz +0 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [1.
|
|
3
|
+
## [1.8.1] - 2022-10-11
|
|
4
|
+
|
|
5
|
+
* We now keep a count of outstanding requests for an indicator, so more than one overlapping request can share the same
|
|
6
|
+
indicator without issues
|
|
7
|
+
* We now track the attribute state of an element and re-initialize it if `htmx.process()` is called on the element and
|
|
8
|
+
the attributes have changed
|
|
9
|
+
* [Idiomorph](https://github.com/bigskysoftware/idiomorph) is now available for all your morph-swapping needs
|
|
10
|
+
* The `unset` directive now works properly for `hx-vals` and `hx-vars`
|
|
11
|
+
* The title of the page is now properly set on a history cache miss
|
|
12
|
+
* The new (`hx-validate`)[/attributes/hx-validate] attribute will force elements to validate before a request, even if
|
|
13
|
+
they are not within a form being submitted
|
|
14
|
+
* Many smaller bug and docs fixes
|
|
15
|
+
|
|
16
|
+
## [1.8.0] - 2022-7-12
|
|
17
|
+
|
|
18
|
+
* **NOTE**: This release involved some changes to touchy code (e.g. history support) so please test thoroughly and let
|
|
19
|
+
us know if you see any issues
|
|
20
|
+
* Boosted forms now will automatically push URLs into history as with links. The [response URL](https://caniuse.com/mdn-api_xmlhttprequest_responseurl)
|
|
21
|
+
detection API support is good enough that we feel comfortable making this the default now.
|
|
22
|
+
* If you do not want this behavior you can add `hx-push-url='false'` to your boosted forms
|
|
23
|
+
* The [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url) attribute was introduced, allowing you to replace
|
|
24
|
+
the current URL in history (to complement `hx-push-url`)
|
|
25
|
+
* Bug fix - if htmx is included in a page more than once, we do not process elements multiple times
|
|
26
|
+
* Bug fix - When localStorage is not available we do not attempt to save history in it
|
|
27
|
+
* [Bug fix](https://github.com/bigskysoftware/htmx/issues/908) - `hx-boost` respects the `enctype` attribute
|
|
28
|
+
* `m` is now a valid timing modifier (e.g. `hx-trigger="every 2m"`)
|
|
29
|
+
* `next` and `previous` are now valid extended query selector modifiers, e.g. `hx-target="next div"` will target the
|
|
30
|
+
next div from the current element
|
|
31
|
+
* Bug fix - `hx-boost` will boost anchor tags with a `_self` target
|
|
32
|
+
* The `load` event now properly supports event filters
|
|
33
|
+
* The websocket extension has had many improvements: (A huge thank you to Denis Palashevskii, our newest committer on the project!)
|
|
34
|
+
* Implement proper `hx-trigger` support
|
|
35
|
+
* Expose trigger handling API to extensions
|
|
36
|
+
* Implement safe message sending with sending queue
|
|
37
|
+
* Fix `ws-send` attributes connecting in new elements
|
|
38
|
+
* Fix OOB swapping of multiple elements in response
|
|
39
|
+
* The `HX-Location` response header now implements client-side redirects entirely within htmx
|
|
40
|
+
* The `HX-Reswap` response header allows you to change the swap behavior of htmx
|
|
41
|
+
* The new [`hx-select-oob`](/attributes/hx-select-oob) attribute selects one or more elements from a server response to swap in via an out of band swap
|
|
42
|
+
* The new [`hx-replace-url`](/attributes/hx-replace-url) attribute can be used to replace the current URL in the location
|
|
43
|
+
bar (very similar to `hx-push-url` but no new history entry is created). The corresponding `HX-Replace-Url` response header can be used as well.
|
|
44
|
+
* htmx now properly handles anchors in both boosted links, as well as in `hx-get`, etc. attributes
|
|
45
|
+
|
|
46
|
+
## [1.7.0] - 2022-02-22
|
|
4
47
|
|
|
5
48
|
* The new [`hx-sync`](/attributes/hx-sync) attribute allows you to synchronize multiple element requests on a single
|
|
6
49
|
element using various strategies (e.g. replace)
|
package/README.md
CHANGED
|
@@ -34,8 +34,7 @@ By removing these arbitrary constraints htmx completes HTML as a
|
|
|
34
34
|
## quick start
|
|
35
35
|
|
|
36
36
|
```html
|
|
37
|
-
|
|
38
|
-
<script src="https://unpkg.com/htmx.org@1.7.0" ></script>
|
|
37
|
+
<script src="https://unpkg.com/htmx.org@1.8.1"></script>
|
|
39
38
|
<!-- have a button POST a click via AJAX -->
|
|
40
39
|
<button hx-post="/clicked" hx-swap="outerHTML">
|
|
41
40
|
Click Me
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Disable Submit Button
|
|
4
|
+
htmx.defineExtension('disable-element', {
|
|
5
|
+
onEvent: function (name, evt) {
|
|
6
|
+
let elt = evt.detail.elt;
|
|
7
|
+
let target = elt.getAttribute("hx-disable-element");
|
|
8
|
+
let targetElement = (target == "self") ? elt : document.querySelector(target);
|
|
9
|
+
|
|
10
|
+
if (name === "htmx:beforeRequest" && targetElement) {
|
|
11
|
+
targetElement.disabled = true;
|
|
12
|
+
} else if (name == "htmx:afterRequest" && targetElement) {
|
|
13
|
+
targetElement.disabled = false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
'data-loading-class',
|
|
70
70
|
'data-loading-class-remove',
|
|
71
71
|
'data-loading-disable',
|
|
72
|
+
'data-loading-aria-busy',
|
|
72
73
|
]
|
|
73
74
|
|
|
74
75
|
let loadingStateEltsByType = {}
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
loadingStateEltsByType[type] = getLoadingStateElts(
|
|
78
79
|
container,
|
|
79
80
|
type,
|
|
80
|
-
evt.detail.pathInfo.
|
|
81
|
+
evt.detail.pathInfo.requestPath
|
|
81
82
|
)
|
|
82
83
|
})
|
|
83
84
|
|
|
@@ -153,6 +154,19 @@
|
|
|
153
154
|
})
|
|
154
155
|
}
|
|
155
156
|
)
|
|
157
|
+
|
|
158
|
+
loadingStateEltsByType['data-loading-aria-busy'].forEach(
|
|
159
|
+
(sourceElt) => {
|
|
160
|
+
getLoadingTarget(sourceElt).forEach((targetElt) => {
|
|
161
|
+
queueLoadingState(
|
|
162
|
+
sourceElt,
|
|
163
|
+
targetElt,
|
|
164
|
+
() => (targetElt.setAttribute("aria-busy", "true")),
|
|
165
|
+
() => (targetElt.removeAttribute("aria-busy"))
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
)
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
if (name === 'htmx:afterOnLoad') {
|
package/dist/ext/ws.js
CHANGED
|
@@ -13,7 +13,7 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* init is called once, when this extension is first registered.
|
|
16
|
-
* @param {import("../htmx").HtmxInternalApi} apiRef
|
|
16
|
+
* @param {import("../htmx").HtmxInternalApi} apiRef
|
|
17
17
|
*/
|
|
18
18
|
init: function(apiRef) {
|
|
19
19
|
|
|
@@ -33,9 +33,9 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* onEvent handles all events passed to this extension.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} name
|
|
38
|
-
* @param {Event} evt
|
|
36
|
+
*
|
|
37
|
+
* @param {string} name
|
|
38
|
+
* @param {Event} evt
|
|
39
39
|
*/
|
|
40
40
|
onEvent: function(name, evt) {
|
|
41
41
|
|
|
@@ -50,7 +50,7 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
50
50
|
internalData.webSocket.close();
|
|
51
51
|
}
|
|
52
52
|
return;
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
// Try to create EventSources when elements are processed
|
|
55
55
|
case "htmx:afterProcessNode":
|
|
56
56
|
var parent = evt.target;
|
|
@@ -58,6 +58,9 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
58
58
|
forEach(queryAttributeOnThisOrChildren(parent, "ws-connect"), function(child) {
|
|
59
59
|
ensureWebSocket(child)
|
|
60
60
|
});
|
|
61
|
+
forEach(queryAttributeOnThisOrChildren(parent, "ws-send"), function (child) {
|
|
62
|
+
ensureWebSocketSend(child)
|
|
63
|
+
});
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
});
|
|
@@ -81,14 +84,14 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
81
84
|
|
|
82
85
|
/**
|
|
83
86
|
* ensureWebSocket creates a new WebSocket on the designated element, using
|
|
84
|
-
* the element's "ws-connect" attribute.
|
|
85
|
-
* @param {HTMLElement} elt
|
|
86
|
-
* @param {number=} retryCount
|
|
87
|
-
* @returns
|
|
87
|
+
* the element's "ws-connect" attribute.
|
|
88
|
+
* @param {HTMLElement} elt
|
|
89
|
+
* @param {number=} retryCount
|
|
90
|
+
* @returns
|
|
88
91
|
*/
|
|
89
92
|
function ensureWebSocket(elt, retryCount) {
|
|
90
93
|
|
|
91
|
-
// If the element containing the WebSocket connection no longer exists, then
|
|
94
|
+
// If the element containing the WebSocket connection no longer exists, then
|
|
92
95
|
// do not connect/reconnect the WebSocket.
|
|
93
96
|
if (!api.bodyContains(elt)) {
|
|
94
97
|
return;
|
|
@@ -125,16 +128,19 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
125
128
|
/** @type {WebSocket} */
|
|
126
129
|
var socket = htmx.createWebSocket(wssSource);
|
|
127
130
|
|
|
131
|
+
var messageQueue = [];
|
|
132
|
+
|
|
128
133
|
socket.onopen = function (e) {
|
|
129
134
|
retryCount = 0;
|
|
135
|
+
handleQueuedMessages(messageQueue, socket);
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
socket.onclose = function (e) {
|
|
133
139
|
// If Abnormal Closure/Service Restart/Try Again Later, then set a timer to reconnect after a pause.
|
|
134
|
-
if ([1006, 1012, 1013].indexOf(e.code) >= 0) {
|
|
140
|
+
if ([1006, 1012, 1013].indexOf(e.code) >= 0) {
|
|
135
141
|
var delay = getWebSocketReconnectDelay(retryCount);
|
|
136
142
|
setTimeout(function() {
|
|
137
|
-
ensureWebSocket(elt, retryCount+1);
|
|
143
|
+
ensureWebSocket(elt, retryCount+1);
|
|
138
144
|
}, delay);
|
|
139
145
|
}
|
|
140
146
|
};
|
|
@@ -158,25 +164,42 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
158
164
|
var fragment = api.makeFragment(response);
|
|
159
165
|
|
|
160
166
|
if (fragment.children.length) {
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
var children = Array.from(fragment.children);
|
|
168
|
+
for (var i = 0; i < children.length; i++) {
|
|
169
|
+
api.oobSwap(api.getAttributeValue(children[i], "hx-swap-oob") || "true", children[i], settleInfo);
|
|
163
170
|
}
|
|
164
171
|
}
|
|
165
172
|
|
|
166
173
|
api.settleImmediately(settleInfo.tasks);
|
|
167
174
|
});
|
|
168
175
|
|
|
169
|
-
// Re-connect any ws-send commands as well.
|
|
170
|
-
forEach(queryAttributeOnThisOrChildren(elt, "ws-send"), function(child) {
|
|
171
|
-
var legacyAttribute = api.getAttributeValue(child, "hx-ws");
|
|
172
|
-
if (legacyAttribute && legacyAttribute !== 'send') {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
processWebSocketSend(elt, child);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
176
|
// Put the WebSocket into the HTML Element's custom data.
|
|
179
177
|
api.getInternalData(elt).webSocket = socket;
|
|
178
|
+
api.getInternalData(elt).webSocketMessageQueue = messageQueue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* ensureWebSocketSend attaches trigger handles to elements with
|
|
183
|
+
* "ws-send" attribute
|
|
184
|
+
* @param {HTMLElement} elt
|
|
185
|
+
*/
|
|
186
|
+
function ensureWebSocketSend(elt) {
|
|
187
|
+
var legacyAttribute = api.getAttributeValue(elt, "hx-ws");
|
|
188
|
+
if (legacyAttribute && legacyAttribute !== 'send') {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var webSocketParent = api.getClosestMatch(elt, hasWebSocket)
|
|
193
|
+
processWebSocketSend(webSocketParent, elt);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* hasWebSocket function checks if a node has webSocket instance attached
|
|
198
|
+
* @param {HTMLElement} node
|
|
199
|
+
* @returns {boolean}
|
|
200
|
+
*/
|
|
201
|
+
function hasWebSocket(node) {
|
|
202
|
+
return api.getInternalData(node).webSocket != null;
|
|
180
203
|
}
|
|
181
204
|
|
|
182
205
|
/**
|
|
@@ -184,29 +207,65 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
184
207
|
* messages can be sent to the WebSocket server when the form is submitted.
|
|
185
208
|
* @param {HTMLElement} parent
|
|
186
209
|
* @param {HTMLElement} child
|
|
187
|
-
*/
|
|
210
|
+
*/
|
|
188
211
|
function processWebSocketSend(parent, child) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
api.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
212
|
+
var nodeData = api.getInternalData(child);
|
|
213
|
+
let triggerSpecs = api.getTriggerSpecs(child);
|
|
214
|
+
triggerSpecs.forEach(function(ts) {
|
|
215
|
+
api.addTriggerHandler(child, ts, nodeData, function (evt) {
|
|
216
|
+
var webSocket = api.getInternalData(parent).webSocket;
|
|
217
|
+
var messageQueue = api.getInternalData(parent).webSocketMessageQueue;
|
|
218
|
+
var headers = api.getHeaders(child, parent);
|
|
219
|
+
var results = api.getInputValues(child, 'post');
|
|
220
|
+
var errors = results.errors;
|
|
221
|
+
var rawParameters = results.values;
|
|
222
|
+
var expressionVars = api.getExpressionVars(child);
|
|
223
|
+
var allParameters = api.mergeObjects(rawParameters, expressionVars);
|
|
224
|
+
var filteredParameters = api.filterValues(allParameters, child);
|
|
225
|
+
filteredParameters['HEADERS'] = headers;
|
|
226
|
+
if (errors && errors.length > 0) {
|
|
227
|
+
api.triggerEvent(child, 'htmx:validation:halted', errors);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
webSocketSend(webSocket, JSON.stringify(filteredParameters), messageQueue);
|
|
231
|
+
if(api.shouldCancel(evt, child)){
|
|
232
|
+
evt.preventDefault();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
207
235
|
});
|
|
208
236
|
}
|
|
209
|
-
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* webSocketSend provides a safe way to send messages through a WebSocket.
|
|
240
|
+
* It checks that the socket is in OPEN state and, otherwise, awaits for it.
|
|
241
|
+
* @param {WebSocket} socket
|
|
242
|
+
* @param {string} message
|
|
243
|
+
* @param {string[]} messageQueue
|
|
244
|
+
* @return {boolean}
|
|
245
|
+
*/
|
|
246
|
+
function webSocketSend(socket, message, messageQueue) {
|
|
247
|
+
if (socket.readyState != socket.OPEN) {
|
|
248
|
+
messageQueue.push(message);
|
|
249
|
+
} else {
|
|
250
|
+
socket.send(message);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* handleQueuedMessages sends messages awaiting in the message queue
|
|
256
|
+
*/
|
|
257
|
+
function handleQueuedMessages(messageQueue, socket) {
|
|
258
|
+
while (messageQueue.length > 0) {
|
|
259
|
+
var message = messageQueue[0]
|
|
260
|
+
if (socket.readyState == socket.OPEN) {
|
|
261
|
+
socket.send(message);
|
|
262
|
+
messageQueue.shift()
|
|
263
|
+
} else {
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
210
269
|
/**
|
|
211
270
|
* getWebSocketReconnectDelay is the default easing function for WebSocket reconnects.
|
|
212
271
|
* @param {number} retryCount // The number of retries that have already taken place
|
|
@@ -230,12 +289,12 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
230
289
|
|
|
231
290
|
/**
|
|
232
291
|
* maybeCloseWebSocketSource checks to the if the element that created the WebSocket
|
|
233
|
-
* still exists in the DOM. If NOT, then the WebSocket is closed and this function
|
|
292
|
+
* still exists in the DOM. If NOT, then the WebSocket is closed and this function
|
|
234
293
|
* returns TRUE. If the element DOES EXIST, then no action is taken, and this function
|
|
235
294
|
* returns FALSE.
|
|
236
|
-
*
|
|
237
|
-
* @param {*} elt
|
|
238
|
-
* @returns
|
|
295
|
+
*
|
|
296
|
+
* @param {*} elt
|
|
297
|
+
* @returns
|
|
239
298
|
*/
|
|
240
299
|
function maybeCloseWebSocketSource(elt) {
|
|
241
300
|
if (!api.bodyContains(elt)) {
|
|
@@ -248,8 +307,8 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
248
307
|
/**
|
|
249
308
|
* createWebSocket is the default method for creating new WebSocket objects.
|
|
250
309
|
* it is hoisted into htmx.createWebSocket to be overridden by the user, if needed.
|
|
251
|
-
*
|
|
252
|
-
* @param {string} url
|
|
310
|
+
*
|
|
311
|
+
* @param {string} url
|
|
253
312
|
* @returns WebSocket
|
|
254
313
|
*/
|
|
255
314
|
function createWebSocket(url){
|
|
@@ -258,9 +317,9 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
258
317
|
|
|
259
318
|
/**
|
|
260
319
|
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
|
|
261
|
-
*
|
|
262
|
-
* @param {HTMLElement} elt
|
|
263
|
-
* @param {string} attributeName
|
|
320
|
+
*
|
|
321
|
+
* @param {HTMLElement} elt
|
|
322
|
+
* @param {string} attributeName
|
|
264
323
|
*/
|
|
265
324
|
function queryAttributeOnThisOrChildren(elt, attributeName) {
|
|
266
325
|
|
|
@@ -281,8 +340,8 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
281
340
|
|
|
282
341
|
/**
|
|
283
342
|
* @template T
|
|
284
|
-
* @param {T[]} arr
|
|
285
|
-
* @param {(T) => void} func
|
|
343
|
+
* @param {T[]} arr
|
|
344
|
+
* @param {(T) => void} func
|
|
286
345
|
*/
|
|
287
346
|
function forEach(arr, func) {
|
|
288
347
|
if (arr) {
|
|
@@ -292,4 +351,5 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
|
|
|
292
351
|
}
|
|
293
352
|
}
|
|
294
353
|
|
|
295
|
-
})();
|
|
354
|
+
})();
|
|
355
|
+
|