@lablup/ui-common 0.2.0-alpha.18 → 0.2.0-alpha.19
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 +2 -2
- package/astryx/components/Form.doc.mjs +1 -1
- package/dist/assets/components/Form/FormItemVisual.css +1 -1
- package/dist/astryx/DateInput/tokens.stylex.d.ts +1 -0
- package/dist/astryx/DateInput/tokens.stylex.js +1 -0
- package/dist/astryx/Icon/IconSize.stylex.d.ts +1 -0
- package/dist/astryx/Icon/IconSize.stylex.js +1 -0
- package/dist/astryx/Indicator/indicator.markers.stylex.d.ts +1 -0
- package/dist/astryx/Indicator/indicator.markers.stylex.js +1 -0
- package/dist/astryx/Layer/layerAnimations.stylex.d.ts +1 -0
- package/dist/astryx/Layer/layerAnimations.stylex.js +1 -0
- package/dist/astryx/Layer/layerTextReset.stylex.d.ts +1 -0
- package/dist/astryx/Layer/layerTextReset.stylex.js +1 -0
- package/dist/astryx/Layer/layerViewportInset.stylex.d.ts +1 -0
- package/dist/astryx/Layer/layerViewportInset.stylex.js +1 -0
- package/dist/astryx/Layout/container.stylex.d.ts +1 -0
- package/dist/astryx/Layout/container.stylex.js +1 -0
- package/dist/astryx/Layout/edgeCompensation.stylex.d.ts +1 -0
- package/dist/astryx/Layout/edgeCompensation.stylex.js +1 -0
- package/dist/astryx/Layout/padding.stylex.d.ts +1 -0
- package/dist/astryx/Layout/padding.stylex.js +1 -0
- package/dist/astryx/Markdown/plugin-renderer.d.ts +1 -0
- package/dist/astryx/Markdown/plugin-renderer.js +1 -0
- package/dist/astryx/NavItem/navItemStyles.stylex.d.ts +1 -0
- package/dist/astryx/NavItem/navItemStyles.stylex.js +1 -0
- package/dist/astryx/Selector/selectorPresentation.stylex.d.ts +1 -0
- package/dist/astryx/Selector/selectorPresentation.stylex.js +1 -0
- package/dist/astryx/Stack/stack.stylex.d.ts +1 -0
- package/dist/astryx/Stack/stack.stylex.js +1 -0
- package/dist/astryx/Stack/stackItem.stylex.d.ts +1 -0
- package/dist/astryx/Stack/stackItem.stylex.js +1 -0
- package/dist/astryx/Text/text.stylex.d.ts +1 -0
- package/dist/astryx/Text/text.stylex.js +1 -0
- package/dist/astryx/theme/dataTokens.stylex.d.ts +1 -0
- package/dist/astryx/theme/dataTokens.stylex.js +1 -0
- package/dist/astryx/theme/neutral/fonts.css +3 -0
- package/dist/astryx/utils/focusOutline.stylex.d.ts +1 -0
- package/dist/astryx/utils/focusOutline.stylex.js +1 -0
- package/dist/astryx/utils/interactionOverlay.stylex.d.ts +1 -0
- package/dist/astryx/utils/interactionOverlay.stylex.js +1 -0
- package/dist/components/Form/FormItem.d.ts +5 -0
- package/dist/components/Form/FormItem.js +68 -67
- package/dist/components/Form/FormItem.js.map +1 -1
- package/dist/components/Form/FormItemVisual.d.ts +7 -0
- package/dist/components/Form/FormItemVisual2.js +60 -55
- package/dist/components/Form/FormItemVisual2.js.map +1 -1
- package/dist/components/NotificationStack/NotificationStack2.js +1 -0
- package/dist/components/NotificationStack/NotificationStack2.js.map +1 -1
- package/dist/components/PagedSelector/PagedSelector.d.ts +1 -1
- package/dist/components/PagedSelector/PagedSelector2.js.map +1 -1
- package/dist/forks/ComplexSelector/ComplexSelector.d.ts +82 -6
- package/dist/forks/ComplexSelector/ComplexSelector.js +170 -114
- package/dist/forks/ComplexSelector/ComplexSelector.js.map +1 -1
- package/dist/forks/ComplexSelector/ComplexSelector.styles.d.ts +40 -0
- package/dist/forks/ComplexSelector/ComplexSelector.styles.js +46 -6
- package/dist/forks/ComplexSelector/ComplexSelector.styles.js.map +1 -1
- package/dist/forks/ComplexSelector/index.d.ts +1 -1
- package/dist/forks/Drawer/Drawer.d.ts +21 -3
- package/dist/forks/Drawer/Drawer.js +70 -65
- package/dist/forks/Drawer/Drawer.js.map +1 -1
- package/dist/forks/Drawer/Drawer.styles.d.ts +16 -0
- package/dist/forks/Drawer/Drawer.styles.js +16 -0
- package/dist/forks/Drawer/Drawer.styles.js.map +1 -1
- package/dist/forks/Drawer/useDrawerDialogPresence.js.map +1 -1
- package/dist/forks/Tour/Tour.js +38 -40
- package/dist/forks/Tour/Tour.js.map +1 -1
- package/dist/forks/Tour/TourContext.js.map +1 -1
- package/dist/forks/Tour/TourStep.js.map +1 -1
- package/dist/forks/Tour/TourStep.styles.js.map +1 -1
- package/dist/forks/Tour/useTour.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/instance.js +2 -2
- package/dist/locales/en.generated.js +34 -0
- package/dist/locales/en.json +136 -0
- package/dist/locales/pseudo.generated.js +34 -0
- package/dist/locales/pseudo.json +102 -0
- package/dist/theme/lablup/built/lablup.d.ts +3 -3
- package/dist/theme/lablup/built/lablup.js +24 -5
- package/dist/theme/lablup/built/lablup.variants.d.ts +2 -2
- package/dist/theme/lablup/built/theme.css +70 -63
- package/dist/theme/lablup/built/theme.css.d.ts +9 -0
- package/package.json +86 -6
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import { default as React, ReactNode } from 'react';
|
|
2
2
|
import { StyleXStyles } from '@stylexjs/stylex';
|
|
3
|
-
import {
|
|
4
|
-
import { FieldStatusVariant } from '@astryxdesign/core/Field';
|
|
3
|
+
import { FieldInputBaseProps, FieldPartStyleProps, FieldStatusVariant } from '@astryxdesign/core/Field';
|
|
5
4
|
import { IconType } from '@astryxdesign/core/Icon';
|
|
6
5
|
import { LayerAlignment, LayerPlacement } from '@astryxdesign/core/Layer';
|
|
7
6
|
import { SizeValue } from '@astryxdesign/core/utils';
|
|
8
7
|
export type ComplexSelectorVariant = "input" | "ghost";
|
|
9
8
|
export type ComplexSelectorSize = "sm" | "md" | "lg";
|
|
9
|
+
/**
|
|
10
|
+
* Props the `renderTrigger` render prop hands to the control the caller
|
|
11
|
+
* renders.
|
|
12
|
+
* Spread them onto that control: it becomes the popup's anchor, the element
|
|
13
|
+
* focus returns to, and the control that announces the popup's state.
|
|
14
|
+
*/
|
|
15
|
+
export interface ComplexSelectorRenderTriggerProps extends Omit<FieldInputBaseProps, keyof FieldPartStyleProps | "id" | "onClick" | "onKeyDown" | "aria-haspopup" | "aria-expanded" | "aria-controls" | "aria-busy"> {
|
|
16
|
+
/**
|
|
17
|
+
* Attaches the control as the popup's anchor and focus-return target, and
|
|
18
|
+
* forwards it to the selector's own `ref`.
|
|
19
|
+
*/
|
|
20
|
+
ref: (element: HTMLElement | null) => void;
|
|
21
|
+
/** The id the field would have given its own button. */
|
|
22
|
+
id: string;
|
|
23
|
+
onClick: (event: React.MouseEvent<HTMLElement>) => void;
|
|
24
|
+
/** ArrowDown opens the popup, as it does on the built-in button. */
|
|
25
|
+
onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
|
|
26
|
+
"aria-haspopup": "dialog";
|
|
27
|
+
"aria-expanded": boolean;
|
|
28
|
+
"aria-controls": string;
|
|
29
|
+
"aria-busy": boolean | undefined;
|
|
30
|
+
}
|
|
10
31
|
export interface ComplexSelectorRenderState {
|
|
11
32
|
/** Whether the selector surface is open. */
|
|
12
33
|
isOpen: boolean;
|
|
@@ -40,9 +61,29 @@ export interface ComplexSelectorStatus {
|
|
|
40
61
|
type: "warning" | "error" | "success";
|
|
41
62
|
message?: string;
|
|
42
63
|
}
|
|
43
|
-
export interface ComplexSelectorProps<Value> extends Omit<
|
|
64
|
+
export interface ComplexSelectorProps<Value> extends Omit<FieldInputBaseProps, "children" | "onChange"> {
|
|
44
65
|
/** Label text for accessibility and the field label. */
|
|
45
66
|
label: string;
|
|
67
|
+
/**
|
|
68
|
+
* Ref to the trigger: the button that opens the popup and carries the
|
|
69
|
+
* accessible name — or, with `renderTrigger`, the caller's control.
|
|
70
|
+
*/
|
|
71
|
+
ref?: React.Ref<HTMLElement>;
|
|
72
|
+
/**
|
|
73
|
+
* Click handler on the trigger. It runs before the popup opens; call
|
|
74
|
+
* `preventDefault()` to keep the popup closed. A press elsewhere in the
|
|
75
|
+
* control box goes through the trigger, so it reaches this handler too.
|
|
76
|
+
* With `renderTrigger` it lands on the caller's control and runs after the
|
|
77
|
+
* built-in toggle, which it cannot cancel.
|
|
78
|
+
*/
|
|
79
|
+
onClick?: React.MouseEventHandler<HTMLElement>;
|
|
80
|
+
/**
|
|
81
|
+
* Key handler on the trigger. It runs after the built-in handling and
|
|
82
|
+
* receives every key, including the ArrowDown that opens the popup. With
|
|
83
|
+
* `renderTrigger` it lands on the caller's control, runs after the built-in
|
|
84
|
+
* handling, and is skipped for keys that handling cancels.
|
|
85
|
+
*/
|
|
86
|
+
onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
|
|
46
87
|
/** Current controlled value. */
|
|
47
88
|
value: Value;
|
|
48
89
|
/** Called when custom content commits a new value. */
|
|
@@ -77,7 +118,13 @@ export interface ComplexSelectorProps<Value> extends Omit<BaseProps<HTMLDivEleme
|
|
|
77
118
|
onClear?: () => void;
|
|
78
119
|
/** Validation status. */
|
|
79
120
|
status?: ComplexSelectorStatus;
|
|
80
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* How the status message is placed relative to the input.
|
|
123
|
+
* - 'attached': message overlaps directly below the input (bordered treatment; detached when variant is ghost)
|
|
124
|
+
* - 'detached': message floats below as a separate element with spacing
|
|
125
|
+
* - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
|
|
126
|
+
* @default 'attached'
|
|
127
|
+
*/
|
|
81
128
|
statusVariant?: FieldStatusVariant;
|
|
82
129
|
/** Tooltip text displayed next to the label. */
|
|
83
130
|
labelTooltip?: string;
|
|
@@ -99,6 +146,35 @@ export interface ComplexSelectorProps<Value> extends Omit<BaseProps<HTMLDivEleme
|
|
|
99
146
|
* state in the parent — the selector owns its visibility.
|
|
100
147
|
*/
|
|
101
148
|
handleRef?: React.Ref<ComplexSelectorHandle>;
|
|
149
|
+
/**
|
|
150
|
+
* Render the control the popup hangs off — a glyph in a list row, a chip,
|
|
151
|
+
* an icon button — instead of the selector's own field and button. Spread
|
|
152
|
+
* the given props onto it; the popup is then anchored to that control and
|
|
153
|
+
* still labelled by `label`. The field chrome (`Field`, status, spinner,
|
|
154
|
+
* chevron) is not rendered; the caller owns the opener. Pair with
|
|
155
|
+
* `handleRef` to open the popup from a keystroke elsewhere.
|
|
156
|
+
*
|
|
157
|
+
* Hover and pressed paint stay yours. The open state reaches your control
|
|
158
|
+
* as `aria-expanded` on the given props, so style it from the rendered
|
|
159
|
+
* attribute. A pressed look keyed to `:active` is not a substitute:
|
|
160
|
+
* `:active` does not behave the same under a coarse pointer, which is why
|
|
161
|
+
* menu rows drop coarse-pointer `:active` paint entirely.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```
|
|
165
|
+
* <ComplexSelector
|
|
166
|
+
* label="Assignee"
|
|
167
|
+
* renderTrigger={props => <IconButton icon="user" label="Assign" {...props} />}
|
|
168
|
+
* />
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```
|
|
173
|
+
* // Styling the open state from the rendered attribute:
|
|
174
|
+
* // .my-trigger[aria-expanded='true'] { background: var(--color-overlay-pressed); }
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
renderTrigger?: (props: ComplexSelectorRenderTriggerProps) => ReactNode;
|
|
102
178
|
/**
|
|
103
179
|
* Called whenever the selector surface opens or closes, however it happened
|
|
104
180
|
* — the trigger, the keyboard, a light dismiss, Escape, content that calls
|
|
@@ -108,7 +184,7 @@ export interface ComplexSelectorProps<Value> extends Omit<BaseProps<HTMLDivEleme
|
|
|
108
184
|
onOpenChange?: (isOpen: boolean) => void;
|
|
109
185
|
/** StyleX styles for the popup content container. */
|
|
110
186
|
contentXstyle?: StyleXStyles;
|
|
111
|
-
/** Test ID for the trigger
|
|
187
|
+
/** Test ID for the trigger button. */
|
|
112
188
|
"data-testid"?: string;
|
|
113
189
|
}
|
|
114
190
|
/**
|
|
@@ -138,7 +214,7 @@ export interface ComplexSelectorProps<Value> extends Omit<BaseProps<HTMLDivEleme
|
|
|
138
214
|
* </ComplexSelector>
|
|
139
215
|
* ```
|
|
140
216
|
*/
|
|
141
|
-
export declare function ComplexSelector<Value>({ label, value, onChange, changeAction, children, triggerLabel, placeholder: placeholderFromProps, isLabelHidden, description, isOptional, isRequired, isDisabled, isLoading, hasClear, onClear, status, statusVariant, labelTooltip, size, variant, startIcon, width, placement, alignment, handleRef, onOpenChange, contentXstyle,
|
|
217
|
+
export declare function ComplexSelector<Value>({ label, value, onChange, changeAction, children, triggerLabel, placeholder: placeholderFromProps, isLabelHidden, description, isOptional, isRequired, isDisabled, isLoading, hasClear, onClear, status, statusVariant, labelTooltip, size, variant, startIcon, width, placement, alignment, handleRef, renderTrigger, onOpenChange, contentXstyle, containerXstyle, containerClassName, containerStyle, controlXstyle, controlClassName, controlStyle, ref, id, onClick: onClickProp, onKeyDown: onKeyDownProp, "aria-describedby": ariaDescribedByProp, ...restProps }: ComplexSelectorProps<Value>): React.JSX.Element;
|
|
142
218
|
export declare namespace ComplexSelector {
|
|
143
219
|
var displayName: string;
|
|
144
220
|
}
|
|
@@ -1,157 +1,213 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { compiledStyles as e } from "../compiled.js";
|
|
3
3
|
import { interactionOverlayStyles as t, styles as n } from "./ComplexSelector.styles.js";
|
|
4
|
-
import { use as r, useCallback as i, useId as a, useImperativeHandle as
|
|
5
|
-
import { useTranslator as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
4
|
+
import { use as r, useCallback as i, useId as a, useImperativeHandle as o, useOptimistic as s, useRef as c, useTransition as l } from "react";
|
|
5
|
+
import { useTranslator as u } from "@astryxdesign/core/i18n";
|
|
6
|
+
import { useDevWarning as d, useMergedRefs as f, usePressFeedback as p } from "@astryxdesign/core/hooks";
|
|
7
|
+
import { layerAnimations as ee } from "@astryxdesign/core/Layer";
|
|
8
|
+
import { composeEventHandlers as m, focusOutlineStyles as te, isRenderable as ne, mergeProps as re, mergeRefs as ie, themeProps as h } from "@astryxdesign/core/utils";
|
|
9
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
10
|
+
import { Icon as ae, renderIconSlot as oe } from "@astryxdesign/core/Icon";
|
|
11
|
+
import { usePopover as se } from "@astryxdesign/core/Popover";
|
|
12
|
+
import * as y from "@stylexjs/stylex";
|
|
13
|
+
import { Field as ce, InputClearButton as le, inputWrapperStyles as ue } from "@astryxdesign/core/Field";
|
|
14
|
+
import { FormLayoutContext as b } from "@astryxdesign/core/FormLayout";
|
|
15
|
+
import { useInputGroup as x } from "@astryxdesign/core/InputGroup";
|
|
16
|
+
import { Spinner as de } from "@astryxdesign/core/Spinner";
|
|
17
|
+
import { spacingVars as fe } from "@astryxdesign/core/theme/tokens.stylex";
|
|
16
18
|
//#region src/forks/ComplexSelector/ComplexSelector.tsx
|
|
17
|
-
var
|
|
18
|
-
function
|
|
19
|
-
let { defaultOptionality: n } = r(
|
|
19
|
+
var S = e(n), pe = e(t);
|
|
20
|
+
function me({ isRequired: e = !1, isOptional: t = !1 }) {
|
|
21
|
+
let { defaultOptionality: n } = r(b);
|
|
20
22
|
return !t && (e || n === "required");
|
|
21
23
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
var C = "`xstyle`, `className`, and `style` are not supported on Field-composed inputs and were ignored. Style the field with `containerXstyle`/`containerClassName`/`containerStyle` and the control box with `controlXstyle`/`controlClassName`/`controlStyle`. `astryx upgrade` rewrites existing usage.", w = "`containerXstyle`/`containerClassName`/`containerStyle` have no effect here because no Field box renders (inside InputGroup or a `horizontal-labels` FormLayout). Inside InputGroup, style the InputGroup.";
|
|
25
|
+
function he(e, t, n) {
|
|
26
|
+
let i = x(), { direction: a } = r(b), o = i == null && a !== "horizontal-labels", s = t, c = s.xstyle != null || s.className != null || s.style != null, l = t;
|
|
27
|
+
if (c) {
|
|
28
|
+
let { xstyle: e, className: t, style: n, ...r } = s;
|
|
29
|
+
l = r;
|
|
30
|
+
}
|
|
31
|
+
d(e, C, c);
|
|
32
|
+
let { containerXstyle: u, containerClassName: f, containerStyle: p } = n;
|
|
33
|
+
return d(e, w, (u != null || f != null || p != null) && !o), {
|
|
34
|
+
rest: l,
|
|
35
|
+
hasFieldBox: o,
|
|
36
|
+
container: o ? {
|
|
37
|
+
xstyle: u,
|
|
38
|
+
className: f,
|
|
39
|
+
style: p
|
|
40
|
+
} : {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
var ge = "`container*` and `control*` styling props have no effect with `renderTrigger`: no Field box or control box renders. Style the control you render instead.";
|
|
44
|
+
function T({ label: e, value: t, onChange: n, changeAction: r, children: b, triggerLabel: x, placeholder: C, isLabelHidden: w = !1, description: T, isOptional: E = !1, isRequired: D = !1, isDisabled: O = !1, isLoading: _e = !1, hasClear: ve = !1, onClear: k, status: A, statusVariant: j = "attached", labelTooltip: ye, size: M = "md", variant: N = "input", startIcon: be, width: xe, placement: P = "below", alignment: Se = "start", handleRef: Ce, renderTrigger: F, onOpenChange: I, contentXstyle: we, containerXstyle: L, containerClassName: R, containerStyle: z, controlXstyle: B, controlClassName: V, controlStyle: H, ref: U, id: Te, onClick: W, onKeyDown: G, "aria-describedby": K, ...Ee }) {
|
|
45
|
+
let { rest: De, container: Oe } = he("ComplexSelector", Ee, {
|
|
46
|
+
containerXstyle: L,
|
|
47
|
+
containerClassName: R,
|
|
48
|
+
containerStyle: z
|
|
49
|
+
});
|
|
50
|
+
d("ComplexSelector", ge, F != null && (L != null || R != null || z != null || B != null || V != null || H != null));
|
|
51
|
+
let ke = u(), Ae = p(), je = me({
|
|
52
|
+
isRequired: D,
|
|
53
|
+
isOptional: E
|
|
54
|
+
}), Me = C ?? ke("@astryx.selector.placeholder"), Ne = N === "ghost" && j === "attached" ? "detached" : j, Pe = a(), q = Te ?? Pe, Fe = a(), J = a(), Ie = a(), Le = a(), Re = [T ? Ie : null, A?.message ? Le : null].filter((e) => e != null).join(" ") || void 0, Y = c(null), ze = f(Y, U), [Be, X] = l(), [Ve, He] = s(t), Z = _e || Be, Ue = i(() => {
|
|
55
|
+
I?.(!0);
|
|
56
|
+
}, [I]), We = i(() => {
|
|
57
|
+
Y.current?.focus(), I?.(!1);
|
|
58
|
+
}, [I]), Q = se({
|
|
31
59
|
dialogLabel: e,
|
|
32
60
|
hasCloseButton: !1,
|
|
33
61
|
hasAutoFocus: !0,
|
|
34
62
|
surfaceTarget: "complex-selector-popup",
|
|
35
|
-
onShow:
|
|
36
|
-
onHide:
|
|
37
|
-
}),
|
|
38
|
-
|
|
39
|
-
}, [
|
|
40
|
-
|
|
41
|
-
}, [
|
|
42
|
-
|
|
63
|
+
onShow: Ue,
|
|
64
|
+
onHide: We
|
|
65
|
+
}), $ = Q.isOpen, Ge = i(() => {
|
|
66
|
+
O || (Q.isOpen ? Q.hide() : Q.show());
|
|
67
|
+
}, [O, Q]), Ke = i(() => {
|
|
68
|
+
Q.hide();
|
|
69
|
+
}, [Q]), qe = i((e) => {
|
|
70
|
+
e.key === "ArrowDown" && !$ && !O && (e.preventDefault(), Q.show());
|
|
71
|
+
}, [
|
|
72
|
+
O,
|
|
73
|
+
$,
|
|
74
|
+
Q
|
|
75
|
+
]), Je = m(W, Ge), Ye = (e) => {
|
|
76
|
+
qe(e), G?.(e);
|
|
77
|
+
}, Xe = i((e) => {
|
|
78
|
+
Y.current?.contains(e.target) || Y.current?.click();
|
|
79
|
+
}, []);
|
|
80
|
+
o(Ce, () => ({
|
|
43
81
|
open: () => {
|
|
44
|
-
|
|
82
|
+
O || Q.show();
|
|
45
83
|
},
|
|
46
|
-
close: () =>
|
|
84
|
+
close: () => Q.hide(),
|
|
47
85
|
toggle: () => {
|
|
48
|
-
|
|
86
|
+
O || (Q.isOpen ? Q.hide() : Q.show());
|
|
49
87
|
},
|
|
50
|
-
isOpen: () =>
|
|
51
|
-
}), [
|
|
52
|
-
let
|
|
53
|
-
n?.(e), r &&
|
|
54
|
-
|
|
88
|
+
isOpen: () => Q.isOpen
|
|
89
|
+
}), [O, Q]);
|
|
90
|
+
let Ze = i((e) => {
|
|
91
|
+
n?.(e), r && X(async () => {
|
|
92
|
+
He(e), await r(e);
|
|
55
93
|
});
|
|
56
94
|
}, [
|
|
57
95
|
r,
|
|
58
96
|
n,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
]),
|
|
97
|
+
He,
|
|
98
|
+
X
|
|
99
|
+
]), Qe = x ?? Me, $e = oe(be, {
|
|
62
100
|
size: "sm",
|
|
63
101
|
color: "secondary"
|
|
64
|
-
}),
|
|
65
|
-
id:
|
|
66
|
-
...
|
|
67
|
-
children:
|
|
68
|
-
isOpen:
|
|
69
|
-
isBusy:
|
|
70
|
-
triggerId:
|
|
71
|
-
contentId:
|
|
102
|
+
}), et = /* @__PURE__ */ _("div", {
|
|
103
|
+
id: J,
|
|
104
|
+
...y.props(S.content, we),
|
|
105
|
+
children: b(Ve, Ze, Ke, {
|
|
106
|
+
isOpen: $,
|
|
107
|
+
isBusy: Z,
|
|
108
|
+
triggerId: q,
|
|
109
|
+
contentId: J
|
|
72
110
|
})
|
|
73
|
-
}),
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
111
|
+
}), tt = Q.render(et, {
|
|
112
|
+
placement: P,
|
|
113
|
+
alignment: Se,
|
|
114
|
+
offset: fe["--spacing-1"],
|
|
115
|
+
xstyle: [S.popover, ee[P]]
|
|
116
|
+
});
|
|
117
|
+
if (F != null) {
|
|
118
|
+
let e = {
|
|
119
|
+
...De,
|
|
120
|
+
"aria-describedby": K,
|
|
121
|
+
ref: ie((e) => {
|
|
122
|
+
Q.triggerRef(e), Y.current = e;
|
|
123
|
+
}, U),
|
|
124
|
+
id: q,
|
|
125
|
+
onClick: m(Ge, W),
|
|
126
|
+
onKeyDown: m(qe, G),
|
|
127
|
+
"aria-haspopup": "dialog",
|
|
128
|
+
"aria-expanded": $,
|
|
129
|
+
"aria-controls": J,
|
|
130
|
+
"aria-busy": Z || void 0
|
|
131
|
+
};
|
|
132
|
+
return /* @__PURE__ */ v(g, { children: [F(e), tt] });
|
|
133
|
+
}
|
|
134
|
+
let nt = /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ v("div", {
|
|
135
|
+
ref: Q.triggerRef,
|
|
136
|
+
...Ae,
|
|
137
|
+
onClick: Xe,
|
|
138
|
+
...re(h("complex-selector", {
|
|
139
|
+
variant: N,
|
|
140
|
+
size: M,
|
|
141
|
+
status: A?.type ?? null
|
|
142
|
+
}), y.props(ue.base, S.triggerContainer, S[M], te.focusWithin, N === "ghost" && S.triggerGhost, N === "ghost" && pe.backgroundImage, O && ue.disabled, N === "ghost" && O && S.triggerGhostDisabled, O && S.disabled, x == null && S.placeholder, B), V, H),
|
|
83
143
|
children: [
|
|
84
|
-
|
|
85
|
-
/* @__PURE__ */
|
|
86
|
-
|
|
87
|
-
|
|
144
|
+
ne($e) && $e,
|
|
145
|
+
/* @__PURE__ */ _("button", {
|
|
146
|
+
...De,
|
|
147
|
+
ref: ze,
|
|
148
|
+
id: q,
|
|
88
149
|
type: "button",
|
|
89
150
|
"aria-haspopup": "dialog",
|
|
90
|
-
"aria-expanded":
|
|
91
|
-
"aria-controls":
|
|
92
|
-
"aria-describedby":
|
|
93
|
-
"aria-labelledby":
|
|
94
|
-
"aria-required":
|
|
95
|
-
"aria-invalid":
|
|
96
|
-
"aria-busy":
|
|
97
|
-
disabled:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
children: je
|
|
151
|
+
"aria-expanded": $,
|
|
152
|
+
"aria-controls": J,
|
|
153
|
+
"aria-describedby": [K, Re].filter(Boolean).join(" ") || void 0,
|
|
154
|
+
"aria-labelledby": Fe,
|
|
155
|
+
"aria-required": je ? "true" : void 0,
|
|
156
|
+
"aria-invalid": A?.type === "error" ? "true" : void 0,
|
|
157
|
+
"aria-busy": Z || void 0,
|
|
158
|
+
disabled: O,
|
|
159
|
+
onClick: Je,
|
|
160
|
+
onKeyDown: Ye,
|
|
161
|
+
...y.props(S.trigger),
|
|
162
|
+
children: /* @__PURE__ */ _("span", {
|
|
163
|
+
...y.props(S.triggerText),
|
|
164
|
+
children: Qe
|
|
105
165
|
})
|
|
106
166
|
}),
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
label:
|
|
167
|
+
Z && /* @__PURE__ */ _(de, { size: "sm" }),
|
|
168
|
+
ve && x != null && !O && /* @__PURE__ */ _(le, {
|
|
169
|
+
label: ke("@astryx.selector.clearLabel", { label: e }),
|
|
110
170
|
onClick: (e) => {
|
|
111
|
-
e.stopPropagation(),
|
|
171
|
+
e.stopPropagation(), k ? k() : n?.(void 0);
|
|
112
172
|
}
|
|
113
173
|
}),
|
|
114
|
-
/* @__PURE__ */
|
|
174
|
+
/* @__PURE__ */ _(ae, {
|
|
115
175
|
icon: "chevronDown",
|
|
116
176
|
size: "sm",
|
|
117
177
|
color: "secondary",
|
|
118
178
|
xstyle: [
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
179
|
+
S.triggerIcon,
|
|
180
|
+
S.triggerIconRotation,
|
|
181
|
+
$ && S.triggerIconOpen
|
|
122
182
|
],
|
|
123
|
-
...
|
|
183
|
+
...h("complex-selector-indicator-icon", { state: $ ? "expanded" : "collapsed" })
|
|
124
184
|
})
|
|
125
185
|
]
|
|
126
|
-
}),
|
|
127
|
-
|
|
128
|
-
alignment: I,
|
|
129
|
-
offset: ue["--spacing-1"],
|
|
130
|
-
xstyle: [y.popover, ne[F]]
|
|
131
|
-
})] });
|
|
132
|
-
return /* @__PURE__ */ u(g, {
|
|
186
|
+
}), tt] });
|
|
187
|
+
return /* @__PURE__ */ _(ce, {
|
|
133
188
|
label: e,
|
|
134
|
-
isLabelHidden:
|
|
135
|
-
description:
|
|
136
|
-
inputID:
|
|
137
|
-
descriptionID:
|
|
138
|
-
labelID:
|
|
139
|
-
isOptional:
|
|
140
|
-
isRequired:
|
|
141
|
-
isDisabled:
|
|
142
|
-
status:
|
|
143
|
-
type:
|
|
144
|
-
message:
|
|
145
|
-
messageID:
|
|
189
|
+
isLabelHidden: w,
|
|
190
|
+
description: T,
|
|
191
|
+
inputID: q,
|
|
192
|
+
descriptionID: T ? Ie : void 0,
|
|
193
|
+
labelID: Fe,
|
|
194
|
+
isOptional: E,
|
|
195
|
+
isRequired: D,
|
|
196
|
+
isDisabled: O,
|
|
197
|
+
status: A ? {
|
|
198
|
+
type: A.type,
|
|
199
|
+
message: A.message,
|
|
200
|
+
messageID: A.message ? Le : void 0
|
|
146
201
|
} : void 0,
|
|
147
|
-
statusVariant:
|
|
148
|
-
labelTooltip:
|
|
149
|
-
width:
|
|
150
|
-
|
|
202
|
+
statusVariant: Ne,
|
|
203
|
+
labelTooltip: ye,
|
|
204
|
+
width: xe,
|
|
205
|
+
...Oe,
|
|
206
|
+
children: nt
|
|
151
207
|
});
|
|
152
208
|
}
|
|
153
|
-
|
|
209
|
+
T.displayName = "ComplexSelector";
|
|
154
210
|
//#endregion
|
|
155
|
-
export {
|
|
211
|
+
export { T as ComplexSelector };
|
|
156
212
|
|
|
157
213
|
//# sourceMappingURL=ComplexSelector.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComplexSelector.js","names":[],"sources":["../../../src/forks/ComplexSelector/ComplexSelector.tsx"],"sourcesContent":["// Copyright (c) Meta Platforms, Inc. and affiliates.\n// Modifications copyright (c) Lablup Inc.\n//\n// Forked from @astryxdesign/core 0.6.5, src/ComplexSelector/ComplexSelector.tsx\n// (MIT; see NOTICE). Provenance and the drift guard: src/forks/provenance.json.\n\n\"use client\";\n\n/**\n * ComplexSelector, ui-common's copy of Astryx's, with one upstream fix\n * applied: `hasClear` / `onClear`, a clear button between the loading spinner\n * and the chevron, as `Selector`'s `hasClear` has (facebook/astryx#6362, open).\n *\n * Everything else is upstream's, line for line. Two things differ in form\n * only: the style namespaces are Astryx's compiled output (src/forks/compiled.ts),\n * and the two internals Astryx does not export are inlined —\n * `useResolvedRequired` (read through the public `FormLayoutContext`) and\n * `interactionOverlayStyles` (compiled, like the rest).\n *\n * Delete this fork, and its exports.exclude.json entry, once Astryx ships\n * the fix (CONTRIBUTING, \"Forks of Astryx components\").\n */\n\nimport React, {\n use,\n useCallback,\n useId,\n useImperativeHandle,\n useOptimistic,\n useRef,\n useTransition,\n type ReactNode,\n} from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport type { StyleXStyles } from \"@stylexjs/stylex\";\nimport type { BaseProps } from \"@astryxdesign/core/BaseProps\";\nimport {\n Field,\n InputClearButton,\n inputWrapperStyles,\n type FieldStatusVariant,\n} from \"@astryxdesign/core/Field\";\nimport { FormLayoutContext } from \"@astryxdesign/core/FormLayout\";\nimport { Icon, renderIconSlot, type IconType } from \"@astryxdesign/core/Icon\";\nimport { Spinner } from \"@astryxdesign/core/Spinner\";\nimport { useTranslator } from \"@astryxdesign/core/i18n\";\nimport {\n layerAnimations,\n type LayerAlignment,\n type LayerPlacement,\n} from \"@astryxdesign/core/Layer\";\nimport { usePopover } from \"@astryxdesign/core/Popover\";\nimport { spacingVars } from \"@astryxdesign/core/theme/tokens.stylex\";\nimport {\n composeEventHandlers,\n focusOutlineStyles,\n isRenderable,\n mergeProps,\n themeProps,\n type SizeValue,\n} from \"@astryxdesign/core/utils\";\n\nimport { compiledStyles } from \"../compiled\";\nimport * as compiled from \"./ComplexSelector.styles\";\n\n// dist/ComplexSelector/ComplexSelector.js `styles`, and\n// dist/utils/interactionOverlay.stylex.js, which core does not export.\nconst styles = compiledStyles(compiled.styles);\nconst interactionOverlayStyles = compiledStyles(compiled.interactionOverlayStyles);\n\n// @astryxdesign/core 0.6.5 src/hooks/useResolvedRequired.ts, which core does\n// not export: a field under `FormLayout defaultOptionality=\"required\"` is\n// required unless it opts out.\nfunction useResolvedRequired({\n isRequired = false,\n isOptional = false,\n}: {\n isRequired?: boolean;\n isOptional?: boolean;\n}): boolean {\n const { defaultOptionality } = use(FormLayoutContext);\n return !isOptional && (isRequired || defaultOptionality === \"required\");\n}\n\nexport type ComplexSelectorVariant = \"input\" | \"ghost\";\n\nexport type ComplexSelectorSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface ComplexSelectorRenderState {\n /** Whether the selector surface is open. */\n isOpen: boolean;\n /** Whether changeAction/isLoading is pending. */\n isBusy: boolean;\n /** ID of the trigger button. */\n triggerId: string;\n /** ID of the popup content container. */\n contentId: string;\n}\n\n/**\n * Imperative control surface for ComplexSelector, accessed via the `handleRef`\n * prop. Methods drive the same popover machinery as the built-in trigger, so\n * they respect focus restoration, light dismiss, and Escape. Prefer these\n * callbacks over mirroring open state in the parent — the selector owns its\n * visibility, and imperative calls avoid the focus-management pitfalls of\n * syncing an external `isOpen` prop. Pair with `onOpenChange` to observe every\n * open and close, including the ones the selector performs itself.\n */\nexport interface ComplexSelectorHandle {\n /** Open the selector surface. No-op when disabled or already open. */\n open(): void;\n /** Close the selector surface. Restores focus to the trigger. */\n close(): void;\n /** Toggle the selector surface open or closed. */\n toggle(): void;\n /** Whether the selector surface is currently open. Reads live state. */\n isOpen(): boolean;\n}\n\nexport interface ComplexSelectorStatus {\n type: \"warning\" | \"error\" | \"success\";\n message?: string;\n}\n\nexport interface ComplexSelectorProps<Value> extends Omit<\n BaseProps<HTMLDivElement>,\n \"children\" | \"onChange\"\n> {\n /** Label text for accessibility and the field label. */\n label: string;\n /** Current controlled value. */\n value: Value;\n /** Called when custom content commits a new value. */\n onChange?: (value: Value) => void;\n /** Optional async action after onChange; drives optimistic UI. */\n changeAction?: (value: Value) => void | Promise<void>;\n /** Custom selector surface content rendered inside a dialog popover. */\n children: (\n value: Value,\n onChange: (value: Value) => void,\n close: () => void,\n state: ComplexSelectorRenderState,\n ) => ReactNode;\n /** Label/content shown in the closed trigger. */\n triggerLabel?: ReactNode;\n /** Placeholder shown when triggerLabel is omitted. */\n placeholder?: ReactNode;\n /** Whether to visually hide the field label. */\n isLabelHidden?: boolean;\n /** Helper text displayed below the label. */\n description?: string;\n /** Marks the field optional. */\n isOptional?: boolean;\n /** Marks the field required. */\n isRequired?: boolean;\n /** Disables the selector. */\n isDisabled?: boolean;\n /** Shows loading state on the trigger. */\n isLoading?: boolean;\n /**\n * Shows a clear button between the loading spinner and the chevron while\n * `triggerLabel` is set, as `Selector`'s `hasClear` does. Activating it\n * calls `onClear`, or `onChange(undefined)` when `onClear` is not given.\n */\n hasClear?: boolean;\n /** Called when the clear button is activated. */\n onClear?: () => void;\n /** Validation status. */\n status?: ComplexSelectorStatus;\n /** Status placement. */\n statusVariant?: FieldStatusVariant;\n /** Tooltip text displayed next to the label. */\n labelTooltip?: string;\n /** Trigger and field size. */\n size?: ComplexSelectorSize;\n /** Visual trigger style. Ghost matches toolbar buttons. */\n variant?: ComplexSelectorVariant;\n /** Icon displayed at the start of the trigger. */\n startIcon?: ReactNode | IconType;\n /** Width of the field. */\n width?: SizeValue;\n /** Popup placement. */\n placement?: LayerPlacement;\n /** Popup alignment along the placement axis. */\n alignment?: LayerAlignment;\n /**\n * Imperative handle for programmatic open/close control. Exposes open,\n * close, toggle, and the isOpen query. Use this instead of mirroring open\n * state in the parent — the selector owns its visibility.\n */\n handleRef?: React.Ref<ComplexSelectorHandle>;\n /**\n * Called whenever the selector surface opens or closes, however it happened\n * — the trigger, the keyboard, a light dismiss, Escape, content that calls\n * `close()`, or the imperative handle. Pair it with `handleRef` to drive the\n * surface from outside without mirroring its state.\n */\n onOpenChange?: (isOpen: boolean) => void;\n /** StyleX styles for the popup content container. */\n contentXstyle?: StyleXStyles;\n /** Test ID for the trigger container. */\n \"data-testid\"?: string;\n}\n\n/**\n * A selector shell for rich, custom selection surfaces.\n *\n * ComplexSelector owns the field, trigger, popover, focus restore, and async\n * change action flow. Consumers provide the dialog content as a render function,\n * using the supplied `value`, `onChange`, and `close` helpers to compose the\n * right accessible structure for the custom selector.\n *\n * @example\n * ```\n * <ComplexSelector\n * label=\"Fruit\"\n * value={value}\n * onChange={setValue}\n * triggerLabel={`${value.fruit} ${value.ripeness}`}>\n * {(value, onChange, close) => (\n * <FruitGrid\n * value={value}\n * onChange={nextValue => {\n * onChange(nextValue);\n * close();\n * }}\n * />\n * )}\n * </ComplexSelector>\n * ```\n */\nexport function ComplexSelector<Value>({\n label,\n value,\n onChange,\n changeAction,\n children,\n triggerLabel,\n placeholder: placeholderFromProps,\n isLabelHidden = false,\n description,\n isOptional = false,\n isRequired = false,\n isDisabled = false,\n isLoading = false,\n hasClear = false,\n onClear,\n status,\n statusVariant = \"attached\",\n labelTooltip,\n size = \"md\",\n variant = \"input\",\n startIcon,\n width,\n placement = \"below\",\n alignment = \"start\",\n handleRef,\n onOpenChange,\n contentXstyle,\n xstyle,\n className,\n style,\n \"data-testid\": testId,\n onClick: onClickProp,\n ...props\n}: ComplexSelectorProps<Value>) {\n const t = useTranslator();\n const isEffectivelyRequired = useResolvedRequired({ isRequired, isOptional });\n const placeholder = placeholderFromProps ?? t(\"@astryx.selector.placeholder\");\n const effectiveStatusVariant =\n variant === \"ghost\" && statusVariant === \"attached\" ? \"detached\" : statusVariant;\n\n const triggerId = useId();\n const labelId = useId();\n const contentId = useId();\n const descriptionId = useId();\n const statusMessageId = useId();\n const ariaDescribedBy =\n [description ? descriptionId : null, status?.message ? statusMessageId : null]\n .filter((id): id is string => id != null)\n .join(\" \") || undefined;\n\n const triggerRef = useRef<HTMLButtonElement>(null);\n\n const [isPending, startTransition] = useTransition();\n const [optimisticValue, setOptimisticValue] = useOptimistic(value);\n const isBusy = isLoading || isPending;\n\n const handlePopoverShow = useCallback(() => {\n onOpenChange?.(true);\n }, [onOpenChange]);\n\n const handlePopoverHide = useCallback(() => {\n // Focus is restored first so a consumer that moves focus elsewhere from\n // the callback wins, instead of being overwritten a line later.\n triggerRef.current?.focus();\n onOpenChange?.(false);\n }, [onOpenChange]);\n\n const popover = usePopover({\n dialogLabel: label,\n hasCloseButton: false,\n hasAutoFocus: true,\n surfaceTarget: \"complex-selector-popup\",\n onShow: handlePopoverShow,\n onHide: handlePopoverHide,\n });\n\n const isOpen = popover.isOpen;\n\n const handleTriggerClick = useCallback(() => {\n if (isDisabled) {\n return;\n }\n if (popover.isOpen) {\n popover.hide();\n } else {\n popover.show();\n }\n }, [isDisabled, popover]);\n\n const close = useCallback(() => {\n popover.hide();\n }, [popover]);\n\n useImperativeHandle(\n handleRef,\n () => ({\n open: () => {\n if (!isDisabled) {\n popover.show();\n }\n },\n close: () => popover.hide(),\n toggle: () => {\n if (isDisabled) {\n return;\n }\n if (popover.isOpen) {\n popover.hide();\n } else {\n popover.show();\n }\n },\n isOpen: () => popover.isOpen,\n }),\n [isDisabled, popover],\n );\n\n const commitValue = useCallback(\n (nextValue: Value) => {\n onChange?.(nextValue);\n if (changeAction) {\n startTransition(async () => {\n setOptimisticValue(nextValue);\n await changeAction(nextValue);\n });\n }\n },\n [changeAction, onChange, setOptimisticValue, startTransition],\n );\n\n const triggerContent = triggerLabel ?? placeholder;\n\n const startIconSlot = renderIconSlot(startIcon, {\n size: \"sm\",\n color: \"secondary\",\n });\n\n const content = (\n <div id={contentId} {...stylex.props(styles.content, contentXstyle)}>\n {children(optimisticValue, commitValue, close, {\n isOpen,\n isBusy,\n triggerId,\n contentId,\n })}\n </div>\n );\n\n const selectorContent = (\n <>\n <div\n ref={popover.triggerRef}\n data-testid={testId}\n {...props}\n onClick={composeEventHandlers(onClickProp, handleTriggerClick)}\n {...mergeProps(\n themeProps(\"complex-selector\", {\n variant,\n size,\n status: status?.type ?? null,\n }),\n stylex.props(\n inputWrapperStyles.base,\n styles.triggerContainer,\n styles[size],\n // The ring belongs to the wrapper (the focusable `<button>` sits\n // inside it), but it must still be a KEYBOARD ring: `:focus-within`\n // matched a mouse click on the trigger and drew the outline for\n // pointer users too. `focusWithin` here is `:has(:focus-visible)`.\n focusOutlineStyles.focusWithin,\n variant === \"ghost\" && styles.triggerGhost,\n variant === \"ghost\" && interactionOverlayStyles.backgroundImage,\n isDisabled && inputWrapperStyles.disabled,\n variant === \"ghost\" && isDisabled && styles.triggerGhostDisabled,\n isDisabled && styles.disabled,\n triggerLabel == null && styles.placeholder,\n xstyle,\n ),\n className,\n style,\n )}\n >\n {isRenderable(startIconSlot) && startIconSlot}\n <button\n ref={triggerRef}\n id={triggerId}\n type=\"button\"\n aria-haspopup=\"dialog\"\n aria-expanded={isOpen}\n aria-controls={contentId}\n aria-describedby={ariaDescribedBy}\n aria-labelledby={labelId}\n aria-required={isEffectivelyRequired ? \"true\" : undefined}\n aria-invalid={status?.type === \"error\" ? \"true\" : undefined}\n aria-busy={isBusy || undefined}\n disabled={isDisabled}\n onKeyDown={(event) => {\n if (event.key === \"ArrowDown\" && !isOpen && !isDisabled) {\n event.preventDefault();\n popover.show();\n }\n }}\n {...stylex.props(styles.trigger)}\n >\n <span {...stylex.props(styles.triggerText)}>{triggerContent}</span>\n </button>\n {isBusy && <Spinner size=\"sm\" />}\n {/* ui-common: the clear button (facebook/astryx#6362). */}\n {hasClear && triggerLabel != null && !isDisabled && (\n <InputClearButton\n label={t(\"@astryx.selector.clearLabel\", { label })}\n // The container's own click opens the popover; a clear must not.\n onClick={(event) => {\n event.stopPropagation();\n if (onClear) {\n onClear();\n } else {\n // Clearing hands back \"no value\", which `Value` may not name.\n onChange?.(undefined as Value);\n }\n }}\n />\n )}\n <Icon\n icon=\"chevronDown\"\n size=\"sm\"\n color=\"secondary\"\n // No wrapper: Icon's own span already provides the 16px box (`sm`)\n // and the secondary icon color the wrapper used to set, so the glyph\n // IS the trigger's icon element — one node carrying the box, the\n // color, the rotation, and the theme target.\n xstyle={[\n styles.triggerIcon,\n styles.triggerIconRotation,\n isOpen && styles.triggerIconOpen,\n ]}\n {...themeProps(\"complex-selector-indicator-icon\", {\n state: isOpen ? \"expanded\" : \"collapsed\",\n })}\n />\n </div>\n\n {popover.render(content, {\n placement,\n alignment,\n offset: spacingVars[\"--spacing-1\"],\n xstyle: [styles.popover, layerAnimations[placement]],\n })}\n </>\n );\n\n return (\n <Field\n label={label}\n isLabelHidden={isLabelHidden}\n description={description}\n inputID={triggerId}\n descriptionID={description ? descriptionId : undefined}\n labelID={labelId}\n isOptional={isOptional}\n isRequired={isRequired}\n isDisabled={isDisabled}\n status={\n status\n ? {\n type: status.type,\n message: status.message,\n messageID: status.message ? statusMessageId : undefined,\n }\n : undefined\n }\n statusVariant={effectiveStatusVariant}\n labelTooltip={labelTooltip}\n width={width}\n >\n {selectorContent}\n </Field>\n );\n}\n\nComplexSelector.displayName = \"ComplexSelector\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ComplexSelector.js","names":[],"sources":["../../../src/forks/ComplexSelector/ComplexSelector.tsx"],"sourcesContent":["// Copyright (c) Meta Platforms, Inc. and affiliates.\n// Modifications copyright (c) Lablup Inc.\n//\n// Forked from @astryxdesign/core 0.7.0, src/ComplexSelector/ComplexSelector.tsx\n// (MIT; see NOTICE). Provenance and the drift guard: src/forks/provenance.json.\n\n\"use client\";\n\n/**\n * ComplexSelector, ui-common's copy of Astryx's, with one upstream fix\n * applied: `hasClear` / `onClear`, a clear button between the loading spinner\n * and the chevron, as `Selector`'s `hasClear` has (facebook/astryx#6362, open).\n *\n * Everything else is upstream's, line for line. Two things differ in form\n * only: the style namespaces are Astryx's compiled output (src/forks/compiled.ts),\n * and the two internals Astryx does not export are inlined —\n * `useResolvedRequired` (read through the public `FormLayoutContext`) and\n * `interactionOverlayStyles` (compiled, like the rest).\n *\n * Delete this fork, and its exports.exclude.json entry, once Astryx ships\n * the fix (CONTRIBUTING, \"Forks of Astryx components\").\n */\n\nimport React, {\n use,\n useCallback,\n useId,\n useImperativeHandle,\n useOptimistic,\n useRef,\n useTransition,\n type ReactNode,\n} from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport type { StyleXStyles } from \"@stylexjs/stylex\";\nimport {\n Field,\n InputClearButton,\n inputWrapperStyles,\n type FieldInputBaseProps,\n type FieldPartStyleProps,\n type FieldContainerStyleProps,\n type FieldStatusVariant,\n} from \"@astryxdesign/core/Field\";\nimport { FormLayoutContext } from \"@astryxdesign/core/FormLayout\";\nimport {\n useDevWarning,\n useMergedRefs,\n usePressFeedback,\n} from \"@astryxdesign/core/hooks\";\nimport { Icon, renderIconSlot, type IconType } from \"@astryxdesign/core/Icon\";\nimport { useInputGroup } from \"@astryxdesign/core/InputGroup\";\nimport { Spinner } from \"@astryxdesign/core/Spinner\";\nimport { useTranslator } from \"@astryxdesign/core/i18n\";\nimport {\n layerAnimations,\n type LayerAlignment,\n type LayerPlacement,\n} from \"@astryxdesign/core/Layer\";\nimport { usePopover } from \"@astryxdesign/core/Popover\";\nimport { spacingVars } from \"@astryxdesign/core/theme/tokens.stylex\";\nimport {\n composeEventHandlers,\n focusOutlineStyles,\n isRenderable,\n mergeProps,\n mergeRefs,\n themeProps,\n type SizeValue,\n} from \"@astryxdesign/core/utils\";\n\nimport { compiledStyles } from \"../compiled\";\nimport * as compiled from \"./ComplexSelector.styles\";\n\n// dist/ComplexSelector/ComplexSelector.js `styles`, and\n// dist/utils/interactionOverlay.stylex.js.\nconst styles = compiledStyles(compiled.styles);\nconst interactionOverlayStyles = compiledStyles(compiled.interactionOverlayStyles);\n\n// @astryxdesign/core 0.7.0 src/hooks/useResolvedRequired.ts, which core does\n// not export: a field under `FormLayout defaultOptionality=\"required\"` is\n// required unless it opts out.\nfunction useResolvedRequired({\n isRequired = false,\n isOptional = false,\n}: {\n isRequired?: boolean;\n isOptional?: boolean;\n}): boolean {\n const { defaultOptionality } = use(FormLayoutContext);\n return !isOptional && (isRequired || defaultOptionality === \"required\");\n}\n\nconst ROOT_STYLING_MESSAGE =\n \"`xstyle`, `className`, and `style` are not supported on Field-composed inputs and were ignored. \" +\n \"Style the field with `containerXstyle`/`containerClassName`/`containerStyle` and the control box with \" +\n \"`controlXstyle`/`controlClassName`/`controlStyle`. `astryx upgrade` rewrites existing usage.\";\n\nconst CONTAINER_WITHOUT_FIELD_MESSAGE =\n \"`containerXstyle`/`containerClassName`/`containerStyle` have no effect here because no Field box renders \" +\n \"(inside InputGroup or a `horizontal-labels` FormLayout). Inside InputGroup, style the InputGroup.\";\n\n// @astryxdesign/core 0.7.0 src/Field/fieldParts.ts `useFieldInputStyling`,\n// which core does not export.\nfunction useFieldInputStyling<R extends object>(\n component: string,\n restProps: R,\n containerProps: FieldContainerStyleProps,\n) {\n const inputGroup = useInputGroup();\n const { direction } = use(FormLayoutContext);\n const hasFieldBox = inputGroup == null && direction !== \"horizontal-labels\";\n\n const untyped = restProps as R & {\n xstyle?: unknown;\n className?: unknown;\n style?: unknown;\n };\n const hasRootStyling =\n untyped.xstyle != null || untyped.className != null || untyped.style != null;\n let rest = restProps;\n if (hasRootStyling) {\n const {\n xstyle: _xstyle,\n className: _className,\n style: _style,\n ...remaining\n } = untyped;\n rest = remaining as R;\n }\n useDevWarning(component, ROOT_STYLING_MESSAGE, hasRootStyling);\n\n const { containerXstyle, containerClassName, containerStyle } = containerProps;\n const hasContainerStyling =\n containerXstyle != null || containerClassName != null || containerStyle != null;\n useDevWarning(\n component,\n CONTAINER_WITHOUT_FIELD_MESSAGE,\n hasContainerStyling && !hasFieldBox,\n );\n\n return {\n rest,\n hasFieldBox,\n container: hasFieldBox\n ? {\n xstyle: containerXstyle,\n className: containerClassName,\n style: containerStyle,\n }\n : {},\n };\n}\n\nexport type ComplexSelectorVariant = \"input\" | \"ghost\";\n\nexport type ComplexSelectorSize = \"sm\" | \"md\" | \"lg\";\n\nconst RENDER_TRIGGER_PARTS_MESSAGE =\n \"`container*` and `control*` styling props have no effect with `renderTrigger`: \" +\n \"no Field box or control box renders. Style the control you render instead.\";\n\n/**\n * Props the `renderTrigger` render prop hands to the control the caller\n * renders.\n * Spread them onto that control: it becomes the popup's anchor, the element\n * focus returns to, and the control that announces the popup's state.\n */\nexport interface ComplexSelectorRenderTriggerProps extends Omit<\n FieldInputBaseProps,\n | keyof FieldPartStyleProps\n | \"id\"\n | \"onClick\"\n | \"onKeyDown\"\n | \"aria-haspopup\"\n | \"aria-expanded\"\n | \"aria-controls\"\n | \"aria-busy\"\n> {\n /**\n * Attaches the control as the popup's anchor and focus-return target, and\n * forwards it to the selector's own `ref`.\n */\n ref: (element: HTMLElement | null) => void;\n /** The id the field would have given its own button. */\n id: string;\n onClick: (event: React.MouseEvent<HTMLElement>) => void;\n /** ArrowDown opens the popup, as it does on the built-in button. */\n onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;\n \"aria-haspopup\": \"dialog\";\n \"aria-expanded\": boolean;\n \"aria-controls\": string;\n \"aria-busy\": boolean | undefined;\n}\n\nexport interface ComplexSelectorRenderState {\n /** Whether the selector surface is open. */\n isOpen: boolean;\n /** Whether changeAction/isLoading is pending. */\n isBusy: boolean;\n /** ID of the trigger button. */\n triggerId: string;\n /** ID of the popup content container. */\n contentId: string;\n}\n\n/**\n * Imperative control surface for ComplexSelector, accessed via the `handleRef`\n * prop. Methods drive the same popover machinery as the built-in trigger, so\n * they respect focus restoration, light dismiss, and Escape. Prefer these\n * callbacks over mirroring open state in the parent — the selector owns its\n * visibility, and imperative calls avoid the focus-management pitfalls of\n * syncing an external `isOpen` prop. Pair with `onOpenChange` to observe every\n * open and close, including the ones the selector performs itself.\n */\nexport interface ComplexSelectorHandle {\n /** Open the selector surface. No-op when disabled or already open. */\n open(): void;\n /** Close the selector surface. Restores focus to the trigger. */\n close(): void;\n /** Toggle the selector surface open or closed. */\n toggle(): void;\n /** Whether the selector surface is currently open. Reads live state. */\n isOpen(): boolean;\n}\n\nexport interface ComplexSelectorStatus {\n type: \"warning\" | \"error\" | \"success\";\n message?: string;\n}\n\nexport interface ComplexSelectorProps<Value> extends Omit<\n FieldInputBaseProps,\n \"children\" | \"onChange\"\n> {\n /** Label text for accessibility and the field label. */\n label: string;\n /**\n * Ref to the trigger: the button that opens the popup and carries the\n * accessible name — or, with `renderTrigger`, the caller's control.\n */\n ref?: React.Ref<HTMLElement>;\n /**\n * Click handler on the trigger. It runs before the popup opens; call\n * `preventDefault()` to keep the popup closed. A press elsewhere in the\n * control box goes through the trigger, so it reaches this handler too.\n * With `renderTrigger` it lands on the caller's control and runs after the\n * built-in toggle, which it cannot cancel.\n */\n onClick?: React.MouseEventHandler<HTMLElement>;\n /**\n * Key handler on the trigger. It runs after the built-in handling and\n * receives every key, including the ArrowDown that opens the popup. With\n * `renderTrigger` it lands on the caller's control, runs after the built-in\n * handling, and is skipped for keys that handling cancels.\n */\n onKeyDown?: React.KeyboardEventHandler<HTMLElement>;\n /** Current controlled value. */\n value: Value;\n /** Called when custom content commits a new value. */\n onChange?: (value: Value) => void;\n /** Optional async action after onChange; drives optimistic UI. */\n changeAction?: (value: Value) => void | Promise<void>;\n /** Custom selector surface content rendered inside a dialog popover. */\n children: (\n value: Value,\n onChange: (value: Value) => void,\n close: () => void,\n state: ComplexSelectorRenderState,\n ) => ReactNode;\n /** Label/content shown in the closed trigger. */\n triggerLabel?: ReactNode;\n /** Placeholder shown when triggerLabel is omitted. */\n placeholder?: ReactNode;\n /** Whether to visually hide the field label. */\n isLabelHidden?: boolean;\n /** Helper text displayed below the label. */\n description?: string;\n /** Marks the field optional. */\n isOptional?: boolean;\n /** Marks the field required. */\n isRequired?: boolean;\n /** Disables the selector. */\n isDisabled?: boolean;\n /** Shows loading state on the trigger. */\n isLoading?: boolean;\n /**\n * Shows a clear button between the loading spinner and the chevron while\n * `triggerLabel` is set, as `Selector`'s `hasClear` does. Activating it\n * calls `onClear`, or `onChange(undefined)` when `onClear` is not given.\n */\n hasClear?: boolean;\n /** Called when the clear button is activated. */\n onClear?: () => void;\n /** Validation status. */\n status?: ComplexSelectorStatus;\n /**\n * How the status message is placed relative to the input.\n * - 'attached': message overlaps directly below the input (bordered treatment; detached when variant is ghost)\n * - 'detached': message floats below as a separate element with spacing\n * - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap\n * @default 'attached'\n */\n statusVariant?: FieldStatusVariant;\n /** Tooltip text displayed next to the label. */\n labelTooltip?: string;\n /** Trigger and field size. */\n size?: ComplexSelectorSize;\n /** Visual trigger style. Ghost matches toolbar buttons. */\n variant?: ComplexSelectorVariant;\n /** Icon displayed at the start of the trigger. */\n startIcon?: ReactNode | IconType;\n /** Width of the field. */\n width?: SizeValue;\n /** Popup placement. */\n placement?: LayerPlacement;\n /** Popup alignment along the placement axis. */\n alignment?: LayerAlignment;\n /**\n * Imperative handle for programmatic open/close control. Exposes open,\n * close, toggle, and the isOpen query. Use this instead of mirroring open\n * state in the parent — the selector owns its visibility.\n */\n handleRef?: React.Ref<ComplexSelectorHandle>;\n /**\n * Render the control the popup hangs off — a glyph in a list row, a chip,\n * an icon button — instead of the selector's own field and button. Spread\n * the given props onto it; the popup is then anchored to that control and\n * still labelled by `label`. The field chrome (`Field`, status, spinner,\n * chevron) is not rendered; the caller owns the opener. Pair with\n * `handleRef` to open the popup from a keystroke elsewhere.\n *\n * Hover and pressed paint stay yours. The open state reaches your control\n * as `aria-expanded` on the given props, so style it from the rendered\n * attribute. A pressed look keyed to `:active` is not a substitute:\n * `:active` does not behave the same under a coarse pointer, which is why\n * menu rows drop coarse-pointer `:active` paint entirely.\n *\n * @example\n * ```\n * <ComplexSelector\n * label=\"Assignee\"\n * renderTrigger={props => <IconButton icon=\"user\" label=\"Assign\" {...props} />}\n * />\n * ```\n *\n * @example\n * ```\n * // Styling the open state from the rendered attribute:\n * // .my-trigger[aria-expanded='true'] { background: var(--color-overlay-pressed); }\n * ```\n */\n renderTrigger?: (props: ComplexSelectorRenderTriggerProps) => ReactNode;\n /**\n * Called whenever the selector surface opens or closes, however it happened\n * — the trigger, the keyboard, a light dismiss, Escape, content that calls\n * `close()`, or the imperative handle. Pair it with `handleRef` to drive the\n * surface from outside without mirroring its state.\n */\n onOpenChange?: (isOpen: boolean) => void;\n /** StyleX styles for the popup content container. */\n contentXstyle?: StyleXStyles;\n /** Test ID for the trigger button. */\n \"data-testid\"?: string;\n}\n\n/**\n * A selector shell for rich, custom selection surfaces.\n *\n * ComplexSelector owns the field, trigger, popover, focus restore, and async\n * change action flow. Consumers provide the dialog content as a render function,\n * using the supplied `value`, `onChange`, and `close` helpers to compose the\n * right accessible structure for the custom selector.\n *\n * @example\n * ```\n * <ComplexSelector\n * label=\"Fruit\"\n * value={value}\n * onChange={setValue}\n * triggerLabel={`${value.fruit} ${value.ripeness}`}>\n * {(value, onChange, close) => (\n * <FruitGrid\n * value={value}\n * onChange={nextValue => {\n * onChange(nextValue);\n * close();\n * }}\n * />\n * )}\n * </ComplexSelector>\n * ```\n */\nexport function ComplexSelector<Value>({\n label,\n value,\n onChange,\n changeAction,\n children,\n triggerLabel,\n placeholder: placeholderFromProps,\n isLabelHidden = false,\n description,\n isOptional = false,\n isRequired = false,\n isDisabled = false,\n isLoading = false,\n hasClear = false,\n onClear,\n status,\n statusVariant = \"attached\",\n labelTooltip,\n size = \"md\",\n variant = \"input\",\n startIcon,\n width,\n placement = \"below\",\n alignment = \"start\",\n handleRef,\n renderTrigger,\n onOpenChange,\n contentXstyle,\n containerXstyle,\n containerClassName,\n containerStyle,\n controlXstyle,\n controlClassName,\n controlStyle,\n ref,\n id,\n onClick: onClickProp,\n onKeyDown: onKeyDownProp,\n \"aria-describedby\": ariaDescribedByProp,\n ...restProps\n}: ComplexSelectorProps<Value>) {\n const { rest, container } = useFieldInputStyling(\"ComplexSelector\", restProps, {\n containerXstyle,\n containerClassName,\n containerStyle,\n });\n useDevWarning(\n \"ComplexSelector\",\n RENDER_TRIGGER_PARTS_MESSAGE,\n renderTrigger != null &&\n (containerXstyle != null ||\n containerClassName != null ||\n containerStyle != null ||\n controlXstyle != null ||\n controlClassName != null ||\n controlStyle != null),\n );\n const t = useTranslator();\n const pressable = usePressFeedback();\n const isEffectivelyRequired = useResolvedRequired({ isRequired, isOptional });\n const placeholder = placeholderFromProps ?? t(\"@astryx.selector.placeholder\");\n const effectiveStatusVariant =\n variant === \"ghost\" && statusVariant === \"attached\" ? \"detached\" : statusVariant;\n\n const generatedTriggerId = useId();\n // A caller's `id` lands on the trigger, so the label wiring must use it.\n const triggerId = id ?? generatedTriggerId;\n const labelId = useId();\n const contentId = useId();\n const descriptionId = useId();\n const statusMessageId = useId();\n const ariaDescribedBy =\n [description ? descriptionId : null, status?.message ? statusMessageId : null]\n .filter((id): id is string => id != null)\n .join(\" \") || undefined;\n\n const triggerRef = useRef<HTMLElement>(null);\n const triggerButtonRef = useMergedRefs<HTMLElement>(triggerRef, ref);\n\n const [isPending, startTransition] = useTransition();\n const [optimisticValue, setOptimisticValue] = useOptimistic(value);\n const isBusy = isLoading || isPending;\n\n const handlePopoverShow = useCallback(() => {\n onOpenChange?.(true);\n }, [onOpenChange]);\n\n const handlePopoverHide = useCallback(() => {\n // Focus is restored first so a consumer that moves focus elsewhere from\n // the callback wins, instead of being overwritten a line later.\n triggerRef.current?.focus();\n onOpenChange?.(false);\n }, [onOpenChange]);\n\n const popover = usePopover({\n dialogLabel: label,\n hasCloseButton: false,\n hasAutoFocus: true,\n surfaceTarget: \"complex-selector-popup\",\n onShow: handlePopoverShow,\n onHide: handlePopoverHide,\n });\n\n const isOpen = popover.isOpen;\n\n const handleTriggerClick = useCallback(() => {\n if (isDisabled) {\n return;\n }\n if (popover.isOpen) {\n popover.hide();\n } else {\n popover.show();\n }\n }, [isDisabled, popover]);\n\n const close = useCallback(() => {\n popover.hide();\n }, [popover]);\n\n const handleTriggerKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (event.key === \"ArrowDown\" && !isOpen && !isDisabled) {\n event.preventDefault();\n popover.show();\n }\n },\n [isDisabled, isOpen, popover],\n );\n\n // A caller `onClick` runs first and can cancel opening with\n // `preventDefault()`, as it always could. It now lands on the trigger, so\n // the trigger composes it; a press elsewhere in the control box (padding,\n // start icon, chevron) opens the popup directly.\n const handleTriggerButtonClick = composeEventHandlers(\n onClickProp,\n handleTriggerClick,\n );\n // A caller `onKeyDown` sees every key, including the ArrowDown the trigger\n // has already used to open the popup.\n const handleTriggerButtonKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {\n handleTriggerKeyDown(event);\n onKeyDownProp?.(event);\n };\n const handleControlBoxClick = useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n if (triggerRef.current?.contains(event.target as Node)) {\n return;\n }\n // Route the press through the trigger so it behaves exactly like a\n // press on the trigger itself, caller `onClick` included.\n triggerRef.current?.click();\n },\n [],\n );\n\n useImperativeHandle(\n handleRef,\n () => ({\n open: () => {\n if (!isDisabled) {\n popover.show();\n }\n },\n close: () => popover.hide(),\n toggle: () => {\n if (isDisabled) {\n return;\n }\n if (popover.isOpen) {\n popover.hide();\n } else {\n popover.show();\n }\n },\n isOpen: () => popover.isOpen,\n }),\n [isDisabled, popover],\n );\n\n const commitValue = useCallback(\n (nextValue: Value) => {\n onChange?.(nextValue);\n if (changeAction) {\n startTransition(async () => {\n setOptimisticValue(nextValue);\n await changeAction(nextValue);\n });\n }\n },\n [changeAction, onChange, setOptimisticValue, startTransition],\n );\n\n const triggerContent = triggerLabel ?? placeholder;\n\n const startIconSlot = renderIconSlot(startIcon, {\n size: \"sm\",\n color: \"secondary\",\n });\n\n const content = (\n <div id={contentId} {...stylex.props(styles.content, contentXstyle)}>\n {children(optimisticValue, commitValue, close, {\n isOpen,\n isBusy,\n triggerId,\n contentId,\n })}\n </div>\n );\n\n const popup = popover.render(content, {\n placement,\n alignment,\n offset: spacingVars[\"--spacing-1\"],\n xstyle: [styles.popover, layerAnimations[placement]],\n });\n\n if (renderTrigger != null) {\n // Anchor-only mode: the caller renders the opener and spreads these props\n // on it. No Field, no status, no chevron — the caller owns the control;\n // the selector owns the popup, its anchor, and focus return.\n const triggerProps: ComplexSelectorRenderTriggerProps = {\n // Base props reach the caller's control, the primary semantic control\n // in this mode; the selector's own wiring below wins over them.\n ...rest,\n \"aria-describedby\": ariaDescribedByProp,\n ref: mergeRefs<HTMLElement>((el) => {\n popover.triggerRef(el);\n triggerRef.current = el;\n }, ref),\n id: triggerId,\n // This path never forwarded caller handlers before, so the newly\n // forwarded ones compose component first and cannot cancel opening.\n onClick: composeEventHandlers(handleTriggerClick, onClickProp),\n onKeyDown: composeEventHandlers(handleTriggerKeyDown, onKeyDownProp),\n \"aria-haspopup\": \"dialog\",\n \"aria-expanded\": isOpen,\n \"aria-controls\": contentId,\n \"aria-busy\": isBusy || undefined,\n };\n return (\n <>\n {renderTrigger(triggerProps)}\n {popup}\n </>\n );\n }\n\n const selectorContent = (\n <>\n <div\n ref={popover.triggerRef}\n {...pressable}\n onClick={handleControlBoxClick}\n {...mergeProps(\n themeProps(\"complex-selector\", {\n variant,\n size,\n status: status?.type ?? null,\n }),\n stylex.props(\n inputWrapperStyles.base,\n styles.triggerContainer,\n styles[size],\n // The ring belongs to the wrapper (the focusable `<button>` sits\n // inside it), but it must still be a KEYBOARD ring: `:focus-within`\n // matched a mouse click on the trigger and drew the outline for\n // pointer users too. `focusWithin` here is `:has(:focus-visible)`.\n focusOutlineStyles.focusWithin,\n variant === \"ghost\" && styles.triggerGhost,\n variant === \"ghost\" && interactionOverlayStyles.backgroundImage,\n isDisabled && inputWrapperStyles.disabled,\n variant === \"ghost\" && isDisabled && styles.triggerGhostDisabled,\n isDisabled && styles.disabled,\n triggerLabel == null && styles.placeholder,\n controlXstyle,\n ),\n controlClassName,\n controlStyle,\n )}\n >\n {isRenderable(startIconSlot) && startIconSlot}\n <button\n {...rest}\n ref={triggerButtonRef}\n id={triggerId}\n type=\"button\"\n aria-haspopup=\"dialog\"\n aria-expanded={isOpen}\n aria-controls={contentId}\n // A caller's description is kept alongside the field's own.\n aria-describedby={\n [ariaDescribedByProp, ariaDescribedBy].filter(Boolean).join(\" \") ||\n undefined\n }\n aria-labelledby={labelId}\n aria-required={isEffectivelyRequired ? \"true\" : undefined}\n aria-invalid={status?.type === \"error\" ? \"true\" : undefined}\n aria-busy={isBusy || undefined}\n disabled={isDisabled}\n onClick={handleTriggerButtonClick}\n onKeyDown={handleTriggerButtonKeyDown}\n {...stylex.props(styles.trigger)}\n >\n <span {...stylex.props(styles.triggerText)}>{triggerContent}</span>\n </button>\n {isBusy && <Spinner size=\"sm\" />}\n {/* ui-common: the clear button (facebook/astryx#6362). */}\n {hasClear && triggerLabel != null && !isDisabled && (\n <InputClearButton\n label={t(\"@astryx.selector.clearLabel\", { label })}\n // The container's own click opens the popover; a clear must not.\n onClick={(event) => {\n event.stopPropagation();\n if (onClear) {\n onClear();\n } else {\n // Clearing hands back \"no value\", which `Value` may not name.\n onChange?.(undefined as Value);\n }\n }}\n />\n )}\n <Icon\n icon=\"chevronDown\"\n size=\"sm\"\n color=\"secondary\"\n // No wrapper: Icon's own span already provides the 16px box (`sm`)\n // and the secondary icon color the wrapper used to set, so the glyph\n // IS the trigger's icon element — one node carrying the box, the\n // color, the rotation, and the theme target.\n xstyle={[\n styles.triggerIcon,\n styles.triggerIconRotation,\n isOpen && styles.triggerIconOpen,\n ]}\n {...themeProps(\"complex-selector-indicator-icon\", {\n state: isOpen ? \"expanded\" : \"collapsed\",\n })}\n />\n </div>\n\n {popup}\n </>\n );\n\n return (\n <Field\n label={label}\n isLabelHidden={isLabelHidden}\n description={description}\n inputID={triggerId}\n descriptionID={description ? descriptionId : undefined}\n labelID={labelId}\n isOptional={isOptional}\n isRequired={isRequired}\n isDisabled={isDisabled}\n status={\n status\n ? {\n type: status.type,\n message: status.message,\n messageID: status.message ? statusMessageId : undefined,\n }\n : undefined\n }\n statusVariant={effectiveStatusVariant}\n labelTooltip={labelTooltip}\n width={width}\n {...container}\n >\n {selectorContent}\n </Field>\n );\n}\n\nComplexSelector.displayName = \"ComplexSelector\";\n"],"mappings":"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"}
|