@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 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
- // Actions to execute when a match is selected (Enter/Tab or click).
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
- actions: [
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
- actions: [
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
- actions: [
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
- actions: [
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
- actions: [
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
- | `actions` | `Array<TypeaheadSelectActionSet>` | Actions to execute when a match is selected |
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
- ## Advanced Actions
356
+ ## onDismiss
356
357
 
357
- Action functions receive 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
+ 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
- actions: [
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
- **Action payload:**
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 actions include focus restoration if needed:
562
+ - Ensure your onSelect includes focus restoration if needed:
519
563
  ```tsx
520
- actions: [
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
- * Actions to execute when a match is selected.
102
- * Typically deletes the trigger text and inserts the selected content.
101
+ * Called when a match is selected.
102
+ * Returns behavior actions to execute (e.g., delete trigger text, insert content).
103
103
  */
104
- actions: Array<TypeaheadSelectActionSet<TMatch>>
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
- * Actions to execute when a match is selected.
131
- * Typically deletes the trigger text and inserts the selected content.
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
- actions: Array<TypeaheadSelectActionSet<TMatch>>
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
- * actions: [insertEmojiAction],
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
- * actions: [insertMentionAction],
159
- * })
160
- * ```
161
- *
162
- * @example Slash commands at start of block
163
- * ```ts
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
- * actions: [({event}) => [
184
- * raise({type: 'delete', at: event.patternSelection}),
185
- * raise({type: 'insert.text', text: event.match.emoji}),
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
- * actions: [insertEmojiAction],
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: symbol
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
- * Actions to execute when a match is selected.
427
- * Typically deletes the trigger text and inserts the selected content.
464
+ * Called when a match is selected.
465
+ * Returns behavior actions to execute (e.g., delete trigger text, insert content).
428
466
  *
429
- * @see {@link TypeaheadSelectActionSet}
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
- actions: Array<TypeaheadSelectActionSet<TMatch>>
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 function that runs when a match is selected.
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 typeahead select action sets.
569
+ * Event passed to `onSelect` when a match is selected.
529
570
  *
530
571
  * @public
531
572
  */