@estiva-app/ui 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/Checkbox.d.ts +16 -1
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/ScrollArea.d.ts +6 -0
- package/dist/ScrollArea.d.ts.map +1 -1
- package/dist/eslint/index.d.ts +2 -0
- package/dist/eslint/index.d.ts.map +1 -1
- package/dist/eslint/index.js +459 -2
- package/dist/eslint/index.js.map +4 -4
- package/dist/eslint/no-rebuilt-behaviour.d.ts +88 -0
- package/dist/eslint/no-rebuilt-behaviour.d.ts.map +1 -0
- package/dist/index.js +10 -2
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/src/Checkbox.mdx +20 -1
- package/src/Checkbox.stories.tsx +44 -0
- package/src/Checkbox.test.tsx +63 -0
- package/src/Checkbox.tsx +30 -1
- package/src/ScrollArea.stories.tsx +18 -0
- package/src/ScrollArea.tsx +7 -1
- package/src/eslint/index.test.ts +31 -7
- package/src/eslint/index.ts +10 -3
- package/src/eslint/no-rebuilt-behaviour.test.ts +276 -0
- package/src/eslint/no-rebuilt-behaviour.ts +593 -0
- package/stories/Choosing.mdx +1 -1
package/README.md
CHANGED
|
@@ -67,20 +67,28 @@ export default {
|
|
|
67
67
|
|
|
68
68
|
The UI Guardrails' rules ship with the package, as an ESLint plugin, so an app
|
|
69
69
|
gets a new rule with a version bump. Each rule names the component to use
|
|
70
|
-
instead.
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
instead. There are two:
|
|
71
|
+
|
|
72
|
+
- `estiva/no-raw-element` refuses a raw interactive element (`<a>`, `<input>`,
|
|
73
|
+
`<form>`, `<dialog>`…) and names the part to use, or says the package has none yet.
|
|
74
|
+
- `estiva/no-rebuilt-behaviour` refuses behaviour a part already owns, written by
|
|
75
|
+
hand: a Base UI import, `createPortal`, a click or key listener on the whole
|
|
76
|
+
page, arrow keys compared by hand, a hand-written `role` (`option`, `menu`,
|
|
77
|
+
`dialog`, `alert`…), `tabIndex` 0 or more on a box, and a scrolling box
|
|
78
|
+
(`overflow-auto`, behind any variant). `OWNED_BEHAVIOURS` lists each behaviour,
|
|
79
|
+
the Base UI parts that do it, and the components that own it. It cannot find a
|
|
80
|
+
box that should scroll and does not: that has no class to read.
|
|
73
81
|
|
|
74
82
|
```js
|
|
75
83
|
// eslint.config.js — alongside your own rules
|
|
76
84
|
import estiva from '@estiva-app/ui/eslint'
|
|
77
85
|
export default [
|
|
78
86
|
// …your config, with a parser that reads TSX
|
|
79
|
-
{ files: ['src/**/*.tsx'], ignores: ['**/*.test.tsx'], ...estiva.configs.recommended },
|
|
87
|
+
{ files: ['src/**/*.{ts,tsx}'], ignores: ['**/*.test.{ts,tsx}'], ...estiva.configs.recommended },
|
|
80
88
|
]
|
|
81
89
|
```
|
|
82
90
|
|
|
83
|
-
A place that keeps
|
|
91
|
+
A place that keeps one on purpose says why, on the line above:
|
|
84
92
|
|
|
85
93
|
```tsx
|
|
86
94
|
// @estiva-escape: the reason, at least ten characters
|
package/dist/Checkbox.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
1
2
|
/**
|
|
2
3
|
* A 16px square that fills with the accent when checked — Peek's Checkbox
|
|
3
4
|
* (2026-09-01), verbatim; on Base UI Checkbox since stage 1 of the migration
|
|
@@ -27,6 +28,12 @@
|
|
|
27
28
|
* Read state panel's, where it was written by hand (UIG-7, 16 September). The
|
|
28
29
|
* words keep their colour when the box is disabled (Katerina: "no need").
|
|
29
30
|
*
|
|
31
|
+
* With `row`, the whole row is the target: an optional `leading` picture, the
|
|
32
|
+
* words, the box at the end, and the row fills on hover and while checked — a
|
|
33
|
+
* list you tick several from. The class list is Peek's "Add to Open work" rows,
|
|
34
|
+
* which drew it by hand as a `role="option"` in a list that was not one (UIG-8,
|
|
35
|
+
* Katerina's pick B, 16 September: the same pixels, on the package part).
|
|
36
|
+
*
|
|
30
37
|
* Inside a busy `Form` it is disabled, and looks it (`formBusy.ts`).
|
|
31
38
|
*/
|
|
32
39
|
export interface CheckboxProps {
|
|
@@ -38,10 +45,18 @@ export interface CheckboxProps {
|
|
|
38
45
|
* they name it, so no `aria-label` is needed. `className` stays on the box.
|
|
39
46
|
*/
|
|
40
47
|
label?: string;
|
|
48
|
+
/**
|
|
49
|
+
* The whole row is the target: `leading`, then the words, then the box at
|
|
50
|
+
* the end; the row fills on hover and while checked. For a list you tick
|
|
51
|
+
* several from. Needs `label` and `onChange`; without `onChange` it is ignored.
|
|
52
|
+
*/
|
|
53
|
+
row?: boolean;
|
|
54
|
+
/** In a `row`, a picture before the words: an icon, a status. */
|
|
55
|
+
leading?: ReactNode;
|
|
41
56
|
'aria-label'?: string;
|
|
42
57
|
/** Set by a `Field` with `required`; a caller inside one owes nothing. */
|
|
43
58
|
'aria-required'?: boolean | 'true' | 'false';
|
|
44
59
|
className?: string;
|
|
45
60
|
}
|
|
46
|
-
export declare function Checkbox({ checked, onChange, disabled: ownDisabled, label, className, ...aria }: CheckboxProps): import("react").JSX.Element;
|
|
61
|
+
export declare function Checkbox({ checked, onChange, disabled: ownDisabled, label, row, leading, className, ...aria }: CheckboxProps): import("react").JSX.Element;
|
|
47
62
|
//# sourceMappingURL=Checkbox.d.ts.map
|
package/dist/Checkbox.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../src/Checkbox.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../src/Checkbox.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,iEAAiE;IACjE,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmBD,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAmB,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,aAAa,+BAyDpI"}
|
package/dist/ScrollArea.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ import type { ReactNode, Ref, UIEventHandler } from 'react';
|
|
|
27
27
|
* the thin scrollbar both apps had styled by hand in their `index.css`, drawn
|
|
28
28
|
* once here instead.
|
|
29
29
|
*
|
|
30
|
+
* The bar sits above the content (`z-10`). A sticky row inside — a date line
|
|
31
|
+
* in a conversation, `sticky top-0 z-10` — is at the same level, and the bar
|
|
32
|
+
* comes after the content in the page, so it paints on top. Without it the
|
|
33
|
+
* row hid the bar wherever it crossed it: a gap in the thumb, in Peek's topic
|
|
34
|
+
* and direct-message lists (UIG-8, 16 September).
|
|
35
|
+
*
|
|
30
36
|
* `orientation` says which way the region scrolls; a table that is wider
|
|
31
37
|
* than its box scrolls `horizontal`, a list `vertical` (the default), a board
|
|
32
38
|
* `both`. A vertical region keeps its content no wider than itself, so a
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;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/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ESLint, Linter } from 'eslint';
|
|
2
2
|
export { ESCAPE_MARKER, MIN_REASON, SETTINGS_KEY, isEscaped, type EstivaSettings } from './escape';
|
|
3
|
+
export { OWNED_BEHAVIOURS, type OwnedBehaviour } from './no-rebuilt-behaviour';
|
|
3
4
|
/** The name the configs register the plugin under, so every rule id is `estiva/<rule>`. */
|
|
4
5
|
export declare const PLUGIN_KEY = "estiva";
|
|
5
6
|
declare const plugin: {
|
|
@@ -13,6 +14,7 @@ declare const plugin: {
|
|
|
13
14
|
'component-has-a-page': import("eslint").Rule.RuleModule;
|
|
14
15
|
'component-has-a-story': import("eslint").Rule.RuleModule;
|
|
15
16
|
'no-raw-element': import("eslint").Rule.RuleModule;
|
|
17
|
+
'no-rebuilt-behaviour': import("eslint").Rule.RuleModule;
|
|
16
18
|
};
|
|
17
19
|
configs: {
|
|
18
20
|
recommended: Linter.Config;
|
|
@@ -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;AAO5C,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAClG,OAAO,EAAE,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAI9E,2FAA2F;AAC3F,eAAO,MAAM,UAAU,WAAW,CAAA;AAiClC,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;AA6BhG,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
|
@@ -262,6 +262,458 @@ var noRawElement = {
|
|
|
262
262
|
}
|
|
263
263
|
};
|
|
264
264
|
|
|
265
|
+
// src/eslint/no-rebuilt-behaviour.ts
|
|
266
|
+
var BASE_UI_PARTS = {
|
|
267
|
+
"alert-dialog": { use: "DialogShell", more: " To ask yes or no, `ConfirmDialog`." },
|
|
268
|
+
autocomplete: { use: "CommandPalette" },
|
|
269
|
+
avatar: { use: "Avatar" },
|
|
270
|
+
button: { use: "Button", more: " Icon only, `IconButton`." },
|
|
271
|
+
checkbox: { use: "Checkbox" },
|
|
272
|
+
collapsible: { use: "CollapsibleSection" },
|
|
273
|
+
combobox: { use: "ChipInput" },
|
|
274
|
+
dialog: { use: "DialogShell", more: " To search and act, `CommandPalette`." },
|
|
275
|
+
field: { use: "Field" },
|
|
276
|
+
fieldset: { use: "Form" },
|
|
277
|
+
form: { use: "Form" },
|
|
278
|
+
input: { use: "TextInput", more: " For a search, `SearchInput`." },
|
|
279
|
+
menu: { use: "Menu" },
|
|
280
|
+
popover: { use: "Popover" },
|
|
281
|
+
"preview-card": { use: "PreviewCard" },
|
|
282
|
+
progress: { use: "ProgressBar" },
|
|
283
|
+
"scroll-area": { use: "ScrollArea" },
|
|
284
|
+
select: { use: "Select" },
|
|
285
|
+
separator: { use: "Divider" },
|
|
286
|
+
tabs: { use: "Tabs" },
|
|
287
|
+
toast: { use: "Toast" },
|
|
288
|
+
toggle: { use: "Reaction" },
|
|
289
|
+
toolbar: { use: "Toolbar" },
|
|
290
|
+
tooltip: { use: "Tooltip", more: " Around a control, `WithTooltip`." },
|
|
291
|
+
accordion: null,
|
|
292
|
+
"checkbox-group": null,
|
|
293
|
+
"context-menu": null,
|
|
294
|
+
drawer: null,
|
|
295
|
+
menubar: null,
|
|
296
|
+
meter: null,
|
|
297
|
+
"navigation-menu": null,
|
|
298
|
+
"number-field": null,
|
|
299
|
+
"otp-field": null,
|
|
300
|
+
radio: null,
|
|
301
|
+
"radio-group": null,
|
|
302
|
+
slider: null,
|
|
303
|
+
switch: null,
|
|
304
|
+
"toggle-group": null
|
|
305
|
+
};
|
|
306
|
+
var LIST_PART = {
|
|
307
|
+
use: "Select",
|
|
308
|
+
more: " To pick several, `ChipInput`; to search and act, `CommandPalette`; to tick several in a list, `Checkbox` with `row`."
|
|
309
|
+
};
|
|
310
|
+
var MESSAGE_PART = { use: "FieldLine", more: " For a notice, `Banner`; for a message that comes and goes, `Toast`." };
|
|
311
|
+
var ROLE_PARTS = {
|
|
312
|
+
dialog: { thing: "dialog", part: { use: "DialogShell", more: " To ask yes or no, `ConfirmDialog`." } },
|
|
313
|
+
alertdialog: { thing: "dialog", part: { use: "ConfirmDialog" } },
|
|
314
|
+
menu: { thing: "menu", part: { use: "Menu" } },
|
|
315
|
+
menuitem: { thing: "menu", part: { use: "Menu" } },
|
|
316
|
+
menuitemcheckbox: { thing: "menu", part: { use: "Menu" } },
|
|
317
|
+
menuitemradio: { thing: "menu", part: { use: "Menu" } },
|
|
318
|
+
listbox: { thing: "list", part: LIST_PART },
|
|
319
|
+
option: { thing: "list", part: LIST_PART },
|
|
320
|
+
combobox: { thing: "list", part: LIST_PART },
|
|
321
|
+
tablist: { thing: "set of tabs", part: { use: "Tabs" } },
|
|
322
|
+
tab: { thing: "set of tabs", part: { use: "Tabs" } },
|
|
323
|
+
tabpanel: { thing: "set of tabs", part: { use: "Tabs" } },
|
|
324
|
+
toolbar: { thing: "toolbar", part: { use: "Toolbar" } },
|
|
325
|
+
progressbar: { thing: "progress bar", part: { use: "ProgressBar" } },
|
|
326
|
+
checkbox: { thing: "tick box", part: { use: "Checkbox" } },
|
|
327
|
+
separator: { thing: "divider", part: { use: "Divider" } },
|
|
328
|
+
button: { thing: "button", part: { use: "Button", more: " Icon only, `IconButton`." } },
|
|
329
|
+
link: { thing: "link", part: { use: "Link" } },
|
|
330
|
+
tooltip: { thing: "tooltip", part: { use: "Tooltip", more: " Around a control, `WithTooltip`." } },
|
|
331
|
+
alert: { thing: "message", part: MESSAGE_PART },
|
|
332
|
+
status: { thing: "message", part: MESSAGE_PART },
|
|
333
|
+
menubar: { thing: "menu bar", part: null },
|
|
334
|
+
switch: { thing: "switch", part: null },
|
|
335
|
+
radio: { thing: "radio button", part: null },
|
|
336
|
+
radiogroup: { thing: "radio group", part: null },
|
|
337
|
+
slider: { thing: "slider", part: null },
|
|
338
|
+
spinbutton: { thing: "number field", part: null },
|
|
339
|
+
meter: { thing: "meter", part: null }
|
|
340
|
+
};
|
|
341
|
+
var WALKING_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"];
|
|
342
|
+
var PAGE_EVENTS = {
|
|
343
|
+
mousedown: "press",
|
|
344
|
+
pointerdown: "press",
|
|
345
|
+
click: "press",
|
|
346
|
+
touchstart: "press",
|
|
347
|
+
keydown: "key",
|
|
348
|
+
keyup: "key",
|
|
349
|
+
keypress: "key",
|
|
350
|
+
focusin: "focus",
|
|
351
|
+
focusout: "focus",
|
|
352
|
+
scroll: "follow",
|
|
353
|
+
resize: "follow"
|
|
354
|
+
};
|
|
355
|
+
var INTERACTIVE_ELEMENTS = /* @__PURE__ */ new Set(["a", "button", "input", "select", "textarea", "summary", "iframe", "embed", "object", "audio", "video"]);
|
|
356
|
+
var SCROLL_CLASS = /^overflow(?:-[xy])?-(?:auto|scroll)$/;
|
|
357
|
+
var uses = (parts) => [
|
|
358
|
+
...new Set(parts.flatMap((p) => p ? [p.use, ...[...(p.more ?? "").matchAll(/`([A-Z]\w+)`/g)].map((m) => m[1])] : []))
|
|
359
|
+
];
|
|
360
|
+
var OWNED_BEHAVIOURS = [
|
|
361
|
+
{
|
|
362
|
+
id: "base-ui",
|
|
363
|
+
behaviour: "Is built on Base UI",
|
|
364
|
+
baseUi: Object.keys(BASE_UI_PARTS),
|
|
365
|
+
owners: uses(Object.values(BASE_UI_PARTS)),
|
|
366
|
+
reads: "an import from @base-ui/react (and the old @base-ui-components spelling)"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: "portal",
|
|
370
|
+
behaviour: "Floats on top of the page",
|
|
371
|
+
baseUi: ["Dialog", "AlertDialog", "Popover", "Menu", "Select", "Combobox", "Autocomplete", "Tooltip", "PreviewCard", "Toast"],
|
|
372
|
+
owners: ["DialogShell", "ConfirmDialog", "CommandPalette", "Popover", "Menu", "Select", "ChipInput", "Tooltip", "PreviewCard", "Toast"],
|
|
373
|
+
reads: "createPortal, called, or imported and never called"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: "press-outside",
|
|
377
|
+
behaviour: "Closes on a press outside",
|
|
378
|
+
baseUi: ["Dialog", "AlertDialog", "Popover", "Menu", "Select", "Combobox", "Tooltip", "PreviewCard"],
|
|
379
|
+
owners: ["DialogShell", "Popover", "Menu", "Select", "PreviewCard"],
|
|
380
|
+
reads: "a mousedown, pointerdown, click or touchstart listener on window or document"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: "page-keys",
|
|
384
|
+
behaviour: "Closes on Escape, and takes its keys, by itself",
|
|
385
|
+
baseUi: ["Dialog", "AlertDialog", "Popover", "Menu", "Select", "Combobox", "Tooltip", "PreviewCard", "Toast"],
|
|
386
|
+
owners: ["DialogShell", "Popover", "Menu", "Select", "Tabs", "Toolbar"],
|
|
387
|
+
reads: "a keydown, keyup or keypress listener on window or document"
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: "focus",
|
|
391
|
+
behaviour: "Holds focus inside while open, and gives it back",
|
|
392
|
+
baseUi: ["Dialog", "AlertDialog", "Popover", "Menu", "Select", "Combobox"],
|
|
393
|
+
owners: ["DialogShell", "CommandPalette"],
|
|
394
|
+
reads: "a focusin or focusout listener on window or document; the Tab key compared by hand"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
id: "scroll-lock",
|
|
398
|
+
behaviour: "Stops the page behind it scrolling",
|
|
399
|
+
baseUi: ["Dialog", "AlertDialog"],
|
|
400
|
+
owners: ["DialogShell"],
|
|
401
|
+
reads: "overflow written into the style of document.body or document.documentElement"
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "follow",
|
|
405
|
+
behaviour: "Stays attached to its anchor on scroll and resize",
|
|
406
|
+
baseUi: ["Popover", "Menu", "Select", "Combobox", "Tooltip", "PreviewCard", "Toast"],
|
|
407
|
+
owners: ["Popover", "Menu", "Select", "Tooltip", "PreviewCard"],
|
|
408
|
+
reads: "a scroll or resize listener on window or document"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
id: "walking",
|
|
412
|
+
behaviour: "Moves through its items with the arrow keys",
|
|
413
|
+
baseUi: ["Menu", "Select", "Combobox", "Autocomplete", "Tabs", "Toolbar"],
|
|
414
|
+
owners: ["Menu", "Select", "ChipInput", "CommandPalette", "Tabs", "Toolbar"],
|
|
415
|
+
reads: `a key compared by hand with ${WALKING_KEYS.join(", ")}`
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: "role",
|
|
419
|
+
behaviour: "Says what it is to assistive technology",
|
|
420
|
+
baseUi: ["Dialog", "AlertDialog", "Menu", "Select", "Combobox", "Tabs", "Toolbar", "Progress", "Checkbox", "Separator", "Button"],
|
|
421
|
+
owners: uses(Object.values(ROLE_PARTS).map((r) => r.part)),
|
|
422
|
+
reads: `role written by hand: ${Object.keys(ROLE_PARTS).join(", ")}`
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
id: "tab-stop",
|
|
426
|
+
behaviour: "Is reachable with Tab",
|
|
427
|
+
baseUi: ["Button", "Toggle", "Checkbox", "Tabs", "Toolbar", "ScrollArea"],
|
|
428
|
+
owners: ["Button", "IconButton", "Link"],
|
|
429
|
+
reads: "tabIndex that can be 0 or more, on an element that is not a control"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: "scroll",
|
|
433
|
+
behaviour: "Scrolls with our scrollbar",
|
|
434
|
+
baseUi: ["ScrollArea"],
|
|
435
|
+
owners: ["ScrollArea"],
|
|
436
|
+
reads: "overflow-auto or overflow-scroll (x or y, behind any variant) in a string, or overflow auto or scroll in a style"
|
|
437
|
+
}
|
|
438
|
+
];
|
|
439
|
+
var PAGE_TARGETS = /* @__PURE__ */ new Set(["window", "document", "globalThis", "self", "document.body", "document.documentElement", "window.document"]);
|
|
440
|
+
function anchorOf(node) {
|
|
441
|
+
if (node.type === "FunctionDeclaration") {
|
|
442
|
+
return node.parent && /^Export/.test(node.parent.type) ? node.parent : node;
|
|
443
|
+
}
|
|
444
|
+
let child = node;
|
|
445
|
+
for (let p = node.parent; p; child = p, p = p.parent) {
|
|
446
|
+
if (p.type === "JSXOpeningElement" || p.type === "Property" || p.type === "MethodDefinition" || p.type === "PropertyDefinition") return p;
|
|
447
|
+
if (p.type === "ArrayExpression") return child;
|
|
448
|
+
if (p.type !== "BlockStatement" && /(Statement|Declaration)$/.test(p.type)) return p;
|
|
449
|
+
}
|
|
450
|
+
return node;
|
|
451
|
+
}
|
|
452
|
+
function enclosingFunction(node) {
|
|
453
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
454
|
+
if (p.type === "FunctionDeclaration" || p.type === "FunctionExpression" || p.type === "ArrowFunctionExpression") return p;
|
|
455
|
+
}
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
function stringOf(node) {
|
|
459
|
+
if (!node) return void 0;
|
|
460
|
+
if (node.type === "Literal" && typeof node.value === "string") return node.value;
|
|
461
|
+
const quasis = node.quasis;
|
|
462
|
+
const expressions = node.expressions;
|
|
463
|
+
if (node.type === "TemplateLiteral" && expressions?.length === 0) return quasis?.[0]?.value.cooked ?? void 0;
|
|
464
|
+
return void 0;
|
|
465
|
+
}
|
|
466
|
+
function stringsOf(node) {
|
|
467
|
+
if (!node) return [];
|
|
468
|
+
if (node.type === "JSXExpressionContainer") return stringsOf(node.expression);
|
|
469
|
+
const s = stringOf(node);
|
|
470
|
+
if (s !== void 0) return [s];
|
|
471
|
+
if (node.type === "ConditionalExpression") return [...stringsOf(node.consequent), ...stringsOf(node.alternate)];
|
|
472
|
+
if (node.type === "LogicalExpression") return [...stringsOf(node.left), ...stringsOf(node.right)];
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
function canBeTabStop(node) {
|
|
476
|
+
if (!node) return false;
|
|
477
|
+
if (node.type === "JSXExpressionContainer") return canBeTabStop(node.expression);
|
|
478
|
+
if (node.type === "Literal") {
|
|
479
|
+
if (typeof node.value === "number") return node.value >= 0;
|
|
480
|
+
if (typeof node.value === "string") return /^\s*\d+\s*$/.test(node.value);
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
if (node.type === "ConditionalExpression") return canBeTabStop(node.consequent) || canBeTabStop(node.alternate);
|
|
484
|
+
if (node.type === "LogicalExpression") return canBeTabStop(node.left) || canBeTabStop(node.right);
|
|
485
|
+
if (node.type === "TSAsExpression" || node.type === "TSNonNullExpression") return canBeTabStop(node.expression);
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
function utilityOf(token) {
|
|
489
|
+
let depth = 0;
|
|
490
|
+
let last = -1;
|
|
491
|
+
for (let i = 0; i < token.length; i++) {
|
|
492
|
+
const c = token[i];
|
|
493
|
+
if (c === "[") depth++;
|
|
494
|
+
else if (c === "]") depth--;
|
|
495
|
+
else if (c === ":" && depth === 0) last = i;
|
|
496
|
+
}
|
|
497
|
+
return token.slice(last + 1).replace(/^!/, "").replace(/!$/, "");
|
|
498
|
+
}
|
|
499
|
+
function baseUiModule(source) {
|
|
500
|
+
const m = /^@base-ui(?:-components)?\/([^/]+)(?:\/(.+))?$/.exec(source);
|
|
501
|
+
if (!m) return void 0;
|
|
502
|
+
const [, pkg, sub] = m;
|
|
503
|
+
return pkg === "react" ? sub ?? "" : `${pkg}${sub ? `/${sub}` : ""}`;
|
|
504
|
+
}
|
|
505
|
+
function isKeySubject(node) {
|
|
506
|
+
if (!node) return false;
|
|
507
|
+
if (node.type === "Identifier") return node.name === "key" || node.name === "code";
|
|
508
|
+
if (node.type !== "MemberExpression" || node.computed) return false;
|
|
509
|
+
const property = node.property;
|
|
510
|
+
return property.type === "Identifier" && (property.name === "key" || property.name === "code");
|
|
511
|
+
}
|
|
512
|
+
function pageTarget(node, text) {
|
|
513
|
+
return (node.type === "Identifier" || node.type === "MemberExpression") && PAGE_TARGETS.has(text);
|
|
514
|
+
}
|
|
515
|
+
var noRebuiltBehaviour = {
|
|
516
|
+
meta: {
|
|
517
|
+
type: "problem",
|
|
518
|
+
docs: { description: "Behaviour @estiva-app/ui already owns, rebuilt by hand in an app" },
|
|
519
|
+
schema: [],
|
|
520
|
+
messages: {
|
|
521
|
+
baseUi: "Only @estiva-app/ui imports Base UI (`{{module}}`). Use `{{use}}` from @estiva-app/ui.{{more}}",
|
|
522
|
+
baseUiNoPart: "Only @estiva-app/ui imports Base UI (`{{module}}`), and it has no part built on it yet. Do not build one here: ask Katerina, and it gets made in @estiva-app/ui.",
|
|
523
|
+
portal: "A layer put on top of the page by hand (`createPortal`). Every floating part of @estiva-app/ui carries its own: `DialogShell` for a dialog, `Popover` for a panel, `Menu`, `Select`, `Tooltip`, `PreviewCard`, `Toast`.",
|
|
524
|
+
press: "A `{{event}}` listener on `{{target}}`: closing on a press outside, by hand. `Popover`, `Menu`, `Select`, `DialogShell` and `PreviewCard` from @estiva-app/ui close themselves.",
|
|
525
|
+
key: "A `{{event}}` listener on `{{target}}`: keys taken for the whole page, by hand. `DialogShell`, `Popover`, `Menu` and `Select` from @estiva-app/ui close on Escape themselves; `Menu`, `Select`, `Tabs` and `Toolbar` move with the arrow keys.",
|
|
526
|
+
focus: "A `{{event}}` listener on `{{target}}`: focus held by hand. `DialogShell` and `CommandPalette` from @estiva-app/ui keep focus inside and give it back.",
|
|
527
|
+
follow: "A `{{event}}` listener on `{{target}}`: following an anchor by hand. `Popover`, `Menu`, `Select`, `Tooltip` and `PreviewCard` from @estiva-app/ui stay attached to theirs.",
|
|
528
|
+
scrollLock: "The page's scroll locked by hand. `DialogShell` from @estiva-app/ui stops the page scrolling while it is open, and gives it back.",
|
|
529
|
+
walking: "Arrow keys handled by hand (`{{key}}`). `Menu`, `Select`, `ChipInput`, `CommandPalette`, `Tabs` and `Toolbar` from @estiva-app/ui move through their items themselves.",
|
|
530
|
+
tabKey: "The Tab key handled by hand. `DialogShell` and `CommandPalette` from @estiva-app/ui keep focus inside themselves.",
|
|
531
|
+
role: 'A hand-written `role="{{role}}"` is a hand-made {{thing}}. Use `{{use}}` from @estiva-app/ui.{{more}}',
|
|
532
|
+
roleNoPart: 'A hand-written `role="{{role}}"` is a hand-made {{thing}}, and @estiva-app/ui has no part for one yet. Do not build one here: ask Katerina, and it gets made in @estiva-app/ui.',
|
|
533
|
+
tabStop: "`tabIndex={{value}}` makes a `<{{element}}>` a Tab stop by hand. Use `Button`, `IconButton` or `Link` from @estiva-app/ui, which are reachable already.",
|
|
534
|
+
scrollClass: "`{{token}}` scrolls with the browser's scrollbar. Use `ScrollArea` from @estiva-app/ui, which draws ours.",
|
|
535
|
+
scrollStyle: "`{{style}}` scrolls with the browser's scrollbar. Use `ScrollArea` from @estiva-app/ui, which draws ours.",
|
|
536
|
+
...ESCAPE_MESSAGES
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
create(context) {
|
|
540
|
+
const { sourceCode } = context;
|
|
541
|
+
const text = (node) => sourceCode.getText(node);
|
|
542
|
+
const escapes = /* @__PURE__ */ new Map();
|
|
543
|
+
const escaped = (anchor) => {
|
|
544
|
+
if (!escapes.has(anchor)) escapes.set(anchor, isEscaped(context, anchor));
|
|
545
|
+
return escapes.get(anchor) === true;
|
|
546
|
+
};
|
|
547
|
+
const seen = /* @__PURE__ */ new Set();
|
|
548
|
+
const report = (anchor, at, messageId, data = {}, once = `${at.range[0]}`) => {
|
|
549
|
+
const key = `${anchor.range[0]}|${messageId}|${once}`;
|
|
550
|
+
if (seen.has(key)) return;
|
|
551
|
+
seen.add(key);
|
|
552
|
+
if (escaped(anchor)) return;
|
|
553
|
+
context.report({ loc: at.loc, messageId, data });
|
|
554
|
+
};
|
|
555
|
+
const importSource = (node, source) => {
|
|
556
|
+
const value = stringOf(source);
|
|
557
|
+
if (value === void 0) return;
|
|
558
|
+
const module = baseUiModule(value);
|
|
559
|
+
if (module === void 0) return;
|
|
560
|
+
const part = Object.hasOwn(BASE_UI_PARTS, module) ? BASE_UI_PARTS[module] : null;
|
|
561
|
+
const anchor = anchorOf(node);
|
|
562
|
+
if (part) report(anchor, node, "baseUi", { module: value, use: part.use, more: part.more ?? "" });
|
|
563
|
+
else report(anchor, node, "baseUiNoPart", { module: value });
|
|
564
|
+
};
|
|
565
|
+
const keyCompared = (node, key) => {
|
|
566
|
+
if (key === void 0) return;
|
|
567
|
+
const fn = enclosingFunction(node);
|
|
568
|
+
const anchor = anchorOf(fn ?? node);
|
|
569
|
+
if (WALKING_KEYS.includes(key)) report(anchor, node, "walking", { key }, `walking@${fn?.range[0] ?? node.range[0]}`);
|
|
570
|
+
else if (key === "Tab") report(anchor, node, "tabKey", {}, `tab@${fn?.range[0] ?? node.range[0]}`);
|
|
571
|
+
};
|
|
572
|
+
const classTokens = (node, value) => {
|
|
573
|
+
for (const token of value.split(/\s+/)) {
|
|
574
|
+
if (token && SCROLL_CLASS.test(utilityOf(token))) report(anchorOf(node), node, "scrollClass", { token }, `class@${token}`);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
const portalImports = /* @__PURE__ */ new Map();
|
|
578
|
+
let portalCalled = false;
|
|
579
|
+
return {
|
|
580
|
+
ImportDeclaration(ruleNode) {
|
|
581
|
+
const node = ruleNode;
|
|
582
|
+
importSource(node, node.source);
|
|
583
|
+
if (stringOf(node.source) !== "react-dom") return;
|
|
584
|
+
for (const specifier of node.specifiers) {
|
|
585
|
+
const imported = specifier.imported;
|
|
586
|
+
if (specifier.type === "ImportSpecifier" && imported?.name === "createPortal") portalImports.set(specifier.local.name, node);
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
ExportNamedDeclaration(ruleNode) {
|
|
590
|
+
const node = ruleNode;
|
|
591
|
+
if (node.source) importSource(node, node.source);
|
|
592
|
+
},
|
|
593
|
+
ExportAllDeclaration(ruleNode) {
|
|
594
|
+
const node = ruleNode;
|
|
595
|
+
importSource(node, node.source);
|
|
596
|
+
},
|
|
597
|
+
ImportExpression(ruleNode) {
|
|
598
|
+
const node = ruleNode;
|
|
599
|
+
importSource(node, node.source);
|
|
600
|
+
},
|
|
601
|
+
CallExpression(ruleNode) {
|
|
602
|
+
const node = ruleNode;
|
|
603
|
+
const callee = node.callee;
|
|
604
|
+
const args = node.arguments;
|
|
605
|
+
if (callee.type === "Identifier" && callee.name === "require") importSource(node, args[0]);
|
|
606
|
+
if (callee.type === "Identifier" && portalImports.has(callee.name)) {
|
|
607
|
+
portalCalled = true;
|
|
608
|
+
report(anchorOf(node), node, "portal");
|
|
609
|
+
}
|
|
610
|
+
if (callee.type !== "MemberExpression") return;
|
|
611
|
+
const object = callee.object;
|
|
612
|
+
const property = callee.property;
|
|
613
|
+
if (property.type !== "Identifier") return;
|
|
614
|
+
if (property.name === "createPortal") {
|
|
615
|
+
report(anchorOf(node), node, "portal");
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (property.name === "addEventListener") {
|
|
619
|
+
const target = text(object);
|
|
620
|
+
if (!pageTarget(object, target)) return;
|
|
621
|
+
const event = stringOf(args[0]);
|
|
622
|
+
const group = event === void 0 ? void 0 : PAGE_EVENTS[event];
|
|
623
|
+
if (!event || !group) return;
|
|
624
|
+
report(anchorOf(node), node, group, { event, target });
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (property.name === "includes" && object.type === "ArrayExpression" && isKeySubject(args[0])) {
|
|
628
|
+
for (const element of object.elements) keyCompared(node, stringOf(element));
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
AssignmentExpression(ruleNode) {
|
|
632
|
+
const node = ruleNode;
|
|
633
|
+
const left = node.left;
|
|
634
|
+
if (left.type !== "MemberExpression") return;
|
|
635
|
+
const property = left.property;
|
|
636
|
+
const object = left.object;
|
|
637
|
+
const handler = property.type === "Identifier" ? /^on([a-z]+)$/.exec(property.name) : null;
|
|
638
|
+
if (handler && pageTarget(object, text(object)) && PAGE_EVENTS[handler[1]]) {
|
|
639
|
+
report(anchorOf(node), node, PAGE_EVENTS[handler[1]], { event: handler[1], target: text(object) });
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (/^document\.(?:body|documentElement)\.style\.overflow[XY]?$/.test(text(left))) report(anchorOf(node), node, "scrollLock");
|
|
643
|
+
},
|
|
644
|
+
BinaryExpression(ruleNode) {
|
|
645
|
+
const node = ruleNode;
|
|
646
|
+
if (!["===", "==", "!==", "!="].includes(node.operator)) return;
|
|
647
|
+
const left = node.left;
|
|
648
|
+
const right = node.right;
|
|
649
|
+
if (isKeySubject(left)) keyCompared(node, stringOf(right));
|
|
650
|
+
else if (isKeySubject(right)) keyCompared(node, stringOf(left));
|
|
651
|
+
},
|
|
652
|
+
SwitchCase(ruleNode) {
|
|
653
|
+
const node = ruleNode;
|
|
654
|
+
const statement2 = node.parent;
|
|
655
|
+
if (node.test && isKeySubject(statement2.discriminant)) keyCompared(node, stringOf(node.test));
|
|
656
|
+
},
|
|
657
|
+
JSXAttribute(ruleNode) {
|
|
658
|
+
const node = ruleNode;
|
|
659
|
+
const nameNode = node.name;
|
|
660
|
+
if (nameNode.type !== "JSXIdentifier") return;
|
|
661
|
+
const name = nameNode.name;
|
|
662
|
+
const element = node.parent;
|
|
663
|
+
const value = node.value;
|
|
664
|
+
if (name === "role") {
|
|
665
|
+
for (const role of stringsOf(value).flatMap((v) => v.trim().split(/\s+/))) {
|
|
666
|
+
if (!Object.hasOwn(ROLE_PARTS, role)) continue;
|
|
667
|
+
const { thing, part } = ROLE_PARTS[role];
|
|
668
|
+
if (part) report(element, node, "role", { role, thing, use: part.use, more: part.more ?? "" });
|
|
669
|
+
else report(element, node, "roleNoPart", { role, thing });
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (name === "tabIndex" || name === "tabindex") {
|
|
675
|
+
const tag = element.name;
|
|
676
|
+
if (tag.type !== "JSXIdentifier" || !/^[a-z]/.test(tag.name) || INTERACTIVE_ELEMENTS.has(tag.name)) return;
|
|
677
|
+
const attributes = element.attributes;
|
|
678
|
+
const editable = attributes.some((a) => a.type === "JSXAttribute" && /^contenteditable$/i.test(a.name.name ?? ""));
|
|
679
|
+
if (editable || !canBeTabStop(value)) return;
|
|
680
|
+
report(element, node, "tabStop", { value: value ? text(value).replace(/^\{|\}$/g, "") : "", element: tag.name });
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (name === "style" && value?.type === "JSXExpressionContainer") {
|
|
684
|
+
const object = value.expression;
|
|
685
|
+
if (object.type !== "ObjectExpression") return;
|
|
686
|
+
for (const property of object.properties) {
|
|
687
|
+
if (property.type !== "Property") continue;
|
|
688
|
+
const key = property.key;
|
|
689
|
+
const keyName = key.type === "Identifier" ? key.name : stringOf(key);
|
|
690
|
+
if (!keyName || !/^overflow[XY]?$/.test(keyName)) continue;
|
|
691
|
+
const setting = stringOf(property.value);
|
|
692
|
+
if (setting === "auto" || setting === "scroll") report(element, property, "scrollStyle", { style: `${keyName}: '${setting}'` }, `style@${keyName}`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
Literal(ruleNode) {
|
|
697
|
+
const node = ruleNode;
|
|
698
|
+
if (typeof node.value !== "string") return;
|
|
699
|
+
const parent = node.parent;
|
|
700
|
+
if (parent && /^(Import|Export)/.test(parent.type)) return;
|
|
701
|
+
if (parent?.type === "TSLiteralType" || parent?.type === "TSExternalModuleReference") return;
|
|
702
|
+
classTokens(node, node.value);
|
|
703
|
+
},
|
|
704
|
+
TemplateElement(ruleNode) {
|
|
705
|
+
const node = ruleNode;
|
|
706
|
+
const cooked = node.value.cooked;
|
|
707
|
+
if (cooked && node.parent) classTokens(node.parent, cooked);
|
|
708
|
+
},
|
|
709
|
+
"Program:exit"() {
|
|
710
|
+
if (portalCalled) return;
|
|
711
|
+
for (const declaration of new Set(portalImports.values())) report(anchorOf(declaration), declaration, "portal");
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
|
|
265
717
|
// src/eslint/raw-element-outside-a-wrapper.ts
|
|
266
718
|
var RAW_ELEMENTS = /* @__PURE__ */ new Set(["a", "button", "input", "textarea", "select", "dialog", "form", "label"]);
|
|
267
719
|
var rawElementOutsideAWrapper = {
|
|
@@ -302,7 +754,8 @@ var rawElementOutsideAWrapper = {
|
|
|
302
754
|
var { version } = createRequire(import.meta.url)("../../package.json");
|
|
303
755
|
var PLUGIN_KEY = "estiva";
|
|
304
756
|
var appRules = {
|
|
305
|
-
"no-raw-element": noRawElement
|
|
757
|
+
"no-raw-element": noRawElement,
|
|
758
|
+
"no-rebuilt-behaviour": noRebuiltBehaviour
|
|
306
759
|
};
|
|
307
760
|
var packageRules = {
|
|
308
761
|
"raw-element-outside-a-wrapper": rawElementOutsideAWrapper,
|
|
@@ -321,7 +774,10 @@ var PACKAGE_RULE_IDS = Object.keys(packageRules).map((name) => `${PLUGIN_KEY}/${
|
|
|
321
774
|
plugin.configs.recommended = {
|
|
322
775
|
name: "@estiva-app/ui/recommended",
|
|
323
776
|
plugins: { [PLUGIN_KEY]: plugin },
|
|
324
|
-
rules: {
|
|
777
|
+
rules: {
|
|
778
|
+
[`${PLUGIN_KEY}/no-raw-element`]: "error",
|
|
779
|
+
[`${PLUGIN_KEY}/no-rebuilt-behaviour`]: "error"
|
|
780
|
+
}
|
|
325
781
|
};
|
|
326
782
|
plugin.configs.strict = {
|
|
327
783
|
name: "@estiva-app/ui/strict",
|
|
@@ -356,6 +812,7 @@ export {
|
|
|
356
812
|
APP_RULE_IDS,
|
|
357
813
|
ESCAPE_MARKER,
|
|
358
814
|
MIN_REASON,
|
|
815
|
+
OWNED_BEHAVIOURS,
|
|
359
816
|
PACKAGE_RULE_IDS,
|
|
360
817
|
PLUGIN_KEY,
|
|
361
818
|
SETTINGS_KEY,
|