Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | 220x 220x 220x 220x 847x 220x 2014x 1339x 675x 675x 172x 172x 32178x 778x 778x 348x 2420x 220x 880x 42192x 17686x 17686x 17686x 36006x 15280x 32178x 16863x 2406x 927x 4260x 4260x 6666x | import React, { createContext, useContext, useState, useSyncExternalStore } from 'react';
type ViewportElementRegistry = {
registerViewport: (viewportId: string, element: HTMLElement) => void;
unregisterViewport: (viewportId: string) => void;
getViewportElement: (viewportId: string) => HTMLElement | null;
subscribe: (listener: () => void) => () => void;
};
const ViewportElementsContext = createContext<ViewportElementRegistry | undefined>(undefined);
/**
* Per-viewport DOM elements, so that a component which is not a viewport's child
* can still reach its element - the colorbar and the size and mouse-position
* hooks all need one. The viewport that owns the element registers it as React
* attaches it; everyone else looks it up by viewport ID.
*
* The registry lives outside React, so registering is not a render. Readers are
* notified explicitly rather than relying on a re-render happening to occur -
* see useViewportElement.
*/
function createViewportElementRegistry(): ViewportElementRegistry {
const viewportElements = new Map<string, HTMLElement>();
const listeners = new Set<() => void>();
const notify = () => {
listeners.forEach(listener => listener());
};
return {
registerViewport: (viewportId: string, element: HTMLElement) => {
if (viewportElements.get(viewportId) === element) {
return;
}
viewportElements.set(viewportId, element);
notify();
},
unregisterViewport: (viewportId: string) => {
if (viewportElements.delete(viewportId)) {
notify();
}
},
getViewportElement: (viewportId: string): HTMLElement | null =>
viewportElements.get(viewportId) || null,
subscribe: (listener: () => void) => {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
},
};
}
export const ViewportElementsProvider = ({ children }: { children: React.ReactNode }) => {
// useState rather than useMemo: useMemo is a hint React may discard, and
// discarding this would throw away every registration in the viewer.
const [registry] = useState(createViewportElementRegistry);
return React.createElement(ViewportElementsContext.Provider, { value: registry }E, children)E;
};
function useViewportElementRegistry(): ViewportElementRegistry {
const registry = useContext(ViewportElementsContext);
Iif (registry === undefined) {
throw new Error('Viewport element hooks must be used within a ViewportElementsProvider');
}
return registry;
}
/**
* The DOM element for a viewport, or null while none is attached. Re-renders the
* caller when that viewport's element is registered or removed.
*
* Notification is registry-wide, but useSyncExternalStore compares the value it
* reads back, so a caller watching one viewport does not re-render when another
* viewport registers.
*
* The element type is the caller's assertion, as with useRef<T> - the registry
* itself only knows it holds an HTMLElement.
*/
export function useViewportElement<T extends HTMLElement = HTMLElement>(
viewportId: string
): T | null {
const { getViewportElement, subscribe } = useViewportElementRegistry();
return useSyncExternalStore(subscribe, () => getViewportElement(viewportId)) as T | null;
}
/**
* For the component that owns a viewport's element. Subscribes to nothing, so
* registering does not re-render the owner - which matters, because the owner is
* the viewport itself.
*
* `unregister` is returned as a plain function rather than handled by a ref
* cleanup so that callers keep control of when it runs relative to the rest of
* their teardown.
*/
export const useViewportElementRegistration = (viewportId: string) => {
const { registerViewport, unregisterViewport } = useViewportElementRegistry();
return {
register: (element: HTMLElement) => registerViewport(viewportId, element),
unregister: () => unregisterViewport(viewportId),
};
};
|