@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.
@@ -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,qBAuC7B;yBAhDe,sBAAsB"}
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
- /*#__PURE__*/
80
- // Two elements, two responsibilities. The outer one centres the pill and
81
- // holds the gap above the composer — spacing outside the pill's border
82
- // box, which the pill cannot own itself. The inner one is the pill: it is
83
- // what a reader sees, so it carries the painted surface AND the public
84
- // theming target. Keeping the target on the outer element would satisfy
85
- // every automated check while leaving a theme styling an invisible
86
- // full-width row (architecture:component-theming-surface INV4).
87
- _jsx("div", {
88
- ref: ref,
89
- ...mergeProps(stylex.props(styles.wrapper, xstyle), className, style),
90
- ...rest,
91
- children: /*#__PURE__*/_jsx("div", {
92
- ...mergeProps(themeProps('chat-layout-scroll-button'), {
93
- 0: {
94
- className: "xawy1a7 xb3r6kr xjspbzw x1prclbq x14hfi27 x1ueg155 xrgg2z9 xlr8y92 x2rcmwa x12w9bfk xg01cxk x47corl xlshs6z x1jys5uj"
95
- },
96
- 2: {
97
- className: "xawy1a7 xb3r6kr xjspbzw x1prclbq x14hfi27 x1ueg155 xrgg2z9 xlr8y92 x2rcmwa x12w9bfk x1hc1fzr x67bb7w xnpuxes x1jys5uj"
98
- },
99
- 1: {
100
- className: "xawy1a7 xb3r6kr xjspbzw x1prclbq x14hfi27 x1ueg155 xrgg2z9 xlr8y92 x2rcmwa x12w9bfk xg01cxk x47corl xlshs6z x1ncir08"
101
- },
102
- 3: {
103
- className: "xawy1a7 xb3r6kr xjspbzw x1prclbq x14hfi27 x1ueg155 xrgg2z9 xlr8y92 x2rcmwa x12w9bfk x1hc1fzr x67bb7w xnpuxes x1ncir08"
104
- }
105
- }[!!isVisible << 1 | !!label << 0]),
106
- children: /*#__PURE__*/_jsx(Button, {
107
- label: label ?? t('@astryx.chatLayoutScrollButton.scrollToBottom'),
108
- "aria-label": label ?? t('@astryx.chatLayoutScrollButton.scrollToBottom'),
109
- icon: /*#__PURE__*/_jsx(Icon, {
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), optional warnings array
19
- * @output CSS rule strings, split by layer (component vs prose), and warning text
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, warnings?: string[]): string[];
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, warnings?: string[]): ThemeRulesSplit;
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, warnings?: string[]): string;
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, warnings?: string[]): ThemeCSSOutput;
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, warnings?: string[]): ThemeCSSOutput;
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;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAE,YAAY,EAAC,MAAM,eAAe,CAAC;AAqHnE;;;;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;AAmUD;;;;;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,CAChC,KAAK,EAAE,eAAe,EACtB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,MAAM,EAAE,CAiDV;AAoZD;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,YAAY,EACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,eAAe,CAejB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,MAAM,CAmDR;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,YAAY,EACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,cAAc,CAoFhB;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAKrD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,YAAY,EACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,cAAc,CAoChB"}
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), optional warnings array
21
- * @output CSS rule strings, split by layer (component vs prose), and warning text
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, warnings) {
306
+ export function generateThemeRules(theme) {
385
307
  const parts = [];
386
- const sink = diagnosticSink(warnings);
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 tokenRules = tokenBlock(theme, '', sink);
395
- if (tokenRules !== null) {
396
- parts.push(tokenRules);
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 (theme.tokens['--font-family-body']) {
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' && checkDeclarationValue(fontWeight) === null ? fontWeight : undefined;
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 if (acceptDeclaration(toKebabCase(prop), `${value}`, location, sink)) {
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 = joinDeclarations(finalProps, location, sink, toKebabCase);
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 = joinDeclarations(pseudoEntries, `${location}[${JSON.stringify(pseudo)}]`, sink, toKebabCase);
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, warnings) {
733
- const allRules = generateThemeRules(theme, warnings);
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, warnings) {
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 = joinDeclarations(tokenEntries, `${location}tokens`, sink);
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, location, sink) {
710
+ function generateAdaptationRuleRules(rule) {
805
711
  const parts = [];
806
- const tokens = tokenBlock(rule, location, sink);
807
- if (tokens !== null) {
808
- parts.push(tokens);
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, warnings) {
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 [index, rule] of rules.entries()) {
861
- const parts = generateAdaptationRuleRules(rule, `adaptations[${index}].`, sink);
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 the rule start without rewriting newlines inside authored values. */
800
+ /** Indent a generated rule one level further, for nesting inside `@media`. */
897
801
  function indentRule(rule) {
898
- return ` ${rule}`;
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, warnings) {
841
+ export function generateThemeCSS(theme) {
938
842
  const {
939
843
  component,
940
844
  prose
941
- } = generateThemeRulesSplit(theme, warnings);
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, warnings);
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, warnings);
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.2c831e4",
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.2c831e4",
696
- "@astryxdesign/cli": "0.6.3-canary.2c831e4",
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(stylex.props(styles.wrapper, xstyle), className, style)}
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
- {...mergeProps(
155
- themeProps('chat-layout-scroll-button'),
156
- stylex.props(
157
- styles.container,
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
- * The pill: the element wrapping the Button, whose visibility and height are
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 = {
@@ -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}>