htmx.org 2.0.0-alpha1 → 2.0.0-beta1
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/README.md +1 -1
- package/dist/ext/README.md +9 -0
- package/dist/ext/ajax-header.js +7 -0
- package/dist/ext/alpine-morph.js +16 -0
- package/dist/ext/class-tools.js +92 -0
- package/dist/ext/client-side-templates.js +96 -0
- package/dist/ext/debug.js +11 -0
- package/dist/ext/disable-element.js +18 -0
- package/dist/ext/event-header.js +37 -0
- package/dist/ext/head-support.js +141 -0
- package/dist/ext/include-vals.js +24 -0
- package/dist/ext/json-enc.js +12 -0
- package/dist/ext/loading-states.js +183 -0
- package/dist/ext/method-override.js +11 -0
- package/dist/ext/morphdom-swap.js +17 -0
- package/dist/ext/multi-swap.js +45 -0
- package/dist/ext/path-deps.js +60 -0
- package/dist/ext/preload.js +147 -0
- package/dist/ext/rails-method.js +10 -0
- package/dist/ext/remove-me.js +27 -0
- package/dist/ext/response-targets.js +130 -0
- package/dist/ext/restored.js +15 -0
- package/dist/ext/sse.js +369 -0
- package/dist/ext/ws.js +476 -0
- package/dist/htmx.amd.js +2329 -871
- package/dist/htmx.cjs.js +2329 -871
- package/dist/htmx.esm.js +2329 -871
- package/dist/htmx.js +2329 -871
- package/dist/htmx.min.js +1 -1
- package/dist/htmx.min.js.gz +0 -0
- package/package.json +3 -3
- package/CHANGELOG.md +0 -474
- package/dist/htmx.d.ts +0 -450
package/dist/htmx.amd.js
CHANGED
|
@@ -1,79 +1,309 @@
|
|
|
1
1
|
define(() => {
|
|
2
|
-
|
|
2
|
+
var htmx = (function() {
|
|
3
3
|
'use strict'
|
|
4
4
|
|
|
5
5
|
// Public API
|
|
6
|
-
//* * @type {import("./htmx").HtmxApi} */
|
|
7
|
-
// TODO: list all methods in public API
|
|
8
6
|
const htmx = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
// Tsc madness here, assigning the functions directly results in an invalid TypeScript output, but reassigning is fine
|
|
8
|
+
/* Event processing */
|
|
9
|
+
/** @type {typeof onLoadHelper} */
|
|
10
|
+
onLoad: null,
|
|
11
|
+
/** @type {typeof processNode} */
|
|
12
|
+
process: null,
|
|
13
|
+
/** @type {typeof addEventListenerImpl} */
|
|
14
|
+
on: null,
|
|
15
|
+
/** @type {typeof removeEventListenerImpl} */
|
|
16
|
+
off: null,
|
|
17
|
+
/** @type {typeof triggerEvent} */
|
|
18
|
+
trigger: null,
|
|
19
|
+
/** @type {typeof ajaxHelper} */
|
|
20
|
+
ajax: null,
|
|
21
|
+
/* DOM querying helpers */
|
|
22
|
+
/** @type {typeof find} */
|
|
23
|
+
find: null,
|
|
24
|
+
/** @type {typeof findAll} */
|
|
25
|
+
findAll: null,
|
|
26
|
+
/** @type {typeof closest} */
|
|
27
|
+
closest: null,
|
|
28
|
+
/**
|
|
29
|
+
* Returns the input values that would resolve for a given element via the htmx value resolution mechanism
|
|
30
|
+
*
|
|
31
|
+
* @see https://htmx.org/api/#values
|
|
32
|
+
*
|
|
33
|
+
* @param {Element} elt the element to resolve values on
|
|
34
|
+
* @param {HttpVerb} type the request type (e.g. **get** or **post**) non-GET's will include the enclosing form of the element. Defaults to **post**
|
|
35
|
+
* @returns {Object}
|
|
36
|
+
*/
|
|
37
|
+
values: function(elt, type) {
|
|
19
38
|
const inputValues = getInputValues(elt, type || 'post')
|
|
20
39
|
return inputValues.values
|
|
21
40
|
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
/* DOM manipulation helpers */
|
|
42
|
+
/** @type {typeof removeElement} */
|
|
43
|
+
remove: null,
|
|
44
|
+
/** @type {typeof addClassToElement} */
|
|
45
|
+
addClass: null,
|
|
46
|
+
/** @type {typeof removeClassFromElement} */
|
|
47
|
+
removeClass: null,
|
|
48
|
+
/** @type {typeof toggleClassOnElement} */
|
|
49
|
+
toggleClass: null,
|
|
50
|
+
/** @type {typeof takeClassForElement} */
|
|
51
|
+
takeClass: null,
|
|
52
|
+
/** @type {typeof swap} */
|
|
53
|
+
swap: null,
|
|
54
|
+
/* Extension entrypoints */
|
|
55
|
+
/** @type {typeof defineExtension} */
|
|
56
|
+
defineExtension: null,
|
|
57
|
+
/** @type {typeof removeExtension} */
|
|
58
|
+
removeExtension: null,
|
|
59
|
+
/* Debugging */
|
|
60
|
+
/** @type {typeof logAll} */
|
|
61
|
+
logAll: null,
|
|
62
|
+
/** @type {typeof logNone} */
|
|
63
|
+
logNone: null,
|
|
64
|
+
/* Debugging */
|
|
65
|
+
/**
|
|
66
|
+
* The logger htmx uses to log with
|
|
67
|
+
*
|
|
68
|
+
* @see https://htmx.org/api/#logger
|
|
69
|
+
*/
|
|
31
70
|
logger: null,
|
|
71
|
+
/**
|
|
72
|
+
* A property holding the configuration htmx uses at runtime.
|
|
73
|
+
*
|
|
74
|
+
* Note that using a [meta tag](https://htmx.org/docs/#config) is the preferred mechanism for setting these properties.
|
|
75
|
+
*
|
|
76
|
+
* @see https://htmx.org/api/#config
|
|
77
|
+
*/
|
|
32
78
|
config: {
|
|
79
|
+
/**
|
|
80
|
+
* Whether to use history.
|
|
81
|
+
* @type boolean
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
33
84
|
historyEnabled: true,
|
|
85
|
+
/**
|
|
86
|
+
* The number of pages to keep in **localStorage** for history support.
|
|
87
|
+
* @type number
|
|
88
|
+
* @default 10
|
|
89
|
+
*/
|
|
34
90
|
historyCacheSize: 10,
|
|
91
|
+
/**
|
|
92
|
+
* @type boolean
|
|
93
|
+
* @default false
|
|
94
|
+
*/
|
|
35
95
|
refreshOnHistoryMiss: false,
|
|
96
|
+
/**
|
|
97
|
+
* The default swap style to use if **[hx-swap](https://htmx.org/attributes/hx-swap)** is omitted.
|
|
98
|
+
* @type HtmxSwapStyle
|
|
99
|
+
* @default 'innerHTML'
|
|
100
|
+
*/
|
|
36
101
|
defaultSwapStyle: 'innerHTML',
|
|
102
|
+
/**
|
|
103
|
+
* The default delay between receiving a response from the server and doing the swap.
|
|
104
|
+
* @type number
|
|
105
|
+
* @default 0
|
|
106
|
+
*/
|
|
37
107
|
defaultSwapDelay: 0,
|
|
108
|
+
/**
|
|
109
|
+
* The default delay between completing the content swap and settling attributes.
|
|
110
|
+
* @type number
|
|
111
|
+
* @default 20
|
|
112
|
+
*/
|
|
38
113
|
defaultSettleDelay: 20,
|
|
114
|
+
/**
|
|
115
|
+
* If true, htmx will inject a small amount of CSS into the page to make indicators invisible unless the **htmx-indicator** class is present.
|
|
116
|
+
* @type boolean
|
|
117
|
+
* @default true
|
|
118
|
+
*/
|
|
39
119
|
includeIndicatorStyles: true,
|
|
120
|
+
/**
|
|
121
|
+
* The class to place on indicators when a request is in flight.
|
|
122
|
+
* @type string
|
|
123
|
+
* @default 'htmx-indicator'
|
|
124
|
+
*/
|
|
40
125
|
indicatorClass: 'htmx-indicator',
|
|
126
|
+
/**
|
|
127
|
+
* The class to place on triggering elements when a request is in flight.
|
|
128
|
+
* @type string
|
|
129
|
+
* @default 'htmx-request'
|
|
130
|
+
*/
|
|
41
131
|
requestClass: 'htmx-request',
|
|
132
|
+
/**
|
|
133
|
+
* The class to temporarily place on elements that htmx has added to the DOM.
|
|
134
|
+
* @type string
|
|
135
|
+
* @default 'htmx-added'
|
|
136
|
+
*/
|
|
42
137
|
addedClass: 'htmx-added',
|
|
138
|
+
/**
|
|
139
|
+
* The class to place on target elements when htmx is in the settling phase.
|
|
140
|
+
* @type string
|
|
141
|
+
* @default 'htmx-settling'
|
|
142
|
+
*/
|
|
43
143
|
settlingClass: 'htmx-settling',
|
|
144
|
+
/**
|
|
145
|
+
* The class to place on target elements when htmx is in the swapping phase.
|
|
146
|
+
* @type string
|
|
147
|
+
* @default 'htmx-swapping'
|
|
148
|
+
*/
|
|
44
149
|
swappingClass: 'htmx-swapping',
|
|
150
|
+
/**
|
|
151
|
+
* Allows the use of eval-like functionality in htmx, to enable **hx-vars**, trigger conditions & script tag evaluation. Can be set to **false** for CSP compatibility.
|
|
152
|
+
* @type boolean
|
|
153
|
+
* @default true
|
|
154
|
+
*/
|
|
45
155
|
allowEval: true,
|
|
156
|
+
/**
|
|
157
|
+
* If set to false, disables the interpretation of script tags.
|
|
158
|
+
* @type boolean
|
|
159
|
+
* @default true
|
|
160
|
+
*/
|
|
46
161
|
allowScriptTags: true,
|
|
162
|
+
/**
|
|
163
|
+
* If set, the nonce will be added to inline scripts.
|
|
164
|
+
* @type string
|
|
165
|
+
* @default ''
|
|
166
|
+
*/
|
|
47
167
|
inlineScriptNonce: '',
|
|
168
|
+
/**
|
|
169
|
+
* The attributes to settle during the settling phase.
|
|
170
|
+
* @type string[]
|
|
171
|
+
* @default ['class', 'style', 'width', 'height']
|
|
172
|
+
*/
|
|
48
173
|
attributesToSettle: ['class', 'style', 'width', 'height'],
|
|
174
|
+
/**
|
|
175
|
+
* Allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates.
|
|
176
|
+
* @type boolean
|
|
177
|
+
* @default false
|
|
178
|
+
*/
|
|
49
179
|
withCredentials: false,
|
|
180
|
+
/**
|
|
181
|
+
* @type number
|
|
182
|
+
* @default 0
|
|
183
|
+
*/
|
|
50
184
|
timeout: 0,
|
|
185
|
+
/**
|
|
186
|
+
* The default implementation of **getWebSocketReconnectDelay** for reconnecting after unexpected connection loss by the event code **Abnormal Closure**, **Service Restart** or **Try Again Later**.
|
|
187
|
+
* @type {'full-jitter' | ((retryCount:number) => number)}
|
|
188
|
+
* @default "full-jitter"
|
|
189
|
+
*/
|
|
51
190
|
wsReconnectDelay: 'full-jitter',
|
|
191
|
+
/**
|
|
192
|
+
* The type of binary data being received over the WebSocket connection
|
|
193
|
+
* @type BinaryType
|
|
194
|
+
* @default 'blob'
|
|
195
|
+
*/
|
|
52
196
|
wsBinaryType: 'blob',
|
|
197
|
+
/**
|
|
198
|
+
* @type string
|
|
199
|
+
* @default '[hx-disable], [data-hx-disable]'
|
|
200
|
+
*/
|
|
53
201
|
disableSelector: '[hx-disable], [data-hx-disable]',
|
|
54
|
-
|
|
202
|
+
/**
|
|
203
|
+
* @type {'auto' | 'instant' | 'smooth'}
|
|
204
|
+
* @default 'smooth'
|
|
205
|
+
*/
|
|
55
206
|
scrollBehavior: 'instant',
|
|
207
|
+
/**
|
|
208
|
+
* If the focused element should be scrolled into view.
|
|
209
|
+
* @type boolean
|
|
210
|
+
* @default false
|
|
211
|
+
*/
|
|
56
212
|
defaultFocusScroll: false,
|
|
213
|
+
/**
|
|
214
|
+
* If set to true htmx will include a cache-busting parameter in GET requests to avoid caching partial responses by the browser
|
|
215
|
+
* @type boolean
|
|
216
|
+
* @default false
|
|
217
|
+
*/
|
|
57
218
|
getCacheBusterParam: false,
|
|
219
|
+
/**
|
|
220
|
+
* If set to true, htmx will use the View Transition API when swapping in new content.
|
|
221
|
+
* @type boolean
|
|
222
|
+
* @default false
|
|
223
|
+
*/
|
|
58
224
|
globalViewTransitions: false,
|
|
225
|
+
/**
|
|
226
|
+
* htmx will format requests with these methods by encoding their parameters in the URL, not the request body
|
|
227
|
+
* @type {(HttpVerb)[]}
|
|
228
|
+
* @default ['get', 'delete']
|
|
229
|
+
*/
|
|
59
230
|
methodsThatUseUrlParams: ['get', 'delete'],
|
|
231
|
+
/**
|
|
232
|
+
* If set to true, disables htmx-based requests to non-origin hosts.
|
|
233
|
+
* @type boolean
|
|
234
|
+
* @default false
|
|
235
|
+
*/
|
|
60
236
|
selfRequestsOnly: true,
|
|
237
|
+
/**
|
|
238
|
+
* If set to true htmx will not update the title of the document when a title tag is found in new content
|
|
239
|
+
* @type boolean
|
|
240
|
+
* @default false
|
|
241
|
+
*/
|
|
61
242
|
ignoreTitle: false,
|
|
243
|
+
/**
|
|
244
|
+
* Whether the target of a boosted element is scrolled into the viewport.
|
|
245
|
+
* @type boolean
|
|
246
|
+
* @default true
|
|
247
|
+
*/
|
|
62
248
|
scrollIntoViewOnBoost: true,
|
|
63
|
-
|
|
249
|
+
/**
|
|
250
|
+
* The cache to store evaluated trigger specifications into.
|
|
251
|
+
* 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)
|
|
252
|
+
* @type {Object|null}
|
|
253
|
+
* @default null
|
|
254
|
+
*/
|
|
255
|
+
triggerSpecsCache: null,
|
|
256
|
+
/** @type boolean */
|
|
257
|
+
disableInheritance: false,
|
|
258
|
+
/** @type HtmxResponseHandlingConfig[] */
|
|
259
|
+
responseHandling: [
|
|
260
|
+
{ code: '204', swap: false },
|
|
261
|
+
{ code: '[23]..', swap: true },
|
|
262
|
+
{ code: '[45]..', swap: false, error: true }
|
|
263
|
+
],
|
|
264
|
+
/**
|
|
265
|
+
* Whether to process OOB swaps on elements that are nested within the main response element.
|
|
266
|
+
* @type boolean
|
|
267
|
+
* @default true
|
|
268
|
+
*/
|
|
269
|
+
allowNestedOobSwaps: true
|
|
64
270
|
},
|
|
65
|
-
parseInterval
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
271
|
+
/** @type {typeof parseInterval} */
|
|
272
|
+
parseInterval: null,
|
|
273
|
+
/** @type {typeof internalEval} */
|
|
274
|
+
_: null,
|
|
275
|
+
version: '2.0a'
|
|
276
|
+
}
|
|
277
|
+
// Tsc madness part 2
|
|
278
|
+
htmx.onLoad = onLoadHelper
|
|
279
|
+
htmx.process = processNode
|
|
280
|
+
htmx.on = addEventListenerImpl
|
|
281
|
+
htmx.off = removeEventListenerImpl
|
|
282
|
+
htmx.trigger = triggerEvent
|
|
283
|
+
htmx.ajax = ajaxHelper
|
|
284
|
+
htmx.find = find
|
|
285
|
+
htmx.findAll = findAll
|
|
286
|
+
htmx.closest = closest
|
|
287
|
+
htmx.remove = removeElement
|
|
288
|
+
htmx.addClass = addClassToElement
|
|
289
|
+
htmx.removeClass = removeClassFromElement
|
|
290
|
+
htmx.toggleClass = toggleClassOnElement
|
|
291
|
+
htmx.takeClass = takeClassForElement
|
|
292
|
+
htmx.swap = swap
|
|
293
|
+
htmx.defineExtension = defineExtension
|
|
294
|
+
htmx.removeExtension = removeExtension
|
|
295
|
+
htmx.logAll = logAll
|
|
296
|
+
htmx.logNone = logNone
|
|
297
|
+
htmx.parseInterval = parseInterval
|
|
298
|
+
htmx._ = internalEval
|
|
69
299
|
|
|
70
|
-
/** @type {import("./htmx").HtmxInternalApi} */
|
|
71
300
|
const internalAPI = {
|
|
72
301
|
addTriggerHandler,
|
|
73
302
|
bodyContains,
|
|
74
303
|
canAccessLocalStorage,
|
|
75
304
|
findThisElement,
|
|
76
305
|
filterValues,
|
|
306
|
+
swap,
|
|
77
307
|
hasAttribute,
|
|
78
308
|
getAttributeValue,
|
|
79
309
|
getClosestAttributeValue,
|
|
@@ -90,7 +320,6 @@ const htmx = (function () {
|
|
|
90
320
|
makeSettleInfo,
|
|
91
321
|
oobSwap,
|
|
92
322
|
querySelectorExt,
|
|
93
|
-
selectAndSwap,
|
|
94
323
|
settleImmediately,
|
|
95
324
|
shouldCancel,
|
|
96
325
|
triggerEvent,
|
|
@@ -99,13 +328,11 @@ const htmx = (function () {
|
|
|
99
328
|
}
|
|
100
329
|
|
|
101
330
|
const VERBS = ['get', 'post', 'put', 'delete', 'patch']
|
|
102
|
-
const VERB_SELECTOR = VERBS.map(function
|
|
331
|
+
const VERB_SELECTOR = VERBS.map(function(verb) {
|
|
103
332
|
return '[hx-' + verb + '], [data-hx-' + verb + ']'
|
|
104
333
|
}).join(', ')
|
|
105
334
|
|
|
106
335
|
const HEAD_TAG_REGEX = makeTagRegEx('head')
|
|
107
|
-
const TITLE_TAG_REGEX = makeTagRegEx('title')
|
|
108
|
-
const SVG_TAGS_REGEX = makeTagRegEx('svg', true)
|
|
109
336
|
|
|
110
337
|
//= ===================================================================
|
|
111
338
|
// Utilities
|
|
@@ -116,12 +343,22 @@ const htmx = (function () {
|
|
|
116
343
|
* @param {boolean} global
|
|
117
344
|
* @returns {RegExp}
|
|
118
345
|
*/
|
|
119
|
-
function makeTagRegEx
|
|
346
|
+
function makeTagRegEx(tag, global = false) {
|
|
120
347
|
return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`,
|
|
121
348
|
global ? 'gim' : 'im')
|
|
122
349
|
}
|
|
123
350
|
|
|
124
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Parses an interval string consistent with the way htmx does. Useful for plugins that have timing-related attributes.
|
|
353
|
+
*
|
|
354
|
+
* Caution: Accepts an int followed by either **s** or **ms**. All other values use **parseFloat**
|
|
355
|
+
*
|
|
356
|
+
* @see https://htmx.org/api/#parseInterval
|
|
357
|
+
*
|
|
358
|
+
* @param {string} str timing string
|
|
359
|
+
* @returns {number|undefined}
|
|
360
|
+
*/
|
|
361
|
+
function parseInterval(str) {
|
|
125
362
|
if (str == undefined) {
|
|
126
363
|
return undefined
|
|
127
364
|
}
|
|
@@ -140,35 +377,40 @@ const htmx = (function () {
|
|
|
140
377
|
}
|
|
141
378
|
|
|
142
379
|
/**
|
|
143
|
-
* @param {
|
|
380
|
+
* @param {Node} elt
|
|
144
381
|
* @param {string} name
|
|
145
382
|
* @returns {(string | null)}
|
|
146
383
|
*/
|
|
147
|
-
function getRawAttribute
|
|
148
|
-
return elt
|
|
384
|
+
function getRawAttribute(elt, name) {
|
|
385
|
+
return elt instanceof Element && elt.getAttribute(name)
|
|
149
386
|
}
|
|
150
387
|
|
|
388
|
+
/**
|
|
389
|
+
* @param {Element} elt
|
|
390
|
+
* @param {string} qualifiedName
|
|
391
|
+
* @returns {boolean}
|
|
392
|
+
*/
|
|
151
393
|
// resolve with both hx and data-hx prefixes
|
|
152
|
-
function hasAttribute
|
|
153
|
-
return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
|
|
394
|
+
function hasAttribute(elt, qualifiedName) {
|
|
395
|
+
return !!elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
|
|
154
396
|
elt.hasAttribute('data-' + qualifiedName))
|
|
155
397
|
}
|
|
156
398
|
|
|
157
399
|
/**
|
|
158
400
|
*
|
|
159
|
-
* @param {
|
|
401
|
+
* @param {Node} elt
|
|
160
402
|
* @param {string} qualifiedName
|
|
161
403
|
* @returns {(string | null)}
|
|
162
404
|
*/
|
|
163
|
-
function getAttributeValue
|
|
405
|
+
function getAttributeValue(elt, qualifiedName) {
|
|
164
406
|
return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, 'data-' + qualifiedName)
|
|
165
407
|
}
|
|
166
408
|
|
|
167
409
|
/**
|
|
168
|
-
* @param {
|
|
169
|
-
* @returns {
|
|
410
|
+
* @param {Node} elt
|
|
411
|
+
* @returns {Node | null}
|
|
170
412
|
*/
|
|
171
|
-
function parentElt
|
|
413
|
+
function parentElt(elt) {
|
|
172
414
|
const parent = elt.parentElement
|
|
173
415
|
if (!parent && elt.parentNode instanceof ShadowRoot) return elt.parentNode
|
|
174
416
|
return parent
|
|
@@ -177,23 +419,25 @@ const htmx = (function () {
|
|
|
177
419
|
/**
|
|
178
420
|
* @returns {Document}
|
|
179
421
|
*/
|
|
180
|
-
function getDocument
|
|
422
|
+
function getDocument() {
|
|
181
423
|
return document
|
|
182
424
|
}
|
|
183
425
|
|
|
184
426
|
/**
|
|
185
|
-
* @
|
|
427
|
+
* @param {Node} elt
|
|
428
|
+
* @param {boolean} global
|
|
429
|
+
* @returns {Node|Document}
|
|
186
430
|
*/
|
|
187
|
-
function getRootNode
|
|
431
|
+
function getRootNode(elt, global) {
|
|
188
432
|
return elt.getRootNode ? elt.getRootNode({ composed: global }) : getDocument()
|
|
189
433
|
}
|
|
190
434
|
|
|
191
435
|
/**
|
|
192
|
-
* @param {
|
|
193
|
-
* @param {(e:
|
|
194
|
-
* @returns {
|
|
436
|
+
* @param {Node} elt
|
|
437
|
+
* @param {(e:Node) => boolean} condition
|
|
438
|
+
* @returns {Node | null}
|
|
195
439
|
*/
|
|
196
|
-
function getClosestMatch
|
|
440
|
+
function getClosestMatch(elt, condition) {
|
|
197
441
|
while (elt && !condition(elt)) {
|
|
198
442
|
elt = parentElt(elt)
|
|
199
443
|
}
|
|
@@ -201,25 +445,40 @@ const htmx = (function () {
|
|
|
201
445
|
return elt || null
|
|
202
446
|
}
|
|
203
447
|
|
|
204
|
-
|
|
448
|
+
/**
|
|
449
|
+
* @param {Element} initialElement
|
|
450
|
+
* @param {Element} ancestor
|
|
451
|
+
* @param {string} attributeName
|
|
452
|
+
* @returns {string|null}
|
|
453
|
+
*/
|
|
454
|
+
function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName) {
|
|
205
455
|
const attributeValue = getAttributeValue(ancestor, attributeName)
|
|
206
456
|
const disinherit = getAttributeValue(ancestor, 'hx-disinherit')
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
457
|
+
var inherit = getAttributeValue(ancestor, 'hx-inherit')
|
|
458
|
+
if (initialElement !== ancestor) {
|
|
459
|
+
if (htmx.config.disableInheritance) {
|
|
460
|
+
if (inherit && (inherit === '*' || inherit.split(' ').indexOf(attributeName) >= 0)) {
|
|
461
|
+
return attributeValue
|
|
462
|
+
} else {
|
|
463
|
+
return null
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (disinherit && (disinherit === '*' || disinherit.split(' ').indexOf(attributeName) >= 0)) {
|
|
467
|
+
return 'unset'
|
|
468
|
+
}
|
|
211
469
|
}
|
|
470
|
+
return attributeValue
|
|
212
471
|
}
|
|
213
472
|
|
|
214
473
|
/**
|
|
215
|
-
* @param {
|
|
474
|
+
* @param {Element} elt
|
|
216
475
|
* @param {string} attributeName
|
|
217
476
|
* @returns {string | null}
|
|
218
477
|
*/
|
|
219
|
-
function getClosestAttributeValue
|
|
478
|
+
function getClosestAttributeValue(elt, attributeName) {
|
|
220
479
|
let closestAttr = null
|
|
221
|
-
getClosestMatch(elt, function
|
|
222
|
-
return closestAttr = getAttributeValueWithDisinheritance(elt, e, attributeName)
|
|
480
|
+
getClosestMatch(elt, function(e) {
|
|
481
|
+
return !!(closestAttr = getAttributeValueWithDisinheritance(elt, asElement(e), attributeName))
|
|
223
482
|
})
|
|
224
483
|
if (closestAttr !== 'unset') {
|
|
225
484
|
return closestAttr
|
|
@@ -227,22 +486,22 @@ const htmx = (function () {
|
|
|
227
486
|
}
|
|
228
487
|
|
|
229
488
|
/**
|
|
230
|
-
* @param {
|
|
489
|
+
* @param {Node} elt
|
|
231
490
|
* @param {string} selector
|
|
232
491
|
* @returns {boolean}
|
|
233
492
|
*/
|
|
234
|
-
function matches
|
|
493
|
+
function matches(elt, selector) {
|
|
235
494
|
// @ts-ignore: non-standard properties for browser compatibility
|
|
236
495
|
// noinspection JSUnresolvedVariable
|
|
237
|
-
const matchesFunction = elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector
|
|
238
|
-
return matchesFunction && matchesFunction.call(elt, selector)
|
|
496
|
+
const matchesFunction = elt instanceof Element && (elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector)
|
|
497
|
+
return !!matchesFunction && matchesFunction.call(elt, selector)
|
|
239
498
|
}
|
|
240
499
|
|
|
241
500
|
/**
|
|
242
501
|
* @param {string} str
|
|
243
502
|
* @returns {string}
|
|
244
503
|
*/
|
|
245
|
-
function getStartTag
|
|
504
|
+
function getStartTag(str) {
|
|
246
505
|
const tagMatcher = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i
|
|
247
506
|
const match = tagMatcher.exec(str)
|
|
248
507
|
if (match) {
|
|
@@ -253,77 +512,129 @@ const htmx = (function () {
|
|
|
253
512
|
}
|
|
254
513
|
|
|
255
514
|
/**
|
|
256
|
-
*
|
|
257
515
|
* @param {string} resp
|
|
258
|
-
* @
|
|
259
|
-
* @returns {Element}
|
|
516
|
+
* @returns {Document}
|
|
260
517
|
*/
|
|
261
|
-
function parseHTML
|
|
518
|
+
function parseHTML(resp) {
|
|
262
519
|
const parser = new DOMParser()
|
|
263
|
-
|
|
520
|
+
return parser.parseFromString(resp, 'text/html')
|
|
521
|
+
}
|
|
264
522
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
523
|
+
/**
|
|
524
|
+
* @param {DocumentFragment} fragment
|
|
525
|
+
* @param {Node} elt
|
|
526
|
+
*/
|
|
527
|
+
function takeChildrenFor(fragment, elt) {
|
|
528
|
+
while (elt.childNodes.length > 0) {
|
|
529
|
+
fragment.append(elt.childNodes[0])
|
|
271
530
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* @param {HTMLScriptElement} script
|
|
535
|
+
* @returns {HTMLScriptElement}
|
|
536
|
+
*/
|
|
537
|
+
function duplicateScript(script) {
|
|
538
|
+
const newScript = getDocument().createElement('script')
|
|
539
|
+
forEach(script.attributes, function(attr) {
|
|
540
|
+
newScript.setAttribute(attr.name, attr.value)
|
|
541
|
+
})
|
|
542
|
+
newScript.textContent = script.textContent
|
|
543
|
+
newScript.async = false
|
|
544
|
+
if (htmx.config.inlineScriptNonce) {
|
|
545
|
+
newScript.nonce = htmx.config.inlineScriptNonce
|
|
275
546
|
}
|
|
276
|
-
return
|
|
547
|
+
return newScript
|
|
277
548
|
}
|
|
278
549
|
|
|
279
|
-
|
|
280
|
-
|
|
550
|
+
/**
|
|
551
|
+
* @param {HTMLScriptElement} script
|
|
552
|
+
* @returns {boolean}
|
|
553
|
+
*/
|
|
554
|
+
function isJavaScriptScriptNode(script) {
|
|
555
|
+
return script.matches('script') && (script.type === 'text/javascript' || script.type === 'module' || script.type === '')
|
|
281
556
|
}
|
|
282
557
|
|
|
283
558
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
559
|
+
* we have to make new copies of script tags that we are going to insert because
|
|
560
|
+
* SOME browsers (not saying who, but it involves an element and an animal) don't
|
|
561
|
+
* execute scripts created in <template> tags when they are inserted into the DOM
|
|
562
|
+
* and all the others do lmao
|
|
563
|
+
* @param {DocumentFragment} fragment
|
|
287
564
|
*/
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
565
|
+
function normalizeScriptTags(fragment) {
|
|
566
|
+
Array.from(fragment.querySelectorAll('script')).forEach(/** @param {HTMLScriptElement} script */ (script) => {
|
|
567
|
+
if (isJavaScriptScriptNode(script)) {
|
|
568
|
+
const newScript = duplicateScript(script)
|
|
569
|
+
const parent = script.parentNode
|
|
570
|
+
try {
|
|
571
|
+
parent.insertBefore(newScript, script)
|
|
572
|
+
} catch (e) {
|
|
573
|
+
logError(e)
|
|
574
|
+
} finally {
|
|
575
|
+
script.remove()
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* @typedef {DocumentFragment & {title?: string}} DocumentFragmentWithTitle
|
|
583
|
+
* @description a document fragment representing the response HTML, including
|
|
584
|
+
* a `title` property for any title information found
|
|
585
|
+
*/
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* @param {string} response HTML
|
|
589
|
+
* @returns {DocumentFragmentWithTitle}
|
|
590
|
+
*/
|
|
591
|
+
function makeFragment(response) {
|
|
592
|
+
// strip head tag to determine shape of response we are dealing with
|
|
593
|
+
const responseWithNoHead = response.replace(HEAD_TAG_REGEX, '')
|
|
594
|
+
const startTag = getStartTag(responseWithNoHead)
|
|
595
|
+
/** @type DocumentFragmentWithTitle */
|
|
596
|
+
let fragment
|
|
597
|
+
if (startTag === 'html') {
|
|
598
|
+
// if it is a full document, parse it and return the body
|
|
599
|
+
fragment = /** @type DocumentFragmentWithTitle */ (new DocumentFragment())
|
|
600
|
+
const doc = parseHTML(response)
|
|
601
|
+
takeChildrenFor(fragment, doc.body)
|
|
602
|
+
fragment.title = doc.title
|
|
603
|
+
} else if (startTag === 'body') {
|
|
604
|
+
// parse body w/o wrapping in template
|
|
605
|
+
fragment = /** @type DocumentFragmentWithTitle */ (new DocumentFragment())
|
|
606
|
+
const doc = parseHTML(responseWithNoHead)
|
|
607
|
+
takeChildrenFor(fragment, doc.body)
|
|
608
|
+
fragment.title = doc.title
|
|
609
|
+
} else {
|
|
610
|
+
// otherwise we have non-body partial HTML content, so wrap it in a template to maximize parsing flexibility
|
|
611
|
+
const doc = parseHTML('<body><template class="internal-htmx-wrapper">' + responseWithNoHead + '</template></body>')
|
|
612
|
+
fragment = /** @type DocumentFragmentWithTitle */ (doc.querySelector('template').content)
|
|
613
|
+
// extract title into fragment for later processing
|
|
614
|
+
fragment.title = doc.title
|
|
615
|
+
|
|
616
|
+
// for legacy reasons we support a title tag at the root level of non-body responses, so we need to handle it
|
|
617
|
+
var titleElement = fragment.querySelector('title')
|
|
618
|
+
if (titleElement && titleElement.parentNode === fragment) {
|
|
619
|
+
titleElement.remove()
|
|
620
|
+
fragment.title = titleElement.innerText
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (fragment) {
|
|
624
|
+
if (htmx.config.allowScriptTags) {
|
|
625
|
+
normalizeScriptTags(fragment)
|
|
626
|
+
} else {
|
|
627
|
+
// remove all script tags if scripts are disabled
|
|
628
|
+
fragment.querySelectorAll('script').forEach((script) => script.remove())
|
|
629
|
+
}
|
|
320
630
|
}
|
|
631
|
+
return fragment
|
|
321
632
|
}
|
|
322
633
|
|
|
323
634
|
/**
|
|
324
635
|
* @param {Function} func
|
|
325
636
|
*/
|
|
326
|
-
function maybeCall
|
|
637
|
+
function maybeCall(func) {
|
|
327
638
|
if (func) {
|
|
328
639
|
func()
|
|
329
640
|
}
|
|
@@ -334,7 +645,7 @@ const htmx = (function () {
|
|
|
334
645
|
* @param {string} type
|
|
335
646
|
* @returns
|
|
336
647
|
*/
|
|
337
|
-
function isType
|
|
648
|
+
function isType(o, type) {
|
|
338
649
|
return Object.prototype.toString.call(o) === '[object ' + type + ']'
|
|
339
650
|
}
|
|
340
651
|
|
|
@@ -342,24 +653,65 @@ const htmx = (function () {
|
|
|
342
653
|
* @param {*} o
|
|
343
654
|
* @returns {o is Function}
|
|
344
655
|
*/
|
|
345
|
-
function isFunction
|
|
346
|
-
return
|
|
656
|
+
function isFunction(o) {
|
|
657
|
+
return typeof o === 'function'
|
|
347
658
|
}
|
|
348
659
|
|
|
349
660
|
/**
|
|
350
661
|
* @param {*} o
|
|
351
662
|
* @returns {o is Object}
|
|
352
663
|
*/
|
|
353
|
-
function isRawObject
|
|
664
|
+
function isRawObject(o) {
|
|
354
665
|
return isType(o, 'Object')
|
|
355
666
|
}
|
|
356
667
|
|
|
668
|
+
/**
|
|
669
|
+
* @typedef {Object} OnHandler
|
|
670
|
+
* @property {(keyof HTMLElementEventMap)|string} event
|
|
671
|
+
* @property {EventListener} listener
|
|
672
|
+
*/
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* @typedef {Object} ListenerInfo
|
|
676
|
+
* @property {string} trigger
|
|
677
|
+
* @property {EventListener} listener
|
|
678
|
+
* @property {EventTarget} on
|
|
679
|
+
*/
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* @typedef {Object} HtmxNodeInternalData
|
|
683
|
+
* Element data
|
|
684
|
+
* @property {number} [initHash]
|
|
685
|
+
* @property {boolean} [boosted]
|
|
686
|
+
* @property {OnHandler[]} [onHandlers]
|
|
687
|
+
* @property {number} [timeout]
|
|
688
|
+
* @property {ListenerInfo[]} [listenerInfos]
|
|
689
|
+
* @property {boolean} [cancelled]
|
|
690
|
+
* @property {boolean} [triggeredOnce]
|
|
691
|
+
* @property {number} [delayed]
|
|
692
|
+
* @property {number|null} [throttle]
|
|
693
|
+
* @property {string} [lastValue]
|
|
694
|
+
* @property {boolean} [loaded]
|
|
695
|
+
* @property {string} [path]
|
|
696
|
+
* @property {string} [verb]
|
|
697
|
+
* @property {boolean} [polling]
|
|
698
|
+
* @property {HTMLButtonElement|HTMLInputElement|null} [lastButtonClicked]
|
|
699
|
+
* @property {number} [requestCount]
|
|
700
|
+
* @property {XMLHttpRequest} [xhr]
|
|
701
|
+
* @property {(() => void)[]} [queuedRequests]
|
|
702
|
+
* @property {boolean} [abortable]
|
|
703
|
+
*
|
|
704
|
+
* Event data
|
|
705
|
+
* @property {HtmxTriggerSpecification} [triggerSpec]
|
|
706
|
+
* @property {EventTarget[]} [handledFor]
|
|
707
|
+
*/
|
|
708
|
+
|
|
357
709
|
/**
|
|
358
710
|
* getInternalData retrieves "private" data stored by htmx within an element
|
|
359
|
-
* @param {
|
|
360
|
-
* @returns {
|
|
711
|
+
* @param {EventTarget|Event} elt
|
|
712
|
+
* @returns {HtmxNodeInternalData}
|
|
361
713
|
*/
|
|
362
|
-
function getInternalData
|
|
714
|
+
function getInternalData(elt) {
|
|
363
715
|
const dataProp = 'htmx-internal-data'
|
|
364
716
|
let data = elt[dataProp]
|
|
365
717
|
if (!data) {
|
|
@@ -370,10 +722,11 @@ const htmx = (function () {
|
|
|
370
722
|
|
|
371
723
|
/**
|
|
372
724
|
* toArray converts an ArrayLike object into a real array.
|
|
373
|
-
* @
|
|
374
|
-
* @
|
|
725
|
+
* @template T
|
|
726
|
+
* @param {ArrayLike<T>} arr
|
|
727
|
+
* @returns {T[]}
|
|
375
728
|
*/
|
|
376
|
-
function toArray
|
|
729
|
+
function toArray(arr) {
|
|
377
730
|
const returnArr = []
|
|
378
731
|
if (arr) {
|
|
379
732
|
for (let i = 0; i < arr.length; i++) {
|
|
@@ -383,7 +736,12 @@ const htmx = (function () {
|
|
|
383
736
|
return returnArr
|
|
384
737
|
}
|
|
385
738
|
|
|
386
|
-
|
|
739
|
+
/**
|
|
740
|
+
* @template T
|
|
741
|
+
* @param {T[]|NamedNodeMap|HTMLCollection|HTMLFormControlsCollection|ArrayLike<T>} arr
|
|
742
|
+
* @param {(T) => void} func
|
|
743
|
+
*/
|
|
744
|
+
function forEach(arr, func) {
|
|
387
745
|
if (arr) {
|
|
388
746
|
for (let i = 0; i < arr.length; i++) {
|
|
389
747
|
func(arr[i])
|
|
@@ -391,43 +749,64 @@ const htmx = (function () {
|
|
|
391
749
|
}
|
|
392
750
|
}
|
|
393
751
|
|
|
394
|
-
|
|
752
|
+
/**
|
|
753
|
+
* @param {Element} el
|
|
754
|
+
* @returns {boolean}
|
|
755
|
+
*/
|
|
756
|
+
function isScrolledIntoView(el) {
|
|
395
757
|
const rect = el.getBoundingClientRect()
|
|
396
758
|
const elemTop = rect.top
|
|
397
759
|
const elemBottom = rect.bottom
|
|
398
760
|
return elemTop < window.innerHeight && elemBottom >= 0
|
|
399
761
|
}
|
|
400
762
|
|
|
401
|
-
|
|
763
|
+
/**
|
|
764
|
+
* @param {Node} elt
|
|
765
|
+
* @returns {boolean}
|
|
766
|
+
*/
|
|
767
|
+
function bodyContains(elt) {
|
|
402
768
|
// IE Fix
|
|
403
|
-
|
|
404
|
-
|
|
769
|
+
const rootNode = elt.getRootNode && elt.getRootNode()
|
|
770
|
+
if (rootNode && rootNode instanceof window.ShadowRoot) {
|
|
771
|
+
return getDocument().body.contains(rootNode.host)
|
|
405
772
|
} else {
|
|
406
773
|
return getDocument().body.contains(elt)
|
|
407
774
|
}
|
|
408
775
|
}
|
|
409
776
|
|
|
410
|
-
|
|
777
|
+
/**
|
|
778
|
+
* @param {string} trigger
|
|
779
|
+
* @returns {string[]}
|
|
780
|
+
*/
|
|
781
|
+
function splitOnWhitespace(trigger) {
|
|
411
782
|
return trigger.trim().split(/\s+/)
|
|
412
783
|
}
|
|
413
784
|
|
|
414
785
|
/**
|
|
415
|
-
* mergeObjects takes all
|
|
786
|
+
* mergeObjects takes all the keys from
|
|
416
787
|
* obj2 and duplicates them into obj1
|
|
417
|
-
* @
|
|
418
|
-
* @
|
|
419
|
-
* @
|
|
788
|
+
* @template T1
|
|
789
|
+
* @template T2
|
|
790
|
+
* @param {T1} obj1
|
|
791
|
+
* @param {T2} obj2
|
|
792
|
+
* @returns {T1 & T2}
|
|
420
793
|
*/
|
|
421
|
-
function mergeObjects
|
|
794
|
+
function mergeObjects(obj1, obj2) {
|
|
422
795
|
for (const key in obj2) {
|
|
423
796
|
if (obj2.hasOwnProperty(key)) {
|
|
797
|
+
// @ts-ignore tsc doesn't seem to properly handle types merging
|
|
424
798
|
obj1[key] = obj2[key]
|
|
425
799
|
}
|
|
426
800
|
}
|
|
801
|
+
// @ts-ignore tsc doesn't seem to properly handle types merging
|
|
427
802
|
return obj1
|
|
428
803
|
}
|
|
429
804
|
|
|
430
|
-
|
|
805
|
+
/**
|
|
806
|
+
* @param {string} jString
|
|
807
|
+
* @returns {any|null}
|
|
808
|
+
*/
|
|
809
|
+
function parseJSON(jString) {
|
|
431
810
|
try {
|
|
432
811
|
return JSON.parse(jString)
|
|
433
812
|
} catch (error) {
|
|
@@ -436,7 +815,10 @@ const htmx = (function () {
|
|
|
436
815
|
}
|
|
437
816
|
}
|
|
438
817
|
|
|
439
|
-
|
|
818
|
+
/**
|
|
819
|
+
* @returns {boolean}
|
|
820
|
+
*/
|
|
821
|
+
function canAccessLocalStorage() {
|
|
440
822
|
const test = 'htmx:localStorageTest'
|
|
441
823
|
try {
|
|
442
824
|
localStorage.setItem(test, test)
|
|
@@ -447,7 +829,11 @@ const htmx = (function () {
|
|
|
447
829
|
}
|
|
448
830
|
}
|
|
449
831
|
|
|
450
|
-
|
|
832
|
+
/**
|
|
833
|
+
* @param {string} path
|
|
834
|
+
* @returns {string}
|
|
835
|
+
*/
|
|
836
|
+
function normalizePath(path) {
|
|
451
837
|
try {
|
|
452
838
|
const url = new URL(path)
|
|
453
839
|
if (url) {
|
|
@@ -468,51 +854,101 @@ const htmx = (function () {
|
|
|
468
854
|
// public API
|
|
469
855
|
//= =========================================================================================
|
|
470
856
|
|
|
471
|
-
|
|
472
|
-
|
|
857
|
+
/**
|
|
858
|
+
* @param {string} str
|
|
859
|
+
* @returns {any}
|
|
860
|
+
*/
|
|
861
|
+
function internalEval(str) {
|
|
862
|
+
return maybeEval(getDocument().body, function() {
|
|
473
863
|
return eval(str)
|
|
474
864
|
})
|
|
475
865
|
}
|
|
476
866
|
|
|
477
|
-
|
|
478
|
-
|
|
867
|
+
/**
|
|
868
|
+
* Adds a callback for the **htmx:load** event. This can be used to process new content, for example initializing the content with a javascript library
|
|
869
|
+
*
|
|
870
|
+
* @see https://htmx.org/api/#onLoad
|
|
871
|
+
*
|
|
872
|
+
* @param {(elt: Node) => void} callback the callback to call on newly loaded content
|
|
873
|
+
* @returns {EventListener}
|
|
874
|
+
*/
|
|
875
|
+
function onLoadHelper(callback) {
|
|
876
|
+
const value = htmx.on('htmx:load', /** @param {CustomEvent} evt */ function(evt) {
|
|
479
877
|
callback(evt.detail.elt)
|
|
480
878
|
})
|
|
481
879
|
return value
|
|
482
880
|
}
|
|
483
881
|
|
|
484
|
-
|
|
485
|
-
|
|
882
|
+
/**
|
|
883
|
+
* Log all htmx events, useful for debugging.
|
|
884
|
+
*
|
|
885
|
+
* @see https://htmx.org/api/#logAll
|
|
886
|
+
*/
|
|
887
|
+
function logAll() {
|
|
888
|
+
htmx.logger = function(elt, event, data) {
|
|
486
889
|
if (console) {
|
|
487
890
|
console.log(event, elt, data)
|
|
488
891
|
}
|
|
489
892
|
}
|
|
490
893
|
}
|
|
491
894
|
|
|
492
|
-
function logNone
|
|
895
|
+
function logNone() {
|
|
493
896
|
htmx.logger = null
|
|
494
897
|
}
|
|
495
898
|
|
|
496
|
-
|
|
497
|
-
|
|
899
|
+
/**
|
|
900
|
+
* Finds an element matching the selector
|
|
901
|
+
*
|
|
902
|
+
* @see https://htmx.org/api/#find
|
|
903
|
+
*
|
|
904
|
+
* @param {ParentNode|string} eltOrSelector the root element to find the matching element in, inclusive | the selector to match
|
|
905
|
+
* @param {string} [selector] the selector to match
|
|
906
|
+
* @returns {Element|null}
|
|
907
|
+
*/
|
|
908
|
+
function find(eltOrSelector, selector) {
|
|
909
|
+
if (typeof eltOrSelector !== 'string') {
|
|
498
910
|
return eltOrSelector.querySelector(selector)
|
|
499
911
|
} else {
|
|
500
912
|
return find(getDocument(), eltOrSelector)
|
|
501
913
|
}
|
|
502
914
|
}
|
|
503
915
|
|
|
504
|
-
|
|
505
|
-
|
|
916
|
+
/**
|
|
917
|
+
* Finds all elements matching the selector
|
|
918
|
+
*
|
|
919
|
+
* @see https://htmx.org/api/#findAll
|
|
920
|
+
*
|
|
921
|
+
* @param {ParentNode|string} eltOrSelector the root element to find the matching elements in, inclusive | the selector to match
|
|
922
|
+
* @param {string} [selector] the selector to match
|
|
923
|
+
* @returns {NodeListOf<Element>}
|
|
924
|
+
*/
|
|
925
|
+
function findAll(eltOrSelector, selector) {
|
|
926
|
+
if (typeof eltOrSelector !== 'string') {
|
|
506
927
|
return eltOrSelector.querySelectorAll(selector)
|
|
507
928
|
} else {
|
|
508
929
|
return findAll(getDocument(), eltOrSelector)
|
|
509
930
|
}
|
|
510
931
|
}
|
|
511
932
|
|
|
512
|
-
|
|
933
|
+
/**
|
|
934
|
+
* @returns Window
|
|
935
|
+
*/
|
|
936
|
+
function getWindow() {
|
|
937
|
+
return window
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Removes an element from the DOM
|
|
942
|
+
*
|
|
943
|
+
* @see https://htmx.org/api/#remove
|
|
944
|
+
*
|
|
945
|
+
* @param {Node} elt
|
|
946
|
+
* @param {number} [delay]
|
|
947
|
+
*/
|
|
948
|
+
function removeElement(elt, delay) {
|
|
513
949
|
elt = resolveTarget(elt)
|
|
514
950
|
if (delay) {
|
|
515
|
-
setTimeout(function
|
|
951
|
+
getWindow().setTimeout(function() {
|
|
516
952
|
removeElement(elt)
|
|
517
953
|
elt = null
|
|
518
954
|
}, delay)
|
|
@@ -521,10 +957,54 @@ const htmx = (function () {
|
|
|
521
957
|
}
|
|
522
958
|
}
|
|
523
959
|
|
|
524
|
-
|
|
525
|
-
|
|
960
|
+
/**
|
|
961
|
+
* @param {any} elt
|
|
962
|
+
* @return {Element|null}
|
|
963
|
+
*/
|
|
964
|
+
function asElement(elt) {
|
|
965
|
+
return elt instanceof Element ? elt : null
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* @param {any} elt
|
|
970
|
+
* @return {HTMLElement|null}
|
|
971
|
+
*/
|
|
972
|
+
function asHtmlElement(elt) {
|
|
973
|
+
return elt instanceof HTMLElement ? elt : null
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* @param {any} value
|
|
978
|
+
* @return {string|null}
|
|
979
|
+
*/
|
|
980
|
+
function asString(value) {
|
|
981
|
+
return typeof value === 'string' ? value : null
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* @param {EventTarget} elt
|
|
986
|
+
* @return {ParentNode|null}
|
|
987
|
+
*/
|
|
988
|
+
function asParentNode(elt) {
|
|
989
|
+
return elt instanceof Element || elt instanceof Document || elt instanceof DocumentFragment ? elt : null
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* This method adds a class to the given element.
|
|
994
|
+
*
|
|
995
|
+
* @see https://htmx.org/api/#addClass
|
|
996
|
+
*
|
|
997
|
+
* @param {Element|string} elt the element to add the class to
|
|
998
|
+
* @param {string} clazz the class to add
|
|
999
|
+
* @param {number} [delay] the delay (in milliseconds) before class is added
|
|
1000
|
+
*/
|
|
1001
|
+
function addClassToElement(elt, clazz, delay) {
|
|
1002
|
+
elt = asElement(resolveTarget(elt))
|
|
1003
|
+
if (!elt) {
|
|
1004
|
+
return
|
|
1005
|
+
}
|
|
526
1006
|
if (delay) {
|
|
527
|
-
setTimeout(function
|
|
1007
|
+
getWindow().setTimeout(function() {
|
|
528
1008
|
addClassToElement(elt, clazz)
|
|
529
1009
|
elt = null
|
|
530
1010
|
}, delay)
|
|
@@ -533,10 +1013,22 @@ const htmx = (function () {
|
|
|
533
1013
|
}
|
|
534
1014
|
}
|
|
535
1015
|
|
|
536
|
-
|
|
537
|
-
|
|
1016
|
+
/**
|
|
1017
|
+
* Removes a class from the given element
|
|
1018
|
+
*
|
|
1019
|
+
* @see https://htmx.org/api/#removeClass
|
|
1020
|
+
*
|
|
1021
|
+
* @param {Node|string} node element to remove the class from
|
|
1022
|
+
* @param {string} clazz the class to remove
|
|
1023
|
+
* @param {number} [delay] the delay (in milliseconds before class is removed)
|
|
1024
|
+
*/
|
|
1025
|
+
function removeClassFromElement(node, clazz, delay) {
|
|
1026
|
+
let elt = asElement(resolveTarget(node))
|
|
1027
|
+
if (!elt) {
|
|
1028
|
+
return
|
|
1029
|
+
}
|
|
538
1030
|
if (delay) {
|
|
539
|
-
setTimeout(function
|
|
1031
|
+
getWindow().setTimeout(function() {
|
|
540
1032
|
removeClassFromElement(elt, clazz)
|
|
541
1033
|
elt = null
|
|
542
1034
|
}, delay)
|
|
@@ -551,22 +1043,47 @@ const htmx = (function () {
|
|
|
551
1043
|
}
|
|
552
1044
|
}
|
|
553
1045
|
|
|
554
|
-
|
|
1046
|
+
/**
|
|
1047
|
+
* Toggles the given class on an element
|
|
1048
|
+
*
|
|
1049
|
+
* @see https://htmx.org/api/#toggleClass
|
|
1050
|
+
*
|
|
1051
|
+
* @param {Element|string} elt the element to toggle the class on
|
|
1052
|
+
* @param {string} clazz the class to toggle
|
|
1053
|
+
*/
|
|
1054
|
+
function toggleClassOnElement(elt, clazz) {
|
|
555
1055
|
elt = resolveTarget(elt)
|
|
556
1056
|
elt.classList.toggle(clazz)
|
|
557
1057
|
}
|
|
558
1058
|
|
|
559
|
-
|
|
1059
|
+
/**
|
|
1060
|
+
* Takes the given class from its siblings, so that among its siblings, only the given element will have the class.
|
|
1061
|
+
*
|
|
1062
|
+
* @see https://htmx.org/api/#takeClass
|
|
1063
|
+
*
|
|
1064
|
+
* @param {Node|string} elt the element that will take the class
|
|
1065
|
+
* @param {string} clazz the class to take
|
|
1066
|
+
*/
|
|
1067
|
+
function takeClassForElement(elt, clazz) {
|
|
560
1068
|
elt = resolveTarget(elt)
|
|
561
|
-
forEach(elt.parentElement.children, function
|
|
1069
|
+
forEach(elt.parentElement.children, function(child) {
|
|
562
1070
|
removeClassFromElement(child, clazz)
|
|
563
1071
|
})
|
|
564
|
-
addClassToElement(elt, clazz)
|
|
1072
|
+
addClassToElement(asElement(elt), clazz)
|
|
565
1073
|
}
|
|
566
1074
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1075
|
+
/**
|
|
1076
|
+
* Finds the closest matching element in the given elements parentage, inclusive of the element
|
|
1077
|
+
*
|
|
1078
|
+
* @see https://htmx.org/api/#closest
|
|
1079
|
+
*
|
|
1080
|
+
* @param {Element|string} elt the element to find the selector from
|
|
1081
|
+
* @param {string} selector the selector to find
|
|
1082
|
+
* @returns {Element|null}
|
|
1083
|
+
*/
|
|
1084
|
+
function closest(elt, selector) {
|
|
1085
|
+
elt = asElement(resolveTarget(elt))
|
|
1086
|
+
if (elt && elt.closest) {
|
|
570
1087
|
return elt.closest(selector)
|
|
571
1088
|
} else {
|
|
572
1089
|
// TODO remove when IE goes away
|
|
@@ -575,20 +1092,34 @@ const htmx = (function () {
|
|
|
575
1092
|
return elt
|
|
576
1093
|
}
|
|
577
1094
|
}
|
|
578
|
-
while (elt = elt && parentElt(elt))
|
|
1095
|
+
while (elt = elt && asElement(parentElt(elt)))
|
|
579
1096
|
return null
|
|
580
1097
|
}
|
|
581
1098
|
}
|
|
582
1099
|
|
|
583
|
-
|
|
1100
|
+
/**
|
|
1101
|
+
* @param {string} str
|
|
1102
|
+
* @param {string} prefix
|
|
1103
|
+
* @returns {boolean}
|
|
1104
|
+
*/
|
|
1105
|
+
function startsWith(str, prefix) {
|
|
584
1106
|
return str.substring(0, prefix.length) === prefix
|
|
585
1107
|
}
|
|
586
1108
|
|
|
587
|
-
|
|
1109
|
+
/**
|
|
1110
|
+
* @param {string} str
|
|
1111
|
+
* @param {string} suffix
|
|
1112
|
+
* @returns {boolean}
|
|
1113
|
+
*/
|
|
1114
|
+
function endsWith(str, suffix) {
|
|
588
1115
|
return str.substring(str.length - suffix.length) === suffix
|
|
589
1116
|
}
|
|
590
1117
|
|
|
591
|
-
|
|
1118
|
+
/**
|
|
1119
|
+
* @param {string} selector
|
|
1120
|
+
* @returns {string}
|
|
1121
|
+
*/
|
|
1122
|
+
function normalizeSelector(selector) {
|
|
592
1123
|
const trimmedSelector = selector.trim()
|
|
593
1124
|
if (startsWith(trimmedSelector, '<') && endsWith(trimmedSelector, '/>')) {
|
|
594
1125
|
return trimmedSelector.substring(1, trimmedSelector.length - 2)
|
|
@@ -597,17 +1128,24 @@ const htmx = (function () {
|
|
|
597
1128
|
}
|
|
598
1129
|
}
|
|
599
1130
|
|
|
600
|
-
|
|
1131
|
+
/**
|
|
1132
|
+
* @param {Node|Element|Document|string} elt
|
|
1133
|
+
* @param {string} selector
|
|
1134
|
+
* @param {boolean=} global
|
|
1135
|
+
* @returns {(Node|Window)[]}
|
|
1136
|
+
*/
|
|
1137
|
+
function querySelectorAllExt(elt, selector, global) {
|
|
1138
|
+
elt = resolveTarget(elt)
|
|
601
1139
|
if (selector.indexOf('closest ') === 0) {
|
|
602
|
-
return [closest(elt, normalizeSelector(selector.substr(8)))]
|
|
1140
|
+
return [closest(asElement(elt), normalizeSelector(selector.substr(8)))]
|
|
603
1141
|
} else if (selector.indexOf('find ') === 0) {
|
|
604
|
-
return [find(elt, normalizeSelector(selector.substr(5)))]
|
|
1142
|
+
return [find(asParentNode(elt), normalizeSelector(selector.substr(5)))]
|
|
605
1143
|
} else if (selector === 'next') {
|
|
606
|
-
return [elt.nextElementSibling]
|
|
1144
|
+
return [asElement(elt).nextElementSibling]
|
|
607
1145
|
} else if (selector.indexOf('next ') === 0) {
|
|
608
1146
|
return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)), !!global)]
|
|
609
1147
|
} else if (selector === 'previous') {
|
|
610
|
-
return [elt.previousElementSibling]
|
|
1148
|
+
return [asElement(elt).previousElementSibling]
|
|
611
1149
|
} else if (selector.indexOf('previous ') === 0) {
|
|
612
1150
|
return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)), !!global)]
|
|
613
1151
|
} else if (selector === 'document') {
|
|
@@ -621,12 +1159,18 @@ const htmx = (function () {
|
|
|
621
1159
|
} else if (selector.indexOf('global ') === 0) {
|
|
622
1160
|
return querySelectorAllExt(elt, selector.slice(7), true)
|
|
623
1161
|
} else {
|
|
624
|
-
return getRootNode(elt, !!global).querySelectorAll(normalizeSelector(selector))
|
|
1162
|
+
return toArray(asParentNode(getRootNode(elt, !!global)).querySelectorAll(normalizeSelector(selector)))
|
|
625
1163
|
}
|
|
626
1164
|
}
|
|
627
1165
|
|
|
628
|
-
|
|
629
|
-
|
|
1166
|
+
/**
|
|
1167
|
+
* @param {Node} start
|
|
1168
|
+
* @param {string} match
|
|
1169
|
+
* @param {boolean} global
|
|
1170
|
+
* @returns {Element}
|
|
1171
|
+
*/
|
|
1172
|
+
var scanForwardQuery = function(start, match, global) {
|
|
1173
|
+
const results = asParentNode(getRootNode(start, global)).querySelectorAll(match)
|
|
630
1174
|
for (let i = 0; i < results.length; i++) {
|
|
631
1175
|
const elt = results[i]
|
|
632
1176
|
if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_PRECEDING) {
|
|
@@ -635,8 +1179,14 @@ const htmx = (function () {
|
|
|
635
1179
|
}
|
|
636
1180
|
}
|
|
637
1181
|
|
|
638
|
-
|
|
639
|
-
|
|
1182
|
+
/**
|
|
1183
|
+
* @param {Node} start
|
|
1184
|
+
* @param {string} match
|
|
1185
|
+
* @param {boolean} global
|
|
1186
|
+
* @returns {Element}
|
|
1187
|
+
*/
|
|
1188
|
+
var scanBackwardsQuery = function(start, match, global) {
|
|
1189
|
+
const results = asParentNode(getRootNode(start, global)).querySelectorAll(match)
|
|
640
1190
|
for (let i = results.length - 1; i >= 0; i--) {
|
|
641
1191
|
const elt = results[i]
|
|
642
1192
|
if (elt.compareDocumentPosition(start) === Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
@@ -645,40 +1195,78 @@ const htmx = (function () {
|
|
|
645
1195
|
}
|
|
646
1196
|
}
|
|
647
1197
|
|
|
648
|
-
|
|
649
|
-
|
|
1198
|
+
/**
|
|
1199
|
+
* @param {Node|string} eltOrSelector
|
|
1200
|
+
* @param {string=} selector
|
|
1201
|
+
* @returns {Node|Window}
|
|
1202
|
+
*/
|
|
1203
|
+
function querySelectorExt(eltOrSelector, selector) {
|
|
1204
|
+
if (typeof eltOrSelector !== 'string') {
|
|
650
1205
|
return querySelectorAllExt(eltOrSelector, selector)[0]
|
|
651
1206
|
} else {
|
|
652
1207
|
return querySelectorAllExt(getDocument().body, eltOrSelector)[0]
|
|
653
1208
|
}
|
|
654
1209
|
}
|
|
655
1210
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1211
|
+
/**
|
|
1212
|
+
* @template {EventTarget} T
|
|
1213
|
+
* @param {T|string} eltOrSelector
|
|
1214
|
+
* @param {T} [context]
|
|
1215
|
+
* @returns {Element|T|null}
|
|
1216
|
+
*/
|
|
1217
|
+
function resolveTarget(eltOrSelector, context) {
|
|
1218
|
+
if (typeof eltOrSelector === 'string') {
|
|
1219
|
+
return find(asParentNode(context) || document, eltOrSelector)
|
|
659
1220
|
} else {
|
|
660
|
-
return
|
|
1221
|
+
return eltOrSelector
|
|
661
1222
|
}
|
|
662
1223
|
}
|
|
663
1224
|
|
|
664
|
-
|
|
1225
|
+
/**
|
|
1226
|
+
* @typedef {keyof HTMLElementEventMap|string} AnyEventName
|
|
1227
|
+
*/
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* @typedef {Object} EventArgs
|
|
1231
|
+
* @property {EventTarget} target
|
|
1232
|
+
* @property {AnyEventName} event
|
|
1233
|
+
* @property {EventListener} listener
|
|
1234
|
+
*/
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* @param {EventTarget|AnyEventName} arg1
|
|
1238
|
+
* @param {AnyEventName|EventListener} arg2
|
|
1239
|
+
* @param {EventListener} [arg3]
|
|
1240
|
+
* @returns {EventArgs}
|
|
1241
|
+
*/
|
|
1242
|
+
function processEventArgs(arg1, arg2, arg3) {
|
|
665
1243
|
if (isFunction(arg2)) {
|
|
666
1244
|
return {
|
|
667
1245
|
target: getDocument().body,
|
|
668
|
-
event: arg1,
|
|
1246
|
+
event: asString(arg1),
|
|
669
1247
|
listener: arg2
|
|
670
1248
|
}
|
|
671
1249
|
} else {
|
|
672
1250
|
return {
|
|
673
1251
|
target: resolveTarget(arg1),
|
|
674
|
-
event: arg2,
|
|
1252
|
+
event: asString(arg2),
|
|
675
1253
|
listener: arg3
|
|
676
1254
|
}
|
|
677
1255
|
}
|
|
678
1256
|
}
|
|
679
1257
|
|
|
680
|
-
|
|
681
|
-
|
|
1258
|
+
/**
|
|
1259
|
+
* Adds an event listener to an element
|
|
1260
|
+
*
|
|
1261
|
+
* @see https://htmx.org/api/#on
|
|
1262
|
+
*
|
|
1263
|
+
* @param {EventTarget|string} arg1 the element to add the listener to | the event name to add the listener for
|
|
1264
|
+
* @param {string|EventListener} arg2 the event name to add the listener for | the listener to add
|
|
1265
|
+
* @param {EventListener} [arg3] the listener to add
|
|
1266
|
+
* @returns {EventListener}
|
|
1267
|
+
*/
|
|
1268
|
+
function addEventListenerImpl(arg1, arg2, arg3) {
|
|
1269
|
+
ready(function() {
|
|
682
1270
|
const eventArgs = processEventArgs(arg1, arg2, arg3)
|
|
683
1271
|
eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener)
|
|
684
1272
|
})
|
|
@@ -686,8 +1274,18 @@ const htmx = (function () {
|
|
|
686
1274
|
return b ? arg2 : arg3
|
|
687
1275
|
}
|
|
688
1276
|
|
|
689
|
-
|
|
690
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* Removes an event listener from an element
|
|
1279
|
+
*
|
|
1280
|
+
* @see https://htmx.org/api/#off
|
|
1281
|
+
*
|
|
1282
|
+
* @param {EventTarget|string} arg1 the element to remove the listener from | the event name to remove the listener from
|
|
1283
|
+
* @param {string|EventListener} arg2 the event name to remove the listener from | the listener to remove
|
|
1284
|
+
* @param {EventListener} [arg3] the listener to remove
|
|
1285
|
+
* @returns {EventListener}
|
|
1286
|
+
*/
|
|
1287
|
+
function removeEventListenerImpl(arg1, arg2, arg3) {
|
|
1288
|
+
ready(function() {
|
|
691
1289
|
const eventArgs = processEventArgs(arg1, arg2, arg3)
|
|
692
1290
|
eventArgs.target.removeEventListener(eventArgs.event, eventArgs.listener)
|
|
693
1291
|
})
|
|
@@ -699,7 +1297,12 @@ const htmx = (function () {
|
|
|
699
1297
|
//= ===================================================================
|
|
700
1298
|
|
|
701
1299
|
const DUMMY_ELT = getDocument().createElement('output') // dummy element for bad selectors
|
|
702
|
-
|
|
1300
|
+
/**
|
|
1301
|
+
* @param {Element} elt
|
|
1302
|
+
* @param {string} attrName
|
|
1303
|
+
* @returns {(Node|Window)[]}
|
|
1304
|
+
*/
|
|
1305
|
+
function findAttributeTargets(elt, attrName) {
|
|
703
1306
|
const attrTarget = getClosestAttributeValue(elt, attrName)
|
|
704
1307
|
if (attrTarget) {
|
|
705
1308
|
if (attrTarget === 'this') {
|
|
@@ -716,13 +1319,22 @@ const htmx = (function () {
|
|
|
716
1319
|
}
|
|
717
1320
|
}
|
|
718
1321
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1322
|
+
/**
|
|
1323
|
+
* @param {Element} elt
|
|
1324
|
+
* @param {string} attribute
|
|
1325
|
+
* @returns {Element|null}
|
|
1326
|
+
*/
|
|
1327
|
+
function findThisElement(elt, attribute) {
|
|
1328
|
+
return asElement(getClosestMatch(elt, function(elt) {
|
|
1329
|
+
return getAttributeValue(asElement(elt), attribute) != null
|
|
1330
|
+
}))
|
|
723
1331
|
}
|
|
724
1332
|
|
|
725
|
-
|
|
1333
|
+
/**
|
|
1334
|
+
* @param {Element} elt
|
|
1335
|
+
* @returns {Node|Window|null}
|
|
1336
|
+
*/
|
|
1337
|
+
function getTarget(elt) {
|
|
726
1338
|
const targetStr = getClosestAttributeValue(elt, 'hx-target')
|
|
727
1339
|
if (targetStr) {
|
|
728
1340
|
if (targetStr === 'this') {
|
|
@@ -740,7 +1352,11 @@ const htmx = (function () {
|
|
|
740
1352
|
}
|
|
741
1353
|
}
|
|
742
1354
|
|
|
743
|
-
|
|
1355
|
+
/**
|
|
1356
|
+
* @param {string} name
|
|
1357
|
+
* @returns {boolean}
|
|
1358
|
+
*/
|
|
1359
|
+
function shouldSettleAttribute(name) {
|
|
744
1360
|
const attributesToSettle = htmx.config.attributesToSettle
|
|
745
1361
|
for (let i = 0; i < attributesToSettle.length; i++) {
|
|
746
1362
|
if (name === attributesToSettle[i]) {
|
|
@@ -750,20 +1366,29 @@ const htmx = (function () {
|
|
|
750
1366
|
return false
|
|
751
1367
|
}
|
|
752
1368
|
|
|
753
|
-
|
|
754
|
-
|
|
1369
|
+
/**
|
|
1370
|
+
* @param {Element} mergeTo
|
|
1371
|
+
* @param {Element} mergeFrom
|
|
1372
|
+
*/
|
|
1373
|
+
function cloneAttributes(mergeTo, mergeFrom) {
|
|
1374
|
+
forEach(mergeTo.attributes, function(attr) {
|
|
755
1375
|
if (!mergeFrom.hasAttribute(attr.name) && shouldSettleAttribute(attr.name)) {
|
|
756
1376
|
mergeTo.removeAttribute(attr.name)
|
|
757
1377
|
}
|
|
758
1378
|
})
|
|
759
|
-
forEach(mergeFrom.attributes, function
|
|
1379
|
+
forEach(mergeFrom.attributes, function(attr) {
|
|
760
1380
|
if (shouldSettleAttribute(attr.name)) {
|
|
761
1381
|
mergeTo.setAttribute(attr.name, attr.value)
|
|
762
1382
|
}
|
|
763
1383
|
})
|
|
764
1384
|
}
|
|
765
1385
|
|
|
766
|
-
|
|
1386
|
+
/**
|
|
1387
|
+
* @param {HtmxSwapStyle} swapStyle
|
|
1388
|
+
* @param {Element} target
|
|
1389
|
+
* @returns {boolean}
|
|
1390
|
+
*/
|
|
1391
|
+
function isInlineSwap(swapStyle, target) {
|
|
767
1392
|
const extensions = getExtensions(target)
|
|
768
1393
|
for (let i = 0; i < extensions.length; i++) {
|
|
769
1394
|
const extension = extensions[i]
|
|
@@ -779,14 +1404,14 @@ const htmx = (function () {
|
|
|
779
1404
|
}
|
|
780
1405
|
|
|
781
1406
|
/**
|
|
782
|
-
*
|
|
783
1407
|
* @param {string} oobValue
|
|
784
|
-
* @param {
|
|
785
|
-
* @param {
|
|
1408
|
+
* @param {Element} oobElement
|
|
1409
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
786
1410
|
* @returns
|
|
787
1411
|
*/
|
|
788
|
-
function oobSwap
|
|
1412
|
+
function oobSwap(oobValue, oobElement, settleInfo) {
|
|
789
1413
|
let selector = '#' + getRawAttribute(oobElement, 'id')
|
|
1414
|
+
/** @type HtmxSwapStyle */
|
|
790
1415
|
let swapStyle = 'outerHTML'
|
|
791
1416
|
if (oobValue === 'true') {
|
|
792
1417
|
// do nothing
|
|
@@ -801,13 +1426,13 @@ const htmx = (function () {
|
|
|
801
1426
|
if (targets) {
|
|
802
1427
|
forEach(
|
|
803
1428
|
targets,
|
|
804
|
-
function
|
|
1429
|
+
function(target) {
|
|
805
1430
|
let fragment
|
|
806
1431
|
const oobElementClone = oobElement.cloneNode(true)
|
|
807
1432
|
fragment = getDocument().createDocumentFragment()
|
|
808
1433
|
fragment.appendChild(oobElementClone)
|
|
809
1434
|
if (!isInlineSwap(swapStyle, target)) {
|
|
810
|
-
fragment = oobElementClone // if this is not an inline swap, we use the content of the node, not the node itself
|
|
1435
|
+
fragment = asParentNode(oobElementClone) // if this is not an inline swap, we use the content of the node, not the node itself
|
|
811
1436
|
}
|
|
812
1437
|
|
|
813
1438
|
const beforeSwapDetails = { shouldSwap: true, target, fragment }
|
|
@@ -815,9 +1440,9 @@ const htmx = (function () {
|
|
|
815
1440
|
|
|
816
1441
|
target = beforeSwapDetails.target // allow re-targeting
|
|
817
1442
|
if (beforeSwapDetails.shouldSwap) {
|
|
818
|
-
|
|
1443
|
+
swapWithStyle(swapStyle, target, target, fragment, settleInfo)
|
|
819
1444
|
}
|
|
820
|
-
forEach(settleInfo.elts, function
|
|
1445
|
+
forEach(settleInfo.elts, function(elt) {
|
|
821
1446
|
triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails)
|
|
822
1447
|
})
|
|
823
1448
|
}
|
|
@@ -830,33 +1455,11 @@ const htmx = (function () {
|
|
|
830
1455
|
return oobValue
|
|
831
1456
|
}
|
|
832
1457
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const oobSelectValue = oobSelectValues[i].split(':', 2)
|
|
839
|
-
let id = oobSelectValue[0].trim()
|
|
840
|
-
if (id.indexOf('#') === 0) {
|
|
841
|
-
id = id.substring(1)
|
|
842
|
-
}
|
|
843
|
-
const oobValue = oobSelectValue[1] || 'true'
|
|
844
|
-
const oobElement = fragment.querySelector('#' + id)
|
|
845
|
-
if (oobElement) {
|
|
846
|
-
oobSwap(oobValue, oobElement, settleInfo)
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function (oobElement) {
|
|
851
|
-
const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
|
|
852
|
-
if (oobValue != null) {
|
|
853
|
-
oobSwap(oobValue, oobElement, settleInfo)
|
|
854
|
-
}
|
|
855
|
-
})
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
function handlePreservedElements (fragment) {
|
|
859
|
-
forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) {
|
|
1458
|
+
/**
|
|
1459
|
+
* @param {DocumentFragment} fragment
|
|
1460
|
+
*/
|
|
1461
|
+
function handlePreservedElements(fragment) {
|
|
1462
|
+
forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function(preservedElt) {
|
|
860
1463
|
const id = getAttributeValue(preservedElt, 'id')
|
|
861
1464
|
const oldElt = getDocument().getElementById(id)
|
|
862
1465
|
if (oldElt != null) {
|
|
@@ -865,17 +1468,23 @@ const htmx = (function () {
|
|
|
865
1468
|
})
|
|
866
1469
|
}
|
|
867
1470
|
|
|
868
|
-
|
|
869
|
-
|
|
1471
|
+
/**
|
|
1472
|
+
* @param {Node} parentNode
|
|
1473
|
+
* @param {ParentNode} fragment
|
|
1474
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1475
|
+
*/
|
|
1476
|
+
function handleAttributes(parentNode, fragment, settleInfo) {
|
|
1477
|
+
forEach(fragment.querySelectorAll('[id]'), function(newNode) {
|
|
870
1478
|
const id = getRawAttribute(newNode, 'id')
|
|
871
1479
|
if (id && id.length > 0) {
|
|
872
1480
|
const normalizedId = id.replace("'", "\\'")
|
|
873
1481
|
const normalizedTag = newNode.tagName.replace(':', '\\:')
|
|
874
|
-
const
|
|
875
|
-
|
|
1482
|
+
const parentElt = asParentNode(parentNode)
|
|
1483
|
+
const oldNode = parentElt && parentElt.querySelector(normalizedTag + "[id='" + normalizedId + "']")
|
|
1484
|
+
if (oldNode && oldNode !== parentElt) {
|
|
876
1485
|
const newAttributes = newNode.cloneNode()
|
|
877
1486
|
cloneAttributes(newNode, oldNode)
|
|
878
|
-
settleInfo.tasks.push(function
|
|
1487
|
+
settleInfo.tasks.push(function() {
|
|
879
1488
|
cloneAttributes(newNode, newAttributes)
|
|
880
1489
|
})
|
|
881
1490
|
}
|
|
@@ -883,29 +1492,41 @@ const htmx = (function () {
|
|
|
883
1492
|
})
|
|
884
1493
|
}
|
|
885
1494
|
|
|
886
|
-
|
|
887
|
-
|
|
1495
|
+
/**
|
|
1496
|
+
* @param {Node} child
|
|
1497
|
+
* @returns {HtmxSettleTask}
|
|
1498
|
+
*/
|
|
1499
|
+
function makeAjaxLoadTask(child) {
|
|
1500
|
+
return function() {
|
|
888
1501
|
removeClassFromElement(child, htmx.config.addedClass)
|
|
889
|
-
processNode(child)
|
|
890
|
-
|
|
891
|
-
processFocus(child)
|
|
1502
|
+
processNode(asElement(child))
|
|
1503
|
+
processFocus(asParentNode(child))
|
|
892
1504
|
triggerEvent(child, 'htmx:load')
|
|
893
1505
|
}
|
|
894
1506
|
}
|
|
895
1507
|
|
|
896
|
-
|
|
1508
|
+
/**
|
|
1509
|
+
* @param {ParentNode} child
|
|
1510
|
+
*/
|
|
1511
|
+
function processFocus(child) {
|
|
897
1512
|
const autofocus = '[autofocus]'
|
|
898
|
-
const autoFocusedElt = matches(child, autofocus) ? child : child.querySelector(autofocus)
|
|
1513
|
+
const autoFocusedElt = asHtmlElement(matches(child, autofocus) ? child : child.querySelector(autofocus))
|
|
899
1514
|
if (autoFocusedElt != null) {
|
|
900
1515
|
autoFocusedElt.focus()
|
|
901
1516
|
}
|
|
902
1517
|
}
|
|
903
1518
|
|
|
904
|
-
|
|
1519
|
+
/**
|
|
1520
|
+
* @param {Node} parentNode
|
|
1521
|
+
* @param {Node} insertBefore
|
|
1522
|
+
* @param {ParentNode} fragment
|
|
1523
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1524
|
+
*/
|
|
1525
|
+
function insertNodesBefore(parentNode, insertBefore, fragment, settleInfo) {
|
|
905
1526
|
handleAttributes(parentNode, fragment, settleInfo)
|
|
906
1527
|
while (fragment.childNodes.length > 0) {
|
|
907
1528
|
const child = fragment.firstChild
|
|
908
|
-
addClassToElement(child, htmx.config.addedClass)
|
|
1529
|
+
addClassToElement(asElement(child), htmx.config.addedClass)
|
|
909
1530
|
parentNode.insertBefore(child, insertBefore)
|
|
910
1531
|
if (child.nodeType !== Node.TEXT_NODE && child.nodeType !== Node.COMMENT_NODE) {
|
|
911
1532
|
settleInfo.tasks.push(makeAjaxLoadTask(child))
|
|
@@ -913,9 +1534,14 @@ const htmx = (function () {
|
|
|
913
1534
|
}
|
|
914
1535
|
}
|
|
915
1536
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1537
|
+
/**
|
|
1538
|
+
* based on https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0,
|
|
1539
|
+
* derived from Java's string hashcode implementation
|
|
1540
|
+
* @param {string} string
|
|
1541
|
+
* @param {number} hash
|
|
1542
|
+
* @returns {number}
|
|
1543
|
+
*/
|
|
1544
|
+
function stringHash(string, hash) {
|
|
919
1545
|
let char = 0
|
|
920
1546
|
while (char < string.length) {
|
|
921
1547
|
hash = (hash << 5) - hash + string.charCodeAt(char++) | 0 // bitwise or ensures we have a 32-bit int
|
|
@@ -923,7 +1549,11 @@ const htmx = (function () {
|
|
|
923
1549
|
return hash
|
|
924
1550
|
}
|
|
925
1551
|
|
|
926
|
-
|
|
1552
|
+
/**
|
|
1553
|
+
* @param {Element} elt
|
|
1554
|
+
* @returns {number}
|
|
1555
|
+
*/
|
|
1556
|
+
function attributeHash(elt) {
|
|
927
1557
|
let hash = 0
|
|
928
1558
|
// IE fix
|
|
929
1559
|
if (elt.attributes) {
|
|
@@ -938,87 +1568,135 @@ const htmx = (function () {
|
|
|
938
1568
|
return hash
|
|
939
1569
|
}
|
|
940
1570
|
|
|
941
|
-
|
|
1571
|
+
/**
|
|
1572
|
+
* @param {EventTarget} elt
|
|
1573
|
+
*/
|
|
1574
|
+
function deInitOnHandlers(elt) {
|
|
942
1575
|
const internalData = getInternalData(elt)
|
|
943
1576
|
if (internalData.onHandlers) {
|
|
944
1577
|
for (let i = 0; i < internalData.onHandlers.length; i++) {
|
|
945
1578
|
const handlerInfo = internalData.onHandlers[i]
|
|
946
|
-
elt
|
|
1579
|
+
removeEventListenerImpl(elt, handlerInfo.event, handlerInfo.listener)
|
|
947
1580
|
}
|
|
948
1581
|
delete internalData.onHandlers
|
|
949
1582
|
}
|
|
950
1583
|
}
|
|
951
1584
|
|
|
952
|
-
|
|
1585
|
+
/**
|
|
1586
|
+
* @param {Node} element
|
|
1587
|
+
*/
|
|
1588
|
+
function deInitNode(element) {
|
|
953
1589
|
const internalData = getInternalData(element)
|
|
954
1590
|
if (internalData.timeout) {
|
|
955
1591
|
clearTimeout(internalData.timeout)
|
|
956
1592
|
}
|
|
957
1593
|
if (internalData.listenerInfos) {
|
|
958
|
-
forEach(internalData.listenerInfos, function
|
|
1594
|
+
forEach(internalData.listenerInfos, function(info) {
|
|
959
1595
|
if (info.on) {
|
|
960
|
-
info.on
|
|
1596
|
+
removeEventListenerImpl(info.on, info.trigger, info.listener)
|
|
961
1597
|
}
|
|
962
1598
|
})
|
|
963
1599
|
}
|
|
964
1600
|
deInitOnHandlers(element)
|
|
965
|
-
forEach(Object.keys(internalData), function
|
|
1601
|
+
forEach(Object.keys(internalData), function(key) { delete internalData[key] })
|
|
966
1602
|
}
|
|
967
1603
|
|
|
968
|
-
|
|
1604
|
+
/**
|
|
1605
|
+
* @param {Node} element
|
|
1606
|
+
*/
|
|
1607
|
+
function cleanUpElement(element) {
|
|
969
1608
|
triggerEvent(element, 'htmx:beforeCleanupElement')
|
|
970
1609
|
deInitNode(element)
|
|
1610
|
+
// @ts-ignore IE11 code
|
|
1611
|
+
// noinspection JSUnresolvedReference
|
|
971
1612
|
if (element.children) { // IE
|
|
972
|
-
|
|
1613
|
+
// @ts-ignore
|
|
1614
|
+
forEach(element.children, function(child) { cleanUpElement(child) })
|
|
973
1615
|
}
|
|
974
1616
|
}
|
|
975
1617
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1618
|
+
/**
|
|
1619
|
+
* @param {Node} target
|
|
1620
|
+
* @param {ParentNode} fragment
|
|
1621
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1622
|
+
*/
|
|
1623
|
+
function swapOuterHTML(target, fragment, settleInfo) {
|
|
1624
|
+
/** @type {Node} */
|
|
1625
|
+
let newElt
|
|
1626
|
+
const eltBeforeNewContent = target.previousSibling
|
|
1627
|
+
insertNodesBefore(parentElt(target), target, fragment, settleInfo)
|
|
1628
|
+
if (eltBeforeNewContent == null) {
|
|
1629
|
+
newElt = parentElt(target).firstChild
|
|
979
1630
|
} else {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
if (
|
|
985
|
-
newElt
|
|
986
|
-
} else {
|
|
987
|
-
newElt = eltBeforeNewContent.nextSibling
|
|
988
|
-
}
|
|
989
|
-
settleInfo.elts = settleInfo.elts.filter(function (e) { return e != target })
|
|
990
|
-
while (newElt && newElt !== target) {
|
|
991
|
-
if (newElt.nodeType === Node.ELEMENT_NODE) {
|
|
992
|
-
settleInfo.elts.push(newElt)
|
|
993
|
-
}
|
|
1631
|
+
newElt = eltBeforeNewContent.nextSibling
|
|
1632
|
+
}
|
|
1633
|
+
settleInfo.elts = settleInfo.elts.filter(function(e) { return e !== target })
|
|
1634
|
+
while (newElt && newElt !== target) {
|
|
1635
|
+
if (newElt instanceof Element) {
|
|
1636
|
+
settleInfo.elts.push(newElt)
|
|
994
1637
|
newElt = newElt.nextElementSibling
|
|
1638
|
+
} else {
|
|
1639
|
+
newElt = null
|
|
995
1640
|
}
|
|
996
|
-
|
|
997
|
-
|
|
1641
|
+
}
|
|
1642
|
+
cleanUpElement(target)
|
|
1643
|
+
if (target instanceof Element) {
|
|
1644
|
+
target.remove()
|
|
1645
|
+
} else {
|
|
1646
|
+
target.parentNode.removeChild(target)
|
|
998
1647
|
}
|
|
999
1648
|
}
|
|
1000
1649
|
|
|
1001
|
-
|
|
1650
|
+
/**
|
|
1651
|
+
* @param {Node} target
|
|
1652
|
+
* @param {ParentNode} fragment
|
|
1653
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1654
|
+
*/
|
|
1655
|
+
function swapAfterBegin(target, fragment, settleInfo) {
|
|
1002
1656
|
return insertNodesBefore(target, target.firstChild, fragment, settleInfo)
|
|
1003
1657
|
}
|
|
1004
1658
|
|
|
1005
|
-
|
|
1659
|
+
/**
|
|
1660
|
+
* @param {Node} target
|
|
1661
|
+
* @param {ParentNode} fragment
|
|
1662
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1663
|
+
*/
|
|
1664
|
+
function swapBeforeBegin(target, fragment, settleInfo) {
|
|
1006
1665
|
return insertNodesBefore(parentElt(target), target, fragment, settleInfo)
|
|
1007
1666
|
}
|
|
1008
1667
|
|
|
1009
|
-
|
|
1668
|
+
/**
|
|
1669
|
+
* @param {Node} target
|
|
1670
|
+
* @param {ParentNode} fragment
|
|
1671
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1672
|
+
*/
|
|
1673
|
+
function swapBeforeEnd(target, fragment, settleInfo) {
|
|
1010
1674
|
return insertNodesBefore(target, null, fragment, settleInfo)
|
|
1011
1675
|
}
|
|
1012
1676
|
|
|
1013
|
-
|
|
1677
|
+
/**
|
|
1678
|
+
* @param {Node} target
|
|
1679
|
+
* @param {ParentNode} fragment
|
|
1680
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1681
|
+
*/
|
|
1682
|
+
function swapAfterEnd(target, fragment, settleInfo) {
|
|
1014
1683
|
return insertNodesBefore(parentElt(target), target.nextSibling, fragment, settleInfo)
|
|
1015
1684
|
}
|
|
1016
|
-
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* @param {Node} target
|
|
1688
|
+
*/
|
|
1689
|
+
function swapDelete(target) {
|
|
1017
1690
|
cleanUpElement(target)
|
|
1018
1691
|
return parentElt(target).removeChild(target)
|
|
1019
1692
|
}
|
|
1020
1693
|
|
|
1021
|
-
|
|
1694
|
+
/**
|
|
1695
|
+
* @param {Node} target
|
|
1696
|
+
* @param {ParentNode} fragment
|
|
1697
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1698
|
+
*/
|
|
1699
|
+
function swapInnerHTML(target, fragment, settleInfo) {
|
|
1022
1700
|
const firstChild = target.firstChild
|
|
1023
1701
|
insertNodesBefore(target, firstChild, fragment, settleInfo)
|
|
1024
1702
|
if (firstChild) {
|
|
@@ -1031,19 +1709,14 @@ const htmx = (function () {
|
|
|
1031
1709
|
}
|
|
1032
1710
|
}
|
|
1033
1711
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
return fragment
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
function swap (swapStyle, elt, target, fragment, settleInfo) {
|
|
1712
|
+
/**
|
|
1713
|
+
* @param {HtmxSwapStyle} swapStyle
|
|
1714
|
+
* @param {Element} elt
|
|
1715
|
+
* @param {Node} target
|
|
1716
|
+
* @param {ParentNode} fragment
|
|
1717
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1718
|
+
*/
|
|
1719
|
+
function swapWithStyle(swapStyle, elt, target, fragment, settleInfo) {
|
|
1047
1720
|
switch (swapStyle) {
|
|
1048
1721
|
case 'none':
|
|
1049
1722
|
return
|
|
@@ -1063,7 +1736,7 @@ const htmx = (function () {
|
|
|
1063
1736
|
swapAfterEnd(target, fragment, settleInfo)
|
|
1064
1737
|
return
|
|
1065
1738
|
case 'delete':
|
|
1066
|
-
swapDelete(target
|
|
1739
|
+
swapDelete(target)
|
|
1067
1740
|
return
|
|
1068
1741
|
default:
|
|
1069
1742
|
var extensions = getExtensions(elt)
|
|
@@ -1090,33 +1763,181 @@ const htmx = (function () {
|
|
|
1090
1763
|
if (swapStyle === 'innerHTML') {
|
|
1091
1764
|
swapInnerHTML(target, fragment, settleInfo)
|
|
1092
1765
|
} else {
|
|
1093
|
-
|
|
1766
|
+
swapWithStyle(htmx.config.defaultSwapStyle, elt, target, fragment, settleInfo)
|
|
1094
1767
|
}
|
|
1095
1768
|
}
|
|
1096
1769
|
}
|
|
1097
1770
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1771
|
+
/**
|
|
1772
|
+
* @param {DocumentFragment} fragment
|
|
1773
|
+
* @param {HtmxSettleInfo} settleInfo
|
|
1774
|
+
*/
|
|
1775
|
+
function findAndSwapOobElements(fragment, settleInfo) {
|
|
1776
|
+
forEach(findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]'), function(oobElement) {
|
|
1777
|
+
if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
|
|
1778
|
+
const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
|
|
1779
|
+
if (oobValue != null) {
|
|
1780
|
+
oobSwap(oobValue, oobElement, settleInfo)
|
|
1781
|
+
}
|
|
1782
|
+
} else {
|
|
1783
|
+
oobElement.removeAttribute('hx-swap-oob')
|
|
1784
|
+
oobElement.removeAttribute('data-hx-swap-oob')
|
|
1785
|
+
}
|
|
1786
|
+
})
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
/**
|
|
1790
|
+
* Implements complete swapping pipeline, including: focus and selection preservation,
|
|
1791
|
+
* title updates, scroll, OOB swapping, normal swapping and settling
|
|
1792
|
+
* @param {string|Element} target
|
|
1793
|
+
* @param {string} content
|
|
1794
|
+
* @param {HtmxSwapSpecification} swapSpec
|
|
1795
|
+
* @param {SwapOptions} [swapOptions]
|
|
1796
|
+
*/
|
|
1797
|
+
function swap(target, content, swapSpec, swapOptions) {
|
|
1798
|
+
if (!swapOptions) {
|
|
1799
|
+
swapOptions = {}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
target = resolveTarget(target)
|
|
1803
|
+
|
|
1804
|
+
// preserve focus and selection
|
|
1805
|
+
const activeElt = document.activeElement
|
|
1806
|
+
let selectionInfo = {}
|
|
1807
|
+
try {
|
|
1808
|
+
selectionInfo = {
|
|
1809
|
+
elt: activeElt,
|
|
1810
|
+
// @ts-ignore
|
|
1811
|
+
start: activeElt ? activeElt.selectionStart : null,
|
|
1812
|
+
// @ts-ignore
|
|
1813
|
+
end: activeElt ? activeElt.selectionEnd : null
|
|
1104
1814
|
}
|
|
1815
|
+
} catch (e) {
|
|
1816
|
+
// safari issue - see https://github.com/microsoft/playwright/issues/5894
|
|
1105
1817
|
}
|
|
1106
|
-
|
|
1818
|
+
const settleInfo = makeSettleInfo(target)
|
|
1107
1819
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
fragment =
|
|
1820
|
+
// For text content swaps, don't parse the response as HTML, just insert it
|
|
1821
|
+
if (swapSpec.swapStyle === 'textContent') {
|
|
1822
|
+
target.textContent = content
|
|
1823
|
+
// Otherwise, make the fragment and process it
|
|
1824
|
+
} else {
|
|
1825
|
+
let fragment = makeFragment(content)
|
|
1826
|
+
|
|
1827
|
+
settleInfo.title = fragment.title
|
|
1828
|
+
|
|
1829
|
+
// select-oob swaps
|
|
1830
|
+
if (swapOptions.selectOOB) {
|
|
1831
|
+
const oobSelectValues = swapOptions.selectOOB.split(',')
|
|
1832
|
+
for (let i = 0; i < oobSelectValues.length; i++) {
|
|
1833
|
+
const oobSelectValue = oobSelectValues[i].split(':', 2)
|
|
1834
|
+
let id = oobSelectValue[0].trim()
|
|
1835
|
+
if (id.indexOf('#') === 0) {
|
|
1836
|
+
id = id.substring(1)
|
|
1837
|
+
}
|
|
1838
|
+
const oobValue = oobSelectValue[1] || 'true'
|
|
1839
|
+
const oobElement = fragment.querySelector('#' + id)
|
|
1840
|
+
if (oobElement) {
|
|
1841
|
+
oobSwap(oobValue, oobElement, settleInfo)
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
// oob swaps
|
|
1846
|
+
findAndSwapOobElements(fragment, settleInfo)
|
|
1847
|
+
forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
|
|
1848
|
+
findAndSwapOobElements(template.content, settleInfo)
|
|
1849
|
+
if (template.content.childElementCount === 0) {
|
|
1850
|
+
// Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
|
|
1851
|
+
template.remove()
|
|
1852
|
+
}
|
|
1853
|
+
})
|
|
1854
|
+
|
|
1855
|
+
// normal swap
|
|
1856
|
+
if (swapOptions.select) {
|
|
1857
|
+
const newFragment = getDocument().createDocumentFragment()
|
|
1858
|
+
forEach(fragment.querySelectorAll(swapOptions.select), function(node) {
|
|
1859
|
+
newFragment.appendChild(node)
|
|
1860
|
+
})
|
|
1861
|
+
fragment = newFragment
|
|
1862
|
+
}
|
|
1114
1863
|
handlePreservedElements(fragment)
|
|
1115
|
-
|
|
1864
|
+
swapWithStyle(swapSpec.swapStyle, swapOptions.contextElement, target, fragment, settleInfo)
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// apply saved focus and selection information to swapped content
|
|
1868
|
+
if (selectionInfo.elt &&
|
|
1869
|
+
!bodyContains(selectionInfo.elt) &&
|
|
1870
|
+
getRawAttribute(selectionInfo.elt, 'id')) {
|
|
1871
|
+
const newActiveElt = document.getElementById(getRawAttribute(selectionInfo.elt, 'id'))
|
|
1872
|
+
const focusOptions = { preventScroll: swapSpec.focusScroll !== undefined ? !swapSpec.focusScroll : !htmx.config.defaultFocusScroll }
|
|
1873
|
+
if (newActiveElt) {
|
|
1874
|
+
// @ts-ignore
|
|
1875
|
+
if (selectionInfo.start && newActiveElt.setSelectionRange) {
|
|
1876
|
+
try {
|
|
1877
|
+
// @ts-ignore
|
|
1878
|
+
newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end)
|
|
1879
|
+
} catch (e) {
|
|
1880
|
+
// the setSelectionRange method is present on fields that don't support it, so just let this fail
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
newActiveElt.focus(focusOptions)
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
target.classList.remove(htmx.config.swappingClass)
|
|
1888
|
+
forEach(settleInfo.elts, function(elt) {
|
|
1889
|
+
if (elt.classList) {
|
|
1890
|
+
elt.classList.add(htmx.config.settlingClass)
|
|
1891
|
+
}
|
|
1892
|
+
triggerEvent(elt, 'htmx:afterSwap', swapOptions.eventInfo)
|
|
1893
|
+
})
|
|
1894
|
+
if (swapOptions.afterSwapCallback) {
|
|
1895
|
+
swapOptions.afterSwapCallback()
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// merge in new title after swap but before settle
|
|
1899
|
+
if (!swapSpec.ignoreTitle) {
|
|
1900
|
+
handleTitle(settleInfo.title)
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// settle
|
|
1904
|
+
const doSettle = function() {
|
|
1905
|
+
forEach(settleInfo.tasks, function(task) {
|
|
1906
|
+
task.call()
|
|
1907
|
+
})
|
|
1908
|
+
forEach(settleInfo.elts, function(elt) {
|
|
1909
|
+
if (elt.classList) {
|
|
1910
|
+
elt.classList.remove(htmx.config.settlingClass)
|
|
1911
|
+
}
|
|
1912
|
+
triggerEvent(elt, 'htmx:afterSettle', swapOptions.eventInfo)
|
|
1913
|
+
})
|
|
1914
|
+
|
|
1915
|
+
if (swapOptions.anchor) {
|
|
1916
|
+
const anchorTarget = asElement(resolveTarget('#' + swapOptions.anchor))
|
|
1917
|
+
if (anchorTarget) {
|
|
1918
|
+
anchorTarget.scrollIntoView({ block: 'start', behavior: 'auto' })
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
updateScrollState(settleInfo.elts, swapSpec)
|
|
1923
|
+
if (swapOptions.afterSettleCallback) {
|
|
1924
|
+
swapOptions.afterSettleCallback()
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
if (swapSpec.settleDelay > 0) {
|
|
1929
|
+
getWindow().setTimeout(doSettle, swapSpec.settleDelay)
|
|
1930
|
+
} else {
|
|
1931
|
+
doSettle()
|
|
1116
1932
|
}
|
|
1117
1933
|
}
|
|
1118
1934
|
|
|
1119
|
-
|
|
1935
|
+
/**
|
|
1936
|
+
* @param {XMLHttpRequest} xhr
|
|
1937
|
+
* @param {string} header
|
|
1938
|
+
* @param {EventTarget} elt
|
|
1939
|
+
*/
|
|
1940
|
+
function handleTriggerHeader(xhr, header, elt) {
|
|
1120
1941
|
const triggerBody = xhr.getResponseHeader(header)
|
|
1121
1942
|
if (triggerBody.indexOf('{') === 0) {
|
|
1122
1943
|
const triggers = parseJSON(triggerBody)
|
|
@@ -1145,7 +1966,13 @@ const htmx = (function () {
|
|
|
1145
1966
|
const NOT_WHITESPACE = /[^\s]/
|
|
1146
1967
|
const COMBINED_SELECTOR_START = /[{(]/
|
|
1147
1968
|
const COMBINED_SELECTOR_END = /[})]/
|
|
1148
|
-
|
|
1969
|
+
|
|
1970
|
+
/**
|
|
1971
|
+
* @param {string} str
|
|
1972
|
+
* @returns {string[]}
|
|
1973
|
+
*/
|
|
1974
|
+
function tokenizeString(str) {
|
|
1975
|
+
/** @type string[] */
|
|
1149
1976
|
const tokens = []
|
|
1150
1977
|
let position = 0
|
|
1151
1978
|
while (position < str.length) {
|
|
@@ -1175,7 +2002,13 @@ const htmx = (function () {
|
|
|
1175
2002
|
return tokens
|
|
1176
2003
|
}
|
|
1177
2004
|
|
|
1178
|
-
|
|
2005
|
+
/**
|
|
2006
|
+
* @param {string} token
|
|
2007
|
+
* @param {string|null} last
|
|
2008
|
+
* @param {string} paramName
|
|
2009
|
+
* @returns {boolean}
|
|
2010
|
+
*/
|
|
2011
|
+
function isPossibleRelativeReference(token, last, paramName) {
|
|
1179
2012
|
return SYMBOL_START.exec(token.charAt(0)) &&
|
|
1180
2013
|
token !== 'true' &&
|
|
1181
2014
|
token !== 'false' &&
|
|
@@ -1184,7 +2017,13 @@ const htmx = (function () {
|
|
|
1184
2017
|
last !== '.'
|
|
1185
2018
|
}
|
|
1186
2019
|
|
|
1187
|
-
|
|
2020
|
+
/**
|
|
2021
|
+
* @param {EventTarget|string} elt
|
|
2022
|
+
* @param {string[]} tokens
|
|
2023
|
+
* @param {string} paramName
|
|
2024
|
+
* @returns {ConditionalFunction|null}
|
|
2025
|
+
*/
|
|
2026
|
+
function maybeGenerateConditional(elt, tokens, paramName) {
|
|
1188
2027
|
if (tokens[0] === '[') {
|
|
1189
2028
|
tokens.shift()
|
|
1190
2029
|
let bracketCount = 1
|
|
@@ -1192,6 +2031,7 @@ const htmx = (function () {
|
|
|
1192
2031
|
let last = null
|
|
1193
2032
|
while (tokens.length > 0) {
|
|
1194
2033
|
const token = tokens[0]
|
|
2034
|
+
// @ts-ignore For some reason tsc doesn't understand the shift call, and thinks we're comparing the same value here, i.e. '[' vs ']'
|
|
1195
2035
|
if (token === ']') {
|
|
1196
2036
|
bracketCount--
|
|
1197
2037
|
if (bracketCount === 0) {
|
|
@@ -1201,10 +2041,10 @@ const htmx = (function () {
|
|
|
1201
2041
|
tokens.shift()
|
|
1202
2042
|
conditionalSource += ')})'
|
|
1203
2043
|
try {
|
|
1204
|
-
const conditionFunction = maybeEval(elt, function
|
|
2044
|
+
const conditionFunction = maybeEval(elt, function() {
|
|
1205
2045
|
return Function(conditionalSource)()
|
|
1206
2046
|
},
|
|
1207
|
-
function
|
|
2047
|
+
function() { return true })
|
|
1208
2048
|
conditionFunction.source = conditionalSource
|
|
1209
2049
|
return conditionFunction
|
|
1210
2050
|
} catch (e) {
|
|
@@ -1225,7 +2065,12 @@ const htmx = (function () {
|
|
|
1225
2065
|
}
|
|
1226
2066
|
}
|
|
1227
2067
|
|
|
1228
|
-
|
|
2068
|
+
/**
|
|
2069
|
+
* @param {string[]} tokens
|
|
2070
|
+
* @param {RegExp} match
|
|
2071
|
+
* @returns {string}
|
|
2072
|
+
*/
|
|
2073
|
+
function consumeUntil(tokens, match) {
|
|
1229
2074
|
let result = ''
|
|
1230
2075
|
while (tokens.length > 0 && !match.test(tokens[0])) {
|
|
1231
2076
|
result += tokens.shift()
|
|
@@ -1233,7 +2078,11 @@ const htmx = (function () {
|
|
|
1233
2078
|
return result
|
|
1234
2079
|
}
|
|
1235
2080
|
|
|
1236
|
-
|
|
2081
|
+
/**
|
|
2082
|
+
* @param {string[]} tokens
|
|
2083
|
+
* @returns {string}
|
|
2084
|
+
*/
|
|
2085
|
+
function consumeCSSSelector(tokens) {
|
|
1237
2086
|
let result
|
|
1238
2087
|
if (tokens.length > 0 && COMBINED_SELECTOR_START.test(tokens[0])) {
|
|
1239
2088
|
tokens.shift()
|
|
@@ -1248,12 +2097,13 @@ const htmx = (function () {
|
|
|
1248
2097
|
const INPUT_SELECTOR = 'input, textarea, select'
|
|
1249
2098
|
|
|
1250
2099
|
/**
|
|
1251
|
-
* @param {
|
|
2100
|
+
* @param {Element} elt
|
|
1252
2101
|
* @param {string} explicitTrigger
|
|
1253
|
-
* @param {
|
|
1254
|
-
* @returns {
|
|
2102
|
+
* @param {Object} cache for trigger specs
|
|
2103
|
+
* @returns {HtmxTriggerSpecification[]}
|
|
1255
2104
|
*/
|
|
1256
|
-
function parseAndCacheTrigger
|
|
2105
|
+
function parseAndCacheTrigger(elt, explicitTrigger, cache) {
|
|
2106
|
+
/** @type HtmxTriggerSpecification[] */
|
|
1257
2107
|
const triggerSpecs = []
|
|
1258
2108
|
const tokens = tokenizeString(explicitTrigger)
|
|
1259
2109
|
do {
|
|
@@ -1262,6 +2112,7 @@ const htmx = (function () {
|
|
|
1262
2112
|
const trigger = consumeUntil(tokens, /[,\[\s]/)
|
|
1263
2113
|
if (trigger !== '') {
|
|
1264
2114
|
if (trigger === 'every') {
|
|
2115
|
+
/** @type HtmxTriggerSpecification */
|
|
1265
2116
|
const every = { trigger: 'every' }
|
|
1266
2117
|
consumeUntil(tokens, NOT_WHITESPACE)
|
|
1267
2118
|
every.pollInterval = parseInterval(consumeUntil(tokens, /[,\[\s]/))
|
|
@@ -1272,6 +2123,7 @@ const htmx = (function () {
|
|
|
1272
2123
|
}
|
|
1273
2124
|
triggerSpecs.push(every)
|
|
1274
2125
|
} else {
|
|
2126
|
+
/** @type HtmxTriggerSpecification */
|
|
1275
2127
|
const triggerSpec = { trigger }
|
|
1276
2128
|
var eventFilter = maybeGenerateConditional(elt, tokens, 'event')
|
|
1277
2129
|
if (eventFilter) {
|
|
@@ -1339,10 +2191,10 @@ const htmx = (function () {
|
|
|
1339
2191
|
}
|
|
1340
2192
|
|
|
1341
2193
|
/**
|
|
1342
|
-
* @param {
|
|
1343
|
-
* @returns {
|
|
2194
|
+
* @param {Element} elt
|
|
2195
|
+
* @returns {HtmxTriggerSpecification[]}
|
|
1344
2196
|
*/
|
|
1345
|
-
function getTriggerSpecs
|
|
2197
|
+
function getTriggerSpecs(elt) {
|
|
1346
2198
|
const explicitTrigger = getAttributeValue(elt, 'hx-trigger')
|
|
1347
2199
|
let triggerSpecs = []
|
|
1348
2200
|
if (explicitTrigger) {
|
|
@@ -1363,13 +2215,21 @@ const htmx = (function () {
|
|
|
1363
2215
|
}
|
|
1364
2216
|
}
|
|
1365
2217
|
|
|
1366
|
-
|
|
2218
|
+
/**
|
|
2219
|
+
* @param {Element} elt
|
|
2220
|
+
*/
|
|
2221
|
+
function cancelPolling(elt) {
|
|
1367
2222
|
getInternalData(elt).cancelled = true
|
|
1368
2223
|
}
|
|
1369
2224
|
|
|
1370
|
-
|
|
2225
|
+
/**
|
|
2226
|
+
* @param {Element} elt
|
|
2227
|
+
* @param {TriggerHandler} handler
|
|
2228
|
+
* @param {HtmxTriggerSpecification} spec
|
|
2229
|
+
*/
|
|
2230
|
+
function processPolling(elt, handler, spec) {
|
|
1371
2231
|
const nodeData = getInternalData(elt)
|
|
1372
|
-
nodeData.timeout = setTimeout(function
|
|
2232
|
+
nodeData.timeout = getWindow().setTimeout(function() {
|
|
1373
2233
|
if (bodyContains(elt) && nodeData.cancelled !== true) {
|
|
1374
2234
|
if (!maybeFilterEvent(spec, elt, makeEvent('hx:poll:trigger', {
|
|
1375
2235
|
triggerSpec: spec,
|
|
@@ -1382,14 +2242,23 @@ const htmx = (function () {
|
|
|
1382
2242
|
}, spec.pollInterval)
|
|
1383
2243
|
}
|
|
1384
2244
|
|
|
1385
|
-
|
|
2245
|
+
/**
|
|
2246
|
+
* @param {HTMLAnchorElement} elt
|
|
2247
|
+
* @returns {boolean}
|
|
2248
|
+
*/
|
|
2249
|
+
function isLocalLink(elt) {
|
|
1386
2250
|
return location.hostname === elt.hostname &&
|
|
1387
2251
|
getRawAttribute(elt, 'href') &&
|
|
1388
2252
|
getRawAttribute(elt, 'href').indexOf('#') !== 0
|
|
1389
2253
|
}
|
|
1390
2254
|
|
|
1391
|
-
|
|
1392
|
-
|
|
2255
|
+
/**
|
|
2256
|
+
* @param {Element} elt
|
|
2257
|
+
* @param {HtmxNodeInternalData} nodeData
|
|
2258
|
+
* @param {HtmxTriggerSpecification[]} triggerSpecs
|
|
2259
|
+
*/
|
|
2260
|
+
function boostElement(elt, nodeData, triggerSpecs) {
|
|
2261
|
+
if ((elt instanceof HTMLAnchorElement && isLocalLink(elt) && (elt.target === '' || elt.target === '_self')) || elt.tagName === 'FORM') {
|
|
1393
2262
|
nodeData.boosted = true
|
|
1394
2263
|
let verb, path
|
|
1395
2264
|
if (elt.tagName === 'A') {
|
|
@@ -1402,8 +2271,9 @@ const htmx = (function () {
|
|
|
1402
2271
|
}
|
|
1403
2272
|
path = getRawAttribute(elt, 'action')
|
|
1404
2273
|
}
|
|
1405
|
-
triggerSpecs.forEach(function
|
|
1406
|
-
addEventListener(elt, function
|
|
2274
|
+
triggerSpecs.forEach(function(triggerSpec) {
|
|
2275
|
+
addEventListener(elt, function(node, evt) {
|
|
2276
|
+
const elt = asElement(node)
|
|
1407
2277
|
if (closest(elt, htmx.config.disableSelector)) {
|
|
1408
2278
|
cleanUpElement(elt)
|
|
1409
2279
|
return
|
|
@@ -1415,12 +2285,15 @@ const htmx = (function () {
|
|
|
1415
2285
|
}
|
|
1416
2286
|
|
|
1417
2287
|
/**
|
|
1418
|
-
*
|
|
1419
2288
|
* @param {Event} evt
|
|
1420
|
-
* @param {
|
|
1421
|
-
* @returns
|
|
2289
|
+
* @param {Node} node
|
|
2290
|
+
* @returns {boolean}
|
|
1422
2291
|
*/
|
|
1423
|
-
function shouldCancel
|
|
2292
|
+
function shouldCancel(evt, node) {
|
|
2293
|
+
const elt = asElement(node)
|
|
2294
|
+
if (!elt) {
|
|
2295
|
+
return false
|
|
2296
|
+
}
|
|
1424
2297
|
if (evt.type === 'submit' || evt.type === 'click') {
|
|
1425
2298
|
if (elt.tagName === 'FORM') {
|
|
1426
2299
|
return true
|
|
@@ -1428,7 +2301,7 @@ const htmx = (function () {
|
|
|
1428
2301
|
if (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) {
|
|
1429
2302
|
return true
|
|
1430
2303
|
}
|
|
1431
|
-
if (elt
|
|
2304
|
+
if (elt instanceof HTMLAnchorElement && elt.href &&
|
|
1432
2305
|
(elt.getAttribute('href') === '#' || elt.getAttribute('href').indexOf('#') !== 0)) {
|
|
1433
2306
|
return true
|
|
1434
2307
|
}
|
|
@@ -1436,25 +2309,47 @@ const htmx = (function () {
|
|
|
1436
2309
|
return false
|
|
1437
2310
|
}
|
|
1438
2311
|
|
|
1439
|
-
|
|
1440
|
-
|
|
2312
|
+
/**
|
|
2313
|
+
* @param {Node} elt
|
|
2314
|
+
* @param {Event|MouseEvent|KeyboardEvent|TouchEvent} evt
|
|
2315
|
+
* @returns {boolean}
|
|
2316
|
+
*/
|
|
2317
|
+
function ignoreBoostedAnchorCtrlClick(elt, evt) {
|
|
2318
|
+
return getInternalData(elt).boosted && elt instanceof HTMLAnchorElement && evt.type === 'click' &&
|
|
2319
|
+
// @ts-ignore this will resolve to undefined for events that don't define those properties, which is fine
|
|
2320
|
+
(evt.ctrlKey || evt.metaKey)
|
|
1441
2321
|
}
|
|
1442
2322
|
|
|
1443
|
-
|
|
2323
|
+
/**
|
|
2324
|
+
* @param {HtmxTriggerSpecification} triggerSpec
|
|
2325
|
+
* @param {Node} elt
|
|
2326
|
+
* @param {Event} evt
|
|
2327
|
+
* @returns {boolean}
|
|
2328
|
+
*/
|
|
2329
|
+
function maybeFilterEvent(triggerSpec, elt, evt) {
|
|
1444
2330
|
const eventFilter = triggerSpec.eventFilter
|
|
1445
2331
|
if (eventFilter) {
|
|
1446
2332
|
try {
|
|
1447
2333
|
return eventFilter.call(elt, evt) !== true
|
|
1448
2334
|
} catch (e) {
|
|
1449
|
-
|
|
2335
|
+
const source = eventFilter.source
|
|
2336
|
+
triggerErrorEvent(getDocument().body, 'htmx:eventFilter:error', { error: e, source })
|
|
1450
2337
|
return true
|
|
1451
2338
|
}
|
|
1452
2339
|
}
|
|
1453
2340
|
return false
|
|
1454
2341
|
}
|
|
1455
2342
|
|
|
1456
|
-
|
|
2343
|
+
/**
|
|
2344
|
+
* @param {Node} elt
|
|
2345
|
+
* @param {TriggerHandler} handler
|
|
2346
|
+
* @param {HtmxNodeInternalData} nodeData
|
|
2347
|
+
* @param {HtmxTriggerSpecification} triggerSpec
|
|
2348
|
+
* @param {boolean} [explicitCancel]
|
|
2349
|
+
*/
|
|
2350
|
+
function addEventListener(elt, handler, nodeData, triggerSpec, explicitCancel) {
|
|
1457
2351
|
const elementData = getInternalData(elt)
|
|
2352
|
+
/** @type {(Node|Window)[]} */
|
|
1458
2353
|
let eltsToListenOn
|
|
1459
2354
|
if (triggerSpec.from) {
|
|
1460
2355
|
eltsToListenOn = querySelectorAllExt(elt, triggerSpec.from)
|
|
@@ -1463,13 +2358,15 @@ const htmx = (function () {
|
|
|
1463
2358
|
}
|
|
1464
2359
|
// store the initial values of the elements, so we can tell if they change
|
|
1465
2360
|
if (triggerSpec.changed) {
|
|
1466
|
-
eltsToListenOn.forEach(function
|
|
2361
|
+
eltsToListenOn.forEach(function(eltToListenOn) {
|
|
1467
2362
|
const eltToListenOnData = getInternalData(eltToListenOn)
|
|
2363
|
+
// @ts-ignore value will be undefined for non-input elements, which is fine
|
|
1468
2364
|
eltToListenOnData.lastValue = eltToListenOn.value
|
|
1469
2365
|
})
|
|
1470
2366
|
}
|
|
1471
|
-
forEach(eltsToListenOn, function
|
|
1472
|
-
|
|
2367
|
+
forEach(eltsToListenOn, function(eltToListenOn) {
|
|
2368
|
+
/** @type EventListener */
|
|
2369
|
+
const eventListener = function(evt) {
|
|
1473
2370
|
if (!bodyContains(elt)) {
|
|
1474
2371
|
eltToListenOn.removeEventListener(triggerSpec.trigger, eventListener)
|
|
1475
2372
|
return
|
|
@@ -1494,7 +2391,7 @@ const htmx = (function () {
|
|
|
1494
2391
|
evt.stopPropagation()
|
|
1495
2392
|
}
|
|
1496
2393
|
if (triggerSpec.target && evt.target) {
|
|
1497
|
-
if (!matches(evt.target, triggerSpec.target)) {
|
|
2394
|
+
if (!matches(asElement(evt.target), triggerSpec.target)) {
|
|
1498
2395
|
return
|
|
1499
2396
|
}
|
|
1500
2397
|
}
|
|
@@ -1507,10 +2404,12 @@ const htmx = (function () {
|
|
|
1507
2404
|
}
|
|
1508
2405
|
if (triggerSpec.changed) {
|
|
1509
2406
|
const eltToListenOnData = getInternalData(eltToListenOn)
|
|
1510
|
-
|
|
2407
|
+
// @ts-ignore value will be undefined for non-input elements, which is fine
|
|
2408
|
+
const value = eltToListenOn.value
|
|
2409
|
+
if (eltToListenOnData.lastValue === value) {
|
|
1511
2410
|
return
|
|
1512
2411
|
}
|
|
1513
|
-
eltToListenOnData.lastValue =
|
|
2412
|
+
eltToListenOnData.lastValue = value
|
|
1514
2413
|
}
|
|
1515
2414
|
if (elementData.delayed) {
|
|
1516
2415
|
clearTimeout(elementData.delayed)
|
|
@@ -1522,12 +2421,12 @@ const htmx = (function () {
|
|
|
1522
2421
|
if (triggerSpec.throttle > 0) {
|
|
1523
2422
|
if (!elementData.throttle) {
|
|
1524
2423
|
handler(elt, evt)
|
|
1525
|
-
elementData.throttle = setTimeout(function
|
|
2424
|
+
elementData.throttle = getWindow().setTimeout(function() {
|
|
1526
2425
|
elementData.throttle = null
|
|
1527
2426
|
}, triggerSpec.throttle)
|
|
1528
2427
|
}
|
|
1529
2428
|
} else if (triggerSpec.delay > 0) {
|
|
1530
|
-
elementData.delayed = setTimeout(function
|
|
2429
|
+
elementData.delayed = getWindow().setTimeout(function() { handler(elt, evt) }, triggerSpec.delay)
|
|
1531
2430
|
} else {
|
|
1532
2431
|
triggerEvent(elt, 'htmx:trigger')
|
|
1533
2432
|
handler(elt, evt)
|
|
@@ -1548,16 +2447,16 @@ const htmx = (function () {
|
|
|
1548
2447
|
|
|
1549
2448
|
let windowIsScrolling = false // used by initScrollHandler
|
|
1550
2449
|
let scrollHandler = null
|
|
1551
|
-
function initScrollHandler
|
|
2450
|
+
function initScrollHandler() {
|
|
1552
2451
|
if (!scrollHandler) {
|
|
1553
|
-
scrollHandler = function
|
|
2452
|
+
scrollHandler = function() {
|
|
1554
2453
|
windowIsScrolling = true
|
|
1555
2454
|
}
|
|
1556
2455
|
window.addEventListener('scroll', scrollHandler)
|
|
1557
|
-
setInterval(function
|
|
2456
|
+
setInterval(function() {
|
|
1558
2457
|
if (windowIsScrolling) {
|
|
1559
2458
|
windowIsScrolling = false
|
|
1560
|
-
forEach(getDocument().querySelectorAll("[hx-trigger
|
|
2459
|
+
forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"), function(elt) {
|
|
1561
2460
|
maybeReveal(elt)
|
|
1562
2461
|
})
|
|
1563
2462
|
}
|
|
@@ -1565,7 +2464,10 @@ const htmx = (function () {
|
|
|
1565
2464
|
}
|
|
1566
2465
|
}
|
|
1567
2466
|
|
|
1568
|
-
|
|
2467
|
+
/**
|
|
2468
|
+
* @param {Element} elt
|
|
2469
|
+
*/
|
|
2470
|
+
function maybeReveal(elt) {
|
|
1569
2471
|
if (!hasAttribute(elt, 'data-hx-revealed') && isScrolledIntoView(elt)) {
|
|
1570
2472
|
elt.setAttribute('data-hx-revealed', 'true')
|
|
1571
2473
|
const nodeData = getInternalData(elt)
|
|
@@ -1573,37 +2475,50 @@ const htmx = (function () {
|
|
|
1573
2475
|
triggerEvent(elt, 'revealed')
|
|
1574
2476
|
} else {
|
|
1575
2477
|
// if the node isn't initialized, wait for it before triggering the request
|
|
1576
|
-
elt.addEventListener('htmx:afterProcessNode', function
|
|
2478
|
+
elt.addEventListener('htmx:afterProcessNode', function() { triggerEvent(elt, 'revealed') }, { once: true })
|
|
1577
2479
|
}
|
|
1578
2480
|
}
|
|
1579
2481
|
}
|
|
1580
2482
|
|
|
1581
2483
|
//= ===================================================================
|
|
1582
2484
|
|
|
1583
|
-
|
|
1584
|
-
|
|
2485
|
+
/**
|
|
2486
|
+
* @param {Element} elt
|
|
2487
|
+
* @param {TriggerHandler} handler
|
|
2488
|
+
* @param {HtmxNodeInternalData} nodeData
|
|
2489
|
+
* @param {number} delay
|
|
2490
|
+
*/
|
|
2491
|
+
function loadImmediately(elt, handler, nodeData, delay) {
|
|
2492
|
+
const load = function() {
|
|
1585
2493
|
if (!nodeData.loaded) {
|
|
1586
2494
|
nodeData.loaded = true
|
|
1587
2495
|
handler(elt)
|
|
1588
2496
|
}
|
|
1589
2497
|
}
|
|
1590
2498
|
if (delay > 0) {
|
|
1591
|
-
setTimeout(load, delay)
|
|
2499
|
+
getWindow().setTimeout(load, delay)
|
|
1592
2500
|
} else {
|
|
1593
2501
|
load()
|
|
1594
2502
|
}
|
|
1595
2503
|
}
|
|
1596
2504
|
|
|
1597
|
-
|
|
2505
|
+
/**
|
|
2506
|
+
* @param {Element} elt
|
|
2507
|
+
* @param {HtmxNodeInternalData} nodeData
|
|
2508
|
+
* @param {HtmxTriggerSpecification[]} triggerSpecs
|
|
2509
|
+
* @returns {boolean}
|
|
2510
|
+
*/
|
|
2511
|
+
function processVerbs(elt, nodeData, triggerSpecs) {
|
|
1598
2512
|
let explicitAction = false
|
|
1599
|
-
forEach(VERBS, function
|
|
2513
|
+
forEach(VERBS, function(verb) {
|
|
1600
2514
|
if (hasAttribute(elt, 'hx-' + verb)) {
|
|
1601
2515
|
const path = getAttributeValue(elt, 'hx-' + verb)
|
|
1602
2516
|
explicitAction = true
|
|
1603
2517
|
nodeData.path = path
|
|
1604
2518
|
nodeData.verb = verb
|
|
1605
|
-
triggerSpecs.forEach(function
|
|
1606
|
-
addTriggerHandler(elt, triggerSpec, nodeData, function
|
|
2519
|
+
triggerSpecs.forEach(function(triggerSpec) {
|
|
2520
|
+
addTriggerHandler(elt, triggerSpec, nodeData, function(node, evt) {
|
|
2521
|
+
const elt = asElement(node)
|
|
1607
2522
|
if (closest(elt, htmx.config.disableSelector)) {
|
|
1608
2523
|
cleanUpElement(elt)
|
|
1609
2524
|
return
|
|
@@ -1616,11 +2531,23 @@ const htmx = (function () {
|
|
|
1616
2531
|
return explicitAction
|
|
1617
2532
|
}
|
|
1618
2533
|
|
|
1619
|
-
|
|
2534
|
+
/**
|
|
2535
|
+
* @callback TriggerHandler
|
|
2536
|
+
* @param {Node} elt
|
|
2537
|
+
* @param {Event} [evt]
|
|
2538
|
+
*/
|
|
2539
|
+
|
|
2540
|
+
/**
|
|
2541
|
+
* @param {Node} elt
|
|
2542
|
+
* @param {HtmxTriggerSpecification} triggerSpec
|
|
2543
|
+
* @param {HtmxNodeInternalData} nodeData
|
|
2544
|
+
* @param {TriggerHandler} handler
|
|
2545
|
+
*/
|
|
2546
|
+
function addTriggerHandler(elt, triggerSpec, nodeData, handler) {
|
|
1620
2547
|
if (triggerSpec.trigger === 'revealed') {
|
|
1621
2548
|
initScrollHandler()
|
|
1622
2549
|
addEventListener(elt, handler, nodeData, triggerSpec)
|
|
1623
|
-
maybeReveal(elt)
|
|
2550
|
+
maybeReveal(asElement(elt))
|
|
1624
2551
|
} else if (triggerSpec.trigger === 'intersect') {
|
|
1625
2552
|
const observerOptions = {}
|
|
1626
2553
|
if (triggerSpec.root) {
|
|
@@ -1629,7 +2556,7 @@ const htmx = (function () {
|
|
|
1629
2556
|
if (triggerSpec.threshold) {
|
|
1630
2557
|
observerOptions.threshold = parseFloat(triggerSpec.threshold)
|
|
1631
2558
|
}
|
|
1632
|
-
const observer = new IntersectionObserver(function
|
|
2559
|
+
const observer = new IntersectionObserver(function(entries) {
|
|
1633
2560
|
for (let i = 0; i < entries.length; i++) {
|
|
1634
2561
|
const entry = entries[i]
|
|
1635
2562
|
if (entry.isIntersecting) {
|
|
@@ -1638,56 +2565,29 @@ const htmx = (function () {
|
|
|
1638
2565
|
}
|
|
1639
2566
|
}
|
|
1640
2567
|
}, observerOptions)
|
|
1641
|
-
observer.observe(elt)
|
|
1642
|
-
addEventListener(elt, handler, nodeData, triggerSpec)
|
|
2568
|
+
observer.observe(asElement(elt))
|
|
2569
|
+
addEventListener(asElement(elt), handler, nodeData, triggerSpec)
|
|
1643
2570
|
} else if (triggerSpec.trigger === 'load') {
|
|
1644
2571
|
if (!maybeFilterEvent(triggerSpec, elt, makeEvent('load', { elt }))) {
|
|
1645
|
-
loadImmediately(elt, handler, nodeData, triggerSpec.delay)
|
|
2572
|
+
loadImmediately(asElement(elt), handler, nodeData, triggerSpec.delay)
|
|
1646
2573
|
}
|
|
1647
2574
|
} else if (triggerSpec.pollInterval > 0) {
|
|
1648
2575
|
nodeData.polling = true
|
|
1649
|
-
processPolling(elt, handler, triggerSpec)
|
|
2576
|
+
processPolling(asElement(elt), handler, triggerSpec)
|
|
1650
2577
|
} else {
|
|
1651
2578
|
addEventListener(elt, handler, nodeData, triggerSpec)
|
|
1652
2579
|
}
|
|
1653
2580
|
}
|
|
1654
2581
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
if (htmx.config.inlineScriptNonce) {
|
|
1664
|
-
newScript.nonce = htmx.config.inlineScriptNonce
|
|
1665
|
-
}
|
|
1666
|
-
const parent = script.parentElement
|
|
1667
|
-
|
|
1668
|
-
try {
|
|
1669
|
-
parent.insertBefore(newScript, script)
|
|
1670
|
-
} catch (e) {
|
|
1671
|
-
logError(e)
|
|
1672
|
-
} finally {
|
|
1673
|
-
// remove old script element, but only if it is still in DOM
|
|
1674
|
-
if (script.parentElement) {
|
|
1675
|
-
script.parentElement.removeChild(script)
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
function processScripts (elt) {
|
|
1682
|
-
if (matches(elt, 'script')) {
|
|
1683
|
-
evalScript(elt)
|
|
2582
|
+
/**
|
|
2583
|
+
* @param {Node} node
|
|
2584
|
+
* @returns {boolean}
|
|
2585
|
+
*/
|
|
2586
|
+
function shouldProcessHxOn(node) {
|
|
2587
|
+
const elt = asElement(node)
|
|
2588
|
+
if (!elt) {
|
|
2589
|
+
return false
|
|
1684
2590
|
}
|
|
1685
|
-
forEach(findAll(elt, 'script'), function (script) {
|
|
1686
|
-
evalScript(script)
|
|
1687
|
-
})
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
function shouldProcessHxOn (elt) {
|
|
1691
2591
|
const attributes = elt.attributes
|
|
1692
2592
|
for (let j = 0; j < attributes.length; j++) {
|
|
1693
2593
|
const attrName = attributes[j].name
|
|
@@ -1699,23 +2599,32 @@ const htmx = (function () {
|
|
|
1699
2599
|
return false
|
|
1700
2600
|
}
|
|
1701
2601
|
|
|
1702
|
-
|
|
2602
|
+
/**
|
|
2603
|
+
* @param {Node} elt
|
|
2604
|
+
* @returns {Element[]}
|
|
2605
|
+
*/
|
|
2606
|
+
function findHxOnWildcardElements(elt) {
|
|
1703
2607
|
let node = null
|
|
2608
|
+
/** @type {Element[]} */
|
|
1704
2609
|
const elements = []
|
|
1705
2610
|
|
|
1706
2611
|
if (!(elt instanceof ShadowRoot)) {
|
|
1707
2612
|
if (shouldProcessHxOn(elt)) {
|
|
1708
|
-
elements.push(elt)
|
|
2613
|
+
elements.push(asElement(elt))
|
|
1709
2614
|
}
|
|
1710
2615
|
|
|
1711
2616
|
const iter = document.evaluate('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or' +
|
|
1712
2617
|
' starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]', elt)
|
|
1713
|
-
while (node = iter.iterateNext()) elements.push(node)
|
|
2618
|
+
while (node = iter.iterateNext()) elements.push(asElement(node))
|
|
1714
2619
|
}
|
|
1715
2620
|
return elements
|
|
1716
2621
|
}
|
|
1717
2622
|
|
|
1718
|
-
|
|
2623
|
+
/**
|
|
2624
|
+
* @param {Element} elt
|
|
2625
|
+
* @returns {NodeListOf<Element>|[]}
|
|
2626
|
+
*/
|
|
2627
|
+
function findElementsToProcess(elt) {
|
|
1719
2628
|
if (elt.querySelectorAll) {
|
|
1720
2629
|
const boostedSelector = ', [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]'
|
|
1721
2630
|
const results = elt.querySelectorAll(VERB_SELECTOR + boostedSelector + ", form, [type='submit']," +
|
|
@@ -1726,23 +2635,35 @@ const htmx = (function () {
|
|
|
1726
2635
|
}
|
|
1727
2636
|
}
|
|
1728
2637
|
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
2638
|
+
/**
|
|
2639
|
+
* Handle submit buttons/inputs that have the form attribute set
|
|
2640
|
+
* see https://developer.mozilla.org/docs/Web/HTML/Element/button
|
|
2641
|
+
* @param {Event} evt
|
|
2642
|
+
*/
|
|
2643
|
+
function maybeSetLastButtonClicked(evt) {
|
|
2644
|
+
const elt = /** @type {HTMLButtonElement|HTMLInputElement} */ (closest(asElement(evt.target), "button, input[type='submit']"))
|
|
1733
2645
|
const internalData = getRelatedFormData(evt)
|
|
1734
2646
|
if (internalData) {
|
|
1735
2647
|
internalData.lastButtonClicked = elt
|
|
1736
2648
|
}
|
|
1737
|
-
}
|
|
1738
|
-
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
/**
|
|
2652
|
+
* @param {Event} evt
|
|
2653
|
+
*/
|
|
2654
|
+
function maybeUnsetLastButtonClicked(evt) {
|
|
1739
2655
|
const internalData = getRelatedFormData(evt)
|
|
1740
2656
|
if (internalData) {
|
|
1741
2657
|
internalData.lastButtonClicked = null
|
|
1742
2658
|
}
|
|
1743
2659
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* @param {Event} evt
|
|
2663
|
+
* @returns {HtmxNodeInternalData|undefined}
|
|
2664
|
+
*/
|
|
2665
|
+
function getRelatedFormData(evt) {
|
|
2666
|
+
const elt = closest(asElement(evt.target), "button, input[type='submit']")
|
|
1746
2667
|
if (!elt) {
|
|
1747
2668
|
return
|
|
1748
2669
|
}
|
|
@@ -1752,7 +2673,11 @@ const htmx = (function () {
|
|
|
1752
2673
|
}
|
|
1753
2674
|
return getInternalData(form)
|
|
1754
2675
|
}
|
|
1755
|
-
|
|
2676
|
+
|
|
2677
|
+
/**
|
|
2678
|
+
* @param {EventTarget} elt
|
|
2679
|
+
*/
|
|
2680
|
+
function initButtonTracking(elt) {
|
|
1756
2681
|
// need to handle both click and focus in:
|
|
1757
2682
|
// focusin - in case someone tabs in to a button and hits the space bar
|
|
1758
2683
|
// click - on OSX buttons do not focus on click see https://bugs.webkit.org/show_bug.cgi?id=13724
|
|
@@ -1761,28 +2686,20 @@ const htmx = (function () {
|
|
|
1761
2686
|
elt.addEventListener('focusout', maybeUnsetLastButtonClicked)
|
|
1762
2687
|
}
|
|
1763
2688
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
netCurlies++
|
|
1771
|
-
} else if (token === '}') {
|
|
1772
|
-
netCurlies--
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
return netCurlies
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
function addHxOnEventHandler (elt, eventName, code) {
|
|
2689
|
+
/**
|
|
2690
|
+
* @param {EventTarget} elt
|
|
2691
|
+
* @param {string} eventName
|
|
2692
|
+
* @param {string} code
|
|
2693
|
+
*/
|
|
2694
|
+
function addHxOnEventHandler(elt, eventName, code) {
|
|
1779
2695
|
const nodeData = getInternalData(elt)
|
|
1780
2696
|
if (!Array.isArray(nodeData.onHandlers)) {
|
|
1781
2697
|
nodeData.onHandlers = []
|
|
1782
2698
|
}
|
|
1783
2699
|
let func
|
|
1784
|
-
|
|
1785
|
-
|
|
2700
|
+
/** @type EventListener */
|
|
2701
|
+
const listener = function(e) {
|
|
2702
|
+
maybeEval(elt, function() {
|
|
1786
2703
|
if (!func) {
|
|
1787
2704
|
func = new Function('event', code)
|
|
1788
2705
|
}
|
|
@@ -1793,7 +2710,10 @@ const htmx = (function () {
|
|
|
1793
2710
|
nodeData.onHandlers.push({ event: eventName, listener })
|
|
1794
2711
|
}
|
|
1795
2712
|
|
|
1796
|
-
|
|
2713
|
+
/**
|
|
2714
|
+
* @param {Element} elt
|
|
2715
|
+
*/
|
|
2716
|
+
function processHxOnWildcard(elt) {
|
|
1797
2717
|
// wipe any previous on handlers so that this function takes precedence
|
|
1798
2718
|
deInitOnHandlers(elt)
|
|
1799
2719
|
|
|
@@ -1820,7 +2740,10 @@ const htmx = (function () {
|
|
|
1820
2740
|
}
|
|
1821
2741
|
}
|
|
1822
2742
|
|
|
1823
|
-
|
|
2743
|
+
/**
|
|
2744
|
+
* @param {Element|HTMLInputElement} elt
|
|
2745
|
+
*/
|
|
2746
|
+
function initNode(elt) {
|
|
1824
2747
|
if (closest(elt, htmx.config.disableSelector)) {
|
|
1825
2748
|
cleanUpElement(elt)
|
|
1826
2749
|
return
|
|
@@ -1834,7 +2757,9 @@ const htmx = (function () {
|
|
|
1834
2757
|
|
|
1835
2758
|
triggerEvent(elt, 'htmx:beforeProcessNode')
|
|
1836
2759
|
|
|
2760
|
+
// @ts-ignore value will be undefined for non-input elements, which is fine
|
|
1837
2761
|
if (elt.value) {
|
|
2762
|
+
// @ts-ignore
|
|
1838
2763
|
nodeData.lastValue = elt.value
|
|
1839
2764
|
}
|
|
1840
2765
|
|
|
@@ -1845,9 +2770,9 @@ const htmx = (function () {
|
|
|
1845
2770
|
if (getClosestAttributeValue(elt, 'hx-boost') === 'true') {
|
|
1846
2771
|
boostElement(elt, nodeData, triggerSpecs)
|
|
1847
2772
|
} else if (hasAttribute(elt, 'hx-trigger')) {
|
|
1848
|
-
triggerSpecs.forEach(function
|
|
2773
|
+
triggerSpecs.forEach(function(triggerSpec) {
|
|
1849
2774
|
// For "naked" triggers, don't do anything at all
|
|
1850
|
-
addTriggerHandler(elt, triggerSpec, nodeData, function
|
|
2775
|
+
addTriggerHandler(elt, triggerSpec, nodeData, function() {
|
|
1851
2776
|
})
|
|
1852
2777
|
})
|
|
1853
2778
|
}
|
|
@@ -1863,14 +2788,21 @@ const htmx = (function () {
|
|
|
1863
2788
|
}
|
|
1864
2789
|
}
|
|
1865
2790
|
|
|
1866
|
-
|
|
2791
|
+
/**
|
|
2792
|
+
* Processes new content, enabling htmx behavior. This can be useful if you have content that is added to the DOM outside of the normal htmx request cycle but still want htmx attributes to work.
|
|
2793
|
+
*
|
|
2794
|
+
* @see https://htmx.org/api/#process
|
|
2795
|
+
*
|
|
2796
|
+
* @param {Element|string} elt element to process
|
|
2797
|
+
*/
|
|
2798
|
+
function processNode(elt) {
|
|
1867
2799
|
elt = resolveTarget(elt)
|
|
1868
2800
|
if (closest(elt, htmx.config.disableSelector)) {
|
|
1869
2801
|
cleanUpElement(elt)
|
|
1870
2802
|
return
|
|
1871
2803
|
}
|
|
1872
2804
|
initNode(elt)
|
|
1873
|
-
forEach(findElementsToProcess(elt), function
|
|
2805
|
+
forEach(findElementsToProcess(elt), function(child) { initNode(child) })
|
|
1874
2806
|
forEach(findHxOnWildcardElements(elt), processHxOnWildcard)
|
|
1875
2807
|
}
|
|
1876
2808
|
|
|
@@ -1878,11 +2810,20 @@ const htmx = (function () {
|
|
|
1878
2810
|
// Event/Log Support
|
|
1879
2811
|
//= ===================================================================
|
|
1880
2812
|
|
|
1881
|
-
|
|
2813
|
+
/**
|
|
2814
|
+
* @param {string} str
|
|
2815
|
+
* @returns {string}
|
|
2816
|
+
*/
|
|
2817
|
+
function kebabEventName(str) {
|
|
1882
2818
|
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
1883
2819
|
}
|
|
1884
2820
|
|
|
1885
|
-
|
|
2821
|
+
/**
|
|
2822
|
+
* @param {string} eventName
|
|
2823
|
+
* @param {any} detail
|
|
2824
|
+
* @returns {CustomEvent}
|
|
2825
|
+
*/
|
|
2826
|
+
function makeEvent(eventName, detail) {
|
|
1886
2827
|
let evt
|
|
1887
2828
|
if (window.CustomEvent && typeof window.CustomEvent === 'function') {
|
|
1888
2829
|
// TODO: `composed: true` here is a hack to make global event handlers work with events in shadow DOM
|
|
@@ -1895,11 +2836,20 @@ const htmx = (function () {
|
|
|
1895
2836
|
return evt
|
|
1896
2837
|
}
|
|
1897
2838
|
|
|
1898
|
-
|
|
2839
|
+
/**
|
|
2840
|
+
* @param {EventTarget|string} elt
|
|
2841
|
+
* @param {string} eventName
|
|
2842
|
+
* @param {any=} detail
|
|
2843
|
+
*/
|
|
2844
|
+
function triggerErrorEvent(elt, eventName, detail) {
|
|
1899
2845
|
triggerEvent(elt, eventName, mergeObjects({ error: eventName }, detail))
|
|
1900
2846
|
}
|
|
1901
2847
|
|
|
1902
|
-
|
|
2848
|
+
/**
|
|
2849
|
+
* @param {string} eventName
|
|
2850
|
+
* @returns {boolean}
|
|
2851
|
+
*/
|
|
2852
|
+
function ignoreEventForLogging(eventName) {
|
|
1903
2853
|
return eventName === 'htmx:afterProcessNode'
|
|
1904
2854
|
}
|
|
1905
2855
|
|
|
@@ -1908,12 +2858,12 @@ const htmx = (function () {
|
|
|
1908
2858
|
* executes the provided function using each of the active extensions. It should
|
|
1909
2859
|
* be called internally at every extendable execution point in htmx.
|
|
1910
2860
|
*
|
|
1911
|
-
* @param {
|
|
1912
|
-
* @param {(extension:
|
|
2861
|
+
* @param {Element} elt
|
|
2862
|
+
* @param {(extension:HtmxExtension) => void} toDo
|
|
1913
2863
|
* @returns void
|
|
1914
2864
|
*/
|
|
1915
|
-
function withExtensions
|
|
1916
|
-
forEach(getExtensions(elt), function
|
|
2865
|
+
function withExtensions(elt, toDo) {
|
|
2866
|
+
forEach(getExtensions(elt), function(extension) {
|
|
1917
2867
|
try {
|
|
1918
2868
|
toDo(extension)
|
|
1919
2869
|
} catch (e) {
|
|
@@ -1922,7 +2872,7 @@ const htmx = (function () {
|
|
|
1922
2872
|
})
|
|
1923
2873
|
}
|
|
1924
2874
|
|
|
1925
|
-
function logError
|
|
2875
|
+
function logError(msg) {
|
|
1926
2876
|
if (console.error) {
|
|
1927
2877
|
console.error(msg)
|
|
1928
2878
|
} else if (console.log) {
|
|
@@ -1930,7 +2880,17 @@ const htmx = (function () {
|
|
|
1930
2880
|
}
|
|
1931
2881
|
}
|
|
1932
2882
|
|
|
1933
|
-
|
|
2883
|
+
/**
|
|
2884
|
+
* Triggers a given event on an element
|
|
2885
|
+
*
|
|
2886
|
+
* @see https://htmx.org/api/#trigger
|
|
2887
|
+
*
|
|
2888
|
+
* @param {EventTarget|string} elt the element to trigger the event on
|
|
2889
|
+
* @param {string} eventName the name of the event to trigger
|
|
2890
|
+
* @param {any=} detail details for the event
|
|
2891
|
+
* @returns {boolean}
|
|
2892
|
+
*/
|
|
2893
|
+
function triggerEvent(elt, eventName, detail) {
|
|
1934
2894
|
elt = resolveTarget(elt)
|
|
1935
2895
|
if (detail == null) {
|
|
1936
2896
|
detail = {}
|
|
@@ -1950,7 +2910,7 @@ const htmx = (function () {
|
|
|
1950
2910
|
const kebabedEvent = makeEvent(kebabName, event.detail)
|
|
1951
2911
|
eventResult = eventResult && elt.dispatchEvent(kebabedEvent)
|
|
1952
2912
|
}
|
|
1953
|
-
withExtensions(elt, function
|
|
2913
|
+
withExtensions(asElement(elt), function(extension) {
|
|
1954
2914
|
eventResult = eventResult && (extension.onEvent(eventName, event) !== false && !event.defaultPrevented)
|
|
1955
2915
|
})
|
|
1956
2916
|
return eventResult
|
|
@@ -1961,16 +2921,28 @@ const htmx = (function () {
|
|
|
1961
2921
|
//= ===================================================================
|
|
1962
2922
|
let currentPathForHistory = location.pathname + location.search
|
|
1963
2923
|
|
|
1964
|
-
|
|
2924
|
+
/**
|
|
2925
|
+
* @returns {Element}
|
|
2926
|
+
*/
|
|
2927
|
+
function getHistoryElement() {
|
|
1965
2928
|
const historyElt = getDocument().querySelector('[hx-history-elt],[data-hx-history-elt]')
|
|
1966
2929
|
return historyElt || getDocument().body
|
|
1967
2930
|
}
|
|
1968
2931
|
|
|
1969
|
-
|
|
2932
|
+
/**
|
|
2933
|
+
* @param {string} url
|
|
2934
|
+
* @param {Element} rootElt
|
|
2935
|
+
*/
|
|
2936
|
+
function saveToHistoryCache(url, rootElt) {
|
|
1970
2937
|
if (!canAccessLocalStorage()) {
|
|
1971
2938
|
return
|
|
1972
2939
|
}
|
|
1973
2940
|
|
|
2941
|
+
// get state to save
|
|
2942
|
+
const innerHTML = cleanInnerHtmlForHistory(rootElt)
|
|
2943
|
+
const title = getDocument().title
|
|
2944
|
+
const scroll = window.scrollY
|
|
2945
|
+
|
|
1974
2946
|
if (htmx.config.historyCacheSize <= 0) {
|
|
1975
2947
|
// make sure that an eventually already existing cache is purged
|
|
1976
2948
|
localStorage.removeItem('htmx-history-cache')
|
|
@@ -1986,12 +2958,18 @@ const htmx = (function () {
|
|
|
1986
2958
|
break
|
|
1987
2959
|
}
|
|
1988
2960
|
}
|
|
1989
|
-
|
|
2961
|
+
|
|
2962
|
+
/** @type HtmxHistoryItem */
|
|
2963
|
+
const newHistoryItem = { url, content: innerHTML, title, scroll }
|
|
2964
|
+
|
|
1990
2965
|
triggerEvent(getDocument().body, 'htmx:historyItemCreated', { item: newHistoryItem, cache: historyCache })
|
|
2966
|
+
|
|
1991
2967
|
historyCache.push(newHistoryItem)
|
|
1992
2968
|
while (historyCache.length > htmx.config.historyCacheSize) {
|
|
1993
2969
|
historyCache.shift()
|
|
1994
2970
|
}
|
|
2971
|
+
|
|
2972
|
+
// keep trying to save the cache until it succeeds or is empty
|
|
1995
2973
|
while (historyCache.length > 0) {
|
|
1996
2974
|
try {
|
|
1997
2975
|
localStorage.setItem('htmx-history-cache', JSON.stringify(historyCache))
|
|
@@ -2003,7 +2981,19 @@ const htmx = (function () {
|
|
|
2003
2981
|
}
|
|
2004
2982
|
}
|
|
2005
2983
|
|
|
2006
|
-
|
|
2984
|
+
/**
|
|
2985
|
+
* @typedef {Object} HtmxHistoryItem
|
|
2986
|
+
* @property {string} url
|
|
2987
|
+
* @property {string} content
|
|
2988
|
+
* @property {string} title
|
|
2989
|
+
* @property {number} scroll
|
|
2990
|
+
*/
|
|
2991
|
+
|
|
2992
|
+
/**
|
|
2993
|
+
* @param {string} url
|
|
2994
|
+
* @returns {HtmxHistoryItem|null}
|
|
2995
|
+
*/
|
|
2996
|
+
function getCachedHistory(url) {
|
|
2007
2997
|
if (!canAccessLocalStorage()) {
|
|
2008
2998
|
return null
|
|
2009
2999
|
}
|
|
@@ -2019,16 +3009,20 @@ const htmx = (function () {
|
|
|
2019
3009
|
return null
|
|
2020
3010
|
}
|
|
2021
3011
|
|
|
2022
|
-
|
|
3012
|
+
/**
|
|
3013
|
+
* @param {Element} elt
|
|
3014
|
+
* @returns {string}
|
|
3015
|
+
*/
|
|
3016
|
+
function cleanInnerHtmlForHistory(elt) {
|
|
2023
3017
|
const className = htmx.config.requestClass
|
|
2024
|
-
const clone = elt.cloneNode(true)
|
|
2025
|
-
forEach(findAll(clone, '.' + className), function
|
|
3018
|
+
const clone = /** @type Element */ (elt.cloneNode(true))
|
|
3019
|
+
forEach(findAll(clone, '.' + className), function(child) {
|
|
2026
3020
|
removeClassFromElement(child, className)
|
|
2027
3021
|
})
|
|
2028
3022
|
return clone.innerHTML
|
|
2029
3023
|
}
|
|
2030
3024
|
|
|
2031
|
-
function saveCurrentPageToHistory
|
|
3025
|
+
function saveCurrentPageToHistory() {
|
|
2032
3026
|
const elt = getHistoryElement()
|
|
2033
3027
|
const path = currentPathForHistory || location.pathname + location.search
|
|
2034
3028
|
|
|
@@ -2046,13 +3040,16 @@ const htmx = (function () {
|
|
|
2046
3040
|
}
|
|
2047
3041
|
if (!disableHistoryCache) {
|
|
2048
3042
|
triggerEvent(getDocument().body, 'htmx:beforeHistorySave', { path, historyElt: elt })
|
|
2049
|
-
saveToHistoryCache(path,
|
|
3043
|
+
saveToHistoryCache(path, elt)
|
|
2050
3044
|
}
|
|
2051
3045
|
|
|
2052
3046
|
if (htmx.config.historyEnabled) history.replaceState({ htmx: true }, getDocument().title, window.location.href)
|
|
2053
3047
|
}
|
|
2054
3048
|
|
|
2055
|
-
|
|
3049
|
+
/**
|
|
3050
|
+
* @param {string} path
|
|
3051
|
+
*/
|
|
3052
|
+
function pushUrlIntoHistory(path) {
|
|
2056
3053
|
// remove the cache buster parameter, if any
|
|
2057
3054
|
if (htmx.config.getCacheBusterParam) {
|
|
2058
3055
|
path = path.replace(/org\.htmx\.cache-buster=[^&]*&?/, '')
|
|
@@ -2066,18 +3063,27 @@ const htmx = (function () {
|
|
|
2066
3063
|
currentPathForHistory = path
|
|
2067
3064
|
}
|
|
2068
3065
|
|
|
2069
|
-
|
|
3066
|
+
/**
|
|
3067
|
+
* @param {string} path
|
|
3068
|
+
*/
|
|
3069
|
+
function replaceUrlInHistory(path) {
|
|
2070
3070
|
if (htmx.config.historyEnabled) history.replaceState({ htmx: true }, '', path)
|
|
2071
3071
|
currentPathForHistory = path
|
|
2072
3072
|
}
|
|
2073
3073
|
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
3074
|
+
/**
|
|
3075
|
+
* @param {HtmxSettleTask[]} tasks
|
|
3076
|
+
*/
|
|
3077
|
+
function settleImmediately(tasks) {
|
|
3078
|
+
forEach(tasks, function(task) {
|
|
3079
|
+
task.call(undefined)
|
|
2077
3080
|
})
|
|
2078
3081
|
}
|
|
2079
3082
|
|
|
2080
|
-
|
|
3083
|
+
/**
|
|
3084
|
+
* @param {string} path
|
|
3085
|
+
*/
|
|
3086
|
+
function loadHistoryFromServer(path) {
|
|
2081
3087
|
const request = new XMLHttpRequest()
|
|
2082
3088
|
const details = { path, xhr: request }
|
|
2083
3089
|
triggerEvent(getDocument().body, 'htmx:historyCacheMiss', details)
|
|
@@ -2085,25 +3091,17 @@ const htmx = (function () {
|
|
|
2085
3091
|
request.setRequestHeader('HX-Request', 'true')
|
|
2086
3092
|
request.setRequestHeader('HX-History-Restore-Request', 'true')
|
|
2087
3093
|
request.setRequestHeader('HX-Current-URL', getDocument().location.href)
|
|
2088
|
-
request.onload = function
|
|
3094
|
+
request.onload = function() {
|
|
2089
3095
|
if (this.status >= 200 && this.status < 400) {
|
|
2090
3096
|
triggerEvent(getDocument().body, 'htmx:historyCacheMissLoad', details)
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
3097
|
+
const fragment = makeFragment(this.response)
|
|
3098
|
+
/** @type ParentNode */
|
|
3099
|
+
const content = fragment.querySelector('[hx-history-elt],[data-hx-history-elt]') || fragment
|
|
2094
3100
|
const historyElement = getHistoryElement()
|
|
2095
3101
|
const settleInfo = makeSettleInfo(historyElement)
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
if (titleElt) {
|
|
2100
|
-
titleElt.innerHTML = title
|
|
2101
|
-
} else {
|
|
2102
|
-
window.document.title = title
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
// @ts-ignore
|
|
2106
|
-
swapInnerHTML(historyElement, fragment, settleInfo)
|
|
3102
|
+
handleTitle(fragment.title)
|
|
3103
|
+
|
|
3104
|
+
swapInnerHTML(historyElement, content, settleInfo)
|
|
2107
3105
|
settleImmediately(settleInfo.tasks)
|
|
2108
3106
|
currentPathForHistory = path
|
|
2109
3107
|
triggerEvent(getDocument().body, 'htmx:historyRestore', { path, cacheMiss: true, serverResponse: this.response })
|
|
@@ -2114,7 +3112,10 @@ const htmx = (function () {
|
|
|
2114
3112
|
request.send()
|
|
2115
3113
|
}
|
|
2116
3114
|
|
|
2117
|
-
|
|
3115
|
+
/**
|
|
3116
|
+
* @param {string} [path]
|
|
3117
|
+
*/
|
|
3118
|
+
function restoreHistory(path) {
|
|
2118
3119
|
saveCurrentPageToHistory()
|
|
2119
3120
|
path = path || location.pathname + location.search
|
|
2120
3121
|
const cached = getCachedHistory(path)
|
|
@@ -2122,17 +3123,18 @@ const htmx = (function () {
|
|
|
2122
3123
|
const fragment = makeFragment(cached.content)
|
|
2123
3124
|
const historyElement = getHistoryElement()
|
|
2124
3125
|
const settleInfo = makeSettleInfo(historyElement)
|
|
3126
|
+
handleTitle(fragment.title)
|
|
2125
3127
|
swapInnerHTML(historyElement, fragment, settleInfo)
|
|
2126
3128
|
settleImmediately(settleInfo.tasks)
|
|
2127
|
-
|
|
2128
|
-
setTimeout(function () {
|
|
3129
|
+
getWindow().setTimeout(function() {
|
|
2129
3130
|
window.scrollTo(0, cached.scroll)
|
|
2130
3131
|
}, 0) // next 'tick', so browser has time to render layout
|
|
2131
3132
|
currentPathForHistory = path
|
|
2132
3133
|
triggerEvent(getDocument().body, 'htmx:historyRestore', { path, item: cached })
|
|
2133
3134
|
} else {
|
|
2134
3135
|
if (htmx.config.refreshOnHistoryMiss) {
|
|
2135
|
-
|
|
3136
|
+
// @ts-ignore: optional parameter in reload() function throws error
|
|
3137
|
+
// noinspection JSUnresolvedReference
|
|
2136
3138
|
window.location.reload(true)
|
|
2137
3139
|
} else {
|
|
2138
3140
|
loadHistoryFromServer(path)
|
|
@@ -2140,12 +3142,16 @@ const htmx = (function () {
|
|
|
2140
3142
|
}
|
|
2141
3143
|
}
|
|
2142
3144
|
|
|
2143
|
-
|
|
2144
|
-
|
|
3145
|
+
/**
|
|
3146
|
+
* @param {Element} elt
|
|
3147
|
+
* @returns {Element[]}
|
|
3148
|
+
*/
|
|
3149
|
+
function addRequestIndicatorClasses(elt) {
|
|
3150
|
+
let indicators = /** @type Element[] */ (findAttributeTargets(elt, 'hx-indicator'))
|
|
2145
3151
|
if (indicators == null) {
|
|
2146
3152
|
indicators = [elt]
|
|
2147
3153
|
}
|
|
2148
|
-
forEach(indicators, function
|
|
3154
|
+
forEach(indicators, function(ic) {
|
|
2149
3155
|
const internalData = getInternalData(ic)
|
|
2150
3156
|
internalData.requestCount = (internalData.requestCount || 0) + 1
|
|
2151
3157
|
ic.classList.add.call(ic.classList, htmx.config.requestClass)
|
|
@@ -2153,12 +3159,16 @@ const htmx = (function () {
|
|
|
2153
3159
|
return indicators
|
|
2154
3160
|
}
|
|
2155
3161
|
|
|
2156
|
-
|
|
2157
|
-
|
|
3162
|
+
/**
|
|
3163
|
+
* @param {Element} elt
|
|
3164
|
+
* @returns {Element[]}
|
|
3165
|
+
*/
|
|
3166
|
+
function disableElements(elt) {
|
|
3167
|
+
let disabledElts = /** @type Element[] */ (findAttributeTargets(elt, 'hx-disabled-elt'))
|
|
2158
3168
|
if (disabledElts == null) {
|
|
2159
3169
|
disabledElts = []
|
|
2160
3170
|
}
|
|
2161
|
-
forEach(disabledElts, function
|
|
3171
|
+
forEach(disabledElts, function(disabledElement) {
|
|
2162
3172
|
const internalData = getInternalData(disabledElement)
|
|
2163
3173
|
internalData.requestCount = (internalData.requestCount || 0) + 1
|
|
2164
3174
|
disabledElement.setAttribute('disabled', '')
|
|
@@ -2166,15 +3176,19 @@ const htmx = (function () {
|
|
|
2166
3176
|
return disabledElts
|
|
2167
3177
|
}
|
|
2168
3178
|
|
|
2169
|
-
|
|
2170
|
-
|
|
3179
|
+
/**
|
|
3180
|
+
* @param {Element[]} indicators
|
|
3181
|
+
* @param {Element[]} disabled
|
|
3182
|
+
*/
|
|
3183
|
+
function removeRequestIndicators(indicators, disabled) {
|
|
3184
|
+
forEach(indicators, function(ic) {
|
|
2171
3185
|
const internalData = getInternalData(ic)
|
|
2172
3186
|
internalData.requestCount = (internalData.requestCount || 0) - 1
|
|
2173
3187
|
if (internalData.requestCount === 0) {
|
|
2174
3188
|
ic.classList.remove.call(ic.classList, htmx.config.requestClass)
|
|
2175
3189
|
}
|
|
2176
3190
|
})
|
|
2177
|
-
forEach(disabled, function
|
|
3191
|
+
forEach(disabled, function(disabledElement) {
|
|
2178
3192
|
const internalData = getInternalData(disabledElement)
|
|
2179
3193
|
internalData.requestCount = (internalData.requestCount || 0) - 1
|
|
2180
3194
|
if (internalData.requestCount === 0) {
|
|
@@ -2187,7 +3201,12 @@ const htmx = (function () {
|
|
|
2187
3201
|
// Input Value Processing
|
|
2188
3202
|
//= ===================================================================
|
|
2189
3203
|
|
|
2190
|
-
|
|
3204
|
+
/**
|
|
3205
|
+
* @param {Element[]} processed
|
|
3206
|
+
* @param {Element} elt
|
|
3207
|
+
* @returns {boolean}
|
|
3208
|
+
*/
|
|
3209
|
+
function haveSeenNode(processed, elt) {
|
|
2191
3210
|
for (let i = 0; i < processed.length; i++) {
|
|
2192
3211
|
const node = processed[i]
|
|
2193
3212
|
if (node.isSameNode(elt)) {
|
|
@@ -2197,8 +3216,14 @@ const htmx = (function () {
|
|
|
2197
3216
|
return false
|
|
2198
3217
|
}
|
|
2199
3218
|
|
|
2200
|
-
|
|
2201
|
-
|
|
3219
|
+
/**
|
|
3220
|
+
* @param {Element} element
|
|
3221
|
+
* @return {boolean}
|
|
3222
|
+
*/
|
|
3223
|
+
function shouldInclude(element) {
|
|
3224
|
+
// Cast to trick tsc, undefined values will work fine here
|
|
3225
|
+
const elt = /** @type {HTMLInputElement} */ (element)
|
|
3226
|
+
if (elt.name === '' || elt.name == null || elt.disabled || closest(elt, 'fieldset[disabled]')) {
|
|
2202
3227
|
return false
|
|
2203
3228
|
}
|
|
2204
3229
|
// ignore "submitter" types (see jQuery src/serialize.js)
|
|
@@ -2211,30 +3236,43 @@ const htmx = (function () {
|
|
|
2211
3236
|
return true
|
|
2212
3237
|
}
|
|
2213
3238
|
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
3239
|
+
/** @param {string} name
|
|
3240
|
+
* @param {string|Array|FormDataEntryValue} value
|
|
3241
|
+
* @param {FormData} formData */
|
|
3242
|
+
function addValueToFormData(name, value, formData) {
|
|
2217
3243
|
if (name != null && value != null) {
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
values[name] = value
|
|
2221
|
-
} else if (Array.isArray(current)) {
|
|
2222
|
-
if (Array.isArray(value)) {
|
|
2223
|
-
values[name] = current.concat(value)
|
|
2224
|
-
} else {
|
|
2225
|
-
current.push(value)
|
|
2226
|
-
}
|
|
3244
|
+
if (Array.isArray(value)) {
|
|
3245
|
+
value.forEach(function(v) { formData.append(name, v) })
|
|
2227
3246
|
} else {
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
3247
|
+
formData.append(name, value)
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
/** @param {string} name
|
|
3253
|
+
* @param {string|Array} value
|
|
3254
|
+
* @param {FormData} formData */
|
|
3255
|
+
function removeValueFromFormData(name, value, formData) {
|
|
3256
|
+
if (name != null && value != null) {
|
|
3257
|
+
let values = formData.getAll(name)
|
|
3258
|
+
if (Array.isArray(value)) {
|
|
3259
|
+
values = values.filter(v => value.indexOf(v) < 0)
|
|
3260
|
+
} else {
|
|
3261
|
+
values = values.filter(v => v !== value)
|
|
2233
3262
|
}
|
|
3263
|
+
formData.delete(name)
|
|
3264
|
+
forEach(values, v => formData.append(name, v))
|
|
2234
3265
|
}
|
|
2235
3266
|
}
|
|
2236
3267
|
|
|
2237
|
-
|
|
3268
|
+
/**
|
|
3269
|
+
* @param {Element[]} processed
|
|
3270
|
+
* @param {FormData} formData
|
|
3271
|
+
* @param {HtmxElementValidationError[]} errors
|
|
3272
|
+
* @param {Element|HTMLInputElement|HTMLSelectElement|HTMLFormElement} elt
|
|
3273
|
+
* @param {boolean} validate
|
|
3274
|
+
*/
|
|
3275
|
+
function processInputValue(processed, formData, errors, elt, validate) {
|
|
2238
3276
|
if (elt == null || haveSeenNode(processed, elt)) {
|
|
2239
3277
|
return
|
|
2240
3278
|
} else {
|
|
@@ -2242,28 +3280,47 @@ const htmx = (function () {
|
|
|
2242
3280
|
}
|
|
2243
3281
|
if (shouldInclude(elt)) {
|
|
2244
3282
|
const name = getRawAttribute(elt, 'name')
|
|
3283
|
+
// @ts-ignore value will be undefined for non-input elements, which is fine
|
|
2245
3284
|
let value = elt.value
|
|
2246
|
-
if (elt
|
|
2247
|
-
value = toArray(elt.querySelectorAll('option:checked')).map(function
|
|
3285
|
+
if (elt instanceof HTMLSelectElement && elt.multiple) {
|
|
3286
|
+
value = toArray(elt.querySelectorAll('option:checked')).map(function(e) { return (/** @type HTMLOptionElement */(e)).value })
|
|
2248
3287
|
}
|
|
2249
3288
|
// include file inputs
|
|
2250
|
-
if (elt.files) {
|
|
3289
|
+
if (elt instanceof HTMLInputElement && elt.files) {
|
|
2251
3290
|
value = toArray(elt.files)
|
|
2252
3291
|
}
|
|
2253
|
-
|
|
3292
|
+
addValueToFormData(name, value, formData)
|
|
2254
3293
|
if (validate) {
|
|
2255
3294
|
validateElement(elt, errors)
|
|
2256
3295
|
}
|
|
2257
3296
|
}
|
|
2258
|
-
if (
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
3297
|
+
if (elt instanceof HTMLFormElement) {
|
|
3298
|
+
forEach(elt.elements, function(input) {
|
|
3299
|
+
if (processed.indexOf(input) >= 0) {
|
|
3300
|
+
// The input has already been processed and added to the values, but the FormData that will be
|
|
3301
|
+
// constructed right after on the form, will include it once again. So remove that input's value
|
|
3302
|
+
// now to avoid duplicates
|
|
3303
|
+
removeValueFromFormData(input.name, input.value, formData)
|
|
3304
|
+
} else {
|
|
3305
|
+
processed.push(input)
|
|
3306
|
+
}
|
|
3307
|
+
if (validate) {
|
|
3308
|
+
validateElement(input, errors)
|
|
3309
|
+
}
|
|
3310
|
+
})
|
|
3311
|
+
new FormData(elt).forEach(function(value, name) {
|
|
3312
|
+
addValueToFormData(name, value, formData)
|
|
2262
3313
|
})
|
|
2263
3314
|
}
|
|
2264
3315
|
}
|
|
2265
3316
|
|
|
2266
|
-
|
|
3317
|
+
/**
|
|
3318
|
+
*
|
|
3319
|
+
* @param {Element} elt
|
|
3320
|
+
* @param {HtmxElementValidationError[]} errors
|
|
3321
|
+
*/
|
|
3322
|
+
function validateElement(elt, errors) {
|
|
3323
|
+
const element = /** @type {HTMLElement & ElementInternals} */ (elt)
|
|
2267
3324
|
if (element.willValidate) {
|
|
2268
3325
|
triggerEvent(element, 'htmx:validation:validate')
|
|
2269
3326
|
if (!element.checkValidity()) {
|
|
@@ -2274,13 +3331,32 @@ const htmx = (function () {
|
|
|
2274
3331
|
}
|
|
2275
3332
|
|
|
2276
3333
|
/**
|
|
2277
|
-
|
|
2278
|
-
|
|
3334
|
+
* Override values in the one FormData with those from another.
|
|
3335
|
+
* @param {FormData} receiver the formdata that will be mutated
|
|
3336
|
+
* @param {FormData} donor the formdata that will provide the overriding values
|
|
3337
|
+
* @returns {FormData} the {@linkcode receiver}
|
|
3338
|
+
*/
|
|
3339
|
+
function overrideFormData(receiver, donor) {
|
|
3340
|
+
for (const key of donor.keys()) {
|
|
3341
|
+
receiver.delete(key)
|
|
3342
|
+
donor.getAll(key).forEach(function(value) {
|
|
3343
|
+
receiver.append(key, value)
|
|
3344
|
+
})
|
|
3345
|
+
}
|
|
3346
|
+
return receiver
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
/**
|
|
3350
|
+
* @param {Element|HTMLFormElement} elt
|
|
3351
|
+
* @param {HttpVerb} verb
|
|
3352
|
+
* @returns {{errors: HtmxElementValidationError[], formData: FormData, values: Object}}
|
|
2279
3353
|
*/
|
|
2280
|
-
function getInputValues
|
|
3354
|
+
function getInputValues(elt, verb) {
|
|
3355
|
+
/** @type Element[] */
|
|
2281
3356
|
const processed = []
|
|
2282
|
-
|
|
2283
|
-
const
|
|
3357
|
+
const formData = new FormData()
|
|
3358
|
+
const priorityFormData = new FormData()
|
|
3359
|
+
/** @type HtmxElementValidationError[] */
|
|
2284
3360
|
const errors = []
|
|
2285
3361
|
const internalData = getInternalData(elt)
|
|
2286
3362
|
if (internalData.lastButtonClicked && !bodyContains(internalData.lastButtonClicked)) {
|
|
@@ -2289,46 +3365,52 @@ const htmx = (function () {
|
|
|
2289
3365
|
|
|
2290
3366
|
// only validate when form is directly submitted and novalidate or formnovalidate are not set
|
|
2291
3367
|
// or if the element has an explicit hx-validate="true" on it
|
|
2292
|
-
let validate = (
|
|
3368
|
+
let validate = (elt instanceof HTMLFormElement && elt.noValidate !== true) || getAttributeValue(elt, 'hx-validate') === 'true'
|
|
2293
3369
|
if (internalData.lastButtonClicked) {
|
|
2294
3370
|
validate = validate && internalData.lastButtonClicked.formNoValidate !== true
|
|
2295
3371
|
}
|
|
2296
3372
|
|
|
2297
3373
|
// for a non-GET include the closest form
|
|
2298
3374
|
if (verb !== 'get') {
|
|
2299
|
-
processInputValue(processed,
|
|
3375
|
+
processInputValue(processed, priorityFormData, errors, closest(elt, 'form'), validate)
|
|
2300
3376
|
}
|
|
2301
3377
|
|
|
2302
3378
|
// include the element itself
|
|
2303
|
-
processInputValue(processed,
|
|
3379
|
+
processInputValue(processed, formData, errors, elt, validate)
|
|
2304
3380
|
|
|
2305
3381
|
// if a button or submit was clicked last, include its value
|
|
2306
3382
|
if (internalData.lastButtonClicked || elt.tagName === 'BUTTON' ||
|
|
2307
3383
|
(elt.tagName === 'INPUT' && getRawAttribute(elt, 'type') === 'submit')) {
|
|
2308
|
-
const button = internalData.lastButtonClicked || elt
|
|
3384
|
+
const button = internalData.lastButtonClicked || (/** @type HTMLInputElement|HTMLButtonElement */(elt))
|
|
2309
3385
|
const name = getRawAttribute(button, 'name')
|
|
2310
|
-
|
|
3386
|
+
addValueToFormData(name, button.value, priorityFormData)
|
|
2311
3387
|
}
|
|
2312
3388
|
|
|
2313
3389
|
// include any explicit includes
|
|
2314
3390
|
const includes = findAttributeTargets(elt, 'hx-include')
|
|
2315
|
-
forEach(includes, function
|
|
2316
|
-
processInputValue(processed,
|
|
3391
|
+
forEach(includes, function(node) {
|
|
3392
|
+
processInputValue(processed, formData, errors, asElement(node), validate)
|
|
2317
3393
|
// if a non-form is included, include any input values within it
|
|
2318
3394
|
if (!matches(node, 'form')) {
|
|
2319
|
-
forEach(node.querySelectorAll(INPUT_SELECTOR), function
|
|
2320
|
-
processInputValue(processed,
|
|
3395
|
+
forEach(asParentNode(node).querySelectorAll(INPUT_SELECTOR), function(descendant) {
|
|
3396
|
+
processInputValue(processed, formData, errors, descendant, validate)
|
|
2321
3397
|
})
|
|
2322
3398
|
}
|
|
2323
3399
|
})
|
|
2324
3400
|
|
|
2325
|
-
// form
|
|
2326
|
-
|
|
3401
|
+
// values from a <form> take precedence, overriding the regular values
|
|
3402
|
+
overrideFormData(formData, priorityFormData)
|
|
2327
3403
|
|
|
2328
|
-
return { errors, values }
|
|
3404
|
+
return { errors, formData, values: formDataProxy(formData) }
|
|
2329
3405
|
}
|
|
2330
3406
|
|
|
2331
|
-
|
|
3407
|
+
/**
|
|
3408
|
+
* @param {string} returnStr
|
|
3409
|
+
* @param {string} name
|
|
3410
|
+
* @param {any} realValue
|
|
3411
|
+
* @returns {string}
|
|
3412
|
+
*/
|
|
3413
|
+
function appendParam(returnStr, name, realValue) {
|
|
2332
3414
|
if (returnStr !== '') {
|
|
2333
3415
|
returnStr += '&'
|
|
2334
3416
|
}
|
|
@@ -2340,51 +3422,31 @@ const htmx = (function () {
|
|
|
2340
3422
|
return returnStr
|
|
2341
3423
|
}
|
|
2342
3424
|
|
|
2343
|
-
|
|
3425
|
+
/**
|
|
3426
|
+
* @param {FormData|Object} values
|
|
3427
|
+
* @returns string
|
|
3428
|
+
*/
|
|
3429
|
+
function urlEncode(values) {
|
|
3430
|
+
values = formDataFromObject(values)
|
|
2344
3431
|
let returnStr = ''
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
if (Array.isArray(value)) {
|
|
2349
|
-
forEach(value, function (v) {
|
|
2350
|
-
returnStr = appendParam(returnStr, name, v)
|
|
2351
|
-
})
|
|
2352
|
-
} else {
|
|
2353
|
-
returnStr = appendParam(returnStr, name, value)
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
3432
|
+
values.forEach(function(value, key) {
|
|
3433
|
+
returnStr = appendParam(returnStr, key, value)
|
|
3434
|
+
})
|
|
2357
3435
|
return returnStr
|
|
2358
3436
|
}
|
|
2359
3437
|
|
|
2360
|
-
function makeFormData (values) {
|
|
2361
|
-
const formData = new FormData()
|
|
2362
|
-
for (var name in values) {
|
|
2363
|
-
if (values.hasOwnProperty(name)) {
|
|
2364
|
-
const value = values[name]
|
|
2365
|
-
if (Array.isArray(value)) {
|
|
2366
|
-
forEach(value, function (v) {
|
|
2367
|
-
formData.append(name, v)
|
|
2368
|
-
})
|
|
2369
|
-
} else {
|
|
2370
|
-
formData.append(name, value)
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
return formData
|
|
2375
|
-
}
|
|
2376
|
-
|
|
2377
3438
|
//= ===================================================================
|
|
2378
3439
|
// Ajax
|
|
2379
3440
|
//= ===================================================================
|
|
2380
3441
|
|
|
2381
3442
|
/**
|
|
2382
|
-
* @param {
|
|
2383
|
-
* @param {
|
|
3443
|
+
* @param {Element} elt
|
|
3444
|
+
* @param {Element} target
|
|
2384
3445
|
* @param {string} prompt
|
|
2385
|
-
* @returns {
|
|
3446
|
+
* @returns {HtmxHeaderSpecification}
|
|
2386
3447
|
*/
|
|
2387
|
-
function getHeaders
|
|
3448
|
+
function getHeaders(elt, target, prompt) {
|
|
3449
|
+
/** @type HtmxHeaderSpecification */
|
|
2388
3450
|
const headers = {
|
|
2389
3451
|
'HX-Request': 'true',
|
|
2390
3452
|
'HX-Trigger': getRawAttribute(elt, 'id'),
|
|
@@ -2406,28 +3468,30 @@ const htmx = (function () {
|
|
|
2406
3468
|
* filterValues takes an object containing form input values
|
|
2407
3469
|
* and returns a new object that only contains keys that are
|
|
2408
3470
|
* specified by the closest "hx-params" attribute
|
|
2409
|
-
* @param {
|
|
2410
|
-
* @param {
|
|
2411
|
-
* @returns {
|
|
3471
|
+
* @param {FormData} inputValues
|
|
3472
|
+
* @param {Element} elt
|
|
3473
|
+
* @returns {FormData}
|
|
2412
3474
|
*/
|
|
2413
|
-
function filterValues
|
|
3475
|
+
function filterValues(inputValues, elt) {
|
|
2414
3476
|
const paramsValue = getClosestAttributeValue(elt, 'hx-params')
|
|
2415
3477
|
if (paramsValue) {
|
|
2416
3478
|
if (paramsValue === 'none') {
|
|
2417
|
-
return
|
|
3479
|
+
return new FormData()
|
|
2418
3480
|
} else if (paramsValue === '*') {
|
|
2419
3481
|
return inputValues
|
|
2420
3482
|
} else if (paramsValue.indexOf('not ') === 0) {
|
|
2421
|
-
forEach(paramsValue.substr(4).split(','), function
|
|
3483
|
+
forEach(paramsValue.substr(4).split(','), function(name) {
|
|
2422
3484
|
name = name.trim()
|
|
2423
|
-
delete
|
|
3485
|
+
inputValues.delete(name)
|
|
2424
3486
|
})
|
|
2425
3487
|
return inputValues
|
|
2426
3488
|
} else {
|
|
2427
|
-
const newValues =
|
|
2428
|
-
forEach(paramsValue.split(','), function
|
|
3489
|
+
const newValues = new FormData()
|
|
3490
|
+
forEach(paramsValue.split(','), function(name) {
|
|
2429
3491
|
name = name.trim()
|
|
2430
|
-
|
|
3492
|
+
if (inputValues.has(name)) {
|
|
3493
|
+
inputValues.getAll(name).forEach(function(value) { newValues.append(name, value) })
|
|
3494
|
+
}
|
|
2431
3495
|
})
|
|
2432
3496
|
return newValues
|
|
2433
3497
|
}
|
|
@@ -2436,18 +3500,22 @@ const htmx = (function () {
|
|
|
2436
3500
|
}
|
|
2437
3501
|
}
|
|
2438
3502
|
|
|
2439
|
-
|
|
2440
|
-
|
|
3503
|
+
/**
|
|
3504
|
+
* @param {Element} elt
|
|
3505
|
+
* @return {boolean}
|
|
3506
|
+
*/
|
|
3507
|
+
function isAnchorLink(elt) {
|
|
3508
|
+
return !!getRawAttribute(elt, 'href') && getRawAttribute(elt, 'href').indexOf('#') >= 0
|
|
2441
3509
|
}
|
|
2442
3510
|
|
|
2443
3511
|
/**
|
|
2444
|
-
*
|
|
2445
|
-
* @param {
|
|
2446
|
-
* @
|
|
2447
|
-
* @returns {import("./htmx").HtmxSwapSpecification}
|
|
3512
|
+
* @param {Element} elt
|
|
3513
|
+
* @param {HtmxSwapStyle} [swapInfoOverride]
|
|
3514
|
+
* @returns {HtmxSwapSpecification}
|
|
2448
3515
|
*/
|
|
2449
|
-
function getSwapSpecification
|
|
3516
|
+
function getSwapSpecification(elt, swapInfoOverride) {
|
|
2450
3517
|
const swapInfo = swapInfoOverride || getClosestAttributeValue(elt, 'hx-swap')
|
|
3518
|
+
/** @type HtmxSwapSpecification */
|
|
2451
3519
|
const swapSpec = {
|
|
2452
3520
|
swapStyle: getInternalData(elt).boosted ? 'innerHTML' : htmx.config.defaultSwapStyle,
|
|
2453
3521
|
swapDelay: htmx.config.defaultSwapDelay,
|
|
@@ -2474,6 +3542,7 @@ const htmx = (function () {
|
|
|
2474
3542
|
var splitSpec = scrollSpec.split(':')
|
|
2475
3543
|
const scrollVal = splitSpec.pop()
|
|
2476
3544
|
var selectorVal = splitSpec.length > 0 ? splitSpec.join(':') : null
|
|
3545
|
+
// @ts-ignore
|
|
2477
3546
|
swapSpec.scroll = scrollVal
|
|
2478
3547
|
swapSpec.scrollTarget = selectorVal
|
|
2479
3548
|
} else if (value.indexOf('show:') === 0) {
|
|
@@ -2497,14 +3566,24 @@ const htmx = (function () {
|
|
|
2497
3566
|
return swapSpec
|
|
2498
3567
|
}
|
|
2499
3568
|
|
|
2500
|
-
|
|
3569
|
+
/**
|
|
3570
|
+
* @param {Element} elt
|
|
3571
|
+
* @return {boolean}
|
|
3572
|
+
*/
|
|
3573
|
+
function usesFormData(elt) {
|
|
2501
3574
|
return getClosestAttributeValue(elt, 'hx-encoding') === 'multipart/form-data' ||
|
|
2502
3575
|
(matches(elt, 'form') && getRawAttribute(elt, 'enctype') === 'multipart/form-data')
|
|
2503
3576
|
}
|
|
2504
3577
|
|
|
2505
|
-
|
|
3578
|
+
/**
|
|
3579
|
+
* @param {XMLHttpRequest} xhr
|
|
3580
|
+
* @param {Element} elt
|
|
3581
|
+
* @param {FormData} filteredParameters
|
|
3582
|
+
* @returns {*|string|null}
|
|
3583
|
+
*/
|
|
3584
|
+
function encodeParamsForBody(xhr, elt, filteredParameters) {
|
|
2506
3585
|
let encodedParameters = null
|
|
2507
|
-
withExtensions(elt, function
|
|
3586
|
+
withExtensions(elt, function(extension) {
|
|
2508
3587
|
if (encodedParameters == null) {
|
|
2509
3588
|
encodedParameters = extension.encodeParameters(xhr, filteredParameters, elt)
|
|
2510
3589
|
}
|
|
@@ -2513,7 +3592,7 @@ const htmx = (function () {
|
|
|
2513
3592
|
return encodedParameters
|
|
2514
3593
|
} else {
|
|
2515
3594
|
if (usesFormData(elt)) {
|
|
2516
|
-
return
|
|
3595
|
+
return formDataFromObject(filteredParameters)
|
|
2517
3596
|
} else {
|
|
2518
3597
|
return urlEncode(filteredParameters)
|
|
2519
3598
|
}
|
|
@@ -2523,19 +3602,23 @@ const htmx = (function () {
|
|
|
2523
3602
|
/**
|
|
2524
3603
|
*
|
|
2525
3604
|
* @param {Element} target
|
|
2526
|
-
* @returns {
|
|
3605
|
+
* @returns {HtmxSettleInfo}
|
|
2527
3606
|
*/
|
|
2528
|
-
function makeSettleInfo
|
|
3607
|
+
function makeSettleInfo(target) {
|
|
2529
3608
|
return { tasks: [], elts: [target] }
|
|
2530
3609
|
}
|
|
2531
3610
|
|
|
2532
|
-
|
|
3611
|
+
/**
|
|
3612
|
+
* @param {Element[]} content
|
|
3613
|
+
* @param {HtmxSwapSpecification} swapSpec
|
|
3614
|
+
*/
|
|
3615
|
+
function updateScrollState(content, swapSpec) {
|
|
2533
3616
|
const first = content[0]
|
|
2534
3617
|
const last = content[content.length - 1]
|
|
2535
3618
|
if (swapSpec.scroll) {
|
|
2536
3619
|
var target = null
|
|
2537
3620
|
if (swapSpec.scrollTarget) {
|
|
2538
|
-
target = querySelectorExt(first, swapSpec.scrollTarget)
|
|
3621
|
+
target = asElement(querySelectorExt(first, swapSpec.scrollTarget))
|
|
2539
3622
|
}
|
|
2540
3623
|
if (swapSpec.scroll === 'top' && (first || target)) {
|
|
2541
3624
|
target = target || first
|
|
@@ -2553,27 +3636,29 @@ const htmx = (function () {
|
|
|
2553
3636
|
if (swapSpec.showTarget === 'window') {
|
|
2554
3637
|
targetStr = 'body'
|
|
2555
3638
|
}
|
|
2556
|
-
target = querySelectorExt(first, targetStr)
|
|
3639
|
+
target = asElement(querySelectorExt(first, targetStr))
|
|
2557
3640
|
}
|
|
2558
3641
|
if (swapSpec.show === 'top' && (first || target)) {
|
|
2559
3642
|
target = target || first
|
|
3643
|
+
// @ts-ignore For some reason tsc doesn't recognize "instant" as a valid option for now
|
|
2560
3644
|
target.scrollIntoView({ block: 'start', behavior: htmx.config.scrollBehavior })
|
|
2561
3645
|
}
|
|
2562
3646
|
if (swapSpec.show === 'bottom' && (last || target)) {
|
|
2563
3647
|
target = target || last
|
|
3648
|
+
// @ts-ignore For some reason tsc doesn't recognize "instant" as a valid option for now
|
|
2564
3649
|
target.scrollIntoView({ block: 'end', behavior: htmx.config.scrollBehavior })
|
|
2565
3650
|
}
|
|
2566
3651
|
}
|
|
2567
3652
|
}
|
|
2568
3653
|
|
|
2569
3654
|
/**
|
|
2570
|
-
* @param {
|
|
3655
|
+
* @param {Element} elt
|
|
2571
3656
|
* @param {string} attr
|
|
2572
3657
|
* @param {boolean=} evalAsDefault
|
|
2573
3658
|
* @param {Object=} values
|
|
2574
3659
|
* @returns {Object}
|
|
2575
3660
|
*/
|
|
2576
|
-
function getValuesForElement
|
|
3661
|
+
function getValuesForElement(elt, attr, evalAsDefault, values) {
|
|
2577
3662
|
if (values == null) {
|
|
2578
3663
|
values = {}
|
|
2579
3664
|
}
|
|
@@ -2599,7 +3684,7 @@ const htmx = (function () {
|
|
|
2599
3684
|
}
|
|
2600
3685
|
let varsValues
|
|
2601
3686
|
if (evaluateValue) {
|
|
2602
|
-
varsValues = maybeEval(elt, function
|
|
3687
|
+
varsValues = maybeEval(elt, function() { return Function('return (' + str + ')')() }, {})
|
|
2603
3688
|
} else {
|
|
2604
3689
|
varsValues = parseJSON(str)
|
|
2605
3690
|
}
|
|
@@ -2611,10 +3696,16 @@ const htmx = (function () {
|
|
|
2611
3696
|
}
|
|
2612
3697
|
}
|
|
2613
3698
|
}
|
|
2614
|
-
return getValuesForElement(parentElt(elt), attr, evalAsDefault, values)
|
|
3699
|
+
return getValuesForElement(asElement(parentElt(elt)), attr, evalAsDefault, values)
|
|
2615
3700
|
}
|
|
2616
3701
|
|
|
2617
|
-
|
|
3702
|
+
/**
|
|
3703
|
+
* @param {EventTarget|string} elt
|
|
3704
|
+
* @param {() => any} toEval
|
|
3705
|
+
* @param {any=} defaultVal
|
|
3706
|
+
* @returns {any}
|
|
3707
|
+
*/
|
|
3708
|
+
function maybeEval(elt, toEval, defaultVal) {
|
|
2618
3709
|
if (htmx.config.allowEval) {
|
|
2619
3710
|
return toEval()
|
|
2620
3711
|
} else {
|
|
@@ -2624,32 +3715,37 @@ const htmx = (function () {
|
|
|
2624
3715
|
}
|
|
2625
3716
|
|
|
2626
3717
|
/**
|
|
2627
|
-
* @param {
|
|
2628
|
-
* @param {
|
|
3718
|
+
* @param {Element} elt
|
|
3719
|
+
* @param {*?} expressionVars
|
|
2629
3720
|
* @returns
|
|
2630
3721
|
*/
|
|
2631
|
-
function getHXVarsForElement
|
|
3722
|
+
function getHXVarsForElement(elt, expressionVars) {
|
|
2632
3723
|
return getValuesForElement(elt, 'hx-vars', true, expressionVars)
|
|
2633
3724
|
}
|
|
2634
3725
|
|
|
2635
3726
|
/**
|
|
2636
|
-
* @param {
|
|
2637
|
-
* @param {
|
|
3727
|
+
* @param {Element} elt
|
|
3728
|
+
* @param {*?} expressionVars
|
|
2638
3729
|
* @returns
|
|
2639
3730
|
*/
|
|
2640
|
-
function getHXValsForElement
|
|
3731
|
+
function getHXValsForElement(elt, expressionVars) {
|
|
2641
3732
|
return getValuesForElement(elt, 'hx-vals', false, expressionVars)
|
|
2642
3733
|
}
|
|
2643
3734
|
|
|
2644
3735
|
/**
|
|
2645
|
-
* @param {
|
|
2646
|
-
* @returns {
|
|
3736
|
+
* @param {Element} elt
|
|
3737
|
+
* @returns {FormData}
|
|
2647
3738
|
*/
|
|
2648
|
-
function getExpressionVars
|
|
2649
|
-
return mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt))
|
|
3739
|
+
function getExpressionVars(elt) {
|
|
3740
|
+
return formDataFromObject(mergeObjects(getHXVarsForElement(elt), getHXValsForElement(elt)))
|
|
2650
3741
|
}
|
|
2651
3742
|
|
|
2652
|
-
|
|
3743
|
+
/**
|
|
3744
|
+
* @param {XMLHttpRequest} xhr
|
|
3745
|
+
* @param {string} header
|
|
3746
|
+
* @param {string|null} headerValue
|
|
3747
|
+
*/
|
|
3748
|
+
function safelySetHeaderValue(xhr, header, headerValue) {
|
|
2653
3749
|
if (headerValue !== null) {
|
|
2654
3750
|
try {
|
|
2655
3751
|
xhr.setRequestHeader(header, headerValue)
|
|
@@ -2661,7 +3757,11 @@ const htmx = (function () {
|
|
|
2661
3757
|
}
|
|
2662
3758
|
}
|
|
2663
3759
|
|
|
2664
|
-
|
|
3760
|
+
/**
|
|
3761
|
+
* @param {XMLHttpRequest} xhr
|
|
3762
|
+
* @return {string}
|
|
3763
|
+
*/
|
|
3764
|
+
function getPathFromResponse(xhr) {
|
|
2665
3765
|
// NB: IE11 does not support this stuff
|
|
2666
3766
|
if (xhr.responseURL && typeof (URL) !== 'undefined') {
|
|
2667
3767
|
try {
|
|
@@ -2673,14 +3773,29 @@ const htmx = (function () {
|
|
|
2673
3773
|
}
|
|
2674
3774
|
}
|
|
2675
3775
|
|
|
2676
|
-
|
|
3776
|
+
/**
|
|
3777
|
+
* @param {XMLHttpRequest} xhr
|
|
3778
|
+
* @param {RegExp} regexp
|
|
3779
|
+
* @return {boolean}
|
|
3780
|
+
*/
|
|
3781
|
+
function hasHeader(xhr, regexp) {
|
|
2677
3782
|
return regexp.test(xhr.getAllResponseHeaders())
|
|
2678
3783
|
}
|
|
2679
3784
|
|
|
2680
|
-
|
|
2681
|
-
|
|
3785
|
+
/**
|
|
3786
|
+
* Issues an htmx-style AJAX request
|
|
3787
|
+
*
|
|
3788
|
+
* @see https://htmx.org/api/#ajax
|
|
3789
|
+
*
|
|
3790
|
+
* @param {HttpVerb} verb
|
|
3791
|
+
* @param {string} path the URL path to make the AJAX
|
|
3792
|
+
* @param {Element|string|HtmxAjaxHelperContext} context the element to target (defaults to the **body**) | a selector for the target | a context object that contains any of the following
|
|
3793
|
+
* @return {Promise<void>} Promise that resolves immediately if no request is sent, or when the request is complete
|
|
3794
|
+
*/
|
|
3795
|
+
function ajaxHelper(verb, path, context) {
|
|
3796
|
+
verb = (/** @type HttpVerb */(verb.toLowerCase()))
|
|
2682
3797
|
if (context) {
|
|
2683
|
-
if (context instanceof Element ||
|
|
3798
|
+
if (context instanceof Element || typeof context === 'string') {
|
|
2684
3799
|
return issueAjaxRequest(verb, path, null, null, {
|
|
2685
3800
|
targetOverride: resolveTarget(context),
|
|
2686
3801
|
returnPromise: true
|
|
@@ -2704,7 +3819,11 @@ const htmx = (function () {
|
|
|
2704
3819
|
}
|
|
2705
3820
|
}
|
|
2706
3821
|
|
|
2707
|
-
|
|
3822
|
+
/**
|
|
3823
|
+
* @param {Element} elt
|
|
3824
|
+
* @return {Element[]}
|
|
3825
|
+
*/
|
|
3826
|
+
function hierarchyForElt(elt) {
|
|
2708
3827
|
const arr = []
|
|
2709
3828
|
while (elt) {
|
|
2710
3829
|
arr.push(elt)
|
|
@@ -2713,7 +3832,13 @@ const htmx = (function () {
|
|
|
2713
3832
|
return arr
|
|
2714
3833
|
}
|
|
2715
3834
|
|
|
2716
|
-
|
|
3835
|
+
/**
|
|
3836
|
+
* @param {Element} elt
|
|
3837
|
+
* @param {string} path
|
|
3838
|
+
* @param {HtmxRequestConfig} requestConfig
|
|
3839
|
+
* @return {boolean}
|
|
3840
|
+
*/
|
|
3841
|
+
function verifyPath(elt, path, requestConfig) {
|
|
2717
3842
|
let sameHost
|
|
2718
3843
|
let url
|
|
2719
3844
|
if (typeof URL === 'function') {
|
|
@@ -2734,12 +3859,146 @@ const htmx = (function () {
|
|
|
2734
3859
|
return triggerEvent(elt, 'htmx:validateUrl', mergeObjects({ url, sameHost }, requestConfig))
|
|
2735
3860
|
}
|
|
2736
3861
|
|
|
2737
|
-
|
|
3862
|
+
/**
|
|
3863
|
+
* @param {Object|FormData} obj
|
|
3864
|
+
* @return {FormData}
|
|
3865
|
+
*/
|
|
3866
|
+
function formDataFromObject(obj) {
|
|
3867
|
+
if (obj instanceof FormData) return obj
|
|
3868
|
+
const formData = new FormData()
|
|
3869
|
+
for (const key in obj) {
|
|
3870
|
+
if (obj.hasOwnProperty(key)) {
|
|
3871
|
+
if (typeof obj[key].forEach === 'function') {
|
|
3872
|
+
obj[key].forEach(function(v) { formData.append(key, v) })
|
|
3873
|
+
} else if (typeof obj[key] === 'object') {
|
|
3874
|
+
formData.append(key, JSON.stringify(obj[key]))
|
|
3875
|
+
} else {
|
|
3876
|
+
formData.append(key, obj[key])
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
return formData
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
/**
|
|
3884
|
+
* @param {FormData} formData
|
|
3885
|
+
* @param {string} name
|
|
3886
|
+
* @param {Array} array
|
|
3887
|
+
* @returns {Array}
|
|
3888
|
+
*/
|
|
3889
|
+
function formDataArrayProxy(formData, name, array) {
|
|
3890
|
+
// mutating the array should mutate the underlying form data
|
|
3891
|
+
return new Proxy(array, {
|
|
3892
|
+
get: function(target, key) {
|
|
3893
|
+
if (typeof key === 'number') return target[key]
|
|
3894
|
+
if (key === 'length') return target.length
|
|
3895
|
+
if (key === 'push') {
|
|
3896
|
+
return function(value) {
|
|
3897
|
+
target.push(value)
|
|
3898
|
+
formData.append(name, value)
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
if (typeof target[key] === 'function') {
|
|
3902
|
+
return function() {
|
|
3903
|
+
target[key].apply(target, arguments)
|
|
3904
|
+
formData.delete(name)
|
|
3905
|
+
target.forEach(function(v) { formData.append(name, v) })
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
if (target[key] && target[key].length === 1) {
|
|
3910
|
+
return target[key][0]
|
|
3911
|
+
} else {
|
|
3912
|
+
return target[key]
|
|
3913
|
+
}
|
|
3914
|
+
},
|
|
3915
|
+
set: function(target, index, value) {
|
|
3916
|
+
target[index] = value
|
|
3917
|
+
formData.delete(name)
|
|
3918
|
+
target.forEach(function(v) { formData.append(name, v) })
|
|
3919
|
+
return true
|
|
3920
|
+
}
|
|
3921
|
+
})
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
/**
|
|
3925
|
+
* @param {FormData} formData
|
|
3926
|
+
* @returns {Object}
|
|
3927
|
+
*/
|
|
3928
|
+
function formDataProxy(formData) {
|
|
3929
|
+
return new Proxy(formData, {
|
|
3930
|
+
get: function(target, name) {
|
|
3931
|
+
if (typeof name === 'symbol') {
|
|
3932
|
+
// Forward symbol calls to the FormData itself directly
|
|
3933
|
+
return Reflect.get(target, name)
|
|
3934
|
+
}
|
|
3935
|
+
if (name === 'toJSON') {
|
|
3936
|
+
// Support JSON.stringify call on proxy
|
|
3937
|
+
return () => Object.fromEntries(formData)
|
|
3938
|
+
}
|
|
3939
|
+
if (name in target) {
|
|
3940
|
+
// Wrap in function with apply to correctly bind the FormData context, as a direct call would result in an illegal invocation error
|
|
3941
|
+
if (typeof target[name] === 'function') {
|
|
3942
|
+
return function() {
|
|
3943
|
+
return formData[name].apply(formData, arguments)
|
|
3944
|
+
}
|
|
3945
|
+
} else {
|
|
3946
|
+
return target[name]
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
const array = formData.getAll(name)
|
|
3950
|
+
// Those 2 undefined & single value returns are for retro-compatibility as we weren't using FormData before
|
|
3951
|
+
if (array.length === 0) {
|
|
3952
|
+
return undefined
|
|
3953
|
+
} else if (array.length === 1) {
|
|
3954
|
+
return array[0]
|
|
3955
|
+
} else {
|
|
3956
|
+
return formDataArrayProxy(target, name, array)
|
|
3957
|
+
}
|
|
3958
|
+
},
|
|
3959
|
+
set: function(target, name, value) {
|
|
3960
|
+
if (typeof name !== 'string') {
|
|
3961
|
+
return false
|
|
3962
|
+
}
|
|
3963
|
+
target.delete(name)
|
|
3964
|
+
if (typeof value.forEach === 'function') {
|
|
3965
|
+
value.forEach(function(v) { target.append(name, v) })
|
|
3966
|
+
} else {
|
|
3967
|
+
target.append(name, value)
|
|
3968
|
+
}
|
|
3969
|
+
return true
|
|
3970
|
+
},
|
|
3971
|
+
deleteProperty: function(target, name) {
|
|
3972
|
+
if (typeof name === 'string') {
|
|
3973
|
+
target.delete(name)
|
|
3974
|
+
}
|
|
3975
|
+
return true
|
|
3976
|
+
},
|
|
3977
|
+
// Support Object.assign call from proxy
|
|
3978
|
+
ownKeys: function(target) {
|
|
3979
|
+
return Reflect.ownKeys(Object.fromEntries(target))
|
|
3980
|
+
},
|
|
3981
|
+
getOwnPropertyDescriptor: function(target, prop) {
|
|
3982
|
+
return Reflect.getOwnPropertyDescriptor(Object.fromEntries(target), prop)
|
|
3983
|
+
}
|
|
3984
|
+
})
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3987
|
+
/**
|
|
3988
|
+
* @param {HttpVerb} verb
|
|
3989
|
+
* @param {string} path
|
|
3990
|
+
* @param {Element} elt
|
|
3991
|
+
* @param {Event} event
|
|
3992
|
+
* @param {HtmxAjaxEtc} [etc]
|
|
3993
|
+
* @param {boolean} [confirmed]
|
|
3994
|
+
* @return {Promise<void>}
|
|
3995
|
+
*/
|
|
3996
|
+
function issueAjaxRequest(verb, path, elt, event, etc, confirmed) {
|
|
2738
3997
|
let resolve = null
|
|
2739
3998
|
let reject = null
|
|
2740
3999
|
etc = etc != null ? etc : {}
|
|
2741
4000
|
if (etc.returnPromise && typeof Promise !== 'undefined') {
|
|
2742
|
-
var promise = new Promise(function
|
|
4001
|
+
var promise = new Promise(function(_resolve, _reject) {
|
|
2743
4002
|
resolve = _resolve
|
|
2744
4003
|
reject = _reject
|
|
2745
4004
|
})
|
|
@@ -2755,7 +4014,7 @@ const htmx = (function () {
|
|
|
2755
4014
|
maybeCall(resolve)
|
|
2756
4015
|
return promise
|
|
2757
4016
|
}
|
|
2758
|
-
const target = etc.targetOverride || getTarget(elt)
|
|
4017
|
+
const target = etc.targetOverride || asElement(getTarget(elt))
|
|
2759
4018
|
if (target == null || target == DUMMY_ELT) {
|
|
2760
4019
|
triggerErrorEvent(elt, 'htmx:targetError', { target: getAttributeValue(elt, 'hx-target') })
|
|
2761
4020
|
maybeCall(reject)
|
|
@@ -2775,7 +4034,7 @@ const htmx = (function () {
|
|
|
2775
4034
|
if (buttonVerb != null) {
|
|
2776
4035
|
// ignore buttons with formmethod="dialog"
|
|
2777
4036
|
if (buttonVerb.toLowerCase() !== 'dialog') {
|
|
2778
|
-
verb = buttonVerb
|
|
4037
|
+
verb = (/** @type HttpVerb */(buttonVerb))
|
|
2779
4038
|
}
|
|
2780
4039
|
}
|
|
2781
4040
|
}
|
|
@@ -2783,7 +4042,7 @@ const htmx = (function () {
|
|
|
2783
4042
|
const confirmQuestion = getClosestAttributeValue(elt, 'hx-confirm')
|
|
2784
4043
|
// allow event-based confirmation w/ a callback
|
|
2785
4044
|
if (confirmed === undefined) {
|
|
2786
|
-
const issueRequest = function
|
|
4045
|
+
const issueRequest = function(skipConfirmation) {
|
|
2787
4046
|
return issueAjaxRequest(verb, path, elt, event, etc, !!skipConfirmation)
|
|
2788
4047
|
}
|
|
2789
4048
|
const confirmDetails = { target, elt, path, verb, triggeringEvent: event, etc, issueRequest, question: confirmQuestion }
|
|
@@ -2803,7 +4062,7 @@ const htmx = (function () {
|
|
|
2803
4062
|
if (selector === 'this') {
|
|
2804
4063
|
syncElt = findThisElement(elt, 'hx-sync')
|
|
2805
4064
|
} else {
|
|
2806
|
-
syncElt = querySelectorExt(elt, selector)
|
|
4065
|
+
syncElt = asElement(querySelectorExt(elt, selector))
|
|
2807
4066
|
}
|
|
2808
4067
|
// default to the drop strategy
|
|
2809
4068
|
syncStrategy = (syncStrings[1] || 'drop').trim()
|
|
@@ -2845,16 +4104,16 @@ const htmx = (function () {
|
|
|
2845
4104
|
eltData.queuedRequests = []
|
|
2846
4105
|
}
|
|
2847
4106
|
if (queueStrategy === 'first' && eltData.queuedRequests.length === 0) {
|
|
2848
|
-
eltData.queuedRequests.push(function
|
|
4107
|
+
eltData.queuedRequests.push(function() {
|
|
2849
4108
|
issueAjaxRequest(verb, path, elt, event, etc)
|
|
2850
4109
|
})
|
|
2851
4110
|
} else if (queueStrategy === 'all') {
|
|
2852
|
-
eltData.queuedRequests.push(function
|
|
4111
|
+
eltData.queuedRequests.push(function() {
|
|
2853
4112
|
issueAjaxRequest(verb, path, elt, event, etc)
|
|
2854
4113
|
})
|
|
2855
4114
|
} else if (queueStrategy === 'last') {
|
|
2856
4115
|
eltData.queuedRequests = [] // dump existing queue
|
|
2857
|
-
eltData.queuedRequests.push(function
|
|
4116
|
+
eltData.queuedRequests.push(function() {
|
|
2858
4117
|
issueAjaxRequest(verb, path, elt, event, etc)
|
|
2859
4118
|
})
|
|
2860
4119
|
}
|
|
@@ -2866,7 +4125,7 @@ const htmx = (function () {
|
|
|
2866
4125
|
const xhr = new XMLHttpRequest()
|
|
2867
4126
|
eltData.xhr = xhr
|
|
2868
4127
|
eltData.abortable = abortable
|
|
2869
|
-
const endRequestLock = function
|
|
4128
|
+
const endRequestLock = function() {
|
|
2870
4129
|
eltData.xhr = null
|
|
2871
4130
|
eltData.abortable = false
|
|
2872
4131
|
if (eltData.queuedRequests != null &&
|
|
@@ -2906,16 +4165,16 @@ const htmx = (function () {
|
|
|
2906
4165
|
}
|
|
2907
4166
|
const results = getInputValues(elt, verb)
|
|
2908
4167
|
let errors = results.errors
|
|
2909
|
-
|
|
4168
|
+
const rawFormData = results.formData
|
|
2910
4169
|
if (etc.values) {
|
|
2911
|
-
|
|
4170
|
+
overrideFormData(rawFormData, formDataFromObject(etc.values))
|
|
2912
4171
|
}
|
|
2913
4172
|
const expressionVars = getExpressionVars(elt)
|
|
2914
|
-
const
|
|
2915
|
-
let
|
|
4173
|
+
const allFormData = overrideFormData(rawFormData, expressionVars)
|
|
4174
|
+
let filteredFormData = filterValues(allFormData, elt)
|
|
2916
4175
|
|
|
2917
4176
|
if (htmx.config.getCacheBusterParam && verb === 'get') {
|
|
2918
|
-
|
|
4177
|
+
filteredFormData.set('org.htmx.cache-buster', getRawAttribute(target, 'id') || 'true')
|
|
2919
4178
|
}
|
|
2920
4179
|
|
|
2921
4180
|
// behavior of anchors w/ empty href is to use the current URL
|
|
@@ -2923,17 +4182,26 @@ const htmx = (function () {
|
|
|
2923
4182
|
path = getDocument().location.href
|
|
2924
4183
|
}
|
|
2925
4184
|
|
|
4185
|
+
/**
|
|
4186
|
+
* @type {Object}
|
|
4187
|
+
* @property {boolean} [credentials]
|
|
4188
|
+
* @property {number} [timeout]
|
|
4189
|
+
* @property {boolean} [noHeaders]
|
|
4190
|
+
*/
|
|
2926
4191
|
const requestAttrValues = getValuesForElement(elt, 'hx-request')
|
|
2927
4192
|
|
|
2928
4193
|
const eltIsBoosted = getInternalData(elt).boosted
|
|
2929
4194
|
|
|
2930
4195
|
let useUrlParams = htmx.config.methodsThatUseUrlParams.indexOf(verb) >= 0
|
|
2931
4196
|
|
|
4197
|
+
/** @type HtmxRequestConfig */
|
|
2932
4198
|
const requestConfig = {
|
|
2933
4199
|
boosted: eltIsBoosted,
|
|
2934
4200
|
useUrlParams,
|
|
2935
|
-
|
|
2936
|
-
|
|
4201
|
+
formData: filteredFormData,
|
|
4202
|
+
parameters: formDataProxy(filteredFormData),
|
|
4203
|
+
unfilteredFormData: allFormData,
|
|
4204
|
+
unfilteredParameters: formDataProxy(allFormData),
|
|
2937
4205
|
headers,
|
|
2938
4206
|
target,
|
|
2939
4207
|
verb,
|
|
@@ -2954,7 +4222,7 @@ const htmx = (function () {
|
|
|
2954
4222
|
path = requestConfig.path
|
|
2955
4223
|
verb = requestConfig.verb
|
|
2956
4224
|
headers = requestConfig.headers
|
|
2957
|
-
|
|
4225
|
+
filteredFormData = formDataFromObject(requestConfig.parameters)
|
|
2958
4226
|
errors = requestConfig.errors
|
|
2959
4227
|
useUrlParams = requestConfig.useUrlParams
|
|
2960
4228
|
|
|
@@ -2972,14 +4240,14 @@ const htmx = (function () {
|
|
|
2972
4240
|
let finalPath = path
|
|
2973
4241
|
if (useUrlParams) {
|
|
2974
4242
|
finalPath = pathNoAnchor
|
|
2975
|
-
const
|
|
2976
|
-
if (
|
|
4243
|
+
const hasValues = !filteredFormData.keys().next().done
|
|
4244
|
+
if (hasValues) {
|
|
2977
4245
|
if (finalPath.indexOf('?') < 0) {
|
|
2978
4246
|
finalPath += '?'
|
|
2979
4247
|
} else {
|
|
2980
4248
|
finalPath += '&'
|
|
2981
4249
|
}
|
|
2982
|
-
finalPath += urlEncode(
|
|
4250
|
+
finalPath += urlEncode(filteredFormData)
|
|
2983
4251
|
if (anchor) {
|
|
2984
4252
|
finalPath += '#' + anchor
|
|
2985
4253
|
}
|
|
@@ -2990,7 +4258,7 @@ const htmx = (function () {
|
|
|
2990
4258
|
triggerErrorEvent(elt, 'htmx:invalidPath', requestConfig)
|
|
2991
4259
|
maybeCall(reject)
|
|
2992
4260
|
return promise
|
|
2993
|
-
}
|
|
4261
|
+
}
|
|
2994
4262
|
|
|
2995
4263
|
xhr.open(verb.toUpperCase(), finalPath, true)
|
|
2996
4264
|
xhr.overrideMimeType('text/html')
|
|
@@ -3009,6 +4277,7 @@ const htmx = (function () {
|
|
|
3009
4277
|
}
|
|
3010
4278
|
}
|
|
3011
4279
|
|
|
4280
|
+
/** @type {HtmxResponseInfo} */
|
|
3012
4281
|
const responseInfo = {
|
|
3013
4282
|
xhr,
|
|
3014
4283
|
target,
|
|
@@ -3019,11 +4288,12 @@ const htmx = (function () {
|
|
|
3019
4288
|
pathInfo: {
|
|
3020
4289
|
requestPath: path,
|
|
3021
4290
|
finalRequestPath: finalPath,
|
|
4291
|
+
responsePath: null,
|
|
3022
4292
|
anchor
|
|
3023
4293
|
}
|
|
3024
4294
|
}
|
|
3025
4295
|
|
|
3026
|
-
xhr.onload = function
|
|
4296
|
+
xhr.onload = function() {
|
|
3027
4297
|
try {
|
|
3028
4298
|
const hierarchy = hierarchyForElt(elt)
|
|
3029
4299
|
responseInfo.pathInfo.responsePath = getPathFromResponse(xhr)
|
|
@@ -3053,21 +4323,21 @@ const htmx = (function () {
|
|
|
3053
4323
|
throw e
|
|
3054
4324
|
}
|
|
3055
4325
|
}
|
|
3056
|
-
xhr.onerror = function
|
|
4326
|
+
xhr.onerror = function() {
|
|
3057
4327
|
removeRequestIndicators(indicators, disableElts)
|
|
3058
4328
|
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo)
|
|
3059
4329
|
triggerErrorEvent(elt, 'htmx:sendError', responseInfo)
|
|
3060
4330
|
maybeCall(reject)
|
|
3061
4331
|
endRequestLock()
|
|
3062
4332
|
}
|
|
3063
|
-
xhr.onabort = function
|
|
4333
|
+
xhr.onabort = function() {
|
|
3064
4334
|
removeRequestIndicators(indicators, disableElts)
|
|
3065
4335
|
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo)
|
|
3066
4336
|
triggerErrorEvent(elt, 'htmx:sendAbort', responseInfo)
|
|
3067
4337
|
maybeCall(reject)
|
|
3068
4338
|
endRequestLock()
|
|
3069
4339
|
}
|
|
3070
|
-
xhr.ontimeout = function
|
|
4340
|
+
xhr.ontimeout = function() {
|
|
3071
4341
|
removeRequestIndicators(indicators, disableElts)
|
|
3072
4342
|
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo)
|
|
3073
4343
|
triggerErrorEvent(elt, 'htmx:timeout', responseInfo)
|
|
@@ -3082,9 +4352,9 @@ const htmx = (function () {
|
|
|
3082
4352
|
var indicators = addRequestIndicatorClasses(elt)
|
|
3083
4353
|
var disableElts = disableElements(elt)
|
|
3084
4354
|
|
|
3085
|
-
forEach(['loadstart', 'loadend', 'progress', 'abort'], function
|
|
3086
|
-
forEach([xhr, xhr.upload], function
|
|
3087
|
-
target.addEventListener(eventName, function
|
|
4355
|
+
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
|
|
4356
|
+
forEach([xhr, xhr.upload], function(target) {
|
|
4357
|
+
target.addEventListener(eventName, function(event) {
|
|
3088
4358
|
triggerEvent(elt, 'htmx:xhr:' + eventName, {
|
|
3089
4359
|
lengthComputable: event.lengthComputable,
|
|
3090
4360
|
loaded: event.loaded,
|
|
@@ -3094,12 +4364,23 @@ const htmx = (function () {
|
|
|
3094
4364
|
})
|
|
3095
4365
|
})
|
|
3096
4366
|
triggerEvent(elt, 'htmx:beforeSend', responseInfo)
|
|
3097
|
-
const params = useUrlParams ? null : encodeParamsForBody(xhr, elt,
|
|
4367
|
+
const params = useUrlParams ? null : encodeParamsForBody(xhr, elt, filteredFormData)
|
|
3098
4368
|
xhr.send(params)
|
|
3099
4369
|
return promise
|
|
3100
4370
|
}
|
|
3101
4371
|
|
|
3102
|
-
|
|
4372
|
+
/**
|
|
4373
|
+
* @typedef {Object} HtmxHistoryUpdate
|
|
4374
|
+
* @property {string|null} [type]
|
|
4375
|
+
* @property {string|null} [path]
|
|
4376
|
+
*/
|
|
4377
|
+
|
|
4378
|
+
/**
|
|
4379
|
+
* @param {Element} elt
|
|
4380
|
+
* @param {HtmxResponseInfo} responseInfo
|
|
4381
|
+
* @return {HtmxHistoryUpdate}
|
|
4382
|
+
*/
|
|
4383
|
+
function determineHistoryUpdates(elt, responseInfo) {
|
|
3103
4384
|
const xhr = responseInfo.xhr
|
|
3104
4385
|
|
|
3105
4386
|
//= ==========================================
|
|
@@ -3166,8 +4447,7 @@ const htmx = (function () {
|
|
|
3166
4447
|
}
|
|
3167
4448
|
|
|
3168
4449
|
// restore any anchor associated with the request
|
|
3169
|
-
if (responseInfo.pathInfo.anchor &&
|
|
3170
|
-
path.indexOf('#') === -1) {
|
|
4450
|
+
if (responseInfo.pathInfo.anchor && path.indexOf('#') === -1) {
|
|
3171
4451
|
path = path + '#' + responseInfo.pathInfo.anchor
|
|
3172
4452
|
}
|
|
3173
4453
|
|
|
@@ -3180,30 +4460,76 @@ const htmx = (function () {
|
|
|
3180
4460
|
}
|
|
3181
4461
|
}
|
|
3182
4462
|
|
|
3183
|
-
|
|
4463
|
+
/**
|
|
4464
|
+
* @param {HtmxResponseHandlingConfig} responseHandlingConfig
|
|
4465
|
+
* @param {number} status
|
|
4466
|
+
* @return {boolean}
|
|
4467
|
+
*/
|
|
4468
|
+
function codeMatches(responseHandlingConfig, status) {
|
|
4469
|
+
var regExp = new RegExp(responseHandlingConfig.code)
|
|
4470
|
+
return regExp.test(status.toString(10))
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
/**
|
|
4474
|
+
* @param {XMLHttpRequest} xhr
|
|
4475
|
+
* @return {HtmxResponseHandlingConfig}
|
|
4476
|
+
*/
|
|
4477
|
+
function resolveResponseHandling(xhr) {
|
|
4478
|
+
for (var i = 0; i < htmx.config.responseHandling.length; i++) {
|
|
4479
|
+
/** @type HtmxResponseHandlingConfig */
|
|
4480
|
+
var responseHandlingElement = htmx.config.responseHandling[i]
|
|
4481
|
+
if (codeMatches(responseHandlingElement, xhr.status)) {
|
|
4482
|
+
return responseHandlingElement
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
// no matches, return no swap
|
|
4486
|
+
return {
|
|
4487
|
+
swap: false
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
|
|
4491
|
+
/**
|
|
4492
|
+
* @param {string} title
|
|
4493
|
+
*/
|
|
4494
|
+
function handleTitle(title) {
|
|
4495
|
+
if (title) {
|
|
4496
|
+
const titleElt = find('title')
|
|
4497
|
+
if (titleElt) {
|
|
4498
|
+
titleElt.innerHTML = title
|
|
4499
|
+
} else {
|
|
4500
|
+
window.document.title = title
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
/**
|
|
4506
|
+
* @param {Element} elt
|
|
4507
|
+
* @param {HtmxResponseInfo} responseInfo
|
|
4508
|
+
*/
|
|
4509
|
+
function handleAjaxResponse(elt, responseInfo) {
|
|
3184
4510
|
const xhr = responseInfo.xhr
|
|
3185
4511
|
let target = responseInfo.target
|
|
3186
4512
|
const etc = responseInfo.etc
|
|
3187
|
-
const
|
|
3188
|
-
const select = responseInfo.select
|
|
4513
|
+
const responseInfoSelect = responseInfo.select
|
|
3189
4514
|
|
|
3190
4515
|
if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return
|
|
3191
4516
|
|
|
3192
4517
|
if (hasHeader(xhr, /HX-Trigger:/i)) {
|
|
3193
|
-
|
|
4518
|
+
handleTriggerHeader(xhr, 'HX-Trigger', elt)
|
|
3194
4519
|
}
|
|
3195
4520
|
|
|
3196
4521
|
if (hasHeader(xhr, /HX-Location:/i)) {
|
|
3197
4522
|
saveCurrentPageToHistory()
|
|
3198
4523
|
let redirectPath = xhr.getResponseHeader('HX-Location')
|
|
3199
|
-
|
|
4524
|
+
/** @type {HtmxAjaxHelperContext&{path:string}} */
|
|
4525
|
+
var redirectSwapSpec
|
|
3200
4526
|
if (redirectPath.indexOf('{') === 0) {
|
|
3201
|
-
|
|
4527
|
+
redirectSwapSpec = parseJSON(redirectPath)
|
|
3202
4528
|
// what's the best way to throw an error if the user didn't include this
|
|
3203
|
-
redirectPath =
|
|
3204
|
-
delete
|
|
4529
|
+
redirectPath = redirectSwapSpec.path
|
|
4530
|
+
delete redirectSwapSpec.path
|
|
3205
4531
|
}
|
|
3206
|
-
ajaxHelper('
|
|
4532
|
+
ajaxHelper('get', redirectPath, redirectSwapSpec).then(function() {
|
|
3207
4533
|
pushUrlIntoHistory(redirectPath)
|
|
3208
4534
|
})
|
|
3209
4535
|
return
|
|
@@ -3226,27 +4552,56 @@ const htmx = (function () {
|
|
|
3226
4552
|
if (xhr.getResponseHeader('HX-Retarget') === 'this') {
|
|
3227
4553
|
responseInfo.target = elt
|
|
3228
4554
|
} else {
|
|
3229
|
-
responseInfo.target = querySelectorExt(elt, xhr.getResponseHeader('HX-Retarget'))
|
|
4555
|
+
responseInfo.target = asElement(querySelectorExt(elt, xhr.getResponseHeader('HX-Retarget')))
|
|
3230
4556
|
}
|
|
3231
4557
|
}
|
|
3232
4558
|
|
|
3233
4559
|
const historyUpdate = determineHistoryUpdates(elt, responseInfo)
|
|
3234
4560
|
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
4561
|
+
const responseHandling = resolveResponseHandling(xhr)
|
|
4562
|
+
const shouldSwap = responseHandling.swap
|
|
4563
|
+
let isError = !!responseHandling.error
|
|
4564
|
+
let ignoreTitle = htmx.config.ignoreTitle || responseHandling.ignoreTitle
|
|
4565
|
+
let selectOverride = responseHandling.select
|
|
4566
|
+
if (responseHandling.target) {
|
|
4567
|
+
responseInfo.target = asElement(querySelectorExt(elt, responseHandling.target))
|
|
4568
|
+
}
|
|
4569
|
+
var swapOverride = etc.swapOverride
|
|
4570
|
+
if (swapOverride == null && responseHandling.swapOverride) {
|
|
4571
|
+
swapOverride = responseHandling.swapOverride
|
|
4572
|
+
}
|
|
4573
|
+
|
|
4574
|
+
// response headers override response handling config
|
|
4575
|
+
if (hasHeader(xhr, /HX-Retarget:/i)) {
|
|
4576
|
+
if (xhr.getResponseHeader('HX-Retarget') === 'this') {
|
|
4577
|
+
responseInfo.target = elt
|
|
4578
|
+
} else {
|
|
4579
|
+
responseInfo.target = asElement(querySelectorExt(elt, xhr.getResponseHeader('HX-Retarget')))
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
if (hasHeader(xhr, /HX-Reswap:/i)) {
|
|
4583
|
+
swapOverride = xhr.getResponseHeader('HX-Reswap')
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
var serverResponse = xhr.response
|
|
4587
|
+
/** @type HtmxBeforeSwapDetails */
|
|
4588
|
+
var beforeSwapDetails = mergeObjects({
|
|
4589
|
+
shouldSwap,
|
|
4590
|
+
serverResponse,
|
|
4591
|
+
isError,
|
|
4592
|
+
ignoreTitle,
|
|
4593
|
+
selectOverride
|
|
4594
|
+
}, responseInfo)
|
|
4595
|
+
|
|
4596
|
+
if (responseHandling.event && !triggerEvent(target, responseHandling.event, beforeSwapDetails)) return
|
|
4597
|
+
|
|
3244
4598
|
if (!triggerEvent(target, 'htmx:beforeSwap', beforeSwapDetails)) return
|
|
3245
4599
|
|
|
3246
4600
|
target = beforeSwapDetails.target // allow re-targeting
|
|
3247
4601
|
serverResponse = beforeSwapDetails.serverResponse // allow updating content
|
|
3248
4602
|
isError = beforeSwapDetails.isError // allow updating error
|
|
3249
4603
|
ignoreTitle = beforeSwapDetails.ignoreTitle // allow updating ignoring title
|
|
4604
|
+
selectOverride = beforeSwapDetails.selectOverride // allow updating select override
|
|
3250
4605
|
|
|
3251
4606
|
responseInfo.target = target // Make updated target available to response events
|
|
3252
4607
|
responseInfo.failed = isError // Make failed property available to response events
|
|
@@ -3257,7 +4612,7 @@ const htmx = (function () {
|
|
|
3257
4612
|
cancelPolling(elt)
|
|
3258
4613
|
}
|
|
3259
4614
|
|
|
3260
|
-
withExtensions(elt, function
|
|
4615
|
+
withExtensions(elt, function(extension) {
|
|
3261
4616
|
serverResponse = extension.transformResponse(serverResponse, xhr, elt)
|
|
3262
4617
|
})
|
|
3263
4618
|
|
|
@@ -3266,14 +4621,13 @@ const htmx = (function () {
|
|
|
3266
4621
|
saveCurrentPageToHistory()
|
|
3267
4622
|
}
|
|
3268
4623
|
|
|
3269
|
-
let swapOverride = etc.swapOverride
|
|
3270
4624
|
if (hasHeader(xhr, /HX-Reswap:/i)) {
|
|
3271
4625
|
swapOverride = xhr.getResponseHeader('HX-Reswap')
|
|
3272
4626
|
}
|
|
3273
4627
|
var swapSpec = getSwapSpecification(elt, swapOverride)
|
|
3274
4628
|
|
|
3275
|
-
if (swapSpec.hasOwnProperty('ignoreTitle')) {
|
|
3276
|
-
ignoreTitle =
|
|
4629
|
+
if (!swapSpec.hasOwnProperty('ignoreTitle')) {
|
|
4630
|
+
swapSpec.ignoreTitle = ignoreTitle
|
|
3277
4631
|
}
|
|
3278
4632
|
|
|
3279
4633
|
target.classList.add(htmx.config.swappingClass)
|
|
@@ -3282,31 +4636,19 @@ const htmx = (function () {
|
|
|
3282
4636
|
let settleResolve = null
|
|
3283
4637
|
let settleReject = null
|
|
3284
4638
|
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
let selectionInfo = {}
|
|
3289
|
-
try {
|
|
3290
|
-
selectionInfo = {
|
|
3291
|
-
elt: activeElt,
|
|
3292
|
-
// @ts-ignore
|
|
3293
|
-
start: activeElt ? activeElt.selectionStart : null,
|
|
3294
|
-
// @ts-ignore
|
|
3295
|
-
end: activeElt ? activeElt.selectionEnd : null
|
|
3296
|
-
}
|
|
3297
|
-
} catch (e) {
|
|
3298
|
-
// safari issue - see https://github.com/microsoft/playwright/issues/5894
|
|
3299
|
-
}
|
|
4639
|
+
if (responseInfoSelect) {
|
|
4640
|
+
selectOverride = responseInfoSelect
|
|
4641
|
+
}
|
|
3300
4642
|
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
}
|
|
4643
|
+
if (hasHeader(xhr, /HX-Reselect:/i)) {
|
|
4644
|
+
selectOverride = xhr.getResponseHeader('HX-Reselect')
|
|
4645
|
+
}
|
|
3305
4646
|
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
}
|
|
4647
|
+
const selectOOB = getClosestAttributeValue(elt, 'hx-select-oob')
|
|
4648
|
+
const select = getClosestAttributeValue(elt, 'hx-select')
|
|
3309
4649
|
|
|
4650
|
+
let doSwap = function() {
|
|
4651
|
+
try {
|
|
3310
4652
|
// if we need to save history, do so, before swapping so that relative resources have the correct base URL
|
|
3311
4653
|
if (historyUpdate.type) {
|
|
3312
4654
|
triggerEvent(getDocument().body, 'htmx:beforeHistoryUpdate', mergeObjects({ history: historyUpdate }, responseInfo))
|
|
@@ -3319,88 +4661,32 @@ const htmx = (function () {
|
|
|
3319
4661
|
}
|
|
3320
4662
|
}
|
|
3321
4663
|
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
// @ts-ignore
|
|
3334
|
-
try {
|
|
3335
|
-
newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end)
|
|
3336
|
-
} catch (e) {
|
|
3337
|
-
// the setSelectionRange method is present on fields that don't support it, so just let this fail
|
|
4664
|
+
swap(target, serverResponse, swapSpec, {
|
|
4665
|
+
select: selectOverride || select,
|
|
4666
|
+
selectOOB,
|
|
4667
|
+
eventInfo: responseInfo,
|
|
4668
|
+
anchor: responseInfo.pathInfo.anchor,
|
|
4669
|
+
contextElement: elt,
|
|
4670
|
+
afterSwapCallback: function() {
|
|
4671
|
+
if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
|
|
4672
|
+
let finalElt = elt
|
|
4673
|
+
if (!bodyContains(elt)) {
|
|
4674
|
+
finalElt = getDocument().body
|
|
3338
4675
|
}
|
|
4676
|
+
handleTriggerHeader(xhr, 'HX-Trigger-After-Swap', finalElt)
|
|
3339
4677
|
}
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
}
|
|
3349
|
-
triggerEvent(elt, 'htmx:afterSwap', responseInfo)
|
|
3350
|
-
})
|
|
3351
|
-
|
|
3352
|
-
if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
|
|
3353
|
-
let finalElt = elt
|
|
3354
|
-
if (!bodyContains(elt)) {
|
|
3355
|
-
finalElt = getDocument().body
|
|
3356
|
-
}
|
|
3357
|
-
handleTrigger(xhr, 'HX-Trigger-After-Swap', finalElt)
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
|
-
const doSettle = function () {
|
|
3361
|
-
forEach(settleInfo.tasks, function (task) {
|
|
3362
|
-
task.call()
|
|
3363
|
-
})
|
|
3364
|
-
forEach(settleInfo.elts, function (elt) {
|
|
3365
|
-
if (elt.classList) {
|
|
3366
|
-
elt.classList.remove(htmx.config.settlingClass)
|
|
3367
|
-
}
|
|
3368
|
-
triggerEvent(elt, 'htmx:afterSettle', responseInfo)
|
|
3369
|
-
})
|
|
3370
|
-
|
|
3371
|
-
if (responseInfo.pathInfo.anchor) {
|
|
3372
|
-
const anchorTarget = getDocument().getElementById(responseInfo.pathInfo.anchor)
|
|
3373
|
-
if (anchorTarget) {
|
|
3374
|
-
anchorTarget.scrollIntoView({ block: 'start', behavior: 'auto' })
|
|
3375
|
-
}
|
|
3376
|
-
}
|
|
3377
|
-
|
|
3378
|
-
if (settleInfo.title && !ignoreTitle) {
|
|
3379
|
-
const titleElt = find('title')
|
|
3380
|
-
if (titleElt) {
|
|
3381
|
-
titleElt.innerHTML = settleInfo.title
|
|
3382
|
-
} else {
|
|
3383
|
-
window.document.title = settleInfo.title
|
|
3384
|
-
}
|
|
3385
|
-
}
|
|
3386
|
-
|
|
3387
|
-
updateScrollState(settleInfo.elts, swapSpec)
|
|
3388
|
-
|
|
3389
|
-
if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
|
|
3390
|
-
let finalElt = elt
|
|
3391
|
-
if (!bodyContains(elt)) {
|
|
3392
|
-
finalElt = getDocument().body
|
|
4678
|
+
},
|
|
4679
|
+
afterSettleCallback: function() {
|
|
4680
|
+
if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
|
|
4681
|
+
let finalElt = elt
|
|
4682
|
+
if (!bodyContains(elt)) {
|
|
4683
|
+
finalElt = getDocument().body
|
|
4684
|
+
}
|
|
4685
|
+
handleTriggerHeader(xhr, 'HX-Trigger-After-Settle', finalElt)
|
|
3393
4686
|
}
|
|
3394
|
-
|
|
4687
|
+
maybeCall(settleResolve)
|
|
3395
4688
|
}
|
|
3396
|
-
|
|
3397
|
-
}
|
|
3398
|
-
|
|
3399
|
-
if (swapSpec.settleDelay > 0) {
|
|
3400
|
-
setTimeout(doSettle, swapSpec.settleDelay)
|
|
3401
|
-
} else {
|
|
3402
|
-
doSettle()
|
|
3403
|
-
}
|
|
4689
|
+
})
|
|
3404
4690
|
} catch (e) {
|
|
3405
4691
|
triggerErrorEvent(elt, 'htmx:swapError', responseInfo)
|
|
3406
4692
|
maybeCall(settleReject)
|
|
@@ -3414,16 +4700,19 @@ const htmx = (function () {
|
|
|
3414
4700
|
}
|
|
3415
4701
|
|
|
3416
4702
|
if (shouldTransition &&
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
4703
|
+
triggerEvent(elt, 'htmx:beforeTransition', responseInfo) &&
|
|
4704
|
+
typeof Promise !== 'undefined' &&
|
|
4705
|
+
// @ts-ignore experimental feature atm
|
|
4706
|
+
document.startViewTransition) {
|
|
4707
|
+
const settlePromise = new Promise(function(_resolve, _reject) {
|
|
3420
4708
|
settleResolve = _resolve
|
|
3421
4709
|
settleReject = _reject
|
|
3422
4710
|
})
|
|
3423
4711
|
// wrap the original doSwap() in a call to startViewTransition()
|
|
3424
4712
|
const innerDoSwap = doSwap
|
|
3425
|
-
doSwap = function
|
|
3426
|
-
|
|
4713
|
+
doSwap = function() {
|
|
4714
|
+
// @ts-ignore experimental feature atm
|
|
4715
|
+
document.startViewTransition(function() {
|
|
3427
4716
|
innerDoSwap()
|
|
3428
4717
|
return settlePromise
|
|
3429
4718
|
})
|
|
@@ -3431,7 +4720,7 @@ const htmx = (function () {
|
|
|
3431
4720
|
}
|
|
3432
4721
|
|
|
3433
4722
|
if (swapSpec.swapDelay > 0) {
|
|
3434
|
-
setTimeout(doSwap, swapSpec.swapDelay)
|
|
4723
|
+
getWindow().setTimeout(doSwap, swapSpec.swapDelay)
|
|
3435
4724
|
} else {
|
|
3436
4725
|
doSwap()
|
|
3437
4726
|
}
|
|
@@ -3445,31 +4734,33 @@ const htmx = (function () {
|
|
|
3445
4734
|
// Extensions API
|
|
3446
4735
|
//= ===================================================================
|
|
3447
4736
|
|
|
3448
|
-
/** @type {Object<string,
|
|
4737
|
+
/** @type {Object<string, HtmxExtension>} */
|
|
3449
4738
|
const extensions = {}
|
|
3450
4739
|
|
|
3451
4740
|
/**
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
function extensionBase
|
|
4741
|
+
* extensionBase defines the default functions for all extensions.
|
|
4742
|
+
* @returns {HtmxExtension}
|
|
4743
|
+
*/
|
|
4744
|
+
function extensionBase() {
|
|
3456
4745
|
return {
|
|
3457
|
-
init: function
|
|
3458
|
-
onEvent: function
|
|
3459
|
-
transformResponse: function
|
|
3460
|
-
isInlineSwap: function
|
|
3461
|
-
handleSwap: function
|
|
3462
|
-
encodeParameters: function
|
|
4746
|
+
init: function(api) { return null },
|
|
4747
|
+
onEvent: function(name, evt) { return true },
|
|
4748
|
+
transformResponse: function(text, xhr, elt) { return text },
|
|
4749
|
+
isInlineSwap: function(swapStyle) { return false },
|
|
4750
|
+
handleSwap: function(swapStyle, target, fragment, settleInfo) { return false },
|
|
4751
|
+
encodeParameters: function(xhr, parameters, elt) { return null }
|
|
3463
4752
|
}
|
|
3464
4753
|
}
|
|
3465
4754
|
|
|
3466
4755
|
/**
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
4756
|
+
* defineExtension initializes the extension and adds it to the htmx registry
|
|
4757
|
+
*
|
|
4758
|
+
* @see https://htmx.org/api/#defineExtension
|
|
4759
|
+
*
|
|
4760
|
+
* @param {string} name the extension name
|
|
4761
|
+
* @param {HtmxExtension} extension the extension definition
|
|
4762
|
+
*/
|
|
4763
|
+
function defineExtension(name, extension) {
|
|
3473
4764
|
if (extension.init) {
|
|
3474
4765
|
extension.init(internalAPI)
|
|
3475
4766
|
}
|
|
@@ -3477,34 +4768,37 @@ const htmx = (function () {
|
|
|
3477
4768
|
}
|
|
3478
4769
|
|
|
3479
4770
|
/**
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
4771
|
+
* removeExtension removes an extension from the htmx registry
|
|
4772
|
+
*
|
|
4773
|
+
* @see https://htmx.org/api/#removeExtension
|
|
4774
|
+
*
|
|
4775
|
+
* @param {string} name
|
|
4776
|
+
*/
|
|
4777
|
+
function removeExtension(name) {
|
|
3485
4778
|
delete extensions[name]
|
|
3486
4779
|
}
|
|
3487
4780
|
|
|
3488
4781
|
/**
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
return extensionsToReturn
|
|
3498
|
-
}
|
|
4782
|
+
* getExtensions searches up the DOM tree to return all extensions that can be applied to a given element
|
|
4783
|
+
*
|
|
4784
|
+
* @param {Element} elt
|
|
4785
|
+
* @param {HtmxExtension[]=} extensionsToReturn
|
|
4786
|
+
* @param {string[]=} extensionsToIgnore
|
|
4787
|
+
* @returns {HtmxExtension[]}
|
|
4788
|
+
*/
|
|
4789
|
+
function getExtensions(elt, extensionsToReturn, extensionsToIgnore) {
|
|
3499
4790
|
if (extensionsToReturn == undefined) {
|
|
3500
4791
|
extensionsToReturn = []
|
|
3501
4792
|
}
|
|
4793
|
+
if (elt == undefined) {
|
|
4794
|
+
return extensionsToReturn
|
|
4795
|
+
}
|
|
3502
4796
|
if (extensionsToIgnore == undefined) {
|
|
3503
4797
|
extensionsToIgnore = []
|
|
3504
4798
|
}
|
|
3505
4799
|
const extensionsForElement = getAttributeValue(elt, 'hx-ext')
|
|
3506
4800
|
if (extensionsForElement) {
|
|
3507
|
-
forEach(extensionsForElement.split(','), function
|
|
4801
|
+
forEach(extensionsForElement.split(','), function(extensionName) {
|
|
3508
4802
|
extensionName = extensionName.replace(/ /g, '')
|
|
3509
4803
|
if (extensionName.slice(0, 7) == 'ignore:') {
|
|
3510
4804
|
extensionsToIgnore.push(extensionName.slice(7))
|
|
@@ -3518,43 +4812,35 @@ const htmx = (function () {
|
|
|
3518
4812
|
}
|
|
3519
4813
|
})
|
|
3520
4814
|
}
|
|
3521
|
-
return getExtensions(parentElt(elt), extensionsToReturn, extensionsToIgnore)
|
|
4815
|
+
return getExtensions(asElement(parentElt(elt)), extensionsToReturn, extensionsToIgnore)
|
|
3522
4816
|
}
|
|
3523
4817
|
|
|
3524
4818
|
//= ===================================================================
|
|
3525
4819
|
// Initialization
|
|
3526
4820
|
//= ===================================================================
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
*/
|
|
3532
|
-
function ready (functionToCall) {
|
|
3533
|
-
// call the function exactly once no matter how many times this is called
|
|
3534
|
-
const callReadyFunction = function () {
|
|
3535
|
-
if (!functionToCall) return
|
|
3536
|
-
functionToCall()
|
|
3537
|
-
functionToCall = null
|
|
3538
|
-
}
|
|
4821
|
+
var isReady = false
|
|
4822
|
+
getDocument().addEventListener('DOMContentLoaded', function() {
|
|
4823
|
+
isReady = true
|
|
4824
|
+
})
|
|
3539
4825
|
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
4826
|
+
/**
|
|
4827
|
+
* Execute a function now if DOMContentLoaded has fired, otherwise listen for it.
|
|
4828
|
+
*
|
|
4829
|
+
* This function uses isReady because there is no reliable way to ask the browser whether
|
|
4830
|
+
* the DOMContentLoaded event has already been fired; there's a gap between DOMContentLoaded
|
|
4831
|
+
* firing and readystate=complete.
|
|
4832
|
+
*/
|
|
4833
|
+
function ready(fn) {
|
|
4834
|
+
// Checking readyState here is a failsafe in case the htmx script tag entered the DOM by
|
|
4835
|
+
// some means other than the initial page load.
|
|
4836
|
+
if (isReady || getDocument().readyState === 'complete') {
|
|
4837
|
+
fn()
|
|
3543
4838
|
} else {
|
|
3544
|
-
|
|
3545
|
-
* the next DOMContentLoaded or readystatechange event
|
|
3546
|
-
*/
|
|
3547
|
-
getDocument().addEventListener('DOMContentLoaded', function () {
|
|
3548
|
-
callReadyFunction()
|
|
3549
|
-
})
|
|
3550
|
-
getDocument().addEventListener('readystatechange', function () {
|
|
3551
|
-
if (getDocument().readyState !== 'complete') return
|
|
3552
|
-
callReadyFunction()
|
|
3553
|
-
})
|
|
4839
|
+
getDocument().addEventListener('DOMContentLoaded', fn)
|
|
3554
4840
|
}
|
|
3555
4841
|
}
|
|
3556
4842
|
|
|
3557
|
-
function insertIndicatorStyles
|
|
4843
|
+
function insertIndicatorStyles() {
|
|
3558
4844
|
if (htmx.config.includeIndicatorStyles !== false) {
|
|
3559
4845
|
getDocument().head.insertAdjacentHTML('beforeend',
|
|
3560
4846
|
'<style>\
|
|
@@ -3565,17 +4851,17 @@ const htmx = (function () {
|
|
|
3565
4851
|
}
|
|
3566
4852
|
}
|
|
3567
4853
|
|
|
3568
|
-
function getMetaConfig
|
|
4854
|
+
function getMetaConfig() {
|
|
4855
|
+
/** @type HTMLMetaElement */
|
|
3569
4856
|
const element = getDocument().querySelector('meta[name="htmx-config"]')
|
|
3570
4857
|
if (element) {
|
|
3571
|
-
// @ts-ignore
|
|
3572
4858
|
return parseJSON(element.content)
|
|
3573
4859
|
} else {
|
|
3574
4860
|
return null
|
|
3575
4861
|
}
|
|
3576
4862
|
}
|
|
3577
4863
|
|
|
3578
|
-
function mergeMetaConfig
|
|
4864
|
+
function mergeMetaConfig() {
|
|
3579
4865
|
const metaConfig = getMetaConfig()
|
|
3580
4866
|
if (metaConfig) {
|
|
3581
4867
|
htmx.config = mergeObjects(htmx.config, metaConfig)
|
|
@@ -3583,7 +4869,7 @@ const htmx = (function () {
|
|
|
3583
4869
|
}
|
|
3584
4870
|
|
|
3585
4871
|
// initialize the document
|
|
3586
|
-
ready(function
|
|
4872
|
+
ready(function() {
|
|
3587
4873
|
mergeMetaConfig()
|
|
3588
4874
|
insertIndicatorStyles()
|
|
3589
4875
|
let body = getDocument().body
|
|
@@ -3591,7 +4877,7 @@ const htmx = (function () {
|
|
|
3591
4877
|
const restoredElts = getDocument().querySelectorAll(
|
|
3592
4878
|
"[hx-trigger='restored'],[data-hx-trigger='restored']"
|
|
3593
4879
|
)
|
|
3594
|
-
body.addEventListener('htmx:abort', function
|
|
4880
|
+
body.addEventListener('htmx:abort', function(evt) {
|
|
3595
4881
|
const target = evt.target
|
|
3596
4882
|
const internalData = getInternalData(target)
|
|
3597
4883
|
if (internalData && internalData.xhr) {
|
|
@@ -3601,10 +4887,10 @@ const htmx = (function () {
|
|
|
3601
4887
|
/** @type {(ev: PopStateEvent) => any} */
|
|
3602
4888
|
const originalPopstate = window.onpopstate ? window.onpopstate.bind(window) : null
|
|
3603
4889
|
/** @type {(ev: PopStateEvent) => any} */
|
|
3604
|
-
window.onpopstate = function
|
|
4890
|
+
window.onpopstate = function(event) {
|
|
3605
4891
|
if (event.state && event.state.htmx) {
|
|
3606
4892
|
restoreHistory()
|
|
3607
|
-
forEach(restoredElts, function
|
|
4893
|
+
forEach(restoredElts, function(elt) {
|
|
3608
4894
|
triggerEvent(elt, 'htmx:restored', {
|
|
3609
4895
|
document: getDocument(),
|
|
3610
4896
|
triggerEvent
|
|
@@ -3616,14 +4902,186 @@ const htmx = (function () {
|
|
|
3616
4902
|
}
|
|
3617
4903
|
}
|
|
3618
4904
|
}
|
|
3619
|
-
setTimeout(function
|
|
4905
|
+
getWindow().setTimeout(function() {
|
|
3620
4906
|
triggerEvent(body, 'htmx:load', {}) // give ready handlers a chance to load up before firing this event
|
|
3621
4907
|
body = null // kill reference for gc
|
|
3622
4908
|
}, 0)
|
|
3623
4909
|
})
|
|
3624
4910
|
|
|
3625
4911
|
return htmx
|
|
3626
|
-
}
|
|
3627
|
-
|
|
4912
|
+
})()
|
|
4913
|
+
|
|
4914
|
+
/** @typedef {'get'|'head'|'post'|'put'|'delete'|'connect'|'options'|'trace'|'patch'} HttpVerb */
|
|
4915
|
+
|
|
4916
|
+
/**
|
|
4917
|
+
* @typedef {Object} SwapOptions
|
|
4918
|
+
* @property {string} [select]
|
|
4919
|
+
* @property {string} [selectOOB]
|
|
4920
|
+
* @property {*} [eventInfo]
|
|
4921
|
+
* @property {string} [anchor]
|
|
4922
|
+
* @property {Element} [contextElement]
|
|
4923
|
+
* @property {swapCallback} [afterSwapCallback]
|
|
4924
|
+
* @property {swapCallback} [afterSettleCallback]
|
|
4925
|
+
*/
|
|
4926
|
+
|
|
4927
|
+
/**
|
|
4928
|
+
* @callback swapCallback
|
|
4929
|
+
*/
|
|
4930
|
+
|
|
4931
|
+
/**
|
|
4932
|
+
* @typedef {'innerHTML' | 'outerHTML' | 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend' | 'delete' | 'none' | string} HtmxSwapStyle
|
|
4933
|
+
*/
|
|
4934
|
+
|
|
4935
|
+
/**
|
|
4936
|
+
* @typedef HtmxSwapSpecification
|
|
4937
|
+
* @property {HtmxSwapStyle} swapStyle
|
|
4938
|
+
* @property {number} swapDelay
|
|
4939
|
+
* @property {number} settleDelay
|
|
4940
|
+
* @property {boolean} [transition]
|
|
4941
|
+
* @property {boolean} [ignoreTitle]
|
|
4942
|
+
* @property {string} [head]
|
|
4943
|
+
* @property {'top' | 'bottom'} [scroll]
|
|
4944
|
+
* @property {string} [scrollTarget]
|
|
4945
|
+
* @property {string} [show]
|
|
4946
|
+
* @property {string} [showTarget]
|
|
4947
|
+
* @property {boolean} [focusScroll]
|
|
4948
|
+
*/
|
|
4949
|
+
|
|
4950
|
+
/**
|
|
4951
|
+
* @typedef {((this:Node, evt:Event) => boolean) & {source: string}} ConditionalFunction
|
|
4952
|
+
*/
|
|
4953
|
+
|
|
4954
|
+
/**
|
|
4955
|
+
* @typedef {Object} HtmxTriggerSpecification
|
|
4956
|
+
* @property {string} trigger
|
|
4957
|
+
* @property {number} [pollInterval]
|
|
4958
|
+
* @property {ConditionalFunction} [eventFilter]
|
|
4959
|
+
* @property {boolean} [changed]
|
|
4960
|
+
* @property {boolean} [once]
|
|
4961
|
+
* @property {boolean} [consume]
|
|
4962
|
+
* @property {number} [delay]
|
|
4963
|
+
* @property {string} [from]
|
|
4964
|
+
* @property {string} [target]
|
|
4965
|
+
* @property {number} [throttle]
|
|
4966
|
+
* @property {string} [queue]
|
|
4967
|
+
* @property {string} [root]
|
|
4968
|
+
* @property {string} [threshold]
|
|
4969
|
+
*/
|
|
4970
|
+
|
|
4971
|
+
/**
|
|
4972
|
+
* @typedef {{elt: Element, message: string, validity: ValidityState}} HtmxElementValidationError
|
|
4973
|
+
*/
|
|
4974
|
+
|
|
4975
|
+
/**
|
|
4976
|
+
* @typedef {Record<string, string>} HtmxHeaderSpecification
|
|
4977
|
+
* @property {'true'} HX-Request
|
|
4978
|
+
* @property {string|null} HX-Trigger
|
|
4979
|
+
* @property {string|null} HX-Trigger-Name
|
|
4980
|
+
* @property {string|null} HX-Target
|
|
4981
|
+
* @property {string} HX-Current-URL
|
|
4982
|
+
* @property {string} [HX-Prompt]
|
|
4983
|
+
* @property {'true'} [HX-Boosted]
|
|
4984
|
+
* @property {string} [Content-Type]
|
|
4985
|
+
* @property {'true'} [HX-History-Restore-Request]
|
|
4986
|
+
*/
|
|
4987
|
+
|
|
4988
|
+
/** @typedef HtmxAjaxHelperContext
|
|
4989
|
+
* @property {Element|string} [source]
|
|
4990
|
+
* @property {Event} [event]
|
|
4991
|
+
* @property {HtmxAjaxHandler} [handler]
|
|
4992
|
+
* @property {Element|string} target
|
|
4993
|
+
* @property {HtmxSwapStyle} [swap]
|
|
4994
|
+
* @property {Object|FormData} [values]
|
|
4995
|
+
* @property {Record<string,string>} [headers]
|
|
4996
|
+
* @property {string} [select]
|
|
4997
|
+
*/
|
|
4998
|
+
|
|
4999
|
+
/**
|
|
5000
|
+
* @typedef {Object} HtmxRequestConfig
|
|
5001
|
+
* @property {boolean} boosted
|
|
5002
|
+
* @property {boolean} useUrlParams
|
|
5003
|
+
* @property {FormData} formData
|
|
5004
|
+
* @property {Object} parameters formData proxy
|
|
5005
|
+
* @property {FormData} unfilteredFormData
|
|
5006
|
+
* @property {Object} unfilteredParameters unfilteredFormData proxy
|
|
5007
|
+
* @property {HtmxHeaderSpecification} headers
|
|
5008
|
+
* @property {Element} target
|
|
5009
|
+
* @property {HttpVerb} verb
|
|
5010
|
+
* @property {HtmxElementValidationError[]} errors
|
|
5011
|
+
* @property {boolean} withCredentials
|
|
5012
|
+
* @property {number} timeout
|
|
5013
|
+
* @property {string} path
|
|
5014
|
+
* @property {Event} triggeringEvent
|
|
5015
|
+
*/
|
|
5016
|
+
|
|
5017
|
+
/**
|
|
5018
|
+
* @typedef {Object} HtmxResponseInfo
|
|
5019
|
+
* @property {XMLHttpRequest} xhr
|
|
5020
|
+
* @property {Element} target
|
|
5021
|
+
* @property {HtmxRequestConfig} requestConfig
|
|
5022
|
+
* @property {HtmxAjaxEtc} etc
|
|
5023
|
+
* @property {boolean} boosted
|
|
5024
|
+
* @property {string} select
|
|
5025
|
+
* @property {{requestPath: string, finalRequestPath: string, responsePath: string|null, anchor: string}} pathInfo
|
|
5026
|
+
* @property {boolean} [failed]
|
|
5027
|
+
* @property {boolean} [successful]
|
|
5028
|
+
*/
|
|
5029
|
+
|
|
5030
|
+
/**
|
|
5031
|
+
* @typedef {Object} HtmxAjaxEtc
|
|
5032
|
+
* @property {boolean} [returnPromise]
|
|
5033
|
+
* @property {HtmxAjaxHandler} [handler]
|
|
5034
|
+
* @property {string} [select]
|
|
5035
|
+
* @property {Element} [targetOverride]
|
|
5036
|
+
* @property {HtmxSwapStyle} [swapOverride]
|
|
5037
|
+
* @property {Record<string,string>} [headers]
|
|
5038
|
+
* @property {Object|FormData} [values]
|
|
5039
|
+
* @property {boolean} [credentials]
|
|
5040
|
+
* @property {number} [timeout]
|
|
5041
|
+
*/
|
|
5042
|
+
|
|
5043
|
+
/**
|
|
5044
|
+
* @typedef {Object} HtmxResponseHandlingConfig
|
|
5045
|
+
* @property {string} [code]
|
|
5046
|
+
* @property {boolean} swap
|
|
5047
|
+
* @property {boolean} [error]
|
|
5048
|
+
* @property {boolean} [ignoreTitle]
|
|
5049
|
+
* @property {string} [select]
|
|
5050
|
+
* @property {string} [target]
|
|
5051
|
+
* @property {string} [swapOverride]
|
|
5052
|
+
* @property {string} [event]
|
|
5053
|
+
*/
|
|
5054
|
+
|
|
5055
|
+
/**
|
|
5056
|
+
* @typedef {HtmxResponseInfo & {shouldSwap: boolean, serverResponse: any, isError: boolean, ignoreTitle: boolean, selectOverride:string}} HtmxBeforeSwapDetails
|
|
5057
|
+
*/
|
|
5058
|
+
|
|
5059
|
+
/**
|
|
5060
|
+
* @callback HtmxAjaxHandler
|
|
5061
|
+
* @param {Element} elt
|
|
5062
|
+
* @param {HtmxResponseInfo} responseInfo
|
|
5063
|
+
*/
|
|
5064
|
+
|
|
5065
|
+
/**
|
|
5066
|
+
* @typedef {(() => void)} HtmxSettleTask
|
|
5067
|
+
*/
|
|
5068
|
+
|
|
5069
|
+
/**
|
|
5070
|
+
* @typedef {Object} HtmxSettleInfo
|
|
5071
|
+
* @property {HtmxSettleTask[]} tasks
|
|
5072
|
+
* @property {Element[]} elts
|
|
5073
|
+
* @property {string} [title]
|
|
5074
|
+
*/
|
|
5075
|
+
|
|
5076
|
+
/**
|
|
5077
|
+
* @typedef {Object} HtmxExtension
|
|
5078
|
+
* @see https://htmx.org/extensions/#defining
|
|
5079
|
+
* @property {(api: any) => void} init
|
|
5080
|
+
* @property {(name: string, event: Event|CustomEvent) => boolean} onEvent
|
|
5081
|
+
* @property {(text: string, xhr: XMLHttpRequest, elt: Element) => string} transformResponse
|
|
5082
|
+
* @property {(swapStyle: HtmxSwapStyle) => boolean} isInlineSwap
|
|
5083
|
+
* @property {(swapStyle: HtmxSwapStyle, target: Element, fragment: Node, settleInfo: HtmxSettleInfo) => boolean} handleSwap
|
|
5084
|
+
* @property {(xhr: XMLHttpRequest, parameters: FormData, elt: Element) => *|string|null} encodeParameters
|
|
5085
|
+
*/
|
|
3628
5086
|
return htmx
|
|
3629
5087
|
})
|