boot-theme.ts 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940
  1. /**
  2. * Host-rendered theme bootstrap for the browser's pre-plugin interval. Each
  3. * index response embeds the current durable built-in preference; the browser
  4. * resolves only `system`, then writes the same DOM fields ui-layout's
  5. * ThemePresenter owns after the client plugin tree activates.
  6. */
  7. import { DEFAULT_PREFERENCE, type ThemePreference } from './theme-settings.ts'
  8. /** Build the inline script for one schema-validated built-in preference. */
  9. function bootThemeScript(preference: ThemePreference): string {
  10. return `<script>(() => {
  11. const preference = ${JSON.stringify(preference)}
  12. const systemDark = preference === 'system'
  13. && typeof matchMedia !== 'undefined'
  14. && matchMedia('(prefers-color-scheme: dark)').matches
  15. const dark = preference === 'dark' || systemDark
  16. document.documentElement.style.colorScheme = dark ? 'dark' : 'light'
  17. document.body.toggleAttribute('data-ds-dark-theme', dark)
  18. })()</script>`
  19. }
  20. /**
  21. * Insert the theme bootstrap immediately after the opening body tag, before
  22. * the shell mount and module script. Body-less fragments receive it at the
  23. * end, where the HTML parser has already synthesized a body.
  24. * @param html - Raw application index HTML.
  25. * @param preference - Current Host-backed built-in preference.
  26. * @returns HTML containing the theme bootstrap.
  27. */
  28. export function injectBootTheme(
  29. html: string,
  30. preference: ThemePreference = DEFAULT_PREFERENCE,
  31. ): string {
  32. const script = bootThemeScript(preference)
  33. const body = /<body(?:\s[^>]*)?>/i.exec(html)
  34. if (body === null) return `${html}${script}`
  35. const at = body.index + body[0].length
  36. return `${html.slice(0, at)}${script}${html.slice(at)}`
  37. }