Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@
</script>
{% endblock %}
{% block scripts %}
<script src="{{ 'assets/javascripts/bundle.e96c3837.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.77dd0ce9.min.js' | url }}"></script>
{% for script in config.extra_javascript %}
{{ script | script_tag }}
{% endfor %}
Expand Down
18 changes: 16 additions & 2 deletions src/assets/javascripts/components/content/target/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ function reveal(el: HTMLElement, target: HTMLElement): void {
export function mountContentTarget(
el: HTMLElement, { target$ }: MountOptions
): Observable<Component<ContentTarget>> {
const initial = window.location.href
return target$.pipe(
switchMap(target => defer(() => {
switchMap((target, index) => defer(() => {
const id = target.id
const current = document.getElementById(id) || target
if (!el.contains(current))
Expand All @@ -140,6 +141,19 @@ export function mountContentTarget(
? history.state
: null

// Preserve native full-page restoration after revealing hidden targets.
// WebKit favors fragments instead, so retain explicit alignment there.
// Later anchor clicks also need alignment after revealing their target.
const navigation = performance.getEntriesByType("navigation")[0] as
PerformanceNavigationTiming | undefined
const restoring = index === 0 && href === initial &&
!feature("navigation.instant") &&
navigator.vendor !== "Apple Computer, Inc." &&
document.readyState !== "complete" && (
navigation?.type === "reload" ||
navigation?.type === "back_forward"
)

// Reveal the target immediately so synchronous containers, such as
// details and content tabs, don't need to wait for a rendering cycle.
// An annotation is different: its tooltip is detached while inactive
Expand Down Expand Up @@ -188,7 +202,7 @@ export function mountContentTarget(
tap(state => {
if (typeof offset?.y === "number")
window.scrollTo(offset.x, offset.y)
else
else if (!restoring)
state.target.scrollIntoView()
}),
map(state => ({ ref: state.target, id }))
Expand Down
16 changes: 15 additions & 1 deletion src/assets/javascripts/integrations/instant/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ export function setupInstantNavigation(
of(document)
.subscribe(resolve)

// Keep the mounted path separate from a URL whose document is still loading.
let pathname = getLocation().pathname

// --------------------------------------------------------------------------
// Navigation interception
// --------------------------------------------------------------------------
Expand Down Expand Up @@ -377,7 +380,17 @@ export function setupInstantNavigation(
// intercepted by other parts of the application, which can synchronously back
// up or restore state before or after instant navigation happens.
merge(instant$, history$)
.subscribe(location$)
.subscribe(url => {
// WebKit favors the fragment on cross-document history navigation.
// Same-document history and cancelled loads retain their saved position.
if (
navigator.vendor === "Apple Computer, Inc." &&
url.pathname !== pathname && url.hash
)
history.replaceState(null, "")

location$.next(url)
})

// --------------------------------------------------------------------------
// Fetching and parsing
Expand Down Expand Up @@ -410,6 +423,7 @@ export function setupInstantNavigation(
// contents into the currently active document
switchMap(resolve),
switchMap(inject),
tap(() => { pathname = getLocation().pathname }),
share()
)

Expand Down
Loading