@instructure/ui-dom-utils 10.26.1-snapshot-1 → 11.0.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.
@@ -22,34 +22,42 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import ReactDOM from 'react-dom'
26
- import type { ReactInstance, RefObject } from 'react'
27
25
  import type { UIElement } from '@instructure/shared-types'
28
- type ReactNodeWithRef = ReactInstance & {
29
- ref: RefObject<Element | ReactInstance> | Element | ReactInstance
30
- }
31
- type CustomRefNode =
32
- | ReactNodeWithRef
33
- | RefObject<Element>
34
- | Element
35
- | ReactInstance
26
+ import ReactDOM from 'react-dom'
36
27
 
37
- const isReactNodeWithRef = (el: unknown): el is ReactNodeWithRef => {
38
- return !!el && (el as ReactNodeWithRef).ref !== undefined
39
- }
40
- const isRefObject = (obj: unknown): obj is RefObject<unknown> => {
41
- return !!obj && (obj as RefObject<unknown>).current !== undefined
28
+ /**
29
+ * Return the ref of an element in a way that is compatible both with
30
+ * React 18 (element.ref) and React 19+ (element.props.ref)
31
+ */
32
+ function getElementRef(elem: { ref?: any; props?: { ref: any } }) {
33
+ // Note that this is added by InstUI components like Position
34
+ if (elem.ref) {
35
+ return elem.ref
36
+ }
37
+ // note that trying to access this will display a warning in React 18.
38
+ // this is OK because without it React itself would show an error because
39
+ // its using findDOMNode
40
+ return elem?.props?.ref
42
41
  }
42
+
43
43
  /**
44
44
  * ---
45
45
  * category: utilities/DOM
46
46
  * ---
47
47
  *
48
- * Wrapper function for React.findDOMNode
48
+ * Returns the underlying DOM node. It looks the following places:
49
+ * - If it's a native HTML element e.g. `HtmlSpanElement` its returned as is.
50
+ * - If the element has a non-nullish `ref` prop or instance variable which is a
51
+ * `HTMLElement` its returned. If it`s something else then `findDOMNode` is called
52
+ * on the object. (it also checks `ref.current`)
53
+ * - If the element has a `ref` prop or instance variable whose value is `null`,
54
+ * `undefined` is returned.
55
+ * - If the element does not have a `ref` prop or instance variable `undefined`
56
+ * is returned with a warning
49
57
  * @module findDOMNode
50
58
  *
51
- * @param { Node | Window | React.ReactElement | React.Component | function } el - component, DOM node, or function returning a DOM node
52
- * @returns { Node | Window | null | undefined } The root node of this element
59
+ * @param el - component, DOM node, or function returning a DOM node
60
+ * @returns The native DOM node of this element or `undefined`
53
61
  */
54
62
  function findDOMNode(el?: UIElement): Element | Node | Window | undefined {
55
63
  const node = typeof el === 'function' ? el() : el
@@ -63,27 +71,33 @@ function findDOMNode(el?: UIElement): Element | Node | Window | undefined {
63
71
  ) {
64
72
  return node as Node | Window
65
73
  } else if (node) {
66
- const reactNode = node as CustomRefNode
67
-
68
- if (isReactNodeWithRef(reactNode)) {
69
- const refElement = isRefObject(reactNode.ref)
70
- ? reactNode.ref.current
71
- : reactNode.ref
74
+ const ref = getElementRef(node as object)
75
+ const refElement = ref?.current ? ref.current : ref
76
+ if (refElement) {
72
77
  if (!(refElement instanceof HTMLElement)) {
73
78
  return findDOMNode(refElement)
74
79
  }
75
80
  return refElement
76
- } else {
77
- const elName = (reactNode as any).constructor.componentId
78
- ? (reactNode as any).constructor.componentId
79
- : (reactNode as any).constructor.name
80
-
81
- console.warn(
82
- `Warning: ${elName} doesn't have "ref" property.\nReactDOM.findDOMNode is deprecated in Strict mode, consider using refs instead. From InstUI v9, components must have the "ref" property for findDOMNode to work.\nSee more here: https://instructure.design/#accessing-the-dom`
83
- )
84
-
85
- return ReactDOM.findDOMNode(node as ReactInstance)!
81
+ } else if (refElement === null) {
82
+ // weird, but changing it would be a breaking change
83
+ return undefined
84
+ }
85
+ const elName = (node as any).constructor.componentId
86
+ ? (node as any).constructor.componentId
87
+ : (node as any).constructor.name
88
+ console.error(
89
+ `findDOMNode error: ${elName} doesn't have "ref" property.\n
90
+ your code will likely not work with React 19+, because ReactDOM.findDOMNode() was removed.\n
91
+ See more here: https://instructure.design/#accessing-the-dom`
92
+ )
93
+ // TODO remove when we only support React 19 or greater
94
+ const ReactDomInstance = ReactDOM as {
95
+ findDOMNode?: (arg: unknown) => Element
96
+ }
97
+ if (typeof ReactDomInstance.findDOMNode === 'function') {
98
+ return ReactDomInstance.findDOMNode(node)
86
99
  }
100
+ return undefined
87
101
  }
88
102
  return undefined
89
103
  }
@@ -37,7 +37,7 @@
37
37
  // or even better use the native <dialog> component.
38
38
  // tabbable has issues with scrollable containers, e.g.
39
39
  // https://github.com/focus-trap/tabbable/issues/167
40
- import { getComputedStyle, findDOMNode } from './'
40
+ import { getCSSStyleDeclaration, findDOMNode } from './'
41
41
  import type { UIElement } from '@instructure/shared-types'
42
42
 
43
43
  const focusableSelector = [
@@ -103,8 +103,8 @@ function findFocusable(
103
103
  }
104
104
 
105
105
  function hidden(element: Element | Node) {
106
- const cs = getComputedStyle(element)
107
- return cs.display === 'none'
106
+ const cs = getCSSStyleDeclaration(element)
107
+ return cs?.display === 'none'
108
108
  }
109
109
 
110
110
  function positioned(element: Element | Node) {
@@ -112,11 +112,12 @@ function positioned(element: Element | Node) {
112
112
  if (POS.includes((element as HTMLElement).style.position?.toLowerCase())) {
113
113
  return true
114
114
  }
115
- if (
116
- POS.includes(
117
- getComputedStyle(element).getPropertyValue('position')?.toLowerCase()
118
- )
119
- ) {
115
+
116
+ const position = getCSSStyleDeclaration(element)
117
+ ?.getPropertyValue('position')
118
+ ?.toLowerCase()
119
+
120
+ if (position && POS.includes(position)) {
120
121
  return true
121
122
  }
122
123
  return false
@@ -140,6 +141,4 @@ function focusable(element: Element & { disabled?: boolean }) {
140
141
  }
141
142
 
142
143
  export default findFocusable
143
- export {
144
- findFocusable
145
- }
144
+ export { findFocusable }
@@ -34,23 +34,29 @@ import type { UIElement } from '@instructure/shared-types'
34
34
  *
35
35
  * Get the associated CSS properties and values for a
36
36
  * specified element
37
- * @module getComputedStyle
37
+ * @module getCSSStyleDeclaration
38
38
  *
39
39
  * @param { Node | Window | React.ReactElement | React.Component | function } el - component or DOM node
40
40
  * @param { string | null | undefined } pseudoElt - A string specifying the pseudo-element to match. Omitted (or null ) for real elements.
41
- * @returns { Object } object containing css properties and values for the element
41
+ * @returns { CSSStyleDeclaration | undefined } The style declaration containing css properties and values for the element or undefined
42
42
  */
43
- function getComputedStyle(el?: UIElement, pseudoElt?: string | null) {
44
- let style = {}
43
+ function getCSSStyleDeclaration(el?: UIElement, pseudoElt?: string | null) {
44
+ let style: CSSStyleDeclaration | undefined
45
45
  if (canUseDOM) {
46
46
  const node = el && findDOMNode(el)
47
47
  if (node) {
48
48
  const window = ownerWindow(el)
49
- style = window ? window.getComputedStyle(node as Element, pseudoElt) : {}
49
+ try {
50
+ style = window
51
+ ? window.getComputedStyle(node as Element, pseudoElt)
52
+ : undefined
53
+ } catch {
54
+ style = undefined
55
+ }
50
56
  }
51
57
  }
52
- return style as CSSStyleDeclaration
58
+ return style
53
59
  }
54
60
 
55
- export default getComputedStyle
56
- export { getComputedStyle }
61
+ export default getCSSStyleDeclaration
62
+ export { getCSSStyleDeclaration }
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { canUseDOM } from './canUseDOM'
25
25
  import { ownerDocument } from './ownerDocument'
26
- import { getComputedStyle } from './getComputedStyle'
26
+ import { getCSSStyleDeclaration } from './getCSSStyleDeclaration'
27
27
  import React from 'react'
28
28
 
29
29
  const COMPUTED_CACHE: WeakMap<any, number> = new WeakMap()
@@ -63,7 +63,7 @@ function getFontSize(
63
63
  }
64
64
 
65
65
  const fontSize = parseInt(
66
- (getComputedStyle(container) as CSSStyleDeclaration).getPropertyValue(
66
+ (getCSSStyleDeclaration(container) as CSSStyleDeclaration).getPropertyValue(
67
67
  'font-size'
68
68
  )
69
69
  )
@@ -24,7 +24,7 @@
24
24
 
25
25
  import { findDOMNode } from './findDOMNode'
26
26
  import { canUseDOM } from './canUseDOM'
27
- import { getComputedStyle } from './getComputedStyle'
27
+ import { getCSSStyleDeclaration } from './getCSSStyleDeclaration'
28
28
  import { ownerDocument } from './ownerDocument'
29
29
  import { UIElement } from '@instructure/shared-types'
30
30
 
@@ -59,7 +59,7 @@ function getOffsetParents(el?: UIElement) {
59
59
  parent.nodeType === 1 &&
60
60
  (parent as Element).tagName !== 'BODY'
61
61
  ) {
62
- const style = getComputedStyle(parent) as CSSStyleDeclaration
62
+ const style = getCSSStyleDeclaration(parent) as CSSStyleDeclaration
63
63
  const transform =
64
64
  style.getPropertyValue('-webkit-transform') ||
65
65
  style.getPropertyValue('-moz-transform') ||
@@ -24,7 +24,7 @@
24
24
 
25
25
  import { findDOMNode } from './findDOMNode'
26
26
  import { canUseDOM } from './canUseDOM'
27
- import { getComputedStyle } from './getComputedStyle'
27
+ import { getCSSStyleDeclaration } from './getCSSStyleDeclaration'
28
28
  import { UIElement } from '@instructure/shared-types'
29
29
 
30
30
  /**
@@ -53,8 +53,8 @@ function getScrollParents(el?: UIElement) {
53
53
  if (node) {
54
54
  // In firefox if the element is inside an iframe with display: none; window.getComputedStyle() will return null;
55
55
  // https://bugzilla.mozilla.org/show_bug.cgi?id=548397
56
- const computedStyle = getComputedStyle(node) || {}
57
- const position = computedStyle.position
56
+ const computedStyle = getCSSStyleDeclaration(node)
57
+ const position = computedStyle?.position
58
58
 
59
59
  if (position === 'fixed') {
60
60
  return [(node as Node).ownerDocument]
@@ -68,7 +68,7 @@ function getScrollParents(el?: UIElement) {
68
68
  ) {
69
69
  let style
70
70
  try {
71
- style = getComputedStyle(parent)
71
+ style = getCSSStyleDeclaration(parent)
72
72
  } catch (err: any) {} // eslint-disable-line no-empty
73
73
 
74
74
  if (typeof style === 'undefined' || style === null) {
package/src/index.ts CHANGED
@@ -37,7 +37,7 @@ export { getActiveElement } from './getActiveElement'
37
37
  export { getBoundingClientRect } from './getBoundingClientRect'
38
38
  export type { RectType } from './getBoundingClientRect'
39
39
  export { getClassList } from './getClassList'
40
- export { getComputedStyle } from './getComputedStyle'
40
+ export { getCSSStyleDeclaration } from './getCSSStyleDeclaration'
41
41
  export { getFontSize } from './getFontSize'
42
42
  export { getOffsetParents } from './getOffsetParents'
43
43
  export { getScrollParents } from './getScrollParents'
package/src/isVisible.ts CHANGED
@@ -23,7 +23,7 @@
23
23
  */
24
24
 
25
25
  import { findDOMNode } from './findDOMNode'
26
- import { getComputedStyle } from './getComputedStyle'
26
+ import { getCSSStyleDeclaration } from './getCSSStyleDeclaration'
27
27
  import { UIElement } from '@instructure/shared-types'
28
28
 
29
29
  /**
@@ -52,22 +52,24 @@ function isVisible(el?: UIElement, recursive = true): boolean {
52
52
  return isVisible(parent, recursive)
53
53
  }
54
54
 
55
- const style = node ? getComputedStyle(node) : ({} as CSSStyleDeclaration)
55
+ const style = node
56
+ ? getCSSStyleDeclaration(node)
57
+ : ({} as CSSStyleDeclaration)
56
58
  // physically and visually hidden
57
- if (style.display === 'none') {
59
+ if (style?.display === 'none') {
58
60
  return false
59
61
  }
60
62
  // visually hidden
61
- if (style.visibility === 'hidden' || style.opacity === '0') {
63
+ if (style?.visibility === 'hidden' || style?.opacity === '0') {
62
64
  return false
63
65
  }
64
66
  // hidden by clipping
65
67
  if (
66
- style.overflow === 'hidden' &&
67
- style.position === 'absolute' &&
68
- style.clip !== 'auto'
68
+ style?.overflow === 'hidden' &&
69
+ style?.position === 'absolute' &&
70
+ style?.clip !== 'auto'
69
71
  ) {
70
- const rect = style.clip.substring(5).slice(0, -1).split(', ')
72
+ const rect = style?.clip.substring(5).slice(0, -1).split(', ')
71
73
  let zeros = true
72
74
  rect.forEach((a) => {
73
75
  if (a !== '0px') {