UNPKG

react-md

Version:

This is the full react-md library bundled together for convenience.

1,396 lines (1,349 loc) 1.88 MB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom'), require('crypto')) : typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom', 'crypto'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ReactMD = {}, global.React, global.ReactDOM, global.crypto)); }(this, (function (exports, React, ReactDOM, crypto) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM); var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto); var classnames = {exports: {}}; /*! Copyright (c) 2018 Jed Watson. Licensed under the MIT License (MIT), see http://jedwatson.github.io/classnames */ (function (module) { /* global define */ (function () { var hasOwn = {}.hasOwnProperty; function classNames() { var classes = []; for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; if (!arg) continue; var argType = typeof arg; if (argType === 'string' || argType === 'number') { classes.push(arg); } else if (Array.isArray(arg)) { if (arg.length) { var inner = classNames.apply(null, arg); if (inner) { classes.push(inner); } } } else if (argType === 'object') { if (arg.toString === Object.prototype.toString) { for (var key in arg) { if (hasOwn.call(arg, key) && arg[key]) { classes.push(key); } } } else { classes.push(arg.toString()); } } } return classes.join(' '); } if (module.exports) { classNames.default = classNames; module.exports = classNames; } else { window.classNames = classNames; } }()); }(classnames)); var cn = classnames.exports; /** * A utility function to get the current container for the portal. For SSR, the * container will always be `null` since portals don't work server side. * * @param into - The element to portal into * @param intoId - An id for an element to portal into * @returns the portal container element or null */ function getContainer(into, intoId) { if (typeof document === "undefined") { return null; } var container = null; if (typeof into === "undefined" && typeof intoId === "undefined") { container = document.body; } else if (typeof intoId === "string") { container = document.getElementById(intoId); } else if (typeof into === "string") { container = document.querySelector(into); } else if (typeof into === "function") { container = into(); } else if (into) { container = into; } return container; } /** * This component is a simple wrapper for the `createPortal` API from ReactDOM * that will just ensure that `null` is always returned for server side * rendering as well as a "nice" way to choose specific portal targets or just * falling back to the `document.body`. */ function Portal(_a) { var into = _a.into, intoId = _a.intoId, children = _a.children; var _b = React.useState(null), container = _b[0], setContainer = _b[1]; // setting the container via useEffect instead of immediately in the render // just so that it doesn't throw an error immediately if the dom hasn't fully // painted after a SSR React.useEffect(function () { var nextContainer = getContainer(into, intoId); if (container !== nextContainer) { setContainer(nextContainer); } }, [into, intoId, container]); if (!container) { return null; } return ReactDOM.createPortal(children, container); } /** * This is a very simple component that is used in other places within react-md * to conditionally render the children within a portal or not based on general * portal config props. */ function ConditionalPortal(_a) { var portal = _a.portal, portalInto = _a.portalInto, portalIntoId = _a.portalIntoId, children = _a.children; if (!portal && !portalInto && !portalIntoId) { return children; } return (React__default['default'].createElement(Portal, { into: portalInto, intoId: portalIntoId }, children)); } /** * A small utility function that allows me to apply a passed in ref along with * my own custom ref logic. * * @param instance - The DOM Node instance * @param ref - The prop ref */ function applyRef(instance, ref) { if (!ref) { return; } if (typeof ref === "function") { ref(instance); } else if (typeof ref === "object") { ref.current = instance; } } function modify(base, modifier) { if (!modifier) { return base; } var hasOwn = Object.prototype.hasOwnProperty; return Object.keys(modifier).reduce(function (s, mod) { if (hasOwn.call(modifier, mod) && modifier[mod]) { s = s + " " + base + "--" + mod; } return s; }, base); } /** * Applies the BEM styled class name to an element. * * @see https://en.bem.info/methodology/css/ * @param base - The base class to use * @returns a function to call that generates the full class name */ function bem(base) { /** * Creates the full class name from the base block name. This can be called * without any arguments which will just return the base block name (kind of * worthless), or you can provide a child element name and modifiers. * * @param elementOrModifier - This is either the child element name or an * object of modifiers to apply. This **must** be a string if the second * argument is provided. * @param modifier - Any optional modifiers to apply to the block and optional * element. * @returns the full class name */ return function block(elementOrModifier, modifier) { if (!elementOrModifier) { return base; } if (typeof elementOrModifier !== "string") { return modify(base, elementOrModifier); } return modify(base + "__" + elementOrModifier, modifier); }; } var SHORTHAND_REGEX = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; var VERBOSE_REGEX = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; /** * Converts a hex string into an rgb value. This is useful for deteching color * contrast ratios and other stuff. * * @param hex - The hex string to convert * @returns an object containing the r, g, b values for the color. */ function hexToRGB(hex) { hex = hex.replace(SHORTHAND_REGEX, function (_m, r, g, b) { return "" + r + r + g + g + b + b; }); var result = hex.match(VERBOSE_REGEX) || []; var r = parseInt(result[1] || "", 16) || 0; var g = parseInt(result[2] || "", 16) || 0; var b = parseInt(result[3] || "", 16) || 0; return [r, g, b]; } var RED_MULTIPLIER = 0.2126; var GREEN_MULTIPLIER = 0.7152; var BLUE_MULTIPLIER = 0.0722; /** * I really couldn't figure out how to name these "magic" numbers since the * formula doesn't really describe it much: * * @see https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests * @internal */ function get8BitColor(color) { color /= 255; if (color <= 0.03928) { return color / 12.92; } return Math.pow(((color + 0.055) / 1.055), 2.4); } /** * A number closest to 0 should be closest to black while a number closest to 1 * should be closest to white. * * @see https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests * @internal */ function getLuminance(color) { var _a = hexToRGB(color), r = _a[0], g = _a[1], b = _a[2]; var red = get8BitColor(r) * RED_MULTIPLIER; var green = get8BitColor(g) * GREEN_MULTIPLIER; var blue = get8BitColor(b) * BLUE_MULTIPLIER; return red + green + blue; } /** * Gets the contrast ratio between a background color and a foreground color. * * @see https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests * * @param background - The background color * @param foreground - The foreground color. This is normally the `color` css * value. * @returns the contrast ratio between the background and foreground colors. */ function getContrastRatio(background, foreground) { var backgroundLuminance = getLuminance(background) + 0.05; var foregroundLuminance = getLuminance(foreground) + 0.05; return (Math.max(backgroundLuminance, foregroundLuminance) / Math.min(backgroundLuminance, foregroundLuminance)); } /** * The contrast ratio that can be used for large text where large text is * considered 18pt or 14pt bold. */ var LARGE_TEXT_CONTRAST_RATIO = 3; /** * The contrast ratio that can be used for normal text. */ var NORMAL_TEXT_CONTRAST_RATIO = 4.5; /** * The AAA contrast ratio for passing WGAC 2.0 color contrast ratios. */ var AAA_CONTRAST_RATIO = 7; /** * Checks if there is an acceptable contrast ratio between the background and * foreground colors based on the provided compliance level. * * @param background - The background color to check against * @param foreground - The foreground color to check against * @param compliance - The compliance level to use or a custom number as a * ratio. * @returns true if there is enough contrast between the foreground and * background colors for the provided compliance level. */ function isContrastCompliant(background, foreground, compliance) { if (compliance === void 0) { compliance = "normal"; } var ratio; switch (compliance) { case "large": ratio = LARGE_TEXT_CONTRAST_RATIO; break; case "normal": ratio = NORMAL_TEXT_CONTRAST_RATIO; break; case "AAA": ratio = AAA_CONTRAST_RATIO; break; default: ratio = compliance; } return getContrastRatio(background, foreground) >= ratio; } /** * Typeguard that will check if the provided checkable thing is a * MutableRefObject or just an HTMLElement. * * @internal */ var isMutableRefObject = function (thing) { return !!thing && typeof thing.current !== "undefined"; }; /** * Gets the HTMLElement or null from the checkable thing. * * @internal */ var getElement$1 = function (thing) { if (isMutableRefObject(thing)) { return thing.current; } return thing; }; /** * Checks if a container element contains another element as a child while * allowing for nulls or a MutableRefObject of HTMLElement or null. Mostly just * a convenience function that should be used internally. * * @param container - The element to use as a container element. This can be an * HTMLElement, null, or a MutableRefObject of HTMLElement or null. * @param child - The element that might be a child of the container * element. This can be an HTMLElement, null, or a MutableRefObject of * HTMLElement or null. * @returns True if the container contains the child element and both the * container and child are valid HTMLElements (not null). * @internal */ function containsElement(container, child) { container = getElement$1(container); child = getElement$1(child); return !!(container && child && container.contains(child)); } var __assign$hj = (undefined && undefined.__assign) || function () { __assign$hj = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign$hj.apply(this, arguments); }; /** * This is normally used for reusable shareable configs that have multiple * shared options with default values that should be used. This basically works * just like `defaultProps` in react. * * @internal * @param optional - The original object that has the optional/omitted values * @param required - The required default values that should be used to fill the * optional object with * @returns a new object with both the values of the optional and required * objects but use the optional values if they were defined. */ function defaults(optional, required) { var keys = Object.keys(required); return keys.reduce(function (result, key) { if (typeof result[key] === "undefined") { result[key] = required[key]; } return result; }, __assign$hj({}, optional)); } var __rest$2k = (undefined && undefined.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var context$b = React.createContext({ root: true, dir: "ltr", toggleDir: function () { throw new Error("Tried to toggle the current writing direction without initializing the `Dir` component."); }, }); var Provider$5 = context$b.Provider; /** * Gets the writing direction context which provides access to the current `dir` * and a `toggleDir` function. * * @remarks \@since 2.3.0 */ function useDir() { var _a = React.useContext(context$b); _a.root; var current = __rest$2k(_a, ["root"]); return current; } /** * @remarks \@since 2.3.0 */ var DEFAULT_DIR = function () { var dir = "ltr"; if (typeof document !== "undefined") { var rootDir = document.documentElement.getAttribute("dir"); dir = rootDir === "rtl" ? "rtl" : "ltr"; } return dir; }; /** * The `Dir` component is used to handle the current writing direction within * your app as well as conditionally updating the writing direction for small * sections in your app. When this component is used for the first time near the * root of your React component tree, the current direction will be applied to * the root `<html>` element. Otherwise the current dir will be cloned into the * child element so it can be passed as a prop. * * ```tsx * // html element will be updated to have `dir="ltr"` * ReactDOM.render(<Dir><App /></Dir>, root) * ``` * * ```tsx * // html element will be updated to have `dir="rtl"` while the `<span>` will * // now be `<span dir="ltr">` * ReactDOM.render( * <Dir defaultDir="rtl"> * <Some> * <Other> * <Components> * <Dir defaultDir="ltr"> * <span>Content</span> * </Dir> * </Components> * </Other> * </Some> * </Dir>, * root * ); * ``` * * Note: Since the `dir` is cloned into the child element, you need to make sure * that the child is either a DOM element or the `dir` prop is passed from your * custom component. * * @remarks \@since 2.3.0 */ function Dir(_a) { var children = _a.children, _b = _a.defaultDir, defaultDir = _b === void 0 ? DEFAULT_DIR : _b; var root = React.useContext(context$b).root; var _c = React.useState(defaultDir), dir = _c[0], setDir = _c[1]; React.useEffect(function () { if (!root || typeof document === "undefined") { return; } document.documentElement.setAttribute("dir", dir); return function () { document.documentElement.removeAttribute("dir"); }; }, [dir, root]); var toggleDir = React.useCallback(function () { setDir(function (prevDir) { return (prevDir === "ltr" ? "rtl" : "ltr"); }); }, []); var value = React.useMemo(function () { return ({ root: false, dir: dir, toggleDir: toggleDir }); }, [dir, toggleDir]); var child = React.Children.only(children); if (!root) { child = React.cloneElement(child, { dir: dir }); } return React__default['default'].createElement(Provider$5, { value: value }, child); } /* eslint-disable import/no-mutable-exports, getter-return */ var noop$b = function () { // do nothing }; var isSupported = false; /** * Checks if the browser supports passive events. This shouldn't really be used * outside of this file, but you can always check again if needed. */ function update() { if (typeof window === "undefined") { return false; } var isPassiveEventsSupported = false; var opts = Object.defineProperty({}, "passive", { get: function () { isPassiveEventsSupported = true; }, }); window.addEventListener("testSupportsPassive", noop$b, opts); window.removeEventListener("testSupportsPassive", noop$b, opts); isSupported = isPassiveEventsSupported; return isPassiveEventsSupported; } // invoke immediately update(); var passiveEvents = /*#__PURE__*/Object.freeze({ __proto__: null, update: update, get isSupported () { return isSupported; } }); var delegatedEvents = []; /* eslint-disable @typescript-eslint/explicit-function-return-type */ /** * Creates the delegated event handler that will run all the callbacks once an * event happens. The callbacks' invocation can also be throttled for event * types that trigger rapidly for additional performance. * * The `<K extends keyof WindowEventMap` is a nice thing I found while looking * through the `lib.d.ts` implementation of `addEventListener` that will allow * you to get the "correct" event type when using the `add` and `remove` * functions once you have created this event handler. Otherwise there'd be ts * errors trying to do `MouseEvent` or `KeyboardEvent` in your listeners. */ function createEventHandler(throttle, callbacks) { var running = false; var runCallbacks = function (event) { return function () { for (var i = 0; i < callbacks.length; i += 1) { callbacks[i](event); } running = false; }; }; return function eventHandler(event) { if (!throttle) { runCallbacks(event)(); return; } if (running) { return; } running = true; window.requestAnimationFrame(runCallbacks(event)); }; } /* eslint-enable @typescript-eslint/explicit-function-return-type */ /** * Creates a throttled event handler for the provided event type and event * target. */ function createDelegatedEventHandler(eventType, eventTarget, throttle, options) { if (eventTarget === void 0) { eventTarget = window; } if (throttle === void 0) { throttle = false; } var callbacks = []; var handler = createEventHandler(throttle, callbacks); return { /** * Attempts to add the provided callback to the list of callbacks for the * throttled event. If this is the first callback to be added, the throttled * event will also be started. */ add: function (callback) { if (!callbacks.length) { eventTarget.addEventListener(eventType, handler, options); } if (callbacks.indexOf(callback) === -1) { callbacks.push(callback); } }, /** * Attempts to remove the provided callback from the lsit of callbacks for * the throttled event. If this is the last callback that was removed, the * throttled event will also be stopped. */ remove: function (callback) { var i = callbacks.indexOf(callback); if (i >= 0) { callbacks.splice(i, 1); if (!callbacks.length) { eventTarget.removeEventListener(eventType, handler, options); } } }, }; } /** * Creates a delegated event listener using custom events. Most of this code * comes from the MDN about resize listeners. * * This will return an object for adding or removing event handlers for the * provided `eventType` since only one base throttled event listener will be * created. Each callback that is added will be called with the event each time * the event is triggered. This does mean that you will manually need to remove * your callback like normal or else it can be called when no longer in use. * This also means that it doesn't "hurt" to call this function without * immediately calling the `add` function since the event won't start until * there is at least 1 callback. * * @see https://developer.mozilla.org/en-US/docs/Web/Events/resize#Examples * @param eventType - One of the event types that should be used to create a * delegated event for. This should be things like resize, click, scroll, etc. * @param eventTarget - The target that should have the delegated event handler * attached to. This is normally the window, but can be any element as needed. * @param throttle - Boolean if the event should be throttled or not. Normally * only event types like resize or scroll should be throttled for performance * boosts, but anything can be. * @returns The delegated event handler that allows you to add or remove * `EventListener`s to that event. */ function delegateEvent(eventType, eventTarget, throttle, options) { if (eventTarget === void 0) { eventTarget = window; } if (throttle === void 0) { throttle = eventType === "resize" || eventType === "scroll"; } var index = delegatedEvents.findIndex(function (event) { return event.type === eventType && event.target === eventTarget && event.options === options && event.throttle === throttle; }); if (index === -1) { delegatedEvents.push({ type: eventType, target: eventTarget, options: options, throttle: throttle, handler: createDelegatedEventHandler(eventType, eventTarget, throttle, options), }); index = delegatedEvents.length - 1; } return delegatedEvents[index].handler; } var __assign$hi = (undefined && undefined.__assign) || function () { __assign$hi = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign$hi.apply(this, arguments); }; /** * A helper function for manually setting touch events on elements when they * cannot be directly added with a React event listener. This will attempt to * create a passive event if the browser supports passive events so there is * better scroll performance. */ function setTouchEvent( /** * Boolean if the event should be added or removed. */ add, /** * The element to add the touch event to. */ el, /** * One of the touch types to modify. */ eventType, /** * The touch event callback function to use. */ callback, /** * Boolean if the event should be captured if the browser does not support * passive events. */ capture, /** * Any additional options to provide to the passive event. */ options) { if (capture === void 0) { capture = false; } el[(add ? "add" : "remove") + "EventListener"]("touch" + eventType, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error callback, isSupported ? __assign$hi({ passive: true, capture: capture }, options) : capture); } /** * A simple wrapper for the `setTouchEvent` to just always add events. */ function addTouchEvent( /** * The element to add the touch event to. */ el, /** * One of the touch types to modify. */ eventType, /** * The touch event callback function to use. */ callback, /** * Boolean if the event should be captured if the browser does not support * passive events. */ capture, /** * Any additional options to provide to the passive event. */ options) { if (capture === void 0) { capture = false; } setTouchEvent(true, el, eventType, callback, capture, options); } /** * A simple wrapper for the `setTouchEvent` to just always remove events. * * @param el - The element to add the touch event to. * @param eventType - One of the touch types to modify. */ function removeTouchEvent(el, eventType, /** * The touch event callback function to use. */ callback, /** * Boolean if the event should be captured if the browser does not support * passive events. */ capture, /** * Any additional options to provide to the passive event. */ options) { if (capture === void 0) { capture = false; } setTouchEvent(false, el, eventType, callback, capture, options); } /** * This hook allows you to provide anything that should be "cached" and puts it * into a ref that'll be updated each render. This is pretty overkill for most * places, but it's really nice when you want to create event handlers that * shouldn't update if the developer used arrow functions to define callbacks. * (A great example is for ref callbacks that *shouldn't* be triggered each * render. But that might just be a programming error instead). * * @param cacheable - The cacheable thing that gets updated after each render. * @returns a mutable ref object containing the current cache. */ function useRefCache(cacheable) { var ref = React.useRef(cacheable); React.useEffect(function () { ref.current = cacheable; }); return ref; } /** * This hook will create a performant scroll listener by enabling passive events * if it's supported by the browser and delegating the event as needed. */ function useScrollListener(_a) { var _b = _a.enabled, enabled = _b === void 0 ? true : _b, onScroll = _a.onScroll, element = _a.element, _c = _a.options, options = _c === void 0 ? isSupported ? { passive: true } : false : _c; var callback = useRefCache(onScroll); React.useEffect(function () { if (!enabled) { return; } var eventHandler = delegateEvent("scroll", element || window, true, options); var handler = function (event) { return callback.current(event); }; eventHandler.add(handler); return function () { eventHandler.remove(handler); }; // disabled since useRefCache // eslint-disable-next-line react-hooks/exhaustive-deps }, [enabled, element, options]); } /** * This is a simple component wrapper for the `useScrollListener` hook. */ function ScrollListener(props) { useScrollListener(props); return null; } /** * Gets the current percentage based on the min, max, and current value. * * @param min - the min value * @param max - the max value * @param value - the current value to compare against * @returns the percentage that the `value` is between the `min` and `max` * values. */ function getPercentage(min, max, value) { if (min >= max) { throw new RangeError("A range must have the min value less than the max value"); } if (value > max || value < min) { throw new RangeError("A value must be between the min and max values"); } var range = max - min; var start = value - min; var percentage = start / range; return Math.max(0, Math.min(Math.abs(percentage), 1)); } /** * The amount of time a user must hover an element before the temporary element * becomes visible. * * @remarks \@since 2.8.0 */ var DEFAULT_HOVER_MODE_VISIBLE_IN_TIME = 1000; /** * The amount of time the user must no longer hover any element attached to the * {@link HoverModeProvider} to disable the hover mode. * * @remarks \@since 2.8.0 */ var DEFAULT_HOVER_MODE_DEACTIVATION_TIME = 1000; /** * The amount of time the user must not hover any element attached to the same * instance of the {@link useHoverMode} hook when the using the sticky mode. * * @remarks \@since 2.8.0 */ var DEFAULT_HOVER_MODE_STICKY_EXIT_TIME = 300; /** * A simple hook that only triggers the callback when a component is unmounted. * This will make sure that the callback function does not have a stale closure * by the time the component unmounts as well. * * @example * Simple Example * ```ts * useOnUnmount(() => { * console.log('Component is unmounted.'); * }); * * const [data, setData] = useState(initialData); * useOnUnmount(() => { * API.saveCurrentData(data); * }); * * // update data * ``` * * @remarks \@since 2.7.1 * @param callback - the function to call when the component unmounts. */ function useOnUnmount(callback) { var ref = React.useRef(callback); React.useEffect(function () { ref.current = callback; }); return React.useEffect(function () { return function () { return ref.current(); }; }, []); } /** @internal */ var noop$a = function () { // do nothing }; /** @internal */ var context$a = React.createContext({ visibleInTime: DEFAULT_HOVER_MODE_VISIBLE_IN_TIME, enableHoverMode: noop$a, disableHoverMode: noop$a, startDisableTimer: noop$a, }); /** * @internal * @remarks \@since 2.8.0 */ var HoverModeContextProvider = context$a.Provider; /** * Gets the {@link HoverModeContext} which allows you implement hover mode * functionality for any component. This is mostly an internal hook since * everything you need will be available in the {@link useHoverMode} hook. * * @internal * @remarks \@since 2.8.0 * @returns The {@link HoverModeContext} */ function useHoverModeContext() { return React.useContext(context$a); } /** * This component should normally be mounted near the root of your app to enable * hover mode for child components. However, it can also be used at other levels * if hover mode functionality should not carry over between two different parts * of the screen. * * @example * Separating Hover Mode * ```tsx * export default function Example(): ReactElement { * return ( * <> * <HoverModeProvider> * <HeaderActions /> * </HoverModeProvider> * <HoverModeProvider> * <MainContent /> * </HoverModeProvider> * </> * ); * } * ``` * * @remarks \@since 2.8.0 */ function HoverModeProvider(_a) { var children = _a.children, _b = _a.disabled, disabled = _b === void 0 ? false : _b, _c = _a.defaultVisibleInTime, defaultVisibleInTime = _c === void 0 ? DEFAULT_HOVER_MODE_VISIBLE_IN_TIME : _c, _d = _a.deactivateTime, deactivateTime = _d === void 0 ? DEFAULT_HOVER_MODE_DEACTIVATION_TIME : _d; var _e = React.useState(defaultVisibleInTime), visibleInTime = _e[0], setVisibleInTime = _e[1]; var timeoutRef = React.useRef(); var enableHoverMode = React.useCallback(function () { if (disabled) { return; } window.clearTimeout(timeoutRef.current); setVisibleInTime(0); }, [disabled]); var disableHoverMode = React.useCallback(function () { window.clearTimeout(timeoutRef.current); setVisibleInTime(defaultVisibleInTime); }, [defaultVisibleInTime]); var startDisableTimer = React.useCallback(function () { window.clearTimeout(timeoutRef.current); timeoutRef.current = window.setTimeout(function () { setVisibleInTime(defaultVisibleInTime); }, deactivateTime); }, [defaultVisibleInTime, deactivateTime]); React.useEffect(function () { if (disabled) { window.clearTimeout(timeoutRef.current); setVisibleInTime(defaultVisibleInTime); } }, [disabled, defaultVisibleInTime]); useOnUnmount(function () { window.clearTimeout(timeoutRef.current); }); var context = React.useMemo(function () { return ({ visibleInTime: visibleInTime, enableHoverMode: enableHoverMode, disableHoverMode: disableHoverMode, startDisableTimer: startDisableTimer, }); }, [disableHoverMode, enableHoverMode, startDisableTimer, visibleInTime]); return (React__default['default'].createElement(HoverModeContextProvider, { value: context }, children)); } /** * This is copy/pasted from react-redux which has some more information about * this and how to fix "invalid" warnings while running tests. * * @see https://github.com/reduxjs/react-redux/blob/4c907c0870c6b9a136dd69be294c17d1dc63c8f5/src/utils/useIsomorphicLayoutEffect.js */ var useIsomorphicLayoutEffect = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined" ? React.useLayoutEffect : React.useEffect; /** * @internal */ var TOUCH_TIMEOUT = 1200; /** * This hook helps determine the current interaction mode by attaching the * required event listeners to the window. The `mode` will always be defaulted * to `mouse` at first since it has the least possibilities of causing errors * with styles since the mouse-only styles are normally just `:hover` effects. * * ## Switching between modes: * * ### While in `mouse` mode: * * - any `keydown` event will switch to `keyboard` mode * - this does have the side effect of meta keys also causing the switch over, * but it feels fine since it helps show the current focus in the document * as well * - any `touchstart` event will switch to `touch` mode * * ### While in `keyboard` mode: * * - any `mousedown` event will switch to `mouse` mode * - it is perfectly okay to move the mouse while in keyboard mode, but still * want to keep the keyboard styles until the user actually starts clicking * - any `touchstart` event will switch to `touch` mode * * ### While in `touch` mode: * * - any `mousemove` event will switch to `mouse` mode, but **only** if there * hasn't been a `contextmenu` event within the last `1.2s` * - you can really only switch back to `mouse` mode if you are using the * devtools to emulate devices OR using a touch-desktop. I don't know how * common this really is though. * - touching the screen will always fire a `mousemove` event (which is why * the `:hover` styles are normally with `rmd-utils-mouse-only`) and even * after the `contextmenu` event. Normally want to go back to `mouse` mode * when the mouse re-enters the `window` * * Note: It's currently impossible to switch from `touch` to `keyboard` * immediately. You'd first need to switch to `mouse` and then to `keyboard`. I * don't really know of any use-cases other than the weird touch-desktop stuff * and I have no experience using them. * * @internal */ function useInteractionMode() { var _a = React.useState("mouse"), mode = _a[0], setMode = _a[1]; var lastTouchTime = React.useRef(0); var isTouchContextMenu = React.useRef(false); useIsomorphicLayoutEffect(function () { var enableMouseMode = function () { return setMode("mouse"); }; var enableKeyboardMode = function () { return setMode("keyboard"); }; var handleTouchStart = function () { lastTouchTime.current = Date.now(); isTouchContextMenu.current = false; setMode("touch"); }; var handleMouseMove = function () { if (isTouchContextMenu.current || Date.now() - lastTouchTime.current < TOUCH_TIMEOUT) { isTouchContextMenu.current = false; return; } enableMouseMode(); }; var handleContextMenu = function () { isTouchContextMenu.current = true; }; var className = "rmd-utils--" + mode; document.body.classList.add(className); window.addEventListener("touchstart", handleTouchStart, true); if (mode === "mouse") { window.addEventListener("keydown", enableKeyboardMode, true); } else if (mode === "keyboard") { window.addEventListener("mousedown", enableMouseMode, true); } else { window.addEventListener("mousemove", handleMouseMove, true); window.addEventListener("contextmenu", handleContextMenu, true); } return function () { document.body.classList.remove(className); window.removeEventListener("touchstart", handleTouchStart, true); if (mode === "mouse") { window.removeEventListener("keydown", enableKeyboardMode, true); } else if (mode === "keyboard") { window.removeEventListener("mousedown", enableMouseMode, true); } else { window.removeEventListener("mousemove", handleMouseMove, true); window.removeEventListener("contextmenu", handleContextMenu, true); } }; }, [mode]); return mode; } /** * @internal */ var modeContext = React.createContext("mouse"); /** * @internal */ var parentContext = React.createContext(false); /** * @internal */ var UserInteractionModeProvider = modeContext.Provider; /** * @internal */ var ParentProvider = parentContext.Provider; /** * Returns the current user interaction mode. * * @returns {@link UserInteractionMode} */ function useUserInteractionMode() { return React.useContext(modeContext); } /** * Example: * * ```ts * const isKeyboard = useIsUserInteractionMode("keyboard"); * // do stuff if keyboard only * ``` * * @param mode - The {@link UserInteractionMode} to check against. * @returns `true` if the current user interaction mode matches the provided * mode. */ function useIsUserInteractionMode(mode) { return useInteractionMode() === mode; } /** * This component is used to determine how the user is current interacting with * your app as well as modifying the `document.body`'s `className` with the * current mode. This is what allows the `rmd-utils-phone-only`, * `rmd-utils-keyboard-only`, and `rmd-utils-mouse-only` mixins to work. * * @remarks \@since 2.6.0 Renamed from `InteractionModeListener` * @throws When this component has been mounted multiple times in your app. */ function UserInteractionModeListener(_a) { var children = _a.children; var mode = useInteractionMode(); if (React.useContext(parentContext)) { throw new Error("Mounted multiple `UserInteractionModeListener` components."); } return (React__default['default'].createElement(UserInteractionModeProvider, { value: mode }, React__default['default'].createElement(ParentProvider, { value: true }, children))); } var __assign$hh = (undefined && undefined.__assign) || function () { __assign$hh = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign$hh.apply(this, arguments); }; function useHoverMode(_a) { var _b = _a === void 0 ? {} : _a, _c = _b.disabled, disabled = _c === void 0 ? false : _c, _d = _b.sticky, sticky = _d === void 0 ? false : _d, propOnClick = _b.onClick, propOnMouseEnter = _b.onMouseEnter, propOnMouseLeave = _b.onMouseLeave, _e = _b.defaultVisible, defaultVisible = _e === void 0 ? false : _e, _f = _b.exitVisibilityDelay, exitVisibilityDelay = _f === void 0 ? sticky ? DEFAULT_HOVER_MODE_STICKY_EXIT_TIME : 0 : _f; var mode = useUserInteractionMode(); var isTouch = mode === "touch"; var _g = React.useState(defaultVisible), visible = _g[0], setVisible = _g[1]; var _h = React.useState(false), stuck = _h[0], setStuck = _h[1]; var timeoutRef = React.useRef(); var skipReset = React.useRef(defaultVisible); var _j = useHoverModeContext(), visibleInTime = _j.visibleInTime, enableHoverMode = _j.enableHoverMode, disableHoverMode = _j.disableHoverMode, startDisableTimer = _j.startDisableTimer; var active = visibleInTime === 0; React.useEffect(function () { if (sticky && !visible) { setStuck(false); } }, [visible, sticky]); useOnUnmount(function () { window.clearTimeout(timeoutRef.current); }); React.useEffect(function () { if (disabled) { return; } var reset = function () { setVisible(false); disableHoverMode(); window.clearTimeout(timeoutRef.current); }; // this is just used so the `defaultOption` can be used if (!skipReset.current) { reset(); } skipReset.current = false; window.addEventListener("mousedown", reset); return function () { window.removeEventListener("mousedown", reset); }; }, [disableHoverMode, mode, disabled]); var onMouseEnter = React.useCallback(function (event) { propOnMouseEnter === null || propOnMouseEnter === void 0 ? void 0 : propOnMouseEnter(event); if (stuck || disabled || isTouch || event.isPropagationStopped()) { return; } window.clearTimeout(timeoutRef.current); if (visibleInTime === 0) { enableHoverMode(); setVisible(true); return; } timeoutRef.current = window.setTimeout(function () { enableHoverMode(); setVisible(true); }, visibleInTime); }, [disabled, enableHoverMode, isTouch, propOnMouseEnter, stuck, visibleInTime]); var onMouseLeave = React.useCallback(function (event) { propOnMouseLeave === null || propOnMouseLeave === void 0 ? void 0 : propOnMouseLeave(event); if (stuck || disabled || isTouch || event.isPropagationStopped()) { return; } startDisableTimer(); window.clearTimeout(timeoutRef.current); if (exitVisibilityDelay === 0) { setVisible(false); return; } timeoutRef.current = window.setTimeout(function () { setVisible(false); }, exitVisibilityDelay); }, [ disabled, exitVisibilityDelay, isTouch, propOnMouseLeave, startDisableTimer, stuck, ]); var onClick = React.useCallback(function (event) { propOnClick === null || propOnClick === void 0 ? void 0 : propOnClick(event); if (event.isPropagationStopped() || disabled) { return; } startDisableTimer(); window.clearTimeout(timeoutRef.current); }, [disabled, propOnClick, startDisableTimer]); var onStickyClick = React.useCallback(function (event) { propOnClick === null || propOnClick === void 0 ? void 0 : propOnClick(event); if (event.isPropagationStopped() || disabled) { return; } if (!stuck) { setStuck(true); setVisible(true); disableHoverMode(); } else { setStuck(false); setVisible(function (prevVisible) { return !prevVisible; }); } }, [disableHoverMode, disabled, propOnClick, stuck]); var handlers = { onClick: onClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, }; var stickyHandlers; if (sticky) { stickyHandlers = __assign$hh(__assign$hh({}, handlers), { onClick: onStickyClick }); } return { active: active, stuck: sticky ? stuck : undefined, visible: visible, setVisible: setVisible, handlers: handlers, stickyHandlers: stickyHandlers, enableHoverMode: enableHoverMode, disableHoverMode: disableHoverMode, startDisableTimer: startDisableTimer, }; } var DEFAULT_DESKTOP_MIN_WIDTH = 1025 / 16 + "em"; var DEFAULT_TABLET_MIN_WIDTH = 768 / 16 + "em"; var DEFAULT_TABLET_MAX_WIDTH = 1024 / 16 + "em"; var DEFAULT_PHONE_MAX_WIDTH = 767 / 16 + "em"; var DEFAULT_DESKTOP_LARGE_MIN_WIDTH = 1280 / 16 + "em"; /** * An extremely simple "pollyfill" for the `window.screen.orientation` just for * the `type` value that is required for the `useOrientation` hook. */ var getOrientationType = function () { var _a; var screenOrientation = (_a = window.screen.orientation) === null || _a === void 0 ? void 0 : _a.type; if (typeof screenOrientation === "string") { return screenOrientation; } var _b = window.screen, availHeight = _b.availHeight, availWidth = _b.availWidth; return availHeight > availWidth ? "portrait-primary" : "landscape-primary"; }; /** * This media query is used to determine the current orientation of the app * based on the `window.screen.orientation.type`. This will always be * `"landscape-primary"` server side unless a default value is provided. * * @param defaultValue - an optional default value to use. When this is omitted, * it will default to `"landscape-primary"` unless the `window` is defined. If * the `window` is defined, it will immediately check the orientation type on * mount. * @returns the orientation type value. */ function useOrientation$1(defaultValue) { var _a = React.useState(function () { if (defaultValue) { return defaultValue; } if (typeof window !== "undefined") { return getOrientationType(); } return "landscape-primary"; }), value = _a[0], setValue = _a[1]; React.useEffect(function () { if (typeof window === "undefined") { return; } var handler = function () { setValue(getOrientationType()); }; window.addEventListener("orientationchange", handler); return function () { return window.removeEventListener("orientationchange", handler); }; }, []); return value; } /** * A helper hook that is used to create a memoized media query tester for * `window.matchMedia`. * * Note: This is a **client side only** hook as it requires the `window` to * attach a resize event listener to. * * @param query - The media query to use * @param defaultValue - The default value for if this media query matches. When * this is `undefined`, it will default to `false` unless the `window` is * defined and the `checkImmediately` param was not set to `false`. Otherwise, * it will check the media query matches on mount and use that value. * @param disabled - Boolean if the media query checking should be disabled. * @param checkImmediately - Boolean if the media query should be checked * immediately on mount. When omittied, it will default to checking when the * window is defined. * @returns true if the media query is a match. */ function useMediaQuery(query, defaultValue, disabled, checkImmediately) { if (disabled === void 0) { disabled = false; } if (checkImmediately === void 0) { checkImmediately = typeof window !== "undefined"; } var _a = React.useState(function () { if (typeof defaultValue !== "undefined") { return defaultValue; } if (!disabled && checkImmediately && typeof window !== "undefined") { return window.matchMedia(query).matches; } return false; }), matches = _a[0], setMatches = _a[1]; React.useEffect(function () { if (typeof window === "undefined" || disabled) { return; } var mq = window.matchMedia(query); var updater = function (_a) { var matches = _a.matches; return setMatches(matches); }; mq.addEventListener("change", updater); if (mq.matches !== matches) { setMatches(mq.matches); } return function () { mq.removeEventListener("change", updater); }; }, [disabled, matches, query]); return matches; } /** * This is a small helper that will create a media query block based on the * provided width value. */ var toWidthPart = function (v, prefix) { var type = typeof v; if (type === "undefined") { return ""; } var value = type === "number" ? v + "px" : v; return "(" + prefix + "-width: " + value + ")"; }; /** * This is a simple hoo that will create a memoized media query string with the * provided min anx max values. * * @param min - An optional min value to use * @param max - An optional max value to use * @returns a boolean if the current media query is a match. */ function useWidthMediaQuery(_a) { var min = _a.min, max = _a.max; var query = React.useMemo(function () { var parts = [toWidthPart(min, "min"), toWidthPart(max, "max")] .filter(Boolean) .join(" and "); return "screen and " + parts; }, [min, max]); return useMediaQuery(query); } var DEFAULT_APP_SIZE = { isPhone: false, isTa