@godxjp/ui 33.2.1 → 33.4.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.
- package/README.md +5 -5
- package/agent/START-HERE.md +6 -6
- package/agent/components/ResponsiveGrid.json +2 -2
- package/agent/components/SearchForm.json +115 -0
- package/agent/components/Toolbar.json +6 -3
- package/agent/components/TopbarItem.json +1 -1
- package/agent/components-index.json +10 -1
- package/agent/components.json +124 -6
- package/agent/index.json +7 -7
- package/agent/llms.txt +7 -7
- package/agent/patterns/condition-builder.json +31 -0
- package/agent/patterns/data-table-page.json +1 -1
- package/agent/patterns/filter-bar-layouts.json +30 -0
- package/agent/patterns/inertia-list-page.json +1 -1
- package/agent/patterns/list-filters.json +1 -1
- package/agent/patterns/search-form.json +27 -0
- package/agent/patterns-index.json +85 -0
- package/agent/patterns.json +91 -3
- package/agent/rules.json +1 -1
- package/agent/tokens.json +16 -4
- package/dist/components/data-display/popover.js +8 -6
- package/dist/components/feedback/overlay-close-focus.js +3 -2
- package/dist/components/feedback/tooltip.js +2 -1
- package/dist/components/layout/topbar-item.js +1 -1
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/navigation/search-form.d.ts +31 -0
- package/dist/components/navigation/search-form.js +112 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +9 -0
- package/dist/i18n/messages/ja.json +9 -0
- package/dist/i18n/messages/vi.json +9 -0
- package/dist/lab/mega-menu.js +3 -2
- package/dist/lib/active-element.d.ts +12 -0
- package/dist/lib/active-element.js +10 -0
- package/dist/lib/shortcut.js +3 -2
- package/dist/props/components/layout.prop.d.ts +6 -1
- package/dist/props/components/navigation.prop.d.ts +43 -3
- package/dist/props/registry.d.ts +5 -0
- package/dist/props/registry.js +12 -0
- package/dist/styles/layers.json +11 -1
- package/dist/styles/layout.css +41 -0
- package/dist/styles/shell-layout.css +19 -3
- package/dist/styles.css +1 -1
- package/dist/tokens/components/shell.css +4 -2
- package/docs/CONSUMER-RULES.md +1 -1
- package/package.json +2 -2
- package/scripts/ui-audit.mjs +148 -3
package/agent/tokens.json
CHANGED
|
@@ -10968,16 +10968,16 @@
|
|
|
10968
10968
|
"value": "var(--secondary)"
|
|
10969
10969
|
},
|
|
10970
10970
|
{
|
|
10971
|
-
"description": "
|
|
10971
|
+
"description": "How far a one-digit pill's top edge (block) and end edge (inline) sit OUTSIDE the glyph's. Fractions of the pill, not a flat length (gh#1341 — Material 3 badges / Apple HIG): half a pill inline centres it on the glyph's corner, three quarters block leaves a quarter of it on the glyph, so at most a quarter of the glyph is covered; a longer count stays inside the cell and grows back across the glyph, still covering at most a quarter of it. They used to be a flat 4px, which left a 16px pill covering 56% of a 16px bell. Declared `initial` with the default resolved at the call site, so a scoped --topbar-item-badge-size still moves them (the freeze rule). The ring is the surface colour drawn round the pill so it reads cut out of the glyph; role-mirror knob, default = hsl(var(--card)) (the bar) at the call site, and the nav rail sets it to its own surface. default = calc(var(--topbar-item-badge-size) * 3 / 4)",
|
|
10972
10972
|
"name": "--topbar-item-badge-offset-block",
|
|
10973
10973
|
"tier": "component",
|
|
10974
|
-
"value": "
|
|
10974
|
+
"value": "initial"
|
|
10975
10975
|
},
|
|
10976
10976
|
{
|
|
10977
|
-
"description": "
|
|
10977
|
+
"description": "default = calc(var(--topbar-item-badge-size) / 2)",
|
|
10978
10978
|
"name": "--topbar-item-badge-offset-inline",
|
|
10979
10979
|
"tier": "component",
|
|
10980
|
-
"value": "
|
|
10980
|
+
"value": "initial"
|
|
10981
10981
|
},
|
|
10982
10982
|
{
|
|
10983
10983
|
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
@@ -10985,6 +10985,18 @@
|
|
|
10985
10985
|
"tier": "component",
|
|
10986
10986
|
"value": "1rem"
|
|
10987
10987
|
},
|
|
10988
|
+
{
|
|
10989
|
+
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
10990
|
+
"name": "--topbar-item-badge-ring-width",
|
|
10991
|
+
"tier": "component",
|
|
10992
|
+
"value": "var(--stroke-md)"
|
|
10993
|
+
},
|
|
10994
|
+
{
|
|
10995
|
+
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
10996
|
+
"name": "--topbar-item-badge-ring-color",
|
|
10997
|
+
"tier": "component",
|
|
10998
|
+
"value": "initial"
|
|
10999
|
+
},
|
|
10988
11000
|
{
|
|
10989
11001
|
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
10990
11002
|
"name": "--topbar-item-badge-padding-inline",
|
|
@@ -5,6 +5,7 @@ import * as React from "react";
|
|
|
5
5
|
import { chain, mergeRefs } from "@react-aria/utils";
|
|
6
6
|
import { Popover as AriaPopover } from "react-aria-components";
|
|
7
7
|
import { cn } from "../../lib/utils.js";
|
|
8
|
+
import { getActiveElement } from "../../lib/active-element.js";
|
|
8
9
|
import { Slot } from "../../lib/slot.js";
|
|
9
10
|
import { useOverlayCloseFocus } from "../feedback/overlay-close-focus.js";
|
|
10
11
|
function toPlacement(side, align) {
|
|
@@ -278,7 +279,7 @@ function ClickContent({
|
|
|
278
279
|
if (!event.defaultPrevented) {
|
|
279
280
|
const target = firstTabbable(node);
|
|
280
281
|
target?.focus({ preventScroll: true });
|
|
281
|
-
if (!node.contains(
|
|
282
|
+
if (!node.contains(getActiveElement(node))) {
|
|
282
283
|
node.focus({ preventScroll: true });
|
|
283
284
|
}
|
|
284
285
|
}
|
|
@@ -287,11 +288,11 @@ function ClickContent({
|
|
|
287
288
|
React.useEffect(() => {
|
|
288
289
|
if (!open) return;
|
|
289
290
|
const onPointerDown = (event) => {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
if (contentRef.current
|
|
293
|
-
if (triggerRef.current
|
|
294
|
-
if (anchored && anchorRef.current
|
|
291
|
+
const path = event.composedPath();
|
|
292
|
+
const within = (node) => node !== null && path.includes(node);
|
|
293
|
+
if (within(contentRef.current)) return;
|
|
294
|
+
if (within(triggerRef.current)) return;
|
|
295
|
+
if (anchored && within(anchorRef.current)) return;
|
|
295
296
|
setOpen(false);
|
|
296
297
|
};
|
|
297
298
|
document.addEventListener("pointerdown", onPointerDown);
|
|
@@ -305,6 +306,7 @@ function ClickContent({
|
|
|
305
306
|
isOpen: root.open,
|
|
306
307
|
onOpenChange: onAriaOpenChange,
|
|
307
308
|
isNonModal: root.isNonModal,
|
|
309
|
+
shouldCloseOnInteractOutside: (element) => !triggerRef.current?.contains(element),
|
|
308
310
|
triggerRef: root.anchored ? root.anchorRef : root.triggerRef,
|
|
309
311
|
placement: toPlacement(side, align),
|
|
310
312
|
offset: sideOffset,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { getActiveElement } from "../../lib/active-element.js";
|
|
3
4
|
function useOverlayCloseFocus(isOpen, onCloseAutoFocus, contentRef) {
|
|
4
5
|
const nodeToRestore = React.useRef(null);
|
|
5
6
|
const renderedOpen = React.useRef(false);
|
|
@@ -7,7 +8,7 @@ function useOverlayCloseFocus(isOpen, onCloseAutoFocus, contentRef) {
|
|
|
7
8
|
if (isOpen !== renderedOpen.current) {
|
|
8
9
|
renderedOpen.current = isOpen;
|
|
9
10
|
if (isOpen && typeof document !== "undefined") {
|
|
10
|
-
const active =
|
|
11
|
+
const active = getActiveElement();
|
|
11
12
|
nodeToRestore.current = active === document.body ? null : active;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -17,7 +18,7 @@ function useOverlayCloseFocus(isOpen, onCloseAutoFocus, contentRef) {
|
|
|
17
18
|
onCloseAutoFocus?.(event);
|
|
18
19
|
const node = nodeToRestore.current;
|
|
19
20
|
nodeToRestore.current = null;
|
|
20
|
-
const active =
|
|
21
|
+
const active = getActiveElement();
|
|
21
22
|
const focusMovedAway = contentRef != null && active != null && active !== document.body && active.isConnected && contentRef.current?.contains(active) !== true;
|
|
22
23
|
if (!event.defaultPrevented && !focusMovedAway && node?.isConnected === true) {
|
|
23
24
|
node.focus();
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
TooltipTriggerStateContext
|
|
11
11
|
} from "react-aria-components";
|
|
12
12
|
import { cn } from "../../lib/utils.js";
|
|
13
|
+
import { getActiveElement } from "../../lib/active-element.js";
|
|
13
14
|
import { Slot } from "../../lib/slot.js";
|
|
14
15
|
const DEFAULT_DELAY_DURATION = 200;
|
|
15
16
|
const TooltipAnchorContext = React.createContext(
|
|
@@ -166,7 +167,7 @@ function EllipsisTooltip({
|
|
|
166
167
|
show(DEFAULT_DELAY_DURATION);
|
|
167
168
|
};
|
|
168
169
|
const onFocusIn = () => {
|
|
169
|
-
const active =
|
|
170
|
+
const active = getActiveElement(node);
|
|
170
171
|
if (active && (active === host || node.contains(active)) && isFocusVisible(active)) show(0);
|
|
171
172
|
};
|
|
172
173
|
node.addEventListener("pointerenter", onPointerEnter);
|
|
@@ -16,7 +16,7 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
16
16
|
...props
|
|
17
17
|
}, ref) {
|
|
18
18
|
const Comp = asChild ? Slot : "button";
|
|
19
|
-
const showBadge = badge !== void 0 && badge !== "";
|
|
19
|
+
const showBadge = badge !== void 0 && badge !== "" && badge !== 0;
|
|
20
20
|
const iconNode = icon !== void 0 && icon !== null && icon !== false ? /* @__PURE__ */ jsx(
|
|
21
21
|
"span",
|
|
22
22
|
{
|
|
@@ -3,6 +3,8 @@ export { Pagination, PaginationContent, PaginationItem, PaginationLink, Paginati
|
|
|
3
3
|
export { Steps } from "./steps.js";
|
|
4
4
|
export { FilterBar, FilterBarGroup, Toolbar, ToolbarGroup } from "./toolbar.js";
|
|
5
5
|
export type { FilterBarChipProp, FilterBarChipProps, FilterBarFilterProp, FilterBarFilterProps, FilterBarGroupProp, FilterBarGroupProps, FilterBarProp, FilterBarProps, FilterBarSearchProp, FilterBarSearchProps, ToolbarGroupProps, ToolbarProps, } from "./toolbar.js";
|
|
6
|
+
export { SearchForm } from "./search-form.js";
|
|
7
|
+
export type { SearchFormProp, SearchFormProps } from "./search-form.js";
|
|
6
8
|
export { AppSettingPicker } from "./app-setting-picker.js";
|
|
7
9
|
export type { AppSettingPickerProp, AppSettingPickerProps, AppSettingPickerMenuProp, AppSettingPickerCycleProp, AppSettingKind, AppSettingCycleKind, } from "./app-setting-picker.js";
|
|
8
10
|
export type { BreadcrumbItemProp as BreadcrumbItem } from "../../props/vocabulary/navigation.prop.js";
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "./pagination.js";
|
|
11
11
|
import { Steps } from "./steps.js";
|
|
12
12
|
import { FilterBar, FilterBarGroup, Toolbar, ToolbarGroup } from "./toolbar.js";
|
|
13
|
+
import { SearchForm } from "./search-form.js";
|
|
13
14
|
import { AppSettingPicker } from "./app-setting-picker.js";
|
|
14
15
|
import {
|
|
15
16
|
DropdownMenu,
|
|
@@ -54,6 +55,7 @@ export {
|
|
|
54
55
|
PaginationLink,
|
|
55
56
|
PaginationNext,
|
|
56
57
|
PaginationPrevious,
|
|
58
|
+
SearchForm,
|
|
57
59
|
Steps,
|
|
58
60
|
Tabs,
|
|
59
61
|
TabsContent,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SearchFormProp } from "../../props/components/navigation.prop.js";
|
|
3
|
+
export type { SearchFormProp, SearchFormProp as SearchFormProps };
|
|
4
|
+
/**
|
|
5
|
+
* SearchForm — the submit-driven 検索条件フォーム (Ant Design ProComponents `QueryFilter`).
|
|
6
|
+
*
|
|
7
|
+
* A `search` landmark (`<form role="search">`) holding a grid of labelled conditions, aligned in
|
|
8
|
+
* columns. The conditions past the first row sit behind one 詳細条件を開く / 閉じる toggle that
|
|
9
|
+
* names how many are hidden; Search (検索) submits, Clear (クリア) resets. After a search the
|
|
10
|
+
* conditions the results were searched with, and the result count, are announced in one
|
|
11
|
+
* `role="status"` line. Every label is localized through the library catalogue.
|
|
12
|
+
*
|
|
13
|
+
* Choose it over a live FilterBar when the conditions are many, the query is heavy, or the users
|
|
14
|
+
* expect an explicit 検索 button (Japanese business systems). Hidden conditions stay MOUNTED, so a
|
|
15
|
+
* value typed into one survives collapsing the section.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SearchForm: React.ForwardRefExoticComponent<Omit<import("../data-entry/index.js").FormProp, "aria-label" | "asChild" | "children" | "columns" | "onReset" | "role"> & {
|
|
18
|
+
children: import("../../props/index.js").ChildrenProp;
|
|
19
|
+
columns?: import("../../props/components/layout.prop.js").ResponsiveGridColumnsProp;
|
|
20
|
+
visibleFields?: number;
|
|
21
|
+
open?: import("../../props/index.js").OpenProp;
|
|
22
|
+
defaultOpen?: import("../../props/index.js").DefaultOpenProp;
|
|
23
|
+
onOpenChange?: import("../../props/index.js").OnOpenChangeProp;
|
|
24
|
+
onReset?: () => void;
|
|
25
|
+
searchLabel?: import("../../props/index.js").LabelProp;
|
|
26
|
+
resetLabel?: import("../../props/index.js").LabelProp;
|
|
27
|
+
summary?: React.ReactNode;
|
|
28
|
+
resultCount?: number;
|
|
29
|
+
loading?: import("../../props/index.js").PendingProp;
|
|
30
|
+
label?: string;
|
|
31
|
+
} & React.RefAttributes<HTMLFormElement>>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { Form } from "../data-entry/form.js";
|
|
8
|
+
import { Button } from "../general/button.js";
|
|
9
|
+
import { ResponsiveGrid } from "../layout/responsive-grid.js";
|
|
10
|
+
const DEFAULT_COLUMNS = { base: 1, md: 2, lg: 3 };
|
|
11
|
+
function widestStep(columns) {
|
|
12
|
+
if (typeof columns === "number") return Math.max(1, columns);
|
|
13
|
+
const steps = Object.values(columns ?? DEFAULT_COLUMNS).filter(
|
|
14
|
+
(n) => typeof n === "number"
|
|
15
|
+
);
|
|
16
|
+
return Math.max(1, ...steps);
|
|
17
|
+
}
|
|
18
|
+
const SearchForm = React.forwardRef(function SearchForm2({
|
|
19
|
+
children,
|
|
20
|
+
columns = DEFAULT_COLUMNS,
|
|
21
|
+
visibleFields,
|
|
22
|
+
open,
|
|
23
|
+
defaultOpen = false,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
onReset,
|
|
26
|
+
searchLabel,
|
|
27
|
+
resetLabel,
|
|
28
|
+
summary,
|
|
29
|
+
resultCount,
|
|
30
|
+
loading,
|
|
31
|
+
label,
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}, ref) {
|
|
35
|
+
const { t } = useTranslation();
|
|
36
|
+
const baseId = React.useId();
|
|
37
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
|
38
|
+
const isOpen = open ?? uncontrolledOpen;
|
|
39
|
+
const fields = React.Children.toArray(children).filter(React.isValidElement);
|
|
40
|
+
const limit = Math.max(1, visibleFields ?? widestStep(columns));
|
|
41
|
+
const hiddenCount = Math.max(0, fields.length - limit);
|
|
42
|
+
const hiddenIds = Array.from({ length: hiddenCount }, (_, i) => `${baseId}-more-${i}`);
|
|
43
|
+
const toggle = () => {
|
|
44
|
+
const next = !isOpen;
|
|
45
|
+
if (open === void 0) setUncontrolledOpen(next);
|
|
46
|
+
onOpenChange?.(next);
|
|
47
|
+
};
|
|
48
|
+
const showMeta = summary != null || resultCount !== void 0;
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
Form,
|
|
51
|
+
{
|
|
52
|
+
ref,
|
|
53
|
+
role: "search",
|
|
54
|
+
"aria-label": label ?? t("navigation.searchForm.label"),
|
|
55
|
+
className: cn("ui-search-form", className),
|
|
56
|
+
...props,
|
|
57
|
+
children: /* @__PURE__ */ jsxs("div", { className: "ui-search-form-body", children: [
|
|
58
|
+
/* @__PURE__ */ jsx(ResponsiveGrid, { columns, children: fields.map(
|
|
59
|
+
(field, index) => index < limit ? field : (
|
|
60
|
+
/* Past the first row: kept mounted and hidden, so a typed value survives a collapse. */
|
|
61
|
+
/* @__PURE__ */ jsx(
|
|
62
|
+
"div",
|
|
63
|
+
{
|
|
64
|
+
id: hiddenIds[index - limit],
|
|
65
|
+
hidden: !isOpen,
|
|
66
|
+
className: "ui-search-form-more",
|
|
67
|
+
children: field
|
|
68
|
+
},
|
|
69
|
+
field.key ?? index
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
) }),
|
|
73
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-search-form-actions", children: [
|
|
74
|
+
hiddenCount > 0 && /* @__PURE__ */ jsxs(
|
|
75
|
+
Button,
|
|
76
|
+
{
|
|
77
|
+
type: "button",
|
|
78
|
+
variant: "ghost",
|
|
79
|
+
size: "sm",
|
|
80
|
+
className: "ui-search-form-toggle",
|
|
81
|
+
"aria-expanded": isOpen,
|
|
82
|
+
"aria-controls": hiddenIds.join(" "),
|
|
83
|
+
onClick: toggle,
|
|
84
|
+
count: isOpen ? void 0 : hiddenCount,
|
|
85
|
+
showZero: false,
|
|
86
|
+
countLabel: t("navigation.searchForm.hiddenCount"),
|
|
87
|
+
children: [
|
|
88
|
+
isOpen ? t("navigation.searchForm.collapse") : t("navigation.searchForm.expand"),
|
|
89
|
+
isOpen ? /* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" })
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-search-form-submit", children: [
|
|
94
|
+
onReset && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onReset, children: resetLabel ?? t("navigation.searchForm.reset") }),
|
|
95
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", loading, children: searchLabel ?? t("navigation.searchForm.search") })
|
|
96
|
+
] })
|
|
97
|
+
] }),
|
|
98
|
+
showMeta && /* @__PURE__ */ jsxs("p", { role: "status", className: "ui-search-form-meta", children: [
|
|
99
|
+
summary != null && /* @__PURE__ */ jsxs("span", { className: "ui-search-form-summary", children: [
|
|
100
|
+
/* @__PURE__ */ jsx("span", { className: "ui-search-form-summary-label", children: t("navigation.searchForm.summary") }),
|
|
101
|
+
" ",
|
|
102
|
+
summary
|
|
103
|
+
] }),
|
|
104
|
+
resultCount !== void 0 && /* @__PURE__ */ jsx("span", { className: "ui-search-form-count", children: t("navigation.filterBar.resultCount", { count: resultCount }) })
|
|
105
|
+
] })
|
|
106
|
+
] })
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
export {
|
|
111
|
+
SearchForm
|
|
112
|
+
};
|
|
@@ -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": "33.
|
|
3
|
+
"version": "33.4.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,
|
|
@@ -588,6 +588,15 @@
|
|
|
588
588
|
"other": "{count} results"
|
|
589
589
|
}
|
|
590
590
|
},
|
|
591
|
+
"searchForm": {
|
|
592
|
+
"label": "Search conditions",
|
|
593
|
+
"search": "Search",
|
|
594
|
+
"reset": "Clear",
|
|
595
|
+
"expand": "More conditions",
|
|
596
|
+
"collapse": "Fewer conditions",
|
|
597
|
+
"hiddenCount": "hidden conditions",
|
|
598
|
+
"summary": "Searched with:"
|
|
599
|
+
},
|
|
591
600
|
"localePicker": {
|
|
592
601
|
"ariaLabel": "Language"
|
|
593
602
|
},
|
|
@@ -574,6 +574,15 @@
|
|
|
574
574
|
"removeFilter": "フィルターを解除: {label}",
|
|
575
575
|
"resultCount": "{count} 件の結果"
|
|
576
576
|
},
|
|
577
|
+
"searchForm": {
|
|
578
|
+
"label": "検索条件",
|
|
579
|
+
"search": "検索",
|
|
580
|
+
"reset": "クリア",
|
|
581
|
+
"expand": "詳細条件を開く",
|
|
582
|
+
"collapse": "詳細条件を閉じる",
|
|
583
|
+
"hiddenCount": "件の条件が非表示",
|
|
584
|
+
"summary": "検索条件:"
|
|
585
|
+
},
|
|
577
586
|
"localePicker": {
|
|
578
587
|
"ariaLabel": "言語"
|
|
579
588
|
},
|
|
@@ -575,6 +575,15 @@
|
|
|
575
575
|
"removeFilter": "Bỏ bộ lọc: {label}",
|
|
576
576
|
"resultCount": "{count} kết quả"
|
|
577
577
|
},
|
|
578
|
+
"searchForm": {
|
|
579
|
+
"label": "Điều kiện tìm kiếm",
|
|
580
|
+
"search": "Tìm kiếm",
|
|
581
|
+
"reset": "Xoá điều kiện",
|
|
582
|
+
"expand": "Mở thêm điều kiện",
|
|
583
|
+
"collapse": "Thu gọn điều kiện",
|
|
584
|
+
"hiddenCount": "điều kiện đang ẩn",
|
|
585
|
+
"summary": "Điều kiện tìm kiếm:"
|
|
586
|
+
},
|
|
578
587
|
"localePicker": {
|
|
579
588
|
"ariaLabel": "Ngôn ngữ"
|
|
580
589
|
},
|
package/dist/lab/mega-menu.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { ChevronDown } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { getActiveElement } from "../lib/active-element.js";
|
|
6
7
|
import { useTranslation } from "../i18n/use-translation.js";
|
|
7
8
|
const OPEN_DELAY_MS = 0;
|
|
8
9
|
const CLOSE_DELAY_MS = 100;
|
|
@@ -216,7 +217,7 @@ const MegaMenu = React.forwardRef(function MegaMenu2({
|
|
|
216
217
|
if (openKey === null) return;
|
|
217
218
|
const onPointerDown = (event) => {
|
|
218
219
|
const node = rootRef.current;
|
|
219
|
-
if (node && event.
|
|
220
|
+
if (node && !event.composedPath().includes(node)) close();
|
|
220
221
|
};
|
|
221
222
|
document.addEventListener("pointerdown", onPointerDown);
|
|
222
223
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
@@ -381,7 +382,7 @@ const MegaMenu = React.forwardRef(function MegaMenu2({
|
|
|
381
382
|
closeTimer.current = setTimeout(() => {
|
|
382
383
|
closeTimer.current = null;
|
|
383
384
|
const node = rootRef.current;
|
|
384
|
-
if (node && node.contains(
|
|
385
|
+
if (node && node.contains(getActiveElement(node))) return;
|
|
385
386
|
commitOpen(null);
|
|
386
387
|
}, closeDelay);
|
|
387
388
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The element that REALLY has focus, through shadow roots (gh#1340).
|
|
3
|
+
*
|
|
4
|
+
* `document.activeElement` stops at the shadow HOST: with the app mounted in a shadow root (GoDX
|
|
5
|
+
* Dock, `OverlayPortalProvider`), every `node.contains(document.activeElement)` in this library
|
|
6
|
+
* read "focus is not in here" while focus sat on an input inside the panel.
|
|
7
|
+
*
|
|
8
|
+
* With `scope`, the walk starts at that node's own root (`getRootNode().activeElement`), which also
|
|
9
|
+
* sees into a CLOSED shadow root the node lives in; it returns `null` when focus is not inside that
|
|
10
|
+
* root at all. Without it, the walk starts at `document` and descends through open shadow roots.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getActiveElement(scope?: Node | null): Element | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function getActiveElement(scope) {
|
|
2
|
+
if (typeof document === "undefined") return null;
|
|
3
|
+
const root = scope?.getRootNode();
|
|
4
|
+
let active = root instanceof Document || root instanceof ShadowRoot ? root.activeElement : document.activeElement;
|
|
5
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
6
|
+
return active;
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
getActiveElement
|
|
10
|
+
};
|
package/dist/lib/shortcut.js
CHANGED
|
@@ -53,9 +53,10 @@ function isEditableTarget(target) {
|
|
|
53
53
|
}
|
|
54
54
|
function shouldHandleShortcut(spec, event, owner) {
|
|
55
55
|
if (isImeComposing(event) || !shortcutMatches(spec, event)) return false;
|
|
56
|
-
|
|
56
|
+
const target = event.composedPath()[0] ?? event.target;
|
|
57
|
+
if (inForeignLayer(target, owner)) return false;
|
|
57
58
|
const commandModifier = spec.mod || spec.meta || spec.ctrl || spec.alt;
|
|
58
|
-
return commandModifier || !isEditableTarget(
|
|
59
|
+
return commandModifier || !isEditableTarget(target);
|
|
59
60
|
}
|
|
60
61
|
const SHORTCUT_LAYER = '[role="dialog"], [role="alertdialog"], [aria-modal="true"], [role="menu"], [role="listbox"]';
|
|
61
62
|
function inForeignLayer(target, owner) {
|
|
@@ -401,9 +401,11 @@ export type ResponsiveGridPresetProp = "pricing-plans";
|
|
|
401
401
|
* Block-axis alignment of the cells in each grid row — antd `Row align`, spelled with Flex's
|
|
402
402
|
* `FlexAlignProp` values (antd `top` → `start`; `stretch` is the same word). `stretch` gives every
|
|
403
403
|
* cell the height of the tallest one, so an empty Kanban lane is still a full-height drop zone.
|
|
404
|
+
* `center` is antd `middle`: cells of different heights share one centre line (a condition row's
|
|
405
|
+
* number and +/− buttons beside taller controls).
|
|
404
406
|
* Omitted, each flow keeps its own alignment: `columns` → `start`, `rows` → the grid's stretch.
|
|
405
407
|
*/
|
|
406
|
-
export type ResponsiveGridAlignProp = Extract<FlexAlignProp, "start" | "stretch">;
|
|
408
|
+
export type ResponsiveGridAlignProp = Extract<FlexAlignProp, "start" | "center" | "stretch">;
|
|
407
409
|
export type MasterDetailRailWidthProp = "narrow" | "compact" | "standard" | "wide";
|
|
408
410
|
export type MasterDetailRailProp = "master" | "detail";
|
|
409
411
|
/**
|
|
@@ -1654,6 +1656,9 @@ export type TopbarItemProp = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
|
1654
1656
|
* Drawn under `asChild` too (gh#1171): the pill goes inside the child element, after its own
|
|
1655
1657
|
* children, so a bell that is a link (`<TopbarItem asChild badge={3}><a …/></TopbarItem>`) keeps
|
|
1656
1658
|
* its count.
|
|
1659
|
+
*
|
|
1660
|
+
* Nothing renders for `undefined`, `""` or the NUMBER `0` (gh#1341), so pass the count as it is.
|
|
1661
|
+
* To show a zero deliberately, pass the string `"0"`.
|
|
1657
1662
|
*/
|
|
1658
1663
|
badge?: ReactNode;
|
|
1659
1664
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** Navigation component prop types — @see docs/COMPONENTS.md#navigation */
|
|
2
2
|
import type * as React from "react";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
-
import type { AffixProp, AffixTargetProp } from "./layout.prop.js";
|
|
5
|
-
import type { ActionsProp, ChildrenProp, ClassNameProp, DisabledProp, ErrorProp, HasActiveFiltersProp, IdProp, LabelProp, OnClearFiltersProp, PendingProp, PlaceholderProp, StickyProp, TextAlignProp } from "../vocabulary/index.js";
|
|
6
|
-
import type { SearchSelectOptionProp } from "./data-entry.prop.js";
|
|
4
|
+
import type { AffixProp, AffixTargetProp, ResponsiveGridColumnsProp } from "./layout.prop.js";
|
|
5
|
+
import type { ActionsProp, ChildrenProp, ClassNameProp, DisabledProp, ErrorProp, HasActiveFiltersProp, IdProp, LabelProp, OnClearFiltersProp, OnOpenChangeProp, OpenProp, DefaultOpenProp, PendingProp, PlaceholderProp, StickyProp, TextAlignProp } from "../vocabulary/index.js";
|
|
6
|
+
import type { FormProp, SearchSelectOptionProp } from "./data-entry.prop.js";
|
|
7
7
|
/**
|
|
8
8
|
* Renders the canonical SearchInput as the FIRST control of the strip, with a token-owned
|
|
9
9
|
* consistent width (`--filter-bar-search-width`, full-width below 640px). Controlled by consumer
|
|
@@ -123,6 +123,46 @@ export type ToolbarProp = {
|
|
|
123
123
|
export type FilterBarViewsPlacementProp = "above" | "inline";
|
|
124
124
|
/** @see ToolbarProp.clearDisplay */
|
|
125
125
|
export type FilterBarClearDisplayProp = "label" | "icon";
|
|
126
|
+
/**
|
|
127
|
+
* @see SearchForm — the submit-driven 検索条件フォーム (Ant Design ProComponents `QueryFilter`): a
|
|
128
|
+
* grid of labelled conditions, an explicit Search (検索) + Clear (クリア) pair, the conditions past
|
|
129
|
+
* the first row behind one 詳細条件を開く toggle, and a 検索条件 summary + result count after the
|
|
130
|
+
* search. A live FilterBar applies each change at once; a SearchForm applies them on submit.
|
|
131
|
+
*/
|
|
132
|
+
export type SearchFormProp = Omit<FormProp, "role" | "onReset" | "children" | "columns" | "asChild" | "aria-label"> & {
|
|
133
|
+
/** One FormField per condition, in reading order. Each direct child is one grid cell. */
|
|
134
|
+
children: ChildrenProp;
|
|
135
|
+
/** Grid columns per breakpoint. Default `{ base: 1, md: 2, lg: 3 }`: one column on a phone. */
|
|
136
|
+
columns?: ResponsiveGridColumnsProp;
|
|
137
|
+
/**
|
|
138
|
+
* How many conditions stay visible while the detail section is closed (antd
|
|
139
|
+
* `defaultFormItemsNumber`). Defaults to the widest `columns` step: the first row.
|
|
140
|
+
*/
|
|
141
|
+
visibleFields?: number;
|
|
142
|
+
/** Whether the conditions past `visibleFields` are shown (controlled). */
|
|
143
|
+
open?: OpenProp;
|
|
144
|
+
/** Initial open state when uncontrolled. Default `false`: collapsed to the first row. */
|
|
145
|
+
defaultOpen?: DefaultOpenProp;
|
|
146
|
+
/** Fires when the 詳細条件を開く / 閉じる toggle is pressed. */
|
|
147
|
+
onOpenChange?: OnOpenChangeProp;
|
|
148
|
+
/** Clear (クリア) handler. The Clear button renders only when this is set. */
|
|
149
|
+
onReset?: () => void;
|
|
150
|
+
/** Overrides the localized Search (検索) button text. */
|
|
151
|
+
searchLabel?: LabelProp;
|
|
152
|
+
/** Overrides the localized Clear (クリア) button text. */
|
|
153
|
+
resetLabel?: LabelProp;
|
|
154
|
+
/**
|
|
155
|
+
* The conditions the CURRENT results were searched with, shown after the localized 検索条件
|
|
156
|
+
* label in a `role="status"` line (e.g. 「国: ベトナム / 状態: 稼働中」).
|
|
157
|
+
*/
|
|
158
|
+
summary?: ReactNode;
|
|
159
|
+
/** Localized, pluralized result count shown in the same status line. */
|
|
160
|
+
resultCount?: number;
|
|
161
|
+
/** Puts the Search button in its loading state while the query runs. */
|
|
162
|
+
loading?: PendingProp;
|
|
163
|
+
/** Accessible name of the `search` landmark. Defaults to a localized "Search conditions". */
|
|
164
|
+
label?: string;
|
|
165
|
+
};
|
|
126
166
|
/** @see ToolbarGroup */
|
|
127
167
|
export type ToolbarGroupProp = {
|
|
128
168
|
label: LabelProp;
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -2692,6 +2692,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
2692
2692
|
readonly file: "components/navigation.prop.ts";
|
|
2693
2693
|
readonly vocabulary: readonly ["OnClearFiltersProp", "HasActiveFiltersProp", "StickyProp", "ActionsProp", "PendingProp", "DisabledProp", "ErrorProp", "ClassNameProp", "ChildrenProp"];
|
|
2694
2694
|
};
|
|
2695
|
+
readonly SearchFormProp: {
|
|
2696
|
+
readonly group: "navigation";
|
|
2697
|
+
readonly file: "components/navigation.prop.ts";
|
|
2698
|
+
readonly vocabulary: readonly ["OpenProp", "DefaultOpenProp", "OnOpenChangeProp", "LabelProp", "PendingProp", "ChildrenProp"];
|
|
2699
|
+
};
|
|
2695
2700
|
readonly FilterBarClearDisplayProp: {
|
|
2696
2701
|
readonly group: "navigation";
|
|
2697
2702
|
readonly file: "components/navigation.prop.ts";
|
package/dist/props/registry.js
CHANGED
|
@@ -3159,6 +3159,18 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
3159
3159
|
"ChildrenProp"
|
|
3160
3160
|
]
|
|
3161
3161
|
},
|
|
3162
|
+
SearchFormProp: {
|
|
3163
|
+
group: "navigation",
|
|
3164
|
+
file: "components/navigation.prop.ts",
|
|
3165
|
+
vocabulary: [
|
|
3166
|
+
"OpenProp",
|
|
3167
|
+
"DefaultOpenProp",
|
|
3168
|
+
"OnOpenChangeProp",
|
|
3169
|
+
"LabelProp",
|
|
3170
|
+
"PendingProp",
|
|
3171
|
+
"ChildrenProp"
|
|
3172
|
+
]
|
|
3173
|
+
},
|
|
3162
3174
|
FilterBarClearDisplayProp: {
|
|
3163
3175
|
group: "navigation",
|
|
3164
3176
|
file: "components/navigation.prop.ts",
|
package/dist/styles/layers.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "33.
|
|
2
|
+
"version": "33.4.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",
|
|
@@ -853,6 +853,16 @@
|
|
|
853
853
|
"data-display-layout.css"
|
|
854
854
|
]
|
|
855
855
|
},
|
|
856
|
+
"SearchForm": {
|
|
857
|
+
"layers": [
|
|
858
|
+
"layout.css",
|
|
859
|
+
"control.css",
|
|
860
|
+
"text-layout.css",
|
|
861
|
+
"dialog-layout.css",
|
|
862
|
+
"alert-layout.css",
|
|
863
|
+
"form-layout.css"
|
|
864
|
+
]
|
|
865
|
+
},
|
|
856
866
|
"SearchInput": {
|
|
857
867
|
"layers": [
|
|
858
868
|
"control.css"
|
package/dist/styles/layout.css
CHANGED
|
@@ -431,6 +431,10 @@
|
|
|
431
431
|
align-items: stretch;
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
.ui-responsive-grid[data-align="center"] {
|
|
435
|
+
align-items: center;
|
|
436
|
+
}
|
|
437
|
+
|
|
434
438
|
.ui-responsive-grid[data-gap="none"] {
|
|
435
439
|
gap: 0;
|
|
436
440
|
}
|
|
@@ -1580,6 +1584,43 @@
|
|
|
1580
1584
|
color: hsl(var(--text-error));
|
|
1581
1585
|
}
|
|
1582
1586
|
|
|
1587
|
+
.ui-search-form-body {
|
|
1588
|
+
display: flex;
|
|
1589
|
+
flex-direction: column;
|
|
1590
|
+
gap: var(--filter-bar-section-gap);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
.ui-search-form-more {
|
|
1594
|
+
min-inline-size: 0;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.ui-search-form-actions {
|
|
1598
|
+
display: flex;
|
|
1599
|
+
flex-wrap: wrap;
|
|
1600
|
+
align-items: center;
|
|
1601
|
+
gap: var(--filter-bar-gap);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
.ui-search-form-submit {
|
|
1605
|
+
display: flex;
|
|
1606
|
+
flex-wrap: wrap;
|
|
1607
|
+
align-items: center;
|
|
1608
|
+
gap: var(--space-2);
|
|
1609
|
+
margin-inline-start: auto;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
.ui-search-form-meta {
|
|
1613
|
+
display: flex;
|
|
1614
|
+
flex-wrap: wrap;
|
|
1615
|
+
gap: var(--space-1) var(--space-3);
|
|
1616
|
+
font-size: var(--filter-label-font-size);
|
|
1617
|
+
color: hsl(var(--muted-foreground));
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
.ui-search-form-summary-label {
|
|
1621
|
+
font-weight: var(--font-weight-medium);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1583
1624
|
.ui-legal-document-scope {
|
|
1584
1625
|
container: legal-document / inline-size;
|
|
1585
1626
|
padding-inline: var(--legal-document-page-padding-inline, var(--space-page-x));
|