@homecode/ui 5.6.1 → 5.7.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/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 +192 -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,226 @@ 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 isFirstSelectedMeet = useRef(false);
|
|
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 pointerPressTimer = useRef(0);
|
|
34
|
+
const blurTimer = useRef(0);
|
|
38
35
|
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
|
-
}
|
|
36
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
37
|
+
const [innerSearchVal, setInnerSearchVal] = useState('');
|
|
38
|
+
const searchVal = props.searchValue ?? innerSearchVal;
|
|
39
|
+
const searchQuery = isSearching ? searchVal : '';
|
|
40
|
+
const { selectedIds, setSelectedIds } = useSelectedIds(value);
|
|
41
|
+
const { allOptions, visibleOptions, navigableOptions, optionsById } = useSelectOptions({
|
|
42
|
+
options,
|
|
43
|
+
additionalOptions,
|
|
44
|
+
searchQuery,
|
|
45
|
+
});
|
|
46
|
+
const handleOpen = useCallback(() => {
|
|
110
47
|
popupProps?.onOpen?.();
|
|
111
48
|
onOpen?.();
|
|
112
|
-
};
|
|
113
|
-
const
|
|
114
|
-
setIsOpen(false);
|
|
115
|
-
setSearchVal('');
|
|
116
|
-
setIsSearchActive(false);
|
|
117
|
-
setItemFocus(0);
|
|
49
|
+
}, [popupProps?.onOpen, onOpen]);
|
|
50
|
+
const handleClose = useCallback(() => {
|
|
118
51
|
popupProps?.onClose?.();
|
|
119
52
|
onClose?.();
|
|
53
|
+
}, [popupProps?.onClose, onClose]);
|
|
54
|
+
const { isOpen, open, close } = useSelectOpenState({
|
|
55
|
+
isOpen: props.isOpen,
|
|
56
|
+
onOpen: handleOpen,
|
|
57
|
+
onClose: handleClose,
|
|
58
|
+
});
|
|
59
|
+
const isErrorVisible = !isOpen && !!error;
|
|
60
|
+
const hasChips = isMultiple$1 && selectedIds.length > 0;
|
|
61
|
+
const isSelected = (id) => selectedIds.includes(id);
|
|
62
|
+
const getLabel = (id) => optionsById.items[id]?.label || '';
|
|
63
|
+
const isInsideSelect = (elem) => elem instanceof Element &&
|
|
64
|
+
Boolean(elem.closest(`[data-select-id="${instanceId.current}"]`));
|
|
65
|
+
const openIfEnabled = () => {
|
|
66
|
+
if (disabled)
|
|
67
|
+
return;
|
|
68
|
+
open();
|
|
120
69
|
};
|
|
121
|
-
const
|
|
122
|
-
|
|
70
|
+
const resetSearch = () => {
|
|
71
|
+
setIsSearching(false);
|
|
72
|
+
setInnerSearchVal('');
|
|
73
|
+
if (searchVal)
|
|
74
|
+
onSearchChange?.('');
|
|
123
75
|
};
|
|
124
|
-
const
|
|
125
|
-
|
|
76
|
+
const applySelection = (ids, nextValue) => {
|
|
77
|
+
setSelectedIds(ids);
|
|
78
|
+
onChange?.(nextValue);
|
|
126
79
|
};
|
|
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
80
|
const onItemToggle = (id) => {
|
|
81
|
+
if (!isSelectable(optionsById.items[id]))
|
|
82
|
+
return;
|
|
135
83
|
if (isMultiple$1) {
|
|
136
|
-
const
|
|
137
|
-
?
|
|
138
|
-
: [...
|
|
139
|
-
|
|
140
|
-
onChange?.(newValue);
|
|
84
|
+
const ids = isSelected(id)
|
|
85
|
+
? selectedIds.filter(i => i !== id)
|
|
86
|
+
: [...selectedIds, id];
|
|
87
|
+
applySelection(ids, ids);
|
|
141
88
|
}
|
|
142
89
|
else {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const newSelected = isSelected(id) ? [] : [id];
|
|
146
|
-
setSelected(newSelected);
|
|
147
|
-
onChange?.(newValue);
|
|
90
|
+
const nextValue = isSelected(id) ? null : id;
|
|
91
|
+
applySelection(nextValue === null ? [] : [id], nextValue);
|
|
148
92
|
}
|
|
149
|
-
|
|
93
|
+
resetSearch();
|
|
150
94
|
if (closeOnSelect)
|
|
151
|
-
|
|
95
|
+
close();
|
|
96
|
+
};
|
|
97
|
+
const selectAll = () => {
|
|
98
|
+
const ids = allOptions.filter(isSelectable).map(({ id }) => id);
|
|
99
|
+
applySelection(ids, isMultiple$1 ? ids : (ids[0] ?? null));
|
|
100
|
+
};
|
|
101
|
+
const dropSelected = () => applySelection([], isMultiple$1 ? [] : null);
|
|
102
|
+
const applyPreset = (ids) => applySelection(ids, isMultiple$1 ? ids : (ids[0] ?? null));
|
|
103
|
+
const { focusedId, setFocusedId } = useKeyboardNav({
|
|
104
|
+
isOpen,
|
|
105
|
+
navigableOptions,
|
|
106
|
+
selectedIds,
|
|
107
|
+
onSelect: onItemToggle,
|
|
108
|
+
});
|
|
109
|
+
const onOptionHover = useThrottle((id) => {
|
|
110
|
+
if (getInteractionMode() !== INTERACTION_MODE.POINTER)
|
|
111
|
+
return;
|
|
112
|
+
setFocusedId(id);
|
|
113
|
+
}, 100, { trailing: true });
|
|
114
|
+
const onScrollInnerRef = useCallback((elem) => {
|
|
115
|
+
scrollInnerRef.current = elem;
|
|
116
|
+
}, []);
|
|
117
|
+
const onFocusedElemRef = (elem) => {
|
|
118
|
+
const content = contentRef.current;
|
|
119
|
+
const list = scrollInnerRef.current;
|
|
120
|
+
if (!elem || !content || !list)
|
|
121
|
+
return;
|
|
122
|
+
const { top, bottom } = elem.getBoundingClientRect();
|
|
123
|
+
const rect = content.getBoundingClientRect();
|
|
124
|
+
if (top < rect.top) {
|
|
125
|
+
list.scrollTop -= rect.top - top;
|
|
126
|
+
}
|
|
127
|
+
else if (bottom > rect.bottom) {
|
|
128
|
+
list.scrollTop += bottom - rect.bottom;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const handleSearchChange = (e, val) => {
|
|
132
|
+
setIsSearching(true);
|
|
133
|
+
setInnerSearchVal(val);
|
|
134
|
+
onSearchChange?.(val);
|
|
135
|
+
openIfEnabled();
|
|
136
|
+
};
|
|
137
|
+
const handleFocus = e => {
|
|
138
|
+
window.clearTimeout(blurTimer.current);
|
|
139
|
+
onFocus?.(e);
|
|
152
140
|
};
|
|
153
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Popup skips its own blur-close while it is controlled, so closing on blur is
|
|
143
|
+
* up to Select. A press inside the popup blurs the trigger too — that one must
|
|
144
|
+
* not close the list.
|
|
145
|
+
*/
|
|
146
|
+
const handleBlur = e => {
|
|
147
|
+
onBlur?.(e);
|
|
148
|
+
window.clearTimeout(blurTimer.current);
|
|
149
|
+
blurTimer.current = window.setTimeout(() => {
|
|
150
|
+
if (pointerPressedInside.current)
|
|
151
|
+
return;
|
|
152
|
+
close();
|
|
153
|
+
}, BLUR_CLOSE_DELAY);
|
|
154
|
+
};
|
|
155
|
+
const onTriggerPointerDown = () => {
|
|
156
|
+
if (disabled)
|
|
157
|
+
return;
|
|
158
|
+
// A searchable trigger is a text field: pressing it must never close.
|
|
159
|
+
if (isOpen && !isSearchable)
|
|
160
|
+
close();
|
|
161
|
+
else
|
|
162
|
+
open();
|
|
163
|
+
};
|
|
164
|
+
useEvent({
|
|
165
|
+
event: 'pointerdown',
|
|
166
|
+
isCapture: true,
|
|
167
|
+
callback: e => {
|
|
168
|
+
window.clearTimeout(pointerPressTimer.current);
|
|
169
|
+
pointerPressedInside.current = isInsideSelect(e.target);
|
|
170
|
+
pointerPressTimer.current = window.setTimeout(() => (pointerPressedInside.current = false), POINTER_PRESS_TTL);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
useEvent({
|
|
174
|
+
event: 'click',
|
|
175
|
+
isActive: isOpen,
|
|
176
|
+
callback: e => {
|
|
177
|
+
if (!isInsideSelect(e.target))
|
|
178
|
+
close();
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!isOpen)
|
|
183
|
+
resetSearch();
|
|
184
|
+
}, [isOpen]);
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (props.searchValue === undefined)
|
|
187
|
+
return;
|
|
188
|
+
setIsSearching(props.searchValue !== '');
|
|
189
|
+
}, [props.searchValue]);
|
|
190
|
+
useEffect(() => () => {
|
|
191
|
+
window.clearTimeout(blurTimer.current);
|
|
192
|
+
window.clearTimeout(pointerPressTimer.current);
|
|
193
|
+
}, []);
|
|
194
|
+
const triggerProps = useMemo(() => ({
|
|
195
|
+
label,
|
|
196
|
+
size,
|
|
197
|
+
round,
|
|
198
|
+
variant,
|
|
199
|
+
...props.triggerProps,
|
|
200
|
+
}), [props.triggerProps, label, size, round, variant]);
|
|
154
201
|
const getFieldLabel = (label) => {
|
|
155
202
|
if (disableLabel)
|
|
156
203
|
return null;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (isMultiple$1 && length && showSelectedCount)
|
|
160
|
-
return `${label} (${length})`;
|
|
204
|
+
if (isMultiple$1 && selectedIds.length && showSelectedCount)
|
|
205
|
+
return `${label} (${selectedIds.length})`;
|
|
161
206
|
return label;
|
|
162
207
|
};
|
|
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
|
-
};
|
|
208
|
+
const selectedLabel = useMemo(() => selectedIds
|
|
209
|
+
.map(getLabel)
|
|
210
|
+
.filter(Boolean)
|
|
211
|
+
.join(', '), [selectedIds, optionsById]);
|
|
189
212
|
const triggerArrow = useMemo(() => {
|
|
190
213
|
if (disableTriggerArrow || (inputProps?.hasClear && searchVal))
|
|
191
214
|
return null;
|
|
192
215
|
return (jsx(Icon, { type: "chevronDown", className: cn(S.triggerArrow, isOpen && S.isOpen), size: size }));
|
|
193
|
-
}, [isOpen,
|
|
216
|
+
}, [disableTriggerArrow, inputProps?.hasClear, searchVal, isOpen, size]);
|
|
217
|
+
const renderSelectedChips = () => selectedIds.map(id => {
|
|
218
|
+
const label = getLabel(id);
|
|
219
|
+
if (!label)
|
|
220
|
+
return null;
|
|
221
|
+
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));
|
|
222
|
+
});
|
|
194
223
|
const renderTriggerInput = () => {
|
|
195
|
-
const
|
|
196
|
-
const inputValue = isMultiple$1
|
|
197
|
-
? isFocused && isSearchActive
|
|
198
|
-
? searchVal
|
|
199
|
-
: ''
|
|
200
|
-
: isFocused && isSearchActive
|
|
201
|
-
? searchVal
|
|
202
|
-
: selectedLabel;
|
|
224
|
+
const inputValue = isSearching ? searchVal : isMultiple$1 ? '' : selectedLabel;
|
|
203
225
|
return (jsx(Input, { ...triggerProps, ...inputProps,
|
|
204
226
|
// TODO: autoComplete
|
|
205
227
|
addonRight: triggerArrow, error: isErrorVisible, value: inputValue, onChange: handleSearchChange, label: getFieldLabel(label), placeholder: hasChips && !inputValue ? '' : inputProps?.placeholder }));
|
|
206
228
|
};
|
|
207
229
|
const renderTriggerButton = () => {
|
|
208
230
|
const { label, className, ...rest } = triggerProps;
|
|
209
|
-
const
|
|
210
|
-
isMultiple$1 && value && value.length > 0;
|
|
231
|
+
const buttonProps = omit(rest, ['name', 'inputProps']);
|
|
211
232
|
const fullSelectedLabel = [selectedLabel, additionalLabel].filter(Boolean);
|
|
212
233
|
const hasSelected = fullSelectedLabel.length > 0;
|
|
213
234
|
const displayLabel = hasSelected ? fullSelectedLabel : label;
|
|
214
235
|
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) }))] }));
|
|
236
|
+
const classes = cn(S.triggerButton, isErrorVisible && S.isError, triggerArrow && S.hasTriggerArrow, className);
|
|
237
|
+
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
238
|
};
|
|
219
239
|
const renderTrigger = () => {
|
|
220
240
|
if (trigger)
|
|
@@ -222,13 +242,12 @@ function Select2(props) {
|
|
|
222
242
|
const triggerElem = isSearchable
|
|
223
243
|
? renderTriggerInput()
|
|
224
244
|
: 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 })] }));
|
|
245
|
+
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
246
|
};
|
|
228
247
|
const renderPresets = () => {
|
|
229
248
|
const items = presets.map(({ label, ids }) => ({
|
|
230
249
|
children: label,
|
|
231
|
-
onClick: () =>
|
|
250
|
+
onClick: () => applyPreset(ids),
|
|
232
251
|
key: label,
|
|
233
252
|
}));
|
|
234
253
|
if (selectAllButton) {
|
|
@@ -247,87 +266,25 @@ function Select2(props) {
|
|
|
247
266
|
}
|
|
248
267
|
if (items.length === 0)
|
|
249
268
|
return null;
|
|
250
|
-
return (jsx("div", { className: S.presetPanel, children: items.map(
|
|
269
|
+
return (jsx("div", { className: S.presetPanel, children: items.map(({ key, ...rest }) => (jsx(Button, { className: S.presetButton, variant: "clear", ...rest }, key))) }, "preset-panel"));
|
|
251
270
|
};
|
|
252
271
|
const renderOption = (item, level = 0) => {
|
|
253
272
|
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
273
|
const optionProps = {
|
|
259
|
-
className,
|
|
274
|
+
className: cn(S.option, isGroupHeader && S.isGroup, id === focusedId && S.isFocused, isSelected(id) && S.isSelected, S[`level-${level}`], optionClassName),
|
|
260
275
|
onPointerUp: () => onItemToggle(id),
|
|
261
276
|
onPointerEnter: () => onOptionHover(id),
|
|
262
277
|
};
|
|
263
|
-
|
|
264
|
-
if (isFocused)
|
|
278
|
+
if (id === focusedId)
|
|
265
279
|
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();
|
|
280
|
+
return (jsx("div", { ...optionProps, children: renderLabel(item) }, id));
|
|
279
281
|
};
|
|
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]);
|
|
282
|
+
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
283
|
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
|
-
|
|
284
|
+
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: {
|
|
285
|
+
'data-select-id': instanceId.current,
|
|
286
|
+
onFocus: handleFocus,
|
|
287
|
+
onBlur: handleBlur,
|
|
331
288
|
}, content: optionsList }), isErrorVisible && (jsx(AssistiveText, { variant: "danger", size: size, children: error }))] }));
|
|
332
289
|
}
|
|
333
290
|
|
|
@@ -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);
|