@ctrl-ui/react 0.9.11 → 0.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +3 -1
  2. package/dist/control-variants.d.ts +1 -1
  3. package/dist/control-variants.js +1 -0
  4. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  5. package/dist/knob-contracts/button-knobs.js +11 -5
  6. package/dist/knob-contracts/dropzone-knobs.d.ts +1 -1
  7. package/dist/knob-contracts/dropzone-knobs.js +5 -1
  8. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  9. package/dist/knob-contracts/field-knobs.js +10 -0
  10. package/dist/knob-contracts/gradient-editor-knobs.d.ts +1 -1
  11. package/dist/knob-contracts/gradient-editor-knobs.js +1 -4
  12. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  13. package/dist/knob-contracts/page-layout-knobs.js +1 -0
  14. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  15. package/dist/knob-contracts/popup-knobs.js +5 -2
  16. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  17. package/dist/knob-contracts/sidebar-knobs.js +1 -0
  18. package/dist/styles/recipes/avatar.css +2 -0
  19. package/dist/styles/recipes/button-states.css +43 -0
  20. package/dist/styles/recipes/button.css +29 -21
  21. package/dist/styles/recipes/card.css +8 -0
  22. package/dist/styles/recipes/color-picker-values.css +4 -18
  23. package/dist/styles/recipes/color-picker.css +3 -4
  24. package/dist/styles/recipes/dropzone.css +47 -4
  25. package/dist/styles/recipes/field-parts.css +101 -1
  26. package/dist/styles/recipes/gradient-editor.css +3 -47
  27. package/dist/styles/recipes/markdown.css +5 -1
  28. package/dist/styles/recipes/page-layout.css +8 -1
  29. package/dist/styles/recipes/popup-controls.css +7 -1
  30. package/dist/styles/recipes/popup-lists.css +30 -5
  31. package/dist/styles/recipes/popup.css +5 -6
  32. package/dist/styles/recipes/sidebar-parts.css +6 -0
  33. package/dist/styles/recipes/sidebar.css +9 -8
  34. package/dist/styles/recipes/table.css +29 -0
  35. package/dist/styles/theme.css +4 -0
  36. package/dist/ui/autocomplete.d.ts +3 -0
  37. package/dist/ui/autocomplete.js +17 -2
  38. package/dist/ui/chart-cartesian.d.ts +60 -57
  39. package/dist/ui/chart-cartesian.js +7 -7
  40. package/dist/ui/chart-colors.d.ts +4 -2
  41. package/dist/ui/chart-colors.js +6 -2
  42. package/dist/ui/chart-marks.d.ts +4 -4
  43. package/dist/ui/chart-marks.js +14 -5
  44. package/dist/ui/chart-series.d.ts +9 -6
  45. package/dist/ui/chart-series.js +3 -2
  46. package/dist/ui/color-picker-swatches.d.ts +4 -3
  47. package/dist/ui/color-picker-swatches.js +7 -8
  48. package/dist/ui/command.d.ts +12 -0
  49. package/dist/ui/command.js +32 -2
  50. package/dist/ui/dropzone.d.ts +4 -1
  51. package/dist/ui/dropzone.js +16 -8
  52. package/dist/ui/field.d.ts +4 -1
  53. package/dist/ui/field.js +4 -2
  54. package/dist/ui/gradient-editor.d.ts +3 -2
  55. package/dist/ui/gradient-editor.js +7 -8
  56. package/dist/ui/markdown-components.d.ts +27 -0
  57. package/dist/ui/markdown-components.js +57 -0
  58. package/dist/ui/markdown-elements.d.ts +6 -25
  59. package/dist/ui/markdown-elements.js +19 -53
  60. package/dist/ui/markdown.d.ts +2 -7
  61. package/dist/ui/markdown.js +3 -20
  62. package/dist/ui/table.d.ts +6 -0
  63. package/dist/ui/table.js +17 -1
  64. package/lint/class-policy.mjs +31 -1
  65. package/lint/eslint-plugin.mjs +48 -4
  66. package/lint/grit-rules.mjs +52 -11
  67. package/lint/no-component-style-classes.grit +41 -6
  68. package/lint/no-raw-type-classes.grit +1 -1
  69. package/package.json +5 -1
@@ -1,3 +1,4 @@
1
+ import { RenderProp } from "../control-props.js";
1
2
  import { TableKnobStyle } from "../knob-contracts/table-knobs.js";
2
3
  import { CSSProperties, ComponentProps } from "react";
3
4
  //#region src/ui/table.d.ts
@@ -22,6 +23,10 @@ export type TableHeadProps = Omit<ComponentProps<"th">, "style"> & {
22
23
  export type TableCellProps = ComponentProps<"td"> & {
23
24
  style?: CSSProperties & TableKnobStyle;
24
25
  };
26
+ export type TableRowLinkProps = Omit<ComponentProps<"button">, "style"> & {
27
+ render?: RenderProp<ComponentProps<"button">>;
28
+ style?: CSSProperties & TableKnobStyle;
29
+ };
25
30
  export type TableCaptionProps = Omit<ComponentProps<"caption">, "style"> & {
26
31
  style?: CSSProperties & TableKnobStyle;
27
32
  };
@@ -33,4 +38,5 @@ export declare function TableRow({ className, ...props }: TableRowProps): import
33
38
  export declare function TableHead({ className, ...props }: TableHeadProps): import("react").JSX.Element;
34
39
  export declare function TableCell({ className, ...props }: TableCellProps): import("react").JSX.Element;
35
40
  export declare function TableCaption({ className, ...props }: TableCaptionProps): import("react").JSX.Element;
41
+ export declare function TableRowLink({ render, ...props }: TableRowLinkProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
36
42
  //#endregion
package/dist/ui/table.js CHANGED
@@ -1,5 +1,7 @@
1
+ "use client";
1
2
  import { cn } from "../lib/cn.js";
2
3
  import { ScrollArea } from "./scroll-area.js";
4
+ import { useRender } from "@base-ui/react/use-render";
3
5
  import { jsx } from "react/jsx-runtime";
4
6
  //#region src/ui/table.tsx
5
7
  function Table({ className, ...props }) {
@@ -85,5 +87,19 @@ function TableCaption({ className, ...props }) {
85
87
  ...props
86
88
  });
87
89
  }
90
+ function TableRowLink({ render, ...props }) {
91
+ return useRender({
92
+ defaultTagName: "button",
93
+ render,
94
+ props: {
95
+ type: render ? void 0 : "button",
96
+ ...props,
97
+ "data-control-ui": "table",
98
+ "data-control-family": "table",
99
+ "data-slot": "row-link",
100
+ "data-focus-ring": "none"
101
+ }
102
+ });
103
+ }
88
104
  //#endregion
89
- export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
105
+ export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, TableRowLink };
@@ -6,7 +6,37 @@ export function controlUiImportPattern(importSources = []) {
6
6
  return `(?:${[controlUiImportPath, ...importSources.map(escapeRegExp)].join("|")})`;
7
7
  }
8
8
 
9
- export const componentStyleClass = String.raw`[^\s"'\x60]!(?:[\s"'\x60]|$)|(?:^|[\s"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:foreground|muted|primary|secondary|accent|destructive|success|warning|info|card|popover|sidebar|background|canvas|current|inherit|transparent|white|black|[a-z]+-\d{2,3}|\[(?:#|oklch|rgb|hsl|lab|lch|color|var))|font-|leading-|tracking-|border|divide-|rounded|shadow|ring\b|ring-|outline|opacity-|backdrop-|fill-|stroke-|decoration-|underline|line-through|italic|uppercase|lowercase|capitalize|tabular-nums|(?:hover|focus|focus-visible|focus-within|active|disabled|open|checked|dark|placeholder|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)`;
9
+ const classDelimiter = String.raw`\s"'\x60`;
10
+ const classEnd = `(?:[${classDelimiter}]|$)`;
11
+
12
+ function classSuffixOtherThan(suffixes) {
13
+ const firsts = [...new Set(suffixes.filter(Boolean).map((suffix) => suffix[0]))];
14
+ const alternatives = [
15
+ ...(suffixes.includes("") ? [] : [classEnd]),
16
+ `[^${firsts.join("")}${classDelimiter}]`,
17
+ ...firsts.map(
18
+ (first) => first + classSuffixOtherThan(suffixes.filter((suffix) => suffix[0] === first).map((suffix) => suffix.slice(1))),
19
+ ),
20
+ ];
21
+ return `(?:${alternatives.join("|")})`;
22
+ }
23
+
24
+ const textLayoutSuffixes = [
25
+ "left",
26
+ "center",
27
+ "right",
28
+ "justify",
29
+ "start",
30
+ "end",
31
+ "wrap",
32
+ "nowrap",
33
+ "balance",
34
+ "pretty",
35
+ "ellipsis",
36
+ "clip",
37
+ ];
38
+
39
+ export const componentStyleClass = String.raw`[^${classDelimiter}]!${classEnd}|(?:^|[${classDelimiter}])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-${classSuffixOtherThan(textLayoutSuffixes)}|font-|leading-|tracking-|border|divide-|rounded|shadow|inset-shadow|drop-shadow|ring\b|ring-|inset-ring|outline|opacity-|backdrop-|fill-|stroke-|caret-|accent-|scheme-|mask-|decoration-|underline|no-underline|overline|line-through|italic|not-italic|uppercase|lowercase|capitalize|normal-case|antialiased|subpixel-antialiased|ordinal|slashed-zero|(?:normal|lining|oldstyle|proportional|tabular)-nums|(?:diagonal|stacked)-fractions|(?:hover|focus|focus-visible|focus-within|active|disabled|enabled|open|checked|indeterminate|visited|target|invalid|valid|user-invalid|user-valid|required|optional|read-only|autofill|placeholder-shown|in-range|out-of-range|inert|starting|dark|placeholder|selection|marker|file|first-letter|first-line|backdrop|details-content|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)`;
10
40
 
11
41
  export const rawTypeClass = String.raw`(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b|(?:^|[^\w-])text-\[\d[^\]]*\]`;
12
42
 
@@ -11,9 +11,28 @@ function stringsIn(node) {
11
11
  return Object.entries(node).flatMap(([key, child]) => (key === "parent" ? [] : stringsIn(child)));
12
12
  }
13
13
 
14
+ function referencedNames(node) {
15
+ if (Array.isArray(node)) return node.flatMap(referencedNames);
16
+ if (!node || typeof node !== "object") return [];
17
+ if (node.type === "Identifier") return [node.name];
18
+ if (node.type === "MemberExpression" && !node.computed) return referencedNames(node.object);
19
+ if (node.type === "Property" && !node.computed) return referencedNames(node.value);
20
+ return Object.entries(node).flatMap(([key, child]) => (key === "parent" ? [] : referencedNames(child)));
21
+ }
22
+
23
+ const classLiteralTypes = new Set(["Literal", "TemplateLiteral", "ObjectExpression"]);
24
+
25
+ function classLiteral(init) {
26
+ const value = init?.type === "TSAsExpression" || init?.type === "TSSatisfiesExpression" ? init.expression : init;
27
+ return classLiteralTypes.has(value?.type) ? value : undefined;
28
+ }
29
+
30
+ function isClassName(attribute) {
31
+ return attribute.name.name === "className" && Boolean(attribute.value);
32
+ }
33
+
14
34
  function classNameMatches(attribute, pattern) {
15
- if (attribute.name.name !== "className" || !attribute.value) return false;
16
- return stringsIn(attribute.value).some((text) => pattern.test(text));
35
+ return isClassName(attribute) && stringsIn(attribute.value).some((text) => pattern.test(text));
17
36
  }
18
37
 
19
38
  function elementRootName(name) {
@@ -22,6 +41,14 @@ function elementRootName(name) {
22
41
  return undefined;
23
42
  }
24
43
 
44
+ function renderPropOwner(element) {
45
+ const body =
46
+ element.parent.type === "ReturnStatement" && element.parent.parent.type === "BlockStatement" ? element.parent.parent : element;
47
+ const value = body.parent.type === "ArrowFunctionExpression" ? body.parent : element;
48
+ const attribute = value.parent.type === "JSXExpressionContainer" ? value.parent.parent : undefined;
49
+ return attribute?.type === "JSXAttribute" && attribute.name.name === "render" ? attribute.parent : undefined;
50
+ }
51
+
25
52
  function controlUiLocalNames(program, controlUiImport) {
26
53
  const names = new Set();
27
54
  for (const statement of program.body) {
@@ -48,13 +75,30 @@ const noComponentStyleClasses = {
48
75
  create(context) {
49
76
  const controlUiImport = new RegExp(`^${controlUiImportPattern(context.options[0]?.importSources)}`);
50
77
  let controlUiNames = new Set();
78
+ const isControlUi = (element) => element !== undefined && controlUiNames.has(elementRootName(element.name));
79
+ const constantClasses = new Map();
80
+ const componentClassNames = [];
51
81
  return {
52
82
  Program(program) {
53
83
  controlUiNames = controlUiLocalNames(program, controlUiImport);
54
84
  },
85
+ VariableDeclarator(declarator) {
86
+ const literal = classLiteral(declarator.init);
87
+ if (declarator.parent.kind !== "const" || declarator.id.type !== "Identifier" || !literal) return;
88
+ constantClasses.set(declarator.id.name, [...(constantClasses.get(declarator.id.name) ?? []), ...stringsIn(literal)]);
89
+ },
55
90
  JSXAttribute(attribute) {
56
- if (!controlUiNames.has(elementRootName(attribute.parent.name))) return;
57
- if (classNameMatches(attribute, componentStyle)) context.report({ node: attribute, messageId: "componentStyle" });
91
+ if (!isClassName(attribute)) return;
92
+ const element = attribute.parent;
93
+ if (isControlUi(element) || isControlUi(renderPropOwner(element.parent))) componentClassNames.push(attribute);
94
+ },
95
+ "Program:exit"() {
96
+ for (const attribute of componentClassNames) {
97
+ const constants = referencedNames(attribute.value).flatMap((name) => constantClasses.get(name) ?? []);
98
+ if ([...stringsIn(attribute.value), ...constants].some((text) => componentStyle.test(text))) {
99
+ context.report({ node: attribute, messageId: "componentStyle" });
100
+ }
101
+ }
58
102
  },
59
103
  };
60
104
  },
@@ -8,24 +8,65 @@ function gritString(text) {
8
8
  return JSON.stringify(text);
9
9
  }
10
10
 
11
- const stringTexts = "or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() }";
11
+ const stringTexts = `or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $memberName where { $memberName <: r"(?s)[\\"'].*" } }`;
12
+ const classLiterals = "or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }";
13
+ const renderedTag = "or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) }";
14
+
15
+ const styled = gritRegex(componentStyleClass);
16
+ const styledClassName = `$attr <: or {
17
+ contains ${stringTexts} as $text where { $text <: ${styled} },
18
+ contains JsReferenceIdentifier() as $reference where {
19
+ $reference <: r"(.+)"($referenced),
20
+ $module <: contains JsVariableDeclaration(declarators=$declarators) as $declaration where {
21
+ $declaration <: r"(?s)const\\s.*",
22
+ $declarators <: some JsVariableDeclarator(id=$referenced, initializer=JsInitializerClause(expression=$value)),
23
+ $value <: or { ${classLiterals}, TsAsExpression(expression=${classLiterals}), TsSatisfiesExpression(expression=${classLiterals}) },
24
+ $value <: contains ${stringTexts} as $constText where { $constText <: ${styled} }
25
+ }
26
+ }
27
+ }`;
28
+
29
+ function importedFromControlUi(component, importSources, suffix = "") {
30
+ return `$module <: contains JsImport() as $import${suffix} where {
31
+ $import${suffix} <: ${gritRegex(String.raw`from\s+['"]${controlUiImportPattern(importSources)}`)},
32
+ $import${suffix} <: contains JsIdentifierBinding() as $binding${suffix} where { $binding${suffix} <: ${component} }
33
+ }`;
34
+ }
12
35
 
13
36
  export function gritRules(importSources = []) {
14
37
  return {
15
38
  "no-component-style-classes.grit": `language js(typescript, jsx)
16
39
 
17
40
  or {
18
- JsxSelfClosingElement(name=$name, attributes=$attributes),
19
- JsxOpeningElement(name=$name, attributes=$attributes)
20
- } as $element where {
21
- $name <: r"([A-Z]\\w*)(?:\\.[\\w.]+)?"($component),
22
- $attributes <: some JsxAttribute(name=\`className\`) as $attr,
23
- $attr <: contains ${stringTexts} as $text where { $text <: ${gritRegex(componentStyleClass)} },
24
- $element <: within JsModule() as $module,
25
- $module <: contains JsImport() as $import where {
26
- $import <: ${gritRegex(String.raw`from\s+['"]${controlUiImportPattern(importSources)}`)},
27
- $import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
41
+ or {
42
+ JsxSelfClosingElement(name=$name, attributes=$attributes),
43
+ JsxOpeningElement(name=$name, attributes=$attributes)
44
+ } where {
45
+ $attributes <: some JsxAttribute(name=\`className\`) as $attr
28
46
  },
47
+ JsxAttribute(name=\`render\`, initializer=JsxAttributeInitializerClause(value=JsxExpressionAttributeValue(expression=$rendered))) as $render where {
48
+ $rendered <: or {
49
+ JsxTagExpression(tag=$tag),
50
+ JsArrowFunctionExpression(body=or {
51
+ ${renderedTag},
52
+ JsFunctionBody(statements=some JsReturnStatement(argument=${renderedTag}))
53
+ })
54
+ },
55
+ $tag <: or {
56
+ JsxSelfClosingElement(name=$renderedName, attributes=$renderedAttributes),
57
+ JsxElement(opening_element=JsxOpeningElement(name=$renderedName, attributes=$renderedAttributes))
58
+ },
59
+ $renderedAttributes <: some JsxAttribute(name=\`className\`) as $attr,
60
+ $render <: within or { JsxSelfClosingElement(name=$name), JsxOpeningElement(name=$name) },
61
+ $render <: within JsModule() as $module,
62
+ $renderedName <: r"([^.]+).*"($renderedComponent),
63
+ not ${importedFromControlUi("$renderedComponent", importSources, "Rendered")}
64
+ }
65
+ } where {
66
+ $name <: r"([A-Z]\\w*)(?:\\.[\\w.]+)?"($component),
67
+ $attr <: within JsModule() as $module,
68
+ ${styledClassName},
69
+ ${importedFromControlUi("$component", importSources)},
29
70
  register_diagnostic(span=$attr, message=${gritString(componentStyleMessage)}, severity="error")
30
71
  }
31
72
  `,
@@ -1,13 +1,48 @@
1
1
  language js(typescript, jsx)
2
2
 
3
3
  or {
4
- JsxSelfClosingElement(name=$name, attributes=$attributes),
5
- JsxOpeningElement(name=$name, attributes=$attributes)
6
- } as $element where {
4
+ or {
5
+ JsxSelfClosingElement(name=$name, attributes=$attributes),
6
+ JsxOpeningElement(name=$name, attributes=$attributes)
7
+ } where {
8
+ $attributes <: some JsxAttribute(name=`className`) as $attr
9
+ },
10
+ JsxAttribute(name=`render`, initializer=JsxAttributeInitializerClause(value=JsxExpressionAttributeValue(expression=$rendered))) as $render where {
11
+ $rendered <: or {
12
+ JsxTagExpression(tag=$tag),
13
+ JsArrowFunctionExpression(body=or {
14
+ or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) },
15
+ JsFunctionBody(statements=some JsReturnStatement(argument=or { JsxTagExpression(tag=$tag), JsParenthesizedExpression(expression=JsxTagExpression(tag=$tag)) }))
16
+ })
17
+ },
18
+ $tag <: or {
19
+ JsxSelfClosingElement(name=$renderedName, attributes=$renderedAttributes),
20
+ JsxElement(opening_element=JsxOpeningElement(name=$renderedName, attributes=$renderedAttributes))
21
+ },
22
+ $renderedAttributes <: some JsxAttribute(name=`className`) as $attr,
23
+ $render <: within or { JsxSelfClosingElement(name=$name), JsxOpeningElement(name=$name) },
24
+ $render <: within JsModule() as $module,
25
+ $renderedName <: r"([^.]+).*"($renderedComponent),
26
+ not $module <: contains JsImport() as $importRendered where {
27
+ $importRendered <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
28
+ $importRendered <: contains JsIdentifierBinding() as $bindingRendered where { $bindingRendered <: $renderedComponent }
29
+ }
30
+ }
31
+ } where {
7
32
  $name <: r"([A-Z]\w*)(?:\.[\w.]+)?"($component),
8
- $attributes <: some JsxAttribute(name=`className`) as $attr,
9
- $attr <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() } as $text where { $text <: r"(?s).*(?:[^\s\"'\x60]!(?:[\s\"'\x60]|$)|(?:^|[\s\"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:foreground|muted|primary|secondary|accent|destructive|success|warning|info|card|popover|sidebar|background|canvas|current|inherit|transparent|white|black|[a-z]+-\d{2,3}|\[(?:#|oklch|rgb|hsl|lab|lch|color|var))|font-|leading-|tracking-|border|divide-|rounded|shadow|ring\b|ring-|outline|opacity-|backdrop-|fill-|stroke-|decoration-|underline|line-through|italic|uppercase|lowercase|capitalize|tabular-nums|(?:hover|focus|focus-visible|focus-within|active|disabled|open|checked|dark|placeholder|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)).*" },
10
- $element <: within JsModule() as $module,
33
+ $attr <: within JsModule() as $module,
34
+ $attr <: or {
35
+ contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $memberName where { $memberName <: r"(?s)[\"'].*" } } as $text where { $text <: r"(?s).*(?:[^\s\"'\x60]!(?:[\s\"'\x60]|$)|(?:^|[\s\"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:(?:[\s\"'\x60]|$)|[^lcrjsewnbp\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60]))))|c(?:(?:[\s\"'\x60]|$)|[^el\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:[^\s\"'\x60])))))|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|r(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^g\s\"'\x60]|g(?:(?:[\s\"'\x60]|$)|[^h\s\"'\x60]|h(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|j(?:(?:[\s\"'\x60]|$)|[^u\s\"'\x60]|u(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|e(?:(?:[\s\"'\x60]|$)|[^nl\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^d\s\"'\x60]|d(?:[^\s\"'\x60]))|l(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:[^\s\"'\x60]))))))))|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|n(?:(?:[\s\"'\x60]|$)|[^o\s\"'\x60]|o(?:(?:[\s\"'\x60]|$)|[^w\s\"'\x60]|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))))|b(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^c\s\"'\x60]|c(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:[^\s\"'\x60])))))))|p(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|font-|leading-|tracking-|border|divide-|rounded|shadow|inset-shadow|drop-shadow|ring\b|ring-|inset-ring|outline|opacity-|backdrop-|fill-|stroke-|caret-|accent-|scheme-|mask-|decoration-|underline|no-underline|overline|line-through|italic|not-italic|uppercase|lowercase|capitalize|normal-case|antialiased|subpixel-antialiased|ordinal|slashed-zero|(?:normal|lining|oldstyle|proportional|tabular)-nums|(?:diagonal|stacked)-fractions|(?:hover|focus|focus-visible|focus-within|active|disabled|enabled|open|checked|indeterminate|visited|target|invalid|valid|user-invalid|user-valid|required|optional|read-only|autofill|placeholder-shown|in-range|out-of-range|inert|starting|dark|placeholder|selection|marker|file|first-letter|first-line|backdrop|details-content|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)).*" },
36
+ contains JsReferenceIdentifier() as $reference where {
37
+ $reference <: r"(.+)"($referenced),
38
+ $module <: contains JsVariableDeclaration(declarators=$declarators) as $declaration where {
39
+ $declaration <: r"(?s)const\s.*",
40
+ $declarators <: some JsVariableDeclarator(id=$referenced, initializer=JsInitializerClause(expression=$value)),
41
+ $value <: or { or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }, TsAsExpression(expression=or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }), TsSatisfiesExpression(expression=or { JsStringLiteralExpression(), JsTemplateExpression(), JsObjectExpression() }) },
42
+ $value <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $memberName where { $memberName <: r"(?s)[\"'].*" } } as $constText where { $constText <: r"(?s).*(?:[^\s\"'\x60]!(?:[\s\"'\x60]|$)|(?:^|[\s\"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:(?:[\s\"'\x60]|$)|[^lcrjsewnbp\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60]))))|c(?:(?:[\s\"'\x60]|$)|[^el\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:[^\s\"'\x60])))))|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|r(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^g\s\"'\x60]|g(?:(?:[\s\"'\x60]|$)|[^h\s\"'\x60]|h(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|j(?:(?:[\s\"'\x60]|$)|[^u\s\"'\x60]|u(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^f\s\"'\x60]|f(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|s(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:[^\s\"'\x60])))))|e(?:(?:[\s\"'\x60]|$)|[^nl\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^d\s\"'\x60]|d(?:[^\s\"'\x60]))|l(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:(?:[\s\"'\x60]|$)|[^i\s\"'\x60]|i(?:(?:[\s\"'\x60]|$)|[^s\s\"'\x60]|s(?:[^\s\"'\x60]))))))))|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))|n(?:(?:[\s\"'\x60]|$)|[^o\s\"'\x60]|o(?:(?:[\s\"'\x60]|$)|[^w\s\"'\x60]|w(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^p\s\"'\x60]|p(?:[^\s\"'\x60]))))))|b(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^l\s\"'\x60]|l(?:(?:[\s\"'\x60]|$)|[^a\s\"'\x60]|a(?:(?:[\s\"'\x60]|$)|[^n\s\"'\x60]|n(?:(?:[\s\"'\x60]|$)|[^c\s\"'\x60]|c(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:[^\s\"'\x60])))))))|p(?:(?:[\s\"'\x60]|$)|[^r\s\"'\x60]|r(?:(?:[\s\"'\x60]|$)|[^e\s\"'\x60]|e(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^t\s\"'\x60]|t(?:(?:[\s\"'\x60]|$)|[^y\s\"'\x60]|y(?:[^\s\"'\x60])))))))|font-|leading-|tracking-|border|divide-|rounded|shadow|inset-shadow|drop-shadow|ring\b|ring-|inset-ring|outline|opacity-|backdrop-|fill-|stroke-|caret-|accent-|scheme-|mask-|decoration-|underline|no-underline|overline|line-through|italic|not-italic|uppercase|lowercase|capitalize|normal-case|antialiased|subpixel-antialiased|ordinal|slashed-zero|(?:normal|lining|oldstyle|proportional|tabular)-nums|(?:diagonal|stacked)-fractions|(?:hover|focus|focus-visible|focus-within|active|disabled|enabled|open|checked|indeterminate|visited|target|invalid|valid|user-invalid|user-valid|required|optional|read-only|autofill|placeholder-shown|in-range|out-of-range|inert|starting|dark|placeholder|selection|marker|file|first-letter|first-line|backdrop|details-content|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)).*" }
43
+ }
44
+ }
45
+ },
11
46
  $module <: contains JsImport() as $import where {
12
47
  $import <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
13
48
  $import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
@@ -1,6 +1,6 @@
1
1
  language js(typescript, jsx)
2
2
 
3
3
  JsxAttribute(name=`className`) as $attr where {
4
- $attr <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() } as $text where { $text <: r"(?s).*(?:(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b|(?:^|[^\w-])text-\[\d[^\]]*\]).*" },
4
+ $attr <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement(), JsLiteralMemberName() as $memberName where { $memberName <: r"(?s)[\"'].*" } } as $text where { $text <: r"(?s).*(?:(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b|(?:^|[^\w-])text-\[\d[^\]]*\]).*" },
5
5
  register_diagnostic(span=$attr, message="Text size in className: render text through <Text>/<Heading> on the type scale, or size the control with its size prop or --cui-*-font-size knob.", severity="error")
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl-ui/react",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "description": "100+ React components for Tailwind CSS and shadcn/ui: pickers, trees, editors, canvases, app templates, and AI chat surfaces, with neutral defaults and consumer-owned themes.",
5
5
  "keywords": [
6
6
  "react",
@@ -61,6 +61,7 @@
61
61
  },
62
62
  "peerDependencies": {
63
63
  "@base-ui/react": "^1.9.0",
64
+ "@biomejs/biome": ">=2.4.11",
64
65
  "@tanstack/charts": "^1.1.0",
65
66
  "@tanstack/react-virtual": "^3.14.13",
66
67
  "@tiptap/core": "3.31.4",
@@ -98,6 +99,9 @@
98
99
  "zod": "^4.6.5"
99
100
  },
100
101
  "peerDependenciesMeta": {
102
+ "@biomejs/biome": {
103
+ "optional": true
104
+ },
101
105
  "@tanstack/charts": {
102
106
  "optional": true
103
107
  },