forked from roginfarrer/collapsed
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.ts
More file actions
220 lines (197 loc) · 6.96 KB
/
utils.ts
File metadata and controls
220 lines (197 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import * as React from 'react'
import {
RefObject,
useState,
useRef,
useEffect,
useCallback,
useLayoutEffect,
} from 'react'
import warning from 'tiny-warning'
import type { AssignableRef } from './types'
type AnyFunction = (...args: any[]) => unknown
// eslint-disable-next-line @typescript-eslint/no-empty-function
export const noop = (): void => {}
export function getElementHeight(
el: RefObject<HTMLElement> | { current?: { scrollHeight: number } }
): string | number {
if (!el?.current) {
warning(
true,
`useCollapse was not able to find a ref to the collapse element via \`getCollapseProps\`. Ensure that the element exposes its \`ref\` prop. If it exposes the ref prop under a different name (like \`innerRef\`), use the \`refKey\` property to change it. Example:
{...getCollapseProps({refKey: 'innerRef'})}`
)
return 'auto'
}
return el.current.scrollHeight
}
// Helper function for render props. Sets a function to be called, plus any additional functions passed in
export const callAll =
(...fns: AnyFunction[]) =>
(...args: any[]): void =>
fns.forEach((fn) => fn && fn(...args))
// https://github.com/mui-org/material-ui/blob/da362266f7c137bf671d7e8c44c84ad5cfc0e9e2/packages/material-ui/src/styles/transitions.js#L89-L98
export function getAutoHeightDuration(height: number | string): number {
if (!height || typeof height === 'string') {
return 0
}
const constant = height / 36
// https://www.wolframalpha.com/input/?i=(4+%2B+15+*+(x+%2F+36+)+**+0.25+%2B+(x+%2F+36)+%2F+5)+*+10
return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10)
}
export function assignRef<RefValueType = any>(
ref: AssignableRef<RefValueType> | null | undefined,
value: any
) {
if (ref == null) return
if (typeof ref === 'function') {
ref(value)
} else {
try {
ref.current = value
} catch (error) {
throw new Error(`Cannot assign value "${value}" to ref "${ref}"`)
}
}
}
/**
* Passes or assigns a value to multiple refs (typically a DOM node). Useful for
* dealing with components that need an explicit ref for DOM calculations but
* also forwards refs assigned by an app.
*
* @param refs Refs to fork
*/
export function mergeRefs<RefValueType = any>(
...refs: (AssignableRef<RefValueType> | null | undefined)[]
) {
if (refs.every((ref) => ref == null)) {
return null
}
return (node: any) => {
refs.forEach((ref) => {
assignRef(ref, node)
})
}
}
export function useControlledState(
isExpanded?: boolean,
defaultExpanded?: boolean
): [boolean, React.Dispatch<React.SetStateAction<boolean>>] {
const [stateExpanded, setStateExpanded] = useState(defaultExpanded || false)
const initiallyControlled = useRef(isExpanded != null)
const expanded = initiallyControlled.current
? (isExpanded as boolean)
: stateExpanded
const setExpanded = useCallback(
(n: boolean | ((prev: boolean) => boolean)) => {
if (!initiallyControlled.current) {
setStateExpanded(n)
}
},
[]
)
useEffect(() => {
warning(
!(initiallyControlled.current && isExpanded == null),
'useCollapse is changing from controlled to uncontrolled. useCollapse should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled collapse for the lifetime of the component. Check the `isExpanded` prop.'
)
warning(
!(!initiallyControlled.current && isExpanded != null),
'useCollapse is changing from uncontrolled to controlled. useCollapse should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled collapse for the lifetime of the component. Check the `isExpanded` prop.'
)
}, [isExpanded])
return [expanded, setExpanded]
}
export function useEffectAfterMount(
cb: () => void,
dependencies: unknown[]
): void {
const justMounted = useRef(true)
// eslint-disable-next-line consistent-return
useEffect(() => {
if (!justMounted.current) {
return cb()
}
justMounted.current = false
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies)
}
/**
* Taken from Reach
* https://github.com/reach/reach-ui/blob/d2b88c50caf52f473a7d20a4493e39e3c5e95b7b/packages/auto-id
*
* Autogenerate IDs to facilitate WAI-ARIA and server rendering.
*
* Note: The returned ID will initially be `null` and will update after a
* component mounts. Users may need to supply their own ID if they need
* consistent values for SSR.
*
* @see Docs https://reach.tech/auto-id
*/
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect
let serverHandoffComplete = false
let id = 0
const genId = () => ++id
export function useUniqueId(idFromProps?: string | null) {
/*
* If this instance isn't part of the initial render, we don't have to do the
* double render/patch-up dance. We can just generate the ID and return it.
*/
const initialId = idFromProps || (serverHandoffComplete ? genId() : null)
const [id, setId] = useState(initialId)
useIsomorphicLayoutEffect(() => {
if (id === null) {
/*
* Patch the ID after render. We do this in `useLayoutEffect` to avoid any
* rendering flicker, though it'll make the first render slower (unlikely
* to matter, but you're welcome to measure your app and let us know if
* it's a problem).
*/
setId(genId())
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
if (serverHandoffComplete === false) {
/*
* Flag all future uses of `useId` to skip the update dance. This is in
* `useEffect` because it goes after `useLayoutEffect`, ensuring we don't
* accidentally bail out of the patch-up dance prematurely.
*/
serverHandoffComplete = true
}
}, [])
return id != null ? String(id) : undefined
}
// Workaround for https://github.com/webpack/webpack/issues/14814
const maybeReactUseId: undefined | (() => string) = (React as any)['useId' + '']
export function useId(idOverride?: string): string | undefined {
if (maybeReactUseId !== undefined) {
const reactId = maybeReactUseId()
return idOverride ?? reactId
}
return useUniqueId(idOverride)
}
export function usePaddingWarning(element: RefObject<HTMLElement>): void {
// @ts-ignore
let warn = (el?: RefObject<HTMLElement>): void => {}
if (process.env.NODE_ENV !== 'production') {
warn = (el) => {
if (!el?.current) {
return
}
const { paddingTop, paddingBottom } = window.getComputedStyle(el.current)
const hasPadding =
(paddingTop && paddingTop !== '0px') ||
(paddingBottom && paddingBottom !== '0px')
warning(
!hasPadding,
'react-collapsed: Padding applied to the collapse element will cause the animation to break and not perform as expected. To fix, apply equivalent padding to the direct descendent of the collapse element.'
)
}
}
useEffect(() => {
warn(element)
}, [element])
}