fix(theme-mode): export dark mode API, fix SSR icon mismatch - #225
Merged
Merged
Conversation
mode-watcher was a required peer dependency while every other runtime library sits in dependencies. Users had to install it themselves, and package managers that do not auto install peers failed to resolve it at all. It moves to dependencies at the same range. toggleMode and resetConfig were referenced by the setup docs but never exported from the package root, so copying those snippets broke the build. A ThemeMode component now wraps the watcher and the mode functions are re-exported, which keeps the underlying library an implementation detail and leaves room to replace it later. ThemeModeButton chose its icon from mode.current, which is undefined during SSR, so the server always rendered the light branch. Svelte does not repair mismatched html blocks, so the wrong glyph stuck after a reload in dark mode. Both icons now render and CSS picks the visible one, and the accessible name no longer depends on the resolved mode. The docs pages are left untouched, so the findings about setup snippets stay open. Refs #224
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Dark mode had two defects that I reproduced by running code before touching anything.
mode-watcherwas a required peer dependency while every other runtime library sits independencies. I packed the library and installed it into a clean project with peer auto install disabled, then resolved the module from the exact file that imports it:bits-uiis the control: equally required at runtime, and it resolves because it is a regular dependency.ThemeModeButtonpicked its icon from the resolved mode, which isundefinedduring SSR, so the server always rendered the light branch:Svelte deliberately does not repair mismatched
{@html}blocks, so the wrong glyph stuck in the DOM after a reload in dark mode until the next toggle.Refs #224
Type of change
Changes
mode-watchermoves frompeerDependenciestodependenciesat the same^1.0.0range, so nothing extra has to be installed and package managers that do not auto install peers keep working.ThemeModecomponent wrapping the watcher, plustoggleMode,setMode,resetMode,mode,userPrefersMode,systemPrefersModeandresetConfigre-exported from the package root. This keeps the underlying library an implementation detail, so it can be replaced later without a breaking change.ThemeModeButtonrenders both mode icons and lets CSS pick the visible one, so server and client markup are identical. The icon size now resolves through one chain shared by the button and its icons.theme.css, which claimedprefers-color-schemesupport that the custom variant does not provide.Breaking changes
ThemeModeButtonaccessible name is now a fixedToggle themeinstead ofSwitch to light mode/Switch to dark mode. Anything selecting the button by accessible name needs updating.<svg>elements instead of one. Measured impact is small: the hidden icon isdisplay: noneso layout is unchanged and ordinarysvgstyling still applies. Selectors that count children, such assvg:only-child, no longer match.FieldGroup, because the button and its icons now share one resolved size.Checklist
pnpm checkpasses (0 errors, 0 warnings)pnpm lintpassespnpm testpassesCHANGELOG.mdunder[Unreleased]*.types.ts, Material 3 design tokens)Notes
Upgrade safety was verified rather than assumed. I downloaded the published
2.7.0from npm and diffed the exported surface: 0 exports removed, 8 added. I then built a consumer using the old API in full, includingimport { ModeWatcher } from 'mode-watcher', everyThemeModeButtonprop,uiwith the old slot names and thechildren({ isDark })snippet. It type checks with 0 errors and runs correctly in both system color schemes, with exactly one icon visible at a time.Tests: 3853 passing across 107 files. The regression tests assert computed styles rather than class strings, and one of them asserts that the rendered markup is byte identical in both modes, which is the property that removes the mismatch at its root.
Not in this PR, still open on the issue: the
theme-colormeta tag, and a nonce that the upstream library interpolates into a script tag without escaping. Both are recorded for the follow up work of owning the mode engine.