@adea-ai/ui 0.80.0 → 0.81.1

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":"index.d.ts","sourceRoot":"","sources":["../../src/lint/index.ts"],"names":[],"mappings":"AAmBA,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAa/E,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS,CAAA;QACf,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,CAAA;QACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjC,CAAA;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;CACtE,CAAA;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,CAAC,UAAU,EAAE;QACnB,IAAI,EAAE,OAAO,CAAA;QACb,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;KAC5B,KAAK,IAAI,CAAA;CACX;AA2QD,QAAA,MAAM,MAAM;;;;;;;;;CAOX,CAAA;AAED,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lint/index.ts"],"names":[],"mappings":"AAmBA,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAa/E,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS,CAAA;QACf,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,CAAA;QAC7B,MAAM,EAAE,IAAI,EAAE,CAAA;QACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACjC,CAAA;IACD,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;CACtE,CAAA;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,CAAC,UAAU,EAAE;QACnB,IAAI,EAAE,OAAO,CAAA;QACb,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;KAC5B,KAAK,IAAI,CAAA;CACX;AA+UD,QAAA,MAAM,MAAM;;;;;;;;;CAOX,CAAA;AAED,eAAe,MAAM,CAAA"}
package/dist/lint.js CHANGED
@@ -69,11 +69,14 @@ var noRawInteractiveElements = {
69
69
  };
70
70
  var DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([
71
71
  "@kobalte/core",
72
+ "@corvu",
73
+ "cmdk-solid",
72
74
  "@ark-ui",
73
75
  "@zag-js",
74
76
  "@radix-ui",
75
77
  "radix-ui",
76
- "@base-ui-components"
78
+ "@base-ui-components",
79
+ "@base-ui"
77
80
  ]);
78
81
  /**
79
82
  * The interactive ARIA roles an application can dress a generic element in, and
@@ -144,18 +147,44 @@ var GENERIC_ELEMENTS = /* @__PURE__ */ new Set([
144
147
  "img",
145
148
  "svg"
146
149
  ]);
147
- var CLICK_HANDLERS = /* @__PURE__ */ new Set(["onClick", "onDoubleClick"]);
148
- /** Reads a static `role` value; a dynamic role is undecidable statically, so it is left to review. */
150
+ var CLICK_HANDLERS = /* @__PURE__ */ new Set([
151
+ "onClick",
152
+ "onDoubleClick",
153
+ "onPointerDown",
154
+ "onPointerUp",
155
+ "onMouseDown",
156
+ "onMouseUp",
157
+ "on:click",
158
+ "on:dblclick",
159
+ "on:pointerdown",
160
+ "on:pointerup",
161
+ "on:mousedown",
162
+ "on:mouseup"
163
+ ]);
164
+ /** Only statically knowable branches; unresolved roles remain a review boundary. */
165
+ var staticStrings = (node) => {
166
+ if (node?.type === "Literal" && typeof node.value === "string") return [node.value];
167
+ if (node?.type === "JSXExpressionContainer") return staticStrings(node.expression);
168
+ if (node?.type === "ConditionalExpression") return [...staticStrings(node.consequent), ...staticStrings(node.alternate)];
169
+ if (node?.type === "TemplateLiteral" && node.expressions.length === 0) return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw];
170
+ return [];
171
+ };
149
172
  var roleOf = (node) => {
150
173
  for (const attribute of node.attributes ?? []) {
151
174
  if (attribute.type !== "JSXAttribute" || attribute.name?.name !== "role") continue;
152
- if (attribute.value?.type === "Literal" && typeof attribute.value.value === "string") return attribute.value.value;
153
- return null;
175
+ return staticStrings(attribute.value).flatMap((role) => role.split(/\s+/)).find((role) => role in ROLE_PRIMITIVES) ?? null;
154
176
  }
155
177
  return null;
156
178
  };
157
179
  /** Whether the element carries the attribute, by name. */
158
- var has = (node, name) => (node.attributes ?? []).some((attribute) => attribute.type === "JSXAttribute" && attribute.name?.name === name);
180
+ var has = (node, name) => (node.attributes ?? []).some((attribute) => attribute.type === "JSXAttribute" && (attribute.name?.type === "JSXNamespacedName" ? `${attribute.name.namespace.name}:${attribute.name.name.name}` : attribute.name?.name) === name);
181
+ var hasTabStop = (node) => (node.attributes ?? []).some((attribute) => {
182
+ if (attribute.type !== "JSXAttribute" || !["tabIndex", "tabindex"].includes(attribute.name?.name)) return false;
183
+ const value = attribute.value?.type === "JSXExpressionContainer" ? attribute.value.expression : attribute.value;
184
+ if (value?.type === "Literal") return !(Number(value.value) < 0);
185
+ if (value?.type === "UnaryExpression" && value.operator === "-" && value.argument?.type === "Literal") return !(Number(value.argument.value) > 0);
186
+ return true;
187
+ });
159
188
  var plugin = {
160
189
  meta: { name: "adea" },
161
190
  rules: {
@@ -199,6 +228,17 @@ var plugin = {
199
228
  },
200
229
  ImportExpression(node) {
201
230
  if (typeof node.source?.value === "string" && offender(node.source.value)) report(node, node.source.value);
231
+ },
232
+ ExportNamedDeclaration(node) {
233
+ if (offender(node.source?.value)) report(node, node.source.value);
234
+ },
235
+ ExportAllDeclaration(node) {
236
+ if (offender(node.source?.value)) report(node, node.source.value);
237
+ },
238
+ CallExpression(node) {
239
+ if (node.callee?.type !== "Identifier" || node.callee.name !== "require") return;
240
+ const source = node.arguments?.[0]?.value;
241
+ if (typeof source === "string" && offender(source)) report(node, source);
202
242
  }
203
243
  };
204
244
  }
@@ -231,6 +271,7 @@ var plugin = {
231
271
  if (node.name?.type !== "JSXIdentifier") return;
232
272
  const element = node.name.name;
233
273
  if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return;
274
+ if (element[0] === element[0]?.toUpperCase()) return;
234
275
  const role = roleOf(node);
235
276
  if (role && role in ROLE_PRIMITIVES) {
236
277
  const target = ROLE_PRIMITIVES[role];
@@ -262,7 +303,7 @@ var plugin = {
262
303
  });
263
304
  return;
264
305
  }
265
- if (has(node, "tabIndex") || has(node, "tabindex")) context.report({
306
+ if (hasTabStop(node)) context.report({
266
307
  node,
267
308
  messageId: "tabindexWrapper",
268
309
  data: { element },
package/dist/lint.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return\n emit(node, element)\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n])\n\n/**\n * The interactive ARIA roles an application can dress a generic element in, and\n * the primitive that owns each role. `link` is deliberately absent: an `<a>` is\n * natively interactive and Button itself supports `as=\"a\"` for the link-styled\n * case.\n */\nconst ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },\n radio: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },\n switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },\n tab: { primitive: 'Tabs', path: '@adea-ai/ui/components/ui/tabs' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n menuitem: { primitive: 'DropdownMenu', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n })\n\n/**\n * The elements whose only job is layout or text. A click handler or a tabindex on\n * one of these is a control being hand-built out of a non-control.\n */\nconst GENERIC_ELEMENTS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'ul',\n 'ol',\n 'td',\n 'th',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'main',\n 'img',\n 'svg',\n])\n\nconst CLICK_HANDLERS: ReadonlySet<string> = new Set(['onClick', 'onDoubleClick'])\n\ninterface InteractiveWrapperOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\n/** Reads a static `role` value; a dynamic role is undecidable statically, so it is left to review. */\nconst roleOf = (node: any): string | null => {\n for (const attribute of node.attributes ?? []) {\n if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue\n if (attribute.value?.type === 'Literal' && typeof attribute.value.value === 'string')\n return attribute.value.value\n return null\n }\n return null\n}\n\n/** Whether the element carries the attribute, by name. */\nconst has = (node: any, name: string): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) => attribute.type === 'JSXAttribute' && attribute.name?.name === name\n )\n\nconst noInteractiveWrappers: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n roleWrapper:\n 'A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.',\n clickWrapper:\n 'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',\n tabindexWrapper:\n 'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions\n const allowed = new Set(options.allow ?? [])\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return\n\n const role = roleOf(node)\n if (role && role in ROLE_PRIMITIVES) {\n const target = ROLE_PRIMITIVES[role]!\n context.report({\n node,\n messageId: 'roleWrapper',\n data: { element, role, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n\n if (GENERIC_ELEMENTS.has(element)) {\n const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))\n if (click) {\n const target = ROLE_PRIMITIVES.button!\n context.report({\n node,\n messageId: 'clickWrapper',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n if (has(node, 'tabIndex') || has(node, 'tabindex')) {\n context.report({\n node,\n messageId: 'tabindexWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n }\n },\n }\n },\n}\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n 'no-interactive-wrappers': noInteractiveWrappers,\n },\n}\n\nexport default plugin\n"],"mappings":"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"}
1
+ {"version":3,"file":"lint.js","names":[],"sources":["../src/lint/index.ts"],"sourcesContent":["/*\n * The design system's own oxlint plugin.\n *\n * The styling rules live in `@shadcn/lint`; what that plugin cannot know is which\n * *elements* the design system owns. This is the other half of the contract, for\n * the applications that consume the library: an interactive element this package\n * provides is not re-implemented at the application layer, and the primitive\n * libraries this package is built from are not imported around it.\n *\n * Loaded as an oxlint jsPlugin:\n *\n * // .oxlintrc.json\n * { \"jsPlugins\": [\"@adea-ai/ui/lint\"], \"rules\": { \"adea/no-raw-interactive-elements\": \"error\" } }\n *\n * Rules are errors by intent. A violation means an application composed its own\n * control out of raw markup — unreachable focus behaviour, an absent keyboard\n * story, a second implementation of a decision this package exists to make once.\n */\n\ntype Json = string | number | boolean | null | Json[] | { [key: string]: Json }\n\n/** The raw elements this package replaces, and the primitive that replaces them. */\nconst ELEMENT_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n input: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n textarea: { primitive: 'Textarea', path: '@adea-ai/ui/components/ui/textarea' },\n select: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n label: { primitive: 'Label', path: '@adea-ai/ui/components/ui/label' },\n })\n\ntype RuleModule = {\n meta: {\n type: 'problem'\n docs: { description: string }\n schema: Json[]\n messages: Record<string, string>\n }\n create: (context: LintContext) => Record<string, (node: any) => void>\n}\n\ninterface LintContext {\n options?: unknown[]\n report: (descriptor: {\n node: unknown\n messageId?: string\n message?: string\n data?: Record<string, Json>\n }) => void\n}\n\ninterface RawElementOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst noRawInteractiveElements: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Interactive elements this package provides (button, input, textarea, select, option, label) are composed from the package, not written as raw markup.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n rawElement:\n '{{element}} is provided by the design system: use {{primitive}} from {{path}}. A raw element has no keyboard story, no focus behaviour and no token styling — that is the work the primitive already did.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as RawElementOptions\n const allowed = new Set(options.allow ?? [])\n const emit = (node: unknown, element: string) => {\n const target = ELEMENT_PRIMITIVES[element]!\n context.report({\n node,\n messageId: 'rawElement',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (!(element in ELEMENT_PRIMITIVES) || allowed.has(element)) return\n emit(node, element)\n },\n }\n },\n}\n\ninterface PrimitiveImportOptions {\n /** Package prefixes this rule reports. Defaults to the primitive libraries the ui package wraps. */\n packages?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\nconst DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([\n '@kobalte/core',\n '@corvu',\n 'cmdk-solid',\n '@ark-ui',\n '@zag-js',\n '@radix-ui',\n 'radix-ui',\n '@base-ui-components',\n '@base-ui',\n])\n\n/**\n * The interactive ARIA roles an application can dress a generic element in, and\n * the primitive that owns each role. `link` is deliberately absent: an `<a>` is\n * natively interactive and Button itself supports `as=\"a\"` for the link-styled\n * case.\n */\nconst ROLE_PRIMITIVES: Readonly<Record<string, { primitive: string; path: string }>> =\n Object.freeze({\n button: { primitive: 'Button', path: '@adea-ai/ui/components/ui/button' },\n checkbox: { primitive: 'Checkbox', path: '@adea-ai/ui/components/ui/checkbox' },\n radio: { primitive: 'RadioGroup', path: '@adea-ai/ui/components/ui/radio-group' },\n switch: { primitive: 'Switch', path: '@adea-ai/ui/components/ui/switch' },\n tab: { primitive: 'Tabs', path: '@adea-ai/ui/components/ui/tabs' },\n option: { primitive: 'Select', path: '@adea-ai/ui/components/ui/select' },\n menuitem: { primitive: 'DropdownMenu', path: '@adea-ai/ui/components/ui/dropdown-menu' },\n textbox: { primitive: 'Input', path: '@adea-ai/ui/components/ui/input' },\n })\n\n/**\n * The elements whose only job is layout or text. A click handler or a tabindex on\n * one of these is a control being hand-built out of a non-control.\n */\nconst GENERIC_ELEMENTS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'ul',\n 'ol',\n 'td',\n 'th',\n 'p',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'main',\n 'img',\n 'svg',\n])\n\nconst CLICK_HANDLERS: ReadonlySet<string> = new Set([\n 'onClick',\n 'onDoubleClick',\n 'onPointerDown',\n 'onPointerUp',\n 'onMouseDown',\n 'onMouseUp',\n 'on:click',\n 'on:dblclick',\n 'on:pointerdown',\n 'on:pointerup',\n 'on:mousedown',\n 'on:mouseup',\n])\n\ninterface InteractiveWrapperOptions {\n /** Element names this rule should not report (rare; prefer fixing the file). */\n allow?: string[]\n /** Replaces the default report message. */\n message?: string\n}\n\n/** Only statically knowable branches; unresolved roles remain a review boundary. */\nconst staticStrings = (node: any): string[] => {\n if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]\n if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression)\n if (node?.type === 'ConditionalExpression')\n return [...staticStrings(node.consequent), ...staticStrings(node.alternate)]\n if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)\n return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]\n return []\n}\n\nconst roleOf = (node: any): string | null => {\n for (const attribute of node.attributes ?? []) {\n if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue\n return (\n staticStrings(attribute.value)\n .flatMap((role) => role.split(/\\s+/))\n .find((role) => role in ROLE_PRIMITIVES) ?? null\n )\n }\n return null\n}\n\n/** Whether the element carries the attribute, by name. */\nconst has = (node: any, name: string): boolean =>\n (node.attributes ?? []).some(\n (attribute: any) =>\n attribute.type === 'JSXAttribute' &&\n (attribute.name?.type === 'JSXNamespacedName'\n ? `${attribute.name.namespace.name}:${attribute.name.name.name}`\n : attribute.name?.name) === name\n )\n\nconst hasTabStop = (node: any): boolean =>\n (node.attributes ?? []).some((attribute: any) => {\n if (\n attribute.type !== 'JSXAttribute' ||\n !['tabIndex', 'tabindex'].includes(attribute.name?.name)\n )\n return false\n const value =\n attribute.value?.type === 'JSXExpressionContainer'\n ? attribute.value.expression\n : attribute.value\n if (value?.type === 'Literal') return !(Number(value.value) < 0)\n if (\n value?.type === 'UnaryExpression' &&\n value.operator === '-' &&\n value.argument?.type === 'Literal'\n )\n return !(Number(value.argument.value) > 0)\n return true\n })\n\nconst noInteractiveWrappers: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Generic elements dressed as interactive controls — an interactive role, a click handler or a tabindex on a layout element — re-implement a primitive by hand. Compose the real component.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n allow: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n roleWrapper:\n 'A {{element}} with role=\"{{role}}\" hand-builds {{primitive}}: use {{primitive}} from {{path}}. The primitive brings keyboard activation, focus behaviour and ARIA wiring; the wrapper re-implements them badly.',\n clickWrapper:\n 'A click handler on {{element}} composes {{primitive}} by hand: use {{primitive}} from {{path}}. A div that reacts to clicks is a button that cannot be reached by keyboard.',\n tabindexWrapper:\n 'A tabindex on {{element}} makes a layout element focusable, which is focus management the primitives already own: compose the real component instead.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as InteractiveWrapperOptions\n const allowed = new Set(options.allow ?? [])\n return {\n JSXOpeningElement(node: any) {\n if (node.name?.type !== 'JSXIdentifier') return\n const element = node.name.name\n if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return\n // A capitalized element is a component — often a design-system primitive\n // composing a role deliberately (a Button carrying role=\"option\" inside a\n // custom listbox, for example). That is composition, not impersonation.\n if (element[0] === element[0]?.toUpperCase()) return\n\n const role = roleOf(node)\n if (role && role in ROLE_PRIMITIVES) {\n const target = ROLE_PRIMITIVES[role]!\n context.report({\n node,\n messageId: 'roleWrapper',\n data: { element, role, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n\n if (GENERIC_ELEMENTS.has(element)) {\n const click = [...CLICK_HANDLERS].find((handler) => has(node, handler))\n if (click) {\n const target = ROLE_PRIMITIVES.button!\n context.report({\n node,\n messageId: 'clickWrapper',\n data: { element, primitive: target.primitive, path: target.path },\n ...(options.message ? { message: options.message } : {}),\n })\n return\n }\n if (hasTabStop(node)) {\n context.report({\n node,\n messageId: 'tabindexWrapper',\n data: { element },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n }\n },\n }\n },\n}\n\nconst noPrimitiveLibraryImports: RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'The primitive libraries the design system is built on (Kobalte and friends) are its internal affair — compose the exported components instead of importing the primitives.',\n },\n schema: [\n {\n type: 'object',\n properties: {\n packages: { type: 'array', items: { type: 'string' } },\n message: { type: 'string' },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n primitiveImport:\n 'Import from the design system, not from {{package}}: the primitives it wraps carry the focus, keyboard and ARIA behaviour the components exist to provide.',\n },\n },\n create(context) {\n const options = (context.options?.[0] ?? {}) as PrimitiveImportOptions\n const prefixes = options.packages ?? DEFAULT_PRIMITIVE_PACKAGES\n const offender = (source: unknown): string | null => {\n const name = String(source ?? '')\n return prefixes.find((prefix) => name === prefix || name.startsWith(`${prefix}/`)) ?? null\n }\n const report = (node: unknown, source: unknown) => {\n const pkg = offender(source)!\n context.report({\n node,\n messageId: 'primitiveImport',\n data: { package: pkg },\n ...(options.message ? { message: options.message } : {}),\n })\n }\n return {\n ImportDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ImportExpression(node: any) {\n if (typeof node.source?.value === 'string' && offender(node.source.value))\n report(node, node.source.value)\n },\n ExportNamedDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n ExportAllDeclaration(node: any) {\n if (offender(node.source?.value)) report(node, node.source.value)\n },\n CallExpression(node: any) {\n if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return\n const source = node.arguments?.[0]?.value\n if (typeof source === 'string' && offender(source)) report(node, source)\n },\n }\n },\n}\n\nconst plugin = {\n meta: { name: 'adea' },\n rules: {\n 'no-raw-interactive-elements': noRawInteractiveElements,\n 'no-primitive-library-imports': noPrimitiveLibraryImports,\n 'no-interactive-wrappers': noInteractiveWrappers,\n },\n}\n\nexport default plugin\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.80.0",
3
+ "version": "0.81.1",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
package/src/lint/index.ts CHANGED
@@ -111,11 +111,14 @@ interface PrimitiveImportOptions {
111
111
 
112
112
  const DEFAULT_PRIMITIVE_PACKAGES = Object.freeze([
113
113
  '@kobalte/core',
114
+ '@corvu',
115
+ 'cmdk-solid',
114
116
  '@ark-ui',
115
117
  '@zag-js',
116
118
  '@radix-ui',
117
119
  'radix-ui',
118
120
  '@base-ui-components',
121
+ '@base-ui',
119
122
  ])
120
123
 
121
124
  /**
@@ -166,7 +169,20 @@ const GENERIC_ELEMENTS: ReadonlySet<string> = new Set([
166
169
  'svg',
167
170
  ])
168
171
 
169
- const CLICK_HANDLERS: ReadonlySet<string> = new Set(['onClick', 'onDoubleClick'])
172
+ const CLICK_HANDLERS: ReadonlySet<string> = new Set([
173
+ 'onClick',
174
+ 'onDoubleClick',
175
+ 'onPointerDown',
176
+ 'onPointerUp',
177
+ 'onMouseDown',
178
+ 'onMouseUp',
179
+ 'on:click',
180
+ 'on:dblclick',
181
+ 'on:pointerdown',
182
+ 'on:pointerup',
183
+ 'on:mousedown',
184
+ 'on:mouseup',
185
+ ])
170
186
 
171
187
  interface InteractiveWrapperOptions {
172
188
  /** Element names this rule should not report (rare; prefer fixing the file). */
@@ -175,13 +191,25 @@ interface InteractiveWrapperOptions {
175
191
  message?: string
176
192
  }
177
193
 
178
- /** Reads a static `role` value; a dynamic role is undecidable statically, so it is left to review. */
194
+ /** Only statically knowable branches; unresolved roles remain a review boundary. */
195
+ const staticStrings = (node: any): string[] => {
196
+ if (node?.type === 'Literal' && typeof node.value === 'string') return [node.value]
197
+ if (node?.type === 'JSXExpressionContainer') return staticStrings(node.expression)
198
+ if (node?.type === 'ConditionalExpression')
199
+ return [...staticStrings(node.consequent), ...staticStrings(node.alternate)]
200
+ if (node?.type === 'TemplateLiteral' && node.expressions.length === 0)
201
+ return [node.quasis[0].value.cooked ?? node.quasis[0].value.raw]
202
+ return []
203
+ }
204
+
179
205
  const roleOf = (node: any): string | null => {
180
206
  for (const attribute of node.attributes ?? []) {
181
207
  if (attribute.type !== 'JSXAttribute' || attribute.name?.name !== 'role') continue
182
- if (attribute.value?.type === 'Literal' && typeof attribute.value.value === 'string')
183
- return attribute.value.value
184
- return null
208
+ return (
209
+ staticStrings(attribute.value)
210
+ .flatMap((role) => role.split(/\s+/))
211
+ .find((role) => role in ROLE_PRIMITIVES) ?? null
212
+ )
185
213
  }
186
214
  return null
187
215
  }
@@ -189,9 +217,34 @@ const roleOf = (node: any): string | null => {
189
217
  /** Whether the element carries the attribute, by name. */
190
218
  const has = (node: any, name: string): boolean =>
191
219
  (node.attributes ?? []).some(
192
- (attribute: any) => attribute.type === 'JSXAttribute' && attribute.name?.name === name
220
+ (attribute: any) =>
221
+ attribute.type === 'JSXAttribute' &&
222
+ (attribute.name?.type === 'JSXNamespacedName'
223
+ ? `${attribute.name.namespace.name}:${attribute.name.name.name}`
224
+ : attribute.name?.name) === name
193
225
  )
194
226
 
227
+ const hasTabStop = (node: any): boolean =>
228
+ (node.attributes ?? []).some((attribute: any) => {
229
+ if (
230
+ attribute.type !== 'JSXAttribute' ||
231
+ !['tabIndex', 'tabindex'].includes(attribute.name?.name)
232
+ )
233
+ return false
234
+ const value =
235
+ attribute.value?.type === 'JSXExpressionContainer'
236
+ ? attribute.value.expression
237
+ : attribute.value
238
+ if (value?.type === 'Literal') return !(Number(value.value) < 0)
239
+ if (
240
+ value?.type === 'UnaryExpression' &&
241
+ value.operator === '-' &&
242
+ value.argument?.type === 'Literal'
243
+ )
244
+ return !(Number(value.argument.value) > 0)
245
+ return true
246
+ })
247
+
195
248
  const noInteractiveWrappers: RuleModule = {
196
249
  meta: {
197
250
  type: 'problem',
@@ -226,6 +279,10 @@ const noInteractiveWrappers: RuleModule = {
226
279
  if (node.name?.type !== 'JSXIdentifier') return
227
280
  const element = node.name.name
228
281
  if (allowed.has(element) || element in ELEMENT_PRIMITIVES) return
282
+ // A capitalized element is a component — often a design-system primitive
283
+ // composing a role deliberately (a Button carrying role="option" inside a
284
+ // custom listbox, for example). That is composition, not impersonation.
285
+ if (element[0] === element[0]?.toUpperCase()) return
229
286
 
230
287
  const role = roleOf(node)
231
288
  if (role && role in ROLE_PRIMITIVES) {
@@ -251,7 +308,7 @@ const noInteractiveWrappers: RuleModule = {
251
308
  })
252
309
  return
253
310
  }
254
- if (has(node, 'tabIndex') || has(node, 'tabindex')) {
311
+ if (hasTabStop(node)) {
255
312
  context.report({
256
313
  node,
257
314
  messageId: 'tabindexWrapper',
@@ -311,6 +368,17 @@ const noPrimitiveLibraryImports: RuleModule = {
311
368
  if (typeof node.source?.value === 'string' && offender(node.source.value))
312
369
  report(node, node.source.value)
313
370
  },
371
+ ExportNamedDeclaration(node: any) {
372
+ if (offender(node.source?.value)) report(node, node.source.value)
373
+ },
374
+ ExportAllDeclaration(node: any) {
375
+ if (offender(node.source?.value)) report(node, node.source.value)
376
+ },
377
+ CallExpression(node: any) {
378
+ if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return
379
+ const source = node.arguments?.[0]?.value
380
+ if (typeof source === 'string' && offender(source)) report(node, source)
381
+ },
314
382
  }
315
383
  },
316
384
  }