diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 6a443193906..a6e2d230e7c 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -23,7 +23,7 @@ body:
id: reproduce
attributes:
label: Steps to reproduce
- description: 'How do we reproduce the error you described above?'
+ description: 'How do we reproduce the error you described above? Feel free to use the `@primer/react` template on [CodeSandbox](https://codesandbox.io/s/primer-react-qyyepc) to get started'
placeholder: |
1. Go to '...'
2. Click on '....'
diff --git a/lib-esm/Checkbox.js b/lib-esm/Checkbox.js
index dc2049624cb..5e0d13c564a 100644
--- a/lib-esm/Checkbox.js
+++ b/lib-esm/Checkbox.js
@@ -1,7 +1,7 @@
import styled from 'styled-components';
import React, { useContext } from 'react';
import sx from './sx.js';
-import useLayoutEffect from './utils/useIsomorphicLayoutEffect.js';
+import useIsomorphicLayoutEffect from './utils/useIsomorphicLayoutEffect.js';
import { CheckboxGroupContext } from './CheckboxGroupContext.js';
import getGlobalFocusStyles from './_getGlobalFocusStyles.js';
import { useProvidedRefOrCreate } from './hooks/useProvidedRefOrCreate.js';
@@ -34,7 +34,7 @@ const Checkbox = /*#__PURE__*/React.forwardRef(({
onChange && onChange(e);
};
- useLayoutEffect(() => {
+ useIsomorphicLayoutEffect(() => {
if (checkboxRef.current) {
checkboxRef.current.indeterminate = indeterminate || false;
}
diff --git a/lib-esm/Overlay.js b/lib-esm/Overlay.js
index ad6d768c9a9..a5ff9245bf8 100644
--- a/lib-esm/Overlay.js
+++ b/lib-esm/Overlay.js
@@ -1,6 +1,6 @@
import styled from 'styled-components';
import React, { useRef, useEffect } from 'react';
-import useLayoutEffect from './utils/useIsomorphicLayoutEffect.js';
+import useIsomorphicLayoutEffect from './utils/useIsomorphicLayoutEffect.js';
import { get } from './constants.js';
import Portal from './Portal/index.js';
import sx from './sx.js';
@@ -119,7 +119,7 @@ const Overlay = /*#__PURE__*/React.forwardRef(({
overlayRef.current.style.height = `${overlayRef.current.clientHeight}px`;
}
}, [height]);
- useLayoutEffect(() => {
+ useIsomorphicLayoutEffect(() => {
var _overlayRef$current2;
const {
diff --git a/lib-esm/Portal/Portal.js b/lib-esm/Portal/Portal.js
index 4f3dd0aad59..c8aa6483f36 100644
--- a/lib-esm/Portal/Portal.js
+++ b/lib-esm/Portal/Portal.js
@@ -1,6 +1,6 @@
import React from 'react';
import { createPortal } from 'react-dom';
-import useLayoutEffect from '../utils/useIsomorphicLayoutEffect.js';
+import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect.js';
const PRIMER_PORTAL_ROOT_ID = '__primerPortalRoot__';
const DEFAULT_PORTAL_CONTAINER_NAME = '__default__';
@@ -59,7 +59,7 @@ const Portal = ({
hostElement.style.position = 'relative';
hostElement.style.zIndex = '1';
const elementRef = React.useRef(hostElement);
- useLayoutEffect(() => {
+ useIsomorphicLayoutEffect(() => {
let containerName = _containerName;
if (containerName === undefined) {
diff --git a/lib-esm/constants.js b/lib-esm/constants.js
index d87c1ad8381..1c7ce314d16 100644
--- a/lib-esm/constants.js
+++ b/lib-esm/constants.js
@@ -19,6 +19,8 @@ const whiteSpace = system({
});
const TYPOGRAPHY = compose(styledSystem.typography, whiteSpace);
// Border props
-compose(styledSystem.border, styledSystem.shadow);
+const BORDER = compose(styledSystem.border, styledSystem.shadow);
+// Layout props
+const LAYOUT = styledSystem.layout;
-export { COMMON, TYPOGRAPHY, get };
+export { BORDER, COMMON, LAYOUT, TYPOGRAPHY, get };
diff --git a/lib-esm/hooks/index.js b/lib-esm/hooks/index.js
new file mode 100644
index 00000000000..d729f19676a
--- /dev/null
+++ b/lib-esm/hooks/index.js
@@ -0,0 +1,12 @@
+export { useOnOutsideClick } from './useOnOutsideClick.js';
+export { useProvidedRefOrCreate } from './useProvidedRefOrCreate.js';
+export { useOnEscapePress } from './useOnEscapePress.js';
+export { useOpenAndCloseFocus } from './useOpenAndCloseFocus.js';
+export { useAnchoredPosition } from './useAnchoredPosition.js';
+export { useOverlay } from './useOverlay.js';
+export { useRenderForcingRef } from './useRenderForcingRef.js';
+export { useProvidedStateOrCreate } from './useProvidedStateOrCreate.js';
+export { useMenuInitialFocus } from './useMenuInitialFocus.js';
+export { useMenuKeyboardNavigation } from './useMenuKeyboardNavigation.js';
+export { useMnemonics } from './useMnemonics.js';
+export { useRefObjectAsForwardedRef } from './useRefObjectAsForwardedRef.js';
diff --git a/lib-esm/hooks/useAnchoredPosition.js b/lib-esm/hooks/useAnchoredPosition.js
index 9d670042810..5912177806b 100644
--- a/lib-esm/hooks/useAnchoredPosition.js
+++ b/lib-esm/hooks/useAnchoredPosition.js
@@ -2,7 +2,7 @@ import React from 'react';
import { getAnchoredPosition } from '@primer/behaviors';
import { useProvidedRefOrCreate } from './useProvidedRefOrCreate.js';
import { useResizeObserver } from './useResizeObserver.js';
-import useLayoutEffect from '../utils/useIsomorphicLayoutEffect.js';
+import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect.js';
/**
* Calculates the top and left values for an absolutely-positioned floating element
@@ -25,7 +25,7 @@ function useAnchoredPosition(settings, dependencies = []) {
}
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[floatingElementRef, anchorElementRef, ...dependencies]);
- useLayoutEffect(updatePosition, [updatePosition]);
+ useIsomorphicLayoutEffect(updatePosition, [updatePosition]);
useResizeObserver(updatePosition);
return {
floatingElementRef,
diff --git a/lib-esm/hooks/useControllableState.js b/lib-esm/hooks/useControllableState.js
index 95271a090db..38053024103 100644
--- a/lib-esm/hooks/useControllableState.js
+++ b/lib-esm/hooks/useControllableState.js
@@ -43,15 +43,11 @@ function useControllableState({
const controlledValue = value !== undefined; // Uncontrolled -> Controlled
// If the component prop is uncontrolled, the prop value should be undefined
- if (controlled.current === false && controlledValue) {
- warn();
- } // Controlled -> Uncontrolled
+ if (controlled.current === false && controlledValue) ; // Controlled -> Uncontrolled
// If the component prop is controlled, the prop value should be defined
- if (controlled.current === true && !controlledValue) {
- warn();
- }
+ if (controlled.current === true && !controlledValue) ;
}, [name, value]);
if (controlled.current === true) {
@@ -60,8 +56,5 @@ function useControllableState({
return [state, setState];
}
-/** Warn when running in a development environment */
-
-const warn = function emptyFunction() {};
export { useControllableState };
diff --git a/lib-esm/hooks/useMedia.js b/lib-esm/hooks/useMedia.js
index 39b91eef58d..98beaeeee00 100644
--- a/lib-esm/hooks/useMedia.js
+++ b/lib-esm/hooks/useMedia.js
@@ -1,4 +1,4 @@
-import React, { useContext, useEffect, createContext } from 'react';
+import React, { useContext, useEffect, createContext, useState } from 'react';
import { canUseDOM } from '../utils/environment.js';
/**
@@ -77,5 +77,55 @@ function useMedia(mediaQueryString, defaultState) {
// be used for development and demo purposes to emulate specific features if
// unavailable through devtools
const MatchMediaContext = /*#__PURE__*/createContext({});
+const defaultFeatures = {};
+/**
+ * Use `MatchMedia` to emulate media conditions by passing in feature
+ * queries to the `features` prop. If a component uses `useMedia` with the
+ * feature passed in to `MatchMedia` it will force its value to match what is
+ * provided to `MatchMedia`
+ *
+ * This should be used for development and documentation only in situations
+ * where devtools cannot emulate this feature
+ *
+ * @example
+ *
+ *
+ *
+ */
+
+function MatchMedia({
+ children,
+ features = defaultFeatures
+}) {
+ const value = useShallowObject(features);
+ return /*#__PURE__*/React.createElement(MatchMediaContext.Provider, {
+ value: value
+ }, children);
+}
+MatchMedia.displayName = "MatchMedia";
+
+/**
+ * Utility hook to provide a stable identity for a "simple" object which
+ * contains only primitive values. This provides a `useMemo`-esque signature
+ * without dealing with shallow equality checks in the dependency array.
+ *
+ * Note (perf): this hook iterates through keys and values of the object if the
+ * shallow equality check is false each time the hook is called
+ */
+function useShallowObject(object) {
+ const [value, setValue] = useState(object);
+
+ if (value !== object) {
+ const match = Object.keys(object).every(key => {
+ return object[key] === value[key];
+ });
+
+ if (!match) {
+ setValue(object);
+ }
+ }
+
+ return value;
+}
-export { useMedia };
+export { MatchMedia, useMedia };
diff --git a/lib-esm/hooks/useResizeObserver.js b/lib-esm/hooks/useResizeObserver.js
index 5892a706333..75f79e3ddd2 100644
--- a/lib-esm/hooks/useResizeObserver.js
+++ b/lib-esm/hooks/useResizeObserver.js
@@ -1,12 +1,12 @@
import { useRef } from 'react';
-import useLayoutEffect from '../utils/useIsomorphicLayoutEffect.js';
+import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect.js';
function useResizeObserver(callback, target) {
const savedCallback = useRef(callback);
- useLayoutEffect(() => {
+ useIsomorphicLayoutEffect(() => {
savedCallback.current = callback;
});
- useLayoutEffect(() => {
+ useIsomorphicLayoutEffect(() => {
const targetEl = target && 'current' in target ? target.current : document.documentElement;
if (!targetEl) {
diff --git a/lib-esm/index.js b/lib-esm/index.js
index 3c9208d359a..883bd314812 100644
--- a/lib-esm/index.js
+++ b/lib-esm/index.js
@@ -1,4 +1,3 @@
-export { default as theme } from './theme-preval.js';
export { get as themeGet } from './constants.js';
export { default as BaseStyles } from './BaseStyles.js';
export { default as ThemeProvider, useColorSchemeVar, useTheme } from './ThemeProvider.js';
@@ -63,6 +62,7 @@ export { default as UnderlineNav } from './UnderlineNav.js';
export { default as Checkbox } from './Checkbox.js';
export { default as Textarea } from './Textarea.js';
export { default as sx } from './sx.js';
+export { default as theme } from './theme-preval.js';
export { PageLayout } from './PageLayout/PageLayout.js';
export { AnchoredOverlay } from './AnchoredOverlay/AnchoredOverlay.js';
export { default as Autocomplete } from './Autocomplete/Autocomplete.js';
diff --git a/lib-esm/polyfills/eventListenerSignal.js b/lib-esm/polyfills/eventListenerSignal.js
new file mode 100644
index 00000000000..7ec2c8945c8
--- /dev/null
+++ b/lib-esm/polyfills/eventListenerSignal.js
@@ -0,0 +1,59 @@
+/*
+
+This file polyfills the following: https://github.com/whatwg/dom/issues/911
+Once all targeted browsers support this DOM feature, this polyfill can be deleted.
+
+This allows users to pass an AbortSignal to a call to addEventListener as part of the
+AddEventListenerOptions object. When the signal is aborted, the event listener is
+removed.
+
+*/
+let signalSupported = false;
+
+function noop() {}
+
+try {
+ const options = Object.create({}, {
+ signal: {
+ get() {
+ signalSupported = true;
+ }
+
+ }
+ });
+ window.addEventListener('test', noop, options);
+ window.removeEventListener('test', noop, options);
+} catch (e) {
+ /* */
+}
+
+function featureSupported() {
+ return signalSupported;
+}
+
+function monkeyPatch() {
+ if (typeof window === 'undefined') {
+ return;
+ }
+
+ const originalAddEventListener = EventTarget.prototype.addEventListener;
+
+ EventTarget.prototype.addEventListener = function (name, originalCallback, optionsOrCapture) {
+ if (typeof optionsOrCapture === 'object' && 'signal' in optionsOrCapture && optionsOrCapture.signal instanceof AbortSignal) {
+ originalAddEventListener.call(optionsOrCapture.signal, 'abort', () => {
+ this.removeEventListener(name, originalCallback, optionsOrCapture);
+ });
+ }
+
+ return originalAddEventListener.call(this, name, originalCallback, optionsOrCapture);
+ };
+}
+
+function polyfill() {
+ if (!featureSupported()) {
+ monkeyPatch();
+ signalSupported = true;
+ }
+}
+
+export { polyfill };
diff --git a/lib-esm/utils/create-slots.js b/lib-esm/utils/create-slots.js
index e45a33aa740..91594215244 100644
--- a/lib-esm/utils/create-slots.js
+++ b/lib-esm/utils/create-slots.js
@@ -1,6 +1,6 @@
import React from 'react';
import { useForceUpdate } from './use-force-update.js';
-import useLayoutEffect from './useIsomorphicLayoutEffect.js';
+import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect.js';
/** createSlots is a factory that can create a
* typesafe Slots + Slot pair to use in a component definition
@@ -33,7 +33,7 @@ const createSlots = slotNames => {
const rerenderWithSlots = useForceUpdate();
const [isMounted, setIsMounted] = React.useState(false); // fires after all the effects in children
- useLayoutEffect(() => {
+ useIsomorphicLayoutEffect(() => {
rerenderWithSlots();
setIsMounted(true);
}, [rerenderWithSlots]);
@@ -72,7 +72,7 @@ const createSlots = slotNames => {
unregisterSlot,
context
} = React.useContext(SlotsContext);
- useLayoutEffect(() => {
+ useIsomorphicLayoutEffect(() => {
registerSlot(name, typeof children === 'function' ? children(context) : children);
return () => unregisterSlot(name);
}, [name, children, registerSlot, unregisterSlot, context]);
@@ -85,6 +85,4 @@ const createSlots = slotNames => {
};
};
-var createSlots$1 = createSlots;
-
-export { createSlots$1 as default };
+export { createSlots as default };
diff --git a/lib-esm/utils/deprecate.js b/lib-esm/utils/deprecate.js
new file mode 100644
index 00000000000..b6d052b59c6
--- /dev/null
+++ b/lib-esm/utils/deprecate.js
@@ -0,0 +1,55 @@
+import 'react';
+
+const noop = () => {}; // eslint-disable-next-line import/no-mutable-exports
+
+
+let deprecate = noop;
+
+let useDeprecation = null;
+
+{
+ useDeprecation = () => {
+ return noop;
+ };
+}
+class Deprecations {
+ static instance = null;
+
+ static get() {
+ if (!Deprecations.instance) {
+ Deprecations.instance = new Deprecations();
+ }
+
+ return Deprecations.instance;
+ }
+
+ constructor() {
+ this.deprecations = [];
+ }
+
+ static deprecate({
+ name,
+ message,
+ version
+ }) {
+ const msg = `WARNING! ${name} is deprecated and will be removed in version ${version}. ${message}`; // eslint-disable-next-line no-console
+
+ console.warn(msg);
+ this.get().deprecations.push({
+ name,
+ message,
+ version
+ });
+ }
+
+ static getDeprecations() {
+ return this.get().deprecations;
+ }
+
+ static clearDeprecations() {
+ this.get().deprecations.length = 0;
+ }
+
+}
+
+export { Deprecations, deprecate, useDeprecation };
diff --git a/lib-esm/utils/polymorphic.js b/lib-esm/utils/polymorphic.js
new file mode 100644
index 00000000000..8b137891791
--- /dev/null
+++ b/lib-esm/utils/polymorphic.js
@@ -0,0 +1 @@
+
diff --git a/lib-esm/utils/ssr.js b/lib-esm/utils/ssr.js
new file mode 100644
index 00000000000..ec1a73776e3
--- /dev/null
+++ b/lib-esm/utils/ssr.js
@@ -0,0 +1 @@
+export { SSRProvider, useSSRSafeId } from '@react-aria/ssr';
diff --git a/lib-esm/utils/story-helpers.js b/lib-esm/utils/story-helpers.js
new file mode 100644
index 00000000000..8672a3bc550
--- /dev/null
+++ b/lib-esm/utils/story-helpers.js
@@ -0,0 +1,278 @@
+import React from 'react';
+import { createGlobalStyle } from 'styled-components';
+import { get } from '../constants.js';
+import theme from '../theme-preval.js';
+import Box from '../Box.js';
+import ThemeProvider from '../ThemeProvider.js';
+import BaseStyles from '../BaseStyles.js';
+
+// set global theme styles for each story
+const GlobalStyle = createGlobalStyle(["body{background-color:", ";color:", ";}"], get('colors.canvas.default'), get('colors.fg.default')); // only remove padding for multi-theme view grid
+
+const GlobalStyleMultiTheme = createGlobalStyle(["body{padding:0 !important;}"]);
+const withThemeProvider = (Story, context) => {
+ // used for testing ThemeProvider.stories.tsx
+ if (context.parameters.disableThemeDecorator) return /*#__PURE__*/React.createElement(Story, context);
+ const {
+ colorScheme
+ } = context.globals;
+
+ if (colorScheme === 'all') {
+ return /*#__PURE__*/React.createElement(Box, {
+ sx: {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(auto-fit, minmax(0, 1fr))',
+ height: '100vh'
+ }
+ }, /*#__PURE__*/React.createElement(GlobalStyleMultiTheme, null), Object.keys(theme.colorSchemes).map(scheme => /*#__PURE__*/React.createElement(ThemeProvider, {
+ key: scheme,
+ colorMode: "day",
+ dayScheme: scheme
+ }, /*#__PURE__*/React.createElement(BaseStyles, null, /*#__PURE__*/React.createElement(Box, {
+ sx: {
+ padding: '1rem',
+ height: '100%',
+ backgroundColor: 'canvas.default',
+ color: 'fg.default'
+ }
+ }, /*#__PURE__*/React.createElement("div", {
+ id: `html-addon-root-${scheme}`
+ }, /*#__PURE__*/React.createElement(Story, context)))))));
+ }
+
+ return /*#__PURE__*/React.createElement(ThemeProvider, {
+ colorMode: "day",
+ dayScheme: colorScheme
+ }, /*#__PURE__*/React.createElement(GlobalStyle, null), /*#__PURE__*/React.createElement(BaseStyles, null, /*#__PURE__*/React.createElement("div", {
+ id: "html-addon-root"
+ }, /*#__PURE__*/React.createElement(Story, context))));
+};
+withThemeProvider.displayName = "withThemeProvider";
+const toolbarTypes = {
+ colorScheme: {
+ name: 'Color scheme',
+ description: 'Switch color scheme',
+ defaultValue: 'light',
+ toolbar: {
+ icon: 'photo',
+ items: [...Object.keys(theme.colorSchemes), 'all'],
+ showName: true
+ }
+ }
+};
+const inputWrapperArgTypes = {
+ block: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ contrast: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ disabled: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ placeholder: {
+ defaultValue: '',
+ control: {
+ type: 'text'
+ }
+ },
+ size: {
+ name: 'size (input)',
+ // TODO: remove '(input)'
+ defaultValue: 'medium',
+ options: ['small', 'medium', 'large'],
+ control: {
+ type: 'radio'
+ }
+ },
+ validationStatus: {
+ defaultValue: undefined,
+ options: ['error', 'success', 'warning', undefined],
+ control: {
+ type: 'radio'
+ }
+ }
+};
+const textInputArgTypesUnsorted = { ...inputWrapperArgTypes,
+ loading: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ loaderPosition: {
+ defaultValue: 'auto',
+ options: ['auto', 'leading', 'trailing'],
+ control: {
+ type: 'radio'
+ }
+ },
+ monospace: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ }
+}; // Alphabetize and optionally categorize the props
+
+const getTextInputArgTypes = category => Object.keys(textInputArgTypesUnsorted).sort().reduce((obj, key) => {
+ obj[key] = category ? { // have to do weird type casting so we can spread the object
+ ...textInputArgTypesUnsorted[key],
+ table: {
+ category
+ }
+ } : textInputArgTypesUnsorted[key];
+ return obj;
+}, {});
+const textInputExcludedControlKeys = ['as', 'icon', 'leadingVisual', 'sx', 'trailingVisual', 'trailingAction'];
+const textInputWithTokensArgTypes = {
+ hideTokenRemoveButtons: {
+ defaultValue: false,
+ type: 'boolean',
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ },
+ maxHeight: {
+ type: 'string',
+ defaultValue: 'none',
+ description: 'Any valid value for the CSS max-height property',
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ },
+ preventTokenWrapping: {
+ defaultValue: false,
+ type: 'boolean',
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ },
+ size: {
+ name: 'size (token size)',
+ defaultValue: 'xlarge',
+ options: ['small', 'medium', 'large', 'xlarge'],
+ control: {
+ type: 'radio'
+ },
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ },
+ visibleTokenCount: {
+ defaultValue: 999,
+ type: 'number',
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ }
+};
+const formControlArgTypes = {
+ // FormControl
+ required: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ },
+ table: {
+ category: 'FormControl'
+ }
+ },
+ disabled: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ },
+ table: {
+ category: 'FormControl'
+ }
+ },
+ // FormControl.Label
+ labelChildren: {
+ name: 'children',
+ type: 'string',
+ defaultValue: 'Label',
+ table: {
+ category: 'FormControl.Label'
+ }
+ },
+ visuallyHidden: {
+ defaultValue: false,
+ type: 'boolean',
+ table: {
+ category: 'FormControl.Label'
+ }
+ },
+ // FormControl.Caption
+ captionChildren: {
+ name: 'children',
+ type: 'string',
+ defaultValue: '',
+ table: {
+ category: 'FormControl.Caption'
+ }
+ },
+ // FormControl.Validation
+ validationChildren: {
+ name: 'children',
+ type: 'string',
+ defaultValue: '',
+ table: {
+ category: 'FormControl.Validation'
+ }
+ },
+ variant: {
+ defaultValue: 'error',
+ control: {
+ type: 'radio',
+ options: ['error', 'success', 'warning']
+ },
+ table: {
+ category: 'FormControl.Validation'
+ }
+ }
+};
+const formControlArgTypeKeys = Object.keys(formControlArgTypes);
+const formControlArgTypesWithoutValidation = formControlArgTypeKeys.reduce((acc, key) => {
+ if (formControlArgTypes[key].table.category !== 'FormControl.Validation') {
+ acc[key] = formControlArgTypes[key];
+ }
+
+ return acc;
+}, {});
+const getFormControlArgsByChildComponent = ({
+ captionChildren,
+ disabled,
+ labelChildren,
+ required,
+ validationChildren,
+ variant,
+ visuallyHidden
+}) => ({
+ parentArgs: {
+ disabled,
+ required
+ },
+ labelArgs: {
+ visuallyHidden,
+ children: labelChildren
+ },
+ captionArgs: {
+ children: captionChildren
+ },
+ validationArgs: {
+ children: validationChildren,
+ variant
+ }
+});
+
+export { formControlArgTypes, formControlArgTypesWithoutValidation, getFormControlArgsByChildComponent, getTextInputArgTypes, inputWrapperArgTypes, textInputExcludedControlKeys, textInputWithTokensArgTypes, toolbarTypes, withThemeProvider };
diff --git a/lib-esm/utils/test-deprecations.js b/lib-esm/utils/test-deprecations.js
new file mode 100644
index 00000000000..67842f74f3a
--- /dev/null
+++ b/lib-esm/utils/test-deprecations.js
@@ -0,0 +1,17 @@
+import semver from 'semver';
+import { Deprecations } from './deprecate.js';
+
+const ourVersion = require('../../package.json').version;
+
+beforeEach(() => {
+ Deprecations.clearDeprecations();
+});
+afterEach(() => {
+ const deprecations = Deprecations.getDeprecations();
+
+ for (const dep of deprecations) {
+ if (semver.gte(ourVersion, dep.version)) {
+ throw new Error(`Found a deprecation that should be removed in ${dep.version}`);
+ }
+ }
+});
diff --git a/lib-esm/utils/test-helpers.js b/lib-esm/utils/test-helpers.js
new file mode 100644
index 00000000000..5ec1e3cb75f
--- /dev/null
+++ b/lib-esm/utils/test-helpers.js
@@ -0,0 +1,13 @@
+// JSDOM doesn't mock ResizeObserver
+global.ResizeObserver = jest.fn().mockImplementation(() => {
+ return {
+ observe: jest.fn(),
+ disconnect: jest.fn()
+ };
+});
+global.CSS = {
+ escape: jest.fn(),
+ supports: jest.fn().mockImplementation(() => {
+ return false;
+ })
+};
diff --git a/lib-esm/utils/test-matchers.js b/lib-esm/utils/test-matchers.js
new file mode 100644
index 00000000000..ae995e56e1e
--- /dev/null
+++ b/lib-esm/utils/test-matchers.js
@@ -0,0 +1,112 @@
+import '@testing-library/jest-dom';
+import 'jest-styled-components';
+import { styleSheetSerializer } from 'jest-styled-components/serializer';
+import React from 'react';
+import { getClasses, render, getComputedStyles } from './testing.js';
+
+expect.addSnapshotSerializer(styleSheetSerializer);
+
+const stringify = d => JSON.stringify(d, null, ' ');
+
+expect.extend({
+ toMatchKeys(obj, values) {
+ return {
+ pass: Object.keys(values).every(key => this.equals(obj[key], values[key])),
+ message: () => `Expected ${stringify(obj)} to have matching keys: ${stringify(values)}`
+ };
+ },
+
+ toHaveClass(node, klass) {
+ const classes = getClasses(node);
+ const pass = classes.includes(klass);
+ return {
+ pass,
+ message: () => `expected ${stringify(classes)} to include: ${stringify(klass)}`
+ };
+ },
+
+ toHaveClasses(node, klasses, only = false) {
+ const classes = getClasses(node);
+ const pass = only ? this.equals(classes.sort(), klasses.sort()) : klasses.every(klass => classes.includes(klass));
+ return {
+ pass,
+ message: () => `expected ${stringify(classes)} to include: ${stringify(klasses)}`
+ };
+ },
+
+ toImplementSxBehavior(element) {
+ const mediaKey = '@media (max-width:123px)';
+ const sxPropValue = {
+ [mediaKey]: {
+ color: 'red.5'
+ }
+ };
+ const elem = /*#__PURE__*/React.cloneElement(element, {
+ sx: sxPropValue
+ });
+
+ function checkStylesDeep(rendered) {
+ const className = rendered.props.className;
+ const styles = getComputedStyles(className);
+ const mediaStyles = styles[mediaKey];
+
+ if (mediaStyles && mediaStyles.color) {
+ return true;
+ } else if (rendered.children) {
+ return rendered.children.some(child => checkStylesDeep(child));
+ } else {
+ return false;
+ }
+ }
+
+ return {
+ pass: checkStylesDeep(render(elem)),
+ message: () => 'sx prop values did not change styles of component nor of any sub-components'
+ };
+ },
+
+ toSetExports(mod, expectedExports) {
+ if (!Object.keys(expectedExports).includes('default')) {
+ return {
+ pass: false,
+ message: () => "You must specify the module's default export"
+ };
+ }
+
+ const seen = new Set();
+
+ for (const exp of Object.keys(expectedExports)) {
+ seen.add(exp);
+
+ if (mod[exp] !== expectedExports[exp]) {
+ if (!mod[exp] && !expectedExports[exp]) {
+ continue;
+ }
+
+ return {
+ pass: false,
+ message: () => `Module exported a different value from key '${exp}' than expected`
+ };
+ }
+ }
+
+ for (const exp of Object.keys(mod)) {
+ if (seen.has(exp)) {
+ continue;
+ }
+
+ if (mod[exp] !== expectedExports[exp]) {
+ return {
+ pass: false,
+ message: () => `Module exported an unexpected value from key '${exp}'`
+ };
+ }
+ }
+
+ return {
+ pass: true,
+ message: () => ''
+ };
+ }
+
+});
diff --git a/lib-esm/utils/testing.js b/lib-esm/utils/testing.js
new file mode 100644
index 00000000000..9858cc6f477
--- /dev/null
+++ b/lib-esm/utils/testing.js
@@ -0,0 +1,250 @@
+import React from 'react';
+import { promisify } from 'util';
+import renderer from 'react-test-renderer';
+import enzyme from 'enzyme';
+import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
+import { render as render$1 } from '@testing-library/react';
+import { toHaveNoViolations, axe } from 'jest-axe';
+import theme from '../theme-preval.js';
+import ThemeProvider from '../ThemeProvider.js';
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const readFile = promisify(require('fs').readFile);
+const COMPONENT_DISPLAY_NAME_REGEX = /^[A-Z][A-Za-z]+(\.[A-Z][A-Za-z]+)*$/;
+enzyme.configure({
+ adapter: new Adapter()
+});
+function mount(component) {
+ return enzyme.mount(component);
+}
+
+/**
+ * Render the component (a React.createElement() or JSX expression)
+ * into its intermediate object representation with 'type',
+ * 'props', and 'children' keys
+ *
+ * The returned object can be matched with expect().toEqual(), e.g.
+ *
+ * ```js
+ * expect(render()).toEqual(render(
))
+ * ```
+ */
+function render(component, theme$1 = theme) {
+ return renderer.create( /*#__PURE__*/React.createElement(ThemeProvider, {
+ theme: theme$1
+ }, component)).toJSON();
+}
+/**
+ * Render the component (a React.createElement() or JSX expression)
+ * using react-test-renderer and return the root node
+ * ```
+ */
+
+function renderRoot(component) {
+ return renderer.create(component).root;
+}
+/**
+ * Get the HTML class names rendered by the component instance
+ * as an array.
+ *
+ * ```js
+ * expect(renderClasses())
+ * .toEqual(['a', 'b'])
+ * ```
+ */
+
+function renderClasses(component) {
+ const {
+ props: {
+ className
+ }
+ } = render(component);
+ return className ? className.trim().split(' ') : [];
+}
+/**
+ * Returns true if a node renders with a single class.
+ */
+
+function rendersClass(node, klass) {
+ return renderClasses(node).includes(klass);
+}
+function px(value) {
+ return typeof value === 'number' ? `${value}px` : value;
+}
+function percent(value) {
+ return typeof value === 'number' ? `${value}%` : value;
+}
+function renderStyles(node) {
+ const {
+ props: {
+ className
+ }
+ } = render(node);
+ return getComputedStyles(className);
+}
+function getComputedStyles(className) {
+ const div = document.createElement('div');
+ div.className = className;
+ const computed = {};
+
+ for (const sheet of document.styleSheets) {
+ // CSSRulesLists assumes every rule is a CSSRule, not a CSSStyleRule
+ for (const rule of sheet.cssRules) {
+ if (rule instanceof CSSMediaRule) {
+ readMedia(rule);
+ } else if (rule instanceof CSSStyleRule) {
+ readRule(rule, computed);
+ } else ;
+ }
+ }
+
+ return computed;
+
+ function matchesSafe(node, selector) {
+ if (!selector) {
+ return false;
+ }
+
+ try {
+ return node.matches(selector);
+ } catch (error) {
+ return false;
+ }
+ }
+
+ function readRule(rule, dest) {
+ if (matchesSafe(div, rule.selectorText)) {
+ const {
+ style
+ } = rule;
+
+ for (let i = 0; i < style.length; i++) {
+ const prop = style[i];
+ dest[prop] = style.getPropertyValue(prop);
+ }
+ }
+ }
+
+ function readMedia(mediaRule) {
+ const key = `@media ${mediaRule.media[0]}`; // const dest = computed[key] || (computed[key] = {})
+
+ const dest = {};
+
+ for (const rule of mediaRule.cssRules) {
+ if (rule instanceof CSSStyleRule) {
+ readRule(rule, dest);
+ }
+ } // Don't add media rule to computed styles
+ // if no styles were actually applied
+
+
+ if (Object.keys(dest).length > 0) {
+ computed[key] = dest;
+ }
+ }
+}
+/**
+ * This provides a layer of compatibility between the render() function from
+ * react-test-renderer and Enzyme's mount()
+ */
+
+function getProps(node) {
+ return typeof node.props === 'function' ? node.props() : node.props;
+}
+function getClassName(node) {
+ return getProps(node).className;
+}
+function getClasses(node) {
+ const className = getClassName(node);
+ return className ? className.trim().split(/ +/) : [];
+}
+async function loadCSS(path) {
+ const css = await readFile(require.resolve(path), 'utf8');
+ const style = document.createElement('style');
+ style.setAttribute('data-path', path);
+ style.textContent = css;
+ document.head.appendChild(style);
+ return style;
+}
+function unloadCSS(path) {
+ const style = document.querySelector(`style[data-path="${path}"]`);
+
+ if (style) {
+ style.remove();
+ return true;
+ }
+} // If a component requires certain props or other conditions in order
+// to render without errors, you can pass a `toRender` function that
+// returns an element ready to be rendered.
+
+function behavesAsComponent({
+ Component,
+ toRender,
+ options
+}) {
+ options = options || {};
+
+ const getElement = () => toRender ? toRender() : /*#__PURE__*/React.createElement(Component, null);
+
+ if (!options.skipSx) {
+ it('implements sx prop behavior', () => {
+ expect(getElement()).toImplementSxBehavior();
+ });
+ }
+
+ if (!options.skipAs) {
+ it('respects the as prop', () => {
+ const As = /*#__PURE__*/React.forwardRef((_props, ref) => /*#__PURE__*/React.createElement("div", {
+ className: "as-component",
+ ref: ref
+ }));
+ const elem = /*#__PURE__*/React.cloneElement(getElement(), {
+ as: As
+ });
+ expect(render(elem)).toEqual(render( /*#__PURE__*/React.createElement(As, null)));
+ });
+ }
+
+ it('sets a valid displayName', () => {
+ expect(Component.displayName).toMatch(COMPONENT_DISPLAY_NAME_REGEX);
+ });
+ it('renders consistently', () => {
+ expect(render(getElement())).toMatchSnapshot();
+ });
+} // eslint-disable-next-line @typescript-eslint/no-explicit-any
+
+function checkExports(path, exports) {
+ it('has declared exports', () => {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const mod = require(`../${path}`);
+
+ expect(mod).toSetExports(exports);
+ });
+}
+expect.extend(toHaveNoViolations);
+function checkStoriesForAxeViolations(name, storyDir) {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const stories = require(`${storyDir || '../stories/'}${name}.stories`); // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _meta
+
+
+ const {
+ default: _meta,
+ ...Stories
+ } = stories;
+ Object.values(Stories).map(Story => {
+ if (typeof Story !== 'function') return;
+ const {
+ storyName,
+ name: StoryFunctionName
+ } = Story;
+ it(`story ${storyName || StoryFunctionName} should have no axe violations`, async () => {
+ const {
+ container
+ } = render$1( /*#__PURE__*/React.createElement(Story, null));
+ const results = await axe(container);
+ expect(results).toHaveNoViolations();
+ });
+ });
+}
+
+export { COMPONENT_DISPLAY_NAME_REGEX, behavesAsComponent, checkExports, checkStoriesForAxeViolations, getClassName, getClasses, getComputedStyles, getProps, loadCSS, mount, percent, px, render, renderClasses, renderRoot, renderStyles, rendersClass, unloadCSS };
diff --git a/lib-esm/utils/theme.js b/lib-esm/utils/theme.js
new file mode 100644
index 00000000000..dc6f0a5227d
--- /dev/null
+++ b/lib-esm/utils/theme.js
@@ -0,0 +1 @@
+export { default } from './theme2.js';
diff --git a/lib-esm/utils/theme2.js b/lib-esm/utils/theme2.js
new file mode 100644
index 00000000000..cdbfb3b93fe
--- /dev/null
+++ b/lib-esm/utils/theme2.js
@@ -0,0 +1,72 @@
+import require$$0 from 'lodash.isempty';
+import require$$1 from 'lodash.isobject';
+import require$$2 from 'chroma-js';
+
+// Utility functions used in theme-preval.js
+// This file needs to be a JavaScript file using CommonJS to be compatible with preval
+const isEmpty = require$$0;
+
+const isObject = require$$1;
+
+const chroma = require$$2;
+
+function fontStack(fonts) {
+ return fonts.map(font => font.includes(' ') ? `"${font}"` : font).join(', ');
+} // The following functions are a temporary measure for splitting shadow values out from the colors object.
+// Eventually, we will push these structural changes upstream to primer/primitives so this data manipulation
+// will not be needed.
+
+
+function isShadowValue(value) {
+ return typeof value === 'string' && /(inset\s|)([0-9.]+(\w*)\s){1,4}(rgb[a]?\(.*\)|\w+)/.test(value);
+}
+
+function isColorValue(value) {
+ return chroma.valid(value);
+}
+
+function filterObject(obj, predicate) {
+ if (Array.isArray(obj)) {
+ return obj.filter(predicate);
+ }
+
+ return Object.entries(obj).reduce((acc, [key, value]) => {
+ if (isObject(value)) {
+ const result = filterObject(value, predicate); // Don't include empty objects or arrays
+
+ if (!isEmpty(result)) {
+ acc[key] = result;
+ }
+ } else if (predicate(value)) {
+ acc[key] = value;
+ }
+
+ return acc;
+ }, {});
+}
+
+function partitionColors(colors) {
+ return {
+ colors: filterObject(colors, value => isColorValue(value)),
+ shadows: filterObject(colors, value => isShadowValue(value))
+ };
+}
+
+function omitScale(obj) {
+ const {
+ scale,
+ ...rest
+ } = obj;
+ return rest;
+}
+
+var theme = {
+ fontStack,
+ isShadowValue,
+ isColorValue,
+ filterObject,
+ partitionColors,
+ omitScale
+};
+
+export { theme as default };
diff --git a/lib-esm/utils/useIsomorphicLayoutEffect.js b/lib-esm/utils/useIsomorphicLayoutEffect.js
index 38d69bc45c7..a7ff9c74b06 100644
--- a/lib-esm/utils/useIsomorphicLayoutEffect.js
+++ b/lib-esm/utils/useIsomorphicLayoutEffect.js
@@ -1,6 +1,5 @@
-import { useLayoutEffect as useLayoutEffect$1, useEffect } from 'react';
+import { useLayoutEffect, useEffect } from 'react';
-const useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? useLayoutEffect$1 : useEffect;
-var useLayoutEffect = useIsomorphicLayoutEffect;
+const useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? useLayoutEffect : useEffect;
-export { useLayoutEffect as default };
+export { useIsomorphicLayoutEffect as default };
diff --git a/lib/constants.js b/lib/constants.js
index d1a3ef69d82..5923bb6fe54 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -43,8 +43,12 @@ const whiteSpace = system({
});
const TYPOGRAPHY = compose(styledSystem__namespace.typography, whiteSpace);
// Border props
-compose(styledSystem__namespace.border, styledSystem__namespace.shadow);
+const BORDER = compose(styledSystem__namespace.border, styledSystem__namespace.shadow);
+// Layout props
+const LAYOUT = styledSystem__namespace.layout;
+exports.BORDER = BORDER;
exports.COMMON = COMMON;
+exports.LAYOUT = LAYOUT;
exports.TYPOGRAPHY = TYPOGRAPHY;
exports.get = get;
diff --git a/lib/hooks/index.js b/lib/hooks/index.js
new file mode 100644
index 00000000000..a3b1902b973
--- /dev/null
+++ b/lib/hooks/index.js
@@ -0,0 +1,31 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+var useOnOutsideClick = require('./useOnOutsideClick.js');
+var useProvidedRefOrCreate = require('./useProvidedRefOrCreate.js');
+var useOnEscapePress = require('./useOnEscapePress.js');
+var useOpenAndCloseFocus = require('./useOpenAndCloseFocus.js');
+var useAnchoredPosition = require('./useAnchoredPosition.js');
+var useOverlay = require('./useOverlay.js');
+var useRenderForcingRef = require('./useRenderForcingRef.js');
+var useProvidedStateOrCreate = require('./useProvidedStateOrCreate.js');
+var useMenuInitialFocus = require('./useMenuInitialFocus.js');
+var useMenuKeyboardNavigation = require('./useMenuKeyboardNavigation.js');
+var useMnemonics = require('./useMnemonics.js');
+var useRefObjectAsForwardedRef = require('./useRefObjectAsForwardedRef.js');
+
+
+
+exports.useOnOutsideClick = useOnOutsideClick.useOnOutsideClick;
+exports.useProvidedRefOrCreate = useProvidedRefOrCreate.useProvidedRefOrCreate;
+exports.useOnEscapePress = useOnEscapePress.useOnEscapePress;
+exports.useOpenAndCloseFocus = useOpenAndCloseFocus.useOpenAndCloseFocus;
+exports.useAnchoredPosition = useAnchoredPosition.useAnchoredPosition;
+exports.useOverlay = useOverlay.useOverlay;
+exports.useRenderForcingRef = useRenderForcingRef.useRenderForcingRef;
+exports.useProvidedStateOrCreate = useProvidedStateOrCreate.useProvidedStateOrCreate;
+exports.useMenuInitialFocus = useMenuInitialFocus.useMenuInitialFocus;
+exports.useMenuKeyboardNavigation = useMenuKeyboardNavigation.useMenuKeyboardNavigation;
+exports.useMnemonics = useMnemonics.useMnemonics;
+exports.useRefObjectAsForwardedRef = useRefObjectAsForwardedRef.useRefObjectAsForwardedRef;
diff --git a/lib/hooks/useControllableState.js b/lib/hooks/useControllableState.js
index b00f45fa2aa..4f93074f52f 100644
--- a/lib/hooks/useControllableState.js
+++ b/lib/hooks/useControllableState.js
@@ -51,15 +51,11 @@ function useControllableState({
const controlledValue = value !== undefined; // Uncontrolled -> Controlled
// If the component prop is uncontrolled, the prop value should be undefined
- if (controlled.current === false && controlledValue) {
- warn();
- } // Controlled -> Uncontrolled
+ if (controlled.current === false && controlledValue) ; // Controlled -> Uncontrolled
// If the component prop is controlled, the prop value should be defined
- if (controlled.current === true && !controlledValue) {
- warn();
- }
+ if (controlled.current === true && !controlledValue) ;
}, [name, value]);
if (controlled.current === true) {
@@ -68,8 +64,5 @@ function useControllableState({
return [state, setState];
}
-/** Warn when running in a development environment */
-
-const warn = function emptyFunction() {};
exports.useControllableState = useControllableState;
diff --git a/lib/hooks/useMedia.js b/lib/hooks/useMedia.js
index a8e37ee68c8..01baf635618 100644
--- a/lib/hooks/useMedia.js
+++ b/lib/hooks/useMedia.js
@@ -85,5 +85,56 @@ function useMedia(mediaQueryString, defaultState) {
// be used for development and demo purposes to emulate specific features if
// unavailable through devtools
const MatchMediaContext = /*#__PURE__*/React.createContext({});
+const defaultFeatures = {};
+/**
+ * Use `MatchMedia` to emulate media conditions by passing in feature
+ * queries to the `features` prop. If a component uses `useMedia` with the
+ * feature passed in to `MatchMedia` it will force its value to match what is
+ * provided to `MatchMedia`
+ *
+ * This should be used for development and documentation only in situations
+ * where devtools cannot emulate this feature
+ *
+ * @example
+ *
+ *
+ *
+ */
+
+function MatchMedia({
+ children,
+ features = defaultFeatures
+}) {
+ const value = useShallowObject(features);
+ return /*#__PURE__*/React__default["default"].createElement(MatchMediaContext.Provider, {
+ value: value
+ }, children);
+}
+MatchMedia.displayName = "MatchMedia";
+
+/**
+ * Utility hook to provide a stable identity for a "simple" object which
+ * contains only primitive values. This provides a `useMemo`-esque signature
+ * without dealing with shallow equality checks in the dependency array.
+ *
+ * Note (perf): this hook iterates through keys and values of the object if the
+ * shallow equality check is false each time the hook is called
+ */
+function useShallowObject(object) {
+ const [value, setValue] = React.useState(object);
+
+ if (value !== object) {
+ const match = Object.keys(object).every(key => {
+ return object[key] === value[key];
+ });
+
+ if (!match) {
+ setValue(object);
+ }
+ }
+
+ return value;
+}
+exports.MatchMedia = MatchMedia;
exports.useMedia = useMedia;
diff --git a/lib/index.js b/lib/index.js
index 800b6c98718..2feb44fe836 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -2,7 +2,6 @@
Object.defineProperty(exports, '__esModule', { value: true });
-var themePreval = require('./theme-preval.js');
var constants = require('./constants.js');
var BaseStyles = require('./BaseStyles.js');
var ThemeProvider = require('./ThemeProvider.js');
@@ -67,6 +66,7 @@ var UnderlineNav = require('./UnderlineNav.js');
var Checkbox = require('./Checkbox.js');
var Textarea = require('./Textarea.js');
var sx = require('./sx.js');
+var themePreval = require('./theme-preval.js');
var PageLayout = require('./PageLayout/PageLayout.js');
var AnchoredOverlay = require('./AnchoredOverlay/AnchoredOverlay.js');
var Autocomplete = require('./Autocomplete/Autocomplete.js');
@@ -89,7 +89,6 @@ var merge__default = /*#__PURE__*/_interopDefaultLegacy(merge);
-exports.theme = themePreval;
exports.themeGet = constants.get;
exports.BaseStyles = BaseStyles;
exports.ThemeProvider = ThemeProvider["default"];
@@ -158,6 +157,7 @@ exports.UnderlineNav = UnderlineNav;
exports.Checkbox = Checkbox;
exports.Textarea = Textarea["default"];
exports.sx = sx["default"];
+exports.theme = themePreval;
exports.PageLayout = PageLayout.PageLayout;
exports.AnchoredOverlay = AnchoredOverlay.AnchoredOverlay;
exports.Autocomplete = Autocomplete;
diff --git a/lib/polyfills/eventListenerSignal.js b/lib/polyfills/eventListenerSignal.js
new file mode 100644
index 00000000000..deb38beeeda
--- /dev/null
+++ b/lib/polyfills/eventListenerSignal.js
@@ -0,0 +1,63 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+/*
+
+This file polyfills the following: https://github.com/whatwg/dom/issues/911
+Once all targeted browsers support this DOM feature, this polyfill can be deleted.
+
+This allows users to pass an AbortSignal to a call to addEventListener as part of the
+AddEventListenerOptions object. When the signal is aborted, the event listener is
+removed.
+
+*/
+let signalSupported = false;
+
+function noop() {}
+
+try {
+ const options = Object.create({}, {
+ signal: {
+ get() {
+ signalSupported = true;
+ }
+
+ }
+ });
+ window.addEventListener('test', noop, options);
+ window.removeEventListener('test', noop, options);
+} catch (e) {
+ /* */
+}
+
+function featureSupported() {
+ return signalSupported;
+}
+
+function monkeyPatch() {
+ if (typeof window === 'undefined') {
+ return;
+ }
+
+ const originalAddEventListener = EventTarget.prototype.addEventListener;
+
+ EventTarget.prototype.addEventListener = function (name, originalCallback, optionsOrCapture) {
+ if (typeof optionsOrCapture === 'object' && 'signal' in optionsOrCapture && optionsOrCapture.signal instanceof AbortSignal) {
+ originalAddEventListener.call(optionsOrCapture.signal, 'abort', () => {
+ this.removeEventListener(name, originalCallback, optionsOrCapture);
+ });
+ }
+
+ return originalAddEventListener.call(this, name, originalCallback, optionsOrCapture);
+ };
+}
+
+function polyfill() {
+ if (!featureSupported()) {
+ monkeyPatch();
+ signalSupported = true;
+ }
+}
+
+exports.polyfill = polyfill;
diff --git a/lib/utils/create-slots.js b/lib/utils/create-slots.js
index c268eaa8fc3..4707ad614f9 100644
--- a/lib/utils/create-slots.js
+++ b/lib/utils/create-slots.js
@@ -91,6 +91,4 @@ const createSlots = slotNames => {
};
};
-var createSlots$1 = createSlots;
-
-module.exports = createSlots$1;
+module.exports = createSlots;
diff --git a/lib/utils/deprecate.js b/lib/utils/deprecate.js
new file mode 100644
index 00000000000..b1593052ddd
--- /dev/null
+++ b/lib/utils/deprecate.js
@@ -0,0 +1,60 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+require('react');
+
+const noop = () => {}; // eslint-disable-next-line import/no-mutable-exports
+
+
+let deprecate = noop;
+
+exports.useDeprecation = null;
+
+{
+ exports.useDeprecation = () => {
+ return noop;
+ };
+}
+class Deprecations {
+ static instance = null;
+
+ static get() {
+ if (!Deprecations.instance) {
+ Deprecations.instance = new Deprecations();
+ }
+
+ return Deprecations.instance;
+ }
+
+ constructor() {
+ this.deprecations = [];
+ }
+
+ static deprecate({
+ name,
+ message,
+ version
+ }) {
+ const msg = `WARNING! ${name} is deprecated and will be removed in version ${version}. ${message}`; // eslint-disable-next-line no-console
+
+ console.warn(msg);
+ this.get().deprecations.push({
+ name,
+ message,
+ version
+ });
+ }
+
+ static getDeprecations() {
+ return this.get().deprecations;
+ }
+
+ static clearDeprecations() {
+ this.get().deprecations.length = 0;
+ }
+
+}
+
+exports.Deprecations = Deprecations;
+exports.deprecate = deprecate;
diff --git a/lib/utils/polymorphic.js b/lib/utils/polymorphic.js
new file mode 100644
index 00000000000..eb109abbed0
--- /dev/null
+++ b/lib/utils/polymorphic.js
@@ -0,0 +1,2 @@
+'use strict';
+
diff --git a/lib/utils/ssr.js b/lib/utils/ssr.js
new file mode 100644
index 00000000000..ae578645293
--- /dev/null
+++ b/lib/utils/ssr.js
@@ -0,0 +1,16 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+var ssr = require('@react-aria/ssr');
+
+
+
+Object.defineProperty(exports, 'SSRProvider', {
+ enumerable: true,
+ get: function () { return ssr.SSRProvider; }
+});
+Object.defineProperty(exports, 'useSSRSafeId', {
+ enumerable: true,
+ get: function () { return ssr.useSSRSafeId; }
+});
diff --git a/lib/utils/story-helpers.js b/lib/utils/story-helpers.js
new file mode 100644
index 00000000000..75269015070
--- /dev/null
+++ b/lib/utils/story-helpers.js
@@ -0,0 +1,294 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+var React = require('react');
+var styled = require('styled-components');
+var constants = require('../constants.js');
+var themePreval = require('../theme-preval.js');
+var Box = require('../Box.js');
+var ThemeProvider = require('../ThemeProvider.js');
+var BaseStyles = require('../BaseStyles.js');
+
+function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
+
+var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
+
+// set global theme styles for each story
+const GlobalStyle = styled.createGlobalStyle(["body{background-color:", ";color:", ";}"], constants.get('colors.canvas.default'), constants.get('colors.fg.default')); // only remove padding for multi-theme view grid
+
+const GlobalStyleMultiTheme = styled.createGlobalStyle(["body{padding:0 !important;}"]);
+const withThemeProvider = (Story, context) => {
+ // used for testing ThemeProvider.stories.tsx
+ if (context.parameters.disableThemeDecorator) return /*#__PURE__*/React__default["default"].createElement(Story, context);
+ const {
+ colorScheme
+ } = context.globals;
+
+ if (colorScheme === 'all') {
+ return /*#__PURE__*/React__default["default"].createElement(Box, {
+ sx: {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(auto-fit, minmax(0, 1fr))',
+ height: '100vh'
+ }
+ }, /*#__PURE__*/React__default["default"].createElement(GlobalStyleMultiTheme, null), Object.keys(themePreval.colorSchemes).map(scheme => /*#__PURE__*/React__default["default"].createElement(ThemeProvider["default"], {
+ key: scheme,
+ colorMode: "day",
+ dayScheme: scheme
+ }, /*#__PURE__*/React__default["default"].createElement(BaseStyles, null, /*#__PURE__*/React__default["default"].createElement(Box, {
+ sx: {
+ padding: '1rem',
+ height: '100%',
+ backgroundColor: 'canvas.default',
+ color: 'fg.default'
+ }
+ }, /*#__PURE__*/React__default["default"].createElement("div", {
+ id: `html-addon-root-${scheme}`
+ }, /*#__PURE__*/React__default["default"].createElement(Story, context)))))));
+ }
+
+ return /*#__PURE__*/React__default["default"].createElement(ThemeProvider["default"], {
+ colorMode: "day",
+ dayScheme: colorScheme
+ }, /*#__PURE__*/React__default["default"].createElement(GlobalStyle, null), /*#__PURE__*/React__default["default"].createElement(BaseStyles, null, /*#__PURE__*/React__default["default"].createElement("div", {
+ id: "html-addon-root"
+ }, /*#__PURE__*/React__default["default"].createElement(Story, context))));
+};
+withThemeProvider.displayName = "withThemeProvider";
+const toolbarTypes = {
+ colorScheme: {
+ name: 'Color scheme',
+ description: 'Switch color scheme',
+ defaultValue: 'light',
+ toolbar: {
+ icon: 'photo',
+ items: [...Object.keys(themePreval.colorSchemes), 'all'],
+ showName: true
+ }
+ }
+};
+const inputWrapperArgTypes = {
+ block: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ contrast: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ disabled: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ placeholder: {
+ defaultValue: '',
+ control: {
+ type: 'text'
+ }
+ },
+ size: {
+ name: 'size (input)',
+ // TODO: remove '(input)'
+ defaultValue: 'medium',
+ options: ['small', 'medium', 'large'],
+ control: {
+ type: 'radio'
+ }
+ },
+ validationStatus: {
+ defaultValue: undefined,
+ options: ['error', 'success', 'warning', undefined],
+ control: {
+ type: 'radio'
+ }
+ }
+};
+const textInputArgTypesUnsorted = { ...inputWrapperArgTypes,
+ loading: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ loaderPosition: {
+ defaultValue: 'auto',
+ options: ['auto', 'leading', 'trailing'],
+ control: {
+ type: 'radio'
+ }
+ },
+ monospace: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ }
+}; // Alphabetize and optionally categorize the props
+
+const getTextInputArgTypes = category => Object.keys(textInputArgTypesUnsorted).sort().reduce((obj, key) => {
+ obj[key] = category ? { // have to do weird type casting so we can spread the object
+ ...textInputArgTypesUnsorted[key],
+ table: {
+ category
+ }
+ } : textInputArgTypesUnsorted[key];
+ return obj;
+}, {});
+const textInputExcludedControlKeys = ['as', 'icon', 'leadingVisual', 'sx', 'trailingVisual', 'trailingAction'];
+const textInputWithTokensArgTypes = {
+ hideTokenRemoveButtons: {
+ defaultValue: false,
+ type: 'boolean',
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ },
+ maxHeight: {
+ type: 'string',
+ defaultValue: 'none',
+ description: 'Any valid value for the CSS max-height property',
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ },
+ preventTokenWrapping: {
+ defaultValue: false,
+ type: 'boolean',
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ },
+ size: {
+ name: 'size (token size)',
+ defaultValue: 'xlarge',
+ options: ['small', 'medium', 'large', 'xlarge'],
+ control: {
+ type: 'radio'
+ },
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ },
+ visibleTokenCount: {
+ defaultValue: 999,
+ type: 'number',
+ table: {
+ category: 'TextInputWithTokens props'
+ }
+ }
+};
+const formControlArgTypes = {
+ // FormControl
+ required: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ },
+ table: {
+ category: 'FormControl'
+ }
+ },
+ disabled: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ },
+ table: {
+ category: 'FormControl'
+ }
+ },
+ // FormControl.Label
+ labelChildren: {
+ name: 'children',
+ type: 'string',
+ defaultValue: 'Label',
+ table: {
+ category: 'FormControl.Label'
+ }
+ },
+ visuallyHidden: {
+ defaultValue: false,
+ type: 'boolean',
+ table: {
+ category: 'FormControl.Label'
+ }
+ },
+ // FormControl.Caption
+ captionChildren: {
+ name: 'children',
+ type: 'string',
+ defaultValue: '',
+ table: {
+ category: 'FormControl.Caption'
+ }
+ },
+ // FormControl.Validation
+ validationChildren: {
+ name: 'children',
+ type: 'string',
+ defaultValue: '',
+ table: {
+ category: 'FormControl.Validation'
+ }
+ },
+ variant: {
+ defaultValue: 'error',
+ control: {
+ type: 'radio',
+ options: ['error', 'success', 'warning']
+ },
+ table: {
+ category: 'FormControl.Validation'
+ }
+ }
+};
+const formControlArgTypeKeys = Object.keys(formControlArgTypes);
+const formControlArgTypesWithoutValidation = formControlArgTypeKeys.reduce((acc, key) => {
+ if (formControlArgTypes[key].table.category !== 'FormControl.Validation') {
+ acc[key] = formControlArgTypes[key];
+ }
+
+ return acc;
+}, {});
+const getFormControlArgsByChildComponent = ({
+ captionChildren,
+ disabled,
+ labelChildren,
+ required,
+ validationChildren,
+ variant,
+ visuallyHidden
+}) => ({
+ parentArgs: {
+ disabled,
+ required
+ },
+ labelArgs: {
+ visuallyHidden,
+ children: labelChildren
+ },
+ captionArgs: {
+ children: captionChildren
+ },
+ validationArgs: {
+ children: validationChildren,
+ variant
+ }
+});
+
+exports.formControlArgTypes = formControlArgTypes;
+exports.formControlArgTypesWithoutValidation = formControlArgTypesWithoutValidation;
+exports.getFormControlArgsByChildComponent = getFormControlArgsByChildComponent;
+exports.getTextInputArgTypes = getTextInputArgTypes;
+exports.inputWrapperArgTypes = inputWrapperArgTypes;
+exports.textInputExcludedControlKeys = textInputExcludedControlKeys;
+exports.textInputWithTokensArgTypes = textInputWithTokensArgTypes;
+exports.toolbarTypes = toolbarTypes;
+exports.withThemeProvider = withThemeProvider;
diff --git a/lib/utils/test-deprecations.js b/lib/utils/test-deprecations.js
new file mode 100644
index 00000000000..6d9d0ee8521
--- /dev/null
+++ b/lib/utils/test-deprecations.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var semver = require('semver');
+var deprecate = require('./deprecate.js');
+
+function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
+
+var semver__default = /*#__PURE__*/_interopDefaultLegacy(semver);
+
+const ourVersion = require('../../package.json').version;
+
+beforeEach(() => {
+ deprecate.Deprecations.clearDeprecations();
+});
+afterEach(() => {
+ const deprecations = deprecate.Deprecations.getDeprecations();
+
+ for (const dep of deprecations) {
+ if (semver__default["default"].gte(ourVersion, dep.version)) {
+ throw new Error(`Found a deprecation that should be removed in ${dep.version}`);
+ }
+ }
+});
diff --git a/lib/utils/test-helpers.js b/lib/utils/test-helpers.js
new file mode 100644
index 00000000000..cfe6fb8e0df
--- /dev/null
+++ b/lib/utils/test-helpers.js
@@ -0,0 +1,15 @@
+'use strict';
+
+// JSDOM doesn't mock ResizeObserver
+global.ResizeObserver = jest.fn().mockImplementation(() => {
+ return {
+ observe: jest.fn(),
+ disconnect: jest.fn()
+ };
+});
+global.CSS = {
+ escape: jest.fn(),
+ supports: jest.fn().mockImplementation(() => {
+ return false;
+ })
+};
diff --git a/lib/utils/test-matchers.js b/lib/utils/test-matchers.js
new file mode 100644
index 00000000000..708d6f234b1
--- /dev/null
+++ b/lib/utils/test-matchers.js
@@ -0,0 +1,118 @@
+'use strict';
+
+require('@testing-library/jest-dom');
+require('jest-styled-components');
+var serializer = require('jest-styled-components/serializer');
+var React = require('react');
+var testing = require('./testing.js');
+
+function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
+
+var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
+
+expect.addSnapshotSerializer(serializer.styleSheetSerializer);
+
+const stringify = d => JSON.stringify(d, null, ' ');
+
+expect.extend({
+ toMatchKeys(obj, values) {
+ return {
+ pass: Object.keys(values).every(key => this.equals(obj[key], values[key])),
+ message: () => `Expected ${stringify(obj)} to have matching keys: ${stringify(values)}`
+ };
+ },
+
+ toHaveClass(node, klass) {
+ const classes = testing.getClasses(node);
+ const pass = classes.includes(klass);
+ return {
+ pass,
+ message: () => `expected ${stringify(classes)} to include: ${stringify(klass)}`
+ };
+ },
+
+ toHaveClasses(node, klasses, only = false) {
+ const classes = testing.getClasses(node);
+ const pass = only ? this.equals(classes.sort(), klasses.sort()) : klasses.every(klass => classes.includes(klass));
+ return {
+ pass,
+ message: () => `expected ${stringify(classes)} to include: ${stringify(klasses)}`
+ };
+ },
+
+ toImplementSxBehavior(element) {
+ const mediaKey = '@media (max-width:123px)';
+ const sxPropValue = {
+ [mediaKey]: {
+ color: 'red.5'
+ }
+ };
+ const elem = /*#__PURE__*/React__default["default"].cloneElement(element, {
+ sx: sxPropValue
+ });
+
+ function checkStylesDeep(rendered) {
+ const className = rendered.props.className;
+ const styles = testing.getComputedStyles(className);
+ const mediaStyles = styles[mediaKey];
+
+ if (mediaStyles && mediaStyles.color) {
+ return true;
+ } else if (rendered.children) {
+ return rendered.children.some(child => checkStylesDeep(child));
+ } else {
+ return false;
+ }
+ }
+
+ return {
+ pass: checkStylesDeep(testing.render(elem)),
+ message: () => 'sx prop values did not change styles of component nor of any sub-components'
+ };
+ },
+
+ toSetExports(mod, expectedExports) {
+ if (!Object.keys(expectedExports).includes('default')) {
+ return {
+ pass: false,
+ message: () => "You must specify the module's default export"
+ };
+ }
+
+ const seen = new Set();
+
+ for (const exp of Object.keys(expectedExports)) {
+ seen.add(exp);
+
+ if (mod[exp] !== expectedExports[exp]) {
+ if (!mod[exp] && !expectedExports[exp]) {
+ continue;
+ }
+
+ return {
+ pass: false,
+ message: () => `Module exported a different value from key '${exp}' than expected`
+ };
+ }
+ }
+
+ for (const exp of Object.keys(mod)) {
+ if (seen.has(exp)) {
+ continue;
+ }
+
+ if (mod[exp] !== expectedExports[exp]) {
+ return {
+ pass: false,
+ message: () => `Module exported an unexpected value from key '${exp}'`
+ };
+ }
+ }
+
+ return {
+ pass: true,
+ message: () => ''
+ };
+ }
+
+});
diff --git a/lib/utils/testing.js b/lib/utils/testing.js
new file mode 100644
index 00000000000..7037b588542
--- /dev/null
+++ b/lib/utils/testing.js
@@ -0,0 +1,278 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+var React = require('react');
+var util = require('util');
+var renderer = require('react-test-renderer');
+var enzyme = require('enzyme');
+var Adapter = require('@wojtekmaj/enzyme-adapter-react-17');
+var react = require('@testing-library/react');
+var jestAxe = require('jest-axe');
+var themePreval = require('../theme-preval.js');
+var ThemeProvider = require('../ThemeProvider.js');
+
+function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
+
+var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
+var renderer__default = /*#__PURE__*/_interopDefaultLegacy(renderer);
+var enzyme__default = /*#__PURE__*/_interopDefaultLegacy(enzyme);
+var Adapter__default = /*#__PURE__*/_interopDefaultLegacy(Adapter);
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const readFile = util.promisify(require('fs').readFile);
+const COMPONENT_DISPLAY_NAME_REGEX = /^[A-Z][A-Za-z]+(\.[A-Z][A-Za-z]+)*$/;
+enzyme__default["default"].configure({
+ adapter: new Adapter__default["default"]()
+});
+function mount(component) {
+ return enzyme__default["default"].mount(component);
+}
+
+/**
+ * Render the component (a React.createElement() or JSX expression)
+ * into its intermediate object representation with 'type',
+ * 'props', and 'children' keys
+ *
+ * The returned object can be matched with expect().toEqual(), e.g.
+ *
+ * ```js
+ * expect(render()).toEqual(render())
+ * ```
+ */
+function render(component, theme = themePreval) {
+ return renderer__default["default"].create( /*#__PURE__*/React__default["default"].createElement(ThemeProvider["default"], {
+ theme: theme
+ }, component)).toJSON();
+}
+/**
+ * Render the component (a React.createElement() or JSX expression)
+ * using react-test-renderer and return the root node
+ * ```
+ */
+
+function renderRoot(component) {
+ return renderer__default["default"].create(component).root;
+}
+/**
+ * Get the HTML class names rendered by the component instance
+ * as an array.
+ *
+ * ```js
+ * expect(renderClasses())
+ * .toEqual(['a', 'b'])
+ * ```
+ */
+
+function renderClasses(component) {
+ const {
+ props: {
+ className
+ }
+ } = render(component);
+ return className ? className.trim().split(' ') : [];
+}
+/**
+ * Returns true if a node renders with a single class.
+ */
+
+function rendersClass(node, klass) {
+ return renderClasses(node).includes(klass);
+}
+function px(value) {
+ return typeof value === 'number' ? `${value}px` : value;
+}
+function percent(value) {
+ return typeof value === 'number' ? `${value}%` : value;
+}
+function renderStyles(node) {
+ const {
+ props: {
+ className
+ }
+ } = render(node);
+ return getComputedStyles(className);
+}
+function getComputedStyles(className) {
+ const div = document.createElement('div');
+ div.className = className;
+ const computed = {};
+
+ for (const sheet of document.styleSheets) {
+ // CSSRulesLists assumes every rule is a CSSRule, not a CSSStyleRule
+ for (const rule of sheet.cssRules) {
+ if (rule instanceof CSSMediaRule) {
+ readMedia(rule);
+ } else if (rule instanceof CSSStyleRule) {
+ readRule(rule, computed);
+ } else ;
+ }
+ }
+
+ return computed;
+
+ function matchesSafe(node, selector) {
+ if (!selector) {
+ return false;
+ }
+
+ try {
+ return node.matches(selector);
+ } catch (error) {
+ return false;
+ }
+ }
+
+ function readRule(rule, dest) {
+ if (matchesSafe(div, rule.selectorText)) {
+ const {
+ style
+ } = rule;
+
+ for (let i = 0; i < style.length; i++) {
+ const prop = style[i];
+ dest[prop] = style.getPropertyValue(prop);
+ }
+ }
+ }
+
+ function readMedia(mediaRule) {
+ const key = `@media ${mediaRule.media[0]}`; // const dest = computed[key] || (computed[key] = {})
+
+ const dest = {};
+
+ for (const rule of mediaRule.cssRules) {
+ if (rule instanceof CSSStyleRule) {
+ readRule(rule, dest);
+ }
+ } // Don't add media rule to computed styles
+ // if no styles were actually applied
+
+
+ if (Object.keys(dest).length > 0) {
+ computed[key] = dest;
+ }
+ }
+}
+/**
+ * This provides a layer of compatibility between the render() function from
+ * react-test-renderer and Enzyme's mount()
+ */
+
+function getProps(node) {
+ return typeof node.props === 'function' ? node.props() : node.props;
+}
+function getClassName(node) {
+ return getProps(node).className;
+}
+function getClasses(node) {
+ const className = getClassName(node);
+ return className ? className.trim().split(/ +/) : [];
+}
+async function loadCSS(path) {
+ const css = await readFile(require.resolve(path), 'utf8');
+ const style = document.createElement('style');
+ style.setAttribute('data-path', path);
+ style.textContent = css;
+ document.head.appendChild(style);
+ return style;
+}
+function unloadCSS(path) {
+ const style = document.querySelector(`style[data-path="${path}"]`);
+
+ if (style) {
+ style.remove();
+ return true;
+ }
+} // If a component requires certain props or other conditions in order
+// to render without errors, you can pass a `toRender` function that
+// returns an element ready to be rendered.
+
+function behavesAsComponent({
+ Component,
+ toRender,
+ options
+}) {
+ options = options || {};
+
+ const getElement = () => toRender ? toRender() : /*#__PURE__*/React__default["default"].createElement(Component, null);
+
+ if (!options.skipSx) {
+ it('implements sx prop behavior', () => {
+ expect(getElement()).toImplementSxBehavior();
+ });
+ }
+
+ if (!options.skipAs) {
+ it('respects the as prop', () => {
+ const As = /*#__PURE__*/React__default["default"].forwardRef((_props, ref) => /*#__PURE__*/React__default["default"].createElement("div", {
+ className: "as-component",
+ ref: ref
+ }));
+ const elem = /*#__PURE__*/React__default["default"].cloneElement(getElement(), {
+ as: As
+ });
+ expect(render(elem)).toEqual(render( /*#__PURE__*/React__default["default"].createElement(As, null)));
+ });
+ }
+
+ it('sets a valid displayName', () => {
+ expect(Component.displayName).toMatch(COMPONENT_DISPLAY_NAME_REGEX);
+ });
+ it('renders consistently', () => {
+ expect(render(getElement())).toMatchSnapshot();
+ });
+} // eslint-disable-next-line @typescript-eslint/no-explicit-any
+
+function checkExports(path, exports) {
+ it('has declared exports', () => {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const mod = require(`../${path}`);
+
+ expect(mod).toSetExports(exports);
+ });
+}
+expect.extend(jestAxe.toHaveNoViolations);
+function checkStoriesForAxeViolations(name, storyDir) {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const stories = require(`${storyDir || '../stories/'}${name}.stories`); // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _meta
+
+
+ const {
+ default: _meta,
+ ...Stories
+ } = stories;
+ Object.values(Stories).map(Story => {
+ if (typeof Story !== 'function') return;
+ const {
+ storyName,
+ name: StoryFunctionName
+ } = Story;
+ it(`story ${storyName || StoryFunctionName} should have no axe violations`, async () => {
+ const {
+ container
+ } = react.render( /*#__PURE__*/React__default["default"].createElement(Story, null));
+ const results = await jestAxe.axe(container);
+ expect(results).toHaveNoViolations();
+ });
+ });
+}
+
+exports.COMPONENT_DISPLAY_NAME_REGEX = COMPONENT_DISPLAY_NAME_REGEX;
+exports.behavesAsComponent = behavesAsComponent;
+exports.checkExports = checkExports;
+exports.checkStoriesForAxeViolations = checkStoriesForAxeViolations;
+exports.getClassName = getClassName;
+exports.getClasses = getClasses;
+exports.getComputedStyles = getComputedStyles;
+exports.getProps = getProps;
+exports.loadCSS = loadCSS;
+exports.mount = mount;
+exports.percent = percent;
+exports.px = px;
+exports.render = render;
+exports.renderClasses = renderClasses;
+exports.renderRoot = renderRoot;
+exports.renderStyles = renderStyles;
+exports.rendersClass = rendersClass;
+exports.unloadCSS = unloadCSS;
diff --git a/lib/utils/theme.js b/lib/utils/theme.js
new file mode 100644
index 00000000000..18c058557db
--- /dev/null
+++ b/lib/utils/theme.js
@@ -0,0 +1,7 @@
+'use strict';
+
+var theme = require('./theme2.js');
+
+
+
+module.exports = theme;
diff --git a/lib/utils/theme2.js b/lib/utils/theme2.js
new file mode 100644
index 00000000000..7dce30713fe
--- /dev/null
+++ b/lib/utils/theme2.js
@@ -0,0 +1,80 @@
+'use strict';
+
+var require$$0 = require('lodash.isempty');
+var require$$1 = require('lodash.isobject');
+var require$$2 = require('chroma-js');
+
+function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
+
+var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
+var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
+var require$$2__default = /*#__PURE__*/_interopDefaultLegacy(require$$2);
+
+// Utility functions used in theme-preval.js
+// This file needs to be a JavaScript file using CommonJS to be compatible with preval
+const isEmpty = require$$0__default["default"];
+
+const isObject = require$$1__default["default"];
+
+const chroma = require$$2__default["default"];
+
+function fontStack(fonts) {
+ return fonts.map(font => font.includes(' ') ? `"${font}"` : font).join(', ');
+} // The following functions are a temporary measure for splitting shadow values out from the colors object.
+// Eventually, we will push these structural changes upstream to primer/primitives so this data manipulation
+// will not be needed.
+
+
+function isShadowValue(value) {
+ return typeof value === 'string' && /(inset\s|)([0-9.]+(\w*)\s){1,4}(rgb[a]?\(.*\)|\w+)/.test(value);
+}
+
+function isColorValue(value) {
+ return chroma.valid(value);
+}
+
+function filterObject(obj, predicate) {
+ if (Array.isArray(obj)) {
+ return obj.filter(predicate);
+ }
+
+ return Object.entries(obj).reduce((acc, [key, value]) => {
+ if (isObject(value)) {
+ const result = filterObject(value, predicate); // Don't include empty objects or arrays
+
+ if (!isEmpty(result)) {
+ acc[key] = result;
+ }
+ } else if (predicate(value)) {
+ acc[key] = value;
+ }
+
+ return acc;
+ }, {});
+}
+
+function partitionColors(colors) {
+ return {
+ colors: filterObject(colors, value => isColorValue(value)),
+ shadows: filterObject(colors, value => isShadowValue(value))
+ };
+}
+
+function omitScale(obj) {
+ const {
+ scale,
+ ...rest
+ } = obj;
+ return rest;
+}
+
+var theme = {
+ fontStack,
+ isShadowValue,
+ isColorValue,
+ filterObject,
+ partitionColors,
+ omitScale
+};
+
+module.exports = theme;
diff --git a/lib/utils/useIsomorphicLayoutEffect.js b/lib/utils/useIsomorphicLayoutEffect.js
index 86585b2feac..8e96cc95fd2 100644
--- a/lib/utils/useIsomorphicLayoutEffect.js
+++ b/lib/utils/useIsomorphicLayoutEffect.js
@@ -3,6 +3,5 @@
var React = require('react');
const useIsomorphicLayoutEffect = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined' ? React.useLayoutEffect : React.useEffect;
-var useLayoutEffect = useIsomorphicLayoutEffect;
-module.exports = useLayoutEffect;
+module.exports = useIsomorphicLayoutEffect;
diff --git a/package.json b/package.json
index 10266634e76..09d690a7371 100644
--- a/package.json
+++ b/package.json
@@ -26,9 +26,7 @@
"./lib/*.js",
"./lib/*/index.js"
]
- },
- "./lib-esm/utils/test-helpers": "./lib-esm/utils/test-helpers.js",
- "./lib/utils/test-helpers": "./lib/utils/test-helpers.js"
+ }
},
"typings": "lib/index.d.ts",
"sideEffects": false,
diff --git a/rollup.config.js b/rollup.config.js
index f65d4e9d1da..5de47528673 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -17,17 +17,25 @@ const input = new Set([
// "./deprecated"
'src/deprecated/index.ts',
- // "./lib-esm/*"
- ...glob.sync(['src/*', 'src/*/index.js'], {
- cwd: __dirname,
- onlyFiles: true,
- // Note: ignore theme-preval as it is handled through the theme import and
- // specifying it as an entrypoint creates an intermediate file
- ignore: ['src/theme-preval.js']
- }),
+ // Make sure all members are exported
+ 'src/constants.ts',
- // "./lib-esm/utils/test-helpers", "./lib/utils/test-helpers"
- 'src/utils/test-helpers.tsx'
+ ...glob.sync(
+ [
+ // "./lib-esm/hooks/*"
+ 'src/hooks/*',
+
+ // "./lib-esm/polyfills/*"
+ 'src/polyfills/*',
+
+ // "./lib-esm/utils/*"
+ 'src/utils/*'
+ ],
+ {
+ cwd: __dirname,
+ ignore: ['**/__tests__/**', '*.stories.tsx']
+ }
+ )
])
const extensions = ['.js', '.jsx', '.ts', '.tsx']