iff --git a/dist/esm/components/multi-select/CMultiSelect.js b/dist/esm/components/multi-select/CMultiSelect.js
index 1eec708..10836ab 100644
--- a/dist/esm/components/multi-select/CMultiSelect.js
+++ b/dist/esm/components/multi-select/CMultiSelect.js
@@ -122,7 +122,10 @@ const CMultiSelect = forwardRef((_a, ref) => {
nativeSelectRef.current.dispatchEvent(new Event('change', { bubbles: true }));
}
updatePopper();
- }, [JSON.stringify(selected)]);
+ // Keyed on the selected values rather than JSON.stringify(selected). An option may now carry a `labelNode`, and
+ // a React element created during render holds an `_owner` fiber, which makes JSON.stringify throw
+ // ("Converting circular structure to JSON"). The values are what this effect actually depends on.
+ }, [selected.map((option) => option.value).join(',')]);
useEffect(() => {
visible ? openDropdown() : closeDropdown();
}, [visible]);
@@ -333,13 +336,11 @@ const CMultiSelect = forwardRef((_a, ref) => {
? selected.map((option) => option.value.toString())
: selected.map((option) => option.value)[0], onChange: () => onChange && onChange(selected), onKeyDown: handleNativeSelectKeyDown, ref: nativeSelectRef }),
React.createElement("div", Object.assign({ className: "form-multi-select-input-group" }, (!search && !disabled && { tabIndex: 0 }), { onClick: () => !disabled && openDropdown(), onKeyDown: handleTogglerKeyDown, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": `multiselect-listbox-${uniqueId}` }, (portal && { 'aria-owns': `multiselect-listbox-${uniqueId}` }), (disabled && { 'aria-disabled': true }), { ref: dropdownRefElement }),
- React.createElement(CMultiSelectSelection, { ariaTagDeleteLabel: ariaTagDeleteLabel, disabled: disabled, multiple: multiple, onRemove: (option) => !disabled && handleOnOptionClick(option), placeholder: placeholder, search: search, selected: selected, selectionType: selectionType, selectionTypeCounterText: selectionTypeCounterText },
+ React.createElement(CMultiSelectSelection, { ariaTagDeleteLabel: ariaTagDeleteLabel, disabled: disabled, multiple: multiple, onRemove: (option) => !disabled && handleOnOptionClick(option), placeholder: placeholder, search: search, searchValue: searchValue, selected: selected, selectionType: selectionType, selectionTypeCounterText: selectionTypeCounterText },
search && (React.createElement("input", Object.assign({ type: "text", className: "form-multi-select-search", disabled: disabled, id: `search${id !== null && id !== void 0 ? id : uniqueId}`, name: `search${name !== null && name !== void 0 ? name : uniqueId}`, autoComplete: "off", "aria-label": ariaSearchLabel, "aria-autocomplete": "list", "aria-controls": `multiselect-listbox-${uniqueId}`, onChange: handleSearchChange, onKeyDown: handleSearchKeyDown }, (selected.length === 0 && { placeholder: placeholder }), (selected.length > 0 &&
selectionType === 'counter' && {
placeholder: `${selected.length} ${selectionTypeCounterText}`,
}), (selected.length > 0 &&
- !multiple && { placeholder: selected.map((option) => option.label)[0] }), (multiple &&
- selected.length > 0 &&
selectionType !== 'counter' && { size: searchValue.length + 2 }), { ref: searchRef }))),
!search && selected.length === 0 && (React.createElement("span", { className: "form-multi-select-placeholder" }, placeholder))),
React.createElement("div", { className: "form-multi-select-buttons" },
diff --git a/dist/esm/components/multi-select/CMultiSelectSelection.js b/dist/esm/components/multi-select/CMultiSelectSelection.js
index 36a2be1..2568d9c 100644
--- a/dist/esm/components/multi-select/CMultiSelectSelection.js
+++ b/dist/esm/components/multi-select/CMultiSelectSelection.js
@@ -2,7 +2,7 @@ import React, { forwardRef } from 'react';
import classNames from '../../_virtual/index.js';
import PropTypes from 'prop-types';
-const CMultiSelectSelection = forwardRef(({ ariaTagDeleteLabel = 'Remove', children, disabled, multiple, placeholder, onRemove, search, selected = [], selectionType, selectionTypeCounterText, }, ref) => {
+const CMultiSelectSelection = forwardRef(({ ariaTagDeleteLabel = 'Remove', children, disabled, multiple, placeholder, onRemove, search, searchValue = '', selected = [], selectionType, selectionTypeCounterText, }, ref) => {
return (React.createElement("span", { className: classNames('form-multi-select-selection', {
'form-multi-select-selection-tags': multiple && selectionType === 'tags',
}), "aria-live": "polite", ref: ref },
@@ -17,7 +17,7 @@ const CMultiSelectSelection = forwardRef(({ ariaTagDeleteLabel = 'Remove', child
selected.map((option, index) => {
if (selectionType === 'tags') {
return (React.createElement("span", { className: "form-multi-select-tag", key: index },
- option.label,
+ option.labelNode ?? option.label,
!disabled && !option.disabled && (React.createElement("button", { className: "form-multi-select-tag-delete", type: "button", "aria-label": `${ariaTagDeleteLabel} ${option.label}`.trim(), onClick: () => onRemove === null || onRemove === void 0 ? void 0 : onRemove(option) }))));
}
return;
@@ -25,10 +25,13 @@ const CMultiSelectSelection = forwardRef(({ ariaTagDeleteLabel = 'Remove', child
multiple &&
selectionType === 'text' &&
selected.map((option, index) => (React.createElement("span", { key: index },
- option.label,
+ option.labelNode ?? option.label,
index === selected.length - 1 ? '' : ',',
"\u00A0"))),
- !multiple && !search && selected.map((option) => option.label)[0],
+ // Rendered as content rather than the search input's placeholder attribute, so a single select's value
+ // takes normal text colour and can carry a `labelNode` badge. Stepping aside once the user types is what
+ // the placeholder gave us for free.
+ !multiple && !searchValue && selected.map((option) => option.labelNode ?? option.label)[0],
children));
});
CMultiSelectSelection.propTypes = {
@@ -38,6 +41,7 @@ CMultiSelectSelection.propTypes = {
multiple: PropTypes.bool,
onRemove: PropTypes.func,
placeholder: PropTypes.string,
+ searchValue: PropTypes.string,
search: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf(['external', 'global']),
diff --git a/dist/esm/components/multi-select/types.d.ts b/dist/esm/components/multi-select/types.d.ts
index 8015721..8248e3a 100644
--- a/dist/esm/components/multi-select/types.d.ts
+++ b/dist/esm/components/multi-select/types.d.ts
@@ -3,6 +3,13 @@ export type Option = {
disabled?: boolean;
selected?: boolean;
label: string;
+ /**
+ * Patched in by workforce. Rendered instead of `label` wherever CoreUI renders the selection as content
+ * (multi-select tags, `selectionType="text"`, and a single select with `search` off), so a selected value can
+ * carry markup such as a status badge. `label` is still required, and remains what CoreUI uses for the
+ * `placeholder` and `aria-label` attributes, for the mirrored native <select>, and for its own search matching.
+ */
+ labelNode?: import('react').ReactNode;
value: number | string;
[key: string]: number | string | any;
};
This is how we've done it:
Which lets us build something like this:
(which is what we had with
react-selectwhich we're finally porting over so we're fully CoreUI)