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 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 instead.
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.0",
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': 4 };
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
 
@@ -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}