myoperator-mcp 0.2.291 → 0.2.292
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/dist/index.js +267 -129
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1450,88 +1450,154 @@ ContactListItem.displayName = "ContactListItem";
|
|
|
1450
1450
|
|
|
1451
1451
|
export { ContactListItem };
|
|
1452
1452
|
`,
|
|
1453
|
-
"creatable-multi-select": `import * as React from "react"
|
|
1454
|
-
import { ChevronDown, Plus, Info, X } from "lucide-react"
|
|
1453
|
+
"creatable-multi-select": `import * as React from "react";
|
|
1454
|
+
import { ChevronDown, Plus, Info, X } from "lucide-react";
|
|
1455
1455
|
|
|
1456
|
-
import { cn } from "@/lib/utils"
|
|
1456
|
+
import { cn } from "@/lib/utils";
|
|
1457
1457
|
import {
|
|
1458
1458
|
creatableSelectTriggerVariants,
|
|
1459
1459
|
creatableEnterHintKbdClassName,
|
|
1460
1460
|
creatablePrimaryRoleHintRowClassName,
|
|
1461
|
-
} from "./creatable-select"
|
|
1461
|
+
} from "./creatable-select";
|
|
1462
1462
|
|
|
1463
1463
|
/** @deprecated Use \`creatableSelectTriggerVariants\` from \`./creatable-select\` \u2014 aliases the same trigger styles as Primary Role. */
|
|
1464
|
-
const creatableMultiSelectTriggerVariants = creatableSelectTriggerVariants
|
|
1464
|
+
const creatableMultiSelectTriggerVariants = creatableSelectTriggerVariants;
|
|
1465
1465
|
|
|
1466
1466
|
export interface CreatableMultiSelectOption {
|
|
1467
|
-
value: string
|
|
1468
|
-
label: string
|
|
1469
|
-
disabled?: boolean
|
|
1467
|
+
value: string;
|
|
1468
|
+
label: string;
|
|
1469
|
+
disabled?: boolean;
|
|
1470
1470
|
}
|
|
1471
1471
|
|
|
1472
|
-
export interface CreatableMultiSelectProps
|
|
1473
|
-
|
|
1472
|
+
export interface CreatableMultiSelectProps extends Omit<
|
|
1473
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
1474
|
+
"onChange"
|
|
1475
|
+
> {
|
|
1474
1476
|
/** Currently selected values */
|
|
1475
|
-
value?: string[]
|
|
1477
|
+
value?: string[];
|
|
1476
1478
|
/** Callback when values change */
|
|
1477
|
-
onValueChange?: (values: string[]) => void
|
|
1479
|
+
onValueChange?: (values: string[]) => void;
|
|
1478
1480
|
/** Available preset options */
|
|
1479
|
-
options?: CreatableMultiSelectOption[]
|
|
1481
|
+
options?: CreatableMultiSelectOption[];
|
|
1480
1482
|
/** Placeholder when no values selected */
|
|
1481
|
-
placeholder?: string
|
|
1483
|
+
placeholder?: string;
|
|
1482
1484
|
/** Whether the component is disabled */
|
|
1483
|
-
disabled?: boolean
|
|
1485
|
+
disabled?: boolean;
|
|
1484
1486
|
/** Error state */
|
|
1485
|
-
state?: "default" | "error"
|
|
1487
|
+
state?: "default" | "error";
|
|
1486
1488
|
/** Helper text shown below the trigger */
|
|
1487
|
-
helperText?: string
|
|
1489
|
+
helperText?: string;
|
|
1488
1490
|
/**
|
|
1489
1491
|
* Shown inside the open dropdown (e.g. "Type to create a custom tone").
|
|
1490
1492
|
* Pair with {@link maxItems} so users see guidance when no preset matches their typing.
|
|
1491
1493
|
*/
|
|
1492
|
-
createHintText?: string
|
|
1494
|
+
createHintText?: string;
|
|
1493
1495
|
/** Max number of items that can be selected (default: unlimited) */
|
|
1494
|
-
maxItems?: number
|
|
1496
|
+
maxItems?: number;
|
|
1495
1497
|
/** Max character length per item when typing/creating (default: unlimited) */
|
|
1496
|
-
maxLengthPerItem?: number
|
|
1498
|
+
maxLengthPerItem?: number;
|
|
1497
1499
|
/**
|
|
1498
1500
|
* When true (default), shows \`current/max\` under the trigger while typing when \`maxLengthPerItem\` is set.
|
|
1499
1501
|
* Set to false to match Figma (counter lives only in the field flow / not under the control).
|
|
1500
1502
|
*/
|
|
1501
|
-
showPerItemCharacterCounter?: boolean
|
|
1503
|
+
showPerItemCharacterCounter?: boolean;
|
|
1502
1504
|
/**
|
|
1503
1505
|
* Closed trigger: show removable chips (default) or a single comma-separated summary line (Figma Tone).
|
|
1504
1506
|
* While open, the trigger always shows the summary line; selected chips with remove controls appear in the panel.
|
|
1505
1507
|
*/
|
|
1506
|
-
triggerDisplay?: "chips" | "summary"
|
|
1508
|
+
triggerDisplay?: "chips" | "summary";
|
|
1507
1509
|
/**
|
|
1508
1510
|
* When set, the text input is transformed (e.g. strip invalid characters).
|
|
1509
1511
|
* If the raw value differs from the sanitized value, \`onInvalidCharacters\` is called.
|
|
1510
1512
|
*/
|
|
1511
|
-
sanitizeInput?: (raw: string) => string
|
|
1513
|
+
sanitizeInput?: (raw: string) => string;
|
|
1512
1514
|
/** Fired when \`sanitizeInput\` removed one or more characters from the raw input. */
|
|
1513
|
-
onInvalidCharacters?: () => void
|
|
1515
|
+
onInvalidCharacters?: () => void;
|
|
1514
1516
|
/**
|
|
1515
1517
|
* When \`sanitizeInput\` is set, fired on input change if the raw value is already valid.
|
|
1516
1518
|
* Use to clear validation errors when the user corrects input.
|
|
1517
1519
|
*/
|
|
1518
|
-
onValidInput?: () => void
|
|
1520
|
+
onValidInput?: () => void;
|
|
1521
|
+
/** Fired with the current open-dropdown draft text so parents can validate while typing. */
|
|
1522
|
+
onInputValueChange?: (value: string) => void;
|
|
1519
1523
|
}
|
|
1520
1524
|
|
|
1521
1525
|
function joinSelectedLabels(
|
|
1522
1526
|
values: string[],
|
|
1523
|
-
options: CreatableMultiSelectOption[]
|
|
1527
|
+
options: CreatableMultiSelectOption[],
|
|
1528
|
+
sanitizeInput?: (raw: string) => string,
|
|
1529
|
+
maxLengthPerItem?: number
|
|
1524
1530
|
): string {
|
|
1525
1531
|
return values
|
|
1526
|
-
.map((val) =>
|
|
1527
|
-
.join(", ")
|
|
1532
|
+
.map((val) => labelForValue(val, options, sanitizeInput, maxLengthPerItem))
|
|
1533
|
+
.join(", ");
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
function storedValueCandidates(
|
|
1537
|
+
option: CreatableMultiSelectOption,
|
|
1538
|
+
sanitizeInput?: (raw: string) => string,
|
|
1539
|
+
maxLengthPerItem?: number
|
|
1540
|
+
): string[] {
|
|
1541
|
+
const values = [option.value];
|
|
1542
|
+
if (sanitizeInput) values.push(sanitizeInput(option.value).trim());
|
|
1543
|
+
if (maxLengthPerItem != null) {
|
|
1544
|
+
values.push(option.value.slice(0, maxLengthPerItem));
|
|
1545
|
+
if (sanitizeInput) {
|
|
1546
|
+
values.push(
|
|
1547
|
+
sanitizeInput(option.value).trim().slice(0, maxLengthPerItem)
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
1528
1552
|
}
|
|
1529
1553
|
|
|
1530
1554
|
function labelForValue(
|
|
1531
1555
|
val: string,
|
|
1532
|
-
options: CreatableMultiSelectOption[]
|
|
1556
|
+
options: CreatableMultiSelectOption[],
|
|
1557
|
+
sanitizeInput?: (raw: string) => string,
|
|
1558
|
+
maxLengthPerItem?: number
|
|
1533
1559
|
): string {
|
|
1534
|
-
|
|
1560
|
+
const direct = options.find((o) => o.value === val);
|
|
1561
|
+
if (direct) return direct.label;
|
|
1562
|
+
const byStoredForm = options.find((o) =>
|
|
1563
|
+
storedValueCandidates(o, sanitizeInput, maxLengthPerItem).includes(val)
|
|
1564
|
+
);
|
|
1565
|
+
if (byStoredForm) return byStoredForm.label;
|
|
1566
|
+
return val;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
/** Whether a preset option is already in the selection (including legacy stored forms). */
|
|
1570
|
+
function isOptionSelected(
|
|
1571
|
+
option: CreatableMultiSelectOption,
|
|
1572
|
+
selected: string[],
|
|
1573
|
+
sanitizeInput?: (raw: string) => string,
|
|
1574
|
+
maxLengthPerItem?: number
|
|
1575
|
+
): boolean {
|
|
1576
|
+
return storedValueCandidates(option, sanitizeInput, maxLengthPerItem).some(
|
|
1577
|
+
(candidate) => selected.includes(candidate)
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/** Whether a candidate value is already selected (matches raw or legacy preset forms). */
|
|
1582
|
+
function isValueAlreadySelected(
|
|
1583
|
+
candidate: string,
|
|
1584
|
+
selected: string[],
|
|
1585
|
+
options: CreatableMultiSelectOption[],
|
|
1586
|
+
sanitizeInput?: (raw: string) => string,
|
|
1587
|
+
maxLengthPerItem?: number
|
|
1588
|
+
): boolean {
|
|
1589
|
+
if (selected.includes(candidate)) return true;
|
|
1590
|
+
return options.some((o) => {
|
|
1591
|
+
const candidates = storedValueCandidates(
|
|
1592
|
+
o,
|
|
1593
|
+
sanitizeInput,
|
|
1594
|
+
maxLengthPerItem
|
|
1595
|
+
);
|
|
1596
|
+
return (
|
|
1597
|
+
candidates.includes(candidate) &&
|
|
1598
|
+
candidates.some((stored) => selected.includes(stored))
|
|
1599
|
+
);
|
|
1600
|
+
});
|
|
1535
1601
|
}
|
|
1536
1602
|
|
|
1537
1603
|
const CreatableMultiSelect = React.forwardRef(
|
|
@@ -1553,63 +1619,96 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1553
1619
|
sanitizeInput,
|
|
1554
1620
|
onInvalidCharacters,
|
|
1555
1621
|
onValidInput,
|
|
1622
|
+
onInputValueChange,
|
|
1556
1623
|
...props
|
|
1557
1624
|
}: CreatableMultiSelectProps,
|
|
1558
1625
|
ref: React.Ref<HTMLDivElement>
|
|
1559
1626
|
) => {
|
|
1560
|
-
const [isOpen, setIsOpen] = React.useState(false)
|
|
1561
|
-
const [inputValue, setInputValue] = React.useState("")
|
|
1562
|
-
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
1563
|
-
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
1564
|
-
const listboxId = React.useId()
|
|
1627
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
1628
|
+
const [inputValue, setInputValue] = React.useState("");
|
|
1629
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
1630
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
1631
|
+
const listboxId = React.useId();
|
|
1565
1632
|
|
|
1566
|
-
React.useImperativeHandle(ref, () => containerRef.current!)
|
|
1633
|
+
React.useImperativeHandle(ref, () => containerRef.current!);
|
|
1567
1634
|
|
|
1568
|
-
const derivedState = state === "error" ? "error" : "default"
|
|
1635
|
+
const derivedState = state === "error" ? "error" : "default";
|
|
1569
1636
|
|
|
1570
|
-
const selectedSummary = joinSelectedLabels(
|
|
1637
|
+
const selectedSummary = joinSelectedLabels(
|
|
1638
|
+
value,
|
|
1639
|
+
options,
|
|
1640
|
+
sanitizeInput,
|
|
1641
|
+
maxLengthPerItem
|
|
1642
|
+
);
|
|
1571
1643
|
|
|
1572
1644
|
const addValue = (val: string) => {
|
|
1573
|
-
const
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
|
|
1645
|
+
const isPreset = options.some((o) => o.value === val);
|
|
1646
|
+
const afterSanitize = isPreset
|
|
1647
|
+
? val
|
|
1648
|
+
: sanitizeInput
|
|
1649
|
+
? sanitizeInput(val)
|
|
1650
|
+
: val;
|
|
1651
|
+
const trimmed = afterSanitize.trim();
|
|
1652
|
+
if (
|
|
1653
|
+
!trimmed ||
|
|
1654
|
+
isValueAlreadySelected(
|
|
1655
|
+
trimmed,
|
|
1656
|
+
value,
|
|
1657
|
+
options,
|
|
1658
|
+
sanitizeInput,
|
|
1659
|
+
maxLengthPerItem
|
|
1660
|
+
)
|
|
1661
|
+
) {
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
if (maxItems != null && value.length >= maxItems) return;
|
|
1577
1665
|
const toAdd =
|
|
1578
|
-
maxLengthPerItem != null
|
|
1666
|
+
!isPreset && maxLengthPerItem != null
|
|
1579
1667
|
? trimmed.slice(0, maxLengthPerItem)
|
|
1580
|
-
: trimmed
|
|
1668
|
+
: trimmed;
|
|
1581
1669
|
if (toAdd) {
|
|
1582
|
-
|
|
1583
|
-
|
|
1670
|
+
const nextValue = [...value, toAdd];
|
|
1671
|
+
onValueChange?.(nextValue);
|
|
1672
|
+
setInputValue("");
|
|
1673
|
+
onInputValueChange?.("");
|
|
1674
|
+
const reachedMax =
|
|
1675
|
+
maxItems != null && nextValue.length >= maxItems;
|
|
1676
|
+
if (reachedMax) {
|
|
1677
|
+
setIsOpen(false);
|
|
1678
|
+
} else {
|
|
1679
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
1680
|
+
}
|
|
1584
1681
|
}
|
|
1585
|
-
}
|
|
1682
|
+
};
|
|
1586
1683
|
|
|
1587
1684
|
const removeValue = (val: string) => {
|
|
1588
|
-
onValueChange?.(value.filter((v) => v !== val))
|
|
1589
|
-
}
|
|
1685
|
+
onValueChange?.(value.filter((v) => v !== val));
|
|
1686
|
+
};
|
|
1590
1687
|
|
|
1591
1688
|
const handleOpen = React.useCallback(() => {
|
|
1592
|
-
if (disabled) return
|
|
1593
|
-
setIsOpen(true)
|
|
1594
|
-
setInputValue("")
|
|
1595
|
-
|
|
1689
|
+
if (disabled) return;
|
|
1690
|
+
setIsOpen(true);
|
|
1691
|
+
setInputValue("");
|
|
1692
|
+
onInputValueChange?.("");
|
|
1693
|
+
}, [disabled, onInputValueChange]);
|
|
1596
1694
|
|
|
1597
1695
|
React.useEffect(() => {
|
|
1598
|
-
if (!isOpen) return
|
|
1599
|
-
requestAnimationFrame(() => inputRef.current?.focus())
|
|
1600
|
-
}, [isOpen])
|
|
1696
|
+
if (!isOpen) return;
|
|
1697
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
1698
|
+
}, [isOpen]);
|
|
1601
1699
|
|
|
1602
1700
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
1603
1701
|
if (e.key === "Enter") {
|
|
1604
|
-
e.preventDefault()
|
|
1605
|
-
if (inputValue.trim()) addValue(inputValue)
|
|
1702
|
+
e.preventDefault();
|
|
1703
|
+
if (inputValue.trim()) addValue(inputValue);
|
|
1606
1704
|
} else if (e.key === "Backspace" && !inputValue && value.length > 0) {
|
|
1607
|
-
removeValue(value[value.length - 1])
|
|
1705
|
+
removeValue(value[value.length - 1]);
|
|
1608
1706
|
} else if (e.key === "Escape") {
|
|
1609
|
-
setIsOpen(false)
|
|
1610
|
-
setInputValue("")
|
|
1707
|
+
setIsOpen(false);
|
|
1708
|
+
setInputValue("");
|
|
1709
|
+
onInputValueChange?.("");
|
|
1611
1710
|
}
|
|
1612
|
-
}
|
|
1711
|
+
};
|
|
1613
1712
|
|
|
1614
1713
|
// Close on outside click
|
|
1615
1714
|
React.useEffect(() => {
|
|
@@ -1618,34 +1717,45 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1618
1717
|
containerRef.current &&
|
|
1619
1718
|
!containerRef.current.contains(e.target as Node)
|
|
1620
1719
|
) {
|
|
1621
|
-
setIsOpen(false)
|
|
1622
|
-
setInputValue("")
|
|
1720
|
+
setIsOpen(false);
|
|
1721
|
+
setInputValue("");
|
|
1722
|
+
onInputValueChange?.("");
|
|
1623
1723
|
}
|
|
1624
|
-
}
|
|
1625
|
-
document.addEventListener("mousedown", handler)
|
|
1626
|
-
return () => document.removeEventListener("mousedown", handler)
|
|
1627
|
-
}, [])
|
|
1724
|
+
};
|
|
1725
|
+
document.addEventListener("mousedown", handler);
|
|
1726
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
1727
|
+
}, [onInputValueChange]);
|
|
1628
1728
|
|
|
1629
1729
|
const availablePresets = options.filter(
|
|
1630
|
-
(o) =>
|
|
1631
|
-
|
|
1632
|
-
|
|
1730
|
+
(o) =>
|
|
1731
|
+
!isOptionSelected(o, value, sanitizeInput, maxLengthPerItem) &&
|
|
1732
|
+
!o.disabled
|
|
1733
|
+
);
|
|
1734
|
+
const trimmedInput = inputValue.trim();
|
|
1633
1735
|
const filteredPresets = trimmedInput
|
|
1634
1736
|
? availablePresets.filter((o) =>
|
|
1635
1737
|
o.label.toLowerCase().includes(trimmedInput.toLowerCase())
|
|
1636
1738
|
)
|
|
1637
|
-
: availablePresets
|
|
1739
|
+
: availablePresets;
|
|
1638
1740
|
|
|
1639
1741
|
const isCustomDraft =
|
|
1640
1742
|
trimmedInput.length > 0 &&
|
|
1641
1743
|
!options.some(
|
|
1642
1744
|
(o) => o.label.toLowerCase() === trimmedInput.toLowerCase()
|
|
1643
1745
|
) &&
|
|
1644
|
-
!
|
|
1645
|
-
|
|
1746
|
+
!isValueAlreadySelected(
|
|
1747
|
+
trimmedInput,
|
|
1748
|
+
value,
|
|
1749
|
+
options,
|
|
1750
|
+
sanitizeInput,
|
|
1751
|
+
maxLengthPerItem
|
|
1752
|
+
) &&
|
|
1753
|
+
(maxItems == null || value.length < maxItems);
|
|
1646
1754
|
|
|
1647
1755
|
const summaryTriggerLabel =
|
|
1648
|
-
value.length === 0 ? placeholder : selectedSummary
|
|
1756
|
+
value.length === 0 ? placeholder : selectedSummary;
|
|
1757
|
+
|
|
1758
|
+
const canAddMore = maxItems == null || value.length < maxItems;
|
|
1649
1759
|
|
|
1650
1760
|
return (
|
|
1651
1761
|
<div
|
|
@@ -1661,11 +1771,11 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1661
1771
|
"flex h-auto min-h-[42px] cursor-text items-start gap-2 py-2 text-left"
|
|
1662
1772
|
)}
|
|
1663
1773
|
onClick={(e) => {
|
|
1664
|
-
if (disabled) return
|
|
1774
|
+
if (disabled) return;
|
|
1665
1775
|
if ((e.target as HTMLElement).closest("[data-chip-remove]")) {
|
|
1666
|
-
return
|
|
1776
|
+
return;
|
|
1667
1777
|
}
|
|
1668
|
-
inputRef.current?.focus()
|
|
1778
|
+
inputRef.current?.focus();
|
|
1669
1779
|
}}
|
|
1670
1780
|
>
|
|
1671
1781
|
<div className="flex min-h-0 min-w-0 flex-1 flex-wrap content-start items-center gap-1.5">
|
|
@@ -1676,25 +1786,35 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1676
1786
|
className="inline-flex max-w-full items-center gap-0.5 rounded bg-semantic-bg-ui py-1 pl-2 pr-0.5 text-sm text-semantic-text-primary"
|
|
1677
1787
|
>
|
|
1678
1788
|
<span className="min-w-0 truncate">
|
|
1679
|
-
{labelForValue(
|
|
1789
|
+
{labelForValue(
|
|
1790
|
+
val,
|
|
1791
|
+
options,
|
|
1792
|
+
sanitizeInput,
|
|
1793
|
+
maxLengthPerItem
|
|
1794
|
+
)}
|
|
1680
1795
|
</span>
|
|
1681
1796
|
<button
|
|
1682
1797
|
type="button"
|
|
1683
1798
|
data-chip-remove
|
|
1684
1799
|
disabled={disabled}
|
|
1685
|
-
aria-label={\`Remove \${labelForValue(
|
|
1800
|
+
aria-label={\`Remove \${labelForValue(
|
|
1801
|
+
val,
|
|
1802
|
+
options,
|
|
1803
|
+
sanitizeInput,
|
|
1804
|
+
maxLengthPerItem
|
|
1805
|
+
)}\`}
|
|
1686
1806
|
className={cn(
|
|
1687
1807
|
"inline-flex size-6 shrink-0 items-center justify-center rounded text-semantic-text-muted transition-colors",
|
|
1688
1808
|
!disabled &&
|
|
1689
1809
|
"hover:bg-semantic-bg-hover hover:text-semantic-text-primary"
|
|
1690
1810
|
)}
|
|
1691
1811
|
onMouseDown={(e) => {
|
|
1692
|
-
e.preventDefault()
|
|
1693
|
-
e.stopPropagation()
|
|
1812
|
+
e.preventDefault();
|
|
1813
|
+
e.stopPropagation();
|
|
1694
1814
|
}}
|
|
1695
1815
|
onClick={(e) => {
|
|
1696
|
-
e.stopPropagation()
|
|
1697
|
-
if (!disabled) removeValue(val)
|
|
1816
|
+
e.stopPropagation();
|
|
1817
|
+
if (!disabled) removeValue(val);
|
|
1698
1818
|
}}
|
|
1699
1819
|
>
|
|
1700
1820
|
<X className="size-3.5" strokeWidth={2} aria-hidden />
|
|
@@ -1711,17 +1831,18 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1711
1831
|
type="text"
|
|
1712
1832
|
value={inputValue}
|
|
1713
1833
|
onChange={(e) => {
|
|
1714
|
-
const raw = e.target.value
|
|
1715
|
-
const sanitized = sanitizeInput ? sanitizeInput(raw) : raw
|
|
1834
|
+
const raw = e.target.value;
|
|
1835
|
+
const sanitized = sanitizeInput ? sanitizeInput(raw) : raw;
|
|
1716
1836
|
if (sanitizeInput) {
|
|
1717
|
-
if (raw !== sanitized) onInvalidCharacters?.()
|
|
1718
|
-
else onValidInput?.()
|
|
1837
|
+
if (raw !== sanitized) onInvalidCharacters?.();
|
|
1838
|
+
else onValidInput?.();
|
|
1719
1839
|
}
|
|
1720
|
-
|
|
1840
|
+
const nextInput =
|
|
1721
1841
|
maxLengthPerItem != null
|
|
1722
1842
|
? sanitized.slice(0, maxLengthPerItem)
|
|
1723
|
-
: sanitized
|
|
1724
|
-
)
|
|
1843
|
+
: sanitized;
|
|
1844
|
+
setInputValue(nextInput);
|
|
1845
|
+
onInputValueChange?.(nextInput);
|
|
1725
1846
|
}}
|
|
1726
1847
|
maxLength={maxLengthPerItem}
|
|
1727
1848
|
onKeyDown={handleKeyDown}
|
|
@@ -1755,18 +1876,18 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1755
1876
|
aria-controls={listboxId}
|
|
1756
1877
|
aria-disabled={disabled || undefined}
|
|
1757
1878
|
onKeyDown={(e) => {
|
|
1758
|
-
if (disabled) return
|
|
1879
|
+
if (disabled) return;
|
|
1759
1880
|
if (e.key === "Enter" || e.key === " ") {
|
|
1760
|
-
e.preventDefault()
|
|
1761
|
-
handleOpen()
|
|
1881
|
+
e.preventDefault();
|
|
1882
|
+
handleOpen();
|
|
1762
1883
|
}
|
|
1763
1884
|
}}
|
|
1764
1885
|
onClick={(e) => {
|
|
1765
|
-
if (disabled) return
|
|
1886
|
+
if (disabled) return;
|
|
1766
1887
|
if ((e.target as HTMLElement).closest("[data-chip-remove]")) {
|
|
1767
|
-
return
|
|
1888
|
+
return;
|
|
1768
1889
|
}
|
|
1769
|
-
handleOpen()
|
|
1890
|
+
handleOpen();
|
|
1770
1891
|
}}
|
|
1771
1892
|
className={cn(
|
|
1772
1893
|
creatableSelectTriggerVariants({ state: derivedState }),
|
|
@@ -1802,25 +1923,35 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1802
1923
|
className="inline-flex max-w-full items-center gap-0.5 rounded bg-semantic-bg-ui py-1 pl-2 pr-0.5 text-sm text-semantic-text-primary"
|
|
1803
1924
|
>
|
|
1804
1925
|
<span className="min-w-0 truncate">
|
|
1805
|
-
{labelForValue(
|
|
1926
|
+
{labelForValue(
|
|
1927
|
+
val,
|
|
1928
|
+
options,
|
|
1929
|
+
sanitizeInput,
|
|
1930
|
+
maxLengthPerItem
|
|
1931
|
+
)}
|
|
1806
1932
|
</span>
|
|
1807
1933
|
<button
|
|
1808
1934
|
type="button"
|
|
1809
1935
|
data-chip-remove
|
|
1810
1936
|
disabled={disabled}
|
|
1811
|
-
aria-label={\`Remove \${labelForValue(
|
|
1937
|
+
aria-label={\`Remove \${labelForValue(
|
|
1938
|
+
val,
|
|
1939
|
+
options,
|
|
1940
|
+
sanitizeInput,
|
|
1941
|
+
maxLengthPerItem
|
|
1942
|
+
)}\`}
|
|
1812
1943
|
className={cn(
|
|
1813
1944
|
"inline-flex size-6 shrink-0 items-center justify-center rounded text-semantic-text-muted transition-colors",
|
|
1814
1945
|
!disabled &&
|
|
1815
1946
|
"hover:bg-semantic-bg-hover hover:text-semantic-text-primary"
|
|
1816
1947
|
)}
|
|
1817
1948
|
onMouseDown={(e) => {
|
|
1818
|
-
e.preventDefault()
|
|
1819
|
-
e.stopPropagation()
|
|
1949
|
+
e.preventDefault();
|
|
1950
|
+
e.stopPropagation();
|
|
1820
1951
|
}}
|
|
1821
1952
|
onClick={(e) => {
|
|
1822
|
-
e.stopPropagation()
|
|
1823
|
-
if (!disabled) removeValue(val)
|
|
1953
|
+
e.stopPropagation();
|
|
1954
|
+
if (!disabled) removeValue(val);
|
|
1824
1955
|
}}
|
|
1825
1956
|
>
|
|
1826
1957
|
<X className="size-3.5" strokeWidth={2} aria-hidden />
|
|
@@ -1838,30 +1969,28 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1838
1969
|
|
|
1839
1970
|
{/* Dropdown panel: decorative hint row, counter, max-selections, presets. Input lives in the trigger above. */}
|
|
1840
1971
|
{isOpen && (
|
|
1841
|
-
<div
|
|
1842
|
-
className="absolute left-0 top-full z-[9999] mt-1 flex w-full flex-col overflow-hidden rounded border border-solid border-semantic-border-layout bg-semantic-bg-primary shadow-sm animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200"
|
|
1843
|
-
>
|
|
1972
|
+
<div className="absolute left-0 top-full z-[9999] mt-1 flex w-full flex-col overflow-hidden rounded border border-solid border-semantic-border-layout bg-semantic-bg-primary shadow-sm animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200">
|
|
1844
1973
|
{createHintText ? (
|
|
1845
1974
|
<div className={creatablePrimaryRoleHintRowClassName}>
|
|
1846
1975
|
<span className="text-sm text-semantic-text-muted">
|
|
1847
1976
|
{createHintText}
|
|
1848
1977
|
</span>
|
|
1849
|
-
<kbd className={creatableEnterHintKbdClassName}>
|
|
1850
|
-
Enter \u21B5
|
|
1851
|
-
</kbd>
|
|
1978
|
+
<kbd className={creatableEnterHintKbdClassName}>Enter \u21B5</kbd>
|
|
1852
1979
|
</div>
|
|
1853
1980
|
) : null}
|
|
1854
1981
|
|
|
1855
|
-
{(
|
|
1982
|
+
{(canAddMore ||
|
|
1983
|
+
filteredPresets.length > 0 ||
|
|
1984
|
+
isCustomDraft) && (
|
|
1856
1985
|
<div
|
|
1857
1986
|
className={cn(
|
|
1858
1987
|
"flex flex-col px-4",
|
|
1859
|
-
filteredPresets.length > 0
|
|
1988
|
+
filteredPresets.length > 0 || (canAddMore && maxItems != null)
|
|
1860
1989
|
? "gap-2.5 pb-4 pt-2.5"
|
|
1861
1990
|
: "py-1"
|
|
1862
1991
|
)}
|
|
1863
1992
|
>
|
|
1864
|
-
{maxItems != null &&
|
|
1993
|
+
{maxItems != null && canAddMore ? (
|
|
1865
1994
|
<p className="m-0 text-sm text-semantic-text-muted">
|
|
1866
1995
|
Max selections allowed: {maxItems}
|
|
1867
1996
|
</p>
|
|
@@ -1880,8 +2009,8 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1880
2009
|
role="option"
|
|
1881
2010
|
aria-selected={false}
|
|
1882
2011
|
onMouseDown={(e) => {
|
|
1883
|
-
e.preventDefault()
|
|
1884
|
-
addValue(option.value)
|
|
2012
|
+
e.preventDefault();
|
|
2013
|
+
addValue(option.value);
|
|
1885
2014
|
}}
|
|
1886
2015
|
className="inline-flex items-center gap-2.5 whitespace-nowrap rounded border-0 bg-semantic-bg-ui px-2 py-1 text-left text-sm text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
|
|
1887
2016
|
>
|
|
@@ -1902,8 +2031,8 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1902
2031
|
role="option"
|
|
1903
2032
|
aria-selected={false}
|
|
1904
2033
|
onMouseDown={(e) => {
|
|
1905
|
-
e.preventDefault()
|
|
1906
|
-
addValue(inputValue)
|
|
2034
|
+
e.preventDefault();
|
|
2035
|
+
addValue(inputValue);
|
|
1907
2036
|
}}
|
|
1908
2037
|
className="-mx-4 flex w-[calc(100%+2rem)] items-center gap-2 rounded-none px-4 py-2 text-left text-base text-semantic-text-link outline-none transition-colors cursor-pointer select-none hover:bg-semantic-bg-ui"
|
|
1909
2038
|
>
|
|
@@ -1919,18 +2048,16 @@ const CreatableMultiSelect = React.forwardRef(
|
|
|
1919
2048
|
{helperText && !isOpen ? (
|
|
1920
2049
|
<div className="mt-1.5 flex items-center gap-1.5">
|
|
1921
2050
|
<Info className="size-[18px] shrink-0 text-semantic-text-muted" />
|
|
1922
|
-
<p className="m-0 text-sm text-semantic-text-muted">
|
|
1923
|
-
{helperText}
|
|
1924
|
-
</p>
|
|
2051
|
+
<p className="m-0 text-sm text-semantic-text-muted">{helperText}</p>
|
|
1925
2052
|
</div>
|
|
1926
2053
|
) : null}
|
|
1927
2054
|
</div>
|
|
1928
|
-
)
|
|
2055
|
+
);
|
|
1929
2056
|
}
|
|
1930
|
-
)
|
|
1931
|
-
CreatableMultiSelect.displayName = "CreatableMultiSelect"
|
|
2057
|
+
);
|
|
2058
|
+
CreatableMultiSelect.displayName = "CreatableMultiSelect";
|
|
1932
2059
|
|
|
1933
|
-
export { CreatableMultiSelect, creatableMultiSelectTriggerVariants }
|
|
2060
|
+
export { CreatableMultiSelect, creatableMultiSelectTriggerVariants };
|
|
1934
2061
|
`,
|
|
1935
2062
|
"creatable-select": `import * as React from "react"
|
|
1936
2063
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
@@ -1946,7 +2073,7 @@ const creatableSelectTriggerVariants = cva(
|
|
|
1946
2073
|
default:
|
|
1947
2074
|
"border border-solid border-semantic-border-input focus-within:border-semantic-border-input-focus/50 focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
|
|
1948
2075
|
error:
|
|
1949
|
-
"border border-solid border-semantic-error-primary
|
|
2076
|
+
"border border-solid border-semantic-error-primary focus-within:border-semantic-error-primary focus-within:shadow-[0_0_0_1px_rgba(240,68,56,0.12)]",
|
|
1950
2077
|
},
|
|
1951
2078
|
},
|
|
1952
2079
|
defaultVariants: {
|
|
@@ -2020,6 +2147,8 @@ export interface CreatableSelectProps
|
|
|
2020
2147
|
* (nothing was stripped). Use to clear validation errors when the user corrects input.
|
|
2021
2148
|
*/
|
|
2022
2149
|
onValidInput?: () => void
|
|
2150
|
+
/** Fired with the current open-dropdown draft text so parents can validate while typing. */
|
|
2151
|
+
onInputValueChange?: (value: string) => void
|
|
2023
2152
|
}
|
|
2024
2153
|
|
|
2025
2154
|
const CreatableSelect = React.forwardRef(
|
|
@@ -2038,6 +2167,7 @@ const CreatableSelect = React.forwardRef(
|
|
|
2038
2167
|
normalizeComboboxInput,
|
|
2039
2168
|
onInvalidCharacters,
|
|
2040
2169
|
onValidInput,
|
|
2170
|
+
onInputValueChange,
|
|
2041
2171
|
...props
|
|
2042
2172
|
}: CreatableSelectProps,
|
|
2043
2173
|
ref: React.Ref<HTMLDivElement>
|
|
@@ -2072,6 +2202,7 @@ const CreatableSelect = React.forwardRef(
|
|
|
2072
2202
|
if (disabled) return
|
|
2073
2203
|
setOpen(true)
|
|
2074
2204
|
setSearch("")
|
|
2205
|
+
onInputValueChange?.("")
|
|
2075
2206
|
setHighlightIndex(-1)
|
|
2076
2207
|
requestAnimationFrame(() => inputRef.current?.focus())
|
|
2077
2208
|
}
|
|
@@ -2081,8 +2212,9 @@ const CreatableSelect = React.forwardRef(
|
|
|
2081
2212
|
onValueChange?.(val)
|
|
2082
2213
|
setOpen(false)
|
|
2083
2214
|
setSearch("")
|
|
2215
|
+
onInputValueChange?.("")
|
|
2084
2216
|
},
|
|
2085
|
-
[onValueChange]
|
|
2217
|
+
[onInputValueChange, onValueChange]
|
|
2086
2218
|
)
|
|
2087
2219
|
|
|
2088
2220
|
const handleCreate = React.useCallback(() => {
|
|
@@ -2096,10 +2228,12 @@ const CreatableSelect = React.forwardRef(
|
|
|
2096
2228
|
onValueChange?.(value)
|
|
2097
2229
|
setOpen(false)
|
|
2098
2230
|
setSearch("")
|
|
2231
|
+
onInputValueChange?.("")
|
|
2099
2232
|
}
|
|
2100
2233
|
}, [
|
|
2101
2234
|
search,
|
|
2102
2235
|
onValueChange,
|
|
2236
|
+
onInputValueChange,
|
|
2103
2237
|
maxLength,
|
|
2104
2238
|
sanitizeInput,
|
|
2105
2239
|
normalizeComboboxInput,
|
|
@@ -2109,6 +2243,8 @@ const CreatableSelect = React.forwardRef(
|
|
|
2109
2243
|
if (e.key === "Escape") {
|
|
2110
2244
|
e.preventDefault()
|
|
2111
2245
|
setOpen(false)
|
|
2246
|
+
setSearch("")
|
|
2247
|
+
onInputValueChange?.("")
|
|
2112
2248
|
return
|
|
2113
2249
|
}
|
|
2114
2250
|
|
|
@@ -2161,11 +2297,12 @@ const CreatableSelect = React.forwardRef(
|
|
|
2161
2297
|
!containerRef.current.contains(e.target as Node)
|
|
2162
2298
|
) {
|
|
2163
2299
|
setOpen(false)
|
|
2300
|
+
onInputValueChange?.("")
|
|
2164
2301
|
}
|
|
2165
2302
|
}
|
|
2166
2303
|
document.addEventListener("mousedown", handler)
|
|
2167
2304
|
return () => document.removeEventListener("mousedown", handler)
|
|
2168
|
-
}, [open])
|
|
2305
|
+
}, [onInputValueChange, open])
|
|
2169
2306
|
|
|
2170
2307
|
// Reset highlight when filter changes
|
|
2171
2308
|
React.useEffect(() => {
|
|
@@ -2201,9 +2338,10 @@ const CreatableSelect = React.forwardRef(
|
|
|
2201
2338
|
const next = normalizeComboboxInput
|
|
2202
2339
|
? normalizeComboboxInput(sanitized)
|
|
2203
2340
|
: sanitized
|
|
2204
|
-
|
|
2341
|
+
const nextSearch =
|
|
2205
2342
|
maxLength != null ? next.slice(0, maxLength) : next
|
|
2206
|
-
)
|
|
2343
|
+
setSearch(nextSearch)
|
|
2344
|
+
onInputValueChange?.(nextSearch)
|
|
2207
2345
|
}}
|
|
2208
2346
|
maxLength={maxLength}
|
|
2209
2347
|
onKeyDown={handleKeyDown}
|
|
@@ -5830,7 +5968,7 @@ const selectTriggerVariants = cva(
|
|
|
5830
5968
|
default:
|
|
5831
5969
|
"border border-solid border-semantic-border-input focus:outline-none focus:border-semantic-border-input-focus focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
|
|
5832
5970
|
error:
|
|
5833
|
-
"border border-solid border-semantic-error-primary
|
|
5971
|
+
"border border-solid border-semantic-error-primary focus:outline-none focus:border-semantic-error-primary focus:shadow-[0_0_0_1px_rgba(240,68,56,0.12)]",
|
|
5834
5972
|
},
|
|
5835
5973
|
},
|
|
5836
5974
|
defaultVariants: {
|
package/package.json
CHANGED