diff --git a/src/htmx.js b/src/htmx.js
index 024baa612..424dad0ea 100644
--- a/src/htmx.js
+++ b/src/htmx.js
@@ -685,12 +685,13 @@ var htmx = (() => {
}
if (ctx.hx.location) { // HX-Location
let path = ctx.hx.location, opts = {};
- if (path[0] === '{' || /[\s,]/.test(path)) {
- opts = HCON.parse(path);
+ let parsed = HCON.parse(path);
+ if (path[0] === '{' || parsed.path != null) {
+ opts = parsed;
path = opts.path;
delete opts.path;
}
- opts.push ??= 'true';
+ if (opts.push == null && opts.replace == null) opts.push = 'true';
this.ajax('GET', path, opts);
return true
}
diff --git a/test/tests/unit/__handleHxHeadersAndMaybeReturnEarly.js b/test/tests/unit/__handleHxHeadersAndMaybeReturnEarly.js
index 2914ae1b1..89c1b2225 100644
--- a/test/tests/unit/__handleHxHeadersAndMaybeReturnEarly.js
+++ b/test/tests/unit/__handleHxHeadersAndMaybeReturnEarly.js
@@ -34,4 +34,73 @@ describe('__handleHxHeadersAndMaybeReturnEarly unit tests', function() {
assert.isNotOk(result)
})
+ it('keeps commas in plain HX-Location paths', function() {
+ let originalAjax = htmx.ajax
+ let request
+ htmx.ajax = (...args) => request = args
+
+ try {
+ let result = htmx.__handleHeadersAndMaybeReturnEarly({hx: {location: '/files/a,b'}})
+
+ assert.isTrue(result)
+ assert.deepEqual(request, ['GET', '/files/a,b', {push: 'true'}])
+ } finally {
+ htmx.ajax = originalAjax
+ }
+ })
+
+ it('parses an HCON HX-Location value', function() {
+ let originalAjax = htmx.ajax
+ let request
+ htmx.ajax = (...args) => request = args
+
+ try {
+ let result = htmx.__handleHeadersAndMaybeReturnEarly({hx: {location: 'path:/search'}})
+
+ assert.isTrue(result)
+ assert.deepEqual(request, ['GET', '/search', {push: 'true'}])
+ } finally {
+ htmx.ajax = originalAjax
+ }
+ })
+
+ it('honors HX-Location replace without pushing', async function() {
+ mockResponse('GET', '/test', 'ignored', {
+ headers: {
+ 'HX-Location': '{"path":"/location-replaced","target":"#destination","replace":"/location-replaced"}'
+ }
+ })
+ mockResponse('GET', '/location-replaced', 'Located')
+ let source = createProcessedHTML('
')
+ .querySelector('#source')
+ let requestFinished = new Promise(resolve => {
+ find('#destination').addEventListener('htmx:finally:request', resolve, {once: true})
+ })
+ let originalPushState = history.pushState
+ let originalReplaceState = history.replaceState
+ let pushes = 0
+ let replaces = 0
+
+ history.pushState = function(...args) {
+ pushes++
+ return originalPushState.apply(history, args)
+ }
+ history.replaceState = function(...args) {
+ replaces++
+ return originalReplaceState.apply(history, args)
+ }
+
+ try {
+ source.click()
+ await requestFinished
+
+ assert.equal(find('#destination').textContent, 'Located')
+ assert.equal(pushes, 0)
+ assert.equal(replaces, 1)
+ } finally {
+ history.pushState = originalPushState
+ history.replaceState = originalReplaceState
+ }
+ })
+
});
diff --git a/www/src/content/reference/02-headers/11-HX-Location.md b/www/src/content/reference/02-headers/11-HX-Location.md
index 876926145..1272639a9 100644
--- a/www/src/content/reference/02-headers/11-HX-Location.md
+++ b/www/src/content/reference/02-headers/11-HX-Location.md
@@ -1,51 +1,34 @@
---
title: "HX-Location"
-description: "Navigates with `htmx.ajax()`"
+description: "Redirect without a full page load"
---
-The `HX-Location` response header navigates to a new URL without a full page reload.
+The `HX-Location` response header redirects without reloading the page.
-Like clicking a boosted link—htmx fetches the content and updates the page via AJAX.
+## Usage
-## Simple Usage
-
-Redirect to a path:
+Return a path:
```http
HX-Location: /dashboard
```
-## Advanced Usage
-
-Specify target and other options:
+`HX-Location` calls `htmx.ajax()`. The header above is equivalent to:
-```http
-HX-Location: {"path":"/search", "target":"#results", "push":"false"}
+```js
+htmx.ajax('GET', '/dashboard', { push: 'true' })
```
-## Options
+Use any serializable [`htmx.ajax()` option](/reference/methods/htmx-ajax#context). Include `path`:
-The JSON value mirrors the htmx ajax API. All fields except `path` are optional.
+```text
+# HCON
+HX-Location: path:/search target:#results select:#matches
-- `path` - URL to load the response from (required)
-- `target` - Element to swap the response into (defaults to `document.body`)
-- `source` - The source element of the request
-- `event` - Event that triggered the request
-- `handler` - Callback to handle the response HTML
-- `swap` - How to swap the response relative to the target
-- `values` - Values to submit with the request
-- `headers` - Headers to submit with the request
-- `select` - Selects content from the response to swap
-- `push` - Prevents or overrides the URL pushed to history (`'false'` or a path string)
-- `replace` - Path to replace in browser history instead of pushing
+# JSON
+HX-Location: {"path":"/search","target":"#results","select":"#matches"}
+```
## Notes
-Response headers are not processed on 3xx response codes. Return a 2xx status when using this header.
-
-## Example
-
-```python
-headers = {'HX-Location': '/profile'}
-return Response(content, headers=headers)
-```
+`HX-Location` is not processed on 3xx responses. Return a 2xx response instead.