@homecode/ui 5.6.1 → 5.7.1
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/esm/src/components/Autocomplete/Autocomplete.js +6 -3
- package/dist/esm/src/components/InputFile/InputFile.js +4 -1
- package/dist/esm/src/components/Popup/Popup.helpers.js +8 -1
- package/dist/esm/src/components/Popup/Popup.js +12 -6
- package/dist/esm/src/components/PromptComposer/PromptComposer.js +5 -1
- package/dist/esm/src/components/PromptComposer/promptComposerInsert.js +2 -1
- package/dist/esm/src/components/PromptComposer/usePromptComposerEditor.js +99 -21
- package/dist/esm/src/components/Select/Select.helpers.js +28 -1
- package/dist/esm/src/components/Select/Select.hooks.js +131 -0
- package/dist/esm/src/components/Select/Select2.js +213 -235
- package/dist/esm/src/components/Tooltip/Tooltip.styl.js +1 -1
- package/dist/esm/src/components/Tooltip/TooltipOverTrigger.js +4 -3
- package/dist/esm/src/components/Tooltip/tooltipTextStyles.js +17 -2
- package/dist/esm/src/tiptap/slash-mention/createSlashMentionExtension.js +51 -31
- package/dist/esm/src/tools/dom.js +6 -8
- package/dist/esm/src/tools/resizeObserver.js +1 -1
- package/dist/esm/types/src/components/Popup/Popup.d.ts +2 -1
- package/dist/esm/types/src/components/Popup/Popup.helpers.d.ts +1 -0
- package/dist/esm/types/src/components/Popup/Popup.types.d.ts +1 -0
- package/dist/esm/types/src/components/PromptComposer/PromptComposer.d.ts +4 -0
- package/dist/esm/types/src/components/PromptComposer/PromptComposer.types.d.ts +17 -3
- package/dist/esm/types/src/components/PromptComposer/index.d.ts +1 -1
- package/dist/esm/types/src/components/PromptComposer/usePromptComposerEditor.d.ts +6 -2
- package/dist/esm/types/src/components/Select/Select.helpers.d.ts +7 -1
- package/dist/esm/types/src/components/Select/Select.hooks.d.ts +55 -0
- package/dist/esm/types/src/components/Select/Select.types.d.ts +1 -1
- package/dist/esm/types/src/components/Select/Select2.test.d.ts +1 -0
- package/dist/esm/types/src/tiptap/slash-mention/createSlashMentionExtension.d.ts +1 -1
- package/dist/esm/types/src/tiptap/slash-mention/index.d.ts +1 -1
- package/dist/esm/types/src/tiptap/slash-mention/types.d.ts +12 -1
- package/graphify-out/.graphify_labels.json +18 -25
- package/graphify-out/2026-07-26/.graphify_labels.json +216 -0
- package/graphify-out/2026-07-26/GRAPH_REPORT.md +1036 -0
- package/graphify-out/2026-07-26/cost.json +12 -0
- package/graphify-out/2026-07-26/graph.json +71426 -0
- package/graphify-out/2026-07-26/manifest.json +2372 -0
- package/graphify-out/2026-07-27/.graphify_labels.json +214 -0
- package/graphify-out/2026-07-27/GRAPH_REPORT.md +1014 -0
- package/graphify-out/2026-07-27/cost.json +12 -0
- package/graphify-out/2026-07-27/graph.json +71778 -0
- package/graphify-out/2026-07-27/manifest.json +2372 -0
- package/graphify-out/2026-07-30/.graphify_labels.json +197 -0
- package/graphify-out/2026-07-30/GRAPH_REPORT.md +929 -0
- package/graphify-out/2026-07-30/cost.json +12 -0
- package/graphify-out/2026-07-30/graph.json +71798 -0
- package/graphify-out/2026-07-30/manifest.json +2372 -0
- package/graphify-out/GRAPH_REPORT.md +167 -249
- package/graphify-out/cache/ast/2690b69f91795ec634393c1fb8d9fd21ec09ec6759eaf6711e3b86c2a8edb385.json +1 -0
- package/graphify-out/cache/ast/2ae06c5acabf11eea845c93c2a8486317fbb18dccc1843ecd88077848af2ab29.json +1 -0
- package/graphify-out/cache/stat-index.json +1 -1
- package/graphify-out/graph.html +4 -4
- package/graphify-out/graph.json +3354 -2931
- package/graphify-out/manifest.json +47 -47
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useRef, useState,
|
|
1
|
+
import { useRef, useState, useCallback, useEffect, useMemo, createElement } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { isMultiple, renderLabel, isSelectable } from './Select.helpers.js';
|
|
4
|
+
import { getInteractionMode, INTERACTION_MODE } from '../../tools/dom.js';
|
|
5
5
|
import cn from 'classnames';
|
|
6
6
|
import { omit } from '../../tools/object.js';
|
|
7
7
|
import { AssistiveText } from '../AssistiveText/AssistiveText.js';
|
|
@@ -15,206 +15,247 @@ import { RequiredStar } from '../RequiredStar/RequiredStar.js';
|
|
|
15
15
|
import { Scroll } from '../Scroll/Scroll.js';
|
|
16
16
|
import { useThrottle } from '../../hooks/useThrottle.js';
|
|
17
17
|
import useEvent from '../../hooks/useEvent.js';
|
|
18
|
+
import { generateUID } from '../../tools/uid.js';
|
|
19
|
+
import { useSelectedIds, useSelectOptions, useSelectOpenState, useKeyboardNav } from './Select.hooks.js';
|
|
18
20
|
import S from './Select.styl.js';
|
|
19
21
|
|
|
22
|
+
/** Time given to a pointer press inside the popup before a blur closes it. */
|
|
23
|
+
const BLUR_CLOSE_DELAY = 60;
|
|
24
|
+
const POINTER_PRESS_TTL = 100;
|
|
20
25
|
function Select2(props) {
|
|
21
|
-
const { className, value, onChange, onChipClick, onSearchChange, disableTriggerArrow, inputProps, popupProps, scrollProps, size = 'm', round, optionClassName, selectedChipClassName, additionalOptions = [], options, variant, label, additionalLabel, error, blur, disabled, trigger, required, hideRequiredStar, isSearchable, presets = [], selectAllButton, clearButton, showSelectedCount, disableLabel, selectedChipRemoveTooltip, selectedChipIds, onOpen, onClose, } = props;
|
|
26
|
+
const { className, value, onChange, onChipClick, onSearchChange, disableTriggerArrow, inputProps, popupProps, scrollProps, size = 'm', round, optionsClassName, optionClassName, selectedChipClassName, additionalOptions = [], options = [], variant, label, additionalLabel, error, blur, disabled, trigger, required, hideRequiredStar, isSearchable, presets = [], selectAllButton, clearButton, showSelectedCount, disableLabel, selectedChipRemoveTooltip, selectedChipIds, onOpen, onClose, onFocus, onBlur, } = props;
|
|
22
27
|
const isMultiple$1 = isMultiple(value);
|
|
23
28
|
const closeOnSelect = props.closeOnSelect ?? !isMultiple$1;
|
|
29
|
+
const instanceId = useRef(generateUID());
|
|
24
30
|
const contentRef = useRef(null);
|
|
25
31
|
const scrollInnerRef = useRef(null);
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const searchValLower = useRef('');
|
|
31
|
-
const [searchVal, _setSearchVal] = useState('');
|
|
32
|
-
const setSearchVal = (val) => {
|
|
33
|
-
_setSearchVal(val);
|
|
34
|
-
searchValLower.current = val.toLowerCase();
|
|
35
|
-
};
|
|
36
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
37
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
32
|
+
const pointerPressedInside = useRef(false);
|
|
33
|
+
const pressStartedInside = useRef(false);
|
|
34
|
+
const pointerPressTimer = useRef(0);
|
|
35
|
+
const blurTimer = useRef(0);
|
|
38
36
|
const [labelClipPath, setLabelClipPath] = useState('');
|
|
39
|
-
const [
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
round,
|
|
51
|
-
variant,
|
|
52
|
-
...props.triggerProps,
|
|
53
|
-
}), [props.triggerProps, label, size, round, variant]);
|
|
54
|
-
const isSelected = (id) => isMultiple$1
|
|
55
|
-
? selected.includes(id)
|
|
56
|
-
: selected[0] === id;
|
|
57
|
-
const isClickedInside = (elem) => !!(elem instanceof Element &&
|
|
58
|
-
(elem.closest(`.${S.root}`) || elem.closest(`.${S.options}`)));
|
|
59
|
-
const setNewItems = (newItems) => {
|
|
60
|
-
maxIndex.current = newItems.length - 1;
|
|
61
|
-
setItems(newItems);
|
|
62
|
-
setOptionsUpdated(optionsUpdated + 1);
|
|
63
|
-
if (focusedItemIndex > maxIndex.current && maxIndex.current >= 0) {
|
|
64
|
-
setFocusedItemIndex(maxIndex.current);
|
|
65
|
-
focusedItemId.current = newItems[maxIndex.current]?.id;
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
const setItemFocus = index => {
|
|
69
|
-
focusedItemId.current = items[index]?.id;
|
|
70
|
-
setFocusedItemIndex(index);
|
|
71
|
-
};
|
|
72
|
-
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
73
|
-
const handleSearchChange = (e, value) => {
|
|
74
|
-
setSearchVal(value);
|
|
75
|
-
setIsSearchActive(true);
|
|
76
|
-
onSearchChange?.(value);
|
|
77
|
-
setNewItems(options.filter(filterOption));
|
|
78
|
-
};
|
|
79
|
-
const selectAll = () => {
|
|
80
|
-
const newValue = options.map(({ id }) => id);
|
|
81
|
-
setSelected(newValue);
|
|
82
|
-
onChange?.(newValue);
|
|
83
|
-
};
|
|
84
|
-
const dropSelected = () => {
|
|
85
|
-
setSelected([]);
|
|
86
|
-
onChange?.([]);
|
|
87
|
-
};
|
|
88
|
-
const onFocusedElemRef = elem => {
|
|
89
|
-
focusedElem.current = elem;
|
|
90
|
-
if (elem) {
|
|
91
|
-
const content = contentRef.current;
|
|
92
|
-
if (!content)
|
|
93
|
-
return;
|
|
94
|
-
const { top, bottom } = elem.getBoundingClientRect();
|
|
95
|
-
const rect = contentRef.current.getBoundingClientRect();
|
|
96
|
-
const list = scrollInnerRef.current;
|
|
97
|
-
if (top < rect.top) {
|
|
98
|
-
list.scrollTop -= rect.top - top;
|
|
99
|
-
}
|
|
100
|
-
else if (bottom > rect.bottom) {
|
|
101
|
-
list.scrollTop += bottom - rect.bottom;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
const onPopupOpen = () => {
|
|
106
|
-
setIsOpen(true);
|
|
107
|
-
if (focusedItemIndex === -1) {
|
|
108
|
-
setItemFocus(0);
|
|
109
|
-
}
|
|
37
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
38
|
+
const [innerSearchVal, setInnerSearchVal] = useState('');
|
|
39
|
+
const searchVal = props.searchValue ?? innerSearchVal;
|
|
40
|
+
const searchQuery = isSearching ? searchVal : '';
|
|
41
|
+
const { selectedIds, setSelectedIds } = useSelectedIds(value);
|
|
42
|
+
const { allOptions, visibleOptions, navigableOptions, optionsById } = useSelectOptions({
|
|
43
|
+
options,
|
|
44
|
+
additionalOptions,
|
|
45
|
+
searchQuery,
|
|
46
|
+
});
|
|
47
|
+
const handleOpen = useCallback(() => {
|
|
110
48
|
popupProps?.onOpen?.();
|
|
111
49
|
onOpen?.();
|
|
112
|
-
};
|
|
113
|
-
const
|
|
114
|
-
setIsOpen(false);
|
|
115
|
-
setSearchVal('');
|
|
116
|
-
setIsSearchActive(false);
|
|
117
|
-
setItemFocus(0);
|
|
50
|
+
}, [popupProps?.onOpen, onOpen]);
|
|
51
|
+
const handleClose = useCallback(() => {
|
|
118
52
|
popupProps?.onClose?.();
|
|
119
53
|
onClose?.();
|
|
54
|
+
}, [popupProps?.onClose, onClose]);
|
|
55
|
+
const { isOpen, open, close } = useSelectOpenState({
|
|
56
|
+
isOpen: props.isOpen,
|
|
57
|
+
onOpen: handleOpen,
|
|
58
|
+
onClose: handleClose,
|
|
59
|
+
});
|
|
60
|
+
const isErrorVisible = !isOpen && !!error;
|
|
61
|
+
const hasChips = isMultiple$1 && selectedIds.length > 0;
|
|
62
|
+
const isSelected = (id) => selectedIds.includes(id);
|
|
63
|
+
const getLabel = (id) => optionsById.items[id]?.label || '';
|
|
64
|
+
const isInsideSelect = (elem) => elem instanceof Element &&
|
|
65
|
+
Boolean(elem.closest(`[data-select-id="${instanceId.current}"]`));
|
|
66
|
+
const openIfEnabled = () => {
|
|
67
|
+
if (disabled)
|
|
68
|
+
return;
|
|
69
|
+
open();
|
|
120
70
|
};
|
|
121
|
-
const
|
|
122
|
-
|
|
71
|
+
const resetSearch = () => {
|
|
72
|
+
setIsSearching(false);
|
|
73
|
+
setInnerSearchVal('');
|
|
74
|
+
if (searchVal)
|
|
75
|
+
onSearchChange?.('');
|
|
123
76
|
};
|
|
124
|
-
const
|
|
125
|
-
|
|
77
|
+
const applySelection = (ids, nextValue) => {
|
|
78
|
+
setSelectedIds(ids);
|
|
79
|
+
onChange?.(nextValue);
|
|
126
80
|
};
|
|
127
|
-
const onOptionHover = useThrottle(id => {
|
|
128
|
-
const mode = getInteractionMode();
|
|
129
|
-
if (mode !== INTERACTION_MODE.POINTER)
|
|
130
|
-
return;
|
|
131
|
-
const index = items.findIndex(item => item.id === id);
|
|
132
|
-
setItemFocus(index);
|
|
133
|
-
}, 100, { trailing: true });
|
|
134
81
|
const onItemToggle = (id) => {
|
|
82
|
+
if (!isSelectable(optionsById.items[id]))
|
|
83
|
+
return;
|
|
135
84
|
if (isMultiple$1) {
|
|
136
|
-
const
|
|
137
|
-
?
|
|
138
|
-
: [...
|
|
139
|
-
|
|
140
|
-
onChange?.(newValue);
|
|
85
|
+
const ids = isSelected(id)
|
|
86
|
+
? selectedIds.filter(i => i !== id)
|
|
87
|
+
: [...selectedIds, id];
|
|
88
|
+
applySelection(ids, ids);
|
|
141
89
|
}
|
|
142
90
|
else {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const newSelected = isSelected(id) ? [] : [id];
|
|
146
|
-
setSelected(newSelected);
|
|
147
|
-
onChange?.(newValue);
|
|
91
|
+
const nextValue = isSelected(id) ? null : id;
|
|
92
|
+
applySelection(nextValue === null ? [] : [id], nextValue);
|
|
148
93
|
}
|
|
149
|
-
|
|
94
|
+
resetSearch();
|
|
150
95
|
if (closeOnSelect)
|
|
151
|
-
|
|
96
|
+
close();
|
|
97
|
+
};
|
|
98
|
+
const selectAll = () => {
|
|
99
|
+
const ids = allOptions.filter(isSelectable).map(({ id }) => id);
|
|
100
|
+
applySelection(ids, isMultiple$1 ? ids : (ids[0] ?? null));
|
|
101
|
+
};
|
|
102
|
+
const dropSelected = () => applySelection([], isMultiple$1 ? [] : null);
|
|
103
|
+
const applyPreset = (ids) => applySelection(ids, isMultiple$1 ? ids : (ids[0] ?? null));
|
|
104
|
+
const { focusedId, setFocusedId } = useKeyboardNav({
|
|
105
|
+
isOpen,
|
|
106
|
+
navigableOptions,
|
|
107
|
+
selectedIds,
|
|
108
|
+
onSelect: onItemToggle,
|
|
109
|
+
});
|
|
110
|
+
const onOptionHover = useThrottle((id) => {
|
|
111
|
+
if (getInteractionMode() !== INTERACTION_MODE.POINTER)
|
|
112
|
+
return;
|
|
113
|
+
setFocusedId(id);
|
|
114
|
+
}, 100, { trailing: true });
|
|
115
|
+
const onScrollInnerRef = useCallback((elem) => {
|
|
116
|
+
scrollInnerRef.current = elem;
|
|
117
|
+
}, []);
|
|
118
|
+
const onFocusedElemRef = (elem) => {
|
|
119
|
+
const content = contentRef.current;
|
|
120
|
+
const list = scrollInnerRef.current;
|
|
121
|
+
if (!elem || !content || !list)
|
|
122
|
+
return;
|
|
123
|
+
const { top, bottom } = elem.getBoundingClientRect();
|
|
124
|
+
const rect = content.getBoundingClientRect();
|
|
125
|
+
if (top < rect.top) {
|
|
126
|
+
list.scrollTop -= rect.top - top;
|
|
127
|
+
}
|
|
128
|
+
else if (bottom > rect.bottom) {
|
|
129
|
+
list.scrollTop += bottom - rect.bottom;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const handleSearchChange = (e, val) => {
|
|
133
|
+
setIsSearching(true);
|
|
134
|
+
setInnerSearchVal(val);
|
|
135
|
+
onSearchChange?.(val);
|
|
136
|
+
openIfEnabled();
|
|
137
|
+
};
|
|
138
|
+
const handleFocus = e => {
|
|
139
|
+
window.clearTimeout(blurTimer.current);
|
|
140
|
+
onFocus?.(e);
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Popup skips its own blur-close while it is controlled, so closing on blur is
|
|
144
|
+
* up to Select. A press inside the popup blurs the trigger too — that one must
|
|
145
|
+
* not close the list.
|
|
146
|
+
*/
|
|
147
|
+
const handleBlur = e => {
|
|
148
|
+
onBlur?.(e);
|
|
149
|
+
window.clearTimeout(blurTimer.current);
|
|
150
|
+
blurTimer.current = window.setTimeout(() => {
|
|
151
|
+
if (pointerPressedInside.current)
|
|
152
|
+
return;
|
|
153
|
+
close();
|
|
154
|
+
}, BLUR_CLOSE_DELAY);
|
|
152
155
|
};
|
|
153
|
-
const
|
|
156
|
+
const onTriggerPointerDown = () => {
|
|
157
|
+
if (disabled)
|
|
158
|
+
return;
|
|
159
|
+
// A searchable trigger is a text field: pressing it must never close.
|
|
160
|
+
if (isOpen && !isSearchable)
|
|
161
|
+
close();
|
|
162
|
+
else
|
|
163
|
+
open();
|
|
164
|
+
};
|
|
165
|
+
const markPressInside = (isInside) => {
|
|
166
|
+
window.clearTimeout(pointerPressTimer.current);
|
|
167
|
+
pointerPressedInside.current = isInside;
|
|
168
|
+
pointerPressTimer.current = window.setTimeout(() => (pointerPressedInside.current = false), POINTER_PRESS_TTL);
|
|
169
|
+
};
|
|
170
|
+
useEvent({
|
|
171
|
+
event: 'pointerdown',
|
|
172
|
+
isCapture: true,
|
|
173
|
+
callback: e => {
|
|
174
|
+
pressStartedInside.current = isInsideSelect(e.target);
|
|
175
|
+
markPressInside(pressStartedInside.current);
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
useEvent({
|
|
179
|
+
event: 'pointerup',
|
|
180
|
+
isCapture: true,
|
|
181
|
+
callback: () => {
|
|
182
|
+
// The release can land on the portalled popup that mounted under the
|
|
183
|
+
// cursor mid-gesture, so the up-target is not a reliable inside signal.
|
|
184
|
+
if (pressStartedInside.current)
|
|
185
|
+
markPressInside(true);
|
|
186
|
+
pressStartedInside.current = false;
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
useEvent({
|
|
190
|
+
event: 'click',
|
|
191
|
+
isActive: isOpen,
|
|
192
|
+
callback: e => {
|
|
193
|
+
// The click concluding a press that started inside must not count as an
|
|
194
|
+
// outside click: the portalled popup can mount under the cursor, which
|
|
195
|
+
// retargets the click to a common ancestor outside the select.
|
|
196
|
+
if (pointerPressedInside.current)
|
|
197
|
+
return;
|
|
198
|
+
if (!isInsideSelect(e.target))
|
|
199
|
+
close();
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (!isOpen)
|
|
204
|
+
resetSearch();
|
|
205
|
+
}, [isOpen]);
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (props.searchValue === undefined)
|
|
208
|
+
return;
|
|
209
|
+
setIsSearching(props.searchValue !== '');
|
|
210
|
+
}, [props.searchValue]);
|
|
211
|
+
useEffect(() => () => {
|
|
212
|
+
window.clearTimeout(blurTimer.current);
|
|
213
|
+
window.clearTimeout(pointerPressTimer.current);
|
|
214
|
+
}, []);
|
|
215
|
+
const triggerProps = useMemo(() => ({
|
|
216
|
+
label,
|
|
217
|
+
size,
|
|
218
|
+
round,
|
|
219
|
+
variant,
|
|
220
|
+
...props.triggerProps,
|
|
221
|
+
}), [props.triggerProps, label, size, round, variant]);
|
|
154
222
|
const getFieldLabel = (label) => {
|
|
155
223
|
if (disableLabel)
|
|
156
224
|
return null;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (isMultiple$1 && length && showSelectedCount)
|
|
160
|
-
return `${label} (${length})`;
|
|
225
|
+
if (isMultiple$1 && selectedIds.length && showSelectedCount)
|
|
226
|
+
return `${label} (${selectedIds.length})`;
|
|
161
227
|
return label;
|
|
162
228
|
};
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (!isMultiple$1)
|
|
168
|
-
return getLabel(value);
|
|
169
|
-
if (!value)
|
|
170
|
-
return '';
|
|
171
|
-
return (value
|
|
172
|
-
// @ts-ignore
|
|
173
|
-
.reduce((acc, id) => {
|
|
174
|
-
const label = getLabel(id);
|
|
175
|
-
return label ? [...acc, label] : acc;
|
|
176
|
-
}, [])
|
|
177
|
-
.join(', '));
|
|
178
|
-
}, [isMultiple$1, value, ids]);
|
|
179
|
-
const renderSelectedChips = () => {
|
|
180
|
-
if (!isMultiple$1 || !value || !value.length)
|
|
181
|
-
return null;
|
|
182
|
-
return value.map(id => {
|
|
183
|
-
const label = getLabel(id);
|
|
184
|
-
if (!label)
|
|
185
|
-
return null;
|
|
186
|
-
return (jsx(Chip, { className: cn(S.chip, selectedChipIds?.includes(id) && selectedChipClassName, ids.items[id]?.chipClassName), size: size, selected: selectedChipIds?.includes(id), onRemove: () => onItemToggle(id), onClick: () => onChipClick?.(id), removeTooltip: selectedChipRemoveTooltip, children: label }, id));
|
|
187
|
-
});
|
|
188
|
-
};
|
|
229
|
+
const selectedLabel = useMemo(() => selectedIds
|
|
230
|
+
.map(getLabel)
|
|
231
|
+
.filter(Boolean)
|
|
232
|
+
.join(', '), [selectedIds, optionsById]);
|
|
189
233
|
const triggerArrow = useMemo(() => {
|
|
190
234
|
if (disableTriggerArrow || (inputProps?.hasClear && searchVal))
|
|
191
235
|
return null;
|
|
192
236
|
return (jsx(Icon, { type: "chevronDown", className: cn(S.triggerArrow, isOpen && S.isOpen), size: size }));
|
|
193
|
-
}, [isOpen,
|
|
237
|
+
}, [disableTriggerArrow, inputProps?.hasClear, searchVal, isOpen, size]);
|
|
238
|
+
const renderSelectedChips = () => selectedIds.map(id => {
|
|
239
|
+
const label = getLabel(id);
|
|
240
|
+
if (!label)
|
|
241
|
+
return null;
|
|
242
|
+
return (jsx(Chip, { className: cn(S.chip, selectedChipIds?.includes(id) && selectedChipClassName, optionsById.items[id]?.chipClassName), size: size, selected: selectedChipIds?.includes(id), onRemove: () => onItemToggle(id), onClick: () => onChipClick?.(id), removeTooltip: selectedChipRemoveTooltip, children: label }, id));
|
|
243
|
+
});
|
|
194
244
|
const renderTriggerInput = () => {
|
|
195
|
-
const
|
|
196
|
-
const inputValue = isMultiple$1
|
|
197
|
-
? isFocused && isSearchActive
|
|
198
|
-
? searchVal
|
|
199
|
-
: ''
|
|
200
|
-
: isFocused && isSearchActive
|
|
201
|
-
? searchVal
|
|
202
|
-
: selectedLabel;
|
|
245
|
+
const inputValue = isSearching ? searchVal : isMultiple$1 ? '' : selectedLabel;
|
|
203
246
|
return (jsx(Input, { ...triggerProps, ...inputProps,
|
|
204
247
|
// TODO: autoComplete
|
|
205
248
|
addonRight: triggerArrow, error: isErrorVisible, value: inputValue, onChange: handleSearchChange, label: getFieldLabel(label), placeholder: hasChips && !inputValue ? '' : inputProps?.placeholder }));
|
|
206
249
|
};
|
|
207
250
|
const renderTriggerButton = () => {
|
|
208
251
|
const { label, className, ...rest } = triggerProps;
|
|
209
|
-
const
|
|
210
|
-
isMultiple$1 && value && value.length > 0;
|
|
252
|
+
const buttonProps = omit(rest, ['name', 'inputProps']);
|
|
211
253
|
const fullSelectedLabel = [selectedLabel, additionalLabel].filter(Boolean);
|
|
212
254
|
const hasSelected = fullSelectedLabel.length > 0;
|
|
213
255
|
const displayLabel = hasSelected ? fullSelectedLabel : label;
|
|
214
256
|
const title = hasSelected && !isMultiple$1 ? fullSelectedLabel : null;
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
return (jsxs("div", { children: [jsxs(Button, { className: classes, variant: "default", ...props, style: { clipPath: labelClipPath }, title: title?.join?.(', '), children: [jsx("div", { className: cn(S.triggerButtonLabel, hasSelected && S.hasSelected), children: displayLabel }), triggerArrow] }), !isMultiple$1 && (jsx(Label, { size: size, isOnTop: hasSelected, isError: isError, onClipPathChange: setLabelClipPath, children: getFieldLabel(label) }))] }));
|
|
257
|
+
const classes = cn(S.triggerButton, isErrorVisible && S.isError, triggerArrow && S.hasTriggerArrow, className);
|
|
258
|
+
return (jsxs("div", { children: [jsxs(Button, { className: classes, variant: "default", ...buttonProps, style: { clipPath: labelClipPath }, title: title?.join?.(', '), children: [jsx("div", { className: cn(S.triggerButtonLabel, hasSelected && S.hasSelected), children: displayLabel }), triggerArrow] }), !isMultiple$1 && (jsx(Label, { size: size, isOnTop: hasSelected, isError: isErrorVisible, onClipPathChange: setLabelClipPath, children: getFieldLabel(label) }))] }));
|
|
218
259
|
};
|
|
219
260
|
const renderTrigger = () => {
|
|
220
261
|
if (trigger)
|
|
@@ -222,13 +263,12 @@ function Select2(props) {
|
|
|
222
263
|
const triggerElem = isSearchable
|
|
223
264
|
? renderTriggerInput()
|
|
224
265
|
: renderTriggerButton();
|
|
225
|
-
|
|
226
|
-
return (jsxs("div", { className: S.trigger, children: [hasChips && (jsx(Scroll, { y: true, className: S.chipsContainer, innerClassName: S.chipContainerInner, size: size, fadeSize: size, autoHide: true, children: renderSelectedChips() })), triggerElem, required && !hideRequiredStar && jsx(RequiredStar, { size: size })] }));
|
|
266
|
+
return (jsxs("div", { className: S.trigger, onPointerDown: onTriggerPointerDown, children: [hasChips && (jsx(Scroll, { y: true, className: S.chipsContainer, innerClassName: S.chipContainerInner, size: size, fadeSize: size, autoHide: true, children: renderSelectedChips() })), triggerElem, required && !hideRequiredStar && jsx(RequiredStar, { size: size })] }));
|
|
227
267
|
};
|
|
228
268
|
const renderPresets = () => {
|
|
229
269
|
const items = presets.map(({ label, ids }) => ({
|
|
230
270
|
children: label,
|
|
231
|
-
onClick: () =>
|
|
271
|
+
onClick: () => applyPreset(ids),
|
|
232
272
|
key: label,
|
|
233
273
|
}));
|
|
234
274
|
if (selectAllButton) {
|
|
@@ -247,87 +287,25 @@ function Select2(props) {
|
|
|
247
287
|
}
|
|
248
288
|
if (items.length === 0)
|
|
249
289
|
return null;
|
|
250
|
-
return (jsx("div", { className: S.presetPanel, children: items.map(
|
|
290
|
+
return (jsx("div", { className: S.presetPanel, children: items.map(({ key, ...rest }) => (jsx(Button, { className: S.presetButton, variant: "clear", ...rest }, key))) }, "preset-panel"));
|
|
251
291
|
};
|
|
252
292
|
const renderOption = (item, level = 0) => {
|
|
253
293
|
const { id, isGroupHeader } = item;
|
|
254
|
-
const isFocused = id === focusedItemId.current;
|
|
255
|
-
const isSelected = selected.includes(id);
|
|
256
|
-
const items = [];
|
|
257
|
-
const className = cn(S.option, isGroupHeader && S.isGroup, isFocused && S.isFocused, isSelected && S.isSelected, S[`level-${level}`], optionClassName);
|
|
258
294
|
const optionProps = {
|
|
259
|
-
className,
|
|
295
|
+
className: cn(S.option, isGroupHeader && S.isGroup, id === focusedId && S.isFocused, isSelected(id) && S.isSelected, S[`level-${level}`], optionClassName),
|
|
260
296
|
onPointerUp: () => onItemToggle(id),
|
|
261
297
|
onPointerEnter: () => onOptionHover(id),
|
|
262
298
|
};
|
|
263
|
-
|
|
264
|
-
if (isFocused)
|
|
299
|
+
if (id === focusedId)
|
|
265
300
|
optionProps.ref = onFocusedElemRef;
|
|
266
|
-
|
|
267
|
-
isFirstSelectedMeet.current = true;
|
|
268
|
-
}
|
|
269
|
-
if (filterOption(item)) {
|
|
270
|
-
items.unshift(jsx("div", { ...optionProps, children: renderLabel(item) }, id));
|
|
271
|
-
}
|
|
272
|
-
return items;
|
|
273
|
-
};
|
|
274
|
-
const renderOptions = () => {
|
|
275
|
-
isFirstSelectedMeet.current = false;
|
|
276
|
-
return [...additionalOptions, ...items]
|
|
277
|
-
.map(item => renderOption(item))
|
|
278
|
-
.flat();
|
|
301
|
+
return (jsx("div", { ...optionProps, children: renderLabel(item) }, id));
|
|
279
302
|
};
|
|
280
|
-
|
|
281
|
-
const items = additionalOptions?.length
|
|
282
|
-
? [...additionalOptions, ...options]
|
|
283
|
-
: options;
|
|
284
|
-
setNewItems(items);
|
|
285
|
-
}, [options]);
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
setIsOpen(props.isOpen);
|
|
288
|
-
}, [props.isOpen]);
|
|
289
|
-
useEffect(() => {
|
|
290
|
-
const searchVal = props.searchValue;
|
|
291
|
-
if (searchVal)
|
|
292
|
-
setSearchVal(searchVal);
|
|
293
|
-
}, [props.searchValue]);
|
|
294
|
-
const onKeyDown = useCallback((e) => {
|
|
295
|
-
const currIndex = focusedItemIndex;
|
|
296
|
-
if (e.key === 'ArrowUp') {
|
|
297
|
-
if (currIndex > 0)
|
|
298
|
-
setItemFocus(currIndex - 1);
|
|
299
|
-
}
|
|
300
|
-
if (e.key === 'ArrowDown') {
|
|
301
|
-
if (currIndex < maxIndex.current)
|
|
302
|
-
setItemFocus(currIndex + 1);
|
|
303
|
-
}
|
|
304
|
-
if (currIndex === -1 || !isOpen)
|
|
305
|
-
return;
|
|
306
|
-
if (e.key === 'Enter') {
|
|
307
|
-
e.preventDefault();
|
|
308
|
-
e.stopPropagation();
|
|
309
|
-
onItemToggle(items[currIndex].id);
|
|
310
|
-
}
|
|
311
|
-
}, [items, focusedItemIndex, isOpen, selected]);
|
|
312
|
-
useEvent({
|
|
313
|
-
event: 'keydown',
|
|
314
|
-
isActive: isOpen,
|
|
315
|
-
callback: onKeyDown,
|
|
316
|
-
});
|
|
317
|
-
useEvent({
|
|
318
|
-
event: 'click',
|
|
319
|
-
// isActive: isMultiple,
|
|
320
|
-
callback: e => {
|
|
321
|
-
if (!isClickedInside(e.target)) {
|
|
322
|
-
setIsOpen(false);
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
});
|
|
326
|
-
const optionsList = useMemo(() => (jsxs("div", { ref: contentRef, children: [renderPresets(), createElement(Scroll, { y: true, ...scrollProps, offset: { y: { before: 10, after: 10 } }, className: cn(S.options, S[`size-${size}`], scrollProps?.className), onInnerRef: elem => (scrollInnerRef.current = elem), key: "items-scroll" }, renderOptions())] })), [items, searchVal, focusedItemIndex, selected]);
|
|
303
|
+
const optionsList = (jsxs("div", { ref: contentRef, "data-select-id": instanceId.current, children: [renderPresets(), createElement(Scroll, { y: true, ...scrollProps, offset: { y: { before: 10, after: 10 } }, className: cn(S.options, S[`size-${size}`], optionsClassName, scrollProps?.className), onInnerRef: onScrollInnerRef, key: "items-scroll" }, visibleOptions.map(item => renderOption(item)))] }));
|
|
327
304
|
const classes = cn(S.root, className, S[`size-${size}`]);
|
|
328
|
-
return (jsxs(Fragment, { children: [jsx(Popup, { className: classes, direction: "bottom", size: size, focusControl: true,
|
|
329
|
-
|
|
330
|
-
|
|
305
|
+
return (jsxs(Fragment, { children: [jsx(Popup, { className: classes, direction: "bottom", size: size, focusControl: true, blur: blur, round: round, disabled: disabled, ...popupProps, isOpen: isOpen, onOpen: openIfEnabled, onClose: close, trigger: renderTrigger(), triggerProps: {
|
|
306
|
+
'data-select-id': instanceId.current,
|
|
307
|
+
onFocus: handleFocus,
|
|
308
|
+
onBlur: handleBlur,
|
|
331
309
|
}, content: optionsList }), isErrorVisible && (jsx(AssistiveText, { variant: "danger", size: size, children: error }))] }));
|
|
332
310
|
}
|
|
333
311
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import styleInject from '../../../node_modules/style-inject/dist/style-inject.es.js';
|
|
2
2
|
|
|
3
|
-
var css_248z = ".Tooltip_trigger__9GSNV{display:inline-block}.Tooltip_tooltip__tzL8q{word-wrap:break-word;background:var(--decent-color-alpha-900);border-radius:4px;box-shadow:0 0 0 1px var(--accent-color-alpha-100);color:var(--accent-color);font-size:14px;line-height:1.4;margin:0;max-width:250px;padding:6px 12px;pointer-events:none;position:fixed;z-index:9999}.Tooltip_tooltip__tzL8q.Tooltip_blur__SQYHW{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:var(--decent-color-alpha-500)}.Tooltip_tooltip__tzL8q:after{content:\"\";position:absolute}.Tooltip_tooltip__tzL8q[data-direction=top]{transform:translateY(0)}.Tooltip_tooltip__tzL8q[data-direction=top]:after{left:50%;top:100%;transform:translateX(-50%)}.Tooltip_tooltip__tzL8q[data-direction=bottom]{transform:translateY(0)}.Tooltip_tooltip__tzL8q[data-direction=bottom]:after{bottom:100%;left:50%;transform:translateX(-50%)}.Tooltip_tooltip__tzL8q[data-direction=left]{transform:translateX(0)}.Tooltip_tooltip__tzL8q[data-direction=left]:after{left:100%;top:50%;transform:translateY(-50%)}.Tooltip_tooltip__tzL8q[data-direction=right]{transform:translateX(0)}.Tooltip_tooltip__tzL8q[data-direction=right]:after{right:100%;top:50%;transform:translateY(-50%)}.Tooltip_tooltip__tzL8q:popover-open{animation:Tooltip_fadeIn__Buhx4 .2s ease-out}.Tooltip_tooltip__tzL8q.Tooltip_tooltipOverTrigger__gRUoT{word-wrap:break-word;box-sizing:border-box;height:-moz-fit-content;height:fit-content;max-width:none;overflow-wrap:break-word;white-space:normal}.Tooltip_tooltip__tzL8q.Tooltip_tooltipOverTrigger__gRUoT:after{display:none}.Tooltip_tooltip__tzL8q.Tooltip_tooltipOverTrigger__gRUoT:popover-open{animation:Tooltip_fadeIn__Buhx4 .15s ease-out}@keyframes Tooltip_fadeIn__Buhx4{0%{opacity:0}to{opacity:1}}";
|
|
3
|
+
var css_248z = ".Tooltip_trigger__9GSNV{display:inline-block}.Tooltip_tooltip__tzL8q{word-wrap:break-word;background:var(--decent-color-alpha-900);border-radius:4px;box-shadow:0 0 0 1px var(--accent-color-alpha-100);color:var(--accent-color);font-size:14px;line-height:1.4;margin:0;max-width:250px;padding:6px 12px;pointer-events:none;position:fixed;z-index:9999}.Tooltip_tooltip__tzL8q.Tooltip_blur__SQYHW{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:var(--decent-color-alpha-500)}.Tooltip_tooltip__tzL8q:after{content:\"\";position:absolute}.Tooltip_tooltip__tzL8q[data-direction=top]{transform:translateY(0)}.Tooltip_tooltip__tzL8q[data-direction=top]:after{left:50%;top:100%;transform:translateX(-50%)}.Tooltip_tooltip__tzL8q[data-direction=bottom]{transform:translateY(0)}.Tooltip_tooltip__tzL8q[data-direction=bottom]:after{bottom:100%;left:50%;transform:translateX(-50%)}.Tooltip_tooltip__tzL8q[data-direction=left]{transform:translateX(0)}.Tooltip_tooltip__tzL8q[data-direction=left]:after{left:100%;top:50%;transform:translateY(-50%)}.Tooltip_tooltip__tzL8q[data-direction=right]{transform:translateX(0)}.Tooltip_tooltip__tzL8q[data-direction=right]:after{right:100%;top:50%;transform:translateY(-50%)}.Tooltip_tooltip__tzL8q:popover-open{animation:Tooltip_fadeIn__Buhx4 .2s ease-out}.Tooltip_tooltip__tzL8q.Tooltip_tooltipOverTrigger__gRUoT{word-wrap:break-word;box-sizing:border-box;font-size:inherit;height:-moz-fit-content;height:fit-content;line-height:inherit;max-width:none;overflow-wrap:break-word;white-space:normal}.Tooltip_tooltip__tzL8q.Tooltip_tooltipOverTrigger__gRUoT:after{display:none}.Tooltip_tooltip__tzL8q.Tooltip_tooltipOverTrigger__gRUoT:popover-open{animation:Tooltip_fadeIn__Buhx4 .15s ease-out}@keyframes Tooltip_fadeIn__Buhx4{0%{opacity:0}to{opacity:1}}";
|
|
4
4
|
var S = {"trigger":"Tooltip_trigger__9GSNV","tooltip":"Tooltip_tooltip__tzL8q","blur":"Tooltip_blur__SQYHW","fadeIn":"Tooltip_fadeIn__Buhx4","tooltipOverTrigger":"Tooltip_tooltipOverTrigger__gRUoT"};
|
|
5
5
|
styleInject(css_248z);
|
|
6
6
|
|
|
@@ -2,13 +2,14 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { useRef, useCallback, useLayoutEffect, useEffect } from 'react';
|
|
3
3
|
import S from './Tooltip.styl.js';
|
|
4
4
|
import { TooltipCore } from './TooltipCore.js';
|
|
5
|
-
import {
|
|
5
|
+
import { getTextStyleSource, applyTriggerTextStyles, clearTriggerTextStyles } from './tooltipTextStyles.js';
|
|
6
6
|
|
|
7
7
|
function parsePx(value) {
|
|
8
8
|
return Number.parseFloat(value) || 0;
|
|
9
9
|
}
|
|
10
10
|
function applyOverTriggerStyles(contentEl, triggerEl) {
|
|
11
|
-
const
|
|
11
|
+
const textSource = getTextStyleSource(triggerEl);
|
|
12
|
+
const rect = textSource.getBoundingClientRect();
|
|
12
13
|
const contentComputed = window.getComputedStyle(contentEl);
|
|
13
14
|
const paddingLeft = parsePx(contentComputed.paddingLeft);
|
|
14
15
|
const paddingRight = parsePx(contentComputed.paddingRight);
|
|
@@ -21,7 +22,7 @@ function applyOverTriggerStyles(contentEl, triggerEl) {
|
|
|
21
22
|
contentEl.style.setProperty('transform', 'none');
|
|
22
23
|
contentEl.style.width = `${rect.width + paddingLeft + paddingRight + borderLeft + borderRight}px`;
|
|
23
24
|
contentEl.style.boxSizing = 'border-box';
|
|
24
|
-
applyTriggerTextStyles(contentEl,
|
|
25
|
+
applyTriggerTextStyles(contentEl, textSource);
|
|
25
26
|
// Trigger ancestors (e.g. menu items) often use nowrap for ellipsis; allow wrap here.
|
|
26
27
|
contentEl.style.setProperty('white-space', 'normal');
|
|
27
28
|
contentEl.style.setProperty('overflow-wrap', 'break-word');
|
|
@@ -30,9 +30,24 @@ const TRIGGER_TEXT_STYLE_PROPS = [
|
|
|
30
30
|
'overflow-wrap',
|
|
31
31
|
'hyphens',
|
|
32
32
|
];
|
|
33
|
+
function hasDirectText(el) {
|
|
34
|
+
return Array.from(el.childNodes).some(node => node.nodeType === Node.TEXT_NODE &&
|
|
35
|
+
(node.textContent?.trim().length ?? 0) > 0);
|
|
36
|
+
}
|
|
33
37
|
function getTextStyleSource(triggerEl) {
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
let source = triggerEl;
|
|
39
|
+
const walk = (el) => {
|
|
40
|
+
if (hasDirectText(el)) {
|
|
41
|
+
source = el;
|
|
42
|
+
}
|
|
43
|
+
for (const child of el.children) {
|
|
44
|
+
if (child instanceof HTMLElement) {
|
|
45
|
+
walk(child);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
walk(triggerEl);
|
|
50
|
+
return source;
|
|
36
51
|
}
|
|
37
52
|
function applyTriggerTextStyles(contentEl, sourceEl) {
|
|
38
53
|
const computed = window.getComputedStyle(sourceEl);
|