diff --git a/src/core/frames/form_interceptor.ts b/src/core/frames/form_interceptor.ts deleted file mode 100644 index 5cd3e94f4..000000000 --- a/src/core/frames/form_interceptor.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface FormInterceptorDelegate { - shouldInterceptFormSubmission(element: HTMLFormElement, submitter?: HTMLElement): boolean - formSubmissionIntercepted(element: HTMLFormElement, submitter?: HTMLElement): void -} - -export class FormInterceptor { - readonly delegate: FormInterceptorDelegate - readonly element: Element - - constructor(delegate: FormInterceptorDelegate, element: Element) { - this.delegate = delegate - this.element = element - } - - start() { - this.element.addEventListener("submit", this.submitBubbled) - } - - stop() { - this.element.removeEventListener("submit", this.submitBubbled) - } - - submitBubbled = ((event: SubmitEvent) => { - const form = event.target - if ( - !event.defaultPrevented && - form instanceof HTMLFormElement && - form.closest("turbo-frame, html") == this.element - ) { - const submitter = event.submitter || undefined - const method = submitter?.getAttribute("formmethod") || form.method - - if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) { - event.preventDefault() - event.stopImmediatePropagation() - this.delegate.formSubmissionIntercepted(form, submitter) - } - } - }) -} diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index 3e5404957..f2b4c6e7b 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -12,7 +12,7 @@ import { FormSubmission, FormSubmissionDelegate } from "../drive/form_submission import { Snapshot } from "../snapshot" import { ViewDelegate, ViewRenderOptions } from "../view" import { getAction, expandURL, urlsAreEqual, locationIsVisitable } from "../url" -import { FormInterceptor, FormInterceptorDelegate } from "./form_interceptor" +import { FormSubmitObserver, FormSubmitObserverDelegate } from "../../observers/form_submit_observer" import { FrameView } from "./frame_view" import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" import { FormLinkInterceptor, FormLinkInterceptorDelegate } from "../../observers/form_link_interceptor" @@ -25,7 +25,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequestDelegate, - FormInterceptorDelegate, + FormSubmitObserverDelegate, FormSubmissionDelegate, FrameElementDelegate, FormLinkInterceptorDelegate, @@ -37,7 +37,7 @@ export class FrameController readonly appearanceObserver: AppearanceObserver readonly formLinkInterceptor: FormLinkInterceptor readonly linkInterceptor: LinkInterceptor - readonly formInterceptor: FormInterceptor + readonly formSubmitObserver: FormSubmitObserver formSubmission?: FormSubmission fetchResponseLoaded = (_fetchResponse: FetchResponse) => {} private currentFetchRequest: FetchRequest | null = null @@ -53,7 +53,7 @@ export class FrameController this.appearanceObserver = new AppearanceObserver(this, this.element) this.formLinkInterceptor = new FormLinkInterceptor(this, this.element) this.linkInterceptor = new LinkInterceptor(this, this.element) - this.formInterceptor = new FormInterceptor(this, this.element) + this.formSubmitObserver = new FormSubmitObserver(this, this.element) } connect() { @@ -66,7 +66,7 @@ export class FrameController } this.formLinkInterceptor.start() this.linkInterceptor.start() - this.formInterceptor.start() + this.formSubmitObserver.start() } } @@ -76,7 +76,7 @@ export class FrameController this.appearanceObserver.stop() this.formLinkInterceptor.stop() this.linkInterceptor.stop() - this.formInterceptor.stop() + this.formSubmitObserver.stop() } } @@ -184,11 +184,12 @@ export class FrameController // Form interceptor delegate - shouldInterceptFormSubmission(element: HTMLFormElement, submitter?: HTMLElement) { - return this.shouldInterceptNavigation(element, submitter) + willSubmitForm(element: HTMLFormElement, submitter?: HTMLElement) { + return element.closest("turbo-frame") == this.element && + this.shouldInterceptNavigation(element, submitter) } - formSubmissionIntercepted(element: HTMLFormElement, submitter?: HTMLElement) { + formSubmitted(element: HTMLFormElement, submitter?: HTMLElement) { if (this.formSubmission) { this.formSubmission.stop() } diff --git a/src/core/frames/frame_redirector.ts b/src/core/frames/frame_redirector.ts index 1ddb5a0d5..b28136572 100644 --- a/src/core/frames/frame_redirector.ts +++ b/src/core/frames/frame_redirector.ts @@ -1,27 +1,27 @@ -import { FormInterceptor, FormInterceptorDelegate } from "./form_interceptor" +import { FormSubmitObserver, FormSubmitObserverDelegate } from "../../observers/form_submit_observer" import { FrameElement } from "../../elements/frame_element" import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" import { expandURL, getAction, locationIsVisitable } from "../url" -export class FrameRedirector implements LinkInterceptorDelegate, FormInterceptorDelegate { +export class FrameRedirector implements LinkInterceptorDelegate, FormSubmitObserverDelegate { readonly element: Element readonly linkInterceptor: LinkInterceptor - readonly formInterceptor: FormInterceptor + readonly formSubmitObserver: FormSubmitObserver constructor(element: Element) { this.element = element this.linkInterceptor = new LinkInterceptor(this, element) - this.formInterceptor = new FormInterceptor(this, element) + this.formSubmitObserver = new FormSubmitObserver(this, element) } start() { this.linkInterceptor.start() - this.formInterceptor.start() + this.formSubmitObserver.start() } stop() { this.linkInterceptor.stop() - this.formInterceptor.stop() + this.formSubmitObserver.stop() } shouldInterceptLinkClick(element: Element, _url: string) { @@ -35,14 +35,16 @@ export class FrameRedirector implements LinkInterceptorDelegate, FormInterceptor } } - shouldInterceptFormSubmission(element: HTMLFormElement, submitter?: HTMLElement) { - return this.shouldSubmit(element, submitter) + willSubmitForm(element: HTMLFormElement, submitter?: HTMLElement) { + return element.closest("turbo-frame") == null && + this.shouldSubmit(element, submitter) && + this.shouldRedirect(element, submitter) } - formSubmissionIntercepted(element: HTMLFormElement, submitter?: HTMLElement) { + formSubmitted(element: HTMLFormElement, submitter?: HTMLElement) { const frame = this.findFrameElement(element, submitter) if (frame) { - frame.delegate.formSubmissionIntercepted(element, submitter) + frame.delegate.formSubmitted(element, submitter) } } diff --git a/src/core/session.ts b/src/core/session.ts index 7b2bf24e7..a6d53540c 100644 --- a/src/core/session.ts +++ b/src/core/session.ts @@ -54,7 +54,7 @@ export class Session readonly pageObserver = new PageObserver(this) readonly cacheObserver = new CacheObserver() readonly linkClickObserver = new LinkClickObserver(this) - readonly formSubmitObserver = new FormSubmitObserver(this) + readonly formSubmitObserver = new FormSubmitObserver(this, document) readonly scrollObserver = new ScrollObserver(this) readonly streamObserver = new StreamObserver(this) readonly formLinkInterceptor = new FormLinkInterceptor(this, document.documentElement) diff --git a/src/elements/frame_element.ts b/src/elements/frame_element.ts index 7e8dfab5c..e42191e7f 100644 --- a/src/elements/frame_element.ts +++ b/src/elements/frame_element.ts @@ -1,5 +1,7 @@ import { FetchResponse } from "../http/fetch_response" import { Snapshot } from "../core/snapshot" +import { LinkInterceptorDelegate } from "../core/frames/link_interceptor" +import { FormSubmitObserverDelegate } from "../observers/form_submit_observer" export enum FrameLoadingStyle { eager = "eager", @@ -8,15 +10,13 @@ export enum FrameLoadingStyle { export type FrameElementObservedAttribute = keyof FrameElement & ("disabled" | "complete" | "loading" | "src") -export interface FrameElementDelegate { +export interface FrameElementDelegate extends LinkInterceptorDelegate, FormSubmitObserverDelegate { connect(): void disconnect(): void completeChanged(): void loadingStyleChanged(): void sourceURLChanged(): void disabledChanged(): void - formSubmissionIntercepted(element: HTMLFormElement, submitter?: HTMLElement): void - linkClickIntercepted(element: Element, url: string): void loadResponse(response: FetchResponse): void fetchResponseLoaded: (fetchResponse: FetchResponse) => void visitCachedSnapshot: (snapshot: Snapshot) => void diff --git a/src/observers/form_submit_observer.ts b/src/observers/form_submit_observer.ts index b4b96b69a..5ffd960ba 100644 --- a/src/observers/form_submit_observer.ts +++ b/src/observers/form_submit_observer.ts @@ -5,29 +5,31 @@ export interface FormSubmitObserverDelegate { export class FormSubmitObserver { readonly delegate: FormSubmitObserverDelegate + readonly eventTarget: EventTarget started = false - constructor(delegate: FormSubmitObserverDelegate) { + constructor(delegate: FormSubmitObserverDelegate, eventTarget: EventTarget) { this.delegate = delegate + this.eventTarget = eventTarget } start() { if (!this.started) { - addEventListener("submit", this.submitCaptured, true) + this.eventTarget.addEventListener("submit", this.submitCaptured, true) this.started = true } } stop() { if (this.started) { - removeEventListener("submit", this.submitCaptured, true) + this.eventTarget.removeEventListener("submit", this.submitCaptured, true) this.started = false } } submitCaptured = () => { - removeEventListener("submit", this.submitBubbled, false) - addEventListener("submit", this.submitBubbled, false) + this.eventTarget.removeEventListener("submit", this.submitBubbled, false) + this.eventTarget.addEventListener("submit", this.submitBubbled, false) } submitBubbled = ((event: SubmitEvent) => {