@godxjp/ui 31.14.0 → 31.15.0

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` 31.14.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 31.15.0.** 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.
@@ -56,10 +56,10 @@ Then ask it: `search_components`, `get_component`, `get_tokens`, `get_rule`, `li
56
56
  its `importPath`, and its examples. Fetch only the handful you picked in step 1.
57
57
  3. `rules.json` — 50 cardinal rules. The ones about raw HTML and hardcoded colour are not
58
58
  style advice.
59
- 4. `tokens.json` — 2119 design tokens, each tagged with its `tier`. **If you were handed a
59
+ 4. `tokens.json` — 2121 design tokens, each tagged with its `tier`. **If you were handed a
60
60
  brand, read the 211 `foundation` entries first** — `--primary`, `--background`,
61
61
  `--radius`, `--font-size-base` are the handful everything else derives from. The
62
- 1804 `component` entries are per-part knobs; reach for one only when a role is
62
+ 1806 `component` entries are per-part knobs; reach for one only when a role is
63
63
  right everywhere except one component.
64
64
  5. `anti-ai-tells.json` — 26 shapes that make generated UI look generated, each with the
65
65
  fix. Read before you reach for a gradient hero or a wall of coloured chips.
@@ -145,7 +145,7 @@ has stopped following the brand.
145
145
  |---|---|---|---|
146
146
  | `foundation` | 211 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** Handed a brand colour, this is where it goes: `:root { --primary: <H> <S>% <L>%; }` (HSL components, no `hsl()` wrapper) |
147
147
  | `semantic` | 104 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
148
- | `component` | 1804 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
148
+ | `component` | 1806 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
149
149
 
150
150
  A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
151
151
  value, so the real default is computed where the element paints it. Set it and yours wins.
@@ -1,11 +1,11 @@
1
1
  {
2
- "example": "import { Timeline } from \"@godxjp/ui/data-display\";\n\n// Default icon variant\n<Timeline items={[\n { title: \"注文受付\", time: \"2024-06-01 10:00\" },\n { title: \"発送準備中\", time: \"2024-06-01 14:00\" },\n { title: \"配送中\", current: true },\n]} />\n\n// Numbered route stepper (Pattern A)\n<Timeline variant=\"ordinal\" items={[\n { title: \"集荷\", location: \"東京 → 名古屋\", status: \"pending\" },\n { title: \"幹線輸送\", location: \"名古屋 → 大阪\", status: \"pending\" },\n]} />\n\n// Status tracker (Pattern B): done → check, current → dot, pending → number\n<Timeline variant=\"status\" items={[\n { title: \"請求書を発行\", status: \"done\" },\n { title: \"承認待ち\", status: \"current\" },\n { title: \"消費税を計上\", status: \"pending\" },\n]} />",
2
+ "example": "import { Timeline } from \"@godxjp/ui/data-display\";\nimport { ArrowRightLeft, MessageSquare } from \"lucide-react\";\n\n// Default icon variant\n<Timeline items={[\n { title: \"注文受付\", time: \"2024-06-01 10:00\" },\n { title: \"発送準備中\", time: \"2024-06-01 14:00\" },\n { title: \"配送中\", current: true },\n]} />\n\n// Numbered route stepper (Pattern A)\n<Timeline variant=\"ordinal\" items={[\n { title: \"集荷\", location: \"東京 → 名古屋\", status: \"pending\" },\n { title: \"幹線輸送\", location: \"名古屋 → 大阪\", status: \"pending\" },\n]} />\n\n// Activity log / issue history: neutral dots, no status prefix (gh#1092)\n<Timeline density=\"compact\" items={[\n { title: \"Aki がステータスを変更 未対応 → 処理中\", time: \"10/01 08:12\", status: \"log\", icon: ArrowRightLeft },\n { title: \"Ken がコメント\", time: \"10/01 09:30\", status: \"log\", icon: MessageSquare },\n]} />\n\n// Status tracker (Pattern B): done → check, current → dot, pending → number\n<Timeline variant=\"status\" items={[\n { title: \"請求書を発行\", status: \"done\" },\n { title: \"承認待ち\", status: \"current\" },\n { title: \"消費税を計上\", status: \"pending\" },\n]} />",
3
3
  "group": "data-display",
4
4
  "importPath": "@godxjp/ui/data-display",
5
5
  "name": "Timeline",
6
6
  "props": [
7
7
  {
8
- "description": "Array of `{ title, location?, time?, note?, current?, status?, icon? }`. `status` is the explicit 3-state ('done' | 'current' | 'pending'); `current: true` is shorthand for `status: 'current'`. `icon` (a LucideIcon) overrides the auto-glyph for that item.",
8
+ "description": "Array of `{ title, location?, time?, note?, current?, status?, icon? }`. `status` is 'done' | 'current' | 'pending' | 'log'; `current: true` is shorthand for `status: 'current'`. 'log' (gh#1092) is an activity-log / history row: the neutral dot + hairline rail `pending` draws, and NO sr-only status prefix (antd's `color=\"gray\"` item). `icon` (a LucideIcon) overrides the auto-glyph for that item.",
9
9
  "name": "items",
10
10
  "required": true,
11
11
  "type": "TimelineItem[]"
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "rules": [],
31
31
  "storyPath": "data-display/Timeline.stories.tsx",
32
- "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a 3-state `status` (done/current/pending) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
32
+ "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a `status` (done/current/pending, or `log` for an activity-log entry with no status semantics) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
33
33
  "usage": [
34
34
  "DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other props are `variant` and `density`.",
35
35
  "DO mark the in-progress event with `current: true` (or `status: 'current'`). In the default `variant='icon'`, the current item renders a `Plane` icon and every other item a `CheckCircle2`. Use `variant='ordinal'` for a numbered route stepper (1,2,3…) or `variant='status'` for a done→check / current→dot / pending→number tracker. Pass `status: 'done' | 'current' | 'pending'` per item to drive colour and (in the status variant) the glyph; pass `icon` (a LucideIcon) to override the glyph for a single item.",
@@ -39,6 +39,7 @@
39
39
  "DO NOT use Timeline for user-facing wizard progress (steps the user must complete in order) — use `Steps` for that. Timeline is read-only historical/status display; it has no interactive state, no `onClick`, and no concept of 'go to step'.",
40
40
  "DO wrap Timeline in `<CardContent>` when placing it inside a `Card` — bare `Card` has no inner padding, so the rail will render flush against the card edge without `CardContent`.",
41
41
  "THE PROGRESS COLUMN IS ONE HUE (gh#731). The done dot, the current dot and the travelled line all default to `hsl(var(--primary))` — the same contract `Steps` uses for `finish` + `process`, and antd's (only `error` leaves the primary hue). The current item is told apart by a ring (`--timeline-dot-current-ring-width`, `--steps-dot-process-ring-width`'s value), by `aria-current=\"step\"` + a localized sr-only prefix, and by a different glyph — never by a second colour role. Re-theme `--primary` and the whole rail follows. Measured light: done 2.18:1 → 6.31:1 against the card, glyph 6.31:1 on the fill; dark 7.22:1 → 9.84:1, glyph 10.72:1.",
42
+ "DO use `status: 'log'` for every row of an activity log / issue history / audit trail (gh#1092). Those rows already HAPPENED and are not steps: `done` paints them primary and prefixes 「Completed:」, `pending` reads 「Upcoming:」 to a screen reader. `log` draws the quiet neutral dot (a plain pip, or the item's `icon`) and hairline rail, with no prefix — antd's gray item.",
42
43
  "DON'T expect `--success` on a `status: 'done'` item — `done | current | pending` is POSITION IN A SEQUENCE, not status, and Timeline has no `tone` axis. Put a real status on the item's `title` or `note` with a `<Badge tone=\"success\">`, which is the surface that owns the status green.",
43
44
  "TO RESTORE the pre-27.9 green/violet pairing, set the done dot's fill AND its ink together in your theme (global or `[data-tenant]`) — the two roles have opposite ink polarity, so the fill alone leaves a near-white glyph on 若竹 green at 2.18:1 (gh#643): `--timeline-dot-done-background: hsl(var(--success)); --timeline-dot-done-foreground: hsl(var(--success-foreground));`. `--timeline-dot-current-background` and `--timeline-line-completed-background` retint the other two surfaces the same way."
44
45
  ],
@@ -90,6 +90,27 @@
90
90
  "name": "loadData",
91
91
  "type": "(node: TreeNodeProp) => void | Promise<void>"
92
92
  },
93
+ {
94
+ "defaultValue": "false",
95
+ "description": "Move nodes by dragging (antd `draggable`, gh#1093). Dropping on a row's upper / lower quarter lands in the gap before / after it, the middle files it inside. The tree never edits `treeData` — it reports the move via `onDrop` and you persist it. Keyboard equivalent (WCAG 2.5.7): Alt+↑ / Alt+↓ move past the previous / next sibling, Alt+← outdents (after the parent), Alt+→ indents (into the previous sibling, as its last child); every move is announced in a live region and described on the tree. A node never drops into itself or its own subtree.",
96
+ "name": "draggable",
97
+ "type": "boolean"
98
+ },
99
+ {
100
+ "description": "Per-node drag gate (antd `draggable.nodeDraggable`). Disabled nodes never drag.",
101
+ "name": "allowDrag",
102
+ "type": "(node: TreeNodeProp) => boolean"
103
+ },
104
+ {
105
+ "description": "Per-target drop gate (antd `allowDrop`), asked for pointer AND keyboard moves. Use it for your own rules (max depth, no leaf parents); the self/subtree cycle is already refused.",
106
+ "name": "allowDrop",
107
+ "type": "(info: { dragNode: TreeNodeProp; dropNode: TreeNodeProp; dropPosition: -1 | 0 | 1 }) => boolean"
108
+ },
109
+ {
110
+ "description": "A committed move (antd `onDrop`). `dropPosition` is RELATIVE to `node` — -1 before, 0 inside, 1 after (antd reports an absolute index that every antd demo converts back to this). `dropToGap` is true for before/after (sibling of `node`), false for inside (child of `node`). Update `treeData` here.",
111
+ "name": "onDrop",
112
+ "type": "(info: { dragNode: TreeNodeProp; node: TreeNodeProp; dropPosition: -1 | 0 | 1; dropToGap: boolean }) => void"
113
+ },
93
114
  {
94
115
  "description": "Render a node's title yourself (antd `titleRender`).",
95
116
  "name": "titleRender",
@@ -179,7 +200,7 @@
179
200
  45
180
201
  ],
181
202
  "storyPath": "data-display/Tree.stories.tsx",
182
- "tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes and async `loadData`. Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover.",
203
+ "tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes, async `loadData` and `draggable` reorder/reparent (antd `onDrop`). Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover.",
183
204
  "usage": [
184
205
  "DO name the tree — pass `aria-label` (through your own `t()`) or `aria-labelledby`. A tree with no name is an unlabelled landmark for every screen reader user.",
185
206
  "DO keep SELECTION and CHECKS apart: `value`/`onValueChange` is which node is open in the detail pane; `checkedValues`/`onCheckedValuesChange` is which nodes are ticked. They are two axes, exactly as in antd — never drive one from the other.",
@@ -304,7 +304,7 @@
304
304
  {
305
305
  "group": "data-display",
306
306
  "name": "Timeline",
307
- "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a 3-state `status` (done/current/pending) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`."
307
+ "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a `status` (done/current/pending, or `log` for an activity-log entry with no status semantics) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`."
308
308
  },
309
309
  {
310
310
  "group": "data-display",
@@ -585,7 +585,7 @@
585
585
  {
586
586
  "group": "data-display",
587
587
  "name": "Tree",
588
- "tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes and async `loadData`. Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover."
588
+ "tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes, async `loadData` and `draggable` reorder/reparent (antd `onDrop`). Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover."
589
589
  },
590
590
  {
591
591
  "group": "feedback",
@@ -4959,13 +4959,13 @@
4959
4959
  ]
4960
4960
  },
4961
4961
  {
4962
- "example": "import { Timeline } from \"@godxjp/ui/data-display\";\n\n// Default icon variant\n<Timeline items={[\n { title: \"注文受付\", time: \"2024-06-01 10:00\" },\n { title: \"発送準備中\", time: \"2024-06-01 14:00\" },\n { title: \"配送中\", current: true },\n]} />\n\n// Numbered route stepper (Pattern A)\n<Timeline variant=\"ordinal\" items={[\n { title: \"集荷\", location: \"東京 → 名古屋\", status: \"pending\" },\n { title: \"幹線輸送\", location: \"名古屋 → 大阪\", status: \"pending\" },\n]} />\n\n// Status tracker (Pattern B): done → check, current → dot, pending → number\n<Timeline variant=\"status\" items={[\n { title: \"請求書を発行\", status: \"done\" },\n { title: \"承認待ち\", status: \"current\" },\n { title: \"消費税を計上\", status: \"pending\" },\n]} />",
4962
+ "example": "import { Timeline } from \"@godxjp/ui/data-display\";\nimport { ArrowRightLeft, MessageSquare } from \"lucide-react\";\n\n// Default icon variant\n<Timeline items={[\n { title: \"注文受付\", time: \"2024-06-01 10:00\" },\n { title: \"発送準備中\", time: \"2024-06-01 14:00\" },\n { title: \"配送中\", current: true },\n]} />\n\n// Numbered route stepper (Pattern A)\n<Timeline variant=\"ordinal\" items={[\n { title: \"集荷\", location: \"東京 → 名古屋\", status: \"pending\" },\n { title: \"幹線輸送\", location: \"名古屋 → 大阪\", status: \"pending\" },\n]} />\n\n// Activity log / issue history: neutral dots, no status prefix (gh#1092)\n<Timeline density=\"compact\" items={[\n { title: \"Aki がステータスを変更 未対応 → 処理中\", time: \"10/01 08:12\", status: \"log\", icon: ArrowRightLeft },\n { title: \"Ken がコメント\", time: \"10/01 09:30\", status: \"log\", icon: MessageSquare },\n]} />\n\n// Status tracker (Pattern B): done → check, current → dot, pending → number\n<Timeline variant=\"status\" items={[\n { title: \"請求書を発行\", status: \"done\" },\n { title: \"承認待ち\", status: \"current\" },\n { title: \"消費税を計上\", status: \"pending\" },\n]} />",
4963
4963
  "group": "data-display",
4964
4964
  "importPath": "@godxjp/ui/data-display",
4965
4965
  "name": "Timeline",
4966
4966
  "props": [
4967
4967
  {
4968
- "description": "Array of `{ title, location?, time?, note?, current?, status?, icon? }`. `status` is the explicit 3-state ('done' | 'current' | 'pending'); `current: true` is shorthand for `status: 'current'`. `icon` (a LucideIcon) overrides the auto-glyph for that item.",
4968
+ "description": "Array of `{ title, location?, time?, note?, current?, status?, icon? }`. `status` is 'done' | 'current' | 'pending' | 'log'; `current: true` is shorthand for `status: 'current'`. 'log' (gh#1092) is an activity-log / history row: the neutral dot + hairline rail `pending` draws, and NO sr-only status prefix (antd's `color=\"gray\"` item). `icon` (a LucideIcon) overrides the auto-glyph for that item.",
4969
4969
  "name": "items",
4970
4970
  "required": true,
4971
4971
  "type": "TimelineItem[]"
@@ -4989,7 +4989,7 @@
4989
4989
  ],
4990
4990
  "rules": [],
4991
4991
  "storyPath": "data-display/Timeline.stories.tsx",
4992
- "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a 3-state `status` (done/current/pending) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
4992
+ "tagline": "Vertical event list with an icon rail. Current item gets a highlighted glyph. `variant` switches the rail to numbered (ordinal) or status-driven glyphs, and each item carries a `status` (done/current/pending, or `log` for an activity-log entry with no status semantics) plus an optional per-item `icon`. The whole progress column is ONE hue: done dot, current dot and travelled line all paint `--primary`, exactly as `Steps` paints `finish` and `process`.",
4993
4993
  "usage": [
4994
4994
  "DO pass an array of `TimelineItem` objects to `items`. Each item is `{ title, location?, time?, note?, current?, status?, icon? }`. All fields except `title` are optional. The only other props are `variant` and `density`.",
4995
4995
  "DO mark the in-progress event with `current: true` (or `status: 'current'`). In the default `variant='icon'`, the current item renders a `Plane` icon and every other item a `CheckCircle2`. Use `variant='ordinal'` for a numbered route stepper (1,2,3…) or `variant='status'` for a done→check / current→dot / pending→number tracker. Pass `status: 'done' | 'current' | 'pending'` per item to drive colour and (in the status variant) the glyph; pass `icon` (a LucideIcon) to override the glyph for a single item.",
@@ -4999,6 +4999,7 @@
4999
4999
  "DO NOT use Timeline for user-facing wizard progress (steps the user must complete in order) — use `Steps` for that. Timeline is read-only historical/status display; it has no interactive state, no `onClick`, and no concept of 'go to step'.",
5000
5000
  "DO wrap Timeline in `<CardContent>` when placing it inside a `Card` — bare `Card` has no inner padding, so the rail will render flush against the card edge without `CardContent`.",
5001
5001
  "THE PROGRESS COLUMN IS ONE HUE (gh#731). The done dot, the current dot and the travelled line all default to `hsl(var(--primary))` — the same contract `Steps` uses for `finish` + `process`, and antd's (only `error` leaves the primary hue). The current item is told apart by a ring (`--timeline-dot-current-ring-width`, `--steps-dot-process-ring-width`'s value), by `aria-current=\"step\"` + a localized sr-only prefix, and by a different glyph — never by a second colour role. Re-theme `--primary` and the whole rail follows. Measured light: done 2.18:1 → 6.31:1 against the card, glyph 6.31:1 on the fill; dark 7.22:1 → 9.84:1, glyph 10.72:1.",
5002
+ "DO use `status: 'log'` for every row of an activity log / issue history / audit trail (gh#1092). Those rows already HAPPENED and are not steps: `done` paints them primary and prefixes 「Completed:」, `pending` reads 「Upcoming:」 to a screen reader. `log` draws the quiet neutral dot (a plain pip, or the item's `icon`) and hairline rail, with no prefix — antd's gray item.",
5002
5003
  "DON'T expect `--success` on a `status: 'done'` item — `done | current | pending` is POSITION IN A SEQUENCE, not status, and Timeline has no `tone` axis. Put a real status on the item's `title` or `note` with a `<Badge tone=\"success\">`, which is the surface that owns the status green.",
5003
5004
  "TO RESTORE the pre-27.9 green/violet pairing, set the done dot's fill AND its ink together in your theme (global or `[data-tenant]`) — the two roles have opposite ink polarity, so the fill alone leaves a near-white glyph on 若竹 green at 2.18:1 (gh#643): `--timeline-dot-done-background: hsl(var(--success)); --timeline-dot-done-foreground: hsl(var(--success-foreground));`. `--timeline-dot-current-background` and `--timeline-line-completed-background` retint the other two surfaces the same way."
5004
5005
  ],
@@ -11130,6 +11131,27 @@
11130
11131
  "name": "loadData",
11131
11132
  "type": "(node: TreeNodeProp) => void | Promise<void>"
11132
11133
  },
11134
+ {
11135
+ "defaultValue": "false",
11136
+ "description": "Move nodes by dragging (antd `draggable`, gh#1093). Dropping on a row's upper / lower quarter lands in the gap before / after it, the middle files it inside. The tree never edits `treeData` — it reports the move via `onDrop` and you persist it. Keyboard equivalent (WCAG 2.5.7): Alt+↑ / Alt+↓ move past the previous / next sibling, Alt+← outdents (after the parent), Alt+→ indents (into the previous sibling, as its last child); every move is announced in a live region and described on the tree. A node never drops into itself or its own subtree.",
11137
+ "name": "draggable",
11138
+ "type": "boolean"
11139
+ },
11140
+ {
11141
+ "description": "Per-node drag gate (antd `draggable.nodeDraggable`). Disabled nodes never drag.",
11142
+ "name": "allowDrag",
11143
+ "type": "(node: TreeNodeProp) => boolean"
11144
+ },
11145
+ {
11146
+ "description": "Per-target drop gate (antd `allowDrop`), asked for pointer AND keyboard moves. Use it for your own rules (max depth, no leaf parents); the self/subtree cycle is already refused.",
11147
+ "name": "allowDrop",
11148
+ "type": "(info: { dragNode: TreeNodeProp; dropNode: TreeNodeProp; dropPosition: -1 | 0 | 1 }) => boolean"
11149
+ },
11150
+ {
11151
+ "description": "A committed move (antd `onDrop`). `dropPosition` is RELATIVE to `node` — -1 before, 0 inside, 1 after (antd reports an absolute index that every antd demo converts back to this). `dropToGap` is true for before/after (sibling of `node`), false for inside (child of `node`). Update `treeData` here.",
11152
+ "name": "onDrop",
11153
+ "type": "(info: { dragNode: TreeNodeProp; node: TreeNodeProp; dropPosition: -1 | 0 | 1; dropToGap: boolean }) => void"
11154
+ },
11133
11155
  {
11134
11156
  "description": "Render a node's title yourself (antd `titleRender`).",
11135
11157
  "name": "titleRender",
@@ -11219,7 +11241,7 @@
11219
11241
  45
11220
11242
  ],
11221
11243
  "storyPath": "data-display/Tree.stories.tsx",
11222
- "tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes and async `loadData`. Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover.",
11244
+ "tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes, async `loadData` and `draggable` reorder/reparent (antd `onDrop`). Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover.",
11223
11245
  "usage": [
11224
11246
  "DO name the tree — pass `aria-label` (through your own `t()`) or `aria-labelledby`. A tree with no name is an unlabelled landmark for every screen reader user.",
11225
11247
  "DO keep SELECTION and CHECKS apart: `value`/`onValueChange` is which node is open in the detail pane; `checkedValues`/`onCheckedValuesChange` is which nodes are ticked. They are two axes, exactly as in antd — never drive one from the other.",
package/agent/index.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "components": 178,
5
5
  "patterns": 21,
6
6
  "rules": 50,
7
- "tokens": 2119,
7
+ "tokens": 2121,
8
8
  "vocabulary": 14
9
9
  },
10
10
  "files": [
@@ -48,19 +48,19 @@
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/v31.14.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.15.0/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",
55
55
  "tokenTiers": {
56
56
  "component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
57
57
  "counts": {
58
- "component": 1804,
58
+ "component": 1806,
59
59
  "foundation": 211,
60
60
  "semantic": 104
61
61
  },
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": "31.14.0"
65
+ "version": "31.15.0"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 178 components, 2119 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.14.0.
3
+ > A Japanese-enterprise React design system: 178 components, 2121 design tokens,
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.15.0.
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@31.14.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@31.15.0`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -18,7 +18,7 @@ that can only fetch URLs.
18
18
  - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 48 KB — all 178 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
19
  - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–36 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.
20
20
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
21
- - [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` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 104 `semantic` roles, 1804 `component` knobs.
21
+ - [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` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 104 `semantic` roles, 1806 `component` knobs.
22
22
  - [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
23
23
  - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 50 cardinal rules.
24
24
  - [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 26 shapes that make generated UI look generated, each with its fix.
@@ -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/v31.14.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v31.15.0/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.
package/agent/tokens.json CHANGED
@@ -12317,6 +12317,18 @@
12317
12317
  "tier": "component",
12318
12318
  "value": "initial"
12319
12319
  },
12320
+ {
12321
+ "description": "Drop indicator of a `draggable` tree (gh#1093): the gap line / inside frame. A role mirror, so `initial` with the role at the call site. Documented defaults: --tree-drop-indicator-color = hsl(var(--primary)); --tree-drop-indicator-width = var(--stroke-md).",
12322
+ "name": "--tree-drop-indicator-color",
12323
+ "tier": "component",
12324
+ "value": "initial"
12325
+ },
12326
+ {
12327
+ "description": "TREE — the standalone, on-page WAI-ARIA \"Tree View\" (`src/components/data-display/tree.tsx`). Every value the tree paints is a knob (cardinal rule #45), and the defaults are the QUIETEST reading (#44): no rails, a muted disclosure glyph, a selected band that tints rather than inverts. A service retunes the whole outline in one place instead of forking a row. ROLE-MIRROR KNOBS ARE `initial`, WITH THE DEFAULT AT THE CALL SITE. A knob whose default is a role token or a RE-SCOPED tier may not be bound at `:root` — CSS substitutes the `var()` where it is DECLARED, so the value would freeze at `:root` and a scoped `[data-tenant]` / `.dark` / a shell that re-scopes `--control-height` would never reach it. @see docs/TOKENS.md · \"Role-mirror knobs MUST be `initial`\".",
12328
+ "name": "--tree-drop-indicator-width",
12329
+ "tier": "component",
12330
+ "value": "initial"
12331
+ },
12320
12332
  {
12321
12333
  "description": "Width of the indeterminate bar shown while `loadData` resolves a branch. A fraction, not a length: it has to read as \"part of a row\" at every row width and every density.",
12322
12334
  "name": "--tree-loading-bar-inline-size",
@@ -52,7 +52,7 @@ export type { RangeTimelineProps, RangeTimelineRow } from "./range-timeline.js";
52
52
  export { ChatBubble, ChatBubbleList } from "./chat-bubble.js";
53
53
  export type { ChatBubbleListProp, ChatBubbleListProps, ChatBubblePlacementProp, ChatBubbleProp, ChatBubbleProps, ChatBubbleToneProp, ChatBubbleTypingProp, ChatBubbleVariantProp, ChatMessageProp, } from "./chat-bubble.js";
54
54
  export { Tree } from "./tree.js";
55
- export type { TreeNodeProp, TreeProp, TreeProps } from "./tree.js";
55
+ export type { TreeAllowDropInfoProp, TreeDropInfoProp, TreeDropPositionProp, TreeNodeProp, TreeProp, TreeProps, } from "./tree.js";
56
56
  export { OrgChart } from "./org-chart.js";
57
57
  export type { OrgChartNodeProp, OrgChartNodeVariantProp, OrgChartProp, OrgChartProps, } from "./org-chart.js";
58
58
  export { Welcome } from "./welcome.js";
@@ -1,6 +1,13 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { type LucideIcon } from "lucide-react";
3
- export type TimelineStatus = "done" | "current" | "pending";
3
+ /**
4
+ * `done` / `current` / `pending` are a PROCESS: steps behind, the one you are on, steps ahead.
5
+ * `log` is not a step at all — it is an entry in an activity log or history (antd's
6
+ * `<Timeline.Item color="gray">`, GitHub's conversation rail): a neutral dot, a hairline rail and
7
+ * NO screen-reader status prefix, because a thing that merely happened is neither "completed" nor
8
+ * "upcoming" (gh#1092).
9
+ */
10
+ export type TimelineStatus = "done" | "current" | "pending" | "log";
4
11
  export type TimelineVariant = "icon" | "ordinal" | "status";
5
12
  /**
6
13
  * A Timeline-local subset of the density vocabulary — it has no `comfortable` step, the same shape
@@ -14,7 +21,10 @@ export type TimelineItem = {
14
21
  note?: ReactNode;
15
22
  /** Shorthand for `status: "current"`. `status` wins when both are set. */
16
23
  current?: boolean;
17
- /** Explicit 3-state. Resolves as `status ?? */
24
+ /**
25
+ * Explicit status. Resolves as `status ?? (current ? "current" : "done")`. Use `log` for an
26
+ * activity-log / history row: neutral look, no "Completed:"/"Upcoming:" prefix.
27
+ */
18
28
  status?: TimelineStatus;
19
29
  /** Per-item glyph override; wins over the variant/status auto-glyph. */
20
30
  icon?: LucideIcon;
@@ -5,7 +5,9 @@ import { Check, CheckCircle2, Plane } from "lucide-react";
5
5
  const SR_PREFIX_KEY = {
6
6
  done: "dataDisplay.timeline.statusDone",
7
7
  current: "dataDisplay.timeline.statusCurrent",
8
- pending: "dataDisplay.timeline.statusPending"
8
+ pending: "dataDisplay.timeline.statusPending",
9
+ // A log entry carries no status semantics (antd's items have no prefix at all), gh#1092.
10
+ log: null
9
11
  };
10
12
  function resolveStatus(item) {
11
13
  if (item.status) {
@@ -27,6 +29,8 @@ function Timeline({ items, variant = "icon", density = "default" }) {
27
29
  if (item.icon) {
28
30
  const Icon = item.icon;
29
31
  glyph = /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" });
32
+ } else if (status === "log" && variant !== "ordinal") {
33
+ glyph = /* @__PURE__ */ jsx("span", { className: "ui-timeline-pip", "aria-hidden": "true" });
30
34
  } else if (variant === "ordinal") {
31
35
  glyph = /* @__PURE__ */ jsx("span", { className: "ui-timeline-ordinal", children: ordinal });
32
36
  } else if (variant === "status") {
@@ -69,7 +73,7 @@ function Timeline({ items, variant = "icon", density = "default" }) {
69
73
  /* @__PURE__ */ jsxs("div", { className: "ui-timeline-body", children: [
70
74
  /* @__PURE__ */ jsxs("div", { className: "ui-timeline-head", children: [
71
75
  /* @__PURE__ */ jsxs("span", { className: "ui-timeline-title", "data-current": isCurrent ? "true" : void 0, children: [
72
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: t(SR_PREFIX_KEY[status]) }),
76
+ SR_PREFIX_KEY[status] ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: t(SR_PREFIX_KEY[status]) }) : null,
73
77
  item.title
74
78
  ] }),
75
79
  item.time ? /* @__PURE__ */ jsx("span", { className: "ui-timeline-time", children: item.time }) : null
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import type { TreeProp } from "../../props/components/data-display.prop.js";
3
- export type { TreeNodeProp, TreeProp, TreeProp as TreeProps, } from "../../props/components/data-display.prop.js";
3
+ export type { TreeAllowDropInfoProp, TreeDropInfoProp, TreeDropPositionProp, TreeNodeProp, TreeProp, TreeProp as TreeProps, } from "../../props/components/data-display.prop.js";
4
4
  /**
5
5
  * `ref` reaches the `role="tree"` container — the element a page scrolls to, measures, or
6
6
  * focus-manages. Every unknown prop (`aria-*`, `data-*`) rides `...ariaProps` onto the same node.
@@ -71,6 +71,10 @@ function TreeRoot({
71
71
  showLine = false,
72
72
  showIcon = false,
73
73
  divided = false,
74
+ draggable = false,
75
+ allowDrag,
76
+ allowDrop,
77
+ onDrop,
74
78
  variant = "default",
75
79
  size = "md",
76
80
  disabled = false,
@@ -82,6 +86,7 @@ function TreeRoot({
82
86
  const { t } = useTranslation();
83
87
  const reactId = React.useId();
84
88
  const treeId = id ?? `${reactId}-tree`;
89
+ const dragHintId = `${treeId}-drag-hint`;
85
90
  const options = React.useMemo(
86
91
  () => normalizeTreeOptions(treeData, fieldNames),
87
92
  [treeData, fieldNames]
@@ -166,6 +171,92 @@ function TreeRoot({
166
171
  if (node) requestLoadRef.current(node);
167
172
  }
168
173
  }, [expandedSet, loadData, nodesByValue, requestLoadRef]);
174
+ const parentOf = React.useMemo(() => {
175
+ const map = /* @__PURE__ */ new Map();
176
+ const walk = (list, parent) => {
177
+ for (const node of list) {
178
+ map.set(node.value, parent);
179
+ if (node.children?.length) walk(node.children, node);
180
+ }
181
+ };
182
+ walk(options, null);
183
+ return map;
184
+ }, [options]);
185
+ const isDraggable = draggable && !disabled;
186
+ const canDrag = (node) => isDraggable && !node.disabled && (allowDrag ? allowDrag(node) : true);
187
+ const canDrop = (dragNode, dropNode, dropPosition) => {
188
+ for (let cursor = dropNode; cursor; ) {
189
+ if (cursor.value === dragNode.value) return false;
190
+ cursor = parentOf.get(cursor.value);
191
+ }
192
+ return allowDrop ? allowDrop({
193
+ dragNode,
194
+ dropNode,
195
+ dropPosition
196
+ }) : true;
197
+ };
198
+ const dragRef = React.useRef(null);
199
+ const [dragValue, setDragValue] = React.useState(null);
200
+ const [dropTarget, setDropTarget] = React.useState(null);
201
+ const [moveAnnouncement, setMoveAnnouncement] = React.useState("");
202
+ const commitDrop = (dragNode, dropNode, dropPosition) => {
203
+ onDrop?.({
204
+ dragNode,
205
+ node: dropNode,
206
+ dropPosition,
207
+ dropToGap: dropPosition !== 0
208
+ });
209
+ const key = dropPosition === -1 ? "dataDisplay.tree.movedBefore" : dropPosition === 1 ? "dataDisplay.tree.movedAfter" : "dataDisplay.tree.movedInside";
210
+ setMoveAnnouncement(
211
+ t(key, { node: reactNodeText(dragNode.label), target: reactNodeText(dropNode.label) })
212
+ );
213
+ };
214
+ const dropPositionAt = (event) => {
215
+ const rect = event.currentTarget.getBoundingClientRect();
216
+ if (rect.height <= 0) return 0;
217
+ const offset = (event.clientY - rect.top) / rect.height;
218
+ if (offset < 0.25) return -1;
219
+ if (offset > 0.75) return 1;
220
+ return 0;
221
+ };
222
+ const clearDrag = () => {
223
+ dragRef.current = null;
224
+ setDragValue(null);
225
+ setDropTarget(null);
226
+ };
227
+ const keyboardMove = (node, direction) => {
228
+ const parent = parentOf.get(node.value) ?? null;
229
+ const siblings = parent ? parent.children ?? [] : options;
230
+ const position = siblings.findIndex((entry) => entry.value === node.value);
231
+ let target;
232
+ let dropPosition;
233
+ if (direction === "up") {
234
+ target = siblings[position - 1];
235
+ dropPosition = -1;
236
+ } else if (direction === "down") {
237
+ target = siblings[position + 1];
238
+ dropPosition = 1;
239
+ } else if (direction === "out") {
240
+ target = parent;
241
+ dropPosition = 1;
242
+ } else {
243
+ const previous = siblings[position - 1];
244
+ const lastChild = previous?.children?.[previous.children.length - 1];
245
+ target = lastChild ?? previous;
246
+ dropPosition = lastChild ? 1 : 0;
247
+ }
248
+ if (!target || !canDrag(node) || !canDrop(node, target, dropPosition)) {
249
+ setMoveAnnouncement(t("dataDisplay.tree.moveRefused", { node: reactNodeText(node.label) }));
250
+ return;
251
+ }
252
+ if (direction === "in") {
253
+ const host = dropPosition === 0 ? target : siblings[position - 1];
254
+ if (host && !expandedSet.has(host.value)) commitExpanded([...expanded, host.value]);
255
+ }
256
+ commitDrop(node, target, dropPosition);
257
+ setActiveValue(node.value);
258
+ pendingFocus.current = node.value;
259
+ };
169
260
  const visible = flattenVisibleTree(options, expandedSet);
170
261
  const visibleIndex = new Map(visible.map((entry, index) => [entry.node.value, index]));
171
262
  const [activeValue, setActiveValue] = React.useState(null);
@@ -300,6 +391,14 @@ function TreeRoot({
300
391
  const rtl = isRtl(event.currentTarget);
301
392
  const inward = rtl ? "ArrowLeft" : "ArrowRight";
302
393
  const outward = rtl ? "ArrowRight" : "ArrowLeft";
394
+ if (isDraggable && event.altKey && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === inward || event.key === outward)) {
395
+ event.preventDefault();
396
+ keyboardMove(
397
+ node,
398
+ event.key === "ArrowUp" ? "up" : event.key === "ArrowDown" ? "down" : event.key === inward ? "in" : "out"
399
+ );
400
+ return;
401
+ }
303
402
  if (event.key === "ArrowDown") {
304
403
  event.preventDefault();
305
404
  moveTo(index + 1);
@@ -366,6 +465,8 @@ function TreeRoot({
366
465
  const checkState = checkableProp ? checkStateOf(node) : "unchecked";
367
466
  const nodeDisabled = disabled || Boolean(node.disabled);
368
467
  const labelId = `${treeId}-${node.value}-label`;
468
+ const nodeDraggable = canDrag(node);
469
+ const dropHere = dropTarget?.value === node.value ? dropTarget.position : void 0;
369
470
  const glyph = node.icon ?? (variant === "directory" ? hasChildren ? isOpen ? /* @__PURE__ */ jsx(FolderOpen, {}) : /* @__PURE__ */ jsx(Folder, {}) : /* @__PURE__ */ jsx(FileIcon, {}) : null);
370
471
  return /* @__PURE__ */ jsxs(
371
472
  "div",
@@ -395,6 +496,47 @@ function TreeRoot({
395
496
  "data-selected": isSelected ? "true" : void 0,
396
497
  "data-disabled": nodeDisabled ? "" : void 0,
397
498
  "data-filter-node": isFilterMatch ? "true" : void 0,
499
+ draggable: nodeDraggable || void 0,
500
+ "data-dragging": dragValue === node.value ? "true" : void 0,
501
+ "data-drop-position": dropHere === void 0 ? void 0 : dropHere === -1 ? "before" : dropHere === 1 ? "after" : "inside",
502
+ onDragStart: isDraggable ? (event) => {
503
+ if (!nodeDraggable) {
504
+ event.preventDefault();
505
+ return;
506
+ }
507
+ event.dataTransfer.effectAllowed = "move";
508
+ event.dataTransfer.setData("text/plain", node.value);
509
+ dragRef.current = node.value;
510
+ setDragValue(node.value);
511
+ } : void 0,
512
+ onDragOver: isDraggable ? (event) => {
513
+ const dragged = dragRef.current === null ? void 0 : nodesByValue.get(dragRef.current);
514
+ if (!dragged) return;
515
+ const position = dropPositionAt(event);
516
+ if (!canDrop(dragged, node, position)) {
517
+ if (dropTarget) setDropTarget(null);
518
+ return;
519
+ }
520
+ event.preventDefault();
521
+ event.dataTransfer.dropEffect = "move";
522
+ if (dropTarget?.value !== node.value || dropTarget.position !== position) {
523
+ setDropTarget({ value: node.value, position });
524
+ }
525
+ } : void 0,
526
+ onDragLeave: isDraggable ? (event) => {
527
+ if (event.currentTarget.contains(event.relatedTarget)) return;
528
+ if (dropTarget?.value === node.value) setDropTarget(null);
529
+ } : void 0,
530
+ onDrop: isDraggable ? (event) => {
531
+ event.preventDefault();
532
+ const dragged = dragRef.current === null ? void 0 : nodesByValue.get(dragRef.current);
533
+ const position = dropPositionAt(event);
534
+ if (dragged && canDrop(dragged, node, position)) {
535
+ commitDrop(dragged, node, position);
536
+ }
537
+ clearDrag();
538
+ } : void 0,
539
+ onDragEnd: isDraggable ? clearDrag : void 0,
398
540
  className: "ui-tree-node ui-focus-ring",
399
541
  style: { "--tree-node-level": depth },
400
542
  children: [
@@ -549,6 +691,7 @@ function TreeRoot({
549
691
  "div",
550
692
  {
551
693
  ...ariaProps,
694
+ "aria-describedby": isDraggable ? [ariaProps["aria-describedby"], dragHintId].filter(Boolean).join(" ") : ariaProps["aria-describedby"],
552
695
  ref: mergeRefs(forwardedRef, treeRef),
553
696
  id: treeId,
554
697
  role: "tree",
@@ -602,6 +745,10 @@ function TreeRoot({
602
745
  ]
603
746
  }
604
747
  ),
748
+ isDraggable ? /* @__PURE__ */ jsxs(Fragment, { children: [
749
+ /* @__PURE__ */ jsx("span", { id: dragHintId, className: "sr-only", children: t("dataDisplay.tree.dragHint") }),
750
+ /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", className: "sr-only", children: moveAnnouncement })
751
+ ] }) : null,
605
752
  isEmpty ? /* @__PURE__ */ jsx("p", { role: "status", className: "ui-tree-empty", children: t("dataDisplay.tree.empty") }) : null
606
753
  ] });
607
754
  }
@@ -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": "31.14.0",
3
+ "version": "31.15.0",
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,
@@ -364,7 +364,12 @@
364
364
  "loading": "Loading child nodes…",
365
365
  "empty": "Nothing to show",
366
366
  "selected": "Selected",
367
- "filterMatch": "Matches the filter"
367
+ "filterMatch": "Matches the filter",
368
+ "dragHint": "Alt plus arrow keys move the focused item: up and down past a sibling, left to outdent, right to indent.",
369
+ "movedBefore": "{node} moved before {target}",
370
+ "movedAfter": "{node} moved after {target}",
371
+ "movedInside": "{node} moved into {target}",
372
+ "moveRefused": "{node} cannot move there"
368
373
  },
369
374
  "thoughtChain": {
370
375
  "label": "Reasoning steps",
@@ -357,7 +357,12 @@
357
357
  "loading": "子ノードを読み込み中…",
358
358
  "empty": "表示する項目がありません",
359
359
  "selected": "選択中",
360
- "filterMatch": "フィルターに一致"
360
+ "filterMatch": "フィルターに一致",
361
+ "dragHint": "Alt キーと矢印キーでフォーカス中の項目を移動します。上下で兄弟の前後へ、左で階層を上げ、右で階層を下げます。",
362
+ "movedBefore": "{node} を {target} の前に移動しました",
363
+ "movedAfter": "{node} を {target} の後に移動しました",
364
+ "movedInside": "{node} を {target} の中に移動しました",
365
+ "moveRefused": "{node} はそこへ移動できません"
361
366
  },
362
367
  "thoughtChain": {
363
368
  "label": "思考の手順",
@@ -358,7 +358,12 @@
358
358
  "loading": "Đang tải nút con…",
359
359
  "empty": "Không có gì để hiển thị",
360
360
  "selected": "Đã chọn",
361
- "filterMatch": "Khớp bộ lọc"
361
+ "filterMatch": "Khớp bộ lọc",
362
+ "dragHint": "Alt cùng phím mũi tên di chuyển mục đang chọn: lên và xuống qua mục ngang hàng, trái để lùi cấp, phải để thụt cấp.",
363
+ "movedBefore": "Đã chuyển {node} lên trước {target}",
364
+ "movedAfter": "Đã chuyển {node} xuống sau {target}",
365
+ "movedInside": "Đã chuyển {node} vào trong {target}",
366
+ "moveRefused": "Không thể chuyển {node} tới đó"
362
367
  },
363
368
  "thoughtChain": {
364
369
  "label": "Các bước suy luận",
@@ -711,6 +711,29 @@ export type TreeNodeProp = TreeOptionProp & {
711
711
  * `checkedValues`/`defaultCheckedValues`/`onCheckedValuesChange`.
712
712
  * @see docs/roadmap/tree-components.md §3
713
713
  */
714
+ /**
715
+ * Where a drop lands relative to `node` (gh#1093): `-1` in the gap BEFORE it, `0` INSIDE it (the
716
+ * dragged node becomes its child), `1` in the gap AFTER it. antd's `onDrop` reports an absolute
717
+ * index and every antd demo subtracts the node's position back out to get exactly this number, so
718
+ * the relative form is what a consumer needs.
719
+ */
720
+ export type TreeDropPositionProp = -1 | 0 | 1;
721
+ /** `allowDrop` argument (antd `allowDrop({ dragNode, dropNode, dropPosition })`). */
722
+ export type TreeAllowDropInfoProp = {
723
+ dragNode: TreeNodeProp;
724
+ dropNode: TreeNodeProp;
725
+ dropPosition: TreeDropPositionProp;
726
+ };
727
+ /** `onDrop` argument (antd `onDrop({ dragNode, node, dropPosition, dropToGap })`). */
728
+ export type TreeDropInfoProp = {
729
+ /** The node being moved. */
730
+ dragNode: TreeNodeProp;
731
+ /** The node it was dropped on or beside. */
732
+ node: TreeNodeProp;
733
+ dropPosition: TreeDropPositionProp;
734
+ /** `true` when it lands in a gap (becomes a SIBLING of `node`), `false` when it lands inside. */
735
+ dropToGap: boolean;
736
+ };
714
737
  export type TreeProp = {
715
738
  /** The hierarchy (antd `treeData`). Use `fieldNames` to remap keys off an API response. */
716
739
  treeData: readonly TreeNodeProp[];
@@ -781,6 +804,21 @@ export type TreeProp = {
781
804
  * capability; see docs/DESIGN-AUTHORITY.md.
782
805
  */
783
806
  divided?: boolean;
807
+ /**
808
+ * Let the reader move nodes by dragging (antd `draggable`, gh#1093): drop on the upper or lower
809
+ * quarter of a row to land in the gap before / after it, on the middle to file it inside. The
810
+ * tree never mutates `treeData` — it reports the move through `onDrop` and the consumer persists
811
+ * it. Keyboard equivalent (WCAG 2.5.7): Alt+↑ / Alt+↓ move past the previous / next sibling,
812
+ * Alt+← outdents (after its parent), Alt+→ indents (into the previous sibling); each move is
813
+ * announced through a live region. A node is never dropped into itself or its own subtree.
814
+ */
815
+ draggable?: boolean;
816
+ /** Per-node drag gate (antd `draggable.nodeDraggable`). Disabled nodes never drag. */
817
+ allowDrag?: (node: TreeNodeProp) => boolean;
818
+ /** Per-target drop gate (antd `allowDrop`). Asked for pointer AND keyboard moves. */
819
+ allowDrop?: (info: TreeAllowDropInfoProp) => boolean;
820
+ /** A move the reader committed (antd `onDrop`). Update `treeData` here. */
821
+ onDrop?: (info: TreeDropInfoProp) => void;
784
822
  /** Draw each node's `icon` (antd `showIcon`). `variant="directory"` supplies folder/file glyphs. */
785
823
  showIcon?: boolean;
786
824
  /** `directory` is antd's `<DirectoryTree>`: folder/file glyphs and a full-row selected band. */
@@ -793,7 +831,8 @@ export type TreeProp = {
793
831
  id?: IdProp;
794
832
  /** Accessible name of the `role="tree"` container. Required unless `aria-labelledby` is given. */
795
833
  "aria-label"?: string;
796
- "aria-labelledby"?: string;
834
+ "aria-labelledby"?: string; /** Extra description ids; a `draggable` tree appends its own keyboard-move hint. */
835
+ "aria-describedby"?: string;
797
836
  };
798
837
  /**
799
838
  * @see ChatBubble — which side of the conversation the message sits on. LOGICAL, not physical:
@@ -3165,6 +3165,49 @@ export declare const COMPONENT_PROP_REGISTRY: {
3165
3165
  readonly reason: "The node's own child nodes — the hierarchy itself, not a slot.";
3166
3166
  }];
3167
3167
  };
3168
+ readonly TreeDropPositionProp: {
3169
+ readonly group: "data-display";
3170
+ readonly file: "components/data-display.prop.ts";
3171
+ readonly vocabulary: readonly [];
3172
+ };
3173
+ readonly TreeAllowDropInfoProp: {
3174
+ readonly group: "data-display";
3175
+ readonly file: "components/data-display.prop.ts";
3176
+ readonly vocabulary: readonly [{
3177
+ readonly field: "dragNode";
3178
+ readonly local: true;
3179
+ readonly reason: "antd `allowDrop` argument: the node being moved (gh#1093).";
3180
+ }, {
3181
+ readonly field: "dropNode";
3182
+ readonly local: true;
3183
+ readonly reason: "antd `allowDrop` argument: the node it would land on or beside.";
3184
+ }, {
3185
+ readonly field: "dropPosition";
3186
+ readonly local: true;
3187
+ readonly reason: "antd `allowDrop` argument: -1 before, 0 inside, 1 after.";
3188
+ }];
3189
+ };
3190
+ readonly TreeDropInfoProp: {
3191
+ readonly group: "data-display";
3192
+ readonly file: "components/data-display.prop.ts";
3193
+ readonly vocabulary: readonly [{
3194
+ readonly field: "dragNode";
3195
+ readonly local: true;
3196
+ readonly reason: "antd `onDrop` info: the node being moved (gh#1093).";
3197
+ }, {
3198
+ readonly field: "node";
3199
+ readonly local: true;
3200
+ readonly reason: "antd `onDrop` info: the node it landed on or beside.";
3201
+ }, {
3202
+ readonly field: "dropPosition";
3203
+ readonly local: true;
3204
+ readonly reason: "antd `onDrop` info, relative: -1 before, 0 inside, 1 after.";
3205
+ }, {
3206
+ readonly field: "dropToGap";
3207
+ readonly local: true;
3208
+ readonly reason: "antd `onDrop` info: true when it lands as a sibling, false inside.";
3209
+ }];
3210
+ };
3168
3211
  readonly TreeProp: {
3169
3212
  readonly group: "data-display";
3170
3213
  readonly file: "components/data-display.prop.ts";
@@ -3751,6 +3751,58 @@ const COMPONENT_PROP_REGISTRY = {
3751
3751
  }
3752
3752
  ]
3753
3753
  },
3754
+ TreeDropPositionProp: {
3755
+ group: "data-display",
3756
+ file: "components/data-display.prop.ts",
3757
+ vocabulary: []
3758
+ },
3759
+ TreeAllowDropInfoProp: {
3760
+ group: "data-display",
3761
+ file: "components/data-display.prop.ts",
3762
+ vocabulary: [
3763
+ {
3764
+ field: "dragNode",
3765
+ local: true,
3766
+ reason: "antd `allowDrop` argument: the node being moved (gh#1093)."
3767
+ },
3768
+ {
3769
+ field: "dropNode",
3770
+ local: true,
3771
+ reason: "antd `allowDrop` argument: the node it would land on or beside."
3772
+ },
3773
+ {
3774
+ field: "dropPosition",
3775
+ local: true,
3776
+ reason: "antd `allowDrop` argument: -1 before, 0 inside, 1 after."
3777
+ }
3778
+ ]
3779
+ },
3780
+ TreeDropInfoProp: {
3781
+ group: "data-display",
3782
+ file: "components/data-display.prop.ts",
3783
+ vocabulary: [
3784
+ {
3785
+ field: "dragNode",
3786
+ local: true,
3787
+ reason: "antd `onDrop` info: the node being moved (gh#1093)."
3788
+ },
3789
+ {
3790
+ field: "node",
3791
+ local: true,
3792
+ reason: "antd `onDrop` info: the node it landed on or beside."
3793
+ },
3794
+ {
3795
+ field: "dropPosition",
3796
+ local: true,
3797
+ reason: "antd `onDrop` info, relative: -1 before, 0 inside, 1 after."
3798
+ },
3799
+ {
3800
+ field: "dropToGap",
3801
+ local: true,
3802
+ reason: "antd `onDrop` info: true when it lands as a sibling, false inside."
3803
+ }
3804
+ ]
3805
+ },
3754
3806
  TreeProp: {
3755
3807
  group: "data-display",
3756
3808
  file: "components/data-display.prop.ts",
@@ -523,7 +523,8 @@
523
523
  hsl(var(--primary) / 0.2);
524
524
  }
525
525
 
526
- .ui-timeline-dot[data-status="pending"] {
526
+ .ui-timeline-dot[data-status="pending"],
527
+ .ui-timeline-dot[data-status="log"] {
527
528
  border-color: hsl(var(--border));
528
529
  background: hsl(var(--background));
529
530
  color: hsl(var(--muted-foreground));
@@ -2126,6 +2127,44 @@
2126
2127
  opacity: var(--disabled-opacity);
2127
2128
  }
2128
2129
 
2130
+ .ui-tree-node[draggable="true"] {
2131
+ cursor: grab;
2132
+ }
2133
+
2134
+ .ui-tree-node[data-dragging="true"] {
2135
+ opacity: var(--disabled-opacity);
2136
+ }
2137
+
2138
+ .ui-tree-node[data-drop-position] {
2139
+ position: relative;
2140
+ }
2141
+
2142
+ .ui-tree-node[data-drop-position="before"]::after,
2143
+ .ui-tree-node[data-drop-position="after"]::after {
2144
+ content: "";
2145
+ position: absolute;
2146
+ inset-inline: calc(
2147
+ var(--tree-node-padding-inline) + var(--tree-node-level, 0) * var(--tree-indent-width)
2148
+ )
2149
+ 0;
2150
+ block-size: var(--tree-drop-indicator-width, var(--stroke-md));
2151
+ background: var(--tree-drop-indicator-color, hsl(var(--primary)));
2152
+ pointer-events: none;
2153
+ }
2154
+
2155
+ .ui-tree-node[data-drop-position="before"]::after {
2156
+ inset-block-start: 0;
2157
+ }
2158
+
2159
+ .ui-tree-node[data-drop-position="after"]::after {
2160
+ inset-block-end: 0;
2161
+ }
2162
+
2163
+ .ui-tree-node[data-drop-position="inside"] {
2164
+ box-shadow: inset 0 0 0 var(--tree-drop-indicator-width, var(--stroke-md))
2165
+ var(--tree-drop-indicator-color, hsl(var(--primary)));
2166
+ }
2167
+
2129
2168
  .ui-tree-switcher {
2130
2169
  display: inline-flex;
2131
2170
  flex: none;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.14.0",
2
+ "version": "31.15.0",
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",
@@ -29,5 +29,8 @@
29
29
 
30
30
  --tree-node-filter-foreground: initial;
31
31
 
32
+ --tree-drop-indicator-color: initial;
33
+ --tree-drop-indicator-width: initial;
34
+
32
35
  --tree-loading-bar-inline-size: 40%;
33
36
  }
@@ -13,6 +13,7 @@ import {
13
13
  } from "@godxjp/ui/data-display";
14
14
  import { Text } from "@godxjp/ui/general";
15
15
  import { Flex, PageContainer } from "@godxjp/ui/layout";
16
+ import { ArrowRightLeft, MessageSquare } from "lucide-react";
16
17
 
17
18
  /**
18
19
  * Timeline — vertical event list with an icon rail; the current item gets a
@@ -89,6 +90,23 @@ const richItems: TimelineItem[] = [
89
90
 
90
91
  export default function Demo() {
91
92
  const { t, locale } = useTranslation();
93
+ // An activity log (gh#1092): every row already happened, so none is "done" or "upcoming" —
94
+ // `status: "log"` keeps the neutral dot and adds no screen-reader status prefix.
95
+ const logItems: TimelineItem[] = [
96
+ {
97
+ title: t("timelineDocs.log.status"),
98
+ time: "10/01 08:12",
99
+ status: "log",
100
+ icon: ArrowRightLeft,
101
+ },
102
+ {
103
+ title: t("timelineDocs.log.comment"),
104
+ time: "10/01 09:30",
105
+ status: "log",
106
+ icon: MessageSquare,
107
+ },
108
+ { title: t("timelineDocs.log.assignee"), time: "10/01 10:05", status: "log" },
109
+ ];
92
110
  const dayLabel = (day: number) =>
93
111
  new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", timeZone: "UTC" }).format(
94
112
  Date.UTC(2026, 8, day),
@@ -139,6 +157,16 @@ export default function Demo() {
139
157
  </CardContent>
140
158
  </Card>
141
159
 
160
+ <Card>
161
+ <CardHeader>
162
+ <CardTitle level={2}>{t("timelineDocs.log.title")}</CardTitle>
163
+ <CardDescription>{t("timelineDocs.log.description")}</CardDescription>
164
+ </CardHeader>
165
+ <CardContent>
166
+ <Timeline density="compact" items={logItems} />
167
+ </CardContent>
168
+ </Card>
169
+
142
170
  <Card>
143
171
  <CardHeader>
144
172
  <CardTitle level={2}>配送ルート進捗</CardTitle>
@@ -10,6 +10,7 @@ import {
10
10
  EmptyState,
11
11
  ScrollArea,
12
12
  Tree,
13
+ type TreeDropInfoProp,
13
14
  type TreeNodeProp,
14
15
  } from "@godxjp/ui/data-display";
15
16
  import { SearchInput } from "@godxjp/ui/data-entry";
@@ -246,6 +247,52 @@ function labelSuiteTree(
246
247
  });
247
248
  }
248
249
 
250
+ // ── card · draggable (gh#1093) ─────────────────────────────────────────────────────────────
251
+ const movablePagesSeed: TreeNodeProp[] = [
252
+ {
253
+ value: "guide",
254
+ label: "guide",
255
+ children: [
256
+ { value: "guide/start", label: "start" },
257
+ { value: "guide/setup", label: "setup" },
258
+ ],
259
+ },
260
+ { value: "faq", label: "faq" },
261
+ { value: "notes", label: "notes" },
262
+ ];
263
+
264
+ /** The seed's labels are catalogue keys (`treeDocs.drag.pages.*`), read in the reader's locale. */
265
+ function labelPages(
266
+ data: TreeNodeProp[],
267
+ t: (key: string) => string,
268
+ ): TreeNodeProp[] {
269
+ return data.map((node) => ({
270
+ ...node,
271
+ label: t(`treeDocs.drag.pages.${String(node.label)}`),
272
+ children: node.children ? labelPages(node.children, t) : undefined,
273
+ }));
274
+ }
275
+
276
+ /** What a consumer does in `onDrop`: take the node out, put it back where the drop says. */
277
+ function moveNode(data: TreeNodeProp[], info: TreeDropInfoProp): TreeNodeProp[] {
278
+ let moved: TreeNodeProp | undefined;
279
+ const without = (list: TreeNodeProp[]): TreeNodeProp[] =>
280
+ list
281
+ .filter((node) => (node.value === info.dragNode.value ? ((moved = node), false) : true))
282
+ .map((node) => (node.children ? { ...node, children: without(node.children) } : node));
283
+ const place = (list: TreeNodeProp[]): TreeNodeProp[] =>
284
+ list.flatMap((node) => {
285
+ if (node.value !== info.node.value) {
286
+ return node.children ? [{ ...node, children: place(node.children) }] : [node];
287
+ }
288
+ if (info.dropPosition === 0)
289
+ return [{ ...node, children: [...(node.children ?? []), moved!] }];
290
+ return info.dropPosition === -1 ? [moved!, node] : [node, moved!];
291
+ });
292
+ const rest = without(data);
293
+ return moved ? place(rest) : data;
294
+ }
295
+
249
296
  export default function Demo() {
250
297
  const { t } = useTranslation();
251
298
  // Card 1 — checks are controlled so the readout beside the tree can never disagree with it.
@@ -253,6 +300,8 @@ export default function Demo() {
253
300
  // Card 2 — selection is controlled and drives the detail pane.
254
301
  const [openFile, setOpenFile] = React.useState<string | undefined>("src/components/tree.tsx");
255
302
  // The page index — the tree IS the navigation, so selection is what the reading pane follows.
303
+ const [movablePages, setMovablePages] = React.useState(movablePagesSeed);
304
+ const [lastMove, setLastMove] = React.useState("");
256
305
  const [openPage, setOpenPage] = React.useState<string | undefined>("handbook/security/passwords");
257
306
  // Card 3 — async children land in state, exactly as a real fetch would.
258
307
  const [departments, setDepartments] = React.useState<Department[]>(initialDepartments);
@@ -452,6 +501,35 @@ export default function Demo() {
452
501
  </CardContent>
453
502
  </Card>
454
503
 
504
+ <Card>
505
+ <CardHeader>
506
+ <CardTitle level={2}>{t("treeDocs.drag.title")}</CardTitle>
507
+ <CardDescription>{t("treeDocs.drag.description")}</CardDescription>
508
+ </CardHeader>
509
+ <CardContent>
510
+ <Flex direction="col" gap="sm">
511
+ <Tree
512
+ aria-label={t("treeDocs.drag.treeLabel")}
513
+ treeData={labelPages(movablePages, t)}
514
+ defaultExpandAll
515
+ draggable
516
+ allowDrop={({ dropNode, dropPosition }) =>
517
+ !(dropNode.value === "notes" && dropPosition === 0)
518
+ }
519
+ onDrop={(info) => {
520
+ setMovablePages((current) => moveNode(current, info));
521
+ setLastMove(
522
+ `${String(info.dragNode.label)} → ${String(info.node.label)}(dropPosition ${info.dropPosition}, dropToGap ${info.dropToGap})`,
523
+ );
524
+ }}
525
+ />
526
+ <Text size="sm" tone="muted">
527
+ {t("treeDocs.drag.lastMove", { move: lastMove || t("treeDocs.drag.none") })}
528
+ </Text>
529
+ </Flex>
530
+ </CardContent>
531
+ </Card>
532
+
455
533
  <Card>
456
534
  <CardHeader>
457
535
  <CardTitle level={2}>組織ツリー(loadData · fieldNames · titleRender)</CardTitle>
@@ -632,6 +710,10 @@ export default function Demo() {
632
710
  },
633
711
  { label: "*", children: "同じ階層の兄弟をすべて展開" },
634
712
  { label: "文字キー", children: "先頭一致で次のノードへジャンプ(type-ahead)" },
713
+ {
714
+ label: "Alt + ↑ / ↓ / ← / →",
715
+ children: t("treeDocs.drag.keys"),
716
+ },
635
717
  ]}
636
718
  />
637
719
  </CardContent>
@@ -1507,6 +1507,21 @@
1507
1507
  "one": "Load {count} more",
1508
1508
  "other": "Load {count} more"
1509
1509
  }
1510
+ },
1511
+ "drag": {
1512
+ "title": "Drag to reorder or reparent (draggable · allowDrop · onDrop)",
1513
+ "description": "Drop on the upper quarter of a row to land before it, the lower quarter after it, the middle inside it. Tree never edits treeData; onDrop reports the move. From the keyboard: Alt+↑ / Alt+↓ move past a sibling, Alt+← outdents, Alt+→ indents into the previous sibling. Every move is announced in a live region. Here allowDrop refuses dropping inside “Release notes”.",
1514
+ "treeLabel": "Reorderable pages",
1515
+ "lastMove": "Last move: {move}",
1516
+ "none": "none",
1517
+ "keys": "When draggable: past a sibling / outdent / indent",
1518
+ "pages": {
1519
+ "guide": "User guide",
1520
+ "start": "Getting started",
1521
+ "setup": "Setup",
1522
+ "faq": "FAQ",
1523
+ "notes": "Release notes"
1524
+ }
1510
1525
  }
1511
1526
  },
1512
1527
  "progressDocs": {
@@ -1525,5 +1540,14 @@
1525
1540
  "home": "Home",
1526
1541
  "longSuite": "Q3 integration test · payment module · invoice re-issue scenarios",
1527
1542
  "longSection": "Acceptance criteria review for the release candidate of September 2026"
1543
+ },
1544
+ "timelineDocs": {
1545
+ "log": {
1546
+ "title": "status=\"log\" (activity log)",
1547
+ "description": "For a record of things that already happened, such as an issue history, use status=\"log\": a neutral dot and hairline rail, and no “Completed:” / “Upcoming:” prefix read out (antd's gray item).",
1548
+ "status": "Tanaka changed the status Open → In progress",
1549
+ "comment": "Nguyen commented",
1550
+ "assignee": "Tanaka changed the assignee"
1551
+ }
1528
1552
  }
1529
1553
  }
@@ -1500,6 +1500,21 @@
1500
1500
  "loadMore": {
1501
1501
  "other": "さらに {count} 件を読み込む"
1502
1502
  }
1503
+ },
1504
+ "drag": {
1505
+ "title": "ドラッグで並べ替え・親を変更(draggable · allowDrop · onDrop)",
1506
+ "description": "行の上 1/4 に落とすと前へ、下 1/4 で後ろへ、中央でその中(子)へ移動します。Tree は treeData を書き換えず、onDrop で移動を知らせるだけです。キーボードでは Alt+↑ / Alt+↓ で兄弟の前後へ、Alt+← で階層を上げ、Alt+→ で直前の兄弟の中へ。移動は毎回ライブリージョンで読み上げられます。ここでは allowDrop で「リリースノート」の中へは落とせません。",
1507
+ "treeLabel": "並べ替えできるページ一覧",
1508
+ "lastMove": "最後の移動: {move}",
1509
+ "none": "なし",
1510
+ "keys": "draggable のとき:兄弟の前後へ/階層を上げる/階層を下げる",
1511
+ "pages": {
1512
+ "guide": "利用ガイド",
1513
+ "start": "はじめに",
1514
+ "setup": "初期設定",
1515
+ "faq": "よくある質問",
1516
+ "notes": "リリースノート"
1517
+ }
1503
1518
  }
1504
1519
  },
1505
1520
  "progressDocs": {
@@ -1518,5 +1533,14 @@
1518
1533
  "home": "ホーム",
1519
1534
  "longSuite": "第3四半期 結合テスト 決済モジュール 請求書再発行シナリオ",
1520
1535
  "longSection": "2026年9月 リリース候補の受入基準レビュー"
1536
+ },
1537
+ "timelineDocs": {
1538
+ "log": {
1539
+ "title": "status=\"log\"(アクティビティログ)",
1540
+ "description": "課題の履歴のように、すべて起きたことの記録には status=\"log\"。中立の点と細い線で描き、「完了:」「予定:」のような読み上げ接頭辞を付けません(antd の gray item と同じ)。",
1541
+ "status": "田中 がステータスを変更 未対応 → 処理中",
1542
+ "comment": "グエン がコメント",
1543
+ "assignee": "田中 が担当者を変更"
1544
+ }
1521
1545
  }
1522
1546
  }
@@ -1500,6 +1500,21 @@
1500
1500
  "loadMore": {
1501
1501
  "other": "Tải thêm {count} ca"
1502
1502
  }
1503
+ },
1504
+ "drag": {
1505
+ "title": "Kéo để sắp xếp hoặc đổi cha (draggable · allowDrop · onDrop)",
1506
+ "description": "Thả vào 1/4 trên của dòng để đặt trước nó, 1/4 dưới để đặt sau, phần giữa để đặt vào trong. Tree không sửa treeData; onDrop báo nước đi. Bàn phím: Alt+↑ / Alt+↓ qua mục ngang hàng, Alt+← lùi cấp, Alt+→ thụt vào mục liền trước. Mỗi lần di chuyển được đọc qua live region. Ở đây allowDrop không cho thả vào trong “Ghi chú phát hành”.",
1507
+ "treeLabel": "Danh sách trang sắp xếp được",
1508
+ "lastMove": "Lần di chuyển cuối: {move}",
1509
+ "none": "chưa có",
1510
+ "keys": "Khi draggable: qua mục ngang hàng / lùi cấp / thụt cấp",
1511
+ "pages": {
1512
+ "guide": "Hướng dẫn sử dụng",
1513
+ "start": "Bắt đầu",
1514
+ "setup": "Cài đặt",
1515
+ "faq": "Câu hỏi thường gặp",
1516
+ "notes": "Ghi chú phát hành"
1517
+ }
1503
1518
  }
1504
1519
  },
1505
1520
  "progressDocs": {
@@ -1518,5 +1533,14 @@
1518
1533
  "home": "Trang chủ",
1519
1534
  "longSuite": "Kiểm thử tích hợp quý 3 · mô-đun thanh toán · kịch bản xuất lại hoá đơn",
1520
1535
  "longSection": "Rà soát tiêu chí nghiệm thu cho bản ứng viên tháng 9/2026"
1536
+ },
1537
+ "timelineDocs": {
1538
+ "log": {
1539
+ "title": "status=\"log\" (nhật ký hoạt động)",
1540
+ "description": "Với bản ghi những việc đã xảy ra như lịch sử issue, dùng status=\"log\": chấm trung tính, đường mảnh, không đọc tiền tố “Hoàn thành:” / “Sắp tới:” (giống item gray của antd).",
1541
+ "status": "Tanaka đổi trạng thái Mở → Đang xử lý",
1542
+ "comment": "Nguyễn đã bình luận",
1543
+ "assignee": "Tanaka đổi người phụ trách"
1544
+ }
1521
1545
  }
1522
1546
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "31.14.0",
4
- "godxUiMcp": "31.14.0",
3
+ "version": "31.15.0",
4
+ "godxUiMcp": "31.15.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",