@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.
- package/index.d.ts +9 -5
- package/index.esm.js +114 -82
- package/index.js +114 -82
- 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
|
|
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", "
|
|
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
|
|
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
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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(
|
|
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
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
1082
|
-
|
|
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
|
|
1138
|
+
'li:first-child': {
|
|
1094
1139
|
borderTopRightRadius: listBorderRadius,
|
|
1095
1140
|
borderTopLeftRadius: listBorderRadius,
|
|
1096
1141
|
},
|
|
1097
|
-
'li:last-child
|
|
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
|
-
|
|
1107
|
-
return (React.createElement(ListItem, { key: v, variant: "full", className: p.listItemClassName, role: "option", "aria-selected":
|
|
1108
|
-
React.createElement(
|
|
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
|
|
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", "
|
|
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
|
|
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
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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(
|
|
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
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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
|
-
|
|
1100
|
-
|
|
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
|
|
1156
|
+
'li:first-child': {
|
|
1112
1157
|
borderTopRightRadius: listBorderRadius,
|
|
1113
1158
|
borderTopLeftRadius: listBorderRadius,
|
|
1114
1159
|
},
|
|
1115
|
-
'li:last-child
|
|
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
|
-
|
|
1125
|
-
return (React__namespace.createElement(ListItem, { key: v, variant: "full", className: p.listItemClassName, role: "option", "aria-selected":
|
|
1126
|
-
React__namespace.createElement(
|
|
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
|
|