@estiva-app/ui 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/Checkbox.d.ts +16 -1
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/CommandPalette.d.ts.map +1 -1
- package/dist/Form.d.ts +16 -1
- package/dist/Form.d.ts.map +1 -1
- package/dist/ScrollArea.d.ts +6 -0
- package/dist/ScrollArea.d.ts.map +1 -1
- package/dist/eslint/index.d.ts +2 -0
- package/dist/eslint/index.d.ts.map +1 -1
- package/dist/eslint/index.js +459 -2
- package/dist/eslint/index.js.map +4 -4
- package/dist/eslint/no-rebuilt-behaviour.d.ts +88 -0
- package/dist/eslint/no-rebuilt-behaviour.d.ts.map +1 -0
- package/dist/index.js +208 -188
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/Checkbox.mdx +20 -1
- package/src/Checkbox.stories.tsx +44 -0
- package/src/Checkbox.test.tsx +63 -0
- package/src/Checkbox.tsx +30 -1
- package/src/CommandPalette.mdx +6 -2
- package/src/CommandPalette.stories.tsx +6 -3
- package/src/CommandPalette.test.tsx +15 -0
- package/src/CommandPalette.tsx +34 -46
- package/src/Form.mdx +12 -2
- package/src/Form.test.tsx +103 -0
- package/src/Form.tsx +64 -4
- package/src/ScrollArea.stories.tsx +18 -0
- package/src/ScrollArea.tsx +7 -1
- package/src/eslint/index.test.ts +31 -7
- package/src/eslint/index.ts +10 -3
- package/src/eslint/no-rebuilt-behaviour.test.ts +276 -0
- package/src/eslint/no-rebuilt-behaviour.ts +593 -0
- package/stories/Choosing.mdx +1 -1
package/dist/eslint/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/eslint/index.ts", "../../src/eslint/has-a-page-and-a-story.ts", "../../src/eslint/escape.ts", "../../src/eslint/no-hand-rolled-behaviour.ts", "../../src/eslint/no-raw-element.ts", "../../src/eslint/raw-element-outside-a-wrapper.ts"],
|
|
4
|
-
"sourcesContent": ["/// <reference types=\"node\" />\n/**\n * `@estiva-app/ui/eslint` \u2014 the UI Guardrails' lint rules, as a plugin (UIG-3,\n * seam S1 in docs/GATES.md \u00A716).\n *\n * A plugin rather than config, so every app \u2014 Peek, Ship, Leaf \u2014 gets a new\n * rule through an ordinary version bump, and an app's editor hook runs the\n * very rule code its CI runs.\n *\n * import estiva from '@estiva-app/ui/eslint'\n * export default [{ files: ['src/**\\/*.tsx'], ...estiva.configs.recommended }]\n *\n * Register it under `estiva` (the configs do): the rule ids are\n * `estiva/<rule>`, and `countGates` counts those ids.\n *\n * Built on its own by build.mjs, for Node, into `dist/eslint/`; nothing here\n * reaches the components' browser bundle.\n */\nimport { createRequire } from 'node:module'\nimport type { ESLint, Linter } from 'eslint'\nimport { componentHasAPage, componentHasAStory } from './has-a-page-and-a-story'\nimport { noHandRolledBehaviour } from './no-hand-rolled-behaviour'\nimport { noRawElement } from './no-raw-element'\nimport { rawElementOutsideAWrapper } from './raw-element-outside-a-wrapper'\n\nexport { ESCAPE_MARKER, MIN_REASON, SETTINGS_KEY, isEscaped, type EstivaSettings } from './escape'\n\nconst { version } = createRequire(import.meta.url)('../../package.json') as { version: string }\n\n/** The name the configs register the plugin under, so every rule id is `estiva/<rule>`. */\nexport const PLUGIN_KEY = 'estiva'\n\n/**\n * The rules an **app** runs: they say an app must not build what the package\n * already has. `recommended` and `strict` carry these and only these.\n */\nconst appRules = {\n 'no-raw-element': noRawElement,\n}\n\n/**\n * The rules the **package itself** runs, pointed inward (UIG-5): don't bury a\n * raw element inside a component, don't rebuild what Base UI owns, don't ship a\n * component without a page or a story.\n *\n * They are in `configs.package`, never in `recommended`, on purpose. Peek and\n * Ship spread `recommended`, so a rule added here must not arrive in an app\n * with the next version bump: an app's components are not primitives, so\n * \"buried inside a component\" means nothing there (`no-raw-element` is the\n * app's version, UIG-7), and an app has no `.mdx` pages at all. `index.test.ts` holds the\n * apps' list to exactly the app rules.\n */\nconst packageRules = {\n 'raw-element-outside-a-wrapper': rawElementOutsideAWrapper,\n 'no-hand-rolled-behaviour': noHandRolledBehaviour,\n 'component-has-a-page': componentHasAPage,\n 'component-has-a-story': componentHasAStory,\n}\n\nconst rules = { ...appRules, ...packageRules }\n\nconst plugin = {\n meta: { name: '@estiva-app/ui/eslint', version },\n rules,\n configs: {} as { recommended: Linter.Config; strict: Linter.Config; package: Linter.Config },\n} satisfies ESLint.Plugin\n\n/** The ids `recommended` and `strict` carry \u2014 what an app's gate runs and counts. */\nexport const APP_RULE_IDS = Object.keys(appRules).map((name) => `${PLUGIN_KEY}/${name}`)\n/** The ids `package` carries \u2014 what this package's own gate runs and counts (UIG-5). */\nexport const PACKAGE_RULE_IDS = Object.keys(packageRules).map((name) => `${PLUGIN_KEY}/${name}`)\n\n/**\n * `recommended` switches every **app** rule on at the level it was ruled at: an\n * error blocks, a warning is reported and never blocks. `strict` makes every app\n * rule an error. With one rule, an error, the two are the same today; they part\n * when the first warning-level rule arrives (UIG-25).\n *\n * `package` is the inward set (UIG-5), which only this package runs.\n */\nplugin.configs.recommended = {\n name: '@estiva-app/ui/recommended',\n plugins: { [PLUGIN_KEY]: plugin },\n rules: { [`${PLUGIN_KEY}/no-raw-element`]: 'error' },\n}\nplugin.configs.strict = {\n name: '@estiva-app/ui/strict',\n plugins: { [PLUGIN_KEY]: plugin },\n rules: Object.fromEntries(APP_RULE_IDS.map((id) => [id, 'error'])),\n}\nplugin.configs.package = {\n name: '@estiva-app/ui/package',\n plugins: { [PLUGIN_KEY]: plugin },\n rules: Object.fromEntries(PACKAGE_RULE_IDS.map((id) => [id, 'error'])),\n}\n\nexport default plugin\n\nexport interface GateRuleCount {\n errors: number\n warnings: number\n escapes: number\n}\n\nexport interface GateCount {\n /** Per rule id, including rules with nothing to report. */\n rules: Record<string, GateRuleCount>\n /**\n * Reports of these rules that an `eslint-disable` directive silenced. Not\n * an escape: a gate fails on any of these (docs/GATES.md \u00A716, S4).\n */\n disabled: { filePath: string; line: number; ruleId: string }[]\n}\n\n/**\n * Count what a lint of these rules found, for `.gates-count.json` (seam S3).\n *\n * Run the lint with `settings: { estiva: { reportEscapes: true } }` for the\n * escapes to be counted; without it they are silent and count 0. A marker\n * with no reason, or inside a directive, counts as an error of its rule.\n *\n * `seed` is which rules the count lists when they found nothing, and it is the\n * app rules unless a caller says otherwise: an app's count file must not gain\n * rows for the inward rules (UIG-5) that its gate does not run. This package's\n * own count script passes `PACKAGE_RULE_IDS`.\n */\nexport function countGates(results: ESLint.LintResult[], seed: readonly string[] = APP_RULE_IDS): GateCount {\n const counts: Record<string, GateRuleCount> = Object.fromEntries(seed.map((id) => [id, { errors: 0, warnings: 0, escapes: 0 }]))\n const disabled: GateCount['disabled'] = []\n const ours = (ruleId: string | null | undefined): ruleId is string => typeof ruleId === 'string' && ruleId.startsWith(`${PLUGIN_KEY}/`)\n for (const result of results) {\n for (const message of result.messages) {\n // Seeded or not: every rule of this plugin that reported is counted, so a\n // lint of a set the caller did not name is still counted in full.\n if (!ours(message.ruleId)) continue\n const count = (counts[message.ruleId] ??= { errors: 0, warnings: 0, escapes: 0 })\n if (message.messageId === 'escaped') count.escapes += 1\n else if (message.severity === 2) count.errors += 1\n else count.warnings += 1\n }\n for (const message of result.suppressedMessages ?? []) {\n if (ours(message.ruleId)) disabled.push({ filePath: result.filePath, line: message.line, ruleId: message.ruleId })\n }\n }\n return { rules: counts, disabled }\n}\n", "import { existsSync } from 'node:fs'\nimport type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\n/**\n * A component of the package with no page, and one with no story (UIG-5, P2 and\n * P3).\n *\n * A component nobody can read about is a component nobody uses correctly, and\n * one nobody can look at is one nobody reviews. Both were at zero when these\n * were switched on; they are here so the next component cannot arrive without\n * them.\n *\n * The pair is by file name, beside the component, which is how this package is\n * laid out: `Button.tsx`, `Button.mdx`, `Button.stories.tsx`, `Button.test.tsx`\n * side by side in `src/`.\n *\n * **The false positive to avoid** (UIG-1 confirmed it is real): `FieldLine` and\n * `MenuItem` have a page and a story but no `.tsx` of their own \u2014 they are\n * exported from a sibling. These rules read a component file and ask for its\n * page and its story, never the other way round, so a page without a component\n * is not this rule's business.\n *\n * A file that stays without one says why at its top: `// @estiva-escape: <why>`.\n */\ninterface ProgramNode {\n body: { loc?: Rule.Node['loc']; range?: [number, number] }[]\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n}\n\n/** A component file: a `.tsx` in the package's source that is not a story or a test. */\nfunction componentFile(filename: string): boolean {\n return filename.endsWith('.tsx') && !/\\.(stories|test)\\.tsx$/.test(filename)\n}\n\nfunction sibling(filename: string, extension: string): string {\n return filename.replace(/\\.tsx$/, extension)\n}\n\n/**\n * Report on the file's first statement, so the escape is a comment at the top of\n * the file \u2014 the only place a reason for a missing page could go.\n */\nfunction reportOnFile(context: Rule.RuleContext, program: ProgramNode, messageId: string, data: Record<string, string>): void {\n const anchor = program.body[0] ?? program\n if (anchor.loc && anchor.range && isEscaped(context, { loc: anchor.loc, range: anchor.range })) return\n context.report({ loc: anchor.loc ?? program.loc, messageId, data })\n}\n\nexport const componentHasAPage: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'Every component of the package has a usage page beside it' },\n schema: [],\n messages: {\n missing: '{{name}} has no page. Write {{page}} beside it \u2014 what it is, when, when not, how, and what it owns \u2014 or say why not at the top of the file.',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n Program(node) {\n const filename = context.filename\n if (!componentFile(filename)) return\n const page = sibling(filename, '.mdx')\n if (existsSync(page)) return\n const name = filename.split(/[\\\\/]/).pop() ?? filename\n reportOnFile(context, node as unknown as ProgramNode, 'missing', { name, page: page.split(/[\\\\/]/).pop() ?? page })\n },\n }\n },\n}\n\nexport const componentHasAStory: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'Every component of the package has a story beside it' },\n schema: [],\n messages: {\n missing: '{{name}} has no story. Write {{story}} beside it, so it can be seen and reviewed, or say why not at the top of the file.',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n Program(node) {\n const filename = context.filename\n if (!componentFile(filename)) return\n const story = sibling(filename, '.stories.tsx')\n if (existsSync(story)) return\n const name = filename.split(/[\\\\/]/).pop() ?? filename\n reportOnFile(context, node as unknown as ProgramNode, 'missing', { name, story: story.split(/[\\\\/]/).pop() ?? story })\n },\n }\n },\n}\n", "import type { AST, Rule } from 'eslint'\n\n/**\n * The escape marker (UIG-3, seam S4 in docs/GATES.md \u00A716): one written reason\n * why one element stays as it is.\n *\n * A line comment directly above the element,\n *\n * // @estiva-escape: <reason>\n *\n * or, where the element is a JSX child, the JSX comment on the line above,\n *\n * {/* @estiva-escape: <reason> *\\/}\n *\n * with at least ten characters of reason, spaces not counted.\n *\n * Not `eslint-disable`. A directive switches a rule off without saying it was\n * meant, and the count of escapes (`.gates-count.json`) could not see it. A\n * marker written inside a directive that switches one of these rules off is\n * reported for that reason.\n *\n * The token lint's older notes (`eslint-disable-next-line <rule> -- @estiva-escape:\n * <reason>`, Katerina's ruling A2 of 15 September) are not read here: they\n * escape the token lint's rules, which are not this plugin's, and they stay as\n * they are (Katerina, 15 September).\n */\nexport const ESCAPE_MARKER = '@estiva-escape'\n\n/** Characters of reason, spaces not counted. */\nexport const MIN_REASON = 10\n\n/** The key under ESLint's `settings` this plugin reads. */\nexport const SETTINGS_KEY = 'estiva'\n\n/**\n * Every rule spreads these into its `meta.messages`, because `isEscaped`\n * reports through the rule that called it.\n */\nexport const ESCAPE_MESSAGES = {\n escapeWithoutReason: `An escape needs its reason, at least ${MIN_REASON} characters: \\`// ${ESCAPE_MARKER}: <why this stays>\\`. An escape with no reason hides nothing.`,\n escapeInDirective: `Write the escape as its own comment on the line above: \\`// ${ESCAPE_MARKER}: <reason>\\`. Inside an eslint-disable comment it switches the rule off instead of recording why.`,\n escaped: 'Escaped: {{reason}}',\n} as const\n\nexport interface EstivaSettings {\n /**\n * Report every sanctioned escape as a message with the id `escaped`, so a\n * count can read them. Off in the lint anyone runs; on only in the count.\n */\n reportEscapes?: boolean\n}\n\ninterface Located {\n loc?: AST.SourceLocation | null\n range?: [number, number]\n}\n\nconst DIRECTIVE = /^\\s*eslint-disable(?:-next-line|-line)?(?=\\s|$)([^]*)$/\n\n/** The rules a directive names: what comes before its ` -- ` description. None means every rule. */\nfunction directiveRules(rest: string): string[] {\n const dashes = rest.search(/(?:^|\\s)--(?:\\s|$)/)\n return (dashes === -1 ? rest : rest.slice(0, dashes)).split(/[\\s,]+/).filter(Boolean)\n}\n\n/** Only spaces and a JSX expression's closing brace between the marker and the element. */\nconst BETWEEN = /^[\\s}]*$/\n\n/**\n * Whether the element at `node` carries a valid escape on the line above.\n *\n * Every rule of this plugin calls it before it reports. It reports, through\n * that rule, a marker with too short a reason and a marker inside an\n * eslint-disable directive for this rule; either way the element is not\n * escaped, so the rule reports it too.\n */\nexport function isEscaped(context: Rule.RuleContext, node: Located): boolean {\n const { sourceCode } = context\n if (!node.loc || !node.range) return false\n const line = node.loc.start.line\n const nodeStart = node.range[0]\n\n const comment = sourceCode\n .getAllComments()\n .find((c) => c.loc && c.range && c.loc.end.line === line - 1 && BETWEEN.test(sourceCode.text.slice(c.range[1], nodeStart)))\n if (!comment?.loc) return false\n\n const at = comment.value.indexOf(ESCAPE_MARKER)\n if (at === -1) return false\n\n const directive = DIRECTIVE.exec(comment.value)\n if (directive) {\n const rules = directiveRules(directive[1])\n // A directive for other rules (the token lint's notes) is not an escape of this one.\n if (rules.length > 0 && !rules.includes(context.id)) return false\n context.report({ loc: comment.loc, messageId: 'escapeInDirective' })\n return false\n }\n\n const reason = comment.value\n .slice(at + ESCAPE_MARKER.length)\n .replace(/^:/, '')\n .trim()\n if (reason.replace(/\\s/g, '').length < MIN_REASON) {\n context.report({ loc: comment.loc, messageId: 'escapeWithoutReason' })\n return false\n }\n\n const settings = context.settings[SETTINGS_KEY] as EstivaSettings | undefined\n if (settings?.reportEscapes) context.report({ loc: comment.loc, messageId: 'escaped', data: { reason } })\n return true\n}\n", "import type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\n/**\n * Behaviour Base UI already owns, written by hand inside the package (UIG-5,\n * P1 \u2014 the migration's standing rule D6 as a machine).\n *\n * This is the rule with the most to lose. A hand-made portal or a global\n * listener inside `DialogShell` is inherited by every caller in every app, and\n * nobody sees it from there.\n *\n * Two facts, both mechanical, both the signature of a floating part rebuilt by\n * hand:\n *\n * - **`createPortal`**, or importing it from `react-dom`. Base UI's parts carry\n * their own portal.\n * - **a listener on `window` or `document`** for the events a floating part\n * lives on \u2014 resize, scroll, an outside press, a key. Base UI's Positioner\n * follows the anchor and its Root closes on an outside press and on Escape.\n *\n * What it deliberately does not try to read: measuring arithmetic. `Popover`\n * hands Base UI a virtual anchor with a `getBoundingClientRect`, which is the\n * documented way to anchor to a rectangle rather than an element, and a rule\n * that guessed at arithmetic would report it. Anything that shape is caught by\n * a reader, not by this.\n *\n * A place that keeps one says why on the line above: `// @estiva-escape: <why>`.\n */\nconst FLOATING_EVENTS = new Set(['resize', 'scroll', 'mousedown', 'pointerdown', 'keydown', 'keyup', 'focusin', 'focusout', 'click'])\n\ninterface Node {\n type: string\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n}\n\ninterface Identifier extends Node {\n name: string\n}\n\ninterface ImportDeclaration extends Node {\n source: { value: unknown }\n specifiers: { type: string; imported?: { name?: string }; local?: { name?: string }; loc?: Rule.Node['loc']; range?: [number, number] }[]\n}\n\ninterface CallExpression extends Node {\n callee: { type: string; name?: string; object?: { type: string; name?: string }; property?: { type: string; name?: string } }\n arguments: { type: string; value?: unknown }[]\n parent?: { type: string; loc?: Rule.Node['loc']; range?: [number, number]; parent?: CallExpression['parent'] }\n}\n\n/**\n * The statement a call sits in, which is what an escape is written above:\n * `return createPortal(\u2026)` puts the call in the middle of its line, and a\n * comment above the line is above the statement.\n */\nfunction statement(node: { loc: NonNullable<Rule.Node['loc']>; range: [number, number]; parent?: CallExpression['parent'] }): {\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n} {\n for (let p = node.parent; p; p = p.parent) {\n if (!/(Statement|Declaration)$/.test(p.type)) continue\n if (p.loc && p.range) return { loc: p.loc, range: p.range }\n break\n }\n return { loc: node.loc, range: node.range }\n}\n\nexport const noHandRolledBehaviour: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'Overlay behaviour written by hand where Base UI has a part (D6)' },\n schema: [],\n messages: {\n portal:\n 'A portal written by hand. Base UI\\'s parts carry their own (Popover, Menu, Select, Tooltip, Dialog, Toast): use the part, or say why this one stays.',\n listener:\n 'A `{{event}}` listener on `{{target}}`. A floating part follows its anchor and closes on an outside press and on Escape by itself (Base UI Positioner and Root): use the part, or say why this one stays.',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n ImportDeclaration(node: Rule.Node) {\n const declaration = node as unknown as ImportDeclaration\n if (declaration.source.value !== 'react-dom') return\n for (const specifier of declaration.specifiers) {\n if (specifier.type !== 'ImportSpecifier' || specifier.imported?.name !== 'createPortal') continue\n // The escape goes above the import line, not above the name inside it.\n if (isEscaped(context, declaration)) return\n context.report({ loc: declaration.loc, messageId: 'portal' })\n return\n }\n },\n CallExpression(node: Rule.Node) {\n const call = node as unknown as CallExpression\n const { callee } = call\n\n if (callee.type === 'Identifier' && callee.name === 'createPortal') {\n if (isEscaped(context, statement(call))) return\n context.report({ loc: call.loc, messageId: 'portal' })\n return\n }\n\n if (callee.type !== 'MemberExpression' || callee.property?.name !== 'addEventListener') return\n const target = callee.object?.type === 'Identifier' ? callee.object.name : undefined\n if (target !== 'window' && target !== 'document') return\n const [first] = call.arguments\n const event = first?.type === 'Literal' && typeof first.value === 'string' ? first.value : undefined\n if (!event || !FLOATING_EVENTS.has(event)) return\n if (isEscaped(context, statement(call))) return\n context.report({ loc: call.loc, messageId: 'listener', data: { event, target } })\n },\n }\n },\n}\n", "import type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\ninterface JSXAttributeValue {\n type: string\n value?: unknown\n expression?: { type: string; value?: unknown; expressions?: unknown[]; quasis?: { value: { cooked: string | null } }[] }\n}\n\ninterface JSXAttribute {\n type: 'JSXAttribute'\n name: { type: string; name?: string }\n value: JSXAttributeValue | null\n}\n\ninterface JSXOpeningElement {\n name: { type: string; name?: string }\n attributes: (JSXAttribute | { type: 'JSXSpreadAttribute' })[]\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n}\n\n/**\n * What the package has instead of one raw element: the component the message\n * names, and where one element does more than one job, the others after it.\n * `null` is an element the package has no part for yet.\n */\nexport interface RawElementPart {\n use: string\n more?: string\n}\n\n/**\n * The mapping (UIG-7), derived from the package's exports: every interactive\n * element in the HTML standard's list (\"interactive content\"), plus `<dialog>`,\n * `<form>` and `<progress>`, which have a part here. docs/GATES.md prints it.\n *\n * Not here, on purpose: `<summary>` (it lives inside a `<details>`, which is\n * reported), `<option>`, `<optgroup>` and `<datalist>` (they live inside a\n * `<select>` or beside an input), `<fieldset>` and `<legend>` (a group's frame,\n * no control of its own), and `<img>` without `usemap`, `<hr>`, `<kbd>`, which\n * are not controls. An `<audio>` or `<video>` is a control only with `controls`.\n */\nexport const RAW_ELEMENT_PARTS: Record<string, RawElementPart | null> = {\n a: { use: 'Link', more: ' For a chip, `InlineChip`; for a whole card, `Card` with `href`.' },\n button: { use: 'Button' },\n textarea: { use: 'Textarea' },\n select: { use: 'Select' },\n dialog: { use: 'DialogShell', more: ' To ask yes or no, `ConfirmDialog`.' },\n label: { use: 'Field', more: ' For words beside a tick box, `Checkbox` with `label`.' },\n details: { use: 'CollapsibleSection' },\n progress: { use: 'ProgressBar' },\n form: { use: 'Form' },\n meter: null,\n iframe: null,\n embed: null,\n object: null,\n audio: null,\n video: null,\n img: null,\n}\n\n/**\n * An `<input>` is as many controls as its `type`. A type missing from here\n * (text, email, password, url, tel, number, or one the browser does not know)\n * is a text box.\n */\nexport const RAW_INPUT_PARTS: Record<string, RawElementPart | null> = {\n search: { use: 'SearchInput' },\n checkbox: { use: 'Checkbox' },\n file: { use: 'FilePicker' },\n submit: { use: 'Button' },\n button: { use: 'Button' },\n reset: { use: 'Button' },\n image: { use: 'Button' },\n radio: null,\n range: null,\n color: null,\n date: null,\n 'datetime-local': null,\n month: null,\n week: null,\n time: null,\n}\n\nconst TEXT_INPUT: RawElementPart = { use: 'TextInput' }\nconst ANY_INPUT: RawElementPart = {\n use: 'TextInput',\n more: ' For a search, `SearchInput`; for a tick box, `Checkbox`; to pick files, `FilePicker`.',\n}\n\nfunction attribute(element: JSXOpeningElement, name: string): JSXAttribute | undefined {\n return element.attributes.find((a): a is JSXAttribute => a.type === 'JSXAttribute' && a.name.name === name)\n}\n\n/** An attribute's value when the code spells it out, `undefined` when it is computed. */\nfunction literal(attr: JSXAttribute): string | boolean | undefined {\n const value = attr.value\n if (value === null) return true\n if (value.type === 'Literal') return typeof value.value === 'string' ? value.value : undefined\n if (value.type !== 'JSXExpressionContainer' || !value.expression) return undefined\n const expression = value.expression\n if (expression.type === 'Literal' && (typeof expression.value === 'string' || typeof expression.value === 'boolean')) return expression.value\n if (expression.type === 'TemplateLiteral' && expression.expressions?.length === 0) return expression.quasis?.[0]?.value.cooked ?? undefined\n return undefined\n}\n\n/** Present, and not written as `={false}`. */\nfunction isOn(element: JSXOpeningElement, name: string): boolean {\n const attr = attribute(element, name)\n return attr !== undefined && literal(attr) !== false\n}\n\ntype Found = { report: 'raw'; element: string; part: RawElementPart } | { report: 'noPart'; element: string } | null\n\n/** What this element is to the rule: a raw element with a part, one with none yet, or none of its business. */\nexport function classify(element: JSXOpeningElement): Found {\n if (element.name.type !== 'JSXIdentifier' || !element.name.name) return null\n const name = element.name.name\n\n if (name === 'input') {\n const attr = attribute(element, 'type')\n const type = attr ? literal(attr) : 'text'\n if (typeof type !== 'string') return { report: 'raw', element: '<input>', part: ANY_INPUT }\n const key = type.toLowerCase()\n // A hidden input draws nothing and takes no focus: it is data, not a control.\n if (key === 'hidden') return null\n if (!(key in RAW_INPUT_PARTS)) return { report: 'raw', element: '<input>', part: TEXT_INPUT }\n const part = RAW_INPUT_PARTS[key]\n const shown = `<input type=\"${key}\">`\n return part ? { report: 'raw', element: shown, part } : { report: 'noPart', element: shown }\n }\n\n if (!Object.hasOwn(RAW_ELEMENT_PARTS, name)) return null\n if ((name === 'audio' || name === 'video') && !isOn(element, 'controls')) return null\n if (name === 'img' && !attribute(element, 'usemap') && !attribute(element, 'useMap')) return null\n\n const part = RAW_ELEMENT_PARTS[name]\n return part ? { report: 'raw', element: `<${name}>`, part } : { report: 'noPart', element: `<${name}>` }\n}\n\n/**\n * A raw interactive element in an app, where the package has a part for it\n * (UIG-3 began it with `<button>`; UIG-7 made it every element). A control\n * the app writes itself has a look, a focus ring and keys that nobody else\n * keeps in step with \u2014 and it looks nearly right, which is why nobody catches\n * it by eye. Every message names the part to use.\n *\n * An element the package has no part for yet is refused too, with a message\n * that says so: the part is made in the package, not by hand in an app\n * (Katerina, 16 September).\n *\n * Only a JSX element written with a lowercase name. `<Button>`,\n * `<Foo.button>` and `createElement('button')` are not this rule's business.\n */\nexport const noRawElement: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'A raw interactive element where @estiva-app/ui has a part, or where it needs one' },\n schema: [],\n messages: {\n raw: 'Use `{{use}}` from @estiva-app/ui instead of a raw {{element}}.{{more}}',\n noPart: '@estiva-app/ui has no part for a raw {{element}} yet. Do not build one here: ask Katerina, and it gets made in @estiva-app/ui.',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n // ESLint's types know ESTree's nodes, not JSX's; the parser hands this one over.\n JSXOpeningElement(node: Rule.Node) {\n const element = node as unknown as JSXOpeningElement\n const found = classify(element)\n if (!found) return\n if (isEscaped(context, element)) return\n if (found.report === 'noPart') context.report({ loc: element.loc, messageId: 'noPart', data: { element: found.element } })\n else context.report({ loc: element.loc, messageId: 'raw', data: { element: found.element, use: found.part.use, more: found.part.more ?? '' } })\n },\n }\n },\n}\n", "import type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\ninterface JSXOpeningElement {\n type: string\n name: { type: string; name?: string }\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n parent?: Parent\n}\n\ninterface Parent {\n type: string\n name?: { name?: string }\n openingElement?: unknown\n parent?: Parent\n}\n\n/**\n * The elements this package writes by hand. The set the count used (UIG-1,\n * docs/GATES.md \u00A75 and \u00A77): every element that carries behaviour or a role of\n * its own. A `div` or a `span` is layout and is nobody's business here.\n */\nexport const RAW_ELEMENTS = new Set(['a', 'button', 'input', 'textarea', 'select', 'dialog', 'form', 'label'])\n\n/**\n * A raw element nested inside another element, in the package itself (UIG-5,\n * the inward tracer).\n *\n * The package is allowed to write raw elements \u2014 that is what a primitive is.\n * Two shapes are right, and both are facts about the code rather than a\n * judgement:\n *\n * - **the component's own outermost element.** `NavItem` is an `<a>`, `Link` is\n * an `<a>`, a `MenuItem` outside a `Menu` is a `<button>`. A primitive owning\n * its element is the whole point of it.\n * - **handed to a Base UI `render` prop.** `render={<button type=\"button\" />}`\n * tells a Base UI part which element to be; the behaviour stays Base UI's.\n *\n * Anything else is an element buried inside a bigger component, where its look\n * and its behaviour are kept in step by nobody \u2014 the package's own version of\n * the rule the apps get from UIG-3 and UIG-7.\n *\n * It keeps its reason on the line above if it stays: `// @estiva-escape: <why>`.\n */\nexport const rawElementOutsideAWrapper: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'A raw element nested inside a component, rather than the component itself' },\n schema: [],\n messages: {\n nested:\n 'A raw <{{name}}> inside another element. In this package a raw element is either the component\\'s own outermost element or handed to a Base UI `render` prop; one buried inside belongs in a component of its own (Link, Button, MenuItem...).',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n // ESLint's types know ESTree's nodes, not JSX's; the parser hands this one over.\n JSXOpeningElement(node: Rule.Node) {\n const element = node as unknown as JSXOpeningElement\n const name = element.name.name\n if (element.name.type !== 'JSXIdentifier' || !name || !RAW_ELEMENTS.has(name)) return\n\n // An opening tag's own parent is its JSXElement: that is this element,\n // not an element around it.\n let parent = element.parent\n if (parent?.type === 'JSXElement' && parent.openingElement === (element as unknown)) parent = parent.parent\n\n // Whichever comes first going outward decides: an element around it\n // means it is nested; a `render` prop means Base UI draws it.\n for (let p = parent; p; p = p.parent) {\n if (p.type === 'JSXAttribute') {\n if (p.name?.name === 'render') return\n continue\n }\n if (p.type !== 'JSXElement' && p.type !== 'JSXFragment') continue\n if (isEscaped(context, element)) return\n context.report({ loc: element.loc, messageId: 'nested', data: { name } })\n return\n }\n },\n }\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["rules", "part"]
|
|
3
|
+
"sources": ["../../src/eslint/index.ts", "../../src/eslint/has-a-page-and-a-story.ts", "../../src/eslint/escape.ts", "../../src/eslint/no-hand-rolled-behaviour.ts", "../../src/eslint/no-raw-element.ts", "../../src/eslint/no-rebuilt-behaviour.ts", "../../src/eslint/raw-element-outside-a-wrapper.ts"],
|
|
4
|
+
"sourcesContent": ["/// <reference types=\"node\" />\n/**\n * `@estiva-app/ui/eslint` \u2014 the UI Guardrails' lint rules, as a plugin (UIG-3,\n * seam S1 in docs/GATES.md \u00A716).\n *\n * A plugin rather than config, so every app \u2014 Peek, Ship, Leaf \u2014 gets a new\n * rule through an ordinary version bump, and an app's editor hook runs the\n * very rule code its CI runs.\n *\n * import estiva from '@estiva-app/ui/eslint'\n * export default [{ files: ['src/**\\/*.tsx'], ...estiva.configs.recommended }]\n *\n * Register it under `estiva` (the configs do): the rule ids are\n * `estiva/<rule>`, and `countGates` counts those ids.\n *\n * Built on its own by build.mjs, for Node, into `dist/eslint/`; nothing here\n * reaches the components' browser bundle.\n */\nimport { createRequire } from 'node:module'\nimport type { ESLint, Linter } from 'eslint'\nimport { componentHasAPage, componentHasAStory } from './has-a-page-and-a-story'\nimport { noHandRolledBehaviour } from './no-hand-rolled-behaviour'\nimport { noRawElement } from './no-raw-element'\nimport { noRebuiltBehaviour } from './no-rebuilt-behaviour'\nimport { rawElementOutsideAWrapper } from './raw-element-outside-a-wrapper'\n\nexport { ESCAPE_MARKER, MIN_REASON, SETTINGS_KEY, isEscaped, type EstivaSettings } from './escape'\nexport { OWNED_BEHAVIOURS, type OwnedBehaviour } from './no-rebuilt-behaviour'\n\nconst { version } = createRequire(import.meta.url)('../../package.json') as { version: string }\n\n/** The name the configs register the plugin under, so every rule id is `estiva/<rule>`. */\nexport const PLUGIN_KEY = 'estiva'\n\n/**\n * The rules an **app** runs: they say an app must not build what the package\n * already has \u2014 a raw control (UIG-7), or a behaviour one of its parts owns\n * (UIG-8). `recommended` and `strict` carry these and only these.\n */\nconst appRules = {\n 'no-raw-element': noRawElement,\n 'no-rebuilt-behaviour': noRebuiltBehaviour,\n}\n\n/**\n * The rules the **package itself** runs, pointed inward (UIG-5): don't bury a\n * raw element inside a component, don't rebuild what Base UI owns, don't ship a\n * component without a page or a story.\n *\n * They are in `configs.package`, never in `recommended`, on purpose. Peek and\n * Ship spread `recommended`, so a rule added here must not arrive in an app\n * with the next version bump: an app's components are not primitives, so\n * \"buried inside a component\" means nothing there (`no-raw-element` is the\n * app's version, UIG-7), and an app has no `.mdx` pages at all. `index.test.ts` holds the\n * apps' list to exactly the app rules.\n */\nconst packageRules = {\n 'raw-element-outside-a-wrapper': rawElementOutsideAWrapper,\n 'no-hand-rolled-behaviour': noHandRolledBehaviour,\n 'component-has-a-page': componentHasAPage,\n 'component-has-a-story': componentHasAStory,\n}\n\nconst rules = { ...appRules, ...packageRules }\n\nconst plugin = {\n meta: { name: '@estiva-app/ui/eslint', version },\n rules,\n configs: {} as { recommended: Linter.Config; strict: Linter.Config; package: Linter.Config },\n} satisfies ESLint.Plugin\n\n/** The ids `recommended` and `strict` carry \u2014 what an app's gate runs and counts. */\nexport const APP_RULE_IDS = Object.keys(appRules).map((name) => `${PLUGIN_KEY}/${name}`)\n/** The ids `package` carries \u2014 what this package's own gate runs and counts (UIG-5). */\nexport const PACKAGE_RULE_IDS = Object.keys(packageRules).map((name) => `${PLUGIN_KEY}/${name}`)\n\n/**\n * `recommended` switches every **app** rule on at the level it was ruled at: an\n * error blocks, a warning is reported and never blocks. `strict` makes every app\n * rule an error. With every app rule an error, the two are the same today; they part\n * when the first warning-level rule arrives (UIG-25).\n *\n * `package` is the inward set (UIG-5), which only this package runs.\n */\nplugin.configs.recommended = {\n name: '@estiva-app/ui/recommended',\n plugins: { [PLUGIN_KEY]: plugin },\n rules: {\n [`${PLUGIN_KEY}/no-raw-element`]: 'error',\n [`${PLUGIN_KEY}/no-rebuilt-behaviour`]: 'error',\n },\n}\nplugin.configs.strict = {\n name: '@estiva-app/ui/strict',\n plugins: { [PLUGIN_KEY]: plugin },\n rules: Object.fromEntries(APP_RULE_IDS.map((id) => [id, 'error'])),\n}\nplugin.configs.package = {\n name: '@estiva-app/ui/package',\n plugins: { [PLUGIN_KEY]: plugin },\n rules: Object.fromEntries(PACKAGE_RULE_IDS.map((id) => [id, 'error'])),\n}\n\nexport default plugin\n\nexport interface GateRuleCount {\n errors: number\n warnings: number\n escapes: number\n}\n\nexport interface GateCount {\n /** Per rule id, including rules with nothing to report. */\n rules: Record<string, GateRuleCount>\n /**\n * Reports of these rules that an `eslint-disable` directive silenced. Not\n * an escape: a gate fails on any of these (docs/GATES.md \u00A716, S4).\n */\n disabled: { filePath: string; line: number; ruleId: string }[]\n}\n\n/**\n * Count what a lint of these rules found, for `.gates-count.json` (seam S3).\n *\n * Run the lint with `settings: { estiva: { reportEscapes: true } }` for the\n * escapes to be counted; without it they are silent and count 0. A marker\n * with no reason, or inside a directive, counts as an error of its rule.\n *\n * `seed` is which rules the count lists when they found nothing, and it is the\n * app rules unless a caller says otherwise: an app's count file must not gain\n * rows for the inward rules (UIG-5) that its gate does not run. This package's\n * own count script passes `PACKAGE_RULE_IDS`.\n */\nexport function countGates(results: ESLint.LintResult[], seed: readonly string[] = APP_RULE_IDS): GateCount {\n const counts: Record<string, GateRuleCount> = Object.fromEntries(seed.map((id) => [id, { errors: 0, warnings: 0, escapes: 0 }]))\n const disabled: GateCount['disabled'] = []\n const ours = (ruleId: string | null | undefined): ruleId is string => typeof ruleId === 'string' && ruleId.startsWith(`${PLUGIN_KEY}/`)\n for (const result of results) {\n for (const message of result.messages) {\n // Seeded or not: every rule of this plugin that reported is counted, so a\n // lint of a set the caller did not name is still counted in full.\n if (!ours(message.ruleId)) continue\n const count = (counts[message.ruleId] ??= { errors: 0, warnings: 0, escapes: 0 })\n if (message.messageId === 'escaped') count.escapes += 1\n else if (message.severity === 2) count.errors += 1\n else count.warnings += 1\n }\n for (const message of result.suppressedMessages ?? []) {\n if (ours(message.ruleId)) disabled.push({ filePath: result.filePath, line: message.line, ruleId: message.ruleId })\n }\n }\n return { rules: counts, disabled }\n}\n", "import { existsSync } from 'node:fs'\nimport type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\n/**\n * A component of the package with no page, and one with no story (UIG-5, P2 and\n * P3).\n *\n * A component nobody can read about is a component nobody uses correctly, and\n * one nobody can look at is one nobody reviews. Both were at zero when these\n * were switched on; they are here so the next component cannot arrive without\n * them.\n *\n * The pair is by file name, beside the component, which is how this package is\n * laid out: `Button.tsx`, `Button.mdx`, `Button.stories.tsx`, `Button.test.tsx`\n * side by side in `src/`.\n *\n * **The false positive to avoid** (UIG-1 confirmed it is real): `FieldLine` and\n * `MenuItem` have a page and a story but no `.tsx` of their own \u2014 they are\n * exported from a sibling. These rules read a component file and ask for its\n * page and its story, never the other way round, so a page without a component\n * is not this rule's business.\n *\n * A file that stays without one says why at its top: `// @estiva-escape: <why>`.\n */\ninterface ProgramNode {\n body: { loc?: Rule.Node['loc']; range?: [number, number] }[]\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n}\n\n/** A component file: a `.tsx` in the package's source that is not a story or a test. */\nfunction componentFile(filename: string): boolean {\n return filename.endsWith('.tsx') && !/\\.(stories|test)\\.tsx$/.test(filename)\n}\n\nfunction sibling(filename: string, extension: string): string {\n return filename.replace(/\\.tsx$/, extension)\n}\n\n/**\n * Report on the file's first statement, so the escape is a comment at the top of\n * the file \u2014 the only place a reason for a missing page could go.\n */\nfunction reportOnFile(context: Rule.RuleContext, program: ProgramNode, messageId: string, data: Record<string, string>): void {\n const anchor = program.body[0] ?? program\n if (anchor.loc && anchor.range && isEscaped(context, { loc: anchor.loc, range: anchor.range })) return\n context.report({ loc: anchor.loc ?? program.loc, messageId, data })\n}\n\nexport const componentHasAPage: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'Every component of the package has a usage page beside it' },\n schema: [],\n messages: {\n missing: '{{name}} has no page. Write {{page}} beside it \u2014 what it is, when, when not, how, and what it owns \u2014 or say why not at the top of the file.',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n Program(node) {\n const filename = context.filename\n if (!componentFile(filename)) return\n const page = sibling(filename, '.mdx')\n if (existsSync(page)) return\n const name = filename.split(/[\\\\/]/).pop() ?? filename\n reportOnFile(context, node as unknown as ProgramNode, 'missing', { name, page: page.split(/[\\\\/]/).pop() ?? page })\n },\n }\n },\n}\n\nexport const componentHasAStory: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'Every component of the package has a story beside it' },\n schema: [],\n messages: {\n missing: '{{name}} has no story. Write {{story}} beside it, so it can be seen and reviewed, or say why not at the top of the file.',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n Program(node) {\n const filename = context.filename\n if (!componentFile(filename)) return\n const story = sibling(filename, '.stories.tsx')\n if (existsSync(story)) return\n const name = filename.split(/[\\\\/]/).pop() ?? filename\n reportOnFile(context, node as unknown as ProgramNode, 'missing', { name, story: story.split(/[\\\\/]/).pop() ?? story })\n },\n }\n },\n}\n", "import type { AST, Rule } from 'eslint'\n\n/**\n * The escape marker (UIG-3, seam S4 in docs/GATES.md \u00A716): one written reason\n * why one element stays as it is.\n *\n * A line comment directly above the element,\n *\n * // @estiva-escape: <reason>\n *\n * or, where the element is a JSX child, the JSX comment on the line above,\n *\n * {/* @estiva-escape: <reason> *\\/}\n *\n * with at least ten characters of reason, spaces not counted.\n *\n * Not `eslint-disable`. A directive switches a rule off without saying it was\n * meant, and the count of escapes (`.gates-count.json`) could not see it. A\n * marker written inside a directive that switches one of these rules off is\n * reported for that reason.\n *\n * The token lint's older notes (`eslint-disable-next-line <rule> -- @estiva-escape:\n * <reason>`, Katerina's ruling A2 of 15 September) are not read here: they\n * escape the token lint's rules, which are not this plugin's, and they stay as\n * they are (Katerina, 15 September).\n */\nexport const ESCAPE_MARKER = '@estiva-escape'\n\n/** Characters of reason, spaces not counted. */\nexport const MIN_REASON = 10\n\n/** The key under ESLint's `settings` this plugin reads. */\nexport const SETTINGS_KEY = 'estiva'\n\n/**\n * Every rule spreads these into its `meta.messages`, because `isEscaped`\n * reports through the rule that called it.\n */\nexport const ESCAPE_MESSAGES = {\n escapeWithoutReason: `An escape needs its reason, at least ${MIN_REASON} characters: \\`// ${ESCAPE_MARKER}: <why this stays>\\`. An escape with no reason hides nothing.`,\n escapeInDirective: `Write the escape as its own comment on the line above: \\`// ${ESCAPE_MARKER}: <reason>\\`. Inside an eslint-disable comment it switches the rule off instead of recording why.`,\n escaped: 'Escaped: {{reason}}',\n} as const\n\nexport interface EstivaSettings {\n /**\n * Report every sanctioned escape as a message with the id `escaped`, so a\n * count can read them. Off in the lint anyone runs; on only in the count.\n */\n reportEscapes?: boolean\n}\n\ninterface Located {\n loc?: AST.SourceLocation | null\n range?: [number, number]\n}\n\nconst DIRECTIVE = /^\\s*eslint-disable(?:-next-line|-line)?(?=\\s|$)([^]*)$/\n\n/** The rules a directive names: what comes before its ` -- ` description. None means every rule. */\nfunction directiveRules(rest: string): string[] {\n const dashes = rest.search(/(?:^|\\s)--(?:\\s|$)/)\n return (dashes === -1 ? rest : rest.slice(0, dashes)).split(/[\\s,]+/).filter(Boolean)\n}\n\n/** Only spaces and a JSX expression's closing brace between the marker and the element. */\nconst BETWEEN = /^[\\s}]*$/\n\n/**\n * Whether the element at `node` carries a valid escape on the line above.\n *\n * Every rule of this plugin calls it before it reports. It reports, through\n * that rule, a marker with too short a reason and a marker inside an\n * eslint-disable directive for this rule; either way the element is not\n * escaped, so the rule reports it too.\n */\nexport function isEscaped(context: Rule.RuleContext, node: Located): boolean {\n const { sourceCode } = context\n if (!node.loc || !node.range) return false\n const line = node.loc.start.line\n const nodeStart = node.range[0]\n\n const comment = sourceCode\n .getAllComments()\n .find((c) => c.loc && c.range && c.loc.end.line === line - 1 && BETWEEN.test(sourceCode.text.slice(c.range[1], nodeStart)))\n if (!comment?.loc) return false\n\n const at = comment.value.indexOf(ESCAPE_MARKER)\n if (at === -1) return false\n\n const directive = DIRECTIVE.exec(comment.value)\n if (directive) {\n const rules = directiveRules(directive[1])\n // A directive for other rules (the token lint's notes) is not an escape of this one.\n if (rules.length > 0 && !rules.includes(context.id)) return false\n context.report({ loc: comment.loc, messageId: 'escapeInDirective' })\n return false\n }\n\n const reason = comment.value\n .slice(at + ESCAPE_MARKER.length)\n .replace(/^:/, '')\n .trim()\n if (reason.replace(/\\s/g, '').length < MIN_REASON) {\n context.report({ loc: comment.loc, messageId: 'escapeWithoutReason' })\n return false\n }\n\n const settings = context.settings[SETTINGS_KEY] as EstivaSettings | undefined\n if (settings?.reportEscapes) context.report({ loc: comment.loc, messageId: 'escaped', data: { reason } })\n return true\n}\n", "import type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\n/**\n * Behaviour Base UI already owns, written by hand inside the package (UIG-5,\n * P1 \u2014 the migration's standing rule D6 as a machine).\n *\n * This is the rule with the most to lose. A hand-made portal or a global\n * listener inside `DialogShell` is inherited by every caller in every app, and\n * nobody sees it from there.\n *\n * Two facts, both mechanical, both the signature of a floating part rebuilt by\n * hand:\n *\n * - **`createPortal`**, or importing it from `react-dom`. Base UI's parts carry\n * their own portal.\n * - **a listener on `window` or `document`** for the events a floating part\n * lives on \u2014 resize, scroll, an outside press, a key. Base UI's Positioner\n * follows the anchor and its Root closes on an outside press and on Escape.\n *\n * What it deliberately does not try to read: measuring arithmetic. `Popover`\n * hands Base UI a virtual anchor with a `getBoundingClientRect`, which is the\n * documented way to anchor to a rectangle rather than an element, and a rule\n * that guessed at arithmetic would report it. Anything that shape is caught by\n * a reader, not by this.\n *\n * A place that keeps one says why on the line above: `// @estiva-escape: <why>`.\n */\nconst FLOATING_EVENTS = new Set(['resize', 'scroll', 'mousedown', 'pointerdown', 'keydown', 'keyup', 'focusin', 'focusout', 'click'])\n\ninterface Node {\n type: string\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n}\n\ninterface Identifier extends Node {\n name: string\n}\n\ninterface ImportDeclaration extends Node {\n source: { value: unknown }\n specifiers: { type: string; imported?: { name?: string }; local?: { name?: string }; loc?: Rule.Node['loc']; range?: [number, number] }[]\n}\n\ninterface CallExpression extends Node {\n callee: { type: string; name?: string; object?: { type: string; name?: string }; property?: { type: string; name?: string } }\n arguments: { type: string; value?: unknown }[]\n parent?: { type: string; loc?: Rule.Node['loc']; range?: [number, number]; parent?: CallExpression['parent'] }\n}\n\n/**\n * The statement a call sits in, which is what an escape is written above:\n * `return createPortal(\u2026)` puts the call in the middle of its line, and a\n * comment above the line is above the statement.\n */\nfunction statement(node: { loc: NonNullable<Rule.Node['loc']>; range: [number, number]; parent?: CallExpression['parent'] }): {\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n} {\n for (let p = node.parent; p; p = p.parent) {\n if (!/(Statement|Declaration)$/.test(p.type)) continue\n if (p.loc && p.range) return { loc: p.loc, range: p.range }\n break\n }\n return { loc: node.loc, range: node.range }\n}\n\nexport const noHandRolledBehaviour: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'Overlay behaviour written by hand where Base UI has a part (D6)' },\n schema: [],\n messages: {\n portal:\n 'A portal written by hand. Base UI\\'s parts carry their own (Popover, Menu, Select, Tooltip, Dialog, Toast): use the part, or say why this one stays.',\n listener:\n 'A `{{event}}` listener on `{{target}}`. A floating part follows its anchor and closes on an outside press and on Escape by itself (Base UI Positioner and Root): use the part, or say why this one stays.',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n ImportDeclaration(node: Rule.Node) {\n const declaration = node as unknown as ImportDeclaration\n if (declaration.source.value !== 'react-dom') return\n for (const specifier of declaration.specifiers) {\n if (specifier.type !== 'ImportSpecifier' || specifier.imported?.name !== 'createPortal') continue\n // The escape goes above the import line, not above the name inside it.\n if (isEscaped(context, declaration)) return\n context.report({ loc: declaration.loc, messageId: 'portal' })\n return\n }\n },\n CallExpression(node: Rule.Node) {\n const call = node as unknown as CallExpression\n const { callee } = call\n\n if (callee.type === 'Identifier' && callee.name === 'createPortal') {\n if (isEscaped(context, statement(call))) return\n context.report({ loc: call.loc, messageId: 'portal' })\n return\n }\n\n if (callee.type !== 'MemberExpression' || callee.property?.name !== 'addEventListener') return\n const target = callee.object?.type === 'Identifier' ? callee.object.name : undefined\n if (target !== 'window' && target !== 'document') return\n const [first] = call.arguments\n const event = first?.type === 'Literal' && typeof first.value === 'string' ? first.value : undefined\n if (!event || !FLOATING_EVENTS.has(event)) return\n if (isEscaped(context, statement(call))) return\n context.report({ loc: call.loc, messageId: 'listener', data: { event, target } })\n },\n }\n },\n}\n", "import type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\ninterface JSXAttributeValue {\n type: string\n value?: unknown\n expression?: { type: string; value?: unknown; expressions?: unknown[]; quasis?: { value: { cooked: string | null } }[] }\n}\n\ninterface JSXAttribute {\n type: 'JSXAttribute'\n name: { type: string; name?: string }\n value: JSXAttributeValue | null\n}\n\ninterface JSXOpeningElement {\n name: { type: string; name?: string }\n attributes: (JSXAttribute | { type: 'JSXSpreadAttribute' })[]\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n}\n\n/**\n * What the package has instead of one raw element: the component the message\n * names, and where one element does more than one job, the others after it.\n * `null` is an element the package has no part for yet.\n */\nexport interface RawElementPart {\n use: string\n more?: string\n}\n\n/**\n * The mapping (UIG-7), derived from the package's exports: every interactive\n * element in the HTML standard's list (\"interactive content\"), plus `<dialog>`,\n * `<form>` and `<progress>`, which have a part here. docs/GATES.md prints it.\n *\n * Not here, on purpose: `<summary>` (it lives inside a `<details>`, which is\n * reported), `<option>`, `<optgroup>` and `<datalist>` (they live inside a\n * `<select>` or beside an input), `<fieldset>` and `<legend>` (a group's frame,\n * no control of its own), and `<img>` without `usemap`, `<hr>`, `<kbd>`, which\n * are not controls. An `<audio>` or `<video>` is a control only with `controls`.\n */\nexport const RAW_ELEMENT_PARTS: Record<string, RawElementPart | null> = {\n a: { use: 'Link', more: ' For a chip, `InlineChip`; for a whole card, `Card` with `href`.' },\n button: { use: 'Button' },\n textarea: { use: 'Textarea' },\n select: { use: 'Select' },\n dialog: { use: 'DialogShell', more: ' To ask yes or no, `ConfirmDialog`.' },\n label: { use: 'Field', more: ' For words beside a tick box, `Checkbox` with `label`.' },\n details: { use: 'CollapsibleSection' },\n progress: { use: 'ProgressBar' },\n form: { use: 'Form' },\n meter: null,\n iframe: null,\n embed: null,\n object: null,\n audio: null,\n video: null,\n img: null,\n}\n\n/**\n * An `<input>` is as many controls as its `type`. A type missing from here\n * (text, email, password, url, tel, number, or one the browser does not know)\n * is a text box.\n */\nexport const RAW_INPUT_PARTS: Record<string, RawElementPart | null> = {\n search: { use: 'SearchInput' },\n checkbox: { use: 'Checkbox' },\n file: { use: 'FilePicker' },\n submit: { use: 'Button' },\n button: { use: 'Button' },\n reset: { use: 'Button' },\n image: { use: 'Button' },\n radio: null,\n range: null,\n color: null,\n date: null,\n 'datetime-local': null,\n month: null,\n week: null,\n time: null,\n}\n\nconst TEXT_INPUT: RawElementPart = { use: 'TextInput' }\nconst ANY_INPUT: RawElementPart = {\n use: 'TextInput',\n more: ' For a search, `SearchInput`; for a tick box, `Checkbox`; to pick files, `FilePicker`.',\n}\n\nfunction attribute(element: JSXOpeningElement, name: string): JSXAttribute | undefined {\n return element.attributes.find((a): a is JSXAttribute => a.type === 'JSXAttribute' && a.name.name === name)\n}\n\n/** An attribute's value when the code spells it out, `undefined` when it is computed. */\nfunction literal(attr: JSXAttribute): string | boolean | undefined {\n const value = attr.value\n if (value === null) return true\n if (value.type === 'Literal') return typeof value.value === 'string' ? value.value : undefined\n if (value.type !== 'JSXExpressionContainer' || !value.expression) return undefined\n const expression = value.expression\n if (expression.type === 'Literal' && (typeof expression.value === 'string' || typeof expression.value === 'boolean')) return expression.value\n if (expression.type === 'TemplateLiteral' && expression.expressions?.length === 0) return expression.quasis?.[0]?.value.cooked ?? undefined\n return undefined\n}\n\n/** Present, and not written as `={false}`. */\nfunction isOn(element: JSXOpeningElement, name: string): boolean {\n const attr = attribute(element, name)\n return attr !== undefined && literal(attr) !== false\n}\n\ntype Found = { report: 'raw'; element: string; part: RawElementPart } | { report: 'noPart'; element: string } | null\n\n/** What this element is to the rule: a raw element with a part, one with none yet, or none of its business. */\nexport function classify(element: JSXOpeningElement): Found {\n if (element.name.type !== 'JSXIdentifier' || !element.name.name) return null\n const name = element.name.name\n\n if (name === 'input') {\n const attr = attribute(element, 'type')\n const type = attr ? literal(attr) : 'text'\n if (typeof type !== 'string') return { report: 'raw', element: '<input>', part: ANY_INPUT }\n const key = type.toLowerCase()\n // A hidden input draws nothing and takes no focus: it is data, not a control.\n if (key === 'hidden') return null\n if (!(key in RAW_INPUT_PARTS)) return { report: 'raw', element: '<input>', part: TEXT_INPUT }\n const part = RAW_INPUT_PARTS[key]\n const shown = `<input type=\"${key}\">`\n return part ? { report: 'raw', element: shown, part } : { report: 'noPart', element: shown }\n }\n\n if (!Object.hasOwn(RAW_ELEMENT_PARTS, name)) return null\n if ((name === 'audio' || name === 'video') && !isOn(element, 'controls')) return null\n if (name === 'img' && !attribute(element, 'usemap') && !attribute(element, 'useMap')) return null\n\n const part = RAW_ELEMENT_PARTS[name]\n return part ? { report: 'raw', element: `<${name}>`, part } : { report: 'noPart', element: `<${name}>` }\n}\n\n/**\n * A raw interactive element in an app, where the package has a part for it\n * (UIG-3 began it with `<button>`; UIG-7 made it every element). A control\n * the app writes itself has a look, a focus ring and keys that nobody else\n * keeps in step with \u2014 and it looks nearly right, which is why nobody catches\n * it by eye. Every message names the part to use.\n *\n * An element the package has no part for yet is refused too, with a message\n * that says so: the part is made in the package, not by hand in an app\n * (Katerina, 16 September).\n *\n * Only a JSX element written with a lowercase name. `<Button>`,\n * `<Foo.button>` and `createElement('button')` are not this rule's business.\n */\nexport const noRawElement: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'A raw interactive element where @estiva-app/ui has a part, or where it needs one' },\n schema: [],\n messages: {\n raw: 'Use `{{use}}` from @estiva-app/ui instead of a raw {{element}}.{{more}}',\n noPart: '@estiva-app/ui has no part for a raw {{element}} yet. Do not build one here: ask Katerina, and it gets made in @estiva-app/ui.',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n // ESLint's types know ESTree's nodes, not JSX's; the parser hands this one over.\n JSXOpeningElement(node: Rule.Node) {\n const element = node as unknown as JSXOpeningElement\n const found = classify(element)\n if (!found) return\n if (isEscaped(context, element)) return\n if (found.report === 'noPart') context.report({ loc: element.loc, messageId: 'noPart', data: { element: found.element } })\n else context.report({ loc: element.loc, messageId: 'raw', data: { element: found.element, use: found.part.use, more: found.part.more ?? '' } })\n },\n }\n },\n}\n", "import type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\n/**\n * Behaviour the package already owns, rebuilt by hand in an app (UIG-8, \"forbid\n * the reach\"). A hand-made dropdown, dialog, list or scrolling box does not look\n * wrong in review; it shows itself when somebody uses a keyboard, or has 81\n * folders. Every message names the part that already does it.\n *\n * What it reads is derived from the package source: which `@base-ui/react` part\n * each component imports, and so what it owns (`OWNED_BEHAVIOURS`, printed in\n * docs/GATES.md and reused by UIG-12's registry). `no-rebuilt-behaviour.test.ts`\n * holds the tables to the source, so a new part cannot be added without them.\n *\n * What it deliberately does not read, and why:\n *\n * - **A box that should scroll and does not.** A class rule finds the scrolling\n * box built by hand; it cannot find the one that was never built. Peek's\n * Folders column (fixed in peek 3dc663b) had no overflow class at all. Only a\n * check that opens the page can find that.\n * - **Enter and Escape** compared by hand. In a field or an editor they are\n * typing, and `Form` and `EditableText` own the ones that send or cancel.\n * Escape and every other key taken for the whole page are read, through the\n * listener.\n * - **aria-expanded, aria-pressed and the other states** written on a package\n * component: the component is already the package's (UIG-14's usage rules).\n * - **Measuring arithmetic**, for the reason UIG-5 gave: `Popover` takes a\n * rectangle to hang from, and a rule that guessed at arithmetic would report it.\n * - A listener on a variable that happens to hold the document.\n *\n * A place that keeps one says why on the line above: `// @estiva-escape: <why>`.\n * An escape above a statement, an element or an object property covers what the\n * rule found there.\n */\n\n/** The part a message names, and where one behaviour has more owners, the others after it. `null`: no part yet. */\nexport interface OwnerPart {\n use: string\n more?: string\n}\n\n/**\n * Every Base UI module, and the package component built on it. Read from the\n * package's imports (`src/*.tsx`); the test fails if a component imports a\n * module this does not name, or names a component that does not import it.\n * A module no component uses has no part yet, and neither do Base UI's utilities.\n */\nexport const BASE_UI_PARTS: Record<string, OwnerPart | null> = {\n 'alert-dialog': { use: 'DialogShell', more: ' To ask yes or no, `ConfirmDialog`.' },\n autocomplete: { use: 'CommandPalette' },\n avatar: { use: 'Avatar' },\n button: { use: 'Button', more: ' Icon only, `IconButton`.' },\n checkbox: { use: 'Checkbox' },\n collapsible: { use: 'CollapsibleSection' },\n combobox: { use: 'ChipInput' },\n dialog: { use: 'DialogShell', more: ' To search and act, `CommandPalette`.' },\n field: { use: 'Field' },\n fieldset: { use: 'Form' },\n form: { use: 'Form' },\n input: { use: 'TextInput', more: ' For a search, `SearchInput`.' },\n menu: { use: 'Menu' },\n popover: { use: 'Popover' },\n 'preview-card': { use: 'PreviewCard' },\n progress: { use: 'ProgressBar' },\n 'scroll-area': { use: 'ScrollArea' },\n select: { use: 'Select' },\n separator: { use: 'Divider' },\n tabs: { use: 'Tabs' },\n toast: { use: 'Toast' },\n toggle: { use: 'Reaction' },\n toolbar: { use: 'Toolbar' },\n tooltip: { use: 'Tooltip', more: ' Around a control, `WithTooltip`.' },\n accordion: null,\n 'checkbox-group': null,\n 'context-menu': null,\n drawer: null,\n menubar: null,\n meter: null,\n 'navigation-menu': null,\n 'number-field': null,\n 'otp-field': null,\n radio: null,\n 'radio-group': null,\n slider: null,\n switch: null,\n 'toggle-group': null,\n}\n\nconst LIST_PART: OwnerPart = {\n use: 'Select',\n more: ' To pick several, `ChipInput`; to search and act, `CommandPalette`; to tick several in a list, `Checkbox` with `row`.',\n}\n\nconst MESSAGE_PART: OwnerPart = { use: 'FieldLine', more: ' For a notice, `Banner`; for a message that comes and goes, `Toast`.' }\n\n/**\n * A role written by hand, and the part that sets it. Base UI's parts set most of\n * these; `tooltip` (Base UI's Tooltip sets none), `alert`, `status` and `link` are\n * the package's own. A role Base UI has a part for and this package has not got\n * yet is `null`. Not here, on purpose: `img`, `group`, `presentation`, `none`,\n * `region` and every landmark \u2014 they describe, they do not behave.\n */\nexport const ROLE_PARTS: Record<string, { thing: string; part: OwnerPart | null }> = {\n dialog: { thing: 'dialog', part: { use: 'DialogShell', more: ' To ask yes or no, `ConfirmDialog`.' } },\n alertdialog: { thing: 'dialog', part: { use: 'ConfirmDialog' } },\n menu: { thing: 'menu', part: { use: 'Menu' } },\n menuitem: { thing: 'menu', part: { use: 'Menu' } },\n menuitemcheckbox: { thing: 'menu', part: { use: 'Menu' } },\n menuitemradio: { thing: 'menu', part: { use: 'Menu' } },\n listbox: { thing: 'list', part: LIST_PART },\n option: { thing: 'list', part: LIST_PART },\n combobox: { thing: 'list', part: LIST_PART },\n tablist: { thing: 'set of tabs', part: { use: 'Tabs' } },\n tab: { thing: 'set of tabs', part: { use: 'Tabs' } },\n tabpanel: { thing: 'set of tabs', part: { use: 'Tabs' } },\n toolbar: { thing: 'toolbar', part: { use: 'Toolbar' } },\n progressbar: { thing: 'progress bar', part: { use: 'ProgressBar' } },\n checkbox: { thing: 'tick box', part: { use: 'Checkbox' } },\n separator: { thing: 'divider', part: { use: 'Divider' } },\n button: { thing: 'button', part: { use: 'Button', more: ' Icon only, `IconButton`.' } },\n link: { thing: 'link', part: { use: 'Link' } },\n tooltip: { thing: 'tooltip', part: { use: 'Tooltip', more: ' Around a control, `WithTooltip`.' } },\n alert: { thing: 'message', part: MESSAGE_PART },\n status: { thing: 'message', part: MESSAGE_PART },\n menubar: { thing: 'menu bar', part: null },\n switch: { thing: 'switch', part: null },\n radio: { thing: 'radio button', part: null },\n radiogroup: { thing: 'radio group', part: null },\n slider: { thing: 'slider', part: null },\n spinbutton: { thing: 'number field', part: null },\n meter: { thing: 'meter', part: null },\n}\n\n/** The keys a list, a menu, tabs and a toolbar move with. Enter and Escape are not here (see above). */\nexport const WALKING_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown']\n\n/**\n * A listener on the whole page, by what it is for. The events Base UI's own\n * parts listen to on the document (useDismiss, FloatingFocusManager, the\n * Positioner's autoUpdate): a floating part closes, holds focus and follows its\n * anchor with these. `focus`, `blur`, `visibilitychange` and `storage` are not\n * here: an app refreshing when the window comes back is not a floating part.\n */\nexport const PAGE_EVENTS: Record<string, 'press' | 'key' | 'focus' | 'follow'> = {\n mousedown: 'press',\n pointerdown: 'press',\n click: 'press',\n touchstart: 'press',\n keydown: 'key',\n keyup: 'key',\n keypress: 'key',\n focusin: 'focus',\n focusout: 'focus',\n scroll: 'follow',\n resize: 'follow',\n}\n\n/** Elements that are reachable with Tab by themselves; `tabIndex` on these is not a Tab stop made by hand. */\nexport const INTERACTIVE_ELEMENTS = new Set(['a', 'button', 'input', 'select', 'textarea', 'summary', 'iframe', 'embed', 'object', 'audio', 'video'])\n\n/** `overflow-auto`, `overflow-y-scroll`\u2026: a box that scrolls with the browser's scrollbar. */\nexport const SCROLL_CLASS = /^overflow(?:-[xy])?-(?:auto|scroll)$/\n\n/** One row of the enumeration (UIG-8's acceptance; the shape UIG-12's registry reads). */\nexport interface OwnedBehaviour {\n id: string\n /** What a person notices the part doing. */\n behaviour: string\n /** The Base UI parts that do it, read from their source (1.8.0). */\n baseUi: string[]\n /** The package components an error names. */\n owners: string[]\n /** What the rule reads in an app to find it rebuilt. */\n reads: string\n}\n\nconst uses = (parts: (OwnerPart | null)[]) => [\n ...new Set(parts.flatMap((p) => (p ? [p.use, ...[...(p.more ?? '').matchAll(/`([A-Z]\\w+)`/g)].map((m) => m[1])] : []))),\n]\n\nexport const OWNED_BEHAVIOURS: OwnedBehaviour[] = [\n {\n id: 'base-ui',\n behaviour: 'Is built on Base UI',\n baseUi: Object.keys(BASE_UI_PARTS),\n owners: uses(Object.values(BASE_UI_PARTS)),\n reads: 'an import from @base-ui/react (and the old @base-ui-components spelling)',\n },\n {\n id: 'portal',\n behaviour: 'Floats on top of the page',\n baseUi: ['Dialog', 'AlertDialog', 'Popover', 'Menu', 'Select', 'Combobox', 'Autocomplete', 'Tooltip', 'PreviewCard', 'Toast'],\n owners: ['DialogShell', 'ConfirmDialog', 'CommandPalette', 'Popover', 'Menu', 'Select', 'ChipInput', 'Tooltip', 'PreviewCard', 'Toast'],\n reads: 'createPortal, called, or imported and never called',\n },\n {\n id: 'press-outside',\n behaviour: 'Closes on a press outside',\n baseUi: ['Dialog', 'AlertDialog', 'Popover', 'Menu', 'Select', 'Combobox', 'Tooltip', 'PreviewCard'],\n owners: ['DialogShell', 'Popover', 'Menu', 'Select', 'PreviewCard'],\n reads: 'a mousedown, pointerdown, click or touchstart listener on window or document',\n },\n {\n id: 'page-keys',\n behaviour: 'Closes on Escape, and takes its keys, by itself',\n baseUi: ['Dialog', 'AlertDialog', 'Popover', 'Menu', 'Select', 'Combobox', 'Tooltip', 'PreviewCard', 'Toast'],\n owners: ['DialogShell', 'Popover', 'Menu', 'Select', 'Tabs', 'Toolbar'],\n reads: 'a keydown, keyup or keypress listener on window or document',\n },\n {\n id: 'focus',\n behaviour: 'Holds focus inside while open, and gives it back',\n baseUi: ['Dialog', 'AlertDialog', 'Popover', 'Menu', 'Select', 'Combobox'],\n owners: ['DialogShell', 'CommandPalette'],\n reads: 'a focusin or focusout listener on window or document; the Tab key compared by hand',\n },\n {\n id: 'scroll-lock',\n behaviour: 'Stops the page behind it scrolling',\n baseUi: ['Dialog', 'AlertDialog'],\n owners: ['DialogShell'],\n reads: 'overflow written into the style of document.body or document.documentElement',\n },\n {\n id: 'follow',\n behaviour: 'Stays attached to its anchor on scroll and resize',\n baseUi: ['Popover', 'Menu', 'Select', 'Combobox', 'Tooltip', 'PreviewCard', 'Toast'],\n owners: ['Popover', 'Menu', 'Select', 'Tooltip', 'PreviewCard'],\n reads: 'a scroll or resize listener on window or document',\n },\n {\n id: 'walking',\n behaviour: 'Moves through its items with the arrow keys',\n baseUi: ['Menu', 'Select', 'Combobox', 'Autocomplete', 'Tabs', 'Toolbar'],\n owners: ['Menu', 'Select', 'ChipInput', 'CommandPalette', 'Tabs', 'Toolbar'],\n reads: `a key compared by hand with ${WALKING_KEYS.join(', ')}`,\n },\n {\n id: 'role',\n behaviour: 'Says what it is to assistive technology',\n baseUi: ['Dialog', 'AlertDialog', 'Menu', 'Select', 'Combobox', 'Tabs', 'Toolbar', 'Progress', 'Checkbox', 'Separator', 'Button'],\n owners: uses(Object.values(ROLE_PARTS).map((r) => r.part)),\n reads: `role written by hand: ${Object.keys(ROLE_PARTS).join(', ')}`,\n },\n {\n id: 'tab-stop',\n behaviour: 'Is reachable with Tab',\n baseUi: ['Button', 'Toggle', 'Checkbox', 'Tabs', 'Toolbar', 'ScrollArea'],\n owners: ['Button', 'IconButton', 'Link'],\n reads: 'tabIndex that can be 0 or more, on an element that is not a control',\n },\n {\n id: 'scroll',\n behaviour: 'Scrolls with our scrollbar',\n baseUi: ['ScrollArea'],\n owners: ['ScrollArea'],\n reads: 'overflow-auto or overflow-scroll (x or y, behind any variant) in a string, or overflow auto or scroll in a style',\n },\n]\n\ninterface Node {\n type: string\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n parent?: Node\n [key: string]: unknown\n}\n\nconst PAGE_TARGETS = new Set(['window', 'document', 'globalThis', 'self', 'document.body', 'document.documentElement', 'window.document'])\n\n/** Where an escape for this node is written: its object property, its element, its array item, or its statement. */\nfunction anchorOf(node: Node): Node {\n if (node.type === 'FunctionDeclaration') {\n return node.parent && /^Export/.test(node.parent.type) ? node.parent : node\n }\n let child = node\n for (let p = node.parent; p; child = p, p = p.parent) {\n if (p.type === 'JSXOpeningElement' || p.type === 'Property' || p.type === 'MethodDefinition' || p.type === 'PropertyDefinition') return p\n if (p.type === 'ArrayExpression') return child\n if (p.type !== 'BlockStatement' && /(Statement|Declaration)$/.test(p.type)) return p\n }\n return node\n}\n\nfunction enclosingFunction(node: Node): Node | undefined {\n for (let p = node.parent; p; p = p.parent) {\n if (p.type === 'FunctionDeclaration' || p.type === 'FunctionExpression' || p.type === 'ArrowFunctionExpression') return p\n }\n return undefined\n}\n\nfunction stringOf(node: Node | undefined | null): string | undefined {\n if (!node) return undefined\n if (node.type === 'Literal' && typeof node.value === 'string') return node.value\n const quasis = node.quasis as { value: { cooked: string | null } }[] | undefined\n const expressions = node.expressions as unknown[] | undefined\n if (node.type === 'TemplateLiteral' && expressions?.length === 0) return quasis?.[0]?.value.cooked ?? undefined\n return undefined\n}\n\n/** Every string a JSX attribute's value can be: `\"x\"`, `{'x'}`, `{a ? 'x' : 'y'}`, `{a && 'x'}`. */\nfunction stringsOf(node: Node | undefined | null): string[] {\n if (!node) return []\n if (node.type === 'JSXExpressionContainer') return stringsOf(node.expression as Node)\n const s = stringOf(node)\n if (s !== undefined) return [s]\n if (node.type === 'ConditionalExpression') return [...stringsOf(node.consequent as Node), ...stringsOf(node.alternate as Node)]\n if (node.type === 'LogicalExpression') return [...stringsOf(node.left as Node), ...stringsOf(node.right as Node)]\n return []\n}\n\n/** Whether a `tabIndex` value can be 0 or more, as far as the code spells it out. */\nfunction canBeTabStop(node: Node | undefined | null): boolean {\n if (!node) return false\n if (node.type === 'JSXExpressionContainer') return canBeTabStop(node.expression as Node)\n if (node.type === 'Literal') {\n if (typeof node.value === 'number') return node.value >= 0\n if (typeof node.value === 'string') return /^\\s*\\d+\\s*$/.test(node.value)\n return false\n }\n if (node.type === 'ConditionalExpression') return canBeTabStop(node.consequent as Node) || canBeTabStop(node.alternate as Node)\n if (node.type === 'LogicalExpression') return canBeTabStop(node.left as Node) || canBeTabStop(node.right as Node)\n if (node.type === 'TSAsExpression' || node.type === 'TSNonNullExpression') return canBeTabStop(node.expression as Node)\n return false\n}\n\n/** A class token's utility, past its variants: `[&_pre]:overflow-x-auto` \u2192 `overflow-x-auto`. */\nexport function utilityOf(token: string): string {\n let depth = 0\n let last = -1\n for (let i = 0; i < token.length; i++) {\n const c = token[i]\n if (c === '[') depth++\n else if (c === ']') depth--\n else if (c === ':' && depth === 0) last = i\n }\n return token.slice(last + 1).replace(/^!/, '').replace(/!$/, '')\n}\n\n/** The Base UI module an import names, or `undefined` when it is not Base UI. `''` is the package root. */\nexport function baseUiModule(source: string): string | undefined {\n const m = /^@base-ui(?:-components)?\\/([^/]+)(?:\\/(.+))?$/.exec(source)\n if (!m) return undefined\n const [, pkg, sub] = m\n return pkg === 'react' ? (sub ?? '') : `${pkg}${sub ? `/${sub}` : ''}`\n}\n\nfunction isKeySubject(node: Node | undefined): boolean {\n if (!node) return false\n if (node.type === 'Identifier') return node.name === 'key' || node.name === 'code'\n if (node.type !== 'MemberExpression' || node.computed) return false\n const property = node.property as Node\n return property.type === 'Identifier' && (property.name === 'key' || property.name === 'code')\n}\n\nfunction pageTarget(node: Node, text: string): boolean {\n return (node.type === 'Identifier' || node.type === 'MemberExpression') && PAGE_TARGETS.has(text)\n}\n\nexport const noRebuiltBehaviour: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'Behaviour @estiva-app/ui already owns, rebuilt by hand in an app' },\n schema: [],\n messages: {\n baseUi: 'Only @estiva-app/ui imports Base UI (`{{module}}`). Use `{{use}}` from @estiva-app/ui.{{more}}',\n baseUiNoPart:\n 'Only @estiva-app/ui imports Base UI (`{{module}}`), and it has no part built on it yet. Do not build one here: ask Katerina, and it gets made in @estiva-app/ui.',\n portal:\n 'A layer put on top of the page by hand (`createPortal`). Every floating part of @estiva-app/ui carries its own: `DialogShell` for a dialog, `Popover` for a panel, `Menu`, `Select`, `Tooltip`, `PreviewCard`, `Toast`.',\n press:\n 'A `{{event}}` listener on `{{target}}`: closing on a press outside, by hand. `Popover`, `Menu`, `Select`, `DialogShell` and `PreviewCard` from @estiva-app/ui close themselves.',\n key: 'A `{{event}}` listener on `{{target}}`: keys taken for the whole page, by hand. `DialogShell`, `Popover`, `Menu` and `Select` from @estiva-app/ui close on Escape themselves; `Menu`, `Select`, `Tabs` and `Toolbar` move with the arrow keys.',\n focus:\n 'A `{{event}}` listener on `{{target}}`: focus held by hand. `DialogShell` and `CommandPalette` from @estiva-app/ui keep focus inside and give it back.',\n follow:\n 'A `{{event}}` listener on `{{target}}`: following an anchor by hand. `Popover`, `Menu`, `Select`, `Tooltip` and `PreviewCard` from @estiva-app/ui stay attached to theirs.',\n scrollLock: \"The page's scroll locked by hand. `DialogShell` from @estiva-app/ui stops the page scrolling while it is open, and gives it back.\",\n walking:\n 'Arrow keys handled by hand (`{{key}}`). `Menu`, `Select`, `ChipInput`, `CommandPalette`, `Tabs` and `Toolbar` from @estiva-app/ui move through their items themselves.',\n tabKey: 'The Tab key handled by hand. `DialogShell` and `CommandPalette` from @estiva-app/ui keep focus inside themselves.',\n role: 'A hand-written `role=\"{{role}}\"` is a hand-made {{thing}}. Use `{{use}}` from @estiva-app/ui.{{more}}',\n roleNoPart:\n 'A hand-written `role=\"{{role}}\"` is a hand-made {{thing}}, and @estiva-app/ui has no part for one yet. Do not build one here: ask Katerina, and it gets made in @estiva-app/ui.',\n tabStop:\n '`tabIndex={{value}}` makes a `<{{element}}>` a Tab stop by hand. Use `Button`, `IconButton` or `Link` from @estiva-app/ui, which are reachable already.',\n scrollClass: \"`{{token}}` scrolls with the browser's scrollbar. Use `ScrollArea` from @estiva-app/ui, which draws ours.\",\n scrollStyle: \"`{{style}}` scrolls with the browser's scrollbar. Use `ScrollArea` from @estiva-app/ui, which draws ours.\",\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n const { sourceCode } = context\n const text = (node: Node) => sourceCode.getText(node as unknown as Rule.Node)\n\n // One escape covers what the rule found at its anchor, and is counted once.\n const escapes = new Map<Node, boolean>()\n const escaped = (anchor: Node) => {\n if (!escapes.has(anchor)) escapes.set(anchor, isEscaped(context, anchor))\n return escapes.get(anchor) === true\n }\n const seen = new Set<string>()\n const report = (anchor: Node, at: Node, messageId: string, data: Record<string, string> = {}, once = `${at.range[0]}`) => {\n const key = `${anchor.range[0]}|${messageId}|${once}`\n if (seen.has(key)) return\n seen.add(key)\n if (escaped(anchor)) return\n context.report({ loc: at.loc, messageId, data })\n }\n\n const importSource = (node: Node, source: Node | undefined | null) => {\n const value = stringOf(source)\n if (value === undefined) return\n const module = baseUiModule(value)\n if (module === undefined) return\n const part = Object.hasOwn(BASE_UI_PARTS, module) ? BASE_UI_PARTS[module] : null\n const anchor = anchorOf(node)\n if (part) report(anchor, node, 'baseUi', { module: value, use: part.use, more: part.more ?? '' })\n else report(anchor, node, 'baseUiNoPart', { module: value })\n }\n\n const keyCompared = (node: Node, key: string | undefined) => {\n if (key === undefined) return\n const fn = enclosingFunction(node)\n const anchor = anchorOf(fn ?? node)\n if (WALKING_KEYS.includes(key)) report(anchor, node, 'walking', { key }, `walking@${fn?.range[0] ?? node.range[0]}`)\n else if (key === 'Tab') report(anchor, node, 'tabKey', {}, `tab@${fn?.range[0] ?? node.range[0]}`)\n }\n\n const classTokens = (node: Node, value: string) => {\n for (const token of value.split(/\\s+/)) {\n if (token && SCROLL_CLASS.test(utilityOf(token))) report(anchorOf(node), node, 'scrollClass', { token }, `class@${token}`)\n }\n }\n\n // `createPortal` imported: reported where it is called, or at the import when it never is.\n const portalImports = new Map<string, Node>()\n let portalCalled = false\n\n return {\n ImportDeclaration(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n importSource(node, node.source as Node)\n if (stringOf(node.source as Node) !== 'react-dom') return\n for (const specifier of node.specifiers as Node[]) {\n const imported = specifier.imported as Node | undefined\n if (specifier.type === 'ImportSpecifier' && imported?.name === 'createPortal') portalImports.set((specifier.local as Node).name as string, node)\n }\n },\n ExportNamedDeclaration(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n if (node.source) importSource(node, node.source as Node)\n },\n ExportAllDeclaration(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n importSource(node, node.source as Node)\n },\n ImportExpression(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n importSource(node, node.source as Node)\n },\n\n CallExpression(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n const callee = node.callee as Node\n const args = node.arguments as Node[]\n\n if (callee.type === 'Identifier' && callee.name === 'require') importSource(node, args[0])\n\n if (callee.type === 'Identifier' && portalImports.has(callee.name as string)) {\n portalCalled = true\n report(anchorOf(node), node, 'portal')\n }\n if (callee.type !== 'MemberExpression') return\n const object = callee.object as Node\n const property = callee.property as Node\n if (property.type !== 'Identifier') return\n\n if (property.name === 'createPortal') {\n report(anchorOf(node), node, 'portal')\n return\n }\n\n if (property.name === 'addEventListener') {\n const target = text(object)\n if (!pageTarget(object, target)) return\n const event = stringOf(args[0])\n const group = event === undefined ? undefined : PAGE_EVENTS[event]\n if (!event || !group) return\n report(anchorOf(node), node, group, { event, target })\n return\n }\n\n // ['ArrowUp', 'ArrowDown'].includes(event.key)\n if (property.name === 'includes' && object.type === 'ArrayExpression' && isKeySubject(args[0])) {\n for (const element of object.elements as Node[]) keyCompared(node, stringOf(element))\n }\n },\n\n AssignmentExpression(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n const left = node.left as Node\n if (left.type !== 'MemberExpression') return\n const property = left.property as Node\n const object = left.object as Node\n const handler = property.type === 'Identifier' ? /^on([a-z]+)$/.exec(property.name as string) : null\n if (handler && pageTarget(object, text(object)) && PAGE_EVENTS[handler[1]]) {\n report(anchorOf(node), node, PAGE_EVENTS[handler[1]], { event: handler[1], target: text(object) })\n return\n }\n if (/^document\\.(?:body|documentElement)\\.style\\.overflow[XY]?$/.test(text(left))) report(anchorOf(node), node, 'scrollLock')\n },\n\n BinaryExpression(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n if (!['===', '==', '!==', '!='].includes(node.operator as string)) return\n const left = node.left as Node\n const right = node.right as Node\n if (isKeySubject(left)) keyCompared(node, stringOf(right))\n else if (isKeySubject(right)) keyCompared(node, stringOf(left))\n },\n\n SwitchCase(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n const statement = node.parent as Node\n if (node.test && isKeySubject(statement.discriminant as Node)) keyCompared(node, stringOf(node.test as Node))\n },\n\n JSXAttribute(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n const nameNode = node.name as Node\n if (nameNode.type !== 'JSXIdentifier') return\n const name = nameNode.name as string\n const element = node.parent as Node\n const value = node.value as Node | null\n\n if (name === 'role') {\n for (const role of stringsOf(value).flatMap((v) => v.trim().split(/\\s+/))) {\n if (!Object.hasOwn(ROLE_PARTS, role)) continue\n const { thing, part } = ROLE_PARTS[role]\n if (part) report(element, node, 'role', { role, thing, use: part.use, more: part.more ?? '' })\n else report(element, node, 'roleNoPart', { role, thing })\n return\n }\n return\n }\n\n if (name === 'tabIndex' || name === 'tabindex') {\n const tag = element.name as Node\n if (tag.type !== 'JSXIdentifier' || !/^[a-z]/.test(tag.name as string) || INTERACTIVE_ELEMENTS.has(tag.name as string)) return\n const attributes = element.attributes as Node[]\n const editable = attributes.some((a) => a.type === 'JSXAttribute' && /^contenteditable$/i.test(((a.name as Node).name as string) ?? ''))\n if (editable || !canBeTabStop(value)) return\n report(element, node, 'tabStop', { value: value ? text(value).replace(/^\\{|\\}$/g, '') : '', element: tag.name as string })\n return\n }\n\n if (name === 'style' && value?.type === 'JSXExpressionContainer') {\n const object = value.expression as Node\n if (object.type !== 'ObjectExpression') return\n for (const property of object.properties as Node[]) {\n if (property.type !== 'Property') continue\n const key = property.key as Node\n const keyName = key.type === 'Identifier' ? (key.name as string) : stringOf(key)\n if (!keyName || !/^overflow[XY]?$/.test(keyName)) continue\n const setting = stringOf(property.value as Node)\n if (setting === 'auto' || setting === 'scroll') report(element, property, 'scrollStyle', { style: `${keyName}: '${setting}'` }, `style@${keyName}`)\n }\n }\n },\n\n Literal(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n if (typeof node.value !== 'string') return\n const parent = node.parent\n if (parent && /^(Import|Export)/.test(parent.type)) return\n if (parent?.type === 'TSLiteralType' || parent?.type === 'TSExternalModuleReference') return\n classTokens(node, node.value)\n },\n\n TemplateElement(ruleNode: Rule.Node) {\n const node = ruleNode as unknown as Node\n const cooked = (node.value as { cooked: string | null }).cooked\n if (cooked && node.parent) classTokens(node.parent, cooked)\n },\n\n 'Program:exit'() {\n if (portalCalled) return\n for (const declaration of new Set(portalImports.values())) report(anchorOf(declaration), declaration, 'portal')\n },\n }\n },\n}\n", "import type { Rule } from 'eslint'\nimport { ESCAPE_MESSAGES, isEscaped } from './escape'\n\ninterface JSXOpeningElement {\n type: string\n name: { type: string; name?: string }\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n parent?: Parent\n}\n\ninterface Parent {\n type: string\n name?: { name?: string }\n openingElement?: unknown\n parent?: Parent\n}\n\n/**\n * The elements this package writes by hand. The set the count used (UIG-1,\n * docs/GATES.md \u00A75 and \u00A77): every element that carries behaviour or a role of\n * its own. A `div` or a `span` is layout and is nobody's business here.\n */\nexport const RAW_ELEMENTS = new Set(['a', 'button', 'input', 'textarea', 'select', 'dialog', 'form', 'label'])\n\n/**\n * A raw element nested inside another element, in the package itself (UIG-5,\n * the inward tracer).\n *\n * The package is allowed to write raw elements \u2014 that is what a primitive is.\n * Two shapes are right, and both are facts about the code rather than a\n * judgement:\n *\n * - **the component's own outermost element.** `NavItem` is an `<a>`, `Link` is\n * an `<a>`, a `MenuItem` outside a `Menu` is a `<button>`. A primitive owning\n * its element is the whole point of it.\n * - **handed to a Base UI `render` prop.** `render={<button type=\"button\" />}`\n * tells a Base UI part which element to be; the behaviour stays Base UI's.\n *\n * Anything else is an element buried inside a bigger component, where its look\n * and its behaviour are kept in step by nobody \u2014 the package's own version of\n * the rule the apps get from UIG-3 and UIG-7.\n *\n * It keeps its reason on the line above if it stays: `// @estiva-escape: <why>`.\n */\nexport const rawElementOutsideAWrapper: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'A raw element nested inside a component, rather than the component itself' },\n schema: [],\n messages: {\n nested:\n 'A raw <{{name}}> inside another element. In this package a raw element is either the component\\'s own outermost element or handed to a Base UI `render` prop; one buried inside belongs in a component of its own (Link, Button, MenuItem...).',\n ...ESCAPE_MESSAGES,\n },\n },\n create(context) {\n return {\n // ESLint's types know ESTree's nodes, not JSX's; the parser hands this one over.\n JSXOpeningElement(node: Rule.Node) {\n const element = node as unknown as JSXOpeningElement\n const name = element.name.name\n if (element.name.type !== 'JSXIdentifier' || !name || !RAW_ELEMENTS.has(name)) return\n\n // An opening tag's own parent is its JSXElement: that is this element,\n // not an element around it.\n let parent = element.parent\n if (parent?.type === 'JSXElement' && parent.openingElement === (element as unknown)) parent = parent.parent\n\n // Whichever comes first going outward decides: an element around it\n // means it is nested; a `render` prop means Base UI draws it.\n for (let p = parent; p; p = p.parent) {\n if (p.type === 'JSXAttribute') {\n if (p.name?.name === 'render') return\n continue\n }\n if (p.type !== 'JSXElement' && p.type !== 'JSXFragment') continue\n if (isEscaped(context, element)) return\n context.report({ loc: element.loc, messageId: 'nested', data: { name } })\n return\n }\n },\n }\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["rules", "part", "statement"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
/**
|
|
3
|
+
* Behaviour the package already owns, rebuilt by hand in an app (UIG-8, "forbid
|
|
4
|
+
* the reach"). A hand-made dropdown, dialog, list or scrolling box does not look
|
|
5
|
+
* wrong in review; it shows itself when somebody uses a keyboard, or has 81
|
|
6
|
+
* folders. Every message names the part that already does it.
|
|
7
|
+
*
|
|
8
|
+
* What it reads is derived from the package source: which `@base-ui/react` part
|
|
9
|
+
* each component imports, and so what it owns (`OWNED_BEHAVIOURS`, printed in
|
|
10
|
+
* docs/GATES.md and reused by UIG-12's registry). `no-rebuilt-behaviour.test.ts`
|
|
11
|
+
* holds the tables to the source, so a new part cannot be added without them.
|
|
12
|
+
*
|
|
13
|
+
* What it deliberately does not read, and why:
|
|
14
|
+
*
|
|
15
|
+
* - **A box that should scroll and does not.** A class rule finds the scrolling
|
|
16
|
+
* box built by hand; it cannot find the one that was never built. Peek's
|
|
17
|
+
* Folders column (fixed in peek 3dc663b) had no overflow class at all. Only a
|
|
18
|
+
* check that opens the page can find that.
|
|
19
|
+
* - **Enter and Escape** compared by hand. In a field or an editor they are
|
|
20
|
+
* typing, and `Form` and `EditableText` own the ones that send or cancel.
|
|
21
|
+
* Escape and every other key taken for the whole page are read, through the
|
|
22
|
+
* listener.
|
|
23
|
+
* - **aria-expanded, aria-pressed and the other states** written on a package
|
|
24
|
+
* component: the component is already the package's (UIG-14's usage rules).
|
|
25
|
+
* - **Measuring arithmetic**, for the reason UIG-5 gave: `Popover` takes a
|
|
26
|
+
* rectangle to hang from, and a rule that guessed at arithmetic would report it.
|
|
27
|
+
* - A listener on a variable that happens to hold the document.
|
|
28
|
+
*
|
|
29
|
+
* A place that keeps one says why on the line above: `// @estiva-escape: <why>`.
|
|
30
|
+
* An escape above a statement, an element or an object property covers what the
|
|
31
|
+
* rule found there.
|
|
32
|
+
*/
|
|
33
|
+
/** The part a message names, and where one behaviour has more owners, the others after it. `null`: no part yet. */
|
|
34
|
+
export interface OwnerPart {
|
|
35
|
+
use: string;
|
|
36
|
+
more?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Every Base UI module, and the package component built on it. Read from the
|
|
40
|
+
* package's imports (`src/*.tsx`); the test fails if a component imports a
|
|
41
|
+
* module this does not name, or names a component that does not import it.
|
|
42
|
+
* A module no component uses has no part yet, and neither do Base UI's utilities.
|
|
43
|
+
*/
|
|
44
|
+
export declare const BASE_UI_PARTS: Record<string, OwnerPart | null>;
|
|
45
|
+
/**
|
|
46
|
+
* A role written by hand, and the part that sets it. Base UI's parts set most of
|
|
47
|
+
* these; `tooltip` (Base UI's Tooltip sets none), `alert`, `status` and `link` are
|
|
48
|
+
* the package's own. A role Base UI has a part for and this package has not got
|
|
49
|
+
* yet is `null`. Not here, on purpose: `img`, `group`, `presentation`, `none`,
|
|
50
|
+
* `region` and every landmark — they describe, they do not behave.
|
|
51
|
+
*/
|
|
52
|
+
export declare const ROLE_PARTS: Record<string, {
|
|
53
|
+
thing: string;
|
|
54
|
+
part: OwnerPart | null;
|
|
55
|
+
}>;
|
|
56
|
+
/** The keys a list, a menu, tabs and a toolbar move with. Enter and Escape are not here (see above). */
|
|
57
|
+
export declare const WALKING_KEYS: string[];
|
|
58
|
+
/**
|
|
59
|
+
* A listener on the whole page, by what it is for. The events Base UI's own
|
|
60
|
+
* parts listen to on the document (useDismiss, FloatingFocusManager, the
|
|
61
|
+
* Positioner's autoUpdate): a floating part closes, holds focus and follows its
|
|
62
|
+
* anchor with these. `focus`, `blur`, `visibilitychange` and `storage` are not
|
|
63
|
+
* here: an app refreshing when the window comes back is not a floating part.
|
|
64
|
+
*/
|
|
65
|
+
export declare const PAGE_EVENTS: Record<string, 'press' | 'key' | 'focus' | 'follow'>;
|
|
66
|
+
/** Elements that are reachable with Tab by themselves; `tabIndex` on these is not a Tab stop made by hand. */
|
|
67
|
+
export declare const INTERACTIVE_ELEMENTS: Set<string>;
|
|
68
|
+
/** `overflow-auto`, `overflow-y-scroll`…: a box that scrolls with the browser's scrollbar. */
|
|
69
|
+
export declare const SCROLL_CLASS: RegExp;
|
|
70
|
+
/** One row of the enumeration (UIG-8's acceptance; the shape UIG-12's registry reads). */
|
|
71
|
+
export interface OwnedBehaviour {
|
|
72
|
+
id: string;
|
|
73
|
+
/** What a person notices the part doing. */
|
|
74
|
+
behaviour: string;
|
|
75
|
+
/** The Base UI parts that do it, read from their source (1.8.0). */
|
|
76
|
+
baseUi: string[];
|
|
77
|
+
/** The package components an error names. */
|
|
78
|
+
owners: string[];
|
|
79
|
+
/** What the rule reads in an app to find it rebuilt. */
|
|
80
|
+
reads: string;
|
|
81
|
+
}
|
|
82
|
+
export declare const OWNED_BEHAVIOURS: OwnedBehaviour[];
|
|
83
|
+
/** A class token's utility, past its variants: `[&_pre]:overflow-x-auto` → `overflow-x-auto`. */
|
|
84
|
+
export declare function utilityOf(token: string): string;
|
|
85
|
+
/** The Base UI module an import names, or `undefined` when it is not Base UI. `''` is the package root. */
|
|
86
|
+
export declare function baseUiModule(source: string): string | undefined;
|
|
87
|
+
export declare const noRebuiltBehaviour: Rule.RuleModule;
|
|
88
|
+
//# sourceMappingURL=no-rebuilt-behaviour.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-rebuilt-behaviour.d.ts","sourceRoot":"","sources":["../../src/eslint/no-rebuilt-behaviour.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAGlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,mHAAmH;AACnH,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuC1D,CAAA;AASD;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;CAAE,CA6BhF,CAAA;AAED,wGAAwG;AACxG,eAAO,MAAM,YAAY,UAA2F,CAAA;AAEpH;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAY5E,CAAA;AAED,8GAA8G;AAC9G,eAAO,MAAM,oBAAoB,aAAoH,CAAA;AAErJ,8FAA8F;AAC9F,eAAO,MAAM,YAAY,QAAyC,CAAA;AAElE,0FAA0F;AAC1F,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAA;CACd;AAMD,eAAO,MAAM,gBAAgB,EAAE,cAAc,EA8E5C,CAAA;AAoED,iGAAiG;AACjG,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAU/C;AAED,2GAA2G;AAC3G,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK/D;AAcD,eAAO,MAAM,kBAAkB,EAAE,IAAI,CAAC,UAyOrC,CAAA"}
|