From 510361156e241d1729867f0288b95cb762a7800f Mon Sep 17 00:00:00 2001 From: brysonbw Date: Fri, 21 Nov 2025 13:12:42 -0700 Subject: [PATCH] fix(ui): replace dialog with css based .modal to fix safari rendering --- .changeset/heavy-bottles-press.md | 5 + src/app.js | 434 +++++++++++++++--------------- src/components/header.js | 4 +- src/shared/styles/dialogStyles.js | 112 -------- src/shared/styles/modalStyles.js | 97 +++++++ src/utils/constants.js | 4 +- 6 files changed, 329 insertions(+), 327 deletions(-) create mode 100644 .changeset/heavy-bottles-press.md delete mode 100644 src/shared/styles/dialogStyles.js create mode 100644 src/shared/styles/modalStyles.js diff --git a/.changeset/heavy-bottles-press.md b/.changeset/heavy-bottles-press.md new file mode 100644 index 0000000..37a7a33 --- /dev/null +++ b/.changeset/heavy-bottles-press.md @@ -0,0 +1,5 @@ +--- +'studytimer.io': patch +--- + +Replace with CSS-based .modal to fix Safari rendering diff --git a/src/app.js b/src/app.js index 42f6aba..2a726e1 100644 --- a/src/app.js +++ b/src/app.js @@ -4,8 +4,8 @@ import { LitElement, css, html, nothing } from 'lit'; import { notificationApiService } from './services/notification-api.service.js'; import { buttonStyles } from './shared/styles/buttonStyles.js'; import { captionTextStyles } from './shared/styles/captionTextStyles.js'; -import { dialogStyles } from './shared/styles/dialogStyles.js'; import { linkStyles } from './shared/styles/linkStyles.js'; +import { modalStyles } from './shared/styles/modalStyles.js'; import { appStore } from './stores/app.js'; import { DEFAULT_SETTINGS, settingsStore } from './stores/settings.js'; import { @@ -35,9 +35,11 @@ const SYSTEM_NOTIFICATIONS_RESOURCE_LINKS = Object.freeze({ export class App extends LitElement { static properties = { _hasUserVisited: { type: Boolean, state: true }, - _showEnableNotificationsDialog: { type: Boolean, state: true }, _settingsFormValues: { type: Object, state: true }, _isPageNotFound: { type: Boolean, state: true }, + _enableNotificationsModalOpen: { type: Boolean, state: true }, + _faqModalOpen: { type: Boolean, state: true }, + _settingsModalOpen: { type: Boolean, state: true }, }; #router = new Router(this, [ @@ -73,6 +75,12 @@ export class App extends LitElement { this._settingsFormValues = { ...DEFAULT_SETTINGS }; /** @type {boolean} */ this._isPageNotFound = false; + /** @type {boolean} */ + this._enableNotificationsModalOpen = false; + /** @type {boolean} */ + this._faqModalOpen = false; + /** @type {boolean} */ + this._settingsModalOpen = false; } connectedCallback() { @@ -93,25 +101,10 @@ export class App extends LitElement { Notification.permission !== NOTIFICATION_PERMISSION.GRANTED && !this._hasUserVisited ) { - this.#enableNotificationsDialog?.showModal(); + this._enableNotificationsModalOpen = true; } } - /** @type {HTMLDialogElement | null} */ - get #enableNotificationsDialog() { - return this.renderRoot?.querySelector('#enableNotificationsDialog') ?? null; - } - - /** @type {HTMLDialogElement | null} */ - get #faqDialogEl() { - return this.renderRoot?.querySelector('#faqDialog') ?? null; - } - - /** @type {HTMLDialogElement | null} */ - get #settingsDialogEl() { - return this.renderRoot?.querySelector('#settingsDialog') ?? null; - } - render() { return html`${!this._isPageNotFound ? html`` @@ -119,54 +112,63 @@ export class App extends LitElement {
${!this._isPageNotFound ? html`` : nothing}
${this.#router.outlet()}
- ${this.#renderEnableNotificationsDialog()} ${this.#renderFaqDialog()} ${!this._hasUserVisited ? this.#renderFirstVisitPopover() : nothing} - ${this.#renderSettingsDialog()}`; + ${this.#renderEnableNotificationsModal()} ${this.#renderFaqModal()} + ${this.#renderSettingsModal()}`; } /** @param {Event} event */ #onFaqNavLinkClick(event) { if (event instanceof CustomEvent) { - this.#faqDialogEl?.showModal(); + this._faqModalOpen = true; } } /** @param {Event} event */ #onSettingsNavLinkClick(event) { if (event instanceof CustomEvent) { - this.#settingsDialogEl?.showModal(); + this._settingsModalOpen = true; } } - #renderEnableNotificationsDialog() { + #renderEnableNotificationsModal() { return html` - -

Do you want to enable desktop notifications?

-

- You can change this preference anytime in Settings. Also, if enabling - desktop/browser notifications, be sure your system's notification (Mac, - Linux (Ubuntu), or - Windows) settings are turned on as well. -

-
- - +
+ `; } @@ -182,43 +184,43 @@ export class App extends LitElement { this._settingsFormValues = { ...settingsStore.settings }; } - this.#enableNotificationsDialog?.close(); + this.#closeEnableNotificationsModal(); } #onNoClick() { - this.#enableNotificationsDialog?.close(); + this.#closeEnableNotificationsModal(); } - #renderFaqDialog() { - return html` -

Frequently Asked Questions

-
-
- What is Pomodoro Technique? -

- The pomodoro technique is a time management method developed by - Francesco Cirillo in the late 1980s. It uses a kitchen timer to - break work into intervals, typically 25 minutes in length, separated - by short breaks. Each interval is known as a pomodoro, from the - Italian word for tomato. -

-

- Also, view short - pomodoro video explantion - or visit - pomodoro technique wikipedia page - for more information. -

-
+ #renderFaqModal() { + return html`
`; + `; } #renderFirstVisitPopover() { @@ -243,7 +245,7 @@ export class App extends LitElement { `; } - #renderSettingsDialog() { + #renderSettingsModal() { const { showTimerInTitle, showMotivationalQuote, @@ -257,135 +259,141 @@ export class App extends LitElement { longBreakMinutes, } = this._settingsFormValues; - return html` -

Settings

-
-
-
Preferences
- -
- - - - - - - -
- -
- - - - - -
- -
Set Times (Minutes)
-
- - - - - -
- -
- - -
-
+ return html`
`; + `; } /** @param {Event} event */ @@ -407,7 +415,7 @@ export class App extends LitElement { new CustomEvent(SETTINGS_EVENT.SETTINGS_FORM_SUBMIT) ); - this.#settingsDialogEl?.close(); + this.#closeSettingsModal(); } #onReset() { @@ -438,12 +446,16 @@ export class App extends LitElement { } } - #closeFaqDialog() { - this.#faqDialogEl?.close(); + #closeEnableNotificationsModal() { + this._enableNotificationsModalOpen = false; + } + + #closeFaqModal() { + this._faqModalOpen = false; } - #closeSettingsDialog() { - this.#settingsDialogEl?.close(); + #closeSettingsModal() { + this._settingsModalOpen = false; } #closeFirstVisitPopover() { @@ -453,7 +465,7 @@ export class App extends LitElement { static styles = [ buttonStyles, captionTextStyles, - dialogStyles, + modalStyles, linkStyles, css` :host { diff --git a/src/components/header.js b/src/components/header.js index 8d04c96..daf94b0 100644 --- a/src/components/header.js +++ b/src/components/header.js @@ -44,7 +44,7 @@ export class Header extends LitElement { #faqLinkClick() { this.dispatchEvent( - new CustomEvent(APP_EVENT.FAQ_DIALOG, { + new CustomEvent(APP_EVENT.FAQ_MODAL, { bubbles: true, composed: true, }) @@ -53,7 +53,7 @@ export class Header extends LitElement { #settingsLinkClick() { this.dispatchEvent( - new CustomEvent(APP_EVENT.SETTINGS_DIALOG, { + new CustomEvent(APP_EVENT.SETTINGS_MODAL, { bubbles: true, composed: true, }) diff --git a/src/shared/styles/dialogStyles.js b/src/shared/styles/dialogStyles.js deleted file mode 100644 index e0a02c5..0000000 --- a/src/shared/styles/dialogStyles.js +++ /dev/null @@ -1,112 +0,0 @@ -import { css } from 'lit'; - -const dialogStyles = css` - dialog { - border: none; - padding: 1.5rem; - max-width: 480px; - width: 90%; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); - background: var(--accent); - max-height: 80%; - border-radius: 10px; - opacity: 0; - transform: translateY(-20px) scale(0.95); - transition: - opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1), - transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), - overlay 0.4s ease, - display 0.4s ease; - } - - dialog:open { - opacity: 1; - transform: translateY(0) scale(1); - } - - @starting-style { - dialog:open { - opacity: 0; - transform: translateY(-20px) scale(0.95); - } - } - - dialog::backdrop { - background-color: transparent; - transition: background-color 0.4s ease; - } - - dialog:open::backdrop { - background-color: rgba(0, 0, 0, 0.4); - } - - @starting-style { - dialog:open::backdrop { - background-color: transparent; - } - } - - dialog h1, - dialog h2, - dialog h3 { - margin-top: 0; - font-size: 1.25rem; - font-weight: 600; - color: var(--primary, #222); - } - - dialog h4, - dialog h5, - dialog h6 { - margin-top: 0; - font-size: 1rem; - font-weight: 600; - color: var(--primary, #222); - } - - dialog p { - margin: 0.5rem 0 1rem; - line-height: 1.5; - color: var(--lightgray, #555); - } - - dialog button { - display: inline-block; - padding: 0.5rem 1rem; - border-radius: 0.5rem; - border: none; - font-weight: 500; - cursor: pointer; - transition: background 0.2s; - width: 100%; - } - - dialog button { - border: 0.5px solid var(--gray); - margin-top: 1em; - } - - dialog button.primary { - background: var(--accent, #2563eb); - color: var(--white); - } - - dialog button.primary:hover { - opacity: 0.8; - } - - dialog button.secondary { - background: #f3f4f6; - color: #111; - } - - dialog button.secondary:hover { - background: #e5e7eb; - } - - details:hover { - cursor: pointer; - } -`; - -export { dialogStyles }; diff --git a/src/shared/styles/modalStyles.js b/src/shared/styles/modalStyles.js new file mode 100644 index 0000000..37ad3ee --- /dev/null +++ b/src/shared/styles/modalStyles.js @@ -0,0 +1,97 @@ +import { css } from 'lit'; + +const modalStyles = css` + .modal { + position: fixed; + inset: 0; + display: grid; + place-items: center; + z-index: 9999; + pointer-events: auto; + --backdrop-color: rgba(0, 0, 0, 0.4); + } + + .modal::before { + content: ''; + position: fixed; + inset: 0; + background-color: var(--backdrop-color); + z-index: -1; + } + + .modal[hidden] { + display: none; + } + + .modal-content { + background: var(--accent, #fff); + padding: 1.5rem; + max-width: 480px; + width: 90%; + max-height: 80%; + border-radius: 10px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + z-index: 0; + overflow-y: auto; + } + + .modal-content h1, + .modal-content h2, + .modal-content h3 { + margin-top: 0; + font-size: 1.25rem; + font-weight: 600; + color: var(--primary, #222); + } + + .modal-content h4, + .modal-content h5, + .modal-content h6 { + margin-top: 0; + font-size: 1rem; + font-weight: 600; + color: var(--primary, #222); + } + + .modal-content p { + margin: 0.5rem 0 1rem; + line-height: 1.5; + color: var(--lightgray, #555); + } + + .modal-content button { + display: inline-block; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + border: 0.5px solid var(--gray); + font-weight: 500; + cursor: pointer; + transition: background 0.2s; + width: 100%; + margin-top: 1em; + } + + .modal-content button.primary { + background: var(--accent, #2563eb); + color: var(--white); + } + + .modal-content button.primary:hover { + opacity: 0.8; + } + + .modal-content button.secondary { + background: #f3f4f6; + color: #111; + } + + .modal-content button.secondary:hover { + background: #e5e7eb; + } + + details:hover { + cursor: pointer; + } +`; + +export { modalStyles }; diff --git a/src/utils/constants.js b/src/utils/constants.js index 77844bb..6fe4d81 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,6 +1,6 @@ const APP_EVENT = Object.freeze({ - FAQ_DIALOG: 'faq-dialog', - SETTINGS_DIALOG: 'settings-dialog', + FAQ_MODAL: 'faq-modal', + SETTINGS_MODAL: 'settings-modal', }); const CLIENT_ERROR_MESSAGE = Object.freeze({