@homecode/ui 4.19.9 → 4.20.0-beta-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.
Files changed (29) hide show
  1. package/dist/esm/index.js +1 -0
  2. package/dist/esm/src/components/Autocomplete/Autocomplete.js +3 -1
  3. package/dist/esm/src/components/Input/Input.js +150 -200
  4. package/dist/esm/src/components/Input/Input.styl.js +2 -2
  5. package/dist/esm/src/components/InputFile/InputFile.js +2 -1
  6. package/dist/esm/src/components/Notifications/Notifications.js +7 -7
  7. package/dist/esm/src/components/Popup/Popup.js +28 -31
  8. package/dist/esm/src/components/Router/Link/Link.js +4 -3
  9. package/dist/esm/src/components/Router/Route.js +3 -1
  10. package/dist/esm/src/components/Router/Router.js +4 -2
  11. package/dist/esm/src/components/Select/Select.js +5 -5
  12. package/dist/esm/src/components/Select/Select2.js +307 -0
  13. package/dist/esm/src/hooks/useThrottle.js +28 -0
  14. package/dist/esm/src/services/i18n.js +13 -7
  15. package/dist/esm/types/src/components/Icon/Icon.d.ts +2 -0
  16. package/dist/esm/types/src/components/Input/Input.d.ts +1 -42
  17. package/dist/esm/types/src/components/Lazy/Lazy.d.ts +1 -0
  18. package/dist/esm/types/src/components/Notifications/Notifications.d.ts +5 -1
  19. package/dist/esm/types/src/components/Popup/Popup.d.ts +8 -1
  20. package/dist/esm/types/src/components/Router/Link/Link.d.ts +2 -1
  21. package/dist/esm/types/src/components/Router/Route.d.ts +4 -4
  22. package/dist/esm/types/src/components/Select/Select.d.ts +1 -2
  23. package/dist/esm/types/src/components/Select/Select.helpers.d.ts +1 -1
  24. package/dist/esm/types/src/components/Select/Select.types.d.ts +5 -3
  25. package/dist/esm/types/src/components/Select/Select2.d.ts +2 -0
  26. package/dist/esm/types/src/components/index.d.ts +1 -0
  27. package/dist/esm/types/src/hooks/useThrottle.d.ts +5 -0
  28. package/dist/esm/types/src/services/i18n.d.ts +14 -4
  29. package/package.json +2 -2
@@ -7,7 +7,6 @@ import { Portal } from '../Portal/Portal.js';
7
7
  import S from './Popup.styl.js';
8
8
  import Time from 'timen';
9
9
  import cn from 'classnames';
10
- import { createStore } from 'justorm/react';
11
10
  import debounce from '../../tools/debounce.js';
12
11
  import { getCoords } from '../../tools/dom.js';
13
12
  import { isBrowser } from '../../tools/env.js';
@@ -37,7 +36,6 @@ class Popup extends Component {
37
36
  needDropOffset = false;
38
37
  id;
39
38
  parentPopupContent;
40
- store;
41
39
  timers = Time.create();
42
40
  scrollParent;
43
41
  offset = { ...INITIAL_OFFSET };
@@ -46,18 +44,17 @@ class Popup extends Component {
46
44
  direction: '',
47
45
  animated: true,
48
46
  };
47
+ state = {
48
+ rootPopupId: null,
49
+ isOpen: Boolean(this.props.isOpen),
50
+ isContentVisible: Boolean(this.props.isOpen),
51
+ animating: false,
52
+ direction: this.props.direction,
53
+ triggerBounds: null,
54
+ };
49
55
  constructor(props) {
50
56
  super(props);
51
- const isOpen = Boolean(props.isOpen);
52
57
  this.id = getId();
53
- this.store = createStore(this, {
54
- rootPopupId: null,
55
- isOpen,
56
- isContentVisible: isOpen,
57
- animating: false,
58
- direction: props.direction,
59
- triggerBounds: null,
60
- });
61
58
  }
62
59
  componentDidMount() {
63
60
  const { hoverControl, focusControl } = this.props;
@@ -65,9 +62,9 @@ class Popup extends Component {
65
62
  document.addEventListener('pointerdown', this.onDocPointerDown, true);
66
63
  document.addEventListener('pointerup', this.onDocPointerUp, true);
67
64
  if (parentPopupContent) {
68
- this.store.rootPopupId =
69
- getPopupId(parentPopupContent, 'data-root-popup-id') ||
70
- getPopupId(parentPopupContent, 'data-popup-id');
65
+ const rootPopupId = getPopupId(parentPopupContent, 'data-root-popup-id') ||
66
+ getPopupId(parentPopupContent, 'data-popup-id');
67
+ this.setState({ rootPopupId });
71
68
  }
72
69
  if (focusControl) {
73
70
  document.addEventListener('keydown', this.onDocKeyDown, true);
@@ -80,7 +77,7 @@ class Popup extends Component {
80
77
  componentDidUpdate(prevProps) {
81
78
  const { isOpen, disabled, hoverControl } = this.props;
82
79
  if (disabled) {
83
- this.store.isOpen = false; // close when receive disabled=true
80
+ this.setState({ isOpen: false }); // close when receive disabled=true
84
81
  return;
85
82
  }
86
83
  if (!prevProps.hoverControl && hoverControl)
@@ -136,7 +133,7 @@ class Popup extends Component {
136
133
  document.removeEventListener('pointerup', this.checkHover);
137
134
  }
138
135
  updateBounds() {
139
- if (this.store.animating || !this.containerElem)
136
+ if (this.state.animating || !this.containerElem)
140
137
  return;
141
138
  if (!this.triggerElem.current)
142
139
  return;
@@ -153,7 +150,7 @@ class Popup extends Component {
153
150
  this.triggerElem.current.style[key] = value;
154
151
  });
155
152
  this.updateOffset();
156
- this.store.triggerBounds = bounds;
153
+ this.setState({ triggerBounds: bounds });
157
154
  }, 200, { trailing: true });
158
155
  prevContentBounds = { width: 0, height: 0 };
159
156
  updateOffset = () => {
@@ -192,7 +189,7 @@ class Popup extends Component {
192
189
  checkHover = debounce((e) => {
193
190
  if (this.isPointerPressedInside)
194
191
  return;
195
- const { isOpen, rootPopupId } = this.store;
192
+ const { isOpen, rootPopupId } = this.state;
196
193
  const overTrigger = this.isPointerOver(e.target, S.trigger);
197
194
  const overContent = this.isPointerOver(e.target, S.content);
198
195
  if (!isOpen) {
@@ -233,7 +230,7 @@ class Popup extends Component {
233
230
  return target.closest(`.${elem}[data-popup-id="${this.id}"]`);
234
231
  }
235
232
  onScroll = throttle(e => {
236
- if (!this.store.isOpen) {
233
+ if (!this.state.isOpen) {
237
234
  const { top, left } = this.offset;
238
235
  if (left || top) {
239
236
  this.offset = { ...INITIAL_OFFSET };
@@ -252,7 +249,7 @@ class Popup extends Component {
252
249
  this.pointerDownTarget = null;
253
250
  };
254
251
  onDocKeyUp = (e) => {
255
- if (this.store.isOpen && e.key === 'Escape') {
252
+ if (this.state.isOpen && e.key === 'Escape') {
256
253
  e.stopPropagation();
257
254
  this.close();
258
255
  return;
@@ -293,19 +290,19 @@ class Popup extends Component {
293
290
  this.updateBounds();
294
291
  };
295
292
  open = throttle(() => {
296
- const { rootPopupId } = this.store;
297
- if (this.store.isOpen)
293
+ const { rootPopupId } = this.state;
294
+ if (this.state.isOpen)
298
295
  return;
299
296
  this.updateBounds();
300
297
  this.subscribeSizeChange();
301
298
  this.subscribeScroll();
302
- this.store.isContentVisible = true;
299
+ this.setState({ isContentVisible: true });
303
300
  this.changeState(true, this.afterOpen);
304
301
  if (rootPopupId)
305
302
  setChild(rootPopupId, this.id);
306
303
  }, 100);
307
304
  close = () => {
308
- if (!this.store.isOpen)
305
+ if (!this.state.isOpen)
309
306
  return;
310
307
  this.unsubscribeSizeChange();
311
308
  this.changeState(false, this.afterClose);
@@ -313,12 +310,12 @@ class Popup extends Component {
313
310
  changeState(isOpen, callback) {
314
311
  const { animated, onOpen, onClose } = this.props;
315
312
  this.timers.clear();
316
- this.store.isOpen = isOpen;
313
+ this.setState({ isOpen });
317
314
  isOpen ? onOpen?.() : onClose?.();
318
315
  if (animated) {
319
- this.store.animating = true;
316
+ this.setState({ animating: true });
320
317
  this.timers.after(ANIMATION_DURATION + 500, () => {
321
- this.store.animating = false;
318
+ this.setState({ animating: false });
322
319
  callback();
323
320
  });
324
321
  }
@@ -330,7 +327,7 @@ class Popup extends Component {
330
327
  this.props.onAfterOpen?.();
331
328
  };
332
329
  afterClose = () => {
333
- this.store.isContentVisible = false;
330
+ this.setState({ isContentVisible: false });
334
331
  this.dropOffset();
335
332
  this.props.onAfterClose?.();
336
333
  };
@@ -341,12 +338,12 @@ class Popup extends Component {
341
338
  this.applyOffset();
342
339
  }
343
340
  toggle = throttle(() => {
344
- this.store.isOpen ? this.close() : this.open();
341
+ this.state.isOpen ? this.close() : this.open();
345
342
  }, 100);
346
343
  renderTrigger() {
347
344
  const { trigger, content, disabled, focusControl, hoverControl, ...rest } = this.props;
348
345
  const triggerProps = { ...rest.triggerProps };
349
- const { isOpen } = this.store;
346
+ const { isOpen } = this.state;
350
347
  if (!trigger)
351
348
  return null;
352
349
  const disableTrigger = disabled || !trigger;
@@ -370,7 +367,7 @@ class Popup extends Component {
370
367
  }
371
368
  renderContent() {
372
369
  const { content, contentProps = {}, wrapperProps = {}, size, disabled, inline, outlined, animated, paranja, blur, elevation, } = this.props;
373
- const { isOpen, isContentVisible, animating, direction, triggerBounds, rootPopupId, } = this.store;
370
+ const { isOpen, isContentVisible, animating, direction, triggerBounds, rootPopupId, } = this.state;
374
371
  if (disabled)
375
372
  return null;
376
373
  const wrapperClasses = cn(S.contentWrapper, blur && S.blur, inline && S.inline, isOpen && S.isOpen, wrapperProps.className);
@@ -1,13 +1,14 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useRef, useContext, useMemo, useCallback } from 'react';
3
- import { withStore } from 'justorm/react';
3
+ import { useStore } from 'justorm/react';
4
4
  import cn from 'classnames';
5
5
  import { Icon } from '../../Icon/Icon.js';
6
6
  import Context from '../context.js';
7
7
  import S from './Link.styl.js';
8
8
 
9
9
  const isStartFromDoubleSlash = href => /^\/\//.test(href);
10
- const Link = withStore({ router: ['path'] })(({ className, exactClassName, children, href: hrefProp, isClear = false, isDisabled = false, disableExternalIcon = false, inline = false, store: { router }, isPartialExact = false, onClick, ...rest }) => {
10
+ const Link = ({ className, exactClassName, children, href: hrefProp, isClear = false, isDisabled = false, disableExternalIcon = false, inline = false, isPartialExact = false, onClick, ...rest }) => {
11
+ const { router } = useStore({ router: ['path'] });
11
12
  const { path } = router;
12
13
  const isFromRoot = isStartFromDoubleSlash(hrefProp);
13
14
  const domElem = useRef(null);
@@ -56,6 +57,6 @@ const Link = withStore({ router: ['path'] })(({ className, exactClassName, child
56
57
  props.href = `http://${href}`;
57
58
  }
58
59
  return (jsxs("a", { className: classes, ...props, onClick: handleClick, ref: domElem, children: [children, isExternal && !disableExternalIcon && (jsx(Icon, { type: "externalLink", className: S.externalIcon }))] }));
59
- });
60
+ };
60
61
 
61
62
  export { Link };
@@ -1,4 +1,6 @@
1
- const Route = (props) => null;
1
+ function Route(props) {
2
+ return null;
3
+ }
2
4
  Route.displayName = 'Route';
3
5
 
4
6
  export { Route };
@@ -1,12 +1,14 @@
1
1
  import { jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { useContext, useEffect, useMemo } from 'react';
3
- import { useStore } from 'justorm/dist/esm/src/plugins/react';
3
+ import { useStore } from 'justorm/react';
4
4
  import STORE from './store.js';
5
5
  import Context from './context.js';
6
6
  import { parsePath, replaceParamsInPath } from './Router.helpers.js';
7
7
  export { Route } from './Route.js';
8
8
  export { Redirect } from './Redirect.js';
9
- export { Link } from './Link/Link.js';
9
+ import 'classnames';
10
+ import '../Icon/Icon.js';
11
+ import './Link/Link.styl.js';
10
12
 
11
13
  const Router = (props) => {
12
14
  const { router } = useStore({ router: ['path'] });
@@ -203,7 +203,7 @@ class Select extends Component {
203
203
  };
204
204
  onSearchChange = (e, value) => {
205
205
  this.setSearchVal(value);
206
- this.props.onSeachChange?.(value);
206
+ this.props.onSearchChange?.(value);
207
207
  };
208
208
  onExpandClick(e, id) {
209
209
  const { expanded } = this.store;
@@ -405,7 +405,7 @@ class Select extends Component {
405
405
  return label;
406
406
  }
407
407
  getFieldLabel(label) {
408
- const { showSelectedCount, disableLable, value } = this.props;
408
+ const { showSelectedCount, disableLabel: disableLable, value } = this.props;
409
409
  if (disableLable)
410
410
  return null;
411
411
  // @ts-ignore
@@ -524,8 +524,8 @@ class Select extends Component {
524
524
  onPointerUp: () => this.onItemToggle(id),
525
525
  onPointerEnter: () => this.onOptionHover(id),
526
526
  };
527
- // @ts-ignore
528
- if (isFocused)
527
+ if (isFocused || (isSelected && !this.onFocusedElemRef))
528
+ // @ts-ignore
529
529
  props.ref = this.onFocusedElemRef;
530
530
  if (isIndeterminate || (isSelected && !this.isFirstSelectedMeet)) {
531
531
  this.isFirstSelectedMeet = true;
@@ -587,7 +587,7 @@ class Select extends Component {
587
587
  const { className, popupProps, size, error, blur, disabled } = this.props;
588
588
  const { isOpen, isFocused } = this.store;
589
589
  const classes = cn(S.root, className, S[`size-${size}`]);
590
- return (jsxs(Fragment, { children: [jsx(Popup, { className: classes, direction: "bottom", size: size, focusControl: true, hoverControl: isFocused, blur: blur, isOpen: isOpen, disabled: disabled, ...popupProps, onOpen: this.onPopupOpen, onAfterClose: this.onPopupClose, trigger: this.renderTrigger(), triggerProps: {
590
+ return (jsxs(Fragment, { children: [jsx(Popup, { className: classes, direction: "bottom", size: size, focusControl: true, hoverControl: isFocused, blur: blur, isOpen: isOpen, disabled: disabled, ...popupProps, onOpen: this.onPopupOpen, onClose: this.onPopupClose, trigger: this.renderTrigger(), triggerProps: {
591
591
  onFocus: this.onFocus,
592
592
  onBlur: this.onBlur,
593
593
  }, content: this.renderOptionsList() }), this.isErrorVisible() && (jsx(AssistiveText, { variant: "danger", size: size, children: error }))] }));
@@ -0,0 +1,307 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { mapById, isMultiple, renderLabel } from './Select.helpers.js';
3
+ import { useRef, useState, useMemo, useEffect, useCallback } from 'react';
4
+ import { INTERACTION_MODE, getInteractionMode } from '../../tools/dom.js';
5
+ import cn from 'classnames';
6
+ import omit from 'lodash.omit';
7
+ import { AssistiveText } from '../AssistiveText/AssistiveText.js';
8
+ import { Button } from '../Button/Button.js';
9
+ import { Input } from '../Input/Input.js';
10
+ import { Icon } from '../Icon/Icon.js';
11
+ import { Label } from '../Label/Label.js';
12
+ import { Popup } from '../Popup/Popup.js';
13
+ import { RequiredStar } from '../RequiredStar/RequiredStar.js';
14
+ import { Scroll } from '../Scroll/Scroll.js';
15
+ import { useThrottle } from '../../hooks/useThrottle.js';
16
+ import useEvent from '../../hooks/useEvent.js';
17
+ import S from './Select.styl.js';
18
+
19
+ function Select2(props) {
20
+ const { className, value, onChange, onSearchChange, inputProps, popupProps, size = 'm', optionClassName, additionalOptions = [], options, variant, label, error, blur, disabled, trigger, required, hideRequiredStar, isSearchable, presets = [], selectAllButton, clearButton, showSelectedCount, disableLabel, } = props;
21
+ const isMultiple$1 = isMultiple(value);
22
+ const closeOnSelect = props.closeOnSelect ?? !isMultiple$1;
23
+ const contentRef = useRef(null);
24
+ const scrollInnerRef = useRef(null);
25
+ const focusedItemId = useRef(null);
26
+ const focusedElem = useRef(null);
27
+ const maxIndex = useRef(-1);
28
+ const isFirstSelectedMeet = useRef(false);
29
+ const searchValLower = useRef('');
30
+ const [searchVal, _setSearchVal] = useState('');
31
+ const setSearchVal = (val) => {
32
+ _setSearchVal(val);
33
+ searchValLower.current = val.toLowerCase();
34
+ };
35
+ const [isOpen, setIsOpen] = useState(false);
36
+ const [isFocused, setIsFocused] = useState(false);
37
+ const [labelClipPath, setLabelClipPath] = useState('');
38
+ const [focusedItemIndex, setFocusedItemIndex] = useState(-1);
39
+ // Add tree-related state from Select.tsx
40
+ const [items, setItems] = useState([]);
41
+ const ids = useMemo(() => mapById(props.options), [props.options]);
42
+ // Add basic selection state
43
+ const [selected, setSelected] = useState(isMultiple$1 ? value : [value]);
44
+ const [optionsUpdated, setOptionsUpdated] = useState(0);
45
+ const isErrorVisible = !isOpen && !!error;
46
+ const triggerProps = useMemo(() => ({
47
+ label,
48
+ size,
49
+ variant,
50
+ ...props.triggerProps,
51
+ }), [props.triggerProps, label, size, variant]);
52
+ // Simplified isSelected check
53
+ const isSelected = (id) =>
54
+ // @ts-ignore
55
+ isMultiple$1 ? selected?.includes(id) : selected === id;
56
+ const isClickedInside = elem => elem.closest(`.${S.root}`) || elem.closest(`.${S.options}`);
57
+ const setNewItems = (newItems) => {
58
+ maxIndex.current = newItems.length - 1;
59
+ setItems(newItems);
60
+ setOptionsUpdated(optionsUpdated + 1);
61
+ if (focusedItemIndex > maxIndex.current && maxIndex.current >= 0) {
62
+ setFocusedItemIndex(maxIndex.current);
63
+ focusedItemId.current = newItems[maxIndex.current]?.id;
64
+ }
65
+ };
66
+ const setItemFocus = index => {
67
+ console.log('setItemFocus::', index);
68
+ focusedItemId.current = items[index]?.id;
69
+ setFocusedItemIndex(index);
70
+ };
71
+ const handleSearchChange = (e, value) => {
72
+ setSearchVal(value);
73
+ onSearchChange?.(value);
74
+ setNewItems(options.filter(filterOption));
75
+ };
76
+ const selectAll = () => {
77
+ const newValue = options.map(({ id }) => id);
78
+ setSelected(newValue);
79
+ onChange?.(newValue);
80
+ };
81
+ const dropSelected = () => {
82
+ setSelected([]);
83
+ onChange?.([]);
84
+ };
85
+ const onFocusedElemRef = elem => {
86
+ focusedElem.current = elem;
87
+ if (elem) {
88
+ const content = contentRef.current;
89
+ if (!content)
90
+ return;
91
+ const { top, bottom } = elem.getBoundingClientRect();
92
+ const rect = contentRef.current.getBoundingClientRect();
93
+ const list = scrollInnerRef.current;
94
+ if (top < rect.top) {
95
+ list.scrollTop -= rect.top - top;
96
+ }
97
+ else if (bottom > rect.bottom) {
98
+ list.scrollTop += bottom - rect.bottom;
99
+ }
100
+ }
101
+ };
102
+ const onPopupOpen = () => {
103
+ setIsOpen(true);
104
+ if (focusedItemIndex === -1) {
105
+ setItemFocus(0);
106
+ }
107
+ };
108
+ const onPopupClose = () => {
109
+ setIsOpen(false);
110
+ setItemFocus(0);
111
+ };
112
+ const onFocus = () => {
113
+ setIsFocused(true);
114
+ };
115
+ const onBlur = () => {
116
+ setIsFocused(false);
117
+ };
118
+ const onOptionHover = useThrottle(id => {
119
+ const mode = getInteractionMode();
120
+ if (mode !== INTERACTION_MODE.POINTER)
121
+ return;
122
+ const index = items.findIndex(item => item.id === id);
123
+ setItemFocus(index);
124
+ }, 100, { trailing: true });
125
+ const onItemToggle = (id) => {
126
+ if (isMultiple$1) {
127
+ const newValue = isSelected(id)
128
+ ? selected.filter(i => i !== id)
129
+ : [...selected, id];
130
+ setSelected(newValue);
131
+ onChange?.(newValue);
132
+ }
133
+ else {
134
+ // mono select
135
+ const newValue = isSelected(id) ? null : id;
136
+ const newSelected = isSelected(id) ? [] : [id];
137
+ setSelected(newSelected);
138
+ onChange?.(newValue);
139
+ }
140
+ setSearchVal('');
141
+ if (closeOnSelect)
142
+ setIsOpen(false);
143
+ };
144
+ const getLabel = (id) => ids.items[id]?.label || '';
145
+ const getFieldLabel = (label) => {
146
+ if (disableLabel)
147
+ return null;
148
+ // @ts-ignore
149
+ const length = value?.length;
150
+ if (isMultiple$1 && length && showSelectedCount)
151
+ return `${label} (${length})`;
152
+ return label;
153
+ };
154
+ const filterOption = ({ label }) => {
155
+ return label.toLowerCase().includes(searchValLower.current);
156
+ };
157
+ const selectedLabel = useMemo(() => {
158
+ if (!isMultiple$1)
159
+ return getLabel(value);
160
+ if (!value)
161
+ return '';
162
+ return (value
163
+ // @ts-ignore
164
+ .reduce((acc, id) => {
165
+ const label = getLabel(id);
166
+ return label ? [...acc, label] : acc;
167
+ }, [])
168
+ .join(', '));
169
+ }, [isMultiple$1, value]);
170
+ // console.log('selectedLabel::', selectedLabel);
171
+ const triggerArrow = useMemo(() => {
172
+ if (inputProps?.hasClear && searchVal)
173
+ return null;
174
+ return (jsx(Icon, { type: "chevronDown", className: cn(S.triggerArrow, isOpen && S.isOpen), size: size }));
175
+ }, [isOpen, searchVal]);
176
+ const renderTriggerInput = () => {
177
+ return (jsx(Input, { ...triggerProps, ...inputProps,
178
+ // TODO: autoComplete
179
+ addonRight: triggerArrow, error: isErrorVisible, value: isFocused ? searchVal : selectedLabel, onChange: handleSearchChange, label: getFieldLabel(label) }));
180
+ };
181
+ const renderAdditionalLabel = () => {
182
+ return null;
183
+ };
184
+ const renderTriggerButton = () => {
185
+ const { label, className, ...rest } = triggerProps;
186
+ const props = omit(rest, ['name', 'inputProps']);
187
+ const fullSelectedLabel = [selectedLabel, renderAdditionalLabel()].filter(Boolean);
188
+ const hasSelected = fullSelectedLabel.length > 0;
189
+ const displayLabel = hasSelected ? fullSelectedLabel : label;
190
+ const title = hasSelected ? fullSelectedLabel : null;
191
+ const isError = isErrorVisible;
192
+ const classes = cn(S.triggerButton, isError && S.isError, triggerArrow && S.hasTriggerArrow, className);
193
+ 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] }), jsx(Label, { size: size, isOnTop: hasSelected, isError: isError, onClipPathChange: setLabelClipPath, children: getFieldLabel(label) })] }));
194
+ };
195
+ const renderTrigger = () => {
196
+ if (trigger)
197
+ return trigger;
198
+ const triggerElem = isSearchable
199
+ ? renderTriggerInput()
200
+ : renderTriggerButton();
201
+ return (jsxs("div", { className: S.trigger, children: [triggerElem, required && !hideRequiredStar && jsx(RequiredStar, { size: size })] }));
202
+ };
203
+ const renderPresets = () => {
204
+ const items = presets.map(({ label, ids }) => ({
205
+ children: label,
206
+ onClick: () => setSelected(ids),
207
+ key: label,
208
+ }));
209
+ if (selectAllButton) {
210
+ items.push({
211
+ children: 'Select all',
212
+ onClick: selectAll,
213
+ key: 'select-all-button',
214
+ });
215
+ }
216
+ if (clearButton) {
217
+ items.push({
218
+ children: 'Clear',
219
+ onClick: dropSelected,
220
+ key: 'clear-button',
221
+ });
222
+ }
223
+ if (items.length === 0)
224
+ return null;
225
+ return (jsx("div", { className: S.presetPanel, children: items.map(props => (jsx(Button, { className: S.presetButton, variant: "clear", ...props }))) }, "preset-panel"));
226
+ };
227
+ const renderOption = (item, level = 0) => {
228
+ const { id, isGroupHeader } = item;
229
+ const isFocused = id === focusedItemId.current;
230
+ const isSelected = selected.includes(id);
231
+ const items = [];
232
+ const className = cn(S.option, isGroupHeader && S.isGroup, isFocused && S.isFocused, isSelected && S.isSelected, S[`level-${level}`], optionClassName);
233
+ const optionProps = {
234
+ className,
235
+ key: id,
236
+ onPointerUp: () => onItemToggle(id),
237
+ onPointerEnter: () => onOptionHover(id),
238
+ };
239
+ // @ts-ignore
240
+ if (isFocused)
241
+ optionProps.ref = onFocusedElemRef;
242
+ if (isSelected && !isFirstSelectedMeet.current) {
243
+ isFirstSelectedMeet.current = true;
244
+ }
245
+ if (filterOption(item)) {
246
+ items.unshift(jsx("div", { ...optionProps, children: renderLabel(item) }));
247
+ }
248
+ return items;
249
+ };
250
+ const renderOptions = () => {
251
+ isFirstSelectedMeet.current = false;
252
+ return [...additionalOptions, ...items]
253
+ .map(item => renderOption(item))
254
+ .flat();
255
+ };
256
+ useEffect(() => {
257
+ const items = additionalOptions?.length
258
+ ? [...additionalOptions, ...options]
259
+ : options;
260
+ setNewItems(items);
261
+ }, [options]);
262
+ useEffect(() => {
263
+ const searchVal = props.searchValue;
264
+ if (searchVal)
265
+ setSearchVal(searchVal);
266
+ }, [props.searchValue]);
267
+ const onKeyDown = useCallback((e) => {
268
+ const currIndex = focusedItemIndex;
269
+ if (e.key === 'ArrowUp') {
270
+ if (currIndex > 0)
271
+ setItemFocus(currIndex - 1);
272
+ }
273
+ if (e.key === 'ArrowDown') {
274
+ if (currIndex < maxIndex.current)
275
+ setItemFocus(currIndex + 1);
276
+ }
277
+ if (currIndex === -1 || !isOpen)
278
+ return;
279
+ if (e.key === 'Enter') {
280
+ e.preventDefault();
281
+ e.stopPropagation();
282
+ onItemToggle(items[currIndex].id);
283
+ }
284
+ }, [items, focusedItemIndex, isOpen]);
285
+ useEvent({
286
+ event: 'keydown',
287
+ isActive: isOpen,
288
+ callback: onKeyDown,
289
+ });
290
+ useEvent({
291
+ event: 'click',
292
+ // isActive: isMultiple,
293
+ callback: e => {
294
+ if (!isClickedInside(e.target)) {
295
+ setIsOpen(false);
296
+ }
297
+ },
298
+ });
299
+ const optionsList = useMemo(() => (jsxs("div", { ref: contentRef, children: [renderPresets(), jsx(Scroll, { y: true, offset: { y: { before: 10, after: 10 } }, className: cn(S.options, S[`size-${size}`]), onInnerRef: elem => (scrollInnerRef.current = elem), children: renderOptions() }, "items-scroll")] })), [items, searchVal, focusedItemIndex]);
300
+ const classes = cn(S.root, className, S[`size-${size}`]);
301
+ return (jsxs(Fragment, { children: [jsx(Popup, { className: classes, direction: "bottom", size: size, focusControl: true, hoverControl: isFocused, blur: blur, isOpen: isOpen, disabled: disabled, ...popupProps, onOpen: onPopupOpen, onClose: onPopupClose, trigger: renderTrigger(), triggerProps: {
302
+ onFocus: onFocus,
303
+ onBlur: onBlur,
304
+ }, content: optionsList }), isErrorVisible && (jsx(AssistiveText, { variant: "danger", size: size, children: error }))] }));
305
+ }
306
+
307
+ export { Select2 };
@@ -0,0 +1,28 @@
1
+ import { useRef, useEffect, useCallback } from 'react';
2
+
3
+ function useThrottle(cb, limit, options = { trailing: false }, deps = []) {
4
+ const lastRun = useRef(0);
5
+ const timeoutId = useRef(null);
6
+ useEffect(() => {
7
+ return () => {
8
+ clearTimeout(timeoutId.current);
9
+ };
10
+ }, deps);
11
+ return useCallback((...args) => {
12
+ const now = Date.now();
13
+ const execute = () => {
14
+ cb(...args);
15
+ lastRun.current = now;
16
+ };
17
+ const remaining = limit - (now - lastRun.current);
18
+ if (remaining <= 0) {
19
+ clearTimeout(timeoutId.current);
20
+ execute();
21
+ }
22
+ else if (options.trailing && !timeoutId.current) {
23
+ timeoutId.current = setTimeout(execute, remaining);
24
+ }
25
+ }, [limit, options.trailing, ...deps]);
26
+ }
27
+
28
+ export { useThrottle };
@@ -1,7 +1,8 @@
1
1
  import { jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { memo } from 'react';
3
3
  import { nanoid } from 'nanoid';
4
- import { createStore, withStore } from 'justorm/react';
4
+ import { useStore } from 'justorm/react';
5
+ import { createStore } from 'justorm';
5
6
  import _i18n from 'roddeh-i18n';
6
7
  import LS from '../tools/localStorage.js';
7
8
  import { queryParams } from '../tools/queryParams.js';
@@ -53,15 +54,20 @@ function init(config) {
53
54
  storeName,
54
55
  componentStore,
55
56
  // hook (update when componentStore._updated changed)
56
- withI18N: Component => withStore(storeName)(Component),
57
+ withI18N: Component => props => {
58
+ useStore({ [storeName]: true });
59
+ return jsx(Component, { ...props });
60
+ },
57
61
  i18n: (key, props) => getTrans(key, props),
58
- I18N: memo(withStore({
59
- i18n: ['lang'],
60
- [storeName]: true,
61
- })(function I18N({ id, children, props }) {
62
+ I18N: memo(function I18N({ id, children, props }) {
63
+ useStore({
64
+ i18n: ['lang'],
65
+ [storeName]: true,
66
+ });
62
67
  const key = id ?? children;
68
+ // @ts-ignore
63
69
  return jsx(Fragment, { children: getTrans(key, props) });
64
- })),
70
+ }),
65
71
  };
66
72
  }
67
73
 
@@ -1,6 +1,8 @@
1
1
  import * as T from './Icon.types';
2
2
  export declare const icons: {
3
3
  avatar: () => Promise<any>;
4
+ bookmark: () => Promise<any>;
5
+ bookmarkAdd: () => Promise<any>;
4
6
  brokenImage: () => Promise<any>;
5
7
  camera: () => Promise<any>;
6
8
  check: () => Promise<any>;