@godxjp/ui 33.10.1 → 33.12.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.
Files changed (59) hide show
  1. package/DESIGN.md +1 -0
  2. package/README.md +3 -3
  3. package/agent/DESIGN.md +1 -0
  4. package/agent/START-HERE.md +7 -7
  5. package/agent/components/Anchor.json +1 -1
  6. package/agent/components/AppShell.json +1 -1
  7. package/agent/components/Cascader.json +1 -0
  8. package/agent/components/DataTable.json +1 -1
  9. package/agent/components/DatePicker.json +1 -0
  10. package/agent/components/Input.json +1 -0
  11. package/agent/components/NumberInput.json +1 -0
  12. package/agent/components/Pagination.json +1 -1
  13. package/agent/components/PasswordInput.json +1 -0
  14. package/agent/components/ResizablePanel.json +1 -1
  15. package/agent/components/ScrollArea.json +1 -1
  16. package/agent/components/SearchInput.json +1 -0
  17. package/agent/components/Select.json +1 -0
  18. package/agent/components/Table.json +1 -1
  19. package/agent/components/TimePicker.json +1 -0
  20. package/agent/components/Topbar.json +8 -2
  21. package/agent/components/TopbarItem.json +7 -1
  22. package/agent/components/TreeSelect.json +1 -0
  23. package/agent/components-index.json +7 -7
  24. package/agent/components.json +31 -10
  25. package/agent/index.json +6 -6
  26. package/agent/llms.txt +6 -6
  27. package/agent/tokens.json +39 -3
  28. package/dist/components/data-display/data-table.js +2 -0
  29. package/dist/components/data-display/table.js +3 -0
  30. package/dist/components/data-entry/input.js +16 -1
  31. package/dist/components/data-entry/search-input.js +1 -1
  32. package/dist/components/layout/app-shell.js +16 -6
  33. package/dist/components/layout/topbar-item.d.ts +1 -0
  34. package/dist/components/layout/topbar-item.js +9 -4
  35. package/dist/components/layout/topbar.d.ts +1 -1
  36. package/dist/components/layout/topbar.js +32 -3
  37. package/dist/components/navigation/pagination.js +4 -0
  38. package/dist/contracts/measurement.json +1 -1
  39. package/dist/lab/anchor.js +11 -1
  40. package/dist/lib/focus-reveal.d.ts +5 -0
  41. package/dist/lib/focus-reveal.js +19 -0
  42. package/dist/lib/hooks.js +5 -2
  43. package/dist/props/components/layout.prop.d.ts +13 -0
  44. package/dist/props/registry.d.ts +9 -1
  45. package/dist/props/registry.js +10 -0
  46. package/dist/styles/control.css +131 -3
  47. package/dist/styles/focus-ring.css +79 -19
  48. package/dist/styles/layers.json +1 -1
  49. package/dist/styles/layout.css +4 -4
  50. package/dist/styles/shell-layout.css +48 -0
  51. package/dist/styles.css +1 -1
  52. package/dist/tokens/base.css +2 -0
  53. package/dist/tokens/components/control.css +4 -1
  54. package/dist/tokens/components/resizable.css +4 -0
  55. package/dist/tokens/components/shell.css +3 -0
  56. package/dist/tokens/foundation.css +4 -1
  57. package/docs/TOKENS.md +52 -23
  58. package/package.json +2 -2
  59. package/scripts/visual-audit.mjs +33 -3
@@ -51,3 +51,5 @@
51
51
  @import "./components/marquee.css";
52
52
  @import "./components/affix.css";
53
53
  @import "./components/anchor.css";
54
+
55
+ @import "./components/resizable.css";
@@ -234,6 +234,8 @@
234
234
 
235
235
  --control-affix-inset-inline-end: var(--space-2);
236
236
  --control-affix-action-size: 1.5rem;
237
+
238
+ --control-affix-touch-target-size: var(--band-height-xl);
237
239
  --control-affix-action-radius: initial;
238
240
  --control-affix-icon-size: var(--icon-size-md);
239
241
 
@@ -499,7 +501,8 @@
499
501
  --control-height-default: var(--band-height-xl);
500
502
 
501
503
  --search-input-end-padding: calc(
502
- var(--search-input-edge-inset) + var(--touch-target-min) / 2 + var(--band-height-xl) / 2
504
+ var(--search-input-edge-inset) + var(--touch-target-min) / 2 +
505
+ var(--control-affix-touch-target-size) / 2
503
506
  );
504
507
  }
505
508
  }
@@ -0,0 +1,4 @@
1
+ :root {
2
+
3
+ --resizable-handle-size: var(--space-2);
4
+ }
@@ -155,6 +155,9 @@
155
155
  --topbar-inset: 0px;
156
156
  --topbar-gap: var(--space-2);
157
157
 
158
+ --topbar-identity-max-inline-size: 40%;
159
+ --topbar-identity-min-text-inline-size: 3ch;
160
+
158
161
  --topbar-background-alpha: initial;
159
162
  --topbar-backdrop-blur-size: initial;
160
163
 
@@ -148,9 +148,12 @@
148
148
 
149
149
  --focus-ring-offset: var(--focus-outline-offset);
150
150
 
151
+ --focus-ring-inset-offset: calc(-1 * (var(--focus-ring-width) + var(--focus-outline-offset)));
152
+ --focus-ring-contained-offset: initial;
153
+
151
154
  --focus-ring-clip-margin: 8px;
152
155
 
153
- --region-focus-ring-width: 0;
156
+ --region-focus-ring-width: var(--focus-ring-weight);
154
157
  --region-focus-ring-color: initial;
155
158
 
156
159
  --gradient-brand: none;
package/docs/TOKENS.md CHANGED
@@ -277,7 +277,11 @@ The census confirmed the number and rejected the diagnosis: **`width` is not one
277
277
 
278
278
  **Px, never rem.** A stroke is a _device_ line: its job is to read as one crisp rule at any type size, so it must not grow when the root font-size does. Every token that moved onto this scale was already `px` for that reason; a `rem` thickness (`--slider-track-height: 0.375rem`) is a different decision and stayed where it was. **Not `--scaling`-multiplied** either — `--scaling` is a density knob, and a 1px divider at 0.92px would blur rather than tighten.
279
279
 
280
- The focus mark reads this scale too, through `--focus-ring-weight` — `var(--focus-outline-weight)`, which is `var(--stroke-hairline)` (the light 1px mark the ON state ships). `--focus-ring-width` is DERIVED from it (`weight × --focus-outline`, the on/off switch) and must never be set directly; a theme that wants a 2px focus perimeter (WCAG 2.2 SC 2.4.13) sets `--focus-ring-weight: var(--stroke-md)`. See docs/CUSTOMER-THEMING.md.
280
+ The focus mark reads this scale through `--focus-ring-weight`: the default is `--stroke-md` (2px), with a 2px outline gap. `--focus-ring-width` is derived from the weight and the global `--focus-outline` switch; do not set it directly.
281
+
282
+ Clipping surfaces use `--focus-ring-inset-offset` (default minus the mark width plus the gap, -4px) through the inherited `--focus-ring-contained-offset` context. Pagination, tab strips, cards, bars and scrolling surfaces keep the mark inside their boundary without adding padding. A surface-coloured inset backing separates the brand mark from brand-filled controls. Keyboard-scroll regions show their inset mark by default; `--region-focus-ring-width: 0` is an explicit opt-out.
283
+
284
+ Resize separators use `--resizable-handle-size` (default `--space-2`, 8px), with the inline or block axis selected by ARIA orientation. This reserves room for the inset mark even on an empty handle; focusing does not change its size.
281
285
 
282
286
  #### `--band-height-*` — the vertical extent of a horizontal band
283
287
 
@@ -487,23 +491,23 @@ It is reserved for `@container` / `@media` re-points. Everything static stays in
487
491
 
488
492
  Fifteen existing token defaults build on the eight first-pass changes. Body stays 14px/1.7, labels and headings stay 500, with no Latin-style negative tracking on Japanese. Mobile gutters remain 16px. Zebra stays off; table numeric alignment and sidebar active styling are unchanged. Card header-to-body spacing grows to 24px; horizontal card/table insets and section gaps stay 16px so fixed-width date columns remain on one line. No new token or component API.
489
493
 
490
- | Token | Previous | New |
491
- |---|---|---|
492
- | `--space-page-x` | `var(--space-6)` | `var(--space-8)` |
493
- | `--space-page-y` | `var(--space-6)` | `var(--space-8)` |
494
- | `--card-space-body-y` | `var(--space-section-active)` | `var(--space-6)` |
495
- | `--page-title-font-size` | `var( --heading-h1, calc( var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio) * var(--font-size-ratio) ) )` | `var( --heading-h1, calc( var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio) * var(--font-size-ratio) ) )` |
496
- | `--page-title-font-size-compact` | `var( --heading-h2, calc(var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio)) )` | `var( --heading-h2, calc(var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio)) )` |
497
- | `--overlay-background` | `rgb(0 0 0 / 0.5)` | `rgb(0 0 0 / 0.4)` |
498
- | `--card-title-font-size` | `var(--font-size-base)` | `calc(var(--font-size-base) * var(--font-size-ratio))` |
499
- | `--card-shadow` | `initial` | `none` |
500
- | `--form-field-row-gap` | `var(--space-3)` | `var(--space-3)` |
501
- | `--form-block-gap` | `var(--space-4)` | `var(--space-6)` |
502
- | `--control-height-default` | `var(--band-height-md)` | `var(--band-height-lg)` |
503
- | `--control-focus-ring-width` | `initial` | `var(--stroke-md)` |
504
- | `--table-row-height-default` | `var(--band-height-md)` | `var(--band-height-lg)` |
505
- | `--focus-outline-weight` | `initial` | `var(--stroke-md)` |
506
- | `--focus-outline-offset` | `0px` | `2px` |
494
+ | Token | Previous | New |
495
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
496
+ | `--space-page-x` | `var(--space-6)` | `var(--space-8)` |
497
+ | `--space-page-y` | `var(--space-6)` | `var(--space-8)` |
498
+ | `--card-space-body-y` | `var(--space-section-active)` | `var(--space-6)` |
499
+ | `--page-title-font-size` | `var( --heading-h1, calc( var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio) * var(--font-size-ratio) ) )` | `var( --heading-h1, calc( var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio) * var(--font-size-ratio) ) )` |
500
+ | `--page-title-font-size-compact` | `var( --heading-h2, calc(var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio)) )` | `var( --heading-h2, calc(var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio)) )` |
501
+ | `--overlay-background` | `rgb(0 0 0 / 0.5)` | `rgb(0 0 0 / 0.4)` |
502
+ | `--card-title-font-size` | `var(--font-size-base)` | `calc(var(--font-size-base) * var(--font-size-ratio))` |
503
+ | `--card-shadow` | `initial` | `none` |
504
+ | `--form-field-row-gap` | `var(--space-3)` | `var(--space-3)` |
505
+ | `--form-block-gap` | `var(--space-4)` | `var(--space-6)` |
506
+ | `--control-height-default` | `var(--band-height-md)` | `var(--band-height-lg)` |
507
+ | `--control-focus-ring-width` | `initial` | `var(--stroke-md)` |
508
+ | `--table-row-height-default` | `var(--band-height-md)` | `var(--band-height-lg)` |
509
+ | `--focus-outline-weight` | `initial` | `var(--stroke-md)` |
510
+ | `--focus-outline-offset` | `0px` | `2px` |
507
511
 
508
512
  ### Complete restoration (both passes)
509
513
 
@@ -514,8 +518,17 @@ Load after library and preset styles. The original input colors restore the orig
514
518
  --space-page-x: var(--space-6);
515
519
  --space-page-y: var(--space-6);
516
520
  --card-space-body-y: var(--space-section-active);
517
- --page-title-font-size: var( --heading-h1, calc( var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio) * var(--font-size-ratio) ) );
518
- --page-title-font-size-compact: var( --heading-h2, calc(var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio)) );
521
+ --page-title-font-size: var(
522
+ --heading-h1,
523
+ calc(
524
+ var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio) *
525
+ var(--font-size-ratio)
526
+ )
527
+ );
528
+ --page-title-font-size-compact: var(
529
+ --heading-h2,
530
+ calc(var(--font-size-base) * var(--font-size-ratio) * var(--font-size-ratio))
531
+ );
519
532
  --overlay-background: rgb(0 0 0 / 0.5);
520
533
  --card-title-font-size: var(--font-size-base);
521
534
  --card-shadow: initial;
@@ -528,14 +541,19 @@ Load after library and preset styles. The original input colors restore the orig
528
541
  --focus-outline-offset: 0px;
529
542
  --button-radius: var(--radius-md, calc(var(--radius) / var(--radius-ratio)));
530
543
  --control-shadow: var(--shadow-xs);
531
- --shadow-md: 0 4px 6px -1px rgb(var(--shadow-color) / 0.1), 0 2px 4px -2px rgb(var(--shadow-color) / 0.1);
532
- --shadow-lg: 0 10px 15px -3px rgb(var(--shadow-color) / 0.1), 0 4px 6px -4px rgb(var(--shadow-color) / 0.1);
544
+ --shadow-md:
545
+ 0 4px 6px -1px rgb(var(--shadow-color) / 0.1), 0 2px 4px -2px rgb(var(--shadow-color) / 0.1);
546
+ --shadow-lg:
547
+ 0 10px 15px -3px rgb(var(--shadow-color) / 0.1), 0 4px 6px -4px rgb(var(--shadow-color) / 0.1);
533
548
  --duration-fast: 150ms;
534
549
  --duration-base: 250ms;
535
550
  --duration-slow: 500ms;
536
551
  --input: 30 7% 53%;
537
552
  }
538
- .dark, :root[data-theme="dark"] { --input: 45 6% 47%; }
553
+ .dark,
554
+ :root[data-theme="dark"] {
555
+ --input: 45 6% 47%;
556
+ }
539
557
 
540
558
  .ui-scale-fixed {
541
559
  --space-page-x: var(--space-6);
@@ -543,3 +561,14 @@ Load after library and preset styles. The original input colors restore the orig
543
561
  --space-section: var(--space-4);
544
562
  }
545
563
  ```
564
+
565
+ ### Input affix touch targets
566
+
567
+ `--control-affix-touch-target-size` defaults to `var(--band-height-xl)` (44px) and applies
568
+ only under `pointer: coarse`. It sizes the action box, not the glyph. Input reserves the measured
569
+ width of its complete trailing slot plus the existing inset and gap, including two adjacent
570
+ 44px actions in a 200px field. `--input-trailing-measured-size` is runtime measurement, not a
571
+ service override. `allowClear` still replaces the suffix; DatePicker and TimePicker still swap
572
+ clear and open actions. NumberInput lays out its two steppers horizontally on coarse pointers.
573
+ SearchInput retains its glyph centre while sharing this target token. Fine-pointer geometry is
574
+ unchanged. Override the token on the field or in the service theme; no new prop is required.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "33.10.1",
4
- "godxUiMcp": "33.10.1",
3
+ "version": "33.12.1",
4
+ "godxUiMcp": "33.12.1",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -453,9 +453,9 @@ export const PROBE_RULES = [
453
453
  id: "c04-compact-account-name",
454
454
  severity: "warn",
455
455
  category: "a11y",
456
- standard: `compact (<600px) bar shows no account name (${RC}); trigger name per WCAG 2.2 SC 4.1.2`,
456
+ standard: `compact (<600px) bar shows avatar-only or measured bounded shared-device identity (${RC}); trigger name per WCAG 2.2 SC 4.1.2`,
457
457
  kind: "probe",
458
- fix: "Below 600px the bar shows the avatar only: hide the account name (it belongs in the opened account menu) and make the avatar the trigger — accessible name + aria-haspopup, opening identity and account controls.",
458
+ fix: "Shared-device exception: Topbar identity with TopbarItem truncate keeps a bounded, ellipsised name and full title/accessible name. Otherwise below 600px the bar shows the avatar only: hide the account name (it belongs in the opened account menu) and make the avatar the trigger — accessible name + aria-haspopup, opening identity and account controls.",
459
459
  },
460
460
  {
461
461
  id: "c05-compact-editor-inset",
@@ -733,7 +733,7 @@ export function evaluateProbes(raw, { url, viewport }) {
733
733
  // C04 — compact account access without the name in the bar.
734
734
  const accounts = applies("c04-compact-account-name") ? raw.c04?.mapped || [] : [];
735
735
  for (const a of accounts) {
736
- if (a.nameVisible)
736
+ if (a.nameVisible && !a.compactIdentity)
737
737
  add(
738
738
  "c04-compact-account-name",
739
739
  a.element,
@@ -1158,7 +1158,37 @@ export function collectProbes(opts) {
1158
1158
  const pushAccount = (region, nameEl, avatar, t, nameText) => {
1159
1159
  if (avatar) accountAvatars.add(avatar);
1160
1160
  const text = nameText ?? (nameEl && shown(nameEl) ? snippet(nameEl.textContent) : "");
1161
+ const label = region.querySelector('[data-slot="topbar-item-label"]');
1162
+ const identity = region.closest('[data-slot="topbar-identity"]');
1163
+ const bar = identity?.closest('[data-slot="topbar"]');
1164
+ const r = region.getBoundingClientRect();
1165
+ const b = bar?.getBoundingClientRect();
1166
+ const l = label?.getBoundingClientRect();
1167
+ // An opt-in is not an exemption by itself: measure the visible label, bounded share,
1168
+ // containment, and full-name recovery. Old full-width labels still fail C04.
1169
+ const compactIdentity = !!(
1170
+ identity &&
1171
+ b &&
1172
+ label &&
1173
+ l &&
1174
+ text &&
1175
+ region.getAttribute("data-truncate") === "true" &&
1176
+ cs(label).textOverflow === "ellipsis" &&
1177
+ cs(label).whiteSpace === "nowrap" &&
1178
+ l.width > 1 &&
1179
+ l.height > 1 &&
1180
+ l.left >= r.left - 1 &&
1181
+ l.right <= r.right + 1 &&
1182
+ r.width <= b.width * 0.4 + 1 &&
1183
+ r.left >= b.left - 1 &&
1184
+ r.right <= b.right + 1 &&
1185
+ r.left >= -1 &&
1186
+ r.right <= width + 1 &&
1187
+ region.getAttribute("title")?.includes(label.textContent) &&
1188
+ accName(region).includes(label.textContent)
1189
+ );
1161
1190
  accounts.push({
1191
+ compactIdentity,
1162
1192
  element: desc(region),
1163
1193
  nameVisible: !!text,
1164
1194
  nameText: text,