@portabletext/toolbar 4.0.23 → 4.0.24

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,189 +0,0 @@
1
- import {useEditor, type Editor} from '@portabletext/editor'
2
- import {
3
- defineBehavior,
4
- effect,
5
- type InsertPlacement,
6
- } from '@portabletext/editor/behaviors'
7
- import {useActor} from '@xstate/react'
8
- import {fromCallback, setup, type AnyEventObject} from 'xstate'
9
- import {disableListener, type DisableListenerEvent} from './disable-listener'
10
- import type {ToolbarBlockObjectSchemaType} from './use-toolbar-schema'
11
-
12
- const keyboardShortcutListener = fromCallback<
13
- AnyEventObject,
14
- {editor: Editor; schemaType: ToolbarBlockObjectSchemaType},
15
- BlockObjectButtonEvent
16
- >(({input, sendBack}) => {
17
- const shortcut = input.schemaType.shortcut
18
-
19
- if (!shortcut) {
20
- return
21
- }
22
-
23
- return input.editor.registerBehavior({
24
- behavior: defineBehavior({
25
- on: 'keyboard.keydown',
26
- guard: ({event}) => shortcut.guard(event.originEvent),
27
- actions: [
28
- () => [
29
- effect(() => {
30
- sendBack({type: 'open dialog'})
31
- }),
32
- ],
33
- ],
34
- }),
35
- })
36
- })
37
-
38
- const blockObjectButtonMachine = setup({
39
- types: {
40
- context: {} as {
41
- editor: Editor
42
- schemaType: ToolbarBlockObjectSchemaType
43
- },
44
- input: {} as {
45
- editor: Editor
46
- schemaType: ToolbarBlockObjectSchemaType
47
- },
48
- events: {} as DisableListenerEvent | BlockObjectButtonEvent,
49
- },
50
- actions: {
51
- insert: ({context, event}) => {
52
- if (event.type !== 'insert') {
53
- return
54
- }
55
-
56
- context.editor.send({
57
- type: 'insert.block object',
58
- blockObject: {
59
- name: context.schemaType.name,
60
- value: event.value,
61
- },
62
- placement: event.placement ?? 'auto',
63
- })
64
- context.editor.send({type: 'focus'})
65
- },
66
- },
67
- actors: {
68
- 'disable listener': disableListener,
69
- 'keyboard shortcut listener': keyboardShortcutListener,
70
- },
71
- }).createMachine({
72
- id: 'block object button',
73
- context: ({input}) => ({
74
- editor: input.editor,
75
- schemaType: input.schemaType,
76
- }),
77
- invoke: [
78
- {
79
- src: 'disable listener',
80
- input: ({context}) => ({editor: context.editor}),
81
- },
82
- ],
83
- initial: 'disabled',
84
- states: {
85
- disabled: {
86
- on: {
87
- enable: {
88
- target: 'enabled',
89
- },
90
- },
91
- },
92
- enabled: {
93
- on: {
94
- disable: {
95
- target: 'disabled',
96
- },
97
- },
98
- initial: 'idle',
99
- states: {
100
- 'idle': {
101
- invoke: [
102
- {
103
- src: 'keyboard shortcut listener',
104
- input: ({context}) => ({
105
- editor: context.editor,
106
- schemaType: context.schemaType,
107
- }),
108
- },
109
- ],
110
- on: {
111
- 'open dialog': {
112
- target: 'showing dialog',
113
- },
114
- },
115
- },
116
- 'showing dialog': {
117
- on: {
118
- 'close dialog': {
119
- target: 'idle',
120
- },
121
- 'insert': {
122
- actions: ['insert'],
123
- target: 'idle',
124
- },
125
- },
126
- },
127
- },
128
- },
129
- },
130
- })
131
-
132
- /**
133
- * @beta
134
- */
135
- export type BlockObjectButtonEvent =
136
- | {
137
- type: 'close dialog'
138
- }
139
- | {
140
- type: 'open dialog'
141
- }
142
- | {
143
- type: 'insert'
144
- value: {[key: string]: unknown}
145
- placement: InsertPlacement | undefined
146
- }
147
-
148
- /**
149
- * @beta
150
- */
151
- export type BlockObjectButton = {
152
- snapshot: {
153
- matches: (
154
- state:
155
- | 'disabled'
156
- | 'enabled'
157
- | {enabled: 'idle'}
158
- | {enabled: 'showing dialog'},
159
- ) => boolean
160
- }
161
- send: (event: BlockObjectButtonEvent) => void
162
- }
163
-
164
- /**
165
- * @beta
166
- * Manages the state, keyboard shortcut and available events for a block
167
- * object button.
168
- *
169
- * Note: This hook assumes that the button triggers a dialog for inputting
170
- * the block object value.
171
- */
172
- export function useBlockObjectButton(props: {
173
- schemaType: ToolbarBlockObjectSchemaType
174
- }): BlockObjectButton {
175
- const editor = useEditor()
176
- const [actorSnapshot, send] = useActor(blockObjectButtonMachine, {
177
- input: {
178
- editor,
179
- schemaType: props.schemaType,
180
- },
181
- })
182
-
183
- return {
184
- snapshot: {
185
- matches: (state) => actorSnapshot.matches(state),
186
- },
187
- send,
188
- }
189
- }
@@ -1,285 +0,0 @@
1
- import {
2
- useEditor,
3
- type BlockPath,
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 {ToolbarBlockObjectSchemaType} from './use-toolbar-schema'
14
-
15
- type ActiveContext = {
16
- blockObjects: Array<{
17
- value: PortableTextObject
18
- schemaType: ToolbarBlockObjectSchemaType
19
- at: BlockPath
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<ToolbarBlockObjectSchemaType>},
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 focusBlockObject = selectors.getFocusBlockObject(snapshot)
46
-
47
- if (!focusBlockObject) {
48
- sendBack({type: 'set inactive'})
49
- return
50
- }
51
-
52
- const schemaType = input.schemaTypes.find(
53
- (schemaType) => schemaType.name === focusBlockObject.node._type,
54
- )
55
-
56
- if (!schemaType) {
57
- sendBack({type: 'set inactive'})
58
- return
59
- }
60
-
61
- const selectedNodes = input.editor.dom.getBlockNodes(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
- blockObjects: [
75
- {
76
- value: focusBlockObject.node,
77
- schemaType,
78
- at: focusBlockObject.path,
79
- },
80
- ],
81
- elementRef,
82
- })
83
- }).unsubscribe
84
- })
85
-
86
- const blockObjectPopoverMachine = setup({
87
- types: {
88
- context: {} as {
89
- editor: Editor
90
- schemaTypes: ReadonlyArray<ToolbarBlockObjectSchemaType>
91
- } & ActiveContext,
92
- input: {} as {
93
- editor: Editor
94
- schemaTypes: ReadonlyArray<ToolbarBlockObjectSchemaType>
95
- },
96
- events: {} as
97
- | DisableListenerEvent
98
- | ActiveListenerEvent
99
- | BlockObjectPopoverEvent,
100
- },
101
- actions: {
102
- reset: assign({
103
- blockObjects: [],
104
- elementRef: React.createRef<Element>(),
105
- }),
106
- },
107
- actors: {
108
- 'disable listener': disableListener,
109
- 'active listener': activeListener,
110
- },
111
- }).createMachine({
112
- id: 'block object popover',
113
- context: ({input}) => ({
114
- editor: input.editor,
115
- schemaTypes: input.schemaTypes,
116
- blockObjects: [],
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
- blockObjects: ({event}) => event.blockObjects,
140
- elementRef: ({event}) => event.elementRef,
141
- }),
142
- target: 'active',
143
- },
144
- 'enable': {
145
- target: '#block object popover.enabled.inactive',
146
- },
147
- },
148
- },
149
- active: {
150
- on: {
151
- 'set inactive': {
152
- target: 'inactive',
153
- },
154
- 'enable': {
155
- target: '#block 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
- blockObjects: ({event}) => event.blockObjects,
171
- elementRef: ({event}) => event.elementRef,
172
- }),
173
- },
174
- 'disable': {
175
- target: '#block object popover.disabled.inactive',
176
- },
177
- },
178
- },
179
- active: {
180
- on: {
181
- 'set inactive': {
182
- target: 'inactive',
183
- },
184
- 'disable': {
185
- target: '#block object popover.disabled.active',
186
- },
187
- 'set active': {
188
- actions: assign({
189
- blockObjects: ({event}) => event.blockObjects,
190
- elementRef: ({event}) => event.elementRef,
191
- }),
192
- },
193
- 'edit': {
194
- actions: ({context, event}) => {
195
- context.editor.send({
196
- type: 'block.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.block',
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 BlockObjectPopoverEvent =
229
- | {
230
- type: 'remove'
231
- at: BlockPath
232
- }
233
- | {
234
- type: 'edit'
235
- at: BlockPath
236
- props: {[key: string]: unknown}
237
- }
238
- | {
239
- type: 'close'
240
- }
241
-
242
- /**
243
- * @beta
244
- */
245
- export type BlockObjectPopover = {
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: BlockObjectPopoverEvent) => void
258
- }
259
-
260
- /**
261
- * @beta
262
- * Manages the state and available events for a block object popover.
263
- */
264
- export function useBlockObjectPopover(props: {
265
- schemaTypes: ReadonlyArray<ToolbarBlockObjectSchemaType>
266
- }): BlockObjectPopover {
267
- const editor = useEditor()
268
- const [actorSnapshot, send] = useActor(blockObjectPopoverMachine, {
269
- input: {
270
- editor,
271
- schemaTypes: props.schemaTypes,
272
- },
273
- })
274
-
275
- return {
276
- snapshot: {
277
- context: {
278
- blockObjects: actorSnapshot.context.blockObjects,
279
- elementRef: actorSnapshot.context.elementRef,
280
- },
281
- matches: (state) => actorSnapshot.matches(state),
282
- },
283
- send,
284
- }
285
- }
@@ -1,196 +0,0 @@
1
- import {
2
- useEditor,
3
- type DecoratorSchemaType,
4
- type Editor,
5
- } from '@portabletext/editor'
6
- import * as selectors from '@portabletext/editor/selectors'
7
- import {useActor} from '@xstate/react'
8
- import {fromCallback, setup, type AnyEventObject} from 'xstate'
9
- import {disableListener, type DisableListenerEvent} from './disable-listener'
10
- import {useDecoratorKeyboardShortcut} from './use-decorator-keyboard-shortcut'
11
- import {useMutuallyExclusiveDecorator} from './use-mutually-exclusive-decorator'
12
- import type {ToolbarDecoratorSchemaType} from './use-toolbar-schema'
13
-
14
- const activeListener = fromCallback<
15
- AnyEventObject,
16
- {editor: Editor; schemaType: DecoratorSchemaType},
17
- {type: 'set active'} | {type: 'set inactive'}
18
- >(({input, sendBack}) => {
19
- // Send back the initial state
20
- if (
21
- selectors.isActiveDecorator(input.schemaType.name)(
22
- input.editor.getSnapshot(),
23
- )
24
- ) {
25
- sendBack({type: 'set active'})
26
- } else {
27
- sendBack({type: 'set inactive'})
28
- }
29
-
30
- return input.editor.on('*', () => {
31
- const snapshot = input.editor.getSnapshot()
32
-
33
- if (selectors.isActiveDecorator(input.schemaType.name)(snapshot)) {
34
- sendBack({type: 'set active'})
35
- } else {
36
- sendBack({type: 'set inactive'})
37
- }
38
- }).unsubscribe
39
- })
40
-
41
- const decoratorButtonMachine = setup({
42
- types: {
43
- context: {} as {
44
- editor: Editor
45
- schemaType: DecoratorSchemaType
46
- },
47
- input: {} as {
48
- editor: Editor
49
- schemaType: DecoratorSchemaType
50
- },
51
- events: {} as DisableListenerEvent | DecoratorButtonEvent,
52
- },
53
- actions: {
54
- toggle: ({context, event}) => {
55
- if (event.type !== 'toggle') {
56
- return
57
- }
58
-
59
- context.editor.send({
60
- type: 'decorator.toggle',
61
- decorator: context.schemaType.name,
62
- })
63
- context.editor.send({type: 'focus'})
64
- },
65
- },
66
- actors: {
67
- 'disable listener': disableListener,
68
- 'active listener': activeListener,
69
- },
70
- }).createMachine({
71
- id: 'decorator button',
72
- context: ({input}) => ({
73
- editor: input.editor,
74
- schemaType: input.schemaType,
75
- }),
76
- invoke: [
77
- {src: 'disable listener', input: ({context}) => ({editor: context.editor})},
78
- {
79
- src: 'active listener',
80
- input: ({context}) => ({
81
- editor: context.editor,
82
- schemaType: context.schemaType,
83
- }),
84
- },
85
- ],
86
- initial: 'disabled',
87
- states: {
88
- disabled: {
89
- initial: 'inactive',
90
- states: {
91
- inactive: {
92
- on: {
93
- 'enable': {
94
- target: '#decorator button.enabled.inactive',
95
- },
96
- 'set active': {
97
- target: 'active',
98
- },
99
- },
100
- },
101
- active: {
102
- on: {
103
- 'enable': {
104
- target: '#decorator button.enabled.active',
105
- },
106
- 'set inactive': {
107
- target: 'inactive',
108
- },
109
- },
110
- },
111
- },
112
- },
113
- enabled: {
114
- on: {
115
- toggle: {
116
- actions: ['toggle'],
117
- },
118
- },
119
- initial: 'inactive',
120
- states: {
121
- inactive: {
122
- on: {
123
- 'disable': {
124
- target: '#decorator button.disabled.inactive',
125
- },
126
- 'set active': {
127
- target: 'active',
128
- },
129
- },
130
- },
131
- active: {
132
- on: {
133
- 'disable': {
134
- target: '#decorator button.disabled.active',
135
- },
136
- 'set inactive': {
137
- target: 'inactive',
138
- },
139
- },
140
- },
141
- },
142
- },
143
- },
144
- })
145
-
146
- /**
147
- * @beta
148
- */
149
- export type DecoratorButtonEvent = {
150
- type: 'toggle'
151
- }
152
-
153
- /**
154
- * @beta
155
- */
156
- export type DecoratorButton = {
157
- snapshot: {
158
- matches: (
159
- state:
160
- | 'disabled'
161
- | 'enabled'
162
- | {disabled: 'inactive'}
163
- | {disabled: 'active'}
164
- | {enabled: 'inactive'}
165
- | {enabled: 'active'},
166
- ) => boolean
167
- }
168
- send: (event: DecoratorButtonEvent) => void
169
- }
170
-
171
- /**
172
- * @beta
173
- * Manages the state, keyboard shortcuts and available events for a decorator
174
- * button and sets up mutually exclusive decorator behaviors.
175
- */
176
- export function useDecoratorButton(props: {
177
- schemaType: ToolbarDecoratorSchemaType
178
- }): DecoratorButton {
179
- const editor = useEditor()
180
- const [actorSnapshot, send] = useActor(decoratorButtonMachine, {
181
- input: {
182
- editor,
183
- schemaType: props.schemaType,
184
- },
185
- })
186
-
187
- useDecoratorKeyboardShortcut(props)
188
- useMutuallyExclusiveDecorator(props)
189
-
190
- return {
191
- snapshot: {
192
- matches: (state) => actorSnapshot.matches(state),
193
- },
194
- send,
195
- }
196
- }