@rescui/dropdown 0.11.1 → 0.11.2

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/lib/dropdown.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import React, { useState, useRef, useEffect, useImperativeHandle, useContext, useCallback } from 'react';
2
+ import React, { useState, useRef, useMemo, useImperativeHandle, useContext, useCallback, useEffect } from 'react';
3
3
  import PropTypes from 'prop-types';
4
4
  import cn from 'classnames';
5
5
  import { usePopper } from 'react-popper';
@@ -74,7 +74,7 @@ function useOnOutsideClick(_ref3) {
74
74
  }
75
75
 
76
76
  onOutsideClick();
77
- }, [onOutsideClick, ...ignoreElements]); // eslint-disable-next-line consistent-return
77
+ }, [onOutsideClick, ignoreElements]); // eslint-disable-next-line consistent-return
78
78
 
79
79
  useEffect(() => {
80
80
  if (isEnabled && ignoreElementRefsExists && ownerDocument) {
@@ -143,26 +143,21 @@ const DropdownRender = (_ref5, externalRef) => {
143
143
  disableFlip = false
144
144
  } = _ref5;
145
145
  const [popperRef, setPopperRef] = useState(null);
146
- const [triggerRef, setTriggerRef] = useState(null);
146
+ const [triggerRef, setTriggerRef] = useState(undefined);
147
147
  const {
148
148
  setContentEl,
149
149
  scrollableStyles
150
150
  } = useScrollableContainer();
151
- const scrollableRef = useRef(null); // We set isOpen this way to avoid hydration issue due to
152
- // the Portal to document.body which does not exist on the server.
153
- // So even if isOpen prop is used, the tooltip will not render on the server, and
154
- // it's the same on the first client render, so there is no hydration issue.
151
+ const scrollableRef = useRef(null); // An undefined ref means that React has not committed the trigger yet. It keeps
152
+ // the server and first client render equal without an extra hydration state.
155
153
 
156
- const [isFirstRender, setIsFirstRender] = useState(true);
157
- useEffect(() => {
158
- setIsFirstRender(false);
159
- }, []);
160
- const isOpen = isFirstRender && !renderInTrigger ? false : isOpenFromProps;
154
+ const isOpen = typeof triggerRef !== 'undefined' || renderInTrigger ? isOpenFromProps : false;
161
155
  const ownerDocument = triggerRef?.ownerDocument;
156
+ const outsideClickIgnoreElements = useMemo(() => [popperRef, triggerRef], [popperRef, triggerRef]);
162
157
  useOnOutsideClick({
163
158
  isEnabled: isOpen,
164
159
  onOutsideClick: onRequestClose,
165
- ignoreElements: [popperRef, triggerRef],
160
+ ignoreElements: outsideClickIgnoreElements,
166
161
  ownerDocument
167
162
  });
168
163
  useKeyPress({
@@ -177,12 +172,11 @@ const DropdownRender = (_ref5, externalRef) => {
177
172
  onKeyPress: onRequestClose,
178
173
  ownerDocument
179
174
  });
180
- const [handleAnimationEnd, dropdownState] = useDropdownState(isOpen, cancelCloseAnimation);
181
175
  const {
182
176
  styles: popperStyles,
183
177
  attributes,
184
178
  update
185
- } = usePopper(triggerRef, popperRef, getPopperOptions({
179
+ } = usePopper(triggerRef ?? null, popperRef, getPopperOptions({
186
180
  placement,
187
181
  maxHeight,
188
182
  leastMaxHeight,
@@ -190,11 +184,17 @@ const DropdownRender = (_ref5, externalRef) => {
190
184
  maxWidth,
191
185
  disableFlip
192
186
  }));
187
+ const resolvedPlacement = attributes.popper?.['data-popper-placement'] ?? null;
188
+ const dropdownState = useDropdownState({
189
+ isOpen,
190
+ cancelCloseAnimation,
191
+ contentRef: scrollableRef,
192
+ popperElement: popperRef,
193
+ resolvedPlacement
194
+ });
193
195
  useImperativeHandle(externalRef, () => ({
194
196
  scheduleUpdate: () => {
195
- if (update) {
196
- update();
197
- }
197
+ update?.();
198
198
  }
199
199
  }));
200
200
  const {
@@ -208,7 +208,8 @@ const DropdownRender = (_ref5, externalRef) => {
208
208
  ref: setTriggerRef
209
209
  }, trigger);
210
210
  const dropdownShouldBeRendered = dropdownState !== DROPDOWN_STATES.CLOSE;
211
- const wrapperShouldBeHidden = dropdownState === DROPDOWN_STATES.CLOSING;
211
+ const wrapperIsInitial = dropdownState === DROPDOWN_STATES.OPENING_PREPARE;
212
+ const wrapperShouldBeHidden = wrapperIsInitial || dropdownState === DROPDOWN_STATES.CLOSING;
212
213
  let primaryColors = {};
213
214
  let themeFromCss = 'light';
214
215
 
@@ -230,19 +231,7 @@ const DropdownRender = (_ref5, externalRef) => {
230
231
  noProvider
231
232
  } = useContext(ThemeContext);
232
233
  const theme = themeFromProps || (noProvider ? themeFromCss : themeFromContext);
233
- const childrenTheme = mode === 'rock' ? getInverseTheme(theme) : theme; // immediately fire handleAnimationEnd event on visibility change in case transition is disabled
234
- // it may happen if the user hard-disabled all transitions in his browser
235
-
236
- useEffect(() => {
237
- if (dropdownShouldBeRendered && scrollableRef.current) {
238
- const ownerWindow = scrollableRef.current.ownerDocument.defaultView;
239
- const isTransitionDisabled = ownerWindow?.getComputedStyle(scrollableRef.current).transitionDuration === '0s';
240
-
241
- if (isTransitionDisabled) {
242
- handleAnimationEnd();
243
- }
244
- }
245
- }, [handleAnimationEnd, dropdownShouldBeRendered]);
234
+ const childrenTheme = mode === 'rock' ? getInverseTheme(theme) : theme;
246
235
  return /*#__PURE__*/React.createElement(React.Fragment, null, triggerRenderer, dropdownShouldBeRendered && /*#__PURE__*/React.createElement(Portal, {
247
236
  target: renderInTrigger ? triggerRef : ownerDocument?.body
248
237
  }, /*#__PURE__*/React.createElement("div", {
@@ -261,9 +250,7 @@ const DropdownRender = (_ref5, externalRef) => {
261
250
  }, /*#__PURE__*/React.createElement("div", {
262
251
  ref: scrollableRef,
263
252
  style: scrollableStyles,
264
- className: cn(styles.contentWrapper, wrapperShouldBeHidden && styles.wrapperHidden),
265
- onTransitionEnd: handleAnimationEnd,
266
- onAnimationEnd: handleAnimationEnd,
253
+ className: cn(styles.contentWrapper, wrapperIsInitial && styles.wrapperInitial, wrapperShouldBeHidden && styles.wrapperHidden),
267
254
  "data-test": "dropdown-content"
268
255
  }, /*#__PURE__*/React.createElement(ThemeProvider, {
269
256
  ignoreThemeValueForCSSApi: noProvider,
@@ -1,15 +1,12 @@
1
1
  var styles = {
2
- "themeLight": "_themeLight_1il2d95_59",
3
- "themeDark": "_themeDark_1il2d95_62",
4
- "dropdown": "_dropdown_1il2d95_66",
5
- "triggerWrapper": "_triggerWrapper_1il2d95_73",
6
- "contentWrapper": "_contentWrapper_1il2d95_77",
7
- "fadeInBottomToTop": "_fadeInBottomToTop_1il2d95_1",
8
- "fadeInTopToBottom": "_fadeInTopToBottom_1il2d95_1",
9
- "fadeInLeftToRight": "_fadeInLeftToRight_1il2d95_1",
10
- "fadeInRightToLeft": "_fadeInRightToLeft_1il2d95_1",
11
- "wrapperHidden": "_wrapperHidden_1il2d95_110",
12
- "modeClassic": "_modeClassic_1il2d95_130",
13
- "modeRock": "_modeRock_1il2d95_134"
2
+ "themeLight": "_themeLight_3wijjk_7",
3
+ "themeDark": "_themeDark_3wijjk_10",
4
+ "dropdown": "_dropdown_3wijjk_14",
5
+ "triggerWrapper": "_triggerWrapper_3wijjk_21",
6
+ "contentWrapper": "_contentWrapper_3wijjk_25",
7
+ "wrapperInitial": "_wrapperInitial_3wijjk_41",
8
+ "wrapperHidden": "_wrapperHidden_3wijjk_45",
9
+ "modeClassic": "_modeClassic_3wijjk_65",
10
+ "modeRock": "_modeRock_3wijjk_69"
14
11
  };
15
12
  export { styles as default };
package/lib/index.css CHANGED
@@ -1,58 +1,15 @@
1
- @keyframes _fadeInTopToBottom_1il2d95_1{
2
- 0%{
3
- opacity:0;
4
-
5
- transform:translateY(-10px);
6
- }
7
- 100%{
8
- opacity:1;
9
-
10
- transform:none;
11
- }
12
- }
13
- @keyframes _fadeInBottomToTop_1il2d95_1{
14
- 0%{
15
- opacity:0;
16
-
17
- transform:translateY(10px);
18
- }
19
- 100%{
20
- opacity:1;
21
-
22
- transform:none;
23
- }
1
+ @property --_rs-theme-dark-coefficient{
2
+ syntax:'<number>';
3
+ inherits:true;
4
+ initial-value:0;
24
5
  }
25
- @keyframes _fadeInLeftToRight_1il2d95_1{
26
- 0%{
27
- opacity:0;
28
-
29
- transform:translateX(-10px);
30
- }
31
- 100%{
32
- opacity:1;
33
-
34
- transform:none;
35
- }
36
- }
37
- @keyframes _fadeInRightToLeft_1il2d95_1{
38
- 0%{
39
- opacity:0;
40
-
41
- transform:translateX(10px);
42
- }
43
- 100%{
44
- opacity:1;
45
-
46
- transform:none;
47
- }
48
- }
49
- ._themeLight_1il2d95_59{
6
+ ._themeLight_3wijjk_7{
50
7
  --rs-theme-dark:0;
51
8
  }
52
- ._themeDark_1il2d95_62{
9
+ ._themeDark_3wijjk_10{
53
10
  --rs-theme-dark:1;
54
11
  }
55
- ._dropdown_1il2d95_66{
12
+ ._dropdown_3wijjk_14{
56
13
  --_rs-theme-dark:var(
57
14
  --_rs-internal-force-theme-dark-consult-rescui-before-using,
58
15
  var(--rs-theme-dark, 0)
@@ -62,7 +19,7 @@
62
19
  --_rs-theme-light-coefficient:calc(1 - var(--_rs-theme-dark-coefficient));
63
20
  }
64
21
  @supports (color: color-mix(in srgb, #fff 50%, #000 50%)){
65
- ._dropdown_1il2d95_66{
22
+ ._dropdown_3wijjk_14{
66
23
  --_rs-color-primary:color-mix(in srgb, var(--rs-color-primary-light-theme, #6B57FF) calc(100%*var(--_rs-theme-light-coefficient, 1)), var(--rs-color-primary-dark-theme, #8473FF) calc(100%*var(--_rs-theme-dark-coefficient, 0)));
67
24
  --_rs-color-primary-dim:color-mix(in srgb, var(--rs-color-primary-dim-light-theme, #8979ff) calc(100%*var(--_rs-theme-light-coefficient, 1)), var(--rs-color-primary-dim-dark-theme, #6f61d2) calc(100%*var(--_rs-theme-dark-coefficient, 0)));
68
25
  --_rs-color-primary-fog:color-mix(in srgb, var(--rs-color-primary-fog-light-theme, #e1ddff) calc(100%*var(--_rs-theme-light-coefficient, 1)), var(--rs-color-primary-fog-dark-theme, #2e2b49) calc(100%*var(--_rs-theme-dark-coefficient, 0)));
@@ -71,7 +28,7 @@
71
28
  }
72
29
  }
73
30
  @supports not (color: color-mix(in srgb, #fff 50%, #000 50%)){
74
- ._dropdown_1il2d95_66{
31
+ ._dropdown_3wijjk_14{
75
32
  --_rs-color-primary:rgb(calc(var(--_rs-theme-light-coefficient, 1)*107 + var(--_rs-theme-dark-coefficient, 0)*132), calc(var(--_rs-theme-light-coefficient, 1)*87 + var(--_rs-theme-dark-coefficient, 0)*115), 255);
76
33
  --_rs-color-primary-dim:rgb(calc(var(--_rs-theme-light-coefficient, 1)*137 + var(--_rs-theme-dark-coefficient, 0)*111), calc(var(--_rs-theme-light-coefficient, 1)*121 + var(--_rs-theme-dark-coefficient, 0)*97), calc(var(--_rs-theme-light-coefficient, 1)*255 + var(--_rs-theme-dark-coefficient, 0)*210));
77
34
  --_rs-color-primary-fog:rgb(calc(var(--_rs-theme-light-coefficient, 1)*225 + var(--_rs-theme-dark-coefficient, 0)*46), calc(var(--_rs-theme-light-coefficient, 1)*221 + var(--_rs-theme-dark-coefficient, 0)*43), calc(var(--_rs-theme-light-coefficient, 1)*255 + var(--_rs-theme-dark-coefficient, 0)*73));
@@ -79,14 +36,14 @@
79
36
  --_rs-color-primary-t-fog:rgba(calc(var(--_rs-theme-light-coefficient, 1)*107 + var(--_rs-theme-dark-coefficient, 0)*132), calc(var(--_rs-theme-light-coefficient, 1)*87 + var(--_rs-theme-dark-coefficient, 0)*115), 255, calc(var(--_rs-theme-light-coefficient, 1)*0.2 + var(--_rs-theme-dark-coefficient, 0)*0.3));
80
37
  }
81
38
  }
82
- ._dropdown_1il2d95_66 {
39
+ ._dropdown_3wijjk_14 {
83
40
  display:flex;
84
41
  flex-direction:column;
85
42
  }
86
- ._triggerWrapper_1il2d95_73{
43
+ ._triggerWrapper_3wijjk_21{
87
44
  display:inline-block;
88
45
  }
89
- ._contentWrapper_1il2d95_77{
46
+ ._contentWrapper_3wijjk_25{
90
47
  position:relative;
91
48
 
92
49
  display:block;
@@ -98,42 +55,36 @@
98
55
  background-clip:padding-box;
99
56
  box-shadow:0 0 0 1px rgb(calc(209 - var(--_rs-theme-dark-coefficient, 0)*138), calc(209 - var(--_rs-theme-dark-coefficient, 0)*138), calc(210 - var(--_rs-theme-dark-coefficient, 0)*137));
100
57
 
101
- transition:opacity var(--rs-transition-function, linear) 200ms, transform var(--rs-transition-function, linear) 200ms;
102
- }
103
- [data-popper-placement^='top'] ._contentWrapper_1il2d95_77{
104
- animation:_fadeInBottomToTop_1il2d95_1 var(--rs-transition-function, ease) 200ms;
105
- }
106
- [data-popper-placement^='bottom'] ._contentWrapper_1il2d95_77{
107
- animation:_fadeInTopToBottom_1il2d95_1 var(--rs-transition-function, ease) 200ms;
58
+ transition:opacity var(--rs-transition-function, ease) 200ms, transform var(--rs-transition-function, ease) 200ms;
108
59
  }
109
- [data-popper-placement^='right'] ._contentWrapper_1il2d95_77{
110
- animation:_fadeInLeftToRight_1il2d95_1 var(--rs-transition-function, ease) 200ms;
60
+ ._wrapperInitial_3wijjk_41{
61
+ transition:none;
111
62
  }
112
- [data-popper-placement^='left'] ._contentWrapper_1il2d95_77{
113
- animation:_fadeInRightToLeft_1il2d95_1 var(--rs-transition-function, ease) 200ms;
114
- }
115
- ._wrapperHidden_1il2d95_110{
63
+ ._wrapperHidden_3wijjk_45{
116
64
  opacity:0;
117
65
  }
118
- [data-popper-placement^='top'] ._wrapperHidden_1il2d95_110{
66
+ [data-popper-placement^='top'] ._wrapperHidden_3wijjk_45{
119
67
  transform:translateY(10px);
120
68
  }
121
- [data-popper-placement^='bottom'] ._wrapperHidden_1il2d95_110{
69
+ [data-popper-placement^='bottom'] ._wrapperHidden_3wijjk_45{
122
70
  transform:translateY(-10px);
123
71
  }
124
- [data-popper-placement^='right'] ._wrapperHidden_1il2d95_110{
72
+ [data-popper-placement^='right'] ._wrapperHidden_3wijjk_45{
125
73
  transform:translateX(-10px);
126
74
  }
127
- [data-popper-placement^='left'] ._wrapperHidden_1il2d95_110{
75
+ [data-popper-placement^='left'] ._wrapperHidden_3wijjk_45{
128
76
  transform:translateX(10px);
129
77
  }
130
- ._modeClassic_1il2d95_130 ._contentWrapper_1il2d95_77{
78
+ ._modeClassic_3wijjk_65 ._contentWrapper_3wijjk_25{
131
79
  background-color:rgb(calc(255 - var(--_rs-theme-dark-coefficient, 0)*230), calc(255 - var(--_rs-theme-dark-coefficient, 0)*230), calc(255 - var(--_rs-theme-dark-coefficient, 0)*227));
132
80
  }
133
- ._modeRock_1il2d95_134 ._contentWrapper_1il2d95_77{
81
+ ._modeRock_3wijjk_69 ._contentWrapper_3wijjk_25{
134
82
  background-color:rgb(calc(25 + var(--_rs-theme-dark-coefficient, 0)*230), calc(25 + var(--_rs-theme-dark-coefficient, 0)*230), calc(28 + var(--_rs-theme-dark-coefficient, 0)*227));
135
83
  }
136
- ._modeRock_1il2d95_134{
84
+ ._modeClassic_3wijjk_65._themeDark_3wijjk_10 ._contentWrapper_3wijjk_25, ._modeRock_3wijjk_69._themeLight_3wijjk_7 ._contentWrapper_3wijjk_25{
85
+ color-scheme:dark;
86
+ }
87
+ ._modeRock_3wijjk_69{
137
88
  --rs-parent-color-primary-light-theme:var(--rs-color-primary-light-theme);
138
89
  --rs-parent-color-primary-dim-light-theme:var(
139
90
  --rs-color-primary-dim-light-theme
@@ -162,7 +113,7 @@
162
113
  --rs-color-primary-t-fog-dark-theme
163
114
  );
164
115
  }
165
- ._modeRock_1il2d95_134 > *{
116
+ ._modeRock_3wijjk_69 > *{
166
117
  --rs-color-primary-dark-theme:var(--rs-parent-color-primary-light-theme);
167
118
  --rs-color-primary-dim-dark-theme:var(
168
119
  --rs-parent-color-primary-dim-light-theme
@@ -1,7 +1,17 @@
1
+ import type { RefObject } from 'react';
1
2
  export declare enum DROPDOWN_STATES {
3
+ OPENING_PREPARE = "OPENING_PREPARE",
2
4
  OPENING = "OPENING",
3
5
  OPEN = "OPEN",
4
6
  CLOSING = "CLOSING",
5
7
  CLOSE = "CLOSE"
6
8
  }
7
- export declare function useDropdownState(isOpen: boolean, cancelCloseAnimation?: boolean): [() => void, DROPDOWN_STATES];
9
+ type UseDropdownStateOptions = {
10
+ isOpen: boolean;
11
+ cancelCloseAnimation?: boolean;
12
+ contentRef: RefObject<HTMLDivElement | null>;
13
+ popperElement: HTMLElement | null;
14
+ resolvedPlacement: string | null;
15
+ };
16
+ export declare function useDropdownState({ isOpen, cancelCloseAnimation, contentRef, popperElement, resolvedPlacement }: UseDropdownStateOptions): DROPDOWN_STATES;
17
+ export {};
@@ -1,36 +1,133 @@
1
1
  "use client";
2
- import { useState, useRef, useCallback } from 'react';
2
+ import { useState, useCallback, useEffect, useLayoutEffect } from 'react';
3
+ const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
4
+ const DROPDOWN_TRANSITION_PROPERTIES = ['opacity', 'transform'];
5
+
6
+ const getDropdownTransition = (element, property) => element.getAnimations().find(animation => animation.transitionProperty === property);
7
+
3
8
  var DROPDOWN_STATES;
4
9
 
5
10
  (function (DROPDOWN_STATES) {
11
+ DROPDOWN_STATES["OPENING_PREPARE"] = "OPENING_PREPARE";
6
12
  DROPDOWN_STATES["OPENING"] = "OPENING";
7
13
  DROPDOWN_STATES["OPEN"] = "OPEN";
8
14
  DROPDOWN_STATES["CLOSING"] = "CLOSING";
9
15
  DROPDOWN_STATES["CLOSE"] = "CLOSE";
10
16
  })(DROPDOWN_STATES || (DROPDOWN_STATES = {}));
11
17
 
12
- function useDropdownState(isOpen, cancelCloseAnimation) {
13
- const [, setDummyState] = useState(false);
14
- const stateRef = useRef(DROPDOWN_STATES.CLOSE);
15
- const onAnimationEnd = useCallback(() => {
16
- if ((stateRef.current === DROPDOWN_STATES.CLOSING || stateRef.current === DROPDOWN_STATES.OPENING) && !isOpen) {
17
- stateRef.current = DROPDOWN_STATES.CLOSE; //force rerender
18
+ function useDropdownState(_ref) {
19
+ let {
20
+ isOpen,
21
+ cancelCloseAnimation,
22
+ contentRef,
23
+ popperElement,
24
+ resolvedPlacement
25
+ } = _ref;
26
+ const [dropdownState, setDropdownState] = useState(isOpen ? DROPDOWN_STATES.OPENING_PREPARE : DROPDOWN_STATES.CLOSE);
27
+ const completeTransition = useCallback(expectedState => {
28
+ setDropdownState(state => {
29
+ if (state !== expectedState) {
30
+ return state;
31
+ }
32
+
33
+ if (expectedState === DROPDOWN_STATES.CLOSING) {
34
+ return DROPDOWN_STATES.CLOSE;
35
+ }
36
+
37
+ if (expectedState === DROPDOWN_STATES.OPENING) {
38
+ return DROPDOWN_STATES.OPEN;
39
+ }
40
+
41
+ return state;
42
+ });
43
+ }, []);
44
+ const completeOpeningPreparation = useCallback(() => {
45
+ setDropdownState(state => state === DROPDOWN_STATES.OPENING_PREPARE ? DROPDOWN_STATES.OPENING : state);
46
+ }, []);
47
+ useIsomorphicLayoutEffect(() => {
48
+ const contentElement = contentRef.current;
49
+
50
+ if (dropdownState !== DROPDOWN_STATES.OPENING_PREPARE || !popperElement || !contentElement || !resolvedPlacement) {
51
+ return;
52
+ } // Register the initial opacity/transform with the resolved placement before
53
+ // removing the initial class and starting the opening transition.
54
+
55
+
56
+ contentElement.getBoundingClientRect();
57
+ completeOpeningPreparation();
58
+ }, [completeOpeningPreparation, contentRef, dropdownState, popperElement, resolvedPlacement]); // Track the transitions that currently belong to the OPENING/CLOSING phase:
59
+ // - normally, wait for both opacity and transform before completing the phase;
60
+ // - a style/placement change can cancel or replace a transition without
61
+ // changing the phase; follow the latest one, including a replacement that
62
+ // appears just after the previous transition finishes;
63
+ // - if OPENING changes to CLOSING (or vice versa), cleanup invalidates these
64
+ // waiters and the next phase installs its own, so a stale transition cannot
65
+ // complete it;
66
+ // - if no transitions are created (for example, their duration is 0s),
67
+ // complete the phase immediately.
68
+
69
+ useIsomorphicLayoutEffect(() => {
70
+ if (dropdownState !== DROPDOWN_STATES.OPENING && dropdownState !== DROPDOWN_STATES.CLOSING) {
71
+ return undefined;
72
+ }
18
73
 
19
- setDummyState(s => !s);
20
- } else if (stateRef.current === DROPDOWN_STATES.OPENING && isOpen) {
21
- stateRef.current = DROPDOWN_STATES.OPEN; //force rerender
74
+ const contentElement = contentRef.current;
22
75
 
23
- setDummyState(s => !s);
76
+ if (!contentElement) {
77
+ return undefined;
24
78
  }
25
- }, [isOpen]);
26
79
 
27
- if (isOpen && stateRef.current !== DROPDOWN_STATES.OPEN) {
28
- stateRef.current = DROPDOWN_STATES.OPENING;
29
- } else if (stateRef.current === DROPDOWN_STATES.OPEN && !isOpen) {
30
- stateRef.current = cancelCloseAnimation ? DROPDOWN_STATES.CLOSE : DROPDOWN_STATES.CLOSING;
80
+ let isCurrentPhase = true;
81
+
82
+ const completeCurrentPhase = () => {
83
+ if (isCurrentPhase) {
84
+ completeTransition(dropdownState);
85
+ }
86
+ };
87
+
88
+ const waitForCurrentTransition = (property, previousTransition) => {
89
+ if (!isCurrentPhase) {
90
+ return Promise.resolve();
91
+ }
92
+
93
+ const transition = getDropdownTransition(contentElement, property);
94
+
95
+ if (!transition || transition === previousTransition) {
96
+ return Promise.resolve();
97
+ }
98
+
99
+ return transition.finished.then(() => {
100
+ if (!isCurrentPhase) {
101
+ return Promise.resolve();
102
+ }
103
+
104
+ const replacementTransition = getDropdownTransition(contentElement, property);
105
+
106
+ if (replacementTransition && replacementTransition !== transition) {
107
+ return waitForCurrentTransition(property, transition);
108
+ }
109
+
110
+ return Promise.resolve();
111
+ }, () => waitForCurrentTransition(property, transition));
112
+ };
113
+
114
+ Promise.all(DROPDOWN_TRANSITION_PROPERTIES.map(property => waitForCurrentTransition(property))).then(completeCurrentPhase);
115
+ return () => {
116
+ isCurrentPhase = false;
117
+ };
118
+ }, [completeTransition, contentRef, dropdownState, popperElement, resolvedPlacement]); // React retries this component before rendering its children when the phase changes.
119
+
120
+ if (isOpen && dropdownState === DROPDOWN_STATES.CLOSE) {
121
+ setDropdownState(DROPDOWN_STATES.OPENING_PREPARE);
122
+ } else if (isOpen && dropdownState === DROPDOWN_STATES.CLOSING) {
123
+ setDropdownState(DROPDOWN_STATES.OPENING);
124
+ } else if (!isOpen && dropdownState === DROPDOWN_STATES.OPENING_PREPARE) {
125
+ setDropdownState(DROPDOWN_STATES.CLOSE);
126
+ } else if (!isOpen && (dropdownState === DROPDOWN_STATES.OPEN || dropdownState === DROPDOWN_STATES.OPENING)) {
127
+ setDropdownState(cancelCloseAnimation ? DROPDOWN_STATES.CLOSE : DROPDOWN_STATES.CLOSING);
31
128
  }
32
129
 
33
- return [onAnimationEnd, stateRef.current];
130
+ return dropdownState;
34
131
  }
35
132
 
36
133
  export { DROPDOWN_STATES, useDropdownState };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rescui/dropdown",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "",
5
5
  "license": "Apache-2.0",
6
6
  "author": "JetBrains",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@babel/runtime-corejs3": "^7.26.0",
20
20
  "@popperjs/core": "^2.11.8",
21
- "@rescui/use-resize-observer": "^0.3.3",
21
+ "@rescui/use-resize-observer": "^0.3.4",
22
22
  "classnames": "^2.2.6",
23
23
  "popper-max-size-modifier": "^0.2.0",
24
24
  "react-focus-lock": "^2.8.1",
@@ -31,15 +31,15 @@
31
31
  "react-dom": ">=16.8.0 <20"
32
32
  },
33
33
  "devDependencies": {
34
- "@rescui/colors": "^0.3.2",
35
- "@rescui/icons": "^1.8.3",
34
+ "@rescui/colors": "^0.3.3",
35
+ "@rescui/icons": "^1.8.4",
36
36
  "@rescui/scripts": "^0.5.2",
37
- "@rescui/typography": "^0.27.3",
37
+ "@rescui/typography": "^0.27.4",
38
38
  "@rescui/visual-regression": "^0.1.4"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "rescui-scripts build"
42
42
  },
43
43
  "nx": {},
44
- "gitHead": "7614b4519a4716fbf1151d94c97fda48eab60800"
44
+ "gitHead": "3711cbfc0c797014395421b008aa5331d6d47c6e"
45
45
  }