diff --git a/app/javascript/application.js b/app/javascript/application.js index 86ce6a103..27c79e7e0 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -67,6 +67,7 @@ import ScrollFade from './elements/scroll_fade' import OpenModalMobile from './elements/open_modal_mobile' import HistoryBack from './elements/history_back' import DatePlaceholder from './elements/date_placeholder' +import FlashMessage from './elements/flash_message' @@ -80,6 +81,10 @@ document.addEventListener('keyup', (e) => { } }) +document.addEventListener('turbo:morph', () => { + document.activeElement?.blur() +}) + document.addEventListener('turbo:before-fetch-request', (event) => { event.detail.fetchOptions.headers['X-Turbo'] = 'true' }) @@ -182,6 +187,7 @@ safeRegisterElement('scroll-fade', ScrollFade) safeRegisterElement('open-modal-mobile', OpenModalMobile) safeRegisterElement('history-back', HistoryBack) safeRegisterElement('date-placeholder', DatePlaceholder) +safeRegisterElement('flash-message', FlashMessage) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { diff --git a/app/javascript/elements/flash_message.js b/app/javascript/elements/flash_message.js new file mode 100644 index 000000000..9da5eb7db --- /dev/null +++ b/app/javascript/elements/flash_message.js @@ -0,0 +1,47 @@ +import { actionable } from '@github/catalyst/lib/actionable' + +export default actionable(class extends HTMLElement { + connectedCallback () { + this.addEventListener('turbo:morph-element', this.onMorph) + + this.show() + } + + disconnectedCallback () { + this.removeEventListener('turbo:morph-element', this.onMorph) + + clearTimeout(this.closeTimeout) + } + + onMorph = (event) => { + if (event.target !== this) return + + this.show() + } + + show () { + clearTimeout(this.closeTimeout) + + this.hideAnimation?.cancel() + + this.animate([ + { transform: 'translateY(-100%)', opacity: 0 }, + { transform: 'translateY(0)', opacity: 1 } + ], { duration: 200, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' }) + + this.closeTimeout = setTimeout(this.close, 2000) + } + + close = (event) => { + event?.preventDefault() + + clearTimeout(this.closeTimeout) + + this.hideAnimation = this.animate([ + { transform: 'translateY(0)', opacity: 1 }, + { transform: 'translateY(-100%)', opacity: 0 } + ], { duration: 150, easing: 'ease-in', fill: 'forwards' }) + + this.hideAnimation.onfinish = () => this.remove() + } +}) diff --git a/app/javascript/elements/native_action.js b/app/javascript/elements/native_action.js index f018224d0..543bd88fc 100644 --- a/app/javascript/elements/native_action.js +++ b/app/javascript/elements/native_action.js @@ -11,6 +11,7 @@ export default class extends HTMLElement { if (!target && !this.dataset.native) return this.nativeId ||= `native-action-${++counter}` + this.id = this.nativeId if (target) target.dataset.nativeId = this.nativeId diff --git a/app/javascript/elements/native_modal.js b/app/javascript/elements/native_modal.js index 5f5658a11..dc8f35d8b 100644 --- a/app/javascript/elements/native_modal.js +++ b/app/javascript/elements/native_modal.js @@ -9,7 +9,8 @@ export default class extends HTMLElement { } onSubmit = (e) => { - if (!e.detail.success || e.detail.fetchResponse?.redirected) return + if (!e.detail.success) return + if (e.detail.fetchResponse?.redirected && e.detail.fetchResponse.location.href !== window.location.href) return if (e.detail.formSubmission?.formElement?.method === 'get') return if (this.dataset.closeAfterSubmit === 'false') return if (e.detail.formSubmission?.formElement?.dataset?.closeOnSubmit === 'false') return diff --git a/app/views/shared/_flash.html.erb b/app/views/shared/_flash.html.erb index 1759df18d..9adbfff0b 100644 --- a/app/views/shared/_flash.html.erb +++ b/app/views/shared/_flash.html.erb @@ -1,9 +1,9 @@ <% if turbo_native_app? %>
<% else %> -