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