@mackin.com/styleguide 10.2.1 → 10.2.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 +1 -1
  2. package/index.esm.js +105 -32
  3. package/index.js +105 -32
  4. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -863,7 +863,7 @@ interface SliderProps<T extends SliderValue> {
863
863
  innerTrackClassName?: string;
864
864
  /** Styles applied to the floating handle text with using 'showValue'. */
865
865
  sliderTextClassName?: string;
866
- /** Styles applied to the floating handle text with using 'showValue'. */
866
+ /** Sets the aria-label value. */
867
867
  ariaLabel?: (T extends number ? string : readonly string[] | undefined) | undefined;
868
868
  }
869
869
  declare const Slider: <T extends SliderValue>(p: SliderProps<T>) => React__default.JSX.Element;
package/index.esm.js CHANGED
@@ -497,6 +497,45 @@ const Button = React.forwardRef((props, ref) => {
497
497
  return content;
498
498
  });
499
499
 
500
+ /* Type is not always determinable due to the nature of custom components in typescript. For example OmniLink will not have a type of "a" but rather "OmniLink" so we have to determine
501
+ if the child component is focusable based on certain properties on the control that are found on focusable components */
502
+ const isChildFocusable = (props, type) => {
503
+ if (props.tabIndex !== undefined && props.tabIndex !== null) {
504
+ return true;
505
+ }
506
+ if (props.onClick !== undefined || props.onValueChange !== undefined) { //button or select
507
+ return true;
508
+ }
509
+ if (props.href || props.cols || props.rows || props.maxLength) {
510
+ return true;
511
+ }
512
+ if ((type === 'button' || type === 'input' || type === 'select' || type === 'textarea') && !props.disabled) {
513
+ return true;
514
+ }
515
+ return false;
516
+ };
517
+ const TabIndexContainer = (props) => {
518
+ const processElement = (node) => {
519
+ if (!React__default.isValidElement(node)) {
520
+ return node;
521
+ }
522
+ let updatedNode = node;
523
+ // Use the props-based logic to check focusability
524
+ if (isChildFocusable(node.props, node.type)) {
525
+ updatedNode = React__default.cloneElement(node, { tabIndex: props.tabIndexValue });
526
+ }
527
+ // Recursively process children
528
+ if (updatedNode.props.children) {
529
+ const clonedChildren = React__default.Children.map(updatedNode.props.children, processElement);
530
+ updatedNode = React__default.cloneElement(updatedNode, {
531
+ children: clonedChildren,
532
+ });
533
+ }
534
+ return updatedNode;
535
+ };
536
+ return (React__default.createElement(React__default.Fragment, null, React__default.Children.map(props.children, processElement)));
537
+ };
538
+
500
539
  const accordianExpandTimeMs = 250;
501
540
  const accordianMaxHeight = 1020;
502
541
  const accordianTimingFunction = 'ease-in-out';
@@ -544,7 +583,7 @@ const Accordian = (props) => {
544
583
  }
545
584
  setOpen((_a = props.open) !== null && _a !== void 0 ? _a : false);
546
585
  }, [props.open]);
547
- return (React.createElement("div", { className: "accordian" },
586
+ return (React.createElement("div", { className: "accordian", "aria-expanded": open },
548
587
  React.createElement(Button, { readOnly: props.disabled, variant: props.variant, className: cx(css({
549
588
  display: 'flex',
550
589
  alignItems: 'center',
@@ -563,7 +602,8 @@ const Accordian = (props) => {
563
602
  }, rightIcon: !props.disabled ? React.createElement(Icon, { id: open ? 'collapse' : 'expand' }) : undefined },
564
603
  React.createElement("span", null, props.header)),
565
604
  React.createElement("div", { ref: content, className: cx('accordian__body', contentStyles) },
566
- React.createElement("div", { className: expandedContentWrapperStyles }, props.children))));
605
+ React.createElement("div", { className: expandedContentWrapperStyles },
606
+ React.createElement(TabIndexContainer, { tabIndexValue: open ? 0 : -1 }, props.children)))));
567
607
  };
568
608
  const useAccordianState = (count, openIndex) => {
569
609
  const [panels, setShowPanel] = React.useState(new Array(count).fill(false).map((b, i) => {
@@ -969,13 +1009,14 @@ const defaultMaxShownValues = 7;
969
1009
  const buttonMarkerClass = 'ListItem__button';
970
1010
  const defaultOnPickFocusMs = 100;
971
1011
  const Autocomplete = (p) => {
972
- var _a;
1012
+ var _a, _b;
973
1013
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
974
1014
  const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "listItemButtonClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs"]);
975
1015
  const theme = useThemeSafely();
976
1016
  const element = React.useRef(null);
977
1017
  const input = React.useRef(null);
978
1018
  const list = React.useRef(null);
1019
+ const [selectedResultIndex, setSelectedResultIndex] = React.useState();
979
1020
  const maxShowValues = (_a = p.maxShownValues) !== null && _a !== void 0 ? _a : defaultMaxShownValues;
980
1021
  const displayOptions = React.useMemo(() => {
981
1022
  if (!p.allowScroll) {
@@ -994,10 +1035,12 @@ const Autocomplete = (p) => {
994
1035
  if (direction === -1) {
995
1036
  buttonIndex = displayOptions.length - 1;
996
1037
  }
1038
+ setSelectedResultIndex(buttonIndex);
997
1039
  return (_a = list.current) === null || _a === void 0 ? void 0 : _a.querySelector(`.${buttonMarkerClass}${buttonIndex}`);
998
1040
  }
999
1041
  else {
1000
1042
  const nextIndex = fromIndex + direction;
1043
+ setSelectedResultIndex(nextIndex);
1001
1044
  if (nextIndex >= displayOptions.length || nextIndex < 0) {
1002
1045
  return (_b = input.current) !== null && _b !== void 0 ? _b : undefined;
1003
1046
  }
@@ -1021,6 +1064,7 @@ const Autocomplete = (p) => {
1021
1064
  if (p.round || theme.controls.borderRadius) {
1022
1065
  listBorderRadius = theme.controls.borderRadius || '0.5rem';
1023
1066
  }
1067
+ const id = (_b = p.id) !== null && _b !== void 0 ? _b : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
1024
1068
  const onPickValue = (v) => {
1025
1069
  var _a;
1026
1070
  // the TextInput will not respond to outer value changes if it has focus.
@@ -1068,8 +1112,9 @@ const Autocomplete = (p) => {
1068
1112
  }
1069
1113
  }
1070
1114
  (_c = p.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(p, e);
1071
- } })),
1072
- !!displayOptions.length && (React.createElement(List, { ref: list, className: cx(css({
1115
+ }, "aria-owns": id, "aria-expanded": !!displayOptions.length, "aria-autocomplete": "both", "aria-describedby": `${id}-aria-description` })),
1116
+ React.createElement("span", { id: `${id}-aria-description`, className: css({ display: "none" }) }, "When autocomplete results are available use up and down arrows to review and enter to select."),
1117
+ !!displayOptions.length && (React.createElement(List, { id: id, ref: list, role: "listbox", className: cx(css({
1073
1118
  position: 'absolute',
1074
1119
  width: '100%',
1075
1120
  border: theme.controls.border,
@@ -1092,7 +1137,7 @@ const Autocomplete = (p) => {
1092
1137
  }), p.listClassName) },
1093
1138
  displayOptions.map((v, listItemIndex) => {
1094
1139
  var _a;
1095
- return (React.createElement(ListItem, { key: v, variant: "full", className: p.listItemClassName },
1140
+ return (React.createElement(ListItem, { key: v, variant: "full", className: p.listItemClassName, role: "option", "aria-selected": selectedResultIndex === listItemIndex },
1096
1141
  React.createElement(Button, { title: ((_a = p.showOptionTextAsTitle) !== null && _a !== void 0 ? _a : true) ? v : undefined, onKeyDown: e => {
1097
1142
  var _a, _b;
1098
1143
  if (e.key === 'ArrowDown') {
@@ -1258,15 +1303,18 @@ const useLogger = (componentName, enabled) => {
1258
1303
  const portalId = 'backdrop';
1259
1304
  const BackdropContext = React__default.createContext({
1260
1305
  showing: false,
1261
- showCount: 0,
1262
1306
  portalId: portalId,
1307
+ children: [],
1263
1308
  setShow: () => {
1264
1309
  /* empty */
1265
1310
  }
1266
1311
  });
1312
+ function useBackdropContext() {
1313
+ return useContext(BackdropContext);
1314
+ }
1267
1315
  const BackdropContextProvider = (p) => {
1268
1316
  var _a;
1269
- const [showCount, setShowCount] = useState(0);
1317
+ const [modalChildren, setModalChildren] = useState([]);
1270
1318
  const log = useLogger('BackdropContextProvider', (_a = p.__debug) !== null && _a !== void 0 ? _a : false);
1271
1319
  if (p.__debug) {
1272
1320
  useEffect(() => {
@@ -1276,26 +1324,38 @@ const BackdropContextProvider = (p) => {
1276
1324
  };
1277
1325
  }, []);
1278
1326
  useIgnoreMount(() => {
1279
- log('showCount changed', showCount);
1280
- }, [showCount]);
1327
+ log('showCount changed', modalChildren);
1328
+ }, [modalChildren]);
1281
1329
  }
1282
1330
  return (React__default.createElement(BackdropContext.Provider, { value: {
1283
1331
  portalId: portalId,
1284
- showing: showCount > 0,
1285
- showCount: showCount,
1332
+ showing: modalChildren.length > 0,
1333
+ children: modalChildren.slice(),
1286
1334
  setShow: (show, from) => {
1287
1335
  if (show) {
1288
- setShowCount(s => {
1289
- const count = s + 1;
1290
- log(`setShow from ${from} ${s} -> ${count}`);
1291
- return count;
1336
+ setModalChildren(previousChildren => {
1337
+ const newChildren = [...previousChildren, from];
1338
+ log(`setModalChildren TRUE from ${JSON.stringify(from)}.`);
1339
+ return newChildren;
1292
1340
  });
1293
1341
  }
1294
1342
  else {
1295
- setShowCount(s => {
1296
- const count = Math.max(0, s - 1);
1297
- log(`setShow from ${from} ${s} -> ${count}`);
1298
- return count;
1343
+ setModalChildren(previousChildren => {
1344
+ let newChildren;
1345
+ if (from.key === backdropOverlayKey && !show) {
1346
+ if (previousChildren.length === 1) {
1347
+ newChildren = [];
1348
+ }
1349
+ else {
1350
+ newChildren = [...previousChildren];
1351
+ newChildren.pop();
1352
+ }
1353
+ }
1354
+ else {
1355
+ newChildren = previousChildren.filter(c => c.key !== from.key);
1356
+ }
1357
+ log(`setModalChildren FALSE from ${JSON.stringify(from)}.`);
1358
+ return newChildren;
1299
1359
  });
1300
1360
  }
1301
1361
  }
@@ -1310,9 +1370,10 @@ const BackdropContextProvider = (p) => {
1310
1370
  margin: 0,
1311
1371
  zIndex: 9999
1312
1372
  })) },
1313
- "Backdrop showCount: ",
1314
- showCount))));
1373
+ "Backdrop children: ",
1374
+ JSON.stringify(modalChildren, null, 4)))));
1315
1375
  };
1376
+ const backdropOverlayKey = 'BackdropOverlay';
1316
1377
  const BackdropOverlay = (p) => {
1317
1378
  var _a, _b;
1318
1379
  const context = useContext(BackdropContext);
@@ -1330,15 +1391,25 @@ const BackdropOverlay = (p) => {
1330
1391
  log('context.showing changed', context.showing);
1331
1392
  }, [context.showing]);
1332
1393
  }
1394
+ let zIndex = -1;
1395
+ if (context.showing) {
1396
+ zIndex = theme.zIndexes.backdrop;
1397
+ context.children.forEach(child => {
1398
+ if (child.zIndex > zIndex) {
1399
+ // -1 so it's always directly below the child
1400
+ zIndex = child.zIndex - 1;
1401
+ }
1402
+ });
1403
+ }
1333
1404
  return (React__default.createElement("div", { onClick: () => {
1334
- context === null || context === void 0 ? void 0 : context.setShow(false, 'BackdropOverlay');
1405
+ context === null || context === void 0 ? void 0 : context.setShow(false, { key: backdropOverlayKey, zIndex });
1335
1406
  log('onClick', 'setShow', false);
1336
1407
  }, id: context === null || context === void 0 ? void 0 : context.portalId, className: css({
1337
1408
  cursor: 'pointer',
1338
1409
  position: 'fixed',
1339
1410
  top: 0, right: 0, bottom: 0, left: 0,
1340
1411
  backgroundColor: theme.colors.backdrop,
1341
- zIndex: (context === null || context === void 0 ? void 0 : context.showing) ? theme.zIndexes.backdrop : -1,
1412
+ zIndex,
1342
1413
  visibility: (context === null || context === void 0 ? void 0 : context.showing) ? 'visible' : 'hidden',
1343
1414
  opacity: (context === null || context === void 0 ? void 0 : context.showing) ? 1 : 0,
1344
1415
  transition: `opacity ${showTimeMs}ms ease-in-out`,
@@ -1582,7 +1653,7 @@ const useScrollbarSize = (recalc) => {
1582
1653
 
1583
1654
  const Modal = (p) => {
1584
1655
  var _a, _b, _c, _d;
1585
- const backdrop = useContext(BackdropContext);
1656
+ const backdrop = useBackdropContext();
1586
1657
  const mouseDownElement = useRef(undefined);
1587
1658
  const theme = useThemeSafely();
1588
1659
  const hasHeader = p.closeButton || p.heading;
@@ -1623,13 +1694,14 @@ const Modal = (p) => {
1623
1694
  });
1624
1695
  }
1625
1696
  };
1697
+ const zIndex = theme.zIndexes.modal;
1626
1698
  useEffect(() => {
1627
1699
  log('mounted');
1628
1700
  return () => {
1629
1701
  var _a;
1630
1702
  if (showing.current) {
1631
1703
  log(`un-mount in progress and this modal is showing. decrement the backdrop and try to remove singleton body styles.`);
1632
- backdrop.setShow(false, (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal');
1704
+ backdrop.setShow(false, { key: (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', zIndex });
1633
1705
  log('backdrop.setShow', false);
1634
1706
  tryRemoveScrollStyles();
1635
1707
  }
@@ -1642,7 +1714,7 @@ const Modal = (p) => {
1642
1714
  useBooleanChanged((show, previousShow) => {
1643
1715
  var _a;
1644
1716
  log('show changed', `${previousShow !== null && previousShow !== void 0 ? previousShow : 'undefined'} > ${show}`);
1645
- backdrop.setShow(show, (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal');
1717
+ backdrop.setShow(show, { key: (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', zIndex });
1646
1718
  showing.current = show;
1647
1719
  log('backdrop.setShow', show);
1648
1720
  if (show) {
@@ -1670,7 +1742,7 @@ const Modal = (p) => {
1670
1742
  const modalBodyStyles = css({
1671
1743
  maxHeight: p.scrollable ? undefined : '99vh',
1672
1744
  overflow: 'hidden',
1673
- zIndex: theme.zIndexes.modal,
1745
+ zIndex,
1674
1746
  cursor: 'default',
1675
1747
  margin: '1rem',
1676
1748
  backgroundColor: p.noBackground ? undefined : theme.colors.modalBg,
@@ -2722,7 +2794,7 @@ const Nav = (props) => {
2722
2794
  const theme = useThemeSafely();
2723
2795
  const navWidth = (_a = props.navWidth) !== null && _a !== void 0 ? _a : theme.layout.navWidth;
2724
2796
  const totalNavOffset = `calc(${navWidth} + 20px)`;
2725
- const backdrop = React.useContext(BackdropContext);
2797
+ const backdrop = useBackdropContext();
2726
2798
  const log = useLogger(`Nav ${(_b = props.id) !== null && _b !== void 0 ? _b : '?'}`, (_c = props.__debug) !== null && _c !== void 0 ? _c : false);
2727
2799
  const slideMs = (_d = props.slideMs) !== null && _d !== void 0 ? _d : theme.timings.nav.slideMs;
2728
2800
  const slideRight = keyframes `
@@ -2749,6 +2821,7 @@ const Nav = (props) => {
2749
2821
  const classNavNotShowing = css `
2750
2822
  animation: ${slideLeft} ${slideMs}ms cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
2751
2823
  `;
2824
+ const zIndex = theme.zIndexes.nav;
2752
2825
  // the padding-top here is to offset the navs' content from the header. the shadow creeps over it.
2753
2826
  const navStyles = css `
2754
2827
  label: Nav;
@@ -2761,7 +2834,7 @@ const Nav = (props) => {
2761
2834
  width: ${navWidth};
2762
2835
  min-width: ${navWidth};
2763
2836
  box-shadow: 4px 2px 12px 6px rgba(0, 0, 0, 0.2);
2764
- z-index: ${theme.zIndexes.nav};
2837
+ z-index: ${zIndex};
2765
2838
  overflow-y: auto;
2766
2839
  .omniLink, .omniLink:active, .omniLink:focus, .omniLink:visited {
2767
2840
  color: ${theme.colors.navFont};
@@ -2776,7 +2849,7 @@ const Nav = (props) => {
2776
2849
  useBooleanChanged((current, previous) => {
2777
2850
  var _a;
2778
2851
  log('show changed', `${previous !== null && previous !== void 0 ? previous : 'undefined'} > ${current}`);
2779
- backdrop.setShow(current, (_a = props.id) !== null && _a !== void 0 ? _a : 'Nav');
2852
+ backdrop.setShow(current, { key: (_a = props.id) !== null && _a !== void 0 ? _a : 'Nav', zIndex });
2780
2853
  }, props.show);
2781
2854
  React.useLayoutEffect(() => {
2782
2855
  if (nav && nav.current) {
@@ -4016,7 +4089,7 @@ const TabHeader = (p) => {
4016
4089
  buttonContent = tab.name;
4017
4090
  }
4018
4091
  return (React.createElement("li", { key: index, className: cx(tabStyles, p.tabClassName) },
4019
- React.createElement(Button, { disabled: tabsChanging, className: buttonStyles, variant: buttonVariant, title: title, readOnly: active, onClick: () => {
4092
+ React.createElement(Button, { "aria-role": "tab", "aria-selected": active, disabled: tabsChanging, className: buttonStyles, variant: buttonVariant, title: title, readOnly: active, onClick: () => {
4020
4093
  const onChange = () => {
4021
4094
  var _a;
4022
4095
  setTabIndex(index);
package/index.js CHANGED
@@ -515,6 +515,45 @@ const Button = React__namespace.forwardRef((props, ref) => {
515
515
  return content;
516
516
  });
517
517
 
518
+ /* Type is not always determinable due to the nature of custom components in typescript. For example OmniLink will not have a type of "a" but rather "OmniLink" so we have to determine
519
+ if the child component is focusable based on certain properties on the control that are found on focusable components */
520
+ const isChildFocusable = (props, type) => {
521
+ if (props.tabIndex !== undefined && props.tabIndex !== null) {
522
+ return true;
523
+ }
524
+ if (props.onClick !== undefined || props.onValueChange !== undefined) { //button or select
525
+ return true;
526
+ }
527
+ if (props.href || props.cols || props.rows || props.maxLength) {
528
+ return true;
529
+ }
530
+ if ((type === 'button' || type === 'input' || type === 'select' || type === 'textarea') && !props.disabled) {
531
+ return true;
532
+ }
533
+ return false;
534
+ };
535
+ const TabIndexContainer = (props) => {
536
+ const processElement = (node) => {
537
+ if (!React.isValidElement(node)) {
538
+ return node;
539
+ }
540
+ let updatedNode = node;
541
+ // Use the props-based logic to check focusability
542
+ if (isChildFocusable(node.props, node.type)) {
543
+ updatedNode = React.cloneElement(node, { tabIndex: props.tabIndexValue });
544
+ }
545
+ // Recursively process children
546
+ if (updatedNode.props.children) {
547
+ const clonedChildren = React.Children.map(updatedNode.props.children, processElement);
548
+ updatedNode = React.cloneElement(updatedNode, {
549
+ children: clonedChildren,
550
+ });
551
+ }
552
+ return updatedNode;
553
+ };
554
+ return (React.createElement(React.Fragment, null, React.Children.map(props.children, processElement)));
555
+ };
556
+
518
557
  const accordianExpandTimeMs = 250;
519
558
  const accordianMaxHeight = 1020;
520
559
  const accordianTimingFunction = 'ease-in-out';
@@ -562,7 +601,7 @@ const Accordian = (props) => {
562
601
  }
563
602
  setOpen((_a = props.open) !== null && _a !== void 0 ? _a : false);
564
603
  }, [props.open]);
565
- return (React__namespace.createElement("div", { className: "accordian" },
604
+ return (React__namespace.createElement("div", { className: "accordian", "aria-expanded": open },
566
605
  React__namespace.createElement(Button, { readOnly: props.disabled, variant: props.variant, className: css.cx(css.css({
567
606
  display: 'flex',
568
607
  alignItems: 'center',
@@ -581,7 +620,8 @@ const Accordian = (props) => {
581
620
  }, rightIcon: !props.disabled ? React__namespace.createElement(Icon, { id: open ? 'collapse' : 'expand' }) : undefined },
582
621
  React__namespace.createElement("span", null, props.header)),
583
622
  React__namespace.createElement("div", { ref: content, className: css.cx('accordian__body', contentStyles) },
584
- React__namespace.createElement("div", { className: expandedContentWrapperStyles }, props.children))));
623
+ React__namespace.createElement("div", { className: expandedContentWrapperStyles },
624
+ React__namespace.createElement(TabIndexContainer, { tabIndexValue: open ? 0 : -1 }, props.children)))));
585
625
  };
586
626
  const useAccordianState = (count, openIndex) => {
587
627
  const [panels, setShowPanel] = React__namespace.useState(new Array(count).fill(false).map((b, i) => {
@@ -987,13 +1027,14 @@ const defaultMaxShownValues = 7;
987
1027
  const buttonMarkerClass = 'ListItem__button';
988
1028
  const defaultOnPickFocusMs = 100;
989
1029
  const Autocomplete = (p) => {
990
- var _a;
1030
+ var _a, _b;
991
1031
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
992
1032
  const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "listItemButtonClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs"]);
993
1033
  const theme = useThemeSafely();
994
1034
  const element = React__namespace.useRef(null);
995
1035
  const input = React__namespace.useRef(null);
996
1036
  const list = React__namespace.useRef(null);
1037
+ const [selectedResultIndex, setSelectedResultIndex] = React__namespace.useState();
997
1038
  const maxShowValues = (_a = p.maxShownValues) !== null && _a !== void 0 ? _a : defaultMaxShownValues;
998
1039
  const displayOptions = React__namespace.useMemo(() => {
999
1040
  if (!p.allowScroll) {
@@ -1012,10 +1053,12 @@ const Autocomplete = (p) => {
1012
1053
  if (direction === -1) {
1013
1054
  buttonIndex = displayOptions.length - 1;
1014
1055
  }
1056
+ setSelectedResultIndex(buttonIndex);
1015
1057
  return (_a = list.current) === null || _a === void 0 ? void 0 : _a.querySelector(`.${buttonMarkerClass}${buttonIndex}`);
1016
1058
  }
1017
1059
  else {
1018
1060
  const nextIndex = fromIndex + direction;
1061
+ setSelectedResultIndex(nextIndex);
1019
1062
  if (nextIndex >= displayOptions.length || nextIndex < 0) {
1020
1063
  return (_b = input.current) !== null && _b !== void 0 ? _b : undefined;
1021
1064
  }
@@ -1039,6 +1082,7 @@ const Autocomplete = (p) => {
1039
1082
  if (p.round || theme.controls.borderRadius) {
1040
1083
  listBorderRadius = theme.controls.borderRadius || '0.5rem';
1041
1084
  }
1085
+ const id = (_b = p.id) !== null && _b !== void 0 ? _b : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
1042
1086
  const onPickValue = (v) => {
1043
1087
  var _a;
1044
1088
  // the TextInput will not respond to outer value changes if it has focus.
@@ -1086,8 +1130,9 @@ const Autocomplete = (p) => {
1086
1130
  }
1087
1131
  }
1088
1132
  (_c = p.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(p, e);
1089
- } })),
1090
- !!displayOptions.length && (React__namespace.createElement(List, { ref: list, className: css.cx(css.css({
1133
+ }, "aria-owns": id, "aria-expanded": !!displayOptions.length, "aria-autocomplete": "both", "aria-describedby": `${id}-aria-description` })),
1134
+ React__namespace.createElement("span", { id: `${id}-aria-description`, className: css.css({ display: "none" }) }, "When autocomplete results are available use up and down arrows to review and enter to select."),
1135
+ !!displayOptions.length && (React__namespace.createElement(List, { id: id, ref: list, role: "listbox", className: css.cx(css.css({
1091
1136
  position: 'absolute',
1092
1137
  width: '100%',
1093
1138
  border: theme.controls.border,
@@ -1110,7 +1155,7 @@ const Autocomplete = (p) => {
1110
1155
  }), p.listClassName) },
1111
1156
  displayOptions.map((v, listItemIndex) => {
1112
1157
  var _a;
1113
- return (React__namespace.createElement(ListItem, { key: v, variant: "full", className: p.listItemClassName },
1158
+ return (React__namespace.createElement(ListItem, { key: v, variant: "full", className: p.listItemClassName, role: "option", "aria-selected": selectedResultIndex === listItemIndex },
1114
1159
  React__namespace.createElement(Button, { title: ((_a = p.showOptionTextAsTitle) !== null && _a !== void 0 ? _a : true) ? v : undefined, onKeyDown: e => {
1115
1160
  var _a, _b;
1116
1161
  if (e.key === 'ArrowDown') {
@@ -1276,15 +1321,18 @@ const useLogger = (componentName, enabled) => {
1276
1321
  const portalId = 'backdrop';
1277
1322
  const BackdropContext = React.createContext({
1278
1323
  showing: false,
1279
- showCount: 0,
1280
1324
  portalId: portalId,
1325
+ children: [],
1281
1326
  setShow: () => {
1282
1327
  /* empty */
1283
1328
  }
1284
1329
  });
1330
+ function useBackdropContext() {
1331
+ return React.useContext(BackdropContext);
1332
+ }
1285
1333
  const BackdropContextProvider = (p) => {
1286
1334
  var _a;
1287
- const [showCount, setShowCount] = React.useState(0);
1335
+ const [modalChildren, setModalChildren] = React.useState([]);
1288
1336
  const log = useLogger('BackdropContextProvider', (_a = p.__debug) !== null && _a !== void 0 ? _a : false);
1289
1337
  if (p.__debug) {
1290
1338
  React.useEffect(() => {
@@ -1294,26 +1342,38 @@ const BackdropContextProvider = (p) => {
1294
1342
  };
1295
1343
  }, []);
1296
1344
  useIgnoreMount(() => {
1297
- log('showCount changed', showCount);
1298
- }, [showCount]);
1345
+ log('showCount changed', modalChildren);
1346
+ }, [modalChildren]);
1299
1347
  }
1300
1348
  return (React.createElement(BackdropContext.Provider, { value: {
1301
1349
  portalId: portalId,
1302
- showing: showCount > 0,
1303
- showCount: showCount,
1350
+ showing: modalChildren.length > 0,
1351
+ children: modalChildren.slice(),
1304
1352
  setShow: (show, from) => {
1305
1353
  if (show) {
1306
- setShowCount(s => {
1307
- const count = s + 1;
1308
- log(`setShow from ${from} ${s} -> ${count}`);
1309
- return count;
1354
+ setModalChildren(previousChildren => {
1355
+ const newChildren = [...previousChildren, from];
1356
+ log(`setModalChildren TRUE from ${JSON.stringify(from)}.`);
1357
+ return newChildren;
1310
1358
  });
1311
1359
  }
1312
1360
  else {
1313
- setShowCount(s => {
1314
- const count = Math.max(0, s - 1);
1315
- log(`setShow from ${from} ${s} -> ${count}`);
1316
- return count;
1361
+ setModalChildren(previousChildren => {
1362
+ let newChildren;
1363
+ if (from.key === backdropOverlayKey && !show) {
1364
+ if (previousChildren.length === 1) {
1365
+ newChildren = [];
1366
+ }
1367
+ else {
1368
+ newChildren = [...previousChildren];
1369
+ newChildren.pop();
1370
+ }
1371
+ }
1372
+ else {
1373
+ newChildren = previousChildren.filter(c => c.key !== from.key);
1374
+ }
1375
+ log(`setModalChildren FALSE from ${JSON.stringify(from)}.`);
1376
+ return newChildren;
1317
1377
  });
1318
1378
  }
1319
1379
  }
@@ -1328,9 +1388,10 @@ const BackdropContextProvider = (p) => {
1328
1388
  margin: 0,
1329
1389
  zIndex: 9999
1330
1390
  })) },
1331
- "Backdrop showCount: ",
1332
- showCount))));
1391
+ "Backdrop children: ",
1392
+ JSON.stringify(modalChildren, null, 4)))));
1333
1393
  };
1394
+ const backdropOverlayKey = 'BackdropOverlay';
1334
1395
  const BackdropOverlay = (p) => {
1335
1396
  var _a, _b;
1336
1397
  const context = React.useContext(BackdropContext);
@@ -1348,15 +1409,25 @@ const BackdropOverlay = (p) => {
1348
1409
  log('context.showing changed', context.showing);
1349
1410
  }, [context.showing]);
1350
1411
  }
1412
+ let zIndex = -1;
1413
+ if (context.showing) {
1414
+ zIndex = theme.zIndexes.backdrop;
1415
+ context.children.forEach(child => {
1416
+ if (child.zIndex > zIndex) {
1417
+ // -1 so it's always directly below the child
1418
+ zIndex = child.zIndex - 1;
1419
+ }
1420
+ });
1421
+ }
1351
1422
  return (React.createElement("div", { onClick: () => {
1352
- context === null || context === void 0 ? void 0 : context.setShow(false, 'BackdropOverlay');
1423
+ context === null || context === void 0 ? void 0 : context.setShow(false, { key: backdropOverlayKey, zIndex });
1353
1424
  log('onClick', 'setShow', false);
1354
1425
  }, id: context === null || context === void 0 ? void 0 : context.portalId, className: css.css({
1355
1426
  cursor: 'pointer',
1356
1427
  position: 'fixed',
1357
1428
  top: 0, right: 0, bottom: 0, left: 0,
1358
1429
  backgroundColor: theme.colors.backdrop,
1359
- zIndex: (context === null || context === void 0 ? void 0 : context.showing) ? theme.zIndexes.backdrop : -1,
1430
+ zIndex,
1360
1431
  visibility: (context === null || context === void 0 ? void 0 : context.showing) ? 'visible' : 'hidden',
1361
1432
  opacity: (context === null || context === void 0 ? void 0 : context.showing) ? 1 : 0,
1362
1433
  transition: `opacity ${showTimeMs}ms ease-in-out`,
@@ -1600,7 +1671,7 @@ const useScrollbarSize = (recalc) => {
1600
1671
 
1601
1672
  const Modal = (p) => {
1602
1673
  var _a, _b, _c, _d;
1603
- const backdrop = React.useContext(BackdropContext);
1674
+ const backdrop = useBackdropContext();
1604
1675
  const mouseDownElement = React.useRef(undefined);
1605
1676
  const theme = useThemeSafely();
1606
1677
  const hasHeader = p.closeButton || p.heading;
@@ -1641,13 +1712,14 @@ const Modal = (p) => {
1641
1712
  });
1642
1713
  }
1643
1714
  };
1715
+ const zIndex = theme.zIndexes.modal;
1644
1716
  React.useEffect(() => {
1645
1717
  log('mounted');
1646
1718
  return () => {
1647
1719
  var _a;
1648
1720
  if (showing.current) {
1649
1721
  log(`un-mount in progress and this modal is showing. decrement the backdrop and try to remove singleton body styles.`);
1650
- backdrop.setShow(false, (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal');
1722
+ backdrop.setShow(false, { key: (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', zIndex });
1651
1723
  log('backdrop.setShow', false);
1652
1724
  tryRemoveScrollStyles();
1653
1725
  }
@@ -1660,7 +1732,7 @@ const Modal = (p) => {
1660
1732
  useBooleanChanged((show, previousShow) => {
1661
1733
  var _a;
1662
1734
  log('show changed', `${previousShow !== null && previousShow !== void 0 ? previousShow : 'undefined'} > ${show}`);
1663
- backdrop.setShow(show, (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal');
1735
+ backdrop.setShow(show, { key: (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', zIndex });
1664
1736
  showing.current = show;
1665
1737
  log('backdrop.setShow', show);
1666
1738
  if (show) {
@@ -1688,7 +1760,7 @@ const Modal = (p) => {
1688
1760
  const modalBodyStyles = css.css({
1689
1761
  maxHeight: p.scrollable ? undefined : '99vh',
1690
1762
  overflow: 'hidden',
1691
- zIndex: theme.zIndexes.modal,
1763
+ zIndex,
1692
1764
  cursor: 'default',
1693
1765
  margin: '1rem',
1694
1766
  backgroundColor: p.noBackground ? undefined : theme.colors.modalBg,
@@ -2740,7 +2812,7 @@ const Nav = (props) => {
2740
2812
  const theme = useThemeSafely();
2741
2813
  const navWidth = (_a = props.navWidth) !== null && _a !== void 0 ? _a : theme.layout.navWidth;
2742
2814
  const totalNavOffset = `calc(${navWidth} + 20px)`;
2743
- const backdrop = React__namespace.useContext(BackdropContext);
2815
+ const backdrop = useBackdropContext();
2744
2816
  const log = useLogger(`Nav ${(_b = props.id) !== null && _b !== void 0 ? _b : '?'}`, (_c = props.__debug) !== null && _c !== void 0 ? _c : false);
2745
2817
  const slideMs = (_d = props.slideMs) !== null && _d !== void 0 ? _d : theme.timings.nav.slideMs;
2746
2818
  const slideRight = css.keyframes `
@@ -2767,6 +2839,7 @@ const Nav = (props) => {
2767
2839
  const classNavNotShowing = css.css `
2768
2840
  animation: ${slideLeft} ${slideMs}ms cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
2769
2841
  `;
2842
+ const zIndex = theme.zIndexes.nav;
2770
2843
  // the padding-top here is to offset the navs' content from the header. the shadow creeps over it.
2771
2844
  const navStyles = css.css `
2772
2845
  label: Nav;
@@ -2779,7 +2852,7 @@ const Nav = (props) => {
2779
2852
  width: ${navWidth};
2780
2853
  min-width: ${navWidth};
2781
2854
  box-shadow: 4px 2px 12px 6px rgba(0, 0, 0, 0.2);
2782
- z-index: ${theme.zIndexes.nav};
2855
+ z-index: ${zIndex};
2783
2856
  overflow-y: auto;
2784
2857
  .omniLink, .omniLink:active, .omniLink:focus, .omniLink:visited {
2785
2858
  color: ${theme.colors.navFont};
@@ -2794,7 +2867,7 @@ const Nav = (props) => {
2794
2867
  useBooleanChanged((current, previous) => {
2795
2868
  var _a;
2796
2869
  log('show changed', `${previous !== null && previous !== void 0 ? previous : 'undefined'} > ${current}`);
2797
- backdrop.setShow(current, (_a = props.id) !== null && _a !== void 0 ? _a : 'Nav');
2870
+ backdrop.setShow(current, { key: (_a = props.id) !== null && _a !== void 0 ? _a : 'Nav', zIndex });
2798
2871
  }, props.show);
2799
2872
  React__namespace.useLayoutEffect(() => {
2800
2873
  if (nav && nav.current) {
@@ -4034,7 +4107,7 @@ const TabHeader = (p) => {
4034
4107
  buttonContent = tab.name;
4035
4108
  }
4036
4109
  return (React__namespace.createElement("li", { key: index, className: css.cx(tabStyles, p.tabClassName) },
4037
- React__namespace.createElement(Button, { disabled: tabsChanging, className: buttonStyles, variant: buttonVariant, title: title, readOnly: active, onClick: () => {
4110
+ React__namespace.createElement(Button, { "aria-role": "tab", "aria-selected": active, disabled: tabsChanging, className: buttonStyles, variant: buttonVariant, title: title, readOnly: active, onClick: () => {
4038
4111
  const onChange = () => {
4039
4112
  var _a;
4040
4113
  setTabIndex(index);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mackin.com/styleguide",
3
- "version": "10.2.1",
3
+ "version": "10.2.4",
4
4
  "description": "",
5
5
  "main": "./index.js",
6
6
  "module": "./index.esm.js",