@sigx/lynx-daisyui 0.4.3 → 0.4.5

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/dist/index.d.ts CHANGED
@@ -54,8 +54,8 @@ export { NavDrawer } from './navigation/NavDrawer.js';
54
54
  export type { NavDrawerProps, NavDrawerSide, } from './navigation/NavDrawer.js';
55
55
  export { SwiperIndicator } from './navigation/SwiperIndicator.js';
56
56
  export type { SwiperIndicatorProps, SwiperIndicatorVariant, SwiperIndicatorSize, } from './navigation/SwiperIndicator.js';
57
- export { ThemeProvider, useTheme, listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, } from './theme/ThemeProvider.js';
58
- export type { DaisyTheme, ThemeController, ThemeProviderProps, Theme, ThemePalette, ThemeRadius, ThemeVariant, } from './theme/ThemeProvider.js';
57
+ export { ThemeProvider, useTheme, listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, sizesOf, } from './theme/ThemeProvider.js';
58
+ export type { DaisyTheme, ThemeController, ThemeProviderProps, Theme, ThemePalette, ThemeRadius, ThemeSizes, ThemeVariant, } from './theme/ThemeProvider.js';
59
59
  export { StatusBarSync } from './theme/StatusBarSync.js';
60
60
  export type { StatusBarSyncProps } from './theme/StatusBarSync.js';
61
61
  export { themeController } from './theme/theme-state.js';
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ export { NavHeader } from './navigation/NavHeader.js';
32
32
  export { NavDrawer } from './navigation/NavDrawer.js';
33
33
  export { SwiperIndicator } from './navigation/SwiperIndicator.js';
34
34
  // Theme
35
- export { ThemeProvider, useTheme, listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, } from './theme/ThemeProvider.js';
35
+ export { ThemeProvider, useTheme, listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, sizesOf, } from './theme/ThemeProvider.js';
36
36
  export { StatusBarSync } from './theme/StatusBarSync.js';
37
37
  // Headless theme handle (issue #113): import and call from anywhere — stores,
38
38
  // services, effects, app-boot — with no `<ThemeProvider>` ancestor required.
@@ -39,6 +39,23 @@ const daisyColors = {
39
39
  'error': 'var(--color-error)',
40
40
  'error-content': 'var(--color-error-content)',
41
41
  };
42
+ /**
43
+ * Text ramp → token map. Re-points Tailwind's `text-xs`…`text-3xl` font-size
44
+ * utilities at the daisy `--text-*` custom properties (defaults in
45
+ * `styles/themes/tokens.css`, multiplied app-wide by the controller's
46
+ * `fontScale`). Symmetric with `daisyColors`: a single source of truth. Merged
47
+ * via `theme.extend.fontSize`, so the larger Tailwind keys (`text-4xl`+) keep
48
+ * their rem defaults.
49
+ */
50
+ const daisyFontSizes = {
51
+ 'xs': 'var(--text-xs)',
52
+ 'sm': 'var(--text-sm)',
53
+ 'base': 'var(--text-base)',
54
+ 'lg': 'var(--text-lg)',
55
+ 'xl': 'var(--text-xl)',
56
+ '2xl': 'var(--text-2xl)',
57
+ '3xl': 'var(--text-3xl)',
58
+ };
42
59
  const lynxLayoutPlugin = plugin(({ addUtilities }) => {
43
60
  addUtilities({
44
61
  // Long-form flex-fill — the Lynx-correct "take remaining space along
@@ -58,6 +75,7 @@ export const DaisyLynxPreset = {
58
75
  theme: {
59
76
  extend: {
60
77
  colors: daisyColors,
78
+ fontSize: daisyFontSizes,
61
79
  },
62
80
  },
63
81
  plugins: [lynxLayoutPlugin],
@@ -5,7 +5,7 @@
5
5
  flex-direction: row;
6
6
  align-items: center;
7
7
  padding: var(--padding-box);
8
- border-radius: var(--rounded-box);
8
+ border-radius: var(--radius-box);
9
9
  gap: 12px;
10
10
  background-color: var(--color-base-200);
11
11
  color: var(--color-base-content);
@@ -7,7 +7,7 @@
7
7
  height: var(--badge-md);
8
8
  padding-left: 10px;
9
9
  padding-right: 10px;
10
- border-radius: var(--rounded-badge);
10
+ border-radius: var(--radius-selector);
11
11
  font-size: var(--font-md);
12
12
  font-weight: 600;
13
13
  background-color: var(--color-base-200);
@@ -8,7 +8,7 @@
8
8
  min-height: var(--size-md);
9
9
  padding-left: var(--padding-btn-md);
10
10
  padding-right: var(--padding-btn-md);
11
- border-radius: var(--rounded-btn);
11
+ border-radius: var(--radius-field);
12
12
  font-size: var(--font-md);
13
13
  font-weight: 600;
14
14
  line-height: 1;
@@ -3,7 +3,7 @@
3
3
  .card {
4
4
  background-color: var(--color-base-200);
5
5
  color: var(--color-base-content);
6
- border-radius: var(--rounded-box);
6
+ border-radius: var(--radius-box);
7
7
  overflow: hidden;
8
8
  }
9
9
 
@@ -3,7 +3,7 @@
3
3
  .checkbox {
4
4
  width: var(--checkbox-md);
5
5
  height: var(--checkbox-md);
6
- border-radius: var(--rounded-selector);
6
+ border-radius: var(--radius-selector);
7
7
  border-width: 1px;
8
8
  border-color: var(--color-base-300);
9
9
  background-color: transparent;
@@ -4,7 +4,7 @@
4
4
  height: var(--size-md);
5
5
  padding-left: var(--padding-btn-md);
6
6
  padding-right: var(--padding-btn-md);
7
- border-radius: var(--rounded-btn);
7
+ border-radius: var(--radius-field);
8
8
  font-size: var(--font-md);
9
9
  background-color: var(--color-base-100);
10
10
  color: var(--color-base-content);
@@ -15,7 +15,7 @@
15
15
  .modal-box {
16
16
  max-width: var(--modal-max-width);
17
17
  width: 90%;
18
- border-radius: var(--rounded-box);
18
+ border-radius: var(--radius-box);
19
19
  background-color: var(--color-base-200);
20
20
  padding: 0;
21
21
  overflow: hidden;
@@ -7,7 +7,7 @@
7
7
  height: var(--size-md);
8
8
  padding-left: var(--padding-btn-md);
9
9
  padding-right: var(--padding-btn-md);
10
- border-radius: var(--rounded-btn);
10
+ border-radius: var(--radius-field);
11
11
  font-size: var(--font-md);
12
12
  background-color: var(--color-base-100);
13
13
  color: var(--color-base-content);
@@ -38,7 +38,7 @@
38
38
  background-color: var(--color-base-100);
39
39
  border-width: var(--border-btn);
40
40
  border-color: var(--color-base-300);
41
- border-radius: var(--rounded-btn);
41
+ border-radius: var(--radius-field);
42
42
  overflow: hidden;
43
43
  }
44
44
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  .skeleton {
4
4
  background-color: var(--color-base-300);
5
- border-radius: var(--rounded-btn);
5
+ border-radius: var(--radius-field);
6
6
  }
@@ -4,7 +4,7 @@
4
4
  padding: 12px;
5
5
  padding-left: var(--padding-btn-md);
6
6
  padding-right: var(--padding-btn-md);
7
- border-radius: var(--rounded-btn);
7
+ border-radius: var(--radius-field);
8
8
  font-size: var(--font-md);
9
9
  background-color: var(--color-base-100);
10
10
  color: var(--color-base-content);
@@ -8,10 +8,10 @@
8
8
  }
9
9
 
10
10
  /* Size variants — track dimensions */
11
- .toggle-xs { width: var(--toggle-width-xs); height: var(--toggle-height-xs); border-radius: var(--rounded-toggle); }
12
- .toggle-sm { width: var(--toggle-width-sm); height: var(--toggle-height-sm); border-radius: var(--rounded-toggle); }
13
- .toggle-md { width: var(--toggle-width-md); height: var(--toggle-height-md); border-radius: var(--rounded-toggle); }
14
- .toggle-lg { width: var(--toggle-width-lg); height: var(--toggle-height-lg); border-radius: var(--rounded-toggle); }
11
+ .toggle-xs { width: var(--toggle-width-xs); height: var(--toggle-height-xs); border-radius: var(--radius-selector); }
12
+ .toggle-sm { width: var(--toggle-width-sm); height: var(--toggle-height-sm); border-radius: var(--radius-selector); }
13
+ .toggle-md { width: var(--toggle-width-md); height: var(--toggle-height-md); border-radius: var(--radius-selector); }
14
+ .toggle-lg { width: var(--toggle-width-lg); height: var(--toggle-height-lg); border-radius: var(--radius-selector); }
15
15
 
16
16
  /* Thumb */
17
17
  .toggle-thumb {
@@ -1,5 +1,6 @@
1
1
  /*
2
- * Daisy semantic color utilities.
2
+ * Daisy typography utilities — semantic colors, plus the font-size ramp and
3
+ * font weights (the latter two at the end of this file).
3
4
  *
4
5
  * bg-<token>: every token in DAISY_COLOR_TOKEN_LIST (semantics +
5
6
  * their -content variants, plus the three base surfaces
@@ -64,3 +65,27 @@
64
65
  .bg-warning-content { background-color: var(--color-warning-content); }
65
66
  .bg-error { background-color: var(--color-error); }
66
67
  .bg-error-content { background-color: var(--color-error-content); }
68
+
69
+ /* Font-size ramp — always-on for the same reason as the colors above: the
70
+ * `<Text>` / `<Heading>` components compose `text-<size>` class names
71
+ * dynamically, which a consumer's Tailwind content scanner can't see, so the
72
+ * utilities would otherwise be purged and text falls back to Lynx's native
73
+ * size. These mirror the preset's `text-* → var(--text-*)` mapping (kept in
74
+ * sync), reading the `--text-*` tokens from `themes/tokens.css` (scaled by the
75
+ * controller's `fontScale`). A consumer that DOES scan the package source just
76
+ * regenerates the identical rule — harmless. */
77
+ .text-xs { font-size: var(--text-xs); }
78
+ .text-sm { font-size: var(--text-sm); }
79
+ .text-base { font-size: var(--text-base); }
80
+ .text-lg { font-size: var(--text-lg); }
81
+ .text-xl { font-size: var(--text-xl); }
82
+ .text-2xl { font-size: var(--text-2xl); }
83
+ .text-3xl { font-size: var(--text-3xl); }
84
+
85
+ /* Font weights — always-on for the same reason (composed by `<Text weight>` /
86
+ * `<Heading>`). Values match Tailwind's `font-*` scale. */
87
+ .font-light { font-weight: 300; }
88
+ .font-normal { font-weight: 400; }
89
+ .font-medium { font-weight: 500; }
90
+ .font-semibold { font-weight: 600; }
91
+ .font-bold { font-weight: 700; }
@@ -8,6 +8,11 @@
8
8
  /* Structural design tokens (.daisy) + composable shape modifiers */
9
9
  @import './themes/tokens.css';
10
10
  @import './themes/shapes.css';
11
+ /* Per-theme color tokens for the built-in themes — generated from the theme
12
+ * registry at build time (scripts/gen-theme-css.mjs). Declared as CSS rules so
13
+ * `var(--color-*)` resolves on the first paint; the runtime `setProperty` path
14
+ * can't set inheritable custom properties before descendants have painted. */
15
+ @import './themes/builtins.generated.css';
11
16
 
12
17
  /* Base reset */
13
18
  @import './base.css';
@@ -0,0 +1,140 @@
1
+ /* AUTO-GENERATED from src/theme/registry.ts — do not edit.
2
+ * Regenerated by scripts/gen-theme-css.mjs on every build. */
3
+
4
+ .daisy-light {
5
+ --color-primary: #491dff;
6
+ --color-primary-content: #d3dbff;
7
+ --color-secondary: #ff20cc;
8
+ --color-secondary-content: #fff8fc;
9
+ --color-accent: #00cfbd;
10
+ --color-accent-content: #00100d;
11
+ --color-neutral: #2b3440;
12
+ --color-neutral-content: #d7dde4;
13
+ --color-base-100: #ffffff;
14
+ --color-base-200: #f2f2f2;
15
+ --color-base-300: #e5e6e6;
16
+ --color-base-content: #1f2937;
17
+ --color-info: #00b4fa;
18
+ --color-info-content: #000000;
19
+ --color-success: #00a96e;
20
+ --color-success-content: #000000;
21
+ --color-warning: #ffc100;
22
+ --color-warning-content: #000000;
23
+ --color-error: #ff676a;
24
+ --color-error-content: #000000;
25
+ }
26
+
27
+ .daisy-cupcake {
28
+ --color-primary: #65c3c8;
29
+ --color-primary-content: #052124;
30
+ --color-secondary: #ef9fbc;
31
+ --color-secondary-content: #2d0a16;
32
+ --color-accent: #eeaf3a;
33
+ --color-accent-content: #2d1c00;
34
+ --color-neutral: #291334;
35
+ --color-neutral-content: #f5f1f8;
36
+ --color-base-100: #faf7f5;
37
+ --color-base-200: #efeae6;
38
+ --color-base-300: #e7e2df;
39
+ --color-base-content: #291334;
40
+ --color-info: #00b4fa;
41
+ --color-info-content: #000000;
42
+ --color-success: #00a96e;
43
+ --color-success-content: #000000;
44
+ --color-warning: #ffc100;
45
+ --color-warning-content: #000000;
46
+ --color-error: #ff676a;
47
+ --color-error-content: #000000;
48
+ }
49
+
50
+ .daisy-emerald {
51
+ --color-primary: #66cc8a;
52
+ --color-primary-content: #06200f;
53
+ --color-secondary: #377cfb;
54
+ --color-secondary-content: #02112d;
55
+ --color-accent: #f68067;
56
+ --color-accent-content: #2d0a02;
57
+ --color-neutral: #333c4d;
58
+ --color-neutral-content: #e9eaed;
59
+ --color-base-100: #ffffff;
60
+ --color-base-200: #f3f4f6;
61
+ --color-base-300: #e5e7eb;
62
+ --color-base-content: #333c4d;
63
+ --color-info: #1c92f2;
64
+ --color-info-content: #000a14;
65
+ --color-success: #00a96e;
66
+ --color-success-content: #000a05;
67
+ --color-warning: #ff9900;
68
+ --color-warning-content: #261600;
69
+ --color-error: #ff5724;
70
+ --color-error-content: #000000;
71
+ }
72
+
73
+ .daisy-dark {
74
+ --color-primary: #7582ff;
75
+ --color-primary-content: #050617;
76
+ --color-secondary: #ff71cf;
77
+ --color-secondary-content: #190211;
78
+ --color-accent: #00e7d0;
79
+ --color-accent-content: #001210;
80
+ --color-neutral: #2a323c;
81
+ --color-neutral-content: #a6adbb;
82
+ --color-base-100: #1d232a;
83
+ --color-base-200: #191e24;
84
+ --color-base-300: #343b46;
85
+ --color-base-content: #a6adbb;
86
+ --color-info: #00b4fa;
87
+ --color-info-content: #000000;
88
+ --color-success: #00a96e;
89
+ --color-success-content: #000000;
90
+ --color-warning: #ffc100;
91
+ --color-warning-content: #000000;
92
+ --color-error: #ff676a;
93
+ --color-error-content: #000000;
94
+ }
95
+
96
+ .daisy-synthwave {
97
+ --color-primary: #e779c1;
98
+ --color-primary-content: #2a0a1f;
99
+ --color-secondary: #58c7f3;
100
+ --color-secondary-content: #02141d;
101
+ --color-accent: #f3cc30;
102
+ --color-accent-content: #2a1f00;
103
+ --color-neutral: #20134e;
104
+ --color-neutral-content: #e3e0f5;
105
+ --color-base-100: #2d1b69;
106
+ --color-base-200: #261159;
107
+ --color-base-300: #1f0f4a;
108
+ --color-base-content: #f9f7fd;
109
+ --color-info: #53c0f3;
110
+ --color-info-content: #02151e;
111
+ --color-success: #71ead2;
112
+ --color-success-content: #002721;
113
+ --color-warning: #f3cc30;
114
+ --color-warning-content: #2a1f00;
115
+ --color-error: #e24056;
116
+ --color-error-content: #ffffff;
117
+ }
118
+
119
+ .daisy-dracula {
120
+ --color-primary: #ff79c6;
121
+ --color-primary-content: #2d0414;
122
+ --color-secondary: #bd93f9;
123
+ --color-secondary-content: #160226;
124
+ --color-accent: #50fa7b;
125
+ --color-accent-content: #002a0e;
126
+ --color-neutral: #414558;
127
+ --color-neutral-content: #f8f8f2;
128
+ --color-base-100: #282a36;
129
+ --color-base-200: #21222c;
130
+ --color-base-300: #181920;
131
+ --color-base-content: #f8f8f2;
132
+ --color-info: #8be9fd;
133
+ --color-info-content: #002a31;
134
+ --color-success: #50fa7b;
135
+ --color-success-content: #002a0e;
136
+ --color-warning: #f1fa8c;
137
+ --color-warning-content: #2a2900;
138
+ --color-error: #ff5555;
139
+ --color-error-content: #2a0000;
140
+ }
@@ -3,19 +3,13 @@
3
3
  <ThemeProvider class="daisy-rounded"> → host class="daisy daisy-rounded" */
4
4
 
5
5
  .daisy-flat {
6
- --rounded-box: 0px;
7
- --rounded-btn: 0px;
8
- --rounded-badge: 0px;
9
- --rounded-tab: 0px;
10
- --rounded-selector: 0px;
11
- --rounded-toggle: 0px;
6
+ --radius-selector: 0px;
7
+ --radius-field: 0px;
8
+ --radius-box: 0px;
12
9
  }
13
10
 
14
11
  .daisy-rounded {
15
- --rounded-box: 24px;
16
- --rounded-btn: 9999px;
17
- --rounded-badge: 9999px;
18
- --rounded-tab: 9999px;
19
- --rounded-selector: 9999px;
20
- --rounded-toggle: 9999px;
12
+ --radius-selector: 9999px;
13
+ --radius-field: 9999px;
14
+ --radius-box: 24px;
21
15
  }
@@ -8,21 +8,47 @@
8
8
  * may still override roundness via its `radius` field. */
9
9
 
10
10
  .daisy {
11
- /* ── Roundness ── */
12
- --rounded-box: 16px;
13
- --rounded-btn: 8px;
14
- --rounded-badge: 9999px;
15
- --rounded-tab: 8px;
16
- --rounded-selector: 8px;
17
- --rounded-toggle: 9999px;
11
+ /* ── Roundness (DaisyUI v5 contract) ── */
12
+ --radius-selector: 8px;
13
+ --radius-field: 8px;
14
+ --radius-box: 16px;
18
15
 
19
- /* ── Sizing scale ── */
16
+ /* ── Base size units (DaisyUI v5 contract) ──
17
+ * A theme's `sizes` ({ field, selector }) overrides these; <ThemeProvider>
18
+ * re-derives the component dimensions below from them in JS. We deliberately
19
+ * do NOT use `calc(var() * n)` in CSS — it's unproven in Lynx's runtime CSS
20
+ * engine. Defaults below are base 4px × the per-token multiples shown. */
21
+ --size-selector: 4px;
22
+ --size-field: 4px;
23
+
24
+ /* ── Field sizing scale (button / input / select) — field × 6/8/12/16 ── */
20
25
  --size-xs: 24px;
21
26
  --size-sm: 32px;
22
27
  --size-md: 48px;
23
28
  --size-lg: 64px;
24
29
 
25
- /* ── Font sizes ── */
30
+ /* ── Text ramp (public <Text> / <Heading> sizes) ──
31
+ * The xs→3xl scale the `text-*` utilities resolve to (the daisy preset maps
32
+ * Tailwind's fontSize keys onto these vars). Literal px so they resolve on
33
+ * the first paint, like every other structural token. Anchored on a 17px
34
+ * base — the iOS HIG Body size, so bare <Text> (the default) reads as native
35
+ * body — and stepped as an even ~1.2 modular scale (each step ≈1.17–1.21×).
36
+ *
37
+ * The controller's `fontScale` multiplies this ramp app-wide; <ThemeProvider>
38
+ * re-emits the literals in JS (no `calc(var() * n)` — unproven in Lynx).
39
+ * NOTE: keep in sync with FONT_DEFAULTS in theme/ThemeProvider.tsx. */
40
+ --text-xs: 12px;
41
+ --text-sm: 14px;
42
+ --text-base: 17px;
43
+ --text-lg: 20px;
44
+ --text-xl: 24px;
45
+ --text-2xl: 28px;
46
+ --text-3xl: 34px;
47
+
48
+ /* ── Font sizes (control-internal: button / input / badge labels) ──
49
+ * A separate axis from the text ramp above and deliberately NOT scaled with
50
+ * it — control chrome stays fixed. Kept as independent literals (no
51
+ * var→var indirection; unproven in Lynx) even though values mirror the ramp. */
26
52
  --font-xs: 12px;
27
53
  --font-sm: 14px;
28
54
  --font-md: 14px;
@@ -41,27 +67,28 @@
41
67
  --border-btn: 1px;
42
68
  --border-card: 1px;
43
69
 
44
- /* ── Component-specific sizes ── */
45
- --checkbox-xs: 16px;
46
- --checkbox-sm: 20px;
47
- --checkbox-md: 24px;
48
- --checkbox-lg: 32px;
49
- --toggle-width-xs: 32px;
50
- --toggle-width-sm: 40px;
51
- --toggle-width-md: 48px;
52
- --toggle-width-lg: 56px;
53
- --toggle-height-xs: 22px;
54
- --toggle-height-sm: 24px;
55
- --toggle-height-md: 28px;
56
- --toggle-height-lg: 32px;
57
- --toggle-thumb-xs: 14px;
58
- --toggle-thumb-sm: 16px;
59
- --toggle-thumb-md: 20px;
60
- --toggle-thumb-lg: 24px;
61
- --badge-xs: 16px;
62
- --badge-sm: 20px;
63
- --badge-md: 24px;
64
- --badge-lg: 32px;
70
+ /* ── Selector-driven sizes (checkbox / toggle / badge) — selector × the
71
+ * multiples shown; <ThemeProvider> re-derives these from `sizes.selector`. ── */
72
+ --checkbox-xs: 16px; /* ×4 */
73
+ --checkbox-sm: 20px; /* ×5 */
74
+ --checkbox-md: 24px; /* ×6 */
75
+ --checkbox-lg: 32px; /* ×8 */
76
+ --toggle-width-xs: 32px; /* ×8 */
77
+ --toggle-width-sm: 40px; /* ×10 */
78
+ --toggle-width-md: 48px; /* ×12 */
79
+ --toggle-width-lg: 56px; /* ×14 */
80
+ --toggle-height-xs: 24px; /* ×6 */
81
+ --toggle-height-sm: 24px; /* ×6 */
82
+ --toggle-height-md: 28px; /* ×7 */
83
+ --toggle-height-lg: 32px; /* ×8 */
84
+ --toggle-thumb-xs: 16px; /* ×4 */
85
+ --toggle-thumb-sm: 16px; /* ×4 */
86
+ --toggle-thumb-md: 20px; /* ×5 */
87
+ --toggle-thumb-lg: 24px; /* ×6 */
88
+ --badge-xs: 16px; /* ×4 */
89
+ --badge-sm: 20px; /* ×5 */
90
+ --badge-md: 24px; /* ×6 */
91
+ --badge-lg: 32px; /* ×8 */
65
92
  --step-indicator: 32px;
66
93
  --progress-height: 8px;
67
94
  --modal-max-width: 400px;
@@ -88,6 +88,19 @@ export interface ThemeController {
88
88
  * with no `initial` prop. Useful for a "Reset to system" button.
89
89
  */
90
90
  followSystem(): void;
91
+ /**
92
+ * Current global text-scale multiplier (`1` = the theme's default ramp).
93
+ * Reactive — read inside render/effect to track. Orthogonal to the theme:
94
+ * `set()` / `toggle()` leave it untouched.
95
+ */
96
+ readonly fontScale: number;
97
+ /**
98
+ * Set the global text-scale multiplier — the `--text-*` ramp is re-emitted
99
+ * at `defaultPx × scale`. Persists across theme switches, so it's the place
100
+ * to wire a user accessibility preference or a backend-driven setting (e.g.
101
+ * `setFontScale(1.25)`). Inherits into nested `<ThemeProvider>` subtrees.
102
+ */
103
+ setFontScale(scale: number): void;
91
104
  }
92
105
  /**
93
106
  * Access the active daisyui theme controller. Resolves to the nearest
@@ -118,6 +131,13 @@ Define.Prop<'initial', DaisyTheme, false>
118
131
  * while `followingSystem` is true.
119
132
  */
120
133
  & Define.Prop<'dark', DaisyTheme, false>
134
+ /**
135
+ * Initial global text-scale multiplier (`1` = default ramp). Seeds the
136
+ * controller's `fontScale`; change it later via `controller.setFontScale()`.
137
+ * On the root provider an explicit value wins over any scale a headless
138
+ * caller set before mount.
139
+ */
140
+ & Define.Prop<'fontScale', number, false>
121
141
  /** Extra classes appended to the theme class on the host view. */
122
142
  & Define.Prop<'class', string, false>
123
143
  /** Extra inline style on the host view. Merged after the base flex-fill defaults. */
@@ -138,5 +158,5 @@ Define.Prop<'initial', DaisyTheme, false>
138
158
  export declare const ThemeProvider: import("@sigx/runtime-core").ComponentFactory<ThemeProviderProps, void, {
139
159
  default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
140
160
  }>;
141
- export { listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, } from './registry.js';
142
- export type { Theme, ThemePalette, ThemeRadius, ThemeVariant } from './registry.js';
161
+ export { listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, sizesOf, } from './registry.js';
162
+ export type { Theme, ThemePalette, ThemeRadius, ThemeSizes, ThemeVariant, } from './registry.js';
@@ -38,8 +38,95 @@ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
38
38
  import { component, defineInjectable, defineProvide, effect, onMounted, onUnmounted, signal, untrack, } from '@sigx/lynx';
39
39
  import { useIconColorResolver } from '@sigx/lynx-icons';
40
40
  import { useSystemColorScheme } from '@sigx/lynx-appearance';
41
- import { colorsOf, pickThemeFor, radiusOf } from './registry.js';
42
- import { globalThemeState, makeThemeController, themeController, } from './theme-state.js';
41
+ import { colorsOf, isBuiltInTheme, pickThemeFor, radiusOf, sizesOf, variantOf, } from './registry.js';
42
+ import { globalThemeState, makeThemeController, normalizeFontScale, themeController, } from './theme-state.js';
43
+ // DaisyUI v5 expresses control dimensions as multiples of two base units
44
+ // (`--size-field`, `--size-selector`). Lynx's runtime CSS engine is unproven
45
+ // for `calc(var() * n)`, so when a theme overrides a base unit we do the
46
+ // multiplication here and emit literal px. Bases must be px (engine-safe, like
47
+ // colors); a non-px base sets only the base var and leaves the `.daisy`
48
+ // defaults in place. Multiples mirror the defaults in `styles/themes/tokens.css`.
49
+ const FIELD_STEPS = { xs: 6, sm: 8, md: 12, lg: 16 };
50
+ const SELECTOR_STEPS = {
51
+ 'checkbox-xs': 4, 'checkbox-sm': 5, 'checkbox-md': 6, 'checkbox-lg': 8,
52
+ 'toggle-width-xs': 8, 'toggle-width-sm': 10, 'toggle-width-md': 12, 'toggle-width-lg': 14,
53
+ 'toggle-height-xs': 6, 'toggle-height-sm': 6, 'toggle-height-md': 7, 'toggle-height-lg': 8,
54
+ 'toggle-thumb-xs': 4, 'toggle-thumb-sm': 4, 'toggle-thumb-md': 5, 'toggle-thumb-lg': 6,
55
+ 'badge-xs': 4, 'badge-sm': 5, 'badge-md': 6, 'badge-lg': 8,
56
+ };
57
+ // Default text ramp (px) — MUST mirror `--text-*` in `styles/themes/tokens.css`
58
+ // (iOS-aligned, 17px base). The global `fontScale` multiplies these and emits
59
+ // literal px (no `calc(var() * n)` — unproven in Lynx).
60
+ const FONT_DEFAULTS = {
61
+ 'xs': 12, 'sm': 14, 'base': 17, 'lg': 20, 'xl': 24, '2xl': 28, '3xl': 34,
62
+ };
63
+ const pxValue = (v) => {
64
+ const m = /^\s*(\d+(?:\.\d+)?)px\s*$/.exec(v);
65
+ return m ? Number(m[1]) : undefined;
66
+ };
67
+ /** Emit a theme's `sizes` overrides as literal-px CSS custom properties. */
68
+ function applySizeVars(style, sizes) {
69
+ if (sizes.field) {
70
+ style['--size-field'] = sizes.field;
71
+ const base = pxValue(sizes.field);
72
+ if (base !== undefined) {
73
+ for (const k in FIELD_STEPS)
74
+ style[`--size-${k}`] = `${base * FIELD_STEPS[k]}px`;
75
+ }
76
+ }
77
+ if (sizes.selector) {
78
+ style['--size-selector'] = sizes.selector;
79
+ const base = pxValue(sizes.selector);
80
+ if (base !== undefined) {
81
+ for (const k in SELECTOR_STEPS)
82
+ style[`--${k}`] = `${base * SELECTOR_STEPS[k]}px`;
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * Emit the text ramp scaled by `fontScale` as `--text-*` literal px. Always
88
+ * emits every step — even at `1` (the literal defaults) — because Lynx's
89
+ * `setProperty` only merges and never clears: skipping at `1` would leave a
90
+ * previously-emitted larger ramp stuck after a reset. A nested provider seeds
91
+ * its scale from the inherited ambient value, so emitting here re-states the
92
+ * same ramp rather than clobbering the root's scaled one.
93
+ */
94
+ function applyFontScale(style, fontScale) {
95
+ for (const k in FONT_DEFAULTS) {
96
+ style[`--text-${k}`] = `${Math.round(FONT_DEFAULTS[k] * fontScale)}px`;
97
+ }
98
+ }
99
+ /**
100
+ * The full custom-property set for a theme — colors, any radius/size overrides,
101
+ * and the `fontScale`-adjusted text ramp. Applied at runtime via the Lynx
102
+ * `setProperty` API (see
103
+ * `<ThemeProvider>`), NOT the inline `style` attribute: Lynx does not honor
104
+ * custom properties declared inline in this toolchain, but `setProperty`
105
+ * registers real, inheritable ones — the documented way to theme via CSS
106
+ * variables (https://lynxjs.org/guide/styling/custom-theming).
107
+ */
108
+ function buildThemeVars(name, fontScale) {
109
+ const palette = colorsOf(name) ?? colorsOf('daisy-light');
110
+ const radius = radiusOf(name);
111
+ const sizes = sizesOf(name);
112
+ const vars = {};
113
+ for (const key in palette)
114
+ vars[`--color-${key}`] = palette[key];
115
+ if (radius) {
116
+ if (radius.selector)
117
+ vars['--radius-selector'] = radius.selector;
118
+ if (radius.field)
119
+ vars['--radius-field'] = radius.field;
120
+ if (radius.box)
121
+ vars['--radius-box'] = radius.box;
122
+ }
123
+ if (sizes)
124
+ applySizeVars(vars, sizes);
125
+ applyFontScale(vars, fontScale);
126
+ return vars;
127
+ }
128
+ /** Unique host id per provider instance so `getElementById` targets its own subtree. */
129
+ let themeIdSeq = 0;
43
130
  /**
44
131
  * Access the active daisyui theme controller. Resolves to the nearest
45
132
  * `<ThemeProvider>`'s controller (a content sub-scope), or — at the app root
@@ -57,6 +144,13 @@ export const useTheme = defineInjectable(() => themeController);
57
144
  * global theme or the system bars.
58
145
  */
59
146
  const useThemeDepth = defineInjectable(() => 0);
147
+ /**
148
+ * Ambient text scale inherited by nested providers. A nested `<ThemeProvider>`
149
+ * with no explicit `fontScale` prop seeds from this (the enclosing scale,
150
+ * default 1) instead of resetting to 1 — so the root's scale flows down through
151
+ * color sub-scopes. Each provider re-provides its own current scale.
152
+ */
153
+ const useAmbientFontScale = defineInjectable(() => 1);
60
154
  /**
61
155
  * Wraps children in a `<view class={theme}>` so the daisyui CSS variables
62
156
  * defined inside the theme class inherit down to every descendant.
@@ -82,15 +176,24 @@ export const ThemeProvider = component(({ props, slots }) => {
82
176
  const depth = useThemeDepth();
83
177
  const isRoot = depth === 0;
84
178
  defineProvide(useThemeDepth, () => depth + 1);
179
+ // Stable id for the host view so the runtime `setProperty` call (below) can
180
+ // target it. Unique per instance so nested providers theme their own subtree.
181
+ const hostId = `daisy-theme-${++themeIdSeq}`;
182
+ // A nested provider with no explicit `fontScale` inherits the enclosing
183
+ // scale (default 1 at the root), so the root's scale flows down through
184
+ // color sub-scopes rather than resetting. An explicit prop overrides it.
185
+ const ambientFontScale = useAmbientFontScale();
186
+ const seedScale = normalizeFontScale(props.fontScale, ambientFontScale);
85
187
  const state = isRoot
86
188
  ? globalThemeState
87
189
  : signal(props.initial
88
- ? { name: props.initial, following: false }
190
+ ? { name: props.initial, following: false, fontScale: seedScale }
89
191
  : {
90
192
  name: readScheme() === 'dark'
91
193
  ? (props.dark ?? pickThemeFor('dark'))
92
194
  : (props.light ?? pickThemeFor('light')),
93
195
  following: true,
196
+ fontScale: seedScale,
94
197
  });
95
198
  // Seed the root from props/system. An explicit `initial` pin is author
96
199
  // intent and wins. With no `initial`, reflect the current system scheme into
@@ -107,11 +210,18 @@ export const ThemeProvider = component(({ props, slots }) => {
107
210
  ? (props.dark ?? pickThemeFor('dark'))
108
211
  : (props.light ?? pickThemeFor('light'));
109
212
  }
213
+ // Explicit author intent wins; otherwise keep whatever scale a headless
214
+ // caller may have set before this mounted (default 1).
215
+ if (props.fontScale !== undefined) {
216
+ state.fontScale = normalizeFontScale(props.fontScale, state.fontScale);
217
+ }
110
218
  }
111
219
  const controller = isRoot
112
220
  ? themeController
113
221
  : makeThemeController(state);
114
222
  defineProvide(useTheme, () => controller);
223
+ // Re-provide this scope's current scale so nested providers inherit it.
224
+ defineProvide(useAmbientFontScale, () => state.fontScale);
115
225
  // Wire the daisy color resolver into `@sigx/lynx-icons`'s injectable
116
226
  // so any `<Icon variant="primary">` rendered inside this subtree gets
117
227
  // the daisy primary hex automatically. Reading `state.name` inside
@@ -135,6 +245,7 @@ export const ThemeProvider = component(({ props, slots }) => {
135
245
  // Created on mount (the native publisher may populate the scheme between
136
246
  // setup and mount) and torn down on unmount.
137
247
  let follow;
248
+ let applyVars;
138
249
  onMounted(() => {
139
250
  follow = effect(() => {
140
251
  const following = state.following;
@@ -149,22 +260,43 @@ export const ThemeProvider = component(({ props, slots }) => {
149
260
  state.name = next;
150
261
  });
151
262
  });
263
+ // Built-in themes are themed by their generated CSS class (applied on
264
+ // the host below), which resolves on the very first frame. This
265
+ // `setProperty` path additionally serves runtime-registered themes
266
+ // (`registerTheme`, no shipped CSS class) — applied once they're
267
+ // selected post-mount, where it lands reliably. Reading `state.name`
268
+ // and `state.fontScale` (via buildThemeVars) tracks them, so this
269
+ // re-runs on every theme change and every `setFontScale`.
270
+ applyVars = effect(() => {
271
+ const vars = buildThemeVars(state.name, state.fontScale);
272
+ if (typeof lynx !== 'undefined') {
273
+ lynx.getElementById(hostId)?.setProperty(vars);
274
+ }
275
+ });
152
276
  });
153
277
  onUnmounted(() => {
154
278
  follow?.stop();
155
279
  follow = undefined;
280
+ applyVars?.stop();
281
+ applyVars = undefined;
156
282
  });
157
283
  return () => {
158
- // Every theme is data. Apply its color tokens as inline CSS custom
159
- // properties Lynx inherits custom properties to descendants, so
160
- // component classes resolve `var(--color-*)` against these (the same
161
- // mechanism SafeAreaProvider uses for `--sat`/`--sal`). The `daisy`
162
- // base class supplies theme-agnostic structural tokens (radius,
163
- // sizing); a theme may override roundness via `radius`. The root
164
- // background/text are painted from the palette literals (inline
165
- // `var()` values don't resolve in Lynx).
284
+ // Theme COLORS and any radius/size overrides are applied as real,
285
+ // inheritable CSS custom properties via the Lynx `setProperty` runtime
286
+ // API (see the `applyVars` effect above) Lynx does NOT honor custom
287
+ // properties declared through the inline `style` attribute in this
288
+ // toolchain. The root background/text are painted here from palette
289
+ // literals (real properties, not custom props) so the surface is themed
290
+ // on first paint; descendants resolve `var(--color-*)` once setProperty
291
+ // has run. The `daisy` base class supplies structural token defaults.
166
292
  const palette = colorsOf(state.name) ?? colorsOf('daisy-light');
167
- const radius = radiusOf(state.name);
293
+ // Built-ins ship a generated CSS class, so `state.name` alone paints on
294
+ // the first frame. A runtime-registered theme has no class — fall back
295
+ // to its variant's built-in class for the first frame; the `setProperty`
296
+ // effect above then swaps in its exact palette post-mount.
297
+ const themeClass = isBuiltInTheme(state.name)
298
+ ? state.name
299
+ : `${pickThemeFor(variantOf(state.name) ?? 'light')} ${state.name}`;
168
300
  const style = {
169
301
  flexGrow: 1,
170
302
  flexShrink: 1,
@@ -175,27 +307,10 @@ export const ThemeProvider = component(({ props, slots }) => {
175
307
  backgroundColor: palette['base-100'],
176
308
  color: palette['base-content'],
177
309
  };
178
- for (const key in palette) {
179
- style[`--color-${key}`] = palette[key];
180
- }
181
- if (radius) {
182
- if (radius.box)
183
- style['--rounded-box'] = radius.box;
184
- if (radius.btn)
185
- style['--rounded-btn'] = radius.btn;
186
- if (radius.badge)
187
- style['--rounded-badge'] = radius.badge;
188
- if (radius.tab)
189
- style['--rounded-tab'] = radius.tab;
190
- if (radius.selector)
191
- style['--rounded-selector'] = radius.selector;
192
- if (radius.toggle)
193
- style['--rounded-toggle'] = radius.toggle;
194
- }
195
310
  if (props.style)
196
311
  Object.assign(style, props.style);
197
- return (_jsx("view", { class: `daisy${props.class ? ' ' + props.class : ''}`, style: style, children: slots.default?.() }));
312
+ return (_jsx("view", { id: hostId, class: `daisy ${themeClass}${props.class ? ' ' + props.class : ''}`, style: style, children: slots.default?.() }));
198
313
  };
199
314
  });
200
315
  // Re-export registry helpers so consumers only need `@sigx/lynx-daisyui`.
201
- export { listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, } from './registry.js';
316
+ export { listThemes, registerTheme, extendTheme, pickThemeFor, pairOf, variantOf, colorsOf, radiusOf, sizesOf, } from './registry.js';
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * Structural tokens (radius, sizing, component dimensions) are theme-agnostic
19
19
  * and ship once in the bundled `.daisy` base class (`styles/themes/tokens.css`);
20
- * a theme may override roundness via `radius`.
20
+ * a theme may override roundness via `radius` and base size units via `sizes`.
21
21
  *
22
22
  * Colors are engine-safe strings — hex or `rgb()`. Lynx's CSS engine does not
23
23
  * parse `oklch()`, so convert before registering.
@@ -26,14 +26,29 @@ import type { DaisyColor } from '../shared/styles.js';
26
26
  export type ThemeVariant = 'light' | 'dark';
27
27
  /** Full daisy color palette — every semantic token, no holes. */
28
28
  export type ThemePalette = Record<DaisyColor, string>;
29
- /** Roundness token overrides. Defaults live in the bundled `.daisy` base. */
29
+ /**
30
+ * Roundness token overrides (DaisyUI v5 contract). Emitted as
31
+ * `--radius-selector` / `--radius-field` / `--radius-box`. Defaults live in
32
+ * the bundled `.daisy` base.
33
+ */
30
34
  export interface ThemeRadius {
35
+ /** Small selectable controls — checkbox, toggle, badge. */
36
+ selector?: string;
37
+ /** Fields — button, input, select, textarea. */
38
+ field?: string;
39
+ /** Boxes — card, modal, alert. */
31
40
  box?: string;
32
- btn?: string;
33
- badge?: string;
34
- tab?: string;
41
+ }
42
+ /**
43
+ * Base size-unit overrides (DaisyUI v5 contract). Emitted as
44
+ * `--size-selector` / `--size-field`; component dimensions are integer
45
+ * multiples of these. Defaults live in the bundled `.daisy` base.
46
+ */
47
+ export interface ThemeSizes {
48
+ /** Base unit for selector controls (checkbox, toggle, badge). */
35
49
  selector?: string;
36
- toggle?: string;
50
+ /** Base unit for fields (button, input, select). */
51
+ field?: string;
37
52
  }
38
53
  export interface Theme {
39
54
  /** Unique id — also the value of `theme.name`. */
@@ -49,7 +64,16 @@ export interface Theme {
49
64
  pair?: string;
50
65
  /** Optional roundness overrides; unspecified tokens fall back to `.daisy`. */
51
66
  radius?: ThemeRadius;
67
+ /** Optional base size-unit overrides; unspecified tokens fall back to `.daisy`. */
68
+ sizes?: ThemeSizes;
52
69
  }
70
+ /**
71
+ * Whether `name` is a built-in theme that ships a CSS class — i.e. it paints
72
+ * correctly on the first frame. Runtime-registered themes return `false`;
73
+ * `<ThemeProvider>` falls back to their variant's built-in class for first
74
+ * paint and swaps in the exact palette via `setProperty`.
75
+ */
76
+ export declare function isBuiltInTheme(name: string | undefined): boolean;
53
77
  /**
54
78
  * All registered themes in insertion order. Returns a shallow copy so callers
55
79
  * can't mutate the internal registry — re-registration goes through
@@ -80,6 +104,7 @@ export declare function extendTheme(base: string, patch: {
80
104
  pair?: string;
81
105
  colors?: Partial<ThemePalette>;
82
106
  radius?: ThemeRadius;
107
+ sizes?: ThemeSizes;
83
108
  }): Theme;
84
109
  /** The variant of a registered theme, or `undefined` if not registered. */
85
110
  export declare function variantOf(name: string | undefined): ThemeVariant | undefined;
@@ -87,6 +112,8 @@ export declare function variantOf(name: string | undefined): ThemeVariant | unde
87
112
  export declare function colorsOf(name: string | undefined): ThemePalette | undefined;
88
113
  /** The roundness overrides of a registered theme, if any. */
89
114
  export declare function radiusOf(name: string | undefined): ThemeRadius | undefined;
115
+ /** The base size-unit overrides of a registered theme, if any. */
116
+ export declare function sizesOf(name: string | undefined): ThemeSizes | undefined;
90
117
  /**
91
118
  * Pick a default theme for a given system color scheme — the first registered
92
119
  * theme of that variant (`daisy-light` / `daisy-dark` under the seeded
@@ -89,6 +89,23 @@ const registry = [
89
89
  },
90
90
  },
91
91
  ];
92
+ /**
93
+ * Names of the themes seeded at module load — the built-ins that ship a
94
+ * generated CSS class (`scripts/gen-theme-css.mjs`). Captured before any
95
+ * runtime `registerTheme()`, so it distinguishes "has a first-paint CSS class"
96
+ * from runtime-registered themes (which only have their palette as data and
97
+ * apply via `setProperty` post-mount).
98
+ */
99
+ const BUILTIN_NAMES = new Set(registry.map((t) => t.name));
100
+ /**
101
+ * Whether `name` is a built-in theme that ships a CSS class — i.e. it paints
102
+ * correctly on the first frame. Runtime-registered themes return `false`;
103
+ * `<ThemeProvider>` falls back to their variant's built-in class for first
104
+ * paint and swaps in the exact palette via `setProperty`.
105
+ */
106
+ export function isBuiltInTheme(name) {
107
+ return name != null && BUILTIN_NAMES.has(name);
108
+ }
92
109
  /**
93
110
  * Resolve a `theme.name` to its registered `Theme`. Supports multi-class names
94
111
  * like `'daisy-light daisy-rounded'` by matching the first registered id found.
@@ -147,6 +164,7 @@ export function extendTheme(base, patch) {
147
164
  pair: patch.pair ?? src.pair,
148
165
  colors: { ...src.colors, ...patch.colors },
149
166
  radius: patch.radius ?? src.radius,
167
+ sizes: patch.sizes ?? src.sizes,
150
168
  };
151
169
  }
152
170
  /** The variant of a registered theme, or `undefined` if not registered. */
@@ -161,6 +179,10 @@ export function colorsOf(name) {
161
179
  export function radiusOf(name) {
162
180
  return findTheme(name)?.radius;
163
181
  }
182
+ /** The base size-unit overrides of a registered theme, if any. */
183
+ export function sizesOf(name) {
184
+ return findTheme(name)?.sizes;
185
+ }
164
186
  /**
165
187
  * Pick a default theme for a given system color scheme — the first registered
166
188
  * theme of that variant (`daisy-light` / `daisy-dark` under the seeded
@@ -3,7 +3,21 @@ import type { DaisyTheme, ThemeController } from './ThemeProvider.js';
3
3
  export interface ThemeState {
4
4
  name: DaisyTheme;
5
5
  following: boolean;
6
+ /**
7
+ * Global text-scale multiplier applied on top of the theme's `--text-*`
8
+ * ramp. Orthogonal to `name`: a theme switch / `toggle()` leaves it
9
+ * untouched, so a user/accessibility scale persists across appearance
10
+ * changes. `1` = the default ramp.
11
+ */
12
+ fontScale: number;
6
13
  }
14
+ /**
15
+ * Coerce a font-scale input to a valid positive, finite multiplier. Rejects
16
+ * `NaN`, `±Infinity`, and non-positive values — which would otherwise emit
17
+ * invalid CSS (`NaNpx`, negative font sizes) and break `fontScale === 1`
18
+ * comparisons — by returning `fallback` instead.
19
+ */
20
+ export declare function normalizeFontScale(value: unknown, fallback?: number): number;
7
21
  /**
8
22
  * Build a `ThemeController` over a given state object. Used for both the global
9
23
  * singleton (below) and each nested `<ThemeProvider>`'s local state — same
@@ -20,6 +20,17 @@
20
20
  */
21
21
  import { signal } from '@sigx/lynx';
22
22
  import { pairOf, pickThemeFor } from './registry.js';
23
+ /**
24
+ * Coerce a font-scale input to a valid positive, finite multiplier. Rejects
25
+ * `NaN`, `±Infinity`, and non-positive values — which would otherwise emit
26
+ * invalid CSS (`NaNpx`, negative font sizes) and break `fontScale === 1`
27
+ * comparisons — by returning `fallback` instead.
28
+ */
29
+ export function normalizeFontScale(value, fallback = 1) {
30
+ return typeof value === 'number' && Number.isFinite(value) && value > 0
31
+ ? value
32
+ : fallback;
33
+ }
23
34
  /**
24
35
  * Build a `ThemeController` over a given state object. Used for both the global
25
36
  * singleton (below) and each nested `<ThemeProvider>`'s local state — same
@@ -34,6 +45,9 @@ export function makeThemeController(state) {
34
45
  get followingSystem() {
35
46
  return state.following;
36
47
  },
48
+ get fontScale() {
49
+ return state.fontScale;
50
+ },
37
51
  set(next) {
38
52
  state.name = next;
39
53
  state.following = false;
@@ -45,6 +59,10 @@ export function makeThemeController(state) {
45
59
  followSystem() {
46
60
  state.following = true;
47
61
  },
62
+ setFontScale(scale) {
63
+ // Ignore invalid input (keep the current scale) so state stays valid.
64
+ state.fontScale = normalizeFontScale(scale, state.fontScale);
65
+ },
48
66
  };
49
67
  }
50
68
  // Object signal (not primitive) so the `DaisyTheme` literal union survives —
@@ -54,6 +72,7 @@ export function makeThemeController(state) {
54
72
  const state = signal({
55
73
  name: pickThemeFor('light'),
56
74
  following: true,
75
+ fontScale: 1,
57
76
  });
58
77
  /**
59
78
  * The backing signal for the global theme. Read/written by the root
@@ -8,11 +8,21 @@ const weightClasses = {
8
8
  light: 'font-light', normal: 'font-normal', medium: 'font-medium',
9
9
  semibold: 'font-semibold', bold: 'font-bold',
10
10
  };
11
+ // A `text-<size>` font-size utility already present in `class` (the common
12
+ // `class="text-sm"` override idiom). The trailing `(?![\w-])` guard excludes
13
+ // color tokens like `text-base-content`, so they don't suppress the default.
14
+ const SIZE_IN_CLASS = /(?:^|\s)text-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)(?![\w-])/;
11
15
  export const Text = component(({ props, slots }) => {
12
16
  const getClasses = () => {
13
17
  const c = [];
18
+ // Defined default: `base` (--text-base, 17px) so text has a token-driven
19
+ // size instead of Lynx's native <text> default — unless the caller set an
20
+ // explicit `size` prop or already passed a `text-*` size through `class`
21
+ // (avoids emitting two conflicting font-size utilities).
14
22
  if (props.size)
15
23
  c.push(sizeClasses[props.size]);
24
+ else if (!(props.class && SIZE_IN_CLASS.test(props.class)))
25
+ c.push(sizeClasses.base);
16
26
  if (props.weight)
17
27
  c.push(weightClasses[props.weight]);
18
28
  if (props.color)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigx/lynx-daisyui",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "DaisyUI integration for sigx-lynx",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,15 +37,15 @@
37
37
  "LICENSE"
38
38
  ],
39
39
  "dependencies": {
40
- "@sigx/lynx": "^0.4.3",
41
- "@sigx/lynx-gestures": "^0.4.3",
42
- "@sigx/lynx-icons": "^0.4.3",
43
- "@sigx/lynx-appearance": "^0.4.3",
44
- "@sigx/lynx-motion": "^0.4.3"
40
+ "@sigx/lynx": "^0.4.5",
41
+ "@sigx/lynx-appearance": "^0.4.5",
42
+ "@sigx/lynx-gestures": "^0.4.5",
43
+ "@sigx/lynx-icons": "^0.4.5",
44
+ "@sigx/lynx-motion": "^0.4.5"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "tailwindcss": "^3.0.0 || ^4.0.0",
48
- "@sigx/lynx-navigation": "^0.4.3"
48
+ "@sigx/lynx-navigation": "^0.4.5"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@sigx/lynx-navigation": {
@@ -56,7 +56,7 @@
56
56
  "@typescript/native-preview": "7.0.0-dev.20260521.1",
57
57
  "tailwindcss": "^4.0.0",
58
58
  "typescript": "^6.0.3",
59
- "@sigx/lynx-navigation": "^0.4.3"
59
+ "@sigx/lynx-navigation": "^0.4.5"
60
60
  },
61
61
  "publishConfig": {
62
62
  "access": "public"
@@ -72,7 +72,7 @@
72
72
  "author": "Andreas Ekdahl",
73
73
  "license": "MIT",
74
74
  "scripts": {
75
- "build": "node ../../scripts/clean.mjs dist && tsgo && node ../../scripts/copy-assets.mjs src/styles dist/styles",
75
+ "build": "node ../../scripts/clean.mjs dist && tsgo && node ../../scripts/copy-assets.mjs src/styles dist/styles && node scripts/gen-theme-css.mjs",
76
76
  "dev": "tsgo --watch",
77
77
  "clean": "node ../../scripts/clean.mjs dist .turbo"
78
78
  }