@synerise/ds-select 1.3.34 → 1.5.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/CHANGELOG.md +27 -0
- package/CLAUDE.md +136 -92
- package/README.md +163 -99
- package/dist/Option.d.ts +28 -0
- package/dist/Option.js +5 -0
- package/dist/Select.d.ts +82 -17
- package/dist/Select.js +386 -64
- package/dist/Select.styles.d.ts +42 -11
- package/dist/Select.styles.js +98 -66
- package/dist/Select.types.d.ts +141 -11
- package/dist/components/OptionList.d.ts +23 -0
- package/dist/components/OptionList.js +32 -0
- package/dist/components/SelectorContent.d.ts +34 -0
- package/dist/components/SelectorContent.js +97 -0
- package/dist/hooks/useResponsiveTagCount.d.ts +27 -0
- package/dist/hooks/useResponsiveTagCount.js +72 -0
- package/dist/hooks/useSelectOptions.d.ts +28 -0
- package/dist/hooks/useSelectOptions.js +41 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +6 -1
- package/dist/utils/getOptionsFromChildren.d.ts +11 -0
- package/dist/utils/getOptionsFromChildren.js +41 -0
- package/dist/utils/helpers.d.ts +9 -0
- package/dist/utils/helpers.js +18 -0
- package/package.json +8 -5
- package/dist/assets/style/index-tn0RQdqM.css +0 -0
- package/dist/style/index.css +0 -1
package/dist/Select.styles.js
CHANGED
|
@@ -1,85 +1,117 @@
|
|
|
1
|
-
import { Select } from "antd";
|
|
2
1
|
import styled, { css } from "styled-components";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
`;
|
|
8
|
-
const searchIconWithCustomColor = (color) => {
|
|
9
|
-
const colorValueForSvg = color.replace(/#/, "%23");
|
|
10
|
-
const iconWithColor = `data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-6 -6 36 36' >/><path fill='none' d='M0 0h24v24H0z' /><path style='fill: ${colorValueForSvg};' d='M10.734 17.234a6.463 6.463 0 004.03-1.41l3.721 3.722a.75.75 0 001.06-1.06l-3.72-3.722a6.494 6.494 0 10-5.09 2.47zm0-11.5a5 5 0 11-5 5 5.006 5.006 0 015-5z'/></svg>`;
|
|
11
|
-
return iconWithColor;
|
|
2
|
+
import DSListItem, { ListWrapper } from "@synerise/ds-list-item";
|
|
3
|
+
const HEIGHT = {
|
|
4
|
+
large: 48,
|
|
5
|
+
default: 32
|
|
12
6
|
};
|
|
13
|
-
const
|
|
14
|
-
border-top-left-radius:0;
|
|
15
|
-
border-bottom-left-radius:0;
|
|
16
|
-
`;
|
|
17
|
-
const withSuffixStyles = () => `
|
|
18
|
-
border-top-right-radius:0;
|
|
19
|
-
border-bottom-right-radius:0;
|
|
20
|
-
`;
|
|
21
|
-
const addonStyles = (props) => `
|
|
22
|
-
display: flex;
|
|
23
|
-
align-items: center;
|
|
24
|
-
background: ${props.theme.palette["grey-050"]};
|
|
25
|
-
box-shadow: inset 0 0 0 1px ${props.theme.palette["grey-300"]};
|
|
26
|
-
color: ${props.theme.palette["grey-500"]};
|
|
27
|
-
font-size:13px;
|
|
28
|
-
line-height: 1.39;
|
|
29
|
-
`;
|
|
7
|
+
const addonStyles = /* @__PURE__ */ css(["display:flex;align-items:center;background:", ";box-shadow:inset 0 0 0 1px ", ";color:", ";font-size:13px;line-height:1.39;"], (props) => props.theme.palette["grey-050"], (props) => props.theme.palette["grey-300"], (props) => props.theme.palette["grey-500"]);
|
|
30
8
|
const SelectContainer = /* @__PURE__ */ styled.div.withConfig({
|
|
31
9
|
displayName: "Selectstyles__SelectContainer",
|
|
32
10
|
componentId: "sc-n9lk0v-0"
|
|
33
11
|
})(["display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;margin:0 0 ", "px;"], (props) => props.hasBottomMargin ? 16 : 0);
|
|
34
|
-
const
|
|
35
|
-
displayName: "
|
|
12
|
+
const SelectWrapper = /* @__PURE__ */ styled.div.withConfig({
|
|
13
|
+
displayName: "Selectstyles__SelectWrapper",
|
|
36
14
|
componentId: "sc-n9lk0v-1"
|
|
37
|
-
})(["
|
|
38
|
-
|
|
39
|
-
&.ant-select-single .ant-select-selector,
|
|
40
|
-
&.ant-select-single .ant-select-selection-search-input {
|
|
41
|
-
height:48px;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
&.ant-select-single .ant-select-selection-item,
|
|
45
|
-
&.ant-select-single .ant-select-selection-placeholder {
|
|
46
|
-
line-height:46px;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
&.ant-select-multiple.ant-select-lg {
|
|
50
|
-
.ant-select-arrow {
|
|
51
|
-
top: 23px
|
|
52
|
-
}
|
|
53
|
-
.ant-select-selector {
|
|
54
|
-
padding: 8px;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.ant-select-selector::after,
|
|
58
|
-
.ant-select-selection-item,
|
|
59
|
-
.ant-select-selection-search,
|
|
60
|
-
.ant-select-selection-search-input {
|
|
61
|
-
height: 24px;
|
|
62
|
-
line-height: 22px;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
`, (props) => !!props.selectorStyle && css(props.selectorStyle), (props) => !!props.withPrefixel && withPrefixStyles(), (props) => !!props.withSuffixel && withSuffixStyles(), (props) => `url("${searchIconWithCustomColor(props.theme.palette["grey-400"])}")`, (props) => errorStyle(props), (props) => props.theme.palette["red-050"], (props) => props.readOnly ? props.theme.palette["grey-600"] : props.theme.palette["grey-400"], (props) => props.readOnly ? "default" : "not-allowed", (props) => props.readOnly ? props.theme.palette.white : props.theme.palette["grey-050"]);
|
|
15
|
+
})(["display:flex;width:100%;"]);
|
|
67
16
|
const PrefixWrapper = /* @__PURE__ */ styled.div.withConfig({
|
|
68
17
|
displayName: "Selectstyles__PrefixWrapper",
|
|
69
18
|
componentId: "sc-n9lk0v-2"
|
|
70
|
-
})(["border-radius:3px 0 0 3px;margin-right:-2px;padding-right:1px;", ";"],
|
|
19
|
+
})(["border-radius:3px 0 0 3px;margin-right:-2px;padding-right:1px;", ";"], addonStyles);
|
|
71
20
|
const SuffixWrapper = /* @__PURE__ */ styled.div.withConfig({
|
|
72
21
|
displayName: "Selectstyles__SuffixWrapper",
|
|
73
22
|
componentId: "sc-n9lk0v-3"
|
|
74
|
-
})(["border-radius:0 3px 3px 0;margin-left:-1px;", ";"],
|
|
75
|
-
const
|
|
76
|
-
displayName: "
|
|
23
|
+
})(["border-radius:0 3px 3px 0;margin-left:-1px;", ";"], addonStyles);
|
|
24
|
+
const Selector = /* @__PURE__ */ styled.div.withConfig({
|
|
25
|
+
displayName: "Selectstyles__Selector",
|
|
77
26
|
componentId: "sc-n9lk0v-4"
|
|
78
|
-
})(["display:flex;", "
|
|
27
|
+
})(["position:relative;display:flex;align-items:center;width:100%;min-width:0;box-sizing:border-box;", " border:1px solid ", ";border-radius:3px;background-color:", ";color:", ";font-size:13px;cursor:pointer;transition:all 0.3s;", " ", " ", " ", " ", " ", " ", " ", ""], (props) => {
|
|
28
|
+
const h = HEIGHT[props.$size ?? "default"] ?? HEIGHT.default;
|
|
29
|
+
return props.$multiple ? css(["min-height:", "px;height:auto;padding:3px 30px 3px 8px;flex-wrap:wrap;gap:4px;"], h) : css(["height:", "px;padding:0 30px 0 12px;"], h);
|
|
30
|
+
}, (props) => props.theme.palette["grey-300"], (props) => props.$grey ? props.theme.palette["grey-050"] : props.theme.palette.white, (props) => props.theme.palette["grey-700"], (props) => props.$withPrefixel && css(["border-top-left-radius:0;border-bottom-left-radius:0;"]), (props) => props.$withSuffixel && css(["border-top-right-radius:0;border-bottom-right-radius:0;"]), (props) => !props.$open && !props.$error && css(["&:hover{border-color:", ";}"], props.theme.palette["grey-400"]), (props) => props.$clearable && css(["&:hover .ds-select-arrow{display:none;}&:hover .ds-select-clear{display:flex;}"]), (props) => props.$open && css(["box-shadow:inset 0 0 0 1px ", ";border-color:", ";background-color:", ";"], props.theme.palette["blue-600"], props.theme.palette["blue-600"], props.theme.palette["blue-050"]), (props) => props.$error && css(["border-color:", ";box-shadow:inset 0 0 0 1px ", ";background:", ";"], props.theme.palette["red-600"], props.theme.palette["red-600"], props.theme.palette["red-050"]), (props) => props.$disabled && css(["cursor:", ";color:", ";background-color:", ";&:hover{border-color:", ";}"], props.$readOnly ? "default" : "not-allowed", props.$readOnly ? props.theme.palette["grey-600"] : props.theme.palette["grey-400"], props.$readOnly ? props.theme.palette.white : props.theme.palette["grey-050"], props.theme.palette["grey-300"]), (props) => !!props.$selectorStyle && css(props.$selectorStyle));
|
|
31
|
+
const SelectionItem = /* @__PURE__ */ styled.span.withConfig({
|
|
32
|
+
displayName: "Selectstyles__SelectionItem",
|
|
33
|
+
componentId: "sc-n9lk0v-5"
|
|
34
|
+
})(["flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;"]);
|
|
35
|
+
const Placeholder = /* @__PURE__ */ styled.span.withConfig({
|
|
36
|
+
displayName: "Selectstyles__Placeholder",
|
|
37
|
+
componentId: "sc-n9lk0v-6"
|
|
38
|
+
})(["flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:", ";"], (props) => props.theme.palette["grey-500"]);
|
|
39
|
+
const Arrow = /* @__PURE__ */ styled.span.withConfig({
|
|
40
|
+
displayName: "Selectstyles__Arrow",
|
|
41
|
+
componentId: "sc-n9lk0v-7"
|
|
42
|
+
})(["position:absolute;right:8px;top:50%;display:flex;align-items:center;transform:translateY(-50%) ", ";transition:transform 0.3s;color:", ";pointer-events:none;"], (props) => props.$open ? "rotate(180deg)" : "", (props) => props.theme.palette["grey-500"]);
|
|
43
|
+
const ClearWrapper = /* @__PURE__ */ styled.span.withConfig({
|
|
44
|
+
displayName: "Selectstyles__ClearWrapper",
|
|
45
|
+
componentId: "sc-n9lk0v-8"
|
|
46
|
+
})(["position:absolute;right:8px;top:50%;transform:translateY(-50%);display:none;align-items:center;cursor:pointer;color:", ";"], (props) => props.theme.palette["red-600"]);
|
|
47
|
+
const DropdownWrapper = /* @__PURE__ */ styled.div.withConfig({
|
|
48
|
+
displayName: "Selectstyles__DropdownWrapper",
|
|
49
|
+
componentId: "sc-n9lk0v-9"
|
|
50
|
+
})(["display:flex;flex-direction:column;align-items:stretch;min-width:120px;"]);
|
|
51
|
+
const ScrollList = /* @__PURE__ */ styled(ListWrapper).withConfig({
|
|
52
|
+
displayName: "Selectstyles__ScrollList",
|
|
53
|
+
componentId: "sc-n9lk0v-10"
|
|
54
|
+
})(["&&{padding-right:0;}"]);
|
|
55
|
+
const Inner = /* @__PURE__ */ styled.div.withConfig({
|
|
56
|
+
displayName: "Selectstyles__Inner",
|
|
57
|
+
componentId: "sc-n9lk0v-11"
|
|
58
|
+
})(["padding-right:8px;"]);
|
|
59
|
+
const NotFound = /* @__PURE__ */ styled.div.withConfig({
|
|
60
|
+
displayName: "Selectstyles__NotFound",
|
|
61
|
+
componentId: "sc-n9lk0v-12"
|
|
62
|
+
})(["display:flex;align-items:center;justify-content:center;padding:8px 12px;color:", ";font-weight:normal;"], (props) => props.theme.palette["grey-600"]);
|
|
63
|
+
const Loading = /* @__PURE__ */ styled.div.withConfig({
|
|
64
|
+
displayName: "Selectstyles__Loading",
|
|
65
|
+
componentId: "sc-n9lk0v-13"
|
|
66
|
+
})(["display:flex;align-items:center;justify-content:center;padding:16px;"]);
|
|
67
|
+
const OptionItem = /* @__PURE__ */ styled(DSListItem).withConfig({
|
|
68
|
+
displayName: "Selectstyles__OptionItem",
|
|
69
|
+
componentId: "sc-n9lk0v-14"
|
|
70
|
+
})(["min-width:auto;font-weight:normal;&&:hover:not(.ds-list-item-disabled),&&.ds-select-item-option-active:not(.ds-list-item-disabled){background-color:", ";}"], (props) => props.theme.palette["blue-050"]);
|
|
71
|
+
const SearchInputEl = /* @__PURE__ */ styled.input.withConfig({
|
|
72
|
+
displayName: "Selectstyles__SearchInputEl",
|
|
73
|
+
componentId: "sc-n9lk0v-15"
|
|
74
|
+
})(["flex:1 1 30px;min-width:30px;width:100%;border:none;outline:none;background:transparent;padding:0;margin:0;font-family:inherit;font-size:13px;color:", ";cursor:inherit;&::placeholder{color:", ";}&:disabled{cursor:not-allowed;}", ""], (props) => props.theme.palette["grey-700"], (props) => props.theme.palette["grey-500"], (props) => props.$overlay && css(["position:absolute;top:0;left:0;width:100%;height:100%;flex:none;box-sizing:border-box;padding:0 30px 0 12px;"]));
|
|
75
|
+
const MultiValueArea = /* @__PURE__ */ styled.div.withConfig({
|
|
76
|
+
displayName: "Selectstyles__MultiValueArea",
|
|
77
|
+
componentId: "sc-n9lk0v-16"
|
|
78
|
+
})(["display:flex;flex-wrap:wrap;align-items:center;gap:4px;flex:1;min-width:0;", ""], (props) => props.$responsive && css(["position:relative;flex-wrap:nowrap;overflow:hidden;> .ds-select-selection-item,> .ds-select-selection-overflow{flex:0 0 auto;}> .ds-select-search{flex:1 1 0;min-width:0;}"]));
|
|
79
|
+
const TagMeasureRow = /* @__PURE__ */ styled.div.withConfig({
|
|
80
|
+
displayName: "Selectstyles__TagMeasureRow",
|
|
81
|
+
componentId: "sc-n9lk0v-17"
|
|
82
|
+
})(["position:absolute;top:0;left:0;width:100%;height:0;display:flex;flex-wrap:nowrap;gap:4px;overflow:hidden;visibility:hidden;pointer-events:none;> *{flex:0 0 auto;}"]);
|
|
83
|
+
const Chip = /* @__PURE__ */ styled.span.withConfig({
|
|
84
|
+
displayName: "Selectstyles__Chip",
|
|
85
|
+
componentId: "sc-n9lk0v-18"
|
|
86
|
+
})(["position:relative;display:inline-flex;align-items:center;box-sizing:border-box;width:max-content;max-width:100%;min-height:24px;padding:0 8px;background:", ";border:none;border-radius:3px;font-size:13px;line-height:1;color:", ";&:hover{background:", ";color:", ";}&:hover .ds-select-selection-item-label{max-width:calc(100% - 24px);}&:hover .ds-select-selection-item-remove{display:inline-flex;}"], (props) => props.theme.palette["grey-200"], (props) => props.theme.palette["grey-600"], (props) => props.theme.palette["grey-300"], (props) => props.theme.palette["grey-800"]);
|
|
87
|
+
const ChipLabel = /* @__PURE__ */ styled.span.withConfig({
|
|
88
|
+
displayName: "Selectstyles__ChipLabel",
|
|
89
|
+
componentId: "sc-n9lk0v-19"
|
|
90
|
+
})(["max-width:100%;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;"]);
|
|
91
|
+
const ChipRemove = /* @__PURE__ */ styled.span.withConfig({
|
|
92
|
+
displayName: "Selectstyles__ChipRemove",
|
|
93
|
+
componentId: "sc-n9lk0v-20"
|
|
94
|
+
})(["position:absolute;right:4px;top:50%;transform:translateY(-50%);display:none;align-items:center;justify-content:center;width:24px;height:24px;cursor:pointer;color:", ";"], (props) => props.theme.palette["red-600"]);
|
|
79
95
|
export {
|
|
80
|
-
|
|
96
|
+
Arrow,
|
|
97
|
+
Chip,
|
|
98
|
+
ChipLabel,
|
|
99
|
+
ChipRemove,
|
|
100
|
+
ClearWrapper,
|
|
101
|
+
DropdownWrapper,
|
|
102
|
+
Inner,
|
|
103
|
+
Loading,
|
|
104
|
+
MultiValueArea,
|
|
105
|
+
NotFound,
|
|
106
|
+
OptionItem,
|
|
107
|
+
Placeholder,
|
|
81
108
|
PrefixWrapper,
|
|
109
|
+
ScrollList,
|
|
110
|
+
SearchInputEl,
|
|
82
111
|
SelectContainer,
|
|
83
112
|
SelectWrapper,
|
|
84
|
-
|
|
113
|
+
SelectionItem,
|
|
114
|
+
Selector,
|
|
115
|
+
SuffixWrapper,
|
|
116
|
+
TagMeasureRow
|
|
85
117
|
};
|
package/dist/Select.types.d.ts
CHANGED
|
@@ -1,18 +1,148 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SelectValue } from 'antd/lib/select';
|
|
3
|
-
import { ReactNode, ReactText } from 'react';
|
|
1
|
+
import { AriaAttributes, CSSProperties, FocusEvent, Key, KeyboardEvent, MouseEvent, ReactElement, ReactNode, UIEvent } from 'react';
|
|
4
2
|
import { CSSObject } from 'styled-components';
|
|
3
|
+
import { DropdownPlacement } from '@synerise/ds-dropdown';
|
|
5
4
|
import { FormFieldCommonProps } from '@synerise/ds-form-field';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The selected value. A plain value for single-select, an array for
|
|
7
|
+
* `mode="multiple"` / `mode="tags"`. Replaces antd's `SelectValue`.
|
|
8
|
+
*/
|
|
9
|
+
export type RawValueType = string | number;
|
|
10
|
+
export type SelectValue = RawValueType | RawValueType[] | undefined;
|
|
11
|
+
export type SelectMode = 'multiple' | 'tags';
|
|
12
|
+
/** Internal, normalised option shape (from `options` prop or `<Select.Option>` children). */
|
|
13
|
+
export type SelectOption = {
|
|
14
|
+
value: RawValueType;
|
|
15
|
+
/** antd-parity alias of `value`; some consumers read `option.key` in callbacks. */
|
|
16
|
+
key?: RawValueType;
|
|
17
|
+
/** Rendered content in the dropdown row (and the selector unless `optionLabelProp` picks another field). */
|
|
18
|
+
label?: ReactNode;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/** Native `title` on the option (hover tooltip / accessible text), mirrors antd. */
|
|
21
|
+
title?: ReactNode;
|
|
22
|
+
/** antd parity: raw children of `<Select.Option>`; some consumers read `option.children`. */
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
/** antd parity: forwarded to the rendered option row. */
|
|
25
|
+
style?: CSSProperties;
|
|
26
|
+
/** Value used for client-side filtering when `optionFilterProp` is set. */
|
|
27
|
+
filterValue?: string;
|
|
28
|
+
/** antd parity: per-option `data-*` / `aria-*` attributes, forwarded to the rendered option row. */
|
|
29
|
+
[dataAttr: `data-${string}`]: unknown;
|
|
30
|
+
[ariaAttr: `aria-${string}`]: unknown;
|
|
31
|
+
};
|
|
32
|
+
/** Signature of a client-side option filter (antd-compatible). */
|
|
33
|
+
export type FilterOptionFn = (input: string, option: SelectOption) => boolean;
|
|
34
|
+
/** antd-parity: type for a select `onChange` / `onSelect` handler. */
|
|
35
|
+
export type SelectHandler<VT extends RawValueType = RawValueType> = (value: VT, option?: SelectOption | SelectOption[]) => void;
|
|
36
|
+
export type SelectProps<VT extends SelectValue = SelectValue> = {
|
|
37
|
+
value?: VT;
|
|
38
|
+
defaultValue?: VT;
|
|
39
|
+
onChange?: (value: VT, option?: SelectOption | SelectOption[]) => void;
|
|
40
|
+
onSelect?: (value: RawValueType, option?: SelectOption) => void;
|
|
41
|
+
onDeselect?: (value: RawValueType, option?: SelectOption) => void;
|
|
42
|
+
onBlur?: (event: FocusEvent<HTMLElement>) => void;
|
|
43
|
+
onFocus?: (event: FocusEvent<HTMLElement>) => void;
|
|
44
|
+
onDropdownVisibleChange?: (open: boolean) => void;
|
|
45
|
+
/** Remote-search callback (pair with `filterOption={false}`). */
|
|
46
|
+
onSearch?: (value: string) => void;
|
|
47
|
+
/** antd parity: fired as the dropdown option list scrolls (e.g. to page in more options). */
|
|
48
|
+
onPopupScroll?: (event: UIEvent<HTMLDivElement>) => void;
|
|
49
|
+
/** antd parity: fired when the clear affordance is used. */
|
|
50
|
+
onClear?: () => void;
|
|
51
|
+
/** antd parity: click handler on the selector box. */
|
|
52
|
+
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
53
|
+
/** antd parity: keydown on the inner search input. */
|
|
54
|
+
onInputKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
55
|
+
/** antd parity: keydown on the selector. */
|
|
56
|
+
onKeyDown?: (event: KeyboardEvent<HTMLElement>) => void;
|
|
57
|
+
/** `undefined` = single-select; `'multiple'` / `'tags'` = multi-value. */
|
|
58
|
+
mode?: SelectMode;
|
|
59
|
+
/** Options as data. When omitted, `<Select.Option>` children are read instead. */
|
|
60
|
+
options?: SelectOption[];
|
|
61
|
+
showSearch?: boolean;
|
|
62
|
+
/** Controlled search-input value (antd parity). Pairs with `onSearch`. */
|
|
63
|
+
searchValue?: string;
|
|
64
|
+
/** antd parity: max length of the search input. */
|
|
65
|
+
maxLength?: number;
|
|
66
|
+
/** `true`/`false` toggles built-in filtering; a function is a custom predicate. */
|
|
67
|
+
filterOption?: boolean | FilterOptionFn;
|
|
68
|
+
/** Which option field the built-in filter matches against (default: label). */
|
|
69
|
+
optionFilterProp?: string;
|
|
70
|
+
/** Which option field renders in the selector (default: label). */
|
|
71
|
+
optionLabelProp?: string;
|
|
72
|
+
placeholder?: ReactNode;
|
|
73
|
+
disabled?: boolean;
|
|
74
|
+
loading?: boolean;
|
|
75
|
+
allowClear?: boolean;
|
|
76
|
+
autoFocus?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* antd parity: accepted for back-compat but NOT implemented. The dropdown
|
|
79
|
+
* always highlights the selected (or first enabled) option on open, so this
|
|
80
|
+
* flag has no effect.
|
|
81
|
+
*/
|
|
82
|
+
defaultActiveFirstOption?: boolean;
|
|
83
|
+
showArrow?: boolean;
|
|
84
|
+
/** antd parity: custom icon replacing the dropdown arrow. */
|
|
85
|
+
suffixIcon?: ReactNode;
|
|
86
|
+
/** antd parity: tab index forwarded to the selector / search input. */
|
|
87
|
+
tabIndex?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Max chips rendered in `multiple` / `tags` mode before the rest collapse into
|
|
90
|
+
* an overflow chip. `'responsive'` (antd parity) instead fits as many chips as
|
|
91
|
+
* the selector width allows on a single line and recomputes on resize.
|
|
92
|
+
*/
|
|
93
|
+
maxTagCount?: number | 'responsive';
|
|
94
|
+
/** Max characters shown per chip label; longer labels are truncated with an ellipsis. */
|
|
95
|
+
maxTagTextLength?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Content of the collapsed-overflow chip (antd parity). A node renders as-is;
|
|
98
|
+
* a function receives the omitted options and returns the node. Defaults to `+ N`.
|
|
99
|
+
*/
|
|
100
|
+
maxTagPlaceholder?: ReactNode | ((omittedValues: Array<{
|
|
101
|
+
value: RawValueType;
|
|
102
|
+
label: ReactNode;
|
|
103
|
+
}>) => ReactNode);
|
|
104
|
+
/** Characters that split typed text into tags in `mode="tags"`. */
|
|
105
|
+
tokenSeparators?: string[];
|
|
106
|
+
open?: boolean;
|
|
107
|
+
defaultOpen?: boolean;
|
|
108
|
+
/** Dropdown positioning / sizing. antd parity: consumers may return `parentNode` (`ParentNode | null`). */
|
|
109
|
+
getPopupContainer?: (trigger: HTMLElement) => HTMLElement | ParentNode | null;
|
|
110
|
+
placement?: DropdownPlacement;
|
|
111
|
+
dropdownClassName?: string;
|
|
112
|
+
/** antd v4 alias of `dropdownClassName`. */
|
|
113
|
+
popupClassName?: string;
|
|
114
|
+
/** antd parity: dropdown alignment config (accepted for compatibility). */
|
|
115
|
+
dropdownAlign?: Record<string, unknown>;
|
|
116
|
+
dropdownStyle?: CSSProperties;
|
|
117
|
+
/** Match the dropdown width to the selector (default true). A number fixes the dropdown width in px (antd parity). */
|
|
118
|
+
dropdownMatchSelectWidth?: boolean | number;
|
|
119
|
+
/** Wrap the rendered option menu (custom footer / scroll container). antd parity: receives a `ReactElement`. */
|
|
120
|
+
dropdownRender?: (menu: ReactElement) => ReactNode;
|
|
121
|
+
/** Max dropdown list height (px). */
|
|
122
|
+
listHeight?: number | string;
|
|
123
|
+
/** antd parity: fixed height per option row (px). Accepted for compatibility (non-virtualised list). */
|
|
124
|
+
listItemHeight?: number;
|
|
125
|
+
notFoundContent?: ReactNode;
|
|
126
|
+
clearIcon?: ReactNode;
|
|
127
|
+
/** Item key extractor for `<Select.Option>` children / options. */
|
|
128
|
+
rowKey?: (option: SelectOption) => Key;
|
|
9
129
|
prefixel?: ReactNode;
|
|
10
130
|
suffixel?: ReactNode;
|
|
11
|
-
listHeight?: ReactText;
|
|
12
131
|
grey?: boolean;
|
|
13
|
-
|
|
14
|
-
selectorStyle?: CSSObject;
|
|
132
|
+
/** Render just the selector (no FormField chrome). */
|
|
15
133
|
raw?: boolean;
|
|
16
134
|
readOnly?: boolean;
|
|
17
|
-
|
|
18
|
-
|
|
135
|
+
asFormElement?: boolean;
|
|
136
|
+
selectorStyle?: CSSObject;
|
|
137
|
+
clearTooltip?: string;
|
|
138
|
+
size?: 'middle' | 'large' | 'default';
|
|
139
|
+
error?: boolean;
|
|
140
|
+
id?: string;
|
|
141
|
+
className?: string;
|
|
142
|
+
style?: CSSProperties;
|
|
143
|
+
children?: ReactNode;
|
|
144
|
+
/** antd parity: forward native `data-*` attributes (e.g. `data-testid`) to the select root. */
|
|
145
|
+
[dataAttr: `data-${string}`]: unknown;
|
|
146
|
+
} & FormFieldCommonProps & AriaAttributes;
|
|
147
|
+
/** @deprecated internal alias kept for back-compat with the antd-era `Props` name. */
|
|
148
|
+
export type Props<VT extends SelectValue = SelectValue> = SelectProps<VT>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Key, ReactElement, ReactNode, UIEvent } from 'react';
|
|
2
|
+
import { RawValueType, SelectOption } from '../Select.types';
|
|
3
|
+
type OptionListProps = {
|
|
4
|
+
loading?: boolean;
|
|
5
|
+
/** Options to render (already filtered / with the tags create-row). */
|
|
6
|
+
options: SelectOption[];
|
|
7
|
+
notFoundContent?: ReactNode;
|
|
8
|
+
listHeight?: number | string;
|
|
9
|
+
isMultiple: boolean;
|
|
10
|
+
listboxId: string;
|
|
11
|
+
selectedValues: RawValueType[];
|
|
12
|
+
/** Keyboard-highlighted option index. */
|
|
13
|
+
activeIndex: number;
|
|
14
|
+
rowKey?: (option: SelectOption) => Key;
|
|
15
|
+
optionDomId: (index: number) => string;
|
|
16
|
+
onOptionActivate: (index: number) => void;
|
|
17
|
+
onOptionSelect: (option: SelectOption) => void;
|
|
18
|
+
/** antd parity: fired as the option list scrolls (e.g. to page in more options). */
|
|
19
|
+
onPopupScroll?: (event: UIEvent<HTMLDivElement>) => void;
|
|
20
|
+
};
|
|
21
|
+
/** The dropdown overlay: loading / empty / the scrollable listbox of options. */
|
|
22
|
+
export declare const OptionList: ({ loading, options, notFoundContent, listHeight, isMultiple, listboxId, selectedValues, activeIndex, rowKey, optionDomId, onOptionActivate, onOptionSelect, onPopupScroll, }: OptionListProps) => ReactElement;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import Loader from "@synerise/ds-loader";
|
|
3
|
+
import Scrollbar from "@synerise/ds-scrollbar";
|
|
4
|
+
import { DropdownWrapper, Loading, NotFound, ScrollList, Inner, OptionItem } from "../Select.styles.js";
|
|
5
|
+
import { DEFAULT_LIST_HEIGHT, cx } from "../utils/helpers.js";
|
|
6
|
+
const OptionList = ({
|
|
7
|
+
loading,
|
|
8
|
+
options,
|
|
9
|
+
notFoundContent,
|
|
10
|
+
listHeight,
|
|
11
|
+
isMultiple,
|
|
12
|
+
listboxId,
|
|
13
|
+
selectedValues,
|
|
14
|
+
activeIndex,
|
|
15
|
+
rowKey,
|
|
16
|
+
optionDomId,
|
|
17
|
+
onOptionActivate,
|
|
18
|
+
onOptionSelect,
|
|
19
|
+
onPopupScroll
|
|
20
|
+
}) => /* @__PURE__ */ jsx(DropdownWrapper, { onMouseDown: (event) => {
|
|
21
|
+
const target = event.target;
|
|
22
|
+
if (!target.closest('input, textarea, [contenteditable="true"]')) {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
}
|
|
25
|
+
}, children: loading ? /* @__PURE__ */ jsx(Loading, { className: "ds-select-loading", children: /* @__PURE__ */ jsx(Loader, { size: "M" }) }) : options.length === 0 ? /* @__PURE__ */ jsx(NotFound, { className: "ds-select-empty", children: notFoundContent }) : /* @__PURE__ */ jsx(ScrollList, { children: /* @__PURE__ */ jsx(Scrollbar, { absolute: true, maxHeight: Number(listHeight) || DEFAULT_LIST_HEIGHT, onScroll: onPopupScroll ? (event) => onPopupScroll(event) : void 0, children: /* @__PURE__ */ jsx(Inner, { role: "listbox", id: listboxId, "aria-multiselectable": isMultiple || void 0, children: options.map((option, index) => {
|
|
26
|
+
const isSelected = selectedValues.includes(option.value);
|
|
27
|
+
const optionAttrs = Object.fromEntries(Object.entries(option).filter(([key]) => key.startsWith("data-") || key.startsWith("aria-")));
|
|
28
|
+
return /* @__PURE__ */ jsx(OptionItem, { id: optionDomId(index), role: "option", className: cx("ds-select-item-option", isSelected && "ds-select-item-option-selected", index === activeIndex && "ds-select-item-option-active"), selected: isSelected, "aria-selected": isSelected, "data-testid": "select-option", title: typeof option.title === "string" ? option.title : void 0, text: option.label ?? option.value, style: option.style, disabled: option.disabled, onMouseEnter: () => onOptionActivate(index), onClick: () => onOptionSelect(option), ...optionAttrs }, rowKey ? rowKey(option) : option.value);
|
|
29
|
+
}) }) }) }) });
|
|
30
|
+
export {
|
|
31
|
+
OptionList
|
|
32
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ChangeEvent, KeyboardEvent, ReactElement, ReactNode, RefObject } from 'react';
|
|
2
|
+
import { RawValueType } from '../Select.types';
|
|
3
|
+
type SelectorContentProps = {
|
|
4
|
+
isMultiple: boolean;
|
|
5
|
+
showSearch?: boolean;
|
|
6
|
+
hasValue: boolean;
|
|
7
|
+
placeholder?: ReactNode;
|
|
8
|
+
placeholderStr?: string;
|
|
9
|
+
selectedValues: RawValueType[];
|
|
10
|
+
effectiveQuery: string;
|
|
11
|
+
labelFor: (value: RawValueType) => ReactNode;
|
|
12
|
+
onRemoveValue: (value: RawValueType) => void;
|
|
13
|
+
maxTagCount?: number | 'responsive';
|
|
14
|
+
maxTagTextLength?: number;
|
|
15
|
+
maxTagPlaceholder?: ReactNode | ((omittedValues: Array<{
|
|
16
|
+
value: RawValueType;
|
|
17
|
+
label: ReactNode;
|
|
18
|
+
}>) => ReactNode);
|
|
19
|
+
inputRef: RefObject<HTMLInputElement>;
|
|
20
|
+
isDisabled: boolean;
|
|
21
|
+
hasInput: boolean;
|
|
22
|
+
autoFocus?: boolean;
|
|
23
|
+
maxLength?: number;
|
|
24
|
+
id?: string;
|
|
25
|
+
isOpen: boolean;
|
|
26
|
+
listboxId: string;
|
|
27
|
+
activeIndex: number;
|
|
28
|
+
optionDomId: (index: number) => string;
|
|
29
|
+
onSearchChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
30
|
+
onSearchKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
31
|
+
};
|
|
32
|
+
/** Content rendered inside the selector box: chips / selected label / placeholder + search input. */
|
|
33
|
+
export declare const SelectorContent: ({ isMultiple, showSearch, hasValue, placeholder, placeholderStr, selectedValues, effectiveQuery, labelFor, onRemoveValue, maxTagCount, maxTagTextLength, maxTagPlaceholder, inputRef, isDisabled, hasInput, autoFocus, maxLength, id, isOpen, listboxId, activeIndex, optionDomId, onSearchChange, onSearchKeyDown, }: SelectorContentProps) => ReactElement;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import Icon, { CloseS } from "@synerise/ds-icon";
|
|
3
|
+
import { SearchInputEl, MultiValueArea, TagMeasureRow, Chip, ChipLabel, ChipRemove, SelectionItem, Placeholder } from "../Select.styles.js";
|
|
4
|
+
import { useResponsiveTagCount } from "../hooks/useResponsiveTagCount.js";
|
|
5
|
+
const SelectorContent = ({
|
|
6
|
+
isMultiple,
|
|
7
|
+
showSearch,
|
|
8
|
+
hasValue,
|
|
9
|
+
placeholder,
|
|
10
|
+
placeholderStr,
|
|
11
|
+
selectedValues,
|
|
12
|
+
effectiveQuery,
|
|
13
|
+
labelFor,
|
|
14
|
+
onRemoveValue,
|
|
15
|
+
maxTagCount,
|
|
16
|
+
maxTagTextLength,
|
|
17
|
+
maxTagPlaceholder,
|
|
18
|
+
inputRef,
|
|
19
|
+
isDisabled,
|
|
20
|
+
hasInput,
|
|
21
|
+
autoFocus,
|
|
22
|
+
maxLength,
|
|
23
|
+
id,
|
|
24
|
+
isOpen,
|
|
25
|
+
listboxId,
|
|
26
|
+
activeIndex,
|
|
27
|
+
optionDomId,
|
|
28
|
+
onSearchChange,
|
|
29
|
+
onSearchKeyDown
|
|
30
|
+
}) => {
|
|
31
|
+
const isSingleSearch = !!showSearch && !isMultiple;
|
|
32
|
+
const isResponsive = maxTagCount === "responsive";
|
|
33
|
+
const {
|
|
34
|
+
areaRef,
|
|
35
|
+
measureRef,
|
|
36
|
+
visibleCount
|
|
37
|
+
} = useResponsiveTagCount({
|
|
38
|
+
enabled: isResponsive && isMultiple,
|
|
39
|
+
items: selectedValues,
|
|
40
|
+
reserveInput: !isDisabled
|
|
41
|
+
});
|
|
42
|
+
const searchInput = /* @__PURE__ */ jsx(SearchInputEl, { ref: inputRef, $overlay: isSingleSearch, className: "ds-select-search", value: effectiveQuery, onChange: onSearchChange, onKeyDown: onSearchKeyDown, placeholder: hasValue ? void 0 : placeholderStr, disabled: isDisabled, autoFocus, maxLength, autoComplete: "off", readOnly: !hasInput, id, role: "combobox", "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": isOpen ? listboxId : void 0, "aria-activedescendant": isOpen && activeIndex >= 0 ? optionDomId(activeIndex) : void 0 });
|
|
43
|
+
if (isMultiple) {
|
|
44
|
+
const displayCount = (() => {
|
|
45
|
+
if (isResponsive) {
|
|
46
|
+
return visibleCount ?? selectedValues.length;
|
|
47
|
+
}
|
|
48
|
+
if (typeof maxTagCount === "number") {
|
|
49
|
+
return Math.max(maxTagCount, 0);
|
|
50
|
+
}
|
|
51
|
+
return selectedValues.length;
|
|
52
|
+
})();
|
|
53
|
+
const displayedValues = selectedValues.slice(0, displayCount);
|
|
54
|
+
const omittedValues = selectedValues.slice(displayedValues.length);
|
|
55
|
+
const chipLabel = (v) => {
|
|
56
|
+
const label = labelFor(v);
|
|
57
|
+
if (typeof maxTagTextLength === "number" && typeof label === "string" && label.length > maxTagTextLength) {
|
|
58
|
+
return `${label.slice(0, maxTagTextLength)}...`;
|
|
59
|
+
}
|
|
60
|
+
return label;
|
|
61
|
+
};
|
|
62
|
+
const overflowContentFor = (omitted) => typeof maxTagPlaceholder === "function" ? maxTagPlaceholder(omitted.map((v) => ({
|
|
63
|
+
value: v,
|
|
64
|
+
label: labelFor(v)
|
|
65
|
+
}))) : maxTagPlaceholder ?? `+ ${omitted.length}`;
|
|
66
|
+
const overflowContent = overflowContentFor(omittedValues);
|
|
67
|
+
return /* @__PURE__ */ jsxs(MultiValueArea, { ref: areaRef, className: "ds-select-selection-list", $responsive: isResponsive, children: [
|
|
68
|
+
isResponsive && /* @__PURE__ */ jsxs(TagMeasureRow, { ref: measureRef, "aria-hidden": true, children: [
|
|
69
|
+
selectedValues.map((v) => /* @__PURE__ */ jsx(Chip, { "data-measure-chip": "", children: /* @__PURE__ */ jsx(ChipLabel, { children: chipLabel(v) }) }, v)),
|
|
70
|
+
/* @__PURE__ */ jsx(Chip, { "data-measure-overflow": "", children: /* @__PURE__ */ jsx(ChipLabel, { children: overflowContentFor(selectedValues) }) })
|
|
71
|
+
] }),
|
|
72
|
+
displayedValues.map((v) => /* @__PURE__ */ jsxs(Chip, { className: "ds-select-selection-item", children: [
|
|
73
|
+
/* @__PURE__ */ jsx(ChipLabel, { className: "ds-select-selection-item-label", children: chipLabel(v) }),
|
|
74
|
+
/* @__PURE__ */ jsx(ChipRemove, { className: "ds-select-selection-item-remove", onMouseDown: (e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
onRemoveValue(v);
|
|
78
|
+
}, children: /* @__PURE__ */ jsx(Icon, { component: /* @__PURE__ */ jsx(CloseS, {}), size: 24 }) })
|
|
79
|
+
] }, v)),
|
|
80
|
+
omittedValues.length > 0 && /* @__PURE__ */ jsx(Chip, { className: "ds-select-selection-overflow", "data-testid": "select-tag-overflow", children: /* @__PURE__ */ jsx(ChipLabel, { className: "ds-select-selection-item-label", children: overflowContent }) }),
|
|
81
|
+
searchInput
|
|
82
|
+
] });
|
|
83
|
+
}
|
|
84
|
+
if (showSearch) {
|
|
85
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
86
|
+
hasValue && !effectiveQuery && /* @__PURE__ */ jsx(SelectionItem, { className: "ds-select-selection-item", children: labelFor(selectedValues[0]) }),
|
|
87
|
+
searchInput
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
if (hasValue) {
|
|
91
|
+
return /* @__PURE__ */ jsx(SelectionItem, { className: "ds-select-selection-item", children: labelFor(selectedValues[0]) });
|
|
92
|
+
}
|
|
93
|
+
return /* @__PURE__ */ jsx(Placeholder, { className: "ds-select-selection-placeholder", children: placeholder });
|
|
94
|
+
};
|
|
95
|
+
export {
|
|
96
|
+
SelectorContent
|
|
97
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
import { RawValueType } from '../Select.types';
|
|
3
|
+
type UseResponsiveTagCountParams = {
|
|
4
|
+
/** `maxTagCount === 'responsive'` in a multi-value mode. */
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
/** Every selected value, in render order — one ghost chip each. */
|
|
7
|
+
items: RawValueType[];
|
|
8
|
+
/** Keep room for the search caret (skipped when disabled / readOnly). */
|
|
9
|
+
reserveInput: boolean;
|
|
10
|
+
};
|
|
11
|
+
type UseResponsiveTagCountResult = {
|
|
12
|
+
/** Attach to the chip row — its width is the space chips must fit into. */
|
|
13
|
+
areaRef: RefObject<HTMLDivElement>;
|
|
14
|
+
/** Attach to the off-flow row holding one ghost chip per value + the overflow chip. */
|
|
15
|
+
measureRef: RefObject<HTMLDivElement>;
|
|
16
|
+
/** Chips that fit on one line; `null` while unmeasured (SSR, hidden, zero-width) — render all. */
|
|
17
|
+
visibleCount: number | null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Fit-to-width chip count for `maxTagCount="responsive"` (antd / rc-overflow parity).
|
|
21
|
+
*
|
|
22
|
+
* Widths come from an off-flow ghost row rather than the visible chips: hiding a
|
|
23
|
+
* chip would zero its width, and the ghost's own size never depends on the count
|
|
24
|
+
* we derive from it — so the ResizeObserver can't feed itself.
|
|
25
|
+
*/
|
|
26
|
+
export declare const useResponsiveTagCount: ({ enabled, items, reserveInput, }: UseResponsiveTagCountParams) => UseResponsiveTagCountResult;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useRef, useState, useCallback, useEffect, useLayoutEffect } from "react";
|
|
2
|
+
const CHIP_GAP = 4;
|
|
3
|
+
const INPUT_RESERVE = 30;
|
|
4
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
5
|
+
const useResponsiveTagCount = ({
|
|
6
|
+
enabled,
|
|
7
|
+
items,
|
|
8
|
+
reserveInput
|
|
9
|
+
}) => {
|
|
10
|
+
const areaRef = useRef(null);
|
|
11
|
+
const measureRef = useRef(null);
|
|
12
|
+
const [visibleCount, setVisibleCount] = useState(null);
|
|
13
|
+
const itemCount = items.length;
|
|
14
|
+
const itemsKey = items.join("\0");
|
|
15
|
+
const measure = useCallback(() => {
|
|
16
|
+
const area = areaRef.current;
|
|
17
|
+
const row = measureRef.current;
|
|
18
|
+
if (!enabled || !area || !row) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const areaWidth = area.clientWidth;
|
|
22
|
+
if (areaWidth <= 0) {
|
|
23
|
+
setVisibleCount(null);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const ghosts = Array.from(row.children);
|
|
27
|
+
const widths = ghosts.slice(0, itemCount).map((chip) => chip.offsetWidth);
|
|
28
|
+
const overflowWidth = ghosts[itemCount]?.offsetWidth ?? 0;
|
|
29
|
+
const available = areaWidth - (reserveInput ? INPUT_RESERVE : 0);
|
|
30
|
+
const totalWidth = widths.reduce((sum, width, index) => sum + width + (index > 0 ? CHIP_GAP : 0), 0);
|
|
31
|
+
if (totalWidth <= available) {
|
|
32
|
+
setVisibleCount(itemCount);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let used = 0;
|
|
36
|
+
let count = 0;
|
|
37
|
+
for (let index = 0; index < widths.length; index += 1) {
|
|
38
|
+
const next = used + widths[index] + (count > 0 ? CHIP_GAP : 0);
|
|
39
|
+
if (next + CHIP_GAP + overflowWidth > available) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
used = next;
|
|
43
|
+
count += 1;
|
|
44
|
+
}
|
|
45
|
+
setVisibleCount(count === 0 && itemCount === 1 ? 1 : count);
|
|
46
|
+
}, [enabled, itemCount, reserveInput]);
|
|
47
|
+
useIsomorphicLayoutEffect(() => {
|
|
48
|
+
if (!enabled) {
|
|
49
|
+
setVisibleCount(null);
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
measure();
|
|
53
|
+
if (typeof ResizeObserver === "undefined") {
|
|
54
|
+
window.addEventListener("resize", measure);
|
|
55
|
+
return () => window.removeEventListener("resize", measure);
|
|
56
|
+
}
|
|
57
|
+
const observer = new ResizeObserver(() => measure());
|
|
58
|
+
if (areaRef.current) {
|
|
59
|
+
observer.observe(areaRef.current);
|
|
60
|
+
}
|
|
61
|
+
Array.from(measureRef.current?.children ?? []).forEach((ghost) => observer.observe(ghost));
|
|
62
|
+
return () => observer.disconnect();
|
|
63
|
+
}, [enabled, measure, itemsKey]);
|
|
64
|
+
return {
|
|
65
|
+
areaRef,
|
|
66
|
+
measureRef,
|
|
67
|
+
visibleCount
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export {
|
|
71
|
+
useResponsiveTagCount
|
|
72
|
+
};
|