htmx.org 1.8.4 → 1.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.6] - 2023-03-02
4
+
5
+ * ESM support!
6
+ * Sass has been vanquished from the htmx.org website, which should set us up for some good progress going forward
7
+ * Fixed a bug where the `changed` modifier on `keyup` did not work properly if an input was tabbed into
8
+ * Many other smaller bug fixes and doc fixes
9
+
10
+ ## [1.8.5] - 2023-01-17
11
+
12
+ * Support a new optional cache-busting configuration option, `getCacheBusterParam`, to allow browsers to disambiguate
13
+ between `GET` requests from htmx and from the raw browser
14
+ * Support new `hx-history='false'` attribute, to prevent sensitive data from being stored in the history cache. (Thank you @croxton!)
15
+ * Extensive new event-oriented features are available in the [Web Socket](/extensions/web-sockets/) extension (Thank you @Renerick!)
16
+ * A bug fix for when a form contains multiple empty input values with the same name (Thank you @bluekeyes!)
17
+ * A bug fix around inputs that throw exceptions when calling `setSelectionRange()` (Thank you @gone!)
18
+ * A bug fix to pass through the proper event for the `htmx:configRequest` event
19
+ * A bug fix/improvement for the `preload` extension
20
+ * Many other small bug fixes
21
+
3
22
  ## [1.8.4] - 2022-11-05
4
23
 
5
24
  * Fix the _exact same_ regression in `revealed` logic as in 1.8.2
package/README.md CHANGED
@@ -7,7 +7,6 @@
7
7
  [![Bundlephobia](https://badgen.net/bundlephobia/dependency-count/htmx.org)](https://bundlephobia.com/result?p=htmx.org)
8
8
  [![Bundlephobia](https://badgen.net/bundlephobia/minzip/htmx.org)](https://bundlephobia.com/result?p=htmx.org)
9
9
 
10
-
11
10
  ## introduction
12
11
 
13
12
  htmx allows you to access [AJAX](https://htmx.org/docs#ajax), [CSS Transitions](https://htmx.org/docs#css_transitions),
@@ -34,7 +33,7 @@ By removing these arbitrary constraints htmx completes HTML as a
34
33
  ## quick start
35
34
 
36
35
  ```html
37
- <script src="https://unpkg.com/htmx.org@1.8.4"></script>
36
+ <script src="https://unpkg.com/htmx.org@1.8.6"></script>
38
37
  <!-- have a button POST a click via AJAX -->
39
38
  <button hx-post="/clicked" hx-swap="outerHTML">
40
39
  Click Me
@@ -67,20 +66,24 @@ Note there is an old broken package called `htmx`. This is `htmx.org`.
67
66
  * please write code, including tests, in ES5 for [IE 11 compatibility](https://stackoverflow.com/questions/39902809/support-for-es6-in-internet-explorer-11)
68
67
  * please include test cases in [`/test`](https://github.com/bigskysoftware/htmx/tree/dev/test) and docs in [`/www`](https://github.com/bigskysoftware/htmx/tree/dev/www)
69
68
  * if you are adding a feature, consider doing it as an [extension](https://htmx.org/extensions) instead to
70
- keep the core htmx code tidy
69
+ keep the core htmx code tidy
71
70
  * development pull requests should be against the `dev` branch, docs fixes can be made directly against `master`
72
71
  * No time? Then [become a sponsor](https://github.com/sponsors/bigskysoftware#sponsors)
73
72
 
74
73
  ### hacking guide
75
74
 
76
75
  To develop htmx locally, you will need to install the development dependencies.
77
- Use node 15 and run:
76
+
77
+ __Requires Python 2 and Node 15.__
78
+
79
+ Run:
78
80
 
79
81
  ```
80
82
  npm install
81
83
  ```
82
84
 
83
85
  Then, run a web server in the root.
86
+
84
87
  This is easiest with Python:
85
88
 
86
89
  ```
@@ -1,4 +1,4 @@
1
- (function(){
1
+ (function () {
2
2
 
3
3
  function splitOnWhitespace(trigger) {
4
4
  return trigger.split(/\s+/);
@@ -20,15 +20,29 @@
20
20
  delay = 100;
21
21
  }
22
22
  return {
23
- operation:operation,
24
- cssClass:cssClass,
25
- delay:delay
23
+ operation: operation,
24
+ cssClass: cssClass,
25
+ delay: delay
26
26
  }
27
27
  } else {
28
28
  return null;
29
29
  }
30
30
  }
31
31
 
32
+ function performOperation(elt, classOperation, classList, currentRunTime) {
33
+ setTimeout(function () {
34
+ elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
35
+ }, currentRunTime)
36
+ }
37
+
38
+ function toggleOperation(elt, classOperation, classList, currentRunTime) {
39
+ setTimeout(function () {
40
+ setInterval(function () {
41
+ elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
42
+ }, classOperation.delay);
43
+ }, currentRunTime)
44
+ }
45
+
32
46
  function processClassList(elt, classList) {
33
47
  var runs = classList.split("&");
34
48
  for (var i = 0; i < runs.length; i++) {
@@ -41,17 +55,11 @@
41
55
  var classOperation = parseClassOperation(trimmedValue);
42
56
  if (classOperation) {
43
57
  if (classOperation.operation === "toggle") {
44
- setTimeout(function () {
45
- setInterval(function () {
46
- elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
47
- }, classOperation.delay);
48
- }, currentRunTime);
58
+ toggleOperation(elt, classOperation, classList, currentRunTime);
49
59
  currentRunTime = currentRunTime + classOperation.delay;
50
60
  } else {
51
61
  currentRunTime = currentRunTime + classOperation.delay;
52
- setTimeout(function () {
53
- elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
54
- }, currentRunTime);
62
+ performOperation(elt, classOperation, classList, currentRunTime);
55
63
  }
56
64
  }
57
65
  }
@@ -81,4 +89,4 @@
81
89
  }
82
90
  }
83
91
  });
84
- })();
92
+ })();
@@ -30,6 +30,8 @@
30
30
 
31
31
  htmlDoc.innerHTML = headTag;
32
32
  var newHeadTag = htmlDoc.querySelector("head");
33
+ var currentHead = document.head;
34
+
33
35
  if (newHeadTag == null) {
34
36
  return;
35
37
  } else {
@@ -46,7 +48,6 @@
46
48
  var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
47
49
 
48
50
  // get the current head
49
- var currentHead = document.head;
50
51
  for (const currentHeadElt of currentHead.children) {
51
52
 
52
53
  // If the current head element is in the map
@@ -169,7 +169,7 @@
169
169
  )
170
170
  }
171
171
 
172
- if (name === 'htmx:afterOnLoad') {
172
+ if (name === 'htmx:beforeOnLoad') {
173
173
  while (loadingStatesUndoQueue.length > 0) {
174
174
  loadingStatesUndoQueue.shift()()
175
175
  }
@@ -26,7 +26,9 @@ htmx.defineExtension("preload", {
26
26
  // Called after a successful AJAX request, to mark the
27
27
  // content as loaded (and prevent additional AJAX calls.)
28
28
  var done = function(html) {
29
- node.preloadState = "DONE"
29
+ if (!node.preloadAlways) {
30
+ node.preloadState = "DONE"
31
+ }
30
32
 
31
33
  if (attr(node, "preload-images") == "true") {
32
34
  document.createElement("div").innerHTML = html // create and populate a node to load linked resources, too.
@@ -81,6 +83,10 @@ htmx.defineExtension("preload", {
81
83
 
82
84
  // Get event name from config.
83
85
  var on = attr(node, "preload") || "mousedown"
86
+ const always = on.indexOf("always") !== -1
87
+ if (always) {
88
+ on = on.replace('always', '').trim()
89
+ }
84
90
 
85
91
  // FALL THROUGH to here means we need to add an EventListener
86
92
 
@@ -121,6 +127,7 @@ htmx.defineExtension("preload", {
121
127
 
122
128
  // Mark the node as ready to run.
123
129
  node.preloadState = "PAUSE";
130
+ node.preloadAlways = always;
124
131
  htmx.trigger(node, "preload:init") // This event can be used to load content immediately.
125
132
  }
126
133
 
package/dist/ext/ws.js CHANGED
@@ -4,7 +4,7 @@ WebSockets Extension
4
4
  This extension adds support for WebSockets to htmx. See /www/extensions/ws.md for usage instructions.
5
5
  */
6
6
 
7
- (function(){
7
+ (function () {
8
8
 
9
9
  /** @type {import("../htmx").HtmxInternalApi} */
10
10
  var api;
@@ -15,18 +15,18 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
15
15
  * init is called once, when this extension is first registered.
16
16
  * @param {import("../htmx").HtmxInternalApi} apiRef
17
17
  */
18
- init: function(apiRef) {
18
+ init: function (apiRef) {
19
19
 
20
20
  // Store reference to internal API
21
21
  api = apiRef;
22
22
 
23
23
  // Default function for creating new EventSource objects
24
- if (htmx.createWebSocket == undefined) {
24
+ if (!htmx.createWebSocket) {
25
25
  htmx.createWebSocket = createWebSocket;
26
26
  }
27
27
 
28
28
  // Default setting for reconnect delay
29
- if (htmx.config.wsReconnectDelay == undefined) {
29
+ if (!htmx.config.wsReconnectDelay) {
30
30
  htmx.config.wsReconnectDelay = "full-jitter";
31
31
  }
32
32
  },
@@ -37,30 +37,30 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
37
37
  * @param {string} name
38
38
  * @param {Event} evt
39
39
  */
40
- onEvent: function(name, evt) {
40
+ onEvent: function (name, evt) {
41
41
 
42
42
  switch (name) {
43
43
 
44
- // Try to remove remove an EventSource when elements are removed
45
- case "htmx:beforeCleanupElement":
44
+ // Try to close the socket when elements are removed
45
+ case "htmx:beforeCleanupElement":
46
46
 
47
- var internalData = api.getInternalData(evt.target)
47
+ var internalData = api.getInternalData(evt.target)
48
48
 
49
- if (internalData.webSocket != undefined) {
50
- internalData.webSocket.close();
51
- }
52
- return;
49
+ if (internalData.webSocket) {
50
+ internalData.webSocket.close();
51
+ }
52
+ return;
53
53
 
54
- // Try to create EventSources when elements are processed
55
- case "htmx:afterProcessNode":
56
- var parent = evt.target;
54
+ // Try to create websockets when elements are processed
55
+ case "htmx:afterProcessNode":
56
+ var parent = evt.target;
57
57
 
58
- forEach(queryAttributeOnThisOrChildren(parent, "ws-connect"), function(child) {
59
- ensureWebSocket(child)
60
- });
61
- forEach(queryAttributeOnThisOrChildren(parent, "ws-send"), function (child) {
62
- ensureWebSocketSend(child)
63
- });
58
+ forEach(queryAttributeOnThisOrChildren(parent, "ws-connect"), function (child) {
59
+ ensureWebSocket(child)
60
+ });
61
+ forEach(queryAttributeOnThisOrChildren(parent, "ws-send"), function (child) {
62
+ ensureWebSocketSend(child)
63
+ });
64
64
  }
65
65
  }
66
66
  });
@@ -85,23 +85,22 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
85
85
  /**
86
86
  * ensureWebSocket creates a new WebSocket on the designated element, using
87
87
  * the element's "ws-connect" attribute.
88
- * @param {HTMLElement} elt
89
- * @param {number=} retryCount
88
+ * @param {HTMLElement} socketElt
90
89
  * @returns
91
90
  */
92
- function ensureWebSocket(elt, retryCount) {
91
+ function ensureWebSocket(socketElt) {
93
92
 
94
93
  // If the element containing the WebSocket connection no longer exists, then
95
94
  // do not connect/reconnect the WebSocket.
96
- if (!api.bodyContains(elt)) {
95
+ if (!api.bodyContains(socketElt)) {
97
96
  return;
98
97
  }
99
98
 
100
99
  // Get the source straight from the element's value
101
- var wssSource = api.getAttributeValue(elt, "ws-connect")
100
+ var wssSource = api.getAttributeValue(socketElt, "ws-connect")
102
101
 
103
102
  if (wssSource == null || wssSource === "") {
104
- var legacySource = getLegacyWebsocketURL(elt);
103
+ var legacySource = getLegacyWebsocketURL(socketElt);
105
104
  if (legacySource == null) {
106
105
  return;
107
106
  } else {
@@ -109,58 +108,38 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
109
108
  }
110
109
  }
111
110
 
112
- // Default value for retryCount
113
- if (retryCount == undefined) {
114
- retryCount = 0;
115
- }
116
-
117
111
  // Guarantee that the wssSource value is a fully qualified URL
118
- if (wssSource.indexOf("/") == 0) {
119
- var base_part = location.hostname + (location.port ? ':'+location.port: '');
120
- if (location.protocol == 'https:') {
112
+ if (wssSource.indexOf("/") === 0) {
113
+ var base_part = location.hostname + (location.port ? ':' + location.port : '');
114
+ if (location.protocol === 'https:') {
121
115
  wssSource = "wss://" + base_part + wssSource;
122
- } else if (location.protocol == 'http:') {
116
+ } else if (location.protocol === 'http:') {
123
117
  wssSource = "ws://" + base_part + wssSource;
124
118
  }
125
119
  }
126
120
 
127
- // Create a new WebSocket and event handlers
128
- /** @type {WebSocket} */
129
- var socket = htmx.createWebSocket(wssSource);
130
-
131
- var messageQueue = [];
132
-
133
- socket.onopen = function (e) {
134
- retryCount = 0;
135
- handleQueuedMessages(messageQueue, socket);
136
- }
121
+ var socketWrapper = createWebsocketWrapper(socketElt, function () {
122
+ return htmx.createWebSocket(wssSource)
123
+ });
137
124
 
138
- socket.onclose = function (e) {
139
- // If Abnormal Closure/Service Restart/Try Again Later, then set a timer to reconnect after a pause.
140
- if ([1006, 1012, 1013].indexOf(e.code) >= 0) {
141
- var delay = getWebSocketReconnectDelay(retryCount);
142
- setTimeout(function() {
143
- ensureWebSocket(elt, retryCount+1);
144
- }, delay);
125
+ socketWrapper.addEventListener('message', function (event) {
126
+ if (maybeCloseWebSocketSource(socketElt)) {
127
+ return;
145
128
  }
146
- };
147
129
 
148
- socket.onerror = function (e) {
149
- api.triggerErrorEvent(elt, "htmx:wsError", {error:e, socket:socket});
150
- maybeCloseWebSocketSource(elt);
151
- };
152
-
153
- socket.addEventListener('message', function (event) {
154
- if (maybeCloseWebSocketSource(elt)) {
130
+ var response = event.data;
131
+ if (!api.triggerEvent(socketElt, "htmx:wsBeforeMessage", {
132
+ message: response,
133
+ socketWrapper: socketWrapper.publicInterface
134
+ })) {
155
135
  return;
156
136
  }
157
137
 
158
- var response = event.data;
159
- api.withExtensions(elt, function(extension){
160
- response = extension.transformResponse(response, null, elt);
138
+ api.withExtensions(socketElt, function (extension) {
139
+ response = extension.transformResponse(response, null, socketElt);
161
140
  });
162
141
 
163
- var settleInfo = api.makeSettleInfo(elt);
142
+ var settleInfo = api.makeSettleInfo(socketElt);
164
143
  var fragment = api.makeFragment(response);
165
144
 
166
145
  if (fragment.children.length) {
@@ -171,11 +150,154 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
171
150
  }
172
151
 
173
152
  api.settleImmediately(settleInfo.tasks);
153
+ api.triggerEvent(socketElt, "htmx:wsAfterMessage", { message: response, socketWrapper: socketWrapper.publicInterface })
174
154
  });
175
155
 
176
156
  // Put the WebSocket into the HTML Element's custom data.
177
- api.getInternalData(elt).webSocket = socket;
178
- api.getInternalData(elt).webSocketMessageQueue = messageQueue;
157
+ api.getInternalData(socketElt).webSocket = socketWrapper;
158
+ }
159
+
160
+ /**
161
+ * @typedef {Object} WebSocketWrapper
162
+ * @property {WebSocket} socket
163
+ * @property {Array<{message: string, sendElt: Element}>} messageQueue
164
+ * @property {number} retryCount
165
+ * @property {(message: string, sendElt: Element) => void} sendImmediately sendImmediately sends message regardless of websocket connection state
166
+ * @property {(message: string, sendElt: Element) => void} send
167
+ * @property {(event: string, handler: Function) => void} addEventListener
168
+ * @property {() => void} handleQueuedMessages
169
+ * @property {() => void} init
170
+ * @property {() => void} close
171
+ */
172
+ /**
173
+ *
174
+ * @param socketElt
175
+ * @param socketFunc
176
+ * @returns {WebSocketWrapper}
177
+ */
178
+ function createWebsocketWrapper(socketElt, socketFunc) {
179
+ var wrapper = {
180
+ socket: null,
181
+ messageQueue: [],
182
+ retryCount: 0,
183
+
184
+ /** @type {Object<string, Function[]>} */
185
+ events: {},
186
+
187
+ addEventListener: function (event, handler) {
188
+ if (this.socket) {
189
+ this.socket.addEventListener(event, handler);
190
+ }
191
+
192
+ if (!this.events[event]) {
193
+ this.events[event] = [];
194
+ }
195
+
196
+ this.events[event].push(handler);
197
+ },
198
+
199
+ sendImmediately: function (message, sendElt) {
200
+ if (!this.socket) {
201
+ api.triggerErrorEvent()
202
+ }
203
+ if (sendElt && api.triggerEvent(sendElt, 'htmx:wsBeforeSend', {
204
+ message: message,
205
+ socketWrapper: this.publicInterface
206
+ })) {
207
+ this.socket.send(message);
208
+ sendElt && api.triggerEvent(sendElt, 'htmx:wsAfterSend', {
209
+ message: message,
210
+ socketWrapper: this.publicInterface
211
+ })
212
+ }
213
+ },
214
+
215
+ send: function (message, sendElt) {
216
+ if (this.socket.readyState !== this.socket.OPEN) {
217
+ this.messageQueue.push({ message: message, sendElt: sendElt });
218
+ } else {
219
+ this.sendImmediately(message, sendElt);
220
+ }
221
+ },
222
+
223
+ handleQueuedMessages: function () {
224
+ while (this.messageQueue.length > 0) {
225
+ var queuedItem = this.messageQueue[0]
226
+ if (this.socket.readyState === this.socket.OPEN) {
227
+ this.sendImmediately(queuedItem.message, queuedItem.sendElt);
228
+ this.messageQueue.shift();
229
+ } else {
230
+ break;
231
+ }
232
+ }
233
+ },
234
+
235
+ init: function () {
236
+ if (this.socket && this.socket.readyState === this.socket.OPEN) {
237
+ // Close discarded socket
238
+ this.socket.close()
239
+ }
240
+
241
+ // Create a new WebSocket and event handlers
242
+ /** @type {WebSocket} */
243
+ var socket = socketFunc();
244
+
245
+ // The event.type detail is added for interface conformance with the
246
+ // other two lifecycle events (open and close) so a single handler method
247
+ // can handle them polymorphically, if required.
248
+ api.triggerEvent(socketElt, "htmx:wsConnecting", { event: { type: 'connecting' } });
249
+
250
+ this.socket = socket;
251
+
252
+ socket.onopen = function (e) {
253
+ wrapper.retryCount = 0;
254
+ api.triggerEvent(socketElt, "htmx:wsOpen", { event: e, socketWrapper: wrapper.publicInterface });
255
+ wrapper.handleQueuedMessages();
256
+ }
257
+
258
+ socket.onclose = function (e) {
259
+ // If socket should not be connected, stop further attempts to establish connection
260
+ // If Abnormal Closure/Service Restart/Try Again Later, then set a timer to reconnect after a pause.
261
+ if (!maybeCloseWebSocketSource(socketElt) && [1006, 1012, 1013].indexOf(e.code) >= 0) {
262
+ var delay = getWebSocketReconnectDelay(wrapper.retryCount);
263
+ setTimeout(function () {
264
+ wrapper.retryCount += 1;
265
+ wrapper.init();
266
+ }, delay);
267
+ }
268
+
269
+ // Notify client code that connection has been closed. Client code can inspect `event` field
270
+ // to determine whether closure has been valid or abnormal
271
+ api.triggerEvent(socketElt, "htmx:wsClose", { event: e, socketWrapper: wrapper.publicInterface })
272
+ };
273
+
274
+ socket.onerror = function (e) {
275
+ api.triggerErrorEvent(socketElt, "htmx:wsError", { error: e, socketWrapper: wrapper });
276
+ maybeCloseWebSocketSource(socketElt);
277
+ };
278
+
279
+ var events = this.events;
280
+ Object.keys(events).forEach(function (k) {
281
+ events[k].forEach(function (e) {
282
+ socket.addEventListener(k, e);
283
+ })
284
+ });
285
+ },
286
+
287
+ close: function () {
288
+ this.socket.close()
289
+ }
290
+ }
291
+
292
+ wrapper.init();
293
+
294
+ wrapper.publicInterface = {
295
+ send: wrapper.send.bind(wrapper),
296
+ sendImmediately: wrapper.sendImmediately.bind(wrapper),
297
+ queue: wrapper.messageQueue
298
+ };
299
+
300
+ return wrapper;
179
301
  }
180
302
 
181
303
  /**
@@ -205,67 +327,65 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
205
327
  /**
206
328
  * processWebSocketSend adds event listeners to the <form> element so that
207
329
  * messages can be sent to the WebSocket server when the form is submitted.
208
- * @param {HTMLElement} parent
209
- * @param {HTMLElement} child
330
+ * @param {HTMLElement} socketElt
331
+ * @param {HTMLElement} sendElt
210
332
  */
211
- function processWebSocketSend(parent, child) {
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');
333
+ function processWebSocketSend(socketElt, sendElt) {
334
+ var nodeData = api.getInternalData(sendElt);
335
+ var triggerSpecs = api.getTriggerSpecs(sendElt);
336
+ triggerSpecs.forEach(function (ts) {
337
+ api.addTriggerHandler(sendElt, ts, nodeData, function (elt, evt) {
338
+ if (maybeCloseWebSocketSource(socketElt)) {
339
+ return;
340
+ }
341
+
342
+ /** @type {WebSocketWrapper} */
343
+ var socketWrapper = api.getInternalData(socketElt).webSocket;
344
+ var headers = api.getHeaders(sendElt, socketElt);
345
+ var results = api.getInputValues(sendElt, 'post');
220
346
  var errors = results.errors;
221
347
  var rawParameters = results.values;
222
- var expressionVars = api.getExpressionVars(child);
348
+ var expressionVars = api.getExpressionVars(sendElt);
223
349
  var allParameters = api.mergeObjects(rawParameters, expressionVars);
224
- var filteredParameters = api.filterValues(allParameters, child);
225
- filteredParameters['HEADERS'] = headers;
350
+ var filteredParameters = api.filterValues(allParameters, sendElt);
351
+
352
+ var sendConfig = {
353
+ parameters: filteredParameters,
354
+ unfilteredParameters: allParameters,
355
+ headers: headers,
356
+ errors: errors,
357
+
358
+ triggeringEvent: evt,
359
+ messageBody: undefined,
360
+ socketWrapper: socketWrapper.publicInterface
361
+ };
362
+
363
+ if (!api.triggerEvent(elt, 'htmx:wsConfigSend', sendConfig)) {
364
+ return;
365
+ }
366
+
226
367
  if (errors && errors.length > 0) {
227
- api.triggerEvent(child, 'htmx:validation:halted', errors);
368
+ api.triggerEvent(elt, 'htmx:validation:halted', errors);
228
369
  return;
229
370
  }
230
- webSocketSend(webSocket, JSON.stringify(filteredParameters), messageQueue);
231
- if(api.shouldCancel(evt, child)){
371
+
372
+ var body = sendConfig.messageBody;
373
+ if (body === undefined) {
374
+ var toSend = Object.assign({}, sendConfig.parameters);
375
+ if (sendConfig.headers)
376
+ toSend['HEADERS'] = headers;
377
+ body = JSON.stringify(toSend);
378
+ }
379
+
380
+ socketWrapper.send(body, elt);
381
+
382
+ if (api.shouldCancel(evt, elt)) {
232
383
  evt.preventDefault();
233
384
  }
234
385
  });
235
386
  });
236
387
  }
237
388
 
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
-
269
389
  /**
270
390
  * getWebSocketReconnectDelay is the default easing function for WebSocket reconnects.
271
391
  * @param {number} retryCount // The number of retries that have already taken place
@@ -273,7 +393,7 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
273
393
  */
274
394
  function getWebSocketReconnectDelay(retryCount) {
275
395
 
276
- /** @type {"full-jitter" | (retryCount:number) => number} */
396
+ /** @type {"full-jitter" | ((retryCount:number) => number)} */
277
397
  var delay = htmx.config.wsReconnectDelay;
278
398
  if (typeof delay === 'function') {
279
399
  return delay(retryCount);
@@ -296,7 +416,7 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
296
416
  * @param {*} elt
297
417
  * @returns
298
418
  */
299
- function maybeCloseWebSocketSource(elt) {
419
+ function maybeCloseWebSocketSource(elt) {
300
420
  if (!api.bodyContains(elt)) {
301
421
  api.getInternalData(elt).webSocket.close();
302
422
  return true;
@@ -311,8 +431,10 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
311
431
  * @param {string} url
312
432
  * @returns WebSocket
313
433
  */
314
- function createWebSocket(url){
315
- return new WebSocket(url, []);
434
+ function createWebSocket(url) {
435
+ var sock = new WebSocket(url, []);
436
+ sock.binaryType = htmx.config.wsBinaryType;
437
+ return sock;
316
438
  }
317
439
 
318
440
  /**
@@ -331,7 +453,7 @@ This extension adds support for WebSockets to htmx. See /www/extensions/ws.md f
331
453
  }
332
454
 
333
455
  // Search all child nodes that match the requested attribute
334
- elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "], [data-hx-ws], [hx-ws]").forEach(function(node) {
456
+ elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "], [data-hx-ws], [hx-ws]").forEach(function (node) {
335
457
  result.push(node)
336
458
  })
337
459