Repository navigation
Expand file tree
/
Copy pathcode.ts
More file actions
126 lines (111 loc) · 3.86 KB
/
Copy pathcode.ts
File metadata and controls
126 lines (111 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// What we save about each text layer we can check
interface TextLayerInfo {
id: string;
name: string;
fontSize: number;
fontWeight: number;
color: RGB; // Figma's colour type: { r, g, b }, each from 0 to 1
background: RGB; // colour of the nearest container behind the text
}
// Walks UP from a layer through its parents until one has a visible solid fill.
// Returns that fill's colour, or null if it reaches the top without finding one.
function findBackground(node: SceneNode): RGB | null {
let current = node.parent;
while (current !== null) {
if ("fills" in current && current.fills !== figma.mixed) {
for (const fill of current.fills) {
if (fill.type === "SOLID" && fill.visible !== false) {
return fill.color; // found it: stop walking
}
}
}
current = current.parent; // no usable fill here: go up one level
}
return null; // reached the top: background unknown
}
// Looks at one layer: saves it if it's checkable text, then looks inside it.
// Returns how many text layers were skipped.
function collect(node: SceneNode, results: TextLayerInfo[]): number {
let skipped = 0;
if (node.type === "TEXT") {
const fills = node.fills;
if (node.fontSize === figma.mixed || node.fontWeight === figma.mixed) {
skipped += 1;
} else if (fills === figma.mixed || fills.length === 0 || fills[0].type !== "SOLID") {
skipped += 1;
} else {
const background = findBackground(node);
if (background === null) {
skipped += 1; // background unknown
} else {
results.push({
id: node.id,
name: node.name,
fontSize: node.fontSize,
fontWeight: node.fontWeight,
color: fills[0].color,
background: background,
});
}
}
}
if ("children" in node) {
for (const child of node.children) {
skipped += collect(child, results);
}
}
return skipped;
}
// Finds a layer by its id, selects it and zooms the view to it
async function selectLayer(id: string): Promise<void> {
const node = await figma.getNodeByIdAsync(id);
if (node === null || node.type === "PAGE" || node.type === "DOCUMENT") {
figma.notify("That layer doesn't exist any more. Click Check again.");
return;
}
figma.currentPage.selection = [node];
figma.viewport.scrollAndZoomIntoView([node]);
}
// themeColors: the window gets Figma's light/dark colours as CSS variables
figma.showUI(__html__, { width: 360, height: 520, themeColors: true });
// 2. Message from ui.html arrives here
figma.ui.onmessage = async (msg: { type: string; id?: string }) => {
// An issue in the list was clicked: select and zoom to that layer
if (msg.type === 'select' && msg.id) {
await selectLayer(msg.id);
return;
}
if (msg.type !== 'check') return;
const selection = figma.currentPage.selection;
if (selection.length === 0) {
figma.ui.postMessage({ type: 'result', text: 'Select a frame first.' });
return;
}
// 3. Collect text layers from everything selected
const results: TextLayerInfo[] = [];
let skipped = 0;
for (const node of selection) {
skipped += collect(node, results);
}
// Check contrast for each collected text layer, keep the failures
const contrastIssues: ContrastIssue[] = [];
for (const layer of results) {
const issue = checkContrast(layer);
if (issue !== null) {
contrastIssues.push(issue);
}
}
// Check buttons and icons everywhere in the selection for tap-target size
const tapIssues: TapTargetIssue[] = [];
for (const node of selection) {
checkTapTargets(node, tapIssues);
}
// Send the full lists so ui.html can build the results list
figma.ui.postMessage({
type: 'result',
scope: selection.length === 1 ? selection[0].name : `${selection.length} layers`,
contrastIssues: contrastIssues,
tapIssues: tapIssues,
skipped: skipped,
});
};