@deque/cauldron-react 5.7.1-canary.024ab594 → 5.7.1-canary.2562efe5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Listbox/Listbox.d.ts +16 -0
- package/lib/components/Listbox/ListboxContext.d.ts +27 -0
- package/lib/components/Listbox/ListboxGroup.d.ts +9 -0
- package/lib/components/Listbox/ListboxOption.d.ts +10 -0
- package/lib/components/Listbox/index.d.ts +4 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +222 -0
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ListboxOption } from './ListboxContext';
|
|
3
|
+
import type { ListboxValue } from './ListboxOption';
|
|
4
|
+
interface ListboxProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onSelect'> {
|
|
5
|
+
as?: React.ElementType | string;
|
|
6
|
+
value?: ListboxValue;
|
|
7
|
+
navigation?: 'cycle' | 'bound';
|
|
8
|
+
onSelectionChange?: <T extends HTMLElement = HTMLElement>({ value }: {
|
|
9
|
+
target: T;
|
|
10
|
+
previousValue: ListboxValue;
|
|
11
|
+
value: ListboxValue;
|
|
12
|
+
}) => void;
|
|
13
|
+
onActiveChange?: (option: ListboxOption) => void;
|
|
14
|
+
}
|
|
15
|
+
declare const Listbox: React.ForwardRefExoticComponent<ListboxProps & React.RefAttributes<HTMLElement>>;
|
|
16
|
+
export default Listbox;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type UnknownElement<T> = T extends Element ? T : HTMLElement;
|
|
3
|
+
type UnknownValue<T> = T extends string ? T : number;
|
|
4
|
+
type ListboxOption<Element = HTMLElement, Value = string | number> = {
|
|
5
|
+
element: UnknownElement<Element>;
|
|
6
|
+
value?: UnknownValue<Value>;
|
|
7
|
+
};
|
|
8
|
+
type ListboxContext<T extends ListboxOption> = {
|
|
9
|
+
options: T[];
|
|
10
|
+
active: T | null;
|
|
11
|
+
selected: T | null;
|
|
12
|
+
setOptions: React.Dispatch<React.SetStateAction<T[]>>;
|
|
13
|
+
onSelect: (option: T) => void;
|
|
14
|
+
};
|
|
15
|
+
type ListboxProvider<T extends ListboxOption> = {
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
} & ListboxContext<T>;
|
|
18
|
+
declare const ListboxContext: React.Context<{
|
|
19
|
+
options: never[];
|
|
20
|
+
active: null;
|
|
21
|
+
selected: null;
|
|
22
|
+
setOptions: () => null;
|
|
23
|
+
onSelect: () => null;
|
|
24
|
+
}>;
|
|
25
|
+
declare function ListboxProvider<T extends ListboxOption>({ options, active, selected, setOptions, onSelect, children }: ListboxProvider<T>): JSX.Element;
|
|
26
|
+
declare function useListboxContext<T extends ListboxOption>(): ListboxContext<T>;
|
|
27
|
+
export { ListboxProvider, useListboxContext, ListboxOption };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ContentNode } from '../../types';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
interface ListboxGroupProps extends React.HTMLAttributes<HTMLElement> {
|
|
4
|
+
as?: React.ElementType | string;
|
|
5
|
+
groupLabelProps?: React.HTMLAttributes<HTMLLIElement>;
|
|
6
|
+
label: ContentNode;
|
|
7
|
+
}
|
|
8
|
+
declare const ListboxGroup: React.ForwardRefExoticComponent<ListboxGroupProps & React.RefAttributes<HTMLElement>>;
|
|
9
|
+
export default ListboxGroup;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type ListboxValue = Readonly<string | number | undefined>;
|
|
3
|
+
interface ListboxOptionsProps extends React.HTMLAttributes<HTMLElement> {
|
|
4
|
+
as?: React.ElementType | string;
|
|
5
|
+
value?: ListboxValue;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
activeClass?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const ListboxOption: React.ForwardRefExoticComponent<ListboxOptionsProps & React.RefAttributes<HTMLElement>>;
|
|
10
|
+
export default ListboxOption;
|
package/lib/index.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export { default as FieldWrap } from './components/FieldWrap';
|
|
|
52
52
|
export { default as Breadcrumb, BreadcrumbItem, BreadcrumbLink } from './components/Breadcrumb';
|
|
53
53
|
export { default as TwoColumnPanel, ColumnHeader, ColumnGroupHeader, ColumnLeft, ColumnRight, ColumnList } from './components/TwoColumnPanel';
|
|
54
54
|
export { default as Notice } from './components/Notice';
|
|
55
|
+
export { default as Listbox, ListboxOption, ListboxGroup } from './components/Listbox';
|
|
55
56
|
/**
|
|
56
57
|
* Helpers / Utils
|
|
57
58
|
*/
|
package/lib/index.js
CHANGED
|
@@ -3943,6 +3943,225 @@ Notice.propTypes = {
|
|
|
3943
3943
|
icon: PropTypes__default["default"].string
|
|
3944
3944
|
};
|
|
3945
3945
|
|
|
3946
|
+
/* istanbul ignore next */
|
|
3947
|
+
var ListboxContext = React.createContext({
|
|
3948
|
+
options: [],
|
|
3949
|
+
active: null,
|
|
3950
|
+
selected: null,
|
|
3951
|
+
setOptions: function () { return null; },
|
|
3952
|
+
onSelect: function () { return null; }
|
|
3953
|
+
});
|
|
3954
|
+
function ListboxProvider(_a) {
|
|
3955
|
+
var options = _a.options, active = _a.active, selected = _a.selected, setOptions = _a.setOptions, onSelect = _a.onSelect, children = _a.children;
|
|
3956
|
+
var Provider = ListboxContext.Provider;
|
|
3957
|
+
var value = React.useMemo(function () { return ({
|
|
3958
|
+
options: options,
|
|
3959
|
+
active: active,
|
|
3960
|
+
selected: selected,
|
|
3961
|
+
setOptions: setOptions,
|
|
3962
|
+
onSelect: onSelect
|
|
3963
|
+
}); }, [options, active, selected, setOptions]);
|
|
3964
|
+
return React__default["default"].createElement(Provider, { value: value }, children);
|
|
3965
|
+
}
|
|
3966
|
+
function useListboxContext() {
|
|
3967
|
+
return React.useContext(ListboxContext);
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
var keys = ['ArrowUp', 'ArrowDown', 'Home', 'End', 'Enter', ' '];
|
|
3971
|
+
// id for listbox options should always be defined since it should
|
|
3972
|
+
// be provide via the author, or auto-generated via the component
|
|
3973
|
+
var getOptionId = function (option) {
|
|
3974
|
+
return option.element.getAttribute('id');
|
|
3975
|
+
};
|
|
3976
|
+
var isDisabledOption = function (option) {
|
|
3977
|
+
return option.element.getAttribute('aria-disabled') === 'true';
|
|
3978
|
+
};
|
|
3979
|
+
var optionMatchesValue = function (option, value) {
|
|
3980
|
+
return typeof option.value !== null &&
|
|
3981
|
+
typeof option.value !== 'undefined' &&
|
|
3982
|
+
option.value === value;
|
|
3983
|
+
};
|
|
3984
|
+
var Listbox = React.forwardRef(function (_a, ref) {
|
|
3985
|
+
var _b = _a.as, Component = _b === void 0 ? 'ul' : _b, children = _a.children, defaultValue = _a.defaultValue, value = _a.value, _c = _a.navigation, navigation = _c === void 0 ? 'bound' : _c, onKeyDown = _a.onKeyDown, onFocus = _a.onFocus, onSelectionChange = _a.onSelectionChange, onActiveChange = _a.onActiveChange, props = tslib.__rest(_a, ["as", "children", "defaultValue", "value", "navigation", "onKeyDown", "onFocus", "onSelectionChange", "onActiveChange"]);
|
|
3986
|
+
var _d = tslib.__read(React.useState([]), 2), options = _d[0], setOptions = _d[1];
|
|
3987
|
+
var _e = tslib.__read(React.useState(null), 2), activeOption = _e[0], setActiveOption = _e[1];
|
|
3988
|
+
var _f = tslib.__read(React.useState(null), 2), selectedOption = _f[0], setSelectedOption = _f[1];
|
|
3989
|
+
var listboxRef = useSharedRef(ref);
|
|
3990
|
+
var isControlled = typeof value !== 'undefined';
|
|
3991
|
+
React.useLayoutEffect(function () {
|
|
3992
|
+
if (!isControlled && selectedOption) {
|
|
3993
|
+
return;
|
|
3994
|
+
}
|
|
3995
|
+
var listboxValue = isControlled ? value : defaultValue;
|
|
3996
|
+
var matchingOption = options.find(function (option) {
|
|
3997
|
+
return optionMatchesValue(option, listboxValue);
|
|
3998
|
+
});
|
|
3999
|
+
setSelectedOption(matchingOption || null);
|
|
4000
|
+
setActiveOption(matchingOption || null);
|
|
4001
|
+
}, [isControlled, options, value]);
|
|
4002
|
+
React.useEffect(function () {
|
|
4003
|
+
if (activeOption) {
|
|
4004
|
+
onActiveChange === null || onActiveChange === void 0 ? void 0 : onActiveChange(activeOption);
|
|
4005
|
+
}
|
|
4006
|
+
}, [activeOption]);
|
|
4007
|
+
var handleSelect = React.useCallback(function (option) {
|
|
4008
|
+
setActiveOption(option);
|
|
4009
|
+
// istanbul ignore else
|
|
4010
|
+
if (!isControlled) {
|
|
4011
|
+
setSelectedOption(option);
|
|
4012
|
+
}
|
|
4013
|
+
onSelectionChange === null || onSelectionChange === void 0 ? void 0 : onSelectionChange({
|
|
4014
|
+
target: option.element,
|
|
4015
|
+
value: option.value,
|
|
4016
|
+
previousValue: selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value
|
|
4017
|
+
});
|
|
4018
|
+
}, [isControlled, selectedOption]);
|
|
4019
|
+
var handleKeyDown = React.useCallback(function (event) {
|
|
4020
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
4021
|
+
if (!keys.includes(event.key)) {
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
event.preventDefault();
|
|
4025
|
+
var enabledOptions = options.filter(function (option) { return !isDisabledOption(option); });
|
|
4026
|
+
// istanbul ignore next
|
|
4027
|
+
if (!enabledOptions.length) {
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
var _a = tslib.__read(keys, 6), up = _a[0], down = _a[1], home = _a[2], end = _a[3], enter = _a[4], space = _a[5];
|
|
4031
|
+
var firstOption = enabledOptions[0];
|
|
4032
|
+
if (!activeOption) {
|
|
4033
|
+
setActiveOption(firstOption);
|
|
4034
|
+
return;
|
|
4035
|
+
}
|
|
4036
|
+
var lastOption = enabledOptions[enabledOptions.length - 1];
|
|
4037
|
+
var currentOption = activeOption;
|
|
4038
|
+
var currentIndex = enabledOptions.findIndex(function (_a) {
|
|
4039
|
+
var element = _a.element;
|
|
4040
|
+
return element === currentOption.element;
|
|
4041
|
+
});
|
|
4042
|
+
var allowCyclicalNavigation = navigation === 'cycle';
|
|
4043
|
+
switch (event.key) {
|
|
4044
|
+
case up:
|
|
4045
|
+
var previousOption = currentIndex === 0 && allowCyclicalNavigation
|
|
4046
|
+
? lastOption
|
|
4047
|
+
: enabledOptions[Math.max(currentIndex - 1, 0)];
|
|
4048
|
+
setActiveOption(previousOption);
|
|
4049
|
+
break;
|
|
4050
|
+
case down:
|
|
4051
|
+
var nextOption = currentIndex === enabledOptions.length - 1 &&
|
|
4052
|
+
allowCyclicalNavigation
|
|
4053
|
+
? firstOption
|
|
4054
|
+
: enabledOptions[Math.min(currentIndex + 1, enabledOptions.length - 1)];
|
|
4055
|
+
setActiveOption(nextOption);
|
|
4056
|
+
break;
|
|
4057
|
+
case home:
|
|
4058
|
+
setActiveOption(firstOption);
|
|
4059
|
+
break;
|
|
4060
|
+
case end:
|
|
4061
|
+
setActiveOption(lastOption);
|
|
4062
|
+
break;
|
|
4063
|
+
case enter:
|
|
4064
|
+
case space:
|
|
4065
|
+
activeOption && handleSelect(activeOption);
|
|
4066
|
+
break;
|
|
4067
|
+
}
|
|
4068
|
+
}, [options, activeOption, navigation]);
|
|
4069
|
+
var handleFocus = React.useCallback(function (event) {
|
|
4070
|
+
if (!activeOption && !selectedOption) {
|
|
4071
|
+
var firstOption = options.find(function (option) { return !isDisabledOption(option); });
|
|
4072
|
+
// istanbul ignore else
|
|
4073
|
+
if (firstOption) {
|
|
4074
|
+
setActiveOption(firstOption);
|
|
4075
|
+
}
|
|
4076
|
+
// istanbul ignore else
|
|
4077
|
+
}
|
|
4078
|
+
else if (event.target === listboxRef.current) {
|
|
4079
|
+
setActiveOption(selectedOption);
|
|
4080
|
+
}
|
|
4081
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
4082
|
+
}, [options, activeOption, selectedOption]);
|
|
4083
|
+
return (React__default["default"].createElement(Component, tslib.__assign({ role: "listbox", ref: listboxRef, tabIndex: "0", onKeyDown: handleKeyDown, onFocus: handleFocus, "aria-activedescendant": activeOption ? getOptionId(activeOption) : undefined }, props),
|
|
4084
|
+
React__default["default"].createElement(ListboxProvider, { options: options, active: activeOption, selected: selectedOption, setOptions: setOptions, onSelect: handleSelect }, children)));
|
|
4085
|
+
});
|
|
4086
|
+
Listbox.displayName = 'Listbox';
|
|
4087
|
+
|
|
4088
|
+
function isElementPreceding(a, b) {
|
|
4089
|
+
return !!(b.compareDocumentPosition(a) & Node.DOCUMENT_POSITION_PRECEDING);
|
|
4090
|
+
}
|
|
4091
|
+
var ListboxOption = React.forwardRef(function (_a, ref) {
|
|
4092
|
+
var _b;
|
|
4093
|
+
var _c;
|
|
4094
|
+
var propId = _a.id, className = _a.className, _d = _a.as, Component = _d === void 0 ? 'li' : _d, children = _a.children, value = _a.value, disabled = _a.disabled, _e = _a.activeClass, activeClass = _e === void 0 ? 'ListboxOption--active' : _e, onClick = _a.onClick, props = tslib.__rest(_a, ["id", "className", "as", "children", "value", "disabled", "activeClass", "onClick"]);
|
|
4095
|
+
var _f = useListboxContext(), active = _f.active, selected = _f.selected, setOptions = _f.setOptions, onSelect = _f.onSelect;
|
|
4096
|
+
var listboxOptionRef = useSharedRef(ref);
|
|
4097
|
+
var _g = tslib.__read(propId ? [propId] : nextId.useId(1, 'listbox-option'), 1), id = _g[0];
|
|
4098
|
+
var isActive = active !== null && active.element === listboxOptionRef.current;
|
|
4099
|
+
var isSelected = selected !== null && selected.element === listboxOptionRef.current;
|
|
4100
|
+
var optionValue = typeof value !== 'undefined'
|
|
4101
|
+
? value
|
|
4102
|
+
: (_c = listboxOptionRef.current) === null || _c === void 0 ? void 0 : _c.innerText;
|
|
4103
|
+
React.useEffect(function () {
|
|
4104
|
+
var element = listboxOptionRef.current;
|
|
4105
|
+
setOptions(function (options) {
|
|
4106
|
+
var e_1, _a;
|
|
4107
|
+
var option = { element: element, value: optionValue };
|
|
4108
|
+
// istanbul ignore next
|
|
4109
|
+
if (!element)
|
|
4110
|
+
return options;
|
|
4111
|
+
// Elements are frequently appended, so check to see if the newly rendered
|
|
4112
|
+
// element follows the last element first before any other checks
|
|
4113
|
+
if (!options.length ||
|
|
4114
|
+
isElementPreceding(options[options.length - 1].element, option.element)) {
|
|
4115
|
+
return tslib.__spreadArray(tslib.__spreadArray([], tslib.__read(options), false), [option], false);
|
|
4116
|
+
}
|
|
4117
|
+
try {
|
|
4118
|
+
for (var options_1 = tslib.__values(options), options_1_1 = options_1.next(); !options_1_1.done; options_1_1 = options_1.next()) {
|
|
4119
|
+
var opt = options_1_1.value;
|
|
4120
|
+
if (isElementPreceding(element, opt.element)) {
|
|
4121
|
+
var index = options.indexOf(opt);
|
|
4122
|
+
return tslib.__spreadArray(tslib.__spreadArray(tslib.__spreadArray([], tslib.__read(options.slice(0, index)), false), [
|
|
4123
|
+
option
|
|
4124
|
+
], false), tslib.__read(options.slice(index)), false);
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
4129
|
+
finally {
|
|
4130
|
+
try {
|
|
4131
|
+
if (options_1_1 && !options_1_1.done && (_a = options_1.return)) _a.call(options_1);
|
|
4132
|
+
}
|
|
4133
|
+
finally { if (e_1) throw e_1.error; }
|
|
4134
|
+
}
|
|
4135
|
+
// istanbul ignore next
|
|
4136
|
+
// this should never happen, but just in case fall back to options
|
|
4137
|
+
return options;
|
|
4138
|
+
});
|
|
4139
|
+
return function () {
|
|
4140
|
+
setOptions(function (opts) { return opts.filter(function (opt) { return opt.element !== element; }); });
|
|
4141
|
+
};
|
|
4142
|
+
}, [optionValue]);
|
|
4143
|
+
var handleClick = React.useCallback(function (event) {
|
|
4144
|
+
if (disabled) {
|
|
4145
|
+
return;
|
|
4146
|
+
}
|
|
4147
|
+
onSelect({ element: listboxOptionRef.current, value: optionValue });
|
|
4148
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
4149
|
+
}, [optionValue]);
|
|
4150
|
+
return (React__default["default"].createElement(Component, tslib.__assign({ id: id, className: classNames__default["default"](className, (_b = {},
|
|
4151
|
+
_b[activeClass] = isActive,
|
|
4152
|
+
_b)), role: "option", ref: listboxOptionRef, "aria-disabled": typeof disabled === 'boolean' ? disabled : undefined, "aria-selected": isSelected, onClick: handleClick }, props), children));
|
|
4153
|
+
});
|
|
4154
|
+
ListboxOption.displayName = 'ListboxOption';
|
|
4155
|
+
|
|
4156
|
+
var ListboxGroup = React.forwardRef(function (_a, ref) {
|
|
4157
|
+
var _b = _a.as, Component = _b === void 0 ? 'ul' : _b, children = _a.children, propId = _a.id, label = _a.label, groupLabelProps = _a.groupLabelProps, props = tslib.__rest(_a, ["as", "children", "id", "label", "groupLabelProps"]);
|
|
4158
|
+
var _c = tslib.__read(propId ? [propId] : nextId.useId(1, 'listbox-group-label'), 1), id = _c[0];
|
|
4159
|
+
return (React__default["default"].createElement(Component, tslib.__assign({ role: "group", ref: ref, "aria-labelledby": id }, props),
|
|
4160
|
+
React__default["default"].createElement("li", tslib.__assign({ role: "presentation", id: id }, groupLabelProps), label),
|
|
4161
|
+
children));
|
|
4162
|
+
});
|
|
4163
|
+
ListboxGroup.displayName = 'ListboxGroup';
|
|
4164
|
+
|
|
3946
4165
|
var LIGHT_THEME_CLASS = 'cauldron--theme-light';
|
|
3947
4166
|
var DARK_THEME_CLASS = 'cauldron--theme-dark';
|
|
3948
4167
|
var ThemeContext = React.createContext({
|
|
@@ -4048,6 +4267,9 @@ exports.IssuePanel = IssuePanel;
|
|
|
4048
4267
|
exports.Layout = Layout;
|
|
4049
4268
|
exports.Line = Line;
|
|
4050
4269
|
exports.Link = Link;
|
|
4270
|
+
exports.Listbox = Listbox;
|
|
4271
|
+
exports.ListboxGroup = ListboxGroup;
|
|
4272
|
+
exports.ListboxOption = ListboxOption;
|
|
4051
4273
|
exports.Loader = Loader;
|
|
4052
4274
|
exports.LoaderOverlay = LoaderOverlay;
|
|
4053
4275
|
exports.Main = Main;
|
package/package.json
CHANGED