@@ -7,82 +7,8 @@ import {
77 RefCallback ,
88 useCallback ,
99} from "react" ;
10-
11- type SubscriberCleanup = ( ) => void ;
12- type SubscriberResponse = SubscriberCleanup | void ;
13-
14- // This of course could've been more streamlined with internal state instead of
15- // refs, but then host hooks / components could not opt out of renders.
16- // This could've been exported to its own module, but the current build doesn't
17- // seem to work with module imports and I had no more time to spend on this...
18- function useResolvedElement < T extends Element > (
19- subscriber : ( element : T ) => SubscriberResponse ,
20- refOrElement ?: T | RefObject < T > | null
21- ) : RefCallback < T > {
22- const callbackRefElement = useRef < T | null > ( null ) ;
23- const lastReportRef = useRef < {
24- reporter : ( ) => void ;
25- element : T | null ;
26- } | null > ( null ) ;
27- const cleanupRef = useRef < SubscriberResponse | null > ( ) ;
28-
29- // Resolving ".current" purely so that a new callSubscriber instance is created when needed.
30- const refElement =
31- refOrElement && "current" in refOrElement ? refOrElement . current : null ;
32- const callSubscriber = useCallback ( ( ) => {
33- let element = null ;
34- if ( callbackRefElement . current ) {
35- element = callbackRefElement . current ;
36- } else if ( refOrElement ) {
37- if ( refOrElement instanceof Element ) {
38- element = refOrElement ;
39- } else {
40- element = refOrElement . current ;
41- }
42- }
43-
44- if (
45- lastReportRef . current &&
46- lastReportRef . current . element === element &&
47- lastReportRef . current . reporter === callSubscriber
48- ) {
49- return ;
50- }
51-
52- if ( cleanupRef . current ) {
53- cleanupRef . current ( ) ;
54- // Making sure the cleanup is not called accidentally multiple times.
55- cleanupRef . current = null ;
56- }
57- lastReportRef . current = {
58- reporter : callSubscriber ,
59- element,
60- } ;
61-
62- // Only calling the subscriber, if there's an actual element to report.
63- if ( element ) {
64- cleanupRef . current = subscriber ( element ) ;
65- }
66- } , [ refOrElement , refElement , subscriber ] ) ;
67-
68- // On each render, we check whether a ref changed, or if we got a new raw
69- // element.
70- useEffect ( ( ) => {
71- // With this we're *technically* supporting cases where ref objects' current value changes, but only if there's a
72- // render accompanying that change as well.
73- // To guarantee we always have the right element, one must use the ref callback provided instead, but we support
74- // RefObjects to make the hook API more convenient in certain cases.
75- callSubscriber ( ) ;
76- } , [ callSubscriber ] ) ;
77-
78- return useCallback < RefCallback < T > > (
79- ( element ) => {
80- callbackRefElement . current = element ;
81- callSubscriber ( ) ;
82- } ,
83- [ callSubscriber ]
84- ) ;
85- }
10+ import useResolvedElement from "./utils/useResolvedElement" ;
11+ import extractSize from "./utils/extractSize" ;
8612
8713type ObservedSize = {
8814 width : number | undefined ;
@@ -108,58 +34,6 @@ declare global {
10834 }
10935}
11036
111- // We're only using the first element of the size sequences, until future versions of the spec solidify on how
112- // exactly it'll be used for fragments in multi-column scenarios:
113- // From the spec:
114- // > The box size properties are exposed as FrozenArray in order to support elements that have multiple fragments,
115- // > which occur in multi-column scenarios. However the current definitions of content rect and border box do not
116- // > mention how those boxes are affected by multi-column layout. In this spec, there will only be a single
117- // > ResizeObserverSize returned in the FrozenArray, which will correspond to the dimensions of the first column.
118- // > A future version of this spec will extend the returned FrozenArray to contain the per-fragment size information.
119- // (https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface)
120- //
121- // Also, testing these new box options revealed that in both Chrome and FF everything is returned in the callback,
122- // regardless of the "box" option.
123- // The spec states the following on this:
124- // > This does not have any impact on which box dimensions are returned to the defined callback when the event
125- // > is fired, it solely defines which box the author wishes to observe layout changes on.
126- // (https://drafts.csswg.org/resize-observer/#resize-observer-interface)
127- // I'm not exactly clear on what this means, especially when you consider a later section stating the following:
128- // > This section is non-normative. An author may desire to observe more than one CSS box.
129- // > In this case, author will need to use multiple ResizeObservers.
130- // (https://drafts.csswg.org/resize-observer/#resize-observer-interface)
131- // Which is clearly not how current browser implementations behave, and seems to contradict the previous quote.
132- // For this reason I decided to only return the requested size,
133- // even though it seems we have access to results for all box types.
134- // This also means that we get to keep the current api, being able to return a simple { width, height } pair,
135- // regardless of box option.
136- const extractSize = (
137- entry : ResizeObserverEntry ,
138- boxProp : "borderBoxSize" | "contentBoxSize" | "devicePixelContentBoxSize" ,
139- sizeType : keyof ResizeObserverSize
140- ) : number | undefined => {
141- if ( ! entry [ boxProp ] ) {
142- if ( boxProp === "contentBoxSize" ) {
143- // The dimensions in `contentBoxSize` and `contentRect` are equivalent according to the spec.
144- // See the 6th step in the description for the RO algorithm:
145- // https://drafts.csswg.org/resize-observer/#create-and-populate-resizeobserverentry-h
146- // > Set this.contentRect to logical this.contentBoxSize given target and observedBox of "content-box".
147- // In real browser implementations of course these objects differ, but the width/height values should be equivalent.
148- return entry . contentRect [ sizeType === "inlineSize" ? "width" : "height" ] ;
149- }
150-
151- return undefined ;
152- }
153-
154- // A couple bytes smaller than calling Array.isArray() and just as effective here.
155- return entry [ boxProp ] [ 0 ]
156- ? entry [ boxProp ] [ 0 ] [ sizeType ]
157- : // TS complains about this, because the RO entry type follows the spec and does not reflect Firefox's current
158- // behaviour of returning objects instead of arrays for `borderBoxSize` and `contentBoxSize`.
159- // @ts -ignore
160- entry [ boxProp ] [ sizeType ] ;
161- } ;
162-
16337type RoundingFunction = ( n : number ) => number ;
16438
16539function useResizeObserver < T extends Element > (
0 commit comments