@portabletext/plugin-typeahead-picker 1.0.0 → 2.0.1
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/README.md +74 -75
- package/dist/index.d.ts +149 -65
- package/dist/index.js +223 -157
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
The `useTypeaheadPicker` hook provides the state and logic needed to build typeahead pickers (emoji pickers, mention pickers, slash commands, etc.) for the Portable Text Editor. It manages keyword matching, keyboard navigation, and triggering of actions, but is not concerned with the UI, how the picker is rendered, or how it's positioned in the document.
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
|
+
import {EditorProvider, PortableTextEditable} from '@portabletext/editor'
|
|
10
11
|
import {raise} from '@portabletext/editor/behaviors'
|
|
11
12
|
import {
|
|
12
13
|
defineTypeaheadPicker,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
type AutoCompleteMatch,
|
|
15
16
|
} from '@portabletext/plugin-typeahead-picker'
|
|
16
17
|
|
|
17
|
-
// With `
|
|
18
|
+
// With `delimiter` configured, matches must include `type: 'exact' | 'partial'`
|
|
18
19
|
// for auto-completion to work. Use `AutoCompleteMatch` as the base type.
|
|
19
20
|
type EmojiMatch = AutoCompleteMatch & {
|
|
20
21
|
key: string
|
|
@@ -23,13 +24,15 @@ type EmojiMatch = AutoCompleteMatch & {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const emojiPicker = defineTypeaheadPicker<EmojiMatch>({
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
pattern: /:(\S*)/,
|
|
27
|
+
// Trigger pattern - activates the picker when typed
|
|
28
|
+
trigger: /:/,
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// Keyword pattern - matches characters after the trigger
|
|
31
|
+
keyword: /\S*/,
|
|
32
|
+
|
|
33
|
+
// Optional delimiter enables auto-completion.
|
|
31
34
|
// Typing `:joy:` will auto-insert if "joy" is an exact match.
|
|
32
|
-
|
|
35
|
+
delimiter: ':',
|
|
33
36
|
|
|
34
37
|
// Return matches for the keyword. Can be sync or async (with mode: 'async').
|
|
35
38
|
getMatches: ({keyword}) => searchEmojis(keyword),
|
|
@@ -44,7 +47,7 @@ const emojiPicker = defineTypeaheadPicker<EmojiMatch>({
|
|
|
44
47
|
],
|
|
45
48
|
})
|
|
46
49
|
|
|
47
|
-
function
|
|
50
|
+
function EmojiPickerPlugin() {
|
|
48
51
|
// Activate the picker and get its current state
|
|
49
52
|
const picker = useTypeaheadPicker(emojiPicker)
|
|
50
53
|
|
|
@@ -76,18 +79,30 @@ function EmojiPicker() {
|
|
|
76
79
|
</ul>
|
|
77
80
|
)
|
|
78
81
|
}
|
|
82
|
+
|
|
83
|
+
// Render the picker inside EditorProvider, alongside PortableTextEditable
|
|
84
|
+
function MyEditor() {
|
|
85
|
+
return (
|
|
86
|
+
<EditorProvider /* ...config */>
|
|
87
|
+
<PortableTextEditable />
|
|
88
|
+
<EmojiPickerPlugin />
|
|
89
|
+
</EditorProvider>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
79
92
|
```
|
|
80
93
|
|
|
94
|
+
The picker component must be rendered inside `EditorProvider` to access the editor context. Position it as a sibling to `PortableTextEditable` - you'll handle the visual positioning (popover, dropdown, etc.) separately with CSS or a positioning library.
|
|
95
|
+
|
|
81
96
|
## How It Works
|
|
82
97
|
|
|
83
|
-
The picker activates when users type
|
|
98
|
+
The picker activates when users type the `trigger` pattern (e.g., `:` or `@`). The `keyword` pattern then matches characters typed after the trigger.
|
|
84
99
|
|
|
85
100
|
- **Keyboard shortcuts are built-in**:
|
|
86
101
|
- `Enter` or `Tab` inserts the selected match
|
|
87
102
|
- `↑` / `↓` navigate through matches
|
|
88
103
|
- `Esc` dismisses the picker
|
|
89
104
|
- **Mouse interactions are opt-in**: Use `send({type: 'navigate to', index})` and `send({type: 'select'})` to enable hover and click
|
|
90
|
-
- **Auto-completion**: With `
|
|
105
|
+
- **Auto-completion**: With `delimiter` configured, typing the delimiter after an exact match auto-inserts it (e.g., `:joy:` auto-inserts the emoji)
|
|
91
106
|
|
|
92
107
|
## Examples
|
|
93
108
|
|
|
@@ -95,8 +110,9 @@ The picker activates when users type text matching the `pattern` (e.g., `:smile`
|
|
|
95
110
|
|
|
96
111
|
```ts
|
|
97
112
|
const emojiPicker = defineTypeaheadPicker<EmojiMatch>({
|
|
98
|
-
|
|
99
|
-
|
|
113
|
+
trigger: /:/,
|
|
114
|
+
keyword: /\S*/,
|
|
115
|
+
delimiter: ':',
|
|
100
116
|
getMatches: ({keyword}) => searchEmojis(keyword),
|
|
101
117
|
actions: [
|
|
102
118
|
({event}) => [
|
|
@@ -112,11 +128,12 @@ const emojiPicker = defineTypeaheadPicker<EmojiMatch>({
|
|
|
112
128
|
### Mention picker (async with debounce)
|
|
113
129
|
|
|
114
130
|
```ts
|
|
115
|
-
// Without `
|
|
131
|
+
// Without `delimiter`, the `type` field is not required on matches.
|
|
116
132
|
// MentionMatch can just be: { id: string; name: string }
|
|
117
133
|
const mentionPicker = defineTypeaheadPicker<MentionMatch>({
|
|
118
134
|
mode: 'async',
|
|
119
|
-
|
|
135
|
+
trigger: /@/,
|
|
136
|
+
keyword: /\w*/,
|
|
120
137
|
debounceMs: 200,
|
|
121
138
|
getMatches: async ({keyword}) => api.searchUsers(keyword),
|
|
122
139
|
actions: [
|
|
@@ -136,9 +153,10 @@ const mentionPicker = defineTypeaheadPicker<MentionMatch>({
|
|
|
136
153
|
### Slash command picker (start of block only)
|
|
137
154
|
|
|
138
155
|
```ts
|
|
139
|
-
// Without `
|
|
156
|
+
// Without `delimiter`, the `type` field is not required on matches.
|
|
140
157
|
const commandPicker = defineTypeaheadPicker<CommandMatch>({
|
|
141
|
-
|
|
158
|
+
trigger: /^\//, // ^ anchors to start of block
|
|
159
|
+
keyword: /\w*/,
|
|
142
160
|
getMatches: ({keyword}) => searchCommands(keyword),
|
|
143
161
|
actions: [
|
|
144
162
|
({event}) => {
|
|
@@ -173,73 +191,51 @@ Creates a picker definition to pass to `useTypeaheadPicker`.
|
|
|
173
191
|
|
|
174
192
|
**Config:**
|
|
175
193
|
|
|
176
|
-
| Property
|
|
177
|
-
|
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
194
|
+
| Property | Type | Description |
|
|
195
|
+
| ------------ | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
196
|
+
| `trigger` | `RegExp` | Pattern that activates the picker. Can include `^` for start-of-block triggers. Must be single-character (e.g., `/:/`, `/@/`, `/^\//`). |
|
|
197
|
+
| `keyword` | `RegExp` | Pattern matching characters after the trigger (e.g., `/\S*/`, `/\w*/`). |
|
|
198
|
+
| `delimiter` | `string?` | Optional delimiter that triggers auto-completion (e.g., `':'` for `:joy:`) |
|
|
199
|
+
| `mode` | `'sync' \| 'async'` | Whether `getMatches` returns synchronously or a Promise (default: `'sync'`) |
|
|
200
|
+
| `debounceMs` | `number?` | Delay in ms before calling `getMatches`. Useful for both async (API calls) and sync (expensive local search) modes. (default: `0`) |
|
|
201
|
+
| `getMatches` | `(ctx: {keyword: string}) => TMatch[]` | Function that returns matches for the keyword |
|
|
202
|
+
| `actions` | `Array<TypeaheadSelectActionSet>` | Actions to execute when a match is selected |
|
|
184
203
|
|
|
185
|
-
**
|
|
204
|
+
**Trigger pattern rules:**
|
|
186
205
|
|
|
187
|
-
-
|
|
188
|
-
-
|
|
206
|
+
- Must be a single-character trigger (e.g., `:`, `@`, `/`)
|
|
207
|
+
- Multi-character triggers (e.g., `##`) are not supported
|
|
189
208
|
- Position anchors (`^`) allow start-of-block constraints
|
|
190
|
-
- Regex flags are ignored (the picker normalizes patterns internally)
|
|
191
209
|
|
|
192
210
|
**How triggering works:**
|
|
193
211
|
|
|
194
|
-
The picker activates the moment a trigger character is typed
|
|
212
|
+
The picker activates the moment a trigger character is typed. After activation, the keyword is tracked via editor selection changes.
|
|
195
213
|
|
|
196
214
|
```
|
|
197
|
-
User types `:` →
|
|
215
|
+
User types `:` → Trigger matches → Picker activates with keyword ""
|
|
198
216
|
User types `j` → Keyword updates to "j" (via selection tracking)
|
|
199
217
|
User types `o` → Keyword updates to "jo"
|
|
200
218
|
User types `y` → Keyword updates to "joy"
|
|
201
219
|
```
|
|
202
220
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
**Why some patterns don't work:**
|
|
206
|
-
|
|
207
|
-
Multi-character triggers like `##` fail because the picker can only activate on the character you just typed:
|
|
208
|
-
|
|
209
|
-
```
|
|
210
|
-
User types `#` → Pattern /##(\w*)/ doesn't match yet → Nothing happens
|
|
211
|
-
User types `#` → Pattern matches "##", but the first # was already there
|
|
212
|
-
Picker only activates on newly typed triggers, not existing text
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Same issue with patterns requiring specific characters mid-keyword like `/:(\w*)-(\w*)/`:
|
|
216
|
-
|
|
217
|
-
```
|
|
218
|
-
User types `:foo` → Pattern doesn't match yet (needs a `-`)
|
|
219
|
-
User types `-` → Pattern matches `:foo-`, but `:foo` was already there
|
|
220
|
-
Picker only activates on newly typed triggers
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
**Pattern compatibility summary:**
|
|
221
|
+
**Trigger compatibility summary:**
|
|
224
222
|
|
|
225
|
-
|
|
|
226
|
-
|
|
|
227
|
-
|
|
|
228
|
-
|
|
|
229
|
-
|
|
|
230
|
-
|
|
|
231
|
-
| `/:(\w*)-(\w*)/` | `:a-b` | ❌ | `:` alone doesn't match pattern |
|
|
223
|
+
| Trigger | Example input | Works? | Why |
|
|
224
|
+
| ------- | ------------- | ------ | -------------------------------- |
|
|
225
|
+
| `/:/` | `:joy` | ✅ | Single-char trigger |
|
|
226
|
+
| `/@/` | `@john` | ✅ | Single-char trigger |
|
|
227
|
+
| `/^\//` | `/cmd` | ✅ | Single-char with position anchor |
|
|
228
|
+
| `/##/` | `##tag` | ❌ | Multi-char triggers unsupported |
|
|
232
229
|
|
|
233
|
-
**
|
|
230
|
+
**delimiter requirements:**
|
|
234
231
|
|
|
235
|
-
|
|
232
|
+
Single-character delimiters work regardless of whether the character is included in the keyword pattern. Multi-character delimiters are not supported.
|
|
236
233
|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
| `/#(\w*)/` | `##` | `#tag##` | ❌ | First `#` moves cursor past match, picker dismisses |
|
|
234
|
+
| keyword | delimiter | Example | Works? | Why |
|
|
235
|
+
| ------- | --------- | -------- | ------ | --------------------------------------- |
|
|
236
|
+
| `/\S*/` | `:` | `:joy:` | ✅ | `\S` matches `:`, keyword becomes `joy` |
|
|
237
|
+
| `/\w*/` | `:` | `:joy:` | ✅ | `\w` stops at `:`, keyword is `joy` |
|
|
238
|
+
| `/\w*/` | `##` | `#tag##` | ❌ | Multi-char delimiter not supported |
|
|
243
239
|
|
|
244
240
|
### `useTypeaheadPicker(definition)`
|
|
245
241
|
|
|
@@ -250,7 +246,7 @@ React hook that activates a picker and returns its state.
|
|
|
250
246
|
| Property | Description |
|
|
251
247
|
| -------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
252
248
|
| `snapshot.matches(state)` | Check picker state: `'idle'`, `{active: 'loading'}`, `{active: 'no matches'}`, `{active: 'showing matches'}` |
|
|
253
|
-
| `snapshot.context.keyword` | The current keyword
|
|
249
|
+
| `snapshot.context.keyword` | The current keyword |
|
|
254
250
|
| `snapshot.context.matches` | Array of matches from `getMatches` |
|
|
255
251
|
| `snapshot.context.selectedIndex` | Index of the currently selected match |
|
|
256
252
|
| `send(event)` | Dispatch events: `{type: 'select'}`, `{type: 'dismiss'}`, `{type: 'navigate to', index}` |
|
|
@@ -301,7 +297,7 @@ When users type quickly, earlier slow requests may complete after later fast req
|
|
|
301
297
|
If `getMatches` throws or rejects, the error is captured in `snapshot.context.error`. The picker transitions to `'no matches'` state and continues to function.
|
|
302
298
|
|
|
303
299
|
```tsx
|
|
304
|
-
function
|
|
300
|
+
function EmojiPickerPlugin() {
|
|
305
301
|
const picker = useTypeaheadPicker(emojiPicker)
|
|
306
302
|
const {error} = picker.snapshot.context
|
|
307
303
|
|
|
@@ -326,7 +322,8 @@ Action functions receive more than just the event. The full payload includes acc
|
|
|
326
322
|
|
|
327
323
|
```tsx
|
|
328
324
|
const commandPicker = defineTypeaheadPicker<CommandMatch>({
|
|
329
|
-
|
|
325
|
+
trigger: /^\//,
|
|
326
|
+
keyword: /\w*/,
|
|
330
327
|
getMatches: ({keyword}) => searchCommands(keyword),
|
|
331
328
|
actions: [
|
|
332
329
|
({event, snapshot}) => {
|
|
@@ -388,7 +385,8 @@ Choose debounce values based on your data source:
|
|
|
388
385
|
```tsx
|
|
389
386
|
// Local data - no debounce needed
|
|
390
387
|
const emojiPicker = defineTypeaheadPicker({
|
|
391
|
-
|
|
388
|
+
trigger: /:/,
|
|
389
|
+
keyword: /\S*/,
|
|
392
390
|
getMatches: ({keyword}) => filterEmojis(keyword), // Fast local filter
|
|
393
391
|
// ...
|
|
394
392
|
})
|
|
@@ -397,7 +395,8 @@ const emojiPicker = defineTypeaheadPicker({
|
|
|
397
395
|
const mentionPicker = defineTypeaheadPicker({
|
|
398
396
|
mode: 'async',
|
|
399
397
|
debounceMs: 200,
|
|
400
|
-
|
|
398
|
+
trigger: /@/,
|
|
399
|
+
keyword: /\w*/,
|
|
401
400
|
getMatches: async ({keyword}) => api.searchUsers(keyword),
|
|
402
401
|
// ...
|
|
403
402
|
})
|
|
@@ -460,17 +459,17 @@ The following keyboard shortcuts are handled automatically by the picker:
|
|
|
460
459
|
|
|
461
460
|
### Picker doesn't activate
|
|
462
461
|
|
|
463
|
-
- **Check
|
|
464
|
-
- **Check position anchors**: `^` means start of block, not start of line. `hello /command` won't match
|
|
462
|
+
- **Check trigger**: Must be a single-character trigger (e.g., `/:/`, `/@/`)
|
|
463
|
+
- **Check position anchors**: `^` means start of block, not start of line. `hello /command` won't match `/^\//`
|
|
465
464
|
- **Check for conflicts**: Only one picker can be active at a time
|
|
466
|
-
- **Avoid multi-character triggers**:
|
|
465
|
+
- **Avoid multi-character triggers**: Triggers like `/##/` don't work because the picker only activates on newly typed single characters
|
|
467
466
|
|
|
468
467
|
### Auto-completion doesn't work
|
|
469
468
|
|
|
470
|
-
- **Check `
|
|
469
|
+
- **Check `delimiter`**: Must be set (e.g., `delimiter: ':'`)
|
|
471
470
|
- **Check match type**: Matches must include `type: 'exact' | 'partial'`
|
|
472
471
|
- **Check for exact match**: Auto-completion only triggers when exactly one match has `type: 'exact'`
|
|
473
|
-
- **Check
|
|
472
|
+
- **Check keyword pattern**: The keyword pattern must allow the delimiter character at the boundary. Use `\S*` (matches any non-whitespace) when `delimiter: ':'`
|
|
474
473
|
|
|
475
474
|
### Stale matches appear
|
|
476
475
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,34 +1,58 @@
|
|
|
1
1
|
import type {EditorSelection} from '@portabletext/editor'
|
|
2
2
|
import type {BehaviorActionSet} from '@portabletext/editor/behaviors'
|
|
3
3
|
|
|
4
|
-
declare type
|
|
5
|
-
|
|
4
|
+
declare type AsyncConfigWithDelimiter<TMatch extends AutoCompleteMatch> =
|
|
5
|
+
BaseConfigWithDelimiter<TMatch> & {
|
|
6
|
+
/**
|
|
7
|
+
* Set to `'async'` when `getMatches` returns a Promise.
|
|
8
|
+
*/
|
|
6
9
|
mode: 'async'
|
|
10
|
+
/**
|
|
11
|
+
* Debounce delay in milliseconds before calling `getMatches`.
|
|
12
|
+
* Recommended for API calls to reduce request frequency.
|
|
13
|
+
* @defaultValue `0` (no debounce)
|
|
14
|
+
*/
|
|
7
15
|
debounceMs?: number
|
|
8
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Async function that returns matches for the current keyword.
|
|
18
|
+
* Called whenever the keyword changes (after debounce if configured).
|
|
19
|
+
*/
|
|
20
|
+
getMatches: (context: {keyword: string}) => Promise<ReadonlyArray<TMatch>>
|
|
9
21
|
}
|
|
10
22
|
|
|
11
|
-
declare type
|
|
12
|
-
|
|
23
|
+
declare type AsyncConfigWithoutDelimiter<TMatch extends object> =
|
|
24
|
+
BaseConfigWithoutDelimiter<TMatch> & {
|
|
25
|
+
/**
|
|
26
|
+
* Set to `'async'` when `getMatches` returns a Promise.
|
|
27
|
+
*/
|
|
13
28
|
mode: 'async'
|
|
29
|
+
/**
|
|
30
|
+
* Debounce delay in milliseconds before calling `getMatches`.
|
|
31
|
+
* Recommended for API calls to reduce request frequency.
|
|
32
|
+
* @defaultValue `0` (no debounce)
|
|
33
|
+
*/
|
|
14
34
|
debounceMs?: number
|
|
15
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Async function that returns matches for the current keyword.
|
|
37
|
+
* Called whenever the keyword changes (after debounce if configured).
|
|
38
|
+
*/
|
|
39
|
+
getMatches: (context: {keyword: string}) => Promise<ReadonlyArray<TMatch>>
|
|
16
40
|
}
|
|
17
41
|
|
|
18
42
|
/**
|
|
19
43
|
* Match type for pickers with auto-completion support.
|
|
20
|
-
* Use this when `
|
|
44
|
+
* Use this when `delimiter` is configured.
|
|
21
45
|
*
|
|
22
46
|
* The `type` property indicates how well the match corresponds to the keyword:
|
|
23
47
|
* - `'exact'` - The keyword matches this item exactly (e.g., keyword `joy` matches emoji `:joy:`)
|
|
24
48
|
* - `'partial'` - The keyword partially matches this item (e.g., keyword `jo` matches `:joy:`)
|
|
25
49
|
*
|
|
26
|
-
* When `
|
|
50
|
+
* When `delimiter` is configured and there's exactly one `'exact'` match,
|
|
27
51
|
* the picker will auto-insert that match.
|
|
28
52
|
*
|
|
29
53
|
* @example
|
|
30
54
|
* ```ts
|
|
31
|
-
* // With
|
|
55
|
+
* // With delimiter - type field required for auto-completion
|
|
32
56
|
* type EmojiMatch = AutoCompleteMatch & {
|
|
33
57
|
* key: string
|
|
34
58
|
* emoji: string
|
|
@@ -42,15 +66,57 @@ export declare type AutoCompleteMatch = {
|
|
|
42
66
|
type: 'exact' | 'partial'
|
|
43
67
|
}
|
|
44
68
|
|
|
45
|
-
declare type
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
declare type BaseConfigWithDelimiter<TMatch extends AutoCompleteMatch> = {
|
|
70
|
+
/**
|
|
71
|
+
* Pattern that activates the picker. Must be a single character.
|
|
72
|
+
* Can include `^` for start-of-block triggers.
|
|
73
|
+
*
|
|
74
|
+
* @example `/:/` - activates on colon
|
|
75
|
+
* @example `/@/` - activates on at-sign
|
|
76
|
+
* @example `/^\//` - activates on slash at start of block
|
|
77
|
+
*/
|
|
78
|
+
trigger: RegExp
|
|
79
|
+
/**
|
|
80
|
+
* Pattern matching the keyword portion after the trigger.
|
|
81
|
+
* The entire match becomes the keyword passed to `getMatches`.
|
|
82
|
+
* Common patterns: non-whitespace (`\S*`) or word characters (`\w*`).
|
|
83
|
+
*/
|
|
84
|
+
keyword: RegExp
|
|
85
|
+
/**
|
|
86
|
+
* Character that triggers auto-completion.
|
|
87
|
+
* Typing this after a keyword with an exact match auto-inserts it.
|
|
88
|
+
*
|
|
89
|
+
* @example `':'` - typing `:joy:` auto-inserts the joy emoji
|
|
90
|
+
*/
|
|
91
|
+
delimiter: string
|
|
92
|
+
/**
|
|
93
|
+
* Actions to execute when a match is selected.
|
|
94
|
+
* Typically deletes the trigger text and inserts the selected content.
|
|
95
|
+
*/
|
|
48
96
|
actions: Array<TypeaheadSelectActionSet<TMatch>>
|
|
49
97
|
}
|
|
50
98
|
|
|
51
|
-
declare type
|
|
52
|
-
|
|
53
|
-
|
|
99
|
+
declare type BaseConfigWithoutDelimiter<TMatch extends object> = {
|
|
100
|
+
/**
|
|
101
|
+
* Pattern that activates the picker. Must be a single character.
|
|
102
|
+
* Can include `^` for start-of-block triggers.
|
|
103
|
+
*
|
|
104
|
+
* @example `/:/` - activates on colon
|
|
105
|
+
* @example `/@/` - activates on at-sign
|
|
106
|
+
* @example `/^\//` - activates on slash at start of block
|
|
107
|
+
*/
|
|
108
|
+
trigger: RegExp
|
|
109
|
+
/**
|
|
110
|
+
* Pattern matching the keyword portion after the trigger.
|
|
111
|
+
* The entire match becomes the keyword passed to `getMatches`.
|
|
112
|
+
* Common patterns: non-whitespace (`\S*`) or word characters (`\w*`).
|
|
113
|
+
*/
|
|
114
|
+
keyword: RegExp
|
|
115
|
+
delimiter?: undefined
|
|
116
|
+
/**
|
|
117
|
+
* Actions to execute when a match is selected.
|
|
118
|
+
* Typically deletes the trigger text and inserts the selected content.
|
|
119
|
+
*/
|
|
54
120
|
actions: Array<TypeaheadSelectActionSet<TMatch>>
|
|
55
121
|
}
|
|
56
122
|
|
|
@@ -60,13 +126,11 @@ declare type BaseConfigWithoutAutoComplete<TMatch extends object> = {
|
|
|
60
126
|
* @example Emoji picker with auto-complete
|
|
61
127
|
* ```ts
|
|
62
128
|
* const emojiPicker = defineTypeaheadPicker({
|
|
63
|
-
*
|
|
64
|
-
*
|
|
129
|
+
* trigger: /:/,
|
|
130
|
+
* keyword: /[\S]+/,
|
|
131
|
+
* delimiter: ':',
|
|
65
132
|
* getMatches: ({keyword}) => searchEmojis(keyword),
|
|
66
|
-
* actions: [
|
|
67
|
-
* raise({type: 'delete', at: event.patternSelection}),
|
|
68
|
-
* raise({type: 'insert.text', text: event.match.emoji}),
|
|
69
|
-
* ]],
|
|
133
|
+
* actions: [insertEmojiAction],
|
|
70
134
|
* })
|
|
71
135
|
* ```
|
|
72
136
|
*
|
|
@@ -74,47 +138,43 @@ declare type BaseConfigWithoutAutoComplete<TMatch extends object> = {
|
|
|
74
138
|
* ```ts
|
|
75
139
|
* const mentionPicker = defineTypeaheadPicker({
|
|
76
140
|
* mode: 'async',
|
|
77
|
-
*
|
|
141
|
+
* trigger: /@/,
|
|
142
|
+
* keyword: /[\w]+/,
|
|
78
143
|
* debounceMs: 200,
|
|
79
144
|
* getMatches: async ({keyword}) => api.searchUsers(keyword),
|
|
80
|
-
* actions: [
|
|
81
|
-
* raise({type: 'delete', at: event.patternSelection}),
|
|
82
|
-
* raise({type: 'insert.text', text: event.match.name}),
|
|
83
|
-
* ]],
|
|
145
|
+
* actions: [insertMentionAction],
|
|
84
146
|
* })
|
|
85
147
|
* ```
|
|
86
148
|
*
|
|
87
149
|
* @example Slash commands at start of block
|
|
88
150
|
* ```ts
|
|
89
151
|
* const slashCommandPicker = defineTypeaheadPicker({
|
|
90
|
-
*
|
|
152
|
+
* trigger: /^\//,
|
|
153
|
+
* keyword: /[\w]+/,
|
|
91
154
|
* getMatches: ({keyword}) => filterCommands(keyword),
|
|
92
|
-
* actions: [
|
|
93
|
-
* raise({type: 'delete', at: event.patternSelection}),
|
|
94
|
-
* raise(event.match.action),
|
|
95
|
-
* ]],
|
|
155
|
+
* actions: [executeCommandAction],
|
|
96
156
|
* })
|
|
97
157
|
* ```
|
|
98
158
|
*
|
|
99
159
|
* @public
|
|
100
160
|
*/
|
|
101
161
|
export declare function defineTypeaheadPicker<TMatch extends object>(
|
|
102
|
-
config:
|
|
162
|
+
config: SyncConfigWithoutDelimiter<TMatch>,
|
|
103
163
|
): TypeaheadPickerDefinition<TMatch>
|
|
104
164
|
|
|
105
165
|
/** @public */
|
|
106
166
|
export declare function defineTypeaheadPicker<TMatch extends AutoCompleteMatch>(
|
|
107
|
-
config:
|
|
167
|
+
config: SyncConfigWithDelimiter<TMatch>,
|
|
108
168
|
): TypeaheadPickerDefinition<TMatch>
|
|
109
169
|
|
|
110
170
|
/** @public */
|
|
111
171
|
export declare function defineTypeaheadPicker<TMatch extends object>(
|
|
112
|
-
config:
|
|
172
|
+
config: AsyncConfigWithoutDelimiter<TMatch>,
|
|
113
173
|
): TypeaheadPickerDefinition<TMatch>
|
|
114
174
|
|
|
115
175
|
/** @public */
|
|
116
176
|
export declare function defineTypeaheadPicker<TMatch extends AutoCompleteMatch>(
|
|
117
|
-
config:
|
|
177
|
+
config: AsyncConfigWithDelimiter<TMatch>,
|
|
118
178
|
): TypeaheadPickerDefinition<TMatch>
|
|
119
179
|
|
|
120
180
|
/**
|
|
@@ -142,20 +202,46 @@ export declare function defineTypeaheadPicker<TMatch extends AutoCompleteMatch>(
|
|
|
142
202
|
*/
|
|
143
203
|
export declare type GetMatches<TMatch extends object> = (context: {
|
|
144
204
|
keyword: string
|
|
145
|
-
}) =>
|
|
205
|
+
}) => ReadonlyArray<TMatch> | Promise<ReadonlyArray<TMatch>>
|
|
146
206
|
|
|
147
|
-
declare type
|
|
148
|
-
|
|
207
|
+
declare type SyncConfigWithDelimiter<TMatch extends AutoCompleteMatch> =
|
|
208
|
+
BaseConfigWithDelimiter<TMatch> & {
|
|
209
|
+
/**
|
|
210
|
+
* Whether `getMatches` returns synchronously or asynchronously.
|
|
211
|
+
* @defaultValue `'sync'`
|
|
212
|
+
*/
|
|
149
213
|
mode?: 'sync'
|
|
214
|
+
/**
|
|
215
|
+
* Debounce delay in milliseconds before calling `getMatches`.
|
|
216
|
+
* Useful for expensive local searches.
|
|
217
|
+
* @defaultValue `0` (no debounce)
|
|
218
|
+
*/
|
|
150
219
|
debounceMs?: number
|
|
151
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Function that returns matches for the current keyword.
|
|
222
|
+
* Called whenever the keyword changes (after debounce if configured).
|
|
223
|
+
*/
|
|
224
|
+
getMatches: (context: {keyword: string}) => ReadonlyArray<TMatch>
|
|
152
225
|
}
|
|
153
226
|
|
|
154
|
-
declare type
|
|
155
|
-
|
|
227
|
+
declare type SyncConfigWithoutDelimiter<TMatch extends object> =
|
|
228
|
+
BaseConfigWithoutDelimiter<TMatch> & {
|
|
229
|
+
/**
|
|
230
|
+
* Whether `getMatches` returns synchronously or asynchronously.
|
|
231
|
+
* @defaultValue `'sync'`
|
|
232
|
+
*/
|
|
156
233
|
mode?: 'sync'
|
|
234
|
+
/**
|
|
235
|
+
* Debounce delay in milliseconds before calling `getMatches`.
|
|
236
|
+
* Useful for expensive local searches.
|
|
237
|
+
* @defaultValue `0` (no debounce)
|
|
238
|
+
*/
|
|
157
239
|
debounceMs?: number
|
|
158
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Function that returns matches for the current keyword.
|
|
242
|
+
* Called whenever the keyword changes (after debounce if configured).
|
|
243
|
+
*/
|
|
244
|
+
getMatches: (context: {keyword: string}) => ReadonlyArray<TMatch>
|
|
159
245
|
}
|
|
160
246
|
|
|
161
247
|
/**
|
|
@@ -224,7 +310,7 @@ export declare type TypeaheadPickerContext<TMatch> = {
|
|
|
224
310
|
/** The extracted keyword from the trigger pattern (e.g., `joy` from `:joy`) */
|
|
225
311
|
keyword: string
|
|
226
312
|
/** The current list of matches returned by `getMatches` */
|
|
227
|
-
matches:
|
|
313
|
+
matches: ReadonlyArray<TMatch>
|
|
228
314
|
/** Index of the currently selected match (for keyboard navigation and highlighting) */
|
|
229
315
|
selectedIndex: number
|
|
230
316
|
/** Error from `getMatches` if it threw, otherwise `undefined` */
|
|
@@ -239,8 +325,9 @@ export declare type TypeaheadPickerContext<TMatch> = {
|
|
|
239
325
|
* @example
|
|
240
326
|
* ```ts
|
|
241
327
|
* const emojiPicker = defineTypeaheadPicker({
|
|
242
|
-
*
|
|
243
|
-
*
|
|
328
|
+
* trigger: /:/,
|
|
329
|
+
* keyword: /[\S]+/,
|
|
330
|
+
* delimiter: ':',
|
|
244
331
|
* getMatches: ({keyword}) => searchEmojis(keyword),
|
|
245
332
|
* actions: [insertEmojiAction],
|
|
246
333
|
* })
|
|
@@ -273,32 +360,29 @@ export declare type TypeaheadPickerDefinition<TMatch extends object = object> =
|
|
|
273
360
|
|
|
274
361
|
declare type TypeaheadPickerDefinitionBase<TMatch extends object> = {
|
|
275
362
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* If pattern has capture groups: keyword = first capture group (additional groups ignored).
|
|
279
|
-
* If no capture group: keyword = entire match.
|
|
280
|
-
*
|
|
281
|
-
* Can include position anchors like `^` for start-of-block triggers.
|
|
363
|
+
* Pattern that activates the picker.
|
|
364
|
+
* Can include positional anchors like `^` for start-of-block triggers.
|
|
282
365
|
*
|
|
283
|
-
* @example
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
*
|
|
366
|
+
* @example `/:/` for emoji
|
|
367
|
+
* @example `/@/` for mentions
|
|
368
|
+
* @example `/^\//` for slash commands (start of block only)
|
|
369
|
+
*/
|
|
370
|
+
trigger: RegExp
|
|
371
|
+
/**
|
|
372
|
+
* Pattern matching the keyword portion (after trigger, before delimiter).
|
|
373
|
+
* The entire match is used as the keyword.
|
|
290
374
|
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* ```
|
|
375
|
+
* @example `/[\S]+/` for emoji (any non-whitespace)
|
|
376
|
+
* @example `/[\w]+/` for mentions (word characters only)
|
|
294
377
|
*/
|
|
295
|
-
|
|
378
|
+
keyword: RegExp
|
|
296
379
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
380
|
+
* Character that triggers auto-completion.
|
|
381
|
+
* Typing this after a keyword with exactly one exact match auto-inserts it.
|
|
382
|
+
*
|
|
299
383
|
* @example `':'` - typing `:joy:` auto-inserts the joy emoji
|
|
300
384
|
*/
|
|
301
|
-
|
|
385
|
+
delimiter?: string
|
|
302
386
|
/**
|
|
303
387
|
* Actions to execute when a match is selected.
|
|
304
388
|
* Typically deletes the trigger text and inserts the selected content.
|