This commit is contained in:
Carson Gross 2023-03-28 14:15:00 -06:00
parent 36780c034f
commit fd87ffd516
5 changed files with 111 additions and 5 deletions

View File

@ -70,6 +70,7 @@ return (function () {
scrollBehavior: 'smooth', scrollBehavior: 'smooth',
defaultFocusScroll: false, defaultFocusScroll: false,
getCacheBusterParam: false, getCacheBusterParam: false,
globalViewTransitions: false,
}, },
parseInterval:parseInterval, parseInterval:parseInterval,
_:internalEval, _:internalEval,
@ -2396,6 +2397,9 @@ return (function () {
if (modifier.indexOf("settle:") === 0) { if (modifier.indexOf("settle:") === 0) {
swapSpec["settleDelay"] = parseInterval(modifier.substr(7)); swapSpec["settleDelay"] = parseInterval(modifier.substr(7));
} }
if (modifier.indexOf("transition:") === 0) {
swapSpec["transition"] = modifier.substr(11) === "true";
}
if (modifier.indexOf("scroll:") === 0) { if (modifier.indexOf("scroll:") === 0) {
var scrollSpec = modifier.substr(7); var scrollSpec = modifier.substr(7);
var splitSpec = scrollSpec.split(":"); var splitSpec = scrollSpec.split(":");
@ -3127,9 +3131,13 @@ return (function () {
var swapSpec = getSwapSpecification(elt, swapOverride); var swapSpec = getSwapSpecification(elt, swapOverride);
target.classList.add(htmx.config.swappingClass); target.classList.add(htmx.config.swappingClass);
// optional transition API promise callbacks
var settleResolve = null;
var settleReject = null;
var doSwap = function () { var doSwap = function () {
try { try {
var activeElt = document.activeElement; var activeElt = document.activeElement;
var selectionInfo = {}; var selectionInfo = {};
try { try {
@ -3228,6 +3236,7 @@ return (function () {
} }
handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt); handleTrigger(xhr, "HX-Trigger-After-Settle", finalElt);
} }
maybeCall(settleResolve);
} }
if (swapSpec.settleDelay > 0) { if (swapSpec.settleDelay > 0) {
@ -3237,10 +3246,34 @@ return (function () {
} }
} catch (e) { } catch (e) {
triggerErrorEvent(elt, 'htmx:swapError', responseInfo); triggerErrorEvent(elt, 'htmx:swapError', responseInfo);
maybeCall(settleReject);
throw e; throw e;
} }
}; };
var shouldTransition = htmx.config.globalViewTransitions
if(swapSpec.hasOwnProperty('transition')){
shouldTransition = swapSpec.transition;
}
if(shouldTransition &&
triggerEvent(elt, 'htmx:beforeTransition', responseInfo) &&
typeof Promise !== "undefined" && document.startViewTransition){
var settlePromise = new Promise(function (_resolve, _reject) {
settleResolve = _resolve;
settleReject = _reject;
});
// wrap the original doSwap() in a call to startViewTransition()
var innerDoSwap = doSwap;
doSwap = function() {
document.startViewTransition(function () {
innerDoSwap();
return settlePromise;
});
}
}
if (swapSpec.swapDelay > 0) { if (swapSpec.swapDelay > 0) {
setTimeout(doSwap, swapSpec.swapDelay) setTimeout(doSwap, swapSpec.swapDelay)
} else { } else {

View File

@ -14,6 +14,36 @@
} }
</style> </style>
<style>
@keyframes fade-in {
from { opacity: 0; }
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes slide-from-right {
from { transform: translateX(30px); }
}
@keyframes slide-to-left {
to { transform: translateX(-30px); }
}
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
</style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head> </head>
@ -48,6 +78,7 @@
</script> </script>
<h2>Server Options</h2> <h2>Server Options</h2>
<button onclick="server.respond()">Server Respond</button> <button onclick="server.respond()">Server Respond</button>
<br/> <br/>
@ -62,12 +93,17 @@ Autorespond: <input id="autorespond" type="checkbox" onclick="toggleAutoRespond(
</div> </div>
<script> <script>
this.server.respondWith("GET", "/demo", 'demo response...'); let requestCount = 0;
this.server.respondWith("GET", "/demo", function(xhr){
let randomStr = (Math.random() + 1).toString(36).substring(7);
xhr.respond(200, {}, "Request #" + requestCount++ + " : " + randomStr)
});
</script> </script>
<button>First</button> <button hx-get="/demo"
<input hx-get="/demo" hx-target="next output" hx-trigger="keyup changed" hx-on-before-request="alert('foo')"/> hx-target="next h1"
<output>--</output> hx-swap="innerHTML transition:true">Issue Request</button>
<h1 id="h1">--</h1>
</body> </body>
</html> </html>

View File

@ -35,6 +35,12 @@ The `div` will issue a request to `/example` and append the returned content aft
The `hx-swap` attributes supports modifiers for changing the behavior of the swap. They are outlined below. The `hx-swap` attributes supports modifiers for changing the behavior of the swap. They are outlined below.
#### Transition: `transition`
If you want to use the new (View Transitions)[https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API] API
when a swap occurs, you can use the `transition:true` option for your swap. You can also enable this feature globally by
setting the `htmx.config.globalViewTransitions` config setting to `true`.
#### Timing: `swap` & `settle` #### Timing: `swap` & `settle`
You can modify the amount of time that htmx will wait after receiving a response to swap the content You can modify the amount of time that htmx will wait after receiving a response to swap the content

View File

@ -417,6 +417,22 @@ The following extensions are available for morph-style swaps:
* [Idiomorph](https://github.com/bigskysoftware/idiomorph#htmx) - A newer morphing algorithm developed by us, the creators * [Idiomorph](https://github.com/bigskysoftware/idiomorph#htmx) - A newer morphing algorithm developed by us, the creators
of htmx. Idiomorph will be available out of the box in htmx 2.0. of htmx. Idiomorph will be available out of the box in htmx 2.0.
#### <a name="view-transitions"></a> [View Transitions](#view-transitions)
The new, experimental [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
gives developers a way to create an animated transition between different DOM states. It is still in active development
and is not available in all browsers, but htmx provides a way to work with this new API that falls back to the non-transition
mechanism if the API is not available in a given browser.
You can experiment with this new API using the following approaches:
* Set the `htmx.config.globalViewTransitions` config variable to `true` to use transitions for all swaps
* Use the `transition:true` option in the `hx-swap` attribute
* If an element swap is going to be transitioned due to either of the above configurations, you may catch the
`htmx:beforeTransition` event and call `preventDefault()` on it to cancel the transition.
View Transitions can be configured using CSS, as outlined in [the Chrome documentation for the feature](https://developer.chrome.com/docs/web-platform/view-transitions/#simple-customization).
### <a name="synchronization"></a> [Synchronization](#synchronization) ### <a name="synchronization"></a> [Synchronization](#synchronization)
Often you want to coordinate the requests between two elements. For example, you may want a request from one element Often you want to coordinate the requests between two elements. For example, you may want a request from one element
@ -1402,6 +1418,7 @@ listed below:
| `htmx.config.timeout` | defaults to 0 in milliseconds | | `htmx.config.timeout` | defaults to 0 in milliseconds |
| `htmx.config.defaultFocusScroll` | if the focused element should be scrolled into view, defaults to false and can be overridden using the [focus-scroll](/attributes/hx-swap/#focus-scroll) swap modifier. | | `htmx.config.defaultFocusScroll` | if the focused element should be scrolled into view, defaults to false and can be overridden using the [focus-scroll](/attributes/hx-swap/#focus-scroll) swap modifier. |
| `htmx.config.getCacheBusterParam` | defaults to false, if set to true htmx will include a cache-busting parameter in `GET` requests to avoid caching partial responses by the browser | | `htmx.config.getCacheBusterParam` | defaults to false, if set to true htmx will include a cache-busting parameter in `GET` requests to avoid caching partial responses by the browser |
| `htmx.config.globalViewTransitions` | if set to `true`, htmx will use the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) API when swapping in new content. |
</div> </div>

View File

@ -134,6 +134,20 @@ the documentation on [configuring swapping](/docs#modifying_swapping_behavior_wi
* `detail.shouldSwap` - if the content will be swapped (defaults to `false` for non-200 response codes) * `detail.shouldSwap` - if the content will be swapped (defaults to `false` for non-200 response codes)
* `detail.target` - the target of the swap * `detail.target` - the target of the swap
### <a name="htmx:beforeTransition"></a> Event - [`htmx:beforeTransition`](#htmx:beforeTransition)
This event is triggered before a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
wrapped swap occurs. If the event is cancelled, the View Transition will not occur and the normal swapping logic will
happen instead.
##### Details
* `detail.elt` - the element that dispatched the request
* `detail.xhr` - the `XMLHttpRequest`
* `detail.requestConfig` - the configuration of the AJAX request
* `detail.shouldSwap` - if the content will be swapped (defaults to `false` for non-200 response codes)
* `detail.target` - the target of the swap
### <a name="htmx:configRequest"></a> Event - [`htmx:configRequest`](#htmx:configRequest) ### <a name="htmx:configRequest"></a> Event - [`htmx:configRequest`](#htmx:configRequest)
This event is triggered after htmx has collected parameters for inclusion in the request. It can be This event is triggered after htmx has collected parameters for inclusion in the request. It can be