first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
@@ -0,0 +1,3 @@
import * as React from 'react';
import type { IDropdownProps } from './Dropdown.types';
export declare const DropdownBase: React.FunctionComponent<IDropdownProps>;
+914
View File
@@ -0,0 +1,914 @@
import { __assign, __extends, __spreadArray } from "tslib";
import * as React from 'react';
import { css, KeyCodes, classNamesFunction, divProperties, findIndex, getDocument, getFirstFocusable, getId, getLastFocusable, getNativeProps, initializeComponentRef, isIOS, isMac, mergeAriaAttributeValues, safeRequestAnimationFrame, warn, warnDeprecations, warnMutuallyExclusive, } from '../../Utilities';
import { Callout, DirectionalHint } from '../../Callout';
import { CommandButton } from '../../Button';
import { DropdownMenuItemType } from './Dropdown.types';
import { DropdownSizePosCache } from './utilities/DropdownSizePosCache';
import { FocusZone, FocusZoneDirection } from '../../FocusZone';
import { Icon } from '../../Icon';
import { Label } from '../../Label';
import { Panel } from '../../Panel';
import { ResponsiveMode, useResponsiveMode } from '../../ResponsiveMode';
import { SelectableOptionMenuItemType, getAllSelectedOptions } from '../../SelectableOption';
// import and use V7 Checkbox to ensure no breaking changes.
import { Checkbox } from '../../Checkbox';
import { getNextElement, getPreviousElement, getPropsWithDefaults } from '@fluentui/utilities';
import { useMergedRefs, usePrevious } from '@fluentui/react-hooks';
import { WindowContext } from '@fluentui/react-window-provider';
import { getDocumentEx, getWindowEx } from '../../utilities/dom';
var COMPONENT_NAME = 'Dropdown';
var getClassNames = classNamesFunction();
var DEFAULT_PROPS = {
options: [],
};
function useSelectedItemsState(_a) {
var defaultSelectedKeys = _a.defaultSelectedKeys, selectedKeys = _a.selectedKeys, defaultSelectedKey = _a.defaultSelectedKey, selectedKey = _a.selectedKey, options = _a.options, multiSelect = _a.multiSelect;
var oldOptions = usePrevious(options);
var _b = React.useState([]), selectedIndices = _b[0], setSelectedIndices = _b[1];
// In controlled component usage where selectedKey is provided, update the selectedIndex
// state if the key or options change.
var selectedKeyPropToUse;
// this does a shallow compare (assumes options are pure), for the purposes of determining whether
// defaultSelectedKey/defaultSelectedKeys are respected.
var didOptionsChange = options !== oldOptions;
if (multiSelect) {
if (didOptionsChange && defaultSelectedKeys !== undefined) {
selectedKeyPropToUse = defaultSelectedKeys;
}
else {
selectedKeyPropToUse = selectedKeys;
}
}
else {
if (didOptionsChange && defaultSelectedKey !== undefined) {
selectedKeyPropToUse = defaultSelectedKey;
}
else {
selectedKeyPropToUse = selectedKey;
}
}
var oldSelectedKeyProp = usePrevious(selectedKeyPropToUse);
React.useEffect(function () {
/** Get all selected indexes for multi-select mode */
var getSelectedIndexes = function () {
if (selectedKeyPropToUse === undefined) {
if (multiSelect) {
return getAllSelectedIndices();
}
var selectedIndex = getSelectedIndex(null);
return selectedIndex !== -1 ? [selectedIndex] : [];
}
else if (!Array.isArray(selectedKeyPropToUse)) {
var selectedIndex = getSelectedIndex(selectedKeyPropToUse);
return selectedIndex !== -1 ? [selectedIndex] : [];
}
var returnValue = [];
for (var _i = 0, selectedKeyPropToUse_1 = selectedKeyPropToUse; _i < selectedKeyPropToUse_1.length; _i++) {
var key = selectedKeyPropToUse_1[_i];
var selectedIndex = getSelectedIndex(key);
selectedIndex !== -1 && returnValue.push(selectedIndex);
}
return returnValue;
};
var getAllSelectedIndices = function () {
return options
.map(function (option, index) { return (option.selected ? index : -1); })
.filter(function (index) { return index !== -1; });
};
var getSelectedIndex = function (searchKey) {
return findIndex(options, function (option) {
// eslint-disable-next-line eqeqeq
if (searchKey != null) {
return option.key === searchKey;
}
else {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return !!option.selected || !!option.isSelected;
}
});
};
if ((selectedKeyPropToUse !== undefined || !oldOptions) &&
(selectedKeyPropToUse !== oldSelectedKeyProp || didOptionsChange)) {
setSelectedIndices(getSelectedIndexes());
}
}, [didOptionsChange, multiSelect, oldOptions, oldSelectedKeyProp, options, selectedKeyPropToUse]);
return [selectedIndices, setSelectedIndices];
}
export var DropdownBase = React.forwardRef(function (propsWithoutDefaults, forwardedRef) {
var props = getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults);
var rootRef = React.useRef(null);
var mergedRootRef = useMergedRefs(forwardedRef, rootRef);
var responsiveMode = useResponsiveMode(rootRef, props.responsiveMode);
var _a = useSelectedItemsState(props), selectedIndices = _a[0], setSelectedIndices = _a[1];
return (React.createElement(DropdownInternal, __assign({}, props, { responsiveMode: responsiveMode, hoisted: { rootRef: mergedRootRef, selectedIndices: selectedIndices, setSelectedIndices: setSelectedIndices } })));
});
DropdownBase.displayName = 'DropdownBase';
var DropdownInternal = /** @class */ (function (_super) {
__extends(DropdownInternal, _super);
function DropdownInternal(props) {
var _this = _super.call(this, props) || this;
_this._host = React.createRef();
_this._focusZone = React.createRef();
_this._dropDown = React.createRef();
_this._scrollIdleDelay = 250 /* ms */;
_this._sizePosCache = new DropdownSizePosCache();
_this._requestAnimationFrame = safeRequestAnimationFrame(_this);
/**
* Close menu callout if it is open
*/
_this.dismissMenu = function () {
var isOpen = _this.state.isOpen;
isOpen && _this.setState({ isOpen: false });
};
_this._onChange = function (event, options, index, checked, multiSelect) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
var _a = _this.props, onChange = _a.onChange, onChanged = _a.onChanged;
if (onChange || onChanged) {
// for single-select, option passed in will always be selected.
// for multi-select, flip the checked value
var changedOpt = multiSelect ? __assign(__assign({}, options[index]), { selected: !checked }) : options[index];
onChange && onChange(__assign(__assign({}, event), { target: _this._dropDown.current }), changedOpt, index);
onChanged && onChanged(changedOpt, index);
}
};
/** Get either props.placeholder (new name) or props.placeHolder (old name) */
_this._getPlaceholder = function () {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return _this.props.placeholder || _this.props.placeHolder;
};
/** Get text in dropdown input as a string */
_this._getTitle = function (items, _unused) {
var _a = _this.props.multiSelectDelimiter, multiSelectDelimiter = _a === void 0 ? ', ' : _a;
return items.map(function (i) { return i.text; }).join(multiSelectDelimiter);
};
/** Render text in dropdown input */
_this._onRenderTitle = function (items) {
return React.createElement(React.Fragment, null, _this._getTitle(items));
};
/** Render placeholder text in dropdown input */
_this._onRenderPlaceholder = function (props) {
if (!_this._getPlaceholder()) {
return null;
}
return React.createElement(React.Fragment, null, _this._getPlaceholder());
};
/** Render Callout or Panel container and pass in list */
_this._onRenderContainer = function (props) {
var calloutProps = props.calloutProps, panelProps = props.panelProps;
var _a = _this.props, responsiveMode = _a.responsiveMode, dropdownWidth = _a.dropdownWidth;
var isSmall = responsiveMode <= ResponsiveMode.medium;
var focusTrapZoneProps = { firstFocusableTarget: "#".concat(_this._listId, "1") };
var panelStyles = _this._classNames.subComponentStyles
? _this._classNames.subComponentStyles.panel
: undefined;
var calloutWidth = undefined;
var calloutMinWidth = undefined;
if (dropdownWidth === 'auto') {
calloutMinWidth = _this._dropDown.current ? _this._dropDown.current.clientWidth : 0;
}
else {
calloutWidth = dropdownWidth || (_this._dropDown.current ? _this._dropDown.current.clientWidth : 0);
}
return isSmall ? (React.createElement(Panel, __assign({ closeButtonAriaLabel: "Close", focusTrapZoneProps: focusTrapZoneProps, hasCloseButton: true, isOpen: true, isLightDismiss: true, onDismiss: _this._onDismiss, styles: panelStyles }, panelProps), _this._renderFocusableList(props))) : (React.createElement(Callout, __assign({ isBeakVisible: false, gapSpace: 0, doNotLayer: false, directionalHintFixed: false, directionalHint: DirectionalHint.bottomLeftEdge, calloutWidth: calloutWidth, calloutMinWidth: calloutMinWidth }, calloutProps, { className: _this._classNames.callout, target: _this._dropDown.current, onDismiss: _this._onDismiss, onScroll: _this._onScroll, onPositioned: _this._onPositioned }), _this._renderFocusableList(props)));
};
/** Render Caret Down Icon */
_this._onRenderCaretDown = function (props) {
return React.createElement(Icon, { className: _this._classNames.caretDown, iconName: "ChevronDown", "aria-hidden": true });
};
/** Render List of items */
_this._onRenderList = function (props) {
var _a = props.onRenderItem, onRenderItem = _a === void 0 ? _this._onRenderItem : _a;
var queue = { items: [] };
var renderedList = [];
var emptyQueue = function () {
var newGroup = queue.id
? [
React.createElement("div", { role: "group", key: queue.id, "aria-labelledby": queue.id }, queue.items),
]
: queue.items;
renderedList = __spreadArray(__spreadArray([], renderedList, true), newGroup, true);
// Flush items and id
queue = { items: [] };
};
var placeRenderedOptionIntoQueue = function (item, index) {
/*
Case Header
empty queue if it's not already empty
ensure unique ID for header and set queue ID
push header into queue
Case Divider
push divider into queue if not first item
empty queue if not already empty
Default
push item into queue
*/
switch (item.itemType) {
case SelectableOptionMenuItemType.Header:
queue.items.length > 0 && emptyQueue();
var id = _this._id + item.key;
queue.items.push(onRenderItem(__assign(__assign({ id: id }, item), { index: index }), _this._onRenderItem));
queue.id = id;
break;
case SelectableOptionMenuItemType.Divider:
index > 0 && queue.items.push(onRenderItem(__assign(__assign({}, item), { index: index }), _this._onRenderItem));
queue.items.length > 0 && emptyQueue();
break;
default:
queue.items.push(onRenderItem(__assign(__assign({}, item), { index: index }), _this._onRenderItem));
}
};
// Place options into the queue. Queue will be emptied anytime a Header or Divider is encountered
props.options.forEach(function (item, index) {
placeRenderedOptionIntoQueue(item, index);
});
// Push remaining items into all renderedList
queue.items.length > 0 && emptyQueue();
return React.createElement(React.Fragment, null, renderedList);
};
_this._onRenderItem = function (item) {
switch (item.itemType) {
case SelectableOptionMenuItemType.Divider:
return _this._renderSeparator(item);
case SelectableOptionMenuItemType.Header:
return _this._renderHeader(item);
default:
return _this._renderOption(item);
}
};
_this._renderOption = function (item) {
var _a;
var _b = _this.props, _c = _b.onRenderOption, onRenderOption = _c === void 0 ? _this._onRenderOption : _c, _d = _b.hoisted.selectedIndices, selectedIndices = _d === void 0 ? [] : _d;
var isItemSelected = item.index !== undefined && selectedIndices ? selectedIndices.indexOf(item.index) > -1 : false;
// select the right className based on the combination of selected/disabled
var itemClassName = item.hidden // predicate: item hidden
? _this._classNames.dropdownItemHidden
: isItemSelected && item.disabled === true // predicate: both selected and disabled
? _this._classNames.dropdownItemSelectedAndDisabled
: isItemSelected // predicate: selected only
? _this._classNames.dropdownItemSelected
: item.disabled === true // predicate: disabled only
? _this._classNames.dropdownItemDisabled
: _this._classNames.dropdownItem;
var title = item.title;
// define the id and label id (for multiselect checkboxes)
var id = _this._listId + item.index;
var labelId = (_a = item.id) !== null && _a !== void 0 ? _a : id + '-label';
var multiSelectItemStyles = _this._classNames.subComponentStyles
? _this._classNames.subComponentStyles.multiSelectItem
: undefined;
return !_this.props.multiSelect ? (React.createElement(CommandButton, { id: id, key: item.key, "data-index": item.index, "data-is-focusable": !item.disabled, disabled: item.disabled, className: itemClassName, onClick: _this._onItemClick(item),
// eslint-disable-next-line react/jsx-no-bind
onMouseEnter: _this._onItemMouseEnter.bind(_this, item),
// eslint-disable-next-line react/jsx-no-bind
onMouseLeave: _this._onMouseItemLeave.bind(_this, item),
// eslint-disable-next-line react/jsx-no-bind
onMouseMove: _this._onItemMouseMove.bind(_this, item), role: "option", "aria-selected": isItemSelected ? 'true' : 'false', ariaLabel: item.ariaLabel, title: title, "aria-posinset": _this._sizePosCache.positionInSet(item.index), "aria-setsize": _this._sizePosCache.optionSetSize }, onRenderOption(item, _this._onRenderOption))) : (React.createElement(Checkbox, { id: id, key: item.key, disabled: item.disabled, onChange: _this._onItemClick(item), inputProps: __assign({ 'aria-selected': isItemSelected, onMouseEnter: _this._onItemMouseEnter.bind(_this, item), onMouseLeave: _this._onMouseItemLeave.bind(_this, item), onMouseMove: _this._onItemMouseMove.bind(_this, item), role: 'option' }, {
'data-index': item.index,
'data-is-focusable': !(item.disabled || item.hidden),
}), label: item.text, title: title,
// eslint-disable-next-line react/jsx-no-bind
onRenderLabel: _this._onRenderItemLabel.bind(_this, __assign(__assign({}, item), { id: labelId })), className: css(itemClassName, 'is-multi-select'), checked: isItemSelected, styles: multiSelectItemStyles, ariaPositionInSet: !item.hidden ? _this._sizePosCache.positionInSet(item.index) : undefined, ariaSetSize: !item.hidden ? _this._sizePosCache.optionSetSize : undefined, ariaLabel: item.ariaLabel, ariaLabelledBy: item.ariaLabel ? undefined : labelId }));
};
/** Render content of item (i.e. text/icon inside of button) */
_this._onRenderOption = function (item) {
return React.createElement("span", { className: _this._classNames.dropdownOptionText }, item.text);
};
/*
* Render content of a multiselect item label.
* Text within the label is aria-hidden, to prevent duplicate input/label exposure
*/
_this._onRenderMultiselectOption = function (item) {
return (React.createElement("span", { id: item.id, "aria-hidden": "true", className: _this._classNames.dropdownOptionText }, item.text));
};
/** Render custom label for multiselect checkbox items */
_this._onRenderItemLabel = function (item) {
var _a = _this.props.onRenderOption, onRenderOption = _a === void 0 ? _this._onRenderMultiselectOption : _a;
return onRenderOption(item, _this._onRenderMultiselectOption);
};
_this._onPositioned = function (positions) {
if (_this._focusZone.current) {
// Focusing an element can trigger a reflow. Making this wait until there is an animation
// frame can improve perf significantly.
_this._requestAnimationFrame(function () {
var selectedIndices = _this.props.hoisted.selectedIndices;
if (_this._focusZone.current) {
if (!_this._hasBeenPositioned &&
selectedIndices &&
selectedIndices[0] &&
!_this.props.options[selectedIndices[0]].disabled) {
var element = getDocument().getElementById("".concat(_this._id, "-list").concat(selectedIndices[0]));
if (element) {
_this._focusZone.current.focusElement(element);
}
_this._hasBeenPositioned = true;
}
else {
_this._focusZone.current.focus();
}
}
});
}
if (!_this.state.calloutRenderEdge || _this.state.calloutRenderEdge !== positions.targetEdge) {
_this.setState({
calloutRenderEdge: positions.targetEdge,
});
}
};
_this._onItemClick = function (item) {
return function (event) {
if (!item.disabled) {
_this.setSelectedIndex(event, item.index);
if (!_this.props.multiSelect) {
// only close the callout when it's in single-select mode
_this.setState({
isOpen: false,
});
}
}
};
};
/**
* Scroll handler for the callout to make sure the mouse events
* for updating focus are not interacting during scroll
*/
_this._onScroll = function () {
var win = getWindowEx(_this.context); // can only be called on the client
if (!_this._isScrollIdle && _this._scrollIdleTimeoutId !== undefined) {
win.clearTimeout(_this._scrollIdleTimeoutId);
_this._scrollIdleTimeoutId = undefined;
}
else {
_this._isScrollIdle = false;
}
_this._scrollIdleTimeoutId = win.setTimeout(function () {
_this._isScrollIdle = true;
}, _this._scrollIdleDelay);
};
_this._onMouseItemLeave = function (item, ev) {
if (_this._shouldIgnoreMouseEvent()) {
return;
}
/**
* IE11 focus() method forces parents to scroll to top of element.
* Edge and IE expose a setActive() function for focusable divs that
* sets the page focus but does not scroll the parent element.
*/
if (_this._host.current) {
if (_this._host.current.setActive) {
try {
_this._host.current.setActive();
}
catch (e) {
/* no-op */
}
}
else {
_this._host.current.focus();
}
}
};
_this._onDismiss = function () {
_this.setState({ isOpen: false });
};
_this._onDropdownBlur = function (ev) {
// If Dropdown disabled do not proceed with this logic.
var disabled = _this._isDisabled();
if (disabled) {
return;
}
if (_this.state.isOpen) {
// Do not call onBlur or update focus state when the callout is opened
return;
}
_this.setState({ hasFocus: false });
if (_this.props.onBlur) {
_this.props.onBlur(ev);
}
};
_this._onDropdownKeyDown = function (ev) {
// If Dropdown disabled do not process any keyboard events.
var disabled = _this._isDisabled();
if (disabled) {
return;
}
// Take note if we are processing an alt (option) or meta (command) keydown.
// See comment in _shouldHandleKeyUp for reasoning.
_this._lastKeyDownWasAltOrMeta = _this._isAltOrMeta(ev);
if (_this.props.onKeyDown) {
_this.props.onKeyDown(ev);
if (ev.defaultPrevented) {
return;
}
}
var newIndex;
var selectedIndex = _this.props.hoisted.selectedIndices.length ? _this.props.hoisted.selectedIndices[0] : -1;
var containsExpandCollapseModifier = ev.altKey || ev.metaKey;
var isOpen = _this.state.isOpen;
// eslint-disable-next-line @typescript-eslint/no-deprecated
switch (ev.which) {
case KeyCodes.enter:
_this.setState({
isOpen: !isOpen,
});
break;
case KeyCodes.escape:
if (!isOpen) {
return;
}
_this.setState({
isOpen: false,
});
break;
case KeyCodes.up:
if (containsExpandCollapseModifier) {
if (isOpen) {
_this.setState({ isOpen: false });
break;
}
return;
}
if (_this.props.multiSelect) {
_this.setState({ isOpen: true });
}
else if (!_this._isDisabled()) {
newIndex = _this._moveIndex(ev, -1, selectedIndex - 1, selectedIndex);
}
break;
case KeyCodes.down:
if (containsExpandCollapseModifier) {
ev.stopPropagation();
ev.preventDefault();
}
if ((containsExpandCollapseModifier && !isOpen) || _this.props.multiSelect) {
_this.setState({ isOpen: true });
}
else if (!_this._isDisabled()) {
newIndex = _this._moveIndex(ev, 1, selectedIndex + 1, selectedIndex);
}
break;
case KeyCodes.home:
if (!_this.props.multiSelect) {
newIndex = _this._moveIndex(ev, 1, 0, selectedIndex);
}
break;
case KeyCodes.end:
if (!_this.props.multiSelect) {
newIndex = _this._moveIndex(ev, -1, _this.props.options.length - 1, selectedIndex);
}
break;
case KeyCodes.space:
// event handled in _onDropdownKeyUp
break;
default:
return;
}
if (newIndex !== selectedIndex) {
ev.stopPropagation();
ev.preventDefault();
}
};
_this._onDropdownKeyUp = function (ev) {
// If Dropdown disabled do not process any keyboard events.
var disabled = _this._isDisabled();
if (disabled) {
return;
}
var shouldHandleKey = _this._shouldHandleKeyUp(ev);
var isOpen = _this.state.isOpen;
if (_this.props.onKeyUp) {
_this.props.onKeyUp(ev);
if (ev.defaultPrevented) {
return;
}
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
switch (ev.which) {
case KeyCodes.space:
_this.setState({
isOpen: !isOpen,
});
break;
default:
if (shouldHandleKey && isOpen) {
_this.setState({ isOpen: false });
}
return;
}
ev.stopPropagation();
ev.preventDefault();
};
_this._onZoneKeyDown = function (ev) {
var _a, _b;
var elementToFocus;
// Take note if we are processing an alt (option) or meta (command) keydown.
// See comment in _shouldHandleKeyUp for reasoning.
_this._lastKeyDownWasAltOrMeta = _this._isAltOrMeta(ev);
var containsExpandCollapseModifier = ev.altKey || ev.metaKey;
// eslint-disable-next-line @typescript-eslint/no-deprecated
switch (ev.which) {
case KeyCodes.up:
if (containsExpandCollapseModifier) {
_this.setState({ isOpen: false });
}
else {
if (_this._host.current) {
elementToFocus = getLastFocusable(_this._host.current, _this._host.current.lastChild, true);
}
}
break;
// All directional keystrokes should be canceled when the zone is rendered.
// This avoids the body scroll from reacting and thus dismissing the dropdown.
case KeyCodes.home:
case KeyCodes.end:
case KeyCodes.pageUp:
case KeyCodes.pageDown:
break;
case KeyCodes.down:
if (!containsExpandCollapseModifier && _this._host.current) {
elementToFocus = getFirstFocusable(_this._host.current, _this._host.current.firstChild, true);
}
break;
case KeyCodes.escape:
_this.setState({ isOpen: false });
break;
case KeyCodes.tab:
_this.setState({ isOpen: false });
var document_1 = getDocument();
if (document_1) {
if (ev.shiftKey) {
(_a = getPreviousElement(document_1.body, _this._dropDown.current, false, false, true, true)) === null || _a === void 0 ? void 0 : _a.focus();
}
else {
(_b = getNextElement(document_1.body, _this._dropDown.current, false, false, true, true)) === null || _b === void 0 ? void 0 : _b.focus();
}
}
break;
default:
return;
}
if (elementToFocus) {
elementToFocus.focus();
}
ev.stopPropagation();
ev.preventDefault();
};
_this._onZoneKeyUp = function (ev) {
var shouldHandleKey = _this._shouldHandleKeyUp(ev);
if (shouldHandleKey && _this.state.isOpen) {
_this.setState({ isOpen: false });
ev.preventDefault();
}
};
_this._onDropdownClick = function (ev) {
if (_this.props.onClick) {
_this.props.onClick(ev);
if (ev.defaultPrevented) {
return;
}
}
var isOpen = _this.state.isOpen;
var disabled = _this._isDisabled();
if (!disabled && !_this._shouldOpenOnFocus()) {
_this.setState({
isOpen: !isOpen,
});
}
_this._isFocusedByClick = false; // reset
};
_this._onDropdownMouseDown = function () {
_this._isFocusedByClick = true;
};
_this._onFocus = function (ev) {
var disabled = _this._isDisabled();
if (!disabled) {
if (_this.props.onFocus) {
_this.props.onFocus(ev);
}
var state = { hasFocus: true };
if (_this._shouldOpenOnFocus()) {
state.isOpen = true;
}
_this.setState(state);
}
};
/**
* Because the isDisabled prop is deprecated, we have had to repeat this logic all over the place.
* This helper method avoids all the repetition.
*/
_this._isDisabled = function () {
var disabled = _this.props.disabled;
// eslint-disable-next-line @typescript-eslint/no-deprecated
var isDisabled = _this.props.isDisabled;
// Remove this deprecation workaround at 1.0.0
if (disabled === undefined) {
disabled = isDisabled;
}
return disabled;
};
_this._onRenderLabel = function (props) {
var label = props.label, required = props.required, disabled = props.disabled;
var labelStyles = _this._classNames.subComponentStyles
? _this._classNames.subComponentStyles.label
: undefined;
return label ? (React.createElement(Label, { className: _this._classNames.label, id: _this._labelId, required: required, styles: labelStyles, disabled: disabled }, label)) : null;
};
initializeComponentRef(_this);
var multiSelect = props.multiSelect, selectedKey = props.selectedKey, selectedKeys = props.selectedKeys, defaultSelectedKey = props.defaultSelectedKey, defaultSelectedKeys = props.defaultSelectedKeys, options = props.options;
if (process.env.NODE_ENV !== 'production') {
warnDeprecations(COMPONENT_NAME, props, {
isDisabled: 'disabled',
onChanged: 'onChange',
placeHolder: 'placeholder',
onRenderPlaceHolder: 'onRenderPlaceholder',
});
warnMutuallyExclusive(COMPONENT_NAME, props, {
defaultSelectedKey: 'selectedKey',
defaultSelectedKeys: 'selectedKeys',
selectedKeys: 'selectedKey',
});
if (multiSelect) {
var warnMultiSelect = function (prop) {
return warn("Dropdown property '".concat(prop, "' cannot be used when 'multiSelect' is true. Use '").concat(prop, "s' instead."));
};
if (selectedKey !== undefined) {
warnMultiSelect('selectedKey');
}
if (defaultSelectedKey !== undefined) {
warnMultiSelect('defaultSelectedKey');
}
}
else {
var warnNotMultiSelect = function (prop) {
return warn("Dropdown property '".concat(prop, "s' cannot be used when 'multiSelect' is false/unset. Use '").concat(prop, "' instead."));
};
if (selectedKeys !== undefined) {
warnNotMultiSelect('selectedKey');
}
if (defaultSelectedKeys !== undefined) {
warnNotMultiSelect('defaultSelectedKey');
}
}
}
_this._id = props.id || getId('Dropdown');
_this._labelId = _this._id + '-label';
_this._listId = _this._id + '-list';
_this._optionId = _this._id + '-option';
_this._isScrollIdle = true;
_this._hasBeenPositioned = false;
_this._sizePosCache.updateOptions(options);
_this.state = {
isOpen: false,
hasFocus: false,
calloutRenderEdge: undefined,
};
return _this;
}
Object.defineProperty(DropdownInternal.prototype, "selectedOptions", {
/**
* All selected options
*/
get: function () {
var _a = this.props, options = _a.options, selectedIndices = _a.hoisted.selectedIndices;
return getAllSelectedOptions(options, selectedIndices);
},
enumerable: false,
configurable: true
});
DropdownInternal.prototype.componentWillUnmount = function () {
clearTimeout(this._scrollIdleTimeoutId);
};
DropdownInternal.prototype.componentDidUpdate = function (prevProps, prevState) {
if (prevState.isOpen === true && this.state.isOpen === false) {
this._gotMouseMove = false;
this._hasBeenPositioned = false;
if (this.props.onDismiss) {
this.props.onDismiss();
}
}
};
DropdownInternal.prototype.render = function () {
var id = this._id;
var props = this.props;
var className = props.className, label = props.label, options = props.options, ariaLabel = props.ariaLabel, required = props.required, errorMessage = props.errorMessage, propStyles = props.styles, theme = props.theme, panelProps = props.panelProps, calloutProps = props.calloutProps, _a = props.onRenderTitle, onRenderTitle = _a === void 0 ? this._getTitle : _a, _b = props.onRenderContainer, onRenderContainer = _b === void 0 ? this._onRenderContainer : _b, _c = props.onRenderCaretDown, onRenderCaretDown = _c === void 0 ? this._onRenderCaretDown : _c, _d = props.onRenderLabel, onRenderLabel = _d === void 0 ? this._onRenderLabel : _d, _e = props.onRenderItem, onRenderItem = _e === void 0 ? this._onRenderItem : _e, selectedIndices = props.hoisted.selectedIndices, responsiveMode = props.responsiveMode;
var _f = this.state, isOpen = _f.isOpen, calloutRenderEdge = _f.calloutRenderEdge, hasFocus = _f.hasFocus;
// eslint-disable-next-line @typescript-eslint/no-deprecated
var onRenderPlaceholder = props.onRenderPlaceholder || props.onRenderPlaceHolder || this._getPlaceholder;
// If our cached options are out of date update our cache
if (options !== this._sizePosCache.cachedOptions) {
this._sizePosCache.updateOptions(options);
}
var selectedOptions = getAllSelectedOptions(options, selectedIndices);
var divProps = getNativeProps(props, divProperties);
var disabled = this._isDisabled();
var isSmall = responsiveMode <= ResponsiveMode.medium;
var errorMessageId = id + '-errorMessage';
this._classNames = getClassNames(propStyles, {
theme: theme,
className: className,
hasError: !!(errorMessage && errorMessage.length > 0),
hasLabel: !!label,
isOpen: isOpen,
required: required,
disabled: disabled,
isRenderingPlaceholder: !selectedOptions.length,
panelClassName: panelProps ? panelProps.className : undefined,
calloutClassName: calloutProps ? calloutProps.className : undefined,
calloutRenderEdge: calloutRenderEdge,
});
var hasErrorMessage = !!errorMessage && errorMessage.length > 0;
return (React.createElement("div", { className: this._classNames.root, ref: this.props.hoisted.rootRef, "aria-owns": isOpen && !isSmall ? this._listId : undefined },
onRenderLabel(this.props, this._onRenderLabel),
React.createElement("div", __assign({ "data-is-focusable": !disabled, "data-ktp-target": true, ref: this._dropDown, id: id, tabIndex: disabled ? -1 : 0, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen ? 'true' : 'false', "aria-label": ariaLabel, "aria-labelledby": label && !ariaLabel ? mergeAriaAttributeValues(this._labelId, this._optionId) : undefined, "aria-describedby": hasErrorMessage ? this._id + '-errorMessage' : undefined, "aria-required": required, "aria-disabled": disabled, "aria-invalid": hasErrorMessage, "aria-controls": isOpen && !isSmall ? this._listId : undefined }, divProps, { className: this._classNames.dropdown, onBlur: this._onDropdownBlur, onKeyDown: this._onDropdownKeyDown, onKeyUp: this._onDropdownKeyUp, onClick: this._onDropdownClick, onMouseDown: this._onDropdownMouseDown, onFocus: this._onFocus }),
React.createElement("span", { id: this._optionId, className: this._classNames.title, "aria-live": hasFocus ? 'polite' : undefined, "aria-atomic": hasFocus ? true : undefined },
// If option is selected render title, otherwise render the placeholder text
selectedOptions.length
? onRenderTitle(selectedOptions, this._onRenderTitle)
: onRenderPlaceholder(props, this._onRenderPlaceholder)),
React.createElement("span", { className: this._classNames.caretDownWrapper }, onRenderCaretDown(props, this._onRenderCaretDown))),
isOpen &&
onRenderContainer(__assign(__assign({}, props), { onDismiss: this._onDismiss, onRenderItem: onRenderItem }), this._onRenderContainer),
hasErrorMessage && (React.createElement("div", { role: "alert", id: errorMessageId, className: this._classNames.errorMessage }, errorMessage))));
};
DropdownInternal.prototype.focus = function (shouldOpenOnFocus) {
if (this._dropDown.current) {
this._dropDown.current.focus();
if (shouldOpenOnFocus) {
this.setState({
isOpen: true,
});
}
}
};
DropdownInternal.prototype.setSelectedIndex = function (event, index) {
var _a = this.props, options = _a.options, selectedKey = _a.selectedKey, selectedKeys = _a.selectedKeys, multiSelect = _a.multiSelect, notifyOnReselect = _a.notifyOnReselect, _b = _a.hoisted.selectedIndices, selectedIndices = _b === void 0 ? [] : _b;
var checked = selectedIndices ? selectedIndices.indexOf(index) > -1 : false;
var newIndexes = [];
index = Math.max(0, Math.min(options.length - 1, index));
// If this is a controlled component then no state change should take place.
if (selectedKey !== undefined || selectedKeys !== undefined) {
this._onChange(event, options, index, checked, multiSelect);
return;
}
if (!multiSelect && !notifyOnReselect && index === selectedIndices[0]) {
return;
}
else if (multiSelect) {
newIndexes = selectedIndices ? this._copyArray(selectedIndices) : [];
if (checked) {
var position = newIndexes.indexOf(index);
if (position > -1) {
// unchecked the current one
newIndexes.splice(position, 1);
}
}
else {
// add the new selected index into the existing one
newIndexes.push(index);
}
}
else {
// Set the selected option if this is an uncontrolled component
newIndexes = [index];
}
event.persist();
// Call onChange after state is updated
this.props.hoisted.setSelectedIndices(newIndexes);
this._onChange(event, options, index, checked, multiSelect);
};
DropdownInternal.prototype._copyArray = function (array) {
var newArray = [];
for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
var element = array_1[_i];
newArray.push(element);
}
return newArray;
};
/**
* Finds the next valid Dropdown option and sets the selected index to it.
* @param stepValue - Value of how many items the function should traverse. Should be -1 or 1.
* @param index - Index of where the search should start
* @param selectedIndex - The selectedIndex Dropdown's state
* @returns The next valid dropdown option's index
*/
DropdownInternal.prototype._moveIndex = function (event, stepValue, index, selectedIndex) {
var options = this.props.options;
// Return selectedIndex if nothing has changed or options is empty
if (selectedIndex === index || options.length === 0) {
return selectedIndex;
}
// If the user is pressing the up or down key we want to make
// sure that the dropdown cycles through the options without
// causing the screen to scroll. In _onDropdownKeyDown
// at the very end is a check to see if newIndex !== selectedIndex.
// If the index is less than 0 and we set it back to 0, then
// newIndex will equal selectedIndex and not stop the action
// of the key press happening and vice versa for indexes greater
// than or equal to the options length.
if (index >= options.length) {
index = 0;
}
else if (index < 0) {
index = options.length - 1;
}
var stepCounter = 0;
// If current index is a header or divider, or disabled, increment by step
while (options[index].itemType === DropdownMenuItemType.Header ||
options[index].itemType === DropdownMenuItemType.Divider ||
options[index].disabled) {
// If stepCounter exceeds length of options, then return selectedIndex (-1)
if (stepCounter >= options.length) {
return selectedIndex;
}
// If index + stepValue is out of bounds, wrap around
if (index + stepValue < 0) {
index = options.length;
}
else if (index + stepValue >= options.length) {
index = -1;
}
index = index + stepValue;
stepCounter++;
}
this.setSelectedIndex(event, index);
return index;
};
/** Wrap item list in a FocusZone */
DropdownInternal.prototype._renderFocusableList = function (props) {
var _a = props.onRenderList, onRenderList = _a === void 0 ? this._onRenderList : _a, label = props.label, ariaLabel = props.ariaLabel, multiSelect = props.multiSelect;
return (React.createElement("div", { className: this._classNames.dropdownItemsWrapper, onKeyDown: this._onZoneKeyDown, onKeyUp: this._onZoneKeyUp, ref: this._host, tabIndex: 0 },
React.createElement(FocusZone, { ref: this._focusZone, direction: FocusZoneDirection.vertical, id: this._listId, className: this._classNames.dropdownItems, role: "listbox", "aria-label": ariaLabel, "aria-labelledby": label && !ariaLabel ? this._labelId : undefined, "aria-multiselectable": multiSelect }, onRenderList(props, this._onRenderList))));
};
DropdownInternal.prototype._renderSeparator = function (item) {
var index = item.index, key = item.key;
var separatorClassName = item.hidden ? this._classNames.dropdownDividerHidden : this._classNames.dropdownDivider;
if (index > 0) {
return React.createElement("div", { role: "presentation", key: key, className: separatorClassName });
}
return null;
};
DropdownInternal.prototype._renderHeader = function (item) {
var _a = this.props.onRenderOption, onRenderOption = _a === void 0 ? this._onRenderOption : _a;
var key = item.key, id = item.id;
var headerClassName = item.hidden
? this._classNames.dropdownItemHeaderHidden
: this._classNames.dropdownItemHeader;
return (React.createElement("div", { id: id, key: key, className: headerClassName }, onRenderOption(item, this._onRenderOption)));
};
DropdownInternal.prototype._onItemMouseEnter = function (item, ev) {
if (this._shouldIgnoreMouseEvent()) {
return;
}
var targetElement = ev.currentTarget;
targetElement.focus();
};
DropdownInternal.prototype._onItemMouseMove = function (item, ev) {
var doc = getDocumentEx(this.context); // can only be called on the client
var targetElement = ev.currentTarget;
this._gotMouseMove = true;
if (!this._isScrollIdle || doc.activeElement === targetElement) {
return;
}
targetElement.focus();
};
DropdownInternal.prototype._shouldIgnoreMouseEvent = function () {
return !this._isScrollIdle || !this._gotMouseMove;
};
/**
* Returns true if the key for the event is alt (Mac option) or meta (Mac command).
*/
DropdownInternal.prototype._isAltOrMeta = function (ev) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return ev.which === KeyCodes.alt || ev.key === 'Meta';
};
/**
* We close the menu on key up only if ALL of the following are true:
* - Most recent key down was alt or meta (command)
* - The alt/meta key down was NOT followed by some other key (such as down/up arrow to
* expand/collapse the menu)
* - We're not on a Mac (or iOS)
*
* This is because on Windows, pressing alt moves focus to the application menu bar or similar,
* closing any open context menus. There is not a similar behavior on Macs.
*/
DropdownInternal.prototype._shouldHandleKeyUp = function (ev) {
var keyPressIsAltOrMetaAlone = this._lastKeyDownWasAltOrMeta && this._isAltOrMeta(ev);
this._lastKeyDownWasAltOrMeta = false;
return !!keyPressIsAltOrMetaAlone && !(isMac() || isIOS());
};
/**
* Returns true if dropdown should set to open on focus.
* Otherwise, isOpen state should be toggled on click
*/
DropdownInternal.prototype._shouldOpenOnFocus = function () {
var hasFocus = this.state.hasFocus;
var openOnKeyboardFocus = this.props.openOnKeyboardFocus;
return !this._isFocusedByClick && openOnKeyboardFocus === true && !hasFocus;
};
DropdownInternal.defaultProps = {
options: [],
};
DropdownInternal.contextType = WindowContext;
return DropdownInternal;
}(React.Component));
//# sourceMappingURL=Dropdown.base.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
import * as React from 'react';
import type { IDropdownProps } from './Dropdown.types';
export declare const Dropdown: React.FunctionComponent<IDropdownProps>;
+8
View File
@@ -0,0 +1,8 @@
import { styled } from '../../Utilities';
import { DropdownBase } from './Dropdown.base';
import { getStyles } from './Dropdown.styles';
export var Dropdown = styled(DropdownBase, getStyles, undefined, {
scope: 'Dropdown',
});
Dropdown.displayName = 'Dropdown';
//# sourceMappingURL=Dropdown.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Dropdown.js","sourceRoot":"../src/","sources":["components/Dropdown/Dropdown.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAG9C,MAAM,CAAC,IAAM,QAAQ,GAA4C,MAAM,CAIrE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE;IACpC,KAAK,EAAE,UAAU;CAClB,CAAC,CAAC;AACH,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAC","sourcesContent":["import * as React from 'react';\nimport { styled } from '../../Utilities';\nimport { DropdownBase } from './Dropdown.base';\nimport { getStyles } from './Dropdown.styles';\nimport type { IDropdownProps, IDropdownStyleProps, IDropdownStyles } from './Dropdown.types';\n\nexport const Dropdown: React.FunctionComponent<IDropdownProps> = styled<\n IDropdownProps,\n IDropdownStyleProps,\n IDropdownStyles\n>(DropdownBase, getStyles, undefined, {\n scope: 'Dropdown',\n});\nDropdown.displayName = 'Dropdown';\n"]}
@@ -0,0 +1,3 @@
import type { IDropdownStyles, IDropdownStyleProps } from './Dropdown.types';
import type { IStyleFunction } from '../../Utilities';
export declare const getStyles: IStyleFunction<IDropdownStyleProps, IDropdownStyles>;
+379
View File
@@ -0,0 +1,379 @@
var _a, _b, _c, _d, _e;
import { __assign, __spreadArray } from "tslib";
import { IsFocusVisibleClassName } from '../../Utilities';
import { RectangleEdge } from '../../Positioning';
import { FontWeights, HighContrastSelector, getGlobalClassNames, normalize, HighContrastSelectorWhite, getScreenSelector, ScreenWidthMinMedium, getHighContrastNoAdjustStyle, } from '../../Styling';
var GlobalClassNames = {
root: 'ms-Dropdown-container',
label: 'ms-Dropdown-label',
dropdown: 'ms-Dropdown',
title: 'ms-Dropdown-title',
caretDownWrapper: 'ms-Dropdown-caretDownWrapper',
caretDown: 'ms-Dropdown-caretDown',
callout: 'ms-Dropdown-callout',
panel: 'ms-Dropdown-panel',
dropdownItems: 'ms-Dropdown-items',
dropdownItem: 'ms-Dropdown-item',
dropdownDivider: 'ms-Dropdown-divider',
dropdownOptionText: 'ms-Dropdown-optionText',
dropdownItemHeader: 'ms-Dropdown-header',
titleIsPlaceHolder: 'ms-Dropdown-titleIsPlaceHolder',
titleHasError: 'ms-Dropdown-title--hasError',
};
var DROPDOWN_HEIGHT = 32;
var DROPDOWN_ITEM_HEIGHT = 36;
var highContrastAdjustMixin = (_a = {},
_a["".concat(HighContrastSelector, ", ").concat(HighContrastSelectorWhite.replace('@media ', ''))] = __assign({}, getHighContrastNoAdjustStyle()),
_a);
var highContrastItemAndTitleStateMixin = {
selectors: __assign((_b = {}, _b[HighContrastSelector] = (_c = {
backgroundColor: 'Highlight',
borderColor: 'Highlight',
color: 'HighlightText'
},
_c[".".concat(IsFocusVisibleClassName, " &:focus:after")] = {
borderColor: 'HighlightText',
},
_c), _b['.ms-Checkbox-checkbox'] = (_d = {},
_d[HighContrastSelector] = {
borderColor: 'HighlightText',
},
_d), _b), highContrastAdjustMixin),
};
var highContrastBorderState = {
selectors: (_e = {},
_e[HighContrastSelector] = {
borderColor: 'Highlight',
},
_e),
};
var MinimumScreenSelector = getScreenSelector(0, ScreenWidthMinMedium);
export var getStyles = function (props) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
var theme = props.theme, hasError = props.hasError, hasLabel = props.hasLabel, className = props.className, isOpen = props.isOpen, disabled = props.disabled, required = props.required, isRenderingPlaceholder = props.isRenderingPlaceholder, panelClassName = props.panelClassName, calloutClassName = props.calloutClassName, calloutRenderEdge = props.calloutRenderEdge;
if (!theme) {
throw new Error('theme is undefined or null in base Dropdown getStyles function.');
}
var globalClassnames = getGlobalClassNames(GlobalClassNames, theme);
var palette = theme.palette, semanticColors = theme.semanticColors, effects = theme.effects, fonts = theme.fonts;
var rootHoverFocusActiveSelectorNeutralDarkMixin = {
color: semanticColors.menuItemTextHovered,
};
var rootHoverFocusActiveSelectorNeutralPrimaryMixin = {
color: semanticColors.menuItemText,
};
var borderColorError = {
borderColor: semanticColors.errorText,
};
var dropdownItemStyle = [
globalClassnames.dropdownItem,
{
backgroundColor: 'transparent',
boxSizing: 'border-box',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
padding: '0 8px',
width: '100%',
minHeight: DROPDOWN_ITEM_HEIGHT,
lineHeight: 20,
height: 0,
position: 'relative',
border: '1px solid transparent',
borderRadius: 0,
wordWrap: 'break-word',
overflowWrap: 'break-word',
textAlign: 'left',
'.ms-Button-flexContainer': {
width: '100%',
},
},
];
var dropdownHeaderStyle = [
globalClassnames.dropdownItemHeader,
__assign(__assign({}, fonts.medium), { fontWeight: FontWeights.semibold, color: semanticColors.menuHeader, background: 'none', backgroundColor: 'transparent', border: 'none', height: DROPDOWN_ITEM_HEIGHT, lineHeight: DROPDOWN_ITEM_HEIGHT, cursor: 'default', padding: '0 8px', userSelect: 'none', textAlign: 'left', selectors: (_a = {},
_a[HighContrastSelector] = __assign({ color: 'GrayText' }, getHighContrastNoAdjustStyle()),
_a) }),
];
var selectedItemBackgroundColor = semanticColors.menuItemBackgroundPressed;
var itemSelectors = function (isSelected) {
var _a, _b;
if (isSelected === void 0) { isSelected = false; }
return {
selectors: (_a = {
'&:hover': [
{
color: semanticColors.menuItemTextHovered,
backgroundColor: !isSelected ? semanticColors.menuItemBackgroundHovered : selectedItemBackgroundColor,
},
highContrastItemAndTitleStateMixin,
],
'&.is-multi-select:hover': [
{ backgroundColor: !isSelected ? 'transparent' : selectedItemBackgroundColor },
highContrastItemAndTitleStateMixin,
],
'&:active:hover': [
{
color: semanticColors.menuItemTextHovered,
backgroundColor: !isSelected
? semanticColors.menuItemBackgroundPressed
: semanticColors.menuItemBackgroundHovered,
},
highContrastItemAndTitleStateMixin,
]
},
_a[".".concat(IsFocusVisibleClassName, " &:focus:after, :host(.").concat(IsFocusVisibleClassName, ") &:focus:after")] = (_b = {
left: 0,
top: 0,
bottom: 0,
right: 0
},
_b[HighContrastSelector] = {
inset: '2px',
},
_b),
_a[HighContrastSelector] = {
border: 'none',
},
_a),
};
};
var dropdownItemSelected = __spreadArray(__spreadArray([], dropdownItemStyle, true), [
{
backgroundColor: selectedItemBackgroundColor,
color: semanticColors.menuItemTextHovered,
},
itemSelectors(true),
highContrastItemAndTitleStateMixin,
], false);
var dropdownItemDisabled = __spreadArray(__spreadArray([], dropdownItemStyle, true), [
{
color: semanticColors.disabledText,
cursor: 'default',
selectors: (_b = {},
_b[HighContrastSelector] = {
color: 'GrayText',
border: 'none',
},
_b),
},
], false);
var titleOpenBorderRadius = calloutRenderEdge === RectangleEdge.bottom
? "".concat(effects.roundedCorner2, " ").concat(effects.roundedCorner2, " 0 0")
: "0 0 ".concat(effects.roundedCorner2, " ").concat(effects.roundedCorner2);
var calloutOpenBorderRadius = calloutRenderEdge === RectangleEdge.bottom
? "0 0 ".concat(effects.roundedCorner2, " ").concat(effects.roundedCorner2)
: "".concat(effects.roundedCorner2, " ").concat(effects.roundedCorner2, " 0 0");
return {
root: [globalClassnames.root, className],
label: globalClassnames.label,
dropdown: [
globalClassnames.dropdown,
normalize,
fonts.medium,
{
color: semanticColors.menuItemText,
borderColor: semanticColors.focusBorder,
position: 'relative',
outline: 0,
userSelect: 'none',
selectors: (_c = {},
_c['&:hover .' + globalClassnames.title] = [
!disabled && rootHoverFocusActiveSelectorNeutralDarkMixin,
{ borderColor: isOpen ? palette.neutralSecondary : palette.neutralPrimary },
highContrastBorderState,
],
_c['&:focus .' + globalClassnames.title] = [
!disabled && rootHoverFocusActiveSelectorNeutralDarkMixin,
{ selectors: (_d = {}, _d[HighContrastSelector] = { color: 'Highlight' }, _d) },
],
_c['&:focus:after'] = [
{
pointerEvents: 'none',
content: "''",
position: 'absolute',
boxSizing: 'border-box',
top: '0px',
left: '0px',
width: '100%',
height: '100%',
// see https://github.com/microsoft/fluentui/pull/9182 for semantic color disc
border: !disabled ? "2px solid ".concat(palette.themePrimary) : 'none',
borderRadius: '2px',
selectors: (_e = {},
_e[HighContrastSelector] = {
color: 'Highlight',
},
_e),
},
],
_c['&:active .' + globalClassnames.title] = [
!disabled && rootHoverFocusActiveSelectorNeutralDarkMixin,
{ borderColor: palette.themePrimary },
highContrastBorderState,
],
_c['&:hover .' + globalClassnames.caretDown] = !disabled && rootHoverFocusActiveSelectorNeutralPrimaryMixin,
_c['&:focus .' + globalClassnames.caretDown] = [
!disabled && rootHoverFocusActiveSelectorNeutralPrimaryMixin,
{ selectors: (_f = {}, _f[HighContrastSelector] = { color: 'Highlight' }, _f) },
],
_c['&:active .' + globalClassnames.caretDown] = !disabled && rootHoverFocusActiveSelectorNeutralPrimaryMixin,
_c['&:hover .' + globalClassnames.titleIsPlaceHolder] = !disabled && rootHoverFocusActiveSelectorNeutralPrimaryMixin,
_c['&:focus .' + globalClassnames.titleIsPlaceHolder] = !disabled && rootHoverFocusActiveSelectorNeutralPrimaryMixin,
_c['&:active .' + globalClassnames.titleIsPlaceHolder] = !disabled && rootHoverFocusActiveSelectorNeutralPrimaryMixin,
_c['&:hover .' + globalClassnames.titleHasError] = borderColorError,
_c['&:active .' + globalClassnames.titleHasError] = borderColorError,
_c),
},
isOpen && 'is-open',
disabled && 'is-disabled',
required && 'is-required',
required &&
!hasLabel && {
selectors: (_g = {
':before': {
content: "'*'",
color: semanticColors.errorText,
position: 'absolute',
top: -5,
right: -10,
}
},
_g[HighContrastSelector] = {
selectors: {
':after': {
right: -14, // moving the * 4 pixel to right to alleviate border clipping in HC mode.
},
},
},
_g),
},
],
title: [
globalClassnames.title,
normalize,
{
backgroundColor: semanticColors.inputBackground,
borderWidth: 1,
borderStyle: 'solid',
borderColor: semanticColors.inputBorder,
borderRadius: isOpen ? titleOpenBorderRadius : effects.roundedCorner2,
cursor: 'pointer',
display: 'block',
height: DROPDOWN_HEIGHT,
lineHeight: DROPDOWN_HEIGHT - 2,
padding: "0 28px 0 8px",
position: 'relative',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
isRenderingPlaceholder && [globalClassnames.titleIsPlaceHolder, { color: semanticColors.inputPlaceholderText }],
hasError && [globalClassnames.titleHasError, borderColorError],
disabled && {
backgroundColor: semanticColors.disabledBackground,
border: 'none',
color: semanticColors.disabledText,
cursor: 'default',
selectors: (_h = {},
_h[HighContrastSelector] = __assign({ border: '1px solid GrayText', color: 'GrayText', backgroundColor: 'Window' }, getHighContrastNoAdjustStyle()),
_h),
},
],
caretDownWrapper: [
globalClassnames.caretDownWrapper,
{
height: DROPDOWN_HEIGHT,
lineHeight: DROPDOWN_HEIGHT - 2, // height minus the border
paddingTop: 1,
position: 'absolute',
right: 8,
top: 0,
},
!disabled && {
cursor: 'pointer',
},
],
caretDown: [
globalClassnames.caretDown,
{ color: palette.neutralSecondary, fontSize: fonts.small.fontSize, pointerEvents: 'none' },
disabled && {
color: semanticColors.disabledText,
selectors: (_j = {},
_j[HighContrastSelector] = __assign({ color: 'GrayText' }, getHighContrastNoAdjustStyle()),
_j),
},
],
errorMessage: __assign(__assign({ color: semanticColors.errorText }, theme.fonts.small), { paddingTop: 5 }),
callout: [
globalClassnames.callout,
{
boxShadow: effects.elevation8,
borderRadius: calloutOpenBorderRadius,
selectors: (_k = {},
_k['.ms-Callout-main'] = { borderRadius: calloutOpenBorderRadius },
_k),
},
calloutClassName,
],
dropdownItemsWrapper: { selectors: { '&:focus': { outline: 0 } } },
dropdownItems: [globalClassnames.dropdownItems, { display: 'block' }],
dropdownItem: __spreadArray(__spreadArray([], dropdownItemStyle, true), [itemSelectors()], false),
dropdownItemSelected: dropdownItemSelected,
dropdownItemDisabled: dropdownItemDisabled,
dropdownItemSelectedAndDisabled: [dropdownItemSelected, dropdownItemDisabled, { backgroundColor: 'transparent' }],
dropdownItemHidden: __spreadArray(__spreadArray([], dropdownItemStyle, true), [{ display: 'none' }], false),
dropdownDivider: [globalClassnames.dropdownDivider, { height: 1, backgroundColor: semanticColors.bodyDivider }],
dropdownDividerHidden: [globalClassnames.dropdownDivider, { display: 'none' }],
dropdownOptionText: [
globalClassnames.dropdownOptionText,
{
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
minWidth: 0,
maxWidth: '100%',
wordWrap: 'break-word',
overflowWrap: 'break-word',
margin: '1px',
},
],
dropdownItemHeader: dropdownHeaderStyle,
dropdownItemHeaderHidden: __spreadArray(__spreadArray([], dropdownHeaderStyle, true), [{ display: 'none' }], false),
subComponentStyles: {
label: { root: { display: 'inline-block' } },
multiSelectItem: {
root: {
padding: 0,
},
label: {
alignSelf: 'stretch',
padding: '0 8px',
width: '100%',
},
input: {
selectors: (_l = {},
// eslint-disable-next-line @fluentui/max-len
_l[".".concat(IsFocusVisibleClassName, " &:focus + label::before, :host(.").concat(IsFocusVisibleClassName, ") &:focus + label::before")] = {
outlineOffset: '0px',
},
_l),
},
},
panel: {
root: [panelClassName],
main: {
selectors: (_m = {},
// In case of extra small screen sizes
_m[MinimumScreenSelector] = {
// panelWidth xs
width: 272,
},
_m),
},
contentInner: { padding: '0 0 20px' },
},
},
};
};
//# sourceMappingURL=Dropdown.styles.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,230 @@
import * as React from 'react';
import { ResponsiveMode } from '../../ResponsiveMode';
import { RectangleEdge } from '../../Positioning';
import type { IRenderFunction, IStyleFunctionOrObject } from '../../Utilities';
import type { IStyle, ITheme } from '../../Styling';
import type { ISelectableOption, ISelectableDroppableTextProps } from '../../SelectableOption';
import type { ICheckboxStyleProps, ICheckboxStyles } from '../../Checkbox';
import type { ILabelStyleProps, ILabelStyles } from '../../Label';
import type { IPanelStyleProps, IPanelStyles } from '../../Panel';
export { SelectableOptionMenuItemType as DropdownMenuItemType } from '../../SelectableOption';
/**
* {@docCategory Dropdown}
*/
export interface IDropdown {
/**
* All selected options
*/
readonly selectedOptions: IDropdownOption[];
/**
* An imperative handle to dismiss the popup if it is open
*/
dismissMenu: () => void;
focus: (shouldOpenOnFocus?: boolean) => void;
}
/**
* {@docCategory Dropdown}
*/
export interface IDropdownProps extends ISelectableDroppableTextProps<IDropdown, HTMLDivElement>, React.RefAttributes<HTMLDivElement> {
/**
* Input placeholder text. Displayed until option is selected.
* @deprecated Use `placeholder`
*/
placeHolder?: string;
/**
* Options for the dropdown. If using `defaultSelectedKey` or `defaultSelectedKeys`, options must be
* pure for correct behavior.
*/
options: IDropdownOption[];
/**
* Callback issued when the selected option changes.
*/
onChange?: (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number) => void;
/**
* @deprecated Use `onChange` instead.
*/
onChanged?: (option: IDropdownOption, index?: number) => void;
/**
* Custom render function for the label.
*/
onRenderLabel?: IRenderFunction<IDropdownProps>;
/**
* Optional custom renderer for placeholder text
*/
onRenderPlaceholder?: IRenderFunction<IDropdownProps>;
/**
* Optional custom renderer for placeholder text
* @deprecated Use `onRenderPlaceholder`
*/
onRenderPlaceHolder?: IRenderFunction<IDropdownProps>;
/**
* Optional custom renderer for selected option displayed in input
*/
onRenderTitle?: IRenderFunction<IDropdownOption[]>;
/**
* Optional custom renderer for chevron icon
*/
onRenderCaretDown?: IRenderFunction<IDropdownProps>;
/**
* Custom width for dropdown. If value is 0, width of the input field is used.
* If value is 'auto', width of the input field is used by default, and it can grow wider to fit the content.
* @defaultvalue 0
*/
dropdownWidth?: number | 'auto';
/**
* Pass in ResponsiveMode to manually overwrite the way the Dropdown renders.
* ResponsiveMode.large would, for instance, disable the behavior where Dropdown options
* get rendered into a Panel while ResponsiveMode.small would result in the Dropdown
* options always getting rendered in a Panel.
*/
responsiveMode?: ResponsiveMode;
/**
* Keys that will be initially used to set selected items. This prop is used for `multiSelect`
* scenarios. In other cases, `defaultSelectedKey` should be used.
*/
defaultSelectedKeys?: string[] | number[];
/**
* Keys of the selected items. If you provide this, you must maintain selection
* state by observing onChange events and passing a new value in when changed.
* Passing null in will clear the selection.
* Mutually exclusive with `defaultSelectedKeys`.
*/
selectedKeys?: string[] | number[] | null;
/**
* When multiple items are selected, this still will be used to separate values in
* the dropdown title.
*
* @defaultvalue ", "
*/
multiSelectDelimiter?: string;
/**
* Optional preference to have onChanged still be called when an already selected item is
* clicked in single select mode. Default to false
*/
notifyOnReselect?: boolean;
/**
* @deprecated Use `disabled` instead. Deprecated at v0.52.0.
*/
isDisabled?: boolean;
/**
* Theme provided by higher order component.
*/
theme?: ITheme;
/**
* Call to provide customized styling that will layer on top of the variant rules.
*/
styles?: IStyleFunctionOrObject<IDropdownStyleProps, IDropdownStyles>;
}
/**
* {@docCategory Dropdown}
*/
export interface IDropdownOption<T = any> extends ISelectableOption<T> {
/**
* @deprecated Use `selected` instead. Deprecated at v.65.1.
*/
isSelected?: boolean;
}
/**
* The props needed to construct styles.
* This represents the simplified set of immutable things which control the class names.
* {@docCategory Dropdown}
*/
export type IDropdownStyleProps = Pick<IDropdownProps, 'theme' | 'className' | 'disabled' | 'required'> & {
/**
* Whether the dropdown is in an error state.
*/
hasError: boolean;
/**
* Specifies if the dropdown has label content.
*/
hasLabel: boolean;
/**
* Whether the dropdown is in an opened state.
*/
isOpen: boolean;
/**
* Whether the dropdown is presently rendering a placeholder.
*/
isRenderingPlaceholder: boolean;
/**
* Optional custom className for the panel that displays in small viewports, hosting the Dropdown options.
* This is primarily provided for backwards compatibility.
*/
panelClassName?: string;
/**
* Optional custom className for the callout that displays in larger viewports, hosting the Dropdown options.
* This is primarily provided for backwards compatibility.
*/
calloutClassName?: string;
/**
* Prop to notify on what edge the dropdown callout was positioned respective to the title.
*/
calloutRenderEdge?: RectangleEdge;
};
/**
* Represents the stylable areas of the control.
* {@docCategory Dropdown}
*/
export interface IDropdownStyles {
/** Root element of the Dropdown (includes Label and the actual Dropdown). */
root: IStyle;
/** Refers to the label associated with the dropdown. This is enclosed by the root. */
label: IStyle;
/** Refers to the actual Dropdown element. */
dropdown: IStyle;
/** Refers to the primary title of the Dropdown (rendering the selected options/placeholder/etc.). */
title: IStyle;
/** Refers to the wrapping container around the downward pointing caret users click on to expand the Dropdown. */
caretDownWrapper: IStyle;
/** Refers to the downward pointing caret icon users click on to expand the Dropdown. */
caretDown: IStyle;
/** Refers to the error message being rendered under the Dropdown (if any). */
errorMessage: IStyle;
/** Refers to the element that wraps `dropdownItems`. */
dropdownItemsWrapper: IStyle;
/** Refers to the FocusZone wrapping the individual dropdown items. */
dropdownItems: IStyle;
/** Refers to the individual dropdown item. */
dropdownItem: IStyle;
/** Style for a dropdown item when it is being selected. */
dropdownItemSelected: IStyle;
/** Style for a dropdown item when it is disabled. */
dropdownItemDisabled: IStyle;
/** Style for a dropdown item when it is both selected and disabled. */
dropdownItemSelectedAndDisabled: IStyle;
/** Style for a dropdown item when it is hidden */
dropdownItemHidden: IStyle;
/**
* Refers to the text element that renders the actual dropdown item/option text. This would be wrapped by the element
* referred to by `dropdownItem`.
*/
dropdownOptionText: IStyle;
/** Refers to the dropdown separator. */
dropdownDivider: IStyle;
/** Style for dropdown separator when hidden. */
dropdownDividerHidden: IStyle;
/** Refers to the individual dropdown items that are being rendered as a header. */
dropdownItemHeader: IStyle;
/** Style for dropdown header when hidden. */
dropdownItemHeaderHidden: IStyle;
/**
* Refers to the panel that hosts the Dropdown options in small viewports.
* @deprecated Use `subComponentStyles.panel` instead.
*/
panel: IStyle;
/** Refers to the callout that hosts Dropdown options in larger viewports. */
callout: IStyle;
/** Subcomponent styles. */
subComponentStyles: IDropdownSubComponentStyles;
}
/**
* {@docCategory Dropdown}
*/
export interface IDropdownSubComponentStyles {
/** Refers to the panel that hosts the Dropdown options in small viewports. */
panel: IStyleFunctionOrObject<IPanelStyleProps, IPanelStyles>;
/** Refers to the primary label for the Dropdown. */
label: IStyleFunctionOrObject<ILabelStyleProps, ILabelStyles>;
/** Refers to the individual dropdown item when the multiSelect prop is true. */
multiSelectItem: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles>;
}
@@ -0,0 +1,2 @@
export { SelectableOptionMenuItemType as DropdownMenuItemType } from '../../SelectableOption';
//# sourceMappingURL=Dropdown.types.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
export * from './Dropdown';
export * from './Dropdown.base';
export * from './Dropdown.types';
+4
View File
@@ -0,0 +1,4 @@
export * from './Dropdown';
export * from './Dropdown.base';
export * from './Dropdown.types';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"../src/","sources":["components/Dropdown/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC","sourcesContent":["export * from './Dropdown';\nexport * from './Dropdown.base';\nexport * from './Dropdown.types';\n"]}
@@ -0,0 +1,35 @@
import type { IDropdownOption } from '../Dropdown.types';
/**
* A utility class to cache size and position in cache.
*
* Dropdown options has non-selectable display types. It is therefore not cheap to determine
* the total number of actual selectable options as well as the position an option is in the
* list of options - O(n) cost for each lookup.
*
* Given that we potentially have to make this determination on every single render pass, this
* cache should provide a little bit of relief.
*/
export declare class DropdownSizePosCache {
private _cachedOptions;
private _displayOnlyOptionsCache;
private _notSelectableOptionsCache;
private _size;
/**
* Invalidates the cache and recalculate the size of selectable options.
*/
updateOptions(options: IDropdownOption[]): void;
/**
* The size of all the selectable options.
*/
get optionSetSize(): number;
/**
* The chached options array.
*/
get cachedOptions(): IDropdownOption[];
/**
* Returns the position of this option element relative to the full set of selectable option elements.
* Note: the first selectable element is position 1 in the set.
* @param index The raw index of the option element.
*/
positionInSet(index: number | undefined): number | undefined;
}
@@ -0,0 +1,88 @@
import { __spreadArray } from "tslib";
import { DropdownMenuItemType } from '../Dropdown.types';
/**
* A utility class to cache size and position in cache.
*
* Dropdown options has non-selectable display types. It is therefore not cheap to determine
* the total number of actual selectable options as well as the position an option is in the
* list of options - O(n) cost for each lookup.
*
* Given that we potentially have to make this determination on every single render pass, this
* cache should provide a little bit of relief.
*/
var DropdownSizePosCache = /** @class */ (function () {
function DropdownSizePosCache() {
this._size = 0;
}
/**
* Invalidates the cache and recalculate the size of selectable options.
*/
DropdownSizePosCache.prototype.updateOptions = function (options) {
var displayOnlyOptionsCache = [];
var notSelectableOptionsCache = [];
var size = 0;
for (var i = 0; i < options.length; i++) {
var _a = options[i], itemType = _a.itemType, hidden = _a.hidden;
if (itemType === DropdownMenuItemType.Divider || itemType === DropdownMenuItemType.Header) {
displayOnlyOptionsCache.push(i);
notSelectableOptionsCache.push(i);
}
else if (hidden) {
notSelectableOptionsCache.push(i);
}
else {
size++;
}
}
this._size = size;
this._displayOnlyOptionsCache = displayOnlyOptionsCache;
this._notSelectableOptionsCache = notSelectableOptionsCache;
this._cachedOptions = __spreadArray([], options, true);
};
Object.defineProperty(DropdownSizePosCache.prototype, "optionSetSize", {
/**
* The size of all the selectable options.
*/
get: function () {
return this._size;
},
enumerable: false,
configurable: true
});
Object.defineProperty(DropdownSizePosCache.prototype, "cachedOptions", {
/**
* The chached options array.
*/
get: function () {
return this._cachedOptions;
},
enumerable: false,
configurable: true
});
/**
* Returns the position of this option element relative to the full set of selectable option elements.
* Note: the first selectable element is position 1 in the set.
* @param index The raw index of the option element.
*/
DropdownSizePosCache.prototype.positionInSet = function (index) {
if (index === undefined) {
return undefined;
}
// we could possibly memoize this too but this should be good enough, most of the time (the expectation is that
// when you have a lot of options, the selectable options will heavily dominate over the non-selectable options.
var offset = 0;
while (index > this._notSelectableOptionsCache[offset]) {
offset++;
}
if (this._displayOnlyOptionsCache[offset] === index) {
throw new Error("Unexpected: Option at index ".concat(index, " is not a selectable element."));
}
if (this._notSelectableOptionsCache[offset] === index) {
return undefined;
}
return index - offset + 1;
};
return DropdownSizePosCache;
}());
export { DropdownSizePosCache };
//# sourceMappingURL=DropdownSizePosCache.js.map
@@ -0,0 +1 @@
{"version":3,"file":"DropdownSizePosCache.js","sourceRoot":"../src/","sources":["components/Dropdown/utilities/DropdownSizePosCache.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD;;;;;;;;;GASG;AACH;IAAA;QAIU,UAAK,GAAG,CAAC,CAAC;IAqEpB,CAAC;IAnEC;;OAEG;IACI,4CAAa,GAApB,UAAqB,OAA0B;QAC7C,IAAM,uBAAuB,GAAG,EAAE,CAAC;QACnC,IAAM,yBAAyB,GAAG,EAAE,CAAC;QACrC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,IAAA,KAAuB,OAAO,CAAC,CAAC,CAAC,EAA/B,QAAQ,cAAA,EAAE,MAAM,YAAe,CAAC;YAExC,IAAI,QAAQ,KAAK,oBAAoB,CAAC,OAAO,IAAI,QAAQ,KAAK,oBAAoB,CAAC,MAAM,EAAE,CAAC;gBAC1F,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAChC,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpC,CAAC;iBAAM,IAAI,MAAM,EAAE,CAAC;gBAClB,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,IAAI,EAAE,CAAC;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,wBAAwB,GAAG,uBAAuB,CAAC;QACxD,IAAI,CAAC,0BAA0B,GAAG,yBAAyB,CAAC;QAC5D,IAAI,CAAC,cAAc,qBAAO,OAAO,OAAC,CAAC;IACrC,CAAC;IAKD,sBAAW,+CAAa;QAHxB;;WAEG;aACH;YACE,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;;;OAAA;IAKD,sBAAW,+CAAa;QAHxB;;WAEG;aACH;YACE,OAAO,IAAI,CAAC,cAAc,CAAC;QAC7B,CAAC;;;OAAA;IAED;;;;OAIG;IACI,4CAAa,GAApB,UAAqB,KAAyB;QAC5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,+GAA+G;QAC/G,gHAAgH;QAChH,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,KAAK,GAAG,IAAI,CAAC,0BAA0B,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,EAAE,CAAC;QACX,CAAC;QAED,IAAI,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,sCAA+B,KAAK,kCAA+B,CAAC,CAAC;QACvF,CAAC;QAED,IAAI,IAAI,CAAC,0BAA0B,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE,CAAC;YACtD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;IAC5B,CAAC;IACH,2BAAC;AAAD,CAAC,AAzED,IAyEC","sourcesContent":["import { DropdownMenuItemType } from '../Dropdown.types';\nimport type { IDropdownOption } from '../Dropdown.types';\n\n/**\n * A utility class to cache size and position in cache.\n *\n * Dropdown options has non-selectable display types. It is therefore not cheap to determine\n * the total number of actual selectable options as well as the position an option is in the\n * list of options - O(n) cost for each lookup.\n *\n * Given that we potentially have to make this determination on every single render pass, this\n * cache should provide a little bit of relief.\n */\nexport class DropdownSizePosCache {\n private _cachedOptions: IDropdownOption[];\n private _displayOnlyOptionsCache: number[];\n private _notSelectableOptionsCache: number[];\n private _size = 0;\n\n /**\n * Invalidates the cache and recalculate the size of selectable options.\n */\n public updateOptions(options: IDropdownOption[]): void {\n const displayOnlyOptionsCache = [];\n const notSelectableOptionsCache = [];\n let size = 0;\n for (let i = 0; i < options.length; i++) {\n const { itemType, hidden } = options[i];\n\n if (itemType === DropdownMenuItemType.Divider || itemType === DropdownMenuItemType.Header) {\n displayOnlyOptionsCache.push(i);\n notSelectableOptionsCache.push(i);\n } else if (hidden) {\n notSelectableOptionsCache.push(i);\n } else {\n size++;\n }\n }\n\n this._size = size;\n this._displayOnlyOptionsCache = displayOnlyOptionsCache;\n this._notSelectableOptionsCache = notSelectableOptionsCache;\n this._cachedOptions = [...options];\n }\n\n /**\n * The size of all the selectable options.\n */\n public get optionSetSize(): number {\n return this._size;\n }\n\n /**\n * The chached options array.\n */\n public get cachedOptions(): IDropdownOption[] {\n return this._cachedOptions;\n }\n\n /**\n * Returns the position of this option element relative to the full set of selectable option elements.\n * Note: the first selectable element is position 1 in the set.\n * @param index The raw index of the option element.\n */\n public positionInSet(index: number | undefined): number | undefined {\n if (index === undefined) {\n return undefined;\n }\n\n // we could possibly memoize this too but this should be good enough, most of the time (the expectation is that\n // when you have a lot of options, the selectable options will heavily dominate over the non-selectable options.\n let offset = 0;\n while (index > this._notSelectableOptionsCache[offset]) {\n offset++;\n }\n\n if (this._displayOnlyOptionsCache[offset] === index) {\n throw new Error(`Unexpected: Option at index ${index} is not a selectable element.`);\n }\n\n if (this._notSelectableOptionsCache[offset] === index) {\n return undefined;\n }\n\n return index - offset + 1;\n }\n}\n"]}