@portabletext/editor 1.18.7 → 1.20.0

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.
Files changed (43) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +52 -35
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/selector.is-selection-collapsed.cjs.map +1 -1
  4. package/lib/_chunks-es/behavior.core.js +52 -35
  5. package/lib/_chunks-es/behavior.core.js.map +1 -1
  6. package/lib/_chunks-es/selector.is-selection-collapsed.js.map +1 -1
  7. package/lib/behaviors/index.cjs +1 -0
  8. package/lib/behaviors/index.cjs.map +1 -1
  9. package/lib/behaviors/index.d.cts +78 -86
  10. package/lib/behaviors/index.d.ts +78 -86
  11. package/lib/behaviors/index.js +3 -2
  12. package/lib/behaviors/index.js.map +1 -1
  13. package/lib/index.cjs +277 -422
  14. package/lib/index.cjs.map +1 -1
  15. package/lib/index.d.cts +488 -1127
  16. package/lib/index.d.ts +488 -1127
  17. package/lib/index.js +279 -425
  18. package/lib/index.js.map +1 -1
  19. package/lib/selectors/index.cjs +12 -9
  20. package/lib/selectors/index.cjs.map +1 -1
  21. package/lib/selectors/index.js +12 -9
  22. package/lib/selectors/index.js.map +1 -1
  23. package/package.json +5 -7
  24. package/src/behavior-actions/behavior.actions.ts +28 -36
  25. package/src/behaviors/behavior.core.decorators.ts +36 -42
  26. package/src/behaviors/behavior.core.ts +4 -3
  27. package/src/behaviors/behavior.types.ts +40 -26
  28. package/src/behaviors/index.ts +1 -0
  29. package/src/editor/PortableTextEditor.tsx +14 -16
  30. package/src/editor/__tests__/self-solving.test.tsx +4 -11
  31. package/src/editor/components/Element.tsx +17 -23
  32. package/src/editor/create-editor.ts +18 -3
  33. package/src/editor/editor-machine.ts +67 -45
  34. package/src/editor/nodes/DefaultObject.tsx +2 -2
  35. package/src/editor/plugins/create-with-event-listeners.ts +44 -57
  36. package/src/editor/plugins/createWithHotKeys.ts +1 -11
  37. package/src/editor/plugins/createWithPortableTextMarkModel.ts +12 -1
  38. package/src/editor/plugins/createWithPortableTextSelections.ts +1 -5
  39. package/src/editor/with-applying-behavior-actions.ts +15 -0
  40. package/src/selectors/selector.get-selected-spans.test.ts +122 -0
  41. package/src/selectors/selector.get-selected-spans.ts +3 -1
  42. package/src/selectors/selector.is-active-decorator.test.ts +65 -0
  43. package/src/editor/nodes/index.ts +0 -189
@@ -0,0 +1,122 @@
1
+ import {expect, test} from 'vitest'
2
+ import {getSelectedSpans, type EditorSchema, type EditorSelection} from '.'
3
+ import type {EditorSnapshot} from '../editor/editor-snapshot'
4
+
5
+ test(getSelectedSpans.name, () => {
6
+ function snapshot(selection: EditorSelection): EditorSnapshot {
7
+ return {
8
+ context: {
9
+ schema: {} as EditorSchema,
10
+ keyGenerator: () => '',
11
+ activeDecorators: [],
12
+ value: [
13
+ {
14
+ _type: 'block',
15
+ _key: 'b1',
16
+ children: [
17
+ {
18
+ _type: 'span',
19
+ _key: 's1',
20
+ text: 'foo',
21
+ marks: ['strong'],
22
+ },
23
+ {
24
+ _type: 'span',
25
+ _key: 's2',
26
+ text: 'bar',
27
+ },
28
+ ],
29
+ },
30
+ {
31
+ _type: 'image',
32
+ _key: 'b2',
33
+ },
34
+ {
35
+ _type: 'block',
36
+ _key: 'b3',
37
+ children: [
38
+ {
39
+ _type: 'span',
40
+ _key: 's3',
41
+ text: 'baz',
42
+ },
43
+ ],
44
+ },
45
+ ],
46
+ selection,
47
+ },
48
+ }
49
+ }
50
+
51
+ expect(
52
+ getSelectedSpans(
53
+ snapshot({
54
+ anchor: {
55
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
56
+ offset: 0,
57
+ },
58
+ focus: {
59
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
60
+ offset: 3,
61
+ },
62
+ }),
63
+ ),
64
+ ).toEqual([
65
+ {
66
+ node: {_type: 'span', _key: 's1', text: 'foo', marks: ['strong']},
67
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
68
+ },
69
+ ])
70
+
71
+ expect(
72
+ getSelectedSpans(
73
+ snapshot({
74
+ anchor: {
75
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
76
+ offset: 2,
77
+ },
78
+ focus: {
79
+ path: [{_key: 'b1'}, 'children', {_key: 's2'}],
80
+ offset: 3,
81
+ },
82
+ }),
83
+ ),
84
+ ).toEqual([
85
+ {
86
+ node: {_type: 'span', _key: 's1', text: 'foo', marks: ['strong']},
87
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
88
+ },
89
+ {
90
+ node: {_type: 'span', _key: 's2', text: 'bar'},
91
+ path: [{_key: 'b1'}, 'children', {_key: 's2'}],
92
+ },
93
+ ])
94
+
95
+ expect(
96
+ getSelectedSpans(
97
+ snapshot({
98
+ anchor: {
99
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
100
+ offset: 2,
101
+ },
102
+ focus: {
103
+ path: [{_key: 'b3'}, 'children', {_key: 's3'}],
104
+ offset: 2,
105
+ },
106
+ }),
107
+ ),
108
+ ).toEqual([
109
+ {
110
+ node: {_type: 'span', _key: 's1', text: 'foo', marks: ['strong']},
111
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
112
+ },
113
+ {
114
+ node: {_type: 'span', _key: 's2', text: 'bar'},
115
+ path: [{_key: 'b1'}, 'children', {_key: 's2'}],
116
+ },
117
+ {
118
+ node: {_type: 'span', _key: 's3', text: 'baz'},
119
+ path: [{_key: 'b3'}, 'children', {_key: 's3'}],
120
+ },
121
+ ])
122
+ })
@@ -67,9 +67,11 @@ export const getSelectedSpans: EditorSelector<
67
67
  path: [{_key: block._key}, 'children', {_key: child._key}],
68
68
  })
69
69
 
70
- if (startBlockKey === endBlockKey) {
70
+ if (startSpanKey === endSpanKey) {
71
71
  break
72
72
  }
73
+
74
+ continue
73
75
  }
74
76
 
75
77
  if (endSpanKey && child._key === endSpanKey) {
@@ -0,0 +1,65 @@
1
+ import {expect, test} from 'vitest'
2
+ import type {EditorSchema, EditorSelection} from '.'
3
+ import type {EditorSnapshot} from '../editor/editor-snapshot'
4
+ import {isActiveDecorator} from './selector.is-active-decorator'
5
+
6
+ test(isActiveDecorator.name, () => {
7
+ function snapshot(selection: EditorSelection): EditorSnapshot {
8
+ return {
9
+ context: {
10
+ schema: {} as EditorSchema,
11
+ keyGenerator: () => '',
12
+ activeDecorators: [],
13
+ value: [
14
+ {
15
+ _type: '_block',
16
+ _key: 'b1',
17
+ children: [
18
+ {
19
+ _type: 'span',
20
+ _key: 's1',
21
+ text: 'foo',
22
+ marks: ['strong'],
23
+ },
24
+ {
25
+ _type: 'span',
26
+ _key: 's2',
27
+ text: 'bar',
28
+ },
29
+ ],
30
+ },
31
+ ],
32
+ selection,
33
+ },
34
+ }
35
+ }
36
+
37
+ expect(
38
+ isActiveDecorator('strong')(
39
+ snapshot({
40
+ anchor: {
41
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
42
+ offset: 0,
43
+ },
44
+ focus: {
45
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
46
+ offset: 3,
47
+ },
48
+ }),
49
+ ),
50
+ ).toBe(true)
51
+ expect(
52
+ isActiveDecorator('strong')(
53
+ snapshot({
54
+ anchor: {
55
+ path: [{_key: 'b1'}, 'children', {_key: 's1'}],
56
+ offset: 2,
57
+ },
58
+ focus: {
59
+ path: [{_key: 'b1'}, 'children', {_key: 's2'}],
60
+ offset: 3,
61
+ },
62
+ }),
63
+ ),
64
+ ).toBe(false)
65
+ })
@@ -1,189 +0,0 @@
1
- import {styled} from 'styled-components'
2
-
3
- export const DefaultBlockObject = styled.div<{selected: boolean}>`
4
- user-select: none;
5
- border: ${(props) => {
6
- if (props.selected) {
7
- return '1px solid blue'
8
- }
9
- return '1px solid transparent'
10
- }};
11
- `
12
-
13
- export const DefaultInlineObject = styled.span<{selected: boolean}>`
14
- background: #999;
15
- border: ${(props) => {
16
- if (props.selected) {
17
- return '1px solid blue'
18
- }
19
- return '1px solid transparent'
20
- }};
21
- `
22
-
23
- type ListItemProps = {listLevel: number; listStyle: string}
24
-
25
- export const DefaultListItem = styled.div<ListItemProps>`
26
- &.pt-list-item {
27
- width: fit-content;
28
- position: relative;
29
- display: block;
30
-
31
- /* Important 'transform' in order to force refresh the ::before and ::after rules
32
- in Webkit: https://stackoverflow.com/a/21947628/831480
33
- */
34
- transform: translateZ(0);
35
- margin-left: ${(props: ListItemProps) => getLeftPositionForListLevel(props.listLevel)};
36
- }
37
- &.pt-list-item > .pt-list-item-inner {
38
- display: flex;
39
- margin: 0;
40
- padding: 0;
41
- &:before {
42
- justify-content: flex-start;
43
- vertical-align: top;
44
- }
45
- }
46
- &.pt-list-item-bullet > .pt-list-item-inner:before {
47
- content: '${(props: ListItemProps) =>
48
- getContentForListLevelAndStyle(props.listLevel, props.listStyle)}';
49
- font-size: 0.4375rem; /* 7px */
50
- line-height: 1.5rem; /* Same as body text */
51
- /* Optical alignment */
52
- position: relative;
53
- }
54
- }
55
- &.pt-list-item-bullet > .pt-list-item-inner {
56
- &:before {
57
- min-width: 1.5rem; /* Make sure space between bullet and text never shrinks */
58
- }
59
- }
60
- &.pt-list-item-number {
61
- counter-increment: ${(props: {listLevel: number}) =>
62
- getCounterIncrementForListLevel(props.listLevel)};
63
- counter-reset: ${(props: {listLevel: number}) => getCounterResetForListLevel(props.listLevel)};
64
- }
65
- & + :not(.pt-list-item-number) {
66
- counter-reset: listItemNumber;
67
- }
68
- &.pt-list-item-number > .pt-list-item-inner:before {
69
- content: ${(props) => getCounterContentForListLevel(props.listLevel)};
70
- min-width: 1.5rem; /* Make sure space between number and text never shrinks */
71
- /* Optical alignment */
72
- position: relative;
73
- top: 1px;
74
- }
75
- `
76
-
77
- export const DefaultListItemInner = styled.div``
78
-
79
- function getLeftPositionForListLevel(level: number) {
80
- switch (Number(level)) {
81
- case 1:
82
- return '1.5em'
83
- case 2:
84
- return '3em'
85
- case 3:
86
- return '4.5em'
87
- case 4:
88
- return '6em'
89
- case 5:
90
- return '7.5em'
91
- case 6:
92
- return '9em'
93
- case 7:
94
- return '10.5em'
95
- case 8:
96
- return '12em'
97
- case 9:
98
- return '13.5em'
99
- case 10:
100
- return '15em'
101
- default:
102
- return '0em'
103
- }
104
- }
105
-
106
- const bullets = ['●', '○', '■']
107
-
108
- function getContentForListLevelAndStyle(level: number, style: string) {
109
- const normalizedLevel = (level - 1) % 3
110
- if (style === 'bullet') {
111
- return bullets[normalizedLevel]
112
- }
113
- return '*'
114
- }
115
-
116
- function getCounterIncrementForListLevel(level: number) {
117
- switch (level) {
118
- case 1:
119
- return 'listItemNumber'
120
- case 2:
121
- return 'listItemAlpha'
122
- case 3:
123
- return 'listItemRoman'
124
- case 4:
125
- return 'listItemNumberNext'
126
- case 5:
127
- return 'listItemLetterNext'
128
- case 6:
129
- return 'listItemRomanNext'
130
- case 7:
131
- return 'listItemNumberNextNext'
132
- case 8:
133
- return 'listItemAlphaNextNext'
134
- case 9:
135
- return 'listItemRomanNextNext'
136
- default:
137
- return 'listItemNumberNextNextNext'
138
- }
139
- }
140
-
141
- function getCounterResetForListLevel(level: number) {
142
- switch (level) {
143
- case 1:
144
- return 'listItemAlpha'
145
- case 2:
146
- return 'listItemRoman'
147
- case 3:
148
- return 'listItemNumberNext'
149
- case 4:
150
- return 'listItemLetterNext'
151
- case 5:
152
- return 'listItemRomanNext'
153
- case 6:
154
- return 'listItemNumberNextNext'
155
- case 7:
156
- return 'listItemAlphaNextNext'
157
- case 8:
158
- return 'listItemRomanNextNext'
159
- case 9:
160
- return 'listItemNumberNextNextNext'
161
- default:
162
- return 'listItemNumberNextNextNext'
163
- }
164
- }
165
-
166
- function getCounterContentForListLevel(level: number) {
167
- switch (level) {
168
- case 1:
169
- return `counter(listItemNumber) '. '`
170
- case 2:
171
- return `counter(listItemAlpha, lower-alpha) '. '`
172
- case 3:
173
- return `counter(listItemRoman, lower-roman) '. '`
174
- case 4:
175
- return `counter(listItemNumberNext) '. '`
176
- case 5:
177
- return `counter(listItemLetterNext, lower-alpha) '. '`
178
- case 6:
179
- return `counter(listItemRomanNext, lower-roman) '. '`
180
- case 7:
181
- return `counter(listItemNumberNextNext) '. '`
182
- case 8:
183
- return `counter(listItemAlphaNextNext, lower-alpha) '. '`
184
- case 9:
185
- return `counter(listItemRomanNextNext, lower-roman) '. '`
186
- default:
187
- return `counter(listItemNumberNextNextNext) '. '`
188
- }
189
- }