@nodii/dev 0.6.15 → 0.6.17
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 +16 -6
- package/dist/cli.js +1 -1
- package/dist/core.js +1 -1
- package/dist/design.js +132 -31
- package/dist/devops-openapi.json +1 -1
- package/dist/figma.js +4 -4
- package/dist/vendor/ds-conformance.mjs +55 -12
- package/package.json +1 -1
package/dist/figma.js
CHANGED
|
@@ -106,10 +106,10 @@ export function figmaChangeSetText(cs) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// Scoped design locks (DEC-0032, MW1-57.6 to 57.9). A scope is written
|
|
109
|
-
// kind:id on the command line: component:NODE, page:NODE,
|
|
110
|
-
// create:PAGE:NAME. A node id from a figma.com link (12-34) is Figma's 12:34.
|
|
109
|
+
// kind:id on the command line: component:NODE, page:NODE, section:NODE,
|
|
110
|
+
// foundations, or create:PAGE:NAME. A node id from a figma.com link (12-34) is Figma's 12:34.
|
|
111
111
|
const nodeId = (text) => (/^\d+-\d+$/.test(text) ? text.replace("-", ":") : text);
|
|
112
|
-
const SCOPE_USAGE = "--scope component:NODE_ID | page:NODE_ID | foundations | create:PAGE_ID:NAME";
|
|
112
|
+
const SCOPE_USAGE = "--scope component:NODE_ID | page:NODE_ID | section:NODE_ID | foundations | create:PAGE_ID:NAME";
|
|
113
113
|
|
|
114
114
|
export function parseScope(value) {
|
|
115
115
|
const text = typeof value === "string" ? value.trim() : "";
|
|
@@ -119,7 +119,7 @@ export function parseScope(value) {
|
|
|
119
119
|
if (create && create[2].trim() && create[2].trim().length <= 200)
|
|
120
120
|
return { kind: "create", pageId: nodeId(create[1]), name: create[2].trim() };
|
|
121
121
|
// Dev takes a scope at a top-level node: a plain id, never an instance's (I1:2;3:4).
|
|
122
|
-
const node = text.match(/^(component|page):(\d+[:-]\d+)$/);
|
|
122
|
+
const node = text.match(/^(component|page|section):(\d+[:-]\d+)$/);
|
|
123
123
|
if (!node) throw bad();
|
|
124
124
|
return { kind: node[1], nodeId: nodeId(node[2]) };
|
|
125
125
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// it until it returns [] (each round can reveal instances inside slot
|
|
27
27
|
// content).
|
|
28
28
|
|
|
29
|
-
export const VERSION = "1.0.
|
|
29
|
+
export const VERSION = "1.0.3";
|
|
30
30
|
|
|
31
31
|
/** @typedef {{ nodeId: string, check: string, detail: string }} Finding */
|
|
32
32
|
/**
|
|
@@ -86,6 +86,9 @@ const ALLOWED_FIELDS = new Set([
|
|
|
86
86
|
"exportSettings",
|
|
87
87
|
"pluginData",
|
|
88
88
|
"sharedPluginData",
|
|
89
|
+
// Choosing one of the library's variable modes (a size or density mode)
|
|
90
|
+
// is a mode, like a variant, not a look override.
|
|
91
|
+
"explicitVariableModes",
|
|
89
92
|
"minWidth",
|
|
90
93
|
"maxWidth",
|
|
91
94
|
"minHeight",
|
|
@@ -123,6 +126,10 @@ const EXTERNAL_PLANE = /customer portal|partner room|external/i;
|
|
|
123
126
|
const SELF_CONTAINER =
|
|
124
127
|
/sidebar ?footer|user button|user menu|account menu|profile/i;
|
|
125
128
|
const SELF_TEXT = /\(you\)|^you$/i;
|
|
129
|
+
const SIGNED_IN = /^(signed in( as\b.*)?|not you\?.*)$/i;
|
|
130
|
+
// The signed-in account card (Account Context, kind=Account) shows the
|
|
131
|
+
// viewer's own email.
|
|
132
|
+
const SELF_COMPONENT = /^account context$/i;
|
|
126
133
|
const NOT_A_PERSON = /^(you|the team)$/i;
|
|
127
134
|
const MASKED = /[*•●]/;
|
|
128
135
|
const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
@@ -206,6 +213,14 @@ function fieldValue(node, field, styles) {
|
|
|
206
213
|
return styleKeys(node, styles);
|
|
207
214
|
if (TEXT_STYLE_FIELDS.has(field) || field.startsWith("lineHeight"))
|
|
208
215
|
return node?.style?.[field];
|
|
216
|
+
// An image fill's picture is content (DEC-0048): only its scale mode,
|
|
217
|
+
// filters and opacity are look, so imageRef is left out of the comparison.
|
|
218
|
+
if (field === "fills" || field === "background")
|
|
219
|
+
return (node?.[field] ?? []).map((p) => {
|
|
220
|
+
if (p?.type !== "IMAGE") return p;
|
|
221
|
+
const { imageRef: _picture, gifRef: _gif, ...look } = p;
|
|
222
|
+
return look;
|
|
223
|
+
});
|
|
209
224
|
// A binding is judged per bound field: (un)binding a field the instance
|
|
210
225
|
// may override anyway (spacing, size) is that override, not a visual one.
|
|
211
226
|
if (field === "boundVariables") {
|
|
@@ -365,8 +380,9 @@ function headerCell(a, inst) {
|
|
|
365
380
|
const c = a.componentOf(inst);
|
|
366
381
|
const set = a.setOf(inst);
|
|
367
382
|
if (/^table header$/i.test(set?.name ?? c?.name ?? "")) return true;
|
|
383
|
+
// Exactly a header part: a Command list's part=group-heading has no fill.
|
|
368
384
|
const part = inst.componentProperties?.part?.value;
|
|
369
|
-
if (typeof part === "string" &&
|
|
385
|
+
if (typeof part === "string" && /^head(er)?$/i.test(part)) return true;
|
|
370
386
|
return /(^|,\s*)part=head(er)?(\s*,|$)/i.test(c?.name ?? "");
|
|
371
387
|
}
|
|
372
388
|
|
|
@@ -776,19 +792,22 @@ const ICON = /^(icon|lucide)\//i;
|
|
|
776
792
|
/**
|
|
777
793
|
* Design-system rulings on paint overrides: an icon takes its colour from
|
|
778
794
|
* where it sits (currentColor), so a bound fill or stroke on an Icon/ or
|
|
779
|
-
* lucide/ instance or its vectors is allowed (a Logo/ is not)
|
|
795
|
+
* lucide/ instance or its vectors is allowed (a Logo/ is not), and so is its
|
|
796
|
+
* stroke weight, which scales with the icon's size; a PiiText in
|
|
780
797
|
* mode=revealed inherits ink, so its fill may be set.
|
|
781
798
|
*/
|
|
782
799
|
function exemptPaint(a, from, node, field) {
|
|
783
|
-
if (field !== "fills" && field !== "strokes"
|
|
800
|
+
if (field !== "fills" && field !== "strokes" && field !== "strokeWeight")
|
|
801
|
+
return false;
|
|
784
802
|
const path = [node, ...a.ancestors(node)];
|
|
785
803
|
const upTo = path.slice(0, path.indexOf(from) + 1);
|
|
804
|
+
const inIcon = upTo.some(
|
|
805
|
+
(x) => x.type === "INSTANCE" && ICON.test(a.componentLabel(x)),
|
|
806
|
+
);
|
|
807
|
+
// An icon's stroke weight scales with the size it is placed at.
|
|
808
|
+
if (field === "strokeWeight") return inIcon;
|
|
786
809
|
const bound = (node[field] ?? []).every((p) => p?.boundVariables?.color);
|
|
787
|
-
if (
|
|
788
|
-
bound &&
|
|
789
|
-
upTo.some((x) => x.type === "INSTANCE" && ICON.test(a.componentLabel(x)))
|
|
790
|
-
)
|
|
791
|
-
return true;
|
|
810
|
+
if (bound && inIcon) return true;
|
|
792
811
|
return field === "fills" && revealedByPermission(a, node);
|
|
793
812
|
}
|
|
794
813
|
const paintColours = (paints) =>
|
|
@@ -847,10 +866,34 @@ function handDrawnButton(n) {
|
|
|
847
866
|
function isSelf(a, n) {
|
|
848
867
|
const up = a.ancestors(n);
|
|
849
868
|
if (up.some((x) => SELF_CONTAINER.test(x.name ?? ""))) return true;
|
|
869
|
+
const accountCard = (x) => {
|
|
870
|
+
const set =
|
|
871
|
+
x.type === "INSTANCE"
|
|
872
|
+
? a.componentLabel(x)
|
|
873
|
+
: x.type === "COMPONENT"
|
|
874
|
+
? a.parent.get(x.id)?.name
|
|
875
|
+
: null;
|
|
876
|
+
if (!SELF_COMPONENT.test(set ?? "")) return false;
|
|
877
|
+
const name = x.type === "INSTANCE" ? a.componentOf(x)?.name : x.name;
|
|
878
|
+
const kind = x.componentProperties?.kind?.value;
|
|
879
|
+
return (
|
|
880
|
+
kind === "Account" || /(^|,\s*)kind=account(\s*,|$)/i.test(name ?? "")
|
|
881
|
+
);
|
|
882
|
+
};
|
|
883
|
+
if (up.some(accountCard)) return true;
|
|
850
884
|
const siblings = a.parent.get(n.id)?.children ?? [];
|
|
851
|
-
|
|
852
|
-
(x) => x.type === "TEXT" && SELF_TEXT.test(textOf(x))
|
|
853
|
-
)
|
|
885
|
+
if (
|
|
886
|
+
[n, ...siblings].some((x) => x.type === "TEXT" && SELF_TEXT.test(textOf(x)))
|
|
887
|
+
)
|
|
888
|
+
return true;
|
|
889
|
+
// "Signed in" or "Not you?" in the same card: the nearest instance or
|
|
890
|
+
// component holding it, never a whole row or screen.
|
|
891
|
+
const card = up.find((x) => x.type === "INSTANCE" || x.type === "COMPONENT");
|
|
892
|
+
return card
|
|
893
|
+
? [...indexTree(card).byId.values()].some(
|
|
894
|
+
(x) => x.type === "TEXT" && SIGNED_IN.test(textOf(x)),
|
|
895
|
+
)
|
|
896
|
+
: false;
|
|
854
897
|
}
|
|
855
898
|
function revealedByPermission(a, n) {
|
|
856
899
|
return [n, ...a.ancestors(n)].some((x) => {
|