diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index d85e5f49d..1ec3dd370 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -9,15 +9,15 @@ import { ViewDelegate } from "../view" import { getAction, expandURL, urlsAreEqual, locationIsVisitable, Locatable } from "../url" import { FormInterceptor, FormInterceptorDelegate } from "./form_interceptor" import { FrameView } from "./frame_view" -import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" +import { LinkClickObserver, LinkClickObserverDelegate } from "../../observers/link_click_observer" import { FrameRenderer } from "./frame_renderer" import { session } from "../index" -export class FrameController implements AppearanceObserverDelegate, FetchRequestDelegate, FormInterceptorDelegate, FormSubmissionDelegate, FrameElementDelegate, LinkInterceptorDelegate, ViewDelegate> { +export class FrameController implements AppearanceObserverDelegate, FetchRequestDelegate, FormInterceptorDelegate, FormSubmissionDelegate, FrameElementDelegate, LinkClickObserverDelegate, ViewDelegate> { readonly element: FrameElement readonly view: FrameView readonly appearanceObserver: AppearanceObserver - readonly linkInterceptor: LinkInterceptor + readonly linkClickObserver: LinkClickObserver readonly formInterceptor: FormInterceptor currentURL?: string | null formSubmission?: FormSubmission @@ -31,7 +31,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest this.element = element this.view = new FrameView(this, this.element) this.appearanceObserver = new AppearanceObserver(this, this.element) - this.linkInterceptor = new LinkInterceptor(this, this.element) + this.linkClickObserver = new LinkClickObserver(this, this.element) this.formInterceptor = new FormInterceptor(this, this.element) } @@ -42,7 +42,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest if (this.loadingStyle == FrameLoadingStyle.lazy) { this.appearanceObserver.start() } - this.linkInterceptor.start() + this.linkClickObserver.start() this.formInterceptor.start() this.sourceURLChanged() } @@ -52,7 +52,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest if (this.connected) { this.connected = false this.appearanceObserver.stop() - this.linkInterceptor.stop() + this.linkClickObserver.stop() this.formInterceptor.stop() } } @@ -126,17 +126,18 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest // Link interceptor delegate - shouldInterceptLinkClick(element: Element, url: string) { + willFollowLinkToLocation(element: Element, url: URL) { if (element.hasAttribute("data-turbo-method")) { return false } else { - return this.shouldInterceptNavigation(element) + return element.closest("turbo-frame") == this.element && + this.shouldInterceptNavigation(element) } } - linkClickIntercepted(element: Element, url: string) { + followedLinkToLocation(element: Element, url: URL) { this.reloadable = true - this.navigateFrame(element, url) + this.navigateFrame(element, url.href) } // Form interceptor delegate diff --git a/src/core/frames/frame_redirector.ts b/src/core/frames/frame_redirector.ts index f7dd36555..8193709ef 100644 --- a/src/core/frames/frame_redirector.ts +++ b/src/core/frames/frame_redirector.ts @@ -1,38 +1,38 @@ import { FormInterceptor, FormInterceptorDelegate } from "./form_interceptor" import { FrameElement } from "../../elements/frame_element" -import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" import { expandURL, getAction, locationIsVisitable } from "../url" +import { LinkClickObserver, LinkClickObserverDelegate } from "../../observers/link_click_observer" -export class FrameRedirector implements LinkInterceptorDelegate, FormInterceptorDelegate { +export class FrameRedirector implements LinkClickObserverDelegate, FormInterceptorDelegate { readonly element: Element - readonly linkInterceptor: LinkInterceptor + readonly linkClickObserver: LinkClickObserver readonly formInterceptor: FormInterceptor constructor(element: Element) { this.element = element - this.linkInterceptor = new LinkInterceptor(this, element) + this.linkClickObserver = new LinkClickObserver(this, element) this.formInterceptor = new FormInterceptor(this, element) } start() { - this.linkInterceptor.start() + this.linkClickObserver.start() this.formInterceptor.start() } stop() { - this.linkInterceptor.stop() + this.linkClickObserver.stop() this.formInterceptor.stop() } - shouldInterceptLinkClick(element: Element, url: string) { - return this.shouldRedirect(element) + willFollowLinkToLocation(element: Element, url: URL) { + return element.closest("turbo-frame") == null && this.shouldRedirect(element) } - linkClickIntercepted(element: Element, url: string) { + followedLinkToLocation(element: Element, url: URL) { const frame = this.findFrameElement(element) if (frame) { frame.setAttribute("reloadable", "") - frame.src = url + frame.src = url.href } } diff --git a/src/core/frames/link_interceptor.ts b/src/core/frames/link_interceptor.ts deleted file mode 100644 index 267361450..000000000 --- a/src/core/frames/link_interceptor.ts +++ /dev/null @@ -1,60 +0,0 @@ -export interface LinkInterceptorDelegate { - shouldInterceptLinkClick(element: Element, url: string): boolean - linkClickIntercepted(element: Element, url: string): void -} - -export class LinkInterceptor { - readonly delegate: LinkInterceptorDelegate - readonly element: Element - private clickEvent?: Event - - constructor(delegate: LinkInterceptorDelegate, element: Element) { - this.delegate = delegate - this.element = element - } - - start() { - this.element.addEventListener("click", this.clickBubbled) - document.addEventListener("turbo:click", this.linkClicked) - document.addEventListener("turbo:before-visit", this.willVisit) - } - - stop() { - this.element.removeEventListener("click", this.clickBubbled) - document.removeEventListener("turbo:click", this.linkClicked) - document.removeEventListener("turbo:before-visit", this.willVisit) - } - - clickBubbled = (event: Event) => { - if (this.respondsToEventTarget(event.target)) { - this.clickEvent = event - } else { - delete this.clickEvent - } - } - - linkClicked = ((event: CustomEvent) => { - if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) { - if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) { - this.clickEvent.preventDefault() - event.preventDefault() - this.delegate.linkClickIntercepted(event.target, event.detail.url) - } - } - delete this.clickEvent - }) - - willVisit = () => { - delete this.clickEvent - } - - respondsToEventTarget(target: EventTarget | null) { - const element - = target instanceof Element - ? target - : target instanceof Node - ? target.parentElement - : null - return element && element.closest("turbo-frame, html") == this.element - } -} diff --git a/src/core/session.ts b/src/core/session.ts index 26ff0a2e2..7882d12e6 100644 --- a/src/core/session.ts +++ b/src/core/session.ts @@ -29,7 +29,7 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin readonly pageObserver = new PageObserver(this) readonly cacheObserver = new CacheObserver() - readonly linkClickObserver = new LinkClickObserver(this) + readonly linkClickObserver = new LinkClickObserver(this, document.documentElement) readonly formSubmitObserver = new FormSubmitObserver(this) readonly scrollObserver = new ScrollObserver(this) readonly streamObserver = new StreamObserver(this) @@ -45,11 +45,11 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin if (!this.started) { this.pageObserver.start() this.cacheObserver.start() + this.frameRedirector.start() this.linkClickObserver.start() this.formSubmitObserver.start() this.scrollObserver.start() this.streamObserver.start() - this.frameRedirector.start() this.history.start() this.started = true this.enabled = true @@ -64,11 +64,11 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin if (this.started) { this.pageObserver.stop() this.cacheObserver.stop() + this.frameRedirector.stop() this.linkClickObserver.stop() this.formSubmitObserver.stop() this.scrollObserver.stop() this.streamObserver.stop() - this.frameRedirector.stop() this.history.stop() this.started = false } diff --git a/src/observers/link_click_observer.ts b/src/observers/link_click_observer.ts index 976071ffb..5690dd782 100644 --- a/src/observers/link_click_observer.ts +++ b/src/observers/link_click_observer.ts @@ -7,33 +7,35 @@ export interface LinkClickObserverDelegate { export class LinkClickObserver { readonly delegate: LinkClickObserverDelegate + readonly eventTarget: EventTarget started = false - constructor(delegate: LinkClickObserverDelegate) { + constructor(delegate: LinkClickObserverDelegate, eventTarget: EventTarget) { this.delegate = delegate + this.eventTarget = eventTarget } start() { if (!this.started) { - addEventListener("click", this.clickCaptured, true) + this.eventTarget.addEventListener("click", this.clickCaptured, true) this.started = true } } stop() { if (this.started) { - removeEventListener("click", this.clickCaptured, true) + this.eventTarget.removeEventListener("click", this.clickCaptured, true) this.started = false } } clickCaptured = () => { - removeEventListener("click", this.clickBubbled, false) - addEventListener("click", this.clickBubbled, false) + this.eventTarget.removeEventListener("click", this.clickBubbled, false) + this.eventTarget.addEventListener("click", this.clickBubbled, false) } - clickBubbled = (event: MouseEvent) => { - if (this.clickEventIsSignificant(event)) { + clickBubbled = (event: Event) => { + if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) { const target = (event.composedPath && event.composedPath()[0]) || event.target const link = this.findLinkFromClickTarget(target) if (link) {