@astryxdesign/core 0.7.2 → 0.7.3-canary.b1e1936

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.
@@ -1637,6 +1637,95 @@ describe('Typeahead edit mode', () => {
1637
1637
  expect(onChange).not.toHaveBeenCalled();
1638
1638
  });
1639
1639
 
1640
+ it('empties the input when cleared while editing the selected value (#7038)', async () => {
1641
+ const onChangeQuery = vi.fn();
1642
+ function Controlled() {
1643
+ const [value, setValue] = useState<SearchableItem | null>(fruits[0]);
1644
+ return (
1645
+ <Typeahead
1646
+ label="Fruit"
1647
+ searchSource={fruitSource}
1648
+ value={value}
1649
+ onChange={setValue}
1650
+ onChangeQuery={onChangeQuery}
1651
+ hasClear
1652
+ />
1653
+ );
1654
+ }
1655
+ render(<Controlled />);
1656
+ const input = screen.getByRole<HTMLInputElement>('combobox');
1657
+
1658
+ // Enter edit mode: the token gives way to the label as editable text
1659
+ fireEvent.click(screen.getByText(fruits[0].label).closest('div')!);
1660
+ await waitFor(() => expect(input.value).toBe(fruits[0].label));
1661
+
1662
+ fireEvent.click(screen.getByRole('button', {name: 'Clear selection'}));
1663
+
1664
+ // The value and the visible text agree: nothing selected, nothing typed
1665
+ await waitFor(() => expect(input.value).toBe(''));
1666
+ expect(onChangeQuery).toHaveBeenLastCalledWith('');
1667
+ expect(input).toHaveFocus();
1668
+ });
1669
+
1670
+ // Escape and blur restore the token but leave the edit-mode label in the
1671
+ // base's query, so a clear from the restored token faces the same stale text.
1672
+ it.each([
1673
+ [
1674
+ 'Escape',
1675
+ (input: HTMLElement) => fireEvent.keyDown(input, {key: 'Escape'}),
1676
+ ],
1677
+ ['blur', (input: HTMLElement) => fireEvent.blur(input)],
1678
+ ])(
1679
+ 'empties the input when cleared after %s restored the token (#7038)',
1680
+ async (_name, leaveEditMode) => {
1681
+ const onChangeQuery = vi.fn();
1682
+ function Controlled() {
1683
+ const [value, setValue] = useState<SearchableItem | null>(fruits[0]);
1684
+ return (
1685
+ <Typeahead
1686
+ label="Fruit"
1687
+ searchSource={fruitSource}
1688
+ value={value}
1689
+ onChange={setValue}
1690
+ onChangeQuery={onChangeQuery}
1691
+ hasClear
1692
+ />
1693
+ );
1694
+ }
1695
+ render(<Controlled />);
1696
+ const input = screen.getByRole<HTMLInputElement>('combobox');
1697
+
1698
+ fireEvent.click(screen.getByText(fruits[0].label).closest('div')!);
1699
+ await waitFor(() => expect(input.value).toBe(fruits[0].label));
1700
+
1701
+ leaveEditMode(input);
1702
+ await screen.findByRole('button', {name: 'Clear selection'});
1703
+
1704
+ fireEvent.click(screen.getByRole('button', {name: 'Clear selection'}));
1705
+
1706
+ await waitFor(() => expect(input.value).toBe(''));
1707
+ expect(onChangeQuery).toHaveBeenLastCalledWith('');
1708
+ },
1709
+ );
1710
+
1711
+ it('does not report a query change when cleared outside edit mode', () => {
1712
+ const onChangeQuery = vi.fn();
1713
+ render(
1714
+ <Typeahead
1715
+ label="Fruit"
1716
+ searchSource={fruitSource}
1717
+ value={fruits[0]}
1718
+ onChange={vi.fn()}
1719
+ onChangeQuery={onChangeQuery}
1720
+ hasClear
1721
+ />,
1722
+ );
1723
+
1724
+ fireEvent.click(screen.getByRole('button', {name: 'Clear selection'}));
1725
+
1726
+ expect(onChangeQuery).not.toHaveBeenCalled();
1727
+ });
1728
+
1640
1729
  it('restores token on blur without action', async () => {
1641
1730
  const onChange = vi.fn();
1642
1731
  render(
@@ -519,12 +519,25 @@ export function Typeahead<T extends SearchableItem>({
519
519
  [onChange],
520
520
  );
521
521
 
522
- // Handle clear (explicit X button on token)
522
+ // Handle clear (explicit X button on token). Edit mode copied the label into
523
+ // the base's query, and the base only resets that query on a selection, so
524
+ // clearing the value has to empty it the way entering edit mode filled it
525
+ // (#7038): a native set plus a synthetic input event, which reaches the
526
+ // base's own change handler and its onChangeQuery.
523
527
  const handleClear = useCallback(() => {
528
+ const input = inputRef.current;
529
+ if (input && input.value !== '') {
530
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
531
+ window.HTMLInputElement.prototype,
532
+ 'value',
533
+ )?.set;
534
+ nativeInputValueSetter?.call(input, '');
535
+ input.dispatchEvent(new Event('input', {bubbles: true}));
536
+ }
524
537
  setIsEditing(false);
525
538
  setEditingValue(null);
526
539
  onChange(null);
527
- inputRef.current?.focus();
540
+ input?.focus();
528
541
  }, [onChange]);
529
542
 
530
543
  // Handle Escape during edit mode — restore token