All files / packages/theme-selector/src apply-theme.ts

91.66% Statements 22/24
91.66% Branches 11/12
100% Functions 3/3
91.66% Lines 22/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69                              29x 29x       29x     29x 29x 12x     28x   28x     28x     27x       27x 13x 13x 13x 13x 13x   2x         27x 2x     28x 12x                 3x    
// SPDX-License-Identifier: MIT
/**
 * Theme application logic
 */
 
import { DOM_IDS, DOM_SELECTORS, THEME_FAMILIES } from './constants.js';
import { getBaseUrl, applyThemeClass, loadThemeCSS, resolveAssetPath, getCurrentThemeFromClasses } from './theme-loader.js';
import { setItemActiveState } from './dropdown/helpers.js';
import { ThemeErrors, logThemeError } from './errors.js';
import { resolveTheme } from './theme-resolver.js';
 
/**
 * Applies a theme to the document
 */
export async function applyTheme(doc: Document, themeId: string): Promise<void> {
  const theme = resolveTheme(themeId);
  Iif (!theme) {
    logThemeError(ThemeErrors.NO_THEMES_AVAILABLE());
    return;
  }
  const baseUrl = getBaseUrl(doc);
 
  // Add loading state to trigger button
  const trigger = doc.getElementById(DOM_IDS.THEME_FLAVOR_TRIGGER) as HTMLButtonElement | null;
  if (trigger) {
    trigger.classList.add('is-loading');
  }
 
  try {
    // Apply theme class immediately (before CSS loading)
    applyThemeClass(doc, theme.id);
 
    // Load theme CSS
    await loadThemeCSS(doc, theme, baseUrl);
 
    // Update trigger button icon
    const triggerIcon = doc.getElementById(
      DOM_IDS.THEME_FLAVOR_TRIGGER_ICON
    ) as HTMLImageElement | null;
 
    if (triggerIcon && theme.icon) {
      try {
        triggerIcon.src = resolveAssetPath(theme.icon, baseUrl);
        const familyName = THEME_FAMILIES[theme.family].name;
        triggerIcon.alt = `${familyName} ${theme.name}`;
        triggerIcon.title = `${familyName} ${theme.name}`;
      } catch {
        logThemeError(ThemeErrors.INVALID_ICON_PATH(theme.id));
      }
    }
 
    // Update active state in dropdown
    doc.querySelectorAll(DOM_SELECTORS.DROPDOWN_ITEMS).forEach((item) => {
      setItemActiveState(item, item.getAttribute('data-theme-id') === theme.id);
    });
  } finally {
    if (trigger) {
      trigger.classList.remove('is-loading');
    }
  }
}
 
/**
 * Gets the current theme from classes or returns default
 */
export function getCurrentTheme(doc: Document, defaultTheme: string): string {
  return getCurrentThemeFromClasses(doc.documentElement) || defaultTheme;
}