@hotwired/turbo 8.0.21 → 8.0.22
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/README.md +1 -1
- package/dist/turbo.es2017-esm.js +66 -139
- package/dist/turbo.es2017-umd.js +66 -139
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -2119,7 +2119,6 @@ var Idiomorph = (function () {
|
|
|
2119
2119
|
* @property {ConfigInternal['callbacks']} callbacks
|
|
2120
2120
|
* @property {ConfigInternal['head']} head
|
|
2121
2121
|
* @property {HTMLDivElement} pantry
|
|
2122
|
-
* @property {Element[]} activeElementAndParents
|
|
2123
2122
|
*/
|
|
2124
2123
|
|
|
2125
2124
|
//=============================================================================
|
|
@@ -2195,6 +2194,14 @@ var Idiomorph = (function () {
|
|
|
2195
2194
|
*/
|
|
2196
2195
|
function morphOuterHTML(ctx, oldNode, newNode) {
|
|
2197
2196
|
const oldParent = normalizeParent(oldNode);
|
|
2197
|
+
|
|
2198
|
+
// basis for calulating which nodes were morphed
|
|
2199
|
+
// since there may be unmorphed sibling nodes
|
|
2200
|
+
let childNodes = Array.from(oldParent.childNodes);
|
|
2201
|
+
const index = childNodes.indexOf(oldNode);
|
|
2202
|
+
// how many elements are to the right of the oldNode
|
|
2203
|
+
const rightMargin = childNodes.length - (index + 1);
|
|
2204
|
+
|
|
2198
2205
|
morphChildren(
|
|
2199
2206
|
ctx,
|
|
2200
2207
|
oldParent,
|
|
@@ -2203,8 +2210,10 @@ var Idiomorph = (function () {
|
|
|
2203
2210
|
oldNode, // start point for iteration
|
|
2204
2211
|
oldNode.nextSibling, // end point for iteration
|
|
2205
2212
|
);
|
|
2206
|
-
|
|
2207
|
-
return
|
|
2213
|
+
|
|
2214
|
+
// return just the morphed nodes
|
|
2215
|
+
childNodes = Array.from(oldParent.childNodes);
|
|
2216
|
+
return childNodes.slice(index, childNodes.length - rightMargin);
|
|
2208
2217
|
}
|
|
2209
2218
|
|
|
2210
2219
|
/**
|
|
@@ -2233,11 +2242,8 @@ var Idiomorph = (function () {
|
|
|
2233
2242
|
|
|
2234
2243
|
const results = fn();
|
|
2235
2244
|
|
|
2236
|
-
if (
|
|
2237
|
-
activeElementId
|
|
2238
|
-
activeElementId !== document.activeElement?.getAttribute("id")
|
|
2239
|
-
) {
|
|
2240
|
-
activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`);
|
|
2245
|
+
if (activeElementId && activeElementId !== document.activeElement?.id) {
|
|
2246
|
+
activeElement = ctx.target.querySelector(`#${activeElementId}`);
|
|
2241
2247
|
activeElement?.focus();
|
|
2242
2248
|
}
|
|
2243
2249
|
if (activeElement && !activeElement.selectionEnd && selectionEnd) {
|
|
@@ -2315,23 +2321,17 @@ var Idiomorph = (function () {
|
|
|
2315
2321
|
}
|
|
2316
2322
|
|
|
2317
2323
|
// if the matching node is elsewhere in the original content
|
|
2318
|
-
if (newChild instanceof Element) {
|
|
2319
|
-
//
|
|
2320
|
-
const
|
|
2321
|
-
|
|
2324
|
+
if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) {
|
|
2325
|
+
// move it and all its children here and morph
|
|
2326
|
+
const movedChild = moveBeforeById(
|
|
2327
|
+
oldParent,
|
|
2328
|
+
newChild.id,
|
|
2329
|
+
insertionPoint,
|
|
2330
|
+
ctx,
|
|
2322
2331
|
);
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
oldParent,
|
|
2327
|
-
newChildId,
|
|
2328
|
-
insertionPoint,
|
|
2329
|
-
ctx,
|
|
2330
|
-
);
|
|
2331
|
-
morphNode(movedChild, newChild, ctx);
|
|
2332
|
-
insertionPoint = movedChild.nextSibling;
|
|
2333
|
-
continue;
|
|
2334
|
-
}
|
|
2332
|
+
morphNode(movedChild, newChild, ctx);
|
|
2333
|
+
insertionPoint = movedChild.nextSibling;
|
|
2334
|
+
continue;
|
|
2335
2335
|
}
|
|
2336
2336
|
|
|
2337
2337
|
// last resort: insert the new node from scratch
|
|
@@ -2441,8 +2441,7 @@ var Idiomorph = (function () {
|
|
|
2441
2441
|
|
|
2442
2442
|
// if the current node contains active element, stop looking for better future matches,
|
|
2443
2443
|
// because if one is found, this node will be moved to the pantry, reparenting it and thus losing focus
|
|
2444
|
-
|
|
2445
|
-
if (ctx.activeElementAndParents.includes(cursor)) break;
|
|
2444
|
+
if (cursor.contains(document.activeElement)) break;
|
|
2446
2445
|
|
|
2447
2446
|
cursor = cursor.nextSibling;
|
|
2448
2447
|
}
|
|
@@ -2492,9 +2491,7 @@ var Idiomorph = (function () {
|
|
|
2492
2491
|
// If oldElt has an `id` with possible state and it doesn't match newElt.id then avoid morphing.
|
|
2493
2492
|
// We'll still match an anonymous node with an IDed newElt, though, because if it got this far,
|
|
2494
2493
|
// its not persistent, and new nodes can't have any hidden state.
|
|
2495
|
-
|
|
2496
|
-
(!oldElt.getAttribute?.("id") ||
|
|
2497
|
-
oldElt.getAttribute?.("id") === newElt.getAttribute?.("id"))
|
|
2494
|
+
(!oldElt.id || oldElt.id === newElt.id)
|
|
2498
2495
|
);
|
|
2499
2496
|
}
|
|
2500
2497
|
|
|
@@ -2558,11 +2555,8 @@ var Idiomorph = (function () {
|
|
|
2558
2555
|
const target =
|
|
2559
2556
|
/** @type {Element} - will always be found */
|
|
2560
2557
|
(
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
(ctx.target.getAttribute?.("id") === id && ctx.target) ||
|
|
2564
|
-
ctx.target.querySelector(`[id="${id}"]`) ||
|
|
2565
|
-
ctx.pantry.querySelector(`[id="${id}"]`)
|
|
2558
|
+
ctx.target.querySelector(`#${id}`) ||
|
|
2559
|
+
ctx.pantry.querySelector(`#${id}`)
|
|
2566
2560
|
);
|
|
2567
2561
|
removeElementFromAncestorsIdMaps(target, ctx);
|
|
2568
2562
|
moveBefore(parentNode, target, after);
|
|
@@ -2578,8 +2572,7 @@ var Idiomorph = (function () {
|
|
|
2578
2572
|
* @param {MorphContext} ctx
|
|
2579
2573
|
*/
|
|
2580
2574
|
function removeElementFromAncestorsIdMaps(element, ctx) {
|
|
2581
|
-
|
|
2582
|
-
const id = /** @type {String} */ (element.getAttribute("id"));
|
|
2575
|
+
const id = element.id;
|
|
2583
2576
|
/** @ts-ignore - safe to loop in this way **/
|
|
2584
2577
|
while ((element = element.parentNode)) {
|
|
2585
2578
|
let idSet = ctx.idMap.get(element);
|
|
@@ -3017,7 +3010,6 @@ var Idiomorph = (function () {
|
|
|
3017
3010
|
idMap: idMap,
|
|
3018
3011
|
persistentIds: persistentIds,
|
|
3019
3012
|
pantry: createPantry(),
|
|
3020
|
-
activeElementAndParents: createActiveElementAndParents(oldNode),
|
|
3021
3013
|
callbacks: mergedConfig.callbacks,
|
|
3022
3014
|
head: mergedConfig.head,
|
|
3023
3015
|
};
|
|
@@ -3058,24 +3050,6 @@ var Idiomorph = (function () {
|
|
|
3058
3050
|
return pantry;
|
|
3059
3051
|
}
|
|
3060
3052
|
|
|
3061
|
-
/**
|
|
3062
|
-
* @param {Element} oldNode
|
|
3063
|
-
* @returns {Element[]}
|
|
3064
|
-
*/
|
|
3065
|
-
function createActiveElementAndParents(oldNode) {
|
|
3066
|
-
/** @type {Element[]} */
|
|
3067
|
-
let activeElementAndParents = [];
|
|
3068
|
-
let elt = document.activeElement;
|
|
3069
|
-
if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
|
|
3070
|
-
while (elt) {
|
|
3071
|
-
activeElementAndParents.push(elt);
|
|
3072
|
-
if (elt === oldNode) break;
|
|
3073
|
-
elt = elt.parentElement;
|
|
3074
|
-
}
|
|
3075
|
-
}
|
|
3076
|
-
return activeElementAndParents;
|
|
3077
|
-
}
|
|
3078
|
-
|
|
3079
3053
|
/**
|
|
3080
3054
|
* Returns all elements with an ID contained within the root element and its descendants
|
|
3081
3055
|
*
|
|
@@ -3084,8 +3058,7 @@ var Idiomorph = (function () {
|
|
|
3084
3058
|
*/
|
|
3085
3059
|
function findIdElements(root) {
|
|
3086
3060
|
let elements = Array.from(root.querySelectorAll("[id]"));
|
|
3087
|
-
|
|
3088
|
-
if (root.getAttribute?.("id")) {
|
|
3061
|
+
if (root.id) {
|
|
3089
3062
|
elements.push(root);
|
|
3090
3063
|
}
|
|
3091
3064
|
return elements;
|
|
@@ -3104,9 +3077,7 @@ var Idiomorph = (function () {
|
|
|
3104
3077
|
*/
|
|
3105
3078
|
function populateIdMapWithTree(idMap, persistentIds, root, elements) {
|
|
3106
3079
|
for (const elt of elements) {
|
|
3107
|
-
|
|
3108
|
-
const id = /** @type {String} */ (elt.getAttribute("id"));
|
|
3109
|
-
if (persistentIds.has(id)) {
|
|
3080
|
+
if (persistentIds.has(elt.id)) {
|
|
3110
3081
|
/** @type {Element|null} */
|
|
3111
3082
|
let current = elt;
|
|
3112
3083
|
// walk up the parent hierarchy of that element, adding the id
|
|
@@ -3118,7 +3089,7 @@ var Idiomorph = (function () {
|
|
|
3118
3089
|
idSet = new Set();
|
|
3119
3090
|
idMap.set(current, idSet);
|
|
3120
3091
|
}
|
|
3121
|
-
idSet.add(id);
|
|
3092
|
+
idSet.add(elt.id);
|
|
3122
3093
|
|
|
3123
3094
|
if (current === root) break;
|
|
3124
3095
|
current = current.parentElement;
|
|
@@ -3232,9 +3203,8 @@ var Idiomorph = (function () {
|
|
|
3232
3203
|
if (newContent.parentNode) {
|
|
3233
3204
|
// we can't use the parent directly because newContent may have siblings
|
|
3234
3205
|
// that we don't want in the morph, and reparenting might be expensive (TODO is it?),
|
|
3235
|
-
// so
|
|
3236
|
-
|
|
3237
|
-
return /** @type {any} */ (new SlicedParentNode(newContent));
|
|
3206
|
+
// so we create a duck-typed parent node instead.
|
|
3207
|
+
return createDuckTypedParent(newContent);
|
|
3238
3208
|
} else {
|
|
3239
3209
|
// a single node is added as a child to a dummy parent
|
|
3240
3210
|
const dummyParent = document.createElement("div");
|
|
@@ -3253,78 +3223,33 @@ var Idiomorph = (function () {
|
|
|
3253
3223
|
}
|
|
3254
3224
|
|
|
3255
3225
|
/**
|
|
3256
|
-
*
|
|
3257
|
-
* This is useful because the node may have siblings that we don't want in the morph, and it may also be moved
|
|
3258
|
-
* or replaced with one or more elements during the morph. This class effectively allows us a window into
|
|
3259
|
-
* a slice of a node's children.
|
|
3226
|
+
* Creates a fake duck-typed parent element to wrap a single node, without actually reparenting it.
|
|
3260
3227
|
* "If it walks like a duck, and quacks like a duck, then it must be a duck!" -- James Whitcomb Riley (1849–1916)
|
|
3228
|
+
*
|
|
3229
|
+
* @param {Node} newContent
|
|
3230
|
+
* @returns {Element}
|
|
3261
3231
|
*/
|
|
3262
|
-
|
|
3263
|
-
/** @
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
}
|
|
3282
|
-
|
|
3283
|
-
}
|
|
3284
|
-
|
|
3285
|
-
/**
|
|
3286
|
-
* @param {string} selector
|
|
3287
|
-
* @returns {Element[]}
|
|
3288
|
-
*/
|
|
3289
|
-
querySelectorAll(selector) {
|
|
3290
|
-
return this.childNodes.reduce((results, node) => {
|
|
3291
|
-
if (node instanceof Element) {
|
|
3292
|
-
if (node.matches(selector)) results.push(node);
|
|
3293
|
-
const nodeList = node.querySelectorAll(selector);
|
|
3294
|
-
for (let i = 0; i < nodeList.length; i++) {
|
|
3295
|
-
results.push(nodeList[i]);
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
return results;
|
|
3299
|
-
}, /** @type {Element[]} */ ([]));
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
|
-
/**
|
|
3303
|
-
* @param {Node} node
|
|
3304
|
-
* @param {Node} referenceNode
|
|
3305
|
-
* @returns {Node}
|
|
3306
|
-
*/
|
|
3307
|
-
insertBefore(node, referenceNode) {
|
|
3308
|
-
return this.realParentNode.insertBefore(node, referenceNode);
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
|
-
/**
|
|
3312
|
-
* @param {Node} node
|
|
3313
|
-
* @param {Node} referenceNode
|
|
3314
|
-
* @returns {Node}
|
|
3315
|
-
*/
|
|
3316
|
-
moveBefore(node, referenceNode) {
|
|
3317
|
-
// @ts-ignore - use new moveBefore feature
|
|
3318
|
-
return this.realParentNode.moveBefore(node, referenceNode);
|
|
3319
|
-
}
|
|
3320
|
-
|
|
3321
|
-
/**
|
|
3322
|
-
* for later use with populateIdMapWithTree to halt upwards iteration
|
|
3323
|
-
* @returns {Node}
|
|
3324
|
-
*/
|
|
3325
|
-
get __idiomorphRoot() {
|
|
3326
|
-
return this.originalNode;
|
|
3327
|
-
}
|
|
3232
|
+
function createDuckTypedParent(newContent) {
|
|
3233
|
+
return /** @type {Element} */ (
|
|
3234
|
+
/** @type {unknown} */ ({
|
|
3235
|
+
childNodes: [newContent],
|
|
3236
|
+
/** @ts-ignore - cover your eyes for a minute, tsc */
|
|
3237
|
+
querySelectorAll: (s) => {
|
|
3238
|
+
/** @ts-ignore */
|
|
3239
|
+
const elements = newContent.querySelectorAll(s);
|
|
3240
|
+
/** @ts-ignore */
|
|
3241
|
+
return newContent.matches(s) ? [newContent, ...elements] : elements;
|
|
3242
|
+
},
|
|
3243
|
+
/** @ts-ignore */
|
|
3244
|
+
insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r),
|
|
3245
|
+
/** @ts-ignore */
|
|
3246
|
+
moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r),
|
|
3247
|
+
// for later use with populateIdMapWithTree to halt upwards iteration
|
|
3248
|
+
get __idiomorphRoot() {
|
|
3249
|
+
return newContent;
|
|
3250
|
+
},
|
|
3251
|
+
})
|
|
3252
|
+
);
|
|
3328
3253
|
}
|
|
3329
3254
|
|
|
3330
3255
|
/**
|
|
@@ -6185,7 +6110,9 @@ const deprecatedLocationPropertyDescriptors = {
|
|
|
6185
6110
|
};
|
|
6186
6111
|
|
|
6187
6112
|
const session = new Session(recentRequests);
|
|
6188
|
-
|
|
6113
|
+
|
|
6114
|
+
// Rename `navigator` to avoid shadowing `window.navigator`
|
|
6115
|
+
const { cache, navigator: sessionNavigator } = session;
|
|
6189
6116
|
|
|
6190
6117
|
/**
|
|
6191
6118
|
* Starts the main session.
|
|
@@ -6310,14 +6237,14 @@ function morphTurboFrameElements(currentFrame, newFrame) {
|
|
|
6310
6237
|
|
|
6311
6238
|
var Turbo = /*#__PURE__*/Object.freeze({
|
|
6312
6239
|
__proto__: null,
|
|
6313
|
-
navigator: navigator,
|
|
6314
|
-
session: session,
|
|
6315
|
-
cache: cache,
|
|
6316
6240
|
PageRenderer: PageRenderer,
|
|
6317
6241
|
PageSnapshot: PageSnapshot,
|
|
6318
6242
|
FrameRenderer: FrameRenderer,
|
|
6319
6243
|
fetch: fetchWithTurboHeaders,
|
|
6320
6244
|
config: config,
|
|
6245
|
+
session: session,
|
|
6246
|
+
cache: cache,
|
|
6247
|
+
navigator: sessionNavigator,
|
|
6321
6248
|
start: start,
|
|
6322
6249
|
registerAdapter: registerAdapter,
|
|
6323
6250
|
visit: visit,
|
|
@@ -7251,4 +7178,4 @@ if (customElements.get("turbo-stream-source") === undefined) {
|
|
|
7251
7178
|
window.Turbo = { ...Turbo, StreamActions };
|
|
7252
7179
|
start();
|
|
7253
7180
|
|
|
7254
|
-
export { FetchEnctype, FetchMethod, FetchRequest, FetchResponse, FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, config, connectStreamSource, disconnectStreamSource, fetchWithTurboHeaders as fetch, fetchEnctypeFromString, fetchMethodFromString, isSafe, morphBodyElements, morphChildren, morphElements, morphTurboFrameElements, navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };
|
|
7181
|
+
export { FetchEnctype, FetchMethod, FetchRequest, FetchResponse, FrameElement, FrameLoadingStyle, FrameRenderer, PageRenderer, PageSnapshot, StreamActions, StreamElement, StreamSourceElement, cache, config, connectStreamSource, disconnectStreamSource, fetchWithTurboHeaders as fetch, fetchEnctypeFromString, fetchMethodFromString, isSafe, morphBodyElements, morphChildren, morphElements, morphTurboFrameElements, sessionNavigator as navigator, registerAdapter, renderStreamMessage, session, setConfirmMethod, setFormMode, setProgressBarDelay, start, visit };
|
package/dist/turbo.es2017-umd.js
CHANGED
|
@@ -2125,7 +2125,6 @@ Copyright © 2026 37signals LLC
|
|
|
2125
2125
|
* @property {ConfigInternal['callbacks']} callbacks
|
|
2126
2126
|
* @property {ConfigInternal['head']} head
|
|
2127
2127
|
* @property {HTMLDivElement} pantry
|
|
2128
|
-
* @property {Element[]} activeElementAndParents
|
|
2129
2128
|
*/
|
|
2130
2129
|
|
|
2131
2130
|
//=============================================================================
|
|
@@ -2201,6 +2200,14 @@ Copyright © 2026 37signals LLC
|
|
|
2201
2200
|
*/
|
|
2202
2201
|
function morphOuterHTML(ctx, oldNode, newNode) {
|
|
2203
2202
|
const oldParent = normalizeParent(oldNode);
|
|
2203
|
+
|
|
2204
|
+
// basis for calulating which nodes were morphed
|
|
2205
|
+
// since there may be unmorphed sibling nodes
|
|
2206
|
+
let childNodes = Array.from(oldParent.childNodes);
|
|
2207
|
+
const index = childNodes.indexOf(oldNode);
|
|
2208
|
+
// how many elements are to the right of the oldNode
|
|
2209
|
+
const rightMargin = childNodes.length - (index + 1);
|
|
2210
|
+
|
|
2204
2211
|
morphChildren(
|
|
2205
2212
|
ctx,
|
|
2206
2213
|
oldParent,
|
|
@@ -2209,8 +2216,10 @@ Copyright © 2026 37signals LLC
|
|
|
2209
2216
|
oldNode, // start point for iteration
|
|
2210
2217
|
oldNode.nextSibling, // end point for iteration
|
|
2211
2218
|
);
|
|
2212
|
-
|
|
2213
|
-
return
|
|
2219
|
+
|
|
2220
|
+
// return just the morphed nodes
|
|
2221
|
+
childNodes = Array.from(oldParent.childNodes);
|
|
2222
|
+
return childNodes.slice(index, childNodes.length - rightMargin);
|
|
2214
2223
|
}
|
|
2215
2224
|
|
|
2216
2225
|
/**
|
|
@@ -2239,11 +2248,8 @@ Copyright © 2026 37signals LLC
|
|
|
2239
2248
|
|
|
2240
2249
|
const results = fn();
|
|
2241
2250
|
|
|
2242
|
-
if (
|
|
2243
|
-
activeElementId
|
|
2244
|
-
activeElementId !== document.activeElement?.getAttribute("id")
|
|
2245
|
-
) {
|
|
2246
|
-
activeElement = ctx.target.querySelector(`[id="${activeElementId}"]`);
|
|
2251
|
+
if (activeElementId && activeElementId !== document.activeElement?.id) {
|
|
2252
|
+
activeElement = ctx.target.querySelector(`#${activeElementId}`);
|
|
2247
2253
|
activeElement?.focus();
|
|
2248
2254
|
}
|
|
2249
2255
|
if (activeElement && !activeElement.selectionEnd && selectionEnd) {
|
|
@@ -2321,23 +2327,17 @@ Copyright © 2026 37signals LLC
|
|
|
2321
2327
|
}
|
|
2322
2328
|
|
|
2323
2329
|
// if the matching node is elsewhere in the original content
|
|
2324
|
-
if (newChild instanceof Element) {
|
|
2325
|
-
//
|
|
2326
|
-
const
|
|
2327
|
-
|
|
2330
|
+
if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) {
|
|
2331
|
+
// move it and all its children here and morph
|
|
2332
|
+
const movedChild = moveBeforeById(
|
|
2333
|
+
oldParent,
|
|
2334
|
+
newChild.id,
|
|
2335
|
+
insertionPoint,
|
|
2336
|
+
ctx,
|
|
2328
2337
|
);
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
oldParent,
|
|
2333
|
-
newChildId,
|
|
2334
|
-
insertionPoint,
|
|
2335
|
-
ctx,
|
|
2336
|
-
);
|
|
2337
|
-
morphNode(movedChild, newChild, ctx);
|
|
2338
|
-
insertionPoint = movedChild.nextSibling;
|
|
2339
|
-
continue;
|
|
2340
|
-
}
|
|
2338
|
+
morphNode(movedChild, newChild, ctx);
|
|
2339
|
+
insertionPoint = movedChild.nextSibling;
|
|
2340
|
+
continue;
|
|
2341
2341
|
}
|
|
2342
2342
|
|
|
2343
2343
|
// last resort: insert the new node from scratch
|
|
@@ -2447,8 +2447,7 @@ Copyright © 2026 37signals LLC
|
|
|
2447
2447
|
|
|
2448
2448
|
// if the current node contains active element, stop looking for better future matches,
|
|
2449
2449
|
// because if one is found, this node will be moved to the pantry, reparenting it and thus losing focus
|
|
2450
|
-
|
|
2451
|
-
if (ctx.activeElementAndParents.includes(cursor)) break;
|
|
2450
|
+
if (cursor.contains(document.activeElement)) break;
|
|
2452
2451
|
|
|
2453
2452
|
cursor = cursor.nextSibling;
|
|
2454
2453
|
}
|
|
@@ -2498,9 +2497,7 @@ Copyright © 2026 37signals LLC
|
|
|
2498
2497
|
// If oldElt has an `id` with possible state and it doesn't match newElt.id then avoid morphing.
|
|
2499
2498
|
// We'll still match an anonymous node with an IDed newElt, though, because if it got this far,
|
|
2500
2499
|
// its not persistent, and new nodes can't have any hidden state.
|
|
2501
|
-
|
|
2502
|
-
(!oldElt.getAttribute?.("id") ||
|
|
2503
|
-
oldElt.getAttribute?.("id") === newElt.getAttribute?.("id"))
|
|
2500
|
+
(!oldElt.id || oldElt.id === newElt.id)
|
|
2504
2501
|
);
|
|
2505
2502
|
}
|
|
2506
2503
|
|
|
@@ -2564,11 +2561,8 @@ Copyright © 2026 37signals LLC
|
|
|
2564
2561
|
const target =
|
|
2565
2562
|
/** @type {Element} - will always be found */
|
|
2566
2563
|
(
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
(ctx.target.getAttribute?.("id") === id && ctx.target) ||
|
|
2570
|
-
ctx.target.querySelector(`[id="${id}"]`) ||
|
|
2571
|
-
ctx.pantry.querySelector(`[id="${id}"]`)
|
|
2564
|
+
ctx.target.querySelector(`#${id}`) ||
|
|
2565
|
+
ctx.pantry.querySelector(`#${id}`)
|
|
2572
2566
|
);
|
|
2573
2567
|
removeElementFromAncestorsIdMaps(target, ctx);
|
|
2574
2568
|
moveBefore(parentNode, target, after);
|
|
@@ -2584,8 +2578,7 @@ Copyright © 2026 37signals LLC
|
|
|
2584
2578
|
* @param {MorphContext} ctx
|
|
2585
2579
|
*/
|
|
2586
2580
|
function removeElementFromAncestorsIdMaps(element, ctx) {
|
|
2587
|
-
|
|
2588
|
-
const id = /** @type {String} */ (element.getAttribute("id"));
|
|
2581
|
+
const id = element.id;
|
|
2589
2582
|
/** @ts-ignore - safe to loop in this way **/
|
|
2590
2583
|
while ((element = element.parentNode)) {
|
|
2591
2584
|
let idSet = ctx.idMap.get(element);
|
|
@@ -3023,7 +3016,6 @@ Copyright © 2026 37signals LLC
|
|
|
3023
3016
|
idMap: idMap,
|
|
3024
3017
|
persistentIds: persistentIds,
|
|
3025
3018
|
pantry: createPantry(),
|
|
3026
|
-
activeElementAndParents: createActiveElementAndParents(oldNode),
|
|
3027
3019
|
callbacks: mergedConfig.callbacks,
|
|
3028
3020
|
head: mergedConfig.head,
|
|
3029
3021
|
};
|
|
@@ -3064,24 +3056,6 @@ Copyright © 2026 37signals LLC
|
|
|
3064
3056
|
return pantry;
|
|
3065
3057
|
}
|
|
3066
3058
|
|
|
3067
|
-
/**
|
|
3068
|
-
* @param {Element} oldNode
|
|
3069
|
-
* @returns {Element[]}
|
|
3070
|
-
*/
|
|
3071
|
-
function createActiveElementAndParents(oldNode) {
|
|
3072
|
-
/** @type {Element[]} */
|
|
3073
|
-
let activeElementAndParents = [];
|
|
3074
|
-
let elt = document.activeElement;
|
|
3075
|
-
if (elt?.tagName !== "BODY" && oldNode.contains(elt)) {
|
|
3076
|
-
while (elt) {
|
|
3077
|
-
activeElementAndParents.push(elt);
|
|
3078
|
-
if (elt === oldNode) break;
|
|
3079
|
-
elt = elt.parentElement;
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
return activeElementAndParents;
|
|
3083
|
-
}
|
|
3084
|
-
|
|
3085
3059
|
/**
|
|
3086
3060
|
* Returns all elements with an ID contained within the root element and its descendants
|
|
3087
3061
|
*
|
|
@@ -3090,8 +3064,7 @@ Copyright © 2026 37signals LLC
|
|
|
3090
3064
|
*/
|
|
3091
3065
|
function findIdElements(root) {
|
|
3092
3066
|
let elements = Array.from(root.querySelectorAll("[id]"));
|
|
3093
|
-
|
|
3094
|
-
if (root.getAttribute?.("id")) {
|
|
3067
|
+
if (root.id) {
|
|
3095
3068
|
elements.push(root);
|
|
3096
3069
|
}
|
|
3097
3070
|
return elements;
|
|
@@ -3110,9 +3083,7 @@ Copyright © 2026 37signals LLC
|
|
|
3110
3083
|
*/
|
|
3111
3084
|
function populateIdMapWithTree(idMap, persistentIds, root, elements) {
|
|
3112
3085
|
for (const elt of elements) {
|
|
3113
|
-
|
|
3114
|
-
const id = /** @type {String} */ (elt.getAttribute("id"));
|
|
3115
|
-
if (persistentIds.has(id)) {
|
|
3086
|
+
if (persistentIds.has(elt.id)) {
|
|
3116
3087
|
/** @type {Element|null} */
|
|
3117
3088
|
let current = elt;
|
|
3118
3089
|
// walk up the parent hierarchy of that element, adding the id
|
|
@@ -3124,7 +3095,7 @@ Copyright © 2026 37signals LLC
|
|
|
3124
3095
|
idSet = new Set();
|
|
3125
3096
|
idMap.set(current, idSet);
|
|
3126
3097
|
}
|
|
3127
|
-
idSet.add(id);
|
|
3098
|
+
idSet.add(elt.id);
|
|
3128
3099
|
|
|
3129
3100
|
if (current === root) break;
|
|
3130
3101
|
current = current.parentElement;
|
|
@@ -3238,9 +3209,8 @@ Copyright © 2026 37signals LLC
|
|
|
3238
3209
|
if (newContent.parentNode) {
|
|
3239
3210
|
// we can't use the parent directly because newContent may have siblings
|
|
3240
3211
|
// that we don't want in the morph, and reparenting might be expensive (TODO is it?),
|
|
3241
|
-
// so
|
|
3242
|
-
|
|
3243
|
-
return /** @type {any} */ (new SlicedParentNode(newContent));
|
|
3212
|
+
// so we create a duck-typed parent node instead.
|
|
3213
|
+
return createDuckTypedParent(newContent);
|
|
3244
3214
|
} else {
|
|
3245
3215
|
// a single node is added as a child to a dummy parent
|
|
3246
3216
|
const dummyParent = document.createElement("div");
|
|
@@ -3259,78 +3229,33 @@ Copyright © 2026 37signals LLC
|
|
|
3259
3229
|
}
|
|
3260
3230
|
|
|
3261
3231
|
/**
|
|
3262
|
-
*
|
|
3263
|
-
* This is useful because the node may have siblings that we don't want in the morph, and it may also be moved
|
|
3264
|
-
* or replaced with one or more elements during the morph. This class effectively allows us a window into
|
|
3265
|
-
* a slice of a node's children.
|
|
3232
|
+
* Creates a fake duck-typed parent element to wrap a single node, without actually reparenting it.
|
|
3266
3233
|
* "If it walks like a duck, and quacks like a duck, then it must be a duck!" -- James Whitcomb Riley (1849–1916)
|
|
3234
|
+
*
|
|
3235
|
+
* @param {Node} newContent
|
|
3236
|
+
* @returns {Element}
|
|
3267
3237
|
*/
|
|
3268
|
-
|
|
3269
|
-
/** @
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
}
|
|
3288
|
-
|
|
3289
|
-
}
|
|
3290
|
-
|
|
3291
|
-
/**
|
|
3292
|
-
* @param {string} selector
|
|
3293
|
-
* @returns {Element[]}
|
|
3294
|
-
*/
|
|
3295
|
-
querySelectorAll(selector) {
|
|
3296
|
-
return this.childNodes.reduce((results, node) => {
|
|
3297
|
-
if (node instanceof Element) {
|
|
3298
|
-
if (node.matches(selector)) results.push(node);
|
|
3299
|
-
const nodeList = node.querySelectorAll(selector);
|
|
3300
|
-
for (let i = 0; i < nodeList.length; i++) {
|
|
3301
|
-
results.push(nodeList[i]);
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
return results;
|
|
3305
|
-
}, /** @type {Element[]} */ ([]));
|
|
3306
|
-
}
|
|
3307
|
-
|
|
3308
|
-
/**
|
|
3309
|
-
* @param {Node} node
|
|
3310
|
-
* @param {Node} referenceNode
|
|
3311
|
-
* @returns {Node}
|
|
3312
|
-
*/
|
|
3313
|
-
insertBefore(node, referenceNode) {
|
|
3314
|
-
return this.realParentNode.insertBefore(node, referenceNode);
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
/**
|
|
3318
|
-
* @param {Node} node
|
|
3319
|
-
* @param {Node} referenceNode
|
|
3320
|
-
* @returns {Node}
|
|
3321
|
-
*/
|
|
3322
|
-
moveBefore(node, referenceNode) {
|
|
3323
|
-
// @ts-ignore - use new moveBefore feature
|
|
3324
|
-
return this.realParentNode.moveBefore(node, referenceNode);
|
|
3325
|
-
}
|
|
3326
|
-
|
|
3327
|
-
/**
|
|
3328
|
-
* for later use with populateIdMapWithTree to halt upwards iteration
|
|
3329
|
-
* @returns {Node}
|
|
3330
|
-
*/
|
|
3331
|
-
get __idiomorphRoot() {
|
|
3332
|
-
return this.originalNode;
|
|
3333
|
-
}
|
|
3238
|
+
function createDuckTypedParent(newContent) {
|
|
3239
|
+
return /** @type {Element} */ (
|
|
3240
|
+
/** @type {unknown} */ ({
|
|
3241
|
+
childNodes: [newContent],
|
|
3242
|
+
/** @ts-ignore - cover your eyes for a minute, tsc */
|
|
3243
|
+
querySelectorAll: (s) => {
|
|
3244
|
+
/** @ts-ignore */
|
|
3245
|
+
const elements = newContent.querySelectorAll(s);
|
|
3246
|
+
/** @ts-ignore */
|
|
3247
|
+
return newContent.matches(s) ? [newContent, ...elements] : elements;
|
|
3248
|
+
},
|
|
3249
|
+
/** @ts-ignore */
|
|
3250
|
+
insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r),
|
|
3251
|
+
/** @ts-ignore */
|
|
3252
|
+
moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r),
|
|
3253
|
+
// for later use with populateIdMapWithTree to halt upwards iteration
|
|
3254
|
+
get __idiomorphRoot() {
|
|
3255
|
+
return newContent;
|
|
3256
|
+
},
|
|
3257
|
+
})
|
|
3258
|
+
);
|
|
3334
3259
|
}
|
|
3335
3260
|
|
|
3336
3261
|
/**
|
|
@@ -6191,7 +6116,9 @@ Copyright © 2026 37signals LLC
|
|
|
6191
6116
|
};
|
|
6192
6117
|
|
|
6193
6118
|
const session = new Session(recentRequests);
|
|
6194
|
-
|
|
6119
|
+
|
|
6120
|
+
// Rename `navigator` to avoid shadowing `window.navigator`
|
|
6121
|
+
const { cache, navigator: sessionNavigator } = session;
|
|
6195
6122
|
|
|
6196
6123
|
/**
|
|
6197
6124
|
* Starts the main session.
|
|
@@ -6316,14 +6243,14 @@ Copyright © 2026 37signals LLC
|
|
|
6316
6243
|
|
|
6317
6244
|
var Turbo = /*#__PURE__*/Object.freeze({
|
|
6318
6245
|
__proto__: null,
|
|
6319
|
-
navigator: navigator,
|
|
6320
|
-
session: session,
|
|
6321
|
-
cache: cache,
|
|
6322
6246
|
PageRenderer: PageRenderer,
|
|
6323
6247
|
PageSnapshot: PageSnapshot,
|
|
6324
6248
|
FrameRenderer: FrameRenderer,
|
|
6325
6249
|
fetch: fetchWithTurboHeaders,
|
|
6326
6250
|
config: config,
|
|
6251
|
+
session: session,
|
|
6252
|
+
cache: cache,
|
|
6253
|
+
navigator: sessionNavigator,
|
|
6327
6254
|
start: start,
|
|
6328
6255
|
registerAdapter: registerAdapter,
|
|
6329
6256
|
visit: visit,
|
|
@@ -7281,7 +7208,7 @@ Copyright © 2026 37signals LLC
|
|
|
7281
7208
|
exports.morphChildren = morphChildren;
|
|
7282
7209
|
exports.morphElements = morphElements;
|
|
7283
7210
|
exports.morphTurboFrameElements = morphTurboFrameElements;
|
|
7284
|
-
exports.navigator =
|
|
7211
|
+
exports.navigator = sessionNavigator;
|
|
7285
7212
|
exports.registerAdapter = registerAdapter;
|
|
7286
7213
|
exports.renderStreamMessage = renderStreamMessage;
|
|
7287
7214
|
exports.session = session;
|
package/package.json
CHANGED