react-lightning-design-system
Version:
Salesforce Lightning Design System components built with React
552 lines (528 loc) • 22.5 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof3 = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PicklistItem = exports.Picklist = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
var _react = _interopRequireWildcard(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _FormElement = require("./FormElement");
var _Icon = require("./Icon");
var _AutoAlign = require("./AutoAlign");
var _util = require("./util");
var _ComponentSettings = require("./ComponentSettings");
var _hooks = require("./hooks");
var _common = require("./common");
var _excluded = ["id", "className", "value", "defaultValue", "opened", "defaultOpened", "multiSelect", "selectedText", "optionsSelectedText", "menuSize", "menuStyle", "disabled", "label", "required", "error", "cols", "tooltip", "tooltipIcon", "elementRef", "buttonRef", "dropdownRef", "onSelect", "onComplete", "onValueChange", "onBlur", "onKeyDown", "children"];
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof3(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
/**
* Recursively collect option values from PicklistItem components
*/
function collectOptionValues(children) {
return _react["default"].Children.map(children, function (child) {
if (! /*#__PURE__*/_react["default"].isValidElement(child)) {
return [];
}
var props = child.props;
var isPropsObject = (0, _typeof2["default"])(props) === 'object' && props !== null;
if (!isPropsObject) {
return [];
}
// Recursively check children for nested PicklistItems
if (child.type !== PicklistItem) {
return !('children' in props) ? [] : collectOptionValues(props.children);
}
// Check if this is specifically a PicklistItem component
if (!('value' in props) || typeof props.value !== 'string' && typeof props.value !== 'number') {
return [];
}
// Skip disabled items
if ('disabled' in props && props.disabled === true) {
return [];
}
return [props.value];
}).flat();
}
/**
* Recursively find selected item label from PicklistItem components
*/
function findSelectedItemLabel(children, selectedValue) {
return _react["default"].Children.map(children, function (child) {
if (! /*#__PURE__*/_react["default"].isValidElement(child)) {
return null;
}
var props = child.props;
var isPropsObject = (0, _typeof2["default"])(props) === 'object' && props !== null;
if (!isPropsObject) {
return null;
}
// Recursively check children for nested PicklistItems
if (child.type !== PicklistItem) {
return !('children' in props) ? null : findSelectedItemLabel(props.children, selectedValue);
}
// Check if this is specifically a PicklistItem component
if (!('value' in props) || props.value !== selectedValue) {
return null;
}
// Skip disabled items
if ('disabled' in props && props.disabled === true) {
return null;
}
// Safely access label and children properties with proper type checking
var label = 'label' in props ? props.label : undefined;
var itemChildren = 'children' in props ? props.children : undefined;
// Simple type check for React.ReactNode values
var labelValue = typeof label === 'string' || typeof label === 'number' || /*#__PURE__*/_react["default"].isValidElement(label) ? label : undefined;
var childrenValue = typeof itemChildren === 'string' || typeof itemChildren === 'number' || /*#__PURE__*/_react["default"].isValidElement(itemChildren) || Array.isArray(itemChildren) ? itemChildren : undefined;
return labelValue || childrenValue;
});
}
/**
*
*/
/**
*
*/
var PicklistContext = /*#__PURE__*/(0, _react.createContext)({
values: [],
onSelect: function onSelect() {
// noop
},
optionIdPrefix: ''
});
/**
*
*/
/**
*
*/
var Picklist = exports.Picklist = (0, _common.createFC)(function (props) {
var id_ = props.id,
className = props.className,
value_ = props.value,
defaultValue = props.defaultValue,
opened_ = props.opened,
defaultOpened = props.defaultOpened,
multiSelect = props.multiSelect,
_props$selectedText = props.selectedText,
selectedText = _props$selectedText === void 0 ? '' : _props$selectedText,
_props$optionsSelecte = props.optionsSelectedText,
optionsSelectedText = _props$optionsSelecte === void 0 ? '' : _props$optionsSelecte,
menuSize = props.menuSize,
menuStyle = props.menuStyle,
disabled = props.disabled,
label = props.label,
required = props.required,
error = props.error,
cols = props.cols,
tooltip = props.tooltip,
tooltipIcon = props.tooltipIcon,
elementRef_ = props.elementRef,
buttonRef_ = props.buttonRef,
dropdownRef_ = props.dropdownRef,
onSelect = props.onSelect,
onComplete = props.onComplete,
onValueChange = props.onValueChange,
onBlur_ = props.onBlur,
onKeyDown_ = props.onKeyDown,
children = props.children,
rprops = (0, _objectWithoutProperties2["default"])(props, _excluded);
var fallbackId = (0, _react.useId)();
var id = id_ !== null && id_ !== void 0 ? id_ : fallbackId;
var listboxId = "".concat(id, "-listbox");
var optionIdPrefix = "".concat((0, _react.useId)(), "-option");
var values_ = typeof value_ === 'undefined' ? undefined : value_ == null ? [] : Array.isArray(value_) ? value_ : [value_];
var defaultValues = typeof defaultValue === 'undefined' ? undefined : defaultValue == null ? [] : Array.isArray(defaultValue) ? defaultValue : [defaultValue];
var _useControlledValue = (0, _hooks.useControlledValue)(values_, defaultValues !== null && defaultValues !== void 0 ? defaultValues : []),
_useControlledValue2 = (0, _slicedToArray2["default"])(_useControlledValue, 2),
values = _useControlledValue2[0],
setValues = _useControlledValue2[1];
var _useControlledValue3 = (0, _hooks.useControlledValue)(opened_, defaultOpened !== null && defaultOpened !== void 0 ? defaultOpened : false),
_useControlledValue4 = (0, _slicedToArray2["default"])(_useControlledValue3, 2),
opened = _useControlledValue4[0],
setOpened = _useControlledValue4[1];
var _useState = (0, _react.useState)(),
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
focusedValue = _useState2[0],
setFocusedValue = _useState2[1];
var _useContext = (0, _react.useContext)(_ComponentSettings.ComponentSettingsContext),
getActiveElement = _useContext.getActiveElement;
// Memoized option values - recursively collected from PicklistItem components (excluding disabled items)
var optionValues = (0, _react.useMemo)(function () {
return collectOptionValues(children);
}, [children]);
// Get next option value for keyboard navigation
var getNextValue = (0, _react.useCallback)(function (currentValue) {
if (optionValues.length === 0) return undefined;
if (!currentValue) return optionValues[0];
var currentIndex = optionValues.indexOf(currentValue);
return optionValues[Math.min(currentIndex + 1, optionValues.length - 1)]; // not wrap around
}, [optionValues]);
// Get previous option value for keyboard navigation
var getPrevValue = (0, _react.useCallback)(function (currentValue) {
if (optionValues.length === 0) return undefined;
if (!currentValue) return optionValues[optionValues.length - 1];
var currentIndex = optionValues.indexOf(currentValue);
return optionValues[Math.max(currentIndex - 1, 0)]; // not wrap around
}, [optionValues]);
// Scroll focused element into view
var scrollFocusedElementIntoView = (0, _hooks.useEventCallback)(function (nextFocusedValue) {
if (!nextFocusedValue || !dropdownElRef.current) {
return;
}
var dropdownContainer = dropdownElRef.current;
var targetElement = dropdownContainer.querySelector("#".concat(CSS.escape("".concat(optionIdPrefix, "-").concat(nextFocusedValue))));
if (!(targetElement instanceof HTMLElement)) {
return;
}
targetElement.focus();
});
// Set initial focus when dropdown opens
(0, _react.useEffect)(function () {
if (opened && !focusedValue) {
// Focus on first selected value or first option
var initialFocus = values.length > 0 ? values[0] : optionValues[0];
setFocusedValue(initialFocus);
scrollFocusedElementIntoView(initialFocus);
} else if (!opened) {
// Reset focus when dropdown closes
setFocusedValue(undefined);
}
}, [opened, values, optionValues, focusedValue, scrollFocusedElementIntoView]);
var elRef = (0, _react.useRef)(null);
var elementRef = (0, _hooks.useMergeRefs)([elRef, elementRef_]);
var comboboxElRef = (0, _react.useRef)(null);
var buttonRef = (0, _hooks.useMergeRefs)([comboboxElRef, buttonRef_]);
var dropdownElRef = (0, _react.useRef)(null);
var dropdownRef = (0, _hooks.useMergeRefs)([dropdownElRef, dropdownRef_]);
var setPicklistValues = (0, _hooks.useEventCallback)(function (newValues) {
var prevValues = values;
setValues(newValues);
if (onValueChange && prevValues !== newValues) {
if (multiSelect) {
onValueChange(newValues, prevValues);
} else {
onValueChange(newValues.length > 0 ? newValues[0] : null, prevValues.length > 0 ? prevValues[0] : null);
}
}
});
var updateItemValue = (0, _hooks.useEventCallback)(function (itemValue) {
if (multiSelect) {
var newValues = (0, _toConsumableArray2["default"])(values);
// toggle value
if (newValues.indexOf(itemValue) === -1) {
// add value to array
newValues.push(itemValue);
} else {
// remove from array
newValues.splice(newValues.indexOf(itemValue), 1);
}
setPicklistValues(newValues);
} else {
// set only one value
setPicklistValues([itemValue]);
setOpened(false);
setTimeout(function () {
var _comboboxElRef$curren;
(_comboboxElRef$curren = comboboxElRef.current) === null || _comboboxElRef$curren === void 0 || _comboboxElRef$curren.focus();
onComplete === null || onComplete === void 0 || onComplete();
}, 10);
}
});
var isFocusedInComponent = (0, _hooks.useEventCallback)(function () {
var targetEl = getActiveElement();
return (0, _util.isElInChildren)(elRef.current, targetEl) || (0, _util.isElInChildren)(dropdownElRef.current, targetEl);
});
var onClick = (0, _hooks.useEventCallback)(function () {
if (!disabled) {
setOpened(function (opened) {
return !opened;
});
}
});
var onPicklistItemSelect = (0, _hooks.useEventCallback)(function (value) {
updateItemValue(value);
onSelect === null || onSelect === void 0 || onSelect(value);
});
var onBlur = (0, _hooks.useEventCallback)(function () {
setTimeout(function () {
if (!isFocusedInComponent()) {
setOpened(false);
onBlur_ === null || onBlur_ === void 0 || onBlur_();
onComplete === null || onComplete === void 0 || onComplete();
}
}, 10);
});
var onKeyDown = (0, _hooks.useEventCallback)(function (e) {
if (e.keyCode === 40) {
// down
e.preventDefault();
e.stopPropagation();
if (!opened) {
setOpened(true);
} else {
// Navigate to next option
var nextValue = getNextValue(focusedValue);
setFocusedValue(nextValue);
scrollFocusedElementIntoView(nextValue);
}
} else if (e.keyCode === 38) {
// up
e.preventDefault();
e.stopPropagation();
if (!opened) {
setOpened(true);
} else {
// Navigate to previous option
var _prevValue = getPrevValue(focusedValue);
setFocusedValue(_prevValue);
scrollFocusedElementIntoView(_prevValue);
}
} else if (e.keyCode === 9) {
// Tab or Shift+Tab
if (opened) {
e.preventDefault();
e.stopPropagation();
var currentIndex = focusedValue ? optionValues.indexOf(focusedValue) : -1;
if (e.shiftKey) {
// Shift+Tab - Navigate to previous option or close if at first
if (currentIndex <= 0) {
// At first option or no focus, close the picklist
setOpened(false);
onComplete === null || onComplete === void 0 || onComplete();
} else {
var _prevValue2 = getPrevValue(focusedValue);
setFocusedValue(_prevValue2);
scrollFocusedElementIntoView(_prevValue2);
}
} else {
// Tab - Navigate to next option or close if at last
if (currentIndex >= optionValues.length - 1) {
// At last option, close the picklist
setOpened(false);
onComplete === null || onComplete === void 0 || onComplete();
} else {
var _nextValue = getNextValue(focusedValue);
setFocusedValue(_nextValue);
scrollFocusedElementIntoView(_nextValue);
}
}
}
} else if (e.keyCode === 27) {
// ESC
e.preventDefault();
e.stopPropagation();
setOpened(false);
onComplete === null || onComplete === void 0 || onComplete();
} else if (e.keyCode === 13 || e.keyCode === 32) {
// Enter or Space
e.preventDefault();
e.stopPropagation();
if (opened && focusedValue != null) {
// Select focused option
onPicklistItemSelect(focusedValue);
} else {
setOpened(function (opened) {
return !opened;
});
}
}
onKeyDown_ === null || onKeyDown_ === void 0 || onKeyDown_(e);
});
// Memoized selected item label - displays count for multiple selections, label for single selection, or placeholder text
var selectedItemLabel = (0, _react.useMemo)(function () {
// many items selected
if (values.length > 1) {
return optionsSelectedText;
}
// one item
if (values.length === 1) {
var selectedValue = values[0];
var selected = findSelectedItemLabel(children, selectedValue);
return selected || selectedValue;
}
// zero items
return selectedText;
}, [values, optionsSelectedText, selectedText, children]);
var hasValue = values.length > 0;
var portalClassNames = (0, _classnames["default"])(className, 'react-slds-picklist', 'slds-combobox_container');
var containerClassNames = (0, _classnames["default"])(portalClassNames, 'slds-size_small');
var comboboxClassNames = (0, _classnames["default"])('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', {
'slds-is-open': opened
});
var inputClassNames = (0, _classnames["default"])('slds-input_faux', 'slds-combobox__input', {
'slds-has-focus': opened && !disabled,
'slds-combobox__input-value': hasValue,
'slds-is-disabled': disabled
});
var createDropdownClassNames = (0, _react.useCallback)(function (alignment) {
var _alignment = (0, _slicedToArray2["default"])(alignment, 2),
vertAlign = _alignment[0],
align = _alignment[1];
return (0, _classnames["default"])('slds-dropdown', vertAlign ? "slds-dropdown_".concat(vertAlign) : undefined, align ? "slds-dropdown_".concat(align) : undefined, menuSize ? "slds-dropdown_".concat(menuSize) : 'slds-dropdown_fluid');
}, [menuSize]);
var formElemProps = {
id: id,
label: label,
required: required,
error: error,
cols: cols,
tooltip: tooltip,
tooltipIcon: tooltipIcon,
elementRef: elementRef
};
var contextValue = {
values: values,
multiSelect: multiSelect,
onSelect: onPicklistItemSelect,
focusedValue: focusedValue,
optionIdPrefix: optionIdPrefix
};
return /*#__PURE__*/_react["default"].createElement(_FormElement.FormElement, formElemProps, /*#__PURE__*/_react["default"].createElement("div", {
className: containerClassNames
}, /*#__PURE__*/_react["default"].createElement("div", {
className: comboboxClassNames,
ref: elementRef
}, /*#__PURE__*/_react["default"].createElement("div", {
className: "slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right",
role: "none"
}, /*#__PURE__*/_react["default"].createElement("button", (0, _extends2["default"])({
type: "button",
ref: buttonRef,
role: "combobox",
tabIndex: disabled ? -1 : 0,
className: inputClassNames,
"aria-controls": listboxId,
"aria-expanded": opened,
"aria-haspopup": "listbox",
"aria-disabled": disabled,
"aria-activedescendant": focusedValue ? "".concat(optionIdPrefix, "-").concat(focusedValue) : undefined,
onClick: onClick,
onKeyDown: onKeyDown,
onBlur: onBlur,
disabled: disabled
}, rprops), /*#__PURE__*/_react["default"].createElement("span", {
className: "slds-truncate"
}, selectedItemLabel)), /*#__PURE__*/_react["default"].createElement(_Icon.Icon, {
containerClassName: "slds-input__icon slds-input__icon_right",
category: "utility",
icon: "down",
size: "x-small",
textColor: "default"
})), opened && /*#__PURE__*/_react["default"].createElement(_AutoAlign.AutoAlign, {
triggerSelector: ".react-slds-picklist",
alignmentStyle: "menu",
portalClassName: portalClassNames,
size: menuSize
}, function (_ref) {
var alignment = _ref.alignment,
autoAlignContentRef = _ref.autoAlignContentRef;
return /*#__PURE__*/_react["default"].createElement("div", {
id: listboxId,
className: createDropdownClassNames(alignment),
role: "listbox",
"aria-label": "Options",
tabIndex: 0,
"aria-busy": false,
ref: (0, _hooks.useMergeRefs)([dropdownRef, autoAlignContentRef]),
style: menuStyle
}, /*#__PURE__*/_react["default"].createElement("ul", {
className: "slds-listbox slds-listbox_vertical",
role: "presentation",
onKeyDown: onKeyDown,
onBlur: onBlur
}, /*#__PURE__*/_react["default"].createElement(PicklistContext.Provider, {
value: contextValue
}, children)));
}))));
}, {
isFormElement: true
});
/**
*
*/
/**
*
*/
var PicklistItem = exports.PicklistItem = function PicklistItem(_ref2) {
var label = _ref2.label,
selected_ = _ref2.selected,
value = _ref2.value,
disabled = _ref2.disabled,
icon = _ref2.icon,
iconRight = _ref2.iconRight,
divider = _ref2.divider,
onClick_ = _ref2.onClick,
children = _ref2.children;
var _useContext2 = (0, _react.useContext)(PicklistContext),
values = _useContext2.values,
multiSelect = _useContext2.multiSelect,
onSelect = _useContext2.onSelect,
focusedValue = _useContext2.focusedValue,
optionIdPrefix = _useContext2.optionIdPrefix;
var selected = selected_ !== null && selected_ !== void 0 ? selected_ : value != null ? values.indexOf(value) >= 0 : false;
var isFocused = value != null && focusedValue === value;
var onClick = (0, _hooks.useEventCallback)(function (e) {
if (disabled) {
return;
}
if (value != null) {
onSelect(value);
}
onClick_ === null || onClick_ === void 0 || onClick_(e);
});
var itemClassNames = (0, _classnames["default"])('slds-media', 'slds-listbox__option', 'slds-listbox__option_plain', 'slds-media_small', {
'slds-is-selected': selected,
'slds-has-focus': isFocused
});
var mainListItem = /*#__PURE__*/_react["default"].createElement("li", {
role: "presentation",
className: "slds-listbox__item"
}, /*#__PURE__*/_react["default"].createElement("div", {
id: value ? "".concat(optionIdPrefix, "-").concat(value) : undefined,
className: itemClassNames,
role: "option",
"aria-selected": selected,
"aria-checked": multiSelect ? selected : undefined,
"aria-disabled": disabled,
tabIndex: disabled ? undefined : 0,
onClick: onClick
}, /*#__PURE__*/_react["default"].createElement("span", {
className: "slds-media__figure slds-listbox__option-icon"
}, icon ? /*#__PURE__*/_react["default"].createElement(_Icon.Icon, {
category: "utility",
icon: icon,
size: "x-small"
}) : selected ? /*#__PURE__*/_react["default"].createElement(_Icon.Icon, {
category: "utility",
icon: "check",
size: "x-small",
textColor: "currentColor"
}) : null), /*#__PURE__*/_react["default"].createElement("span", {
className: "slds-media__body"
}, /*#__PURE__*/_react["default"].createElement("span", {
className: "slds-truncate",
title: String(label || children)
}, label || children)), iconRight && /*#__PURE__*/_react["default"].createElement("span", {
className: "slds-media__figure slds-media__figure_reverse"
}, /*#__PURE__*/_react["default"].createElement(_Icon.Icon, {
category: "utility",
icon: iconRight,
size: "x-small"
}))));
return /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, divider === 'top' && /*#__PURE__*/_react["default"].createElement("li", {
className: "slds-has-divider_".concat(divider, "-space"),
role: "separator"
}), mainListItem, divider === 'bottom' && /*#__PURE__*/_react["default"].createElement("li", {
className: "slds-has-divider_".concat(divider, "-space"),
role: "separator"
}));
};
//# sourceMappingURL=Picklist.js.map