import { registerComponent, resolveComponent } from './component-registry'; import type { ComponentRegistration } from './component-registry'; import { resolveConfig, type FluentConfigResult, type FrameworkConfig, type FrameworkTokens } from './config'; import { mergeTokenGroups, applyTokens } from './token-manager'; import { loadWorklets } from './worklet-loader'; import type { ThemeDefinition } from './theme-manager'; import { ThemeManager } from './theme-manager'; import { ColorManager } from '../colors'; import { createFluentTheme } from '../colors/presets'; import { VersionManager } from './version-manager'; import { FeatureLayerManager } from './feature-layers'; import { PolyfillManager } from './polyfill-manager'; import { FeatureFallbackManager } from './feature-fallbacks'; import { LegacySupportManager } from './legacy-support'; export type FluentPlugin = (framework: FluentFramework) => void | Promise; export interface FrameworkHooks { beforeInit?: Array<() => void | Promise>; afterInit?: Array<() => void | Promise>; } /** * Main entry point for the Fluent Grow runtime. Orchestrates configuration, * component registration, worklet loading and theming. */ export class FluentFramework { readonly config: FluentConfigResult; private initialised = false; private plugins: FluentPlugin[] = []; private hooks: FrameworkHooks = { beforeInit: [], afterInit: [] }; private themeManager = new ThemeManager(); private versionManager = VersionManager.getInstance(); private featureManager = new FeatureLayerManager(); private polyfillManager = new PolyfillManager(); private fallbackManager = new FeatureFallbackManager(); private legacyManager = new LegacySupportManager(); constructor(config?: FrameworkConfig) { this.config = resolveConfig(config); } /** Registers a plugin that will be executed during initialisation. */ use(plugin: FluentPlugin): this { this.plugins.push(plugin); return this; } /** Adds a lifecycle hook (beforeInit/afterInit). */ hook(type: T, callback: NonNullable[number]): this { (this.hooks[type] ??= []).push(callback as never); return this; } /** Registers a theme definition with the internal theme manager. */ registerTheme(theme: ThemeDefinition): this { this.themeManager.register(theme); return this; } /** Bootstraps the framework if it has not been initialised already. */ async initialise(): Promise { if (this.initialised) { return; } const startTime = performance.now(); this.initialised = true; // Load polyfills first (including legacy browser support) await this.polyfillManager.loadRequiredPolyfills(); // Inject legacy browser support if needed if (this.legacyManager.isLegacyBrowser()) { this.legacyManager.injectLegacySupport(); } await this.runHooks('beforeInit'); await this.executePlugins(); if (typeof window !== 'undefined') { this.applyTokens(this.config.tokens); // Register and apply default Fluent palettes to expose --fluent-color-*-* variables try { const colorManager = new ColorManager(); // Create and apply fluent theme to ensure CSS variables are set const fluentTheme = createFluentTheme('fluent'); colorManager.registerTheme(fluentTheme); colorManager.applyTheme('fluent'); } catch (error) { if (typeof console !== 'undefined') { console.warn('[FluentFramework] Failed to register default color palettes', error); } } // Register default theme if no themes exist yet if ((this.themeManager as any).themes?.size === 0) { this.themeManager.register({ name: 'default', tokens: { colors: { // These will be overridden by the color manager but defined as fallbacks }, typography: { fontFamily: { body: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif', heading: 'inherit' } }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem' } } }); } // Apply default theme if none is active if (!this.themeManager.getActiveTheme()) { this.themeManager.apply('default'); } await Promise.all([ this.registerConfiguredComponents(), loadWorklets(this.config.worklets.paint, 'paint'), loadWorklets(this.config.worklets.animation, 'animation'), loadWorklets(this.config.worklets.layout, 'layout') ]); } await this.runHooks('afterInit'); const initTime = performance.now() - startTime; console.info(this.versionManager.getCompatibilityReport()); console.info(`[FluentFramework] Initialized in ${initTime.toFixed(2)}ms`); } /** Applies tokens to the document root. */ applyTokens(tokens: FrameworkTokens = this.config.tokens): void { if (typeof document === 'undefined') { return; } applyTokens(tokens); } /** Merges new tokens into the existing graph and reapplies them. */ updateTokens(partialTokens: FrameworkTokens): void { this.config.tokens = mergeTokenGroups(this.config.tokens, partialTokens); this.applyTokens(this.config.tokens); } async registerComponents(...components: ComponentRegistration[]): Promise { await Promise.all(components.map((component) => registerComponent(component))); } /** Alias for registerComponents to match test expectations */ registerComponent = this.registerComponents; async ensureComponent(tag: string): Promise { return resolveComponent(tag); } applyTheme(name: string): void { this.themeManager.apply(name); } /** Get browser compatibility information */ getCompatibilityInfo() { return { version: this.versionManager.getVersionInfo(), support: this.versionManager.getBrowserSupport(), enabledFeatures: this.featureManager.getEnabledFeatures(), loadedPolyfills: this.polyfillManager.getLoadedPolyfills(), fallbacks: this.fallbackManager.getFeatureReport() }; } /** Check if a specific feature is available */ isFeatureAvailable(feature: string): boolean { return this.featureManager.isFeatureAvailable(feature); } /** Get performance and usage metrics */ getMetrics() { return { performance: {}, usage: {} }; } /** Manually track feature usage */ trackFeature(_feature: string): void { // Analytics removed - this is now a no-op } /** Load a polyfill conditionally */ async loadPolyfill(feature: string): Promise { return this.polyfillManager.loadConditionalPolyfill(feature); } private async registerConfiguredComponents(): Promise { if (!this.config.autoRegisterComponents || !this.config.components.length) { return; } await this.registerComponents(...this.config.components); } private async executePlugins(): Promise { for (const plugin of this.plugins) { await plugin(this); } } private async runHooks(type: keyof FrameworkHooks): Promise { const hooks = this.hooks[type]; if (!hooks?.length) { return; } for (const hook of hooks) { await hook(); } } /** Cleanup resources when framework is destroyed */ destroy(): void { // Cleanup if needed } }