@portabletext/toolbar 4.0.23 → 4.0.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/toolbar",
3
- "version": "4.0.23",
3
+ "version": "4.0.25",
4
4
  "description": "Utilities for building a toolbar for the Portable Text Editor",
5
5
  "keywords": [
6
6
  "portabletext",
@@ -29,14 +29,13 @@
29
29
  "main": "./dist/index.js",
30
30
  "types": "./dist/index.d.ts",
31
31
  "files": [
32
- "src",
33
32
  "dist"
34
33
  ],
35
34
  "dependencies": {
36
35
  "@xstate/react": "^6.0.0",
37
36
  "react-compiler-runtime": "^1.0.0",
38
37
  "xstate": "^5.24.0",
39
- "@portabletext/keyboard-shortcuts": "^2.1.0"
38
+ "@portabletext/keyboard-shortcuts": "^2.1.1"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@sanity/pkg-utils": "^10.1.1",
@@ -49,10 +48,10 @@
49
48
  "react": "^19.2.1",
50
49
  "typescript": "5.9.3",
51
50
  "typescript-eslint": "^8.48.0",
52
- "@portabletext/editor": "3.3.3"
51
+ "@portabletext/editor": "3.3.5"
53
52
  },
54
53
  "peerDependencies": {
55
- "@portabletext/editor": "^3.3.3",
54
+ "@portabletext/editor": "^3.3.5",
56
55
  "react": "^18.3 || ^19"
57
56
  },
58
57
  "engines": {
@@ -1,25 +0,0 @@
1
- import type {Editor} from '@portabletext/editor'
2
- import {fromCallback, type AnyEventObject} from 'xstate'
3
-
4
- export type DisableListenerEvent = {type: 'enable'} | {type: 'disable'}
5
-
6
- export const disableListener = fromCallback<
7
- AnyEventObject,
8
- {editor: Editor},
9
- DisableListenerEvent
10
- >(({input, sendBack}) => {
11
- // Send back the initial state
12
- if (input.editor.getSnapshot().context.readOnly) {
13
- sendBack({type: 'disable'})
14
- } else {
15
- sendBack({type: 'enable'})
16
- }
17
-
18
- return input.editor.on('*', () => {
19
- if (input.editor.getSnapshot().context.readOnly) {
20
- sendBack({type: 'disable'})
21
- } else {
22
- sendBack({type: 'enable'})
23
- }
24
- }).unsubscribe
25
- })
package/src/index.ts DELETED
@@ -1,11 +0,0 @@
1
- export * from './use-toolbar-schema'
2
- export * from './use-annotation-button'
3
- export * from './use-annotation-popover'
4
- export * from './use-block-object-button'
5
- export * from './use-block-object-popover'
6
- export * from './use-decorator-button'
7
- export * from './use-history-buttons'
8
- export * from './use-inline-object-button'
9
- export * from './use-inline-object-popover'
10
- export * from './use-list-button'
11
- export * from './use-style-selector'
@@ -1,316 +0,0 @@
1
- import {useEditor, type Editor} from '@portabletext/editor'
2
- import {defineBehavior, effect, raise} from '@portabletext/editor/behaviors'
3
- import * as selectors from '@portabletext/editor/selectors'
4
- import {useActor} from '@xstate/react'
5
- import {fromCallback, setup, type AnyEventObject} from 'xstate'
6
- import {disableListener, type DisableListenerEvent} from './disable-listener'
7
- import {useMutuallyExclusiveAnnotation} from './use-mutually-exclusive-annotation'
8
- import type {ToolbarAnnotationSchemaType} from './use-toolbar-schema'
9
-
10
- const activeListener = fromCallback<
11
- AnyEventObject,
12
- {editor: Editor; schemaType: ToolbarAnnotationSchemaType},
13
- {type: 'set active'} | {type: 'set inactive'}
14
- >(({input, sendBack}) => {
15
- // Send back the initial state
16
- if (
17
- selectors.isActiveAnnotation(input.schemaType.name)(
18
- input.editor.getSnapshot(),
19
- )
20
- ) {
21
- sendBack({type: 'set active'})
22
- } else {
23
- sendBack({type: 'set inactive'})
24
- }
25
-
26
- return input.editor.on('*', () => {
27
- const snapshot = input.editor.getSnapshot()
28
-
29
- if (selectors.isActiveAnnotation(input.schemaType.name)(snapshot)) {
30
- sendBack({type: 'set active'})
31
- } else {
32
- sendBack({type: 'set inactive'})
33
- }
34
- }).unsubscribe
35
- })
36
-
37
- const keyboardShortcutRemove = fromCallback<
38
- AnyEventObject,
39
- {editor: Editor; schemaType: ToolbarAnnotationSchemaType}
40
- >(({input}) => {
41
- const shortcut = input.schemaType.shortcut
42
-
43
- if (!shortcut) {
44
- return
45
- }
46
-
47
- return input.editor.registerBehavior({
48
- behavior: defineBehavior({
49
- on: 'keyboard.keydown',
50
- guard: ({event}) => shortcut.guard(event.originEvent),
51
- actions: [
52
- () => [
53
- raise({
54
- type: 'annotation.remove',
55
- annotation: {
56
- name: input.schemaType.name,
57
- },
58
- }),
59
- effect(() => {
60
- input.editor.send({type: 'focus'})
61
- }),
62
- ],
63
- ],
64
- }),
65
- })
66
- })
67
-
68
- const keyboardShortcutShowInsertDialog = fromCallback<
69
- AnyEventObject,
70
- {editor: Editor; schemaType: ToolbarAnnotationSchemaType},
71
- {type: 'open dialog'}
72
- >(({input, sendBack}) => {
73
- const shortcut = input.schemaType.shortcut
74
-
75
- if (!shortcut) {
76
- return
77
- }
78
-
79
- return input.editor.registerBehavior({
80
- behavior: defineBehavior({
81
- on: 'keyboard.keydown',
82
- guard: ({event}) => shortcut.guard(event.originEvent),
83
- actions: [
84
- () => [
85
- effect(() => {
86
- sendBack({type: 'open dialog'})
87
- }),
88
- ],
89
- ],
90
- }),
91
- })
92
- })
93
-
94
- const annotationButtonMachine = setup({
95
- types: {
96
- context: {} as {
97
- editor: Editor
98
- schemaType: ToolbarAnnotationSchemaType
99
- },
100
- input: {} as {
101
- editor: Editor
102
- schemaType: ToolbarAnnotationSchemaType
103
- },
104
- events: {} as
105
- | AnnotationButtonEvent
106
- | DisableListenerEvent
107
- | {type: 'set active'}
108
- | {type: 'set inactive'},
109
- },
110
- actions: {
111
- 'add annotation': ({context, event}) => {
112
- if (event.type !== 'add') {
113
- return
114
- }
115
-
116
- context.editor.send({
117
- type: 'annotation.add',
118
- annotation: {
119
- name: context.schemaType.name,
120
- value: event.annotation.value,
121
- },
122
- })
123
- context.editor.send({type: 'focus'})
124
- },
125
- 'remove annotation': ({context}) => {
126
- context.editor.send({
127
- type: 'annotation.remove',
128
- annotation: {
129
- name: context.schemaType.name,
130
- },
131
- })
132
- context.editor.send({type: 'focus'})
133
- },
134
- },
135
- actors: {
136
- 'active listener': activeListener,
137
- 'disable listener': disableListener,
138
- 'keyboard shortcut.remove': keyboardShortcutRemove,
139
- 'keyboard shortcut.show insert dialog': keyboardShortcutShowInsertDialog,
140
- },
141
- }).createMachine({
142
- id: 'annotation button',
143
- context: ({input}) => ({
144
- editor: input.editor,
145
- schemaType: input.schemaType,
146
- }),
147
- invoke: [
148
- {
149
- src: 'active listener',
150
- input: ({context}) => ({
151
- editor: context.editor,
152
- schemaType: context.schemaType,
153
- }),
154
- },
155
- {
156
- src: 'disable listener',
157
- input: ({context}) => ({
158
- editor: context.editor,
159
- schemaType: context.schemaType,
160
- }),
161
- },
162
- ],
163
- initial: 'disabled',
164
- states: {
165
- disabled: {
166
- initial: 'inactive',
167
- states: {
168
- inactive: {
169
- on: {
170
- 'enable': {
171
- target: '#annotation button.enabled.inactive',
172
- },
173
- 'set active': {
174
- target: 'active',
175
- },
176
- },
177
- },
178
- active: {
179
- on: {
180
- 'enable': {
181
- target: '#annotation button.enabled.active',
182
- },
183
- 'set inactive': {
184
- target: 'inactive',
185
- },
186
- },
187
- },
188
- },
189
- },
190
- enabled: {
191
- initial: 'inactive',
192
- states: {
193
- inactive: {
194
- initial: 'idle',
195
- on: {
196
- 'disable': {
197
- target: '#annotation button.disabled.inactive',
198
- },
199
- 'set active': {
200
- target: '#annotation button.enabled.active',
201
- },
202
- 'add': {
203
- actions: 'add annotation',
204
- },
205
- },
206
- states: {
207
- 'idle': {
208
- invoke: {
209
- src: 'keyboard shortcut.show insert dialog',
210
- input: ({context}) => ({
211
- editor: context.editor,
212
- schemaType: context.schemaType,
213
- }),
214
- },
215
- on: {
216
- 'open dialog': {
217
- target: 'showing dialog',
218
- },
219
- },
220
- },
221
- 'showing dialog': {
222
- on: {
223
- 'close dialog': {
224
- target: 'idle',
225
- },
226
- },
227
- },
228
- },
229
- },
230
- active: {
231
- invoke: {
232
- src: 'keyboard shortcut.remove',
233
- input: ({context}) => ({
234
- editor: context.editor,
235
- schemaType: context.schemaType,
236
- }),
237
- },
238
- on: {
239
- 'set inactive': {
240
- target: '#annotation button.enabled.inactive',
241
- },
242
- 'disable': {
243
- target: '#annotation button.disabled.active',
244
- },
245
- 'remove': {
246
- actions: 'remove annotation',
247
- },
248
- },
249
- },
250
- },
251
- },
252
- },
253
- })
254
-
255
- /**
256
- * @beta
257
- */
258
- export type AnnotationButtonEvent =
259
- | {type: 'close dialog'}
260
- | {type: 'open dialog'}
261
- | {
262
- type: 'add'
263
- annotation: {
264
- value: Record<string, unknown>
265
- }
266
- }
267
- | {type: 'remove'}
268
-
269
- /**
270
- * @beta
271
- */
272
- export type AnnotationButton = {
273
- snapshot: {
274
- matches: (
275
- state:
276
- | 'disabled'
277
- | 'enabled'
278
- | {disabled: 'inactive'}
279
- | {disabled: 'active'}
280
- | {enabled: 'inactive'}
281
- | {enabled: {inactive: 'idle'}}
282
- | {enabled: {inactive: 'showing dialog'}}
283
- | {enabled: 'active'},
284
- ) => boolean
285
- }
286
- send: (event: AnnotationButtonEvent) => void
287
- }
288
-
289
- /**
290
- * @beta
291
- * Manages the state, keyboard shortcut and available events for an annotation
292
- * button.
293
- *
294
- * Note: This hook assumes that the button triggers a dialog for inputting
295
- * the annotation value.
296
- */
297
- export function useAnnotationButton(props: {
298
- schemaType: ToolbarAnnotationSchemaType
299
- }): AnnotationButton {
300
- const editor = useEditor()
301
- const [snapshot, send] = useActor(annotationButtonMachine, {
302
- input: {
303
- editor,
304
- schemaType: props.schemaType,
305
- },
306
- })
307
-
308
- useMutuallyExclusiveAnnotation(props)
309
-
310
- return {
311
- snapshot: {
312
- matches: (state) => snapshot.matches(state),
313
- },
314
- send,
315
- }
316
- }
@@ -1,301 +0,0 @@
1
- import {
2
- useEditor,
3
- type AnnotationPath,
4
- type AnnotationSchemaType,
5
- type Editor,
6
- type PortableTextObject,
7
- } from '@portabletext/editor'
8
- import * as selectors from '@portabletext/editor/selectors'
9
- import {useActor} from '@xstate/react'
10
- import * as React from 'react'
11
- import type {RefObject} from 'react'
12
- import {assign, fromCallback, setup, type AnyEventObject} from 'xstate'
13
- import {disableListener, type DisableListenerEvent} from './disable-listener'
14
- import type {ToolbarAnnotationSchemaType} from './use-toolbar-schema'
15
-
16
- type ActiveContext = {
17
- annotations: Array<{
18
- value: PortableTextObject
19
- schemaType: ToolbarAnnotationSchemaType
20
- at: AnnotationPath
21
- }>
22
- elementRef: RefObject<Element | null>
23
- }
24
-
25
- type ActiveListenerEvent =
26
- | ({
27
- type: 'set active'
28
- } & ActiveContext)
29
- | {
30
- type: 'set inactive'
31
- }
32
-
33
- const activeListener = fromCallback<
34
- AnyEventObject,
35
- {editor: Editor; schemaTypes: ReadonlyArray<ToolbarAnnotationSchemaType>},
36
- ActiveListenerEvent
37
- >(({input, sendBack}) => {
38
- return input.editor.on('*', () => {
39
- const snapshot = input.editor.getSnapshot()
40
- const activeAnnotations = selectors.getActiveAnnotations(snapshot)
41
- const focusBlock = selectors.getFocusBlock(snapshot)
42
-
43
- if (activeAnnotations.length === 0 || !focusBlock) {
44
- sendBack({type: 'set inactive'})
45
- return
46
- }
47
-
48
- const selectedChildren = input.editor.dom.getChildNodes(snapshot)
49
- const firstSelectedChild = selectedChildren.at(0)
50
-
51
- if (!firstSelectedChild || !(firstSelectedChild instanceof Element)) {
52
- sendBack({type: 'set inactive'})
53
- return
54
- }
55
-
56
- const elementRef = React.createRef<Element>()
57
- elementRef.current = firstSelectedChild
58
-
59
- sendBack({
60
- type: 'set active',
61
- annotations: activeAnnotations.flatMap((annotation) => {
62
- const schemaType = input.schemaTypes.find(
63
- (schemaType) => schemaType.name === annotation._type,
64
- )
65
-
66
- if (!schemaType) {
67
- return []
68
- }
69
-
70
- return {
71
- value: annotation,
72
- schemaType,
73
- at: [
74
- {_key: focusBlock.node._key},
75
- 'markDefs',
76
- {_key: annotation._key},
77
- ],
78
- }
79
- }),
80
- elementRef,
81
- })
82
- }).unsubscribe
83
- })
84
-
85
- const annotationPopoverMachine = setup({
86
- types: {
87
- context: {} as {
88
- editor: Editor
89
- schemaTypes: ReadonlyArray<ToolbarAnnotationSchemaType>
90
- } & ActiveContext,
91
- input: {} as {
92
- editor: Editor
93
- schemaTypes: ReadonlyArray<ToolbarAnnotationSchemaType>
94
- },
95
- events: {} as
96
- | DisableListenerEvent
97
- | ActiveListenerEvent
98
- | AnnotationPopoverEvent,
99
- },
100
- actions: {
101
- reset: assign({
102
- annotations: [],
103
- elementRef: React.createRef<Element>(),
104
- }),
105
- remove: ({context, event}) => {
106
- if (event.type !== 'remove') {
107
- return
108
- }
109
-
110
- context.editor.send({
111
- type: 'annotation.remove',
112
- annotation: {
113
- name: event.schemaType.name,
114
- },
115
- })
116
- context.editor.send({type: 'focus'})
117
- },
118
- },
119
- actors: {
120
- 'disable listener': disableListener,
121
- 'active listener': activeListener,
122
- },
123
- }).createMachine({
124
- id: 'annotation popover',
125
- context: ({input}) => ({
126
- editor: input.editor,
127
- schemaTypes: input.schemaTypes,
128
- annotations: [],
129
- elementRef: React.createRef<Element>(),
130
- }),
131
- invoke: [
132
- {src: 'disable listener', input: ({context}) => ({editor: context.editor})},
133
- ],
134
- initial: 'disabled',
135
- states: {
136
- disabled: {
137
- initial: 'inactive',
138
- states: {
139
- inactive: {
140
- entry: ['reset'],
141
- on: {
142
- 'set active': {
143
- actions: assign({
144
- annotations: ({event}) => event.annotations,
145
- elementRef: ({event}) => event.elementRef,
146
- }),
147
- target: 'active',
148
- },
149
- 'enable': {
150
- target: '#annotation popover.enabled.inactive',
151
- },
152
- },
153
- },
154
- active: {
155
- on: {
156
- 'set inactive': {
157
- target: 'inactive',
158
- },
159
- 'enable': {
160
- target: '#annotation popover.enabled.active',
161
- },
162
- },
163
- },
164
- },
165
- },
166
- enabled: {
167
- invoke: [
168
- {
169
- src: 'active listener',
170
- input: ({context}) => ({
171
- editor: context.editor,
172
- schemaTypes: context.schemaTypes,
173
- }),
174
- },
175
- ],
176
- initial: 'inactive',
177
- states: {
178
- inactive: {
179
- entry: ['reset'],
180
- on: {
181
- 'set active': {
182
- target: 'active',
183
- actions: assign({
184
- annotations: ({event}) => event.annotations,
185
- elementRef: ({event}) => event.elementRef,
186
- }),
187
- },
188
- 'disable': {
189
- target: '#annotation popover.disabled.inactive',
190
- },
191
- },
192
- },
193
- active: {
194
- on: {
195
- 'set inactive': {
196
- target: 'inactive',
197
- },
198
- 'disable': {
199
- target: '#annotation popover.disabled.active',
200
- },
201
- 'set active': {
202
- actions: assign({
203
- annotations: ({event}) => event.annotations,
204
- elementRef: ({event}) => event.elementRef,
205
- }),
206
- },
207
- 'edit': {
208
- actions: ({context, event}) => {
209
- context.editor.send({
210
- type: 'annotation.set',
211
- at: event.at,
212
- props: event.props,
213
- })
214
- context.editor.send({type: 'focus'})
215
- },
216
- },
217
- 'remove': {
218
- actions: ({context, event}) => {
219
- context.editor.send({
220
- type: 'annotation.remove',
221
- annotation: {
222
- name: event.schemaType.name,
223
- },
224
- })
225
- context.editor.send({type: 'focus'})
226
- },
227
- },
228
- 'close': {
229
- actions: ({context}) => {
230
- context.editor.send({type: 'focus'})
231
- },
232
- target: 'inactive',
233
- },
234
- },
235
- },
236
- },
237
- },
238
- },
239
- })
240
-
241
- /**
242
- * @beta
243
- */
244
- export type AnnotationPopoverEvent =
245
- | {
246
- type: 'remove'
247
- schemaType: AnnotationSchemaType
248
- }
249
- | {
250
- type: 'edit'
251
- at: AnnotationPath
252
- props: {[key: string]: unknown}
253
- }
254
- | {
255
- type: 'close'
256
- }
257
-
258
- /**
259
- * @beta
260
- */
261
- export type AnnotationPopover = {
262
- snapshot: {
263
- context: ActiveContext
264
- matches: (
265
- state:
266
- | 'disabled'
267
- | 'enabled'
268
- | {
269
- enabled: 'inactive' | 'active'
270
- },
271
- ) => boolean
272
- }
273
- send: (event: AnnotationPopoverEvent) => void
274
- }
275
-
276
- /**
277
- * @beta
278
- * Manages the state and available events for an annotation popover.
279
- */
280
- export function useAnnotationPopover(props: {
281
- schemaTypes: ReadonlyArray<ToolbarAnnotationSchemaType>
282
- }): AnnotationPopover {
283
- const editor = useEditor()
284
- const [actorSnapshot, send] = useActor(annotationPopoverMachine, {
285
- input: {
286
- editor,
287
- schemaTypes: props.schemaTypes,
288
- },
289
- })
290
-
291
- return {
292
- snapshot: {
293
- context: {
294
- annotations: actorSnapshot.context.annotations,
295
- elementRef: actorSnapshot.context.elementRef,
296
- },
297
- matches: (state) => actorSnapshot.matches(state),
298
- },
299
- send,
300
- }
301
- }