htmx.org 2.0.2 → 2.0.4

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/dist/htmx.esm.js CHANGED
@@ -277,7 +277,7 @@ var htmx = (function() {
277
277
  parseInterval: null,
278
278
  /** @type {typeof internalEval} */
279
279
  _: null,
280
- version: '2.0.2'
280
+ version: '2.0.4'
281
281
  }
282
282
  // Tsc madness part 2
283
283
  htmx.onLoad = onLoadHelper
@@ -337,22 +337,10 @@ var htmx = (function() {
337
337
  return '[hx-' + verb + '], [data-hx-' + verb + ']'
338
338
  }).join(', ')
339
339
 
340
- const HEAD_TAG_REGEX = makeTagRegEx('head')
341
-
342
340
  //= ===================================================================
343
341
  // Utilities
344
342
  //= ===================================================================
345
343
 
346
- /**
347
- * @param {string} tag
348
- * @param {boolean} global
349
- * @returns {RegExp}
350
- */
351
- function makeTagRegEx(tag, global = false) {
352
- return new RegExp(`<${tag}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${tag}>`,
353
- global ? 'gim' : 'im')
354
- }
355
-
356
344
  /**
357
345
  * Parses an interval string consistent with the way htmx does. Useful for plugins that have timing-related attributes.
358
346
  *
@@ -595,7 +583,7 @@ var htmx = (function() {
595
583
  */
596
584
  function makeFragment(response) {
597
585
  // strip head tag to determine shape of response we are dealing with
598
- const responseWithNoHead = response.replace(HEAD_TAG_REGEX, '')
586
+ const responseWithNoHead = response.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i, '')
599
587
  const startTag = getStartTag(responseWithNoHead)
600
588
  /** @type DocumentFragmentWithTitle */
601
589
  let fragment
@@ -695,7 +683,7 @@ var htmx = (function() {
695
683
  * @property {boolean} [triggeredOnce]
696
684
  * @property {number} [delayed]
697
685
  * @property {number|null} [throttle]
698
- * @property {string} [lastValue]
686
+ * @property {WeakMap<HtmxTriggerSpecification,WeakMap<EventTarget,string>>} [lastValue]
699
687
  * @property {boolean} [loaded]
700
688
  * @property {string} [path]
701
689
  * @property {string} [verb]
@@ -705,6 +693,7 @@ var htmx = (function() {
705
693
  * @property {XMLHttpRequest} [xhr]
706
694
  * @property {(() => void)[]} [queuedRequests]
707
695
  * @property {boolean} [abortable]
696
+ * @property {boolean} [firstInitCompleted]
708
697
  *
709
698
  * Event data
710
699
  * @property {HtmxTriggerSpecification} [triggerSpec]
@@ -766,17 +755,14 @@ var htmx = (function() {
766
755
  }
767
756
 
768
757
  /**
758
+ * Checks whether the element is in the document (includes shadow roots).
759
+ * This function this is a slight misnomer; it will return true even for elements in the head.
760
+ *
769
761
  * @param {Node} elt
770
762
  * @returns {boolean}
771
763
  */
772
764
  function bodyContains(elt) {
773
- // IE Fix
774
- const rootNode = elt.getRootNode && elt.getRootNode()
775
- if (rootNode && rootNode instanceof window.ShadowRoot) {
776
- return getDocument().body.contains(rootNode.host)
777
- } else {
778
- return getDocument().body.contains(elt)
779
- }
765
+ return elt.getRootNode({ composed: true }) === document
780
766
  }
781
767
 
782
768
  /**
@@ -1140,32 +1126,77 @@ var htmx = (function() {
1140
1126
  * @returns {(Node|Window)[]}
1141
1127
  */
1142
1128
  function querySelectorAllExt(elt, selector, global) {
1143
- elt = resolveTarget(elt)
1144
- if (selector.indexOf('closest ') === 0) {
1145
- return [closest(asElement(elt), normalizeSelector(selector.substr(8)))]
1146
- } else if (selector.indexOf('find ') === 0) {
1147
- return [find(asParentNode(elt), normalizeSelector(selector.substr(5)))]
1148
- } else if (selector === 'next') {
1149
- return [asElement(elt).nextElementSibling]
1150
- } else if (selector.indexOf('next ') === 0) {
1151
- return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)), !!global)]
1152
- } else if (selector === 'previous') {
1153
- return [asElement(elt).previousElementSibling]
1154
- } else if (selector.indexOf('previous ') === 0) {
1155
- return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)), !!global)]
1156
- } else if (selector === 'document') {
1157
- return [document]
1158
- } else if (selector === 'window') {
1159
- return [window]
1160
- } else if (selector === 'body') {
1161
- return [document.body]
1162
- } else if (selector === 'root') {
1163
- return [getRootNode(elt, !!global)]
1164
- } else if (selector.indexOf('global ') === 0) {
1129
+ if (selector.indexOf('global ') === 0) {
1165
1130
  return querySelectorAllExt(elt, selector.slice(7), true)
1166
- } else {
1167
- return toArray(asParentNode(getRootNode(elt, !!global)).querySelectorAll(normalizeSelector(selector)))
1168
1131
  }
1132
+
1133
+ elt = resolveTarget(elt)
1134
+
1135
+ const parts = []
1136
+ {
1137
+ let chevronsCount = 0
1138
+ let offset = 0
1139
+ for (let i = 0; i < selector.length; i++) {
1140
+ const char = selector[i]
1141
+ if (char === ',' && chevronsCount === 0) {
1142
+ parts.push(selector.substring(offset, i))
1143
+ offset = i + 1
1144
+ continue
1145
+ }
1146
+ if (char === '<') {
1147
+ chevronsCount++
1148
+ } else if (char === '/' && i < selector.length - 1 && selector[i + 1] === '>') {
1149
+ chevronsCount--
1150
+ }
1151
+ }
1152
+ if (offset < selector.length) {
1153
+ parts.push(selector.substring(offset))
1154
+ }
1155
+ }
1156
+
1157
+ const result = []
1158
+ const unprocessedParts = []
1159
+ while (parts.length > 0) {
1160
+ const selector = normalizeSelector(parts.shift())
1161
+ let item
1162
+ if (selector.indexOf('closest ') === 0) {
1163
+ item = closest(asElement(elt), normalizeSelector(selector.substr(8)))
1164
+ } else if (selector.indexOf('find ') === 0) {
1165
+ item = find(asParentNode(elt), normalizeSelector(selector.substr(5)))
1166
+ } else if (selector === 'next' || selector === 'nextElementSibling') {
1167
+ item = asElement(elt).nextElementSibling
1168
+ } else if (selector.indexOf('next ') === 0) {
1169
+ item = scanForwardQuery(elt, normalizeSelector(selector.substr(5)), !!global)
1170
+ } else if (selector === 'previous' || selector === 'previousElementSibling') {
1171
+ item = asElement(elt).previousElementSibling
1172
+ } else if (selector.indexOf('previous ') === 0) {
1173
+ item = scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)), !!global)
1174
+ } else if (selector === 'document') {
1175
+ item = document
1176
+ } else if (selector === 'window') {
1177
+ item = window
1178
+ } else if (selector === 'body') {
1179
+ item = document.body
1180
+ } else if (selector === 'root') {
1181
+ item = getRootNode(elt, !!global)
1182
+ } else if (selector === 'host') {
1183
+ item = (/** @type ShadowRoot */(elt.getRootNode())).host
1184
+ } else {
1185
+ unprocessedParts.push(selector)
1186
+ }
1187
+
1188
+ if (item) {
1189
+ result.push(item)
1190
+ }
1191
+ }
1192
+
1193
+ if (unprocessedParts.length > 0) {
1194
+ const standardSelector = unprocessedParts.join(',')
1195
+ const rootNode = asParentNode(getRootNode(elt, !!global))
1196
+ result.push(...toArray(rootNode.querySelectorAll(standardSelector)))
1197
+ }
1198
+
1199
+ return result
1169
1200
  }
1170
1201
 
1171
1202
  /**
@@ -1236,26 +1267,30 @@ var htmx = (function() {
1236
1267
  * @property {EventTarget} target
1237
1268
  * @property {AnyEventName} event
1238
1269
  * @property {EventListener} listener
1270
+ * @property {Object|boolean} options
1239
1271
  */
1240
1272
 
1241
1273
  /**
1242
1274
  * @param {EventTarget|AnyEventName} arg1
1243
1275
  * @param {AnyEventName|EventListener} arg2
1244
- * @param {EventListener} [arg3]
1276
+ * @param {EventListener|Object|boolean} [arg3]
1277
+ * @param {Object|boolean} [arg4]
1245
1278
  * @returns {EventArgs}
1246
1279
  */
1247
- function processEventArgs(arg1, arg2, arg3) {
1280
+ function processEventArgs(arg1, arg2, arg3, arg4) {
1248
1281
  if (isFunction(arg2)) {
1249
1282
  return {
1250
1283
  target: getDocument().body,
1251
1284
  event: asString(arg1),
1252
- listener: arg2
1285
+ listener: arg2,
1286
+ options: arg3
1253
1287
  }
1254
1288
  } else {
1255
1289
  return {
1256
1290
  target: resolveTarget(arg1),
1257
1291
  event: asString(arg2),
1258
- listener: arg3
1292
+ listener: arg3,
1293
+ options: arg4
1259
1294
  }
1260
1295
  }
1261
1296
  }
@@ -1267,13 +1302,14 @@ var htmx = (function() {
1267
1302
  *
1268
1303
  * @param {EventTarget|string} arg1 the element to add the listener to | the event name to add the listener for
1269
1304
  * @param {string|EventListener} arg2 the event name to add the listener for | the listener to add
1270
- * @param {EventListener} [arg3] the listener to add
1305
+ * @param {EventListener|Object|boolean} [arg3] the listener to add | options to add
1306
+ * @param {Object|boolean} [arg4] options to add
1271
1307
  * @returns {EventListener}
1272
1308
  */
1273
- function addEventListenerImpl(arg1, arg2, arg3) {
1309
+ function addEventListenerImpl(arg1, arg2, arg3, arg4) {
1274
1310
  ready(function() {
1275
- const eventArgs = processEventArgs(arg1, arg2, arg3)
1276
- eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener)
1311
+ const eventArgs = processEventArgs(arg1, arg2, arg3, arg4)
1312
+ eventArgs.target.addEventListener(eventArgs.event, eventArgs.listener, eventArgs.options)
1277
1313
  })
1278
1314
  const b = isFunction(arg2)
1279
1315
  return b ? arg2 : arg3
@@ -1412,22 +1448,26 @@ var htmx = (function() {
1412
1448
  * @param {string} oobValue
1413
1449
  * @param {Element} oobElement
1414
1450
  * @param {HtmxSettleInfo} settleInfo
1451
+ * @param {Node|Document} [rootNode]
1415
1452
  * @returns
1416
1453
  */
1417
- function oobSwap(oobValue, oobElement, settleInfo) {
1454
+ function oobSwap(oobValue, oobElement, settleInfo, rootNode) {
1455
+ rootNode = rootNode || getDocument()
1418
1456
  let selector = '#' + getRawAttribute(oobElement, 'id')
1419
1457
  /** @type HtmxSwapStyle */
1420
1458
  let swapStyle = 'outerHTML'
1421
1459
  if (oobValue === 'true') {
1422
1460
  // do nothing
1423
1461
  } else if (oobValue.indexOf(':') > 0) {
1424
- swapStyle = oobValue.substr(0, oobValue.indexOf(':'))
1425
- selector = oobValue.substr(oobValue.indexOf(':') + 1, oobValue.length)
1462
+ swapStyle = oobValue.substring(0, oobValue.indexOf(':'))
1463
+ selector = oobValue.substring(oobValue.indexOf(':') + 1)
1426
1464
  } else {
1427
1465
  swapStyle = oobValue
1428
1466
  }
1467
+ oobElement.removeAttribute('hx-swap-oob')
1468
+ oobElement.removeAttribute('data-hx-swap-oob')
1429
1469
 
1430
- const targets = getDocument().querySelectorAll(selector)
1470
+ const targets = querySelectorAllExt(rootNode, selector, false)
1431
1471
  if (targets) {
1432
1472
  forEach(
1433
1473
  targets,
@@ -1445,7 +1485,9 @@ var htmx = (function() {
1445
1485
 
1446
1486
  target = beforeSwapDetails.target // allow re-targeting
1447
1487
  if (beforeSwapDetails.shouldSwap) {
1488
+ handlePreservedElements(fragment)
1448
1489
  swapWithStyle(swapStyle, target, target, fragment, settleInfo)
1490
+ restorePreservedElements()
1449
1491
  }
1450
1492
  forEach(settleInfo.elts, function(elt) {
1451
1493
  triggerEvent(elt, 'htmx:oobAfterSwap', beforeSwapDetails)
@@ -1460,15 +1502,39 @@ var htmx = (function() {
1460
1502
  return oobValue
1461
1503
  }
1462
1504
 
1505
+ function restorePreservedElements() {
1506
+ const pantry = find('#--htmx-preserve-pantry--')
1507
+ if (pantry) {
1508
+ for (const preservedElt of [...pantry.children]) {
1509
+ const existingElement = find('#' + preservedElt.id)
1510
+ // @ts-ignore - use proposed moveBefore feature
1511
+ existingElement.parentNode.moveBefore(preservedElt, existingElement)
1512
+ existingElement.remove()
1513
+ }
1514
+ pantry.remove()
1515
+ }
1516
+ }
1517
+
1463
1518
  /**
1464
- * @param {DocumentFragment} fragment
1519
+ * @param {DocumentFragment|ParentNode} fragment
1465
1520
  */
1466
1521
  function handlePreservedElements(fragment) {
1467
1522
  forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function(preservedElt) {
1468
1523
  const id = getAttributeValue(preservedElt, 'id')
1469
- const oldElt = getDocument().getElementById(id)
1470
- if (oldElt != null) {
1471
- preservedElt.parentNode.replaceChild(oldElt, preservedElt)
1524
+ const existingElement = getDocument().getElementById(id)
1525
+ if (existingElement != null) {
1526
+ if (preservedElt.moveBefore) { // if the moveBefore API exists, use it
1527
+ // get or create a storage spot for stuff
1528
+ let pantry = find('#--htmx-preserve-pantry--')
1529
+ if (pantry == null) {
1530
+ getDocument().body.insertAdjacentHTML('afterend', "<div id='--htmx-preserve-pantry--'></div>")
1531
+ pantry = find('#--htmx-preserve-pantry--')
1532
+ }
1533
+ // @ts-ignore - use proposed moveBefore feature
1534
+ pantry.moveBefore(existingElement, null)
1535
+ } else {
1536
+ preservedElt.parentNode.replaceChild(existingElement, preservedElt)
1537
+ }
1472
1538
  }
1473
1539
  })
1474
1540
  }
@@ -1603,7 +1669,7 @@ var htmx = (function() {
1603
1669
  })
1604
1670
  }
1605
1671
  deInitOnHandlers(element)
1606
- forEach(Object.keys(internalData), function(key) { delete internalData[key] })
1672
+ forEach(Object.keys(internalData), function(key) { if (key !== 'firstInitCompleted') delete internalData[key] })
1607
1673
  }
1608
1674
 
1609
1675
  /**
@@ -1632,9 +1698,13 @@ var htmx = (function() {
1632
1698
  /** @type {Node} */
1633
1699
  let newElt
1634
1700
  const eltBeforeNewContent = target.previousSibling
1635
- insertNodesBefore(parentElt(target), target, fragment, settleInfo)
1701
+ const parentNode = parentElt(target)
1702
+ if (!parentNode) { // when parent node disappears, we can't do anything
1703
+ return
1704
+ }
1705
+ insertNodesBefore(parentNode, target, fragment, settleInfo)
1636
1706
  if (eltBeforeNewContent == null) {
1637
- newElt = parentElt(target).firstChild
1707
+ newElt = parentNode.firstChild
1638
1708
  } else {
1639
1709
  newElt = eltBeforeNewContent.nextSibling
1640
1710
  }
@@ -1696,7 +1766,10 @@ var htmx = (function() {
1696
1766
  */
1697
1767
  function swapDelete(target) {
1698
1768
  cleanUpElement(target)
1699
- return parentElt(target).removeChild(target)
1769
+ const parent = parentElt(target)
1770
+ if (parent) {
1771
+ return parent.removeChild(target)
1772
+ }
1700
1773
  }
1701
1774
 
1702
1775
  /**
@@ -1779,14 +1852,15 @@ var htmx = (function() {
1779
1852
  /**
1780
1853
  * @param {DocumentFragment} fragment
1781
1854
  * @param {HtmxSettleInfo} settleInfo
1855
+ * @param {Node|Document} [rootNode]
1782
1856
  */
1783
- function findAndSwapOobElements(fragment, settleInfo) {
1857
+ function findAndSwapOobElements(fragment, settleInfo, rootNode) {
1784
1858
  var oobElts = findAll(fragment, '[hx-swap-oob], [data-hx-swap-oob]')
1785
1859
  forEach(oobElts, function(oobElement) {
1786
1860
  if (htmx.config.allowNestedOobSwaps || oobElement.parentElement === null) {
1787
1861
  const oobValue = getAttributeValue(oobElement, 'hx-swap-oob')
1788
1862
  if (oobValue != null) {
1789
- oobSwap(oobValue, oobElement, settleInfo)
1863
+ oobSwap(oobValue, oobElement, settleInfo, rootNode)
1790
1864
  }
1791
1865
  } else {
1792
1866
  oobElement.removeAttribute('hx-swap-oob')
@@ -1810,6 +1884,7 @@ var htmx = (function() {
1810
1884
  }
1811
1885
 
1812
1886
  target = resolveTarget(target)
1887
+ const rootNode = swapOptions.contextElement ? getRootNode(swapOptions.contextElement, false) : getDocument()
1813
1888
 
1814
1889
  // preserve focus and selection
1815
1890
  const activeElt = document.activeElement
@@ -1848,14 +1923,14 @@ var htmx = (function() {
1848
1923
  const oobValue = oobSelectValue[1] || 'true'
1849
1924
  const oobElement = fragment.querySelector('#' + id)
1850
1925
  if (oobElement) {
1851
- oobSwap(oobValue, oobElement, settleInfo)
1926
+ oobSwap(oobValue, oobElement, settleInfo, rootNode)
1852
1927
  }
1853
1928
  }
1854
1929
  }
1855
1930
  // oob swaps
1856
- findAndSwapOobElements(fragment, settleInfo)
1931
+ findAndSwapOobElements(fragment, settleInfo, rootNode)
1857
1932
  forEach(findAll(fragment, 'template'), /** @param {HTMLTemplateElement} template */function(template) {
1858
- if (findAndSwapOobElements(template.content, settleInfo)) {
1933
+ if (template.content && findAndSwapOobElements(template.content, settleInfo, rootNode)) {
1859
1934
  // Avoid polluting the DOM with empty templates that were only used to encapsulate oob swap
1860
1935
  template.remove()
1861
1936
  }
@@ -1871,6 +1946,7 @@ var htmx = (function() {
1871
1946
  }
1872
1947
  handlePreservedElements(fragment)
1873
1948
  swapWithStyle(swapSpec.swapStyle, swapOptions.contextElement, target, fragment, settleInfo)
1949
+ restorePreservedElements()
1874
1950
  }
1875
1951
 
1876
1952
  // apply saved focus and selection information to swapped content
@@ -1993,7 +2069,7 @@ var htmx = (function() {
1993
2069
  while (SYMBOL_CONT.exec(str.charAt(position + 1))) {
1994
2070
  position++
1995
2071
  }
1996
- tokens.push(str.substr(startPosition, position - startPosition + 1))
2072
+ tokens.push(str.substring(startPosition, position + 1))
1997
2073
  } else if (STRINGISH_START.indexOf(str.charAt(position)) !== -1) {
1998
2074
  const startChar = str.charAt(position)
1999
2075
  var startPosition = position
@@ -2004,7 +2080,7 @@ var htmx = (function() {
2004
2080
  }
2005
2081
  position++
2006
2082
  }
2007
- tokens.push(str.substr(startPosition, position - startPosition + 1))
2083
+ tokens.push(str.substring(startPosition, position + 1))
2008
2084
  } else {
2009
2085
  const symbol = str.charAt(position)
2010
2086
  tokens.push(symbol)
@@ -2141,8 +2217,8 @@ var htmx = (function() {
2141
2217
  if (eventFilter) {
2142
2218
  triggerSpec.eventFilter = eventFilter
2143
2219
  }
2220
+ consumeUntil(tokens, NOT_WHITESPACE)
2144
2221
  while (tokens.length > 0 && tokens[0] !== ',') {
2145
- consumeUntil(tokens, NOT_WHITESPACE)
2146
2222
  const token = tokens.shift()
2147
2223
  if (token === 'changed') {
2148
2224
  triggerSpec.changed = true
@@ -2187,6 +2263,7 @@ var htmx = (function() {
2187
2263
  } else {
2188
2264
  triggerErrorEvent(elt, 'htmx:syntax:error', { token: tokens.shift() })
2189
2265
  }
2266
+ consumeUntil(tokens, NOT_WHITESPACE)
2190
2267
  }
2191
2268
  triggerSpecs.push(triggerSpec)
2192
2269
  }
@@ -2281,14 +2358,20 @@ var htmx = (function() {
2281
2358
  nodeData.boosted = true
2282
2359
  let verb, path
2283
2360
  if (elt.tagName === 'A') {
2284
- verb = 'get'
2361
+ verb = (/** @type HttpVerb */('get'))
2285
2362
  path = getRawAttribute(elt, 'href')
2286
2363
  } else {
2287
2364
  const rawAttribute = getRawAttribute(elt, 'method')
2288
- verb = rawAttribute ? rawAttribute.toLowerCase() : 'get'
2289
- if (verb === 'get') {
2290
- }
2365
+ verb = (/** @type HttpVerb */(rawAttribute ? rawAttribute.toLowerCase() : 'get'))
2291
2366
  path = getRawAttribute(elt, 'action')
2367
+ if (path == null || path === '') {
2368
+ // if there is no action attribute on the form set path to current href before the
2369
+ // following logic to properly clear parameters on a GET (not on a POST!)
2370
+ path = getDocument().location.href
2371
+ }
2372
+ if (verb === 'get' && path.includes('?')) {
2373
+ path = path.replace(/\?[^#]+/, '')
2374
+ }
2292
2375
  }
2293
2376
  triggerSpecs.forEach(function(triggerSpec) {
2294
2377
  addEventListener(elt, function(node, evt) {
@@ -2317,7 +2400,8 @@ var htmx = (function() {
2317
2400
  if (elt.tagName === 'FORM') {
2318
2401
  return true
2319
2402
  }
2320
- if (matches(elt, 'input[type="submit"], button') && closest(elt, 'form') !== null) {
2403
+ if (matches(elt, 'input[type="submit"], button') &&
2404
+ (matches(elt, '[form]') || closest(elt, 'form') !== null)) {
2321
2405
  return true
2322
2406
  }
2323
2407
  if (elt instanceof HTMLAnchorElement && elt.href &&
@@ -2377,10 +2461,15 @@ var htmx = (function() {
2377
2461
  }
2378
2462
  // store the initial values of the elements, so we can tell if they change
2379
2463
  if (triggerSpec.changed) {
2464
+ if (!('lastValue' in elementData)) {
2465
+ elementData.lastValue = new WeakMap()
2466
+ }
2380
2467
  eltsToListenOn.forEach(function(eltToListenOn) {
2381
- const eltToListenOnData = getInternalData(eltToListenOn)
2468
+ if (!elementData.lastValue.has(triggerSpec)) {
2469
+ elementData.lastValue.set(triggerSpec, new WeakMap())
2470
+ }
2382
2471
  // @ts-ignore value will be undefined for non-input elements, which is fine
2383
- eltToListenOnData.lastValue = eltToListenOn.value
2472
+ elementData.lastValue.get(triggerSpec).set(eltToListenOn, eltToListenOn.value)
2384
2473
  })
2385
2474
  }
2386
2475
  forEach(eltsToListenOn, function(eltToListenOn) {
@@ -2422,13 +2511,14 @@ var htmx = (function() {
2422
2511
  }
2423
2512
  }
2424
2513
  if (triggerSpec.changed) {
2425
- const eltToListenOnData = getInternalData(eltToListenOn)
2514
+ const node = event.target
2426
2515
  // @ts-ignore value will be undefined for non-input elements, which is fine
2427
- const value = eltToListenOn.value
2428
- if (eltToListenOnData.lastValue === value) {
2516
+ const value = node.value
2517
+ const lastValue = elementData.lastValue.get(triggerSpec)
2518
+ if (lastValue.has(node) && lastValue.get(node) === value) {
2429
2519
  return
2430
2520
  }
2431
- eltToListenOnData.lastValue = value
2521
+ lastValue.set(node, value)
2432
2522
  }
2433
2523
  if (elementData.delayed) {
2434
2524
  clearTimeout(elementData.delayed)
@@ -2476,6 +2566,7 @@ var htmx = (function() {
2476
2566
  windowIsScrolling = true
2477
2567
  }
2478
2568
  window.addEventListener('scroll', scrollHandler)
2569
+ window.addEventListener('resize', scrollHandler)
2479
2570
  setInterval(function() {
2480
2571
  if (windowIsScrolling) {
2481
2572
  windowIsScrolling = false
@@ -2515,6 +2606,7 @@ var htmx = (function() {
2515
2606
  const load = function() {
2516
2607
  if (!nodeData.loaded) {
2517
2608
  nodeData.loaded = true
2609
+ triggerEvent(elt, 'htmx:trigger')
2518
2610
  handler(elt)
2519
2611
  }
2520
2612
  }
@@ -2590,7 +2682,7 @@ var htmx = (function() {
2590
2682
  }, observerOptions)
2591
2683
  observer.observe(asElement(elt))
2592
2684
  addEventListener(asElement(elt), handler, nodeData, triggerSpec)
2593
- } else if (triggerSpec.trigger === 'load') {
2685
+ } else if (!nodeData.firstInitCompleted && triggerSpec.trigger === 'load') {
2594
2686
  if (!maybeFilterEvent(triggerSpec, elt, makeEvent('load', { elt }))) {
2595
2687
  loadImmediately(asElement(elt), handler, nodeData, triggerSpec.delay)
2596
2688
  }
@@ -2797,20 +2889,15 @@ var htmx = (function() {
2797
2889
  return
2798
2890
  }
2799
2891
  const nodeData = getInternalData(elt)
2800
- if (nodeData.initHash !== attributeHash(elt)) {
2892
+ const attrHash = attributeHash(elt)
2893
+ if (nodeData.initHash !== attrHash) {
2801
2894
  // clean up any previously processed info
2802
2895
  deInitNode(elt)
2803
2896
 
2804
- nodeData.initHash = attributeHash(elt)
2897
+ nodeData.initHash = attrHash
2805
2898
 
2806
2899
  triggerEvent(elt, 'htmx:beforeProcessNode')
2807
2900
 
2808
- // @ts-ignore value will be undefined for non-input elements, which is fine
2809
- if (elt.value) {
2810
- // @ts-ignore
2811
- nodeData.lastValue = elt.value
2812
- }
2813
-
2814
2901
  const triggerSpecs = getTriggerSpecs(elt)
2815
2902
  const hasExplicitHttpAction = processVerbs(elt, nodeData, triggerSpecs)
2816
2903
 
@@ -2832,6 +2919,7 @@ var htmx = (function() {
2832
2919
  initButtonTracking(elt)
2833
2920
  }
2834
2921
 
2922
+ nodeData.firstInitCompleted = true
2835
2923
  triggerEvent(elt, 'htmx:afterProcessNode')
2836
2924
  }
2837
2925
  }
@@ -3153,7 +3241,9 @@ var htmx = (function() {
3153
3241
  const settleInfo = makeSettleInfo(historyElement)
3154
3242
  handleTitle(fragment.title)
3155
3243
 
3244
+ handlePreservedElements(fragment)
3156
3245
  swapInnerHTML(historyElement, content, settleInfo)
3246
+ restorePreservedElements()
3157
3247
  settleImmediately(settleInfo.tasks)
3158
3248
  currentPathForHistory = path
3159
3249
  triggerEvent(getDocument().body, 'htmx:historyRestore', { path, cacheMiss: true, serverResponse: this.response })
@@ -3175,8 +3265,10 @@ var htmx = (function() {
3175
3265
  const fragment = makeFragment(cached.content)
3176
3266
  const historyElement = getHistoryElement()
3177
3267
  const settleInfo = makeSettleInfo(historyElement)
3178
- handleTitle(fragment.title)
3268
+ handleTitle(cached.title)
3269
+ handlePreservedElements(fragment)
3179
3270
  swapInnerHTML(historyElement, fragment, settleInfo)
3271
+ restorePreservedElements()
3180
3272
  settleImmediately(settleInfo.tasks)
3181
3273
  getWindow().setTimeout(function() {
3182
3274
  window.scrollTo(0, cached.scroll)
@@ -3234,16 +3326,18 @@ var htmx = (function() {
3234
3326
  * @param {Element[]} disabled
3235
3327
  */
3236
3328
  function removeRequestIndicators(indicators, disabled) {
3329
+ forEach(indicators.concat(disabled), function(ele) {
3330
+ const internalData = getInternalData(ele)
3331
+ internalData.requestCount = (internalData.requestCount || 1) - 1
3332
+ })
3237
3333
  forEach(indicators, function(ic) {
3238
3334
  const internalData = getInternalData(ic)
3239
- internalData.requestCount = (internalData.requestCount || 0) - 1
3240
3335
  if (internalData.requestCount === 0) {
3241
3336
  ic.classList.remove.call(ic.classList, htmx.config.requestClass)
3242
3337
  }
3243
3338
  })
3244
3339
  forEach(disabled, function(disabledElement) {
3245
3340
  const internalData = getInternalData(disabledElement)
3246
- internalData.requestCount = (internalData.requestCount || 0) - 1
3247
3341
  if (internalData.requestCount === 0) {
3248
3342
  disabledElement.removeAttribute('disabled')
3249
3343
  disabledElement.removeAttribute('data-disabled-by-htmx')
@@ -3537,7 +3631,7 @@ var htmx = (function() {
3537
3631
  } else if (paramsValue === '*') {
3538
3632
  return inputValues
3539
3633
  } else if (paramsValue.indexOf('not ') === 0) {
3540
- forEach(paramsValue.substr(4).split(','), function(name) {
3634
+ forEach(paramsValue.slice(4).split(','), function(name) {
3541
3635
  name = name.trim()
3542
3636
  inputValues.delete(name)
3543
3637
  })
@@ -3587,15 +3681,15 @@ var htmx = (function() {
3587
3681
  for (let i = 0; i < split.length; i++) {
3588
3682
  const value = split[i]
3589
3683
  if (value.indexOf('swap:') === 0) {
3590
- swapSpec.swapDelay = parseInterval(value.substr(5))
3684
+ swapSpec.swapDelay = parseInterval(value.slice(5))
3591
3685
  } else if (value.indexOf('settle:') === 0) {
3592
- swapSpec.settleDelay = parseInterval(value.substr(7))
3686
+ swapSpec.settleDelay = parseInterval(value.slice(7))
3593
3687
  } else if (value.indexOf('transition:') === 0) {
3594
- swapSpec.transition = value.substr(11) === 'true'
3688
+ swapSpec.transition = value.slice(11) === 'true'
3595
3689
  } else if (value.indexOf('ignoreTitle:') === 0) {
3596
- swapSpec.ignoreTitle = value.substr(12) === 'true'
3690
+ swapSpec.ignoreTitle = value.slice(12) === 'true'
3597
3691
  } else if (value.indexOf('scroll:') === 0) {
3598
- const scrollSpec = value.substr(7)
3692
+ const scrollSpec = value.slice(7)
3599
3693
  var splitSpec = scrollSpec.split(':')
3600
3694
  const scrollVal = splitSpec.pop()
3601
3695
  var selectorVal = splitSpec.length > 0 ? splitSpec.join(':') : null
@@ -3603,14 +3697,14 @@ var htmx = (function() {
3603
3697
  swapSpec.scroll = scrollVal
3604
3698
  swapSpec.scrollTarget = selectorVal
3605
3699
  } else if (value.indexOf('show:') === 0) {
3606
- const showSpec = value.substr(5)
3700
+ const showSpec = value.slice(5)
3607
3701
  var splitSpec = showSpec.split(':')
3608
3702
  const showVal = splitSpec.pop()
3609
3703
  var selectorVal = splitSpec.length > 0 ? splitSpec.join(':') : null
3610
3704
  swapSpec.show = showVal
3611
3705
  swapSpec.showTarget = selectorVal
3612
3706
  } else if (value.indexOf('focus-scroll:') === 0) {
3613
- const focusScrollVal = value.substr('focus-scroll:'.length)
3707
+ const focusScrollVal = value.slice('focus-scroll:'.length)
3614
3708
  swapSpec.focusScroll = focusScrollVal == 'true'
3615
3709
  } else if (i == 0) {
3616
3710
  swapSpec.swapStyle = value
@@ -3732,10 +3826,10 @@ var htmx = (function() {
3732
3826
  return null
3733
3827
  }
3734
3828
  if (str.indexOf('javascript:') === 0) {
3735
- str = str.substr(11)
3829
+ str = str.slice(11)
3736
3830
  evaluateValue = true
3737
3831
  } else if (str.indexOf('js:') === 0) {
3738
- str = str.substr(3)
3832
+ str = str.slice(3)
3739
3833
  evaluateValue = true
3740
3834
  }
3741
3835
  if (str.indexOf('{') !== 0) {
@@ -3856,16 +3950,22 @@ var htmx = (function() {
3856
3950
  if (context) {
3857
3951
  if (context instanceof Element || typeof context === 'string') {
3858
3952
  return issueAjaxRequest(verb, path, null, null, {
3859
- targetOverride: resolveTarget(context),
3953
+ targetOverride: resolveTarget(context) || DUMMY_ELT,
3860
3954
  returnPromise: true
3861
3955
  })
3862
3956
  } else {
3957
+ let resolvedTarget = resolveTarget(context.target)
3958
+ // If target is supplied but can't resolve OR source is supplied but both target and source can't be resolved
3959
+ // then use DUMMY_ELT to abort the request with htmx:targetError to avoid it replacing body by mistake
3960
+ if ((context.target && !resolvedTarget) || (context.source && !resolvedTarget && !resolveTarget(context.source))) {
3961
+ resolvedTarget = DUMMY_ELT
3962
+ }
3863
3963
  return issueAjaxRequest(verb, path, resolveTarget(context.source), context.event,
3864
3964
  {
3865
3965
  handler: context.handler,
3866
3966
  headers: context.headers,
3867
3967
  values: context.values,
3868
- targetOverride: resolveTarget(context.target),
3968
+ targetOverride: resolvedTarget,
3869
3969
  swapOverride: context.swap,
3870
3970
  select: context.select,
3871
3971
  returnPromise: true
@@ -3927,7 +4027,7 @@ var htmx = (function() {
3927
4027
  const formData = new FormData()
3928
4028
  for (const key in obj) {
3929
4029
  if (obj.hasOwnProperty(key)) {
3930
- if (typeof obj[key].forEach === 'function') {
4030
+ if (obj[key] && typeof obj[key].forEach === 'function') {
3931
4031
  obj[key].forEach(function(v) { formData.append(key, v) })
3932
4032
  } else if (typeof obj[key] === 'object' && !(obj[key] instanceof Blob)) {
3933
4033
  formData.append(key, JSON.stringify(obj[key]))
@@ -3989,7 +4089,15 @@ var htmx = (function() {
3989
4089
  get: function(target, name) {
3990
4090
  if (typeof name === 'symbol') {
3991
4091
  // Forward symbol calls to the FormData itself directly
3992
- return Reflect.get(target, name)
4092
+ const result = Reflect.get(target, name)
4093
+ // Wrap in function with apply to correctly bind the FormData context, as a direct call would result in an illegal invocation error
4094
+ if (typeof result === 'function') {
4095
+ return function() {
4096
+ return result.apply(formData, arguments)
4097
+ }
4098
+ } else {
4099
+ return result
4100
+ }
3993
4101
  }
3994
4102
  if (name === 'toJSON') {
3995
4103
  // Support JSON.stringify call on proxy
@@ -4020,7 +4128,7 @@ var htmx = (function() {
4020
4128
  return false
4021
4129
  }
4022
4130
  target.delete(name)
4023
- if (typeof value.forEach === 'function') {
4131
+ if (value && typeof value.forEach === 'function') {
4024
4132
  value.forEach(function(v) { target.append(name, v) })
4025
4133
  } else if (typeof value === 'object' && !(value instanceof Blob)) {
4026
4134
  target.append(name, JSON.stringify(value))
@@ -4655,7 +4763,8 @@ var htmx = (function() {
4655
4763
  serverResponse,
4656
4764
  isError,
4657
4765
  ignoreTitle,
4658
- selectOverride
4766
+ selectOverride,
4767
+ swapOverride
4659
4768
  }, responseInfo)
4660
4769
 
4661
4770
  if (responseHandling.event && !triggerEvent(target, responseHandling.event, beforeSwapDetails)) return
@@ -4667,6 +4776,7 @@ var htmx = (function() {
4667
4776
  isError = beforeSwapDetails.isError // allow updating error
4668
4777
  ignoreTitle = beforeSwapDetails.ignoreTitle // allow updating ignoring title
4669
4778
  selectOverride = beforeSwapDetails.selectOverride // allow updating select override
4779
+ swapOverride = beforeSwapDetails.swapOverride // allow updating swap override
4670
4780
 
4671
4781
  responseInfo.target = target // Make updated target available to response events
4672
4782
  responseInfo.failed = isError // Make failed property available to response events
@@ -4686,9 +4796,6 @@ var htmx = (function() {
4686
4796
  saveCurrentPageToHistory()
4687
4797
  }
4688
4798
 
4689
- if (hasHeader(xhr, /HX-Reswap:/i)) {
4690
- swapOverride = xhr.getResponseHeader('HX-Reswap')
4691
- }
4692
4799
  var swapSpec = getSwapSpecification(elt, swapOverride)
4693
4800
 
4694
4801
  if (!swapSpec.hasOwnProperty('ignoreTitle')) {
@@ -4824,7 +4931,7 @@ var htmx = (function() {
4824
4931
  * @see https://htmx.org/api/#defineExtension
4825
4932
  *
4826
4933
  * @param {string} name the extension name
4827
- * @param {HtmxExtension} extension the extension definition
4934
+ * @param {Partial<HtmxExtension>} extension the extension definition
4828
4935
  */
4829
4936
  function defineExtension(name, extension) {
4830
4937
  if (extension.init) {
@@ -5121,7 +5228,7 @@ var htmx = (function() {
5121
5228
  */
5122
5229
 
5123
5230
  /**
5124
- * @typedef {HtmxResponseInfo & {shouldSwap: boolean, serverResponse: any, isError: boolean, ignoreTitle: boolean, selectOverride:string}} HtmxBeforeSwapDetails
5231
+ * @typedef {HtmxResponseInfo & {shouldSwap: boolean, serverResponse: any, isError: boolean, ignoreTitle: boolean, selectOverride:string, swapOverride:string}} HtmxBeforeSwapDetails
5125
5232
  */
5126
5233
 
5127
5234
  /**