@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.
@@ -1,96 +0,0 @@
1
- import {useEditor, type Editor} from '@portabletext/editor'
2
- import {defineBehavior, raise} from '@portabletext/editor/behaviors'
3
- import {useActorRef} from '@xstate/react'
4
- import {fromCallback, setup, type AnyEventObject} from 'xstate'
5
- import {disableListener, type DisableListenerEvent} from './disable-listener'
6
- import type {ToolbarDecoratorSchemaType} from './use-toolbar-schema'
7
-
8
- const keyboardShortcutListener = fromCallback<
9
- AnyEventObject,
10
- {editor: Editor; schemaType: ToolbarDecoratorSchemaType}
11
- >(({input}) => {
12
- const shortcut = input.schemaType.shortcut
13
-
14
- if (!shortcut) {
15
- return
16
- }
17
-
18
- return input.editor.registerBehavior({
19
- behavior: defineBehavior({
20
- on: 'keyboard.keydown',
21
- guard: ({event}) => shortcut.guard(event.originEvent),
22
- actions: [
23
- () => [
24
- raise({
25
- type: 'decorator.toggle',
26
- decorator: input.schemaType.name,
27
- }),
28
- ],
29
- ],
30
- }),
31
- })
32
- })
33
-
34
- const decoratorKeyboardShortcutMachine = setup({
35
- types: {
36
- context: {} as {
37
- editor: Editor
38
- schemaType: ToolbarDecoratorSchemaType
39
- },
40
- input: {} as {
41
- editor: Editor
42
- schemaType: ToolbarDecoratorSchemaType
43
- },
44
- events: {} as DisableListenerEvent | {type: 'decorator.toggle'},
45
- },
46
- actors: {
47
- 'disable listener': disableListener,
48
- 'keyboard shortcut listener': keyboardShortcutListener,
49
- },
50
- }).createMachine({
51
- id: 'decorator keyboard shortcut',
52
- context: ({input}) => ({
53
- editor: input.editor,
54
- schemaType: input.schemaType,
55
- }),
56
- invoke: {
57
- src: 'disable listener',
58
- input: ({context}) => ({editor: context.editor}),
59
- },
60
- initial: 'disabled',
61
- states: {
62
- disabled: {
63
- on: {
64
- enable: {
65
- target: 'enabled',
66
- },
67
- },
68
- },
69
- enabled: {
70
- invoke: {
71
- src: 'keyboard shortcut listener',
72
- input: ({context}) => ({
73
- editor: context.editor,
74
- schemaType: context.schemaType,
75
- }),
76
- },
77
- on: {
78
- disable: {
79
- target: 'disabled',
80
- },
81
- },
82
- },
83
- },
84
- })
85
-
86
- export function useDecoratorKeyboardShortcut(props: {
87
- schemaType: ToolbarDecoratorSchemaType
88
- }) {
89
- const editor = useEditor()
90
- useActorRef(decoratorKeyboardShortcutMachine, {
91
- input: {
92
- editor,
93
- schemaType: props.schemaType,
94
- },
95
- })
96
- }
@@ -1,97 +0,0 @@
1
- import {useEditor, type Editor} from '@portabletext/editor'
2
- import {useActor} from '@xstate/react'
3
- import {setup} from 'xstate'
4
- import {disableListener, type DisableListenerEvent} from './disable-listener'
5
-
6
- const historyButtonsMachine = setup({
7
- types: {
8
- context: {} as {
9
- editor: Editor
10
- },
11
- input: {} as {
12
- editor: Editor
13
- },
14
- events: {} as DisableListenerEvent | HistoryButtonsEvent,
15
- },
16
- actors: {
17
- 'disable listener': disableListener,
18
- },
19
- }).createMachine({
20
- id: 'history buttons',
21
- context: ({input}) => ({
22
- editor: input.editor,
23
- }),
24
- invoke: {
25
- src: 'disable listener',
26
- input: ({context}) => ({editor: context.editor}),
27
- },
28
- initial: 'disabled',
29
- states: {
30
- disabled: {
31
- on: {
32
- enable: {
33
- target: 'enabled',
34
- },
35
- },
36
- },
37
- enabled: {
38
- on: {
39
- 'disable': {
40
- target: 'disabled',
41
- },
42
- 'history.undo': {
43
- actions: ({context}) => {
44
- context.editor.send({type: 'history.undo'})
45
- context.editor.send({type: 'focus'})
46
- },
47
- },
48
- 'history.redo': {
49
- actions: ({context}) => {
50
- context.editor.send({type: 'history.redo'})
51
- context.editor.send({type: 'focus'})
52
- },
53
- },
54
- },
55
- },
56
- },
57
- })
58
-
59
- /**
60
- * @beta
61
- */
62
- export type HistoryButtonsEvent =
63
- | {
64
- type: 'history.undo'
65
- }
66
- | {
67
- type: 'history.redo'
68
- }
69
-
70
- /**
71
- * @beta
72
- */
73
- export type HistoryButtons = {
74
- snapshot: {
75
- matches: (state: 'disabled' | 'enabled') => boolean
76
- }
77
- send: (event: HistoryButtonsEvent) => void
78
- }
79
-
80
- /**
81
- * @beta
82
- */
83
- export function useHistoryButtons(): HistoryButtons {
84
- const editor = useEditor()
85
- const [actorSnapshot, send] = useActor(historyButtonsMachine, {
86
- input: {
87
- editor,
88
- },
89
- })
90
-
91
- return {
92
- snapshot: {
93
- matches: (state) => actorSnapshot.matches(state),
94
- },
95
- send,
96
- }
97
- }
@@ -1,183 +0,0 @@
1
- import {useEditor, type Editor} from '@portabletext/editor'
2
- import {defineBehavior, effect} from '@portabletext/editor/behaviors'
3
- import {useActor} from '@xstate/react'
4
- import {fromCallback, setup, type AnyEventObject} from 'xstate'
5
- import {disableListener, type DisableListenerEvent} from './disable-listener'
6
- import type {ToolbarInlineObjectSchemaType} from './use-toolbar-schema'
7
-
8
- const keyboardShortcutListener = fromCallback<
9
- AnyEventObject,
10
- {editor: Editor; schemaType: ToolbarInlineObjectSchemaType},
11
- InlineObjectButtonEvent
12
- >(({input, sendBack}) => {
13
- const shortcut = input.schemaType.shortcut
14
-
15
- if (!shortcut) {
16
- return
17
- }
18
-
19
- return input.editor.registerBehavior({
20
- behavior: defineBehavior({
21
- on: 'keyboard.keydown',
22
- guard: ({event}) => shortcut.guard(event.originEvent),
23
- actions: [
24
- () => [
25
- effect(() => {
26
- sendBack({type: 'open dialog'})
27
- }),
28
- ],
29
- ],
30
- }),
31
- })
32
- })
33
-
34
- const inlineObjectButtonMachine = setup({
35
- types: {
36
- context: {} as {
37
- editor: Editor
38
- schemaType: ToolbarInlineObjectSchemaType
39
- },
40
- input: {} as {
41
- editor: Editor
42
- schemaType: ToolbarInlineObjectSchemaType
43
- },
44
- events: {} as DisableListenerEvent | InlineObjectButtonEvent,
45
- },
46
- actions: {
47
- insert: ({context, event}) => {
48
- if (event.type !== 'insert') {
49
- return
50
- }
51
-
52
- context.editor.send({
53
- type: 'insert.inline object',
54
- inlineObject: {
55
- name: context.schemaType.name,
56
- value: event.value,
57
- },
58
- })
59
- context.editor.send({type: 'focus'})
60
- },
61
- },
62
- actors: {
63
- 'disable listener': disableListener,
64
- 'keyboard shortcut listener': keyboardShortcutListener,
65
- },
66
- }).createMachine({
67
- id: 'inline object button',
68
- context: ({input}) => ({
69
- editor: input.editor,
70
- schemaType: input.schemaType,
71
- }),
72
- invoke: [
73
- {
74
- src: 'disable listener',
75
- input: ({context}) => ({editor: context.editor}),
76
- },
77
- ],
78
- initial: 'disabled',
79
- states: {
80
- disabled: {
81
- on: {
82
- enable: {
83
- target: 'enabled',
84
- },
85
- },
86
- },
87
- enabled: {
88
- on: {
89
- disable: {
90
- target: 'disabled',
91
- },
92
- },
93
- initial: 'idle',
94
- states: {
95
- 'idle': {
96
- invoke: [
97
- {
98
- src: 'keyboard shortcut listener',
99
- input: ({context}) => ({
100
- editor: context.editor,
101
- schemaType: context.schemaType,
102
- }),
103
- },
104
- ],
105
- on: {
106
- 'open dialog': {
107
- target: 'showing dialog',
108
- },
109
- },
110
- },
111
- 'showing dialog': {
112
- on: {
113
- 'close dialog': {
114
- target: 'idle',
115
- },
116
- 'insert': {
117
- actions: ['insert'],
118
- target: 'idle',
119
- },
120
- },
121
- },
122
- },
123
- },
124
- },
125
- })
126
-
127
- /**
128
- * @beta
129
- */
130
- export type InlineObjectButtonEvent =
131
- | {
132
- type: 'close dialog'
133
- }
134
- | {
135
- type: 'open dialog'
136
- }
137
- | {
138
- type: 'insert'
139
- value: {[key: string]: unknown}
140
- }
141
-
142
- /**
143
- * @beta
144
- */
145
- export type InlineObjectButton = {
146
- snapshot: {
147
- matches: (
148
- state:
149
- | 'disabled'
150
- | 'enabled'
151
- | {enabled: 'idle'}
152
- | {enabled: 'showing dialog'},
153
- ) => boolean
154
- }
155
- send: (event: InlineObjectButtonEvent) => void
156
- }
157
-
158
- /**
159
- * @beta
160
- * Manages the state, keyboard shortcut and available events for an inline
161
- * object button.
162
- *
163
- * Note: This hook assumes that the button triggers a dialog for inputting
164
- * the inline object value.
165
- */
166
- export function useInlineObjectButton(props: {
167
- schemaType: ToolbarInlineObjectSchemaType
168
- }): InlineObjectButton {
169
- const editor = useEditor()
170
- const [actorSnapshot, send] = useActor(inlineObjectButtonMachine, {
171
- input: {
172
- editor,
173
- schemaType: props.schemaType,
174
- },
175
- })
176
-
177
- return {
178
- snapshot: {
179
- matches: (state) => actorSnapshot.matches(state),
180
- },
181
- send,
182
- }
183
- }
@@ -1,285 +0,0 @@
1
- import {
2
- useEditor,
3
- type ChildPath,
4
- type Editor,
5
- type PortableTextObject,
6
- } from '@portabletext/editor'
7
- import * as selectors from '@portabletext/editor/selectors'
8
- import {useActor} from '@xstate/react'
9
- import * as React from 'react'
10
- import type {RefObject} from 'react'
11
- import {assign, fromCallback, setup, type AnyEventObject} from 'xstate'
12
- import {disableListener, type DisableListenerEvent} from './disable-listener'
13
- import type {ToolbarInlineObjectSchemaType} from './use-toolbar-schema'
14
-
15
- type ActiveContext = {
16
- inlineObjects: Array<{
17
- value: PortableTextObject
18
- schemaType: ToolbarInlineObjectSchemaType
19
- at: ChildPath
20
- }>
21
- elementRef: RefObject<Element | null>
22
- }
23
-
24
- type ActiveListenerEvent =
25
- | ({
26
- type: 'set active'
27
- } & ActiveContext)
28
- | {
29
- type: 'set inactive'
30
- }
31
-
32
- const activeListener = fromCallback<
33
- AnyEventObject,
34
- {editor: Editor; schemaTypes: ReadonlyArray<ToolbarInlineObjectSchemaType>},
35
- ActiveListenerEvent
36
- >(({input, sendBack}) => {
37
- return input.editor.on('selection', () => {
38
- const snapshot = input.editor.getSnapshot()
39
-
40
- if (!selectors.isSelectionCollapsed(snapshot)) {
41
- sendBack({type: 'set inactive'})
42
- return
43
- }
44
-
45
- const focusInlineObject = selectors.getFocusInlineObject(snapshot)
46
-
47
- if (!focusInlineObject) {
48
- sendBack({type: 'set inactive'})
49
- return
50
- }
51
-
52
- const schemaType = input.schemaTypes.find(
53
- (schemaType) => schemaType.name === focusInlineObject.node._type,
54
- )
55
-
56
- if (!schemaType) {
57
- sendBack({type: 'set inactive'})
58
- return
59
- }
60
-
61
- const selectedNodes = input.editor.dom.getChildNodes(snapshot)
62
- const firstSelectedNode = selectedNodes.at(0)
63
-
64
- if (!firstSelectedNode || !(firstSelectedNode instanceof Element)) {
65
- sendBack({type: 'set inactive'})
66
- return
67
- }
68
-
69
- const elementRef = React.createRef<Element>()
70
- elementRef.current = firstSelectedNode
71
-
72
- sendBack({
73
- type: 'set active',
74
- inlineObjects: [
75
- {
76
- value: focusInlineObject.node,
77
- schemaType,
78
- at: focusInlineObject.path,
79
- },
80
- ],
81
- elementRef,
82
- })
83
- }).unsubscribe
84
- })
85
-
86
- const inlineObjectPopoverMachine = setup({
87
- types: {
88
- context: {} as {
89
- editor: Editor
90
- schemaTypes: ReadonlyArray<ToolbarInlineObjectSchemaType>
91
- } & ActiveContext,
92
- input: {} as {
93
- editor: Editor
94
- schemaTypes: ReadonlyArray<ToolbarInlineObjectSchemaType>
95
- },
96
- events: {} as
97
- | DisableListenerEvent
98
- | ActiveListenerEvent
99
- | InlineObjectPopoverEvent,
100
- },
101
- actions: {
102
- reset: assign({
103
- inlineObjects: [],
104
- elementRef: React.createRef<Element>(),
105
- }),
106
- },
107
- actors: {
108
- 'disable listener': disableListener,
109
- 'active listener': activeListener,
110
- },
111
- }).createMachine({
112
- id: 'inline object popover',
113
- context: ({input}) => ({
114
- editor: input.editor,
115
- schemaTypes: input.schemaTypes,
116
- inlineObjects: [],
117
- elementRef: React.createRef<Element>(),
118
- }),
119
- invoke: [
120
- {src: 'disable listener', input: ({context}) => ({editor: context.editor})},
121
- {
122
- src: 'active listener',
123
- input: ({context}) => ({
124
- editor: context.editor,
125
- schemaTypes: context.schemaTypes,
126
- }),
127
- },
128
- ],
129
- initial: 'disabled',
130
- states: {
131
- disabled: {
132
- initial: 'inactive',
133
- states: {
134
- inactive: {
135
- entry: ['reset'],
136
- on: {
137
- 'set active': {
138
- actions: assign({
139
- inlineObjects: ({event}) => event.inlineObjects,
140
- elementRef: ({event}) => event.elementRef,
141
- }),
142
- target: 'active',
143
- },
144
- 'enable': {
145
- target: '#inline object popover.enabled.inactive',
146
- },
147
- },
148
- },
149
- active: {
150
- on: {
151
- 'set inactive': {
152
- target: 'inactive',
153
- },
154
- 'enable': {
155
- target: '#inline object popover.enabled.active',
156
- },
157
- },
158
- },
159
- },
160
- },
161
- enabled: {
162
- initial: 'inactive',
163
- states: {
164
- inactive: {
165
- entry: ['reset'],
166
- on: {
167
- 'set active': {
168
- target: 'active',
169
- actions: assign({
170
- inlineObjects: ({event}) => event.inlineObjects,
171
- elementRef: ({event}) => event.elementRef,
172
- }),
173
- },
174
- 'disable': {
175
- target: '#inline object popover.disabled.inactive',
176
- },
177
- },
178
- },
179
- active: {
180
- on: {
181
- 'set inactive': {
182
- target: 'inactive',
183
- },
184
- 'disable': {
185
- target: '#inline object popover.disabled.active',
186
- },
187
- 'set active': {
188
- actions: assign({
189
- inlineObjects: ({event}) => event.inlineObjects,
190
- elementRef: ({event}) => event.elementRef,
191
- }),
192
- },
193
- 'edit': {
194
- actions: ({context, event}) => {
195
- context.editor.send({
196
- type: 'child.set',
197
- at: event.at,
198
- props: event.props,
199
- })
200
- context.editor.send({type: 'focus'})
201
- },
202
- },
203
- 'remove': {
204
- actions: ({context, event}) => {
205
- context.editor.send({
206
- type: 'delete.child',
207
- at: event.at,
208
- })
209
- context.editor.send({type: 'focus'})
210
- },
211
- },
212
- 'close': {
213
- actions: ({context}) => {
214
- context.editor.send({type: 'focus'})
215
- },
216
- target: 'inactive',
217
- },
218
- },
219
- },
220
- },
221
- },
222
- },
223
- })
224
-
225
- /**
226
- * @beta
227
- */
228
- export type InlineObjectPopoverEvent =
229
- | {
230
- type: 'remove'
231
- at: ChildPath
232
- }
233
- | {
234
- type: 'edit'
235
- at: ChildPath
236
- props: {[key: string]: unknown}
237
- }
238
- | {
239
- type: 'close'
240
- }
241
-
242
- /**
243
- * @beta
244
- */
245
- export type InlineObjectPopover = {
246
- snapshot: {
247
- context: ActiveContext
248
- matches: (
249
- state:
250
- | 'disabled'
251
- | 'enabled'
252
- | {
253
- enabled: 'inactive' | 'active'
254
- },
255
- ) => boolean
256
- }
257
- send: (event: InlineObjectPopoverEvent) => void
258
- }
259
-
260
- /**
261
- * @beta
262
- * Manages the state and available events for an inline object popover.
263
- */
264
- export function useInlineObjectPopover(props: {
265
- schemaTypes: ReadonlyArray<ToolbarInlineObjectSchemaType>
266
- }): InlineObjectPopover {
267
- const editor = useEditor()
268
- const [actorSnapshot, send] = useActor(inlineObjectPopoverMachine, {
269
- input: {
270
- editor,
271
- schemaTypes: props.schemaTypes,
272
- },
273
- })
274
-
275
- return {
276
- snapshot: {
277
- context: {
278
- inlineObjects: actorSnapshot.context.inlineObjects,
279
- elementRef: actorSnapshot.context.elementRef,
280
- },
281
- matches: (state) => actorSnapshot.matches(state),
282
- },
283
- send,
284
- }
285
- }