Diff: STRATO-apps/wordpress_03/app/wp-content/themes/blocksy/static/js/options/helpers/usePopoverMaker.js

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + import { useMemo, useRef, useState, useEffect } from '@wordpress/element'
2 + import classnames from 'classnames'
3 + import { __ } from 'ct-i18n'
4 +
5 + // TODO: Rework this to use useLayoutEffect here.
6 + // Also, always compute layout first, animate a bit later.
7 + //
8 + // Need to get rid of all the <Transition> components and use useSpring directly
9 + // instead.
10 + //
11 + // This way nullifyTransforms() will not be needed anymore.
12 +
13 + export function nullifyTransforms(el) {
14 + const parseTransform = (el) =>
15 + window
16 + .getComputedStyle(el)
17 + .transform.split(/\(|,|\)/)
18 + .slice(1, -1)
19 + .map((v) => parseFloat(v))
20 +
21 + // 1
22 + let { top, left, width, height } = el.getBoundingClientRect()
23 + let transformArr = parseTransform(el)
24 +
25 + if (transformArr.length == 6) {
26 + // 2D matrix
27 + const t = transformArr
28 +
29 + // 2
30 + let det = t[0] * t[3] - t[1] * t[2]
31 +
32 + // 3
33 + return {
34 + width: width / t[0],
35 + height: height / t[3],
36 + left: (left * t[3] - top * t[2] + t[2] * t[5] - t[4] * t[3]) / det,
37 + top: (-left * t[1] + top * t[0] + t[4] * t[1] - t[0] * t[5]) / det,
38 + }
39 + } else {
40 + // This case is not handled because it's very rarely needed anyway.
41 + // We just return the tranformed metrics, as they are, for consistency.
42 + return { top, left, width, height }
43 + }
44 + }
45 +
46 + const usePopoverMaker = ({
47 + contentRef: contentRefProp,
48 + shouldCalculate = true,
49 + ref,
50 + defaultHeight = 0,
51 + } = {}) => {
52 + const contentRef = useRef()
53 + const [s, setState] = useState(null)
54 +
55 + const refresh = () => {
56 + if (!shouldCalculate) {
57 + return
58 + }
59 +
60 + setState(Math.random())
61 + }
62 +
63 + const refreshOnScroll = (e) => {
64 + if (!e.target || !e.target.classList || !e.target.classList.contains) {
65 + return
66 + }
67 +
68 + let modalRef = contentRefProp || contentRef
69 +
70 + if (e.target.classList.contains('ct-modal-scroll')) {
71 + refresh()
72 + }
73 +
74 + if (
75 + modalRef &&
76 + modalRef.current &&
77 + !modalRef.current.contains(e.target)
78 + ) {
79 + refresh()
80 + }
81 + }
82 +
83 + useEffect(() => {
84 + setTimeout(() => {
85 + refresh()
86 + }, 500)
87 +
88 + window.addEventListener('resize', refresh)
89 + window.addEventListener('scroll', refreshOnScroll, true)
90 +
91 + let observer
92 +
93 + if (ref.current) {
94 + observer = new window.ResizeObserver(refresh)
95 +
96 + observer.observe(ref.current, {
97 + attributes: true,
98 + })
99 +
100 + if (ref.current.closest('.ct-tabs-scroll')) {
101 + observer.observe(ref.current.closest('.ct-tabs-scroll'), {
102 + attributes: true,
103 + })
104 + }
105 +
106 + if (ref.current.closest('.ct-modal-scroll')) {
107 + observer.observe(ref.current.closest('.ct-modal-scroll'), {
108 + attributes: true,
109 + })
110 + }
111 +
112 + if (ref.current.closest('.customize-pane-child')) {
113 + observer.observe(ref.current.closest('.customize-pane-child'), {
114 + attributes: true,
115 + })
116 + }
117 + }
118 +
119 + if (contentRefProp ? contentRefProp.current : contentRef.current) {
120 + if (!observer) {
121 + observer = new window.ResizeObserver(refresh)
122 + }
123 +
124 + observer.observe(
125 + contentRefProp ? contentRefProp.current : contentRef.current,
126 + {
127 + attributes: true,
128 + }
129 + )
130 + }
131 +
132 + return () => {
133 + window.removeEventListener('resize', refresh)
134 + window.removeEventListener('scroll', refreshOnScroll, true)
135 +
136 + if (observer) {
137 + observer.disconnect()
138 + }
139 + }
140 + }, [shouldCalculate, contentRef.current, contentRefProp, ref.current])
141 +
142 + let { right, yOffset, position, otherStyles } = useMemo(() => {
143 + let right = 0
144 + let yOffset = 0
145 + let position = 'bottom'
146 + let otherStyles = {}
147 +
148 + if (!shouldCalculate) {
149 + return { yOffset, right, position }
150 + }
151 +
152 + if (ref.current) {
153 + let rect = ref.current.getBoundingClientRect()
154 +
155 + let el = ref.current.closest('.ct-select-input')
156 + ? ref.current.closest('.ct-select-input')
157 + : ref.current
158 +
159 + let maybeWidthFlag = getComputedStyle(el, ':before').content
160 +
161 + yOffset = rect.top + rect.height
162 + right = window.innerWidth - rect.right
163 +
164 + if (document.body.classList.contains('rtl')) {
165 + right = rect.left
166 + }
167 +
168 + if (maybeWidthFlag.indexOf('ref-width') > -1) {
169 + let width = rect.width
170 +
171 + if (
172 + maybeWidthFlag.indexOf('left') > -1 &&
173 + el.previousElementSibling
174 + ) {
175 + if (document.body.classList.contains('rtl')) {
176 + width =
177 + el.previousElementSibling.getBoundingClientRect()
178 + .right - rect.left
179 + } else {
180 + width =
181 + rect.right -
182 + el.previousElementSibling.getBoundingClientRect()
183 + .left
184 + }
185 + }
186 +
187 + if (maybeWidthFlag.indexOf('right') > -1) {
188 + let nextRect = el.parentNode // el.nextElementSibling || el.parentNode
189 + .getBoundingClientRect()
190 +
191 + if (document.body.classList.contains('rtl')) {
192 + right = nextRect.left
193 + width = rect.right - nextRect.left
194 + } else {
195 + right = window.innerWidth - nextRect.right
196 + width = nextRect.right - rect.left
197 + }
198 + }
199 +
200 + otherStyles['--x-select-dropdown-width'] = `${width}px`
201 + }
202 +
203 + let popoverRect =
204 + (contentRefProp && contentRefProp.current) || contentRef.current
205 + ? nullifyTransforms(
206 + contentRefProp
207 + ? contentRefProp.current
208 + : contentRef.current
209 + )
210 + : { height: defaultHeight }
211 +
212 + if (
213 + yOffset + popoverRect.height > window.innerHeight &&
214 + rect.top - 15 > popoverRect.height
215 + ) {
216 + position = 'top'
217 + yOffset = window.innerHeight - rect.bottom + rect.height
218 + }
219 +
220 + if (
221 + yOffset + popoverRect.height > window.innerHeight &&
222 + position === 'bottom'
223 + ) {
224 + position = 'top'
225 + yOffset = 0
226 + }
227 + }
228 +
229 + return { yOffset, right, position, otherStyles }
230 + }, [
231 + s,
232 + shouldCalculate,
233 + ref,
234 + ref.current,
235 + contentRefProp,
236 + contentRef.current,
237 + defaultHeight,
238 + ])
239 +
240 + return {
241 + refreshPopover: refresh,
242 + styles: {
243 + '--modal-y-offset': `${yOffset}px`,
244 + '--modal-x-offset': `${right}px`,
245 + ...otherStyles,
246 + // [position === 'bottom' ? 'top' : 'bottom']: `${yOffset}px`,
247 + // right: `${right}px`,
248 + },
249 +
250 + position,
251 +
252 + popoverProps: {
253 + ref: contentRefProp || contentRef,
254 + ...(position
255 + ? {
256 + 'data-position': position,
257 + }
258 + : {}),
259 + },
260 + }
261 + }
262 +
263 + export default usePopoverMaker
264 +