@astryxdesign/core 0.4.2 → 0.4.3-canary.ac850d9
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/CHANGELOG.md +45 -0
- package/README.md +79 -45
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +3 -19
- package/dist/Banner/Banner.d.ts +6 -2
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +71 -30
- package/dist/Banner/index.d.ts +14 -12
- package/dist/Banner/index.d.ts.map +1 -1
- package/dist/Banner/index.js +10 -8
- package/dist/Chat/ChatTokenizedText.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts +38 -4
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +96 -35
- package/dist/ComplexSelector/index.d.ts +2 -2
- package/dist/ComplexSelector/index.d.ts.map +1 -1
- package/dist/ComplexSelector/index.js +1 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +55 -12
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +4 -1
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +2 -4
- package/dist/Table/columnUtils.d.ts.map +1 -1
- package/dist/Table/columnUtils.js +4 -1
- package/dist/TextArea/TextArea.d.ts +6 -3
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +17 -6
- package/dist/TreeList/TreeList.js +2 -1
- package/dist/astryx.css +6 -4
- package/dist/theme/defineTheme.d.ts +30 -7
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +36 -47
- package/dist/theme/expandColorScale.d.ts +17 -2
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +63 -33
- package/dist/theme/mergeComponents.d.ts +20 -0
- package/dist/theme/mergeComponents.d.ts.map +1 -0
- package/dist/theme/mergeComponents.js +56 -0
- package/dist/theme/onMediaTokens.d.ts +6 -1
- package/dist/theme/onMediaTokens.d.ts.map +1 -1
- package/dist/theme/onMediaTokens.js +11 -3
- package/dist/utils/characters.d.ts +27 -0
- package/dist/utils/characters.d.ts.map +1 -0
- package/dist/utils/characters.js +83 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/package.json +4 -8
- package/src/Avatar/Avatar.test.tsx +6 -1
- package/src/Avatar/Avatar.tsx +3 -21
- package/src/Banner/Banner.doc.mjs +9 -7
- package/src/Banner/Banner.test.tsx +68 -0
- package/src/Banner/Banner.tsx +97 -36
- package/src/Banner/index.ts +15 -13
- package/src/Chat/ChatTokenizedText.tsx +1 -1
- package/src/ComplexSelector/ComplexSelector.doc.mjs +55 -6
- package/src/ComplexSelector/ComplexSelector.test.tsx +197 -6
- package/src/ComplexSelector/ComplexSelector.tsx +153 -32
- package/src/ComplexSelector/index.ts +3 -1
- package/src/Markdown/parser.test.ts +53 -0
- package/src/Markdown/parser.ts +53 -12
- package/src/PowerSearch/PowerSearch.test.tsx +48 -3
- package/src/PowerSearch/PowerSearch.tsx +4 -1
- package/src/PowerSearch/formatFilterValue.test.ts +22 -0
- package/src/PowerSearch/formatFilterValue.ts +2 -4
- package/src/Table/Table.test.tsx +6 -0
- package/src/Table/columnUtils.ts +3 -1
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.test.tsx +72 -0
- package/src/TextArea/TextArea.tsx +26 -8
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/TreeList/TreeList.tsx +1 -1
- package/src/theme/defineTheme.test.ts +127 -0
- package/src/theme/defineTheme.ts +70 -55
- package/src/theme/expandColorScale.test.ts +138 -0
- package/src/theme/expandColorScale.ts +89 -44
- package/src/theme/extensibleAxes.test.ts +365 -0
- package/src/theme/mergeComponents.ts +59 -0
- package/src/theme/onMediaTokens.ts +9 -2
- package/src/utils/characters.test.ts +141 -0
- package/src/utils/characters.ts +88 -0
- package/src/utils/index.ts +2 -0
- package/dist/astryx.umd.js +0 -147
- package/dist/astryx.umd.js.map +0 -7
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - /apps/storybook/stories/TextArea.stories.tsx (storybook stories)
|
|
16
16
|
* - /packages/cli/assets/templates/blocks/components/TextArea/ (showcase blocks)
|
|
17
17
|
*/
|
|
18
|
-
import { useId, useOptimistic, useTransition, useRef, useCallback } from 'react';
|
|
18
|
+
import { useId, useOptimistic, useTransition, useRef, useCallback, useMemo } from 'react';
|
|
19
19
|
import * as stylex from '@stylexjs/stylex';
|
|
20
20
|
import "../theme/tokens.stylex.js";
|
|
21
21
|
import { colorVars, spacingVars, typographyVars, typeScaleVars } from "../theme/tokens.stylex.js";
|
|
@@ -29,6 +29,7 @@ import { useInputStatusIcon } from "../hooks/useInputStatusIcon.js";
|
|
|
29
29
|
import { useAnnounce } from "../hooks/useAnnounce.js";
|
|
30
30
|
import { useSize } from "../SizeContext/SizeContext.js";
|
|
31
31
|
import { themeProps } from "../utils/themeProps.js";
|
|
32
|
+
import { characterCount } from "../utils/characters.js";
|
|
32
33
|
import { useTranslator } from "../i18n/index.js";
|
|
33
34
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
34
35
|
const COUNTER_WARNING_THRESHOLD = 0.8;
|
|
@@ -208,7 +209,11 @@ export function TextArea({
|
|
|
208
209
|
return;
|
|
209
210
|
}
|
|
210
211
|
const newValue = e.target.value;
|
|
211
|
-
announceCounter
|
|
212
|
+
// Guarded here, not just inside announceCounter, so textareas without a
|
|
213
|
+
// maxLength never pay for segmenting the whole value on each keystroke.
|
|
214
|
+
if (maxLength != null) {
|
|
215
|
+
announceCounter(characterCount(newValue));
|
|
216
|
+
}
|
|
212
217
|
onChange?.(newValue, e);
|
|
213
218
|
if (changeAction && !e.defaultPrevented) {
|
|
214
219
|
startTransition(async () => {
|
|
@@ -217,6 +222,12 @@ export function TextArea({
|
|
|
217
222
|
});
|
|
218
223
|
}
|
|
219
224
|
};
|
|
225
|
+
|
|
226
|
+
// Counter semantics count user-perceived characters, so an emoji or flag
|
|
227
|
+
// sequence counts as one character, not its code units.
|
|
228
|
+
// Only measured when a counter exists — segmentation is O(value length),
|
|
229
|
+
// and memoized so re-renders that keep the same value skip it entirely.
|
|
230
|
+
const valueLength = useMemo(() => maxLength != null ? characterCount(optimisticValue) : 0, [maxLength, optimisticValue]);
|
|
220
231
|
const effectivelyDisabled = isDisabled || isBusy;
|
|
221
232
|
|
|
222
233
|
// Focus textarea when clicking anywhere on the wrapper (icons, padding, etc.)
|
|
@@ -293,7 +304,7 @@ export function TextArea({
|
|
|
293
304
|
"data-autofocus": hasAutoFocus || undefined,
|
|
294
305
|
"aria-describedby": ariaDescribedBy,
|
|
295
306
|
"aria-required": isRequired && !isOptional ? 'true' : undefined,
|
|
296
|
-
"aria-invalid": status?.type === 'error' || maxLength != null &&
|
|
307
|
+
"aria-invalid": status?.type === 'error' || maxLength != null && valueLength > maxLength ? 'true' : undefined,
|
|
297
308
|
"aria-busy": isBusy || undefined,
|
|
298
309
|
...stylex.props(styles.textarea, textareaSizeStyles[size], isDisabled && styles.textareaDisabled, Boolean(startIcon) && styles.textareaWithStartIcon,
|
|
299
310
|
// Reserve trailing space only when the end slot actually renders
|
|
@@ -318,8 +329,8 @@ export function TextArea({
|
|
|
318
329
|
1: {
|
|
319
330
|
className: "x10l6tqk x112dmjn x13oyzgk x47corl x78zum5 x6s0dn4 xzye2dw x9ynric x141an7d x1ltkj2j xjt36v0"
|
|
320
331
|
}
|
|
321
|
-
}[!!(
|
|
322
|
-
children: [
|
|
332
|
+
}[!!(valueLength > maxLength) << 0],
|
|
333
|
+
children: [valueLength > maxLength &&
|
|
323
334
|
/*#__PURE__*/
|
|
324
335
|
// Non-color cue so the over-limit state isn't conveyed by the red
|
|
325
336
|
// color alone (WCAG 1.4.1). Decorative — the count text and the
|
|
@@ -327,7 +338,7 @@ export function TextArea({
|
|
|
327
338
|
_jsx(Icon, {
|
|
328
339
|
icon: "warning",
|
|
329
340
|
size: "sm"
|
|
330
|
-
}),
|
|
341
|
+
}), valueLength, "/", maxLength]
|
|
331
342
|
})]
|
|
332
343
|
}), showsDisabledMessage && disabledMessageTooltip.renderTooltip(disabledMessage)]
|
|
333
344
|
});
|
|
@@ -226,7 +226,8 @@ export function TreeList({
|
|
|
226
226
|
ref: ref,
|
|
227
227
|
"data-testid": testId,
|
|
228
228
|
...mergeProps(themeProps('tree-list', {
|
|
229
|
-
density
|
|
229
|
+
density,
|
|
230
|
+
variant
|
|
230
231
|
}), stylex.props(styles.root, xstyle), className, style),
|
|
231
232
|
children: [header != null && /*#__PURE__*/_jsx("div", {
|
|
232
233
|
id: headerId,
|
package/dist/astryx.css
CHANGED
|
@@ -378,6 +378,7 @@
|
|
|
378
378
|
.x1pjcqnp:not(#\#):not(#\#){border-radius:inherit}
|
|
379
379
|
.x1ws5lxm:not(#\#):not(#\#){border-radius:max(0px,calc(var(--_dropdown-menu-radius,var(--spacing-2)) - var(--_dropdown-menu-padding,var(--spacing-1))))}
|
|
380
380
|
.xc910v0:not(#\#):not(#\#){border-radius:max(0px,calc(var(--_segmented-control-radius) - var(--_segmented-control-padding)))}
|
|
381
|
+
.x1vf2byn:not(#\#):not(#\#){border-radius:var(--_banner-radius,var(--radius-container))}
|
|
381
382
|
.x1jxw6zd:not(#\#):not(#\#){border-radius:var(--_button-radius,var(--radius-element))}
|
|
382
383
|
.xy0xnkn:not(#\#):not(#\#){border-radius:var(--_button-radius,var(--radius-full))}
|
|
383
384
|
.x153u1i6:not(#\#):not(#\#){border-radius:var(--_card-radius)}
|
|
@@ -639,14 +640,14 @@
|
|
|
639
640
|
.x18o3ruo:not(#\#):not(#\#):not(#\#){background-image:none}
|
|
640
641
|
.x1mwwwfo:not(#\#):not(#\#):not(#\#){border-collapse:collapse}
|
|
641
642
|
.x16uus16:not(#\#):not(#\#):not(#\#){border-end-end-radius:0}
|
|
643
|
+
.x1eprgri:not(#\#):not(#\#):not(#\#){border-end-end-radius:var(--_banner-radius,var(--radius-container))}
|
|
642
644
|
.x6k0y1c:not(#\#):not(#\#):not(#\#){border-end-end-radius:var(--_chat-composer-radius)}
|
|
643
|
-
.xiaxfje:not(#\#):not(#\#):not(#\#){border-end-end-radius:var(--radius-container)}
|
|
644
645
|
.x14i3lts:not(#\#):not(#\#):not(#\#){border-end-end-radius:var(--radius-element)}
|
|
645
646
|
.x1dgc8on:not(#\#):not(#\#):not(#\#){border-end-end-radius:var(--radius-full)}
|
|
646
647
|
.x1whkvtz:not(#\#):not(#\#):not(#\#){border-end-end-radius:var(--radius-inner)}
|
|
647
648
|
.xbiv7yw:not(#\#):not(#\#):not(#\#){border-end-start-radius:0}
|
|
649
|
+
.x1k5g1gk:not(#\#):not(#\#):not(#\#){border-end-start-radius:var(--_banner-radius,var(--radius-container))}
|
|
648
650
|
.xkvxy4d:not(#\#):not(#\#):not(#\#){border-end-start-radius:var(--_chat-composer-radius)}
|
|
649
|
-
.x14k8p9y:not(#\#):not(#\#):not(#\#){border-end-start-radius:var(--radius-container)}
|
|
650
651
|
.xquck67:not(#\#):not(#\#):not(#\#){border-end-start-radius:var(--radius-element)}
|
|
651
652
|
.x1wc3881:not(#\#):not(#\#):not(#\#){border-end-start-radius:var(--radius-full)}
|
|
652
653
|
.xrfs2vl:not(#\#):not(#\#):not(#\#){border-end-start-radius:var(--radius-inner)}
|
|
@@ -664,16 +665,16 @@
|
|
|
664
665
|
.xtpqe59:not(#\#):not(#\#):not(#\#){border-inline-start-width:var(--spacing-0-5)}
|
|
665
666
|
.x1gukg7c:not(#\#):not(#\#):not(#\#){border-spacing:0}
|
|
666
667
|
.x1ga7v0g:not(#\#):not(#\#):not(#\#){border-start-end-radius:0}
|
|
668
|
+
.x13nour3:not(#\#):not(#\#):not(#\#){border-start-end-radius:var(--_banner-radius,var(--radius-container))}
|
|
667
669
|
.xzc3kxz:not(#\#):not(#\#):not(#\#){border-start-end-radius:var(--_chat-composer-radius)}
|
|
668
670
|
.xg0tfu:not(#\#):not(#\#):not(#\#){border-start-end-radius:var(--radius-chat)}
|
|
669
|
-
.x7hs6f1:not(#\#):not(#\#):not(#\#){border-start-end-radius:var(--radius-container)}
|
|
670
671
|
.xxbepff:not(#\#):not(#\#):not(#\#){border-start-end-radius:var(--radius-element)}
|
|
671
672
|
.x8agd5o:not(#\#):not(#\#):not(#\#){border-start-end-radius:var(--radius-full)}
|
|
672
673
|
.x126per9:not(#\#):not(#\#):not(#\#){border-start-end-radius:var(--radius-inner)}
|
|
673
674
|
.x15mokao:not(#\#):not(#\#):not(#\#){border-start-start-radius:0}
|
|
675
|
+
.xwxb7qi:not(#\#):not(#\#):not(#\#){border-start-start-radius:var(--_banner-radius,var(--radius-container))}
|
|
674
676
|
.xy7p8pm:not(#\#):not(#\#):not(#\#){border-start-start-radius:var(--_chat-composer-radius)}
|
|
675
677
|
.x1n70msy:not(#\#):not(#\#):not(#\#){border-start-start-radius:var(--radius-chat)}
|
|
676
|
-
.x81l70g:not(#\#):not(#\#):not(#\#){border-start-start-radius:var(--radius-container)}
|
|
677
678
|
.x19415el:not(#\#):not(#\#):not(#\#){border-start-start-radius:var(--radius-full)}
|
|
678
679
|
.x3qb8m:not(#\#):not(#\#):not(#\#){border-start-start-radius:var(--radius-inner)}
|
|
679
680
|
.x183tx6i:not(#\#):not(#\#):not(#\#){border-start-start-radius:var(--radius-page)}
|
|
@@ -972,6 +973,7 @@
|
|
|
972
973
|
.x1t137rt:not(#\#):not(#\#):not(#\#){outline-style:none}
|
|
973
974
|
.x1k57tk5:not(#\#):not(#\#):not(#\#){outline-width:0}
|
|
974
975
|
.x9y5y3v:not(#\#):not(#\#):not(#\#){overflow-clip-margin:1px}
|
|
976
|
+
.xj0a0fe:not(#\#):not(#\#):not(#\#){overflow-wrap:anywhere}
|
|
975
977
|
.x1mzt3pk:not(#\#):not(#\#):not(#\#){overflow-wrap:break-word}
|
|
976
978
|
.x1h4wwuj:not(#\#):not(#\#):not(#\#){overflow-wrap:normal}
|
|
977
979
|
.xyri2b:not(#\#):not(#\#):not(#\#){padding-inline-end:0}
|
|
@@ -112,9 +112,14 @@ export interface DefineThemeInput {
|
|
|
112
112
|
/** Theme name — used for data-astryx-theme attribute and identification */
|
|
113
113
|
name: string;
|
|
114
114
|
/**
|
|
115
|
-
* Base theme to extend. When provided, the new theme starts with
|
|
116
|
-
* base
|
|
117
|
-
*
|
|
115
|
+
* Base theme to extend. When provided, the new theme starts with everything
|
|
116
|
+
* the base resolved to — tokens, component overrides, icons, indicators, and
|
|
117
|
+
* its `onDark`/`onLight` surfaces — then applies this input on top. The base
|
|
118
|
+
* theme's values have lowest precedence.
|
|
119
|
+
*
|
|
120
|
+
* The result is flat: an extended theme carries its inheritance in its own
|
|
121
|
+
* resolved output, so `astryx theme build` emits one self-contained
|
|
122
|
+
* stylesheet and the base's CSS does not need to be loaded alongside it.
|
|
118
123
|
*
|
|
119
124
|
* Use this to create variant themes that customize only a few aspects
|
|
120
125
|
* (e.g. icons, accent color) without re-specifying the full theme.
|
|
@@ -189,20 +194,38 @@ export interface DefineThemeInput {
|
|
|
189
194
|
*/
|
|
190
195
|
radius?: RadiusScaleConfig;
|
|
191
196
|
/**
|
|
192
|
-
* Color scale configuration. Generates color token overrides from
|
|
193
|
-
*
|
|
197
|
+
* Color scale configuration. Generates color token overrides from an
|
|
198
|
+
* accent seed using the HCT perceptual color model.
|
|
194
199
|
*
|
|
195
200
|
* Only generates tokens derivable from the accent — status colors,
|
|
196
201
|
* categorical hues, and fixed tokens (on-dark/on-light) use defaults.
|
|
197
|
-
*
|
|
202
|
+
*
|
|
203
|
+
* `accent` accepts a single hex (same seed for both color schemes) or a
|
|
204
|
+
* `[light, dark]` tuple, matching `TokenValue`. With a tuple, the light
|
|
205
|
+
* scheme's full palette derives from the light seed and the dark
|
|
206
|
+
* scheme's from the dark seed.
|
|
198
207
|
*
|
|
199
208
|
* `accent` is optional — omit it for a neutral-only theme, which keeps
|
|
200
209
|
* the default accent tokens and only themes the neutrals.
|
|
201
210
|
*
|
|
211
|
+
* Precedence vs `tokens`: explicit `tokens` entries win over generated
|
|
212
|
+
* values, token by token. Because `--color-accent-muted`,
|
|
213
|
+
* `--color-text-accent` and `--color-icon-accent` are generated as
|
|
214
|
+
* `var(--color-accent)` references, a `tokens['--color-accent']`
|
|
215
|
+
* override re-points them at runtime. `--color-on-accent` does NOT
|
|
216
|
+
* follow: it is baked from the `color.accent` seed (a contrast
|
|
217
|
+
* computation CSS cannot express), so overriding the accent through
|
|
218
|
+
* `tokens` without also overriding `--color-on-accent` leaves the two
|
|
219
|
+
* out of sync. To re-seat the whole palette per scheme, prefer a tuple
|
|
220
|
+
* `color.accent` over the `tokens['--color-accent']` workaround.
|
|
221
|
+
*
|
|
202
222
|
* @example
|
|
203
|
-
* ```
|
|
223
|
+
* ```
|
|
204
224
|
* color: { accent: '#0064E0', neutralStyle: 'cool', contrast: 'standard' }
|
|
205
225
|
*
|
|
226
|
+
* // Per-scheme accents — light palette from the first seed, dark from the second
|
|
227
|
+
* color: { accent: ['#0064E0', '#48CAE4'] }
|
|
228
|
+
*
|
|
206
229
|
* // Neutral-only — accent tokens stay at their defaults
|
|
207
230
|
* color: { neutralStyle: 'warm' }
|
|
208
231
|
* ```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineTheme.d.ts","sourceRoot":"","sources":["../../src/theme/defineTheme.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAC,gBAAgB,EAAa,MAAM,SAAS,CAAC;AAC1D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,eAAe,EACf,YAAY,EACZ,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAMzB,OAAO,EAAoB,KAAK,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAoB,KAAK,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAmB,KAAK,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AAE3E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAEpD,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"defineTheme.d.ts","sourceRoot":"","sources":["../../src/theme/defineTheme.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 (
|
|
374
|
-
|
|
375
|
-
const
|
|
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
|
|
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":"
|
|
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
|
|
10
|
-
*
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
221
|
+
'--color-on-accent': ld(PL[100], PD[20])
|
|
191
222
|
} : null),
|
|
192
|
-
'--color-neutral': ld(hexWithAlpha(
|
|
193
|
-
'--color-background-surface': ld(
|
|
194
|
-
'--color-background-body': ld(
|
|
195
|
-
'--color-overlay': ld(hexWithAlpha(
|
|
196
|
-
'--color-overlay-hover': ld(hexWithAlpha(
|
|
197
|
-
'--color-overlay-pressed': ld(hexWithAlpha(
|
|
198
|
-
'--color-background-muted': ld(hexWithAlpha(
|
|
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(
|
|
201
|
-
'--color-text-secondary': ld(
|
|
202
|
-
'--color-text-disabled': ld(
|
|
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(
|
|
207
|
-
'--color-icon-secondary': ld(
|
|
208
|
-
'--color-icon-disabled': ld(
|
|
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(
|
|
211
|
-
'--color-background-popover': ld(
|
|
212
|
-
'--color-background-inverted': ld(
|
|
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(
|
|
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(
|
|
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(
|
|
224
|
-
'--color-shadow': ld(hexWithAlpha(
|
|
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"}
|