@jobber/components 4.60.3-replace-da.7 → 4.60.3

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var Chip = require('../Chip-f26246d1.js');
5
+ var Chip = require('../Chip-f8b8fd89.js');
6
6
  var tslib_es6 = require('../tslib.es6-5b8768b7.js');
7
7
  var React = require('react');
8
8
  var Icon = require('../Icon-405a216c.js');
@@ -0,0 +1,514 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var uuid = require('uuid');
5
+ var styleInject_es = require('./style-inject.es-9d2f5f4e.js');
6
+ var classnames = require('classnames');
7
+ var useAssert = require('@jobber/hooks/useAssert');
8
+ var Icon = require('./Icon-405a216c.js');
9
+ var Avatar = require('./Avatar-1aa30ee1.js');
10
+ var Typography = require('./Typography-83915c54.js');
11
+ var Tooltip = require('./Tooltip-6b12f1c2.js');
12
+ var sortBy = require('lodash/sortBy');
13
+ var useLiveAnnounce = require('@jobber/hooks/useLiveAnnounce');
14
+ var debounce = require('lodash/debounce');
15
+ var reactPopper = require('react-popper');
16
+ var Text = require('./Text-54334431.js');
17
+ var Button = require('./Button-db6114a6.js');
18
+ var Spinner = require('./Spinner-9d8fc7ff.js');
19
+
20
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
21
+
22
+ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
23
+ var classnames__default = /*#__PURE__*/_interopDefaultLegacy(classnames);
24
+ var sortBy__default = /*#__PURE__*/_interopDefaultLegacy(sortBy);
25
+ var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce);
26
+
27
+ var css_248z$1 = "/**\n * Used in the wrapping chips component:\n * - InternalChipSingleSelect\n * - InternalChipMultiSelect\n */\n\n.zn6onyiQVmA- {\n display: -ms-flexbox;\n display: flex;\n gap: calc(16px / 2);\n gap: var(--space-small);\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.PEnZzMEgbhU- {\n display: inline-grid;\n position: relative;\n max-width: 100%;\n box-sizing: border-box;\n padding: calc(16px / 2);\n padding: var(--space-small);\n border: calc(16px / 16) solid rgb(225, 225, 225);\n border: var(--border-base) solid var(--color-border);\n border-radius: calc(16px / 2);\n border-radius: var(--radius-larger);\n color: rgba(101, 120, 132, 1);\n color: var(--color-text--secondary);\n text-align: left;\n vertical-align: middle;\n background-color: rgba(255, 255, 255, 1);\n background-color: var(--color-surface);\n transition: 200ms ease-in-out;\n transition: var(--timing-base) ease-in-out;\n transition-property: background-color, box-shadow, color;\n grid-template-columns: -webkit-max-content;\n grid-template-columns: max-content;\n -ms-flex-align: center;\n align-items: center;\n gap: calc(16px / 2);\n gap: var(--space-small);\n}\n\n.dtysUhwYdG4-,\n.xGaMCAYsdwA- {\n padding: calc((16px / 4) + (16px / 8)) calc(16px / 2);\n padding: calc(calc(16px / 4) + calc(16px / 8)) calc(16px / 2);\n padding: calc(var(--space-smaller) + var(--space-smallest)) var(--space-small);\n}\n\n.dtysUhwYdG4- {\n grid-template-columns: -webkit-min-content 1fr;\n grid-template-columns: min-content 1fr;\n}\n\n.xGaMCAYsdwA- {\n grid-template-columns: 1fr -webkit-min-content;\n grid-template-columns: 1fr min-content;\n}\n\n.dtysUhwYdG4-.xGaMCAYsdwA- {\n grid-template-columns: -webkit-min-content 1fr -webkit-min-content;\n grid-template-columns: min-content 1fr min-content;\n}\n\n.SNT7gGV-h-o-,\n.W4D98cvEth4- ~ .PEnZzMEgbhU- {\n box-shadow: 0px calc(16px / 16) calc(16px / 4) 0px\n rgba(0, 0, 0, 0.1),\n 0px calc(16px / 4) 12px 0px rgba(0, 0, 0, 0.05);\n box-shadow: var(--shadow-base);\n cursor: pointer;\n -webkit-appearance: none;\n appearance: none;\n}\n\n/**\n * Interaction\n */\n\n.PEnZzMEgbhU-:focus,\n.W4D98cvEth4-:focus ~ .PEnZzMEgbhU- {\n box-shadow: 0px 0px calc(16px / 4) calc(16px / 8)\n rgb(231, 213, 87);\n box-shadow: var(--shadow-focus);\n outline: none;\n}\n\n.SNT7gGV-h-o-:hover,\n.W4D98cvEth4- ~ .PEnZzMEgbhU-:not(._0hEzaTXK9M0-):hover,\n.W4D98cvEth4-:focus ~ .PEnZzMEgbhU-:not(._0hEzaTXK9M0-) {\n background-color: rgb(250, 246, 219);\n background-color: var(--color-surface--hover);\n}\n\n/**\n * States\n */\n\n.PEnZzMEgbhU-._0hEzaTXK9M0- {\n border-color: rgb(1, 41, 57);\n border-color: var(--color-surface--reverse);\n color: rgba(255, 255, 255, 1);\n color: var(--color-text--reverse);\n background-color: rgb(1, 41, 57);\n background-color: var(--color-surface--reverse);\n}\n\n.PEnZzMEgbhU-.JApS2787BU8- {\n border-color: rgb(128, 25, 0);\n border-color: var(--color-critical--onSurface);\n color: rgb(128, 25, 0);\n color: var(--color-critical--onSurface);\n background-color: rgb(255, 226, 219);\n background-color: var(--color-critical--surface);\n}\n\n.PEnZzMEgbhU-.Kg0c8WVgMpg- {\n border-color: rgb(225, 225, 225);\n border-color: var(--color-disabled--secondary);\n color: rgb(181, 181, 181);\n color: var(--color-disabled);\n background-color: rgb(225, 225, 225);\n background-color: var(--color-disabled--secondary);\n}\n\n._0hEzaTXK9M0-.Kg0c8WVgMpg- {\n border-color: rgb(181, 181, 181);\n border-color: var(--color-disabled);\n color: rgba(255, 255, 255, 1);\n color: var(--color-white);\n background-color: rgb(181, 181, 181);\n background-color: var(--color-disabled);\n}\n\n.JApS2787BU8-.Kg0c8WVgMpg- {\n border-color: rgb(201, 66, 33);\n border-color: var(--color-critical);\n}\n\n/**\n * Input checkbox/radio\n *\n * Hide checkbox on UI but not screen readers and still allow focus state\n */\n\n.W4D98cvEth4- {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n -webkit-clip-path: inset(100%);\n clip-path: inset(100%);\n white-space: nowrap;\n}\n\n/**\n * Button\n */\n\n._0-ZWexS4Gfg- {\n display: -ms-flexbox;\n display: flex;\n border-radius: calc(16px / 8);\n border-radius: var(--radius-base);\n cursor: pointer;\n transition: background-color 200ms ease;\n}\n\n._0-ZWexS4Gfg-:hover,\n._0-ZWexS4Gfg-:focus {\n background-color: rgb(250, 246, 219);\n background-color: var(--color-surface--hover);\n}\n\n._0-ZWexS4Gfg-:focus,\n.SNT7gGV-h-o- ._0-ZWexS4Gfg-:hover {\n box-shadow: 0px 0px calc(16px / 4) calc(16px / 8)\n rgb(231, 213, 87);\n box-shadow: var(--shadow-focus);\n outline: none;\n}\n\n/**\n * Override icon default colors.\n *\n * NOTE: This is a workaround since some icons have default colors that we don't\n * want to override until the component color prop overrides it.\n * DO NOT COPY!\n */\n\n.DQPnZP4UZpw- > svg {\n fill: rgba(101, 120, 132, 1);\n fill: var(--color-interactive--subtle);\n}\n\n.DQPnZP4UZpw- {\n display: -ms-flexbox;\n display: flex;\n}\n";
28
+ var styles$1 = {"wrapper":"zn6onyiQVmA-","chip":"PEnZzMEgbhU-","hasPrefix":"dtysUhwYdG4-","hasSuffix":"xGaMCAYsdwA-","clickable":"SNT7gGV-h-o-","input":"W4D98cvEth4-","active":"_0hEzaTXK9M0-","invalid":"JApS2787BU8-","disabled":"Kg0c8WVgMpg-","button":"_0-ZWexS4Gfg-","icon":"DQPnZP4UZpw-"};
29
+ styleInject_es.styleInject(css_248z$1);
30
+
31
+ function InternalChipButton({ icon, invalid, disabled, label, onClick, }) {
32
+ const buttonRef = React.useRef();
33
+ return (React__default["default"].createElement("div", { ref: buttonRef, className: styles$1.button, tabIndex: 0, onKeyUp: handleKeyUp, onKeyDown: handleKeyDown, onClick: handleClick, role: "button", "aria-label": `Remove ${label}`, "aria-hidden": true, "aria-disabled": disabled, "data-testid": "remove-chip-button" },
34
+ React__default["default"].createElement(Icon.Icon, { name: icon, color: getColor() })));
35
+ function handleKeyUp(event) {
36
+ if (document.activeElement === buttonRef.current &&
37
+ (event.key === " " || event.key === "Enter")) {
38
+ event.stopPropagation();
39
+ buttonRef.current.click();
40
+ }
41
+ }
42
+ function handleKeyDown(event) {
43
+ if (document.activeElement === buttonRef.current && event.key === " ") {
44
+ // Don't scroll down
45
+ event.preventDefault();
46
+ }
47
+ }
48
+ function getColor() {
49
+ if (disabled)
50
+ return "disabled";
51
+ if (invalid)
52
+ return "critical";
53
+ return "greyBlue";
54
+ }
55
+ function handleClick(event) {
56
+ event.stopPropagation();
57
+ onClick === null || onClick === void 0 ? void 0 : onClick(event);
58
+ }
59
+ }
60
+
61
+ function InternalChipAffix({ affix, active, invalid, disabled, }) {
62
+ assertProps();
63
+ if ((affix === null || affix === void 0 ? void 0 : affix.type) === Avatar.Avatar) {
64
+ return React__default["default"].createElement(Avatar.Avatar, Object.assign({}, affix.props, { size: "small" }));
65
+ }
66
+ if ((affix === null || affix === void 0 ? void 0 : affix.type) === Icon.Icon) {
67
+ return renderChipIcon(affix);
68
+ }
69
+ if ((affix === null || affix === void 0 ? void 0 : affix.type) === InternalChipButton) {
70
+ return React__default["default"].createElement(InternalChipButton, Object.assign({}, affix.props));
71
+ }
72
+ return React__default["default"].createElement(React__default["default"].Fragment, null);
73
+ function renderChipIcon(icon) {
74
+ const color = getIconColor();
75
+ return (React__default["default"].createElement("div", { className: styles$1.icon }, React__default["default"].cloneElement(icon, Object.assign({ size: "base" }, (color && { color: color })))));
76
+ }
77
+ function getIconColor() {
78
+ if (disabled && !active)
79
+ return "disabled";
80
+ if (invalid && !disabled)
81
+ return "critical";
82
+ if (active)
83
+ return "white";
84
+ return;
85
+ }
86
+ function assertProps() {
87
+ const isAvatar = (affix === null || affix === void 0 ? void 0 : affix.type) === Avatar.Avatar || false;
88
+ const isIcon = (affix === null || affix === void 0 ? void 0 : affix.type) === Icon.Icon || false;
89
+ const isChipButton = (affix === null || affix === void 0 ? void 0 : affix.type) === InternalChipButton || false;
90
+ useAssert.useAssert(!!affix && !(isAvatar || isIcon || isChipButton), `Prefix prop only accepts "<Avatar />" or "<Icon />" component. You have "${affix === null || affix === void 0 ? void 0 : affix.type}".`);
91
+ }
92
+ }
93
+
94
+ function InternalChip({ label, active = false, disabled = false, invalid = false, prefix, suffix, tabIndex, ariaLabel, onClick, onKeyDown, }) {
95
+ const [truncateRef, setTruncateRef] = React.useState();
96
+ const isClickable = onClick && !disabled;
97
+ const classNames = classnames__default["default"](styles$1.chip, {
98
+ [styles$1.clickable]: isClickable,
99
+ [styles$1.active]: active,
100
+ [styles$1.disabled]: disabled,
101
+ [styles$1.invalid]: invalid,
102
+ [styles$1.hasPrefix]: prefix,
103
+ [styles$1.hasSuffix]: suffix,
104
+ });
105
+ const affixProps = { active, invalid, disabled };
106
+ const Tag = onClick ? "button" : "div";
107
+ const chip = (React__default["default"].createElement(Tag, Object.assign({ className: classNames }, (isClickable && { onClick, disabled }), { onKeyDown: onKeyDown, "data-testid": "chip-wrapper", "aria-label": ariaLabel, tabIndex: tabIndex, role: tabIndex !== undefined ? "option" : undefined }),
108
+ React__default["default"].createElement(InternalChipAffix, Object.assign({ affix: prefix }, affixProps)),
109
+ React__default["default"].createElement(Typography.Typography, { element: "span", numberOfLines: 1, size: "base" },
110
+ React__default["default"].createElement("span", { ref: setTruncateRef }, label)),
111
+ React__default["default"].createElement(InternalChipAffix, Object.assign({ affix: suffix }, affixProps))));
112
+ return isTruncated() ? React__default["default"].createElement(Tooltip.Tooltip, { message: label }, chip) : chip;
113
+ function isTruncated() {
114
+ var _a;
115
+ const truncateParentHeight = ((_a = truncateRef === null || truncateRef === void 0 ? void 0 : truncateRef.parentElement) === null || _a === void 0 ? void 0 : _a.offsetHeight) || 0;
116
+ const truncateChildHeight = (truncateRef === null || truncateRef === void 0 ? void 0 : truncateRef.offsetHeight) || 0;
117
+ return truncateChildHeight >= truncateParentHeight * 2;
118
+ }
119
+ }
120
+
121
+ function InternalChipSingleSelect({ children, selected, name = uuid.v1(), onChange, onClick, }) {
122
+ return (React__default["default"].createElement("div", { className: styles$1.wrapper, "data-testid": "singleselect-chips" }, React__default["default"].Children.map(children, child => {
123
+ const isSelected = child.props.value === selected;
124
+ return (React__default["default"].createElement("label", null,
125
+ React__default["default"].createElement("input", { type: "radio", checked: isSelected, className: styles$1.input, name: name, onClick: handleClick(child.props.value), onKeyUp: handleKeyUp(isSelected, child.props.value), onChange: () => {
126
+ /* No op. onClick handles the change to allow deselecting. */
127
+ }, disabled: child.props.disabled }),
128
+ React__default["default"].createElement(InternalChip, Object.assign({}, child.props, { active: isSelected }))));
129
+ })));
130
+ function handleKeyUp(active, value) {
131
+ if (!active)
132
+ return;
133
+ return (event) => {
134
+ if (event.key === " ") {
135
+ // Wait for DOM changes before applying the new change.
136
+ setTimeout(() => handleChange(value), 0);
137
+ }
138
+ };
139
+ }
140
+ function handleClick(value) {
141
+ return (event) => {
142
+ onClick === null || onClick === void 0 ? void 0 : onClick(event, value);
143
+ handleChange(value);
144
+ };
145
+ }
146
+ function handleChange(value) {
147
+ const newValue = value !== selected ? value : undefined;
148
+ onChange(newValue);
149
+ }
150
+ }
151
+
152
+ function InternalChipMultiSelect({ children, selected, onChange, onClick, }) {
153
+ return (React__default["default"].createElement("div", { className: styles$1.wrapper, "data-testid": "multiselect-chips" }, React__default["default"].Children.map(children, chip => {
154
+ const isChipActive = isChipSelected(chip.props.value);
155
+ return (React__default["default"].createElement("label", null,
156
+ React__default["default"].createElement("input", { type: "checkbox", checked: isChipActive, className: styles$1.input, onClick: handleClick(chip.props.value), onChange: handleChange(chip.props.value), disabled: chip.props.disabled }),
157
+ React__default["default"].createElement(InternalChip, Object.assign({}, chip.props, { active: isChipActive, suffix: checkmarkIcon(isChipActive) }))));
158
+ })));
159
+ function isChipSelected(value) {
160
+ return selected.includes(value);
161
+ }
162
+ function handleClick(value) {
163
+ return (event) => onClick === null || onClick === void 0 ? void 0 : onClick(event, value);
164
+ }
165
+ function handleChange(value) {
166
+ return () => {
167
+ if (isChipSelected(value)) {
168
+ handleDeselect(value);
169
+ }
170
+ else {
171
+ handleSelect(value);
172
+ }
173
+ };
174
+ }
175
+ function handleSelect(value) {
176
+ const newVal = [...selected, value];
177
+ onChange(newVal);
178
+ }
179
+ function handleDeselect(value) {
180
+ const values = selected;
181
+ const newVal = values.filter(val => val !== value);
182
+ onChange(newVal);
183
+ }
184
+ function checkmarkIcon(show) {
185
+ // Ideally, we should be returning a fragment `<></>` when a function
186
+ // returns a component / element. However, this one needs to return nothing
187
+ // to prevent it from randomly rendering a suffix.
188
+ //
189
+ // DO NOT COPY!
190
+ if (!show)
191
+ return;
192
+ return React__default["default"].createElement(Icon.Icon, { name: "checkmark" });
193
+ }
194
+ }
195
+
196
+ var css_248z = ".sCNIjluCskc- {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n border: calc(16px / 16) solid transparent;\n border: var(--border-base) solid transparent;\n border-radius: calc(16px / 8);\n border-radius: var(--radius-base);\n transition: all 200ms;\n transition: all var(--timing-base);\n gap: calc(16px / 2);\n gap: var(--space-small);\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.MFUlz1WQLdk- {\n -ms-flex: 1;\n flex: 1;\n min-width: 120px;\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n border: none;\n border-radius: 0;\n color: inherit;\n font-family: inherit;\n font-size: inherit;\n font-size: calc(calc(16px * 1) * 0.875);\n font-size: var(--typography--fontSize-base);\n line-height: 1.25;\n line-height: var(--typography--lineHeight-base);\n background: transparent;\n -webkit-appearance: none;\n appearance: none;\n}\n\n.MFUlz1WQLdk-:focus {\n outline: none;\n}\n\n.V9eYJdjPAeY- {\n position: relative;\n z-index: 6;\n z-index: var(--elevation-menu);\n width: 100%;\n}\n\n.LdytkOeCU9c- {\n max-height: 320px;\n box-shadow: 0px calc(16px / 16) calc(16px / 4) 0px\n rgba(0, 0, 0, 0.1),\n 0px calc(16px / 4) 12px 0px rgba(0, 0, 0, 0.05);\n box-shadow: var(--shadow-base);\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n border-radius: calc(16px / 8);\n border-radius: var(--radius-base);\n overflow-y: scroll;\n background-color: rgba(255, 255, 255, 1);\n background-color: var(--color-surface);\n}\n\n.y7Cz2u8BuNE-,\n._-7bmao8Q9As- {\n display: -ms-flexbox;\n display: flex;\n width: 100%;\n padding: calc(16px / 2);\n padding: var(--space-small);\n border: none;\n background-color: transparent;\n cursor: pointer;\n -webkit-appearance: none;\n appearance: none;\n -ms-flex-align: center;\n align-items: center;\n gap: calc(16px / 2);\n gap: var(--space-small);\n}\n\n.y7Cz2u8BuNE-:focus {\n box-shadow: 0px 0px calc(16px / 4) calc(16px / 8)\n rgb(231, 213, 87);\n box-shadow: var(--shadow-focus);\n}\n\n.y7Cz2u8BuNE-:focus,\n.iCwWdcmtInc- {\n outline: none;\n background-color: rgb(250, 246, 219);\n background-color: var(--color-surface--hover);\n}\n\n._-7bmao8Q9As- {\n -ms-flex-pack: center;\n justify-content: center;\n}\n";
197
+ var styles = {"wrapper":"sCNIjluCskc-","input":"MFUlz1WQLdk-","menu":"V9eYJdjPAeY-","menuList":"LdytkOeCU9c-","menuListOption":"y7Cz2u8BuNE-","loadingIndicator":"_-7bmao8Q9As-","activeOption":"iCwWdcmtInc-"};
198
+ styleInject_es.styleInject(css_248z);
199
+
200
+ function useInternalChipDismissible({ children, selected, onChange, onClick, onCustomAdd, }) {
201
+ const ref = React.useRef(null);
202
+ const chipOptions = children.map(chip => chip.props);
203
+ const visibleChipOptions = chipOptions.filter(chip => selected.includes(chip.value));
204
+ const sortedVisibleChipOptions = sortBy__default["default"](visibleChipOptions, chip => selected.indexOf(chip.value));
205
+ const availableChipOptions = chipOptions.filter(chip => !selected.includes(chip.value));
206
+ const { liveAnnounce } = useLiveAnnounce.useLiveAnnounce();
207
+ const actions = {
208
+ handleChipRemove: (value) => {
209
+ return () => {
210
+ liveAnnounce(`${value} Removed`);
211
+ onChange(selected.filter(val => val !== value));
212
+ };
213
+ },
214
+ handleChipAdd: (value) => onChange([...selected, value]),
215
+ handleCustomAdd: onCustomAdd,
216
+ handleChipClick: (value) => {
217
+ if (onClick === undefined)
218
+ return;
219
+ return (event) => onClick(event, value);
220
+ },
221
+ handleWrapperKeyDown: (event) => {
222
+ const target = event.target;
223
+ const isInputAndHasValue = target instanceof HTMLInputElement && target.value;
224
+ if (isInputAndHasValue)
225
+ return;
226
+ const nextElementToFocus = target.nextElementSibling;
227
+ const prevElementToFocus = target.previousElementSibling;
228
+ if (event.key === "ArrowLeft" &&
229
+ prevElementToFocus instanceof HTMLElement) {
230
+ prevElementToFocus.focus();
231
+ }
232
+ if (event.key === "ArrowRight" &&
233
+ nextElementToFocus instanceof HTMLElement) {
234
+ nextElementToFocus.focus();
235
+ }
236
+ },
237
+ handleChipKeyDown: (value) => {
238
+ return (event) => {
239
+ if (event.key === "Backspace" || event.key === "Delete") {
240
+ const target = event.target;
241
+ if (target instanceof HTMLElement) {
242
+ const prevElement = target.previousElementSibling;
243
+ const nextElement = target.nextElementSibling;
244
+ if (prevElement instanceof HTMLElement) {
245
+ prevElement.focus();
246
+ }
247
+ else if (nextElement instanceof HTMLElement) {
248
+ nextElement.focus();
249
+ }
250
+ }
251
+ actions.handleChipRemove(value)();
252
+ }
253
+ };
254
+ },
255
+ };
256
+ return Object.assign(Object.assign({}, actions), { ref,
257
+ sortedVisibleChipOptions,
258
+ availableChipOptions });
259
+ }
260
+
261
+ const menuId = uuid.v1();
262
+ // eslint-disable-next-line max-statements
263
+ function useInternalChipDismissibleInput({ options, isLoadingMore = false, onCustomOptionSelect, onOptionSelect, onSearch, }) {
264
+ const [allOptions, setAllOptions] = React.useState([]);
265
+ const [searchValue, setSearchValue] = React.useState("");
266
+ const [menuOpen, setMenuOpen] = React.useState(false);
267
+ const [activeIndex, setActiveIndex] = React.useState(0);
268
+ const [shouldCancelBlur, setShouldCancelBlur] = React.useState(false);
269
+ const [shouldCancelEnter, setShouldCancelEnter] = React.useState(false);
270
+ const canAddCustomOption = onCustomOptionSelect !== undefined && !isLoadingMore;
271
+ const maxOptionIndex = allOptions.length - 1;
272
+ const { liveAnnounce } = useLiveAnnounce.useLiveAnnounce();
273
+ React.useEffect(() => {
274
+ setAllOptions(generateOptions(options, searchValue, canAddCustomOption));
275
+ }, [options]);
276
+ const computed = {
277
+ menuId,
278
+ inputRef: React.useRef(null),
279
+ activeOption: allOptions[activeIndex],
280
+ hasAvailableOptions: allOptions.length > 0,
281
+ nextOptionIndex: activeIndex < maxOptionIndex ? activeIndex + 1 : 0,
282
+ previousOptionIndex: activeIndex > 0 ? activeIndex - 1 : maxOptionIndex,
283
+ };
284
+ const actions = {
285
+ generateDescendantId: (index) => `${computed.menuId}-${index}`,
286
+ handleReset: () => {
287
+ setActiveIndex(activeIndex === 0 ? activeIndex : activeIndex - 1);
288
+ setSearchValue("");
289
+ },
290
+ handleOpenMenu: () => setMenuOpen(true),
291
+ handleCloseMenu: () => {
292
+ setMenuOpen(false);
293
+ setActiveIndex(0);
294
+ },
295
+ handleCancelBlur: () => setShouldCancelBlur(true),
296
+ handleEnableBlur: () => setShouldCancelBlur(false),
297
+ handleBlur: () => {
298
+ if (shouldCancelBlur)
299
+ return;
300
+ actions.handleReset();
301
+ actions.handleCloseMenu();
302
+ },
303
+ handleSearchChange: (event) => {
304
+ setActiveIndex(0);
305
+ setSearchValue(event.currentTarget.value);
306
+ setShouldCancelEnter(true);
307
+ },
308
+ handleSetActiveOnMouseOver: (index) => {
309
+ return () => setActiveIndex(index);
310
+ },
311
+ handleSelectOption: (selected) => {
312
+ var _a;
313
+ if (allOptions.length === 0)
314
+ return;
315
+ const setValue = selected.custom ? onCustomOptionSelect : onOptionSelect;
316
+ if (setValue) {
317
+ setValue(selected.value);
318
+ liveAnnounce(`${selected.label} Added`);
319
+ actions.handleReset();
320
+ (_a = computed.inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
321
+ }
322
+ },
323
+ handleKeyDown: (event) => {
324
+ const callbacks = {
325
+ Enter: () => {
326
+ if (shouldCancelEnter)
327
+ return;
328
+ actions.handleSelectOption(computed.activeOption);
329
+ },
330
+ Tab: () => actions.handleSelectOption(computed.activeOption),
331
+ ",": () => {
332
+ if (searchValue.length === 0)
333
+ return;
334
+ actions.handleSelectOption(generateCustomOptionObject(searchValue));
335
+ },
336
+ ArrowDown: () => {
337
+ if (isLoadingMore && activeIndex === maxOptionIndex)
338
+ return;
339
+ setActiveIndex(computed.nextOptionIndex);
340
+ },
341
+ ArrowUp: () => setActiveIndex(computed.previousOptionIndex),
342
+ };
343
+ if (searchValue.length === 0) {
344
+ callbacks.Backspace = () => {
345
+ var _a;
346
+ // If there's no text left to delete,
347
+ // and delete is pressed again, focus on a chip instead.
348
+ const target = (_a = computed.inputRef.current) === null || _a === void 0 ? void 0 : _a.previousElementSibling;
349
+ if (target instanceof HTMLElement) {
350
+ target.focus();
351
+ }
352
+ };
353
+ }
354
+ handleKeydownEvents(callbacks, event);
355
+ },
356
+ handleDebouncedSearch: debounce__default["default"](() => {
357
+ onSearch && onSearch(searchValue);
358
+ setAllOptions(generateOptions(options, searchValue, canAddCustomOption));
359
+ setShouldCancelEnter(false);
360
+ }, 300),
361
+ };
362
+ return Object.assign(Object.assign(Object.assign(Object.assign({}, actions), { activeIndex,
363
+ allOptions }), computed), { menuOpen,
364
+ searchValue,
365
+ shouldCancelBlur });
366
+ }
367
+ function generateOptions(options, searchValue, canAddCustomOption) {
368
+ const allOptions = options
369
+ .filter(option => option.label.toLowerCase().match(searchValue.trim().toLowerCase()))
370
+ .map(opt => (Object.assign(Object.assign({}, opt), { custom: false })));
371
+ const { shouldAddCustomOption, customOption } = generateCustomOption(allOptions, searchValue, canAddCustomOption);
372
+ shouldAddCustomOption && allOptions.push(customOption);
373
+ return allOptions;
374
+ }
375
+ function generateCustomOption(options, searchValue, canAddCustomOption) {
376
+ function shouldAddCustomOption() {
377
+ const isMatchingOption = options.some(option => option.label.toLowerCase() === searchValue.trim().toLowerCase());
378
+ return !!searchValue && !isMatchingOption;
379
+ }
380
+ return {
381
+ shouldAddCustomOption: canAddCustomOption && shouldAddCustomOption(),
382
+ customOption: generateCustomOptionObject(searchValue),
383
+ };
384
+ }
385
+ function generateCustomOptionObject(searchValue) {
386
+ return {
387
+ value: searchValue,
388
+ label: searchValue,
389
+ prefix: React__default["default"].createElement(Icon.Icon, { name: "add" }),
390
+ custom: true,
391
+ };
392
+ }
393
+ function handleKeydownEvents(callbacks, event) {
394
+ if (event.shiftKey)
395
+ return;
396
+ Object.entries(callbacks).forEach(([key, callback]) => {
397
+ if (event.key === key) {
398
+ event.preventDefault();
399
+ callback(event);
400
+ }
401
+ });
402
+ }
403
+
404
+ function useInView() {
405
+ const ref = React.useRef(null);
406
+ const [isInView, setIsInView] = React.useState(false);
407
+ if (window.IntersectionObserver) {
408
+ const observer = new IntersectionObserver(entries => {
409
+ setIsInView(entries[0].isIntersecting);
410
+ });
411
+ React.useEffect(() => {
412
+ ref.current && observer.observe(ref.current);
413
+ return () => {
414
+ if (!ref.current)
415
+ return;
416
+ observer.unobserve(ref.current);
417
+ };
418
+ });
419
+ }
420
+ return { ref, isInView };
421
+ }
422
+
423
+ function useRepositionMenu(attachTo) {
424
+ var _a;
425
+ const [positionElement, setPositionedElementRef] = React.useState();
426
+ const popper = reactPopper.usePopper(attachTo.current, positionElement, {
427
+ modifiers: [
428
+ { name: "offset", options: { offset: [0, 8] } },
429
+ { name: "flip", options: { fallbackPlacements: ["top"] } },
430
+ ],
431
+ });
432
+ const targetWidth = (_a = attachTo.current) === null || _a === void 0 ? void 0 : _a.clientWidth;
433
+ return Object.assign(Object.assign({}, popper), { setPositionedElementRef, targetWidth });
434
+ }
435
+
436
+ function useScrollToActive(index) {
437
+ const ref = React.useRef(null);
438
+ React.useEffect(() => {
439
+ var _a, _b;
440
+ if (!window.HTMLElement.prototype.scrollIntoView)
441
+ return;
442
+ (_b = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.children[index]) === null || _b === void 0 ? void 0 : _b.scrollIntoView({
443
+ behavior: "smooth",
444
+ block: "nearest",
445
+ inline: "start",
446
+ });
447
+ }, [index]);
448
+ return ref;
449
+ }
450
+
451
+ function InternalChipDismissibleInput(props) {
452
+ const { activator = React__default["default"].createElement(Button.Button, { icon: "add", type: "secondary", ariaLabel: "Add" }), attachTo, isLoadingMore = false, onLoadMore, } = props;
453
+ const { activeIndex, allOptions, hasAvailableOptions, inputRef, menuId, menuOpen, searchValue, generateDescendantId, handleBlur, handleOpenMenu, handleSearchChange, handleCancelBlur, handleEnableBlur, handleSetActiveOnMouseOver, handleKeyDown, handleSelectOption, handleDebouncedSearch, } = useInternalChipDismissibleInput(props);
454
+ const menuRef = useScrollToActive(activeIndex);
455
+ const { ref: visibleChildRef, isInView } = useInView();
456
+ const { styles: popperStyles, attributes, update, setPositionedElementRef, } = useRepositionMenu(attachTo);
457
+ React.useEffect(() => {
458
+ if (menuOpen && update)
459
+ update();
460
+ }, [allOptions]);
461
+ React.useEffect(() => {
462
+ handleDebouncedSearch();
463
+ return handleDebouncedSearch.cancel;
464
+ }, [searchValue]);
465
+ React.useEffect(() => {
466
+ isInView && onLoadMore && onLoadMore(searchValue);
467
+ }, [isInView]);
468
+ if (!menuOpen) {
469
+ return React__default["default"].cloneElement(activator, { onClick: handleOpenMenu });
470
+ }
471
+ return (React__default["default"].createElement(React__default["default"].Fragment, null,
472
+ React__default["default"].createElement("input", { ref: inputRef, className: styles.input, type: "text", role: "combobox", "aria-label": "Press up and down arrow to cycle through the options or type to narrow down the results", "aria-autocomplete": "list", "aria-owns": menuId, "aria-expanded": hasAvailableOptions, "aria-activedescendant": generateDescendantId(activeIndex), value: searchValue, onChange: handleSearchChange, onKeyDown: handleKeyDown, onBlur: debounce__default["default"](handleBlur, 200), onFocus: handleOpenMenu, autoFocus: true }),
473
+ (hasAvailableOptions || isLoadingMore) && (React__default["default"].createElement("div", Object.assign({ ref: setPositionedElementRef, className: styles.menu, style: popperStyles.popper }, attributes.popper),
474
+ React__default["default"].createElement("div", { ref: menuRef, role: "listbox", id: menuId, className: styles.menuList, "data-testid": "chip-menu" },
475
+ allOptions.map((option, i) => (React__default["default"].createElement("button", { key: option.value, role: "option", type: "button", id: generateDescendantId(i), className: classnames__default["default"](styles.menuListOption, {
476
+ [styles.activeOption]: activeIndex === i,
477
+ }), onClick: () => handleSelectOption(option), onMouseEnter: handleSetActiveOnMouseOver(i), onMouseDown: handleCancelBlur, onMouseUp: handleEnableBlur },
478
+ React__default["default"].createElement("span", { "aria-hidden": true }, option.prefix),
479
+ React__default["default"].createElement(Text.Text, null, option.label)))),
480
+ React__default["default"].createElement("div", { ref: visibleChildRef }),
481
+ isLoadingMore && (React__default["default"].createElement("div", { className: styles.loadingIndicator },
482
+ React__default["default"].createElement(Spinner.Spinner, { size: "small", inline: true }))))))));
483
+ }
484
+
485
+ function InternalChipDismissible(props) {
486
+ const { availableChipOptions, ref: wrapperRef, sortedVisibleChipOptions, handleChipAdd, handleChipClick, handleChipKeyDown, handleChipRemove, handleCustomAdd, handleWrapperKeyDown, } = useInternalChipDismissible(props);
487
+ return (React__default["default"].createElement("div", { ref: wrapperRef, className: styles.wrapper, "data-testid": "dismissible-chips", onKeyDown: handleWrapperKeyDown, role: "listbox" },
488
+ sortedVisibleChipOptions.map(chip => (React__default["default"].createElement(InternalChip, Object.assign({ key: chip.value }, chip, { onKeyDown: handleChipKeyDown(chip.value), onClick: handleChipClick(chip.value), ariaLabel: `${chip.label}. Press delete or backspace to remove ${chip.label}`, tabIndex: 0, suffix: React__default["default"].createElement(InternalChipButton, { icon: "remove", invalid: chip.invalid, disabled: chip.disabled, label: chip.label, onClick: handleChipRemove(chip.value) }) })))),
489
+ React__default["default"].createElement(InternalChipDismissibleInput, { activator: props.activator, attachTo: wrapperRef, isLoadingMore: props.isLoadingMore, options: availableChipOptions, onOptionSelect: handleChipAdd, onCustomOptionSelect: handleCustomAdd, onSearch: props.onSearch, onLoadMore: props.onLoadMore })));
490
+ }
491
+
492
+ function Chips(props) {
493
+ switch (props.type) {
494
+ case "dismissible":
495
+ return React__default["default"].createElement(InternalChipDismissible, Object.assign({}, props));
496
+ case "multiselect":
497
+ return React__default["default"].createElement(InternalChipMultiSelect, Object.assign({}, props));
498
+ default:
499
+ return React__default["default"].createElement(InternalChipSingleSelect, Object.assign({}, props));
500
+ }
501
+ }
502
+
503
+ // Only need the component to pass in the data as a react children
504
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
505
+ function Chip(props) {
506
+ // Throw error when <Chip /> gets used outside of the <Chips />
507
+ useAssert.useAssert(true, "`<Chip>` component can only be used inside `<Chips>`");
508
+ return React__default["default"].createElement(React__default["default"].Fragment, null);
509
+ }
510
+
511
+ exports.Chip = Chip;
512
+ exports.Chips = Chips;
513
+ exports.InternalChip = InternalChip;
514
+ exports.InternalChipButton = InternalChipButton;
@@ -43,7 +43,7 @@ function ChipSuffix({ children, className }) {
43
43
  }
44
44
  return (React__default["default"].createElement("span", { className: classnames__default["default"](styles.suffix, className, !singleChild && styles.empty) }, singleChild));
45
45
  }
46
- const allowedSuffixIcons = ["cross", "add", "checkmark", "arrowDown"];
46
+ const allowedSuffixIcons = ["cross", "add", "checkmark"];
47
47
 
48
48
  const Chip = ({ ariaLabel, disabled, heading, invalid, label, value, onClick, onKeyDown, children, role = "button", tabIndex = 0, variation = "base", }) => {
49
49
  const classes = classnames__default["default"](styles.chip, {