@tamagui/use-element-layout 2.0.0-rc.3 → 2.0.0-rc.30
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/dist/cjs/index.cjs +123 -53
- package/dist/cjs/index.js +217 -132
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.native.js +181 -86
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/index.js +198 -136
- package/dist/esm/index.js.map +1 -6
- package/dist/esm/index.mjs +123 -54
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +181 -87
- package/dist/esm/index.native.js.map +1 -1
- package/package.json +7 -10
- package/src/index.tsx +280 -89
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +4 -4
package/dist/cjs/index.cjs
CHANGED
|
@@ -31,19 +31,24 @@ __export(index_exports, {
|
|
|
31
31
|
measureInWindow: () => measureInWindow,
|
|
32
32
|
measureLayout: () => measureLayout,
|
|
33
33
|
measureNode: () => measureNode,
|
|
34
|
+
registerLayoutNode: () => registerLayoutNode,
|
|
34
35
|
setOnLayoutStrategy: () => setOnLayoutStrategy,
|
|
35
36
|
useElementLayout: () => useElementLayout
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(index_exports);
|
|
38
39
|
var import_constants = require("@tamagui/constants"),
|
|
39
|
-
import_is_equal_shallow = require("@tamagui/is-equal-shallow"),
|
|
40
40
|
import_react = require("react"),
|
|
41
41
|
import_jsx_runtime = require("react/jsx-runtime");
|
|
42
42
|
const LayoutHandlers = /* @__PURE__ */new WeakMap(),
|
|
43
43
|
LayoutDisableKey = /* @__PURE__ */new WeakMap(),
|
|
44
44
|
Nodes = /* @__PURE__ */new Set(),
|
|
45
45
|
IntersectionState = /* @__PURE__ */new WeakMap(),
|
|
46
|
-
|
|
46
|
+
usePretransformDimensions = () => globalThis.__TAMAGUI_ONLAYOUT_PRETRANSFORM === !0 || process.env.TAMAGUI_ONLAYOUT_PRETRANSFORM === "1";
|
|
47
|
+
let _debugLayout;
|
|
48
|
+
function isDebugLayout() {
|
|
49
|
+
return _debugLayout === void 0 && (_debugLayout = typeof window < "u" && new URLSearchParams(window.location.search).has("__tamaDebugLayout")), _debugLayout;
|
|
50
|
+
}
|
|
51
|
+
const DisableLayoutContextValues = {},
|
|
47
52
|
DisableLayoutContextKey = (0, import_react.createContext)(""),
|
|
48
53
|
ENABLE = typeof IntersectionObserver < "u",
|
|
49
54
|
LayoutMeasurementController = ({
|
|
@@ -63,8 +68,7 @@ let globalIntersectionObserver = null,
|
|
|
63
68
|
function setOnLayoutStrategy(state) {
|
|
64
69
|
strategy = state;
|
|
65
70
|
}
|
|
66
|
-
const NodeRectCache = /* @__PURE__ */new WeakMap()
|
|
67
|
-
LastChangeTime = /* @__PURE__ */new WeakMap();
|
|
71
|
+
const NodeRectCache = /* @__PURE__ */new WeakMap();
|
|
68
72
|
let avoidUpdates = !0;
|
|
69
73
|
const queuedUpdates = /* @__PURE__ */new Map();
|
|
70
74
|
function enable() {
|
|
@@ -72,16 +76,33 @@ function enable() {
|
|
|
72
76
|
}
|
|
73
77
|
function startGlobalObservers() {
|
|
74
78
|
!ENABLE || globalIntersectionObserver || (globalIntersectionObserver = new IntersectionObserver(entries => {
|
|
75
|
-
entries.
|
|
76
|
-
const
|
|
79
|
+
for (let i = 0; i < entries.length; i++) {
|
|
80
|
+
const entry = entries[i],
|
|
81
|
+
node = entry.target;
|
|
77
82
|
IntersectionState.get(node) !== entry.isIntersecting && IntersectionState.set(node, entry.isIntersecting);
|
|
78
|
-
}
|
|
83
|
+
}
|
|
79
84
|
}, {
|
|
80
85
|
threshold: 0
|
|
81
86
|
}));
|
|
82
87
|
}
|
|
88
|
+
function rectsEqual(a, b) {
|
|
89
|
+
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
90
|
+
}
|
|
83
91
|
if (ENABLE) {
|
|
92
|
+
let ensureRectFetchObserver = function () {
|
|
93
|
+
return rectFetchObserver || (rectFetchObserver = new IntersectionObserver(entries => {
|
|
94
|
+
lastCallbackDelay = Math.round(performance.now() - rectFetchStartTime);
|
|
95
|
+
for (let i = 0; i < entries.length; i++) BoundingRects.set(entries[i].target, entries[i].boundingClientRect);
|
|
96
|
+
process.env.NODE_ENV === "development" && isDebugLayout() && lastCallbackDelay > 50 && console.warn("[onLayout-io-delay]", lastCallbackDelay + "ms", entries.length, "entries"), rectFetchResolve && (rectFetchResolve(!0), rectFetchResolve = null);
|
|
97
|
+
}, {
|
|
98
|
+
threshold: 0
|
|
99
|
+
}), rectFetchObserver);
|
|
100
|
+
};
|
|
84
101
|
const BoundingRects = /* @__PURE__ */new WeakMap();
|
|
102
|
+
let rectFetchObserver = null,
|
|
103
|
+
rectFetchResolve = null,
|
|
104
|
+
rectFetchStartTime = 0,
|
|
105
|
+
lastCallbackDelay = 0;
|
|
85
106
|
async function updateLayoutIfChanged(node) {
|
|
86
107
|
const onLayout = LayoutHandlers.get(node);
|
|
87
108
|
if (typeof onLayout != "function") return;
|
|
@@ -89,68 +110,90 @@ if (ENABLE) {
|
|
|
89
110
|
if (!parentNode) return;
|
|
90
111
|
let nodeRect, parentRect;
|
|
91
112
|
if (strategy === "async") {
|
|
92
|
-
|
|
93
|
-
if (!nr || !pr) return;
|
|
94
|
-
nodeRect = nr, parentRect = pr;
|
|
113
|
+
if (nodeRect = BoundingRects.get(node), parentRect = BoundingRects.get(parentNode), !nodeRect || !parentRect) return;
|
|
95
114
|
} else nodeRect = node.getBoundingClientRect(), parentRect = parentNode.getBoundingClientRect();
|
|
96
|
-
if (!nodeRect || !parentRect) return;
|
|
97
115
|
const cachedRect = NodeRectCache.get(node),
|
|
98
|
-
cachedParentRect = NodeRectCache.get(parentNode)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
!(0, import_is_equal_shallow.isEqualShallow)(cachedRect, nodeRect) ||
|
|
103
|
-
// @ts-expect-error DOMRectReadOnly can go into object
|
|
104
|
-
!(0, import_is_equal_shallow.isEqualShallow)(cachedParentRect, parentRect)) {
|
|
116
|
+
cachedParentRect = NodeRectCache.get(parentNode),
|
|
117
|
+
nodeChanged = !cachedRect || !rectsEqual(cachedRect, nodeRect),
|
|
118
|
+
parentChanged = !cachedParentRect || !rectsEqual(cachedParentRect, parentRect);
|
|
119
|
+
if (nodeChanged || parentChanged) {
|
|
105
120
|
NodeRectCache.set(node, nodeRect), NodeRectCache.set(parentNode, parentRect);
|
|
106
|
-
const event = getElementLayoutEvent(nodeRect, parentRect);
|
|
107
|
-
|
|
121
|
+
const event = getElementLayoutEvent(nodeRect, parentRect, node);
|
|
122
|
+
process.env.NODE_ENV === "development" && isDebugLayout() && console.log("[useElementLayout] change", {
|
|
123
|
+
tag: node.tagName,
|
|
124
|
+
id: node.id || void 0,
|
|
125
|
+
className: (node.className || "").slice(0, 60) || void 0,
|
|
126
|
+
layout: event.nativeEvent.layout,
|
|
127
|
+
first: !cachedRect
|
|
128
|
+
}), avoidUpdates ? queuedUpdates.set(node, () => onLayout(event)) : onLayout(event);
|
|
108
129
|
}
|
|
109
130
|
}
|
|
110
|
-
const
|
|
111
|
-
|
|
131
|
+
const rAF = typeof requestAnimationFrame < "u" ? requestAnimationFrame : void 0,
|
|
132
|
+
userSkipVal = process.env.TAMAGUI_LAYOUT_FRAME_SKIP,
|
|
133
|
+
BASE_SKIP_FRAMES = userSkipVal ? +userSkipVal : 10,
|
|
134
|
+
MAX_SKIP_FRAMES = 20;
|
|
135
|
+
let skipFrames = BASE_SKIP_FRAMES,
|
|
136
|
+
frameCount = 0;
|
|
112
137
|
async function layoutOnAnimationFrame() {
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (!(node.parentElement instanceof HTMLElement)) continue;
|
|
126
|
-
const disableKey = LayoutDisableKey.get(node);
|
|
127
|
-
disableKey && DisableLayoutContextValues[disableKey] === !0 || IntersectionState.get(node) !== !1 && (didObserve = !0, io.observe(node), io.observe(node.parentElement), visibleNodes.push(node));
|
|
138
|
+
if (frameCount++ % skipFrames !== 0) {
|
|
139
|
+
rAF ? rAF(layoutOnAnimationFrame) : setTimeout(layoutOnAnimationFrame, 16);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (frameCount >= Number.MAX_SAFE_INTEGER && (frameCount = 0), strategy !== "off") {
|
|
143
|
+
const visibleNodes = [],
|
|
144
|
+
parentsToObserve = /* @__PURE__ */new Set();
|
|
145
|
+
for (const node of Nodes) {
|
|
146
|
+
const parentElement = node.parentElement;
|
|
147
|
+
if (!(parentElement instanceof HTMLElement)) {
|
|
148
|
+
cleanupNode(node);
|
|
149
|
+
continue;
|
|
128
150
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
151
|
+
const disableKey = LayoutDisableKey.get(node);
|
|
152
|
+
disableKey && DisableLayoutContextValues[disableKey] === !0 || IntersectionState.get(node) !== !1 && (visibleNodes.push(node), parentsToObserve.add(parentElement));
|
|
153
|
+
}
|
|
154
|
+
if (visibleNodes.length > 0) {
|
|
155
|
+
const io = ensureRectFetchObserver();
|
|
156
|
+
rectFetchStartTime = performance.now();
|
|
157
|
+
for (let i = 0; i < visibleNodes.length; i++) io.observe(visibleNodes[i]);
|
|
158
|
+
for (const parent of parentsToObserve) io.observe(parent);
|
|
159
|
+
await new Promise(res => {
|
|
160
|
+
rectFetchResolve = res;
|
|
161
|
+
});
|
|
162
|
+
for (let i = 0; i < visibleNodes.length; i++) io.unobserve(visibleNodes[i]);
|
|
163
|
+
for (const parent of parentsToObserve) io.unobserve(parent);
|
|
164
|
+
lastCallbackDelay > 50 ? skipFrames = Math.min(skipFrames + 2, MAX_SKIP_FRAMES) : lastCallbackDelay < 20 && (skipFrames = Math.max(skipFrames - 1, BASE_SKIP_FRAMES));
|
|
165
|
+
for (let i = 0; i < visibleNodes.length; i++) updateLayoutIfChanged(visibleNodes[i]);
|
|
166
|
+
}
|
|
133
167
|
}
|
|
134
|
-
setTimeout(layoutOnAnimationFrame, 16
|
|
168
|
+
rAF ? rAF(layoutOnAnimationFrame) : setTimeout(layoutOnAnimationFrame, 16);
|
|
135
169
|
}
|
|
136
170
|
layoutOnAnimationFrame();
|
|
137
171
|
}
|
|
138
|
-
const getElementLayoutEvent = (nodeRect, parentRect) => ({
|
|
172
|
+
const getElementLayoutEvent = (nodeRect, parentRect, node) => ({
|
|
139
173
|
nativeEvent: {
|
|
140
|
-
layout: getRelativeDimensions(nodeRect, parentRect),
|
|
174
|
+
layout: getRelativeDimensions(nodeRect, parentRect, node),
|
|
141
175
|
target: nodeRect
|
|
142
176
|
},
|
|
143
177
|
timeStamp: Date.now()
|
|
144
178
|
}),
|
|
145
|
-
|
|
179
|
+
getPreTransformDimensions = node => ({
|
|
180
|
+
width: node.offsetWidth,
|
|
181
|
+
height: node.offsetHeight
|
|
182
|
+
}),
|
|
183
|
+
getRelativeDimensions = (a, b, aNode) => {
|
|
146
184
|
const {
|
|
147
|
-
height,
|
|
148
185
|
left,
|
|
149
|
-
top
|
|
150
|
-
width
|
|
186
|
+
top
|
|
151
187
|
} = a,
|
|
152
188
|
x = left - b.left,
|
|
153
|
-
y = top - b.top
|
|
189
|
+
y = top - b.top,
|
|
190
|
+
{
|
|
191
|
+
width,
|
|
192
|
+
height
|
|
193
|
+
} = usePretransformDimensions() && aNode ? getPreTransformDimensions(aNode) : {
|
|
194
|
+
width: a.width,
|
|
195
|
+
height: a.height
|
|
196
|
+
};
|
|
154
197
|
return {
|
|
155
198
|
x,
|
|
156
199
|
y,
|
|
@@ -160,17 +203,44 @@ const getElementLayoutEvent = (nodeRect, parentRect) => ({
|
|
|
160
203
|
pageY: a.top
|
|
161
204
|
};
|
|
162
205
|
};
|
|
206
|
+
function registerLayoutNode(node, onChange, disableKey) {
|
|
207
|
+
return Nodes.add(node), LayoutHandlers.set(node, onChange), disableKey && LayoutDisableKey.set(node, disableKey), startGlobalObservers(), globalIntersectionObserver && (globalIntersectionObserver.observe(node), IntersectionState.set(node, !0)), () => cleanupNode(node);
|
|
208
|
+
}
|
|
209
|
+
function cleanupNode(node) {
|
|
210
|
+
Nodes.delete(node), LayoutHandlers.delete(node), LayoutDisableKey.delete(node), NodeRectCache.delete(node), IntersectionState.delete(node), globalIntersectionObserver && globalIntersectionObserver.unobserve(node);
|
|
211
|
+
}
|
|
212
|
+
const PrevHostNode = /* @__PURE__ */new WeakMap();
|
|
163
213
|
function useElementLayout(ref, onLayout) {
|
|
164
214
|
const disableKey = (0, import_react.useContext)(DisableLayoutContextKey),
|
|
165
215
|
node = ensureWebElement(ref.current?.host);
|
|
166
216
|
node && onLayout && (LayoutHandlers.set(node, onLayout), LayoutDisableKey.set(node, disableKey)), (0, import_constants.useIsomorphicLayoutEffect)(() => {
|
|
217
|
+
if (!onLayout) return;
|
|
218
|
+
const nextNode = ensureWebElement(ref.current?.host),
|
|
219
|
+
prevNode = PrevHostNode.get(ref);
|
|
220
|
+
if (nextNode === prevNode || (prevNode && cleanupNode(prevNode), PrevHostNode.set(ref, nextNode), !nextNode)) return;
|
|
221
|
+
Nodes.add(nextNode), startGlobalObservers(), globalIntersectionObserver && (globalIntersectionObserver.observe(nextNode), IntersectionState.set(nextNode, !0));
|
|
222
|
+
const handler = LayoutHandlers.get(nextNode);
|
|
223
|
+
if (typeof handler != "function") return;
|
|
224
|
+
const parentNode = nextNode.parentElement;
|
|
225
|
+
if (!parentNode) return;
|
|
226
|
+
const nodeRect = nextNode.getBoundingClientRect(),
|
|
227
|
+
parentRect = parentNode.getBoundingClientRect();
|
|
228
|
+
NodeRectCache.set(nextNode, nodeRect), NodeRectCache.set(parentNode, parentRect), handler(getElementLayoutEvent(nodeRect, parentRect, nextNode));
|
|
229
|
+
}), (0, import_constants.useIsomorphicLayoutEffect)(() => {
|
|
167
230
|
if (!onLayout) return;
|
|
168
231
|
const node2 = ref.current?.host;
|
|
169
232
|
if (!node2) return;
|
|
170
|
-
Nodes.add(node2), startGlobalObservers(), globalIntersectionObserver && (globalIntersectionObserver.observe(node2), IntersectionState.set(node2, !0))
|
|
233
|
+
Nodes.add(node2), startGlobalObservers(), globalIntersectionObserver && (globalIntersectionObserver.observe(node2), IntersectionState.set(node2, !0)), process.env.NODE_ENV === "development" && isDebugLayout() && console.log("[useElementLayout] register", {
|
|
234
|
+
tag: node2.tagName,
|
|
235
|
+
id: node2.id || void 0,
|
|
236
|
+
className: (node2.className || "").slice(0, 60) || void 0,
|
|
237
|
+
totalNodes: Nodes.size
|
|
238
|
+
});
|
|
171
239
|
const parentNode = node2.parentNode;
|
|
172
|
-
return parentNode && onLayout(getElementLayoutEvent(node2.getBoundingClientRect(), parentNode.getBoundingClientRect())), () => {
|
|
173
|
-
|
|
240
|
+
return parentNode && onLayout(getElementLayoutEvent(node2.getBoundingClientRect(), parentNode.getBoundingClientRect(), node2)), () => {
|
|
241
|
+
cleanupNode(node2);
|
|
242
|
+
const swappedNode = PrevHostNode.get(ref);
|
|
243
|
+
swappedNode && swappedNode !== node2 && cleanupNode(swappedNode), PrevHostNode.delete(ref);
|
|
174
244
|
};
|
|
175
245
|
}, [ref, !!onLayout]);
|
|
176
246
|
}
|
|
@@ -188,7 +258,7 @@ const getBoundingClientRectAsync = node => new Promise(res => {
|
|
|
188
258
|
const relativeNode = relativeTo || node?.parentElement;
|
|
189
259
|
if (relativeNode instanceof HTMLElement) {
|
|
190
260
|
const [nodeDim, relativeNodeDim] = await Promise.all([getBoundingClientRectAsync(node), getBoundingClientRectAsync(relativeNode)]);
|
|
191
|
-
if (relativeNodeDim && nodeDim) return getRelativeDimensions(nodeDim, relativeNodeDim);
|
|
261
|
+
if (relativeNodeDim && nodeDim) return getRelativeDimensions(nodeDim, relativeNodeDim, node);
|
|
192
262
|
}
|
|
193
263
|
return null;
|
|
194
264
|
},
|