@godxjp/ui 33.8.0 → 33.9.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/agent/index.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "counts": {
3
3
  "anti-ai-tells": 35,
4
- "components": 166,
4
+ "components": 169,
5
5
  "patterns": 28,
6
6
  "rules": 44,
7
7
  "tokens": 2235,
@@ -16,7 +16,7 @@
16
16
  },
17
17
  {
18
18
  "file": "components-index.json",
19
- "note": "50 KB — name + group + tagline for all 166. FETCH THIS FIRST, then fetch only the components you chose.",
19
+ "note": "52 KB — name + group + tagline for all 169. FETCH THIS FIRST, then fetch only the components you chose.",
20
20
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json"
21
21
  },
22
22
  {
@@ -54,7 +54,7 @@
54
54
  "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.",
55
55
  "read": {
56
56
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
57
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.8.0/agent/index.json"
57
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.9.1/agent/index.json"
58
58
  },
59
59
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
60
60
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
@@ -68,5 +68,5 @@
68
68
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
69
69
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
70
70
  },
71
- "version": "33.8.0"
71
+ "version": "33.9.1"
72
72
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 166 components, 2235 design tokens,
4
- > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.8.0.
3
+ > A Japanese-enterprise React design system: 169 components, 2235 design tokens,
4
+ > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.9.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@33.8.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@33.9.1`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -16,7 +16,7 @@ that can only fetch URLs.
16
16
  ## Catalog
17
17
 
18
18
  - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 28 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
19
- - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 50 KB — all 166 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
+ - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 52 KB — all 169 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
20
20
  - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–37 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
21
21
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.3 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
22
22
  - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 216 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1906 `component` knobs.
@@ -27,7 +27,7 @@ that can only fetch URLs.
27
27
  ## Pinning
28
28
 
29
29
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
30
- the tag: `.../godx-jp/godxjp-ui/v33.8.0/agent/...`. A catalog that does not match the installed
30
+ the tag: `.../godx-jp/godxjp-ui/v33.9.1/agent/...`. A catalog that does not match the installed
31
31
  package describes props that are absent, or hides props that are present, and says nothing either way.
32
32
 
33
33
  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.
@@ -9,8 +9,8 @@ export { CheckboxGroup } from "./checkbox-group.js";
9
9
  export { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
10
10
  export { Textarea } from "./textarea.js";
11
11
  export type { TextareaProps } from "./textarea.js";
12
- export { TranslatableInput, TranslatableTextarea } from "./translatable-input.js";
13
- export type { TranslatableValue, TranslatableInputProp, TranslatableInputProps, TranslatableTextareaProp, TranslatableTextareaProps, } from "./translatable-input.js";
12
+ export { TranslatableField, TranslatableInput, TranslatableTextarea } from "./translatable-input.js";
13
+ export type { TranslatableValue, TranslatableLocaleProp, TranslatableFieldProp, TranslatableFieldProps, TranslatableFieldControlProp, TranslatableInputProp, TranslatableInputProps, TranslatableTextareaProp, TranslatableTextareaProps, } from "./translatable-input.js";
14
14
  export { Form, useFormLayout, type FormLayoutContextValue } from "./form.js";
15
15
  export type { FormProp, FormProps } from "./form.js";
16
16
  export { FormField } from "./form-field.js";
@@ -17,7 +17,7 @@ import { Checkbox } from "./checkbox.js";
17
17
  import { CheckboxGroup } from "./checkbox-group.js";
18
18
  import { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
19
19
  import { Textarea } from "./textarea.js";
20
- import { TranslatableInput, TranslatableTextarea } from "./translatable-input.js";
20
+ import { TranslatableField, TranslatableInput, TranslatableTextarea } from "./translatable-input.js";
21
21
  import { Form, useFormLayout } from "./form.js";
22
22
  import { FormField } from "./form-field.js";
23
23
  import { FormErrors, FormErrorsProvider } from "./form-errors.js";
@@ -126,6 +126,7 @@ export {
126
126
  ToggleGroup,
127
127
  ToggleGroupItem,
128
128
  Transfer,
129
+ TranslatableField,
129
130
  TranslatableInput,
130
131
  TranslatableTextarea,
131
132
  TreeSelect,
@@ -1,6 +1,26 @@
1
1
  import * as React from "react";
2
- import type { TranslatableValue } from "../../props/components/data-entry.prop.js";
3
- export type { TranslatableValue, TranslatableInputProp, TranslatableInputProp as TranslatableInputProps, TranslatableTextareaProp, TranslatableTextareaProp as TranslatableTextareaProps, } from "../../props/components/data-entry.prop.js";
2
+ import type { TranslatableFieldProp } from "../../props/components/data-entry.prop.js";
3
+ export type { TranslatableValue, TranslatableLocaleProp, TranslatableFieldProp, TranslatableFieldProp as TranslatableFieldProps, TranslatableFieldControlProp, TranslatableInputProp, TranslatableInputProp as TranslatableInputProps, TranslatableTextareaProp, TranslatableTextareaProp as TranslatableTextareaProps, } from "../../props/components/data-entry.prop.js";
4
+ /**
5
+ * TranslatableField — the shared engine of {@link TranslatableInput}, {@link TranslatableTextarea}
6
+ * and `@godxjp/block-editor`'s `TranslatableRichText` (gh#1365): it owns the locale list, the active
7
+ * tab, the per-locale map, the fallback placeholder, the error marks and the FormField error report,
8
+ * and renders the ONE control in the panel through `renderControl`. Every prop it does not own is
9
+ * passed through to that control.
10
+ *
11
+ * WHY THE SWITCHER IS `Tabs`, NOT `Segmented`. The WAI-ARIA APG radio group is for CHOOSING A VALUE
12
+ * — a segmented control's choice is form state and is submitted with the form. The locale here is
13
+ * not form state: every locale's text is submitted together, and the switcher only decides which
14
+ * of them the one editing region shows. That is the APG tabs pattern — a tablist whose selected
15
+ * tab controls one tabpanel — so a screen reader announces "English, tab, 2 of 3" and the region
16
+ * that follows, instead of offering a "language" question the form never asks.
17
+ *
18
+ * ONE CONTROL, NOT ONE PER LOCALE. The panel holds a single control whose value, `lang` and
19
+ * placeholder follow the active tab (`Tabs.controls` points the tab at it, gh#1021). Swapping
20
+ * elements per tab would hand the forwarded `ref`, the FormField `id` and an IME composition to a
21
+ * new node on every switch.
22
+ */
23
+ export declare function TranslatableField({ renderControl, toPlaceholder, value: valueProp, defaultValue, onValueChange, locales: localesProp, defaultLocale, fallbackLocale: fallbackLocaleProp, errors, size, placeholder, className, ...rest }: TranslatableFieldProp): React.JSX.Element;
4
24
  /**
5
25
  * TranslatableInput — ONE text field that holds a value per locale (`{ ja, en, vi }`), switched by
6
26
  * locale tabs above it (gh#1336). The locale list and the first tab come from `AppProvider`
@@ -8,22 +28,6 @@ export type { TranslatableValue, TranslatableInputProp, TranslatableInputProp as
8
28
  * its placeholder; a locale with an error is marked on its tab. Every other `Input` prop applies to
9
29
  * the single control, and `ref` is that control's `<input>`.
10
30
  */
11
- export declare const TranslatableInput: React.ForwardRefExoticComponent<Omit<import("./input.js").InputProp, "defaultValue" | "lang" | "onValueChange" | "value"> & {
12
- value?: import("../../props/index.js").ValueProp<TranslatableValue>;
13
- defaultValue?: import("../../props/index.js").DefaultValueProp<TranslatableValue>;
14
- onValueChange?: import("../../props/index.js").OnValueChangeProp<TranslatableValue>;
15
- locales?: readonly string[];
16
- defaultLocale?: string;
17
- fallbackLocale?: string;
18
- errors?: Partial<Record<string, import("../../props/index.js").ErrorProp>>;
19
- } & React.RefAttributes<HTMLInputElement>>;
31
+ export declare const TranslatableInput: React.ForwardRefExoticComponent<Omit<import("./input.js").InputProp, "defaultValue" | "lang" | "onValueChange" | "value"> & import("./translatable-input.js").TranslatableLocaleProp & React.RefAttributes<HTMLInputElement>>;
20
32
  /** TranslatableTextarea — {@link TranslatableInput} over a `Textarea`; `ref` is the `<textarea>`. */
21
- export declare const TranslatableTextarea: React.ForwardRefExoticComponent<Omit<import("./textarea.js").TextareaProp, "defaultValue" | "lang" | "onValueChange" | "value"> & {
22
- value?: import("../../props/index.js").ValueProp<TranslatableValue>;
23
- defaultValue?: import("../../props/index.js").DefaultValueProp<TranslatableValue>;
24
- onValueChange?: import("../../props/index.js").OnValueChangeProp<TranslatableValue>;
25
- locales?: readonly string[];
26
- defaultLocale?: string;
27
- fallbackLocale?: string;
28
- errors?: Partial<Record<string, import("../../props/index.js").ErrorProp>>;
29
- } & React.RefAttributes<HTMLTextAreaElement>>;
33
+ export declare const TranslatableTextarea: React.ForwardRefExoticComponent<Omit<import("./textarea.js").TextareaProp, "defaultValue" | "lang" | "onValueChange" | "value"> & import("./translatable-input.js").TranslatableLocaleProp & React.RefAttributes<HTMLTextAreaElement>>;
@@ -21,7 +21,8 @@ function localeName(code, uiLocale) {
21
21
  }
22
22
  const idPart = (code) => code.replace(/[^A-Za-z0-9_-]/g, "_");
23
23
  function TranslatableField({
24
- kind,
24
+ renderControl,
25
+ toPlaceholder,
25
26
  value: valueProp,
26
27
  defaultValue,
27
28
  onValueChange,
@@ -32,7 +33,6 @@ function TranslatableField({
32
33
  size,
33
34
  placeholder,
34
35
  className,
35
- forwardedRef,
36
36
  ...rest
37
37
  }) {
38
38
  const { t, locale: uiLocale, fallbackLocale: appFallbackLocale } = useTranslation();
@@ -74,11 +74,11 @@ function TranslatableField({
74
74
  const showOwnError = hasActiveError && !insideFormField;
75
75
  const controlProps = {
76
76
  ...rest,
77
- ref: forwardedRef,
77
+ size,
78
78
  lang: active,
79
79
  value: activeText,
80
80
  onValueChange: handleText,
81
- placeholder: fallbackText ? fallbackText : placeholder,
81
+ placeholder: fallbackText ? toPlaceholder?.(fallbackText) ?? fallbackText : placeholder,
82
82
  // The control is named by the field AND the locale it holds: "商品名 English".
83
83
  "aria-labelledby": labelledBy ? `${labelledBy} ${localeNameId}` : void 0,
84
84
  "aria-invalid": hasActiveError ? true : rest["aria-invalid"],
@@ -139,19 +139,7 @@ function TranslatableField({
139
139
  id: panelId,
140
140
  "aria-labelledby": tabId(active),
141
141
  className: "ui-translatable-field-panel",
142
- children: kind === "input" ? /* @__PURE__ */ jsx(
143
- Input,
144
- {
145
- ...controlProps,
146
- size
147
- }
148
- ) : /* @__PURE__ */ jsx(
149
- Textarea,
150
- {
151
- ...controlProps,
152
- size
153
- }
154
- )
142
+ children: renderControl(controlProps)
155
143
  }
156
144
  ),
157
145
  showOwnError ? /* @__PURE__ */ jsx("p", { id: ownErrorId, role: "alert", className: "ui-form-field-note text-error-strong text-xs", children: activeError }) : null
@@ -164,8 +152,7 @@ const TranslatableInput = React.forwardRef(
164
152
  TranslatableField,
165
153
  {
166
154
  ...props,
167
- kind: "input",
168
- forwardedRef: ref
155
+ renderControl: (control) => /* @__PURE__ */ jsx(Input, { ...control, ref })
169
156
  }
170
157
  )
171
158
  );
@@ -175,13 +162,13 @@ const TranslatableTextarea = React.forwardRef(
175
162
  TranslatableField,
176
163
  {
177
164
  ...props,
178
- kind: "textarea",
179
- forwardedRef: ref
165
+ renderControl: (control) => /* @__PURE__ */ jsx(Textarea, { ...control, ref })
180
166
  }
181
167
  )
182
168
  );
183
169
  TranslatableTextarea.displayName = "TranslatableTextarea";
184
170
  export {
171
+ TranslatableField,
185
172
  TranslatableInput,
186
173
  TranslatableTextarea
187
174
  };
@@ -15,10 +15,6 @@ export type TabsScrollBox = {
15
15
  * coordinates, so it is direction- and orientation-agnostic (LTR, RTL, vertical rail alike).
16
16
  */
17
17
  export declare function isTabFullyVisible(scrollport: TabsScrollBox, trigger: TabsScrollBox): boolean;
18
- /**
19
- * The trigger the strip must keep reachable: the focused trigger when the roving focus is inside
20
- * this list (manual activation can park focus off the selected tab), otherwise the active one.
21
- */
22
18
  export declare function resolveTabScrollTarget(list: HTMLElement): HTMLElement | null;
23
19
  /** `true` when the user has asked for reduced motion (falsy/unsupported environments say no). */
24
20
  export declare function prefersReducedMotion(): boolean;
@@ -80,7 +76,7 @@ export declare function useTabsScrollReporter(listRef: React.RefObject<HTMLEleme
80
76
  * removed) and the strip scrolling (the user swiping, or `useKeepActiveTabVisible` re-pinning a
81
77
  * trigger).
82
78
  *
83
- * `values` is joined into the effect key rather than passed as a dependency, so a consumer that
79
+ * `values` is serialized into the effect key rather than passed as a dependency, so a consumer that
84
80
  * rebuilds its `items` array on every render does not re-arm the observers on every render.
85
81
  *
86
82
  * The first measurement ALWAYS reports, even when nothing is hidden. Suppressing it looks harmless
@@ -7,12 +7,15 @@ function isTabFullyVisible(scrollport, trigger) {
7
7
  if (width <= 0 && height <= 0) return true;
8
8
  return trigger.left >= scrollport.left - VISIBILITY_EPSILON && trigger.right <= scrollport.right + VISIBILITY_EPSILON && trigger.top >= scrollport.top - VISIBILITY_EPSILON && trigger.bottom <= scrollport.bottom + VISIBILITY_EPSILON;
9
9
  }
10
+ const TAB_TRIGGER_SELECTOR = '[role="tab"], [data-slot="tabs-trigger"]';
10
11
  function resolveTabScrollTarget(list) {
11
12
  const focused = list.ownerDocument?.activeElement;
12
- if (focused instanceof HTMLElement && focused !== list && list.contains(focused) && focused.getAttribute("role") === "tab") {
13
+ if (focused instanceof HTMLElement && focused !== list && list.contains(focused) && focused.matches(TAB_TRIGGER_SELECTOR)) {
13
14
  return focused;
14
15
  }
15
- return list.querySelector('[role="tab"][data-state="active"]');
16
+ return list.querySelector(
17
+ '[role="tab"][data-state="active"], [data-slot="tabs-trigger"][data-state="active"]'
18
+ );
16
19
  }
17
20
  function prefersReducedMotion() {
18
21
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
@@ -69,7 +72,7 @@ function useKeepActiveTabVisible(listRef) {
69
72
  }
70
73
  function resolveHiddenTabValues(list, values) {
71
74
  const scrollport = list.getBoundingClientRect();
72
- const triggers = [...list.querySelectorAll('[role="tab"]')];
75
+ const triggers = [...list.querySelectorAll(TAB_TRIGGER_SELECTOR)];
73
76
  const hidden = [];
74
77
  triggers.forEach((trigger, index) => {
75
78
  const value = values[index];
@@ -115,16 +118,30 @@ function useTabsOverflowValues(listRef, values, onChange) {
115
118
  React.useEffect(() => {
116
119
  onChangeRef.current = onChange;
117
120
  });
118
- const key = values === void 0 ? null : values.join(" ");
119
- React.useEffect(() => {
121
+ const key = values === void 0 ? null : JSON.stringify(values);
122
+ React.useLayoutEffect(() => {
120
123
  if (key === null) return void 0;
121
124
  const list = listRef.current;
122
125
  if (!list) return void 0;
123
- const all = key === "" ? [] : key.split(" ");
126
+ const all = JSON.parse(key);
124
127
  let last = null;
125
128
  const measure = () => {
126
- const hidden = resolveHiddenTabValues(list, all);
127
- const next = hidden.join(" ");
129
+ const triggers = [...list.querySelectorAll(TAB_TRIGGER_SELECTOR)];
130
+ const menu = list.parentElement?.querySelector(".ui-tabs-overflow");
131
+ const vertical = list.dataset.orientation === "vertical";
132
+ const style = getComputedStyle(list);
133
+ const boxes = triggers.map((trigger) => trigger.getBoundingClientRect());
134
+ const extent = boxes.length ? Math.max(...boxes.map((box2) => vertical ? box2.bottom : box2.right)) - Math.min(...boxes.map((box2) => vertical ? box2.top : box2.left)) : 0;
135
+ const padding = vertical ? parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) : parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
136
+ const box = list.getBoundingClientRect();
137
+ const menuBox = menu?.getBoundingClientRect();
138
+ const gap = menu && list.parentElement ? parseFloat(getComputedStyle(list.parentElement).gap) || 0 : 0;
139
+ const fitsWithoutMenu = menuBox && extent + padding <= (vertical ? box.height + menuBox.height : box.width + menuBox.width) + gap;
140
+ const hidden = fitsWithoutMenu ? [] : resolveHiddenTabValues(list, all);
141
+ triggers.forEach((trigger, index) => {
142
+ trigger.toggleAttribute("data-overflow-hidden", hidden.includes(all[index]));
143
+ });
144
+ const next = JSON.stringify(hidden);
128
145
  if (next === last) return;
129
146
  last = next;
130
147
  onChangeRef.current(hidden);
@@ -139,12 +156,18 @@ function useTabsOverflowValues(listRef, values, onChange) {
139
156
  if (typeof ResizeObserver !== "undefined") {
140
157
  const resizeObserver = new ResizeObserver(measure);
141
158
  resizeObserver.observe(list);
159
+ list.querySelectorAll(TAB_TRIGGER_SELECTOR).forEach((trigger) => {
160
+ resizeObserver.observe(trigger);
161
+ });
142
162
  cleanups.push(() => {
143
163
  resizeObserver.disconnect();
144
164
  });
145
165
  }
146
166
  return () => {
147
167
  for (const cleanup of cleanups) cleanup();
168
+ list.querySelectorAll(TAB_TRIGGER_SELECTOR).forEach((trigger) => {
169
+ trigger.removeAttribute("data-overflow-hidden");
170
+ });
148
171
  };
149
172
  }, [key, listRef]);
150
173
  }
@@ -8,7 +8,7 @@ export type TabsProps = Omit<React.ComponentPropsWithoutRef<"div">, "defaultValu
8
8
  dir?: "ltr" | "rtl";
9
9
  activationMode?: "manual" | "automatic";
10
10
  };
11
- export declare function Tabs({ className, orientation, items, value, defaultValue, onValueChange, activationMode, dir, variant, tabPlacement, size, centered, bodied, extra, destroyOnHidden, onEdit, addIcon, hideAdd, closeIcon, onTabClick, overflow, animated, indicator, moreIcon, onTabScroll, controls, listClassName, contentClassName, children, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }: TabsProps): React.JSX.Element;
11
+ export declare function Tabs({ className, orientation, items, value, defaultValue, onValueChange, activationMode, dir, variant, tabPlacement, size, centered, bodied, extra, destroyOnHidden, onEdit, addIcon, hideAdd, closeIcon, onTabClick, overflow, animated, indicator, moreIcon, onTabScroll, controls, linkComponent, listClassName, contentClassName, children, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }: TabsProps): React.JSX.Element;
12
12
  export declare const TabsList: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
13
13
  variant?: "default" | "line";
14
14
  /**