UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

98 lines (96 loc) 3.47 kB
/** * @jsxRuntime classic * @jsx jsx */ import React, { Fragment, useContext, useMemo, useRef, useState } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports -- Ignored via go/DSP-18766; jsx required at runtime for @jsxRuntime classic import { css, jsx } from '@emotion/react'; import memoizeOne from 'memoize-one'; import rafSchedule from 'raf-schd'; import { WidthObserver } from '@atlaskit/width-detector'; import { isSSR } from '../../core-utils/is-ssr'; const styles = css({ position: 'relative', width: '100%' }); const SCROLLBAR_WIDTH = 30; export function getBreakpoint(width = 0) { const MAX_S = 1266; const MAX_M = 2146; if (width >= MAX_S && width < MAX_M) { return 'M'; } else if (width >= MAX_M) { return 'L'; } return 'S'; } export function createWidthContext(width = 0) { return { width, breakpoint: getBreakpoint(width) }; } export const WidthContext = /*#__PURE__*/React.createContext(createWidthContext()); const Provider = WidthContext.Provider; const Consumer = WidthContext.Consumer; /** * 🧱 Internal function: Editor FE Platform * * Returns the width of the document body. * * This function is memoized to avoid forcing a layout reflow multiple times. * It uses `document.body.offsetWidth` as the source of the width, which can lead to * a layout reflow if accessed repeatedly. To mitigate performance issues, the result * is cached using `memoizeOne`. * * @returns {number} The width of the document body or 0 if the document is undefined. */ export const getBodyWidth = memoizeOne(() => { var _document$body$offset, _document$body; return isSSR() ? 0 : (_document$body$offset = (_document$body = document.body) === null || _document$body === void 0 ? void 0 : _document$body.offsetWidth) !== null && _document$body$offset !== void 0 ? _document$body$offset : 0; }); export const WidthProvider = ({ className, shouldCheckExistingValue, children }) => { const existingContextValue = useContext(WidthContext); const [width, setWidth] = useState(getBodyWidth); const widthRef = useRef(width); const isMountedRef = useRef(true); const providerValue = useMemo(() => createWidthContext(width), [width]); const updateWidth = useMemo(() => { return rafSchedule(nextWidth => { const currentWidth = widthRef.current || 0; // Ignore changes that are less than SCROLLBAR_WIDTH, otherwise it can cause infinite re-scaling if (Math.abs(currentWidth - nextWidth) < SCROLLBAR_WIDTH) { return; } // Avoid React memory leak by checking if the component is still mounted if (!isMountedRef.current) { return; } widthRef.current = nextWidth; setWidth(nextWidth); }); }, []); const skipWidthDetection = shouldCheckExistingValue && existingContextValue.width > 0; React.useLayoutEffect(() => { isMountedRef.current = true; return () => { isMountedRef.current = false; }; }, []); return jsx("div", { css: styles // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: className }, !skipWidthDetection && jsx(Fragment, null, jsx(WidthObserver, { setWidth: updateWidth, offscreen: true }), jsx(Provider, { value: providerValue }, children)), skipWidthDetection && children); }; export { Consumer as WidthConsumer };