You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.6 KiB
134 lines
3.6 KiB
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>('App');
|
|
|
|
/**
|
|
* Fallback App used when no <App> 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
|
|
});
|