import { Context } from 'runed'; import { createActiveDom } from '$uix/adom'; import type { AppOptions, AppDom, AppLangs, AppNums, AppMoney, AppDates, AppUnits, AppPresentation, AppLogger, Direction, DateOrder, HourCycle } from './types'; import { fallbackDom, fallbackLangs, fallbackPresentation, consoleLogger } from './defaults'; /** * App — root service compositor. * * Holds references to all domain services. Owns no preferences directly — * each service owns its own state. App provides convenience getters/setters * that delegate to the appropriate service. * * All services that depend on locale receive `langs` at construction and * subscribe to `onLocaleChange` internally. When locale changes, each * recalculates its defaults unless the user has set an explicit override. */ export class App { // ── Services ──────────────────────────────────────────────────────── readonly dom: AppDom; readonly langs: AppLangs; readonly nums: AppNums | undefined; readonly money: AppMoney | undefined; readonly dates: AppDates | undefined; readonly units: AppUnits | undefined; readonly presentation: AppPresentation; readonly logger: AppLogger; constructor(opts: AppOptions) { this.dom = opts.dom ?? createActiveDom(); this.langs = opts.langs; this.nums = opts.nums; this.money = opts.money; this.dates = opts.dates; this.units = opts.units; this.presentation = opts.presentation; this.logger = opts.logger ?? consoleLogger; } // ── Convenience getters (delegate to services) ────────────────────── get currency(): string | undefined { return this.money?.getCurrency(); } get dateOrder(): DateOrder | undefined { return this.dates?.getDateOrder(); } get hourCycle(): HourCycle | undefined { return this.dates?.getHourCycle(); } get theme(): string { return this.presentation.getTheme(); } get mode(): 'light' | 'dark' { return this.presentation.getMode(); } // ── Convenience setters (delegate to services) ────────────────────── setLocale(locale: string) { this.langs.setLocale(locale); } setCurrency(currency: string) { this.money?.setCurrency(currency); } setDir(dir: Direction | 'auto') { this.presentation.setDir(dir); } setTheme(theme: string) { this.presentation.setTheme(theme); } setMode(mode: 'light' | 'dark' | 'auto') { this.presentation.setMode(mode); } // ── Static distribution methods ───────────────────────────────────── static create(opts: AppOptions): App { const app = new App(opts); return _ctx.set(app); } static get(): App { return _ctx.getOr(_fallback); } static require(): App { const app = _ctx.getOr(null as unknown as App); if (!app) { throw new Error( '[app] No App found. Make sure this component is rendered inside a App.create() scope.' ); } return app; } } // ── Module-level context ──────────────────────────────────────────────────── const _ctx = new Context('App'); /** * Fallback App used when no ancestor exists. * Only provides core services (langs, presentation, logger). * Optional services (nums, money, dates, units) are undefined — * components must handle their absence gracefully. */ const _fallback = new App({ dom: fallbackDom, langs: fallbackLangs, presentation: fallbackPresentation, logger: consoleLogger });