@estiva-app/ui 0.15.0 → 0.16.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/dist/Breadcrumb.d.ts.map +1 -1
- package/dist/CommandPalette.d.ts +114 -0
- package/dist/CommandPalette.d.ts.map +1 -0
- package/dist/Menu.d.ts +4 -0
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Toast.d.ts.map +1 -1
- package/dist/eslint/has-a-page-and-a-story.d.ts +4 -0
- package/dist/eslint/has-a-page-and-a-story.d.ts.map +1 -0
- package/dist/eslint/index.d.ts +15 -1
- package/dist/eslint/index.d.ts.map +1 -1
- package/dist/eslint/index.js +176 -8
- package/dist/eslint/index.js.map +3 -3
- package/dist/eslint/no-hand-rolled-behaviour.d.ts +3 -0
- package/dist/eslint/no-hand-rolled-behaviour.d.ts.map +1 -0
- package/dist/eslint/raw-element-outside-a-wrapper.d.ts +29 -0
- package/dist/eslint/raw-element-outside-a-wrapper.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +609 -287
- package/dist/index.js.map +4 -4
- package/package.json +5 -1
- package/src/Breadcrumb.tsx +6 -2
- package/src/CommandPalette.mdx +133 -0
- package/src/CommandPalette.stories.tsx +416 -0
- package/src/CommandPalette.test.tsx +392 -0
- package/src/CommandPalette.tsx +643 -0
- package/src/Menu.tsx +4 -2
- package/src/Toast.tsx +12 -24
- package/src/eslint/has-a-page-and-a-story.test.ts +57 -0
- package/src/eslint/has-a-page-and-a-story.ts +97 -0
- package/src/eslint/index.test.ts +35 -3
- package/src/eslint/index.ts +58 -13
- package/src/eslint/no-hand-rolled-behaviour.test.ts +70 -0
- package/src/eslint/no-hand-rolled-behaviour.ts +116 -0
- package/src/eslint/raw-element-outside-a-wrapper.test.ts +82 -0
- package/src/eslint/raw-element-outside-a-wrapper.ts +85 -0
- package/src/index.ts +17 -0
- package/stories/Choosing.mdx +1 -0
package/dist/Breadcrumb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Breadcrumb.d.ts","sourceRoot":"","sources":["../src/Breadcrumb.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4D,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"Breadcrumb.d.ts","sourceRoot":"","sources":["../src/Breadcrumb.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4D,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAKxH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qFAAqF;IACrF,OAAO,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAA;IAC9C,oDAAoD;IACpD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,KAAK,EAAE,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,eAAe,+BAqF/D"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface CommandPaletteProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
/** The window's name for a screen reader — there is no title on screen. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** The footer's left side: where the palette was opened from. */
|
|
8
|
+
where?: string;
|
|
9
|
+
/**
|
|
10
|
+
* How the footer spells the modifier in `Ctrl+Backspace` and `Ctrl+Enter` —
|
|
11
|
+
* `Cmd` on a Mac. The keys answer to Ctrl and Cmd either way; the package
|
|
12
|
+
* does not guess the platform, the app says it.
|
|
13
|
+
*/
|
|
14
|
+
modKey?: string;
|
|
15
|
+
/** One level: a `CommandPaletteSearch` or a `CommandPaletteForm`. */
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
export declare function CommandPalette({ open, onOpenChange, label, where, modKey, children }: CommandPaletteProps): import("react").JSX.Element;
|
|
19
|
+
/**
|
|
20
|
+
* The level you are in, drawn as a chip before the field. Removing it goes
|
|
21
|
+
* back, and so does Backspace at the start of the field — one meaning for the
|
|
22
|
+
* chip, whichever way you reach it.
|
|
23
|
+
*/
|
|
24
|
+
export interface CommandPaletteChip {
|
|
25
|
+
label: string;
|
|
26
|
+
/** Before the label, 16px — an icon, a mark. */
|
|
27
|
+
leading?: ReactNode;
|
|
28
|
+
onBack: () => void;
|
|
29
|
+
}
|
|
30
|
+
export interface CommandPaletteRow {
|
|
31
|
+
/** Unique in the level. The footer follows the lit row by it. */
|
|
32
|
+
id: string;
|
|
33
|
+
label: string;
|
|
34
|
+
/** The second line — where it is, who said it, when. */
|
|
35
|
+
description?: string;
|
|
36
|
+
/** A 16px icon, drawn on the row's 32px tile. */
|
|
37
|
+
icon?: ReactNode;
|
|
38
|
+
/** Instead of `icon`: something with a look of its own — a face, a status mark — in the same 32px space, with no tile. */
|
|
39
|
+
leading?: ReactNode;
|
|
40
|
+
/** Enter, or a click. */
|
|
41
|
+
onSelect: () => void;
|
|
42
|
+
/** The row leads to more rows: Tab, or → at the end of the text, goes in, and the row shows a chevron. */
|
|
43
|
+
onGoIn?: () => void;
|
|
44
|
+
/** The row is the person's own history: Ctrl+Backspace forgets it. */
|
|
45
|
+
onForget?: () => void;
|
|
46
|
+
}
|
|
47
|
+
export interface CommandPaletteGroup {
|
|
48
|
+
/** The heading over the rows. A group with no rows is not drawn. */
|
|
49
|
+
label: string;
|
|
50
|
+
rows: CommandPaletteRow[];
|
|
51
|
+
}
|
|
52
|
+
export interface CommandPaletteSearchProps {
|
|
53
|
+
query: string;
|
|
54
|
+
onQueryChange: (query: string) => void;
|
|
55
|
+
placeholder: string;
|
|
56
|
+
groups: CommandPaletteGroup[];
|
|
57
|
+
/** The level you are in. Without one, this is the first level and there is nowhere to go back to. */
|
|
58
|
+
chip?: CommandPaletteChip;
|
|
59
|
+
/** A line under the rows while more are on their way — "Searching…". */
|
|
60
|
+
pending?: string;
|
|
61
|
+
/** The line when there are no rows. Leave it out while rows are still on their way. */
|
|
62
|
+
empty?: string;
|
|
63
|
+
/** Above the rows: a `CommandPaletteWorking`, `CommandPaletteAnswer` or `CommandPaletteQuote`. */
|
|
64
|
+
children?: ReactNode;
|
|
65
|
+
}
|
|
66
|
+
export declare function CommandPaletteSearch({ query, onQueryChange, placeholder, groups, chip, pending, empty, children }: CommandPaletteSearchProps): import("react").JSX.Element;
|
|
67
|
+
export interface CommandPaletteFormProps {
|
|
68
|
+
/** Names what the form makes, and goes back when removed. */
|
|
69
|
+
chip: CommandPaletteChip;
|
|
70
|
+
/** Before the chip, 16px — the mark of whatever the form writes to. */
|
|
71
|
+
icon?: ReactNode;
|
|
72
|
+
/** The button's word, and the footer's beside Ctrl+Enter. */
|
|
73
|
+
submitLabel: string;
|
|
74
|
+
onSubmit: () => void;
|
|
75
|
+
/** Why submitting has to wait — nothing changed yet. The button says it on hover, and Ctrl+Enter does nothing. */
|
|
76
|
+
submitWaits?: string;
|
|
77
|
+
/** While the thing is being made: the fields lock, the button reads `button`, and `line` says what is happening. */
|
|
78
|
+
working?: {
|
|
79
|
+
button: string;
|
|
80
|
+
line: string;
|
|
81
|
+
};
|
|
82
|
+
/** What went wrong, beside the button. */
|
|
83
|
+
error?: string;
|
|
84
|
+
/** The fields, each in a `Field`. */
|
|
85
|
+
children: ReactNode;
|
|
86
|
+
}
|
|
87
|
+
export declare function CommandPaletteForm({ chip, icon, submitLabel, onSubmit, submitWaits, working, error, children }: CommandPaletteFormProps): import("react").JSX.Element;
|
|
88
|
+
export interface CommandPaletteWorkingProps {
|
|
89
|
+
/** What is happening — "Reading 12 messages…". */
|
|
90
|
+
children: ReactNode;
|
|
91
|
+
/** Grey bars under the line, where the result will be. Default 2. */
|
|
92
|
+
bars?: 0 | 1 | 2 | 3;
|
|
93
|
+
}
|
|
94
|
+
/** A line saying what is being worked on, with grey bars where the result will land. */
|
|
95
|
+
export declare function CommandPaletteWorking({ children, bars }: CommandPaletteWorkingProps): import("react").JSX.Element;
|
|
96
|
+
export interface CommandPaletteAnswerProps {
|
|
97
|
+
/**
|
|
98
|
+
* The answer's text. A blank line starts a new paragraph, and a number in
|
|
99
|
+
* square brackets — `[1]` — is drawn as a small mark pointing at the row of
|
|
100
|
+
* the same number below.
|
|
101
|
+
*/
|
|
102
|
+
children: string;
|
|
103
|
+
/** A quiet line under the answer — what a key will do now. */
|
|
104
|
+
note?: string;
|
|
105
|
+
}
|
|
106
|
+
/** A written answer, with marks that point at the rows it came from. */
|
|
107
|
+
export declare function CommandPaletteAnswer({ children, note }: CommandPaletteAnswerProps): import("react").JSX.Element;
|
|
108
|
+
export interface CommandPaletteQuoteProps {
|
|
109
|
+
/** Text written for someone to use — shown as it will be used, line breaks kept. */
|
|
110
|
+
children: string;
|
|
111
|
+
}
|
|
112
|
+
/** Text written to be used elsewhere, set off by a rule at its left edge. */
|
|
113
|
+
export declare function CommandPaletteQuote({ children }: CommandPaletteQuoteProps): import("react").JSX.Element;
|
|
114
|
+
//# sourceMappingURL=CommandPalette.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../src/CommandPalette.tsx"],"names":[],"mappings":"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"}
|
package/dist/Menu.d.ts
CHANGED
|
@@ -151,6 +151,10 @@ export declare function menuItemClassName({ size, selected, className }: {
|
|
|
151
151
|
selected?: boolean;
|
|
152
152
|
className?: string;
|
|
153
153
|
}): string;
|
|
154
|
+
/** Everything inside the row — written once, for the same reason. Shared
|
|
155
|
+
* inside the package like `menuItemClassName`: `CommandPalette` draws it
|
|
156
|
+
* inside Base UI's `Autocomplete.Item`. Not exported from the index. */
|
|
157
|
+
export declare function MenuItemBody({ label, children, size, description, leading, trailing, hint, shortcut, submenu, destructive, selected }: Pick<MenuItemProps, 'label' | 'children' | 'size' | 'description' | 'leading' | 'trailing' | 'hint' | 'shortcut' | 'submenu' | 'destructive' | 'selected'>): import("react").JSX.Element;
|
|
154
158
|
export declare function MenuItem({ label, children, size, description, leading, trailing, hint, shortcut, submenu, destructive, selected, className, ...props }: MenuItemProps): import("react").JSX.Element;
|
|
155
159
|
/**
|
|
156
160
|
* The keyboard hint a picker row shows while highlighted — hand-rolled in
|
package/dist/Menu.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../src/Menu.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../src/Menu.tsx"],"names":[],"mappings":"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"}
|
package/dist/Toast.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../src/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../src/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAM1E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAE7E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAuDD;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,IAAgB,EAAE,WAAkB,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,UAAU,+BAelH;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,sGAAsG;IACtG,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,UAAU,UAAU;IAClB,4DAA4D;IAC5D,SAAS,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,CAAA;IACzC,+EAA+E;IAC/E,YAAY,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC;AAcD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAgClE;AA0CD,wBAAgB,QAAQ,IAAI,UAAU,CAIrC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"has-a-page-and-a-story.d.ts","sourceRoot":"","sources":["../../src/eslint/has-a-page-and-a-story.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAiDlC,eAAO,MAAM,iBAAiB,EAAE,IAAI,CAAC,UAsBpC,CAAA;AAED,eAAO,MAAM,kBAAkB,EAAE,IAAI,CAAC,UAsBrC,CAAA"}
|
package/dist/eslint/index.d.ts
CHANGED
|
@@ -8,13 +8,22 @@ declare const plugin: {
|
|
|
8
8
|
version: string;
|
|
9
9
|
};
|
|
10
10
|
rules: {
|
|
11
|
+
'raw-element-outside-a-wrapper': import("eslint").Rule.RuleModule;
|
|
12
|
+
'no-hand-rolled-behaviour': import("eslint").Rule.RuleModule;
|
|
13
|
+
'component-has-a-page': import("eslint").Rule.RuleModule;
|
|
14
|
+
'component-has-a-story': import("eslint").Rule.RuleModule;
|
|
11
15
|
'no-raw-button': import("eslint").Rule.RuleModule;
|
|
12
16
|
};
|
|
13
17
|
configs: {
|
|
14
18
|
recommended: Linter.Config;
|
|
15
19
|
strict: Linter.Config;
|
|
20
|
+
package: Linter.Config;
|
|
16
21
|
};
|
|
17
22
|
};
|
|
23
|
+
/** The ids `recommended` and `strict` carry — what an app's gate runs and counts. */
|
|
24
|
+
export declare const APP_RULE_IDS: string[];
|
|
25
|
+
/** The ids `package` carries — what this package's own gate runs and counts (UIG-5). */
|
|
26
|
+
export declare const PACKAGE_RULE_IDS: string[];
|
|
18
27
|
export default plugin;
|
|
19
28
|
export interface GateRuleCount {
|
|
20
29
|
errors: number;
|
|
@@ -40,6 +49,11 @@ export interface GateCount {
|
|
|
40
49
|
* Run the lint with `settings: { estiva: { reportEscapes: true } }` for the
|
|
41
50
|
* escapes to be counted; without it they are silent and count 0. A marker
|
|
42
51
|
* with no reason, or inside a directive, counts as an error of its rule.
|
|
52
|
+
*
|
|
53
|
+
* `seed` is which rules the count lists when they found nothing, and it is the
|
|
54
|
+
* app rules unless a caller says otherwise: an app's count file must not gain
|
|
55
|
+
* rows for the inward rules (UIG-5) that its gate does not run. This package's
|
|
56
|
+
* own count script passes `PACKAGE_RULE_IDS`.
|
|
43
57
|
*/
|
|
44
|
-
export declare function countGates(results: ESLint.LintResult[]): GateCount;
|
|
58
|
+
export declare function countGates(results: ESLint.LintResult[], seed?: readonly string[]): GateCount;
|
|
45
59
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/eslint/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/eslint/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAM5C,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAIlG,2FAA2F;AAC3F,eAAO,MAAM,UAAU,WAAW,CAAA;AA8BlC,QAAA,MAAM,MAAM;;;;;;;;;;;;aAGK;QAAE,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAA;KAAE;CACrE,CAAA;AAEzB,qFAAqF;AACrF,eAAO,MAAM,YAAY,UAA+D,CAAA;AACxF,wFAAwF;AACxF,eAAO,MAAM,gBAAgB,UAAmE,CAAA;AA0BhG,eAAe,MAAM,CAAA;AAErB,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IACpC;;;OAGG;IACH,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,SAAS,CAmB1G"}
|
package/dist/eslint/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// src/eslint/index.ts
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
|
|
4
|
+
// src/eslint/has-a-page-and-a-story.ts
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
|
|
4
7
|
// src/eslint/escape.ts
|
|
5
8
|
var ESCAPE_MARKER = "@estiva-escape";
|
|
6
9
|
var MIN_REASON = 10;
|
|
@@ -42,6 +45,119 @@ function isEscaped(context, node) {
|
|
|
42
45
|
return true;
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
// src/eslint/has-a-page-and-a-story.ts
|
|
49
|
+
function componentFile(filename) {
|
|
50
|
+
return filename.endsWith(".tsx") && !/\.(stories|test)\.tsx$/.test(filename);
|
|
51
|
+
}
|
|
52
|
+
function sibling(filename, extension) {
|
|
53
|
+
return filename.replace(/\.tsx$/, extension);
|
|
54
|
+
}
|
|
55
|
+
function reportOnFile(context, program, messageId, data) {
|
|
56
|
+
const anchor = program.body[0] ?? program;
|
|
57
|
+
if (anchor.loc && anchor.range && isEscaped(context, { loc: anchor.loc, range: anchor.range })) return;
|
|
58
|
+
context.report({ loc: anchor.loc ?? program.loc, messageId, data });
|
|
59
|
+
}
|
|
60
|
+
var componentHasAPage = {
|
|
61
|
+
meta: {
|
|
62
|
+
type: "problem",
|
|
63
|
+
docs: { description: "Every component of the package has a usage page beside it" },
|
|
64
|
+
schema: [],
|
|
65
|
+
messages: {
|
|
66
|
+
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.",
|
|
67
|
+
...ESCAPE_MESSAGES
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
create(context) {
|
|
71
|
+
return {
|
|
72
|
+
Program(node) {
|
|
73
|
+
const filename = context.filename;
|
|
74
|
+
if (!componentFile(filename)) return;
|
|
75
|
+
const page = sibling(filename, ".mdx");
|
|
76
|
+
if (existsSync(page)) return;
|
|
77
|
+
const name = filename.split(/[\\/]/).pop() ?? filename;
|
|
78
|
+
reportOnFile(context, node, "missing", { name, page: page.split(/[\\/]/).pop() ?? page });
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var componentHasAStory = {
|
|
84
|
+
meta: {
|
|
85
|
+
type: "problem",
|
|
86
|
+
docs: { description: "Every component of the package has a story beside it" },
|
|
87
|
+
schema: [],
|
|
88
|
+
messages: {
|
|
89
|
+
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.",
|
|
90
|
+
...ESCAPE_MESSAGES
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
create(context) {
|
|
94
|
+
return {
|
|
95
|
+
Program(node) {
|
|
96
|
+
const filename = context.filename;
|
|
97
|
+
if (!componentFile(filename)) return;
|
|
98
|
+
const story = sibling(filename, ".stories.tsx");
|
|
99
|
+
if (existsSync(story)) return;
|
|
100
|
+
const name = filename.split(/[\\/]/).pop() ?? filename;
|
|
101
|
+
reportOnFile(context, node, "missing", { name, story: story.split(/[\\/]/).pop() ?? story });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/eslint/no-hand-rolled-behaviour.ts
|
|
108
|
+
var FLOATING_EVENTS = /* @__PURE__ */ new Set(["resize", "scroll", "mousedown", "pointerdown", "keydown", "keyup", "focusin", "focusout", "click"]);
|
|
109
|
+
function statement(node) {
|
|
110
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
111
|
+
if (!/(Statement|Declaration)$/.test(p.type)) continue;
|
|
112
|
+
if (p.loc && p.range) return { loc: p.loc, range: p.range };
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
return { loc: node.loc, range: node.range };
|
|
116
|
+
}
|
|
117
|
+
var noHandRolledBehaviour = {
|
|
118
|
+
meta: {
|
|
119
|
+
type: "problem",
|
|
120
|
+
docs: { description: "Overlay behaviour written by hand where Base UI has a part (D6)" },
|
|
121
|
+
schema: [],
|
|
122
|
+
messages: {
|
|
123
|
+
portal: "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.",
|
|
124
|
+
listener: "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.",
|
|
125
|
+
...ESCAPE_MESSAGES
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
create(context) {
|
|
129
|
+
return {
|
|
130
|
+
ImportDeclaration(node) {
|
|
131
|
+
const declaration = node;
|
|
132
|
+
if (declaration.source.value !== "react-dom") return;
|
|
133
|
+
for (const specifier of declaration.specifiers) {
|
|
134
|
+
if (specifier.type !== "ImportSpecifier" || specifier.imported?.name !== "createPortal") continue;
|
|
135
|
+
if (isEscaped(context, declaration)) return;
|
|
136
|
+
context.report({ loc: declaration.loc, messageId: "portal" });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
CallExpression(node) {
|
|
141
|
+
const call = node;
|
|
142
|
+
const { callee } = call;
|
|
143
|
+
if (callee.type === "Identifier" && callee.name === "createPortal") {
|
|
144
|
+
if (isEscaped(context, statement(call))) return;
|
|
145
|
+
context.report({ loc: call.loc, messageId: "portal" });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (callee.type !== "MemberExpression" || callee.property?.name !== "addEventListener") return;
|
|
149
|
+
const target = callee.object?.type === "Identifier" ? callee.object.name : void 0;
|
|
150
|
+
if (target !== "window" && target !== "document") return;
|
|
151
|
+
const [first] = call.arguments;
|
|
152
|
+
const event = first?.type === "Literal" && typeof first.value === "string" ? first.value : void 0;
|
|
153
|
+
if (!event || !FLOATING_EVENTS.has(event)) return;
|
|
154
|
+
if (isEscaped(context, statement(call))) return;
|
|
155
|
+
context.report({ loc: call.loc, messageId: "listener", data: { event, target } });
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
45
161
|
// src/eslint/no-raw-button.ts
|
|
46
162
|
var noRawButton = {
|
|
47
163
|
meta: {
|
|
@@ -66,18 +182,62 @@ var noRawButton = {
|
|
|
66
182
|
}
|
|
67
183
|
};
|
|
68
184
|
|
|
185
|
+
// src/eslint/raw-element-outside-a-wrapper.ts
|
|
186
|
+
var RAW_ELEMENTS = /* @__PURE__ */ new Set(["a", "button", "input", "textarea", "select", "dialog", "form", "label"]);
|
|
187
|
+
var rawElementOutsideAWrapper = {
|
|
188
|
+
meta: {
|
|
189
|
+
type: "problem",
|
|
190
|
+
docs: { description: "A raw element nested inside a component, rather than the component itself" },
|
|
191
|
+
schema: [],
|
|
192
|
+
messages: {
|
|
193
|
+
nested: "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...).",
|
|
194
|
+
...ESCAPE_MESSAGES
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
create(context) {
|
|
198
|
+
return {
|
|
199
|
+
// ESLint's types know ESTree's nodes, not JSX's; the parser hands this one over.
|
|
200
|
+
JSXOpeningElement(node) {
|
|
201
|
+
const element = node;
|
|
202
|
+
const name = element.name.name;
|
|
203
|
+
if (element.name.type !== "JSXIdentifier" || !name || !RAW_ELEMENTS.has(name)) return;
|
|
204
|
+
let parent = element.parent;
|
|
205
|
+
if (parent?.type === "JSXElement" && parent.openingElement === element) parent = parent.parent;
|
|
206
|
+
for (let p = parent; p; p = p.parent) {
|
|
207
|
+
if (p.type === "JSXAttribute") {
|
|
208
|
+
if (p.name?.name === "render") return;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (p.type !== "JSXElement" && p.type !== "JSXFragment") continue;
|
|
212
|
+
if (isEscaped(context, element)) return;
|
|
213
|
+
context.report({ loc: element.loc, messageId: "nested", data: { name } });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
69
221
|
// src/eslint/index.ts
|
|
70
222
|
var { version } = createRequire(import.meta.url)("../../package.json");
|
|
71
223
|
var PLUGIN_KEY = "estiva";
|
|
72
|
-
var
|
|
224
|
+
var appRules = {
|
|
73
225
|
"no-raw-button": noRawButton
|
|
74
226
|
};
|
|
227
|
+
var packageRules = {
|
|
228
|
+
"raw-element-outside-a-wrapper": rawElementOutsideAWrapper,
|
|
229
|
+
"no-hand-rolled-behaviour": noHandRolledBehaviour,
|
|
230
|
+
"component-has-a-page": componentHasAPage,
|
|
231
|
+
"component-has-a-story": componentHasAStory
|
|
232
|
+
};
|
|
233
|
+
var rules = { ...appRules, ...packageRules };
|
|
75
234
|
var plugin = {
|
|
76
235
|
meta: { name: "@estiva-app/ui/eslint", version },
|
|
77
236
|
rules,
|
|
78
237
|
configs: {}
|
|
79
238
|
};
|
|
80
|
-
var
|
|
239
|
+
var APP_RULE_IDS = Object.keys(appRules).map((name) => `${PLUGIN_KEY}/${name}`);
|
|
240
|
+
var PACKAGE_RULE_IDS = Object.keys(packageRules).map((name) => `${PLUGIN_KEY}/${name}`);
|
|
81
241
|
plugin.configs.recommended = {
|
|
82
242
|
name: "@estiva-app/ui/recommended",
|
|
83
243
|
plugins: { [PLUGIN_KEY]: plugin },
|
|
@@ -86,29 +246,37 @@ plugin.configs.recommended = {
|
|
|
86
246
|
plugin.configs.strict = {
|
|
87
247
|
name: "@estiva-app/ui/strict",
|
|
88
248
|
plugins: { [PLUGIN_KEY]: plugin },
|
|
89
|
-
rules: Object.fromEntries(
|
|
249
|
+
rules: Object.fromEntries(APP_RULE_IDS.map((id) => [id, "error"]))
|
|
250
|
+
};
|
|
251
|
+
plugin.configs.package = {
|
|
252
|
+
name: "@estiva-app/ui/package",
|
|
253
|
+
plugins: { [PLUGIN_KEY]: plugin },
|
|
254
|
+
rules: Object.fromEntries(PACKAGE_RULE_IDS.map((id) => [id, "error"]))
|
|
90
255
|
};
|
|
91
256
|
var index_default = plugin;
|
|
92
|
-
function countGates(results) {
|
|
93
|
-
const counts = Object.fromEntries(
|
|
257
|
+
function countGates(results, seed = APP_RULE_IDS) {
|
|
258
|
+
const counts = Object.fromEntries(seed.map((id) => [id, { errors: 0, warnings: 0, escapes: 0 }]));
|
|
94
259
|
const disabled = [];
|
|
260
|
+
const ours = (ruleId) => typeof ruleId === "string" && ruleId.startsWith(`${PLUGIN_KEY}/`);
|
|
95
261
|
for (const result of results) {
|
|
96
262
|
for (const message of result.messages) {
|
|
97
|
-
|
|
98
|
-
|
|
263
|
+
if (!ours(message.ruleId)) continue;
|
|
264
|
+
const count = counts[message.ruleId] ??= { errors: 0, warnings: 0, escapes: 0 };
|
|
99
265
|
if (message.messageId === "escaped") count.escapes += 1;
|
|
100
266
|
else if (message.severity === 2) count.errors += 1;
|
|
101
267
|
else count.warnings += 1;
|
|
102
268
|
}
|
|
103
269
|
for (const message of result.suppressedMessages ?? []) {
|
|
104
|
-
if (message.ruleId
|
|
270
|
+
if (ours(message.ruleId)) disabled.push({ filePath: result.filePath, line: message.line, ruleId: message.ruleId });
|
|
105
271
|
}
|
|
106
272
|
}
|
|
107
273
|
return { rules: counts, disabled };
|
|
108
274
|
}
|
|
109
275
|
export {
|
|
276
|
+
APP_RULE_IDS,
|
|
110
277
|
ESCAPE_MARKER,
|
|
111
278
|
MIN_REASON,
|
|
279
|
+
PACKAGE_RULE_IDS,
|
|
112
280
|
PLUGIN_KEY,
|
|
113
281
|
SETTINGS_KEY,
|
|
114
282
|
countGates,
|
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/escape.ts", "../../src/eslint/no-raw-button.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 { noRawButton } from './no-raw-button'\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\nconst rules = {\n 'no-raw-button': noRawButton,\n}\n\nconst plugin = {\n meta: { name: '@estiva-app/ui/eslint', version },\n rules,\n configs: {} as { recommended: Linter.Config; strict: Linter.Config },\n} satisfies ESLint.Plugin\n\nconst ruleIds = Object.keys(rules).map((name) => `${PLUGIN_KEY}/${name}`)\n\n/**\n * `recommended` switches every rule on at the level it was ruled at: an error\n * blocks, a warning is reported and never blocks. `strict` makes every rule an\n * error. With one rule, an error, the two are the same today; they part when\n * the first warning-level rule arrives (UIG-25).\n */\nplugin.configs.recommended = {\n name: '@estiva-app/ui/recommended',\n plugins: { [PLUGIN_KEY]: plugin },\n rules: { [`${PLUGIN_KEY}/no-raw-button`]: 'error' },\n}\nplugin.configs.strict = {\n name: '@estiva-app/ui/strict',\n plugins: { [PLUGIN_KEY]: plugin },\n rules: Object.fromEntries(ruleIds.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 */\nexport function countGates(results: ESLint.LintResult[]): GateCount {\n const counts: Record<string, GateRuleCount> = Object.fromEntries(ruleIds.map((id) => [id, { errors: 0, warnings: 0, escapes: 0 }]))\n const disabled: GateCount['disabled'] = []\n for (const result of results) {\n for (const message of result.messages) {\n const count = message.ruleId ? counts[message.ruleId] : undefined\n if (!count) continue\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 (message.ruleId && counts[message.ruleId]) disabled.push({ filePath: result.filePath, line: message.line, ruleId: message.ruleId })\n }\n }\n return { rules: counts, disabled }\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\ninterface JSXOpeningElement {\n name: { type: string; name?: string }\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n}\n\n/**\n * A raw `<button>` in an app (UIG-3, the tracer). The package's `Button`,\n * `IconButton`, `MenuItem` and chips are buttons already; an app that writes\n * its own has a look and a behaviour nobody else keeps in step with.\n *\n * Only the JSX element named `button`. `<Button>`, `<Foo.button>` and\n * `createElement('button')` are not this rule's business.\n */\nexport const noRawButton: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'A raw <button> where @estiva-app/ui has one' },\n schema: [],\n messages: {\n raw: 'Use `Button` from @estiva-app/ui instead of a raw <button>.',\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 if (element.name.type !== 'JSXIdentifier' || element.name.name !== 'button') return\n if (isEscaped(context, element)) return\n context.report({ loc: element.loc, messageId: 'raw' })\n },\n }\n },\n}\n"],
|
|
5
|
-
"mappings": ";AAkBA,SAAS,qBAAqB;;;
|
|
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-button.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 { noRawButton } from './no-raw-button'\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-button': noRawButton,\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 is full of raw elements it is allowed to\n * have until UIG-7, and 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-button`]: '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 JSXOpeningElement {\n name: { type: string; name?: string }\n loc: NonNullable<Rule.Node['loc']>\n range: [number, number]\n}\n\n/**\n * A raw `<button>` in an app (UIG-3, the tracer). The package's `Button`,\n * `IconButton`, `MenuItem` and chips are buttons already; an app that writes\n * its own has a look and a behaviour nobody else keeps in step with.\n *\n * Only the JSX element named `button`. `<Button>`, `<Foo.button>` and\n * `createElement('button')` are not this rule's business.\n */\nexport const noRawButton: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: { description: 'A raw <button> where @estiva-app/ui has one' },\n schema: [],\n messages: {\n raw: 'Use `Button` from @estiva-app/ui instead of a raw <button>.',\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 if (element.name.type !== 'JSXIdentifier' || element.name.name !== 'button') return\n if (isEscaped(context, element)) return\n context.report({ loc: element.loc, messageId: 'raw' })\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
6
|
"names": ["rules"]
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-hand-rolled-behaviour.d.ts","sourceRoot":"","sources":["../../src/eslint/no-hand-rolled-behaviour.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAoElC,eAAO,MAAM,qBAAqB,EAAE,IAAI,CAAC,UA+CxC,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
/**
|
|
3
|
+
* The elements this package writes by hand. The set the count used (UIG-1,
|
|
4
|
+
* docs/GATES.md §5 and §7): every element that carries behaviour or a role of
|
|
5
|
+
* its own. A `div` or a `span` is layout and is nobody's business here.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RAW_ELEMENTS: Set<string>;
|
|
8
|
+
/**
|
|
9
|
+
* A raw element nested inside another element, in the package itself (UIG-5,
|
|
10
|
+
* the inward tracer).
|
|
11
|
+
*
|
|
12
|
+
* The package is allowed to write raw elements — that is what a primitive is.
|
|
13
|
+
* Two shapes are right, and both are facts about the code rather than a
|
|
14
|
+
* judgement:
|
|
15
|
+
*
|
|
16
|
+
* - **the component's own outermost element.** `NavItem` is an `<a>`, `Link` is
|
|
17
|
+
* an `<a>`, a `MenuItem` outside a `Menu` is a `<button>`. A primitive owning
|
|
18
|
+
* its element is the whole point of it.
|
|
19
|
+
* - **handed to a Base UI `render` prop.** `render={<button type="button" />}`
|
|
20
|
+
* tells a Base UI part which element to be; the behaviour stays Base UI's.
|
|
21
|
+
*
|
|
22
|
+
* Anything else is an element buried inside a bigger component, where its look
|
|
23
|
+
* and its behaviour are kept in step by nobody — the package's own version of
|
|
24
|
+
* the rule the apps get from UIG-3 and UIG-7.
|
|
25
|
+
*
|
|
26
|
+
* It keeps its reason on the line above if it stays: `// @estiva-escape: <why>`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const rawElementOutsideAWrapper: Rule.RuleModule;
|
|
29
|
+
//# sourceMappingURL=raw-element-outside-a-wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-element-outside-a-wrapper.d.ts","sourceRoot":"","sources":["../../src/eslint/raw-element-outside-a-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAkBlC;;;;GAIG;AACH,eAAO,MAAM,YAAY,aAAqF,CAAA;AAE9G;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,yBAAyB,EAAE,IAAI,CAAC,UAuC5C,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant } from '.
|
|
|
18
18
|
export { Card, type CardAttention, type CardFill, type CardHover, type CardProps } from './Card';
|
|
19
19
|
export { Checkbox, type CheckboxProps } from './Checkbox';
|
|
20
20
|
export { Chip, type ChipProps, type ChipType } from './Chip';
|
|
21
|
+
export { CommandPalette, CommandPaletteAnswer, CommandPaletteForm, CommandPaletteQuote, CommandPaletteSearch, CommandPaletteWorking, type CommandPaletteAnswerProps, type CommandPaletteChip, type CommandPaletteFormProps, type CommandPaletteGroup, type CommandPaletteProps, type CommandPaletteQuoteProps, type CommandPaletteRow, type CommandPaletteSearchProps, type CommandPaletteWorkingProps, } from './CommandPalette';
|
|
21
22
|
export { ChipInput, InputChip, type ChipInputOption, type ChipInputProps, type InputChipProps } from './ChipInput';
|
|
22
23
|
export { INLINE_CHIP_CLASSES, INLINE_CHIP_TONE_CLASSES, InlineChip, inlineChipClassName, type InlineChipProps, type InlineChipTone, } from './InlineChip';
|
|
23
24
|
export { Link, type LinkProps, type LinkVariant } from './Link';
|