@godxjp/ui 32.2.0 → 32.2.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.
@@ -3,7 +3,7 @@
3
3
  You are about to write code against a design system you did not author. This file is the whole
4
4
  contract. Read it before you write JSX.
5
5
 
6
- **This catalog describes `@godxjp/ui` 32.2.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 32.2.1.** If the project you are editing has a different
7
7
  version in its `package.json`, read the pinned catalog for THAT version instead
8
8
  (`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
9
9
  not exist yet; older, and it hides props that do. Neither failure announces itself.
@@ -38,7 +38,7 @@
38
38
  "type": "number"
39
39
  },
40
40
  {
41
- "description": "On the ROOT. A page-level key that OPENS the menu — `\"f\"` for a filter menu, `\"mod+shift+v\"` for a view menu (gh#1264). Same combo syntax and guard as SearchInput/CommandPalette: a bare key is ignored while focus is in an editable element or an IME is composing. Off by default; ignored while `disabled`. Show the key in the trigger (e.g. with `DropdownMenuShortcut`) so it is discoverable.",
41
+ "description": "On the ROOT. A page-level key that OPENS the menu — `\"f\"` for a filter menu, `\"mod+shift+v\"` for a view menu (gh#1264). Same combo syntax and guard as SearchInput/CommandPalette: a bare key is ignored while focus is in an editable element or an IME is composing. Off by default; ignored while `disabled`. The trigger gets `aria-keyshortcuts` automatically (an explicit consumer value wins); show the key visibly too (e.g. `DropdownMenuShortcut`). Never fires from inside a dialog, menu or listbox that does not contain the menu (gh#1267).",
42
42
  "name": "shortcut",
43
43
  "type": "string"
44
44
  },
@@ -90,6 +90,12 @@
90
90
  "name": "views",
91
91
  "type": "ReactNode"
92
92
  },
93
+ {
94
+ "defaultValue": "\"above\"",
95
+ "description": "Where `views` sits (gh#1267). `above` is its own row over the strip; `inline` puts it at the START of the strip's first row so an idle bar stays ONE row (with `above`, an idle bar measured 55 → 95px and one table row fewer above the fold at 1440×900).",
96
+ "name": "viewsPlacement",
97
+ "type": "\"above\" | \"inline\""
98
+ },
93
99
  {
94
100
  "description": "Extra classes on the root element.",
95
101
  "name": "className",
@@ -8301,7 +8301,7 @@
8301
8301
  "type": "number"
8302
8302
  },
8303
8303
  {
8304
- "description": "On the ROOT. A page-level key that OPENS the menu — `\"f\"` for a filter menu, `\"mod+shift+v\"` for a view menu (gh#1264). Same combo syntax and guard as SearchInput/CommandPalette: a bare key is ignored while focus is in an editable element or an IME is composing. Off by default; ignored while `disabled`. Show the key in the trigger (e.g. with `DropdownMenuShortcut`) so it is discoverable.",
8304
+ "description": "On the ROOT. A page-level key that OPENS the menu — `\"f\"` for a filter menu, `\"mod+shift+v\"` for a view menu (gh#1264). Same combo syntax and guard as SearchInput/CommandPalette: a bare key is ignored while focus is in an editable element or an IME is composing. Off by default; ignored while `disabled`. The trigger gets `aria-keyshortcuts` automatically (an explicit consumer value wins); show the key visibly too (e.g. `DropdownMenuShortcut`). Never fires from inside a dialog, menu or listbox that does not contain the menu (gh#1267).",
8305
8305
  "name": "shortcut",
8306
8306
  "type": "string"
8307
8307
  },
@@ -8560,6 +8560,12 @@
8560
8560
  "name": "views",
8561
8561
  "type": "ReactNode"
8562
8562
  },
8563
+ {
8564
+ "defaultValue": "\"above\"",
8565
+ "description": "Where `views` sits (gh#1267). `above` is its own row over the strip; `inline` puts it at the START of the strip's first row so an idle bar stays ONE row (with `above`, an idle bar measured 55 → 95px and one table row fewer above the fold at 1440×900).",
8566
+ "name": "viewsPlacement",
8567
+ "type": "\"above\" | \"inline\""
8568
+ },
8563
8569
  {
8564
8570
  "description": "Extra classes on the root element.",
8565
8571
  "name": "className",
package/agent/index.json CHANGED
@@ -48,7 +48,7 @@
48
48
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.2.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.2.1/agent/index.json"
52
52
  },
53
53
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
@@ -62,5 +62,5 @@
62
62
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
63
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
64
  },
65
- "version": "32.2.0"
65
+ "version": "32.2.1"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
3
  > A Japanese-enterprise React design system: 161 components, 2193 design tokens,
4
- > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.2.0.
4
+ > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.2.1.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@32.2.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@32.2.1`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -26,7 +26,7 @@ that can only fetch URLs.
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v32.2.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v32.2.1/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
@@ -916,7 +916,7 @@ DataTable.Content = function DataTableContent() {
916
916
  const el = scrollRef.current;
917
917
  if (!el) return;
918
918
  for (const cell of el.querySelectorAll("tbody td.ui-data-table-ellipsis")) {
919
- if (cell === document.activeElement) continue;
919
+ if (cell.contains(document.activeElement)) continue;
920
920
  const ownTitle = cell.dataset.truncatedTitle !== void 0;
921
921
  if (cell.scrollWidth - cell.clientWidth > 1) {
922
922
  cell.dataset.truncated = "";
@@ -8,7 +8,12 @@ import { Label } from "../data-entry/label.js";
8
8
  import { cn } from "../../lib/utils.js";
9
9
  import { resolveFieldA11y } from "../../lib/field-a11y.js";
10
10
  import { useDebouncedValue } from "../../lib/hooks.js";
11
- import { parseShortcut, shortcutHint, shouldHandleShortcut } from "../../lib/shortcut.js";
11
+ import {
12
+ ariaKeyShortcuts,
13
+ parseShortcut,
14
+ shortcutHint,
15
+ shouldHandleShortcut
16
+ } from "../../lib/shortcut.js";
12
17
  function SearchInput({
13
18
  value: controlledValue,
14
19
  defaultValue = "",
@@ -55,7 +60,7 @@ function SearchInput({
55
60
  React.useEffect(() => {
56
61
  if (!shortcutSpec || disabled) return;
57
62
  const handleKeyDown = (event) => {
58
- if (!shouldHandleShortcut(shortcutSpec, event)) return;
63
+ if (!shouldHandleShortcut(shortcutSpec, event, inputRef.current)) return;
59
64
  event.preventDefault();
60
65
  inputRef.current?.focus();
61
66
  };
@@ -78,6 +83,7 @@ function SearchInput({
78
83
  id: inputId,
79
84
  type: "text",
80
85
  role: "searchbox",
86
+ "aria-keyshortcuts": shortcutSpec ? ariaKeyShortcuts(shortcutSpec) : void 0,
81
87
  value,
82
88
  onChange: (e) => {
83
89
  setValue(e.target.value);
@@ -20,7 +20,7 @@ import {
20
20
  useLocale
21
21
  } from "react-aria-components";
22
22
  import { Check, ChevronRight } from "lucide-react";
23
- import { parseShortcut, shouldHandleShortcut } from "../../lib/shortcut.js";
23
+ import { ariaKeyShortcuts, parseShortcut, shouldHandleShortcut } from "../../lib/shortcut.js";
24
24
  import { cn } from "../../lib/utils.js";
25
25
  import { Slot } from "../../lib/slot.js";
26
26
  const DROPDOWN_MENU_PLACEMENT = {
@@ -189,9 +189,10 @@ function DropdownMenu({
189
189
  disabled,
190
190
  hover,
191
191
  racOwnsContextMenu,
192
- openedByHover
192
+ openedByHover,
193
+ keyShortcuts: shortcutSpec && !disabled ? ariaKeyShortcuts(shortcutSpec) : void 0
193
194
  }),
194
- [modal, wantsContextMenu, disabled, hover, racOwnsContextMenu, wantsHover]
195
+ [modal, wantsContextMenu, disabled, hover, racOwnsContextMenu, wantsHover, shortcutSpec]
195
196
  );
196
197
  return /* @__PURE__ */ jsx(DropdownMenuModalContext.Provider, { value: options, children: /* @__PURE__ */ jsx(
197
198
  MenuTrigger,
@@ -208,7 +209,7 @@ function DropdownMenuTrigger({ asChild, children, ...props }) {
208
209
  const menuState = React.useContext(
209
210
  RootMenuTriggerStateContext
210
211
  );
211
- const { contextMenuGesture, disabled, hover, racOwnsContextMenu } = React.useContext(DropdownMenuModalContext);
212
+ const { contextMenuGesture, disabled, hover, racOwnsContextMenu, keyShortcuts } = React.useContext(DropdownMenuModalContext);
212
213
  const dataState = state?.isOpen ? "open" : "closed";
213
214
  const wantsContextMenu = contextMenuGesture;
214
215
  const isDisabled = disabled || props.disabled;
@@ -251,6 +252,8 @@ function DropdownMenuTrigger({ asChild, children, ...props }) {
251
252
  return /* @__PURE__ */ jsx(Pressable, { isDisabled, children: React.cloneElement(children, {
252
253
  "data-slot": "dropdown-menu-trigger",
253
254
  "data-state": dataState,
255
+ // The root's `shortcut`, announced (gh#1267); a consumer's own value wins.
256
+ ...keyShortcuts && children.props["aria-keyshortcuts"] === void 0 ? { "aria-keyshortcuts": keyShortcuts } : {},
254
257
  // A right click target is usually a region rather than a button, and Shift+F10 needs it
255
258
  // focusable — so it is given a tab stop unless the consumer already placed one.
256
259
  ...wantsContextMenu && children.props.tabIndex === void 0 ? { tabIndex: 0 } : {},
@@ -270,6 +273,7 @@ function DropdownMenuTrigger({ asChild, children, ...props }) {
270
273
  isDisabled,
271
274
  "data-slot": "dropdown-menu-trigger",
272
275
  "data-state": dataState,
276
+ "aria-keyshortcuts": props["aria-keyshortcuts"] ?? keyShortcuts,
273
277
  children
274
278
  }
275
279
  );
@@ -1,6 +1,6 @@
1
1
  import type { ToolbarGroupProp, ToolbarProp } from "../../props/components/navigation.prop.js";
2
2
  export type { FilterBarChipProp, FilterBarChipProp as FilterBarChipProps, FilterBarFilterProp, FilterBarFilterProp as FilterBarFilterProps, FilterBarOverflowProp, FilterBarSearchProp, FilterBarSearchProp as FilterBarSearchProps, ToolbarGroupProp, ToolbarGroupProp as ToolbarGroupProps, ToolbarGroupProp as FilterBarGroupProp, ToolbarGroupProp as FilterBarGroupProps, ToolbarProp, ToolbarProp as ToolbarProps, ToolbarProp as FilterBarProp, ToolbarProp as FilterBarProps, } from "../../props/components/navigation.prop.js";
3
- export declare function Toolbar({ onClear, hasActiveFilters, sticky, overflow, search, filters, chips, onChipRemove, actions, resultCount, loading, disabled, error, clearDisplay, views, className, children, }: ToolbarProp): import("react").JSX.Element;
3
+ export declare function Toolbar({ onClear, hasActiveFilters, sticky, overflow, search, filters, chips, onChipRemove, actions, resultCount, loading, disabled, error, clearDisplay, views, viewsPlacement, className, children, }: ToolbarProp): import("react").JSX.Element;
4
4
  export declare function ToolbarGroup({ label, controlId, className, children }: ToolbarGroupProp): import("react").JSX.Element;
5
5
  /** Canonical list-page filter strip. `Toolbar` remains available for backwards compatibility. */
6
6
  export declare const FilterBar: typeof Toolbar;
@@ -25,6 +25,7 @@ function Toolbar({
25
25
  error,
26
26
  clearDisplay = "label",
27
27
  views,
28
+ viewsPlacement = "above",
28
29
  className,
29
30
  children
30
31
  }) {
@@ -90,7 +91,7 @@ function Toolbar({
90
91
  className: cn("ui-filter-bar", sticky && "ui-toolbar-sticky", className),
91
92
  "data-loading": loading || void 0,
92
93
  children: [
93
- views != null && /* @__PURE__ */ jsx("div", { className: "ui-filter-bar-views", children: views }),
94
+ views != null && viewsPlacement === "above" && /* @__PURE__ */ jsx("div", { className: "ui-filter-bar-views", children: views }),
94
95
  /* @__PURE__ */ jsxs(
95
96
  "div",
96
97
  {
@@ -100,6 +101,7 @@ function Toolbar({
100
101
  "data-overflow": overflow,
101
102
  className: "ui-toolbar",
102
103
  children: [
104
+ views != null && viewsPlacement === "inline" && /* @__PURE__ */ jsx("div", { className: "ui-filter-bar-views", "data-placement": "inline", children: views }),
103
105
  search && /* @__PURE__ */ jsx(
104
106
  SearchInput,
105
107
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "32.2.0",
3
+ "version": "32.2.1",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -25,9 +25,16 @@ export declare function isEditableTarget(target: EventTarget | null): boolean;
25
25
  /**
26
26
  * Should this keydown run the shortcut? The ONE gate every global shortcut goes through:
27
27
  * - never during IME composition (the key belongs to the conversion, gh#1054);
28
+ * - never from inside a dialog/menu/listbox that does not contain `owner` (gh#1267);
28
29
  * - the combo must match;
29
30
  * - a shortcut with NO command modifier (`"/"`, `"f"`, `"shift+?"`) is ignored while focus is in
30
31
  * an editable element, where that key is text. A modified one (`"mod+k"`) still fires there —
31
32
  * no text field types ⌘K — which is how GitHub's and Linear's palettes behave.
32
33
  */
33
- export declare function shouldHandleShortcut(spec: ShortcutSpec, event: KeyboardEvent): boolean;
34
+ export declare function shouldHandleShortcut(spec: ShortcutSpec, event: KeyboardEvent, owner?: Element | null): boolean;
35
+ /**
36
+ * The `aria-keyshortcuts` value for a spec (WAI-ARIA 1.2: modifiers then key, `+`-joined, with
37
+ * `Control`/`Meta`/`Alt`/`Shift` spelled out and a letter upper-cased): `mod+k` → `Meta+K` on
38
+ * Apple platforms, `Control+K` elsewhere; `"/"` → `/` (gh#1267).
39
+ */
40
+ export declare function ariaKeyShortcuts(spec: ShortcutSpec): string;
@@ -51,12 +51,29 @@ function isEditableTarget(target) {
51
51
  const role = target.getAttribute("role");
52
52
  return role === "textbox" || role === "combobox" || role === "searchbox";
53
53
  }
54
- function shouldHandleShortcut(spec, event) {
54
+ function shouldHandleShortcut(spec, event, owner) {
55
55
  if (isImeComposing(event) || !shortcutMatches(spec, event)) return false;
56
+ if (inForeignLayer(event.target, owner)) return false;
56
57
  const commandModifier = spec.mod || spec.meta || spec.ctrl || spec.alt;
57
58
  return commandModifier || !isEditableTarget(event.target);
58
59
  }
60
+ const SHORTCUT_LAYER = '[role="dialog"], [role="alertdialog"], [aria-modal="true"], [role="menu"], [role="listbox"]';
61
+ function inForeignLayer(target, owner) {
62
+ if (!(target instanceof Element)) return false;
63
+ const layer = target.closest(SHORTCUT_LAYER);
64
+ return layer !== null && !(owner && layer.contains(owner));
65
+ }
66
+ function ariaKeyShortcuts(spec) {
67
+ return [
68
+ spec.ctrl || spec.mod && !isApplePlatform() ? "Control" : "",
69
+ spec.meta || spec.mod && isApplePlatform() ? "Meta" : "",
70
+ spec.alt ? "Alt" : "",
71
+ spec.shift ? "Shift" : "",
72
+ spec.key.length === 1 ? spec.key.toUpperCase() : spec.key
73
+ ].filter(Boolean).join("+");
74
+ }
59
75
  export {
76
+ ariaKeyShortcuts,
60
77
  isEditableTarget,
61
78
  parseShortcut,
62
79
  shortcutHint,
@@ -112,9 +112,17 @@ export type ToolbarProp = {
112
112
  * filter set, so it is not one more filter in the strip.
113
113
  */
114
114
  views?: React.ReactNode;
115
+ /**
116
+ * Where `views` sits (gh#1267). `above` (default) is its own row over the strip; `inline` puts it
117
+ * at the START of the strip's first row, so an idle bar stays ONE row — measured with `above`: an
118
+ * idle bar 55 → 95px, one table row fewer above the fold at 1440×900.
119
+ */
120
+ viewsPlacement?: FilterBarViewsPlacementProp;
115
121
  className?: ClassNameProp;
116
122
  children?: ChildrenProp;
117
123
  };
124
+ /** @see ToolbarProp.viewsPlacement */
125
+ export type FilterBarViewsPlacementProp = "above" | "inline";
118
126
  /** @see ToolbarProp.clearDisplay */
119
127
  export type FilterBarClearDisplayProp = "label" | "icon";
120
128
  /** @see ToolbarGroup */
@@ -2596,6 +2596,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
2596
2596
  readonly file: "components/navigation.prop.ts";
2597
2597
  readonly vocabulary: readonly [];
2598
2598
  };
2599
+ readonly FilterBarViewsPlacementProp: {
2600
+ readonly group: "navigation";
2601
+ readonly file: "components/navigation.prop.ts";
2602
+ readonly vocabulary: readonly [];
2603
+ };
2599
2604
  readonly FilterBarOverflowProp: {
2600
2605
  readonly group: "navigation";
2601
2606
  readonly file: "components/navigation.prop.ts";
@@ -3028,6 +3028,11 @@ const COMPONENT_PROP_REGISTRY = {
3028
3028
  file: "components/navigation.prop.ts",
3029
3029
  vocabulary: []
3030
3030
  },
3031
+ FilterBarViewsPlacementProp: {
3032
+ group: "navigation",
3033
+ file: "components/navigation.prop.ts",
3034
+ vocabulary: []
3035
+ },
3031
3036
  FilterBarOverflowProp: {
3032
3037
  group: "navigation",
3033
3038
  file: "components/navigation.prop.ts",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "32.2.0",
2
+ "version": "32.2.1",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -535,7 +535,8 @@
535
535
  white-space: nowrap;
536
536
  }
537
537
 
538
- .ui-data-table-ellipsis[data-truncated]:focus-visible {
538
+ .ui-data-table-ellipsis[data-truncated]:focus-visible,
539
+ .ui-data-table-ellipsis[data-truncated]:has(:focus-visible) {
539
540
  white-space: normal;
540
541
  overflow-wrap: anywhere;
541
542
  }