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