@estiva-app/ui 0.35.2 → 0.37.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/Panel.d.ts +44 -0
- package/dist/Panel.d.ts.map +1 -0
- package/dist/ScrollArea.d.ts +7 -2
- package/dist/ScrollArea.d.ts.map +1 -1
- package/dist/eslint/escape.d.ts +23 -6
- package/dist/eslint/escape.d.ts.map +1 -1
- package/dist/eslint/index.js +86 -12
- package/dist/eslint/index.js.map +3 -3
- package/dist/eslint/no-handmade-header.d.ts +5 -0
- package/dist/eslint/no-handmade-header.d.ts.map +1 -1
- package/dist/eslint/no-native-title.d.ts +1 -1
- package/dist/eslint/no-native-title.d.ts.map +1 -1
- package/dist/eslint/no-raw-element.d.ts +7 -0
- package/dist/eslint/no-raw-element.d.ts.map +1 -1
- package/dist/eslint/no-restyled-part.d.ts +8 -0
- package/dist/eslint/no-restyled-part.d.ts.map +1 -1
- package/dist/gates/app-checks.d.ts +1 -1
- package/dist/gates/app-checks.d.ts.map +1 -1
- package/dist/gates/{chunk-DRIHHXQK.js → chunk-CC6L4B5U.js} +23 -17
- package/dist/gates/chunk-CC6L4B5U.js.map +7 -0
- package/dist/gates/chunk-SYVKGOWM.js +1131 -0
- package/dist/gates/chunk-SYVKGOWM.js.map +7 -0
- package/dist/gates/ci.d.ts +18 -0
- package/dist/gates/ci.d.ts.map +1 -0
- package/dist/gates/cli.js +20 -3
- package/dist/gates/cli.js.map +2 -2
- package/dist/gates/create-app.d.ts.map +1 -1
- package/dist/gates/create-app.js +1 -1
- package/dist/gates/escapes.d.ts +23 -0
- package/dist/gates/escapes.d.ts.map +1 -0
- package/dist/gates/gate-config.d.ts.map +1 -1
- package/dist/gates/hook.d.ts +2 -0
- package/dist/gates/hook.d.ts.map +1 -1
- package/dist/gates/index.d.ts +5 -1
- package/dist/gates/index.d.ts.map +1 -1
- package/dist/gates/index.js +42 -185
- package/dist/gates/index.js.map +4 -4
- package/dist/gates/name-escapes.d.ts +14 -0
- package/dist/gates/name-escapes.d.ts.map +1 -0
- package/dist/gates/status.d.ts +3 -1
- package/dist/gates/status.d.ts.map +1 -1
- package/dist/gates/token-lint.d.ts.map +1 -1
- package/dist/gates/token-style.d.ts +21 -0
- package/dist/gates/token-style.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -49
- package/dist/index.js.map +4 -4
- package/dist/registry/{app-3ALZE6XO.js → app-YMABQII6.js} +3 -3
- package/dist/registry/app.d.ts.map +1 -1
- package/dist/registry/behaviours.d.ts +71 -0
- package/dist/registry/behaviours.d.ts.map +1 -0
- package/dist/registry/{build-NDXMVFQ5.js → build-3EANARLQ.js} +2 -2
- package/dist/registry/build.d.ts.map +1 -1
- package/dist/registry/{chunk-3WJ7ZZ7Z.js → chunk-EVJW26UR.js} +51 -4
- package/dist/registry/chunk-EVJW26UR.js.map +7 -0
- package/dist/registry/{chunk-XVII7P6G.js → chunk-SIB44CES.js} +276 -90
- package/dist/registry/chunk-SIB44CES.js.map +7 -0
- package/dist/registry/{chunk-5K2TRR4X.js → chunk-XK747STJ.js} +18 -3
- package/dist/registry/chunk-XK747STJ.js.map +7 -0
- package/dist/registry/cli.js +7 -6
- package/dist/registry/cli.js.map +2 -2
- package/dist/registry/contract.d.ts +13 -1
- package/dist/registry/contract.d.ts.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +11 -5
- package/package.json +4 -1
- package/registry.json +369 -19
- package/skill/estiva-ui.md +2 -1
- package/src/AppShell.mdx +2 -1
- package/src/AttachmentCard.tsx +7 -7
- package/src/Button.tsx +1 -1
- package/src/Card.tsx +2 -2
- package/src/ChipInput.mdx +2 -0
- package/src/ChipInput.tsx +1 -1
- package/src/CommandPalette.tsx +3 -3
- package/src/DialogShell.tsx +1 -1
- package/src/EmptyState.mdx +4 -3
- package/src/Lightbox.tsx +1 -1
- package/src/NavItem.tsx +1 -1
- package/src/Panel.mdx +78 -0
- package/src/Panel.stories.tsx +98 -0
- package/src/Panel.tsx +80 -0
- package/src/PersonTrigger.tsx +1 -1
- package/src/Reaction.tsx +1 -1
- package/src/ScrollArea.mdx +6 -2
- package/src/ScrollArea.stories.tsx +1 -1
- package/src/ScrollArea.tsx +9 -4
- package/src/Select.tsx +1 -1
- package/src/TopBar.mdx +1 -1
- package/src/eslint/escape.ts +43 -14
- package/src/eslint/has-a-page-and-a-story.test.ts +1 -1
- package/src/eslint/index.test.ts +2 -2
- package/src/eslint/no-copied-look.test.ts +1 -1
- package/src/eslint/no-hand-rolled-behaviour.test.ts +3 -3
- package/src/eslint/no-handmade-empty-state.test.ts +2 -2
- package/src/eslint/no-handmade-header.test.ts +11 -2
- package/src/eslint/no-handmade-header.ts +24 -2
- package/src/eslint/no-native-title.test.ts +2 -2
- package/src/eslint/no-native-title.ts +1 -1
- package/src/eslint/no-raw-element.test.ts +47 -15
- package/src/eslint/no-raw-element.ts +53 -3
- package/src/eslint/no-rebuilt-behaviour.test.ts +9 -9
- package/src/eslint/no-restyled-part.test.ts +21 -2
- package/src/eslint/no-restyled-part.ts +30 -0
- package/src/eslint/raw-element-outside-a-wrapper.test.ts +3 -3
- package/src/gates/app-checks.ts +9 -6
- package/src/gates/ci.ts +152 -0
- package/src/gates/cli.ts +19 -3
- package/src/gates/count.ts +1 -1
- package/src/gates/create-app.test.ts +2 -2
- package/src/gates/create-app.ts +22 -16
- package/src/gates/escapes.ts +111 -0
- package/src/gates/gate-config.ts +2 -0
- package/src/gates/gates.test.ts +86 -1
- package/src/gates/hook.ts +38 -4
- package/src/gates/index.ts +5 -1
- package/src/gates/name-escapes.ts +77 -0
- package/src/gates/status.ts +45 -2
- package/src/gates/token-lint.ts +11 -4
- package/src/gates/token-style.ts +79 -0
- package/src/how.test.ts +282 -0
- package/src/index.ts +1 -0
- package/src/pages.test.ts +6 -0
- package/src/registry/app.test.ts +37 -1
- package/src/registry/app.ts +52 -2
- package/src/registry/behaviours.ts +223 -0
- package/src/registry/build.ts +36 -0
- package/src/registry/cli.ts +5 -3
- package/src/registry/contract.test.ts +27 -2
- package/src/registry/contract.ts +28 -3
- package/src/registry/index.ts +1 -1
- package/src/registry/registry.test.ts +18 -4
- package/stories/Choosing.mdx +4 -3
- package/dist/gates/chunk-DBMUQAR6.js +0 -607
- package/dist/gates/chunk-DBMUQAR6.js.map +0 -7
- package/dist/gates/chunk-DRIHHXQK.js.map +0 -7
- package/dist/registry/chunk-3WJ7ZZ7Z.js.map +0 -7
- package/dist/registry/chunk-5K2TRR4X.js.map +0 -7
- package/dist/registry/chunk-XVII7P6G.js.map +0 -7
- /package/dist/registry/{app-3ALZE6XO.js.map → app-YMABQII6.js.map} +0 -0
- /package/dist/registry/{build-NDXMVFQ5.js.map → build-3EANARLQ.js.map} +0 -0
package/dist/Panel.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ReactNode, Ref, UIEventHandler } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* A panel of a page: its name in a `ContainerHeader`, a body under it that
|
|
4
|
+
* scrolls whenever it is taller than the room it has, and, when there is one,
|
|
5
|
+
* a row fixed at the bottom that never scrolls — a reply box.
|
|
6
|
+
*
|
|
7
|
+
* Made so that scrolling comes from the package, not from each app remembering
|
|
8
|
+
* it (Katerina, 26 September 2026). The apps built their panels from the
|
|
9
|
+
* package's header and a box of their own, and a box without a `ScrollArea`
|
|
10
|
+
* simply cut off what did not fit: Peek's widget panel on a Ship file lost the
|
|
11
|
+
* end of a long description and the status control under it, with no bar and
|
|
12
|
+
* nothing to scroll. Here the body is always a `ScrollArea`, so the bar shows
|
|
13
|
+
* as soon as there is more, and an empty panel or a short one has none.
|
|
14
|
+
*
|
|
15
|
+
* It takes the room its column gives it (`flex-1 min-h-0`), never its content's
|
|
16
|
+
* height, so the body is what scrolls, not the page. Two panels in one column
|
|
17
|
+
* share it; a `max-h-*` in `className` caps one of them.
|
|
18
|
+
*
|
|
19
|
+
* It keeps a crash inside itself, as ListColumn does: if its body, its actions
|
|
20
|
+
* or its footer break, the panel keeps its title and shows `ErrorBoundary`'s
|
|
21
|
+
* message in the room the body had.
|
|
22
|
+
*/
|
|
23
|
+
export interface PanelProps {
|
|
24
|
+
/** The panel's name, in its header. A node for a title that is more than words. */
|
|
25
|
+
title: ReactNode;
|
|
26
|
+
/** A chevron after the title, as `ContainerHeader` draws it. */
|
|
27
|
+
chevron?: boolean;
|
|
28
|
+
/** The panel's own actions, at the header's right — a `Toolbar` of `ToolbarButton`s, or one `IconButton`. */
|
|
29
|
+
actions?: ReactNode;
|
|
30
|
+
/** The body. It scrolls when it is taller than the panel. */
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
/** A row fixed under the body that never scrolls — a reply box. */
|
|
33
|
+
footer?: ReactNode;
|
|
34
|
+
/** The body's own box: its padding, gap and layout — `px-4 pt-3 pb-4`, `flex flex-col gap-2`. */
|
|
35
|
+
bodyClassName?: string;
|
|
36
|
+
/** The body's scrolling box, for a panel that reads or drives its scroll: a thread that opens at its newest reply. */
|
|
37
|
+
bodyRef?: Ref<HTMLDivElement>;
|
|
38
|
+
/** Fires as the body scrolls. */
|
|
39
|
+
onBodyScroll?: UIEventHandler<HTMLDivElement>;
|
|
40
|
+
/** Placement only: a cap such as `max-h-[60%]` when it shares a column. */
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function Panel(props: PanelProps): import("react").JSX.Element;
|
|
44
|
+
//# sourceMappingURL=Panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../src/Panel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAM3D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,UAAU;IACzB,mFAAmF;IACnF,KAAK,EAAE,SAAS,CAAA;IAChB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6GAA6G;IAC7G,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,6DAA6D;IAC7D,QAAQ,EAAE,SAAS,CAAA;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,iGAAiG;IACjG,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sHAAsH;IACtH,OAAO,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAA;IAC7B,iCAAiC;IACjC,YAAY,CAAC,EAAE,cAAc,CAAC,cAAc,CAAC,CAAA;IAC7C,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmBD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,+BActC"}
|
package/dist/ScrollArea.d.ts
CHANGED
|
@@ -7,8 +7,13 @@ import type { ReactNode, Ref, UIEventHandler } from 'react';
|
|
|
7
7
|
* right-hand padding looks wider than the left. Katerina saw it in every
|
|
8
8
|
* scrolling surface of both apps (2026-09-08). Base UI's `ScrollArea` hides
|
|
9
9
|
* the native bar and draws its own over the content, so nothing moves when
|
|
10
|
-
* it appears
|
|
11
|
-
*
|
|
10
|
+
* it appears.
|
|
11
|
+
*
|
|
12
|
+
* The bar shows whenever the region has more to scroll to, at rest too
|
|
13
|
+
* (Katerina, 26 September: a long folder list in Peek's side column looked
|
|
14
|
+
* complete, because the bar waited for the pointer). Base UI writes
|
|
15
|
+
* `data-has-overflow-y` / `-x` on the bar while that axis overflows; a region
|
|
16
|
+
* with nothing more to show keeps no bar at all.
|
|
12
17
|
*
|
|
13
18
|
* Three boxes, and each class prop lands on one:
|
|
14
19
|
*
|
package/dist/ScrollArea.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../src/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3D
|
|
1
|
+
{"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../src/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,GAAG,MAAM,CAAA;IAChD,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAA;IACjC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,cAAc,CAAC,cAAc,CAAC,CAAA;IACzC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAKD,wBAAgB,UAAU,CAAC,EAAE,WAAwB,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,eAAe,+BA4CxJ"}
|
package/dist/eslint/escape.d.ts
CHANGED
|
@@ -3,15 +3,21 @@ import type { AST, Rule } from 'eslint';
|
|
|
3
3
|
* The escape marker (UIG-3, seam S4 in docs/GATES.md §16): one written reason
|
|
4
4
|
* why one element stays as it is.
|
|
5
5
|
*
|
|
6
|
-
* A line comment directly above the element,
|
|
6
|
+
* A line comment directly above the element, naming the rule it keeps off,
|
|
7
7
|
*
|
|
8
|
-
* // @estiva-escape: <reason>
|
|
8
|
+
* // @estiva-escape(no-raw-element): <reason>
|
|
9
9
|
*
|
|
10
10
|
* or, where the element is a JSX child, the JSX comment on the line above,
|
|
11
11
|
*
|
|
12
|
-
* {/* @estiva-escape: <reason> *\/}
|
|
12
|
+
* {/* @estiva-escape(no-raw-element): <reason> *\/}
|
|
13
13
|
*
|
|
14
|
-
* with at least ten characters of reason, spaces not counted.
|
|
14
|
+
* with at least ten characters of reason, spaces not counted. It escapes only
|
|
15
|
+
* the rules it names (Katerina's ruling B3, 25 September: one reason used to
|
|
16
|
+
* switch off every rule on the element, so a raw button that also wrote a
|
|
17
|
+
* dialog's role by hand passed under one reason). Two rules on one element are
|
|
18
|
+
* named together, `@estiva-escape(no-raw-element, no-rebuilt-behaviour)`, each
|
|
19
|
+
* on purpose. A marker that names no rule escapes nothing, and says which name
|
|
20
|
+
* to write.
|
|
15
21
|
*
|
|
16
22
|
* Not `eslint-disable`. A directive switches a rule off without saying it was
|
|
17
23
|
* meant, and the count of escapes (`.gates-count.json`) could not see it. A
|
|
@@ -33,10 +39,21 @@ export declare const SETTINGS_KEY = "estiva";
|
|
|
33
39
|
* reports through the rule that called it.
|
|
34
40
|
*/
|
|
35
41
|
export declare const ESCAPE_MESSAGES: {
|
|
36
|
-
readonly escapeWithoutReason: "An escape needs its reason, at least 10 characters: `// @estiva-escape: <why this stays>`. An escape with no reason hides nothing.";
|
|
37
|
-
readonly escapeInDirective: "Write the escape as its own comment on the line above: `// @estiva-escape: <reason>`. Inside an eslint-disable comment it switches the rule off instead of recording why.";
|
|
42
|
+
readonly escapeWithoutReason: "An escape needs its reason, at least 10 characters: `// @estiva-escape({{rule}}): <why this stays>`. An escape with no reason hides nothing.";
|
|
43
|
+
readonly escapeInDirective: "Write the escape as its own comment on the line above: `// @estiva-escape({{rule}}): <reason>`. Inside an eslint-disable comment it switches the rule off instead of recording why.";
|
|
44
|
+
readonly escapeWithoutRule: "Name the rule this escape keeps off: `@estiva-escape({{rule}}): <reason>`. An escape now keeps off only the rules it names.";
|
|
38
45
|
readonly escaped: "Escaped: {{reason}}";
|
|
39
46
|
};
|
|
47
|
+
/** A rule's name as a marker writes it: `no-raw-element` for `estiva/no-raw-element`. */
|
|
48
|
+
export declare const shortRuleName: (id: string) => string;
|
|
49
|
+
/**
|
|
50
|
+
* What a marker says, read from a comment's text: the rules it names (none when
|
|
51
|
+
* it names none) and its reason. Null when the comment carries no marker.
|
|
52
|
+
*/
|
|
53
|
+
export declare function readMarker(text: string): {
|
|
54
|
+
rules: string[];
|
|
55
|
+
reason: string;
|
|
56
|
+
} | null;
|
|
40
57
|
export interface EstivaSettings {
|
|
41
58
|
/**
|
|
42
59
|
* Report every sanctioned escape as a message with the id `escaped`, so a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../../src/eslint/escape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAEvC
|
|
1
|
+
{"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../../src/eslint/escape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,aAAa,mBAAmB,CAAA;AAE7C,gDAAgD;AAChD,eAAO,MAAM,UAAU,KAAK,CAAA;AAE5B,2DAA2D;AAC3D,eAAO,MAAM,YAAY,WAAW,CAAA;AAEpC;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;CAKlB,CAAA;AAEV,yFAAyF;AACzF,eAAO,MAAM,aAAa,GAAI,IAAI,MAAM,WAAsC,CAAA;AAE9E;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQnF;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,UAAU,OAAO;IACf,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,GAAG,IAAI,CAAA;IAC/B,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACzB;AAaD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAwC3E"}
|
package/dist/eslint/index.js
CHANGED
|
@@ -9,10 +9,21 @@ var ESCAPE_MARKER = "@estiva-escape";
|
|
|
9
9
|
var MIN_REASON = 10;
|
|
10
10
|
var SETTINGS_KEY = "estiva";
|
|
11
11
|
var ESCAPE_MESSAGES = {
|
|
12
|
-
escapeWithoutReason: `An escape needs its reason, at least ${MIN_REASON} characters: \`// ${ESCAPE_MARKER}: <why this stays>\`. An escape with no reason hides nothing.`,
|
|
13
|
-
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.`,
|
|
12
|
+
escapeWithoutReason: `An escape needs its reason, at least ${MIN_REASON} characters: \`// ${ESCAPE_MARKER}({{rule}}): <why this stays>\`. An escape with no reason hides nothing.`,
|
|
13
|
+
escapeInDirective: `Write the escape as its own comment on the line above: \`// ${ESCAPE_MARKER}({{rule}}): <reason>\`. Inside an eslint-disable comment it switches the rule off instead of recording why.`,
|
|
14
|
+
escapeWithoutRule: `Name the rule this escape keeps off: \`${ESCAPE_MARKER}({{rule}}): <reason>\`. An escape now keeps off only the rules it names.`,
|
|
14
15
|
escaped: "Escaped: {{reason}}"
|
|
15
16
|
};
|
|
17
|
+
var shortRuleName = (id) => id.slice(id.lastIndexOf("/") + 1);
|
|
18
|
+
function readMarker(text) {
|
|
19
|
+
const at = text.indexOf(ESCAPE_MARKER);
|
|
20
|
+
if (at === -1) return null;
|
|
21
|
+
const rest = text.slice(at + ESCAPE_MARKER.length);
|
|
22
|
+
const named = /^\s*\(([^)]*)\)/.exec(rest);
|
|
23
|
+
const rules2 = named ? named[1].split(/[\s,]+/).filter(Boolean).map(shortRuleName) : [];
|
|
24
|
+
const reason = (named ? rest.slice(named[0].length) : rest).replace(/^\s*:/, "").trim();
|
|
25
|
+
return { rules: rules2, reason };
|
|
26
|
+
}
|
|
16
27
|
var DIRECTIVE = /^\s*eslint-disable(?:-next-line|-line)?(?=\s|$)([^]*)$/;
|
|
17
28
|
function directiveRules(rest) {
|
|
18
29
|
const dashes = rest.search(/(?:^|\s)--(?:\s|$)/);
|
|
@@ -26,18 +37,24 @@ function isEscaped(context, node) {
|
|
|
26
37
|
const nodeStart = node.range[0];
|
|
27
38
|
const comment = sourceCode.getAllComments().find((c) => c.loc && c.range && c.loc.end.line === line - 1 && BETWEEN.test(sourceCode.text.slice(c.range[1], nodeStart)));
|
|
28
39
|
if (!comment?.loc) return false;
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
40
|
+
const marker = readMarker(comment.value);
|
|
41
|
+
if (!marker) return false;
|
|
42
|
+
const rule = shortRuleName(context.id);
|
|
31
43
|
const directive = DIRECTIVE.exec(comment.value);
|
|
32
44
|
if (directive) {
|
|
33
45
|
const rules2 = directiveRules(directive[1]);
|
|
34
46
|
if (rules2.length > 0 && !rules2.includes(context.id)) return false;
|
|
35
|
-
context.report({ loc: comment.loc, messageId: "escapeInDirective" });
|
|
47
|
+
context.report({ loc: comment.loc, messageId: "escapeInDirective", data: { rule } });
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (marker.rules.length > 0 && !marker.rules.includes(rule)) return false;
|
|
51
|
+
if (marker.rules.length === 0) {
|
|
52
|
+
context.report({ loc: comment.loc, messageId: "escapeWithoutRule", data: { rule } });
|
|
36
53
|
return false;
|
|
37
54
|
}
|
|
38
|
-
const reason =
|
|
55
|
+
const { reason } = marker;
|
|
39
56
|
if (reason.replace(/\s/g, "").length < MIN_REASON) {
|
|
40
|
-
context.report({ loc: comment.loc, messageId: "escapeWithoutReason" });
|
|
57
|
+
context.report({ loc: comment.loc, messageId: "escapeWithoutReason", data: { rule } });
|
|
41
58
|
return false;
|
|
42
59
|
}
|
|
43
60
|
const settings = context.settings[SETTINGS_KEY];
|
|
@@ -175,6 +192,7 @@ var PLACEMENT = [
|
|
|
175
192
|
// Draws nothing: it names the part, so what is inside can answer its hover (`group-hover:`).
|
|
176
193
|
{ what: "a name for hover", pattern: /^(?:group|peer)(?:\/[\w-]+)?$/ }
|
|
177
194
|
];
|
|
195
|
+
var PLACEMENT_STYLE = /^(?:margin|padding|inset|scrollMargin)(?:Top|Right|Bottom|Left|Block|Inline|BlockStart|BlockEnd|InlineStart|InlineEnd)?$|^(?:width|height|minWidth|minHeight|maxWidth|maxHeight|inlineSize|blockSize|minInlineSize|minBlockSize|maxInlineSize|maxBlockSize)$|^(?:display|flex|flexGrow|flexShrink|flexBasis|order|gap|rowGap|columnGap|alignSelf|justifySelf|placeSelf|gridArea|gridColumn|gridColumnStart|gridColumnEnd|gridRow|gridRowStart|gridRowEnd)$|^(?:position|top|right|bottom|left|zIndex|transform|translate)$/;
|
|
178
196
|
var REACHING_VARIANT = /^\[|^(?:before|after|placeholder|file|marker|selection|first-letter|first-line|backdrop|\*|\*\*)$/;
|
|
179
197
|
function splitClass(token) {
|
|
180
198
|
const variants = [];
|
|
@@ -606,6 +624,7 @@ var noRestyledPart = {
|
|
|
606
624
|
schema: [],
|
|
607
625
|
messages: {
|
|
608
626
|
restyled: "{{classes}} on {{where}} changes how it looks. A part of @estiva-app/ui is placed from outside, never restyled: only space, size, flex and grid, and position pass in.{{use}} A look for what is around it goes on your own element around it.",
|
|
627
|
+
restyledStyle: "{{keys}} in the `style` of {{where}} changes how it looks. A part of @estiva-app/ui is placed from outside, never restyled, by class or by style: only space, size, flex and grid, and position pass in.{{use}} A look for what is around it goes on your own element around it.",
|
|
609
628
|
emptyStatePadding: "{{classes}} pads {{where}}. EmptyState takes no padding (Katerina, 14 September): its room comes from the box its rows live in, so put the padding on that box.",
|
|
610
629
|
...ESCAPE_MESSAGES
|
|
611
630
|
}
|
|
@@ -639,6 +658,19 @@ var noRestyledPart = {
|
|
|
639
658
|
}
|
|
640
659
|
if (padding.length) context.report({ loc: at.loc, messageId: "emptyStatePadding", data: { classes: code(padding), where } });
|
|
641
660
|
};
|
|
661
|
+
const checkStyle = (element, binding, value, at) => {
|
|
662
|
+
if (binding.props !== "all" || value?.type !== "JSXExpressionContainer") return;
|
|
663
|
+
let object = child(value, "expression");
|
|
664
|
+
if (object?.type === "Identifier") object = constantOf(object, scopeOf(object));
|
|
665
|
+
while (object?.type === "TSAsExpression" || object?.type === "TSSatisfiesExpression") object = child(object, "expression");
|
|
666
|
+
if (object?.type !== "ObjectExpression") return;
|
|
667
|
+
const keys = children(object, "properties").map((property) => property?.type === "Property" && !property.computed ? nameOf(child(property, "key")) : void 0).filter((key) => !!key && !key.startsWith("--") && !PLACEMENT_STYLE.test(key));
|
|
668
|
+
if (!keys.length || escaped(element)) return;
|
|
669
|
+
const props = PART_LOOK_PROPS[binding.part];
|
|
670
|
+
const use = props ? ` Use its ${or(props)}.` : ` \`${binding.part}\` has no prop for this yet: ask Katerina, and it gets made in @estiva-app/ui.`;
|
|
671
|
+
const where = binding.via && binding.via !== binding.part ? `\`${binding.via}\` (it hands its props to \`${binding.part}\`)` : `\`${binding.part}\``;
|
|
672
|
+
context.report({ loc: at.loc, messageId: "restyledStyle", data: { keys: code([...new Set(keys)]), where, use } });
|
|
673
|
+
};
|
|
642
674
|
return {
|
|
643
675
|
Program(program) {
|
|
644
676
|
locals = moduleParts(file, program, parser, /* @__PURE__ */ new Set([file])).locals;
|
|
@@ -652,6 +684,7 @@ var noRestyledPart = {
|
|
|
652
684
|
if (attribute3.type === "JSXAttribute") {
|
|
653
685
|
const prop = nameOf(child(attribute3, "name"));
|
|
654
686
|
if (prop && CLASS_PROP.test(prop)) check(element, binding, prop, child(attribute3, "value"), attribute3);
|
|
687
|
+
else if (prop === "style") checkStyle(element, binding, child(attribute3, "value"), attribute3);
|
|
655
688
|
continue;
|
|
656
689
|
}
|
|
657
690
|
let spread = child(attribute3, "argument");
|
|
@@ -958,15 +991,28 @@ var INTERACTIVE = ["onClick", "href", "role", "onKeyDown", "onPointerDown"];
|
|
|
958
991
|
var noHandmadeHeader = {
|
|
959
992
|
meta: {
|
|
960
993
|
type: "problem",
|
|
961
|
-
docs: { description: "A column's header bar drawn by hand where
|
|
994
|
+
docs: { description: "A column's header bar drawn by hand, or the package's header on its own in an app, where Panel is the part" },
|
|
962
995
|
schema: [],
|
|
963
996
|
messages: {
|
|
964
|
-
handmade: "A column's header bar drawn by hand. Use `
|
|
997
|
+
handmade: "A column's header bar drawn by hand. Use `Panel` from @estiva-app/ui: its header (48px, the title, the column's buttons at the right edge in `actions`, a hairline under it) and a body under it that scrolls. For a list beside the page, `ListColumn`.",
|
|
998
|
+
bare: "A panel put together by hand: `ContainerHeader` over a box of your own, which cuts off whatever does not fit. Use `Panel` from @estiva-app/ui: the same header (`title`, `actions`), a body that scrolls whenever it is taller, and a `footer` that stays put. For a list beside the page, `ListColumn`.",
|
|
965
999
|
...ESCAPE_MESSAGES
|
|
966
1000
|
}
|
|
967
1001
|
},
|
|
968
1002
|
create(context) {
|
|
1003
|
+
const bare2 = /* @__PURE__ */ new Set();
|
|
969
1004
|
return {
|
|
1005
|
+
ImportDeclaration(node) {
|
|
1006
|
+
const declaration = node;
|
|
1007
|
+
if (declaration.source.value !== "@estiva-app/ui") return;
|
|
1008
|
+
for (const specifier of declaration.specifiers) if (specifier.type === "ImportSpecifier" && specifier.imported?.name === "ContainerHeader") bare2.add(specifier.local.name);
|
|
1009
|
+
},
|
|
1010
|
+
JSXOpeningElement(node) {
|
|
1011
|
+
const element = node;
|
|
1012
|
+
if (element.name.type !== "JSXIdentifier" || !bare2.has(element.name.name ?? "")) return;
|
|
1013
|
+
if (isEscaped(context, element)) return;
|
|
1014
|
+
context.report({ loc: element.loc, messageId: "bare" });
|
|
1015
|
+
},
|
|
970
1016
|
JSXElement(node) {
|
|
971
1017
|
const column = node;
|
|
972
1018
|
const kids = meaningful(column);
|
|
@@ -1356,7 +1402,7 @@ var noCopiedLook = {
|
|
|
1356
1402
|
};
|
|
1357
1403
|
|
|
1358
1404
|
// src/eslint/no-native-title.ts
|
|
1359
|
-
var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "SectionHeader"];
|
|
1405
|
+
var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "Panel", "SectionHeader"];
|
|
1360
1406
|
var child3 = (node, key) => node[key];
|
|
1361
1407
|
var nameOf4 = (node) => node?.type === "JSXIdentifier" || node?.type === "Identifier" ? node.name : void 0;
|
|
1362
1408
|
var noNativeTitle = {
|
|
@@ -1452,6 +1498,28 @@ function isOn(element, name) {
|
|
|
1452
1498
|
const attr = attribute2(element, name);
|
|
1453
1499
|
return attr !== void 0 && literal(attr) !== false;
|
|
1454
1500
|
}
|
|
1501
|
+
var CLICK_HANDLERS = ["onClick", "onMouseDown", "onMouseUp", "onPointerDown", "onPointerUp", "onDoubleClick"];
|
|
1502
|
+
function onlyStops(handler) {
|
|
1503
|
+
if (!handler || handler.type !== "ArrowFunctionExpression" && handler.type !== "FunctionExpression") return false;
|
|
1504
|
+
const body = handler.body;
|
|
1505
|
+
const calls = body.type === "BlockStatement" ? body.body.map((s) => s.type === "ExpressionStatement" ? s.expression : s) : [body];
|
|
1506
|
+
return calls.length > 0 && calls.every((call) => {
|
|
1507
|
+
if (call.type !== "CallExpression") return false;
|
|
1508
|
+
const callee = call.callee;
|
|
1509
|
+
const property = callee.type === "MemberExpression" ? callee.property : void 0;
|
|
1510
|
+
return property?.type === "Identifier" && (property.name === "stopPropagation" || property.name === "preventDefault");
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
function clickHandler(element) {
|
|
1514
|
+
for (const name of CLICK_HANDLERS) {
|
|
1515
|
+
const attr = attribute2(element, name);
|
|
1516
|
+
if (!attr) continue;
|
|
1517
|
+
const value = attr.value;
|
|
1518
|
+
const handler = value?.type === "JSXExpressionContainer" ? value.expression : void 0;
|
|
1519
|
+
if (!onlyStops(handler)) return name;
|
|
1520
|
+
}
|
|
1521
|
+
return void 0;
|
|
1522
|
+
}
|
|
1455
1523
|
function classify(element) {
|
|
1456
1524
|
if (element.name.type !== "JSXIdentifier" || !element.name.name) return null;
|
|
1457
1525
|
const name = element.name.name;
|
|
@@ -1466,7 +1534,11 @@ function classify(element) {
|
|
|
1466
1534
|
const shown = `<input type="${key}">`;
|
|
1467
1535
|
return part2 ? { report: "raw", element: shown, part: part2 } : { report: "noPart", element: shown };
|
|
1468
1536
|
}
|
|
1469
|
-
if (!Object.hasOwn(RAW_ELEMENT_PARTS, name))
|
|
1537
|
+
if (!Object.hasOwn(RAW_ELEMENT_PARTS, name)) {
|
|
1538
|
+
if (!/^[a-z][a-z0-9]*$/.test(name) || name === "option" || name === "summary") return null;
|
|
1539
|
+
const handler = clickHandler(element);
|
|
1540
|
+
return handler ? { report: "clickable", element: `<${name} ${handler}>` } : null;
|
|
1541
|
+
}
|
|
1470
1542
|
if ((name === "audio" || name === "video") && !isOn(element, "controls")) return null;
|
|
1471
1543
|
if (name === "img" && !attribute2(element, "usemap") && !attribute2(element, "useMap")) return null;
|
|
1472
1544
|
const part = RAW_ELEMENT_PARTS[name];
|
|
@@ -1479,6 +1551,7 @@ var noRawElement = {
|
|
|
1479
1551
|
schema: [],
|
|
1480
1552
|
messages: {
|
|
1481
1553
|
raw: "Use `{{use}}` from @estiva-app/ui instead of a raw {{element}}.{{more}}",
|
|
1554
|
+
clickable: "A clickable {{element}} is a button made by hand: it takes no focus and answers no key. Use `Button` from @estiva-app/ui; for a whole card that opens something, `Card` with `href`. A handler that only stops the click (`e.stopPropagation()`) is fine.",
|
|
1482
1555
|
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.",
|
|
1483
1556
|
...ESCAPE_MESSAGES
|
|
1484
1557
|
}
|
|
@@ -1491,7 +1564,8 @@ var noRawElement = {
|
|
|
1491
1564
|
const found = classify(element);
|
|
1492
1565
|
if (!found) return;
|
|
1493
1566
|
if (isEscaped(context, element)) return;
|
|
1494
|
-
if (found.report === "
|
|
1567
|
+
if (found.report === "clickable") context.report({ loc: element.loc, messageId: "clickable", data: { element: found.element } });
|
|
1568
|
+
else if (found.report === "noPart") context.report({ loc: element.loc, messageId: "noPart", data: { element: found.element } });
|
|
1495
1569
|
else context.report({ loc: element.loc, messageId: "raw", data: { element: found.element, use: found.part.use, more: found.part.more ?? "" } });
|
|
1496
1570
|
}
|
|
1497
1571
|
};
|