starchy 0.1.0 → 0.1.1
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 +3 -1
- package/package.json +1 -1
- package/src/analyze.js +1 -1
- package/src/fingerprint.js +22 -0
- package/src/report.js +2 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ the whole range and lets the page show it where the layout shifts:
|
|
|
37
37
|
| Overlap | In-flow sibling elements overlap each other. |
|
|
38
38
|
| Escapes container | An element sticks out of its parent's box. |
|
|
39
39
|
| Clipped text | Text is cut off by `overflow: hidden`. |
|
|
40
|
+
| Wrapped link | A link or button label of 5 words or fewer breaks onto 2+ lines. Links inside running text and headings are skipped. |
|
|
40
41
|
| Tiny text | Text is below `--min-font` (default 12px). |
|
|
41
42
|
|
|
42
43
|
## Install
|
|
@@ -104,7 +105,8 @@ open test-report/report.html
|
|
|
104
105
|
- **Some content isn't inspected.** Shadow DOM and iframes are skipped.
|
|
105
106
|
Auto-rotating carousels and other animated elements need `--ignore`.
|
|
106
107
|
- **Inline text reflow is ignored on purpose.** It changes at almost every
|
|
107
|
-
pixel. Text is checked through the clipped-text and tiny-text rules
|
|
108
|
+
pixel. Text is checked through the clipped-text, wrapped-link and tiny-text rules
|
|
109
|
+
instead.
|
|
108
110
|
- **Chromium only for now.**
|
|
109
111
|
|
|
110
112
|
## Project layout
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starchy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Keep your layout crisp at every width. Finds every screen width where a page's layout changes, and flags the ones that are bugs.",
|
|
5
5
|
"homepage": "https://starchy.dev",
|
|
6
6
|
"repository": {
|
package/src/analyze.js
CHANGED
|
@@ -104,7 +104,7 @@ function issueRanges(first, transitions, min, last) {
|
|
|
104
104
|
const atEnd = last && last.issues.find((y) => y.key === o.issue.key);
|
|
105
105
|
done.push({ ...o, issue: atEnd || o.issue, to: min });
|
|
106
106
|
}
|
|
107
|
-
const rank = { 'page-overflow': 0, overlap: 1, 'container-overflow': 2, 'text-clipped': 3, 'tiny-text':
|
|
107
|
+
const rank = { 'page-overflow': 0, overlap: 1, 'container-overflow': 2, 'text-clipped': 3, 'link-wrapped': 4, 'tiny-text': 5 };
|
|
108
108
|
return done.sort((a, b) => rank[a.issue.kind] - rank[b.issue.kind] || b.from - a.from);
|
|
109
109
|
}
|
|
110
110
|
|
package/src/fingerprint.js
CHANGED
|
@@ -207,6 +207,28 @@ function fingerprint(opts) {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
// 6. short link/button labels broken onto 2+ lines. Skips links in running text and
|
|
211
|
+
// headings, and only counts wrapping inside one text node, so <br>/block lines pass.
|
|
212
|
+
for (const el of document.body.querySelectorAll('a[href], button, [role="button"]')) {
|
|
213
|
+
if (ignored.length && ignored.some((ig) => ig.contains(el))) continue;
|
|
214
|
+
const text = el.textContent.trim().replace(/\s+/g, ' ');
|
|
215
|
+
if (!text || text.split(' ').length > 5 || el.closest('h1, h2, h3, h4, h5, h6')) continue;
|
|
216
|
+
const cs = getComputedStyle(el);
|
|
217
|
+
if (cs.visibility !== 'visible' || el.getClientRects().length === 0) continue;
|
|
218
|
+
if (cs.display === 'inline' && el.parentElement && el.parentElement.textContent.trim().replace(/\s+/g, ' ') !== text) continue;
|
|
219
|
+
let lines = 0;
|
|
220
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
221
|
+
for (let n; (n = walker.nextNode());) {
|
|
222
|
+
if (!n.textContent.trim()) continue;
|
|
223
|
+
const range = document.createRange();
|
|
224
|
+
range.selectNodeContents(n);
|
|
225
|
+
const tops = Array.from(range.getClientRects(), (r) => (r.width > 0 ? r.top : null)).filter((t) => t !== null).sort((a, b) => a - b);
|
|
226
|
+
const half = parseFloat(getComputedStyle(n.parentElement).fontSize) / 2;
|
|
227
|
+
lines = Math.max(lines, tops.filter((t, i) => i === 0 || t - tops[i - 1] > half).length);
|
|
228
|
+
}
|
|
229
|
+
if (lines > 1) add('link-wrapped', [{ path: getPath(el), label: label(el) }], `"${text}" wraps onto ${lines} lines`);
|
|
230
|
+
}
|
|
231
|
+
|
|
210
232
|
const labels = {};
|
|
211
233
|
for (const u of units.values()) labels[u.path] = u.label;
|
|
212
234
|
labels.body = 'body';
|
package/src/report.js
CHANGED
|
@@ -9,6 +9,7 @@ const KIND = {
|
|
|
9
9
|
overlap: 'Overlap',
|
|
10
10
|
'container-overflow': 'Escapes container',
|
|
11
11
|
'text-clipped': 'Clipped text',
|
|
12
|
+
'link-wrapped': 'Wrapped link',
|
|
12
13
|
'tiny-text': 'Tiny text',
|
|
13
14
|
};
|
|
14
15
|
const CAT = {
|
|
@@ -177,7 +178,7 @@ code{font:12.5px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;background:var(
|
|
|
177
178
|
.problems .range{grid-column:3;grid-row:1;font-variant-numeric:tabular-nums;font-size:13px;white-space:nowrap}
|
|
178
179
|
.problems .more{display:block;color:var(--muted)}.problems .kind{justify-self:start}.at{opacity:.75}
|
|
179
180
|
.kind{font-size:11.5px;font-weight:600;padding:2px 8px;border-radius:99px;white-space:nowrap;color:var(--red);background:color-mix(in srgb,var(--red) 12%,transparent)}
|
|
180
|
-
.k-text-clipped,.k-tiny-text{color:var(--amber);background:color-mix(in srgb,var(--amber) 14%,transparent)}
|
|
181
|
+
.k-text-clipped,.k-link-wrapped,.k-tiny-text{color:var(--amber);background:color-mix(in srgb,var(--amber) 14%,transparent)}
|
|
181
182
|
.ok{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:12px 14px;color:var(--green);margin:0}
|
|
182
183
|
.change{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:14px 16px;margin:0 0 12px;scroll-margin-top:12px}
|
|
183
184
|
.change header{display:flex;align-items:center;justify-content:space-between;gap:10px}
|