From 9c7f30a150d414e31ba5f1da4533575d4e933a79 Mon Sep 17 00:00:00 2001 From: Miika Kuisma Date: Wed, 13 May 2026 10:08:59 +0300 Subject: [PATCH] Add `theme` attribute for each web component, which can be used for forcing dark or light theme (in case the application doesn't have dark mode support it might be odd if just menubar turns dark) --- src/puter-js/src/ui/PuterWebComponent.js | 55 ++++++ src/puter-js/src/ui/components.md | 20 +- .../src/ui/components/PuterColorPicker.js | 80 ++++---- .../src/ui/components/PuterContextMenu.js | 181 +++++++++--------- .../src/ui/components/PuterFontPicker.js | 86 ++++----- .../src/ui/components/PuterMenubar.js | 37 ++-- .../src/ui/components/PuterNotification.js | 47 +++-- 7 files changed, 292 insertions(+), 214 deletions(-) diff --git a/src/puter-js/src/ui/PuterWebComponent.js b/src/puter-js/src/ui/PuterWebComponent.js index a369e2144..f7b2a7e1e 100644 --- a/src/puter-js/src/ui/PuterWebComponent.js +++ b/src/puter-js/src/ui/PuterWebComponent.js @@ -12,9 +12,64 @@ class PuterWebComponent extends (globalThis.HTMLElement || Object) { } connectedCallback () { + this._setupThemeWatchers(); + this._applyTheme(); this._rerender(); } + disconnectedCallback () { + this._teardownThemeWatchers(); + } + + _setupThemeWatchers () { + if ( typeof globalThis.MutationObserver !== 'undefined' && ! this._themeObserver ) { + this._themeObserver = new globalThis.MutationObserver(() => this._applyTheme()); + this._themeObserver.observe(this, { attributes: true, attributeFilter: ['theme'] }); + } + if ( typeof globalThis.matchMedia === 'function' && ! this._themeMediaQuery ) { + this._themeMediaQuery = globalThis.matchMedia('(prefers-color-scheme: dark)'); + this._themeMediaListener = () => this._applyTheme(); + if ( this._themeMediaQuery.addEventListener ) { + this._themeMediaQuery.addEventListener('change', this._themeMediaListener); + } + } + } + + _teardownThemeWatchers () { + if ( this._themeObserver ) { + this._themeObserver.disconnect(); + this._themeObserver = null; + } + if ( this._themeMediaQuery && this._themeMediaListener && this._themeMediaQuery.removeEventListener ) { + this._themeMediaQuery.removeEventListener('change', this._themeMediaListener); + } + this._themeMediaQuery = null; + this._themeMediaListener = null; + } + + /** + * Resolves the effective theme from the `theme` attribute and the system + * preference, then toggles a `.puter-theme-dark` class on the host so + * components can style with `:host(.puter-theme-dark) ...`. + * theme="dark" → always dark + * theme="light" → always light + * unset / other → follow system (prefers-color-scheme) + */ + _applyTheme () { + if ( typeof this.getAttribute !== 'function' ) return; + const themeAttr = this.getAttribute('theme'); + let isDark; + if ( themeAttr === 'dark' ) { + isDark = true; + } else if ( themeAttr === 'light' ) { + isDark = false; + } else { + isDark = typeof globalThis.matchMedia === 'function' + && globalThis.matchMedia('(prefers-color-scheme: dark)').matches; + } + this.classList.toggle('puter-theme-dark', isDark); + } + _rerender () { if ( ! this.shadowRoot ) return; this.shadowRoot.innerHTML = `${this.render()}`; diff --git a/src/puter-js/src/ui/components.md b/src/puter-js/src/ui/components.md index f41cfe792..555eafa6b 100644 --- a/src/puter-js/src/ui/components.md +++ b/src/puter-js/src/ui/components.md @@ -340,7 +340,25 @@ All components render inside Shadow DOM and match puter.com's native GUI appeara ## Dark mode -Components with `@media (prefers-color-scheme: dark)` rules auto-adapt when the OS is in dark mode: ``, ``, ``, ``, ``. +By default, components that ship dark-mode styles auto-adapt to the OS setting (`prefers-color-scheme: dark`): ``, ``, ``, ``, ``. + +To force a specific theme regardless of the OS preference, set the `theme` attribute on the component: + +| Value | Behavior | +|-------|----------| +| `dark` | Always render in dark theme | +| `light` | Always render in light theme | +| _unset_ | Follow the system `prefers-color-scheme` (default) | + +```html + + + + + +``` + +The attribute is live — changing it at runtime re-paints the component immediately. For `` and ``, the `theme` is forwarded to any dropdowns and submenus they spawn, so the whole tree stays in sync. ## Responsive / mobile diff --git a/src/puter-js/src/ui/components/PuterColorPicker.js b/src/puter-js/src/ui/components/PuterColorPicker.js index 1f9b84f74..13024ea5c 100644 --- a/src/puter-js/src/ui/components/PuterColorPicker.js +++ b/src/puter-js/src/ui/components/PuterColorPicker.js @@ -176,47 +176,45 @@ class PuterColorPicker extends PuterWebComponent { flex: 1; } } - @media (prefers-color-scheme: dark) { - .picker-body { - background-color: rgba(40, 44, 52, .95); - color: #e6e6e6; - box-shadow: 0px 0px 15px #000000aa; - } - .preview { - border-color: #555; - } - .header-label { - color: #aaa; - } - .hex-input { - background-color: #1f1f1f; - border-color: #555; - color: #e6e6e6; - } - .native-color-row { - background: rgba(255, 255, 255, 0.05); - border-color: #555; - } - .native-color-row label { - color: #aaa; - } - input[type="color"] { - border-color: #555; - } - .swatch { - border-color: rgba(255, 255, 255, 0.1); - } - .btn { - color: #e6e6e6; - border-color: #555; - background: linear-gradient(#4a4a4a, #3a3a3a); - box-shadow: inset 0px 1px 0px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 25%); - } - .btn:active { - background-color: #333; - border-color: #444; - color: #999; - } + :host(.puter-theme-dark) .picker-body { + background-color: rgba(40, 44, 52, .95); + color: #e6e6e6; + box-shadow: 0px 0px 15px #000000aa; + } + :host(.puter-theme-dark) .preview { + border-color: #555; + } + :host(.puter-theme-dark) .header-label { + color: #aaa; + } + :host(.puter-theme-dark) .hex-input { + background-color: #1f1f1f; + border-color: #555; + color: #e6e6e6; + } + :host(.puter-theme-dark) .native-color-row { + background: rgba(255, 255, 255, 0.05); + border-color: #555; + } + :host(.puter-theme-dark) .native-color-row label { + color: #aaa; + } + :host(.puter-theme-dark) input[type="color"] { + border-color: #555; + } + :host(.puter-theme-dark) .swatch { + border-color: rgba(255, 255, 255, 0.1); + } + :host(.puter-theme-dark) .btn { + color: #e6e6e6; + border-color: #555; + background: linear-gradient(#4a4a4a, #3a3a3a); + box-shadow: inset 0px 1px 0px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 25%); + } + :host(.puter-theme-dark) .btn:active { + background-color: #333; + border-color: #444; + color: #999; } `; } diff --git a/src/puter-js/src/ui/components/PuterContextMenu.js b/src/puter-js/src/ui/components/PuterContextMenu.js index 873aa0786..3e4446736 100644 --- a/src/puter-js/src/ui/components/PuterContextMenu.js +++ b/src/puter-js/src/ui/components/PuterContextMenu.js @@ -345,99 +345,100 @@ class PuterContextMenu extends PuterWebComponent { height: 20px; } - @media (prefers-color-scheme: dark) { - .context-menu { - background: #2d2d2d; - background-color: rgb(45 45 45 / 94%); - color: #e6e6e6; - border-color: #00000080; - box-shadow: 0px 0px 15px #000000aa; - } - .menu-item { - color: #e6e6e6; - } - /* Inactive items: icon/check/shortcut/arrow tones */ - .icon, - .check { - color: #e6e6e6; - } - .submenu-arrow { - color: #b0b0b0; - } - .shortcut { - color: #888; - } - .icon img { - filter: drop-shadow(0px 0px 0.3px rgb(230, 230, 230)); - } - /* Inactive icon SVGs use currentColor already; nothing to invert */ + /* Dark theme — applied when system prefers dark and no light + override is set, or when theme="dark" is forced. The base + class toggles .puter-theme-dark on the host accordingly. */ + :host(.puter-theme-dark) .context-menu { + background: #2d2d2d; + background-color: rgb(45 45 45 / 94%); + color: #e6e6e6; + border-color: #00000080; + box-shadow: 0px 0px 15px #000000aa; + } + :host(.puter-theme-dark) .menu-item { + color: #e6e6e6; + } + /* Inactive items: icon/check/shortcut/arrow tones */ + :host(.puter-theme-dark) .icon, + :host(.puter-theme-dark) .check { + color: #e6e6e6; + } + :host(.puter-theme-dark) .submenu-arrow { + color: #b0b0b0; + } + :host(.puter-theme-dark) .shortcut { + color: #888; + } + :host(.puter-theme-dark) .icon img { + filter: drop-shadow(0px 0px 0.3px rgb(230, 230, 230)); + } + /* Inactive icon SVGs use currentColor already; nothing to invert */ - /* Safe-triangle / keyboard-nav: non-active hover restored colors should match dark */ - .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider), - .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) { - color: #e6e6e6; - } - .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon, - .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .check, - .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .submenu-arrow, - .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .label, - .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon, - .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .check, - .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .submenu-arrow, - .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .label { - color: #e6e6e6; - } - .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .shortcut, - .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .shortcut { - color: #888; - } - .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon img, - .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon img { - filter: drop-shadow(0px 0px 0.3px rgb(230, 230, 230)); - } + /* Safe-triangle / keyboard-nav: non-active hover restored colors should match dark */ + :host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider), + :host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) { + color: #e6e6e6; + } + :host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon, + :host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .check, + :host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .submenu-arrow, + :host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .label, + :host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon, + :host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .check, + :host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .submenu-arrow, + :host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .label { + color: #e6e6e6; + } + :host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .shortcut, + :host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .shortcut { + color: #888; + } + :host(.puter-theme-dark) .context-menu.safe-traverse .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon img, + :host(.puter-theme-dark) .context-menu.keyboard-nav .menu-item:hover:not(.has-open-submenu):not(.focused):not(.disabled):not(.divider) .icon img { + filter: drop-shadow(0px 0px 0.3px rgb(230, 230, 230)); + } - /* Submenu-open parent (no hover): subtle dark highlight */ - .menu-item.has-open-submenu:not(:hover) { - background-color: #3f3f3f; - color: #e6e6e6; - } - .menu-item.has-open-submenu:not(:hover) .icon, - .menu-item.has-open-submenu:not(:hover) .icon svg, - .menu-item.has-open-submenu:not(:hover) .icon img { - color: #e6e6e6; - } + /* Submenu-open parent (no hover): subtle dark highlight */ + :host(.puter-theme-dark) .menu-item.has-open-submenu:not(:hover) { + background-color: #3f3f3f; + color: #e6e6e6; + } + :host(.puter-theme-dark) .menu-item.has-open-submenu:not(:hover) .icon, + :host(.puter-theme-dark) .menu-item.has-open-submenu:not(:hover) .icon svg, + :host(.puter-theme-dark) .menu-item.has-open-submenu:not(:hover) .icon img { + color: #e6e6e6; + } - /* Danger items */ - .menu-item.danger, - .menu-item.danger .icon { - color: #ff7b72; - } + /* Danger items */ + :host(.puter-theme-dark) .menu-item.danger, + :host(.puter-theme-dark) .menu-item.danger .icon { + color: #ff7b72; + } - /* Divider */ - .divider hr { - background: #444; - } + /* Divider */ + :host(.puter-theme-dark) .divider hr { + background: #444; + } - /* Sheet mode (mobile) */ - :host(.sheet-mode) .context-menu { - background-color: rgb(40 40 40 / 96%); - box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.45); - } - :host(.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) { - background-color: rgba(0, 122, 255, 0.22); - } - :host(.sheet-mode) .menu-item:active:not(.disabled):not(.divider) { - background-color: rgba(0, 122, 255, 0.35); - } - :host(.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) .label, - :host(.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) .icon, - :host(.sheet-mode) .menu-item:active:not(.disabled):not(.divider) .label, - :host(.sheet-mode) .menu-item:active:not(.disabled):not(.divider) .icon { - color: #e6e6e6; - } - :host(.sheet-mode) .divider hr { - background: rgba(255, 255, 255, 0.15); - } + /* Sheet mode (mobile) */ + :host(.puter-theme-dark.sheet-mode) .context-menu { + background-color: rgb(40 40 40 / 96%); + box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.45); + } + :host(.puter-theme-dark.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) { + background-color: rgba(0, 122, 255, 0.22); + } + :host(.puter-theme-dark.sheet-mode) .menu-item:active:not(.disabled):not(.divider) { + background-color: rgba(0, 122, 255, 0.35); + } + :host(.puter-theme-dark.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) .label, + :host(.puter-theme-dark.sheet-mode) .menu-item:hover:not(.disabled):not(.divider) .icon, + :host(.puter-theme-dark.sheet-mode) .menu-item:active:not(.disabled):not(.divider) .label, + :host(.puter-theme-dark.sheet-mode) .menu-item:active:not(.disabled):not(.divider) .icon { + color: #e6e6e6; + } + :host(.puter-theme-dark.sheet-mode) .divider hr { + background: rgba(255, 255, 255, 0.15); } `; } @@ -930,6 +931,9 @@ class PuterContextMenu extends PuterWebComponent { const submenu = document.createElement('puter-context-menu'); submenu.setAttribute('data-submenu', ''); submenu.setAttribute('data-parent-managed', ''); + // Forward any forced theme so the submenu paints in the same theme. + const themeAttr = this.getAttribute('theme'); + if ( themeAttr ) submenu.setAttribute('theme', themeAttr); submenu.items = items; submenu._parentMenu = this; submenu._parentItemEl = parentEl; @@ -1184,6 +1188,7 @@ class PuterContextMenu extends PuterWebComponent { } disconnectedCallback () { + super.disconnectedCallback(); if ( this._outsideClickHandler ) { document.removeEventListener('pointerdown', this._outsideClickHandler, true); } diff --git a/src/puter-js/src/ui/components/PuterFontPicker.js b/src/puter-js/src/ui/components/PuterFontPicker.js index 438dd8cc5..dfecaa8f0 100644 --- a/src/puter-js/src/ui/components/PuterFontPicker.js +++ b/src/puter-js/src/ui/components/PuterFontPicker.js @@ -185,50 +185,48 @@ class PuterFontPicker extends PuterWebComponent { flex: 1; } } - @media (prefers-color-scheme: dark) { - .picker-body { - background-color: rgba(40, 44, 52, .95); - color: #e6e6e6; - box-shadow: 0px 0px 15px #000000aa; - } - .title { - color: #e6e6e6; - text-shadow: none; - } - .search { - background-color: #1f1f1f; - border-color: #555; - color: #e6e6e6; - } - .font-list { - background-color: #1f1f1f; - border-color: #555; - } - .font-item { - color: #e6e6e6; - } - .font-item:hover { - background: rgba(255, 255, 255, 0.06); - } - .font-name-label { - color: #888; - } - .preview { - background: #1f1f1f; - border-color: #555; - color: #e6e6e6; - } - .btn { - color: #e6e6e6; - border-color: #555; - background: linear-gradient(#4a4a4a, #3a3a3a); - box-shadow: inset 0px 1px 0px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 25%); - } - .btn:active { - background-color: #333; - border-color: #444; - color: #999; - } + :host(.puter-theme-dark) .picker-body { + background-color: rgba(40, 44, 52, .95); + color: #e6e6e6; + box-shadow: 0px 0px 15px #000000aa; + } + :host(.puter-theme-dark) .title { + color: #e6e6e6; + text-shadow: none; + } + :host(.puter-theme-dark) .search { + background-color: #1f1f1f; + border-color: #555; + color: #e6e6e6; + } + :host(.puter-theme-dark) .font-list { + background-color: #1f1f1f; + border-color: #555; + } + :host(.puter-theme-dark) .font-item { + color: #e6e6e6; + } + :host(.puter-theme-dark) .font-item:hover { + background: rgba(255, 255, 255, 0.06); + } + :host(.puter-theme-dark) .font-name-label { + color: #888; + } + :host(.puter-theme-dark) .preview { + background: #1f1f1f; + border-color: #555; + color: #e6e6e6; + } + :host(.puter-theme-dark) .btn { + color: #e6e6e6; + border-color: #555; + background: linear-gradient(#4a4a4a, #3a3a3a); + box-shadow: inset 0px 1px 0px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 25%); + } + :host(.puter-theme-dark) .btn:active { + background-color: #333; + border-color: #444; + color: #999; } `; } diff --git a/src/puter-js/src/ui/components/PuterMenubar.js b/src/puter-js/src/ui/components/PuterMenubar.js index 6997f10cc..0104d678a 100644 --- a/src/puter-js/src/ui/components/PuterMenubar.js +++ b/src/puter-js/src/ui/components/PuterMenubar.js @@ -91,22 +91,23 @@ class PuterMenubar extends PuterWebComponent { flex-shrink: 0; } } - @media (prefers-color-scheme: dark) { - .menubar { - background-color: #2a2a2a; - border-bottom-color: #3a3a3a; - } - .menu-button { - color: #e6e6e6; - } - .menu-button:hover, - .menu-button.active, - .menu-button.focused { - background-color: #3a3a3a; - } - .menubar.keyboard-nav .menu-button:hover:not(.focused):not(.active) { - background-color: transparent; - } + /* Dark theme — applied when system prefers dark and no light + override is set, or when theme="dark" is forced. The base + class toggles .puter-theme-dark on the host accordingly. */ + :host(.puter-theme-dark) .menubar { + background-color: #2a2a2a; + border-bottom-color: #3a3a3a; + } + :host(.puter-theme-dark) .menu-button { + color: #e6e6e6; + } + :host(.puter-theme-dark) .menu-button:hover, + :host(.puter-theme-dark) .menu-button.active, + :host(.puter-theme-dark) .menu-button.focused { + background-color: #3a3a3a; + } + :host(.puter-theme-dark) .menubar.keyboard-nav .menu-button:hover:not(.focused):not(.active) { + background-color: transparent; } `; } @@ -352,6 +353,9 @@ class PuterMenubar extends PuterWebComponent { const rect = buttonEl.getBoundingClientRect(); const dropdown = document.createElement('puter-context-menu'); dropdown.setAttribute('data-submenu', ''); // skip mobile sheet behavior + // Forward any forced theme so the dropdown paints in the same theme. + const themeAttr = this.getAttribute('theme'); + if ( themeAttr ) dropdown.setAttribute('theme', themeAttr); dropdown.items = item.items; dropdown.setAttribute('x', String(rect.left)); dropdown.setAttribute('y', String(rect.bottom)); @@ -410,6 +414,7 @@ class PuterMenubar extends PuterWebComponent { } disconnectedCallback () { + super.disconnectedCallback(); this._closeDropdown(); clearTimeout(this._suppressClickTimer); this._suppressClickFor = null; diff --git a/src/puter-js/src/ui/components/PuterNotification.js b/src/puter-js/src/ui/components/PuterNotification.js index 869fa65a7..9fc72f5a3 100644 --- a/src/puter-js/src/ui/components/PuterNotification.js +++ b/src/puter-js/src/ui/components/PuterNotification.js @@ -175,30 +175,28 @@ class PuterNotification extends PuterWebComponent { width: auto; } } - @media (prefers-color-scheme: dark) { - .notification { - background: #2d2d2dcd; - border-color: #3a3a3a; - box-shadow: 0px 0px 17px -6px #000; - } - .close-btn { - background: #3a3a3a; - color: #ccc; - filter: drop-shadow(0px 0px 0.5px rgb(230, 230, 230)); - } - .close-btn:hover { - background: #4a4a4a; - color: #fff; - } - .icon-area { - filter: drop-shadow(0px 0px 0.5px rgb(230, 230, 230)); - } - .title { - color: #e6e6e6; - } - .text { - color: #b0b0b0; - } + :host(.puter-theme-dark) .notification { + background: #2d2d2dcd; + border-color: #3a3a3a; + box-shadow: 0px 0px 17px -6px #000; + } + :host(.puter-theme-dark) .close-btn { + background: #3a3a3a; + color: #ccc; + filter: drop-shadow(0px 0px 0.5px rgb(230, 230, 230)); + } + :host(.puter-theme-dark) .close-btn:hover { + background: #4a4a4a; + color: #fff; + } + :host(.puter-theme-dark) .icon-area { + filter: drop-shadow(0px 0px 0.5px rgb(230, 230, 230)); + } + :host(.puter-theme-dark) .title { + color: #e6e6e6; + } + :host(.puter-theme-dark) .text { + color: #b0b0b0; } `; } @@ -280,6 +278,7 @@ class PuterNotification extends PuterWebComponent { } disconnectedCallback () { + super.disconnectedCallback(); if ( this._dismissTimer ) clearTimeout(this._dismissTimer); const idx = activeNotifications.indexOf(this); if ( idx !== -1 ) activeNotifications.splice(idx, 1);