@estiva-app/ui 0.48.0 → 0.49.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 +6 -0
- package/dist/DialogShell.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 +84 -25
- package/dist/eslint/index.js.map +4 -4
- package/dist/eslint/no-field-outside-a-form.d.ts +35 -0
- package/dist/eslint/no-field-outside-a-form.d.ts.map +1 -0
- package/dist/eslint/rule-ids.d.ts +1 -1
- package/dist/eslint/rule-ids.d.ts.map +1 -1
- package/dist/gates/{chunk-DZVDOJGV.js → chunk-FIBZLYQI.js} +3 -2
- package/dist/gates/{chunk-DZVDOJGV.js.map → chunk-FIBZLYQI.js.map} +2 -2
- package/dist/gates/create-app.js +1 -1
- package/dist/gates/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/registry.json +1 -1
- package/src/DialogShell.stories.tsx +18 -0
- package/src/DialogShell.tsx +4 -2
- package/src/Form.mdx +4 -0
- package/src/eslint/index.test.ts +6 -4
- package/src/eslint/index.ts +4 -0
- package/src/eslint/no-field-outside-a-form.test.ts +63 -0
- package/src/eslint/no-field-outside-a-form.ts +92 -0
- package/src/eslint/rule-ids.ts +1 -0
- package/src/gates/create-app.test.ts +1 -1
- package/src/gates/gates.test.ts +1 -1
package/README.md
CHANGED
|
@@ -102,6 +102,12 @@ instead. There are seven — six block, one warns:
|
|
|
102
102
|
`registry.json`, and the app's own exported components; the fix is to use
|
|
103
103
|
the part. In the package the fix is to write the look once in `src/looks.ts`.
|
|
104
104
|
A look written once as an exported `…_CLASSES` constant counts as a look too.
|
|
105
|
+
- `estiva/no-field-outside-a-form` refuses a `Field`, `TextInput` or
|
|
106
|
+
`Textarea` with no `Form` around it in its file (the palette's
|
|
107
|
+
`CommandPaletteForm` is one), and names `Form`: without it nothing owns
|
|
108
|
+
sending the field. In a dialog that is `DialogShell` with a `Form`, never a
|
|
109
|
+
field inside `ConfirmDialog`. `EditableText` and `SearchInput` pass; a
|
|
110
|
+
component that draws fields for its caller's form says so with an escape.
|
|
105
111
|
|
|
106
112
|
```js
|
|
107
113
|
// eslint.config.js — alongside your own rules
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DialogShell.d.ts","sourceRoot":"","sources":["../src/DialogShell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAS9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;8CAC0C;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB;yDACqD;IACrD,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB;uCACmC;IACnC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;IACnB,0GAA0G;IAC1G,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,KAAW,EAAE,KAAa,EAAE,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"DialogShell.d.ts","sourceRoot":"","sources":["../src/DialogShell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAS9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;8CAC0C;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB;yDACqD;IACrD,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB;uCACmC;IACnC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;IACnB,0GAA0G;IAC1G,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,KAAW,EAAE,KAAa,EAAE,EAAE,gBAAgB,+BAyG1J"}
|
package/dist/eslint/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { OWNED_BEHAVIOURS, type OwnedBehaviour } from './no-rebuilt-behaviour';
|
|
|
5
5
|
export { PART_LOOK_PROPS, PLACEMENT } from './no-restyled-part';
|
|
6
6
|
export { PARTS_WITH_A_TITLE } from './no-native-title';
|
|
7
7
|
export { MIN_COVER, MIN_SHARED } from './looks-of';
|
|
8
|
+
export { FIELD_PARTS } from './no-field-outside-a-form';
|
|
8
9
|
export { APP_RULE_IDS, PLUGIN_KEY };
|
|
9
10
|
declare const plugin: {
|
|
10
11
|
meta: {
|
|
@@ -23,6 +24,7 @@ declare const plugin: {
|
|
|
23
24
|
'no-copied-look': import("eslint").Rule.RuleModule;
|
|
24
25
|
'no-raw-element': import("eslint").Rule.RuleModule;
|
|
25
26
|
'no-rebuilt-behaviour': import("eslint").Rule.RuleModule;
|
|
27
|
+
'no-field-outside-a-form': import("eslint").Rule.RuleModule;
|
|
26
28
|
};
|
|
27
29
|
configs: {
|
|
28
30
|
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;AAY5C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAoB,MAAM,YAAY,CAAA;AAEvE,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;AAC9E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAIvD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,CAAA;AAqDnC,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,wFAAwF;AACxF,eAAO,MAAM,gBAAgB,UAAmE,CAAA;AAqChG,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
|
@@ -991,16 +991,16 @@ function classesOf2(el) {
|
|
|
991
991
|
}
|
|
992
992
|
function meaningful(el) {
|
|
993
993
|
const out = [];
|
|
994
|
-
for (const
|
|
995
|
-
if (
|
|
996
|
-
if (String(
|
|
997
|
-
} else if (
|
|
998
|
-
const e =
|
|
994
|
+
for (const child5 of el.children) {
|
|
995
|
+
if (child5.type === "JSXText") {
|
|
996
|
+
if (String(child5.value).trim()) out.push(child5);
|
|
997
|
+
} else if (child5.type === "JSXExpressionContainer") {
|
|
998
|
+
const e = child5.expression;
|
|
999
999
|
if (e.type === "JSXEmptyExpression") continue;
|
|
1000
1000
|
if (e.type === "LogicalExpression" && isElement(e.right)) out.push(e.right);
|
|
1001
1001
|
else if (isElement(e)) out.push(e);
|
|
1002
|
-
else out.push(
|
|
1003
|
-
} else if (
|
|
1002
|
+
else out.push(child5);
|
|
1003
|
+
} else if (child5.type === "JSXElement" || child5.type === "JSXFragment") out.push(child5);
|
|
1004
1004
|
}
|
|
1005
1005
|
return out;
|
|
1006
1006
|
}
|
|
@@ -1008,16 +1008,16 @@ var TEXT_PARTS = /* @__PURE__ */ new Set(["EditableText", "Person", "Breadcrumb"
|
|
|
1008
1008
|
var VALUE_EXPRESSIONS = /* @__PURE__ */ new Set(["Identifier", "MemberExpression", "TemplateLiteral", "Literal", "LogicalExpression", "ConditionalExpression", "ChainExpression", "CallExpression"]);
|
|
1009
1009
|
function carriesTitle(el, depth = 0) {
|
|
1010
1010
|
if (depth > 3) return false;
|
|
1011
|
-
for (const
|
|
1012
|
-
if (
|
|
1013
|
-
if (
|
|
1014
|
-
if (VALUE_EXPRESSIONS.has(
|
|
1011
|
+
for (const child5 of meaningful(el)) {
|
|
1012
|
+
if (child5.type === "JSXText") return true;
|
|
1013
|
+
if (child5.type === "JSXExpressionContainer") {
|
|
1014
|
+
if (VALUE_EXPRESSIONS.has(child5.expression.type)) return true;
|
|
1015
1015
|
continue;
|
|
1016
1016
|
}
|
|
1017
|
-
if (!isElement(
|
|
1018
|
-
const tag = tagOf(
|
|
1017
|
+
if (!isElement(child5)) continue;
|
|
1018
|
+
const tag = tagOf(child5);
|
|
1019
1019
|
if (/^h[1-6]$/.test(tag) || TEXT_PARTS.has(tag) || /\.(?:Title|Heading)$/.test(tag)) return true;
|
|
1020
|
-
if (/^[a-z]/.test(tag) && carriesTitle(
|
|
1020
|
+
if (/^[a-z]/.test(tag) && carriesTitle(child5, depth + 1)) return true;
|
|
1021
1021
|
}
|
|
1022
1022
|
return false;
|
|
1023
1023
|
}
|
|
@@ -1439,10 +1439,65 @@ var noCopiedLook = {
|
|
|
1439
1439
|
}
|
|
1440
1440
|
};
|
|
1441
1441
|
|
|
1442
|
-
// src/eslint/no-
|
|
1443
|
-
var
|
|
1442
|
+
// src/eslint/no-field-outside-a-form.ts
|
|
1443
|
+
var FIELD_PARTS = ["Field", "TextInput", "Textarea"];
|
|
1444
|
+
var FORM_PARTS = ["Form", "CommandPaletteForm"];
|
|
1444
1445
|
var child3 = (node, key) => node[key];
|
|
1445
1446
|
var nameOf4 = (node) => node?.type === "JSXIdentifier" || node?.type === "Identifier" ? node.name : void 0;
|
|
1447
|
+
var noFieldOutsideAForm = {
|
|
1448
|
+
meta: {
|
|
1449
|
+
type: "problem",
|
|
1450
|
+
docs: { description: "A field with no Form around it" },
|
|
1451
|
+
schema: [],
|
|
1452
|
+
messages: {
|
|
1453
|
+
loose: "`{{part}}` has no `Form` around it, so nothing owns sending it: Enter and Ctrl+Enter, `busy`, and focus after. Put it in `Form` from @estiva-app/ui; in a dialog, that is `DialogShell` with a `Form`, never `ConfirmDialog` (Primitives/Form says how).",
|
|
1454
|
+
...ESCAPE_MESSAGES
|
|
1455
|
+
}
|
|
1456
|
+
},
|
|
1457
|
+
create(context) {
|
|
1458
|
+
const parser = context.languageOptions?.parser ?? {};
|
|
1459
|
+
let locals;
|
|
1460
|
+
const loose = [];
|
|
1461
|
+
let drawsAField = false;
|
|
1462
|
+
const partOf = (element) => {
|
|
1463
|
+
const tag = child3(child3(element, "openingElement"), "name");
|
|
1464
|
+
const name = nameOf4(tag);
|
|
1465
|
+
if (!name || /^[a-z]/.test(name)) return void 0;
|
|
1466
|
+
const binding = locals && bindingOfTag(tag, locals);
|
|
1467
|
+
return binding && binding.props === "all" ? binding.part : void 0;
|
|
1468
|
+
};
|
|
1469
|
+
return {
|
|
1470
|
+
Program(program) {
|
|
1471
|
+
locals = partsOfFile(context.filename, program, parser);
|
|
1472
|
+
},
|
|
1473
|
+
JSXElement(ruleNode) {
|
|
1474
|
+
const element = ruleNode;
|
|
1475
|
+
const part = partOf(element);
|
|
1476
|
+
if (!part || !FIELD_PARTS.includes(part)) return;
|
|
1477
|
+
if (part === "Field") drawsAField = true;
|
|
1478
|
+
for (let up = element.parent; up; up = up.parent) {
|
|
1479
|
+
if (up.type !== "JSXElement") continue;
|
|
1480
|
+
const around = partOf(up);
|
|
1481
|
+
if (around && FORM_PARTS.includes(around)) return;
|
|
1482
|
+
if (around === "Field") return;
|
|
1483
|
+
}
|
|
1484
|
+
loose.push({ opening: child3(element, "openingElement"), part });
|
|
1485
|
+
},
|
|
1486
|
+
"Program:exit"() {
|
|
1487
|
+
for (const { opening, part } of loose) {
|
|
1488
|
+
if (part !== "Field" && drawsAField) continue;
|
|
1489
|
+
if (isEscaped(context, opening)) continue;
|
|
1490
|
+
context.report({ loc: opening.loc, messageId: "loose", data: { part } });
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
// src/eslint/no-native-title.ts
|
|
1498
|
+
var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "Panel", "SectionHeader"];
|
|
1499
|
+
var child4 = (node, key) => node[key];
|
|
1500
|
+
var nameOf5 = (node) => node?.type === "JSXIdentifier" || node?.type === "Identifier" ? node.name : void 0;
|
|
1446
1501
|
var noNativeTitle = {
|
|
1447
1502
|
meta: {
|
|
1448
1503
|
type: "problem",
|
|
@@ -1462,10 +1517,10 @@ var noNativeTitle = {
|
|
|
1462
1517
|
},
|
|
1463
1518
|
JSXAttribute(ruleNode) {
|
|
1464
1519
|
const attribute3 = ruleNode;
|
|
1465
|
-
if (
|
|
1520
|
+
if (nameOf5(child4(attribute3, "name")) !== "title") return;
|
|
1466
1521
|
const opening = attribute3.parent;
|
|
1467
|
-
const tag =
|
|
1468
|
-
const name =
|
|
1522
|
+
const tag = child4(opening, "name");
|
|
1523
|
+
const name = nameOf5(tag);
|
|
1469
1524
|
if (!(name && /^[a-z]/.test(name))) {
|
|
1470
1525
|
const binding = locals && bindingOfTag(tag, locals);
|
|
1471
1526
|
if (!binding || binding.props !== "all") return;
|
|
@@ -1789,10 +1844,10 @@ function anchorOf(node) {
|
|
|
1789
1844
|
if (node.type === "FunctionDeclaration") {
|
|
1790
1845
|
return node.parent && /^Export/.test(node.parent.type) ? node.parent : node;
|
|
1791
1846
|
}
|
|
1792
|
-
let
|
|
1793
|
-
for (let p = node.parent; p;
|
|
1847
|
+
let child5 = node;
|
|
1848
|
+
for (let p = node.parent; p; child5 = p, p = p.parent) {
|
|
1794
1849
|
if (p.type === "JSXOpeningElement" || p.type === "Property" || p.type === "MethodDefinition" || p.type === "PropertyDefinition") return p;
|
|
1795
|
-
if (p.type === "ArrayExpression") return
|
|
1850
|
+
if (p.type === "ArrayExpression") return child5;
|
|
1796
1851
|
if (p.type !== "BlockStatement" && /(Statement|Declaration)$/.test(p.type)) return p;
|
|
1797
1852
|
}
|
|
1798
1853
|
return node;
|
|
@@ -2107,7 +2162,8 @@ var APP_RULE_NAMES = [
|
|
|
2107
2162
|
"no-handmade-header",
|
|
2108
2163
|
"no-handmade-empty-state",
|
|
2109
2164
|
"no-native-title",
|
|
2110
|
-
"no-copied-look"
|
|
2165
|
+
"no-copied-look",
|
|
2166
|
+
"no-field-outside-a-form"
|
|
2111
2167
|
];
|
|
2112
2168
|
var APP_RULE_IDS = APP_RULE_NAMES.map((name) => `${PLUGIN_KEY}/${name}`);
|
|
2113
2169
|
|
|
@@ -2120,7 +2176,8 @@ var appRules = {
|
|
|
2120
2176
|
"no-handmade-header": noHandmadeHeader,
|
|
2121
2177
|
"no-handmade-empty-state": noHandmadeEmptyState,
|
|
2122
2178
|
"no-native-title": noNativeTitle,
|
|
2123
|
-
"no-copied-look": noCopiedLook
|
|
2179
|
+
"no-copied-look": noCopiedLook,
|
|
2180
|
+
"no-field-outside-a-form": noFieldOutsideAForm
|
|
2124
2181
|
};
|
|
2125
2182
|
var packageRules = {
|
|
2126
2183
|
"raw-element-outside-a-wrapper": rawElementOutsideAWrapper,
|
|
@@ -2151,7 +2208,8 @@ plugin.configs.recommended = {
|
|
|
2151
2208
|
[`${PLUGIN_KEY}/no-handmade-empty-state`]: "error",
|
|
2152
2209
|
[`${PLUGIN_KEY}/no-native-title`]: "error",
|
|
2153
2210
|
// A warning, never an error (Katerina, 13 September): likeness is a judgement.
|
|
2154
|
-
[`${PLUGIN_KEY}/no-copied-look`]: "warn"
|
|
2211
|
+
[`${PLUGIN_KEY}/no-copied-look`]: "warn",
|
|
2212
|
+
[`${PLUGIN_KEY}/no-field-outside-a-form`]: "error"
|
|
2155
2213
|
}
|
|
2156
2214
|
};
|
|
2157
2215
|
plugin.configs.strict = {
|
|
@@ -2187,6 +2245,7 @@ function countGates(results, seed = APP_RULE_IDS) {
|
|
|
2187
2245
|
export {
|
|
2188
2246
|
APP_RULE_IDS,
|
|
2189
2247
|
ESCAPE_MARKER,
|
|
2248
|
+
FIELD_PARTS,
|
|
2190
2249
|
MIN_COVER,
|
|
2191
2250
|
MIN_REASON,
|
|
2192
2251
|
MIN_SHARED,
|