@estiva-app/ui 0.48.0 → 0.50.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 +8 -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 +110 -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/no-restyled-part.d.ts.map +1 -1
- 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 +10 -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/no-restyled-part.test.ts +16 -0
- package/src/eslint/no-restyled-part.ts +37 -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
|
@@ -79,6 +79,8 @@ instead. There are seven — six block, one warns:
|
|
|
79
79
|
`className` or an inner box's class prop — a colour, a text size, a border, a
|
|
80
80
|
corner, a shadow — and names the part's look props (`PART_LOOK_PROPS`). Only
|
|
81
81
|
placement goes through (`PLACEMENT`), and `EmptyState` takes no padding.
|
|
82
|
+
A `Form` takes no layout of its own, and sits in no spaced box beside other
|
|
83
|
+
things: what comes before its fields goes inside it.
|
|
82
84
|
- `estiva/no-handmade-header` refuses a column's header bar drawn by hand: a
|
|
83
85
|
row first in a column, with its own side padding and a title, where the column
|
|
84
86
|
fills its height or the row has a bar's height or hairline. It reads the
|
|
@@ -102,6 +104,12 @@ instead. There are seven — six block, one warns:
|
|
|
102
104
|
`registry.json`, and the app's own exported components; the fix is to use
|
|
103
105
|
the part. In the package the fix is to write the look once in `src/looks.ts`.
|
|
104
106
|
A look written once as an exported `…_CLASSES` constant counts as a look too.
|
|
107
|
+
- `estiva/no-field-outside-a-form` refuses a `Field`, `TextInput` or
|
|
108
|
+
`Textarea` with no `Form` around it in its file (the palette's
|
|
109
|
+
`CommandPaletteForm` is one), and names `Form`: without it nothing owns
|
|
110
|
+
sending the field. In a dialog that is `DialogShell` with a `Form`, never a
|
|
111
|
+
field inside `ConfirmDialog`. `EditableText` and `SearchInput` pass; a
|
|
112
|
+
component that draws fields for its caller's form says so with an escape.
|
|
105
113
|
|
|
106
114
|
```js
|
|
107
115
|
// 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
|
@@ -218,6 +218,10 @@ var isFieldsLayout = (token) => {
|
|
|
218
218
|
const { variants, utility } = splitClass(token);
|
|
219
219
|
return !variants.some((variant) => REACHING_VARIANT.test(variant)) && FIELDS_LAYOUT.test(utility);
|
|
220
220
|
};
|
|
221
|
+
var isSpacing = (token) => {
|
|
222
|
+
const { variants, utility } = splitClass(token);
|
|
223
|
+
return !variants.some((variant) => REACHING_VARIANT.test(variant)) && /^(?:gap|gap-x|gap-y|space-x|space-y)-/.test(utility);
|
|
224
|
+
};
|
|
221
225
|
var isPadding = (token) => {
|
|
222
226
|
const { variants, utility } = splitClass(token);
|
|
223
227
|
return !variants.some((variant) => REACHING_VARIANT.test(variant)) && /^p[xytrblse]?-/.test(utility);
|
|
@@ -635,6 +639,7 @@ var noRestyledPart = {
|
|
|
635
639
|
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.",
|
|
636
640
|
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.",
|
|
637
641
|
formLayout: '{{classes}} lays out the fields of {{where}}. A Form places its own fields, 24px apart (Katerina, 28 September), so every form reads the same: remove it. A form that is not a list of fields (a field and its button, a composer) says `layout="free"` and lays itself out.',
|
|
642
|
+
formSpacedFromOutside: "{{classes}} on the box around `Form` spaces the form from what is beside it, so the gap above its first field is not the form\u2019s 24px. Put what is beside it inside the `Form`, as one block before the fields, and drop the box: the Form puts 24px between them (Katerina, 1 October; MoveDialog in Peek does this).",
|
|
638
643
|
...ESCAPE_MESSAGES
|
|
639
644
|
}
|
|
640
645
|
},
|
|
@@ -688,6 +693,26 @@ var noRestyledPart = {
|
|
|
688
693
|
const where = binding.via && binding.via !== binding.part ? `\`${binding.via}\` (it hands its props to \`${binding.part}\`)` : `\`${binding.part}\``;
|
|
689
694
|
context.report({ loc: at.loc, messageId: "restyledStyle", data: { keys: code([...new Set(keys)]), where, use } });
|
|
690
695
|
};
|
|
696
|
+
const checkSpacedFromOutside = (opening) => {
|
|
697
|
+
const form = opening.parent;
|
|
698
|
+
const box = form?.parent;
|
|
699
|
+
if (form?.type !== "JSXElement" || box?.type !== "JSXElement") return;
|
|
700
|
+
const beside = children(box, "children").filter((node) => {
|
|
701
|
+
if (!node || node === form) return false;
|
|
702
|
+
if (node.type === "JSXText") return String(node.value).trim() !== "";
|
|
703
|
+
if (node.type === "JSXExpressionContainer") return child(node, "expression")?.type !== "JSXEmptyExpression";
|
|
704
|
+
return true;
|
|
705
|
+
});
|
|
706
|
+
if (!beside.length) return;
|
|
707
|
+
const boxOpening = child(box, "openingElement");
|
|
708
|
+
const spacing = children(boxOpening, "attributes").flatMap((attribute3) => {
|
|
709
|
+
if (attribute3?.type !== "JSXAttribute") return [];
|
|
710
|
+
const prop = nameOf(child(attribute3, "name"));
|
|
711
|
+
return prop === "className" || prop === "bodyClassName" ? classesOf(child(attribute3, "value"), scopeOf).filter(isSpacing) : [];
|
|
712
|
+
});
|
|
713
|
+
if (!spacing.length || escaped(opening)) return;
|
|
714
|
+
context.report({ loc: opening.loc, messageId: "formSpacedFromOutside", data: { classes: code([...new Set(spacing)]) } });
|
|
715
|
+
};
|
|
691
716
|
return {
|
|
692
717
|
Program(program) {
|
|
693
718
|
locals = moduleParts(file, program, parser, /* @__PURE__ */ new Set([file])).locals;
|
|
@@ -696,6 +721,7 @@ var noRestyledPart = {
|
|
|
696
721
|
const element = ruleNode;
|
|
697
722
|
const binding = locals && bindingOfTag(child(element, "name"), locals);
|
|
698
723
|
if (!binding) return;
|
|
724
|
+
if (binding.part === "Form" && binding.props === "all" && placesItsFields(element)) checkSpacedFromOutside(element);
|
|
699
725
|
for (const attribute3 of children(element, "attributes")) {
|
|
700
726
|
if (!attribute3) continue;
|
|
701
727
|
if (attribute3.type === "JSXAttribute") {
|
|
@@ -991,16 +1017,16 @@ function classesOf2(el) {
|
|
|
991
1017
|
}
|
|
992
1018
|
function meaningful(el) {
|
|
993
1019
|
const out = [];
|
|
994
|
-
for (const
|
|
995
|
-
if (
|
|
996
|
-
if (String(
|
|
997
|
-
} else if (
|
|
998
|
-
const e =
|
|
1020
|
+
for (const child5 of el.children) {
|
|
1021
|
+
if (child5.type === "JSXText") {
|
|
1022
|
+
if (String(child5.value).trim()) out.push(child5);
|
|
1023
|
+
} else if (child5.type === "JSXExpressionContainer") {
|
|
1024
|
+
const e = child5.expression;
|
|
999
1025
|
if (e.type === "JSXEmptyExpression") continue;
|
|
1000
1026
|
if (e.type === "LogicalExpression" && isElement(e.right)) out.push(e.right);
|
|
1001
1027
|
else if (isElement(e)) out.push(e);
|
|
1002
|
-
else out.push(
|
|
1003
|
-
} else if (
|
|
1028
|
+
else out.push(child5);
|
|
1029
|
+
} else if (child5.type === "JSXElement" || child5.type === "JSXFragment") out.push(child5);
|
|
1004
1030
|
}
|
|
1005
1031
|
return out;
|
|
1006
1032
|
}
|
|
@@ -1008,16 +1034,16 @@ var TEXT_PARTS = /* @__PURE__ */ new Set(["EditableText", "Person", "Breadcrumb"
|
|
|
1008
1034
|
var VALUE_EXPRESSIONS = /* @__PURE__ */ new Set(["Identifier", "MemberExpression", "TemplateLiteral", "Literal", "LogicalExpression", "ConditionalExpression", "ChainExpression", "CallExpression"]);
|
|
1009
1035
|
function carriesTitle(el, depth = 0) {
|
|
1010
1036
|
if (depth > 3) return false;
|
|
1011
|
-
for (const
|
|
1012
|
-
if (
|
|
1013
|
-
if (
|
|
1014
|
-
if (VALUE_EXPRESSIONS.has(
|
|
1037
|
+
for (const child5 of meaningful(el)) {
|
|
1038
|
+
if (child5.type === "JSXText") return true;
|
|
1039
|
+
if (child5.type === "JSXExpressionContainer") {
|
|
1040
|
+
if (VALUE_EXPRESSIONS.has(child5.expression.type)) return true;
|
|
1015
1041
|
continue;
|
|
1016
1042
|
}
|
|
1017
|
-
if (!isElement(
|
|
1018
|
-
const tag = tagOf(
|
|
1043
|
+
if (!isElement(child5)) continue;
|
|
1044
|
+
const tag = tagOf(child5);
|
|
1019
1045
|
if (/^h[1-6]$/.test(tag) || TEXT_PARTS.has(tag) || /\.(?:Title|Heading)$/.test(tag)) return true;
|
|
1020
|
-
if (/^[a-z]/.test(tag) && carriesTitle(
|
|
1046
|
+
if (/^[a-z]/.test(tag) && carriesTitle(child5, depth + 1)) return true;
|
|
1021
1047
|
}
|
|
1022
1048
|
return false;
|
|
1023
1049
|
}
|
|
@@ -1439,10 +1465,65 @@ var noCopiedLook = {
|
|
|
1439
1465
|
}
|
|
1440
1466
|
};
|
|
1441
1467
|
|
|
1442
|
-
// src/eslint/no-
|
|
1443
|
-
var
|
|
1468
|
+
// src/eslint/no-field-outside-a-form.ts
|
|
1469
|
+
var FIELD_PARTS = ["Field", "TextInput", "Textarea"];
|
|
1470
|
+
var FORM_PARTS = ["Form", "CommandPaletteForm"];
|
|
1444
1471
|
var child3 = (node, key) => node[key];
|
|
1445
1472
|
var nameOf4 = (node) => node?.type === "JSXIdentifier" || node?.type === "Identifier" ? node.name : void 0;
|
|
1473
|
+
var noFieldOutsideAForm = {
|
|
1474
|
+
meta: {
|
|
1475
|
+
type: "problem",
|
|
1476
|
+
docs: { description: "A field with no Form around it" },
|
|
1477
|
+
schema: [],
|
|
1478
|
+
messages: {
|
|
1479
|
+
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).",
|
|
1480
|
+
...ESCAPE_MESSAGES
|
|
1481
|
+
}
|
|
1482
|
+
},
|
|
1483
|
+
create(context) {
|
|
1484
|
+
const parser = context.languageOptions?.parser ?? {};
|
|
1485
|
+
let locals;
|
|
1486
|
+
const loose = [];
|
|
1487
|
+
let drawsAField = false;
|
|
1488
|
+
const partOf = (element) => {
|
|
1489
|
+
const tag = child3(child3(element, "openingElement"), "name");
|
|
1490
|
+
const name = nameOf4(tag);
|
|
1491
|
+
if (!name || /^[a-z]/.test(name)) return void 0;
|
|
1492
|
+
const binding = locals && bindingOfTag(tag, locals);
|
|
1493
|
+
return binding && binding.props === "all" ? binding.part : void 0;
|
|
1494
|
+
};
|
|
1495
|
+
return {
|
|
1496
|
+
Program(program) {
|
|
1497
|
+
locals = partsOfFile(context.filename, program, parser);
|
|
1498
|
+
},
|
|
1499
|
+
JSXElement(ruleNode) {
|
|
1500
|
+
const element = ruleNode;
|
|
1501
|
+
const part = partOf(element);
|
|
1502
|
+
if (!part || !FIELD_PARTS.includes(part)) return;
|
|
1503
|
+
if (part === "Field") drawsAField = true;
|
|
1504
|
+
for (let up = element.parent; up; up = up.parent) {
|
|
1505
|
+
if (up.type !== "JSXElement") continue;
|
|
1506
|
+
const around = partOf(up);
|
|
1507
|
+
if (around && FORM_PARTS.includes(around)) return;
|
|
1508
|
+
if (around === "Field") return;
|
|
1509
|
+
}
|
|
1510
|
+
loose.push({ opening: child3(element, "openingElement"), part });
|
|
1511
|
+
},
|
|
1512
|
+
"Program:exit"() {
|
|
1513
|
+
for (const { opening, part } of loose) {
|
|
1514
|
+
if (part !== "Field" && drawsAField) continue;
|
|
1515
|
+
if (isEscaped(context, opening)) continue;
|
|
1516
|
+
context.report({ loc: opening.loc, messageId: "loose", data: { part } });
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// src/eslint/no-native-title.ts
|
|
1524
|
+
var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "Panel", "SectionHeader"];
|
|
1525
|
+
var child4 = (node, key) => node[key];
|
|
1526
|
+
var nameOf5 = (node) => node?.type === "JSXIdentifier" || node?.type === "Identifier" ? node.name : void 0;
|
|
1446
1527
|
var noNativeTitle = {
|
|
1447
1528
|
meta: {
|
|
1448
1529
|
type: "problem",
|
|
@@ -1462,10 +1543,10 @@ var noNativeTitle = {
|
|
|
1462
1543
|
},
|
|
1463
1544
|
JSXAttribute(ruleNode) {
|
|
1464
1545
|
const attribute3 = ruleNode;
|
|
1465
|
-
if (
|
|
1546
|
+
if (nameOf5(child4(attribute3, "name")) !== "title") return;
|
|
1466
1547
|
const opening = attribute3.parent;
|
|
1467
|
-
const tag =
|
|
1468
|
-
const name =
|
|
1548
|
+
const tag = child4(opening, "name");
|
|
1549
|
+
const name = nameOf5(tag);
|
|
1469
1550
|
if (!(name && /^[a-z]/.test(name))) {
|
|
1470
1551
|
const binding = locals && bindingOfTag(tag, locals);
|
|
1471
1552
|
if (!binding || binding.props !== "all") return;
|
|
@@ -1789,10 +1870,10 @@ function anchorOf(node) {
|
|
|
1789
1870
|
if (node.type === "FunctionDeclaration") {
|
|
1790
1871
|
return node.parent && /^Export/.test(node.parent.type) ? node.parent : node;
|
|
1791
1872
|
}
|
|
1792
|
-
let
|
|
1793
|
-
for (let p = node.parent; p;
|
|
1873
|
+
let child5 = node;
|
|
1874
|
+
for (let p = node.parent; p; child5 = p, p = p.parent) {
|
|
1794
1875
|
if (p.type === "JSXOpeningElement" || p.type === "Property" || p.type === "MethodDefinition" || p.type === "PropertyDefinition") return p;
|
|
1795
|
-
if (p.type === "ArrayExpression") return
|
|
1876
|
+
if (p.type === "ArrayExpression") return child5;
|
|
1796
1877
|
if (p.type !== "BlockStatement" && /(Statement|Declaration)$/.test(p.type)) return p;
|
|
1797
1878
|
}
|
|
1798
1879
|
return node;
|
|
@@ -2107,7 +2188,8 @@ var APP_RULE_NAMES = [
|
|
|
2107
2188
|
"no-handmade-header",
|
|
2108
2189
|
"no-handmade-empty-state",
|
|
2109
2190
|
"no-native-title",
|
|
2110
|
-
"no-copied-look"
|
|
2191
|
+
"no-copied-look",
|
|
2192
|
+
"no-field-outside-a-form"
|
|
2111
2193
|
];
|
|
2112
2194
|
var APP_RULE_IDS = APP_RULE_NAMES.map((name) => `${PLUGIN_KEY}/${name}`);
|
|
2113
2195
|
|
|
@@ -2120,7 +2202,8 @@ var appRules = {
|
|
|
2120
2202
|
"no-handmade-header": noHandmadeHeader,
|
|
2121
2203
|
"no-handmade-empty-state": noHandmadeEmptyState,
|
|
2122
2204
|
"no-native-title": noNativeTitle,
|
|
2123
|
-
"no-copied-look": noCopiedLook
|
|
2205
|
+
"no-copied-look": noCopiedLook,
|
|
2206
|
+
"no-field-outside-a-form": noFieldOutsideAForm
|
|
2124
2207
|
};
|
|
2125
2208
|
var packageRules = {
|
|
2126
2209
|
"raw-element-outside-a-wrapper": rawElementOutsideAWrapper,
|
|
@@ -2151,7 +2234,8 @@ plugin.configs.recommended = {
|
|
|
2151
2234
|
[`${PLUGIN_KEY}/no-handmade-empty-state`]: "error",
|
|
2152
2235
|
[`${PLUGIN_KEY}/no-native-title`]: "error",
|
|
2153
2236
|
// A warning, never an error (Katerina, 13 September): likeness is a judgement.
|
|
2154
|
-
[`${PLUGIN_KEY}/no-copied-look`]: "warn"
|
|
2237
|
+
[`${PLUGIN_KEY}/no-copied-look`]: "warn",
|
|
2238
|
+
[`${PLUGIN_KEY}/no-field-outside-a-form`]: "error"
|
|
2155
2239
|
}
|
|
2156
2240
|
};
|
|
2157
2241
|
plugin.configs.strict = {
|
|
@@ -2187,6 +2271,7 @@ function countGates(results, seed = APP_RULE_IDS) {
|
|
|
2187
2271
|
export {
|
|
2188
2272
|
APP_RULE_IDS,
|
|
2189
2273
|
ESCAPE_MARKER,
|
|
2274
|
+
FIELD_PARTS,
|
|
2190
2275
|
MIN_COVER,
|
|
2191
2276
|
MIN_REASON,
|
|
2192
2277
|
MIN_SHARED,
|