Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combobox rework #3168

Merged
merged 41 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
acb0b91
Commit yarn.lock
12joan May 1, 2024
869f868
Fix: www app uses separate slate-react to packages, breaking useSelected
12joan Apr 25, 2024
437738e
Prototype combobox input inside slash-input-element.tsx
12joan May 1, 2024
4aaf43c
Refactor combobox input hooks into package
12joan May 1, 2024
71bbfce
Refactor `createSlashPlugin` to use combobox helper
12joan May 1, 2024
2f486a2
Prototype InlineCombobox component
12joan May 1, 2024
7ed9cdb
Refactor out InlineCombobox component
12joan May 1, 2024
1783605
Add tests for matchWords
12joan May 1, 2024
f866183
Fully implement slash commands using new combobox
12joan May 1, 2024
2c98587
Fix error when focusing
12joan May 1, 2024
297657e
Fix CSS leaking into combobox popover
12joan May 1, 2024
f9f21b6
Fix: Aliases backwards for [un]ordered lists
12joan May 1, 2024
2890a67
Use cva for combobox item styles
12joan May 2, 2024
b6e1dab
Refactor aliases -> keywords
12joan May 2, 2024
4818f2d
Remove SlashPlugin type
12joan May 2, 2024
42f2421
Refactor query -> search
12joan May 2, 2024
766ab37
Drop label and make value user-facing
12joan May 2, 2024
878b1ca
Merge branch 'main' into feat/combobox
12joan May 2, 2024
e5034c8
Fix: SlashCommandRule doesn't need exporting
12joan May 2, 2024
a20be89
Refactor withInsertTextTriggerCombobox -> withTriggerCombobox
12joan May 2, 2024
f5e0553
Refactor TriggerComboboxPlugin options
12joan May 2, 2024
26e92a7
Revert "Fix: www app uses separate slate-react to packages, breaking …
12joan May 2, 2024
88f386c
Merge branch 'main' into feat/combobox
12joan May 2, 2024
7cca80c
Rename match -> filter
12joan May 2, 2024
13f4fa9
Refactor InlineCombobox into multiple components
12joan May 2, 2024
2c86fbf
Fix: InlineComboboxItem className should be merged
12joan May 2, 2024
538cc02
Remove unnecessary `?.`
12joan May 2, 2024
4a5c168
Use new combobox API for mentions
12joan May 3, 2024
d958373
Remove mention-combobox from registry.ts
12joan May 3, 2024
2aa6b5b
yarn build:registry
12joan May 3, 2024
f74f4f8
Merge branch 'refs/heads/main' into feat/combobox
May 6, 2024
d32625b
eslint
May 6, 2024
751a9bb
eslint
May 6, 2024
8cf1b3d
Use combobox with emoji picker
12joan Jun 3, 2024
27616fe
Delete old combobox code
12joan Jun 3, 2024
af87f80
Remove old combobox component
12joan Jun 3, 2024
035752f
Fix: Trigger is inserted in the wrong place on selection change
12joan Jun 3, 2024
9288232
Update registry and docs
12joan Jun 5, 2024
5424940
Linter and brl fixes
12joan Jun 5, 2024
6985cf6
Add package changesets
12joan Jun 5, 2024
4dac4cb
Merge branch 'main' into feat/combobox
12joan Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/combobox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
"@udecode/plate-combobox": major
---

- Major rework. The combobox package is no longer a plugin. Instead, it is now a collection of utilities that can be used by other plugins and components.
- Added the following exports:
- `withTriggerCombobox`: Insert a combobox input when a trigger character is typed
- `TriggerComboboxPlugin`: Plugin options type for `withTriggerCombobox`
- `useComboboxInput`: Manages the behavior of an inline combobox input element
- `useHTMLInputCursorState`: Tracks whether the cursor is at the start or end of a HTML `<input type="text">` element
- `ComboboxInputCursorState`: Return type for `useHTMLInputCursorState`
- `CancelComboboxInputCause`: A unison type of possible reasons why a combobox input may be cancelled (used by `useComboboxInput`)
- Removed the following exports:
- `comboboxStore`
- `createComboboxPlugin`
- `useComboboxContent`
- `useComboboxControls`
- `useComboboxItem`
- `onChangeCombobox`
- `onKeyDownCombobox`
- `ComboboxOnSelectItem`
- `ComboboxProps`
- `getNextNonDisabledIndex`
- `getNextWrappingIndex`
- `getTextFromTrigger`
12 changes: 12 additions & 0 deletions .changeset/emoji.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@udecode/plate-emoji": major
---

- Now uses the reworked combobox package
- Added `ELEMENT_EMOJI_INPUT`; combobox functionality must now be handled in the component
- Plugin options:
- Now extends from `TriggerComboboxPlugin`
- Added `createEmojiNode` to support custom emoji nodes
- Removed `emojiTriggeringController`
- Removed `id` (no longer needed)
- Removed `createEmoji`
18 changes: 18 additions & 0 deletions .changeset/mention.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@udecode/plate-mention": major
---

- Now uses the reworked combobox package
- `ELEMENT_MENTION_INPUT` is now an inline void element, and combobox functionality must now be handled in the component
- Plugin options:
- Now extends from `TriggerComboboxPlugin`
- Renamed `query` to `triggerQuery` (provided by `TriggerComboboxPlugin`)
- Removed `id` (no longer needed)
- Removed `inputCreation` (see `TriggerComboboxPlugin['createComboboxInput']`)
- Removed queries and transforms relating to the mention input:
- `findMentionInput`
- `isNodeMentionInput`
- `isSelectionInMentionInput`
- `removeMentionInput`
- Removed `withMention` (no longer needed)
- Removed `mentionOnKeyDownHandler` (no longer needed)
20 changes: 20 additions & 0 deletions .changeset/slash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@udecode/plate-slash-command": major
---

- Now uses the reworked combobox package
- `ELEMENT_SLASH_INPUT` is now an inline void element, and combobox functionality must now be handled in the component
- Replaced all plugin options with those extended from `TriggerComboboxPlugin`
- Removed `createSlashNode`
- Removed `id` (no longer needed)
- Removed `inputCreation` (see `createComboboxInput`)
- Renamed `query` to `triggerQuery` (provided by `TriggerComboboxPlugin`)
- Removed `rules`: Slash command rules must now be provided in the component
- Removed queries and transforms relating to the slash input:
- `findSlashInput`
- `isNodeSlashInput`
- `isSelectionInSlashInput`
- `removeSlashInput`
- Removed `withSlashCommand` (no longer needed)
- Removed `slashOnKeyDownHandler` (no longer needed)
- Removed `getSlashOnSelectItem`: This should now be handled in the component
283 changes: 64 additions & 219 deletions apps/www/content/docs/combobox.mdx
Original file line number Diff line number Diff line change
@@ -1,249 +1,94 @@
---
title: Combobox
description: Select options from a list of predefined values.
docs:
- route: /docs/components/combobox
title: Combobox
- route: /docs/components/emoji-combobox
title: Emoji Combobox
- route: /docs/components/mention-combobox
title: Mention Combobox
description: Utilities for adding comboboxes to your editor.
---

<PackageInfo>
## TriggerComboboxPlugin

## Features
The TriggerComboboxPlugin mixin configures your plugin to insert a combobox input element when the user types a specified trigger character is typed.

- Displays a combobox for selecting options from a set list.
- Suitable for creating mentions, tags, or slash commands.
- Works in conjunction with the [Mention plugin](/docs/mention).
For example, the [Mention](/docs/mention) plugin uses TriggerComboboxPlugin to insert an `ELEMENT_MENTION_INPUT` whenever the user types `@`.

**Activation Conditions:**
### Usage

- Collapsed text selection.
- Cursor placement immediately after the trigger character.
- Text without spaces follows the trigger character.
<Steps>

**On Activation:**

- Sets `id`, `text`, and `targetRange` in the combobox store.

</PackageInfo>

## Installation

```bash
npm install @udecode/plate-combobox
```

## Usage

```tsx
import { createComboboxPlugin } from '@udecode/plate-combobox';
import { createMentionPlugin } from '@udecode/plate-mention';

const plugins = [
// ...otherPlugins,
createComboboxPlugin(),
createMentionPlugin(),
];
```

Then render the combobox component inside `Plate`. You can use the [Combobox component](/docs/components/combobox) or create your own.

## Keyboard Interactions

<KeyTable>
<KeyTableItem hotkey="ArrowDown">
Highlights the next item in the list.
</KeyTableItem>
<KeyTableItem hotkey="ArrowUp">
Highlights the previous item in the list.
</KeyTableItem>
<KeyTableItem hotkey="Esc">Closes the combobox.</KeyTableItem>
<KeyTableItem hotkey="Tab">
Selects the currently highlighted item.
</KeyTableItem>
<KeyTableItem hotkey="Enter">
Selects the currently highlighted item.
</KeyTableItem>
</KeyTable>

## API

### createComboboxPlugin

### ComboboxProps

Here are some key aspects of the **`Combobox`**:

- Multiple Instances: You can render the **`Combobox`** multiple times, each with its unique configuration provided by a different **`ComboboxStateById`**.
- Singleton Behavior: Only one **`Combobox`** can be opened at a time. The state of the active **`Combobox`** is stored in the **`comboboxStore`**.
- Extends **`ComboboxState`**, **`ComboboxStateById`**:

<APIParameters>
<APIItem name="items" type="TComboboxItem[]">
The items for the combobox. An alternative to setting the items is to use
`comboboxActions.items(items)`.
</APIItem>
<APIItem name="component" type="React.FC<{ store: ComboboxStoreById }>">
A component that is rendered when the combobox is open. Useful for injecting
hooks.
</APIItem>
<APIItem name="onRenderItem" type="React.FC<ComboboxItemProps>">
A function to render the combobox item.

- **Default:** item text

<APISubList>
<APISubListItem parent="ComboboxItemProps" name="item" type="TComboboxItem">

The combobox item.

</APISubListItem>
<APISubListItem parent="ComboboxItemProps" name="search" type="string">

The search text.

</APISubListItem>
</APISubList>

</APIItem>
<APIItem name="portalElement" type="HTMLElement" optional>

The element to which the combobox is rendered.

- **Default:** `document.body`

</APIItem>

</APIParameters>

### ComboboxState

Represents a combobox's state. The state resides in `comboboxStore`, which uses the [zustood store](https://github.com/udecode/zustood).

<APIParameters>
<APIItem name="activeId" type="string">
Opened combobox ID.
</APIItem>
<APIItem name="byId" type="Record<string, ComboboxStoreById>">
A collection of combobox configuration stores, each identified by a unique combobox ID (e.g., one for tags, one for mentions).

- `ComboboxStateById`:

<APISubList>

<APISubListItem parent="byId" name="id" type="string">
Combobox ID.
</APISubListItem>
<APISubListItem parent="byId" name="filter" type="function" optional>
An optional function to filter items by text.

- **Default:** A function that checks if the item's text begins with the search text. It compares lowercase strings.
<Step>
Extend your plugin options type with TriggerComboboxPlugin.

```ts
(search: string) => (item: TComboboxItem<TData>) => boolean;
interface MyPlugin extends TriggerComboboxPlugin {}
const createMyPlugin = createPluginFactory<MyPlugin>({
// ...
});

// Or simply:
const createMyPlugin = createPluginFactory<TriggerComboboxPlugin>({
// ...
});
```
</Step>

</APISubListItem>

<APISubListItem parent="byId" name="sort" type="function" optional>
An optional function that sorts filtered items before applying `maxSuggestions`.
<Step>
Add the withTriggerCombobox override and specify default values for the required options. (See below for the full list of options).

```ts
(search: string) => (a: TComboboxItem<TData>, b: TComboboxItem<TData>) =>
number;
const createMyPlugin = createPluginFactory<MyPlugin>({
// ...
withOverrides: withTriggerCombobox,
options: {
createComboboxInput: (trigger) => ({
children: [{ text: '' }],
trigger,
type: ELEMENT_MY_INPUT,
}),
trigger: '@',
triggerPreviousCharPattern: /^\s?$/,
},
});
```
</Step>

</APISubListItem>
<APISubListItem parent="byId" name="maxSuggestions" type="number" optional>
The maximum number of suggestions to be shown.

- **Default:** The length of the **`items`** array.

</APISubListItem>

<APISubListItem parent="byId" name="trigger" type="string">
The trigger character to activate the combobox.
</APISubListItem>
<APISubListItem parent="byId" name="searchPattern" type="string" optional>
An optional regular expression for searching, for example, to allow whitespaces.
</APISubListItem>
<APISubListItem parent="byId" name="onSelectItem" type="function" optional>
An optional callback function invoked when an item is selected.
<Step>
Define your input element as an inline void element. It's often useful to do this inside a nested plugin.

```ts
(editor: PlateEditor, item: TComboboxItem) => any;
const createMyPlugin = createPluginFactory<MyPlugin>({
// ...
plugins: [
{
isElement: true,
isInline: true,
isVoid: true,
key: ELEMENT_MY_INPUT,
},
],
});
```

</APISubListItem>
<APISubListItem parent="byId" name="controlled" type="boolean" optional>
Indicates if the opening/closing of the combobox is controlled by the client.
</APISubListItem>
</APISubList>

</APIItem>
<APIItem name="items" type="TComboboxItem[]">
The list of unfiltered items.
</APIItem>
<APIItem name="filteredItems" type="TComboboxItem[]">
The list of filtered items.
</APIItem>
<APIItem name="highlightedIndex" type="number">
The index of the currently highlighted item.
</APIItem>
<APIItem name="targetRange" type="Range | null">
The range from the trigger to the cursor.
</APIItem>
<APIItem name="text" type="string | null">
The text that appears after the trigger.
</APIItem>
The input element component can be built using [Inline Combobox](/docs/components/inline-combobox).
</Step>

</APIParameters>
</Steps>

### TComboboxItem
### Options

The data structure representing a single item in a combobox.
<APIOptions>

<APIParameters>
<APIItem name="key" type="string">
A unique key for the item.
</APIItem>
<APIItem name="text" type="any">
The text of the item.
<APIItem name="createComboboxInput" type="(trigger: string) => TElement">
A function to create the input node.
</APIItem>
<APIItem name="disabled" type="boolean" optional>
Indicates whether the item is disabled.

- **Default:** `false`
<APIItem name="trigger" type="string">
The character that triggers the combobox.
</APIItem>

<APIItem name="triggerPreviousCharPattern" type="RegExp">
Only trigger the combobox if the char before the trigger character matches a regular expression. For example, `/^\s?$/` matches beginning of the line or a space.
</APIItem>
<APIItem name="data" type="any" optional>
Data available to `onRenderItem`.

<APIItem name="triggerQuery" type="(editor: PlateEditor) => boolean" optional>
A query function to enable the behavior.
</APIItem>
</APIParameters>

## API Components

### useComboboxContent

A behavior hook for the `ComboboxContent` component.

<APIState>
<APIItem name="items" type="TComboboxItem<TData>[] | undefined">
The items for the combobox.
</APIItem>
<APIItem name="combobox" type="function">
The combobox store.
</APIItem>
</APIState>

<APIReturns>
<APIItem name="menuProps" type="any">
The menu props for the combobox content.
</APIItem>
<APIItem name="targetRange" type="BaseRange">
The target range of the combobox.
</APIItem>
</APIReturns>

</APIOptions>
Loading