@moni-labs/moni-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -194
- package/custom-elements.json +1636 -350
- package/dist/actions/index.d.ts +6 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +6 -0
- package/dist/components/_base/field-styles.d.ts +51 -16
- package/dist/components/_base/field-styles.d.ts.map +1 -1
- package/dist/components/_base/field-styles.js +164 -36
- package/dist/components/_base/index.d.ts +25 -0
- package/dist/components/_base/index.d.ts.map +1 -1
- package/dist/components/_base/index.js +25 -0
- package/dist/components/_base/interaction-styles.d.ts +39 -12
- package/dist/components/_base/interaction-styles.d.ts.map +1 -1
- package/dist/components/_base/interaction-styles.js +85 -33
- package/dist/components/_base/moni-element.d.ts +43 -8
- package/dist/components/_base/moni-element.d.ts.map +1 -1
- package/dist/components/_base/moni-element.js +43 -8
- package/dist/components/_base/shared-styles.d.ts +41 -17
- package/dist/components/_base/shared-styles.d.ts.map +1 -1
- package/dist/components/_base/shared-styles.js +113 -21
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -0
- package/dist/components/loading-shapes.d.ts +6 -0
- package/dist/components/loading-shapes.d.ts.map +1 -1
- package/dist/components/loading-shapes.js +6 -0
- package/dist/components/moni-app-bar.d.ts +128 -33
- package/dist/components/moni-app-bar.d.ts.map +1 -1
- package/dist/components/moni-app-bar.js +121 -26
- package/dist/components/moni-badge.d.ts +122 -14
- package/dist/components/moni-badge.d.ts.map +1 -1
- package/dist/components/moni-badge.js +122 -14
- package/dist/components/moni-bottom-sheet.d.ts +120 -15
- package/dist/components/moni-bottom-sheet.d.ts.map +1 -1
- package/dist/components/moni-bottom-sheet.js +116 -12
- package/dist/components/moni-button-group.d.ts +53 -27
- package/dist/components/moni-button-group.d.ts.map +1 -1
- package/dist/components/moni-button-group.js +49 -23
- package/dist/components/moni-button-segment.d.ts +28 -8
- package/dist/components/moni-button-segment.d.ts.map +1 -1
- package/dist/components/moni-button-segment.js +27 -7
- package/dist/components/moni-button.d.ts +51 -32
- package/dist/components/moni-button.d.ts.map +1 -1
- package/dist/components/moni-button.js +50 -31
- package/dist/components/moni-card.d.ts +91 -31
- package/dist/components/moni-card.d.ts.map +1 -1
- package/dist/components/moni-card.js +86 -26
- package/dist/components/moni-carousel.d.ts +67 -17
- package/dist/components/moni-carousel.d.ts.map +1 -1
- package/dist/components/moni-carousel.js +59 -16
- package/dist/components/moni-checkbox.d.ts +122 -17
- package/dist/components/moni-checkbox.d.ts.map +1 -1
- package/dist/components/moni-checkbox.js +118 -14
- package/dist/components/moni-chip.d.ts +56 -30
- package/dist/components/moni-chip.d.ts.map +1 -1
- package/dist/components/moni-chip.js +51 -25
- package/dist/components/moni-color-field.d.ts +44 -6
- package/dist/components/moni-color-field.d.ts.map +1 -1
- package/dist/components/moni-color-field.js +43 -5
- package/dist/components/moni-context-menu.d.ts +44 -22
- package/dist/components/moni-context-menu.d.ts.map +1 -1
- package/dist/components/moni-context-menu.js +43 -21
- package/dist/components/moni-dialog.d.ts +107 -15
- package/dist/components/moni-dialog.d.ts.map +1 -1
- package/dist/components/moni-dialog.js +105 -14
- package/dist/components/moni-divider.d.ts +50 -15
- package/dist/components/moni-divider.d.ts.map +1 -1
- package/dist/components/moni-divider.js +49 -14
- package/dist/components/moni-expansion.d.ts +44 -8
- package/dist/components/moni-expansion.d.ts.map +1 -1
- package/dist/components/moni-expansion.js +43 -7
- package/dist/components/moni-fab-menu.d.ts +39 -20
- package/dist/components/moni-fab-menu.d.ts.map +1 -1
- package/dist/components/moni-fab-menu.js +38 -19
- package/dist/components/moni-fab.d.ts +49 -23
- package/dist/components/moni-fab.d.ts.map +1 -1
- package/dist/components/moni-fab.js +46 -20
- package/dist/components/moni-file-field.d.ts +54 -14
- package/dist/components/moni-file-field.d.ts.map +1 -1
- package/dist/components/moni-file-field.js +53 -13
- package/dist/components/moni-icon.d.ts +78 -11
- package/dist/components/moni-icon.d.ts.map +1 -1
- package/dist/components/moni-icon.js +77 -10
- package/dist/components/moni-list-item.d.ts +61 -30
- package/dist/components/moni-list-item.d.ts.map +1 -1
- package/dist/components/moni-list-item.js +55 -24
- package/dist/components/moni-list.d.ts +37 -13
- package/dist/components/moni-list.d.ts.map +1 -1
- package/dist/components/moni-list.js +36 -12
- package/dist/components/moni-loading-indicator.d.ts +38 -11
- package/dist/components/moni-loading-indicator.d.ts.map +1 -1
- package/dist/components/moni-loading-indicator.js +37 -10
- package/dist/components/moni-menu-item.d.ts +31 -8
- package/dist/components/moni-menu-item.d.ts.map +1 -1
- package/dist/components/moni-menu-item.js +30 -7
- package/dist/components/moni-menu.d.ts +58 -33
- package/dist/components/moni-menu.d.ts.map +1 -1
- package/dist/components/moni-menu.js +51 -26
- package/dist/components/moni-morph-modal.d.ts +7 -1
- package/dist/components/moni-morph-modal.d.ts.map +1 -1
- package/dist/components/moni-morph-modal.js +46 -24
- package/dist/components/moni-nav-item.d.ts +50 -10
- package/dist/components/moni-nav-item.d.ts.map +1 -1
- package/dist/components/moni-nav-item.js +48 -8
- package/dist/components/moni-nav.d.ts +57 -22
- package/dist/components/moni-nav.d.ts.map +1 -1
- package/dist/components/moni-nav.js +53 -18
- package/dist/components/moni-progress.d.ts +108 -20
- package/dist/components/moni-progress.d.ts.map +1 -1
- package/dist/components/moni-progress.js +104 -16
- package/dist/components/moni-radio.d.ts +106 -14
- package/dist/components/moni-radio.d.ts.map +1 -1
- package/dist/components/moni-radio.js +104 -13
- package/dist/components/moni-ripple.d.ts +121 -10
- package/dist/components/moni-ripple.d.ts.map +1 -1
- package/dist/components/moni-ripple.js +120 -9
- package/dist/components/moni-segmented-button.d.ts +31 -11
- package/dist/components/moni-segmented-button.d.ts.map +1 -1
- package/dist/components/moni-segmented-button.js +30 -10
- package/dist/components/moni-select-option.d.ts +43 -9
- package/dist/components/moni-select-option.d.ts.map +1 -1
- package/dist/components/moni-select-option.js +41 -7
- package/dist/components/moni-select.d.ts +59 -2
- package/dist/components/moni-select.d.ts.map +1 -1
- package/dist/components/moni-select.js +58 -1
- package/dist/components/moni-shape.d.ts +1 -1
- package/dist/components/moni-side-sheet.d.ts +56 -19
- package/dist/components/moni-side-sheet.d.ts.map +1 -1
- package/dist/components/moni-side-sheet.js +53 -16
- package/dist/components/moni-slider.d.ts +56 -25
- package/dist/components/moni-slider.d.ts.map +1 -1
- package/dist/components/moni-slider.js +55 -24
- package/dist/components/moni-snackbar.d.ts +86 -17
- package/dist/components/moni-snackbar.d.ts.map +1 -1
- package/dist/components/moni-snackbar.js +85 -16
- package/dist/components/moni-split-button.d.ts +38 -9
- package/dist/components/moni-split-button.d.ts.map +1 -1
- package/dist/components/moni-split-button.js +37 -8
- package/dist/components/moni-step.d.ts +42 -9
- package/dist/components/moni-step.d.ts.map +1 -1
- package/dist/components/moni-step.js +41 -8
- package/dist/components/moni-stepper.d.ts +43 -6
- package/dist/components/moni-stepper.d.ts.map +1 -1
- package/dist/components/moni-stepper.js +42 -5
- package/dist/components/moni-switch.d.ts +103 -16
- package/dist/components/moni-switch.d.ts.map +1 -1
- package/dist/components/moni-switch.js +99 -13
- package/dist/components/moni-tab.d.ts +35 -8
- package/dist/components/moni-tab.d.ts.map +1 -1
- package/dist/components/moni-tab.js +34 -7
- package/dist/components/moni-tabs.d.ts +51 -13
- package/dist/components/moni-tabs.d.ts.map +1 -1
- package/dist/components/moni-tabs.js +48 -10
- package/dist/components/moni-text-field.d.ts +55 -10
- package/dist/components/moni-text-field.d.ts.map +1 -1
- package/dist/components/moni-text-field.js +54 -9
- package/dist/components/moni-textarea.d.ts +51 -21
- package/dist/components/moni-textarea.d.ts.map +1 -1
- package/dist/components/moni-textarea.js +48 -18
- package/dist/components/moni-time-picker.d.ts +41 -11
- package/dist/components/moni-time-picker.d.ts.map +1 -1
- package/dist/components/moni-time-picker.js +40 -10
- package/dist/components/moni-toolbar.d.ts +43 -15
- package/dist/components/moni-toolbar.d.ts.map +1 -1
- package/dist/components/moni-toolbar.js +42 -14
- package/dist/components/moni-tooltip.d.ts +55 -25
- package/dist/components/moni-tooltip.d.ts.map +1 -1
- package/dist/components/moni-tooltip.js +54 -24
- package/dist/components/moni-typography.d.ts +43 -18
- package/dist/components/moni-typography.d.ts.map +1 -1
- package/dist/components/moni-typography.js +42 -17
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -2
- package/dist/styles/tailwind.css +67 -0
- package/dist/styles/tokens.css +111 -99
- package/dist/utils/color.d.ts +181 -2
- package/dist/utils/color.d.ts.map +1 -1
- package/dist/utils/color.js +182 -4
- package/dist/utils/theme.svelte.d.ts +305 -2
- package/dist/utils/theme.svelte.d.ts.map +1 -1
- package/dist/utils/theme.svelte.js +331 -2
- package/dist/web-components.d.ts +28 -0
- package/dist/web-components.d.ts.map +1 -1
- package/dist/web-components.js +29 -2
- package/package.json +1 -1
- package/src/actions/index.ts +7 -0
- package/src/components/_base/field-styles.ts +165 -37
- package/src/components/_base/index.ts +27 -0
- package/src/components/_base/interaction-styles.ts +86 -33
- package/src/components/_base/moni-element.ts +44 -8
- package/src/components/_base/shared-styles.ts +114 -21
- package/src/components/index.ts +7 -0
- package/src/components/loading-shapes.ts +7 -0
- package/src/components/moni-app-bar.ts +127 -26
- package/src/components/moni-badge.ts +128 -14
- package/src/components/moni-bottom-sheet.ts +125 -13
- package/src/components/moni-button-group.ts +50 -23
- package/src/components/moni-button-segment.ts +28 -7
- package/src/components/moni-button.ts +51 -31
- package/src/components/moni-card.ts +90 -26
- package/src/components/moni-carousel.ts +67 -16
- package/src/components/moni-checkbox.ts +125 -14
- package/src/components/moni-chip.ts +52 -25
- package/src/components/moni-color-field.ts +44 -5
- package/src/components/moni-context-menu.ts +44 -21
- package/src/components/moni-dialog.ts +111 -14
- package/src/components/moni-divider.ts +50 -14
- package/src/components/moni-expansion.ts +44 -7
- package/src/components/moni-fab-menu.ts +39 -19
- package/src/components/moni-fab.ts +47 -20
- package/src/components/moni-file-field.ts +54 -13
- package/src/components/moni-icon.ts +80 -10
- package/src/components/moni-list-item.ts +56 -24
- package/src/components/moni-list.ts +37 -12
- package/src/components/moni-loading-indicator.ts +38 -10
- package/src/components/moni-menu-item.ts +31 -7
- package/src/components/moni-menu.ts +52 -26
- package/src/components/moni-morph-modal.ts +58 -24
- package/src/components/moni-nav-item.ts +49 -8
- package/src/components/moni-nav.ts +54 -18
- package/src/components/moni-progress.ts +109 -16
- package/src/components/moni-radio.ts +111 -13
- package/src/components/moni-ripple.ts +126 -9
- package/src/components/moni-segmented-button.ts +31 -10
- package/src/components/moni-select-option.ts +42 -7
- package/src/components/moni-select.ts +79 -1
- package/src/components/moni-side-sheet.ts +54 -16
- package/src/components/moni-slider.ts +56 -24
- package/src/components/moni-snackbar.ts +90 -16
- package/src/components/moni-split-button.ts +38 -8
- package/src/components/moni-step.ts +42 -8
- package/src/components/moni-stepper.ts +43 -5
- package/src/components/moni-switch.ts +106 -13
- package/src/components/moni-tab.ts +35 -7
- package/src/components/moni-tabs.ts +49 -10
- package/src/components/moni-text-field.ts +55 -9
- package/src/components/moni-textarea.ts +49 -18
- package/src/components/moni-time-picker.ts +41 -10
- package/src/components/moni-toolbar.ts +43 -14
- package/src/components/moni-tooltip.ts +55 -24
- package/src/components/moni-typography.ts +43 -17
- package/src/index.ts +67 -3
- package/src/styles/tailwind.css +67 -0
- package/src/styles/tokens.css +111 -99
- package/src/types/svelte-runes.d.ts +64 -2
- package/src/utils/color.ts +286 -5
- package/src/utils/theme.svelte.ts +411 -2
- package/src/web-components.ts +31 -2
- package/dist/assets/shapes/arch.svg +0 -1
- package/dist/assets/shapes/arrow.svg +0 -1
- package/dist/assets/shapes/boom.svg +0 -1
- package/dist/assets/shapes/burst.svg +0 -1
- package/dist/assets/shapes/circle.svg +0 -1
- package/dist/assets/shapes/clamshell.svg +0 -1
- package/dist/assets/shapes/diamond.svg +0 -1
- package/dist/assets/shapes/fan.svg +0 -1
- package/dist/assets/shapes/flower.svg +0 -1
- package/dist/assets/shapes/gem.svg +0 -1
- package/dist/assets/shapes/ghost-ish.svg +0 -1
- package/dist/assets/shapes/heart.svg +0 -1
- package/dist/assets/shapes/leaf-clover4.svg +0 -1
- package/dist/assets/shapes/leaf-clover8.svg +0 -1
- package/dist/assets/shapes/loading-indicator.svg +0 -1
- package/dist/assets/shapes/oval.svg +0 -1
- package/dist/assets/shapes/pentagon.svg +0 -1
- package/dist/assets/shapes/pill.svg +0 -1
- package/dist/assets/shapes/pixel-circle.svg +0 -1
- package/dist/assets/shapes/pixel-triangle.svg +0 -1
- package/dist/assets/shapes/puffy-diamond.svg +0 -1
- package/dist/assets/shapes/puffy.svg +0 -1
- package/dist/assets/shapes/semicircle.svg +0 -1
- package/dist/assets/shapes/sided-cookie12.svg +0 -1
- package/dist/assets/shapes/sided-cookie4.svg +0 -1
- package/dist/assets/shapes/sided-cookie6.svg +0 -1
- package/dist/assets/shapes/sided-cookie7.svg +0 -1
- package/dist/assets/shapes/sided-cookie9.svg +0 -1
- package/dist/assets/shapes/slanted.svg +0 -1
- package/dist/assets/shapes/soft-boom.svg +0 -1
- package/dist/assets/shapes/soft-burst.svg +0 -1
- package/dist/assets/shapes/square.svg +0 -1
- package/dist/assets/shapes/sunny.svg +0 -1
- package/dist/assets/shapes/triangle.svg +0 -1
- package/dist/assets/shapes/very-sunny.svg +0 -1
- package/dist/assets/shapes/wavy-circle.svg +0 -1
- package/dist/assets/shapes/wavy.svg +0 -1
- package/src/assets/shapes/arch.svg +0 -1
- package/src/assets/shapes/arrow.svg +0 -1
- package/src/assets/shapes/boom.svg +0 -1
- package/src/assets/shapes/burst.svg +0 -1
- package/src/assets/shapes/circle.svg +0 -1
- package/src/assets/shapes/clamshell.svg +0 -1
- package/src/assets/shapes/diamond.svg +0 -1
- package/src/assets/shapes/fan.svg +0 -1
- package/src/assets/shapes/flower.svg +0 -1
- package/src/assets/shapes/gem.svg +0 -1
- package/src/assets/shapes/ghost-ish.svg +0 -1
- package/src/assets/shapes/heart.svg +0 -1
- package/src/assets/shapes/leaf-clover4.svg +0 -1
- package/src/assets/shapes/leaf-clover8.svg +0 -1
- package/src/assets/shapes/loading-indicator.svg +0 -1
- package/src/assets/shapes/oval.svg +0 -1
- package/src/assets/shapes/pentagon.svg +0 -1
- package/src/assets/shapes/pill.svg +0 -1
- package/src/assets/shapes/pixel-circle.svg +0 -1
- package/src/assets/shapes/pixel-triangle.svg +0 -1
- package/src/assets/shapes/puffy-diamond.svg +0 -1
- package/src/assets/shapes/puffy.svg +0 -1
- package/src/assets/shapes/semicircle.svg +0 -1
- package/src/assets/shapes/sided-cookie12.svg +0 -1
- package/src/assets/shapes/sided-cookie4.svg +0 -1
- package/src/assets/shapes/sided-cookie6.svg +0 -1
- package/src/assets/shapes/sided-cookie7.svg +0 -1
- package/src/assets/shapes/sided-cookie9.svg +0 -1
- package/src/assets/shapes/slanted.svg +0 -1
- package/src/assets/shapes/soft-boom.svg +0 -1
- package/src/assets/shapes/soft-burst.svg +0 -1
- package/src/assets/shapes/square.svg +0 -1
- package/src/assets/shapes/sunny.svg +0 -1
- package/src/assets/shapes/triangle.svg +0 -1
- package/src/assets/shapes/very-sunny.svg +0 -1
- package/src/assets/shapes/wavy-circle.svg +0 -1
- package/src/assets/shapes/wavy.svg +0 -1
|
@@ -1,29 +1,153 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @file utils/theme.svelte.ts
|
|
3
|
+
* @package @moni-labs/moni-ui
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @contributors Moni Labs & Contributors
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @module theme
|
|
10
|
+
*
|
|
11
|
+
* Reactive theme engine for the Moni design system.
|
|
12
|
+
*
|
|
13
|
+
* `ThemeEngine` is the single source of truth for all visual customization
|
|
14
|
+
* options exposed to users: color seed, light/dark mode, contrast, density,
|
|
15
|
+
* border radius, typography, and motion reduction.
|
|
16
|
+
*
|
|
17
|
+
* **Architecture:**
|
|
18
|
+
* - The engine is a **singleton** accessed via {@link getThemeEngine}. This
|
|
19
|
+
* ensures that all app-level components share the same reactive state.
|
|
20
|
+
* - Settings are persisted to `localStorage` under {@link STORAGE_KEY} so
|
|
21
|
+
* they survive page reloads.
|
|
22
|
+
* - The class uses Svelte 5 `$state` runes for reactivity. Consumers in
|
|
23
|
+
* Svelte components can read `engine.resolvedMode` or `engine.scheme`
|
|
24
|
+
* reactively without any additional subscription setup.
|
|
25
|
+
* - In SSR or non-browser environments, all DOM-side effects are skipped
|
|
26
|
+
* gracefully via `typeof window === 'undefined'` / `typeof document === 'undefined'` guards.
|
|
27
|
+
*
|
|
28
|
+
* **CSS custom properties set by {@link ThemeEngine.apply}:**
|
|
29
|
+
* - `--moni-spacing-density` — numeric multiplier (e.g. 0.75, 1.0, 1.25, 1.6)
|
|
30
|
+
* - `--moni-radius-base` — border-radius base value (e.g. `'0px'`, `'12px'`, `'20px'`)
|
|
31
|
+
* - `--moni-font-sans` — font-family stack for body text
|
|
32
|
+
* - `--moni-font-title` — font-family stack for headings
|
|
33
|
+
* - `--moni-grain-opacity` — CSS opacity for the texture overlay (0–1)
|
|
34
|
+
* - `moni-reduce-motion` — class toggled on `<html>` when reduce-motion is active
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* // In a Svelte component or framework-agnostic module:
|
|
39
|
+
* import { getThemeEngine } from '@moni-labs/moni-ui';
|
|
40
|
+
*
|
|
41
|
+
* const theme = getThemeEngine();
|
|
42
|
+
* theme.setSeedColor('#4f46e5');
|
|
43
|
+
* theme.setMode('dark');
|
|
44
|
+
* ```
|
|
4
45
|
*/
|
|
5
46
|
|
|
6
47
|
import { generateScheme, applySchemeToDocument, getDefaultSeed, type ColorScheme, type ContrastLevel } from './color.js';
|
|
7
48
|
|
|
49
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
|
+
// Public type aliases
|
|
51
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Color scheme polarity / light-dark preference.
|
|
55
|
+
*
|
|
56
|
+
* - `'light'` — Always apply the light palette regardless of OS setting.
|
|
57
|
+
* - `'dark'` — Always apply the dark palette regardless of OS setting.
|
|
58
|
+
* - `'system'` — Follow `prefers-color-scheme` media query. Updates
|
|
59
|
+
* automatically when the user changes their OS preference.
|
|
60
|
+
*/
|
|
8
61
|
export type ThemeMode = 'light' | 'dark' | 'system';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Spacing density scale.
|
|
65
|
+
*
|
|
66
|
+
* Controls the `--moni-spacing-density` custom property, which scales padding,
|
|
67
|
+
* gap, and height values throughout the component library.
|
|
68
|
+
*
|
|
69
|
+
* | Value | Multiplier | Use case |
|
|
70
|
+
* |---------------|------------|----------------------------------------|
|
|
71
|
+
* | `'compact'` | 0.75 | Dense data tables, admin dashboards |
|
|
72
|
+
* | `'default'` | 1.0 | Standard consumer UIs |
|
|
73
|
+
* | `'comfortable'`| 1.25 | Touch-friendly interfaces |
|
|
74
|
+
* | `'spacious'` | 1.6 | Large-display / presentation screens |
|
|
75
|
+
*/
|
|
9
76
|
export type ThemeDensity = 'compact' | 'default' | 'comfortable' | 'spacious';
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Global border-radius style.
|
|
80
|
+
*
|
|
81
|
+
* Sets the `--moni-radius-base` custom property used as the base radius for
|
|
82
|
+
* cards, dialogs, sheets, and other container elements.
|
|
83
|
+
*
|
|
84
|
+
* - `'sharp'` → `0px` — No rounding; geometric aesthetic.
|
|
85
|
+
* - `'default'` → `12px` — Moderate rounding per M3 spec (medium shape).
|
|
86
|
+
* - `'rounded'` → `20px` — Expressive full-curve shapes; default for Moni brand.
|
|
87
|
+
*/
|
|
10
88
|
export type ThemeRadius = 'sharp' | 'default' | 'rounded';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Available typeface families for body and title text.
|
|
92
|
+
*
|
|
93
|
+
* Each value maps to a specific font-family stack in {@link fontFamily}.
|
|
94
|
+
*
|
|
95
|
+
* - `'geist'` — Geist (Vercel), system-ui fallback. Default body font.
|
|
96
|
+
* - `'inter'` — Inter, system-ui fallback. Classic UI sans-serif.
|
|
97
|
+
* - `'roboto'` — Roboto, system-ui fallback. Material Design reference font.
|
|
98
|
+
* - `'instrument'` — Instrument Serif, Georgia fallback. Editorial serif for titles.
|
|
99
|
+
*/
|
|
11
100
|
export type ThemeFont = 'geist' | 'inter' | 'roboto' | 'instrument';
|
|
12
101
|
|
|
102
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
// Settings interface
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Complete snapshot of all user-configurable theme preferences.
|
|
108
|
+
*
|
|
109
|
+
* This interface is serialized to JSON and stored in `localStorage`. Adding
|
|
110
|
+
* new optional fields is backward-compatible; the {@link parseSettings}
|
|
111
|
+
* function merges with {@link defaults} to fill any missing keys.
|
|
112
|
+
*/
|
|
13
113
|
export interface ThemeSettings {
|
|
114
|
+
/** Light/dark/system color scheme preference. */
|
|
14
115
|
mode: ThemeMode;
|
|
116
|
+
/** Seed hex color used to generate the full color scheme. */
|
|
15
117
|
seedColor: string;
|
|
118
|
+
/** WCAG contrast enhancement level for generated colors. */
|
|
16
119
|
contrast: ContrastLevel;
|
|
120
|
+
/** Component spacing density multiplier. */
|
|
17
121
|
density: ThemeDensity;
|
|
122
|
+
/** Global border-radius style for containers. */
|
|
18
123
|
radius: ThemeRadius;
|
|
124
|
+
/** Typeface for body and UI text. */
|
|
19
125
|
font: ThemeFont;
|
|
126
|
+
/** Typeface for display, headline, and title text. */
|
|
20
127
|
titleFont: ThemeFont;
|
|
128
|
+
/** Opacity of the film-grain texture overlay (0–1). `0` disables the grain. */
|
|
21
129
|
grainOpacity: number;
|
|
130
|
+
/** When `true`, disables non-essential CSS transitions and animations. */
|
|
22
131
|
reduceMotion: boolean;
|
|
23
132
|
}
|
|
24
133
|
|
|
134
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
135
|
+
// Constants
|
|
136
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `localStorage` key under which theme settings are persisted.
|
|
140
|
+
*
|
|
141
|
+
* The `v1` suffix allows future breaking changes to use a new key (e.g.
|
|
142
|
+
* `moni-theme-settings-v2`) without conflicting with stale stored values.
|
|
143
|
+
*/
|
|
25
144
|
const STORAGE_KEY = 'moni-theme-settings-v1';
|
|
26
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Factory default values for all {@link ThemeSettings} fields.
|
|
148
|
+
*
|
|
149
|
+
* Applied when no stored settings exist or when {@link ThemeEngine.reset} is called.
|
|
150
|
+
*/
|
|
27
151
|
const defaults: ThemeSettings = {
|
|
28
152
|
mode: 'system',
|
|
29
153
|
seedColor: getDefaultSeed(),
|
|
@@ -36,21 +160,55 @@ const defaults: ThemeSettings = {
|
|
|
36
160
|
reduceMotion: false
|
|
37
161
|
};
|
|
38
162
|
|
|
163
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
164
|
+
// Private helpers
|
|
165
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parses a raw JSON string from `localStorage` into a {@link ThemeSettings} object.
|
|
169
|
+
*
|
|
170
|
+
* Unknown or missing keys are filled in from {@link defaults}, making this
|
|
171
|
+
* function safe to call after schema additions (forward-compatible reads).
|
|
172
|
+
* Malformed JSON falls back to defaults silently rather than throwing.
|
|
173
|
+
*
|
|
174
|
+
* @param raw - The raw string from `localStorage.getItem(STORAGE_KEY)`,
|
|
175
|
+
* or `null` if the key was not present.
|
|
176
|
+
* @returns A fully populated `ThemeSettings` object.
|
|
177
|
+
*/
|
|
39
178
|
function parseSettings(raw: string | null): ThemeSettings {
|
|
40
179
|
if (!raw) return { ...defaults };
|
|
41
180
|
try {
|
|
42
181
|
const parsed = JSON.parse(raw) as Partial<ThemeSettings>;
|
|
182
|
+
// Merge: stored values override defaults; missing keys fall back to defaults.
|
|
43
183
|
return { ...defaults, ...parsed };
|
|
44
184
|
} catch {
|
|
185
|
+
// JSON.parse failed (corrupted storage). Fall back to defaults.
|
|
45
186
|
return { ...defaults };
|
|
46
187
|
}
|
|
47
188
|
}
|
|
48
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Resolves the effective light/dark mode based on the OS media query.
|
|
192
|
+
*
|
|
193
|
+
* Safe to call in SSR — returns `'light'` when `window` is not available,
|
|
194
|
+
* which is the least disruptive default for server-rendered HTML.
|
|
195
|
+
*
|
|
196
|
+
* @returns `'dark'` if the user's OS prefers dark mode, otherwise `'light'`.
|
|
197
|
+
*/
|
|
49
198
|
function getSystemMode(): 'light' | 'dark' {
|
|
50
199
|
if (typeof window === 'undefined') return 'light';
|
|
51
200
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
52
201
|
}
|
|
53
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Maps a {@link ThemeDensity} token to its numeric multiplier.
|
|
205
|
+
*
|
|
206
|
+
* The returned value is written to `--moni-spacing-density`. Components that
|
|
207
|
+
* support density scaling multiply their base spacing by this factor.
|
|
208
|
+
*
|
|
209
|
+
* @param density - The density label to resolve.
|
|
210
|
+
* @returns A numeric multiplier: `0.75` | `1.0` | `1.25` | `1.6`.
|
|
211
|
+
*/
|
|
54
212
|
function densityValue(density: ThemeDensity): number {
|
|
55
213
|
switch (density) {
|
|
56
214
|
case 'compact':
|
|
@@ -64,6 +222,15 @@ function densityValue(density: ThemeDensity): number {
|
|
|
64
222
|
}
|
|
65
223
|
}
|
|
66
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Maps a {@link ThemeRadius} token to a CSS length value.
|
|
227
|
+
*
|
|
228
|
+
* The returned string is written to `--moni-radius-base` and is consumed by
|
|
229
|
+
* component styles that derive their border-radius from this token.
|
|
230
|
+
*
|
|
231
|
+
* @param radius - The radius label to resolve.
|
|
232
|
+
* @returns A CSS length string: `'0px'` | `'12px'` | `'20px'`.
|
|
233
|
+
*/
|
|
67
234
|
function radiusValue(radius: ThemeRadius): string {
|
|
68
235
|
switch (radius) {
|
|
69
236
|
case 'sharp':
|
|
@@ -75,6 +242,15 @@ function radiusValue(radius: ThemeRadius): string {
|
|
|
75
242
|
}
|
|
76
243
|
}
|
|
77
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Maps a {@link ThemeFont} token to a CSS `font-family` stack string.
|
|
247
|
+
*
|
|
248
|
+
* The stacks follow the pattern `'Primary Font', system-ui fallback` to
|
|
249
|
+
* ensure graceful degradation when the primary font is not loaded.
|
|
250
|
+
*
|
|
251
|
+
* @param font - The font label to resolve.
|
|
252
|
+
* @returns A CSS `font-family` value string ready to pass to `setProperty`.
|
|
253
|
+
*/
|
|
78
254
|
function fontFamily(font: ThemeFont): string {
|
|
79
255
|
switch (font) {
|
|
80
256
|
case 'inter':
|
|
@@ -84,17 +260,101 @@ function fontFamily(font: ThemeFont): string {
|
|
|
84
260
|
case 'instrument':
|
|
85
261
|
return "'Instrument Serif', Georgia, serif";
|
|
86
262
|
default:
|
|
263
|
+
// 'geist' is the Moni brand default.
|
|
87
264
|
return "'Geist', system-ui, sans-serif";
|
|
88
265
|
}
|
|
89
266
|
}
|
|
90
267
|
|
|
268
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
269
|
+
// ThemeEngine class
|
|
270
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Reactive singleton that orchestrates the full Moni visual theme.
|
|
274
|
+
*
|
|
275
|
+
* `ThemeEngine` is responsible for:
|
|
276
|
+
* 1. Loading persisted settings from `localStorage` on construction.
|
|
277
|
+
* 2. Listening to OS `prefers-color-scheme` changes when mode is `'system'`.
|
|
278
|
+
* 3. Generating the full 27-role `ColorScheme` from the current seed color.
|
|
279
|
+
* 4. Writing all CSS custom properties to the document root.
|
|
280
|
+
* 5. Persisting settings to `localStorage` on every user-initiated change.
|
|
281
|
+
*
|
|
282
|
+
* **Reactivity:** Public fields `settings`, `resolvedMode`, and `scheme` are
|
|
283
|
+
* Svelte 5 `$state` runes. Any Svelte template reading these fields will
|
|
284
|
+
* automatically re-render when they change.
|
|
285
|
+
*
|
|
286
|
+
* **Singleton pattern:** Do not instantiate this class directly. Always use
|
|
287
|
+
* {@link getThemeEngine}, which returns a module-level singleton and ensures
|
|
288
|
+
* exactly one engine exists per page lifecycle.
|
|
289
|
+
*
|
|
290
|
+
* **SSR compatibility:** The constructor is safe to call in a server context —
|
|
291
|
+
* all DOM/window access is guarded and skipped when unavailable.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* import { getThemeEngine } from '@moni-labs/moni-ui';
|
|
296
|
+
*
|
|
297
|
+
* const theme = getThemeEngine();
|
|
298
|
+
*
|
|
299
|
+
* // Change the seed color — triggers scheme regeneration and DOM update.
|
|
300
|
+
* theme.setSeedColor('#e91e63');
|
|
301
|
+
*
|
|
302
|
+
* // Read the current resolved scheme in a framework-agnostic way.
|
|
303
|
+
* console.log(theme.scheme.primary);
|
|
304
|
+
*
|
|
305
|
+
* // Reset to factory defaults.
|
|
306
|
+
* theme.reset();
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
91
309
|
export class ThemeEngine {
|
|
310
|
+
/**
|
|
311
|
+
* The complete set of current user preferences.
|
|
312
|
+
*
|
|
313
|
+
* Mutate via the setter methods (e.g. `setMode`, `setSeedColor`) rather
|
|
314
|
+
* than directly, to ensure DOM updates and persistence are triggered.
|
|
315
|
+
*/
|
|
92
316
|
settings = $state<ThemeSettings>({ ...defaults });
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The resolved light/dark mode after applying the `'system'` logic.
|
|
320
|
+
*
|
|
321
|
+
* Always either `'light'` or `'dark'` — never `'system'`. Use this value
|
|
322
|
+
* for icon toggling, conditional rendering, and `aria-*` attributes.
|
|
323
|
+
*/
|
|
93
324
|
resolvedMode = $state<'light' | 'dark'>('light');
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* The currently active 27-role color scheme, derived from `settings.seedColor`,
|
|
328
|
+
* `resolvedMode`, and `settings.contrast`.
|
|
329
|
+
*
|
|
330
|
+
* Reactive: re-assigned every time {@link apply} runs.
|
|
331
|
+
*/
|
|
94
332
|
scheme = $state<ColorScheme>(generateScheme(defaults.seedColor, 'light', defaults.contrast));
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The OS `prefers-color-scheme` media query list.
|
|
336
|
+
* Used to listen for system-level dark mode changes when mode is `'system'`.
|
|
337
|
+
* `null` in SSR or when `window` is unavailable.
|
|
338
|
+
*/
|
|
95
339
|
private mediaQuery: MediaQueryList | null = null;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Guard flag to prevent attaching duplicate `'change'` listeners to
|
|
343
|
+
* {@link mediaQuery} if `bindSystem` is called more than once.
|
|
344
|
+
*/
|
|
96
345
|
private bound = false;
|
|
97
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Initializes the engine.
|
|
349
|
+
*
|
|
350
|
+
* In browser environments:
|
|
351
|
+
* 1. Loads and merges stored settings from `localStorage`.
|
|
352
|
+
* 2. Captures the `prefers-color-scheme` media query.
|
|
353
|
+
* 3. Attaches a `'change'` listener to react to OS preference updates.
|
|
354
|
+
* 4. Runs the first {@link apply} pass to paint the document immediately.
|
|
355
|
+
*
|
|
356
|
+
* In SSR environments, the constructor is a no-op.
|
|
357
|
+
*/
|
|
98
358
|
constructor() {
|
|
99
359
|
if (typeof window !== 'undefined') {
|
|
100
360
|
this.settings = parseSettings(localStorage.getItem(STORAGE_KEY));
|
|
@@ -104,31 +364,68 @@ export class ThemeEngine {
|
|
|
104
364
|
}
|
|
105
365
|
}
|
|
106
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Attaches a listener to the OS color-scheme media query.
|
|
369
|
+
*
|
|
370
|
+
* Calls {@link apply} whenever the OS switches between light and dark mode,
|
|
371
|
+
* but only when `settings.mode === 'system'` — the effective mode check
|
|
372
|
+
* inside `apply` handles the conditional logic.
|
|
373
|
+
*
|
|
374
|
+
* Idempotent: calling this method multiple times is safe; the `bound` flag
|
|
375
|
+
* ensures only one listener is ever attached.
|
|
376
|
+
*/
|
|
107
377
|
private bindSystem() {
|
|
108
378
|
if (this.bound || !this.mediaQuery) return;
|
|
109
379
|
this.bound = true;
|
|
110
380
|
this.mediaQuery.addEventListener('change', () => this.apply());
|
|
111
381
|
}
|
|
112
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Resolves the effective polarity (`'light'` or `'dark'`) from `settings.mode`.
|
|
385
|
+
*
|
|
386
|
+
* When mode is `'system'`, defers to {@link getSystemMode} which reads the
|
|
387
|
+
* OS media query. Otherwise returns the explicit mode directly.
|
|
388
|
+
*
|
|
389
|
+
* @returns `'light'` or `'dark'`.
|
|
390
|
+
*/
|
|
113
391
|
private getEffectiveMode(): 'light' | 'dark' {
|
|
114
392
|
if (this.settings.mode === 'system') return getSystemMode();
|
|
115
393
|
return this.settings.mode;
|
|
116
394
|
}
|
|
117
395
|
|
|
396
|
+
/**
|
|
397
|
+
* Regenerates the color scheme and applies all theme tokens to the document.
|
|
398
|
+
*
|
|
399
|
+
* This is the central "render" method of the engine. It:
|
|
400
|
+
* 1. Resolves the effective mode (`'light'` | `'dark'`).
|
|
401
|
+
* 2. Generates a fresh {@link ColorScheme} from the current seed and contrast.
|
|
402
|
+
* 3. Writes all 81 CSS custom properties (3 namespaces × 27 roles) to `<html>`.
|
|
403
|
+
* 4. Sets `--moni-spacing-density`, `--moni-radius-base`, `--moni-font-sans`,
|
|
404
|
+
* `--moni-font-title`, and `--moni-grain-opacity`.
|
|
405
|
+
* 5. Toggles the `moni-reduce-motion` class on `<html>`.
|
|
406
|
+
*
|
|
407
|
+
* **Side effects:** Mutates `document.documentElement.style` and `classList`.
|
|
408
|
+
* No-op in SSR (`typeof document === 'undefined'`).
|
|
409
|
+
*/
|
|
118
410
|
apply() {
|
|
119
411
|
if (typeof document === 'undefined') return;
|
|
412
|
+
|
|
120
413
|
const mode = this.getEffectiveMode();
|
|
121
414
|
this.resolvedMode = mode;
|
|
122
415
|
this.scheme = generateScheme(this.settings.seedColor, mode, this.settings.contrast);
|
|
123
416
|
applySchemeToDocument(this.scheme, mode, document);
|
|
124
417
|
|
|
125
418
|
const root = document.documentElement;
|
|
419
|
+
|
|
420
|
+
// Write non-color theme tokens.
|
|
126
421
|
root.style.setProperty('--moni-spacing-density', densityValue(this.settings.density).toString());
|
|
127
422
|
root.style.setProperty('--moni-radius-base', radiusValue(this.settings.radius));
|
|
128
423
|
root.style.setProperty('--moni-font-sans', fontFamily(this.settings.font));
|
|
129
424
|
root.style.setProperty('--moni-font-title', fontFamily(this.settings.titleFont));
|
|
130
425
|
root.style.setProperty('--moni-grain-opacity', this.settings.grainOpacity.toString());
|
|
131
426
|
|
|
427
|
+
// Toggle motion reduction class. CSS `@media (prefers-reduced-motion)`
|
|
428
|
+
// handles native OS preference; this class covers user-initiated opt-in.
|
|
132
429
|
if (this.settings.reduceMotion) {
|
|
133
430
|
root.classList.add('moni-reduce-motion');
|
|
134
431
|
} else {
|
|
@@ -136,6 +433,12 @@ export class ThemeEngine {
|
|
|
136
433
|
}
|
|
137
434
|
}
|
|
138
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Persists the current {@link settings} to `localStorage` and re-applies the theme.
|
|
438
|
+
*
|
|
439
|
+
* Called internally by all setter methods after mutating `settings`.
|
|
440
|
+
* No-op when `localStorage` is unavailable (SSR, private browsing, quota exceeded).
|
|
441
|
+
*/
|
|
139
442
|
persist() {
|
|
140
443
|
if (typeof localStorage !== 'undefined') {
|
|
141
444
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.settings));
|
|
@@ -143,64 +446,170 @@ export class ThemeEngine {
|
|
|
143
446
|
this.apply();
|
|
144
447
|
}
|
|
145
448
|
|
|
449
|
+
// ── Public setters ──────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Updates the color scheme polarity.
|
|
453
|
+
*
|
|
454
|
+
* @param mode - `'light'`, `'dark'`, or `'system'` (follows OS preference).
|
|
455
|
+
*/
|
|
146
456
|
setMode(mode: ThemeMode) {
|
|
147
457
|
this.settings.mode = mode;
|
|
148
458
|
this.persist();
|
|
149
459
|
}
|
|
150
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Updates the seed color used to generate the full color scheme.
|
|
463
|
+
*
|
|
464
|
+
* Automatically prepends `#` if the value doesn't already have one,
|
|
465
|
+
* ensuring the stored value is always a valid 7-character hex string.
|
|
466
|
+
*
|
|
467
|
+
* @param color - A hex color string, with or without the `#` prefix
|
|
468
|
+
* (e.g. `'#4f46e5'` or `'4f46e5'`).
|
|
469
|
+
*/
|
|
151
470
|
setSeedColor(color: string) {
|
|
152
471
|
this.settings.seedColor = color.startsWith('#') ? color : `#${color}`;
|
|
153
472
|
this.persist();
|
|
154
473
|
}
|
|
155
474
|
|
|
475
|
+
/**
|
|
476
|
+
* Updates the WCAG contrast enhancement level for the generated palette.
|
|
477
|
+
*
|
|
478
|
+
* @param contrast - `'standard'` (AA), `'medium'` (enhanced), or `'high'` (AAA).
|
|
479
|
+
*/
|
|
156
480
|
setContrast(contrast: ContrastLevel) {
|
|
157
481
|
this.settings.contrast = contrast;
|
|
158
482
|
this.persist();
|
|
159
483
|
}
|
|
160
484
|
|
|
485
|
+
/**
|
|
486
|
+
* Updates the spacing density multiplier.
|
|
487
|
+
*
|
|
488
|
+
* @param density - `'compact'`, `'default'`, `'comfortable'`, or `'spacious'`.
|
|
489
|
+
*/
|
|
161
490
|
setDensity(density: ThemeDensity) {
|
|
162
491
|
this.settings.density = density;
|
|
163
492
|
this.persist();
|
|
164
493
|
}
|
|
165
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Updates the global border-radius style.
|
|
497
|
+
*
|
|
498
|
+
* @param radius - `'sharp'` (0px), `'default'` (12px), or `'rounded'` (20px).
|
|
499
|
+
*/
|
|
166
500
|
setRadius(radius: ThemeRadius) {
|
|
167
501
|
this.settings.radius = radius;
|
|
168
502
|
this.persist();
|
|
169
503
|
}
|
|
170
504
|
|
|
505
|
+
/**
|
|
506
|
+
* Updates the body/UI typeface.
|
|
507
|
+
*
|
|
508
|
+
* @param font - One of `'geist'` | `'inter'` | `'roboto'` | `'instrument'`.
|
|
509
|
+
*/
|
|
171
510
|
setFont(font: ThemeFont) {
|
|
172
511
|
this.settings.font = font;
|
|
173
512
|
this.persist();
|
|
174
513
|
}
|
|
175
514
|
|
|
515
|
+
/**
|
|
516
|
+
* Updates the display/title typeface.
|
|
517
|
+
*
|
|
518
|
+
* @param font - One of `'geist'` | `'inter'` | `'roboto'` | `'instrument'`.
|
|
519
|
+
*/
|
|
176
520
|
setTitleFont(font: ThemeFont) {
|
|
177
521
|
this.settings.titleFont = font;
|
|
178
522
|
this.persist();
|
|
179
523
|
}
|
|
180
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Updates the grain texture opacity.
|
|
527
|
+
*
|
|
528
|
+
* The value is clamped to [0, 1] before storing. Setting to `0` effectively
|
|
529
|
+
* disables the grain overlay without removing the element from the DOM.
|
|
530
|
+
*
|
|
531
|
+
* @param opacity - A number in the range [0, 1].
|
|
532
|
+
*/
|
|
181
533
|
setGrainOpacity(opacity: number) {
|
|
182
534
|
this.settings.grainOpacity = Math.max(0, Math.min(1, opacity));
|
|
183
535
|
this.persist();
|
|
184
536
|
}
|
|
185
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Enables or disables user-initiated motion reduction.
|
|
540
|
+
*
|
|
541
|
+
* When `true`, the `moni-reduce-motion` class is added to `<html>`, which
|
|
542
|
+
* CSS components use to disable non-essential transitions and animations.
|
|
543
|
+
* This is separate from `prefers-reduced-motion` (OS-level) and allows
|
|
544
|
+
* users to opt in without changing their OS settings.
|
|
545
|
+
*
|
|
546
|
+
* @param reduce - `true` to disable motion, `false` to restore it.
|
|
547
|
+
*/
|
|
186
548
|
setReduceMotion(reduce: boolean) {
|
|
187
549
|
this.settings.reduceMotion = reduce;
|
|
188
550
|
this.persist();
|
|
189
551
|
}
|
|
190
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Resets all settings to factory defaults and re-applies the theme.
|
|
555
|
+
*
|
|
556
|
+
* The reset is also persisted to `localStorage` so subsequent page loads
|
|
557
|
+
* start from defaults rather than the old stored values.
|
|
558
|
+
*/
|
|
191
559
|
reset() {
|
|
192
560
|
this.settings = { ...defaults };
|
|
193
561
|
this.persist();
|
|
194
562
|
}
|
|
195
563
|
}
|
|
196
564
|
|
|
565
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
566
|
+
// Module-level singleton
|
|
567
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Module-level singleton instance.
|
|
571
|
+
*
|
|
572
|
+
* `null` until the first call to {@link getThemeEngine}. Using `null` (rather
|
|
573
|
+
* than eagerly constructing) ensures the engine is only created in browser
|
|
574
|
+
* contexts where DOM and `localStorage` are actually available.
|
|
575
|
+
*/
|
|
197
576
|
let engine: ThemeEngine | null = null;
|
|
198
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Returns the module-level {@link ThemeEngine} singleton, creating it on first call.
|
|
580
|
+
*
|
|
581
|
+
* This is the **recommended** entry point for all consumers. Calling this
|
|
582
|
+
* function multiple times always returns the same instance.
|
|
583
|
+
*
|
|
584
|
+
* @returns The shared `ThemeEngine` instance.
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```ts
|
|
588
|
+
* import { getThemeEngine } from '@moni-labs/moni-ui';
|
|
589
|
+
*
|
|
590
|
+
* const theme = getThemeEngine();
|
|
591
|
+
* theme.setMode('dark');
|
|
592
|
+
* ```
|
|
593
|
+
*/
|
|
199
594
|
export function getThemeEngine(): ThemeEngine {
|
|
200
595
|
if (!engine) engine = new ThemeEngine();
|
|
201
596
|
return engine;
|
|
202
597
|
}
|
|
203
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Destroys the module-level singleton, forcing the next call to
|
|
601
|
+
* {@link getThemeEngine} to create a fresh instance.
|
|
602
|
+
*
|
|
603
|
+
* **Intended for test isolation only.** Do not call this in application code,
|
|
604
|
+
* as it will cause components sharing the singleton to lose their reference.
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* ```ts
|
|
608
|
+
* afterEach(() => {
|
|
609
|
+
* resetThemeEngine(); // Start each test with a clean engine.
|
|
610
|
+
* });
|
|
611
|
+
* ```
|
|
612
|
+
*/
|
|
204
613
|
export function resetThemeEngine() {
|
|
205
614
|
engine = null;
|
|
206
615
|
}
|
package/src/web-components.ts
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file web-components.ts
|
|
3
|
+
* @package @moni-labs/moni-ui
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @contributors Moni Labs & Contributors
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @module web-components
|
|
10
|
+
*
|
|
11
|
+
* Lightweight entry point that registers and exports only the Moni UI Web
|
|
12
|
+
* Components, without importing any Svelte-specific utilities.
|
|
13
|
+
*
|
|
14
|
+
* **When to use this instead of the main `index.ts`:**
|
|
15
|
+
* - In non-Svelte projects (React, Vue, vanilla JS, HTML-only) where importing
|
|
16
|
+
* `theme.svelte.ts` would introduce an unnecessary Svelte 5 dependency.
|
|
17
|
+
* - When bundler tree-shaking is not sufficient to eliminate Svelte imports.
|
|
18
|
+
*
|
|
19
|
+
* This file has the same side-effect as the main entry: it calls
|
|
20
|
+
* `customElements.define()` for every `<moni-*>` element via the
|
|
21
|
+
* `./components/index.js` import.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* // In a React or Vue project:
|
|
26
|
+
* import '@moni-labs/moni-ui/web-components';
|
|
27
|
+
* // All <moni-*> elements are now available globally in the DOM.
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// Side-effect: auto-registers every <moni-*> custom element.
|
|
3
32
|
import './components/index.js';
|
|
4
33
|
|
|
5
34
|
export * from './components/index.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="304" height="304" fill="none" viewBox="0 0 304 304"><path fill="#d0bcff" d="M304 253.72c0 6.11 0 9.17-.31 11.74-2.38 20.05-18.18 35.85-38.23 38.23-2.57.31-5.63.31-11.74.31H50.281c-6.112 0-9.168 0-11.737-.31-20.049-2.38-35.856-18.18-38.239-38.23C0 262.89 0 259.83 0 253.72V152C0 68.05 68.053 0 152 0c83.95 0 152 68.05 152 152z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="316" height="278" fill="none" viewBox="0 0 316 278"><path fill="#d0bcff" d="M271.57 122.2c-14.018-21.58-28.035-43.19-42.053-64.77-9.094-14.01-18.35-28.21-30.645-39.34-12.296-11.15-28.224-19.03-44.556-18-14.34.92-27.632 8.63-38.125 18.7S97.546 41.26 89.528 53.54C67.842 86.72 46.13 119.9 24.444 153.1 14.139 168.86 3.565 185.31.713 204.09c-3.444 22.69 5.839 45.8 22.305 60.89 17.219 15.78 45.12 14.5 66.08 10.18 22.977-4.75 45.443-13.68 68.877-13.65 20.072 0 39.471 6.6 59.004 11.4 19.506 4.77 40.466 7.71 59.3.61 23.38-8.79 40.169-33.79 39.712-59.45-.431-23.41-13.534-44.32-26.152-63.8-6.081-9.35-12.161-18.72-18.242-28.07z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" fill="none" viewBox="0 0 320 320"><path fill="#d0bcff" d="M156.818 10.16c.753-6.02 1.13-9.03 1.702-9.57a2.145 2.145 0 0 1 2.96 0c.572.54.949 3.55 1.702 9.57l9.578 76.58c.304 2.43.456 3.64.892 4.15.633.72 1.668.95 2.541.54.601-.28 1.232-1.33 2.493-3.43l39.729-66.04c3.124-5.19 4.686-7.79 5.427-8.05 1.078-.37 2.26.16 2.704 1.22.305.72-.569 3.63-2.317 9.44l-22.23 73.87c-.706 2.35-1.058 3.52-.863 4.15a2.17 2.17 0 0 0 2.102 1.54c.663-.01 1.663-.71 3.663-2.11l63.01-44.08c4.956-3.47 7.433-5.2 8.214-5.14a2.177 2.177 0 0 1 1.98 2.21c-.014.79-1.988 3.09-5.935 7.68l-50.194 58.4c-1.593 1.85-2.389 2.78-2.468 3.44-.115.96.414 1.88 1.299 2.26.609.27 1.806.03 4.199-.43l75.397-14.5c5.929-1.14 8.894-1.71 9.58-1.33.998.55 1.398 1.79.915 2.83-.332.71-3.064 2-8.528 4.58l-69.478 32.83c-2.206 1.04-3.308 1.56-3.648 2.13-.493.83-.382 1.89.272 2.6.45.49 1.636.77 4.009 1.32l74.747 17.59c5.878 1.38 8.818 2.07 9.29 2.7.687.91.552 2.21-.309 2.96-.593.51-3.61.58-9.646.7l-76.75 1.57c-2.436.05-3.654.08-4.195.46a2.19 2.19 0 0 0-.803 2.49c.214.63 1.184 1.37 3.126 2.85l61.173 46.62c4.81 3.67 7.216 5.51 7.393 6.27a2.18 2.18 0 0 1-1.48 2.58c-.749.23-3.531-.95-9.096-3.3l-70.749-29.95c-2.246-.95-3.369-1.43-4.019-1.3a2.18 2.18 0 0 0-1.739 1.94c-.059.66.528 1.74 1.703 3.88l37.021 67.62c2.911 5.31 4.367 7.97 4.219 8.75a2.175 2.175 0 0 1-2.395 1.75c-.778-.1-2.843-2.31-6.973-6.74l-52.517-56.3c-1.667-1.78-2.501-2.68-3.146-2.82a2.15 2.15 0 0 0-2.374 1.06c-.323.58-.22 1.8-.015 4.24l6.467 76.91c.509 6.05.763 9.07.315 9.72a2.16 2.16 0 0 1-2.895.62c-.672-.41-1.663-3.28-3.645-9.01l-25.204-72.9c-.8-2.32-1.2-3.47-1.73-3.87a2.16 2.16 0 0 0-2.598 0c-.53.4-.93 1.55-1.73 3.87l-25.204 72.9c-1.982 5.73-2.973 8.6-3.645 9.01-.976.59-2.242.32-2.895-.62-.448-.65-.194-3.67.315-9.72l6.467-76.91c.205-2.44.308-3.66-.015-4.24a2.146 2.146 0 0 0-2.373-1.06c-.646.14-1.48 1.04-3.147 2.82l-52.517 56.3c-4.13 4.43-6.195 6.64-6.973 6.74a2.175 2.175 0 0 1-2.395-1.75c-.148-.78 1.308-3.44 4.219-8.75l37.021-67.62c1.175-2.14 1.762-3.22 1.703-3.88a2.18 2.18 0 0 0-1.739-1.94c-.65-.13-1.773.35-4.019 1.3l-70.75 29.95c-5.564 2.35-8.346 3.53-9.095 3.3a2.18 2.18 0 0 1-1.48-2.58c.177-.76 2.583-2.6 7.393-6.27l61.173-46.62c1.942-1.48 2.912-2.22 3.126-2.85a2.19 2.19 0 0 0-.803-2.49c-.541-.38-1.76-.41-4.195-.46l-76.75-1.57c-6.036-.12-9.054-.19-9.646-.7a2.19 2.19 0 0 1-.309-2.96c.472-.63 3.412-1.32 9.29-2.7l74.747-17.59c2.373-.55 3.559-.83 4.01-1.32.653-.71.764-1.77.271-2.6-.34-.57-1.442-1.09-3.648-2.13L15.63 117.94c-5.464-2.58-8.196-3.87-8.528-4.58a2.18 2.18 0 0 1 .915-2.83c.686-.38 3.65.19 9.58 1.33l75.397 14.5c2.393.46 3.59.7 4.199.43a2.18 2.18 0 0 0 1.299-2.26c-.078-.66-.875-1.59-2.468-3.44L45.83 62.69c-3.947-4.59-5.92-6.89-5.935-7.68a2.18 2.18 0 0 1 1.98-2.21c.78-.06 3.258 1.67 8.214 5.14l63.01 44.08c2 1.4 3 2.1 3.663 2.11a2.17 2.17 0 0 0 2.102-1.54c.195-.63-.157-1.8-.863-4.15l-22.23-73.87c-1.748-5.81-2.622-8.72-2.317-9.44a2.17 2.17 0 0 1 2.704-1.22c.741.26 2.303 2.86 5.427 8.05L141.314 88c1.261 2.1 1.892 3.15 2.493 3.43.873.41 1.908.18 2.541-.54.436-.51.588-1.72.892-4.15z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" fill="none" viewBox="0 0 320 320"><path fill="#d0bcff" d="M157.39 2.55c.74-1.32 1.1-1.97 1.55-2.25.65-.4 1.47-.4 2.12 0 .45.28.81.93 1.55 2.25l25.26 45.15c.45.81.68 1.22.99 1.46.44.36 1.01.51 1.57.42.39-.06.79-.29 1.58-.77l44.46-26.47c1.29-.77 1.94-1.16 2.46-1.17.77-.03 1.47.38 1.84 1.05.25.47.24 1.22.22 2.73l-.7 51.73c-.02.93-.02 1.39.12 1.76.2.53.62.95 1.15 1.15.37.14.83.14 1.76.12l51.73-.7c1.51-.02 2.26-.03 2.73.22.67.37 1.08 1.07 1.05 1.84-.01.52-.4 1.17-1.17 2.46l-26.47 44.46c-.48.79-.71 1.19-.77 1.58-.09.56.06 1.13.42 1.57.24.31.65.54 1.46.99l45.15 25.26c1.32.74 1.97 1.1 2.25 1.55.4.65.4 1.47 0 2.12-.28.45-.93.81-2.25 1.55l-45.15 25.26c-.81.45-1.22.68-1.46.99-.36.44-.51 1.01-.42 1.57.06.39.29.79.77 1.58l26.47 44.46c.77 1.29 1.16 1.94 1.17 2.46.03.77-.38 1.47-1.05 1.84-.47.25-1.22.24-2.73.22l-51.73-.7c-.93-.02-1.39-.02-1.76.12-.53.2-.95.62-1.15 1.15-.14.37-.14.83-.12 1.76l.7 51.73c.02 1.51.03 2.26-.22 2.73-.37.67-1.07 1.08-1.84 1.05-.52-.01-1.17-.4-2.46-1.17l-44.46-26.47c-.79-.48-1.19-.71-1.58-.77-.56-.09-1.13.06-1.57.42-.31.24-.54.65-.99 1.46l-25.26 45.15c-.74 1.32-1.1 1.97-1.55 2.25-.65.4-1.47.4-2.12 0-.45-.28-.81-.93-1.55-2.25l-25.26-45.15c-.45-.81-.68-1.22-.99-1.46-.44-.36-1.01-.51-1.57-.42-.39.06-.79.29-1.58.77l-44.46 26.47c-1.29.77-1.94 1.16-2.46 1.17-.77.03-1.47-.38-1.84-1.05-.25-.47-.24-1.22-.22-2.73l.7-51.73c.02-.93.02-1.39-.12-1.76-.2-.53-.62-.95-1.15-1.15-.37-.14-.83-.14-1.76-.12l-51.73.7c-1.51.02-2.26.03-2.73-.22a2.01 2.01 0 0 1-1.05-1.84c.01-.52.4-1.17 1.17-2.46l26.47-44.46c.48-.79.71-1.19.77-1.58.09-.56-.06-1.13-.42-1.57-.24-.31-.65-.54-1.46-.99L2.55 162.61c-1.32-.74-1.97-1.1-2.25-1.55-.4-.65-.4-1.47 0-2.12.28-.45.93-.81 2.25-1.55l45.15-25.26c.81-.45 1.22-.68 1.46-.99.36-.44.51-1.01.42-1.57-.06-.39-.29-.79-.77-1.58L22.34 83.53c-.77-1.29-1.16-1.94-1.17-2.46-.03-.77.38-1.47 1.05-1.84.47-.25 1.22-.24 2.73-.22l51.73.7c.93.02 1.39.02 1.76-.12.53-.2.95-.62 1.15-1.15.14-.37.14-.83.12-1.76l-.7-51.73c-.02-1.51-.03-2.26.22-2.73.37-.67 1.07-1.08 1.84-1.05.52.01 1.17.4 2.46 1.17l44.46 26.47c.79.48 1.19.71 1.58.77.56.09 1.13-.06 1.57-.42.31-.24.54-.65.99-1.46z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" fill="none" viewBox="0 0 320 320"><path fill="#d0bcff" d="M320 160c0 88.366-71.634 160-160 160S0 248.366 0 160 71.635 0 160 0c88.366 0 160 71.635 160 160"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="254" fill="none" viewBox="0 0 320 254"><path fill="#d0bcff" d="M306.405 84.081c6.709 13.51 10.063 20.265 11.757 27.322a67.1 67.1 0 0 1 0 31.194c-1.694 7.057-5.048 13.812-11.757 27.322l-20.438 41.16c-6.709 13.51-10.063 20.265-14.472 25.501-6.375 7.568-14.411 12.963-23.236 15.597C242.155 254 235.446 254 222.028 254H97.972c-13.418 0-20.127 0-26.231-1.822-8.825-2.635-16.861-8.029-23.236-15.598-4.409-5.235-7.763-11.991-14.472-25.501l-20.438-41.16c-6.709-13.51-10.063-20.265-11.757-27.322a67.1 67.1 0 0 1 0-31.194c1.694-7.057 5.048-13.812 11.757-27.322l20.438-41.16c6.709-13.51 10.063-20.266 14.472-25.501C54.88 9.851 62.916 4.457 71.741 1.822 77.845 0 84.554 0 97.972 0h124.056c13.418 0 20.127 0 26.231 1.822 8.825 2.635 16.861 8.029 23.236 15.598 4.409 5.235 7.763 11.99 14.472 25.501z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="268" height="320" fill="none" viewBox="0 0 268 320"><path fill="#d0bcff" d="M191.442 276.481c-16.836 22.033-25.254 33.049-34.976 38.067a49.02 49.02 0 0 1-44.932 0c-9.722-5.018-18.14-16.034-34.976-38.067l-55.912-73.173c-10.24-13.402-15.36-20.102-17.895-27.276a48.1 48.1 0 0 1 0-32.064c2.535-7.174 7.655-13.874 17.895-27.276l55.912-73.173c16.836-22.033 25.254-33.05 34.976-38.067a49.02 49.02 0 0 1 44.932 0c9.722 5.018 18.14 16.034 34.976 38.068l55.912 73.172c10.24 13.402 15.36 20.102 17.895 27.276a48.1 48.1 0 0 1 0 32.064c-2.535 7.174-7.655 13.874-17.895 27.276z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="280" height="280" fill="none" viewBox="0 0 280 280"><path fill="#d0bcff" d="M0 44.21c0-1.56 0-2.34.02-2.999C.717 18.757 18.757.717 41.211.021c.66-.021 1.44-.021 3-.021C52.53 0 56.69 0 60.208.11 179.96 3.825 276.17 100.037 279.89 219.791c.11 3.518.11 7.678.11 15.997 0 1.56 0 2.34-.02 3-.7 22.454-18.74 40.494-41.19 41.191-.66.02-1.44.02-3 .02H62.945c-19.018 0-28.528 0-36.071-2.987a42.53 42.53 0 0 1-23.887-23.887C0 245.583 0 236.073 0 217.055z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" fill="none" viewBox="0 0 320 320"><path fill="#d0bcff" fill-rule="evenodd" d="M273.13 46.863c-11.88-11.875-38.94-6.003-71.58 12.818C191.77 23.286 176.79 0 160 0c-16.8 0-31.78 23.284-41.55 59.678-32.65-18.82-59.7-24.69-71.58-12.817-11.87 11.876-6 38.935 12.82 71.585C23.29 128.222 0 143.204 0 160c0 16.793 23.28 31.773 59.68 41.549-18.83 32.651-24.7 59.712-12.83 71.588 11.88 11.876 38.94 6.001 71.59-12.827C128.22 296.711 143.2 320 160 320c16.79 0 31.77-23.291 41.55-59.693 32.66 18.829 59.72 24.705 71.6 12.829 11.87-11.876 6-38.936-12.83-71.587C296.72 191.773 320 176.793 320 160c0-16.796-23.29-31.778-59.7-41.554 18.83-32.65 24.7-59.708 12.83-71.583" clip-rule="evenodd"/></svg>
|