react-md
Version:
This is the full react-md library bundled together for convenience.
1,322 lines (1,278 loc) • 1 MB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom'], factory) :
(global = global || self, factory(global.ReactMD = {}, global.React, global.ReactDOM));
}(this, (function (exports, React, ReactDOM) { 'use strict';
var React__default = 'default' in React ? React['default'] : React;
var ReactDOM__default = 'default' in ReactDOM ? ReactDOM['default'] : ReactDOM;
function createCommonjsModule(fn, basedir, module) {
return module = {
path: basedir,
exports: {},
require: function (path, base) {
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
}
}, fn(module, module.exports), module.exports;
}
function commonjsRequire () {
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
}
var classnames = createCommonjsModule(function (module) {
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/* 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) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
if ( module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else {
window.classNames = classNames;
}
}());
});
/**
* 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
* @return 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);
}
{
try {
var PropTypes = require("prop-types");
Portal.propTypes = {
into: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.object,
]),
intoId: PropTypes.string,
children: PropTypes.node,
};
}
catch (e) { }
}
/**
* 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.createElement(Portal, { into: portalInto, intoId: portalIntoId }, children));
}
{
try {
var PropTypes$1 = require("prop-types");
ConditionalPortal.propTypes = {
portal: PropTypes$1.bool,
portalInto: PropTypes$1.oneOfType([
PropTypes$1.string,
PropTypes$1.func,
PropTypes$1.object,
]),
portalIntoId: PropTypes$1.string,
children: PropTypes$1.node,
};
}
catch (e) { }
}
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
* @return an object containing the r, g, b values for the color.
*/
function hexToRGB(hex) {
if (
!SHORTHAND_REGEX.test(hex) &&
!VERBOSE_REGEX.test(hex)) {
throw new TypeError("Invalid color string.");
}
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
* @private
*/
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
* @private
*/
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.
* @return 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.
* @return 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;
}
/* eslint-disable import/no-mutable-exports, getter-return */
var noop = function () { };
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, opts);
window.removeEventListener("testSupportsPassive", noop, 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.
* @return 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 = (undefined && undefined.__assign) || function () {
__assign = 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.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; }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
el[(add ? "add" : "remove") + "EventListener"]("touch" + eventType, callback, isSupported ? __assign({ 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.
* @return 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;
}
/**
* This hook will apply the current mode class name to the `document.body` so
* that the specific mode style mixins work as expected.
*
* @private
*/
function useModeClassName(mode) {
React.useEffect(function () {
var className = "rmd-utils--" + mode;
document.body.classList.add(className);
return function () {
document.body.classList.remove(className);
};
}, [mode]);
}
var DEFAULT_TOUCH_TIMEOUT = 1200;
/**
* This is a small hook that is used to determine if the app is currently being
* used by a touch device or not. All this really does is switch between
* mousemove and touchstart events to determine which mode you are in. This
* also tracks the `contextmenu` appearance since long touches can trigger the
* context menu on mobile devices. When the context menu appears after a touch,
* the mode will still be considered "touch" instead of swapping to mouse.
*
* @param touchTimeout This is the amount of time that can occur between a
* touchstart and mousemove event but still be considered part of a "touch" user
* mode. This should probably be kept at the default value, but if the touch
* mode isn't updating as you would expect, you can try increasing or decreasing
* this value until it does.
* @return true if the app is in touch mode.
* @private
*/
function useTouchDetection(touchTimeout) {
if (touchTimeout === void 0) { touchTimeout = DEFAULT_TOUCH_TIMEOUT; }
var _a = React.useState(0), lastTouchTime = _a[0], setTouchTime = _a[1];
var touchRef = useRefCache(lastTouchTime);
var contextMenuRef = React.useRef(false);
var updateTouchTime = React.useCallback(function () {
setTouchTime(Date.now());
contextMenuRef.current = false;
}, []);
var resetTouchTime = React.useCallback(function () {
var lastTouchTime = touchRef.current;
if (contextMenuRef.current || Date.now() - lastTouchTime < touchTimeout) {
contextMenuRef.current = false;
return;
}
setTouchTime(0);
// disabled since useRefCache for touchRef
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [touchTimeout]);
React.useEffect(function () {
window.addEventListener("touchstart", updateTouchTime, true);
return function () {
window.removeEventListener("touchstart", updateTouchTime, true);
};
}, [updateTouchTime]);
React.useEffect(function () {
if (lastTouchTime === 0) {
contextMenuRef.current = false;
return;
}
var updateContextMenu = function () {
contextMenuRef.current = true;
};
window.addEventListener("mousemove", resetTouchTime, true);
window.addEventListener("contextmenu", updateContextMenu, true);
return function () {
window.removeEventListener("mousemove", resetTouchTime, true);
window.removeEventListener("contextmenu", updateContextMenu, true);
};
}, [lastTouchTime, resetTouchTime]);
return lastTouchTime !== 0;
}
/**
* This hooks provides an easy way to toggle a boolean flag for React
* components. The main use case for this will be toggling the visibility of
* something. All the provided actions are guaranteed to never change.
*
* @param defaultToggled Boolean if the visibility should be enabled first
* render.
* @return an array containing the toggled state, an enable function, a disable
* function, a toggle function, and then a manual set toggle function.
*/
function useToggle(defaultToggled) {
var _a = React.useState(defaultToggled), toggled = _a[0], setToggled = _a[1];
var previous = useRefCache(toggled);
var enable = React.useCallback(function () {
if (!previous.current) {
setToggled(true);
}
// disabled since useRefCache
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
var disable = React.useCallback(function () {
if (previous.current) {
setToggled(false);
}
// disabled since useRefCache
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
var toggle = React.useCallback(function () {
setToggled(function (prevVisible) { return !prevVisible; });
}, []);
return [toggled, enable, disable, toggle, setToggled];
}
/**
* A small hook for checking if the app is currently being interacted with by a
* keyboard.
*
* @return true if the app is in keyboard mode
* @private
*/
function useKeyboardDetection() {
var _a = useToggle(false), enabled = _a[0], enable = _a[1], disable = _a[2];
React.useEffect(function () {
if (enabled) {
return;
}
window.addEventListener("keydown", enable, true);
return function () {
window.removeEventListener("keydown", enable, true);
};
}, [enabled, enable]);
React.useEffect(function () {
if (!enabled) {
return;
}
window.addEventListener("mousedown", disable, true);
window.addEventListener("touchstart", disable, true);
return function () {
window.removeEventListener("mousedown", disable, true);
window.removeEventListener("touchstart", disable, true);
};
}, [enabled, disable]);
return enabled;
}
/**
* This hook combines the touch and keyboard detection hooks and returns a
* string of the current interaction mode of the app/user.
*
* @private
*/
function useModeDetection() {
var touch = useTouchDetection();
var keyboard = useKeyboardDetection();
if (touch) {
return "touch";
}
if (keyboard) {
return "keyboard";
}
return "mouse";
}
var InteractionMode = React.createContext("mouse");
var ParentContext = React.createContext(false);
/**
* Gets the current interaction mode of the user.
*/
function useUserInteractionMode() {
return React.useContext(InteractionMode);
}
/**
* Checks if the provided user interaction mode matches the current interaction
* mode within the app.
*
* @param mode The mode to check against.
* @return true if the mode matches.
*/
function useIsUserInteractionMode(mode) {
return useUserInteractionMode() === mode;
}
/**
* A component that should be mounted once in your app near the top of the tree
* to determine the current interaction mode for your app.
*/
function InteractionModeListener(_a) {
var children = _a.children;
if (React.useContext(ParentContext)) {
throw new Error("Nested `InteractionModeListener` components");
}
var mode = useModeDetection();
useModeClassName(mode);
return (React__default.createElement(InteractionMode.Provider, { value: mode },
React__default.createElement(ParentContext.Provider, { value: true }, children)));
}
{
try {
var PropTypes$2 = require("prop-types");
InteractionModeListener.propTypes = {
children: PropTypes$2.node.isRequired,
};
}
catch (e) { }
}
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
* @return a function to call that generates the full class name
*/
function bem(base) {
{
if (!base) {
throw new Error("bem requires a base block class but none were provided.");
}
}
/**
* 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.
* @return the full class name
*/
return function block(elementOrModifier, modifier) {
{
if (typeof elementOrModifier !== "string" && modifier) {
throw new TypeError("bem does not support having two modifier arguments.");
}
}
if (!elementOrModifier) {
return base;
}
if (typeof elementOrModifier !== "string") {
return modify(base, elementOrModifier);
}
return modify(base + "__" + elementOrModifier, modifier);
};
}
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";
/**
* 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.
* @return 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.addListener(updater);
if (mq.matches !== matches) {
setMatches(mq.matches);
}
return function () { return mq.removeListener(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
* @return 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);
}
/**
* 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.
* @return the orientation type value.
*/
function useOrientation(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;
}
var DEFAULT_APP_SIZE = {
isPhone: false,
isTablet: false,
isDesktop: true,
isLargeDesktop: false,
isLandscape: true,
};
/**
* This hook is used to determine the current application size based on the
* provided query sizes. When you want to render your app server side, you will
* need to provide a custom `defaultSize` that implements your logic to
* determine the type of device requesting a page. Once the app has been
* rendered in the DOM, this hook will attach event listeners to automatically
* update the app size when the page is resized.
*
* @private
*/
function useAppSizeMedia(_a) {
var _b = _a === void 0 ? {} : _a, _c = _b.phoneMaxWidth, phoneMaxWidth = _c === void 0 ? DEFAULT_PHONE_MAX_WIDTH : _c, _d = _b.tabletMinWidth, tabletMinWidth = _d === void 0 ? DEFAULT_TABLET_MIN_WIDTH : _d, _e = _b.tabletMaxWidth, tabletMaxWidth = _e === void 0 ? DEFAULT_TABLET_MAX_WIDTH : _e, _f = _b.desktopMinWidth, desktopMinWidth = _f === void 0 ? DEFAULT_DESKTOP_MIN_WIDTH : _f, _g = _b.desktopLargeMinWidth, desktopLargeMinWidth = _g === void 0 ? DEFAULT_DESKTOP_LARGE_MIN_WIDTH : _g, _h = _b.defaultSize, defaultSize = _h === void 0 ? DEFAULT_APP_SIZE : _h;
/* eslint-disable react-hooks/rules-of-hooks */
// disabled since this is conditionally applied for SSR
if (typeof window === "undefined") {
return defaultSize;
}
var matchesDesktop = useWidthMediaQuery({ min: desktopMinWidth });
var matchesLargeDesktop = useWidthMediaQuery({ min: desktopLargeMinWidth });
var matchesTablet = useWidthMediaQuery({
min: tabletMinWidth,
max: tabletMaxWidth,
});
var matchesPhone = useWidthMediaQuery({ max: phoneMaxWidth });
var isDesktop = matchesDesktop;
var isTablet = !matchesDesktop && matchesTablet;
var isPhone = !isTablet && !isDesktop && matchesPhone;
var isLandscape = useOrientation().includes("landscape");
var isLargeDesktop = matchesLargeDesktop;
var _j = React.useState(defaultSize), appSize = _j[0], setAppSize = _j[1];
React.useEffect(function () {
if (appSize.isPhone === isPhone &&
appSize.isTablet === isTablet &&
appSize.isDesktop === isDesktop &&
appSize.isLargeDesktop === isLargeDesktop &&
appSize.isLandscape === isLandscape) {
return;
}
// for some reason, it's sometimes possible to fail every single matchMedia
// value when you are resizing the browser a lot. this is an "invalid" event
// so skip it. It normally happens between 760px-768px
if (!isPhone && !isTablet && !isDesktop && !isLargeDesktop) {
return;
}
setAppSize({ isPhone: isPhone, isTablet: isTablet, isDesktop: isDesktop, isLargeDesktop: isLargeDesktop, isLandscape: isLandscape });
}, [isPhone, isTablet, isDesktop, isLargeDesktop, isLandscape, appSize]);
return appSize;
}
var __assign$1 = (undefined && undefined.__assign) || function () {
__assign$1 = 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$1.apply(this, arguments);
};
var __rest = (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;
};
/**
* @private
*/
var AppSizeContext = React.createContext(__assign$1(__assign$1({}, DEFAULT_APP_SIZE), { __initialized: false }));
/**
* Gets the current app size.
*
* @return the current AppSize
*/
function useAppSize() {
var _a = React.useContext(AppSizeContext), __initialized = _a.__initialized, context = __rest(_a, ["__initialized"]);
if (!__initialized) {
throw new Error("Attempted to use the current `AppSizeContext` without mounting the `AppSizeListener` component beforehand.");
}
return context;
}
var __assign$2 = (undefined && undefined.__assign) || function () {
__assign$2 = 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$2.apply(this, arguments);
};
var __rest$1 = (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 block = bem("rmd-grid");
var GridCell = React.forwardRef(function GridCell(_a, ref) {
var _b, _c, _d, _e, _f, _g, _h;
var style = _a.style, className = _a.className, clone = _a.clone, children = _a.children, propColSpan = _a.colSpan, propColStart = _a.colStart, propColEnd = _a.colEnd, propRowSpan = _a.rowSpan, propRowStart = _a.rowStart, propRowEnd = _a.rowEnd, phone = _a.phone, tablet = _a.tablet, desktop = _a.desktop, largeDesktop = _a.largeDesktop, props = __rest$1(_a, ["style", "className", "clone", "children", "colSpan", "colStart", "colEnd", "rowSpan", "rowStart", "rowEnd", "phone", "tablet", "desktop", "largeDesktop"]);
var _j = useAppSize(), isPhone = _j.isPhone, isTablet = _j.isTablet, isDesktop = _j.isDesktop, isLargeDesktop = _j.isLargeDesktop;
var colSpan = propColSpan;
var colStart = propColStart;
var colEnd = propColEnd;
var rowSpan = propRowSpan;
var rowStart = propRowStart;
var rowEnd = propRowEnd;
var media = (isPhone && phone) ||
(isTablet && tablet) ||
(isDesktop && desktop) ||
(isLargeDesktop && largeDesktop);
if (media) {
(_b = media.rowSpan, rowSpan = _b === void 0 ? propRowSpan : _b, _c = media.rowStart, rowStart = _c === void 0 ? propRowStart : _c, _d = media.rowEnd, rowEnd = _d === void 0 ? propRowEnd : _d, _e = media.colSpan, colSpan = _e === void 0 ? propColSpan : _e, _f = media.colStart, colStart = _f === void 0 ? propColStart : _f, _g = media.colEnd, colEnd = _g === void 0 ? propColEnd : _g);
}
var cellStyle = __assign$2({ gridColumnStart: colStart, gridColumnEnd: colEnd, gridRowStart: rowStart, gridRowEnd: rowSpan ? "span " + rowSpan : rowEnd }, style);
var cellClassName = classnames(block("cell", (_h = {},
_h["" + colSpan] = colSpan,
_h)), className);
if (clone && React.isValidElement(children)) {
var child = React.Children.only(children);
return React.cloneElement(child, {
style: __assign$2(__assign$2({}, child.props.style), cellStyle),
className: classnames(cellClassName, child.props.className),
});
}
return (React__default.createElement("div", __assign$2({}, props, { ref: ref, style: cellStyle, className: cellClassName }), children));
});
{
try {
var PropTypes$3 = require("prop-types");
var gridCSSProperties = PropTypes$3.shape({
rowSpan: PropTypes$3.number,
rowStart: PropTypes$3.oneOfType([PropTypes$3.number, PropTypes$3.string]),
rowEnd: PropTypes$3.oneOfType([PropTypes$3.number, PropTypes$3.string]),
colSpan: PropTypes$3.number,
colStart: PropTypes$3.oneOfType([PropTypes$3.number, PropTypes$3.string]),
colEnd: PropTypes$3.oneOfType([PropTypes$3.number, PropTypes$3.string]),
});
GridCell.propTypes = {
style: PropTypes$3.object,
className: PropTypes$3.string,
clone: PropTypes$3.bool,
rowSpan: PropTypes$3.number,
rowStart: PropTypes$3.oneOfType([PropTypes$3.number, PropTypes$3.string]),
rowEnd: PropTypes$3.oneOfType([PropTypes$3.number, PropTypes$3.string]),
colSpan: PropTypes$3.number,
colStart: PropTypes$3.oneOfType([PropTypes$3.number, PropTypes$3.string]),
colEnd: PropTypes$3.oneOfType([PropTypes$3.number, PropTypes$3.string]),
phone: gridCSSProperties,
tablet: gridCSSProperties,
desktop: gridCSSProperties,
largeDesktop: gridCSSProperties,
children: PropTypes$3.node,
};
}
catch (e) { }
}
var __assign$3 = (undefined && undefined.__assign) || function () {
__assign$3 = 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$3.apply(this, arguments);
};
var __rest$2 = (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;
};
/**
* This CSS Variable allows you to override the number of columns that should be
* displayed in the grid. This is automatically updated with media queries with
* the default grid implementation, but is used here to add additional
* inline-style overrides.
*
* @private
*/
var GRID_COLUMNS_VAR = "--rmd-grid-cols";
/**
* This CSS Variable allows you to override the gutter (grid-gap) between each
* cell in the grid.
*
* @private
*/
var GRID_GUTTER_VAR = "--rmd-grid-gutter";
var block$1 = bem("rmd-grid");
/**
* The grid component is generally used for a base layout in your app to provide
* nice padding and spacing between each item.
*
* Note: This component relies on the `AppSizeListener` as a parent component to
* work and will throw an error if it does not exist as a parent.
*/
var Grid = React.forwardRef(function Grid(_a, ref) {
var _b;
var style = _a.style, className = _a.className, children = _a.children, _c = _a.clone, clone = _c === void 0 ? false : _c, _d = _a.wrapOnly, wrapOnly = _d === void 0 ? false : _d, columns = _a.columns, phoneColumns = _a.phoneColumns, tabletColumns = _a.tabletColumns, desktopColumns = _a.desktopColumns, largeDesktopColumns = _a.largeDesktopColumns, padding = _a.padding, gutter = _a.gutter, minCellWidth = _a.minCellWidth, props = __rest$2(_a, ["style", "className", "children", "clone", "wrapOnly", "columns", "phoneColumns", "tabletColumns", "desktopColumns", "largeDesktopColumns", "padding", "gutter", "minCellWidth"]);
var _e = useAppSize(), isPhone = _e.isPhone, isTablet = _e.isTablet, isDesktop = _e.isDesktop, isLargeDesktop = _e.isLargeDesktop;
var mergedStyle = __assign$3(__assign$3({ padding: (padding !== 0 && padding) || undefined, gridTemplateColumns: minCellWidth
? "repeat(auto-fill, minmax(" + minCellWidth + ", 1fr))"
: undefined }, style), (_b = {}, _b[GRID_COLUMNS_VAR] = (isPhone && phoneColumns) ||
(isTablet && tabletColumns) ||
(isLargeDesktop && largeDesktopColumns) ||
(isDesktop && desktopColumns) ||
columns, _b[GRID_GUTTER_VAR] = gutter, _b));
var content = children;
if (clone || wrapOnly) {
content = React.Children.map(children, function (child) { return child && React__default.createElement(GridCell, { clone: clone }, child); });
}
return (React__default.createElement("div", __assign$3({}, props, { ref: ref, style: mergedStyle, className: classnames(block$1({ "no-padding": padding === 0 }), className) }), content));
});
{
try {
var PropTypes$4 = require("prop-types");
Grid.propTypes = {
style: PropTypes$4.object,
className: PropTypes$4.string,
clone: PropTypes$4.bool,
wrapOnly: PropTypes$4.bool,
columns: PropTypes$4.number,
phoneColumns: PropTypes$4.number,
tabletColumns: PropTypes$4.number,
desktopColumns: PropTypes$4.number,
largeDesktopColumns: PropTypes$4.number,
padding: PropTypes$4.oneOfType([PropTypes$4.number, PropTypes$4.string]),
gutter: PropTypes$4.string,
children: PropTypes$4.node,
minCellWidth: PropTypes$4.string,
};
}
catch (e) { }
}
/**
* 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;
}
}
/**
* A collection of shims that provide minimal functionality of the ES6 collections.
*
* These implementations are not meant to be used outside of the ResizeObserver
* modules as they cover only a limited range of use cases.
*/
/* eslint-disable require-jsdoc, valid-jsdoc */
var MapShim = (function () {
if (typeof Map !== 'undefined') {
return Map;
}
/**
* Returns index in provided array that matches the specified key.
*
* @param {Array<Array>} arr
* @param {*} key
* @returns {number}
*/
function getIndex(arr, key) {
var result = -1;
arr.some(function (entry, index) {
if (entry[0] === key) {
result = index;
return true;
}
return false;
});
return result;
}
return /** @class */ (function () {
function class_1() {
this.__entries__ = [];
}
Object.defineProperty(class_1.prototype, "size", {
/**
* @returns {boolean}
*/
get: function () {
return this.__entries__.length;
},
enumerable: true,
configurable: true
});
/**
* @param {*} key
* @returns {*}
*/
class_1.prototype.get = function (key) {
var index = getIndex(this.__entries__, key);
var entry = this.__entries__[index];
return entry && entry[1];
};
/**
* @param {*} key
* @param {*} value
* @returns {void}
*/
class_1.prototype.set = function (key, value) {
var index = getIndex(this.__entries__, key);
if (~index) {
this.__entries__[index][1] = value;
}
else {
this.__entries__.push([key, value]);
}
};
/**
* @param {*} key