@portabletext/plugin-typeahead-picker 2.1.0 → 3.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 +57 -15
- package/dist/index.d.ts +83 -42
- package/dist/index.js +242 -122
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,9 +37,9 @@ const emojiPicker = defineTypeaheadPicker<EmojiMatch>({
|
|
|
37
37
|
// Return matches for the keyword. Can be sync or async (with mode: 'async').
|
|
38
38
|
getMatches: ({keyword}) => searchEmojis(keyword),
|
|
39
39
|
|
|
40
|
-
//
|
|
40
|
+
// Action to execute when a match is selected (Enter/Tab or click).
|
|
41
41
|
// Receives the event containing the selected match and pattern selection.
|
|
42
|
-
|
|
42
|
+
onSelect: [
|
|
43
43
|
({event}) => [
|
|
44
44
|
raise({type: 'delete', at: event.patternSelection}), // Delete `:joy`
|
|
45
45
|
raise({type: 'insert.text', text: event.match.emoji}), // Insert 😂
|
|
@@ -114,7 +114,7 @@ const emojiPicker = defineTypeaheadPicker<EmojiMatch>({
|
|
|
114
114
|
keyword: /\S*/,
|
|
115
115
|
delimiter: ':',
|
|
116
116
|
getMatches: ({keyword}) => searchEmojis(keyword),
|
|
117
|
-
|
|
117
|
+
onSelect: [
|
|
118
118
|
({event}) => [
|
|
119
119
|
raise({type: 'delete', at: event.patternSelection}),
|
|
120
120
|
raise({type: 'insert.text', text: event.match.emoji}),
|
|
@@ -136,7 +136,7 @@ const mentionPicker = defineTypeaheadPicker<MentionMatch>({
|
|
|
136
136
|
keyword: /\w*/,
|
|
137
137
|
debounceMs: 200,
|
|
138
138
|
getMatches: async ({keyword}) => api.searchUsers(keyword),
|
|
139
|
-
|
|
139
|
+
onSelect: [
|
|
140
140
|
({event}) => [
|
|
141
141
|
raise({type: 'delete', at: event.patternSelection}),
|
|
142
142
|
raise({
|
|
@@ -158,7 +158,7 @@ const commandPicker = defineTypeaheadPicker<CommandMatch>({
|
|
|
158
158
|
trigger: /^\//, // ^ anchors to start of block
|
|
159
159
|
keyword: /\w*/,
|
|
160
160
|
getMatches: ({keyword}) => searchCommands(keyword),
|
|
161
|
-
|
|
161
|
+
onSelect: [
|
|
162
162
|
({event}) => {
|
|
163
163
|
switch (event.match.command) {
|
|
164
164
|
case 'h1':
|
|
@@ -204,7 +204,7 @@ const emojiPicker = defineTypeaheadPicker<EmojiMatch>({
|
|
|
204
204
|
return true
|
|
205
205
|
},
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
onSelect: [
|
|
208
208
|
({event}) => [
|
|
209
209
|
raise({type: 'delete', at: event.patternSelection}),
|
|
210
210
|
raise({type: 'insert.text', text: event.match.emoji}),
|
|
@@ -235,7 +235,8 @@ Creates a picker definition to pass to `useTypeaheadPicker`.
|
|
|
235
235
|
| `mode` | `'sync' \| 'async'` | Whether `getMatches` returns synchronously or a Promise (default: `'sync'`) |
|
|
236
236
|
| `debounceMs` | `number?` | Delay in ms before calling `getMatches`. Useful for both async (API calls) and sync (expensive local search) modes. (default: `0`) |
|
|
237
237
|
| `getMatches` | `(ctx: {keyword: string}) => TMatch[]` | Function that returns matches for the keyword |
|
|
238
|
-
| `
|
|
238
|
+
| `onSelect` | `TypeaheadSelectActionSet[]` | Action sets to execute when a match is selected |
|
|
239
|
+
| `onDismiss` | `TypeaheadDismissActionSet[]?` | Optional action sets to execute when the picker is dismissed |
|
|
239
240
|
|
|
240
241
|
**Trigger pattern rules:**
|
|
241
242
|
|
|
@@ -352,16 +353,59 @@ function EmojiPickerPlugin() {
|
|
|
352
353
|
|
|
353
354
|
The error is cleared when the picker returns to idle (e.g., via Escape or cursor movement).
|
|
354
355
|
|
|
355
|
-
##
|
|
356
|
+
## onDismiss
|
|
356
357
|
|
|
357
|
-
|
|
358
|
+
The optional `onDismiss` callback runs when the picker is dismissed via Escape. This is useful for cleaning up the typed trigger and keyword text.
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
const mentionPicker = defineTypeaheadPicker<MentionMatch>({
|
|
362
|
+
trigger: /@/,
|
|
363
|
+
keyword: /\w*/,
|
|
364
|
+
getMatches: ({keyword}) => searchUsers(keyword),
|
|
365
|
+
onSelect: [
|
|
366
|
+
({event}) => [
|
|
367
|
+
raise({type: 'delete', at: event.patternSelection}),
|
|
368
|
+
raise({type: 'insert.text', text: `@${event.match.name}`}),
|
|
369
|
+
],
|
|
370
|
+
],
|
|
371
|
+
// Delete the typed text when user presses Escape
|
|
372
|
+
onDismiss: [
|
|
373
|
+
({event}) => [raise({type: 'delete', at: event.patternSelection})],
|
|
374
|
+
],
|
|
375
|
+
})
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Without `onDismiss`, pressing Escape leaves the typed text in place (e.g., `@john` remains in the editor). With `onDismiss` configured to delete the pattern, the text is removed.
|
|
379
|
+
|
|
380
|
+
**onDismiss payload:**
|
|
381
|
+
|
|
382
|
+
| Property | Description |
|
|
383
|
+
| ------------------------ | -------------------------------------------------------------- |
|
|
384
|
+
| `event.patternSelection` | Selection range covering the trigger + keyword (e.g., `@john`) |
|
|
385
|
+
| `snapshot` | Current editor snapshot |
|
|
386
|
+
|
|
387
|
+
Note: `onDismiss` is called when the user actively dismisses the picker:
|
|
388
|
+
|
|
389
|
+
- Pressing Escape
|
|
390
|
+
- Pressing Enter/Tab when there are no matches
|
|
391
|
+
- Programmatically via `picker.send({type: 'dismiss'})`
|
|
392
|
+
|
|
393
|
+
It is NOT called when:
|
|
394
|
+
|
|
395
|
+
- The user selects a match (Enter/Tab/click with a match selected)
|
|
396
|
+
- The picker is dismissed due to cursor movement
|
|
397
|
+
- The picker is dismissed due to invalid pattern (e.g., typing a space)
|
|
398
|
+
|
|
399
|
+
## Advanced onSelect
|
|
400
|
+
|
|
401
|
+
The `onSelect` callback receives more than just the event. The full payload includes access to the editor snapshot, which is useful for generating keys, accessing the schema, or reading the current editor state.
|
|
358
402
|
|
|
359
403
|
```tsx
|
|
360
404
|
const commandPicker = defineTypeaheadPicker<CommandMatch>({
|
|
361
405
|
trigger: /^\//,
|
|
362
406
|
keyword: /\w*/,
|
|
363
407
|
getMatches: ({keyword}) => searchCommands(keyword),
|
|
364
|
-
|
|
408
|
+
onSelect: [
|
|
365
409
|
({event, snapshot}) => {
|
|
366
410
|
// Access schema to check for block object fields
|
|
367
411
|
const blockObjectSchema = snapshot.context.schema.blockObjects.find(
|
|
@@ -383,7 +427,7 @@ const commandPicker = defineTypeaheadPicker<CommandMatch>({
|
|
|
383
427
|
})
|
|
384
428
|
```
|
|
385
429
|
|
|
386
|
-
**
|
|
430
|
+
**onSelect payload:**
|
|
387
431
|
|
|
388
432
|
| Property | Description |
|
|
389
433
|
| ---------- | ----------------------------------------------------------------------------- |
|
|
@@ -515,14 +559,12 @@ The following keyboard shortcuts are handled automatically by the picker:
|
|
|
515
559
|
|
|
516
560
|
### Focus issues after selection
|
|
517
561
|
|
|
518
|
-
- Ensure your
|
|
562
|
+
- Ensure your onSelect includes focus restoration if needed:
|
|
519
563
|
```tsx
|
|
520
|
-
|
|
564
|
+
onSelect: [
|
|
521
565
|
({event}) => [
|
|
522
566
|
raise({type: 'delete', at: event.patternSelection}),
|
|
523
567
|
raise({type: 'insert.text', text: event.match.emoji}),
|
|
524
|
-
],
|
|
525
|
-
() => [
|
|
526
568
|
effect(({send}) => {
|
|
527
569
|
send({type: 'focus'})
|
|
528
570
|
}),
|
package/dist/index.d.ts
CHANGED
|
@@ -98,10 +98,15 @@ declare type BaseConfigWithDelimiter<TMatch extends AutoCompleteMatch> = {
|
|
|
98
98
|
*/
|
|
99
99
|
guard?: TypeaheadTriggerGuard
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
101
|
+
* Called when a match is selected.
|
|
102
|
+
* Returns behavior actions to execute (e.g., delete trigger text, insert content).
|
|
103
103
|
*/
|
|
104
|
-
|
|
104
|
+
onSelect: TypeaheadSelectActionSet<TMatch>[]
|
|
105
|
+
/**
|
|
106
|
+
* Called when the picker is dismissed.
|
|
107
|
+
* Returns behavior actions to execute (optional cleanup).
|
|
108
|
+
*/
|
|
109
|
+
onDismiss?: TypeaheadDismissActionSet[]
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
declare type BaseConfigWithoutDelimiter<TMatch extends object> = {
|
|
@@ -127,10 +132,15 @@ declare type BaseConfigWithoutDelimiter<TMatch extends object> = {
|
|
|
127
132
|
*/
|
|
128
133
|
guard?: TypeaheadTriggerGuard
|
|
129
134
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
135
|
+
* Called when a match is selected.
|
|
136
|
+
* Returns behavior actions to execute (e.g., delete trigger text, insert content).
|
|
137
|
+
*/
|
|
138
|
+
onSelect: TypeaheadSelectActionSet<TMatch>[]
|
|
139
|
+
/**
|
|
140
|
+
* Called when the picker is dismissed.
|
|
141
|
+
* Returns behavior actions to execute (optional cleanup).
|
|
132
142
|
*/
|
|
133
|
-
|
|
143
|
+
onDismiss?: TypeaheadDismissActionSet[]
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
/**
|
|
@@ -143,7 +153,12 @@ declare type BaseConfigWithoutDelimiter<TMatch extends object> = {
|
|
|
143
153
|
* keyword: /[\S]+/,
|
|
144
154
|
* delimiter: ':',
|
|
145
155
|
* getMatches: ({keyword}) => searchEmojis(keyword),
|
|
146
|
-
*
|
|
156
|
+
* onSelect: [
|
|
157
|
+
* ({event}) => [
|
|
158
|
+
* raise({type: 'delete', at: event.patternSelection}),
|
|
159
|
+
* raise({type: 'insert.text', text: event.match.emoji}),
|
|
160
|
+
* ],
|
|
161
|
+
* ],
|
|
147
162
|
* })
|
|
148
163
|
* ```
|
|
149
164
|
*
|
|
@@ -155,17 +170,12 @@ declare type BaseConfigWithoutDelimiter<TMatch extends object> = {
|
|
|
155
170
|
* keyword: /[\w]+/,
|
|
156
171
|
* debounceMs: 200,
|
|
157
172
|
* getMatches: async ({keyword}) => api.searchUsers(keyword),
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* const slashCommandPicker = defineTypeaheadPicker({
|
|
165
|
-
* trigger: /^\//,
|
|
166
|
-
* keyword: /[\w]+/,
|
|
167
|
-
* getMatches: ({keyword}) => filterCommands(keyword),
|
|
168
|
-
* actions: [executeCommandAction],
|
|
173
|
+
* onSelect: [
|
|
174
|
+
* ({event}) => [
|
|
175
|
+
* raise({type: 'delete', at: event.patternSelection}),
|
|
176
|
+
* raise({type: 'insert.inline object', inlineObject: {_type: 'mention', userId: event.match.id}}),
|
|
177
|
+
* ],
|
|
178
|
+
* ],
|
|
169
179
|
* })
|
|
170
180
|
* ```
|
|
171
181
|
*
|
|
@@ -175,15 +185,16 @@ declare type BaseConfigWithoutDelimiter<TMatch extends object> = {
|
|
|
175
185
|
* trigger: /:/,
|
|
176
186
|
* keyword: /[\S]+/,
|
|
177
187
|
* getMatches: ({keyword}) => searchEmojis(keyword),
|
|
178
|
-
* guard: ({snapshot}) => {
|
|
179
|
-
* // Return false to prevent picker from activating
|
|
188
|
+
* guard: ({snapshot, event, dom}) => {
|
|
180
189
|
* if (anotherPickerIsOpen()) return false
|
|
181
190
|
* return true
|
|
182
191
|
* },
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
192
|
+
* onSelect: [
|
|
193
|
+
* ({event}) => [
|
|
194
|
+
* raise({type: 'delete', at: event.patternSelection}),
|
|
195
|
+
* raise({type: 'insert.text', text: event.match.emoji}),
|
|
196
|
+
* ],
|
|
197
|
+
* ],
|
|
187
198
|
* })
|
|
188
199
|
* ```
|
|
189
200
|
*
|
|
@@ -275,6 +286,28 @@ declare type SyncConfigWithoutDelimiter<TMatch extends object> =
|
|
|
275
286
|
getMatches: (context: {keyword: string}) => ReadonlyArray<TMatch>
|
|
276
287
|
}
|
|
277
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Action set that runs when the picker is dismissed.
|
|
291
|
+
* Returns an array of behavior actions to execute (optional cleanup).
|
|
292
|
+
*
|
|
293
|
+
* @public
|
|
294
|
+
*/
|
|
295
|
+
export declare type TypeaheadDismissActionSet = BehaviorActionSet<
|
|
296
|
+
TypeaheadDismissEvent,
|
|
297
|
+
true
|
|
298
|
+
>
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Event passed to `onDismiss` when the picker is dismissed.
|
|
302
|
+
*
|
|
303
|
+
* @public
|
|
304
|
+
*/
|
|
305
|
+
export declare type TypeaheadDismissEvent = {
|
|
306
|
+
type: 'custom.typeahead dismiss'
|
|
307
|
+
/** Selection range covering the full pattern match (e.g., `@john`) for cleanup */
|
|
308
|
+
patternSelection: NonNullable<EditorSelection>
|
|
309
|
+
}
|
|
310
|
+
|
|
278
311
|
/**
|
|
279
312
|
* The picker instance returned by {@link useTypeaheadPicker}.
|
|
280
313
|
*
|
|
@@ -360,7 +393,12 @@ export declare type TypeaheadPickerContext<TMatch> = {
|
|
|
360
393
|
* keyword: /[\S]+/,
|
|
361
394
|
* delimiter: ':',
|
|
362
395
|
* getMatches: ({keyword}) => searchEmojis(keyword),
|
|
363
|
-
*
|
|
396
|
+
* onSelect: [
|
|
397
|
+
* ({event}) => [
|
|
398
|
+
* raise({type: 'delete', at: event.patternSelection}),
|
|
399
|
+
* raise({type: 'insert.text', text: event.match.emoji}),
|
|
400
|
+
* ],
|
|
401
|
+
* ],
|
|
364
402
|
* })
|
|
365
403
|
* ```
|
|
366
404
|
*
|
|
@@ -369,7 +407,7 @@ export declare type TypeaheadPickerContext<TMatch> = {
|
|
|
369
407
|
export declare type TypeaheadPickerDefinition<TMatch extends object = object> =
|
|
370
408
|
TypeaheadPickerDefinitionBase<TMatch> & {
|
|
371
409
|
/** @internal Unique identifier for this picker definition */
|
|
372
|
-
readonly _id:
|
|
410
|
+
readonly _id: string
|
|
373
411
|
/**
|
|
374
412
|
* Whether `getMatches` returns synchronously or asynchronously.
|
|
375
413
|
* @defaultValue `'sync'`
|
|
@@ -423,12 +461,25 @@ declare type TypeaheadPickerDefinitionBase<TMatch extends object> = {
|
|
|
423
461
|
*/
|
|
424
462
|
guard?: TypeaheadTriggerGuard
|
|
425
463
|
/**
|
|
426
|
-
*
|
|
427
|
-
*
|
|
464
|
+
* Called when a match is selected.
|
|
465
|
+
* Returns behavior actions to execute (e.g., delete trigger text, insert content).
|
|
428
466
|
*
|
|
429
|
-
* @
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* onSelect: [
|
|
470
|
+
* ({event}) => [
|
|
471
|
+
* raise({type: 'delete', at: event.patternSelection}),
|
|
472
|
+
* raise({type: 'insert.text', text: event.match.emoji}),
|
|
473
|
+
* ],
|
|
474
|
+
* ]
|
|
475
|
+
* ```
|
|
476
|
+
*/
|
|
477
|
+
onSelect: TypeaheadSelectActionSet<TMatch>[]
|
|
478
|
+
/**
|
|
479
|
+
* Called when the picker is dismissed (Escape, cursor movement, etc.).
|
|
480
|
+
* Returns behavior actions to execute (optional cleanup).
|
|
430
481
|
*/
|
|
431
|
-
|
|
482
|
+
onDismiss?: TypeaheadDismissActionSet[]
|
|
432
483
|
}
|
|
433
484
|
|
|
434
485
|
/**
|
|
@@ -504,19 +555,9 @@ export declare type TypeaheadPickerState =
|
|
|
504
555
|
}
|
|
505
556
|
|
|
506
557
|
/**
|
|
507
|
-
* Action
|
|
558
|
+
* Action set that runs when a match is selected.
|
|
508
559
|
* Returns an array of behavior actions to execute (e.g., delete trigger text, insert content).
|
|
509
560
|
*
|
|
510
|
-
* @example
|
|
511
|
-
* ```ts
|
|
512
|
-
* const insertEmoji: TypeaheadSelectActionSet<EmojiMatch> = (
|
|
513
|
-
* {event},
|
|
514
|
-
* ) => [
|
|
515
|
-
* raise({type: 'delete', at: event.patternSelection}),
|
|
516
|
-
* raise({type: 'insert.text', text: event.match.emoji}),
|
|
517
|
-
* ]
|
|
518
|
-
* ```
|
|
519
|
-
*
|
|
520
561
|
* @public
|
|
521
562
|
*/
|
|
522
563
|
export declare type TypeaheadSelectActionSet<TMatch> = BehaviorActionSet<
|
|
@@ -525,7 +566,7 @@ export declare type TypeaheadSelectActionSet<TMatch> = BehaviorActionSet<
|
|
|
525
566
|
>
|
|
526
567
|
|
|
527
568
|
/**
|
|
528
|
-
* Event passed to
|
|
569
|
+
* Event passed to `onSelect` when a match is selected.
|
|
529
570
|
*
|
|
530
571
|
* @public
|
|
531
572
|
*/
|