htmx.org 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,21 +1,111 @@
1
1
  # Changelog
2
2
 
3
-
4
- ## [1.6.0] - 2021-?-??
3
+ ## [1.8.0] - 2022-12-7
4
+
5
+ * **NOTE**: This release involved some changes to toughy code (e.g. history support) so please test thoroughly and let
6
+ us know if you see any issues
7
+ * Boosted forms now will automatically push URLs into history as with links. The [response URL](https://caniuse.com/mdn-api_xmlhttprequest_responseurl)
8
+ detection API support is good enough that we feel comfortable making this the default now.
9
+ * If you do not want this behavior you can add `hx-push-url='false'` to your boosted forms
10
+ * The [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url) attribute was introduced, allowing you to replace
11
+ the current URL in history (to complement `hx-push-url`)
12
+ * Bug fix - if htmx is included in a page more than once, we do not process elements multiple times
13
+ * Bug fix - When localStorage is not available we do not attempt to save history in it
14
+ * [Bug fix](https://github.com/bigskysoftware/htmx/issues/908) - `hx-boost` respects the `enctype` attribute
15
+ * `m` is now a valid timing modifier (e.g. `hx-trigger="every 2m"`)
16
+ * `next` and `previous` are now valid extended query selector modifiers, e.g. `hx-target="next div"` will target the
17
+ next div from the current element
18
+ * Bug fix - `hx-boost` will boost anchor tags with a `_self` target
19
+ * The `load` event now properly supports event filters
20
+ * The websocket extension has had many improvements: (A huge thank you to Denis Palashevskii, our newest committer on the project!)
21
+ * Implement proper `hx-trigger` support
22
+ * Expose trigger handling API to extensions
23
+ * Implement safe message sending with sending queue
24
+ * Fix `ws-send` attributes connecting in new elements
25
+ * Fix OOB swapping of multiple elements in response
26
+ * The `HX-Location` response header now implements client-side redirects entirely within htmx
27
+ * The `HX-Reswap` response header allows you to change the swap behavior of htmx
28
+ * The new [`hx-select-oob`](/attributes/hx-select-oob) attribute selects one or more elements from a server response to swap in via an out of band swap
29
+ * The new [`hx-replace-url`](/attributes/hx-replace-url) attribute can be used to replace the current URL in the location
30
+ bar (very similar to `hx-push-url` but no new history entry is created). The corresponding `HX-Replace-Url` response header can be used as well.
31
+ * htmx now properly handles anchors in both boosted links, as well as in `hx-get`, etc. attributes
32
+
33
+ ## [1.7.0] - 2022-02-2
34
+
35
+ * The new [`hx-sync`](/attributes/hx-sync) attribute allows you to synchronize multiple element requests on a single
36
+ element using various strategies (e.g. replace)
37
+ * You can also now abort an element making a request by sending it the `htmx:abort` event
38
+ * [Server Sent Events](/extensions/server-sent-events) and [Web Sockets](/extensions/web-sockets) are now available as
39
+ extensions, in addition to the normal core support. In htmx 2.0, the current `hx-sse` and `hx-ws` attributes will be
40
+ moved entirely out to these new extensions. By moving these features to extensions we will be able to add functionality
41
+ to both of them without compromising the core file size of htmx. You are encouraged to move over to the new
42
+ extensions, but `hx-sse` and `hx-ws` will continue to work indefinitely in htmx 1.x.
43
+ * You can now mask out [attribute inheritance](/docs#inheritance) via the [`hx-disinherit`](/attributes/hx-disinherit) attribute.
44
+ * The `HX-Push` header can now have the `false` value, which will prevent a history snapshot from occuring.
45
+ * Many new extensions, with a big thanks to all the contributors!
46
+ * A new [`alpine-morph`](/extensions/alpine-morph) allows you to use Alpine's swapping engine, which preserves Alpine
47
+ * A [restored](/extensions/restored) extension was added that will trigger a `restore` event on all elements in the DOM
48
+ on history restoration.
49
+ * A [loading-states](/extensions/loading-states) extension was added that allows you to easily manage loading states
50
+ while a request is in flight, including disabling elements, and adding and removing CSS classes.
51
+ * The `this` symbol now resolves properly for the [`hx-include`](/attributes/hx-include) and [`hx-indicator`](/attributes/hx-indicator)
52
+ attributes
53
+ * When an object is included via the [`hx-vals`](/attributes/hx-vals) attribute, it will be converted to JSON (rather
54
+ than rendering as the string `[Object object]"`)
55
+ * You can now pass a swap style in to the `htmx.ajax()` function call.
56
+ * Poll events now contain a `target` attribute, allowing you to filter a poll on the element that is polling.
57
+ * Two new Out Of Band-related events were added: `htmx:oobBeforeSwap` & `htmx:oobAfterSwap`
58
+
59
+ ## [1.6.1] - 2021-11-22
60
+
61
+ * A new `HX-Retarget` header allows you to change the default target of returned content
62
+ * The `htmx:beforeSwap` event now includes another configurable property: `detail.isError` which can
63
+ be used to indicate if a given response should be treated as an error or not
64
+ * The `htmx:afterRequest` event has two new detail properties: `success` and `failed`, allowing you to write
65
+ trigger filters in htmx or hyperscript:
66
+ ```applescript
67
+ on htmx:afterRequest[failed]
68
+ set #myCheckbox's checked to true
69
+ ```
70
+ * Fixed the `from:` option in [`hx-trigger`](/attributes/hx-trigger) to support `closest <CSS selector>`
71
+ and `find <CSS selector>` forms
72
+ * Don't boost anchor tags with an explicit `target` set
73
+ * Don't cancel all events on boosted elements, only the events that naturally trigger them (click for anchors, submit
74
+ for forms)
75
+ * Persist revealed state in the DOM so that on history navigation, revealed elements are not re-requested
76
+ * Process all [`hx-ext`](/attributes/hx-ext) attributes, even if no other htmx attribute is on the element
77
+ * Snapshot the current URL on load so that history support works properly after a page refresh occurs
78
+ * Many, many documentation updates (thank you to all the contributors!)
79
+
80
+
81
+ ## [1.6.0] - 2021-10-01
82
+
83
+ * Completely reworked `<script>` tag support that now supports the `<script src="...'/>` form
84
+ * You can now use the value `unset` to clear a property that would normally be inherited (e.g. hx-confirm)
85
+ * The `htmx-added` class is added to new content before a swap and removed after the settle phase, which allows you
86
+ more flexibility in writing CSS transitions for added content (rather than relying on the target, as with `htmx-settling`)
87
+ * The `htmx:beforeSwap` event has been updated to allow you to [configure swapping](https://htmx.org/docs/#modifying_swapping_behavior_with_events)
88
+ behavior
89
+ * Improved `<title>` extraction support
90
+ * You can listen to events on the `window` object using the `from:` modifier in `hx-trigger`
91
+ * The `root` option of the `intersect` event was fixed
92
+ * Boosted forms respect the `enctype` declaration
93
+ * The `HX-Boosted` header will be sent on requests from boosted elements
94
+ * Promises are not returned from the main ajax function unless it is an api call (i.e. `htmx.ajax`)
5
95
 
6
96
  ## [1.5.0] - 2021-7-12
7
97
 
8
98
  * Support tracking of button clicked during a form submission
9
- * Conditional polling via the [hx-trigger](/attributes/hx-trigger) attribute
10
- * `document` is now a valid pseudo-selector on the [hx-trigger](/attributes/hx-trigger) `from:` argument, allowing you
99
+ * Conditional polling via the [hx-trigger](https://htmx.org/attributes/hx-trigger) attribute
100
+ * `document` is now a valid pseudo-selector on the [hx-trigger](https://htmx.org/attributes/hx-trigger) `from:` argument, allowing you
11
101
  to listen for events on the document.
12
- * Added the [hx-request](/attributes/hx-request) attribute, allowing you to configure the following aspects of the request
102
+ * Added the [hx-request](https://htmx.org/attributes/hx-request) attribute, allowing you to configure the following aspects of the request
13
103
  * `timeout` - the timeout of the request
14
104
  * `credentials` - if the request will send credentials
15
105
  * `noHeaders` - strips all headers from the request
16
106
  * Along with the above attribute, you can configure the default values for each of these via the corresponding `htmx.config`
17
107
  properties (e.g. `htmx.config.timeout`)
18
- * Both the `scroll` and `show` options on [hx-swap](/attributes/hx-swap) now support extended syntax for selecting the
108
+ * Both the `scroll` and `show` options on [hx-swap](https://htmx.org/attributes/hx-swap) now support extended syntax for selecting the
19
109
  element to scroll or to show, including the pseudo-selectors `window:top` and `window:bottom`.
20
110
 
21
111
  ## [1.4.1] - 2021-6-1
@@ -24,13 +114,13 @@
24
114
 
25
115
  ## [1.4.0] - 2021-5-25
26
116
 
27
- * Added the `queue` option to the [hx-trigger](/attributes/hx-trigger) attribute, allowing you to specify how events
117
+ * Added the `queue` option to the [hx-trigger](https://htmx.org/attributes/hx-trigger) attribute, allowing you to specify how events
28
118
  should be queued when they are received with a request in flight
29
119
  * The `htmx.config.useTemplateFragments` option was added, allowing you to use HTML template tags for parsing content
30
120
  from the server. This allows you to use Out of Band content when returning things like table rows, but it is not
31
121
  IE11 compatible.
32
122
  * The `defaultSettleDelay` was dropped to 20ms from 100ms
33
- * Introduced a new synthetic event, [intersect](/docs#pecial-events) that allows you to trigger when an item is scrolled into view
123
+ * Introduced a new synthetic event, [intersect](https://htmx.org/docs#pecial-events) that allows you to trigger when an item is scrolled into view
34
124
  as specified by the `IntersectionObserver` API
35
125
  * Fixed timing issue that caused exceptions in the `reveal` logic when scrolling at incredible speeds - <https://github.com/bigskysoftware/htmx/issues/463>
36
126
  * Fixed bug causing SVG titles to be incorrectly used as page title - <https://github.com/bigskysoftware/htmx/issues/459>
@@ -46,7 +136,7 @@
46
136
 
47
137
  ## [1.3.3] - 2021-4-5
48
138
 
49
- * Added the [`hx-disabled`](/docs#security) attribute to allow htmx to be turned off for parts of the DOM
139
+ * Added the [`hx-disabled`](https://htmx.org/docs#security) attribute to allow htmx to be turned off for parts of the DOM
50
140
  * SSE now uses a full-jitter exponential backoff algorithm on reconnection, using the `htmx.config.wsReconnectDelay`
51
141
  setting
52
142
 
package/README.md CHANGED
@@ -34,8 +34,7 @@ By removing these arbitrary constraints htmx completes HTML as a
34
34
  ## quick start
35
35
 
36
36
  ```html
37
- <!-- Load from unpkg -->
38
- <script src="https://unpkg.com/htmx.org@1.6.0" ></script>
37
+ <script src="https://unpkg.com/htmx.org@1.8.0"></script>
39
38
  <!-- have a button POST a click via AJAX -->
40
39
  <button hx-post="/clicked" hx-swap="outerHTML">
41
40
  Click Me
@@ -74,30 +73,35 @@ keep the core htmx code tidy
74
73
 
75
74
  ### hacking guide
76
75
 
77
- to develop htmx locally, you will need to install the development dependencies:
76
+ To develop htmx locally, you will need to install the development dependencies.
77
+ Use node 15 and run:
78
78
 
79
- * `npm install`
79
+ ```
80
+ npm install
81
+ ```
80
82
 
81
- and then run a web server in the root (easiest with python):
83
+ Then, run a web server in the root.
84
+ This is easiest with Python:
82
85
 
83
- * `python3 -m http.server
84
- `
86
+ ```
87
+ python3 -m http.server
88
+ ```
85
89
 
86
- you can then run the test suite by navigating to:
90
+ You can then run the test suite by navigating to:
87
91
 
88
92
  <http://0.0.0.0:8000/test/>
89
93
 
90
- at this point you can modify `/src/htmx.js` to add features, and then add tests in the appropriate area under `/test`
94
+ At this point you can modify `/src/htmx.js` to add features, and then add tests in the appropriate area under `/test`.
91
95
 
92
96
  * `/test/index.html` - the root test page from which all other tests are included
93
- * `/test/attributres` - attribute specific tests
97
+ * `/test/attributes` - attribute specific tests
94
98
  * `/test/core` - core functionality tests
95
- * `/test/core/regressions.js` - regresssion tests
99
+ * `/test/core/regressions.js` - regression tests
96
100
  * `/test/ext` - extension tests
97
101
  * `/test/manual` - manual tests that cannot be automated
98
102
 
99
103
  htmx uses the [mocha](https://mochajs.org/) testing framework, the [chai](https://www.chaijs.com/) assertion framework
100
- and [sinon](https://sinonjs.org/releases/v11.1.1/fake-xhr-and-server/) to mock out AJAX requests. They are all OK.
104
+ and [sinon](https://sinonjs.org/releases/v9/fake-xhr-and-server/) to mock out AJAX requests. They are all OK.
101
105
 
102
106
  ## haiku
103
107
 
@@ -0,0 +1,16 @@
1
+ htmx.defineExtension('alpine-morph', {
2
+ isInlineSwap: function (swapStyle) {
3
+ return swapStyle === 'morph';
4
+ },
5
+ handleSwap: function (swapStyle, target, fragment) {
6
+ if (swapStyle === 'morph') {
7
+ if (fragment.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
8
+ Alpine.morph(target, fragment.firstElementChild);
9
+ return [target];
10
+ } else {
11
+ Alpine.morph(target, fragment.outerHTML);
12
+ return [target];
13
+ }
14
+ }
15
+ }
16
+ });
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ // Disable Submit Button
4
+ htmx.defineExtension('disable-element', {
5
+ onEvent: function (name, evt) {
6
+ let elt = evt.detail.elt;
7
+ let target = elt.getAttribute("hx-disable-element");
8
+ let targetElement = (target == "self") ? elt : document.querySelector(target);
9
+
10
+ if (name === "htmx:beforeRequest" && targetElement) {
11
+ targetElement.disabled = true;
12
+ } else if (name == "htmx:afterRequest" && targetElement) {
13
+ targetElement.disabled = false;
14
+ }
15
+ }
16
+ });
@@ -0,0 +1,179 @@
1
+ ;(function () {
2
+ let loadingStatesUndoQueue = []
3
+
4
+ function loadingStateContainer(target) {
5
+ return htmx.closest(target, '[data-loading-states]') || document.body
6
+ }
7
+
8
+ function mayProcessUndoCallback(target, callback) {
9
+ if (document.body.contains(target)) {
10
+ callback()
11
+ }
12
+ }
13
+
14
+ function mayProcessLoadingStateByPath(elt, requestPath) {
15
+ const pathElt = htmx.closest(elt, '[data-loading-path]')
16
+ if (!pathElt) {
17
+ return true
18
+ }
19
+
20
+ return pathElt.getAttribute('data-loading-path') === requestPath
21
+ }
22
+
23
+ function queueLoadingState(sourceElt, targetElt, doCallback, undoCallback) {
24
+ const delayElt = htmx.closest(sourceElt, '[data-loading-delay]')
25
+ if (delayElt) {
26
+ const delayInMilliseconds =
27
+ delayElt.getAttribute('data-loading-delay') || 200
28
+ const timeout = setTimeout(() => {
29
+ doCallback()
30
+
31
+ loadingStatesUndoQueue.push(() => {
32
+ mayProcessUndoCallback(targetElt, () => undoCallback())
33
+ })
34
+ }, delayInMilliseconds)
35
+
36
+ loadingStatesUndoQueue.push(() => {
37
+ mayProcessUndoCallback(targetElt, () => clearTimeout(timeout))
38
+ })
39
+ } else {
40
+ doCallback()
41
+ loadingStatesUndoQueue.push(() => {
42
+ mayProcessUndoCallback(targetElt, () => undoCallback())
43
+ })
44
+ }
45
+ }
46
+
47
+ function getLoadingStateElts(loadingScope, type, path) {
48
+ return Array.from(htmx.findAll(loadingScope, `[${type}]`)).filter(
49
+ (elt) => mayProcessLoadingStateByPath(elt, path)
50
+ )
51
+ }
52
+
53
+ function getLoadingTarget(elt) {
54
+ if (elt.getAttribute('data-loading-target')) {
55
+ return Array.from(
56
+ htmx.findAll(elt.getAttribute('data-loading-target'))
57
+ )
58
+ }
59
+ return [elt]
60
+ }
61
+
62
+ htmx.defineExtension('loading-states', {
63
+ onEvent: function (name, evt) {
64
+ if (name === 'htmx:beforeRequest') {
65
+ const container = loadingStateContainer(evt.target)
66
+
67
+ const loadingStateTypes = [
68
+ 'data-loading',
69
+ 'data-loading-class',
70
+ 'data-loading-class-remove',
71
+ 'data-loading-disable',
72
+ 'data-loading-aria-busy',
73
+ ]
74
+
75
+ let loadingStateEltsByType = {}
76
+
77
+ loadingStateTypes.forEach((type) => {
78
+ loadingStateEltsByType[type] = getLoadingStateElts(
79
+ container,
80
+ type,
81
+ evt.detail.pathInfo.requestPath
82
+ )
83
+ })
84
+
85
+ loadingStateEltsByType['data-loading'].forEach((sourceElt) => {
86
+ getLoadingTarget(sourceElt).forEach((targetElt) => {
87
+ queueLoadingState(
88
+ sourceElt,
89
+ targetElt,
90
+ () =>
91
+ (targetElt.style.display =
92
+ sourceElt.getAttribute('data-loading') ||
93
+ 'inline-block'),
94
+ () => (targetElt.style.display = 'none')
95
+ )
96
+ })
97
+ })
98
+
99
+ loadingStateEltsByType['data-loading-class'].forEach(
100
+ (sourceElt) => {
101
+ const classNames = sourceElt
102
+ .getAttribute('data-loading-class')
103
+ .split(' ')
104
+
105
+ getLoadingTarget(sourceElt).forEach((targetElt) => {
106
+ queueLoadingState(
107
+ sourceElt,
108
+ targetElt,
109
+ () =>
110
+ classNames.forEach((className) =>
111
+ targetElt.classList.add(className)
112
+ ),
113
+ () =>
114
+ classNames.forEach((className) =>
115
+ targetElt.classList.remove(className)
116
+ )
117
+ )
118
+ })
119
+ }
120
+ )
121
+
122
+ loadingStateEltsByType['data-loading-class-remove'].forEach(
123
+ (sourceElt) => {
124
+ const classNames = sourceElt
125
+ .getAttribute('data-loading-class-remove')
126
+ .split(' ')
127
+
128
+ getLoadingTarget(sourceElt).forEach((targetElt) => {
129
+ queueLoadingState(
130
+ sourceElt,
131
+ targetElt,
132
+ () =>
133
+ classNames.forEach((className) =>
134
+ targetElt.classList.remove(className)
135
+ ),
136
+ () =>
137
+ classNames.forEach((className) =>
138
+ targetElt.classList.add(className)
139
+ )
140
+ )
141
+ })
142
+ }
143
+ )
144
+
145
+ loadingStateEltsByType['data-loading-disable'].forEach(
146
+ (sourceElt) => {
147
+ getLoadingTarget(sourceElt).forEach((targetElt) => {
148
+ queueLoadingState(
149
+ sourceElt,
150
+ targetElt,
151
+ () => (targetElt.disabled = true),
152
+ () => (targetElt.disabled = false)
153
+ )
154
+ })
155
+ }
156
+ )
157
+
158
+ loadingStateEltsByType['data-loading-aria-busy'].forEach(
159
+ (sourceElt) => {
160
+ getLoadingTarget(sourceElt).forEach((targetElt) => {
161
+ queueLoadingState(
162
+ sourceElt,
163
+ targetElt,
164
+ () => (targetElt.setAttribute("aria-busy", "true")),
165
+ () => (targetElt.removeAttribute("aria-busy"))
166
+ )
167
+ })
168
+ }
169
+ )
170
+ }
171
+
172
+ if (name === 'htmx:afterOnLoad') {
173
+ while (loadingStatesUndoQueue.length > 0) {
174
+ loadingStatesUndoQueue.shift()()
175
+ }
176
+ }
177
+ },
178
+ })
179
+ })()
@@ -5,6 +5,9 @@
5
5
  var _root = this;
6
6
 
7
7
  function dependsOn(pathSpec, url) {
8
+ if (pathSpec === "ignore") {
9
+ return false;
10
+ }
8
11
  var dependencyPath = pathSpec.split("/");
9
12
  var urlPath = url.split("/");
10
13
  for (var i = 0; i < urlPath.length; i++) {
@@ -35,7 +38,7 @@
35
38
  if (name === "htmx:beforeOnLoad") {
36
39
  var config = evt.detail.requestConfig;
37
40
  // mutating call
38
- if (config.verb !== "get") {
41
+ if (config.verb !== "get" && evt.target.getAttribute('path-deps') !== 'ignore') {
39
42
  refreshPath(config.path);
40
43
  }
41
44
  }
@@ -1,7 +1,7 @@
1
1
  // This adds the "preload" extension to htmx. By default, this will
2
2
  // preload the targets of any tags with `href` or `hx-get` attributes
3
3
  // if they also have a `preload` attribute as well. See documentation
4
- // for more detauls
4
+ // for more details
5
5
  htmx.defineExtension("preload", {
6
6
 
7
7
  onEvent: function(name, event) {
@@ -15,7 +15,7 @@
15
15
  if (elt.getAttribute) {
16
16
  maybeRemoveMe(elt);
17
17
  if (elt.querySelectorAll) {
18
- var children = elt.querySelectorAll("[remove-me], [data-remove-me");
18
+ var children = elt.querySelectorAll("[remove-me], [data-remove-me]");
19
19
  for (var i = 0; i < children.length; i++) {
20
20
  maybeRemoveMe(children[i]);
21
21
  }
@@ -0,0 +1,15 @@
1
+ htmx.defineExtension('restored', {
2
+ onEvent : function(name, evt) {
3
+ if (name === 'htmx:restored'){
4
+ var restoredElts = evt.detail.document.querySelectorAll(
5
+ "[hx-trigger='restored'],[data-hx-trigger='restored']"
6
+ );
7
+ // need a better way to do this, would prefer to just trigger from evt.detail.elt
8
+ var foundElt = Array.from(restoredElts).find(
9
+ (x) => (x.outerHTML === evt.detail.elt.outerHTML)
10
+ );
11
+ var restoredEvent = evt.detail.triggerEvent(foundElt, 'restored');
12
+ }
13
+ return;
14
+ }
15
+ })