@sveltejs/kit 3.0.0-next.24 → 3.0.0-next.27
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/package.json +16 -10
- package/src/constants.js +6 -0
- package/src/core/adapt/builder.js +128 -33
- package/src/core/adapt/index.js +3 -0
- package/src/core/config/index.js +4 -7
- package/src/core/env.js +39 -14
- package/src/core/generate_manifest/index.js +42 -43
- package/src/core/postbuild/analyse.js +4 -4
- package/src/core/postbuild/fallback.js +1 -1
- package/src/core/postbuild/prerender.js +4 -4
- package/src/core/sync/create_manifest_data/index.js +0 -1
- package/src/core/sync/write_app_manifest.js +3 -2
- package/src/core/sync/write_server.js +3 -17
- package/src/core/utils.js +10 -0
- package/src/exports/adapter.js +22 -0
- package/src/exports/public.d.ts +101 -49
- package/src/exports/vite/build/index.js +1173 -0
- package/src/exports/vite/build/remote.js +4 -4
- package/src/exports/vite/build/service-worker.js +98 -0
- package/src/exports/vite/dev/generate_manifest.js +308 -0
- package/src/exports/vite/dev/index.js +40 -282
- package/src/exports/vite/index.js +156 -1717
- package/src/exports/vite/plugins/env-vars.js +53 -34
- package/src/exports/vite/plugins/guard.js +217 -0
- package/src/exports/vite/plugins/remote.js +237 -0
- package/src/exports/vite/preview/index.js +8 -8
- package/src/exports/vite/static_analysis/index.js +15 -15
- package/src/exports/vite/utils.js +98 -1
- package/src/runner.js +4 -3
- package/src/runtime/app/paths/server.js +2 -2
- package/src/runtime/app/server/index.js +3 -3
- package/src/runtime/app/server/public.d.ts +114 -57
- package/src/runtime/app/server/remote/query.js +2 -2
- package/src/runtime/app/server/remote/requested.js +31 -15
- package/src/runtime/app/state/client.svelte.js +3 -1
- package/src/runtime/client/client.js +43 -198
- package/src/runtime/client/fetcher.js +6 -6
- package/src/runtime/client/focus.js +120 -0
- package/src/runtime/client/remote-functions/command.svelte.js +20 -9
- package/src/runtime/client/remote-functions/form.svelte.js +12 -7
- package/src/runtime/client/remote-functions/query/instance.svelte.js +19 -5
- package/src/runtime/client/remote-functions/shared.svelte.js +22 -1
- package/src/runtime/client/scroll.js +39 -0
- package/src/runtime/client/utils.js +28 -0
- package/src/runtime/form-utils.js +254 -264
- package/src/runtime/server/data/index.js +14 -36
- package/src/runtime/server/errors.js +7 -10
- package/src/runtime/server/fetch.js +21 -25
- package/src/runtime/server/index.js +35 -31
- package/src/runtime/server/internal.js +34 -5
- package/src/runtime/server/page/actions.js +6 -8
- package/src/runtime/server/page/data_serializer.js +6 -10
- package/src/runtime/server/page/index.js +18 -28
- package/src/runtime/server/page/load_data.js +2 -2
- package/src/runtime/server/page/render.js +22 -40
- package/src/runtime/server/page/respond_with_error.js +10 -13
- package/src/runtime/server/page/server_routing.js +21 -27
- package/src/runtime/server/remote-functions.js +136 -136
- package/src/runtime/server/respond.js +66 -64
- package/src/runtime/server/state.js +2 -0
- package/src/runtime/server/utils.js +4 -11
- package/src/runtime/shared.js +9 -0
- package/src/runtime/utils.js +41 -0
- package/src/types/ambient-private.d.ts +1 -2
- package/src/types/global-private.d.ts +8 -0
- package/src/types/internal.d.ts +56 -17
- package/src/utils/filesystem.js +44 -28
- package/src/utils/streaming.js +5 -15
- package/src/version.js +1 -1
- package/types/index.d.ts +235 -93
- package/types/index.d.ts.map +9 -2
|
@@ -13,6 +13,8 @@ import { decode_pathname, strip_hash, make_trackable, normalize_path } from '../
|
|
|
13
13
|
import { dev_fetch, initial_fetch, lock_fetch, subsequent_fetch, unlock_fetch } from './fetcher.js';
|
|
14
14
|
import { parse_routes, parse_server_route } from './parse.js';
|
|
15
15
|
import * as storage from './session-storage.js';
|
|
16
|
+
import { blur_active_element, is_resetting_focus, reset_focus } from './focus.js';
|
|
17
|
+
import { disable_scroll_handling, reset_scroll_and_focus } from './scroll.js';
|
|
16
18
|
import {
|
|
17
19
|
find_anchor,
|
|
18
20
|
resolve_url,
|
|
@@ -47,7 +49,8 @@ import {
|
|
|
47
49
|
TRAILING_SLASH_PARAM,
|
|
48
50
|
create_remote_key,
|
|
49
51
|
validate_depends,
|
|
50
|
-
validate_load_response
|
|
52
|
+
validate_load_response,
|
|
53
|
+
fetch_cache_url
|
|
51
54
|
} from '../shared.js';
|
|
52
55
|
|
|
53
56
|
import { page, updated, notify_version, update_page, set_navigation } from '#app/state/client';
|
|
@@ -170,47 +173,6 @@ function set_history_options(index, options) {
|
|
|
170
173
|
};
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
/** @param {boolean} reset */
|
|
174
|
-
function blur_active_element(reset) {
|
|
175
|
-
if (
|
|
176
|
-
reset &&
|
|
177
|
-
document.activeElement instanceof HTMLElement &&
|
|
178
|
-
document.activeElement !== document.body
|
|
179
|
-
) {
|
|
180
|
-
document.activeElement.blur();
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* @param {URL} url
|
|
186
|
-
* @param {{ x: number; y: number } | null | undefined} scroll
|
|
187
|
-
* @param {boolean} reset
|
|
188
|
-
* @param {Element | null} active_element
|
|
189
|
-
*/
|
|
190
|
-
function reset_scroll_and_focus(url, scroll, reset, active_element) {
|
|
191
|
-
/** @type {Element | null} */
|
|
192
|
-
let deep_linked = null;
|
|
193
|
-
|
|
194
|
-
if (autoscroll) {
|
|
195
|
-
if (scroll) {
|
|
196
|
-
scrollTo(scroll.x, scroll.y);
|
|
197
|
-
} else if ((deep_linked = get_hash_element(url))) {
|
|
198
|
-
deep_linked.scrollIntoView();
|
|
199
|
-
} else {
|
|
200
|
-
scrollTo(0, 0);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const changed_focus =
|
|
205
|
-
document.activeElement !== active_element && document.activeElement !== document.body;
|
|
206
|
-
|
|
207
|
-
if (reset && !changed_focus) {
|
|
208
|
-
reset_focus(url, !deep_linked);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
autoscroll = true;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
176
|
/**
|
|
215
177
|
* @param {number} current_history_index
|
|
216
178
|
* @param {number} current_navigation_index
|
|
@@ -372,7 +334,6 @@ let current = {
|
|
|
372
334
|
/** this being true means we SSR'd */
|
|
373
335
|
let hydrated = false;
|
|
374
336
|
let started = false;
|
|
375
|
-
let autoscroll = true;
|
|
376
337
|
let updating = false;
|
|
377
338
|
let is_navigating = false;
|
|
378
339
|
/** @type {HistoryMetadata | null} */
|
|
@@ -615,7 +576,7 @@ async function _invalidate(reset_page_state = true) {
|
|
|
615
576
|
|
|
616
577
|
const token = (invalidation_token = {});
|
|
617
578
|
const nav_token = navigation_token;
|
|
618
|
-
const
|
|
579
|
+
const prev_current = current;
|
|
619
580
|
const intent = await get_navigation_intent(current.url, true);
|
|
620
581
|
|
|
621
582
|
// Clear preload, it might be affected by the invalidation.
|
|
@@ -655,9 +616,9 @@ async function _invalidate(reset_page_state = true) {
|
|
|
655
616
|
);
|
|
656
617
|
}
|
|
657
618
|
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
if (
|
|
619
|
+
// a navigation applied its result while the invalidation was loading,
|
|
620
|
+
// so the invalidation contains outdated data for a page we are no longer on
|
|
621
|
+
if (current !== prev_current) {
|
|
661
622
|
return;
|
|
662
623
|
}
|
|
663
624
|
|
|
@@ -1309,10 +1270,7 @@ function resolve_fetch_url(input, init, url) {
|
|
|
1309
1270
|
// we must fixup relative urls so they are resolved from the target page
|
|
1310
1271
|
const resolved = new URL(input instanceof Request ? input.url : input, url);
|
|
1311
1272
|
|
|
1312
|
-
|
|
1313
|
-
// urls, so prerendered pages can be served from any origin, the normalized href otherwise
|
|
1314
|
-
const requested =
|
|
1315
|
-
resolved.origin === url.origin ? resolved.href.slice(url.origin.length) : resolved.href;
|
|
1273
|
+
const requested = fetch_cache_url(resolved, url);
|
|
1316
1274
|
|
|
1317
1275
|
const promise = started
|
|
1318
1276
|
? subsequent_fetch(requested, resolved.href, init)
|
|
@@ -2072,6 +2030,9 @@ async function navigate({
|
|
|
2072
2030
|
if (navigation_result.type === 'redirect') {
|
|
2073
2031
|
// whatwg fetch spec https://fetch.spec.whatwg.org/#http-redirect-fetch says to error after 20 redirects
|
|
2074
2032
|
if (redirect_count < 20) {
|
|
2033
|
+
// a preloaded redirect has been consumed; a later hop back to this route must load afresh
|
|
2034
|
+
if (load_cache?.id === intent?.id) discard_load_cache();
|
|
2035
|
+
|
|
2075
2036
|
await navigate({
|
|
2076
2037
|
type,
|
|
2077
2038
|
url: new URL(navigation_result.location, url),
|
|
@@ -2113,12 +2074,15 @@ async function navigate({
|
|
|
2113
2074
|
|
|
2114
2075
|
updating = true;
|
|
2115
2076
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2077
|
+
if (!popped) {
|
|
2078
|
+
// popstate captures the source entry synchronously, before the traversal is resolved
|
|
2079
|
+
capture_scroll(previous_history_index);
|
|
2080
|
+
capture_snapshot(previous_navigation_index);
|
|
2081
|
+
if (replace_state) {
|
|
2082
|
+
delete_navigation_snapshot(previous_history_index);
|
|
2083
|
+
} else {
|
|
2084
|
+
capture_navigation_snapshot(previous_history_index);
|
|
2085
|
+
}
|
|
2122
2086
|
}
|
|
2123
2087
|
|
|
2124
2088
|
// ensure the url pathname matches the page's trailing slash option
|
|
@@ -2617,7 +2581,7 @@ export function disableScrollHandling() {
|
|
|
2617
2581
|
}
|
|
2618
2582
|
|
|
2619
2583
|
if (updating || !started) {
|
|
2620
|
-
|
|
2584
|
+
disable_scroll_handling();
|
|
2621
2585
|
}
|
|
2622
2586
|
}
|
|
2623
2587
|
|
|
@@ -3374,7 +3338,7 @@ function _start_router() {
|
|
|
3374
3338
|
});
|
|
3375
3339
|
|
|
3376
3340
|
addEventListener('popstate', async (event) => {
|
|
3377
|
-
if (
|
|
3341
|
+
if (is_resetting_focus()) return;
|
|
3378
3342
|
|
|
3379
3343
|
const history_metadata = get_history_metadata(event.state);
|
|
3380
3344
|
|
|
@@ -3404,9 +3368,25 @@ function _start_router() {
|
|
|
3404
3368
|
(history_metadata.pageUrl === undefined || history_metadata.pageUrl === location.href)) ||
|
|
3405
3369
|
is_hash_change);
|
|
3406
3370
|
const shallow_url = history_metadata.pageUrl ? new URL(location.href) : null;
|
|
3371
|
+
|
|
3372
|
+
// the browser has already traversed, so record the source entry and move the
|
|
3373
|
+
// indices before anything async: a navigation that starts while this one
|
|
3374
|
+
// resolves must build on the entry we are actually on
|
|
3375
|
+
const previous_history_index = current_history_index;
|
|
3376
|
+
const previous_navigation_index = current_navigation_index;
|
|
3377
|
+
const previous_reset_index = current_reset_index;
|
|
3378
|
+
capture_scroll(previous_history_index);
|
|
3379
|
+
if (!shallow) capture_snapshot(previous_navigation_index);
|
|
3380
|
+
capture_navigation_snapshot(previous_history_index);
|
|
3381
|
+
current_history_index = history_index;
|
|
3382
|
+
current_navigation_index = navigation_index;
|
|
3383
|
+
current_reset_index = reset_index;
|
|
3384
|
+
|
|
3385
|
+
const token = navigation_token;
|
|
3407
3386
|
const shallow_intent = shallow_url
|
|
3408
3387
|
? await get_navigation_intent(shallow_url, false)
|
|
3409
3388
|
: undefined;
|
|
3389
|
+
if (navigation_token !== token) return;
|
|
3410
3390
|
const shallow_target = shallow_url
|
|
3411
3391
|
? {
|
|
3412
3392
|
params: shallow_intent?.params ?? null,
|
|
@@ -3430,10 +3410,6 @@ function _start_router() {
|
|
|
3430
3410
|
|
|
3431
3411
|
update_url(url);
|
|
3432
3412
|
|
|
3433
|
-
capture_scroll(current_history_index);
|
|
3434
|
-
capture_navigation_snapshot(current_history_index);
|
|
3435
|
-
current_history_index = history_index;
|
|
3436
|
-
current_reset_index = reset_index;
|
|
3437
3413
|
if (reset && scroll) scrollTo(scroll.x, scroll.y);
|
|
3438
3414
|
restore_navigation_snapshot(current_history_index, current_registrations());
|
|
3439
3415
|
return;
|
|
@@ -3449,15 +3425,13 @@ function _start_router() {
|
|
|
3449
3425
|
delta,
|
|
3450
3426
|
shallow: shallow_target
|
|
3451
3427
|
},
|
|
3452
|
-
accept: () => {
|
|
3453
|
-
current_history_index = history_index;
|
|
3454
|
-
current_navigation_index = navigation_index;
|
|
3455
|
-
current_reset_index = reset_index;
|
|
3456
|
-
},
|
|
3457
3428
|
block: () => {
|
|
3429
|
+
current_history_index = previous_history_index;
|
|
3430
|
+
current_navigation_index = previous_navigation_index;
|
|
3431
|
+
current_reset_index = previous_reset_index;
|
|
3458
3432
|
history.go(-delta);
|
|
3459
3433
|
},
|
|
3460
|
-
nav_token:
|
|
3434
|
+
nav_token: token,
|
|
3461
3435
|
event
|
|
3462
3436
|
});
|
|
3463
3437
|
} else {
|
|
@@ -3763,109 +3737,6 @@ function deserialize_uses(uses) {
|
|
|
3763
3737
|
};
|
|
3764
3738
|
}
|
|
3765
3739
|
|
|
3766
|
-
/**
|
|
3767
|
-
* This flag is used to avoid client-side navigation when we're only using
|
|
3768
|
-
* `location.replace()` to set focus.
|
|
3769
|
-
*/
|
|
3770
|
-
let resetting_focus = false;
|
|
3771
|
-
|
|
3772
|
-
/**
|
|
3773
|
-
* @param {URL} url
|
|
3774
|
-
* @param {boolean} [scroll]
|
|
3775
|
-
*/
|
|
3776
|
-
function reset_focus(url, scroll = true) {
|
|
3777
|
-
const autofocus = document.querySelector('[autofocus]');
|
|
3778
|
-
if (autofocus) {
|
|
3779
|
-
// @ts-ignore
|
|
3780
|
-
autofocus.focus();
|
|
3781
|
-
} else {
|
|
3782
|
-
// Reset page selection and focus
|
|
3783
|
-
|
|
3784
|
-
// Mimic the browsers' behaviour and set the sequential focus navigation
|
|
3785
|
-
// starting point to the fragment identifier.
|
|
3786
|
-
const element = get_hash_element(url);
|
|
3787
|
-
if (element) {
|
|
3788
|
-
const { x, y } = scroll_state();
|
|
3789
|
-
|
|
3790
|
-
// `element.focus()` doesn't work on Safari and Firefox Ubuntu so we need
|
|
3791
|
-
// to use this hack with `location.replace()` instead.
|
|
3792
|
-
setTimeout(() => {
|
|
3793
|
-
const history_state = history.state;
|
|
3794
|
-
|
|
3795
|
-
resetting_focus = true;
|
|
3796
|
-
location.replace(new URL(`#${element.id}`, location.href));
|
|
3797
|
-
|
|
3798
|
-
// Firefox has a bug that sets the history state to `null` so we need to
|
|
3799
|
-
// restore it after. See https://bugzilla.mozilla.org/show_bug.cgi?id=1199924
|
|
3800
|
-
// This is also needed to restore the original hash if we're using hash routing
|
|
3801
|
-
history.replaceState(history_state, '', url);
|
|
3802
|
-
|
|
3803
|
-
// If scroll management has already happened earlier, we need to restore
|
|
3804
|
-
// the scroll position after setting the sequential focus navigation starting point
|
|
3805
|
-
if (scroll) scrollTo(x, y);
|
|
3806
|
-
resetting_focus = false;
|
|
3807
|
-
});
|
|
3808
|
-
} else {
|
|
3809
|
-
// If the ID doesn't exist, we try to mimic browsers' behaviour as closely
|
|
3810
|
-
// as possible by targeting the first scrollable region. Unfortunately, it's
|
|
3811
|
-
// not a perfect match — e.g. shift-tabbing won't immediately cycle up from
|
|
3812
|
-
// the end of the page on Chromium
|
|
3813
|
-
// See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
|
|
3814
|
-
const root = document.body;
|
|
3815
|
-
const tabindex = root.getAttribute('tabindex');
|
|
3816
|
-
|
|
3817
|
-
root.tabIndex = -1;
|
|
3818
|
-
root.focus({ preventScroll: true, focusVisible: false });
|
|
3819
|
-
|
|
3820
|
-
// restore `tabindex` as to prevent `root` from stealing input from elements
|
|
3821
|
-
if (tabindex !== null) {
|
|
3822
|
-
root.setAttribute('tabindex', tabindex);
|
|
3823
|
-
} else {
|
|
3824
|
-
root.removeAttribute('tabindex');
|
|
3825
|
-
}
|
|
3826
|
-
}
|
|
3827
|
-
|
|
3828
|
-
// capture current selection, so we can compare the state after
|
|
3829
|
-
// snapshot restoration and afterNavigate callbacks have run
|
|
3830
|
-
const selection = getSelection();
|
|
3831
|
-
|
|
3832
|
-
if (selection && selection.type !== 'None') {
|
|
3833
|
-
/** @type {Range[]} */
|
|
3834
|
-
const ranges = [];
|
|
3835
|
-
|
|
3836
|
-
for (let i = 0; i < selection.rangeCount; i += 1) {
|
|
3837
|
-
ranges.push(selection.getRangeAt(i));
|
|
3838
|
-
}
|
|
3839
|
-
|
|
3840
|
-
setTimeout(() => {
|
|
3841
|
-
if (selection.rangeCount !== ranges.length) return;
|
|
3842
|
-
|
|
3843
|
-
for (let i = 0; i < selection.rangeCount; i += 1) {
|
|
3844
|
-
const a = ranges[i];
|
|
3845
|
-
const b = selection.getRangeAt(i);
|
|
3846
|
-
|
|
3847
|
-
// we need to do a deep comparison rather than just `a !== b` because
|
|
3848
|
-
// Safari behaves differently to other browsers
|
|
3849
|
-
if (
|
|
3850
|
-
a.commonAncestorContainer !== b.commonAncestorContainer ||
|
|
3851
|
-
a.startContainer !== b.startContainer ||
|
|
3852
|
-
a.endContainer !== b.endContainer ||
|
|
3853
|
-
a.startOffset !== b.startOffset ||
|
|
3854
|
-
a.endOffset !== b.endOffset
|
|
3855
|
-
) {
|
|
3856
|
-
return;
|
|
3857
|
-
}
|
|
3858
|
-
}
|
|
3859
|
-
|
|
3860
|
-
// if the selection hasn't changed (as a result of an element being (auto)focused,
|
|
3861
|
-
// or a programmatic selection, we reset everything as part of the navigation)
|
|
3862
|
-
// fixes https://github.com/sveltejs/kit/issues/8439
|
|
3863
|
-
selection.removeAllRanges();
|
|
3864
|
-
});
|
|
3865
|
-
}
|
|
3866
|
-
}
|
|
3867
|
-
}
|
|
3868
|
-
|
|
3869
3740
|
/**
|
|
3870
3741
|
* @template {NavigationType} T
|
|
3871
3742
|
* @param {import('./types.js').NavigationState} current
|
|
@@ -3939,32 +3810,6 @@ function decode_hash(url) {
|
|
|
3939
3810
|
return new_url;
|
|
3940
3811
|
}
|
|
3941
3812
|
|
|
3942
|
-
/**
|
|
3943
|
-
* @param {URL} url
|
|
3944
|
-
* @returns {string}
|
|
3945
|
-
*/
|
|
3946
|
-
function get_id(url) {
|
|
3947
|
-
let id;
|
|
3948
|
-
|
|
3949
|
-
if (app.hash) {
|
|
3950
|
-
const [, , second] = url.hash.split('#', 3);
|
|
3951
|
-
id = second ?? '';
|
|
3952
|
-
} else {
|
|
3953
|
-
id = url.hash.slice(1);
|
|
3954
|
-
}
|
|
3955
|
-
|
|
3956
|
-
return decodeURIComponent(id);
|
|
3957
|
-
}
|
|
3958
|
-
|
|
3959
|
-
/**
|
|
3960
|
-
* @param {URL} url
|
|
3961
|
-
* @returns {Element | null}
|
|
3962
|
-
*/
|
|
3963
|
-
function get_hash_element(url) {
|
|
3964
|
-
const id = get_id(url);
|
|
3965
|
-
return id ? document.getElementById(id) : null;
|
|
3966
|
-
}
|
|
3967
|
-
|
|
3968
3813
|
if (DEV) {
|
|
3969
3814
|
// Nasty hack to silence harmless warnings the user can do nothing about
|
|
3970
3815
|
const console_warn = console.warn;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DEV } from 'esm-env';
|
|
2
2
|
import { hash_request } from '../../utils/hash.js';
|
|
3
3
|
import { base64_decode } from '../utils.js';
|
|
4
|
+
import { fetch_cache_url } from '../shared.js';
|
|
4
5
|
|
|
5
6
|
let loading = 0;
|
|
6
7
|
|
|
@@ -152,15 +153,14 @@ export function dev_fetch(resource, opts) {
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
/**
|
|
155
|
-
*
|
|
156
|
-
* evict the cache entry regardless of how the url is spelled
|
|
156
|
+
* Non-GET requests must evict under the stored key, however the url is spelled
|
|
157
157
|
* @param {RequestInfo | URL} input
|
|
158
158
|
*/
|
|
159
159
|
function requested_url(input) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
return fetch_cache_url(
|
|
161
|
+
new URL(input instanceof Request ? input.url : input, location.href),
|
|
162
|
+
location
|
|
163
|
+
);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
/**
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { hash_routing } from '$app/paths/internal/client';
|
|
2
|
+
import { get_hash_element, scroll_state } from './utils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This flag is used to avoid client-side navigation when we're only using
|
|
6
|
+
* `location.replace()` to set focus.
|
|
7
|
+
*/
|
|
8
|
+
let resetting_focus = false;
|
|
9
|
+
|
|
10
|
+
export function is_resetting_focus() {
|
|
11
|
+
return resetting_focus;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** @param {boolean} reset */
|
|
15
|
+
export function blur_active_element(reset) {
|
|
16
|
+
if (
|
|
17
|
+
reset &&
|
|
18
|
+
document.activeElement instanceof HTMLElement &&
|
|
19
|
+
document.activeElement !== document.body
|
|
20
|
+
) {
|
|
21
|
+
document.activeElement.blur();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {URL} url
|
|
27
|
+
* @param {boolean} [scroll]
|
|
28
|
+
*/
|
|
29
|
+
export function reset_focus(url, scroll = true) {
|
|
30
|
+
const autofocus = document.querySelector('[autofocus]');
|
|
31
|
+
if (autofocus) {
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
autofocus.focus();
|
|
34
|
+
} else {
|
|
35
|
+
// Reset page selection and focus
|
|
36
|
+
|
|
37
|
+
// Mimic the browsers' behaviour and set the sequential focus navigation
|
|
38
|
+
// starting point to the fragment identifier.
|
|
39
|
+
const element = get_hash_element(url, hash_routing);
|
|
40
|
+
if (element) {
|
|
41
|
+
const { x, y } = scroll_state();
|
|
42
|
+
|
|
43
|
+
// `element.focus()` doesn't work on Safari and Firefox Ubuntu so we need
|
|
44
|
+
// to use this hack with `location.replace()` instead.
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
const history_state = history.state;
|
|
47
|
+
|
|
48
|
+
resetting_focus = true;
|
|
49
|
+
location.replace(new URL(`#${element.id}`, location.href));
|
|
50
|
+
|
|
51
|
+
// Firefox has a bug that sets the history state to `null` so we need to
|
|
52
|
+
// restore it after. See https://bugzilla.mozilla.org/show_bug.cgi?id=1199924
|
|
53
|
+
// This is also needed to restore the original hash if we're using hash routing
|
|
54
|
+
history.replaceState(history_state, '', url);
|
|
55
|
+
|
|
56
|
+
// If scroll management has already happened earlier, we need to restore
|
|
57
|
+
// the scroll position after setting the sequential focus navigation starting point
|
|
58
|
+
if (scroll) scrollTo(x, y);
|
|
59
|
+
resetting_focus = false;
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
// If the ID doesn't exist, we try to mimic browsers' behaviour as closely
|
|
63
|
+
// as possible by targeting the first scrollable region. Unfortunately, it's
|
|
64
|
+
// not a perfect match — e.g. shift-tabbing won't immediately cycle up from
|
|
65
|
+
// the end of the page on Chromium
|
|
66
|
+
// See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
|
|
67
|
+
const root = document.body;
|
|
68
|
+
const tabindex = root.getAttribute('tabindex');
|
|
69
|
+
|
|
70
|
+
root.tabIndex = -1;
|
|
71
|
+
root.focus({ preventScroll: true, focusVisible: false });
|
|
72
|
+
|
|
73
|
+
// restore `tabindex` as to prevent `root` from stealing input from elements
|
|
74
|
+
if (tabindex !== null) {
|
|
75
|
+
root.setAttribute('tabindex', tabindex);
|
|
76
|
+
} else {
|
|
77
|
+
root.removeAttribute('tabindex');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// capture current selection, so we can compare the state after
|
|
82
|
+
// snapshot restoration and afterNavigate callbacks have run
|
|
83
|
+
const selection = getSelection();
|
|
84
|
+
|
|
85
|
+
if (selection && selection.type !== 'None') {
|
|
86
|
+
/** @type {Range[]} */
|
|
87
|
+
const ranges = [];
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < selection.rangeCount; i += 1) {
|
|
90
|
+
ranges.push(selection.getRangeAt(i));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
if (selection.rangeCount !== ranges.length) return;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < selection.rangeCount; i += 1) {
|
|
97
|
+
const a = ranges[i];
|
|
98
|
+
const b = selection.getRangeAt(i);
|
|
99
|
+
|
|
100
|
+
// we need to do a deep comparison rather than just `a !== b` because
|
|
101
|
+
// Safari behaves differently to other browsers
|
|
102
|
+
if (
|
|
103
|
+
a.commonAncestorContainer !== b.commonAncestorContainer ||
|
|
104
|
+
a.startContainer !== b.startContainer ||
|
|
105
|
+
a.endContainer !== b.endContainer ||
|
|
106
|
+
a.startOffset !== b.startOffset ||
|
|
107
|
+
a.endOffset !== b.endOffset
|
|
108
|
+
) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// if the selection hasn't changed (as a result of an element being (auto)focused,
|
|
114
|
+
// or a programmatic selection, we reset everything as part of the navigation)
|
|
115
|
+
// fixes https://github.com/sveltejs/kit/issues/8439
|
|
116
|
+
selection.removeAllRanges();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/** @import { RemoteCommand, RemoteQueryUpdate } from '$app/server' */
|
|
2
2
|
import { app_dir, base } from '#app/paths';
|
|
3
3
|
import { stringify_command_arg } from '../../shared.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
get_remote_request_headers,
|
|
6
|
+
categorize_updates,
|
|
7
|
+
remote_request,
|
|
8
|
+
fail_unhandled_refreshes
|
|
9
|
+
} from './shared.svelte.js';
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* Client-version of the `command` function from `$app/server`.
|
|
@@ -45,14 +50,18 @@ export function command(id) {
|
|
|
45
50
|
throw updates_error;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
const response = await remote_request(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const response = await remote_request(
|
|
54
|
+
`${base}/${app_dir}/remote/${id}`,
|
|
55
|
+
{
|
|
56
|
+
method: 'POST',
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
payload: await stringify_command_arg(arg),
|
|
59
|
+
refreshes: Array.from(refreshes ?? [])
|
|
60
|
+
}),
|
|
61
|
+
headers
|
|
62
|
+
},
|
|
63
|
+
refreshes
|
|
64
|
+
);
|
|
56
65
|
|
|
57
66
|
if (response.redirect) {
|
|
58
67
|
throw new Error(
|
|
@@ -60,6 +69,8 @@ export function command(id) {
|
|
|
60
69
|
);
|
|
61
70
|
}
|
|
62
71
|
|
|
72
|
+
fail_unhandled_refreshes(refreshes);
|
|
73
|
+
|
|
63
74
|
return response._;
|
|
64
75
|
} finally {
|
|
65
76
|
overrides?.forEach((fn) => fn());
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from '../client.js';
|
|
14
14
|
import { page } from '#app/state/client';
|
|
15
15
|
import { tick } from 'svelte';
|
|
16
|
-
import { categorize_updates, remote_request } from './shared.svelte.js';
|
|
16
|
+
import { categorize_updates, fail_unhandled_refreshes, remote_request } from './shared.svelte.js';
|
|
17
17
|
import { createAttachmentKey } from 'svelte/attachments';
|
|
18
18
|
import {
|
|
19
19
|
convert_formdata,
|
|
@@ -254,7 +254,8 @@ export function form(id) {
|
|
|
254
254
|
'x-sveltekit-search': location.search
|
|
255
255
|
},
|
|
256
256
|
body: blob
|
|
257
|
-
}
|
|
257
|
+
},
|
|
258
|
+
refreshes
|
|
258
259
|
);
|
|
259
260
|
|
|
260
261
|
({ issues: raw_issues = [], result } = response._ ?? {});
|
|
@@ -274,6 +275,7 @@ export function form(id) {
|
|
|
274
275
|
const succeeded = raw_issues.length === 0;
|
|
275
276
|
|
|
276
277
|
if (succeeded) {
|
|
278
|
+
fail_unhandled_refreshes(refreshes);
|
|
277
279
|
if (should_refresh) {
|
|
278
280
|
await refreshAll();
|
|
279
281
|
}
|
|
@@ -446,7 +448,7 @@ export function form(id) {
|
|
|
446
448
|
set_nested_value(input, previous_submitter, undefined);
|
|
447
449
|
}
|
|
448
450
|
|
|
449
|
-
if (event.submitter) {
|
|
451
|
+
if (event.submitter && /** @type {HTMLInputElement} */ (event.submitter).type !== 'image') {
|
|
450
452
|
const name = event.submitter.getAttribute('name');
|
|
451
453
|
|
|
452
454
|
/** @type {null | ReturnType<typeof parse_form_key>} */
|
|
@@ -535,7 +537,7 @@ export function form(id) {
|
|
|
535
537
|
}
|
|
536
538
|
}
|
|
537
539
|
|
|
538
|
-
set_nested_value(input, field, value);
|
|
540
|
+
set_nested_value(input, field, is_file ? value : coerce_form_value(field.type, value));
|
|
539
541
|
} else if (is_file) {
|
|
540
542
|
if (DEV && element.multiple) {
|
|
541
543
|
throw new Error(
|
|
@@ -554,7 +556,10 @@ export function form(id) {
|
|
|
554
556
|
set_nested_value(
|
|
555
557
|
input,
|
|
556
558
|
field,
|
|
557
|
-
|
|
559
|
+
coerce_form_value(
|
|
560
|
+
field.type,
|
|
561
|
+
element.type === 'checkbox' && !element.checked ? null : element.value
|
|
562
|
+
)
|
|
558
563
|
);
|
|
559
564
|
}
|
|
560
565
|
|
|
@@ -615,7 +620,7 @@ export function form(id) {
|
|
|
615
620
|
}
|
|
616
621
|
|
|
617
622
|
const default_submitter = /** @type {HTMLElement | undefined} */ (
|
|
618
|
-
element.querySelector('button:not([type]), [type="submit"]')
|
|
623
|
+
element.querySelector('button:not([type]), [type="submit"], [type="image"]')
|
|
619
624
|
);
|
|
620
625
|
|
|
621
626
|
const form_data = new FormData(element, default_submitter);
|
|
@@ -706,7 +711,7 @@ export function form(id) {
|
|
|
706
711
|
if (!element) return;
|
|
707
712
|
|
|
708
713
|
const default_submitter = /** @type {HTMLElement | undefined} */ (
|
|
709
|
-
element.querySelector('button:not([type]), [type="submit"]')
|
|
714
|
+
element.querySelector('button:not([type]), [type="submit"], [type="image"]')
|
|
710
715
|
);
|
|
711
716
|
|
|
712
717
|
const form_data = new FormData(element, default_submitter);
|
|
@@ -49,7 +49,15 @@ export class Query {
|
|
|
49
49
|
this.#overrides.length;
|
|
50
50
|
|
|
51
51
|
return (resolve, reject) => {
|
|
52
|
-
const result = p.then(tick).then(() =>
|
|
52
|
+
const result = p.then(tick).then(() => {
|
|
53
|
+
if (!this.#ready) {
|
|
54
|
+
throw new HandledHttpError(
|
|
55
|
+
this.#error ?? { status: 500, message: 'Query resolved without a value' }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return /** @type {T} */ (this.#current);
|
|
60
|
+
});
|
|
53
61
|
|
|
54
62
|
if (resolve || reject) {
|
|
55
63
|
return result.then(resolve, reject);
|
|
@@ -119,14 +127,12 @@ export class Query {
|
|
|
119
127
|
|
|
120
128
|
// Untrack this to not trigger mutation validation errors which can occur if you do e.g. $derived({ a: await queryA(), b: await queryB() })
|
|
121
129
|
untrack(() => {
|
|
122
|
-
this.#latest.splice(0, idx).forEach((r) => r(undefined));
|
|
130
|
+
this.#latest.splice(0, idx + 1).forEach((r) => r(undefined));
|
|
123
131
|
this.#ready = true;
|
|
124
132
|
this.#loading = false;
|
|
125
133
|
this.#raw = value;
|
|
126
134
|
this.#error = undefined;
|
|
127
135
|
});
|
|
128
|
-
|
|
129
|
-
resolve(undefined);
|
|
130
136
|
})
|
|
131
137
|
.catch(async (e) => {
|
|
132
138
|
// TODO: Our behavior here could be better:
|
|
@@ -150,6 +156,7 @@ export class Query {
|
|
|
150
156
|
|
|
151
157
|
untrack(() => {
|
|
152
158
|
this.#latest.splice(0, idx).forEach((r) => r(undefined));
|
|
159
|
+
this.#latest.shift();
|
|
153
160
|
this.#error = error;
|
|
154
161
|
this.#loading = false;
|
|
155
162
|
});
|
|
@@ -234,12 +241,19 @@ export class Query {
|
|
|
234
241
|
// SSR record can never shadow the newly-set value
|
|
235
242
|
delete query_responses[this.#key];
|
|
236
243
|
|
|
244
|
+
// a pending request's promise is settled with the value below; replacing it
|
|
245
|
+
// too would make awaiting consumers settle a second time in a new batch
|
|
246
|
+
const in_flight = this.#latest.length > 0;
|
|
247
|
+
|
|
237
248
|
this.#clear_pending();
|
|
238
249
|
this.#ready = true;
|
|
239
250
|
this.#loading = false;
|
|
240
251
|
this.#error = undefined;
|
|
241
252
|
this.#raw = value;
|
|
242
|
-
|
|
253
|
+
|
|
254
|
+
if (!in_flight) {
|
|
255
|
+
this.#promise = Promise.resolve();
|
|
256
|
+
}
|
|
243
257
|
}
|
|
244
258
|
|
|
245
259
|
/** @param {HttpError} error */
|