@planningcenter/tapestry 3.0.1 → 3.0.2-qa-693.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/dist/components/select/Select.d.ts +66 -0
- package/dist/components/select/Select.d.ts.map +1 -0
- package/dist/components/select/Select.js +58 -0
- package/dist/components/select/Select.js.map +1 -0
- package/dist/components/select/SelectNative.d.ts +3 -1
- package/dist/components/select/SelectNative.d.ts.map +1 -1
- package/dist/components/select/SelectNative.js +34 -0
- package/dist/components/select/SelectNative.js.map +1 -0
- package/dist/components/select/SelectOptions.d.ts +1 -1
- package/dist/components/select/SelectOptions.d.ts.map +1 -1
- package/dist/components/select/SelectOptions.js +35 -0
- package/dist/components/select/SelectOptions.js.map +1 -0
- package/dist/components/select/SelectPopover.d.ts +11 -55
- package/dist/components/select/SelectPopover.d.ts.map +1 -1
- package/dist/components/select/SelectPopover.js +237 -0
- package/dist/components/select/SelectPopover.js.map +1 -0
- package/dist/components/select/index.d.ts +2 -1
- package/dist/components/select/index.d.ts.map +1 -1
- package/dist/index.css +3 -2
- package/dist/index.css.map +1 -1
- package/dist/reactRender.css +1318 -808
- package/dist/reactRender.css.map +1 -1
- package/dist/reactRenderLegacy.css +1318 -808
- package/dist/reactRenderLegacy.css.map +1 -1
- package/dist/unstable.css +512 -2
- package/dist/unstable.css.map +1 -1
- package/dist/unstable.js +1 -0
- package/dist/unstable.js.map +1 -1
- package/dist/utilities/keyboardUtils.d.ts +27 -0
- package/dist/utilities/keyboardUtils.d.ts.map +1 -0
- package/dist/utilities/keyboardUtils.js +101 -0
- package/dist/utilities/keyboardUtils.js.map +1 -0
- package/dist/utilities/selectUtils.d.ts +3 -1
- package/dist/utilities/selectUtils.d.ts.map +1 -1
- package/dist/utilities/selectUtils.js +103 -0
- package/dist/utilities/selectUtils.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import "./index.css";
|
|
2
|
+
import React, { type SelectHTMLAttributes } from "react";
|
|
3
|
+
export type SelectSize = "lg" | "md";
|
|
4
|
+
export interface SelectOptionWithTextLabel {
|
|
5
|
+
/** Whether this option is non-interactive. */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
divider?: never;
|
|
8
|
+
/** Displayed as the option content and used for type-ahead matching. */
|
|
9
|
+
label: string;
|
|
10
|
+
textValue?: never;
|
|
11
|
+
value: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SelectOptionWithNodeLabel {
|
|
14
|
+
/** Whether this option is non-interactive. */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
divider?: never;
|
|
17
|
+
/** Displayed as the option content. */
|
|
18
|
+
label: React.ReactNode;
|
|
19
|
+
/** Plain-text representation required for type-ahead matching. */
|
|
20
|
+
textValue: string;
|
|
21
|
+
value: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SelectOptionDivider {
|
|
24
|
+
disabled?: never;
|
|
25
|
+
divider: true;
|
|
26
|
+
label?: never;
|
|
27
|
+
textValue?: never;
|
|
28
|
+
value?: never;
|
|
29
|
+
}
|
|
30
|
+
export type SelectOption = SelectOptionDivider | SelectOptionWithNodeLabel | SelectOptionWithTextLabel;
|
|
31
|
+
export interface SelectOptionsGroup {
|
|
32
|
+
/** Whether all options in this group are non-interactive. */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
/** Visible heading displayed above the group's options. */
|
|
35
|
+
label: string;
|
|
36
|
+
/** The selectable options within this group. */
|
|
37
|
+
options: SelectOption[];
|
|
38
|
+
}
|
|
39
|
+
export type SelectItem = SelectOption | SelectOptionsGroup;
|
|
40
|
+
export interface SelectProps {
|
|
41
|
+
/** Render as popover listbox (SelectPopover) instead of native <select>. */
|
|
42
|
+
complex?: boolean;
|
|
43
|
+
/** Helper text below the select. Red when invalid. */
|
|
44
|
+
description?: string;
|
|
45
|
+
/** Triggers invalid state (red border, red description). */
|
|
46
|
+
invalid?: boolean;
|
|
47
|
+
/** Label text displayed above the select. */
|
|
48
|
+
label: string;
|
|
49
|
+
/** A flat or mixed array of options and option groups. */
|
|
50
|
+
options: SelectItem[];
|
|
51
|
+
/** Placeholder text for the empty state. */
|
|
52
|
+
placeholder: string;
|
|
53
|
+
/** Visual size of the select. */
|
|
54
|
+
size?: SelectSize;
|
|
55
|
+
}
|
|
56
|
+
type BaseSelectElementProps = Omit<SelectHTMLAttributes<HTMLSelectElement>, keyof SelectProps | "multiple" | "size">;
|
|
57
|
+
export type NativeSelectElementProps = BaseSelectElementProps & SelectProps & {
|
|
58
|
+
complex?: false;
|
|
59
|
+
};
|
|
60
|
+
export type ComplexSelectElementProps = BaseSelectElementProps & SelectProps & {
|
|
61
|
+
complex: true;
|
|
62
|
+
};
|
|
63
|
+
export type SelectElementProps = NativeSelectElementProps | ComplexSelectElementProps;
|
|
64
|
+
export declare const Select: React.ForwardRefExoticComponent<SelectElementProps & React.RefAttributes<HTMLSelectElement>>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=Select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/components/select/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAKpB,OAAO,KAAK,EAAE,EAAc,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAA;AASpE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,CAAA;AAEpC,MAAM,WAAW,yBAAyB;IACxC,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,KAAK,CAAA;IACf,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,KAAK,CAAA;IACf,uCAAuC;IACvC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB,OAAO,EAAE,IAAI,CAAA;IACb,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,MAAM,MAAM,YAAY,GACpB,mBAAmB,GACnB,yBAAyB,GACzB,yBAAyB,CAAA;AAE7B,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,kBAAkB,CAAA;AAM1D,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAA;IACnB,iCAAiC;IACjC,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAED,KAAK,sBAAsB,GAAG,IAAI,CAChC,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,WAAW,GAAG,UAAU,GAAG,MAAM,CACxC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG,sBAAsB,GAC3D,WAAW,GAAG;IACZ,OAAO,CAAC,EAAE,KAAK,CAAA;CAChB,CAAA;AAEH,MAAM,MAAM,yBAAyB,GAAG,sBAAsB,GAC5D,WAAW,GAAG;IACZ,OAAO,EAAE,IAAI,CAAA;CACd,CAAA;AAEH,MAAM,MAAM,kBAAkB,GAC1B,wBAAwB,GACxB,yBAAyB,CAAA;AAkB7B,eAAO,MAAM,MAAM,8FA0FlB,CAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Icon from '../../utilities/Icon.js';
|
|
2
|
+
import { useId } from '../../utilities/useId.js';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import React__default, { forwardRef } from 'react';
|
|
5
|
+
import { SelectNative } from './SelectNative.js';
|
|
6
|
+
import { SelectPopover } from './SelectPopover.js';
|
|
7
|
+
|
|
8
|
+
function normalizeSelectValue(value) {
|
|
9
|
+
if (value === undefined || value === null)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (Array.isArray(value))
|
|
12
|
+
return value[0];
|
|
13
|
+
return String(value);
|
|
14
|
+
}
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Select (public)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const Select = forwardRef(function Select(props, ref) {
|
|
19
|
+
const { "aria-label": userAriaLabel, "aria-labelledby": userAriaLabelledBy, className, complex, defaultValue, description, id, invalid, label, onChange, options, placeholder, required, size, value, ...restProps } = props;
|
|
20
|
+
const stableId = useId();
|
|
21
|
+
const controlId = id || `tds-select-${stableId}`;
|
|
22
|
+
const labelId = `${controlId}-label`;
|
|
23
|
+
const descriptionId = description ? `${controlId}-description` : undefined;
|
|
24
|
+
const computedClassName = classNames("tds-select", {
|
|
25
|
+
"tds-select--invalid": invalid,
|
|
26
|
+
"tds-select--lg": size === "lg",
|
|
27
|
+
"tds-select--required": required,
|
|
28
|
+
}, className);
|
|
29
|
+
const normalizedDefaultValue = normalizeSelectValue(defaultValue);
|
|
30
|
+
const normalizedValue = normalizeSelectValue(value);
|
|
31
|
+
const computedDefaultValue = normalizedDefaultValue === undefined && normalizedValue === undefined
|
|
32
|
+
? ""
|
|
33
|
+
: normalizedDefaultValue;
|
|
34
|
+
const computedAriaLabelledBy = userAriaLabelledBy ?? (userAriaLabel ? undefined : labelId);
|
|
35
|
+
const sharedControlProps = {
|
|
36
|
+
...restProps,
|
|
37
|
+
"aria-describedby": descriptionId,
|
|
38
|
+
"aria-label": userAriaLabel,
|
|
39
|
+
"aria-labelledby": computedAriaLabelledBy,
|
|
40
|
+
defaultValue: computedDefaultValue,
|
|
41
|
+
invalid,
|
|
42
|
+
options,
|
|
43
|
+
placeholder,
|
|
44
|
+
required,
|
|
45
|
+
value: normalizedValue,
|
|
46
|
+
};
|
|
47
|
+
const SelectComponent = complex ? SelectPopover : SelectNative;
|
|
48
|
+
return (React__default.createElement("div", { className: computedClassName },
|
|
49
|
+
complex ? (React__default.createElement("div", { className: "tds-select-label", id: labelId }, label)) : (React__default.createElement("label", { htmlFor: controlId, id: labelId }, label)),
|
|
50
|
+
React__default.createElement(SelectComponent, { ...sharedControlProps, ref: ref, id: controlId, onChange: onChange }),
|
|
51
|
+
description && descriptionId && (React__default.createElement("p", { className: "tds-select-description", id: descriptionId },
|
|
52
|
+
React__default.createElement(Icon, { "aria-hidden": true, className: "tds-select-description-invalid-icon", symbol: "general#exclamation-triangle" }),
|
|
53
|
+
description))));
|
|
54
|
+
});
|
|
55
|
+
Select.displayName = "Select";
|
|
56
|
+
|
|
57
|
+
export { Select };
|
|
58
|
+
//# sourceMappingURL=Select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.js","sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport Icon from \"@utilities/Icon\"\nimport { useId } from \"@utilities/useId\"\nimport classNames from \"classnames\"\nimport React, { forwardRef, type SelectHTMLAttributes } from \"react\"\n\nimport { SelectNative } from \"./SelectNative\"\nimport { SelectPopover } from \"./SelectPopover\"\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport type SelectSize = \"lg\" | \"md\"\n\nexport interface SelectOptionWithTextLabel {\n /** Whether this option is non-interactive. */\n disabled?: boolean\n divider?: never\n /** Displayed as the option content and used for type-ahead matching. */\n label: string\n textValue?: never\n value: string\n}\n\nexport interface SelectOptionWithNodeLabel {\n /** Whether this option is non-interactive. */\n disabled?: boolean\n divider?: never\n /** Displayed as the option content. */\n label: React.ReactNode\n /** Plain-text representation required for type-ahead matching. */\n textValue: string\n value: string\n}\n\nexport interface SelectOptionDivider {\n disabled?: never\n divider: true\n label?: never\n textValue?: never\n value?: never\n}\n\nexport type SelectOption =\n | SelectOptionDivider\n | SelectOptionWithNodeLabel\n | SelectOptionWithTextLabel\n\nexport interface SelectOptionsGroup {\n /** Whether all options in this group are non-interactive. */\n disabled?: boolean\n /** Visible heading displayed above the group's options. */\n label: string\n /** The selectable options within this group. */\n options: SelectOption[]\n}\n\nexport type SelectItem = SelectOption | SelectOptionsGroup\n\n// ---------------------------------------------------------------------------\n// Select component props\n// ---------------------------------------------------------------------------\n\nexport interface SelectProps {\n /** Render as popover listbox (SelectPopover) instead of native <select>. */\n complex?: boolean\n /** Helper text below the select. Red when invalid. */\n description?: string\n /** Triggers invalid state (red border, red description). */\n invalid?: boolean\n /** Label text displayed above the select. */\n label: string\n /** A flat or mixed array of options and option groups. */\n options: SelectItem[]\n /** Placeholder text for the empty state. */\n placeholder: string\n /** Visual size of the select. */\n size?: SelectSize\n}\n\ntype BaseSelectElementProps = Omit<\n SelectHTMLAttributes<HTMLSelectElement>,\n keyof SelectProps | \"multiple\" | \"size\"\n>\n\nexport type NativeSelectElementProps = BaseSelectElementProps &\n SelectProps & {\n complex?: false\n }\n\nexport type ComplexSelectElementProps = BaseSelectElementProps &\n SelectProps & {\n complex: true\n }\n\nexport type SelectElementProps =\n | NativeSelectElementProps\n | ComplexSelectElementProps\n\nfunction normalizeSelectValue(\n value:\n | NativeSelectElementProps[\"defaultValue\"]\n | NativeSelectElementProps[\"value\"]\n | ComplexSelectElementProps[\"defaultValue\"]\n | ComplexSelectElementProps[\"value\"]\n) {\n if (value === undefined || value === null) return undefined\n if (Array.isArray(value)) return value[0]\n return String(value)\n}\n\n// ---------------------------------------------------------------------------\n// Select (public)\n// ---------------------------------------------------------------------------\n\nexport const Select = forwardRef<HTMLSelectElement, SelectElementProps>(\n function Select(props: SelectElementProps, ref) {\n const {\n \"aria-label\": userAriaLabel,\n \"aria-labelledby\": userAriaLabelledBy,\n className,\n complex,\n defaultValue,\n description,\n id,\n invalid,\n label,\n onChange,\n options,\n placeholder,\n required,\n size,\n value,\n ...restProps\n } = props\n\n const stableId = useId()\n const controlId = id || `tds-select-${stableId}`\n const labelId = `${controlId}-label`\n const descriptionId = description ? `${controlId}-description` : undefined\n const computedClassName = classNames(\n \"tds-select\",\n {\n \"tds-select--invalid\": invalid,\n \"tds-select--lg\": size === \"lg\",\n \"tds-select--required\": required,\n },\n className\n )\n\n const normalizedDefaultValue = normalizeSelectValue(defaultValue)\n const normalizedValue = normalizeSelectValue(value)\n\n const computedDefaultValue =\n normalizedDefaultValue === undefined && normalizedValue === undefined\n ? \"\"\n : normalizedDefaultValue\n const computedAriaLabelledBy =\n userAriaLabelledBy ?? (userAriaLabel ? undefined : labelId)\n\n const sharedControlProps = {\n ...restProps,\n \"aria-describedby\": descriptionId,\n \"aria-label\": userAriaLabel,\n \"aria-labelledby\": computedAriaLabelledBy,\n defaultValue: computedDefaultValue,\n invalid,\n options,\n placeholder,\n required,\n value: normalizedValue,\n }\n\n const SelectComponent = complex ? SelectPopover : SelectNative\n\n return (\n <div className={computedClassName}>\n {complex ? (\n <div className=\"tds-select-label\" id={labelId}>\n {label}\n </div>\n ) : (\n <label htmlFor={controlId} id={labelId}>\n {label}\n </label>\n )}\n <SelectComponent\n {...sharedControlProps}\n ref={ref}\n id={controlId}\n onChange={onChange}\n />\n {description && descriptionId && (\n <p className=\"tds-select-description\" id={descriptionId}>\n <Icon\n aria-hidden\n className=\"tds-select-description-invalid-icon\"\n symbol=\"general#exclamation-triangle\"\n />\n {description}\n </p>\n )}\n </div>\n )\n }\n)\n\nSelect.displayName = \"Select\"\n"],"names":["React"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { type SelectHTMLAttributes } from "react";
|
|
2
|
-
import type { SelectItem } from "./
|
|
2
|
+
import type { SelectItem } from "./Select";
|
|
3
3
|
declare global {
|
|
4
4
|
namespace JSX {
|
|
5
5
|
interface IntrinsicElements {
|
|
@@ -8,6 +8,8 @@ declare global {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
export interface SelectNativeProps {
|
|
11
|
+
/** Triggers invalid state (maps to aria-invalid). */
|
|
12
|
+
invalid?: boolean;
|
|
11
13
|
/** Options to render inside the select. */
|
|
12
14
|
options: SelectItem[];
|
|
13
15
|
/** Placeholder text rendered as a disabled hidden option. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectNative.d.ts","sourceRoot":"","sources":["../../../src/components/select/SelectNative.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAEZ,KAAK,oBAAoB,EAG1B,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"SelectNative.d.ts","sourceRoot":"","sources":["../../../src/components/select/SelectNative.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAEZ,KAAK,oBAAoB,EAG1B,MAAM,OAAO,CAAA;AAEd,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAuB1C,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,GAAG,CAAC;QACZ,UAAU,iBAAiB;YACzB,eAAe,EAAE,KAAK,CAAC,iBAAiB,CACtC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EACjC,WAAW,CACZ,CAAA;SACF;KACF;CACF;AAMD,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,2CAA2C;IAC3C,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,iBAAiB,GAAG,UAAU,CACrC,GACC,iBAAiB,CAAA;AAEnB,eAAO,MAAM,YAAY,yLAmCvB,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React__default, { forwardRef, useState, useEffect } from 'react';
|
|
2
|
+
import { SelectOptions } from './SelectOptions.js';
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// base-select feature detection (cached)
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
let _supportsBaseSelect = null;
|
|
8
|
+
function supportsBaseSelect() {
|
|
9
|
+
if (_supportsBaseSelect === null) {
|
|
10
|
+
_supportsBaseSelect =
|
|
11
|
+
typeof CSS !== "undefined" &&
|
|
12
|
+
typeof CSS.supports === "function" &&
|
|
13
|
+
CSS.supports("appearance", "base-select");
|
|
14
|
+
}
|
|
15
|
+
return _supportsBaseSelect;
|
|
16
|
+
}
|
|
17
|
+
const SelectNative = forwardRef(function SelectNative({ invalid, options, placeholder, ...restProps }, ref) {
|
|
18
|
+
// Keep first render deterministic for SSR/hydration, then detect support on mount.
|
|
19
|
+
const [isCustom, setIsCustom] = useState(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (supportsBaseSelect()) {
|
|
22
|
+
setIsCustom(true);
|
|
23
|
+
}
|
|
24
|
+
}, []);
|
|
25
|
+
return (React__default.createElement("select", { ...restProps, "aria-invalid": invalid ? "true" : undefined, ref: ref },
|
|
26
|
+
isCustom && (React__default.createElement("button", null,
|
|
27
|
+
React__default.createElement("selectedcontent", null))),
|
|
28
|
+
placeholder && (React__default.createElement("option", { disabled: true, hidden: true, value: "" }, placeholder)),
|
|
29
|
+
React__default.createElement(SelectOptions, { items: options, supportsBaseSelect: isCustom })));
|
|
30
|
+
});
|
|
31
|
+
SelectNative.displayName = "SelectNative";
|
|
32
|
+
|
|
33
|
+
export { SelectNative };
|
|
34
|
+
//# sourceMappingURL=SelectNative.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectNative.js","sources":["../../../src/components/select/SelectNative.tsx"],"sourcesContent":["import React, {\n forwardRef,\n type SelectHTMLAttributes,\n useEffect,\n useState,\n} from \"react\"\n\nimport type { SelectItem } from \"./Select\"\nimport { SelectOptions } from \"./SelectOptions\"\n\n// ---------------------------------------------------------------------------\n// base-select feature detection (cached)\n// ---------------------------------------------------------------------------\n\nlet _supportsBaseSelect: boolean | null = null\n\nfunction supportsBaseSelect(): boolean {\n if (_supportsBaseSelect === null) {\n _supportsBaseSelect =\n typeof CSS !== \"undefined\" &&\n typeof CSS.supports === \"function\" &&\n CSS.supports(\"appearance\", \"base-select\")\n }\n return _supportsBaseSelect\n}\n\n// ---------------------------------------------------------------------------\n// JSX augmentation for <selectedcontent>\n// ---------------------------------------------------------------------------\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace JSX {\n interface IntrinsicElements {\n selectedcontent: React.DetailedHTMLProps<\n React.HTMLAttributes<HTMLElement>,\n HTMLElement\n >\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// SelectNative\n// ---------------------------------------------------------------------------\n\nexport interface SelectNativeProps {\n /** Triggers invalid state (maps to aria-invalid). */\n invalid?: boolean\n /** Options to render inside the select. */\n options: SelectItem[]\n /** Placeholder text rendered as a disabled hidden option. */\n placeholder: string\n}\n\nexport type SelectNativeElementProps = Omit<\n SelectHTMLAttributes<HTMLSelectElement>,\n keyof SelectNativeProps | \"multiple\"\n> &\n SelectNativeProps\n\nexport const SelectNative = forwardRef<\n HTMLSelectElement,\n SelectNativeElementProps\n>(function SelectNative(\n { invalid, options, placeholder, ...restProps }: SelectNativeElementProps,\n ref\n) {\n // Keep first render deterministic for SSR/hydration, then detect support on mount.\n const [isCustom, setIsCustom] = useState(false)\n\n useEffect(() => {\n if (supportsBaseSelect()) {\n setIsCustom(true)\n }\n }, [])\n\n return (\n <select\n {...restProps}\n aria-invalid={invalid ? \"true\" : undefined}\n ref={ref}\n >\n {isCustom && (\n <button>\n <selectedcontent />\n </button>\n )}\n {placeholder && (\n <option disabled hidden value=\"\">\n {placeholder}\n </option>\n )}\n <SelectOptions items={options} supportsBaseSelect={isCustom} />\n </select>\n )\n})\n\nSelectNative.displayName = \"SelectNative\"\n"],"names":["React"],"mappings":";;;AAUA;AACA;AACA;AAEA,IAAI,mBAAmB,GAAmB,IAAI;AAE9C,SAAS,kBAAkB,GAAA;AACzB,IAAA,IAAI,mBAAmB,KAAK,IAAI,EAAE;QAChC,mBAAmB;YACjB,OAAO,GAAG,KAAK,WAAW;AAC1B,gBAAA,OAAO,GAAG,CAAC,QAAQ,KAAK,UAAU;AAClC,gBAAA,GAAG,CAAC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC;IAC7C;AACA,IAAA,OAAO,mBAAmB;AAC5B;MAqCa,YAAY,GAAG,UAAU,CAGpC,SAAS,YAAY,CACrB,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,SAAS,EAA4B,EACzE,GAAG,EAAA;;IAGH,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IAE/C,SAAS,CAAC,MAAK;QACb,IAAI,kBAAkB,EAAE,EAAE;YACxB,WAAW,CAAC,IAAI,CAAC;QACnB;IACF,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,QACEA,cAAA,CAAA,aAAA,CAAA,QAAA,EAAA,EAAA,GACM,SAAS,EAAA,cAAA,EACC,OAAO,GAAG,MAAM,GAAG,SAAS,EAC1C,GAAG,EAAE,GAAG,EAAA;AAEP,QAAA,QAAQ,KACPA,cAAA,CAAA,aAAA,CAAA,QAAA,EAAA,IAAA;AACE,YAAAA,cAAA,CAAA,aAAA,CAAA,iBAAA,EAAA,IAAA,CAAmB,CACZ,CACV;AACA,QAAA,WAAW,KACVA,cAAA,CAAA,aAAA,CAAA,QAAA,EAAA,EAAQ,QAAQ,EAAA,IAAA,EAAC,MAAM,EAAA,IAAA,EAAC,KAAK,EAAC,EAAE,EAAA,EAC7B,WAAW,CACL,CACV;AACD,QAAAA,cAAA,CAAA,aAAA,CAAC,aAAa,EAAA,EAAC,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAA,CAAI,CACxD;AAEb,CAAC;AAED,YAAY,CAAC,WAAW,GAAG,cAAc;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectOptions.d.ts","sourceRoot":"","sources":["../../../src/components/select/SelectOptions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"SelectOptions.d.ts","sourceRoot":"","sources":["../../../src/components/select/SelectOptions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EACV,UAAU,EAIX,MAAM,UAAU,CAAA;AAMjB,UAAU,kBAAkB;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAuBD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,kBAA0B,GAC3B,EAAE,kBAAkB,GAAG,KAAK,CAAC,SAAS,CA8BtC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
|
|
3
|
+
function isGroup(item) {
|
|
4
|
+
return "options" in item;
|
|
5
|
+
}
|
|
6
|
+
function getOptionLabel(option) {
|
|
7
|
+
if (typeof option.label === "string")
|
|
8
|
+
return option.label;
|
|
9
|
+
return option.textValue ?? option.value ?? "";
|
|
10
|
+
}
|
|
11
|
+
function Option({ option }) {
|
|
12
|
+
return (React__default.createElement("option", { disabled: option.disabled, value: option.value }, getOptionLabel(option)));
|
|
13
|
+
}
|
|
14
|
+
function SelectOptions({ items, supportsBaseSelect = false, }) {
|
|
15
|
+
return items.map((item, index) => {
|
|
16
|
+
if (isGroup(item)) {
|
|
17
|
+
return (React__default.createElement("optgroup", { key: `${item.label}-${index}`, disabled: item.disabled, label: item.label },
|
|
18
|
+
supportsBaseSelect && (React__default.createElement("legend", null,
|
|
19
|
+
React__default.createElement("span", null, item.label))),
|
|
20
|
+
item.options.map((opt, optIndex) => {
|
|
21
|
+
if (opt.divider) {
|
|
22
|
+
return React__default.createElement("hr", { key: `divider-${optIndex}` });
|
|
23
|
+
}
|
|
24
|
+
return React__default.createElement(Option, { key: opt.value ?? `opt-${optIndex}`, option: opt });
|
|
25
|
+
})));
|
|
26
|
+
}
|
|
27
|
+
if (item.divider) {
|
|
28
|
+
return React__default.createElement("hr", { key: `divider-${index}` });
|
|
29
|
+
}
|
|
30
|
+
return React__default.createElement(Option, { key: item.value ?? `opt-${index}`, option: item });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { SelectOptions };
|
|
35
|
+
//# sourceMappingURL=SelectOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectOptions.js","sources":["../../../src/components/select/SelectOptions.tsx"],"sourcesContent":["import React from \"react\"\n\nimport type {\n SelectItem,\n SelectOptionsGroup,\n SelectOptionWithNodeLabel,\n SelectOptionWithTextLabel,\n} from \"./Select\"\n\ninterface OptionProps {\n option: SelectOptionWithNodeLabel | SelectOptionWithTextLabel\n}\n\ninterface SelectOptionsProps {\n items: SelectItem[]\n supportsBaseSelect?: boolean\n}\n\nfunction isGroup(item: SelectItem): item is SelectOptionsGroup {\n return \"options\" in item\n}\n\nfunction getOptionLabel(option: {\n label: React.ReactNode\n textValue?: string\n value?: string\n}): string {\n if (typeof option.label === \"string\") return option.label\n return option.textValue ?? option.value ?? \"\"\n}\n\nfunction Option({ option }: OptionProps) {\n return (\n <option disabled={option.disabled} value={option.value}>\n {getOptionLabel(option)}\n </option>\n )\n}\n\nexport function SelectOptions({\n items,\n supportsBaseSelect = false,\n}: SelectOptionsProps): React.ReactNode {\n return items.map((item, index) => {\n if (isGroup(item)) {\n return (\n <optgroup\n key={`${item.label}-${index}`}\n disabled={item.disabled}\n label={item.label}\n >\n {supportsBaseSelect && (\n <legend>\n <span>{item.label}</span>\n </legend>\n )}\n {item.options.map((opt, optIndex) => {\n if (opt.divider) {\n return <hr key={`divider-${optIndex}`} />\n }\n return <Option key={opt.value ?? `opt-${optIndex}`} option={opt} />\n })}\n </optgroup>\n )\n }\n\n if (item.divider) {\n return <hr key={`divider-${index}`} />\n }\n\n return <Option key={item.value ?? `opt-${index}`} option={item} />\n })\n}\n"],"names":["React"],"mappings":";;AAkBA,SAAS,OAAO,CAAC,IAAgB,EAAA;IAC/B,OAAO,SAAS,IAAI,IAAI;AAC1B;AAEA,SAAS,cAAc,CAAC,MAIvB,EAAA;AACC,IAAA,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK;IACzD,OAAO,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE;AAC/C;AAEA,SAAS,MAAM,CAAC,EAAE,MAAM,EAAe,EAAA;IACrC,QACEA,yCAAQ,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IACnD,cAAc,CAAC,MAAM,CAAC,CAChB;AAEb;AAEM,SAAU,aAAa,CAAC,EAC5B,KAAK,EACL,kBAAkB,GAAG,KAAK,GACP,EAAA;IACnB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,KAAI;AAC/B,QAAA,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE;YACjB,QACEA,2CACE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE,EAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ,EACvB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAA;AAEhB,gBAAA,kBAAkB,KACjBA,cAAA,CAAA,aAAA,CAAA,QAAA,EAAA,IAAA;AACE,oBAAAA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,EAAO,IAAI,CAAC,KAAK,CAAQ,CAClB,CACV;gBACA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,QAAQ,KAAI;AAClC,oBAAA,IAAI,GAAG,CAAC,OAAO,EAAE;AACf,wBAAA,OAAOA,qCAAI,GAAG,EAAE,WAAW,QAAQ,CAAA,CAAE,GAAI;oBAC3C;AACA,oBAAA,OAAOA,6BAAC,MAAM,EAAA,EAAC,GAAG,EAAE,GAAG,CAAC,KAAK,IAAI,CAAA,IAAA,EAAO,QAAQ,CAAA,CAAE,EAAE,MAAM,EAAE,GAAG,GAAI;gBACrE,CAAC,CAAC,CACO;QAEf;AAEA,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAChB,YAAA,OAAOA,qCAAI,GAAG,EAAE,WAAW,KAAK,CAAA,CAAE,GAAI;QACxC;AAEA,QAAA,OAAOA,6BAAC,MAAM,EAAA,EAAC,GAAG,EAAE,IAAI,CAAC,KAAK,IAAI,CAAA,IAAA,EAAO,KAAK,CAAA,CAAE,EAAE,MAAM,EAAE,IAAI,GAAI;AACpE,IAAA,CAAC,CAAC;AACJ;;;;"}
|
|
@@ -1,63 +1,19 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
disabled?: boolean;
|
|
8
|
-
divider?: never;
|
|
9
|
-
/** Displayed as the option content and used for type-ahead matching. */
|
|
10
|
-
label: string;
|
|
11
|
-
textValue?: never;
|
|
12
|
-
value: string;
|
|
13
|
-
}
|
|
14
|
-
interface SelectOptionWithNodeLabel {
|
|
15
|
-
/** Whether this option is non-interactive. */
|
|
16
|
-
disabled?: boolean;
|
|
17
|
-
divider?: never;
|
|
18
|
-
/** Displayed as the option content. */
|
|
19
|
-
label: React.ReactNode;
|
|
20
|
-
/** Plain-text representation required for type-ahead matching. */
|
|
21
|
-
textValue: string;
|
|
22
|
-
value: string;
|
|
23
|
-
}
|
|
24
|
-
export interface SelectOptionDivider {
|
|
25
|
-
disabled?: never;
|
|
26
|
-
divider: true;
|
|
27
|
-
label?: never;
|
|
28
|
-
textValue?: never;
|
|
29
|
-
value?: never;
|
|
30
|
-
}
|
|
31
|
-
export type SelectOption = SelectOptionDivider | SelectOptionWithNodeLabel | SelectOptionWithTextLabel;
|
|
32
|
-
export interface SelectOptionsGroup {
|
|
33
|
-
/** Whether all options in this group are non-interactive. */
|
|
34
|
-
disabled?: boolean;
|
|
35
|
-
/** Visible heading displayed above the group's options. */
|
|
36
|
-
label: string;
|
|
37
|
-
/** The selectable options within this group. */
|
|
38
|
-
options: SelectOption[];
|
|
39
|
-
}
|
|
40
|
-
export type SelectItem = SelectOption | SelectOptionsGroup;
|
|
41
|
-
interface SelectPopoverBaseProps {
|
|
1
|
+
import React, { type SelectHTMLAttributes } from "react";
|
|
2
|
+
import type { SelectItem } from "./Select";
|
|
3
|
+
export interface SelectPopoverProps {
|
|
4
|
+
"aria-describedby"?: string;
|
|
5
|
+
"aria-label"?: string;
|
|
6
|
+
"aria-labelledby"?: string;
|
|
42
7
|
defaultValue?: string;
|
|
43
|
-
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
id?: string;
|
|
44
10
|
invalid?: boolean;
|
|
45
11
|
/** A flat or mixed array of options and option groups. */
|
|
46
12
|
options: SelectItem[];
|
|
47
13
|
placeholder: string;
|
|
48
14
|
required?: boolean;
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
interface SelectPopoverWithLabel extends SelectPopoverBaseProps {
|
|
52
|
-
"aria-labelledby"?: never;
|
|
53
|
-
label: React.ReactNode;
|
|
54
|
-
}
|
|
55
|
-
interface SelectPopoverWithAriaLabelledBy extends SelectPopoverBaseProps {
|
|
56
|
-
"aria-labelledby": string;
|
|
57
|
-
label?: never;
|
|
15
|
+
value?: string;
|
|
58
16
|
}
|
|
59
|
-
export type
|
|
60
|
-
export
|
|
61
|
-
export declare const SelectPopover: React.ForwardRefExoticComponent<SelectPopoverElementProps & React.RefAttributes<HTMLButtonElement>>;
|
|
62
|
-
export {};
|
|
17
|
+
export type SelectPopoverElementProps = Omit<SelectHTMLAttributes<HTMLSelectElement>, keyof SelectPopoverProps | "multiple"> & SelectPopoverProps;
|
|
18
|
+
export declare const SelectPopover: React.ForwardRefExoticComponent<Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "multiple" | keyof SelectPopoverProps> & SelectPopoverProps & React.RefAttributes<HTMLSelectElement>>;
|
|
63
19
|
//# sourceMappingURL=SelectPopover.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectPopover.d.ts","sourceRoot":"","sources":["../../../src/components/select/SelectPopover.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SelectPopover.d.ts","sourceRoot":"","sources":["../../../src/components/select/SelectPopover.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,EAEZ,KAAK,oBAAoB,EAM1B,MAAM,OAAO,CAAA;AAEd,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,UAAU,CAAA;AAQxD,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,0DAA0D;IAC1D,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,kBAAkB,GAAG,UAAU,CACtC,GACC,kBAAkB,CAAA;AAqDpB,eAAO,MAAM,aAAa,2LA0ZzB,CAAA"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { getComboboxActionFromKey, ComboboxAction, getUpdatedIndex } from '../../utilities/keyboardUtils.js';
|
|
2
|
+
import { normalizeOptions, getSelectableOptionsFromSegments, getIndexByLetter } from '../../utilities/selectUtils.js';
|
|
3
|
+
import { useId } from '../../utilities/useId.js';
|
|
4
|
+
import React__default, { forwardRef, useState, useMemo, useRef, useCallback, useEffect } from 'react';
|
|
5
|
+
import { SelectOptions } from './SelectOptions.js';
|
|
6
|
+
|
|
7
|
+
const Group = ({ children, disabled, id, label, }) => (React__default.createElement("ul", { "aria-disabled": disabled || undefined, "aria-labelledby": id, role: "group" },
|
|
8
|
+
React__default.createElement("li", { id: id, role: "presentation" },
|
|
9
|
+
React__default.createElement("span", null, label)),
|
|
10
|
+
children));
|
|
11
|
+
function renderListSegments(segments, baseId, renderItem) {
|
|
12
|
+
let flatIndex = 0;
|
|
13
|
+
return segments.map((segment, segmentIndex) => {
|
|
14
|
+
const startIndex = flatIndex;
|
|
15
|
+
flatIndex += segment.options.length;
|
|
16
|
+
if (segment.type === "group") {
|
|
17
|
+
return (React__default.createElement(Group, { key: `${segment.label}-${segmentIndex}`, disabled: segment.disabled, id: `${baseId}-group-${segmentIndex}`, label: segment.label }, segment.options.map((opt, i) => renderItem(opt, startIndex + i))));
|
|
18
|
+
}
|
|
19
|
+
return (React__default.createElement("ul", { key: `options-${segmentIndex}`, role: "presentation" }, segment.options.map((opt, i) => renderItem(opt, startIndex + i))));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const SelectPopover = forwardRef(({ "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, defaultValue, disabled, id, invalid, onBlur, onChange, onClick, onFocus, options, placeholder, required, value, ...props }, ref) => {
|
|
23
|
+
const stableId = useId();
|
|
24
|
+
const baseId = id || `tds-select-popover-${stableId}`;
|
|
25
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
26
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? null);
|
|
27
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
28
|
+
const isControlled = value !== undefined;
|
|
29
|
+
const selectedValue = isControlled ? (value ?? null) : uncontrolledValue;
|
|
30
|
+
const { allOptions, segments } = useMemo(() => {
|
|
31
|
+
const segments = normalizeOptions(options);
|
|
32
|
+
const allOptions = getSelectableOptionsFromSegments(segments);
|
|
33
|
+
return { allOptions, segments };
|
|
34
|
+
}, [options]);
|
|
35
|
+
const selectedOption = allOptions.find((opt) => opt.value === selectedValue);
|
|
36
|
+
const activeOption = activeIndex >= 0 ? allOptions[activeIndex] : undefined;
|
|
37
|
+
const activeOptionId = activeOption
|
|
38
|
+
? `${baseId}-option-${activeOption.value}`
|
|
39
|
+
: undefined;
|
|
40
|
+
const popoverRef = useRef(null);
|
|
41
|
+
const triggerButtonRef = useRef(null);
|
|
42
|
+
const hiddenSelectRef = useRef(null);
|
|
43
|
+
const pendingChangeValueRef = useRef(null);
|
|
44
|
+
const searchStringRef = useRef("");
|
|
45
|
+
const searchTimeoutRef = useRef();
|
|
46
|
+
const setHiddenSelectRef = useCallback((element) => {
|
|
47
|
+
hiddenSelectRef.current = element;
|
|
48
|
+
if (typeof ref === "function") {
|
|
49
|
+
ref(element);
|
|
50
|
+
}
|
|
51
|
+
else if (ref) {
|
|
52
|
+
ref.current = element;
|
|
53
|
+
}
|
|
54
|
+
}, [ref]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const popoverEl = popoverRef.current;
|
|
57
|
+
if (!popoverEl)
|
|
58
|
+
return;
|
|
59
|
+
const handleToggle = (e) => {
|
|
60
|
+
const newIsOpen = e.newState === "open";
|
|
61
|
+
setIsOpen(newIsOpen);
|
|
62
|
+
if (newIsOpen) {
|
|
63
|
+
const index = allOptions.findIndex((opt) => opt.value === selectedValue && !opt.disabled);
|
|
64
|
+
if (index >= 0) {
|
|
65
|
+
setActiveIndex(index);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Find first non-disabled option
|
|
69
|
+
const firstEnabledIndexOrFirstIndex = Math.max(allOptions.findIndex((opt) => !opt.disabled), 0);
|
|
70
|
+
setActiveIndex(firstEnabledIndexOrFirstIndex);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
setActiveIndex(-1);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
popoverEl.addEventListener("toggle", handleToggle);
|
|
78
|
+
return () => popoverEl.removeEventListener("toggle", handleToggle);
|
|
79
|
+
}, [allOptions, selectedValue]);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (activeOptionId) {
|
|
82
|
+
document
|
|
83
|
+
.getElementById(activeOptionId)
|
|
84
|
+
?.scrollIntoView({ block: "nearest" });
|
|
85
|
+
}
|
|
86
|
+
}, [activeOptionId]);
|
|
87
|
+
useEffect(() => () => {
|
|
88
|
+
clearTimeout(searchTimeoutRef.current);
|
|
89
|
+
}, []);
|
|
90
|
+
const openPopover = () => popoverRef.current?.showPopover({ source: triggerButtonRef.current });
|
|
91
|
+
const closePopover = () => popoverRef.current?.hidePopover();
|
|
92
|
+
const commitSelection = useCallback((nextValue) => {
|
|
93
|
+
if (nextValue === selectedValue) {
|
|
94
|
+
closePopover();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!isControlled) {
|
|
98
|
+
setUncontrolledValue(nextValue);
|
|
99
|
+
}
|
|
100
|
+
const hiddenEl = hiddenSelectRef.current;
|
|
101
|
+
if (hiddenEl) {
|
|
102
|
+
pendingChangeValueRef.current = nextValue;
|
|
103
|
+
hiddenEl.value = nextValue;
|
|
104
|
+
hiddenEl.dispatchEvent(new Event("change", { bubbles: true }));
|
|
105
|
+
if (isControlled) {
|
|
106
|
+
hiddenEl.value = value ?? "";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
closePopover();
|
|
110
|
+
}, [isControlled, selectedValue, value]);
|
|
111
|
+
const selectActiveOption = useCallback(() => {
|
|
112
|
+
if (activeOption && !activeOption.disabled) {
|
|
113
|
+
commitSelection(activeOption.value);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}, [activeOption, commitSelection]);
|
|
118
|
+
const commitActiveOptionOrClose = useCallback(() => {
|
|
119
|
+
if (!selectActiveOption()) {
|
|
120
|
+
closePopover();
|
|
121
|
+
}
|
|
122
|
+
}, [selectActiveOption]);
|
|
123
|
+
const handleKeyDown = (e) => {
|
|
124
|
+
if (disabled)
|
|
125
|
+
return;
|
|
126
|
+
// Tab must not be prevented — allow native focus movement
|
|
127
|
+
if (e.key === "Tab" && isOpen) {
|
|
128
|
+
commitActiveOptionOrClose();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const action = getComboboxActionFromKey(e, isOpen);
|
|
132
|
+
if (!action)
|
|
133
|
+
return;
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
switch (action) {
|
|
136
|
+
case ComboboxAction.OpenPopup:
|
|
137
|
+
openPopover();
|
|
138
|
+
break;
|
|
139
|
+
case ComboboxAction.CommitAndClose:
|
|
140
|
+
commitActiveOptionOrClose();
|
|
141
|
+
break;
|
|
142
|
+
case ComboboxAction.MoveFirst:
|
|
143
|
+
case ComboboxAction.MoveLast:
|
|
144
|
+
if (!isOpen)
|
|
145
|
+
openPopover();
|
|
146
|
+
setActiveIndex(getUpdatedIndex(activeIndex, action, allOptions));
|
|
147
|
+
break;
|
|
148
|
+
case ComboboxAction.MoveNext:
|
|
149
|
+
case ComboboxAction.MovePrevious:
|
|
150
|
+
setActiveIndex(getUpdatedIndex(activeIndex, action, allOptions));
|
|
151
|
+
break;
|
|
152
|
+
case ComboboxAction.TypeCharacter: {
|
|
153
|
+
if (!isOpen)
|
|
154
|
+
openPopover();
|
|
155
|
+
clearTimeout(searchTimeoutRef.current);
|
|
156
|
+
searchStringRef.current += e.key;
|
|
157
|
+
searchTimeoutRef.current = setTimeout(() => {
|
|
158
|
+
searchStringRef.current = "";
|
|
159
|
+
}, 500);
|
|
160
|
+
const searchIndex = getIndexByLetter(allOptions, searchStringRef.current, activeIndex);
|
|
161
|
+
if (searchIndex >= 0) {
|
|
162
|
+
setActiveIndex(searchIndex);
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case ComboboxAction.ClosePopup:
|
|
167
|
+
closePopover();
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const createStableEventTarget = (target, valueSnapshot) => {
|
|
172
|
+
const proxy = Object.create(target);
|
|
173
|
+
Object.defineProperties(proxy, {
|
|
174
|
+
disabled: {
|
|
175
|
+
configurable: true,
|
|
176
|
+
enumerable: true,
|
|
177
|
+
value: target.disabled,
|
|
178
|
+
},
|
|
179
|
+
form: { configurable: true, enumerable: true, value: target.form },
|
|
180
|
+
name: { configurable: true, enumerable: true, value: target.name },
|
|
181
|
+
type: { configurable: true, enumerable: true, value: target.type },
|
|
182
|
+
value: { configurable: true, enumerable: true, value: valueSnapshot },
|
|
183
|
+
});
|
|
184
|
+
return proxy;
|
|
185
|
+
};
|
|
186
|
+
const withSelectTarget = useCallback((event, target, currentTarget = target) => {
|
|
187
|
+
return Object.defineProperties(Object.create(event), {
|
|
188
|
+
currentTarget: { value: currentTarget },
|
|
189
|
+
target: { value: target },
|
|
190
|
+
});
|
|
191
|
+
}, []);
|
|
192
|
+
const handleHiddenSelectChange = useCallback((event) => {
|
|
193
|
+
if (!onChange)
|
|
194
|
+
return;
|
|
195
|
+
const valueSnapshot = pendingChangeValueRef.current ?? event.currentTarget.value;
|
|
196
|
+
pendingChangeValueRef.current = null;
|
|
197
|
+
const stableEvent = withSelectTarget(event, createStableEventTarget(event.target, valueSnapshot), createStableEventTarget(event.currentTarget, valueSnapshot));
|
|
198
|
+
onChange(stableEvent);
|
|
199
|
+
}, [onChange, withSelectTarget]);
|
|
200
|
+
const forwardEventToSelect = useCallback((event, handler) => {
|
|
201
|
+
if (!handler)
|
|
202
|
+
return;
|
|
203
|
+
const hiddenEl = hiddenSelectRef.current;
|
|
204
|
+
if (!hiddenEl)
|
|
205
|
+
return;
|
|
206
|
+
const stableEvent = withSelectTarget(event, hiddenEl);
|
|
207
|
+
handler(stableEvent);
|
|
208
|
+
}, [withSelectTarget]);
|
|
209
|
+
const handleButtonBlur = useCallback((event) => {
|
|
210
|
+
forwardEventToSelect(event, onBlur);
|
|
211
|
+
}, [forwardEventToSelect, onBlur]);
|
|
212
|
+
const handleButtonClick = useCallback((event) => {
|
|
213
|
+
forwardEventToSelect(event, onClick);
|
|
214
|
+
}, [forwardEventToSelect, onClick]);
|
|
215
|
+
const handleButtonFocus = useCallback((event) => {
|
|
216
|
+
forwardEventToSelect(event, onFocus);
|
|
217
|
+
}, [forwardEventToSelect, onFocus]);
|
|
218
|
+
const renderListOption = ({ disabled, divider, label, value }, index) => {
|
|
219
|
+
if (divider)
|
|
220
|
+
return React__default.createElement("li", { key: index, role: "separator" });
|
|
221
|
+
return (
|
|
222
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events -- keyboard events are handled on the combobox button
|
|
223
|
+
React__default.createElement("li", { key: value, "aria-disabled": disabled || undefined, "aria-selected": value === selectedValue, className: value === activeOption?.value
|
|
224
|
+
? "tds-select-option--active"
|
|
225
|
+
: undefined, id: `${baseId}-option-${value}`, onClick: disabled ? undefined : () => commitSelection(value), role: "option" }, label));
|
|
226
|
+
};
|
|
227
|
+
return (React__default.createElement(React__default.Fragment, null,
|
|
228
|
+
React__default.createElement("button", { ref: triggerButtonRef, popovertarget: baseId, "aria-activedescendant": activeOptionId, "aria-controls": baseId, "aria-describedby": ariaDescribedBy, "aria-disabled": disabled ? "true" : undefined, "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-invalid": invalid ? "true" : undefined, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-required": required ? "true" : undefined, disabled: disabled, onBlur: handleButtonBlur, onClick: handleButtonClick, onFocus: handleButtonFocus, onKeyDown: handleKeyDown, role: "combobox", type: "button" }, selectedOption ? selectedOption.label : placeholder),
|
|
229
|
+
React__default.createElement("div", { ref: popoverRef, popover: "", id: baseId, role: "listbox", "aria-describedby": ariaDescribedBy, "aria-invalid": invalid ? "true" : undefined, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-required": required ? "true" : undefined }, renderListSegments(segments, baseId, renderListOption)),
|
|
230
|
+
React__default.createElement("select", { ...props, ref: setHiddenSelectRef, "aria-hidden": true, className: "tds-select-hidden-select", disabled: disabled, id: `${baseId}-control`, onChange: handleHiddenSelectChange, required: required, tabIndex: -1, value: selectedValue ?? "" },
|
|
231
|
+
React__default.createElement("option", { value: "" }),
|
|
232
|
+
React__default.createElement(SelectOptions, { items: options }))));
|
|
233
|
+
});
|
|
234
|
+
SelectPopover.displayName = "SelectPopover";
|
|
235
|
+
|
|
236
|
+
export { SelectPopover };
|
|
237
|
+
//# sourceMappingURL=SelectPopover.js.map
|