@astryxdesign/core 0.4.2 → 0.4.3-canary.551a8de

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +79 -45
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +3 -19
  5. package/dist/Banner/Banner.d.ts +9 -2
  6. package/dist/Banner/Banner.d.ts.map +1 -1
  7. package/dist/Banner/Banner.js +84 -33
  8. package/dist/Banner/index.d.ts +14 -12
  9. package/dist/Banner/index.d.ts.map +1 -1
  10. package/dist/Banner/index.js +10 -8
  11. package/dist/Chat/ChatTokenizedText.js +1 -1
  12. package/dist/ComplexSelector/ComplexSelector.d.ts +38 -4
  13. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  14. package/dist/ComplexSelector/ComplexSelector.js +98 -31
  15. package/dist/ComplexSelector/index.d.ts +2 -2
  16. package/dist/ComplexSelector/index.d.ts.map +1 -1
  17. package/dist/ComplexSelector/index.js +1 -1
  18. package/dist/Markdown/parser.d.ts.map +1 -1
  19. package/dist/Markdown/parser.js +55 -12
  20. package/dist/MobileNav/MobileNav.d.ts +12 -0
  21. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  22. package/dist/MobileNav/MobileNav.js +102 -30
  23. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  24. package/dist/PowerSearch/PowerSearch.js +4 -1
  25. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  26. package/dist/PowerSearch/formatFilterValue.js +2 -4
  27. package/dist/Switch/Switch.d.ts.map +1 -1
  28. package/dist/Switch/Switch.js +5 -1
  29. package/dist/Table/columnUtils.d.ts.map +1 -1
  30. package/dist/Table/columnUtils.js +4 -1
  31. package/dist/TextArea/TextArea.d.ts +6 -3
  32. package/dist/TextArea/TextArea.d.ts.map +1 -1
  33. package/dist/TextArea/TextArea.js +17 -6
  34. package/dist/TreeList/TreeList.js +2 -1
  35. package/dist/astryx.css +8 -4
  36. package/dist/theme/defineTheme.d.ts +30 -7
  37. package/dist/theme/defineTheme.d.ts.map +1 -1
  38. package/dist/theme/defineTheme.js +36 -47
  39. package/dist/theme/expandColorScale.d.ts +17 -2
  40. package/dist/theme/expandColorScale.d.ts.map +1 -1
  41. package/dist/theme/expandColorScale.js +63 -33
  42. package/dist/theme/mergeComponents.d.ts +20 -0
  43. package/dist/theme/mergeComponents.d.ts.map +1 -0
  44. package/dist/theme/mergeComponents.js +56 -0
  45. package/dist/theme/onMediaTokens.d.ts +6 -1
  46. package/dist/theme/onMediaTokens.d.ts.map +1 -1
  47. package/dist/theme/onMediaTokens.js +11 -3
  48. package/dist/utils/characters.d.ts +27 -0
  49. package/dist/utils/characters.d.ts.map +1 -0
  50. package/dist/utils/characters.js +83 -0
  51. package/dist/utils/index.d.ts +1 -0
  52. package/dist/utils/index.d.ts.map +1 -1
  53. package/dist/utils/index.js +1 -0
  54. package/package.json +4 -8
  55. package/src/Avatar/Avatar.test.tsx +6 -1
  56. package/src/Avatar/Avatar.tsx +3 -21
  57. package/src/Banner/Banner.doc.mjs +12 -10
  58. package/src/Banner/Banner.test.tsx +108 -0
  59. package/src/Banner/Banner.tsx +124 -38
  60. package/src/Banner/index.ts +15 -13
  61. package/src/Chat/ChatTokenizedText.tsx +1 -1
  62. package/src/ComplexSelector/ComplexSelector.doc.mjs +55 -6
  63. package/src/ComplexSelector/ComplexSelector.test.tsx +197 -6
  64. package/src/ComplexSelector/ComplexSelector.tsx +156 -28
  65. package/src/ComplexSelector/index.ts +3 -1
  66. package/src/Markdown/parser.test.ts +53 -0
  67. package/src/Markdown/parser.ts +53 -12
  68. package/src/MobileNav/MobileNav.tsx +128 -37
  69. package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +426 -0
  70. package/src/MobileNav/MobileNavCloseTiming.test.ts +58 -0
  71. package/src/MobileNav/MobileNavCloseVisibility.test.tsx +172 -0
  72. package/src/PowerSearch/PowerSearch.test.tsx +48 -3
  73. package/src/PowerSearch/PowerSearch.tsx +4 -1
  74. package/src/PowerSearch/formatFilterValue.test.ts +22 -0
  75. package/src/PowerSearch/formatFilterValue.ts +2 -4
  76. package/src/Switch/Switch.test.tsx +16 -0
  77. package/src/Switch/Switch.tsx +7 -0
  78. package/src/Table/Table.test.tsx +6 -0
  79. package/src/Table/columnUtils.ts +3 -1
  80. package/src/TextArea/TextArea.doc.mjs +1 -1
  81. package/src/TextArea/TextArea.test.tsx +72 -0
  82. package/src/TextArea/TextArea.tsx +26 -8
  83. package/src/TreeList/TreeList.doc.mjs +2 -2
  84. package/src/TreeList/TreeList.tsx +1 -1
  85. package/src/__tests__/stubMatchMedia.test.ts +79 -0
  86. package/src/__tests__/stubMatchMedia.ts +95 -0
  87. package/src/theme/defineTheme.test.ts +127 -0
  88. package/src/theme/defineTheme.ts +70 -55
  89. package/src/theme/expandColorScale.test.ts +138 -0
  90. package/src/theme/expandColorScale.ts +89 -44
  91. package/src/theme/extensibleAxes.test.ts +365 -0
  92. package/src/theme/mergeComponents.ts +59 -0
  93. package/src/theme/onMediaTokens.ts +9 -2
  94. package/src/utils/characters.test.ts +141 -0
  95. package/src/utils/characters.ts +88 -0
  96. package/src/utils/index.ts +2 -0
  97. package/dist/astryx.umd.js +0 -147
  98. package/dist/astryx.umd.js.map +0 -7
@@ -44,6 +44,7 @@ import { expandRadiusScale } from "./expandRadiusScale.js";
44
44
  import { expandColorScale } from "./expandColorScale.js";
45
45
  import { domainTokenDefaults } from "./domainTokens/index.js";
46
46
  import { registerTheme } from "./themeRegistry.js";
47
+ import { deepMergeComponents } from "./mergeComponents.js";
47
48
 
48
49
  // =============================================================================
49
50
  // Types
@@ -155,48 +156,6 @@ function resolveTokenValue(value) {
155
156
  return value;
156
157
  }
157
158
 
158
- /**
159
- * Deep-merge two component style maps.
160
- * Properties in `overrides` take precedence over `base`.
161
- * This allows typeScale-generated rules to be overridden by explicit components.
162
- */
163
- function deepMergeComponents(base, overrides) {
164
- if (!base && !overrides) {
165
- return undefined;
166
- }
167
- if (!base) {
168
- return overrides;
169
- }
170
- if (!overrides) {
171
- return base;
172
- }
173
- const result = {};
174
-
175
- // Start with all base entries
176
- for (const [component, rules] of Object.entries(base)) {
177
- result[component] = {
178
- ...rules
179
- };
180
- }
181
-
182
- // Merge overrides on top
183
- for (const [component, rules] of Object.entries(overrides)) {
184
- if (!result[component]) {
185
- result[component] = {
186
- ...rules
187
- };
188
- } else {
189
- for (const [key, styles] of Object.entries(rules)) {
190
- result[component][key] = {
191
- ...result[component][key],
192
- ...styles
193
- };
194
- }
195
- }
196
- }
197
- return result;
198
- }
199
-
200
159
  /**
201
160
  * Resolve a FontWeight name to a var() reference.
202
161
  * Named weights map to var(--font-weight-*); raw values pass through.
@@ -226,6 +185,24 @@ function buildFontFamily(family, fallbacks) {
226
185
  return quoted;
227
186
  }
228
187
 
188
+ /**
189
+ * Describe a rejected `extends` value for the error message — enough to tell a
190
+ * missed import (`undefined`) from a module namespace or a plain object.
191
+ */
192
+ function describeBadBase(value) {
193
+ if (value === undefined) {
194
+ return 'undefined';
195
+ }
196
+ if (value === null) {
197
+ return 'null';
198
+ }
199
+ if (typeof value !== 'object') {
200
+ return typeof value;
201
+ }
202
+ const keys = Object.keys(value);
203
+ return `an object with keys [${keys.slice(0, 4).join(', ')}${keys.length > 4 ? ', …' : ''}]`;
204
+ }
205
+
229
206
  /**
230
207
  * Create an Astryx theme.
231
208
  *
@@ -239,7 +216,15 @@ function buildFontFamily(family, fallbacks) {
239
216
  export function defineTheme(input) {
240
217
  const tokens = {};
241
218
 
242
- // 0. Pre-seed from base theme when `extends` is provided (lowest precedence)
219
+ // 0. Pre-seed from base theme when `extends` is provided (lowest precedence).
220
+ // A base that is not a theme is refused rather than ignored: `extends` used
221
+ // to inherit nothing when its value was undefined, which is what a named
222
+ // import silently resolving to the wrong module hands over, and the theme
223
+ // then built into a plausible-looking stylesheet missing everything it was
224
+ // supposed to inherit.
225
+ if ('extends' in input && !isDefinedTheme(input.extends)) {
226
+ throw new Error(`defineTheme("${input.name}"): \`extends\` must be a theme from defineTheme(), got ${describeBadBase(input.extends)}. ` + `Check that the import naming your base theme resolves to its source and exports that name — ` + `a generated \`<theme>.js\` artifact sitting next to the source exports \`<name>Theme\`, not the source's own export.`);
227
+ }
243
228
  const base = input.extends;
244
229
  if (base) {
245
230
  for (const [key, value] of Object.entries(base.tokens)) {
@@ -370,9 +355,10 @@ export function defineTheme(input) {
370
355
  components = deepMergeComponents(base.components, components);
371
356
  }
372
357
 
373
- // 4. Resolve on-media token overrides (defaults + user overrides)
374
- const __onDark = resolveOnMedia('dark', input.onDark);
375
- const __onLight = resolveOnMedia('light', input.onLight);
358
+ // 4. Resolve on-media token overrides (base's resolved surface, then
359
+ // defaults, then this theme's own overrides)
360
+ const __onDark = resolveOnMedia('dark', input.onDark, base?.__onDark);
361
+ const __onLight = resolveOnMedia('light', input.onLight, base?.__onLight);
376
362
 
377
363
  // 5. Merge icons — input icons override base icons
378
364
  const icons = input.icons && base?.icons ? {
@@ -392,7 +378,10 @@ export function defineTheme(input) {
392
378
  components,
393
379
  icons,
394
380
  indicators,
395
- __inputTokens: input.tokens,
381
+ __inputTokens: base?.__inputTokens || input.tokens ? {
382
+ ...base?.__inputTokens,
383
+ ...input.tokens
384
+ } : undefined,
396
385
  __onDark,
397
386
  __onLight
398
387
  };
@@ -6,6 +6,9 @@
6
6
  * // Minimal — just a seed color
7
7
  * { accent: '#0064E0' }
8
8
  *
9
+ * // Per-scheme seeds: light palettes from '#0064E0', dark from '#48CAE4'
10
+ * { accent: ['#0064E0', '#48CAE4'] }
11
+ *
9
12
  * // With customization
10
13
  * { accent: '#B7410E', neutralStyle: 'warm', contrast: 'high' }
11
14
  *
@@ -15,14 +18,18 @@
15
18
  */
16
19
  export interface ColorScaleConfig {
17
20
  /**
18
- * Seed accent color as hex (#RRGGBB). Everything derives from this.
21
+ * Seed accent color. Everything derives from this.
22
+ *
23
+ * Either a single hex (#RRGGBB) used for both color schemes, or a
24
+ * `[light, dark]` hex tuple: the light scheme's palettes derive from
25
+ * the light seed and the dark scheme's palettes from the dark seed.
19
26
  *
20
27
  * Optional. When omitted, the neutral palettes are seeded from the
21
28
  * default accent's hue and the accent tokens (--color-accent,
22
29
  * --color-accent-muted, --color-on-accent) are not generated — they
23
30
  * fall through to colorDefaults.
24
31
  */
25
- accent?: string;
32
+ accent?: string | [light: string, dark: string];
26
33
  /**
27
34
  * Neutral tone warmth. Controls how much of the seed's hue bleeds
28
35
  * into neutral/background colors.
@@ -56,6 +63,11 @@ export declare function ensureContrastTone(hue: number, chroma: number, startTon
56
63
  * --color-on-dark/on-light) are NOT generated — they fall through
57
64
  * to colorDefaults.
58
65
  *
66
+ * A `[light, dark]` tuple accent seeds each scheme separately: the light
67
+ * half of every generated `light-dark()` pair comes from the light seed's
68
+ * palettes, the dark half from the dark seed's. A string accent seeds both
69
+ * halves from the same palettes, exactly as before tuples were supported.
70
+ *
59
71
  * Without an `accent`, the accent tokens join that fall-through set: the
60
72
  * neutrals are seeded from the default accent's hue, and --color-accent,
61
73
  * --color-accent-muted and --color-on-accent keep their colorDefaults values.
@@ -65,6 +77,9 @@ export declare function ensureContrastTone(hue: number, chroma: number, startTon
65
77
  * const tokens = expandColorScale({ accent: '#0064E0' });
66
78
  * // tokens['--color-accent'] === 'light-dark(#..., #...)'
67
79
  *
80
+ * const perScheme = expandColorScale({ accent: ['#0064E0', '#48CAE4'] });
81
+ * // light half derives from #0064E0, dark half from #48CAE4
82
+ *
68
83
  * const neutralOnly = expandColorScale({ neutralStyle: 'warm' });
69
84
  * // neutralOnly['--color-accent'] === undefined
70
85
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"expandColorScale.d.ts","sourceRoot":"","sources":["../../src/theme/expandColorScale.ts"],"names":[],"mappings":"AAwCA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAE3C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAyCtD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,CAYR;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAoI3E"}
1
+ {"version":3,"file":"expandColorScale.d.ts","sourceRoot":"","sources":["../../src/theme/expandColorScale.ts"],"names":[],"mappings":"AAgDA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAEhD;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAE3C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAyCtD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,MAAM,CAYR;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CA0J3E"}
@@ -6,8 +6,8 @@
6
6
  * @output Token overrides for derivable color tokens
7
7
  * @position Theme utility; consumed by defineTheme.ts
8
8
  *
9
- * Generates color token overrides from a single accent color using the
10
- * HCT perceptual color model. Only produces tokens that meaningfully
9
+ * Generates color token overrides from an accent seed using the HCT
10
+ * perceptual color model. Only produces tokens that meaningfully
11
11
  * derive from the accent — status colors, categorical hues, and fixed
12
12
  * tokens (on-dark/on-light) fall through to colorDefaults.
13
13
  *
@@ -15,11 +15,19 @@
15
15
  * ramp (seeded from the default accent's hue) while the accent tokens
16
16
  * themselves fall through to colorDefaults, same as the tokens above.
17
17
  *
18
+ * `accent` also accepts a `[light, dark]` tuple (matching `TokenValue` in
19
+ * defineTheme). A tuple seeds each color scheme from its own half: the
20
+ * light side of every generated `light-dark()` pair derives from the light
21
+ * accent's palettes, the dark side from the dark accent's. A plain string
22
+ * seeds both sides identically, token for token the same output as before
23
+ * tuples existed.
24
+ *
18
25
  * WCAG contrast guarantees (asserted in expandColorScale.test.ts):
19
26
  * - Text tones are guaranteed >= 4.5:1 against their surfaces by tone
20
27
  * spacing alone — HCT tone is CIE L*, which fixes relative luminance
21
28
  * regardless of hue/chroma, so the fixed tone assignments hold for any
22
- * accent/neutralStyle (WCAG 1.4.3).
29
+ * accent/neutralStyle (WCAG 1.4.3), and each half of a tuple pairs with
30
+ * surfaces derived from that same seed.
23
31
  * - --color-border-emphasized (form-control boundaries) is tone-bumped
24
32
  * until it reaches >= 3:1 against the generated surface (WCAG 1.4.11).
25
33
  * - --color-border, --color-skeleton, and --color-track are intentionally
@@ -46,6 +54,9 @@ import { hexToHct, hctToHex, tonalPalette, hexWithAlpha } from "./hct.js";
46
54
  * // Minimal — just a seed color
47
55
  * { accent: '#0064E0' }
48
56
  *
57
+ * // Per-scheme seeds: light palettes from '#0064E0', dark from '#48CAE4'
58
+ * { accent: ['#0064E0', '#48CAE4'] }
59
+ *
49
60
  * // With customization
50
61
  * { accent: '#B7410E', neutralStyle: 'warm', contrast: 'high' }
51
62
  *
@@ -126,6 +137,11 @@ export function ensureContrastTone(hue, chroma, startTone, step, background, min
126
137
  * --color-on-dark/on-light) are NOT generated — they fall through
127
138
  * to colorDefaults.
128
139
  *
140
+ * A `[light, dark]` tuple accent seeds each scheme separately: the light
141
+ * half of every generated `light-dark()` pair comes from the light seed's
142
+ * palettes, the dark half from the dark seed's. A string accent seeds both
143
+ * halves from the same palettes, exactly as before tuples were supported.
144
+ *
129
145
  * Without an `accent`, the accent tokens join that fall-through set: the
130
146
  * neutrals are seeded from the default accent's hue, and --color-accent,
131
147
  * --color-accent-muted and --color-on-accent keep their colorDefaults values.
@@ -135,6 +151,9 @@ export function ensureContrastTone(hue, chroma, startTone, step, background, min
135
151
  * const tokens = expandColorScale({ accent: '#0064E0' });
136
152
  * // tokens['--color-accent'] === 'light-dark(#..., #...)'
137
153
  *
154
+ * const perScheme = expandColorScale({ accent: ['#0064E0', '#48CAE4'] });
155
+ * // light half derives from #0064E0, dark half from #48CAE4
156
+ *
138
157
  * const neutralOnly = expandColorScale({ neutralStyle: 'warm' });
139
158
  * // neutralOnly['--color-accent'] === undefined
140
159
  * ```
@@ -145,14 +164,26 @@ export function expandColorScale(config) {
145
164
  neutralStyle = 'cool',
146
165
  contrast = 'standard'
147
166
  } = config;
148
- const seed = hexToHct(accent ?? DEFAULT_ACCENT_SEED);
149
- const seedHue = seed.hue;
150
- const primaryChroma = Math.max(seed.chroma, 48);
167
+
168
+ // Normalize to per-scheme seeds. A string accent (or an absent one) uses
169
+ // the same seed for both halves, which keeps single-seed output identical
170
+ // to the pre-tuple implementation.
171
+ const [lightAccent, darkAccent] = Array.isArray(accent) ? accent : [accent, accent];
172
+ const lightSeed = hexToHct(lightAccent ?? DEFAULT_ACCENT_SEED);
173
+ const sameSeed = darkAccent === lightAccent;
174
+ const darkSeed = sameSeed ? lightSeed : hexToHct(darkAccent ?? DEFAULT_ACCENT_SEED);
151
175
  const neutralChroma = NEUTRAL_CHROMA[neutralStyle] ?? 5;
152
176
  const neutralVariantChroma = NEUTRAL_VARIANT_CHROMA[neutralStyle] ?? 8;
153
- const P = tonalPalette(seedHue, primaryChroma);
154
- const N = tonalPalette(seedHue, neutralChroma);
155
- const NV = tonalPalette(seedHue, neutralVariantChroma);
177
+
178
+ // Palette naming: *L palettes feed the light (first) half of each ld()
179
+ // pair below, *D palettes the dark (second) half. With a single seed the
180
+ // D palettes alias the L ones.
181
+ const PL = tonalPalette(lightSeed.hue, Math.max(lightSeed.chroma, 48));
182
+ const NL = tonalPalette(lightSeed.hue, neutralChroma);
183
+ const NVL = tonalPalette(lightSeed.hue, neutralVariantChroma);
184
+ const PD = sameSeed ? PL : tonalPalette(darkSeed.hue, Math.max(darkSeed.chroma, 48));
185
+ const ND = sameSeed ? NL : tonalPalette(darkSeed.hue, neutralChroma);
186
+ const NVD = sameSeed ? NVL : tonalPalette(darkSeed.hue, neutralVariantChroma);
156
187
  const isHigh = contrast === 'high';
157
188
  const textPrimaryLightTone = isHigh ? 0 : 10;
158
189
  const textPrimaryDarkTone = isHigh ? 99 : 90;
@@ -173,7 +204,7 @@ export function expandColorScale(config) {
173
204
  // contrast starts at 70/30 and walks toward mid-scale only as far as needed.
174
205
  const borderEmphasizedStartLight = isHigh ? 50 : 70;
175
206
  const borderEmphasizedStartDark = isHigh ? 50 : 30;
176
- const borderEmphasized = ld(ensureContrastTone(seedHue, neutralVariantChroma, borderEmphasizedStartLight, -1, N[99], NON_TEXT_MIN_CONTRAST), ensureContrastTone(seedHue, neutralVariantChroma, borderEmphasizedStartDark, 1, N[10], NON_TEXT_MIN_CONTRAST));
207
+ const borderEmphasized = ld(ensureContrastTone(lightSeed.hue, neutralVariantChroma, borderEmphasizedStartLight, -1, NL[99], NON_TEXT_MIN_CONTRAST), ensureContrastTone(darkSeed.hue, neutralVariantChroma, borderEmphasizedStartDark, 1, ND[10], NON_TEXT_MIN_CONTRAST));
177
208
  return {
178
209
  // Core semantic — only with a seed accent. Without one these fall through
179
210
  // to colorDefaults, whose --color-accent is NOT what the default seed
@@ -181,47 +212,46 @@ export function expandColorScale(config) {
181
212
  // recolor every neutral-only theme. Nullish and not truthy, matching the
182
213
  // seed above, so a supplied-but-malformed accent keeps its old behavior.
183
214
  ...(accent != null ? {
184
- '--color-accent': ld(P[40], P[80]),
215
+ '--color-accent': ld(PL[40], PD[80]),
185
216
  // Derived accent tokens reference --color-accent instead of baking its
186
217
  // resolved hex, so a scoped override of the base token re-accents the
187
218
  // whole subtree at runtime. --color-on-accent stays baked: it is a
188
219
  // contrast computation against the accent, which CSS cannot express.
189
220
  '--color-accent-muted': ld(accentWithAlpha(0.2), accentWithAlpha(0.25)),
190
- '--color-on-accent': ld(P[100], P[20])
221
+ '--color-on-accent': ld(PL[100], PD[20])
191
222
  } : null),
192
- '--color-neutral': ld(hexWithAlpha(N[10], 0.1), hexWithAlpha(N[90], 0.2)),
193
- '--color-background-surface': ld(N[99], N[10]),
194
- '--color-background-body': ld(N[95], N[5]),
195
- '--color-overlay': ld(hexWithAlpha(N[10], 0.4), hexWithAlpha(N[10], 0.6)),
196
- '--color-overlay-hover': ld(hexWithAlpha(N[10], 0.05), hexWithAlpha(N[100], 0.05)),
197
- '--color-overlay-pressed': ld(hexWithAlpha(N[10], 0.1), hexWithAlpha(N[100], 0.1)),
198
- '--color-background-muted': ld(hexWithAlpha(N[10], 0.05), hexWithAlpha(N[10], 0.5)),
223
+ '--color-neutral': ld(hexWithAlpha(NL[10], 0.1), hexWithAlpha(ND[90], 0.2)),
224
+ '--color-background-surface': ld(NL[99], ND[10]),
225
+ '--color-background-body': ld(NL[95], ND[5]),
226
+ '--color-overlay': ld(hexWithAlpha(NL[10], 0.4), hexWithAlpha(ND[10], 0.6)),
227
+ '--color-overlay-hover': ld(hexWithAlpha(NL[10], 0.05), hexWithAlpha(ND[100], 0.05)),
228
+ '--color-overlay-pressed': ld(hexWithAlpha(NL[10], 0.1), hexWithAlpha(ND[100], 0.1)),
229
+ '--color-background-muted': ld(hexWithAlpha(NL[10], 0.05), hexWithAlpha(ND[10], 0.5)),
199
230
  // Text
200
- '--color-text-primary': ld(N[textPrimaryLightTone], N[textPrimaryDarkTone]),
201
- '--color-text-secondary': ld(NV[textSecondaryLightTone], NV[textSecondaryDarkTone]),
202
- '--color-text-disabled': ld(NV[60], NV[40]),
231
+ '--color-text-primary': ld(NL[textPrimaryLightTone], ND[textPrimaryDarkTone]),
232
+ '--color-text-secondary': ld(NVL[textSecondaryLightTone], NVD[textSecondaryDarkTone]),
233
+ '--color-text-disabled': ld(NVL[60], NVD[40]),
203
234
  '--color-text-accent': 'var(--color-accent)',
204
235
  // Icon
205
236
  '--color-icon-accent': 'var(--color-accent)',
206
- '--color-icon-primary': ld(N[textPrimaryLightTone], N[textPrimaryDarkTone]),
207
- '--color-icon-secondary': ld(NV[textSecondaryLightTone], NV[textSecondaryDarkTone]),
208
- '--color-icon-disabled': ld(NV[60], NV[40]),
237
+ '--color-icon-primary': ld(NL[textPrimaryLightTone], ND[textPrimaryDarkTone]),
238
+ '--color-icon-secondary': ld(NVL[textSecondaryLightTone], NVD[textSecondaryDarkTone]),
239
+ '--color-icon-disabled': ld(NVL[60], NVD[40]),
209
240
  // Surface variants
210
- '--color-background-card': ld(N[99], N[10]),
211
- '--color-background-popover': ld(N[99], N[20]),
212
- '--color-background-inverted': ld(N[10], N[99]),
213
- // Border
241
+ '--color-background-card': ld(NL[99], ND[10]),
242
+ '--color-background-popover': ld(NL[99], ND[20]),
243
+ '--color-background-inverted': ld(NL[10], ND[99]),
214
244
  // Border
215
245
  // Decorative hairline — not a WCAG 1.4.11 boundary. High contrast
216
246
  // doubles the alpha so structural boundaries stay perceivable.
217
- '--color-border': ld(hexWithAlpha(N[10], borderSubtleAlpha), hexWithAlpha(N[95], borderSubtleAlpha)),
247
+ '--color-border': ld(hexWithAlpha(NL[10], borderSubtleAlpha), hexWithAlpha(ND[95], borderSubtleAlpha)),
218
248
  '--color-border-emphasized': borderEmphasized,
219
249
  // Effects
220
- '--color-skeleton': ld(NV[70], NV[30]),
250
+ '--color-skeleton': ld(NVL[70], NVD[30]),
221
251
  // Channel-on-body surface (ProgressBar/Slider tracks, Switch off-state).
222
252
  // Defaults to the same NV[70]/NV[30] ramp stop as --color-skeleton.
223
- '--color-track': ld(NV[70], NV[30]),
224
- '--color-shadow': ld(hexWithAlpha(N[0], 0.1), hexWithAlpha(N[0], 0.3)),
253
+ '--color-track': ld(NVL[70], NVD[30]),
254
+ '--color-shadow': ld(hexWithAlpha(NL[0], 0.1), hexWithAlpha(ND[0], 0.3)),
225
255
  '--color-tint-hover': ld('black', 'white')
226
256
  };
227
257
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @file Component style-map merging
3
+ *
4
+ * One merge rule for component overrides, shared by every layer that composes
5
+ * them: `extends` inheritance, generated type-scale rules, and the on-media
6
+ * (`onDark`/`onLight`) surfaces. Merging is per style key, so a child that
7
+ * restates one property of `button.base` keeps the rest of the base's.
8
+ *
9
+ * @input two ComponentStyleMaps — the base and the overrides that win
10
+ * @output a merged ComponentStyleMap
11
+ * @position packages/core/src/theme/mergeComponents.ts
12
+ */
13
+ import type { ComponentStyleMap } from './defineTheme';
14
+ /**
15
+ * Deep-merge component style maps: `overrides` wins per style key, and every
16
+ * component and key the base declared that the overrides do not mention is
17
+ * carried through untouched.
18
+ */
19
+ export declare function deepMergeComponents(base?: ComponentStyleMap, overrides?: ComponentStyleMap): ComponentStyleMap | undefined;
20
+ //# sourceMappingURL=mergeComponents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeComponents.d.ts","sourceRoot":"","sources":["../../src/theme/mergeComponents.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,CAAC,EAAE,iBAAiB,EACxB,SAAS,CAAC,EAAE,iBAAiB,GAC5B,iBAAiB,GAAG,SAAS,CAiC/B"}
@@ -0,0 +1,56 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Component style-map merging
5
+ *
6
+ * One merge rule for component overrides, shared by every layer that composes
7
+ * them: `extends` inheritance, generated type-scale rules, and the on-media
8
+ * (`onDark`/`onLight`) surfaces. Merging is per style key, so a child that
9
+ * restates one property of `button.base` keeps the rest of the base's.
10
+ *
11
+ * @input two ComponentStyleMaps — the base and the overrides that win
12
+ * @output a merged ComponentStyleMap
13
+ * @position packages/core/src/theme/mergeComponents.ts
14
+ */
15
+
16
+ /**
17
+ * Deep-merge component style maps: `overrides` wins per style key, and every
18
+ * component and key the base declared that the overrides do not mention is
19
+ * carried through untouched.
20
+ */
21
+ export function deepMergeComponents(base, overrides) {
22
+ if (!base && !overrides) {
23
+ return undefined;
24
+ }
25
+ if (!base) {
26
+ return overrides;
27
+ }
28
+ if (!overrides) {
29
+ return base;
30
+ }
31
+ const result = {};
32
+
33
+ // Start with all base entries
34
+ for (const [component, rules] of Object.entries(base)) {
35
+ result[component] = {
36
+ ...rules
37
+ };
38
+ }
39
+
40
+ // Merge overrides on top
41
+ for (const [component, rules] of Object.entries(overrides)) {
42
+ if (!result[component]) {
43
+ result[component] = {
44
+ ...rules
45
+ };
46
+ } else {
47
+ for (const [key, styles] of Object.entries(rules)) {
48
+ result[component][key] = {
49
+ ...result[component][key],
50
+ ...styles
51
+ };
52
+ }
53
+ }
54
+ }
55
+ return result;
56
+ }
@@ -53,6 +53,11 @@ export declare const defaultOnLightTokens: Record<string, string>;
53
53
  /**
54
54
  * Resolve on-media overrides: merge user tokens with defaults,
55
55
  * pass through component overrides.
56
+ *
57
+ * `base` is the already-resolved surface of a theme being extended. It sits
58
+ * between the defaults and this theme's own input, so a child theme inherits
59
+ * the surface customizations of the theme it extends instead of silently
60
+ * reverting them to the defaults.
56
61
  */
57
- export declare function resolveOnMedia(surface: 'dark' | 'light', input?: OnMediaOverrides): ResolvedOnMedia;
62
+ export declare function resolveOnMedia(surface: 'dark' | 'light', input?: OnMediaOverrides, base?: ResolvedOnMedia): ResolvedOnMedia;
58
63
  //# sourceMappingURL=onMediaTokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"onMediaTokens.d.ts","sourceRoot":"","sources":["../../src/theme/onMediaTokens.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAC7C,yDAAyD;IACzD,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,yDAAyD;IACzD,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAStD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CASvD,CAAC;AAYF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GAAG,OAAO,EACzB,KAAK,CAAC,EAAE,gBAAgB,GACvB,eAAe,CAkBjB"}
1
+ {"version":3,"file":"onMediaTokens.d.ts","sourceRoot":"","sources":["../../src/theme/onMediaTokens.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAGjE;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAC7C,yDAAyD;IACzD,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,yDAAyD;IACzD,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAStD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CASvD,CAAC;AAYF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GAAG,OAAO,EACzB,KAAK,CAAC,EAAE,gBAAgB,EACxB,IAAI,CAAC,EAAE,eAAe,GACrB,eAAe,CAkBjB"}
@@ -20,6 +20,8 @@
20
20
  * `onDark`/`onLight` fields in `defineTheme()`.
21
21
  */
22
22
 
23
+ import { deepMergeComponents } from "./mergeComponents.js";
24
+
23
25
  /**
24
26
  * On-media theme overrides — same shape as the main theme but scoped
25
27
  * to a surface luminance context.
@@ -71,11 +73,17 @@ function resolveValue(value) {
71
73
  /**
72
74
  * Resolve on-media overrides: merge user tokens with defaults,
73
75
  * pass through component overrides.
76
+ *
77
+ * `base` is the already-resolved surface of a theme being extended. It sits
78
+ * between the defaults and this theme's own input, so a child theme inherits
79
+ * the surface customizations of the theme it extends instead of silently
80
+ * reverting them to the defaults.
74
81
  */
75
- export function resolveOnMedia(surface, input) {
82
+ export function resolveOnMedia(surface, input, base) {
76
83
  const defaults = surface === 'dark' ? defaultOnDarkTokens : defaultOnLightTokens;
77
84
  const tokens = {
78
- ...defaults
85
+ ...defaults,
86
+ ...base?.tokens
79
87
  };
80
88
  if (input?.tokens) {
81
89
  for (const [key, value] of Object.entries(input.tokens)) {
@@ -86,6 +94,6 @@ export function resolveOnMedia(surface, input) {
86
94
  }
87
95
  return {
88
96
  tokens,
89
- components: input?.components
97
+ components: deepMergeComponents(base?.components, input?.components)
90
98
  };
91
99
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Number of characters in a string — the count a person would give, where one
3
+ * emoji, flag, or accented letter is one character. Replacement for `.length`
4
+ * on user-visible strings, which counts internal storage units instead.
5
+ */
6
+ export declare function characterCount(str: string): number;
7
+ /**
8
+ * The first character of a string, or '' when empty. Replacement for
9
+ * `.charAt(0)`, which can return half of an emoji. Reads only the first
10
+ * character, so cost does not scale with the length of the string.
11
+ */
12
+ export declare function firstCharacter(str: string): string;
13
+ /**
14
+ * Truncate to at most `max` characters, ellipsis included: strings within
15
+ * `max` characters pass through unchanged; longer strings are cut so the
16
+ * result — content plus `ellipsis` — is exactly `max` characters (or just the
17
+ * ellipsis, when `max` is smaller than the ellipsis itself). Replacement for
18
+ * `str.slice(0, n) + '…'`, which can cut an emoji in half.
19
+ *
20
+ * @example
21
+ * ```
22
+ * truncateCharacters('abcdefghij', 5) // 'abcd…'
23
+ * truncateCharacters('abcdefghij', 8, '...') // 'abcde...'
24
+ * ```
25
+ */
26
+ export declare function truncateCharacters(str: string, max: number, ellipsis?: string): string;
27
+ //# sourceMappingURL=characters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"characters.d.ts","sourceRoot":"","sources":["../../src/utils/characters.ts"],"names":[],"mappings":"AAqCA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,QAAQ,SAAM,GACb,MAAM,CAOR"}
@@ -0,0 +1,83 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file characters.ts
5
+ * @input Strings that may contain characters made of several code units
6
+ * (emoji, flags, joined emoji sequences, accented letters)
7
+ * @output Exports characterCount, firstCharacter, truncateCharacters —
8
+ * replacements for .length / .charAt(0) / .slice(0, n) that measure
9
+ * and cut user-visible strings the way a person reads them
10
+ * @position Shared utility; consumed by Avatar, TextArea, PowerSearch, Table
11
+ *
12
+ * SYNC: When modified, update:
13
+ * - /packages/core/src/utils/characters.test.ts
14
+ * - /packages/core/src/utils/index.ts
15
+ */
16
+
17
+ /**
18
+ * Reuse a single segmenter when the runtime supports Intl.Segmenter.
19
+ * `grapheme` is the Intl granularity name for a user-perceived character.
20
+ */
21
+ const characterSegmenter = typeof Intl.Segmenter === 'function' ? new Intl.Segmenter(undefined, {
22
+ granularity: 'grapheme'
23
+ }) : null;
24
+
25
+ /**
26
+ * Split a string into user-perceived characters. The code-point fallback
27
+ * keeps surrogate pairs intact but may split joined emoji sequences and flag
28
+ * pairs on runtimes without Intl.Segmenter.
29
+ */
30
+ function splitCharacters(str) {
31
+ if (characterSegmenter) {
32
+ return [...characterSegmenter.segment(str)].map(s => s.segment);
33
+ }
34
+ return [...str];
35
+ }
36
+
37
+ /**
38
+ * Number of characters in a string — the count a person would give, where one
39
+ * emoji, flag, or accented letter is one character. Replacement for `.length`
40
+ * on user-visible strings, which counts internal storage units instead.
41
+ */
42
+ export function characterCount(str) {
43
+ if (str === '') {
44
+ return 0;
45
+ }
46
+ return splitCharacters(str).length;
47
+ }
48
+
49
+ /**
50
+ * The first character of a string, or '' when empty. Replacement for
51
+ * `.charAt(0)`, which can return half of an emoji. Reads only the first
52
+ * character, so cost does not scale with the length of the string.
53
+ */
54
+ export function firstCharacter(str) {
55
+ if (characterSegmenter) {
56
+ const first = characterSegmenter.segment(str)[Symbol.iterator]().next();
57
+ return first.done ? '' : first.value.segment;
58
+ }
59
+ const codePoint = str.codePointAt(0);
60
+ return codePoint == null ? '' : String.fromCodePoint(codePoint);
61
+ }
62
+
63
+ /**
64
+ * Truncate to at most `max` characters, ellipsis included: strings within
65
+ * `max` characters pass through unchanged; longer strings are cut so the
66
+ * result — content plus `ellipsis` — is exactly `max` characters (or just the
67
+ * ellipsis, when `max` is smaller than the ellipsis itself). Replacement for
68
+ * `str.slice(0, n) + '…'`, which can cut an emoji in half.
69
+ *
70
+ * @example
71
+ * ```
72
+ * truncateCharacters('abcdefghij', 5) // 'abcd…'
73
+ * truncateCharacters('abcdefghij', 8, '...') // 'abcde...'
74
+ * ```
75
+ */
76
+ export function truncateCharacters(str, max, ellipsis = '…') {
77
+ const characters = splitCharacters(str);
78
+ if (characters.length <= max) {
79
+ return str;
80
+ }
81
+ const keep = Math.max(max - characterCount(ellipsis), 0);
82
+ return characters.slice(0, keep).join('') + ellipsis;
83
+ }
@@ -13,6 +13,7 @@ export { parseISOTime, formatISOTime, formatDisplayTime12h, formatDisplayTime24h
13
13
  export type { ISOTimeString, ParsedTime } from './timeParser';
14
14
  export { parseStyleKey } from './parseStyleKey';
15
15
  export { getKey, type Key, type KeyFallback } from './getKey';
16
+ export { characterCount, firstCharacter, truncateCharacters } from './characters';
16
17
  export { mergeProps } from './mergeProps';
17
18
  export { mergeRefs } from './mergeRefs';
18
19
  export { isFocusDetached } from './focusReturn';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,YAAY,EAAC,SAAS,EAAC,MAAM,SAAS,CAAC;AAEvC,YAAY,EAAC,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,aAAa,CAAC;AAEhF,OAAO,EACL,cAAc,EACd,SAAS,EACT,QAAQ,EACR,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,8BAA8B,EAC9B,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,8BAA8B,EAC9B,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAC,gBAAgB,EAAC,MAAM,aAAa,CAAC;AAElD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,aAAa,EACb,SAAS,EACT,UAAU,EACV,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAC,aAAa,EAAE,UAAU,EAAC,MAAM,cAAc,CAAC;AAE5D,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAC,MAAM,EAAE,KAAK,GAAG,EAAE,KAAK,WAAW,EAAC,MAAM,UAAU,CAAC;AAE5D,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AACxC,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAC,UAAU,EAAE,mBAAmB,EAAC,MAAM,cAAc,CAAC;AAC7D,YAAY,EACV,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,UAAU,EAAE,YAAY,EAAC,MAAM,cAAc,CAAC;AACtD,YAAY,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAC,aAAa,EAAE,eAAe,EAAC,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AACzC,YAAY,EAAC,SAAS,EAAE,mBAAmB,EAAC,MAAM,aAAa,CAAC;AAEhE,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,YAAY,EAAC,IAAI,EAAC,MAAM,SAAS,CAAC;AAElC,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAC,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAItC,OAAO,EAAC,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,YAAY,EAAC,SAAS,EAAC,MAAM,SAAS,CAAC;AAEvC,YAAY,EAAC,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,aAAa,CAAC;AAEhF,OAAO,EACL,cAAc,EACd,SAAS,EACT,QAAQ,EACR,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,EACvB,8BAA8B,EAC9B,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACxB,8BAA8B,EAC9B,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAC,gBAAgB,EAAC,MAAM,aAAa,CAAC;AAElD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,aAAa,EACb,SAAS,EACT,UAAU,EACV,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAC,aAAa,EAAE,UAAU,EAAC,MAAM,cAAc,CAAC;AAE5D,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAC,MAAM,EAAE,KAAK,GAAG,EAAE,KAAK,WAAW,EAAC,MAAM,UAAU,CAAC;AAE5D,OAAO,EAAC,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAEhF,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAC;AACxC,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAC,UAAU,EAAE,mBAAmB,EAAC,MAAM,cAAc,CAAC;AAC7D,YAAY,EACV,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,UAAU,EAAE,YAAY,EAAC,MAAM,cAAc,CAAC;AACtD,YAAY,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAC,aAAa,EAAE,eAAe,EAAC,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AACzC,YAAY,EAAC,SAAS,EAAE,mBAAmB,EAAC,MAAM,aAAa,CAAC;AAEhE,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,YAAY,EAAC,IAAI,EAAC,MAAM,SAAS,CAAC;AAElC,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAC,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAItC,OAAO,EAAC,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,uBAAuB,CAAC"}
@@ -12,6 +12,7 @@ export { plainDateCreate, plainDateFromISO, plainDateToISO, plainDateToDate, pla
12
12
  export { parseISOTime, formatISOTime, formatDisplayTime12h, formatDisplayTime24h, parseTimeInput, compareTime, isTimeInRange, clampTime, adjustTime, createISOTimeString } from "./timeParser.js";
13
13
  export { parseStyleKey } from "./parseStyleKey.js";
14
14
  export { getKey } from "./getKey.js";
15
+ export { characterCount, firstCharacter, truncateCharacters } from "./characters.js";
15
16
  export { mergeProps } from "./mergeProps.js";
16
17
  export { mergeRefs } from "./mergeRefs.js";
17
18
  export { isFocusDetached } from "./focusReturn.js";