@coreui/react 4.9.2 → 4.10.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.
@@ -0,0 +1,2 @@
1
+ declare const executeAfterTransition: (callback: () => void, transitionElement: HTMLElement, waitForTransition?: boolean) => void;
2
+ export default executeAfterTransition;
@@ -0,0 +1,2 @@
1
+ declare const getTransitionDurationFromElement: (element: HTMLElement) => number;
2
+ export default getTransitionDurationFromElement;
@@ -1,4 +1,6 @@
1
+ import executeAfterTransition from './executeAfterTransition';
1
2
  import getRTLPlacement from './getRTLPlacement';
3
+ import getTransitionDurationFromElement from './getTransitionDurationFromElement';
2
4
  import isInViewport from './isInViewport';
3
5
  import isRTL from './isRTL';
4
- export { getRTLPlacement, isInViewport, isRTL };
6
+ export { executeAfterTransition, getRTLPlacement, getTransitionDurationFromElement, isInViewport, isRTL, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreui/react",
3
- "version": "4.9.2",
3
+ "version": "4.10.0",
4
4
  "description": "UI Components Library for React.js",
5
5
  "keywords": [
6
6
  "react",
@@ -11,7 +11,7 @@ import PropTypes from 'prop-types'
11
11
  import classNames from 'classnames'
12
12
  import { Transition } from 'react-transition-group'
13
13
 
14
- import { CBackdrop } from '../backdrop/CBackdrop'
14
+ import { CBackdrop } from '../backdrop'
15
15
  import { CConditionalPortal } from '../conditional-portal'
16
16
  import { CModalContent } from './CModalContent'
17
17
  import { CModalDialog } from './CModalDialog'
@@ -35,6 +35,12 @@ export interface CModalProps extends HTMLAttributes<HTMLDivElement> {
35
35
  * @ignore
36
36
  */
37
37
  duration?: number
38
+ /**
39
+ * Puts the focus on the modal when shown.
40
+ *
41
+ * @since v4.10.0
42
+ */
43
+ focus?: boolean
38
44
  /**
39
45
  * Set modal to covers the entire user viewport.
40
46
  */
@@ -96,6 +102,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
96
102
  backdrop = true,
97
103
  className,
98
104
  duration = 150,
105
+ focus = true,
99
106
  fullscreen,
100
107
  keyboard = true,
101
108
  onClose,
@@ -111,6 +118,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
111
118
  },
112
119
  ref,
113
120
  ) => {
121
+ const activeElementRef = useRef<HTMLElement | null>(null)
114
122
  const modalRef = useRef<HTMLDivElement>(null)
115
123
  const modalContentRef = useRef<HTMLDivElement>(null)
116
124
  const forkedRef = useForkedRef(ref, modalRef)
@@ -129,8 +137,11 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
129
137
 
130
138
  useEffect(() => {
131
139
  if (_visible) {
140
+ activeElementRef.current = document.activeElement as HTMLElement | null
132
141
  document.addEventListener('mouseup', handleClickOutside)
133
142
  document.addEventListener('keydown', handleKeyDown)
143
+ } else {
144
+ activeElementRef.current?.focus()
134
145
  }
135
146
 
136
147
  return () => {
@@ -145,6 +156,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
145
156
  }
146
157
 
147
158
  setVisible(false)
159
+
148
160
  return onClose && onClose()
149
161
  }
150
162
 
@@ -165,7 +177,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
165
177
 
166
178
  setTimeout(
167
179
  () => {
168
- modalRef.current?.focus()
180
+ focus && modalRef.current?.focus()
169
181
  },
170
182
  transition ? duration : 0,
171
183
  )
@@ -227,10 +239,13 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
227
239
  className,
228
240
  )}
229
241
  tabIndex={-1}
230
- role="dialog"
242
+ {...(_visible
243
+ ? { 'aria-modal': true, role: 'dialog' }
244
+ : { 'aria-hidden': 'true' })}
231
245
  style={{
232
246
  ...(state !== 'exited' && { display: 'block' }),
233
247
  }}
248
+ {...rest}
234
249
  ref={forkedRef}
235
250
  >
236
251
  <CModalDialog
@@ -239,9 +254,7 @@ export const CModal = forwardRef<HTMLDivElement, CModalProps>(
239
254
  scrollable={scrollable}
240
255
  size={size}
241
256
  >
242
- <CModalContent {...rest} ref={modalContentRef}>
243
- {children}
244
- </CModalContent>
257
+ <CModalContent ref={modalContentRef}>{children}</CModalContent>
245
258
  </CModalDialog>
246
259
  </div>
247
260
  </CModalContext.Provider>
@@ -264,6 +277,7 @@ CModal.propTypes = {
264
277
  children: PropTypes.node,
265
278
  className: PropTypes.string,
266
279
  duration: PropTypes.number,
280
+ focus: PropTypes.bool,
267
281
  fullscreen: PropTypes.oneOfType([
268
282
  PropTypes.bool,
269
283
  PropTypes.oneOf<'sm' | 'md' | 'lg' | 'xl' | 'xxl'>(['sm', 'md', 'lg', 'xl', 'xxl']),
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
4
  import { Transition } from 'react-transition-group'
5
5
 
6
- import { CBackdrop } from '../backdrop/CBackdrop'
6
+ import { CBackdrop } from '../backdrop'
7
7
  import { CConditionalPortal } from '../conditional-portal'
8
8
 
9
9
  import { useForkedRef } from '../../hooks'
@@ -7,7 +7,7 @@ import { Transition } from 'react-transition-group'
7
7
  import { usePopper } from '../../hooks'
8
8
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
9
  import type { Placements, Triggers } from '../../types'
10
- import { getRTLPlacement } from '../../utils'
10
+ import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
11
11
 
12
12
  export interface CPopoverProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'content'> {
13
13
  /**
@@ -164,7 +164,7 @@ export const CPopover: FC<CPopoverProps> = ({
164
164
  onExit={onHide}
165
165
  timeout={{
166
166
  enter: 0,
167
- exit: 200,
167
+ exit: popoverRef.current ? getTransitionDurationFromElement(popoverRef.current) + 50 : 200,
168
168
  }}
169
169
  unmountOnExit
170
170
  >
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom'
3
3
  import PropTypes from 'prop-types'
4
4
  import classNames from 'classnames'
5
5
 
6
- import { CBackdrop } from '../backdrop/CBackdrop'
6
+ import { CBackdrop } from '../backdrop'
7
7
 
8
8
  import { isInViewport } from '../../utils'
9
9
  import { useForkedRef } from '../../hooks'
@@ -7,7 +7,7 @@ import { Transition } from 'react-transition-group'
7
7
  import { usePopper } from '../../hooks'
8
8
  import { fallbackPlacementsPropType, triggerPropType } from '../../props'
9
9
  import type { Placements, Triggers } from '../../types'
10
- import { getRTLPlacement } from '../../utils'
10
+ import { getRTLPlacement, getTransitionDurationFromElement } from '../../utils'
11
11
 
12
12
  export interface CTooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'content'> {
13
13
  /**
@@ -158,7 +158,7 @@ export const CTooltip: FC<CTooltipProps> = ({
158
158
  onExit={onHide}
159
159
  timeout={{
160
160
  enter: 0,
161
- exit: 200,
161
+ exit: tooltipRef.current ? getTransitionDurationFromElement(tooltipRef.current) + 50 : 200,
162
162
  }}
163
163
  unmountOnExit
164
164
  >
@@ -2,6 +2,8 @@ import { useRef } from 'react'
2
2
  import { createPopper } from '@popperjs/core'
3
3
  import type { Instance, Options } from '@popperjs/core'
4
4
 
5
+ import { executeAfterTransition } from '../utils'
6
+
5
7
  interface UsePopperOutput {
6
8
  popper: Instance | undefined
7
9
  initPopper: (reference: HTMLElement, popper: HTMLElement, options: Partial<Options>) => void
@@ -10,14 +12,20 @@ interface UsePopperOutput {
10
12
 
11
13
  export const usePopper = (): UsePopperOutput => {
12
14
  const _popper = useRef<Instance>()
15
+ const el = useRef<HTMLElement>()
13
16
 
14
17
  const initPopper = (reference: HTMLElement, popper: HTMLElement, options: Partial<Options>) => {
15
18
  _popper.current = createPopper(reference, popper, options)
19
+ el.current = popper
16
20
  }
17
21
 
18
22
  const destroyPopper = () => {
19
- if (_popper.current) {
20
- _popper.current.destroy()
23
+ const popperInstance = _popper.current
24
+
25
+ if (popperInstance && el.current) {
26
+ executeAfterTransition(() => {
27
+ popperInstance.destroy()
28
+ }, el.current)
21
29
  }
22
30
 
23
31
  _popper.current = undefined
@@ -0,0 +1,46 @@
1
+ import getTransitionDurationFromElement from './getTransitionDurationFromElement'
2
+
3
+ const execute = (callback: () => void) => {
4
+ if (typeof callback === 'function') {
5
+ callback()
6
+ }
7
+ }
8
+
9
+ const triggerTransitionEnd = (element: HTMLElement) => {
10
+ element.dispatchEvent(new Event('transitionend'))
11
+ }
12
+
13
+ const executeAfterTransition = (
14
+ callback: () => void,
15
+ transitionElement: HTMLElement,
16
+ waitForTransition = true,
17
+ ) => {
18
+ if (!waitForTransition) {
19
+ execute(callback)
20
+ return
21
+ }
22
+
23
+ const durationPadding = 5
24
+ const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding
25
+
26
+ let called = false
27
+
28
+ const handler = ({ target }: { target: any }) => {
29
+ if (target !== transitionElement) {
30
+ return
31
+ }
32
+
33
+ called = true
34
+ transitionElement.removeEventListener('transitionend', handler)
35
+ execute(callback)
36
+ }
37
+
38
+ transitionElement.addEventListener('transitionend', handler)
39
+ setTimeout(() => {
40
+ if (!called) {
41
+ triggerTransitionEnd(transitionElement)
42
+ }
43
+ }, emulatedDuration)
44
+ }
45
+
46
+ export default executeAfterTransition
@@ -0,0 +1,24 @@
1
+ const getTransitionDurationFromElement = (element: HTMLElement) => {
2
+ if (!element) {
3
+ return 0
4
+ }
5
+
6
+ // Get transition-duration of the element
7
+ let { transitionDuration, transitionDelay } = window.getComputedStyle(element)
8
+
9
+ const floatTransitionDuration = Number.parseFloat(transitionDuration)
10
+ const floatTransitionDelay = Number.parseFloat(transitionDelay)
11
+
12
+ // Return 0 if element or transition duration is not found
13
+ if (!floatTransitionDuration && !floatTransitionDelay) {
14
+ return 0
15
+ }
16
+
17
+ // If multiple durations are defined, take the first
18
+ transitionDuration = transitionDuration.split(',')[0]
19
+ transitionDelay = transitionDelay.split(',')[0]
20
+
21
+ return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * 1000
22
+ }
23
+
24
+ export default getTransitionDurationFromElement
@@ -1,5 +1,13 @@
1
+ import executeAfterTransition from './executeAfterTransition'
1
2
  import getRTLPlacement from './getRTLPlacement'
3
+ import getTransitionDurationFromElement from './getTransitionDurationFromElement'
2
4
  import isInViewport from './isInViewport'
3
5
  import isRTL from './isRTL'
4
6
 
5
- export { getRTLPlacement, isInViewport, isRTL }
7
+ export {
8
+ executeAfterTransition,
9
+ getRTLPlacement,
10
+ getTransitionDurationFromElement,
11
+ isInViewport,
12
+ isRTL,
13
+ }