@godxjp/ui 23.4.7 → 23.4.8

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.
@@ -0,0 +1,57 @@
1
+ {
2
+ "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
+ "version": "23.4.8",
4
+ "targetSize": {
5
+ "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
+ "min": 24,
7
+ "measures": "target",
8
+ "note": "The TARGET is the region that accepts the pointer action, which is not always the painted box. getBoundingClientRect() returns the border box and cannot see a pseudo-element, so it UNDER-REPORTS every selector listed under `expanders`. Measure with elementFromPoint — see `method` — or treat a listed selector as conforming at `targetMin`.",
9
+ "method": "docs/MEASUREMENT-CONTRACT.md#measuring-a-target",
10
+ "expanders": [
11
+ {
12
+ "selector": ".ui-control-inline-affix-action",
13
+ "via": "::after",
14
+ "source": "src/styles/control.css",
15
+ "targetMin": 24,
16
+ "components": [
17
+ "badge",
18
+ "date-picker",
19
+ "input",
20
+ "tag-input",
21
+ "textarea",
22
+ "time-picker"
23
+ ]
24
+ },
25
+ {
26
+ "selector": ".ui-data-table-sort-button",
27
+ "via": "::after",
28
+ "source": "src/styles/table-layout.css",
29
+ "targetMin": 24,
30
+ "components": [
31
+ "data-table"
32
+ ]
33
+ },
34
+ {
35
+ "selector": ".ui-number-input-step",
36
+ "via": "::after",
37
+ "source": "src/styles/control.css",
38
+ "targetMin": 24,
39
+ "components": [
40
+ "number-input"
41
+ ]
42
+ }
43
+ ]
44
+ },
45
+ "spacing": {
46
+ "scale": "--space-* measures the distance BETWEEN things: page sections, siblings, a Card and its content.",
47
+ "doesNotApplyTo": "The INTERIOR of a control. That geometry is derived from the control band, not from --space-*, so a control's own label sits closer to its edge than --space-2 and is not a finding.",
48
+ "derivation": "interior height = --control-height − (interior padding × 2)",
49
+ "predicate": "An element is a control when its painted block-size equals the resolved --control-height for its size. Segmented's track, Button, Input and Select all satisfy it — a gate that applies a --space-* floor inside a control flags all four, not just Segmented.",
50
+ "reference": "docs/SPACING.md#control-interiors-are-not-on-this-scale"
51
+ },
52
+ "issues": {
53
+ "503": "Segmented track padding read against a --space-2 floor that does not govern control interiors.",
54
+ "506": "NumberInput steppers read as 24×13 (paint); the target is 24×26.",
55
+ "507": "DatePicker calendar button read as 20×20 (paint); the target is 24×24."
56
+ }
57
+ }
@@ -0,0 +1,109 @@
1
+ # Measurement contract
2
+
3
+ **What box this library means when it says a target is 24×24, and where the `--space-*` scale stops
4
+ applying.** The facts below also ship as data at
5
+ `node_modules/@godxjp/ui/dist/contracts/measurement.json`, so a gate can read them without reading
6
+ this page.
7
+
8
+ ## Why this page exists
9
+
10
+ Three issues — [#503], [#506], [#507] — were each closed and reopened four times between 23.0.0 and
11
+ 23.4.5. Every reopening carried a correct measurement. Every closing carried a correct fix. The two
12
+ sides were measuring different boxes:
13
+
14
+ | issue | the consumer's gate read | what the library ships |
15
+ | ----- | ------------------------ | ---------------------------- |
16
+ | #507 | `20×20` | a **24×24** target |
17
+ | #506 | `24×13` | a **24×26** target |
18
+ | #503 | text `5.1px` from edge | 5.1px **derived** from the band |
19
+
20
+ `getBoundingClientRect()` returns an element's **border box**, and a border box does not include an
21
+ absolutely-positioned pseudo-element. So a gate built on it reports the same number forever no
22
+ matter what the library ships — which means it cannot tell *fixed* from *ignored*, and reopening the
23
+ issue is the correct thing for it to do. Prose could not close that gap either: `docs/SPACING.md`
24
+ carried the #503 derivation from 23.4.0 onward and the issue was reopened twice afterwards, because
25
+ a gate cannot read prose.
26
+
27
+ ## Target size
28
+
29
+ **WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px.** The criterion measures the
30
+ **target**: the region that accepts the pointer action. That is not always the painted box, and
31
+ carrying the target on a pseudo-element is the technique [Understanding 2.5.8] names.
32
+
33
+ This library does that in three places, because in each one growing the **paint** would move
34
+ geometry that belongs to something else:
35
+
36
+ | selector | paint stays | why the paint cannot grow |
37
+ | ------------------------------- | ----------- | --------------------------------------------------------------------------- |
38
+ | `.ui-control-inline-affix-action` | 20×20 | it is an affix inside a 32px field; growing it moves the field's own geometry |
39
+ | `.ui-number-input-step` | 24×13 | two steppers stacked in a 32px band — 24×2 = 48 does not fit |
40
+ | `.ui-data-table-sort-button` | header size | a header cell's height is the table's row rhythm |
41
+
42
+ The authoritative list is `targetSize.expanders` in `dist/contracts/measurement.json`; it is
43
+ generated from the CSS, and `scripts/check-measurement-contract.mjs` proves every entry in a real
44
+ browser before release, so it cannot claim a target the library does not ship.
45
+
46
+ ### Measuring a target
47
+
48
+ Scan outward from the centre with `elementFromPoint` until the point stops belonging to the element.
49
+ This reads the real target, and it also catches the case `getBoundingClientRect` misses in the other
50
+ direction — a 44×44 button half-covered by something else still reports 44×44 from its rect.
51
+
52
+ ```js
53
+ function hitRegion(el) {
54
+ const r = el.getBoundingClientRect();
55
+ const cx = r.left + r.width / 2;
56
+ const cy = r.top + r.height / 2;
57
+ const edge = (dx, dy) => {
58
+ let reach = 0;
59
+ for (let i = 0; i <= 40; i += 0.25) {
60
+ const hit = document.elementFromPoint(cx + dx * i, cy + dy * i);
61
+ if (hit === el || el.contains(hit)) reach = i;
62
+ else break;
63
+ }
64
+ return reach;
65
+ };
66
+ return { width: edge(-1, 0) + edge(1, 0), height: edge(0, -1) + edge(0, 1) };
67
+ }
68
+ ```
69
+
70
+ If your gate cannot run a hit-test, read the contract and treat a listed selector as conforming at
71
+ its `targetMin` instead:
72
+
73
+ ```js
74
+ const contract = require("@godxjp/ui/contracts/measurement.json");
75
+ const expanded = contract.targetSize.expanders.map((e) => e.selector);
76
+
77
+ // inside the gate, per candidate element:
78
+ if (expanded.some((sel) => el.matches(sel) || el.closest(sel))) return; // target is on a ::after
79
+ ```
80
+
81
+ ## Spacing
82
+
83
+ `--space-*` measures the distance **between** things: page sections, siblings in a stack, a card's
84
+ shell against its content. It is **not** a floor for the **interior** of a control — that geometry
85
+ derives from the control band (`--control-height`, `--control-padding-x`).
86
+
87
+ ```
88
+ interior height = --control-height − (interior padding × 2)
89
+ ```
90
+
91
+ A gate can tell the two apart at runtime without a selector list:
92
+
93
+ > An element is a control when its painted block-size equals the resolved `--control-height` for its
94
+ > size.
95
+
96
+ `Segmented`'s track satisfies it, and so do `Button`, `Input` and `Select` — which is the useful part
97
+ of the test: a `--space-2` floor applied inside a control flags all four, not just the one that got
98
+ reported. `docs/SPACING.md` § *Control interiors are NOT on this scale* carries the full derivation
99
+ and the `--segmented-track-padding` knob for a service that wants a roomier control.
100
+
101
+ ## MCP
102
+
103
+ `get_rule` · `list_audit_rules` · `get_tokens` — and `draft_bug_report` if a measurement taken this
104
+ way still disagrees with what the library claims. Bring the number; that is what this page is for.
105
+
106
+ [#503]: https://github.com/godx-jp/godxjp-ui/issues/503
107
+ [#506]: https://github.com/godx-jp/godxjp-ui/issues/506
108
+ [#507]: https://github.com/godx-jp/godxjp-ui/issues/507
109
+ [understanding 2.5.8]: https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html
package/docs/SPACING.md CHANGED
@@ -89,6 +89,11 @@ this library holds — or the track grows to 44px (28 + 8 × 2) and stops lining
89
89
  control. The same derivation is why a `Button`'s own label sits ~5px inside its border. A control
90
90
  is sized by its band; the band is what a dense enterprise UI is for.
91
91
 
92
+ **For a gate, not a reader.** This derivation also ships as data at
93
+ `dist/contracts/measurement.json` (`spacing`), because gh#503 was reopened twice *after* this
94
+ section was written — prose cannot be read by the browser test that files the bug. See
95
+ `docs/MEASUREMENT-CONTRACT.md`.
96
+
92
97
  **The knob, if a service wants a roomier control.** `--segmented-track-padding` is a published
93
98
  component token (`src/tokens/components/segmented.css`); raising it re-derives the item height from
94
99
  the same formula, so the track stays exactly one control tall. Do not reach for a Tailwind `p-*` on
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "23.4.7",
4
- "godxUiMcp": "23.4.7",
3
+ "version": "23.4.8",
4
+ "godxUiMcp": "23.4.8",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -52,6 +52,7 @@
52
52
  "./styles/*": "./dist/styles/*.css",
53
53
  "./theme/*": "./dist/theme/*",
54
54
  "./tokens": "./dist/tokens/base.css",
55
+ "./contracts/measurement.json": "./dist/contracts/measurement.json",
55
56
  "./admin": {
56
57
  "types": "./dist/components/admin/index.d.ts",
57
58
  "import": "./dist/components/admin/index.js"
@@ -335,7 +336,7 @@
335
336
  "verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:no-external-assets && pnpm check:token-scale-bypass && pnpm check:radix-surface && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:disclosure-duplication && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:frame-coverage && pnpm test",
336
337
  "verify:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:token-scale-bypass && pnpm check:radix-surface && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:disclosure-duplication && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync && pnpm check:contrast && pnpm check:visual-audit && pnpm test",
337
338
  "verify:ci": "pnpm verify:ci:static && pnpm check:frame-contracts && pnpm test",
338
- "verify:ci:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:registry && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:no-external-assets && pnpm check:token-scale-bypass && pnpm check:no-antd-runtime && pnpm check:radix-surface && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:disclosure-duplication && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-lockstep && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-catalog-completeness && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:gate-coverage && pnpm check:mcp-prop-sync && pnpm check:catalog-contradictions && pnpm check:catalog-snippets",
339
+ "verify:ci:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:registry && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:no-external-assets && pnpm check:token-scale-bypass && pnpm check:no-antd-runtime && pnpm check:radix-surface && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:disclosure-duplication && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-lockstep && pnpm check:measurement-contract && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-catalog-completeness && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:gate-coverage && pnpm check:mcp-prop-sync && pnpm check:catalog-contradictions && pnpm check:catalog-snippets",
339
340
  "verify:browser": "pnpm check:contrast && pnpm check:text-ink-clip && pnpm check:font-fallback-metrics && pnpm check:visual-audit",
340
341
  "verify:release": "pnpm verify:static && pnpm check:frame-contracts && pnpm check:frame-coverage",
341
342
  "verify:publish-tree": "pnpm build && pnpm check:packed-public-contract && pnpm check:use-client && pnpm check:dist-tokens-resolve",
@@ -399,6 +400,8 @@
399
400
  "check:data-entry-frame-runtime": "node scripts/check-data-entry-frame-runtime.mjs",
400
401
  "check:data-table-empty-inset": "node scripts/check-data-table-empty-inset.mjs",
401
402
  "check:number-input-step-target": "node scripts/check-number-input-step-target.mjs",
403
+ "check:measurement-contract": "node scripts/gen-measurement-contract.mjs --check",
404
+ "check:measurement-contract-browser": "node scripts/check-measurement-contract.mjs",
402
405
  "check:mcp-token-sync": "node scripts/gen-component-tokens.mjs --check",
403
406
  "check:email-token-sync": "node scripts/gen-email-tokens.mjs --check",
404
407
  "check:focus-ring-paint": "node scripts/check-focus-ring-paint.mjs",