@react-md/form
Version:
This package is for creating all the different form input types.
198 lines • 10.1 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, useCallback, useEffect, useRef } from "react";
import cn from "classnames";
import { List } from "@react-md/list";
import { ConditionalPortal } from "@react-md/portal";
import { useScaleTransition } from "@react-md/transition";
import { bem, DEFAULT_GET_ITEM_VALUE, MovementPresets, omit, scrollIntoView, useActiveDescendantMovement, } from "@react-md/utils";
import { Option } from "./Option";
import { defaultIsOptionDisabled, getOptionId as DEFAULT_GET_OPTION_ID, getOptionLabel as DEFAULT_GET_OPTION_LABEL, isListboxOptionProps, } from "./utils";
var block = bem("rmd-listbox");
var warned;
/**
* This component is used to render the list part of a `<select>` element with
* built-in accessibility and the ability to add custom styles. This should
* probably not be used much outside of `react-md` itself and the `Select`
* component, but I'm planning on adding support for an inline listbox at some
* point.
*/
export var Listbox = forwardRef(function Listbox(_a, nodeRef) {
var propClassName = _a.className, _b = _a.visible, visible = _b === void 0 ? true : _b, _c = _a.temporary, temporary = _c === void 0 ? false : _c, _d = _a.labelKey, labelKey = _d === void 0 ? "label" : _d, _e = _a.valueKey, valueKey = _e === void 0 ? "value" : _e, _f = _a.getOptionId, getOptionId = _f === void 0 ? DEFAULT_GET_OPTION_ID : _f, _g = _a.getOptionLabel, getOptionLabel = _g === void 0 ? DEFAULT_GET_OPTION_LABEL : _g, _h = _a.getOptionValue, getOptionValue = _h === void 0 ? DEFAULT_GET_ITEM_VALUE : _h, _j = _a.isOptionDisabled, isOptionDisabled = _j === void 0 ? defaultIsOptionDisabled : _j, _k = _a.disableMovementChange, disableMovementChange = _k === void 0 ? false : _k, onFocus = _a.onFocus, propOnKeyDown = _a.onKeyDown, name = _a.name, options = _a.options, value = _a.value, onChange = _a.onChange, propTabIndex = _a.tabIndex, portal = _a.portal, portalInto = _a.portalInto, portalIntoId = _a.portalIntoId, onRequestClose = _a.onRequestClose, timeout = _a.timeout, readOnly = _a.readOnly, classNames = _a.classNames, onEnter = _a.onEnter, onEntering = _a.onEntering, onEntered = _a.onEntered, onExit = _a.onExit, onExiting = _a.onExiting, onExited = _a.onExited, props = __rest(_a, ["className", "visible", "temporary", "labelKey", "valueKey", "getOptionId", "getOptionLabel", "getOptionValue", "isOptionDisabled", "disableMovementChange", "onFocus", "onKeyDown", "name", "options", "value", "onChange", "tabIndex", "portal", "portalInto", "portalIntoId", "onRequestClose", "timeout", "readOnly", "classNames", "onEnter", "onEntering", "onEntered", "onExit", "onExiting", "onExited"]);
var id = props.id;
var tabIndex = propTabIndex;
if (temporary) {
tabIndex = -1;
}
else if (typeof propTabIndex === "undefined") {
tabIndex = 0;
}
/**
* Gets the current index of the option that has the same value as the
* provided prop value.
*/
var getIndex = useCallback(function () {
return options.findIndex(function (option) { return value === getOptionValue(option, valueKey); });
}, [getOptionValue, options, value, valueKey]);
/**
* Conditionally calls the onChange callback with the new value and option if
* the value has changed. This will be called when:
* - the user presses the enter or space key while "focusing" an option
* - the user keyboard navigates to a new option while the
* `disableMovementChange` prop is `false`
* - the user clicks the option with a mouse or touch
*/
var handleChange = useCallback(function (index) {
if (readOnly) {
return;
}
var option = options[index];
if (!option || isOptionDisabled(option)) {
return;
}
var optionValue = getOptionValue(option, valueKey);
if (value !== optionValue) {
onChange(optionValue, options[index], {
id: id,
name: name,
value: value,
valueKey: valueKey,
options: options,
});
}
}, [
getOptionValue,
id,
isOptionDisabled,
name,
onChange,
options,
readOnly,
value,
valueKey,
]);
var handleKeyboardClick = useCallback(function (focusedIndex) {
handleChange(focusedIndex);
if (temporary && onRequestClose) {
onRequestClose();
}
}, [handleChange, onRequestClose, temporary]);
var _l = useActiveDescendantMovement(__assign(__assign({}, MovementPresets.VERTICAL_LISTBOX), { defaultFocusedIndex: getIndex, items: options, baseId: id, valueKey: labelKey, getId: getOptionId, getItemValue: function (option, key) {
if (!isListboxOptionProps(option)) {
return "".concat(option);
}
var search = option[key];
if (typeof search === "number" || typeof search === "string") {
return "".concat(search);
}
if (process.env.NODE_ENV !== "production") {
if (!warned) {
warned = new Set();
}
if (!warned.has(id)) {
/* eslint-disable no-console */
console.warn("A listbox with an id of \"".concat(id, "\" has an option that does not have a searchable label string. ") +
"Users will be unable to use the typeahead feature in the Listbox component until this is fixed. " +
"To fix this warning, you can use the `labelKey` prop on the `Listbox`/`Select` component to point " +
"to a string on the following option:", option);
warned.add(id);
}
}
return "";
}, onChange: function (data) {
if (disableMovementChange) {
return;
}
handleChange(data.index);
}, onEnter: handleKeyboardClick, onSpace: handleKeyboardClick, onKeyDown: function (event) {
if (propOnKeyDown) {
propOnKeyDown(event);
}
switch (event.key) {
case "Tab":
case "Escape":
if (event.key === "Escape") {
event.stopPropagation();
}
if (temporary && onRequestClose) {
onRequestClose();
}
break;
// no default
}
} })), activeId = _l.activeId, itemRefs = _l.itemRefs, onKeyDown = _l.onKeyDown, focusedIndex = _l.focusedIndex, setFocusedIndex = _l.setFocusedIndex;
var prevVisible = useRef(visible);
useEffect(function () {
if (prevVisible.current === visible) {
return;
}
prevVisible.current = visible;
// whenever it gains visibility, try to set the focused index to the
// current active value
if (visible) {
setFocusedIndex(getIndex());
}
}, [getIndex, setFocusedIndex, visible]);
var handleFocus = useCallback(function (event) {
if (onFocus) {
onFocus(event);
}
var item = itemRefs[focusedIndex] && itemRefs[focusedIndex].current;
if (item) {
scrollIntoView(event.currentTarget, item);
}
}, [focusedIndex, itemRefs, onFocus]);
var _m = useScaleTransition({
nodeRef: nodeRef,
timeout: timeout,
className: cn(block({ temporary: temporary }), propClassName),
classNames: classNames,
transitionIn: visible,
onEnter: onEnter,
onEntering: onEntering,
onEntered: onEntered,
onExit: onExit,
onExiting: onExiting,
onExited: onExited,
}), elementProps = _m.elementProps, rendered = _m.rendered;
return (_jsx(ConditionalPortal, __assign({ portal: portal, portalInto: portalInto, portalIntoId: portalIntoId }, { children: rendered && (_jsx(List, __assign({}, props, elementProps, { "aria-activedescendant": activeId, role: "listbox", tabIndex: tabIndex, onFocus: handleFocus, onKeyDown: onKeyDown }, { children: options.map(function (option, i) {
var optionId = getOptionId(id, i);
var optionValue = getOptionValue(option, valueKey);
var optionLabel = getOptionLabel(option, labelKey);
var optionProps;
if (isListboxOptionProps(option)) {
optionProps = omit(option, [labelKey, valueKey]);
}
var disabled = isOptionDisabled(option);
var onClick;
if (!readOnly && !disabled) {
onClick = function () {
handleChange(i);
setFocusedIndex(i);
};
}
return (_jsx(Option, __assign({ id: optionId, disabled: disabled }, optionProps, { ref: itemRefs[i], focused: optionId === activeId, selected: value === optionValue, onClick: onClick }, { children: optionLabel }), optionValue));
}) }))) })));
});
//# sourceMappingURL=Listbox.js.map