@astryxdesign/core 0.6.3-canary.2c831e4 → 0.6.3-canary.3ee796c
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/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +33 -43
- package/dist/theme/generateThemeRules.d.ts +7 -13
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +35 -131
- package/package.json +3 -3
- package/src/Chat/ChatLayoutScrollButton.tsx +10 -15
- package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +2 -6
- package/src/theme/Theme.test.tsx +0 -32
- package/src/theme/generateThemeRules.test.ts +2 -470
- package/src/theme/generateThemeRules.ts +51 -219
- package/src/theme/tokenValueCompat.test.ts +1 -4
- package/dist/theme/declarationBoundary.d.ts +0 -17
- package/dist/theme/declarationBoundary.d.ts.map +0 -1
- package/dist/theme/declarationBoundary.js +0 -343
- package/src/Chat/ChatLayoutScrollButton.spec.md +0 -338
- package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +0 -740
- package/src/theme/declarationBoundary.test.ts +0 -126
- package/src/theme/declarationBoundary.ts +0 -347
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatLayoutScrollButton.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatLayoutScrollButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAS5C,MAAM,WAAW,2BAA4B,SAAQ,IAAI,CACvD,SAAS,CAAC,cAAc,CAAC,EACzB,SAAS,CACV;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qCAAqC;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAsED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,GAAG,EACH,SAAS,EACT,KAAK,EACL,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,2BAA2B,
|
|
1
|
+
{"version":3,"file":"ChatLayoutScrollButton.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatLayoutScrollButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAS5C,MAAM,WAAW,2BAA4B,SAAQ,IAAI,CACvD,SAAS,CAAC,cAAc,CAAC,EACzB,SAAS,CACV;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qCAAqC;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAsED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,GAAG,EACH,SAAS,EACT,KAAK,EACL,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,2BAA2B,qBAkC7B;yBA3Ce,sBAAsB"}
|
|
@@ -75,50 +75,40 @@ export function ChatLayoutScrollButton({
|
|
|
75
75
|
...rest
|
|
76
76
|
}) {
|
|
77
77
|
const t = useTranslator();
|
|
78
|
-
return (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
icon: "chevronDown",
|
|
111
|
-
size: "md"
|
|
112
|
-
}),
|
|
113
|
-
variant: "ghost",
|
|
114
|
-
size: "md",
|
|
115
|
-
isIconOnly: !label,
|
|
116
|
-
onClick: onClick,
|
|
117
|
-
xstyle: [styles.button, label ? styles.buttonWithLabel : null],
|
|
118
|
-
children: label ?? undefined
|
|
119
|
-
})
|
|
78
|
+
return /*#__PURE__*/_jsx("div", {
|
|
79
|
+
ref: ref,
|
|
80
|
+
...mergeProps(themeProps('chat-layout-scroll-button'), stylex.props(styles.wrapper, xstyle), className, style),
|
|
81
|
+
...rest,
|
|
82
|
+
children: /*#__PURE__*/_jsx("div", {
|
|
83
|
+
...{
|
|
84
|
+
0: {
|
|
85
|
+
className: "xawy1a7 xb3r6kr xjspbzw x1prclbq x14hfi27 x1ueg155 xrgg2z9 xlr8y92 x2rcmwa x12w9bfk xg01cxk x47corl xlshs6z x1jys5uj"
|
|
86
|
+
},
|
|
87
|
+
2: {
|
|
88
|
+
className: "xawy1a7 xb3r6kr xjspbzw x1prclbq x14hfi27 x1ueg155 xrgg2z9 xlr8y92 x2rcmwa x12w9bfk x1hc1fzr x67bb7w xnpuxes x1jys5uj"
|
|
89
|
+
},
|
|
90
|
+
1: {
|
|
91
|
+
className: "xawy1a7 xb3r6kr xjspbzw x1prclbq x14hfi27 x1ueg155 xrgg2z9 xlr8y92 x2rcmwa x12w9bfk xg01cxk x47corl xlshs6z x1ncir08"
|
|
92
|
+
},
|
|
93
|
+
3: {
|
|
94
|
+
className: "xawy1a7 xb3r6kr xjspbzw x1prclbq x14hfi27 x1ueg155 xrgg2z9 xlr8y92 x2rcmwa x12w9bfk x1hc1fzr x67bb7w xnpuxes x1ncir08"
|
|
95
|
+
}
|
|
96
|
+
}[!!isVisible << 1 | !!label << 0],
|
|
97
|
+
children: /*#__PURE__*/_jsx(Button, {
|
|
98
|
+
label: label ?? t('@astryx.chatLayoutScrollButton.scrollToBottom'),
|
|
99
|
+
"aria-label": label ?? t('@astryx.chatLayoutScrollButton.scrollToBottom'),
|
|
100
|
+
icon: /*#__PURE__*/_jsx(Icon, {
|
|
101
|
+
icon: "chevronDown",
|
|
102
|
+
size: "md"
|
|
103
|
+
}),
|
|
104
|
+
variant: "ghost",
|
|
105
|
+
size: "md",
|
|
106
|
+
isIconOnly: !label,
|
|
107
|
+
onClick: onClick,
|
|
108
|
+
xstyle: [styles.button, label ? styles.buttonWithLabel : null],
|
|
109
|
+
children: label ?? undefined
|
|
120
110
|
})
|
|
121
111
|
})
|
|
122
|
-
);
|
|
112
|
+
});
|
|
123
113
|
}
|
|
124
114
|
ChatLayoutScrollButton.displayName = 'ChatLayoutScrollButton';
|
|
@@ -7,17 +7,11 @@
|
|
|
7
7
|
* Used by both the runtime path (Theme injects <style>) and the build
|
|
8
8
|
* path (`astryx theme build` pre-compiles to CSS files).
|
|
9
9
|
*
|
|
10
|
-
* Every consumer-supplied declaration is checked by declarationBoundary.ts
|
|
11
|
-
* before it is emitted, so a value can never end its declaration or rule
|
|
12
|
-
* early. A refused declaration is dropped and reported in the supplied
|
|
13
|
-
* warnings array (build receipts), or through `console.warn` (runtime).
|
|
14
|
-
*
|
|
15
10
|
* Extracted from defineTheme.ts to reduce cyclomatic complexity and provide
|
|
16
11
|
* a clear single-responsibility module for CSS generation.
|
|
17
12
|
*
|
|
18
|
-
* @input DefinedTheme (resolved theme object from defineTheme)
|
|
19
|
-
* @output CSS rule strings, split by layer (component vs prose)
|
|
20
|
-
* for each dropped declaration
|
|
13
|
+
* @input DefinedTheme (resolved theme object from defineTheme)
|
|
14
|
+
* @output CSS rule strings, split by layer (component vs prose)
|
|
21
15
|
* @position packages/core/src/theme/generateThemeRules.ts
|
|
22
16
|
*/
|
|
23
17
|
import type { ComponentStyleMap, DefinedTheme } from './defineTheme';
|
|
@@ -71,7 +65,7 @@ export interface ThemeRuleSource {
|
|
|
71
65
|
/** Resolved component style overrides. */
|
|
72
66
|
components?: ComponentStyleMap;
|
|
73
67
|
}
|
|
74
|
-
export declare function generateThemeRules(theme: ThemeRuleSource
|
|
68
|
+
export declare function generateThemeRules(theme: ThemeRuleSource): string[];
|
|
75
69
|
/**
|
|
76
70
|
* Generate theme rules split into component and prose groups.
|
|
77
71
|
*
|
|
@@ -82,7 +76,7 @@ export declare function generateThemeRules(theme: ThemeRuleSource, warnings?: st
|
|
|
82
76
|
* Component rules (tokens, stable .astryx-* targets plus data-* selectors) are
|
|
83
77
|
* that need to beat StyleX — they stay in astryx-theme (above StyleX layers).
|
|
84
78
|
*/
|
|
85
|
-
export declare function generateThemeRulesSplit(theme: DefinedTheme
|
|
79
|
+
export declare function generateThemeRulesSplit(theme: DefinedTheme): ThemeRulesSplit;
|
|
86
80
|
/**
|
|
87
81
|
* Generate CSS for on-media token and component overrides.
|
|
88
82
|
*
|
|
@@ -91,7 +85,7 @@ export declare function generateThemeRulesSplit(theme: DefinedTheme, warnings?:
|
|
|
91
85
|
* to media contexts — only tokens change. Themes can further customize
|
|
92
86
|
* via onDark.components / onLight.components.
|
|
93
87
|
*/
|
|
94
|
-
export declare function generateOnMediaCSS(theme: DefinedTheme
|
|
88
|
+
export declare function generateOnMediaCSS(theme: DefinedTheme): string;
|
|
95
89
|
/**
|
|
96
90
|
* Generate CSS for a theme's ordered adaptation rules.
|
|
97
91
|
*
|
|
@@ -101,7 +95,7 @@ export declare function generateOnMediaCSS(theme: DefinedTheme, warnings?: strin
|
|
|
101
95
|
* rule. Bare prose follows semantic variables, so token writes need no duplicate
|
|
102
96
|
* prose selectors here.
|
|
103
97
|
*/
|
|
104
|
-
export declare function generateAdaptationCSS(theme: DefinedTheme
|
|
98
|
+
export declare function generateAdaptationCSS(theme: DefinedTheme): ThemeCSSOutput;
|
|
105
99
|
/**
|
|
106
100
|
* The `--color-data-*` defaults as one unscoped `:root` block.
|
|
107
101
|
*
|
|
@@ -134,5 +128,5 @@ export declare function generateDataTokenDefaultsCSS(): string;
|
|
|
134
128
|
* The theme-independent `--color-data-*` defaults are not part of this output:
|
|
135
129
|
* see `generateDataTokenDefaultsCSS`.
|
|
136
130
|
*/
|
|
137
|
-
export declare function generateThemeCSS(theme: DefinedTheme
|
|
131
|
+
export declare function generateThemeCSS(theme: DefinedTheme): ThemeCSSOutput;
|
|
138
132
|
//# sourceMappingURL=generateThemeRules.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateThemeRules.d.ts","sourceRoot":"","sources":["../../src/theme/generateThemeRules.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"generateThemeRules.d.ts","sourceRoot":"","sources":["../../src/theme/generateThemeRules.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAE,YAAY,EAAC,MAAM,eAAe,CAAC;AAiHnE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kFAAkF;IAClF,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAoND;;;;;GAKG;AACH;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,EAAE,CAuDnE;AAsXD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAe5E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CA6C9D;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,cAAc,CA+EzE;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAKrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,cAAc,CAoCpE"}
|
|
@@ -9,17 +9,11 @@
|
|
|
9
9
|
* Used by both the runtime path (Theme injects <style>) and the build
|
|
10
10
|
* path (`astryx theme build` pre-compiles to CSS files).
|
|
11
11
|
*
|
|
12
|
-
* Every consumer-supplied declaration is checked by declarationBoundary.ts
|
|
13
|
-
* before it is emitted, so a value can never end its declaration or rule
|
|
14
|
-
* early. A refused declaration is dropped and reported in the supplied
|
|
15
|
-
* warnings array (build receipts), or through `console.warn` (runtime).
|
|
16
|
-
*
|
|
17
12
|
* Extracted from defineTheme.ts to reduce cyclomatic complexity and provide
|
|
18
13
|
* a clear single-responsibility module for CSS generation.
|
|
19
14
|
*
|
|
20
|
-
* @input DefinedTheme (resolved theme object from defineTheme)
|
|
21
|
-
* @output CSS rule strings, split by layer (component vs prose)
|
|
22
|
-
* for each dropped declaration
|
|
15
|
+
* @input DefinedTheme (resolved theme object from defineTheme)
|
|
16
|
+
* @output CSS rule strings, split by layer (component vs prose)
|
|
23
17
|
* @position packages/core/src/theme/generateThemeRules.ts
|
|
24
18
|
*/
|
|
25
19
|
|
|
@@ -28,7 +22,6 @@ import { parseStyleKey } from "../utils/parseStyleKey.js";
|
|
|
28
22
|
import { getDerivedVars } from "./derivedVarRegistry.js";
|
|
29
23
|
import { dataTokenDefaults } from "./domainTokens/dataTokens.js";
|
|
30
24
|
import { cssVar, classPrefix, dataAttrNamespace } from "../naming.js";
|
|
31
|
-
import { checkDeclarationName, checkDeclarationValue } from "./declarationBoundary.js";
|
|
32
25
|
|
|
33
26
|
/**
|
|
34
27
|
* Theme @scope selectors.
|
|
@@ -291,77 +284,6 @@ function expandContainerPadding(component, parsed, resetInheritedSpecificity = f
|
|
|
291
284
|
return tokens;
|
|
292
285
|
}
|
|
293
286
|
|
|
294
|
-
// =============================================================================
|
|
295
|
-
// Declaration assembly: a declaration always stays one declaration
|
|
296
|
-
// =============================================================================
|
|
297
|
-
|
|
298
|
-
const warnToConsole = message => {
|
|
299
|
-
console.warn(`[astryx theme] ${message}`);
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
/** Build receipts collect warning text; runtime callers warn by default. */
|
|
303
|
-
function diagnosticSink(warnings) {
|
|
304
|
-
return warnings ? message => warnings.push(message) : warnToConsole;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/** Shorten an authored value for a one-line message. */
|
|
308
|
-
function previewValue(value) {
|
|
309
|
-
const oneLine = JSON.stringify(value);
|
|
310
|
-
return oneLine.length > 80 ? `${oneLine.slice(0, 77)}..."` : oneLine;
|
|
311
|
-
}
|
|
312
|
-
function reportDrop(sink, property, value, location, reason) {
|
|
313
|
-
sink(`dropped "${property}" in ${location}: ${reason} (value: ${previewValue(value)})`);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* True when `name: value;` stays exactly one declaration; otherwise reports
|
|
318
|
-
* the drop and returns false. Both checks follow CSS syntax, so valid CSS a
|
|
319
|
-
* browser keeps inside one declaration (a `data:` URI, `Gill\ Sans`, a closed
|
|
320
|
-
* comment, a vendor-prefixed property) is never refused; see
|
|
321
|
-
* declarationBoundary.ts for the rules.
|
|
322
|
-
*/
|
|
323
|
-
function acceptDeclaration(property, value, location, sink) {
|
|
324
|
-
const reason = checkDeclarationName(property) ?? checkDeclarationValue(value);
|
|
325
|
-
if (reason === null) {
|
|
326
|
-
return true;
|
|
327
|
-
}
|
|
328
|
-
reportDrop(sink, property, value, location, reason);
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Assemble `prop: value;` lines, dropping (and reporting) any entry whose
|
|
334
|
-
* property or value could not stay a single declaration. Theme definitions
|
|
335
|
-
* are code, but apps do assemble them from stored input (brand colors,
|
|
336
|
-
* white-labeling), so the generated stylesheet must never extend beyond the
|
|
337
|
-
* declarations it means to emit.
|
|
338
|
-
*/
|
|
339
|
-
function joinDeclarations(entries, location, sink, mapProp = p => p) {
|
|
340
|
-
const lines = [];
|
|
341
|
-
for (const [rawProp, rawValue] of entries) {
|
|
342
|
-
// Legacy unenrolled tokens retain non-string values. Scan the same text
|
|
343
|
-
// interpolation has always emitted, without changing the normalized theme.
|
|
344
|
-
const value = `${rawValue}`;
|
|
345
|
-
const prop = mapProp(rawProp);
|
|
346
|
-
if (acceptDeclaration(prop, value, location, sink)) {
|
|
347
|
-
lines.push(` ${prop}: ${value};`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return lines.join('\n');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/** The `:scope` token block for portable and theme-local token values. */
|
|
354
|
-
function tokenBlock(source, location, sink) {
|
|
355
|
-
const declarations = [joinDeclarations(Object.entries(source.tokens), `${location}tokens`, sink), joinDeclarations(Object.entries(source.localTokens ?? {}), `${location}localTokens`, sink)].filter(block => block.length > 0).join('\n');
|
|
356
|
-
const hasEntries = Object.keys(source.tokens).length > 0 || Object.keys(source.localTokens ?? {}).length > 0;
|
|
357
|
-
return hasEntries ? ` :scope {\n${declarations}\n }` : null;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/** `components.button["variant:secondary"]`-style location for a rule. */
|
|
361
|
-
function componentLocation(prefix, component, key) {
|
|
362
|
-
return `${prefix}components.${component}[${JSON.stringify(key)}]`;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
287
|
// =============================================================================
|
|
366
288
|
// Public API
|
|
367
289
|
// =============================================================================
|
|
@@ -381,9 +303,9 @@ function componentLocation(prefix, component, key) {
|
|
|
381
303
|
* lost for every conditional rule, with no type error to catch it.
|
|
382
304
|
*/
|
|
383
305
|
|
|
384
|
-
export function generateThemeRules(theme
|
|
306
|
+
export function generateThemeRules(theme) {
|
|
385
307
|
const parts = [];
|
|
386
|
-
const
|
|
308
|
+
const tokens = theme.tokens;
|
|
387
309
|
|
|
388
310
|
// Bare prose rules reference semantic variables instead of baking the root
|
|
389
311
|
// value. That lets adaptation token writes take effect through CSS alone
|
|
@@ -391,9 +313,10 @@ export function generateThemeRules(theme, warnings) {
|
|
|
391
313
|
const val = key => `var(${key})`;
|
|
392
314
|
|
|
393
315
|
// 1. Token block — CSS custom properties on :scope
|
|
394
|
-
const
|
|
395
|
-
if (
|
|
396
|
-
|
|
316
|
+
const tokenEntries = [...Object.entries(tokens), ...Object.entries(theme.localTokens ?? {})];
|
|
317
|
+
if (tokenEntries.length > 0) {
|
|
318
|
+
const declarations = tokenEntries.map(([prop, value]) => ` ${prop}: ${value};`).join('\n');
|
|
319
|
+
parts.push(` :scope {\n${declarations}\n }`);
|
|
397
320
|
}
|
|
398
321
|
|
|
399
322
|
// 1b. Base font — apply the theme's declared body font to the scope root.
|
|
@@ -402,16 +325,14 @@ export function generateThemeRules(theme, warnings) {
|
|
|
402
325
|
// sets a page font. `font-family` inherits, so one rule on :scope covers
|
|
403
326
|
// the tree. Only emitted when the theme declares a body font, so a bare
|
|
404
327
|
// theme still contributes no scope rules of its own.
|
|
405
|
-
if (
|
|
328
|
+
if (tokens['--font-family-body']) {
|
|
406
329
|
parts.push(` :scope {\n font-family: var(--font-family-body);\n }`);
|
|
407
330
|
}
|
|
408
331
|
|
|
409
332
|
// 2. Component overrides: stable .astryx-* target classes combined with
|
|
410
333
|
// reflected data-* selectors for visual props and runtime states.
|
|
411
334
|
if (theme.components) {
|
|
412
|
-
generateComponentRules(theme.components, parts
|
|
413
|
-
sink
|
|
414
|
-
});
|
|
335
|
+
generateComponentRules(theme.components, parts);
|
|
415
336
|
}
|
|
416
337
|
|
|
417
338
|
// 3. Prose HTML element rules (h1-h6, p, small, code, hr)
|
|
@@ -475,21 +396,13 @@ function generateHeadingWeightOverrides(components, parts, {
|
|
|
475
396
|
parts.push(` ${selector} { font-weight: ${effectiveWeight}; }`);
|
|
476
397
|
}
|
|
477
398
|
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* The `fontWeight` a heading rule authors for `styleKey`, or undefined when
|
|
481
|
-
* it has none. A value that cannot stay one declaration is treated as not
|
|
482
|
-
* authored, so the override falls back to the inherited or token weight
|
|
483
|
-
* instead of interpolating it; the drop itself is reported where that same
|
|
484
|
-
* rule's declarations are emitted, so it is not reported twice here.
|
|
485
|
-
*/
|
|
486
399
|
function getAuthoredHeadingWeight(headingRuleMap, styleKey) {
|
|
487
400
|
const rule = headingRuleMap[styleKey];
|
|
488
401
|
if (rule == null || typeof rule !== 'object' || Array.isArray(rule)) {
|
|
489
402
|
return undefined;
|
|
490
403
|
}
|
|
491
404
|
const fontWeight = rule.fontWeight;
|
|
492
|
-
return typeof fontWeight === 'string'
|
|
405
|
+
return typeof fontWeight === 'string' ? fontWeight : undefined;
|
|
493
406
|
}
|
|
494
407
|
|
|
495
408
|
/** Options shared by root, adaptation, and media-surface component lowering. */
|
|
@@ -501,9 +414,7 @@ function getAuthoredHeadingWeight(headingRuleMap, styleKey) {
|
|
|
501
414
|
*/
|
|
502
415
|
function generateComponentRules(components, parts, {
|
|
503
416
|
resetInheritedPaddingSpecificity = false,
|
|
504
|
-
surface
|
|
505
|
-
sink = warnToConsole,
|
|
506
|
-
location: locationPrefix = ''
|
|
417
|
+
surface
|
|
507
418
|
} = {}) {
|
|
508
419
|
for (const [component, rules] of Object.entries(components)) {
|
|
509
420
|
for (const [key, styles] of Object.entries(rules)) {
|
|
@@ -513,7 +424,6 @@ function generateComponentRules(components, parts, {
|
|
|
513
424
|
}
|
|
514
425
|
const suffix = parseStyleKey(key);
|
|
515
426
|
const baseSelector = componentSelector(component, suffix, surface);
|
|
516
|
-
const location = componentLocation(locationPrefix, component, key);
|
|
517
427
|
|
|
518
428
|
// Separate regular properties from pseudo-class overrides
|
|
519
429
|
const props = [];
|
|
@@ -521,7 +431,7 @@ function generateComponentRules(components, parts, {
|
|
|
521
431
|
for (const [prop, value] of entries) {
|
|
522
432
|
if (prop.startsWith(':') && typeof value === 'object') {
|
|
523
433
|
pseudos.push([prop, value]);
|
|
524
|
-
} else
|
|
434
|
+
} else {
|
|
525
435
|
props.push([prop, value]);
|
|
526
436
|
}
|
|
527
437
|
}
|
|
@@ -583,7 +493,7 @@ function generateComponentRules(components, parts, {
|
|
|
583
493
|
|
|
584
494
|
// Emit base rule
|
|
585
495
|
if (finalProps.length > 0) {
|
|
586
|
-
const declarations =
|
|
496
|
+
const declarations = finalProps.map(([prop, value]) => ` ${toKebabCase(prop)}: ${value};`).join('\n');
|
|
587
497
|
parts.push(` ${baseSelector} {\n${declarations}\n }`);
|
|
588
498
|
}
|
|
589
499
|
|
|
@@ -591,7 +501,7 @@ function generateComponentRules(components, parts, {
|
|
|
591
501
|
for (const [pseudo, pseudoStyles] of pseudos) {
|
|
592
502
|
const pseudoEntries = Object.entries(pseudoStyles);
|
|
593
503
|
if (pseudoEntries.length > 0) {
|
|
594
|
-
const declarations =
|
|
504
|
+
const declarations = pseudoEntries.map(([prop, value]) => ` ${toKebabCase(prop)}: ${value};`).join('\n');
|
|
595
505
|
parts.push(` ${appendPseudoToSelectorList(baseSelector, pseudo)} {\n${declarations}\n }`);
|
|
596
506
|
}
|
|
597
507
|
}
|
|
@@ -729,8 +639,8 @@ function generateSizeOverrides(components, parts, surface) {
|
|
|
729
639
|
* Component rules (tokens, stable .astryx-* targets plus data-* selectors) are
|
|
730
640
|
* that need to beat StyleX — they stay in astryx-theme (above StyleX layers).
|
|
731
641
|
*/
|
|
732
|
-
export function generateThemeRulesSplit(theme
|
|
733
|
-
const allRules = generateThemeRules(theme
|
|
642
|
+
export function generateThemeRulesSplit(theme) {
|
|
643
|
+
const allRules = generateThemeRules(theme);
|
|
734
644
|
const prose = [];
|
|
735
645
|
const component = [];
|
|
736
646
|
for (const rule of allRules) {
|
|
@@ -754,21 +664,19 @@ export function generateThemeRulesSplit(theme, warnings) {
|
|
|
754
664
|
* to media contexts — only tokens change. Themes can further customize
|
|
755
665
|
* via onDark.components / onLight.components.
|
|
756
666
|
*/
|
|
757
|
-
export function generateOnMediaCSS(theme
|
|
667
|
+
export function generateOnMediaCSS(theme) {
|
|
758
668
|
const parts = [];
|
|
759
669
|
const scopeSelector = themeScopeStart(theme.name);
|
|
760
|
-
const sink = diagnosticSink(warnings);
|
|
761
670
|
for (const surface of ['dark', 'light']) {
|
|
762
671
|
const onMedia = surface === 'dark' ? theme.__onDark : theme.__onLight;
|
|
763
672
|
if (!onMedia) {
|
|
764
673
|
continue;
|
|
765
674
|
}
|
|
766
|
-
const location = surface === 'dark' ? 'onDark.' : 'onLight.';
|
|
767
675
|
|
|
768
676
|
// Token overrides
|
|
769
677
|
const tokenEntries = Object.entries(onMedia.tokens);
|
|
770
678
|
if (tokenEntries.length > 0) {
|
|
771
|
-
const declarations =
|
|
679
|
+
const declarations = tokenEntries.map(([prop, value]) => ` ${prop}: ${value};`).join('\n');
|
|
772
680
|
parts.push(` ${mediaSelector(surface)} {\n${declarations}\n }`);
|
|
773
681
|
}
|
|
774
682
|
|
|
@@ -777,9 +685,7 @@ export function generateOnMediaCSS(theme, warnings) {
|
|
|
777
685
|
if (onMedia.components) {
|
|
778
686
|
generateComponentRules(onMedia.components, parts, {
|
|
779
687
|
resetInheritedPaddingSpecificity: true,
|
|
780
|
-
surface
|
|
781
|
-
sink,
|
|
782
|
-
location
|
|
688
|
+
surface
|
|
783
689
|
});
|
|
784
690
|
generateColorOverrides(onMedia.components, parts, surface);
|
|
785
691
|
generateSizeOverrides(onMedia.components, parts, surface);
|
|
@@ -801,17 +707,16 @@ export function generateOnMediaCSS(theme, warnings) {
|
|
|
801
707
|
}
|
|
802
708
|
|
|
803
709
|
/** Generate only the declarations one adaptation rule writes. */
|
|
804
|
-
function generateAdaptationRuleRules(rule
|
|
710
|
+
function generateAdaptationRuleRules(rule) {
|
|
805
711
|
const parts = [];
|
|
806
|
-
const
|
|
807
|
-
if (
|
|
808
|
-
|
|
712
|
+
const tokenEntries = [...Object.entries(rule.tokens), ...Object.entries(rule.localTokens ?? {})];
|
|
713
|
+
if (tokenEntries.length > 0) {
|
|
714
|
+
const declarations = tokenEntries.map(([prop, value]) => ` ${prop}: ${value};`).join('\n');
|
|
715
|
+
parts.push(` :scope {\n${declarations}\n }`);
|
|
809
716
|
}
|
|
810
717
|
if (rule.components) {
|
|
811
718
|
generateComponentRules(rule.components, parts, {
|
|
812
|
-
resetInheritedPaddingSpecificity: true
|
|
813
|
-
sink,
|
|
814
|
-
location
|
|
719
|
+
resetInheritedPaddingSpecificity: true
|
|
815
720
|
});
|
|
816
721
|
generateColorOverrides(rule.components, parts);
|
|
817
722
|
generateSizeOverrides(rule.components, parts);
|
|
@@ -835,8 +740,7 @@ function generateAdaptationMediaBlock(query, parts, scopeSelector) {
|
|
|
835
740
|
* rule. Bare prose follows semantic variables, so token writes need no duplicate
|
|
836
741
|
* prose selectors here.
|
|
837
742
|
*/
|
|
838
|
-
export function generateAdaptationCSS(theme
|
|
839
|
-
const sink = diagnosticSink(warnings);
|
|
743
|
+
export function generateAdaptationCSS(theme) {
|
|
840
744
|
// Built themes can reach this compiler without passing through defineTheme.
|
|
841
745
|
// Validate retained metadata even when it has no rules (and therefore emits
|
|
842
746
|
// no CSS): width points remain observable through AppShell and inheritance.
|
|
@@ -857,8 +761,8 @@ export function generateAdaptationCSS(theme, warnings) {
|
|
|
857
761
|
// active whenever the later condition matches, even if the earlier condition
|
|
858
762
|
// does not. Pairwise merging cannot repair that: multiple mutually exclusive
|
|
859
763
|
// earlier rules can each overlap one broad later rule.
|
|
860
|
-
for (const
|
|
861
|
-
const parts = generateAdaptationRuleRules(rule
|
|
764
|
+
for (const rule of rules) {
|
|
765
|
+
const parts = generateAdaptationRuleRules(rule);
|
|
862
766
|
if (parts.length === 0) {
|
|
863
767
|
continue;
|
|
864
768
|
}
|
|
@@ -893,9 +797,9 @@ export function generateAdaptationCSS(theme, warnings) {
|
|
|
893
797
|
};
|
|
894
798
|
}
|
|
895
799
|
|
|
896
|
-
/** Indent
|
|
800
|
+
/** Indent a generated rule one level further, for nesting inside `@media`. */
|
|
897
801
|
function indentRule(rule) {
|
|
898
|
-
return ` ${
|
|
802
|
+
return rule.split('\n').map(line => line.length > 0 ? ` ${line}` : line).join('\n');
|
|
899
803
|
}
|
|
900
804
|
|
|
901
805
|
/**
|
|
@@ -934,11 +838,11 @@ export function generateDataTokenDefaultsCSS() {
|
|
|
934
838
|
* The theme-independent `--color-data-*` defaults are not part of this output:
|
|
935
839
|
* see `generateDataTokenDefaultsCSS`.
|
|
936
840
|
*/
|
|
937
|
-
export function generateThemeCSS(theme
|
|
841
|
+
export function generateThemeCSS(theme) {
|
|
938
842
|
const {
|
|
939
843
|
component,
|
|
940
844
|
prose
|
|
941
|
-
} = generateThemeRulesSplit(theme
|
|
845
|
+
} = generateThemeRulesSplit(theme);
|
|
942
846
|
const scopeSelector = themeScopeStart(theme.name);
|
|
943
847
|
const scopeTo = THEME_SCOPE_TO;
|
|
944
848
|
let proseCss = '';
|
|
@@ -957,11 +861,11 @@ export function generateThemeCSS(theme, warnings) {
|
|
|
957
861
|
|
|
958
862
|
// Adaptations follow the root theme in authored order. Media-surface rules
|
|
959
863
|
// follow adaptations so onDark/onLight keep their specified precedence.
|
|
960
|
-
const adaptationCss = generateAdaptationCSS(theme
|
|
864
|
+
const adaptationCss = generateAdaptationCSS(theme);
|
|
961
865
|
if (adaptationCss.component) {
|
|
962
866
|
componentCss = componentCss ? `${componentCss}\n\n${adaptationCss.component}` : adaptationCss.component;
|
|
963
867
|
}
|
|
964
|
-
const onMediaCss = generateOnMediaCSS(theme
|
|
868
|
+
const onMediaCss = generateOnMediaCSS(theme);
|
|
965
869
|
if (onMediaCss) {
|
|
966
870
|
componentCss = componentCss ? `${componentCss}\n\n${onMediaCss}` : onMediaCss;
|
|
967
871
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.6.3-canary.
|
|
3
|
+
"version": "0.6.3-canary.3ee796c",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -692,8 +692,8 @@
|
|
|
692
692
|
"react-dom": ">=19.0.0"
|
|
693
693
|
},
|
|
694
694
|
"devDependencies": {
|
|
695
|
-
"@astryxdesign/a11y-spec": "0.6.3-canary.
|
|
696
|
-
"@astryxdesign/cli": "0.6.3-canary.
|
|
695
|
+
"@astryxdesign/a11y-spec": "0.6.3-canary.3ee796c",
|
|
696
|
+
"@astryxdesign/cli": "0.6.3-canary.3ee796c",
|
|
697
697
|
"@babel/cli": "^7.29.7",
|
|
698
698
|
"@babel/core": "^7.29.7",
|
|
699
699
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -139,25 +139,20 @@ export function ChatLayoutScrollButton({
|
|
|
139
139
|
}: ChatLayoutScrollButtonProps) {
|
|
140
140
|
const t = useTranslator();
|
|
141
141
|
return (
|
|
142
|
-
// Two elements, two responsibilities. The outer one centres the pill and
|
|
143
|
-
// holds the gap above the composer — spacing outside the pill's border
|
|
144
|
-
// box, which the pill cannot own itself. The inner one is the pill: it is
|
|
145
|
-
// what a reader sees, so it carries the painted surface AND the public
|
|
146
|
-
// theming target. Keeping the target on the outer element would satisfy
|
|
147
|
-
// every automated check while leaving a theme styling an invisible
|
|
148
|
-
// full-width row (architecture:component-theming-surface INV4).
|
|
149
142
|
<div
|
|
150
143
|
ref={ref}
|
|
151
|
-
{...mergeProps(
|
|
144
|
+
{...mergeProps(
|
|
145
|
+
themeProps('chat-layout-scroll-button'),
|
|
146
|
+
stylex.props(styles.wrapper, xstyle),
|
|
147
|
+
className,
|
|
148
|
+
style,
|
|
149
|
+
)}
|
|
152
150
|
{...rest}>
|
|
153
151
|
<div
|
|
154
|
-
{...
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
isVisible ? styles.visible : styles.hidden,
|
|
159
|
-
label ? styles.expanded : styles.collapsed,
|
|
160
|
-
),
|
|
152
|
+
{...stylex.props(
|
|
153
|
+
styles.container,
|
|
154
|
+
isVisible ? styles.visible : styles.hidden,
|
|
155
|
+
label ? styles.expanded : styles.collapsed,
|
|
161
156
|
)}>
|
|
162
157
|
<Button
|
|
163
158
|
label={label ?? t('@astryx.chatLayoutScrollButton.scrollToBottom')}
|
|
@@ -38,12 +38,8 @@ import {
|
|
|
38
38
|
const OUTPUT = path.resolve('test-results/chat-layout-audit-evidence');
|
|
39
39
|
const STORY_ID = 'core-chatlayout--scroll-affordance-states';
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
* the claims below. It also carries the public theming target, so the class is
|
|
44
|
-
* a stable handle for it.
|
|
45
|
-
*/
|
|
46
|
-
const PILL = '.astryx-chat-layout-scroll-button';
|
|
41
|
+
/** The pill wrapper: the element whose visibility and height are the claims. */
|
|
42
|
+
const PILL = '.astryx-chat-layout-scroll-button > div';
|
|
47
43
|
|
|
48
44
|
/** One observed tab stop, identified structurally rather than by its text. */
|
|
49
45
|
type FocusStop = {
|
package/src/theme/Theme.test.tsx
CHANGED
|
@@ -55,38 +55,6 @@ describe('Theme', () => {
|
|
|
55
55
|
expect(getByText('hello')).toBeTruthy();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it('mounts the remaining CSS when one authored declaration is dropped', () => {
|
|
59
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
60
|
-
try {
|
|
61
|
-
const theme = defineTheme({
|
|
62
|
-
name: 'declaration-boundary',
|
|
63
|
-
tokens: {'--color-accent': 'red; } body { color: red'},
|
|
64
|
-
components: {button: {base: {borderRadius: '4px'}}},
|
|
65
|
-
});
|
|
66
|
-
render(
|
|
67
|
-
<Theme theme={theme}>
|
|
68
|
-
<span>child</span>
|
|
69
|
-
</Theme>,
|
|
70
|
-
);
|
|
71
|
-
const css = Array.from(document.querySelectorAll('style'))
|
|
72
|
-
.map(tag => tag.textContent ?? '')
|
|
73
|
-
.join('\n');
|
|
74
|
-
expect(css).not.toContain('body { color: red');
|
|
75
|
-
expect(css).toContain('border-radius: 4px;');
|
|
76
|
-
expect(css).toContain(':where(p)');
|
|
77
|
-
expect(
|
|
78
|
-
warn.mock.calls.filter(([message]) =>
|
|
79
|
-
String(message).startsWith('[astryx theme] dropped'),
|
|
80
|
-
),
|
|
81
|
-
).toHaveLength(1);
|
|
82
|
-
expect(warn).toHaveBeenCalledWith(
|
|
83
|
-
expect.stringContaining('dropped "--color-accent" in tokens'),
|
|
84
|
-
);
|
|
85
|
-
} finally {
|
|
86
|
-
warn.mockRestore();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
58
|
it('sets data-astryx-theme on wrapper div', () => {
|
|
91
59
|
const {container} = render(
|
|
92
60
|
<Theme theme={testTheme}>
|