@mackin.com/styleguide 10.2.6 → 11.0.0

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 +9 -5
  2. package/index.esm.js +114 -82
  3. package/index.js +114 -82
  4. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -33,8 +33,12 @@ interface InputOnFocusProps {
33
33
  allowUpdateOnFocus?: boolean;
34
34
  }
35
35
 
36
- type BaseInputProps$1 = Omit<TextInputProps, 'value' | 'className' | 'wrapperClassName' | 'type' | 'showErrorDisplay'> & InputOnFocusProps;
36
+ type BaseInputProps$1 = Omit<TextInputProps, 'value' | 'className' | 'wrapperClassName' | 'type' | 'showErrorDisplay' | 'id' | 'aria-label'> & InputOnFocusProps;
37
37
  interface AutocompleteProps extends BaseInputProps$1 {
38
+ /** Required for ARIA. Will be used to add unique IDs to the options. */
39
+ id: string;
40
+ /** Required for ARIA. Will be applied to both the input and the popup listbox. */
41
+ ariaLabel: string;
38
42
  value: string | undefined;
39
43
  options: string[];
40
44
  /** Applied to the Autocomplete wrapper. */
@@ -43,15 +47,12 @@ interface AutocompleteProps extends BaseInputProps$1 {
43
47
  inputClassName?: string;
44
48
  listClassName?: string;
45
49
  listItemClassName?: string;
46
- listItemButtonClassName?: string;
47
50
  /** Limits what will be show in the autocomplete options. Default is 7. */
48
51
  maxShownValues?: number;
49
52
  /** Will enable scrolling in the results list. */
50
53
  allowScroll?: boolean;
51
54
  /** Delay before the input is re-focused after picking a value. Adjust if there are issues with the displayed input value after pick. Defaults to 100ms. */
52
55
  onPickFocusWaitMs?: number;
53
- /** The option `title` attribute will be filled with the option text. Defaults to `true`. */
54
- showOptionTextAsTitle?: boolean;
55
56
  onPick: (value: string | undefined, index?: number) => void;
56
57
  }
57
58
  declare const Autocomplete: (p: AutocompleteProps) => React.JSX.Element;
@@ -130,10 +131,11 @@ interface CalendarProps {
130
131
  declare const Calendar: (p: CalendarProps) => React.JSX.Element;
131
132
 
132
133
  interface CheckboxProps extends Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, 'checked' | 'onChange' | 'type'> {
134
+ /** Required for the internal label (ARIA). Pass `true` to `hideLabel` to treat this label as an aria-label. */
135
+ label: string;
133
136
  checked: boolean;
134
137
  onChange: (checked: boolean, event: React.ChangeEvent<HTMLInputElement>) => void;
135
138
  readOnly?: boolean;
136
- label?: string;
137
139
  checkedIcon?: string;
138
140
  uncheckedIcon?: string;
139
141
  /** Background color when checked based on the current theme. Mutually exclusive with 'checkedColor'. */
@@ -141,6 +143,8 @@ interface CheckboxProps extends Omit<React.DetailedHTMLProps<React.InputHTMLAttr
141
143
  /** Background color when checked. Mutually exclusive with 'checkedThemeColor'. */
142
144
  checkedColor?: string;
143
145
  tabIndex?: number | undefined;
146
+ /** Pass `true` to `hideLabel` to treat `label` as an aria-label. */
147
+ hideLabel?: boolean;
144
148
  }
145
149
  declare const Checkbox: (props: CheckboxProps) => React.JSX.Element;
146
150
 
package/index.esm.js CHANGED
@@ -972,18 +972,23 @@ const TabLocker = (props) => {
972
972
  } }, props.children));
973
973
  };
974
974
 
975
+ /*
976
+ ARIA info:
977
+ https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
978
+ https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/
979
+ This would be considered "List autocomplete with manual selection"
980
+ */
975
981
  const defaultMaxShownValues = 7;
976
- const buttonMarkerClass = 'ListItem__button';
977
982
  const defaultOnPickFocusMs = 100;
978
983
  const Autocomplete = (p) => {
979
- var _a, _b;
984
+ var _a;
980
985
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
981
- const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "listItemButtonClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs"]);
986
+ const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs"]);
982
987
  const theme = useThemeSafely();
983
988
  const element = React.useRef(null);
984
989
  const input = React.useRef(null);
985
990
  const list = React.useRef(null);
986
- const [selectedResultIndex, setSelectedResultIndex] = React.useState();
991
+ const [selectedResultIndex, setSelectedResultIndex] = React.useState(-1);
987
992
  const maxShowValues = (_a = p.maxShownValues) !== null && _a !== void 0 ? _a : defaultMaxShownValues;
988
993
  const displayOptions = React.useMemo(() => {
989
994
  if (!p.allowScroll) {
@@ -995,27 +1000,6 @@ const Autocomplete = (p) => {
995
1000
  const resultsText = React.useMemo(() => {
996
1001
  return `${getText("Showing")} ${displayOptions.length.toLocaleString()} ${getText("of")} ${p.options.length.toLocaleString()} ${getText("results")}.`;
997
1002
  }, [language, displayOptions, p.options]);
998
- const getNextTabElement = (fromIndex, direction) => {
999
- var _a, _b, _c;
1000
- if (fromIndex === -1) {
1001
- let buttonIndex = 0;
1002
- if (direction === -1) {
1003
- buttonIndex = displayOptions.length - 1;
1004
- }
1005
- setSelectedResultIndex(buttonIndex);
1006
- return (_a = list.current) === null || _a === void 0 ? void 0 : _a.querySelector(`.${buttonMarkerClass}${buttonIndex}`);
1007
- }
1008
- else {
1009
- const nextIndex = fromIndex + direction;
1010
- setSelectedResultIndex(nextIndex);
1011
- if (nextIndex >= displayOptions.length || nextIndex < 0) {
1012
- return (_b = input.current) !== null && _b !== void 0 ? _b : undefined;
1013
- }
1014
- else {
1015
- return (_c = list.current) === null || _c === void 0 ? void 0 : _c.querySelector(`.${buttonMarkerClass}${nextIndex}`);
1016
- }
1017
- }
1018
- };
1019
1003
  React.useEffect(() => {
1020
1004
  const clearItems = () => {
1021
1005
  if (p.options.length) {
@@ -1031,57 +1015,118 @@ const Autocomplete = (p) => {
1031
1015
  if (p.round || theme.controls.borderRadius) {
1032
1016
  listBorderRadius = theme.controls.borderRadius || '0.5rem';
1033
1017
  }
1034
- const id = (_b = p.id) !== null && _b !== void 0 ? _b : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
1035
- const onPickValue = (v) => {
1018
+ const onPickValue = (v, keepFocus = true) => {
1036
1019
  var _a;
1037
1020
  // the TextInput will not respond to outer value changes if it has focus.
1038
1021
  // here we clear first and then onPickValue will re-focus after all updates.
1039
1022
  (_a = input.current) === null || _a === void 0 ? void 0 : _a.blur();
1040
1023
  setTimeout(() => {
1041
- // blur is now complete
1042
1024
  var _a;
1025
+ // blur is now complete
1043
1026
  let index = v ? p.options.findIndex(o => o === v) : undefined;
1044
1027
  if (index !== undefined && index < 0) {
1045
1028
  index = undefined;
1046
1029
  }
1047
1030
  p.onPick(v, index);
1048
1031
  // wait for the re-render. the value will not update if the control has focus
1049
- setTimeout(() => {
1050
- var _a;
1051
- (_a = input.current) === null || _a === void 0 ? void 0 : _a.focus();
1052
- }, (_a = p.onPickFocusWaitMs) !== null && _a !== void 0 ? _a : defaultOnPickFocusMs);
1032
+ if (keepFocus) {
1033
+ setTimeout(() => {
1034
+ var _a;
1035
+ (_a = input.current) === null || _a === void 0 ? void 0 : _a.focus();
1036
+ }, (_a = p.onPickFocusWaitMs) !== null && _a !== void 0 ? _a : defaultOnPickFocusMs);
1037
+ }
1053
1038
  }, 0);
1054
1039
  };
1040
+ React.useEffect(() => {
1041
+ if (selectedResultIndex === -1) {
1042
+ return;
1043
+ }
1044
+ const element = document.getElementById(getOptionId(p.id, selectedResultIndex));
1045
+ if (element) {
1046
+ element.scrollIntoView();
1047
+ }
1048
+ }, [selectedResultIndex]);
1049
+ const popupId = `${p.id}_ul`;
1050
+ const listItemStyles = css({
1051
+ paddingLeft: theme.controls.padding,
1052
+ paddingRight: theme.controls.padding,
1053
+ backgroundColor: 'white',
1054
+ cursor: 'pointer',
1055
+ color: theme.colors.font,
1056
+ height: theme.controls.height,
1057
+ transition: theme.controls.transition,
1058
+ fontsize: '1rem',
1059
+ fontWeight: 'bold',
1060
+ ':hover': {
1061
+ filter: theme.controls.hoverBrightness
1062
+ }
1063
+ });
1064
+ const listItemFocusStyles = css({
1065
+ outline: 'none',
1066
+ boxShadow: theme.controls.focusOutlineShadow,
1067
+ // prevents box shadow clipping
1068
+ position: 'relative',
1069
+ zIndex: 2
1070
+ });
1071
+ const listItemTextStyles = css({
1072
+ lineHeight: theme.controls.height
1073
+ });
1055
1074
  return (React.createElement("div", { onClick: e => {
1056
1075
  e.stopPropagation();
1057
1076
  }, onKeyDown: e => {
1058
1077
  if (e.key === 'Escape') {
1059
1078
  onPickValue(undefined);
1079
+ setSelectedResultIndex(-1);
1060
1080
  }
1061
- }, ref: element, className: cx(css({
1062
- position: 'relative',
1063
- width: '100%',
1064
- label: 'Autocomplete'
1065
- }), p.className, 'autocomplete') },
1081
+ }, ref: element, className: cx(styles.autocomplete, p.className, 'autocomplete') },
1066
1082
  React.createElement(TabLocker, { disabled: !displayOptions.length, style: { position: 'relative' } },
1067
1083
  React.createElement(TextInput, Object.assign({}, inputProps, { showErrorDisplay: false, ref: input, value: p.value, className: p.inputClassName, wrapperClassName: p.inputWrapperClassName, onKeyDown: e => {
1068
- var _a, _b, _c;
1069
- if (displayOptions.length) {
1070
- if (e.key === 'ArrowDown') {
1071
- e.preventDefault();
1072
- e.stopPropagation();
1073
- (_a = getNextTabElement(-1, 1)) === null || _a === void 0 ? void 0 : _a.focus();
1084
+ let handled = false;
1085
+ switch (e.code) {
1086
+ case 'ArrowDown': {
1087
+ if (displayOptions.length) {
1088
+ let nextIndex = selectedResultIndex + 1;
1089
+ if (nextIndex >= displayOptions.length) {
1090
+ nextIndex = 0;
1091
+ }
1092
+ setSelectedResultIndex(nextIndex);
1093
+ handled = true;
1094
+ }
1095
+ break;
1074
1096
  }
1075
- else if (e.key === 'ArrowUp') {
1076
- e.preventDefault();
1077
- e.stopPropagation();
1078
- (_b = getNextTabElement(-1, -1)) === null || _b === void 0 ? void 0 : _b.focus();
1097
+ case 'ArrowUp': {
1098
+ if (displayOptions.length) {
1099
+ let nextIndex = selectedResultIndex - 1;
1100
+ if (nextIndex < 0) {
1101
+ nextIndex = displayOptions.length - 1;
1102
+ }
1103
+ setSelectedResultIndex(nextIndex);
1104
+ handled = true;
1105
+ }
1106
+ break;
1079
1107
  }
1108
+ case 'Enter': {
1109
+ let pickedValue = p.value;
1110
+ if (selectedResultIndex >= 0) {
1111
+ pickedValue = displayOptions[selectedResultIndex];
1112
+ }
1113
+ onPickValue(pickedValue);
1114
+ setSelectedResultIndex(-1);
1115
+ handled = true;
1116
+ break;
1117
+ }
1118
+ case 'Tab': {
1119
+ onPickValue(p.value, false);
1120
+ setSelectedResultIndex(-1);
1121
+ break;
1122
+ }
1123
+ }
1124
+ if (handled) {
1125
+ e.preventDefault();
1126
+ e.stopPropagation();
1080
1127
  }
1081
- (_c = p.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(p, e);
1082
- }, "aria-owns": id, "aria-expanded": !!displayOptions.length, "aria-autocomplete": "both", "aria-describedby": `${id}-aria-description` })),
1083
- 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."),
1084
- !!displayOptions.length && (React.createElement(List, { id: id, ref: list, role: "listbox", className: cx(css({
1128
+ }, role: "combobox", "aria-expanded": !!displayOptions.length, "aria-controls": popupId, "aria-autocomplete": "list", "aria-activedescendant": selectedResultIndex >= 0 ? getOptionId(p.id, selectedResultIndex) : undefined, "aria-label": p.ariaLabel })),
1129
+ !!displayOptions.length && (React.createElement(List, { id: popupId, ref: list, role: "listbox", "aria-label": p.ariaLabel, className: cx(css({
1085
1130
  position: 'absolute',
1086
1131
  width: '100%',
1087
1132
  border: theme.controls.border,
@@ -1090,11 +1135,11 @@ const Autocomplete = (p) => {
1090
1135
  backgroundColor: theme.colors.bg,
1091
1136
  marginTop: `-4px !important`,
1092
1137
  zIndex: theme.zIndexes.backdrop,
1093
- 'li:first-child button': {
1138
+ 'li:first-child': {
1094
1139
  borderTopRightRadius: listBorderRadius,
1095
1140
  borderTopLeftRadius: listBorderRadius,
1096
1141
  },
1097
- 'li:last-child button': {
1142
+ 'li:last-child': {
1098
1143
  borderBottomRightRadius: listBorderRadius,
1099
1144
  borderBottomLeftRadius: listBorderRadius,
1100
1145
  }
@@ -1103,36 +1148,23 @@ const Autocomplete = (p) => {
1103
1148
  maxHeight: `calc(${theme.controls.height} * ${maxShowValues})`
1104
1149
  }), p.listClassName) },
1105
1150
  displayOptions.map((v, listItemIndex) => {
1106
- var _a;
1107
- return (React.createElement(ListItem, { key: v, variant: "full", className: p.listItemClassName, role: "option", "aria-selected": selectedResultIndex === listItemIndex },
1108
- React.createElement(Button, { title: ((_a = p.showOptionTextAsTitle) !== null && _a !== void 0 ? _a : true) ? v : undefined, onKeyDown: e => {
1109
- var _a, _b;
1110
- if (e.key === 'ArrowDown') {
1111
- e.stopPropagation();
1112
- e.preventDefault();
1113
- (_a = getNextTabElement(listItemIndex, 1)) === null || _a === void 0 ? void 0 : _a.focus();
1114
- }
1115
- else if (e.key === 'ArrowUp') {
1116
- e.stopPropagation();
1117
- e.preventDefault();
1118
- (_b = getNextTabElement(listItemIndex, -1)) === null || _b === void 0 ? void 0 : _b.focus();
1119
- }
1120
- else if (e.key === 'Enter') {
1121
- e.stopPropagation();
1122
- // this will prevent the click event from firing in addition to this enter key event.
1123
- e.preventDefault();
1124
- onPickValue(v);
1125
- }
1126
- }, className: cx(buttonMarkerClass + listItemIndex, css({
1127
- borderRadius: 0,
1128
- }), p.listItemButtonClassName), onClick: () => {
1129
- onPickValue(v);
1130
- } },
1131
- React.createElement(Text, { tag: "div", ellipsis: true, align: "left" }, v))));
1151
+ const selected = selectedResultIndex === listItemIndex;
1152
+ return (React.createElement(ListItem, { key: v, variant: "full", className: cx(listItemStyles, selected ? listItemFocusStyles : undefined, p.listItemClassName), role: "option", id: getOptionId(p.id, listItemIndex), "aria-selected": selected },
1153
+ React.createElement(Text, { tag: "div", ellipsis: true, align: "left", className: listItemTextStyles }, v)));
1132
1154
  }),
1133
1155
  !p.allowScroll && displayOptions.length < p.options.length && (React.createElement(ListItem, { className: p.listItemClassName },
1134
1156
  React.createElement(Text, { tag: "div", italics: true, align: "center" }, resultsText))))))));
1135
1157
  };
1158
+ function getOptionId(baseId, optionIndex) {
1159
+ return `${baseId}_li_${optionIndex}`;
1160
+ }
1161
+ const styles = {
1162
+ autocomplete: css({
1163
+ position: 'relative',
1164
+ width: '100%',
1165
+ label: 'Autocomplete'
1166
+ }),
1167
+ };
1136
1168
 
1137
1169
  /** Returns a UID. Use this instead of a direct call to a library. */
1138
1170
  function createUid() {
@@ -1493,7 +1525,7 @@ const Calendar = (p) => {
1493
1525
 
1494
1526
  const Checkbox = (props) => {
1495
1527
  var _a;
1496
- const { onChange, label, checkedIcon, uncheckedIcon, checkedThemeColor, checkedColor, readOnly } = props, inputProps = __rest(props, ["onChange", "label", "checkedIcon", "uncheckedIcon", "checkedThemeColor", "checkedColor", "readOnly"]);
1528
+ const { onChange, label, checkedIcon, uncheckedIcon, checkedThemeColor, checkedColor, readOnly, hideLabel } = props, inputProps = __rest(props, ["onChange", "label", "checkedIcon", "uncheckedIcon", "checkedThemeColor", "checkedColor", "readOnly", "hideLabel"]);
1497
1529
  const selected = checkedIcon || 'selected';
1498
1530
  const unselected = uncheckedIcon || 'unselected';
1499
1531
  const theme = useThemeSafely();
@@ -1557,7 +1589,7 @@ const Checkbox = (props) => {
1557
1589
  `;
1558
1590
  return (React.createElement("span", { className: cx('checkbox', checkboxStyles, props.className) },
1559
1591
  React.createElement("label", { className: labelStyles },
1560
- React.createElement("input", Object.assign({}, inputProps, { tabIndex: readOnly ? -1 : (_a = props.tabIndex) !== null && _a !== void 0 ? _a : undefined, className: nativeCheckboxStyles, type: "checkbox", onChange: e => {
1592
+ React.createElement("input", Object.assign({}, inputProps, { "aria-label": hideLabel ? label : undefined, tabIndex: readOnly ? -1 : (_a = props.tabIndex) !== null && _a !== void 0 ? _a : undefined, className: nativeCheckboxStyles, type: "checkbox", onChange: e => {
1561
1593
  if (readOnly) {
1562
1594
  e.preventDefault();
1563
1595
  return;
@@ -1565,7 +1597,7 @@ const Checkbox = (props) => {
1565
1597
  return onChange(e.currentTarget.checked, e);
1566
1598
  } })),
1567
1599
  React.createElement(Icon, { className: cx('checkboxIcon', iconStyles), id: props.checked ? selected : unselected }),
1568
- label,
1600
+ !hideLabel && label,
1569
1601
  props.children)));
1570
1602
  };
1571
1603
 
package/index.js CHANGED
@@ -990,18 +990,23 @@ const TabLocker = (props) => {
990
990
  } }, props.children));
991
991
  };
992
992
 
993
+ /*
994
+ ARIA info:
995
+ https://www.w3.org/WAI/ARIA/apg/patterns/combobox/
996
+ https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/
997
+ This would be considered "List autocomplete with manual selection"
998
+ */
993
999
  const defaultMaxShownValues = 7;
994
- const buttonMarkerClass = 'ListItem__button';
995
1000
  const defaultOnPickFocusMs = 100;
996
1001
  const Autocomplete = (p) => {
997
- var _a, _b;
1002
+ var _a;
998
1003
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
999
- const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "listItemButtonClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs"]);
1004
+ const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs"]);
1000
1005
  const theme = useThemeSafely();
1001
1006
  const element = React__namespace.useRef(null);
1002
1007
  const input = React__namespace.useRef(null);
1003
1008
  const list = React__namespace.useRef(null);
1004
- const [selectedResultIndex, setSelectedResultIndex] = React__namespace.useState();
1009
+ const [selectedResultIndex, setSelectedResultIndex] = React__namespace.useState(-1);
1005
1010
  const maxShowValues = (_a = p.maxShownValues) !== null && _a !== void 0 ? _a : defaultMaxShownValues;
1006
1011
  const displayOptions = React__namespace.useMemo(() => {
1007
1012
  if (!p.allowScroll) {
@@ -1013,27 +1018,6 @@ const Autocomplete = (p) => {
1013
1018
  const resultsText = React__namespace.useMemo(() => {
1014
1019
  return `${getText("Showing")} ${displayOptions.length.toLocaleString()} ${getText("of")} ${p.options.length.toLocaleString()} ${getText("results")}.`;
1015
1020
  }, [language, displayOptions, p.options]);
1016
- const getNextTabElement = (fromIndex, direction) => {
1017
- var _a, _b, _c;
1018
- if (fromIndex === -1) {
1019
- let buttonIndex = 0;
1020
- if (direction === -1) {
1021
- buttonIndex = displayOptions.length - 1;
1022
- }
1023
- setSelectedResultIndex(buttonIndex);
1024
- return (_a = list.current) === null || _a === void 0 ? void 0 : _a.querySelector(`.${buttonMarkerClass}${buttonIndex}`);
1025
- }
1026
- else {
1027
- const nextIndex = fromIndex + direction;
1028
- setSelectedResultIndex(nextIndex);
1029
- if (nextIndex >= displayOptions.length || nextIndex < 0) {
1030
- return (_b = input.current) !== null && _b !== void 0 ? _b : undefined;
1031
- }
1032
- else {
1033
- return (_c = list.current) === null || _c === void 0 ? void 0 : _c.querySelector(`.${buttonMarkerClass}${nextIndex}`);
1034
- }
1035
- }
1036
- };
1037
1021
  React__namespace.useEffect(() => {
1038
1022
  const clearItems = () => {
1039
1023
  if (p.options.length) {
@@ -1049,57 +1033,118 @@ const Autocomplete = (p) => {
1049
1033
  if (p.round || theme.controls.borderRadius) {
1050
1034
  listBorderRadius = theme.controls.borderRadius || '0.5rem';
1051
1035
  }
1052
- const id = (_b = p.id) !== null && _b !== void 0 ? _b : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
1053
- const onPickValue = (v) => {
1036
+ const onPickValue = (v, keepFocus = true) => {
1054
1037
  var _a;
1055
1038
  // the TextInput will not respond to outer value changes if it has focus.
1056
1039
  // here we clear first and then onPickValue will re-focus after all updates.
1057
1040
  (_a = input.current) === null || _a === void 0 ? void 0 : _a.blur();
1058
1041
  setTimeout(() => {
1059
- // blur is now complete
1060
1042
  var _a;
1043
+ // blur is now complete
1061
1044
  let index = v ? p.options.findIndex(o => o === v) : undefined;
1062
1045
  if (index !== undefined && index < 0) {
1063
1046
  index = undefined;
1064
1047
  }
1065
1048
  p.onPick(v, index);
1066
1049
  // wait for the re-render. the value will not update if the control has focus
1067
- setTimeout(() => {
1068
- var _a;
1069
- (_a = input.current) === null || _a === void 0 ? void 0 : _a.focus();
1070
- }, (_a = p.onPickFocusWaitMs) !== null && _a !== void 0 ? _a : defaultOnPickFocusMs);
1050
+ if (keepFocus) {
1051
+ setTimeout(() => {
1052
+ var _a;
1053
+ (_a = input.current) === null || _a === void 0 ? void 0 : _a.focus();
1054
+ }, (_a = p.onPickFocusWaitMs) !== null && _a !== void 0 ? _a : defaultOnPickFocusMs);
1055
+ }
1071
1056
  }, 0);
1072
1057
  };
1058
+ React__namespace.useEffect(() => {
1059
+ if (selectedResultIndex === -1) {
1060
+ return;
1061
+ }
1062
+ const element = document.getElementById(getOptionId(p.id, selectedResultIndex));
1063
+ if (element) {
1064
+ element.scrollIntoView();
1065
+ }
1066
+ }, [selectedResultIndex]);
1067
+ const popupId = `${p.id}_ul`;
1068
+ const listItemStyles = css.css({
1069
+ paddingLeft: theme.controls.padding,
1070
+ paddingRight: theme.controls.padding,
1071
+ backgroundColor: 'white',
1072
+ cursor: 'pointer',
1073
+ color: theme.colors.font,
1074
+ height: theme.controls.height,
1075
+ transition: theme.controls.transition,
1076
+ fontsize: '1rem',
1077
+ fontWeight: 'bold',
1078
+ ':hover': {
1079
+ filter: theme.controls.hoverBrightness
1080
+ }
1081
+ });
1082
+ const listItemFocusStyles = css.css({
1083
+ outline: 'none',
1084
+ boxShadow: theme.controls.focusOutlineShadow,
1085
+ // prevents box shadow clipping
1086
+ position: 'relative',
1087
+ zIndex: 2
1088
+ });
1089
+ const listItemTextStyles = css.css({
1090
+ lineHeight: theme.controls.height
1091
+ });
1073
1092
  return (React__namespace.createElement("div", { onClick: e => {
1074
1093
  e.stopPropagation();
1075
1094
  }, onKeyDown: e => {
1076
1095
  if (e.key === 'Escape') {
1077
1096
  onPickValue(undefined);
1097
+ setSelectedResultIndex(-1);
1078
1098
  }
1079
- }, ref: element, className: css.cx(css.css({
1080
- position: 'relative',
1081
- width: '100%',
1082
- label: 'Autocomplete'
1083
- }), p.className, 'autocomplete') },
1099
+ }, ref: element, className: css.cx(styles.autocomplete, p.className, 'autocomplete') },
1084
1100
  React__namespace.createElement(TabLocker, { disabled: !displayOptions.length, style: { position: 'relative' } },
1085
1101
  React__namespace.createElement(TextInput, Object.assign({}, inputProps, { showErrorDisplay: false, ref: input, value: p.value, className: p.inputClassName, wrapperClassName: p.inputWrapperClassName, onKeyDown: e => {
1086
- var _a, _b, _c;
1087
- if (displayOptions.length) {
1088
- if (e.key === 'ArrowDown') {
1089
- e.preventDefault();
1090
- e.stopPropagation();
1091
- (_a = getNextTabElement(-1, 1)) === null || _a === void 0 ? void 0 : _a.focus();
1102
+ let handled = false;
1103
+ switch (e.code) {
1104
+ case 'ArrowDown': {
1105
+ if (displayOptions.length) {
1106
+ let nextIndex = selectedResultIndex + 1;
1107
+ if (nextIndex >= displayOptions.length) {
1108
+ nextIndex = 0;
1109
+ }
1110
+ setSelectedResultIndex(nextIndex);
1111
+ handled = true;
1112
+ }
1113
+ break;
1092
1114
  }
1093
- else if (e.key === 'ArrowUp') {
1094
- e.preventDefault();
1095
- e.stopPropagation();
1096
- (_b = getNextTabElement(-1, -1)) === null || _b === void 0 ? void 0 : _b.focus();
1115
+ case 'ArrowUp': {
1116
+ if (displayOptions.length) {
1117
+ let nextIndex = selectedResultIndex - 1;
1118
+ if (nextIndex < 0) {
1119
+ nextIndex = displayOptions.length - 1;
1120
+ }
1121
+ setSelectedResultIndex(nextIndex);
1122
+ handled = true;
1123
+ }
1124
+ break;
1097
1125
  }
1126
+ case 'Enter': {
1127
+ let pickedValue = p.value;
1128
+ if (selectedResultIndex >= 0) {
1129
+ pickedValue = displayOptions[selectedResultIndex];
1130
+ }
1131
+ onPickValue(pickedValue);
1132
+ setSelectedResultIndex(-1);
1133
+ handled = true;
1134
+ break;
1135
+ }
1136
+ case 'Tab': {
1137
+ onPickValue(p.value, false);
1138
+ setSelectedResultIndex(-1);
1139
+ break;
1140
+ }
1141
+ }
1142
+ if (handled) {
1143
+ e.preventDefault();
1144
+ e.stopPropagation();
1098
1145
  }
1099
- (_c = p.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(p, e);
1100
- }, "aria-owns": id, "aria-expanded": !!displayOptions.length, "aria-autocomplete": "both", "aria-describedby": `${id}-aria-description` })),
1101
- 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."),
1102
- !!displayOptions.length && (React__namespace.createElement(List, { id: id, ref: list, role: "listbox", className: css.cx(css.css({
1146
+ }, role: "combobox", "aria-expanded": !!displayOptions.length, "aria-controls": popupId, "aria-autocomplete": "list", "aria-activedescendant": selectedResultIndex >= 0 ? getOptionId(p.id, selectedResultIndex) : undefined, "aria-label": p.ariaLabel })),
1147
+ !!displayOptions.length && (React__namespace.createElement(List, { id: popupId, ref: list, role: "listbox", "aria-label": p.ariaLabel, className: css.cx(css.css({
1103
1148
  position: 'absolute',
1104
1149
  width: '100%',
1105
1150
  border: theme.controls.border,
@@ -1108,11 +1153,11 @@ const Autocomplete = (p) => {
1108
1153
  backgroundColor: theme.colors.bg,
1109
1154
  marginTop: `-4px !important`,
1110
1155
  zIndex: theme.zIndexes.backdrop,
1111
- 'li:first-child button': {
1156
+ 'li:first-child': {
1112
1157
  borderTopRightRadius: listBorderRadius,
1113
1158
  borderTopLeftRadius: listBorderRadius,
1114
1159
  },
1115
- 'li:last-child button': {
1160
+ 'li:last-child': {
1116
1161
  borderBottomRightRadius: listBorderRadius,
1117
1162
  borderBottomLeftRadius: listBorderRadius,
1118
1163
  }
@@ -1121,36 +1166,23 @@ const Autocomplete = (p) => {
1121
1166
  maxHeight: `calc(${theme.controls.height} * ${maxShowValues})`
1122
1167
  }), p.listClassName) },
1123
1168
  displayOptions.map((v, listItemIndex) => {
1124
- var _a;
1125
- return (React__namespace.createElement(ListItem, { key: v, variant: "full", className: p.listItemClassName, role: "option", "aria-selected": selectedResultIndex === listItemIndex },
1126
- React__namespace.createElement(Button, { title: ((_a = p.showOptionTextAsTitle) !== null && _a !== void 0 ? _a : true) ? v : undefined, onKeyDown: e => {
1127
- var _a, _b;
1128
- if (e.key === 'ArrowDown') {
1129
- e.stopPropagation();
1130
- e.preventDefault();
1131
- (_a = getNextTabElement(listItemIndex, 1)) === null || _a === void 0 ? void 0 : _a.focus();
1132
- }
1133
- else if (e.key === 'ArrowUp') {
1134
- e.stopPropagation();
1135
- e.preventDefault();
1136
- (_b = getNextTabElement(listItemIndex, -1)) === null || _b === void 0 ? void 0 : _b.focus();
1137
- }
1138
- else if (e.key === 'Enter') {
1139
- e.stopPropagation();
1140
- // this will prevent the click event from firing in addition to this enter key event.
1141
- e.preventDefault();
1142
- onPickValue(v);
1143
- }
1144
- }, className: css.cx(buttonMarkerClass + listItemIndex, css.css({
1145
- borderRadius: 0,
1146
- }), p.listItemButtonClassName), onClick: () => {
1147
- onPickValue(v);
1148
- } },
1149
- React__namespace.createElement(Text, { tag: "div", ellipsis: true, align: "left" }, v))));
1169
+ const selected = selectedResultIndex === listItemIndex;
1170
+ return (React__namespace.createElement(ListItem, { key: v, variant: "full", className: css.cx(listItemStyles, selected ? listItemFocusStyles : undefined, p.listItemClassName), role: "option", id: getOptionId(p.id, listItemIndex), "aria-selected": selected },
1171
+ React__namespace.createElement(Text, { tag: "div", ellipsis: true, align: "left", className: listItemTextStyles }, v)));
1150
1172
  }),
1151
1173
  !p.allowScroll && displayOptions.length < p.options.length && (React__namespace.createElement(ListItem, { className: p.listItemClassName },
1152
1174
  React__namespace.createElement(Text, { tag: "div", italics: true, align: "center" }, resultsText))))))));
1153
1175
  };
1176
+ function getOptionId(baseId, optionIndex) {
1177
+ return `${baseId}_li_${optionIndex}`;
1178
+ }
1179
+ const styles = {
1180
+ autocomplete: css.css({
1181
+ position: 'relative',
1182
+ width: '100%',
1183
+ label: 'Autocomplete'
1184
+ }),
1185
+ };
1154
1186
 
1155
1187
  /** Returns a UID. Use this instead of a direct call to a library. */
1156
1188
  function createUid() {
@@ -1511,7 +1543,7 @@ const Calendar = (p) => {
1511
1543
 
1512
1544
  const Checkbox = (props) => {
1513
1545
  var _a;
1514
- const { onChange, label, checkedIcon, uncheckedIcon, checkedThemeColor, checkedColor, readOnly } = props, inputProps = __rest(props, ["onChange", "label", "checkedIcon", "uncheckedIcon", "checkedThemeColor", "checkedColor", "readOnly"]);
1546
+ const { onChange, label, checkedIcon, uncheckedIcon, checkedThemeColor, checkedColor, readOnly, hideLabel } = props, inputProps = __rest(props, ["onChange", "label", "checkedIcon", "uncheckedIcon", "checkedThemeColor", "checkedColor", "readOnly", "hideLabel"]);
1515
1547
  const selected = checkedIcon || 'selected';
1516
1548
  const unselected = uncheckedIcon || 'unselected';
1517
1549
  const theme = useThemeSafely();
@@ -1575,7 +1607,7 @@ const Checkbox = (props) => {
1575
1607
  `;
1576
1608
  return (React__namespace.createElement("span", { className: css.cx('checkbox', checkboxStyles, props.className) },
1577
1609
  React__namespace.createElement("label", { className: labelStyles },
1578
- React__namespace.createElement("input", Object.assign({}, inputProps, { tabIndex: readOnly ? -1 : (_a = props.tabIndex) !== null && _a !== void 0 ? _a : undefined, className: nativeCheckboxStyles, type: "checkbox", onChange: e => {
1610
+ React__namespace.createElement("input", Object.assign({}, inputProps, { "aria-label": hideLabel ? label : undefined, tabIndex: readOnly ? -1 : (_a = props.tabIndex) !== null && _a !== void 0 ? _a : undefined, className: nativeCheckboxStyles, type: "checkbox", onChange: e => {
1579
1611
  if (readOnly) {
1580
1612
  e.preventDefault();
1581
1613
  return;
@@ -1583,7 +1615,7 @@ const Checkbox = (props) => {
1583
1615
  return onChange(e.currentTarget.checked, e);
1584
1616
  } })),
1585
1617
  React__namespace.createElement(Icon, { className: css.cx('checkboxIcon', iconStyles), id: props.checked ? selected : unselected }),
1586
- label,
1618
+ !hideLabel && label,
1587
1619
  props.children)));
1588
1620
  };
1589
1621
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mackin.com/styleguide",
3
- "version": "10.2.6",
3
+ "version": "11.0.0",
4
4
  "description": "",
5
5
  "main": "./index.js",
6
6
  "module": "./index.esm.js",