@mackin.com/styleguide 11.0.3 → 11.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.d.ts +60 -20
  2. package/index.esm.js +193 -52
  3. package/index.js +194 -51
  4. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import * as React from 'react';
2
3
  import React__default, { ReactNode } from 'react';
3
4
  import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
@@ -280,31 +281,34 @@ declare const InfoPanel: (props: {
280
281
  tag?: InfoPanelTag;
281
282
  }) => React.JSX.Element;
282
283
 
284
+ type InfoTipDefaultProps = {
285
+ type: 'Info';
286
+ };
287
+ type InfoTipModalProps = {
288
+ type: 'Modal';
289
+ heading: string;
290
+ __debug?: boolean;
291
+ };
283
292
  interface InfoTipProps {
293
+ /** Required for ARIA wireup. */
294
+ id: string;
295
+ variant: InfoTipDefaultProps | InfoTipModalProps;
284
296
  content?: string | JSX.Element;
285
297
  disabled?: boolean;
286
298
  onClick?: () => void;
287
299
  tabIndex?: number;
288
300
  loadOnHover?: () => Promise<void>;
289
301
  onClose?: () => void;
290
- /** Defaults to 'info'. */
291
- variant?: 'info' | 'modal';
292
302
  /** Defaults to nav color. */
293
303
  bgColor?: string;
294
304
  /** Defaults to nav font color. */
295
305
  fontColor?: string;
296
- /** For variant=modal only. The InfoTip will not render if `variant=modal` and this is missing. */
297
- modalProps?: {
298
- heading: string;
299
- id: string;
300
- __debug?: boolean;
301
- };
302
306
  /** Whether to move the popover on collision with the parent's bounds. Default is false. For variant=info only. */
303
307
  reposition?: boolean;
304
308
  /** Order of positions as the Popover colides with the window edge. The default order is ['right', 'top', 'left', 'bottom']. For variant=info only. */
305
309
  positions?: ("bottom" | "left" | "right" | "top")[] | undefined;
306
310
  }
307
- declare const InfoTip: (props: InfoTipProps) => React.JSX.Element | null;
311
+ declare const InfoTip: (props: InfoTipProps) => React.JSX.Element;
308
312
 
309
313
  interface BaseInputProps extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
310
314
  rightControl?: JSX.Element;
@@ -413,14 +417,16 @@ interface ModalProps {
413
417
  minWidth?: string;
414
418
  /** Use to override default heading styling. */
415
419
  headerClassName?: string;
416
- /** Selector of the element to focus on initial show. Will default to the close button. */
417
- focusSelector?: string;
420
+ /** The DOM ID of the element to focus on initial show. Will default to the close button. */
421
+ focusContentId?: string;
418
422
  scrollable?: boolean;
419
423
  /** Applied to the modal body. */
420
424
  className?: string;
425
+ /** Applied to the close button. */
426
+ closeButtonClassName?: string;
421
427
  __debug?: boolean;
422
428
  /** Only used for the `WaitingIndicator`. This will break accessiblity so do not use. */
423
- __noHeader?: boolean;
429
+ __asWaitingIndicator?: boolean;
424
430
  onClose: () => void;
425
431
  }
426
432
  declare const Modal: (p: ModalProps) => React__default.ReactPortal | null;
@@ -428,7 +434,7 @@ declare const Modal: (p: ModalProps) => React__default.ReactPortal | null;
428
434
  declare const Nav: (props: {
429
435
  show: boolean;
430
436
  /** Required for ARIA. The element to focus when first opened. */
431
- focusSelector: string;
437
+ focusContentId: string;
432
438
  /** Required for ARIA. Description of the `Nav` purpose. */
433
439
  ariaLabel: string;
434
440
  toggle: (show: boolean) => void;
@@ -654,16 +660,18 @@ interface PickerProps<T extends PickerValue> extends SelectProps {
654
660
  }
655
661
  declare const Picker: <T extends PickerValue>(props: PickerProps<T>) => React.JSX.Element;
656
662
 
657
- interface PopoverProps {
663
+ type PopoverPosition = "bottom" | "left" | "right" | "top";
664
+
665
+ type BasePopoverProps = {
658
666
  isOpen: boolean;
659
667
  /** The content inside the popover. */
660
668
  content: JSX.Element;
661
669
  /** The element controlling the state of the popover. */
662
670
  parent: JSX.Element;
663
- id?: string;
671
+ /** Will be called when the user pressed the `Escape` key or clicks outside of the popover. */
672
+ onClose: () => void;
664
673
  /** Whether to move the popover on collision with the parent's bounds. Default is true. */
665
674
  reposition?: boolean;
666
- onClickOutside?: (e: MouseEvent) => void;
667
675
  /** Popover arrow color. Defaults to theme.colors.border. */
668
676
  arrorColor?: string;
669
677
  /** Popover border. Defaults to theme.controls.border. */
@@ -671,11 +679,32 @@ interface PopoverProps {
671
679
  /** Popover background. Defaults to theme.colors.bg. */
672
680
  backgroundColor?: string;
673
681
  /** Order of positions as the Popover colides with the window edge. The default order is ['right', 'top', 'left', 'bottom']. */
674
- positions?: ("bottom" | "left" | "right" | "top")[] | undefined;
682
+ positions?: PopoverPosition[] | undefined;
675
683
  /** Targets the wrapper around `parent`. Use if you're having positioning issues when the tooltip is showing. */
676
684
  parentWrapperClassName?: string;
677
- }
678
- declare const Popover: (p: PopoverProps) => React.JSX.Element;
685
+ };
686
+
687
+ type DialogPopoverProps = BasePopoverProps & {
688
+ /** The DOM ID of the parent element to return focus to after the popup closes. */
689
+ focusParentId: string;
690
+ /** The DOM ID of the element to focus when the popover opens. If there are no focusable elements, you'll need to
691
+ * add `tabindex="-1"` to something in `content` to comply with ARIA guidelines for dialogs.
692
+ */
693
+ focusContentId: string;
694
+ /** For screen readers to read on show. */
695
+ ariaLabel: string;
696
+ /** DO NOT USE. Here only for legacy component support. */
697
+ __asDateInputCalendar?: boolean;
698
+ };
699
+ /** Use this popover for modal dialogs that stay up until dismissed by the user. Never use this for informational tooltips - use TooltipPopover for that instead. */
700
+ declare const DialogPopover: (p: DialogPopoverProps) => React.JSX.Element;
701
+
702
+ type TooltipPopoverProps = BasePopoverProps & {
703
+ /** Required for ARIA linking between elements. */
704
+ id: string;
705
+ };
706
+ /** Use this popover for tooltips that are shown and dimissed on hover. Never use this for modal tooltips - use DialogPopover for that instead. */
707
+ declare const TooltipPopover: (p: TooltipPopoverProps) => React.JSX.Element;
679
708
 
680
709
  interface ProgressBarProps {
681
710
  /** The value in whole pct - 55 NOT 0.55. */
@@ -1195,4 +1224,15 @@ declare const LocalizationProvider: (p: {
1195
1224
  __debug?: boolean;
1196
1225
  }) => React__default.JSX.Element;
1197
1226
 
1198
- export { Accordian, type AccordianProps, type Alignment, Autocomplete, AutocompleteController, AutocompleteEntityController, type AutocompleteProps, Backdrop$1 as Backdrop, Backdrop as Backdrop2, BoundMemoryPager, BoundStaticPager, Button, type ButtonProps, Calendar, type CalendarProps, Checkbox, type CheckboxProps, ConfirmModal, type ConfirmModalProps, CopyButton, DateInput, type DateInputProps, Divider, ErrorModal, FileUploader, Form, FormColumnRow, FormFlexRow, type FormProps, GlobalStyles, Header, Highlight, ICONS, Icon, Image, type ImageProps, InfoPanel, InfoTip, type InfoTipProps, ItemPager, Label, type LabelProps, Link, type LinkProps, List, ListItem, type ListItemProps, type ListProps, LocalizationProvider, type MackinTheme, Modal, type ModalProps, Nav, NormalizeCss, NumberInput, type NumberInputProps, OmniLink, type OmniLinkProps, PagedResult, type PagedResultDto, Pager, type PagerProps, Picker, type PickerOption, type PickerProps, type PickerValue, Popover, ProgressBar, type ProgressBarProps, SearchBox, type SearchBoxProps, Slider, type SliderProps, type SliderValue, StyleGuideLanguage, TabContainer, type TabContainerProps, TabHeader, type TabHeaderProps, TabLocker, Table, Td, TdCurrency, TdNumber, Text, TextArea, type TextAreaProps, TextInput, type TextInputProps, type TextProps, Th, ThSort, ThemeProvider, ThemeRenderer, ToggleButton, ToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps, TogglePasswordInput, Tr, WaitingIndicator, calcDynamicThemeProps, defaultTheme, enumToEntities, getCurrencyDisplay, getFileSizeDisplay, modalScrollFixClassName, useAccordianState, useBooleanChanged, useIgnoreMount, useMediaQuery, useScrollbarSize, useThemeSafely, useWaiting };
1227
+ /** Allows for status notificaiton methods for screen readers.
1228
+ * This hook does not have any dependencies, so it can be used in projects that don't important anything else from the style_guide. */
1229
+ declare function useAriaLiveRegion(): {
1230
+ /**
1231
+ * @param message - The text to be read by the screen reader.
1232
+ * @param clearTimeoutMs - Milliseconds to wait before the message is cleared. Defaults to `2000`..
1233
+ */
1234
+ notify: (message: string, clearTimoutMs?: number) => void;
1235
+ clearNotification: () => void;
1236
+ };
1237
+
1238
+ export { Accordian, type AccordianProps, type Alignment, Autocomplete, AutocompleteController, AutocompleteEntityController, type AutocompleteProps, Backdrop$1 as Backdrop, Backdrop as Backdrop2, BoundMemoryPager, BoundStaticPager, Button, type ButtonProps, Calendar, type CalendarProps, Checkbox, type CheckboxProps, ConfirmModal, type ConfirmModalProps, CopyButton, DateInput, type DateInputProps, DialogPopover, Divider, ErrorModal, FileUploader, Form, FormColumnRow, FormFlexRow, type FormProps, GlobalStyles, Header, Highlight, ICONS, Icon, Image, type ImageProps, InfoPanel, InfoTip, type InfoTipProps, ItemPager, Label, type LabelProps, Link, type LinkProps, List, ListItem, type ListItemProps, type ListProps, LocalizationProvider, type MackinTheme, Modal, type ModalProps, Nav, NormalizeCss, NumberInput, type NumberInputProps, OmniLink, type OmniLinkProps, PagedResult, type PagedResultDto, Pager, type PagerProps, Picker, type PickerOption, type PickerProps, type PickerValue, ProgressBar, type ProgressBarProps, SearchBox, type SearchBoxProps, Slider, type SliderProps, type SliderValue, StyleGuideLanguage, TabContainer, type TabContainerProps, TabHeader, type TabHeaderProps, TabLocker, Table, Td, TdCurrency, TdNumber, Text, TextArea, type TextAreaProps, TextInput, type TextInputProps, type TextProps, Th, ThSort, ThemeProvider, ThemeRenderer, ToggleButton, ToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps, TogglePasswordInput, TooltipPopover, Tr, WaitingIndicator, calcDynamicThemeProps, defaultTheme, enumToEntities, getCurrencyDisplay, getFileSizeDisplay, modalScrollFixClassName, useAccordianState, useAriaLiveRegion, useBooleanChanged, useIgnoreMount, useMediaQuery, useScrollbarSize, useThemeSafely, useWaiting };
package/index.esm.js CHANGED
@@ -8,7 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8
8
  import { uniqueId, sumBy, orderBy, get } from 'lodash';
9
9
  import { format, getDaysInMonth, getDay, isSameMonth, isBefore, isAfter, isSameDay, endOfYear, addYears, endOfMonth, addMonths, startOfMonth, startOfYear, isExists } from 'date-fns';
10
10
  import { createPortal } from 'react-dom';
11
- import { Popover as Popover$1, ArrowContainer } from 'react-tiny-popover';
11
+ import { Popover, ArrowContainer } from 'react-tiny-popover';
12
12
  import { Link as Link$1 } from 'react-router-dom';
13
13
  import ReactSlider from 'react-slider';
14
14
 
@@ -1608,6 +1608,18 @@ const Checkbox = (props) => {
1608
1608
  props.children)));
1609
1609
  };
1610
1610
 
1611
+ function createBodyEscapeHandler(onEscape) {
1612
+ const handler = (e) => {
1613
+ if (e.code === 'Escape') {
1614
+ onEscape();
1615
+ }
1616
+ };
1617
+ document.addEventListener('keydown', handler);
1618
+ return () => {
1619
+ document.removeEventListener('keydown', handler);
1620
+ };
1621
+ }
1622
+
1611
1623
  /** useEffect but it will only fire when the actual truthiness of the value changes.
1612
1624
  * Use for comparing previous states to next states without all the bullshit around useEffect and component mounting.
1613
1625
  */
@@ -1663,12 +1675,13 @@ const Modal = (p) => {
1663
1675
  const backdrop = useBackdropContext();
1664
1676
  const mouseDownElement = useRef(undefined);
1665
1677
  const theme = useThemeSafely();
1666
- const hasHeader = !p.__noHeader;
1678
+ const hasHeader = !p.__asWaitingIndicator;
1667
1679
  const contentRef = React__default.useRef(null);
1668
1680
  const log = useLogger((_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1669
1681
  const showing = useRef(p.show);
1670
1682
  const bodyStyles = useRef('');
1671
1683
  const fixedElementStyles = useRef('');
1684
+ const closeButtonId = `${p.id}_closeButton`;
1672
1685
  const addScrollStyles = () => {
1673
1686
  var _a, _b, _c, _d;
1674
1687
  if (!bodyStyles.current) {
@@ -1748,13 +1761,12 @@ const Modal = (p) => {
1748
1761
  React__default.useLayoutEffect(() => {
1749
1762
  var _a;
1750
1763
  if (p.show === true) {
1751
- const focusSelector = (_a = p.focusSelector) !== null && _a !== void 0 ? _a : '.modalCloseButton';
1764
+ const focusId = (_a = p.focusContentId) !== null && _a !== void 0 ? _a : closeButtonId;
1752
1765
  // still need to wait for the next tick so the children are all rendered.
1753
1766
  setTimeout(() => {
1754
- var _a;
1755
- const element = (_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(focusSelector);
1767
+ const element = document.getElementById(focusId);
1756
1768
  element === null || element === void 0 ? void 0 : element.focus();
1757
- log('set focus', focusSelector);
1769
+ log('set focus', focusId);
1758
1770
  });
1759
1771
  }
1760
1772
  }, [p.show]);
@@ -1803,7 +1815,7 @@ const Modal = (p) => {
1803
1815
  if (p.show) {
1804
1816
  const backdropContainer = document.getElementById(backdrop.portalId);
1805
1817
  if (backdropContainer) {
1806
- return createPortal((React__default.createElement("div", { id: p.id, role: "dialog", "aria-modal": "true", "aria-labelledby": ariaLabelId, onClick: e => {
1818
+ return createPortal((React__default.createElement("div", { id: p.id, "aria-hidden": p.__asWaitingIndicator, role: "dialog", "aria-modal": "true", "aria-labelledby": ariaLabelId, onClick: e => {
1807
1819
  e.stopPropagation();
1808
1820
  if (!mouseDownElement.current) {
1809
1821
  log('backdropContainer onClick');
@@ -1834,11 +1846,11 @@ const Modal = (p) => {
1834
1846
  margin: 0,
1835
1847
  flexGrow: 1
1836
1848
  }), tag: "h1", bold: true }, p.heading),
1837
- React__default.createElement(Button, { className: cx('modalCloseButton', css({
1849
+ React__default.createElement(Button, { id: closeButtonId, className: cx('modalCloseButton', css({
1838
1850
  color: theme.colors.headerFont,
1839
1851
  marginLeft: '1rem',
1840
1852
  backgroundColor: 'transparent'
1841
- })), "aria-label": closeText, variant: "icon", onClick: e => {
1853
+ }), p.closeButtonClassName), "aria-label": closeText, variant: "icon", onClick: e => {
1842
1854
  e.stopPropagation();
1843
1855
  p.onClose();
1844
1856
  } },
@@ -1848,17 +1860,6 @@ const Modal = (p) => {
1848
1860
  }
1849
1861
  return null;
1850
1862
  };
1851
- function createBodyEscapeHandler(onEscape) {
1852
- const handler = (e) => {
1853
- if (e.code === 'Escape') {
1854
- onEscape();
1855
- }
1856
- };
1857
- document.addEventListener('keydown', handler);
1858
- return () => {
1859
- document.removeEventListener('keydown', handler);
1860
- };
1861
- }
1862
1863
 
1863
1864
  const ConfirmModal = (props) => {
1864
1865
  const theme = useThemeSafely();
@@ -2388,34 +2389,60 @@ const Image = React.forwardRef((p, ref) => {
2388
2389
  }), p.className) })));
2389
2390
  });
2390
2391
 
2391
- const Popover = (p) => {
2392
- var _a, _b;
2392
+ function getPopoverContainerClass(theme) {
2393
+ return css({
2394
+ label: 'PopoverContainer',
2395
+ zIndex: theme.zIndexes.tooltip
2396
+ });
2397
+ }
2398
+ function getPopoverContentWrapperClass(theme, props) {
2399
+ var _a, _b, _c;
2400
+ return css({
2401
+ label: 'PopoverContentWrapper',
2402
+ border: (_a = props.border) !== null && _a !== void 0 ? _a : theme.controls.border,
2403
+ borderRadius: (_b = props.border) !== null && _b !== void 0 ? _b : theme.controls.borderRadius,
2404
+ boxShadow: theme.controls.boxShadow,
2405
+ backgroundColor: (_c = props.backgroundColor) !== null && _c !== void 0 ? _c : theme.colors.bg,
2406
+ });
2407
+ }
2408
+ function getPopoverArrowColor(theme, props) {
2409
+ var _a;
2410
+ return (_a = props.arrorColor) !== null && _a !== void 0 ? _a : theme.colors.border;
2411
+ }
2412
+ function getPopoverPositions(props) {
2413
+ var _a;
2414
+ return (_a = props.positions) !== null && _a !== void 0 ? _a : ['right', 'top', 'left', 'bottom'];
2415
+ }
2416
+ const popoverArrowSize = 10;
2417
+
2418
+ /** Use this popover for tooltips that are shown and dimissed on hover. Never use this for modal tooltips - use DialogPopover for that instead. */
2419
+ const TooltipPopover = (p) => {
2420
+ var _a;
2393
2421
  const theme = useThemeSafely();
2394
2422
  const resposition = (_a = p.reposition) !== null && _a !== void 0 ? _a : true;
2395
- return (React.createElement(Popover$1, { containerClassName: css({
2396
- zIndex: theme.zIndexes.tooltip
2397
- }), reposition: resposition, isOpen: p.isOpen, positions: (_b = p.positions) !== null && _b !== void 0 ? _b : ['right', 'top', 'left', 'bottom'], onClickOutside: p.onClickOutside, content: ({ position, childRect, popoverRect }) => {
2398
- var _a, _b, _c, _d;
2399
- return (React.createElement(ArrowContainer, { position: position, childRect: childRect, popoverRect: popoverRect, arrowColor: (_a = p.arrorColor) !== null && _a !== void 0 ? _a : theme.colors.border, arrowSize: 10 },
2400
- React.createElement(TabLocker, null,
2401
- React.createElement("div", { className: css({
2402
- border: (_b = p.border) !== null && _b !== void 0 ? _b : theme.controls.border,
2403
- borderRadius: (_c = p.border) !== null && _c !== void 0 ? _c : theme.controls.borderRadius,
2404
- boxShadow: theme.controls.boxShadow,
2405
- backgroundColor: (_d = p.backgroundColor) !== null && _d !== void 0 ? _d : theme.colors.bg,
2406
- }) }, p.content))));
2407
- } },
2408
- React.createElement("span", { className: p.parentWrapperClassName }, p.parent)));
2423
+ React.useEffect(() => {
2424
+ const escapeRemover = createBodyEscapeHandler(() => {
2425
+ if (p.isOpen) {
2426
+ p.onClose();
2427
+ }
2428
+ });
2429
+ return escapeRemover;
2430
+ }, [p.isOpen]);
2431
+ return (React.createElement(Popover, { containerClassName: getPopoverContainerClass(theme), reposition: resposition, isOpen: p.isOpen, positions: getPopoverPositions(p), onClickOutside: p.onClose, content: ({ position, childRect, popoverRect }) => (React.createElement(ArrowContainer, { position: position, childRect: childRect, popoverRect: popoverRect, arrowColor: getPopoverArrowColor(theme, p), arrowSize: popoverArrowSize },
2432
+ React.createElement("div", { id: p.id, onKeyDown: e => {
2433
+ if (e.code === 'Escape') {
2434
+ e.stopPropagation();
2435
+ e.preventDefault();
2436
+ p.onClose();
2437
+ }
2438
+ }, role: 'tooltip', className: getPopoverContentWrapperClass(theme, p) }, p.content))) },
2439
+ React.createElement("span", { "aria-describedby": p.id, className: p.parentWrapperClassName }, p.parent)));
2409
2440
  };
2410
2441
 
2411
2442
  const InfoTip = (props) => {
2412
2443
  var _a, _b, _c;
2413
2444
  const [showTip, setShowTip] = React.useState(false);
2414
2445
  const theme = useThemeSafely();
2415
- if (props.variant === 'modal' && !props.modalProps) {
2416
- console.warn(`InfoTip with variant=modal requires modalProps.`);
2417
- return null;
2418
- }
2419
2446
  const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
2420
2447
  const fontColor = (_b = props.fontColor) !== null && _b !== void 0 ? _b : theme.colors.navFont;
2421
2448
  const onClick = () => {
@@ -2427,7 +2454,7 @@ const InfoTip = (props) => {
2427
2454
  }
2428
2455
  };
2429
2456
  const onMouseOver = () => {
2430
- if (props.variant === 'modal') {
2457
+ if (props.variant.type === 'Modal') {
2431
2458
  return;
2432
2459
  }
2433
2460
  if (props.loadOnHover) {
@@ -2442,7 +2469,7 @@ const InfoTip = (props) => {
2442
2469
  }
2443
2470
  };
2444
2471
  const onMouseOut = () => {
2445
- if (props.variant === 'modal') {
2472
+ if (props.variant.type === 'Modal') {
2446
2473
  return;
2447
2474
  }
2448
2475
  closeTip();
@@ -2471,17 +2498,22 @@ const InfoTip = (props) => {
2471
2498
  font-family: serif;
2472
2499
  display:inline-block;
2473
2500
  `;
2474
- const button = React.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut }, "i");
2475
- if (props.variant === 'modal' && props.modalProps) {
2501
+ let onButtonBlur = props.variant.type === 'Info' ?
2502
+ () => {
2503
+ closeTip();
2504
+ } :
2505
+ undefined;
2506
+ const button = (React.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut, onBlur: onButtonBlur }, "i"));
2507
+ if (props.variant.type === 'Modal') {
2476
2508
  return (React.createElement(React.Fragment, null,
2477
2509
  button,
2478
- React.createElement(Modal, { id: props.modalProps.id, __debug: props.modalProps.__debug, show: showTip, heading: props.modalProps.heading, onClose: closeTip, className: css({
2510
+ React.createElement(Modal, { id: props.id, __debug: props.variant.__debug, show: showTip, heading: props.variant.heading, onClose: closeTip, className: css({
2479
2511
  whiteSpace: 'normal'
2480
2512
  }) },
2481
2513
  React.createElement("div", { className: css({ padding: '1rem' }) }, props.content))));
2482
2514
  }
2483
2515
  else {
2484
- return (React.createElement(Popover, { positions: props.positions, reposition: (_c = props.reposition) !== null && _c !== void 0 ? _c : false, isOpen: showTip, onClickOutside: closeTip, arrorColor: bgColor, border: '', backgroundColor: bgColor, parent: button, content: (React.createElement("div", { className: css({
2516
+ return (React.createElement(TooltipPopover, { id: props.id, positions: props.positions, reposition: (_c = props.reposition) !== null && _c !== void 0 ? _c : false, isOpen: showTip, onClose: closeTip, arrorColor: bgColor, border: '', backgroundColor: bgColor, parent: button, content: (React.createElement("div", { className: css({
2485
2517
  padding: '0.5rem',
2486
2518
  fontSize: '0.75rem',
2487
2519
  maxWidth: '22rem'
@@ -2489,6 +2521,45 @@ const InfoTip = (props) => {
2489
2521
  }
2490
2522
  };
2491
2523
 
2524
+ /** Use this popover for modal dialogs that stay up until dismissed by the user. Never use this for informational tooltips - use TooltipPopover for that instead. */
2525
+ const DialogPopover = (p) => {
2526
+ var _a;
2527
+ const theme = useThemeSafely();
2528
+ const resposition = (_a = p.reposition) !== null && _a !== void 0 ? _a : true;
2529
+ React.useEffect(() => {
2530
+ if (!p.__asDateInputCalendar) {
2531
+ if (p.isOpen) {
2532
+ const content = document.getElementById(p.focusContentId);
2533
+ if (content) {
2534
+ content.focus();
2535
+ }
2536
+ }
2537
+ else {
2538
+ const parent = document.getElementById(p.focusParentId);
2539
+ if (parent) {
2540
+ parent.focus();
2541
+ }
2542
+ }
2543
+ }
2544
+ const escapeRemover = createBodyEscapeHandler(() => {
2545
+ if (p.isOpen) {
2546
+ p.onClose();
2547
+ }
2548
+ });
2549
+ return escapeRemover;
2550
+ }, [p.isOpen]);
2551
+ return (React.createElement(Popover, { containerClassName: getPopoverContainerClass(theme), reposition: resposition, isOpen: p.isOpen, positions: getPopoverPositions(p), onClickOutside: p.onClose, content: ({ position, childRect, popoverRect }) => (React.createElement(ArrowContainer, { position: position, childRect: childRect, popoverRect: popoverRect, arrowColor: getPopoverArrowColor(theme, p), arrowSize: popoverArrowSize },
2552
+ React.createElement(TabLocker, null,
2553
+ React.createElement("div", { onKeyDown: e => {
2554
+ if (e.code === 'Escape') {
2555
+ e.stopPropagation();
2556
+ e.preventDefault();
2557
+ p.onClose();
2558
+ }
2559
+ }, role: 'dialog', "aria-modal": true, "aria-label": p.ariaLabel, className: getPopoverContentWrapperClass(theme, p) }, p.content)))) },
2560
+ React.createElement("span", { className: p.parentWrapperClassName }, p.parent)));
2561
+ };
2562
+
2492
2563
  const dateRegex = /(\d{1,2})(?:\/|-)(\d{1,2})(?:\/|-)(\d{4})/;
2493
2564
  const datePattern = dateRegex.source;
2494
2565
  const invalidDateMessage = 'Invalid date.';
@@ -2618,9 +2689,16 @@ const DateInput = React.forwardRef((props, ref) => {
2618
2689
  }
2619
2690
  (_b = props.onBlur) === null || _b === void 0 ? void 0 : _b.call(props, e);
2620
2691
  } })));
2621
- return (React.createElement(Popover, { reposition: reposition, isOpen: showCalendar, onClickOutside: () => {
2692
+ return (React.createElement(DialogPopover, { ariaLabel: 'Calendar',
2693
+ // Focus is handled here so we're opting out of the popover behavior in the most hackish way.
2694
+ // When there's time, remove the logic from here that now exists in the DialogPopover.
2695
+ focusContentId: '', focusParentId: '', __asDateInputCalendar: true, onClose: () => {
2622
2696
  toggleCalendar(false);
2623
- }, parent: input, content: (React.createElement("div", { ref: popover, className: css({
2697
+ },
2698
+ // above two lines are required due to ARIA changes.
2699
+ // we don't need to re-do this component since it already manages it's own focus in a very
2700
+ // complicated way
2701
+ reposition: reposition, isOpen: showCalendar, parent: input, content: (React.createElement("div", { ref: popover, className: css({
2624
2702
  paddingLeft: '1rem',
2625
2703
  paddingRight: '1rem',
2626
2704
  paddingBottom: '1rem'
@@ -2899,7 +2977,7 @@ const Nav = (props) => {
2899
2977
  nav.current.classList.add(classNavShowing);
2900
2978
  setTimeout(() => {
2901
2979
  var _a;
2902
- (_a = document.querySelector(props.focusSelector)) === null || _a === void 0 ? void 0 : _a.focus();
2980
+ (_a = document.getElementById(props.focusContentId)) === null || _a === void 0 ? void 0 : _a.focus();
2903
2981
  }, slideMs + 1);
2904
2982
  }
2905
2983
  }
@@ -4508,12 +4586,68 @@ const TogglePasswordInput = React.forwardRef((props, ref) => {
4508
4586
  React.createElement(Icon, { id: show ? 'show' : 'hide' }))) })));
4509
4587
  });
4510
4588
 
4589
+ let clearTimerId = undefined;
4590
+ /** Allows for status notificaiton methods for screen readers.
4591
+ * This hook does not have any dependencies, so it can be used in projects that don't important anything else from the style_guide. */
4592
+ function useAriaLiveRegion() {
4593
+ const id = 'MknAriaLiveRegion';
4594
+ if (!document.getElementById(id)) {
4595
+ const div = document.createElement('div');
4596
+ div.id = id;
4597
+ // different sources cannot decide if this is needed.
4598
+ // "Can work for status messages, but aria-live="polite" + text is usually simpler and more reliable for loading."
4599
+ div.role = 'status';
4600
+ div.ariaLive = 'polite';
4601
+ div.ariaAtomic = 'true';
4602
+ div.style.position = 'absolute';
4603
+ div.style.width = '1px';
4604
+ div.style.height = '1px';
4605
+ div.style.padding = '0px';
4606
+ div.style.margin = '-1px';
4607
+ div.style.overflow = 'hidden';
4608
+ div.style.whiteSpace = 'nowrap';
4609
+ document.body.prepend(div);
4610
+ }
4611
+ const clearNotification = () => {
4612
+ if (clearTimerId) {
4613
+ clearTimeout(clearTimerId);
4614
+ }
4615
+ const element = document.getElementById(id);
4616
+ if (!element) {
4617
+ return;
4618
+ }
4619
+ element.textContent = '';
4620
+ };
4621
+ return {
4622
+ /**
4623
+ * @param message - The text to be read by the screen reader.
4624
+ * @param clearTimeoutMs - Milliseconds to wait before the message is cleared. Defaults to `2000`..
4625
+ */
4626
+ notify: (message, clearTimoutMs) => {
4627
+ if (clearTimerId) {
4628
+ clearTimeout(clearTimerId);
4629
+ }
4630
+ const element = document.getElementById(id);
4631
+ if (!element) {
4632
+ return;
4633
+ }
4634
+ element.textContent = message;
4635
+ clearTimerId = setTimeout(() => {
4636
+ // this is considered a good practice. if you leave text here, some screen readers will read it out randomly as you navigate around the page.
4637
+ clearNotification();
4638
+ }, clearTimoutMs !== null && clearTimoutMs !== void 0 ? clearTimoutMs : 2000);
4639
+ },
4640
+ clearNotification
4641
+ };
4642
+ }
4643
+
4511
4644
  const WaitingIndicator = (p) => {
4512
4645
  var _a, _b, _c;
4513
4646
  const [show, setShow] = useState(p.show);
4514
4647
  const hideTimer = useRef(0);
4515
4648
  const lastShowStatus = useRef(false);
4516
4649
  const log = useLogger(`WaitingIndicator ${(_a = p.id) !== null && _a !== void 0 ? _a : '?'}`, (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
4650
+ const { notify, clearNotification } = useAriaLiveRegion();
4517
4651
  if (p.__debug) {
4518
4652
  useEffect(() => {
4519
4653
  log('mounted');
@@ -4560,9 +4694,16 @@ const WaitingIndicator = (p) => {
4560
4694
  log('ignoring show change due to hideTimer ticking');
4561
4695
  }
4562
4696
  }
4697
+ if (p.show) {
4698
+ // set to a very long time so the hiding of the waiting indicator clears it.
4699
+ notify('Loading content.', 60000);
4700
+ }
4701
+ else {
4702
+ clearNotification();
4703
+ }
4563
4704
  }, [p.show]);
4564
- const id = (_c = p.id) !== null && _c !== void 0 ? _c : 'WaitingIndicator';
4565
- return (React__default.createElement(Modal, { id: id, __debug: p.__debug, __noHeader: true, heading: '', onClose: () => {
4705
+ const id = (_c = p.id) !== null && _c !== void 0 ? _c : 'MknWaitingIndicator';
4706
+ return (React__default.createElement(Modal, { id: id, __debug: p.__debug, __asWaitingIndicator: true, heading: '', onClose: () => {
4566
4707
  /* noop */
4567
4708
  }, className: "waitingIndicator", show: show },
4568
4709
  React__default.createElement("div", { className: css({
@@ -5148,5 +5289,5 @@ const LocalizationProvider = (p) => {
5148
5289
  } }, p.children));
5149
5290
  };
5150
5291
 
5151
- export { Accordian, Autocomplete, AutocompleteController, AutocompleteEntityController, Backdrop$1 as Backdrop, Backdrop as Backdrop2, BoundMemoryPager, BoundStaticPager, Button, Calendar, Checkbox, ConfirmModal, CopyButton, DateInput, Divider, ErrorModal, FileUploader, Form, FormColumnRow, FormFlexRow, GlobalStyles, Header, Highlight, ICONS, Icon, Image, InfoPanel, InfoTip, ItemPager, Label, Link, List, ListItem, LocalizationProvider, Modal, Nav, NormalizeCss, NumberInput, OmniLink, PagedResult, Pager, Picker, Popover, ProgressBar, SearchBox, Slider, StyleGuideLanguage, TabContainer, TabHeader, TabLocker, Table, Td, TdCurrency, TdNumber, Text, TextArea, TextInput, Th, ThSort, ThemeProvider, ThemeRenderer, ToggleButton, ToggleButtonGroup, TogglePasswordInput, Tr, WaitingIndicator, calcDynamicThemeProps, defaultTheme, enumToEntities, getCurrencyDisplay, getFileSizeDisplay, modalScrollFixClassName, useAccordianState, useBooleanChanged, useIgnoreMount, useMediaQuery, useScrollbarSize, useThemeSafely, useWaiting };
5292
+ export { Accordian, Autocomplete, AutocompleteController, AutocompleteEntityController, Backdrop$1 as Backdrop, Backdrop as Backdrop2, BoundMemoryPager, BoundStaticPager, Button, Calendar, Checkbox, ConfirmModal, CopyButton, DateInput, DialogPopover, Divider, ErrorModal, FileUploader, Form, FormColumnRow, FormFlexRow, GlobalStyles, Header, Highlight, ICONS, Icon, Image, InfoPanel, InfoTip, ItemPager, Label, Link, List, ListItem, LocalizationProvider, Modal, Nav, NormalizeCss, NumberInput, OmniLink, PagedResult, Pager, Picker, ProgressBar, SearchBox, Slider, StyleGuideLanguage, TabContainer, TabHeader, TabLocker, Table, Td, TdCurrency, TdNumber, Text, TextArea, TextInput, Th, ThSort, ThemeProvider, ThemeRenderer, ToggleButton, ToggleButtonGroup, TogglePasswordInput, TooltipPopover, Tr, WaitingIndicator, calcDynamicThemeProps, defaultTheme, enumToEntities, getCurrencyDisplay, getFileSizeDisplay, modalScrollFixClassName, useAccordianState, useAriaLiveRegion, useBooleanChanged, useIgnoreMount, useMediaQuery, useScrollbarSize, useThemeSafely, useWaiting };
5152
5293
  //# sourceMappingURL=index.esm.js.map
package/index.js CHANGED
@@ -1626,6 +1626,18 @@ const Checkbox = (props) => {
1626
1626
  props.children)));
1627
1627
  };
1628
1628
 
1629
+ function createBodyEscapeHandler(onEscape) {
1630
+ const handler = (e) => {
1631
+ if (e.code === 'Escape') {
1632
+ onEscape();
1633
+ }
1634
+ };
1635
+ document.addEventListener('keydown', handler);
1636
+ return () => {
1637
+ document.removeEventListener('keydown', handler);
1638
+ };
1639
+ }
1640
+
1629
1641
  /** useEffect but it will only fire when the actual truthiness of the value changes.
1630
1642
  * Use for comparing previous states to next states without all the bullshit around useEffect and component mounting.
1631
1643
  */
@@ -1681,12 +1693,13 @@ const Modal = (p) => {
1681
1693
  const backdrop = useBackdropContext();
1682
1694
  const mouseDownElement = React.useRef(undefined);
1683
1695
  const theme = useThemeSafely();
1684
- const hasHeader = !p.__noHeader;
1696
+ const hasHeader = !p.__asWaitingIndicator;
1685
1697
  const contentRef = React.useRef(null);
1686
1698
  const log = useLogger((_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1687
1699
  const showing = React.useRef(p.show);
1688
1700
  const bodyStyles = React.useRef('');
1689
1701
  const fixedElementStyles = React.useRef('');
1702
+ const closeButtonId = `${p.id}_closeButton`;
1690
1703
  const addScrollStyles = () => {
1691
1704
  var _a, _b, _c, _d;
1692
1705
  if (!bodyStyles.current) {
@@ -1766,13 +1779,12 @@ const Modal = (p) => {
1766
1779
  React.useLayoutEffect(() => {
1767
1780
  var _a;
1768
1781
  if (p.show === true) {
1769
- const focusSelector = (_a = p.focusSelector) !== null && _a !== void 0 ? _a : '.modalCloseButton';
1782
+ const focusId = (_a = p.focusContentId) !== null && _a !== void 0 ? _a : closeButtonId;
1770
1783
  // still need to wait for the next tick so the children are all rendered.
1771
1784
  setTimeout(() => {
1772
- var _a;
1773
- const element = (_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(focusSelector);
1785
+ const element = document.getElementById(focusId);
1774
1786
  element === null || element === void 0 ? void 0 : element.focus();
1775
- log('set focus', focusSelector);
1787
+ log('set focus', focusId);
1776
1788
  });
1777
1789
  }
1778
1790
  }, [p.show]);
@@ -1821,7 +1833,7 @@ const Modal = (p) => {
1821
1833
  if (p.show) {
1822
1834
  const backdropContainer = document.getElementById(backdrop.portalId);
1823
1835
  if (backdropContainer) {
1824
- return reactDom.createPortal((React.createElement("div", { id: p.id, role: "dialog", "aria-modal": "true", "aria-labelledby": ariaLabelId, onClick: e => {
1836
+ return reactDom.createPortal((React.createElement("div", { id: p.id, "aria-hidden": p.__asWaitingIndicator, role: "dialog", "aria-modal": "true", "aria-labelledby": ariaLabelId, onClick: e => {
1825
1837
  e.stopPropagation();
1826
1838
  if (!mouseDownElement.current) {
1827
1839
  log('backdropContainer onClick');
@@ -1852,11 +1864,11 @@ const Modal = (p) => {
1852
1864
  margin: 0,
1853
1865
  flexGrow: 1
1854
1866
  }), tag: "h1", bold: true }, p.heading),
1855
- React.createElement(Button, { className: css.cx('modalCloseButton', css.css({
1867
+ React.createElement(Button, { id: closeButtonId, className: css.cx('modalCloseButton', css.css({
1856
1868
  color: theme.colors.headerFont,
1857
1869
  marginLeft: '1rem',
1858
1870
  backgroundColor: 'transparent'
1859
- })), "aria-label": closeText, variant: "icon", onClick: e => {
1871
+ }), p.closeButtonClassName), "aria-label": closeText, variant: "icon", onClick: e => {
1860
1872
  e.stopPropagation();
1861
1873
  p.onClose();
1862
1874
  } },
@@ -1866,17 +1878,6 @@ const Modal = (p) => {
1866
1878
  }
1867
1879
  return null;
1868
1880
  };
1869
- function createBodyEscapeHandler(onEscape) {
1870
- const handler = (e) => {
1871
- if (e.code === 'Escape') {
1872
- onEscape();
1873
- }
1874
- };
1875
- document.addEventListener('keydown', handler);
1876
- return () => {
1877
- document.removeEventListener('keydown', handler);
1878
- };
1879
- }
1880
1881
 
1881
1882
  const ConfirmModal = (props) => {
1882
1883
  const theme = useThemeSafely();
@@ -2406,34 +2407,60 @@ const Image = React__namespace.forwardRef((p, ref) => {
2406
2407
  }), p.className) })));
2407
2408
  });
2408
2409
 
2409
- const Popover = (p) => {
2410
- var _a, _b;
2410
+ function getPopoverContainerClass(theme) {
2411
+ return css.css({
2412
+ label: 'PopoverContainer',
2413
+ zIndex: theme.zIndexes.tooltip
2414
+ });
2415
+ }
2416
+ function getPopoverContentWrapperClass(theme, props) {
2417
+ var _a, _b, _c;
2418
+ return css.css({
2419
+ label: 'PopoverContentWrapper',
2420
+ border: (_a = props.border) !== null && _a !== void 0 ? _a : theme.controls.border,
2421
+ borderRadius: (_b = props.border) !== null && _b !== void 0 ? _b : theme.controls.borderRadius,
2422
+ boxShadow: theme.controls.boxShadow,
2423
+ backgroundColor: (_c = props.backgroundColor) !== null && _c !== void 0 ? _c : theme.colors.bg,
2424
+ });
2425
+ }
2426
+ function getPopoverArrowColor(theme, props) {
2427
+ var _a;
2428
+ return (_a = props.arrorColor) !== null && _a !== void 0 ? _a : theme.colors.border;
2429
+ }
2430
+ function getPopoverPositions(props) {
2431
+ var _a;
2432
+ return (_a = props.positions) !== null && _a !== void 0 ? _a : ['right', 'top', 'left', 'bottom'];
2433
+ }
2434
+ const popoverArrowSize = 10;
2435
+
2436
+ /** Use this popover for tooltips that are shown and dimissed on hover. Never use this for modal tooltips - use DialogPopover for that instead. */
2437
+ const TooltipPopover = (p) => {
2438
+ var _a;
2411
2439
  const theme = useThemeSafely();
2412
2440
  const resposition = (_a = p.reposition) !== null && _a !== void 0 ? _a : true;
2413
- return (React__namespace.createElement(reactTinyPopover.Popover, { containerClassName: css.css({
2414
- zIndex: theme.zIndexes.tooltip
2415
- }), reposition: resposition, isOpen: p.isOpen, positions: (_b = p.positions) !== null && _b !== void 0 ? _b : ['right', 'top', 'left', 'bottom'], onClickOutside: p.onClickOutside, content: ({ position, childRect, popoverRect }) => {
2416
- var _a, _b, _c, _d;
2417
- return (React__namespace.createElement(reactTinyPopover.ArrowContainer, { position: position, childRect: childRect, popoverRect: popoverRect, arrowColor: (_a = p.arrorColor) !== null && _a !== void 0 ? _a : theme.colors.border, arrowSize: 10 },
2418
- React__namespace.createElement(TabLocker, null,
2419
- React__namespace.createElement("div", { className: css.css({
2420
- border: (_b = p.border) !== null && _b !== void 0 ? _b : theme.controls.border,
2421
- borderRadius: (_c = p.border) !== null && _c !== void 0 ? _c : theme.controls.borderRadius,
2422
- boxShadow: theme.controls.boxShadow,
2423
- backgroundColor: (_d = p.backgroundColor) !== null && _d !== void 0 ? _d : theme.colors.bg,
2424
- }) }, p.content))));
2425
- } },
2426
- React__namespace.createElement("span", { className: p.parentWrapperClassName }, p.parent)));
2441
+ React__namespace.useEffect(() => {
2442
+ const escapeRemover = createBodyEscapeHandler(() => {
2443
+ if (p.isOpen) {
2444
+ p.onClose();
2445
+ }
2446
+ });
2447
+ return escapeRemover;
2448
+ }, [p.isOpen]);
2449
+ return (React__namespace.createElement(reactTinyPopover.Popover, { containerClassName: getPopoverContainerClass(theme), reposition: resposition, isOpen: p.isOpen, positions: getPopoverPositions(p), onClickOutside: p.onClose, content: ({ position, childRect, popoverRect }) => (React__namespace.createElement(reactTinyPopover.ArrowContainer, { position: position, childRect: childRect, popoverRect: popoverRect, arrowColor: getPopoverArrowColor(theme, p), arrowSize: popoverArrowSize },
2450
+ React__namespace.createElement("div", { id: p.id, onKeyDown: e => {
2451
+ if (e.code === 'Escape') {
2452
+ e.stopPropagation();
2453
+ e.preventDefault();
2454
+ p.onClose();
2455
+ }
2456
+ }, role: 'tooltip', className: getPopoverContentWrapperClass(theme, p) }, p.content))) },
2457
+ React__namespace.createElement("span", { "aria-describedby": p.id, className: p.parentWrapperClassName }, p.parent)));
2427
2458
  };
2428
2459
 
2429
2460
  const InfoTip = (props) => {
2430
2461
  var _a, _b, _c;
2431
2462
  const [showTip, setShowTip] = React__namespace.useState(false);
2432
2463
  const theme = useThemeSafely();
2433
- if (props.variant === 'modal' && !props.modalProps) {
2434
- console.warn(`InfoTip with variant=modal requires modalProps.`);
2435
- return null;
2436
- }
2437
2464
  const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
2438
2465
  const fontColor = (_b = props.fontColor) !== null && _b !== void 0 ? _b : theme.colors.navFont;
2439
2466
  const onClick = () => {
@@ -2445,7 +2472,7 @@ const InfoTip = (props) => {
2445
2472
  }
2446
2473
  };
2447
2474
  const onMouseOver = () => {
2448
- if (props.variant === 'modal') {
2475
+ if (props.variant.type === 'Modal') {
2449
2476
  return;
2450
2477
  }
2451
2478
  if (props.loadOnHover) {
@@ -2460,7 +2487,7 @@ const InfoTip = (props) => {
2460
2487
  }
2461
2488
  };
2462
2489
  const onMouseOut = () => {
2463
- if (props.variant === 'modal') {
2490
+ if (props.variant.type === 'Modal') {
2464
2491
  return;
2465
2492
  }
2466
2493
  closeTip();
@@ -2489,17 +2516,22 @@ const InfoTip = (props) => {
2489
2516
  font-family: serif;
2490
2517
  display:inline-block;
2491
2518
  `;
2492
- const button = React__namespace.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut }, "i");
2493
- if (props.variant === 'modal' && props.modalProps) {
2519
+ let onButtonBlur = props.variant.type === 'Info' ?
2520
+ () => {
2521
+ closeTip();
2522
+ } :
2523
+ undefined;
2524
+ const button = (React__namespace.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut, onBlur: onButtonBlur }, "i"));
2525
+ if (props.variant.type === 'Modal') {
2494
2526
  return (React__namespace.createElement(React__namespace.Fragment, null,
2495
2527
  button,
2496
- React__namespace.createElement(Modal, { id: props.modalProps.id, __debug: props.modalProps.__debug, show: showTip, heading: props.modalProps.heading, onClose: closeTip, className: css.css({
2528
+ React__namespace.createElement(Modal, { id: props.id, __debug: props.variant.__debug, show: showTip, heading: props.variant.heading, onClose: closeTip, className: css.css({
2497
2529
  whiteSpace: 'normal'
2498
2530
  }) },
2499
2531
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) }, props.content))));
2500
2532
  }
2501
2533
  else {
2502
- return (React__namespace.createElement(Popover, { positions: props.positions, reposition: (_c = props.reposition) !== null && _c !== void 0 ? _c : false, isOpen: showTip, onClickOutside: closeTip, arrorColor: bgColor, border: '', backgroundColor: bgColor, parent: button, content: (React__namespace.createElement("div", { className: css.css({
2534
+ return (React__namespace.createElement(TooltipPopover, { id: props.id, positions: props.positions, reposition: (_c = props.reposition) !== null && _c !== void 0 ? _c : false, isOpen: showTip, onClose: closeTip, arrorColor: bgColor, border: '', backgroundColor: bgColor, parent: button, content: (React__namespace.createElement("div", { className: css.css({
2503
2535
  padding: '0.5rem',
2504
2536
  fontSize: '0.75rem',
2505
2537
  maxWidth: '22rem'
@@ -2507,6 +2539,45 @@ const InfoTip = (props) => {
2507
2539
  }
2508
2540
  };
2509
2541
 
2542
+ /** Use this popover for modal dialogs that stay up until dismissed by the user. Never use this for informational tooltips - use TooltipPopover for that instead. */
2543
+ const DialogPopover = (p) => {
2544
+ var _a;
2545
+ const theme = useThemeSafely();
2546
+ const resposition = (_a = p.reposition) !== null && _a !== void 0 ? _a : true;
2547
+ React__namespace.useEffect(() => {
2548
+ if (!p.__asDateInputCalendar) {
2549
+ if (p.isOpen) {
2550
+ const content = document.getElementById(p.focusContentId);
2551
+ if (content) {
2552
+ content.focus();
2553
+ }
2554
+ }
2555
+ else {
2556
+ const parent = document.getElementById(p.focusParentId);
2557
+ if (parent) {
2558
+ parent.focus();
2559
+ }
2560
+ }
2561
+ }
2562
+ const escapeRemover = createBodyEscapeHandler(() => {
2563
+ if (p.isOpen) {
2564
+ p.onClose();
2565
+ }
2566
+ });
2567
+ return escapeRemover;
2568
+ }, [p.isOpen]);
2569
+ return (React__namespace.createElement(reactTinyPopover.Popover, { containerClassName: getPopoverContainerClass(theme), reposition: resposition, isOpen: p.isOpen, positions: getPopoverPositions(p), onClickOutside: p.onClose, content: ({ position, childRect, popoverRect }) => (React__namespace.createElement(reactTinyPopover.ArrowContainer, { position: position, childRect: childRect, popoverRect: popoverRect, arrowColor: getPopoverArrowColor(theme, p), arrowSize: popoverArrowSize },
2570
+ React__namespace.createElement(TabLocker, null,
2571
+ React__namespace.createElement("div", { onKeyDown: e => {
2572
+ if (e.code === 'Escape') {
2573
+ e.stopPropagation();
2574
+ e.preventDefault();
2575
+ p.onClose();
2576
+ }
2577
+ }, role: 'dialog', "aria-modal": true, "aria-label": p.ariaLabel, className: getPopoverContentWrapperClass(theme, p) }, p.content)))) },
2578
+ React__namespace.createElement("span", { className: p.parentWrapperClassName }, p.parent)));
2579
+ };
2580
+
2510
2581
  const dateRegex = /(\d{1,2})(?:\/|-)(\d{1,2})(?:\/|-)(\d{4})/;
2511
2582
  const datePattern = dateRegex.source;
2512
2583
  const invalidDateMessage = 'Invalid date.';
@@ -2636,9 +2707,16 @@ const DateInput = React__namespace.forwardRef((props, ref) => {
2636
2707
  }
2637
2708
  (_b = props.onBlur) === null || _b === void 0 ? void 0 : _b.call(props, e);
2638
2709
  } })));
2639
- return (React__namespace.createElement(Popover, { reposition: reposition, isOpen: showCalendar, onClickOutside: () => {
2710
+ return (React__namespace.createElement(DialogPopover, { ariaLabel: 'Calendar',
2711
+ // Focus is handled here so we're opting out of the popover behavior in the most hackish way.
2712
+ // When there's time, remove the logic from here that now exists in the DialogPopover.
2713
+ focusContentId: '', focusParentId: '', __asDateInputCalendar: true, onClose: () => {
2640
2714
  toggleCalendar(false);
2641
- }, parent: input, content: (React__namespace.createElement("div", { ref: popover, className: css.css({
2715
+ },
2716
+ // above two lines are required due to ARIA changes.
2717
+ // we don't need to re-do this component since it already manages it's own focus in a very
2718
+ // complicated way
2719
+ reposition: reposition, isOpen: showCalendar, parent: input, content: (React__namespace.createElement("div", { ref: popover, className: css.css({
2642
2720
  paddingLeft: '1rem',
2643
2721
  paddingRight: '1rem',
2644
2722
  paddingBottom: '1rem'
@@ -2917,7 +2995,7 @@ const Nav = (props) => {
2917
2995
  nav.current.classList.add(classNavShowing);
2918
2996
  setTimeout(() => {
2919
2997
  var _a;
2920
- (_a = document.querySelector(props.focusSelector)) === null || _a === void 0 ? void 0 : _a.focus();
2998
+ (_a = document.getElementById(props.focusContentId)) === null || _a === void 0 ? void 0 : _a.focus();
2921
2999
  }, slideMs + 1);
2922
3000
  }
2923
3001
  }
@@ -4526,12 +4604,68 @@ const TogglePasswordInput = React__namespace.forwardRef((props, ref) => {
4526
4604
  React__namespace.createElement(Icon, { id: show ? 'show' : 'hide' }))) })));
4527
4605
  });
4528
4606
 
4607
+ let clearTimerId = undefined;
4608
+ /** Allows for status notificaiton methods for screen readers.
4609
+ * This hook does not have any dependencies, so it can be used in projects that don't important anything else from the style_guide. */
4610
+ function useAriaLiveRegion() {
4611
+ const id = 'MknAriaLiveRegion';
4612
+ if (!document.getElementById(id)) {
4613
+ const div = document.createElement('div');
4614
+ div.id = id;
4615
+ // different sources cannot decide if this is needed.
4616
+ // "Can work for status messages, but aria-live="polite" + text is usually simpler and more reliable for loading."
4617
+ div.role = 'status';
4618
+ div.ariaLive = 'polite';
4619
+ div.ariaAtomic = 'true';
4620
+ div.style.position = 'absolute';
4621
+ div.style.width = '1px';
4622
+ div.style.height = '1px';
4623
+ div.style.padding = '0px';
4624
+ div.style.margin = '-1px';
4625
+ div.style.overflow = 'hidden';
4626
+ div.style.whiteSpace = 'nowrap';
4627
+ document.body.prepend(div);
4628
+ }
4629
+ const clearNotification = () => {
4630
+ if (clearTimerId) {
4631
+ clearTimeout(clearTimerId);
4632
+ }
4633
+ const element = document.getElementById(id);
4634
+ if (!element) {
4635
+ return;
4636
+ }
4637
+ element.textContent = '';
4638
+ };
4639
+ return {
4640
+ /**
4641
+ * @param message - The text to be read by the screen reader.
4642
+ * @param clearTimeoutMs - Milliseconds to wait before the message is cleared. Defaults to `2000`..
4643
+ */
4644
+ notify: (message, clearTimoutMs) => {
4645
+ if (clearTimerId) {
4646
+ clearTimeout(clearTimerId);
4647
+ }
4648
+ const element = document.getElementById(id);
4649
+ if (!element) {
4650
+ return;
4651
+ }
4652
+ element.textContent = message;
4653
+ clearTimerId = setTimeout(() => {
4654
+ // this is considered a good practice. if you leave text here, some screen readers will read it out randomly as you navigate around the page.
4655
+ clearNotification();
4656
+ }, clearTimoutMs !== null && clearTimoutMs !== void 0 ? clearTimoutMs : 2000);
4657
+ },
4658
+ clearNotification
4659
+ };
4660
+ }
4661
+
4529
4662
  const WaitingIndicator = (p) => {
4530
4663
  var _a, _b, _c;
4531
4664
  const [show, setShow] = React.useState(p.show);
4532
4665
  const hideTimer = React.useRef(0);
4533
4666
  const lastShowStatus = React.useRef(false);
4534
4667
  const log = useLogger(`WaitingIndicator ${(_a = p.id) !== null && _a !== void 0 ? _a : '?'}`, (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
4668
+ const { notify, clearNotification } = useAriaLiveRegion();
4535
4669
  if (p.__debug) {
4536
4670
  React.useEffect(() => {
4537
4671
  log('mounted');
@@ -4578,9 +4712,16 @@ const WaitingIndicator = (p) => {
4578
4712
  log('ignoring show change due to hideTimer ticking');
4579
4713
  }
4580
4714
  }
4715
+ if (p.show) {
4716
+ // set to a very long time so the hiding of the waiting indicator clears it.
4717
+ notify('Loading content.', 60000);
4718
+ }
4719
+ else {
4720
+ clearNotification();
4721
+ }
4581
4722
  }, [p.show]);
4582
- const id = (_c = p.id) !== null && _c !== void 0 ? _c : 'WaitingIndicator';
4583
- return (React.createElement(Modal, { id: id, __debug: p.__debug, __noHeader: true, heading: '', onClose: () => {
4723
+ const id = (_c = p.id) !== null && _c !== void 0 ? _c : 'MknWaitingIndicator';
4724
+ return (React.createElement(Modal, { id: id, __debug: p.__debug, __asWaitingIndicator: true, heading: '', onClose: () => {
4584
4725
  /* noop */
4585
4726
  }, className: "waitingIndicator", show: show },
4586
4727
  React.createElement("div", { className: css.css({
@@ -5180,6 +5321,7 @@ exports.Checkbox = Checkbox;
5180
5321
  exports.ConfirmModal = ConfirmModal;
5181
5322
  exports.CopyButton = CopyButton;
5182
5323
  exports.DateInput = DateInput;
5324
+ exports.DialogPopover = DialogPopover;
5183
5325
  exports.Divider = Divider;
5184
5326
  exports.ErrorModal = ErrorModal;
5185
5327
  exports.FileUploader = FileUploader;
@@ -5208,7 +5350,6 @@ exports.OmniLink = OmniLink;
5208
5350
  exports.PagedResult = PagedResult;
5209
5351
  exports.Pager = Pager;
5210
5352
  exports.Picker = Picker;
5211
- exports.Popover = Popover;
5212
5353
  exports.ProgressBar = ProgressBar;
5213
5354
  exports.SearchBox = SearchBox;
5214
5355
  exports.Slider = Slider;
@@ -5229,6 +5370,7 @@ exports.ThemeRenderer = ThemeRenderer;
5229
5370
  exports.ToggleButton = ToggleButton;
5230
5371
  exports.ToggleButtonGroup = ToggleButtonGroup;
5231
5372
  exports.TogglePasswordInput = TogglePasswordInput;
5373
+ exports.TooltipPopover = TooltipPopover;
5232
5374
  exports.Tr = Tr;
5233
5375
  exports.WaitingIndicator = WaitingIndicator;
5234
5376
  exports.calcDynamicThemeProps = calcDynamicThemeProps;
@@ -5238,6 +5380,7 @@ exports.getCurrencyDisplay = getCurrencyDisplay;
5238
5380
  exports.getFileSizeDisplay = getFileSizeDisplay;
5239
5381
  exports.modalScrollFixClassName = modalScrollFixClassName;
5240
5382
  exports.useAccordianState = useAccordianState;
5383
+ exports.useAriaLiveRegion = useAriaLiveRegion;
5241
5384
  exports.useBooleanChanged = useBooleanChanged;
5242
5385
  exports.useIgnoreMount = useIgnoreMount;
5243
5386
  exports.useMediaQuery = useMediaQuery;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mackin.com/styleguide",
3
- "version": "11.0.3",
3
+ "version": "11.0.4",
4
4
  "description": "",
5
5
  "main": "./index.js",
6
6
  "module": "./index.esm.js",