@portabletext/editor 2.6.10-canary.0 → 2.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "2.6.10-canary.0",
3
+ "version": "2.7.1",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -80,8 +80,8 @@
80
80
  "slate-react": "0.117.4",
81
81
  "xstate": "^5.21.0",
82
82
  "@portabletext/block-tools": "^3.5.1",
83
- "@portabletext/patches": "^1.1.8",
84
83
  "@portabletext/keyboard-shortcuts": "^1.1.1",
84
+ "@portabletext/patches": "^1.1.8",
85
85
  "@portabletext/schema": "^1.2.0"
86
86
  },
87
87
  "devDependencies": {
@@ -95,19 +95,19 @@
95
95
  "@types/lodash.startcase": "^4.4.9",
96
96
  "@types/react": "^19.1.11",
97
97
  "@types/react-dom": "^19.1.7",
98
- "@typescript-eslint/eslint-plugin": "^8.39.1",
99
- "@typescript-eslint/parser": "^8.39.1",
100
98
  "@vitejs/plugin-react": "^4.7.0",
101
99
  "@vitest/browser": "^3.2.4",
102
100
  "@vitest/coverage-istanbul": "^3.2.4",
103
101
  "babel-plugin-react-compiler": "19.1.0-rc.3",
104
- "eslint": "8.57.1",
102
+ "eslint": "^9.34.0",
103
+ "eslint-formatter-gha": "^1.6.0",
105
104
  "eslint-plugin-react-hooks": "6.0.0-rc.2",
106
105
  "jsdom": "^26.0.0",
107
106
  "react": "^19.1.1",
108
107
  "react-dom": "^19.1.1",
109
108
  "rxjs": "^7.8.2",
110
109
  "typescript": "5.9.2",
110
+ "typescript-eslint": "^8.41.0",
111
111
  "vite": "^7.1.3",
112
112
  "vitest": "^3.2.4",
113
113
  "vitest-browser-react": "^1.0.1",
@@ -131,7 +131,7 @@
131
131
  "scripts": {
132
132
  "build": "pkg-utils build --strict --check --clean",
133
133
  "check:lint": "biome lint .",
134
- "check:react-compiler": "eslint --cache --no-inline-config --no-eslintrc --ignore-pattern '**/__tests__/**' --ext .cjs,.mjs,.js,.jsx,.ts,.tsx --parser @typescript-eslint/parser --plugin react-hooks --rule 'react-hooks/rules-of-hooks: [error]' --rule 'react-hooks/exhaustive-deps: [error]' src",
134
+ "check:react-compiler": "eslint --cache .",
135
135
  "check:types": "tsc",
136
136
  "check:types:watch": "tsc --watch",
137
137
  "clean": "del .turbo && del lib && del node_modules",
@@ -73,16 +73,17 @@ export function RenderBlockObject(props: {
73
73
  draggable={!props.readOnly}
74
74
  >
75
75
  {props.renderBlock && legacySchemaType ? (
76
- props.renderBlock({
77
- children: <RenderDefaultBlockObject blockObject={blockObject} />,
78
- editorElementRef: blockObjectRef,
79
- focused,
80
- path: [{_key: props.element._key}],
81
- schemaType: legacySchemaType,
82
- selected,
83
- type: legacySchemaType,
84
- value: blockObject,
85
- })
76
+ <props.renderBlock
77
+ editorElementRef={blockObjectRef}
78
+ focused={focused}
79
+ path={[{_key: props.element._key}]}
80
+ schemaType={legacySchemaType}
81
+ selected={selected}
82
+ type={legacySchemaType}
83
+ value={blockObject}
84
+ >
85
+ <RenderDefaultBlockObject blockObject={blockObject} />
86
+ </props.renderBlock>
86
87
  ) : (
87
88
  <RenderDefaultBlockObject blockObject={blockObject} />
88
89
  )}
@@ -79,17 +79,18 @@ export function RenderInlineObject(props: {
79
79
  {props.children}
80
80
  <span ref={inlineObjectRef} style={{display: 'inline-block'}}>
81
81
  {props.renderChild && block && legacySchemaType ? (
82
- props.renderChild({
83
- annotations: [],
84
- children: <RenderDefaultInlineObject inlineObject={inlineObject} />,
85
- editorElementRef: inlineObjectRef,
86
- selected,
87
- focused,
88
- path: [{_key: block._key}, 'children', {_key: props.element._key}],
89
- schemaType: legacySchemaType,
90
- value: inlineObject,
91
- type: legacySchemaType,
92
- })
82
+ <props.renderChild
83
+ annotations={[]}
84
+ editorElementRef={inlineObjectRef}
85
+ selected={selected}
86
+ focused={focused}
87
+ path={[{_key: block._key}, 'children', {_key: props.element._key}]}
88
+ schemaType={legacySchemaType}
89
+ value={inlineObject}
90
+ type={legacySchemaType}
91
+ >
92
+ <RenderDefaultInlineObject inlineObject={inlineObject} />
93
+ </props.renderChild>
93
94
  ) : (
94
95
  <RenderDefaultInlineObject inlineObject={inlineObject} />
95
96
  )}
@@ -1,24 +1,21 @@
1
+ import {isTextBlock} from '@portabletext/schema'
1
2
  import {useSelector} from '@xstate/react'
2
- import {isEqual, uniq} from 'lodash'
3
+ import {uniq} from 'lodash'
4
+ import {useContext, useMemo, useRef, type ReactElement} from 'react'
5
+ import {useSlateStatic, type RenderLeafProps} from 'slate-react'
3
6
  import {
4
- startTransition,
5
- useCallback,
6
- useContext,
7
- useEffect,
8
- useMemo,
9
- useRef,
10
- useState,
11
- type ReactElement,
12
- } from 'react'
13
- import {useSelected, useSlateStatic, type RenderLeafProps} from 'slate-react'
7
+ getFocusSpan,
8
+ isOverlappingSelection,
9
+ isSelectionCollapsed,
10
+ } from '../../selectors'
14
11
  import type {
12
+ EditorSelection,
15
13
  RenderAnnotationFunction,
16
14
  RenderChildFunction,
17
15
  RenderDecoratorFunction,
18
16
  } from '../../types/editor'
19
17
  import {EditorActorContext} from '../editor-actor-context'
20
- import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
21
- import {PortableTextEditor} from '../PortableTextEditor'
18
+ import {getEditorSnapshot} from '../editor-selector'
22
19
 
23
20
  export interface RenderSpanProps extends RenderLeafProps {
24
21
  children: ReactElement<any>
@@ -35,10 +32,66 @@ export function RenderSpan(props: RenderSpanProps) {
35
32
  s.context.getLegacySchema(),
36
33
  )
37
34
  const spanRef = useRef<HTMLElement>(null)
38
- const portableTextEditor = usePortableTextEditor()
39
- const blockSelected = useSelected()
40
- const [focused, setFocused] = useState(false)
41
- const [selected, setSelected] = useState(false)
35
+
36
+ /**
37
+ * A span is considered focused if the selection is collapsed and the caret
38
+ * is inside the span.
39
+ */
40
+ const focused = useSelector(editorActor, (editorActorSnapshot) => {
41
+ const snapshot = getEditorSnapshot({
42
+ editorActorSnapshot,
43
+ slateEditorInstance: slateEditor,
44
+ })
45
+
46
+ if (!snapshot.context.selection) {
47
+ return false
48
+ }
49
+
50
+ if (!isSelectionCollapsed(snapshot)) {
51
+ return false
52
+ }
53
+
54
+ const focusedSpan = getFocusSpan(snapshot)
55
+
56
+ if (!focusedSpan) {
57
+ return false
58
+ }
59
+
60
+ return focusedSpan.node._key === props.leaf._key
61
+ })
62
+
63
+ /**
64
+ * A span is considered selected if editor selection is overlapping with the
65
+ * span selection points.
66
+ */
67
+ const selected = useSelector(editorActor, (editorActorSnapshot) => {
68
+ const snapshot = getEditorSnapshot({
69
+ editorActorSnapshot,
70
+ slateEditorInstance: slateEditor,
71
+ })
72
+
73
+ if (!snapshot.context.selection) {
74
+ return false
75
+ }
76
+
77
+ const parent = props.children.props.parent
78
+ const block =
79
+ parent && isTextBlock(snapshot.context, parent) ? parent : undefined
80
+ const spanSelection: EditorSelection = block
81
+ ? {
82
+ anchor: {
83
+ path: [{_key: block._key}, 'children', {_key: props.leaf._key}],
84
+ offset: 0,
85
+ },
86
+ focus: {
87
+ path: [{_key: block._key}, 'children', {_key: props.leaf._key}],
88
+ offset: props.leaf.text.length,
89
+ },
90
+ }
91
+ : null
92
+
93
+ return isOverlappingSelection(spanSelection)(snapshot)
94
+ })
42
95
 
43
96
  const parent = props.children.props.parent
44
97
  const block = parent && slateEditor.isTextBlock(parent) ? parent : undefined
@@ -75,106 +128,6 @@ export function RenderSpan(props: RenderSpanProps) {
75
128
  return []
76
129
  })
77
130
 
78
- const shouldTrackSelectionAndFocus =
79
- annotationMarkDefs.length > 0 && blockSelected
80
-
81
- useEffect(() => {
82
- if (!shouldTrackSelectionAndFocus) {
83
- setFocused(false)
84
- return
85
- }
86
-
87
- const sel = PortableTextEditor.getSelection(portableTextEditor)
88
-
89
- if (
90
- sel &&
91
- isEqual(sel.focus.path, path) &&
92
- PortableTextEditor.isCollapsedSelection(portableTextEditor)
93
- ) {
94
- startTransition(() => {
95
- setFocused(true)
96
- })
97
- }
98
- }, [shouldTrackSelectionAndFocus, path, portableTextEditor])
99
-
100
- // Function to check if this leaf is currently inside the user's text selection
101
- const setSelectedFromRange = useCallback(() => {
102
- if (!shouldTrackSelectionAndFocus) {
103
- return
104
- }
105
-
106
- const winSelection = window.getSelection()
107
-
108
- if (!winSelection) {
109
- setSelected(false)
110
- return
111
- }
112
-
113
- if (winSelection && winSelection.rangeCount > 0) {
114
- const range = winSelection.getRangeAt(0)
115
-
116
- if (spanRef.current && range.intersectsNode(spanRef.current)) {
117
- setSelected(true)
118
- } else {
119
- setSelected(false)
120
- }
121
- } else {
122
- setSelected(false)
123
- }
124
- }, [shouldTrackSelectionAndFocus])
125
-
126
- useEffect(() => {
127
- if (!shouldTrackSelectionAndFocus) {
128
- return undefined
129
- }
130
-
131
- const onBlur = editorActor.on('blurred', () => {
132
- setFocused(false)
133
- setSelected(false)
134
- })
135
-
136
- const onFocus = editorActor.on('focused', () => {
137
- const sel = PortableTextEditor.getSelection(portableTextEditor)
138
-
139
- if (
140
- sel &&
141
- isEqual(sel.focus.path, path) &&
142
- PortableTextEditor.isCollapsedSelection(portableTextEditor)
143
- ) {
144
- setFocused(true)
145
- }
146
-
147
- setSelectedFromRange()
148
- })
149
-
150
- const onSelection = editorActor.on('selection', (event) => {
151
- if (
152
- event.selection &&
153
- isEqual(event.selection.focus.path, path) &&
154
- PortableTextEditor.isCollapsedSelection(portableTextEditor)
155
- ) {
156
- setFocused(true)
157
- } else {
158
- setFocused(false)
159
- }
160
- setSelectedFromRange()
161
- })
162
-
163
- return () => {
164
- onBlur.unsubscribe()
165
- onFocus.unsubscribe()
166
- onSelection.unsubscribe()
167
- }
168
- }, [
169
- editorActor,
170
- path,
171
- portableTextEditor,
172
- setSelectedFromRange,
173
- shouldTrackSelectionAndFocus,
174
- ])
175
-
176
- useEffect(() => setSelectedFromRange(), [setSelectedFromRange])
177
-
178
131
  let children = props.children
179
132
 
180
133
  /**
@@ -186,16 +139,19 @@ export function RenderSpan(props: RenderSpanProps) {
186
139
  )
187
140
 
188
141
  if (path && legacyDecoratorSchemaType && props.renderDecorator) {
189
- children = props.renderDecorator({
190
- children: children,
191
- editorElementRef: spanRef,
192
- focused,
193
- path,
194
- selected,
195
- schemaType: legacyDecoratorSchemaType,
196
- value: mark,
197
- type: legacyDecoratorSchemaType,
198
- })
142
+ children = (
143
+ <props.renderDecorator
144
+ editorElementRef={spanRef}
145
+ focused={focused}
146
+ path={path}
147
+ selected={selected}
148
+ schemaType={legacyDecoratorSchemaType}
149
+ value={mark}
150
+ type={legacyDecoratorSchemaType}
151
+ >
152
+ {children}
153
+ </props.renderDecorator>
154
+ )
199
155
  }
200
156
  }
201
157
 
@@ -210,17 +166,18 @@ export function RenderSpan(props: RenderSpanProps) {
210
166
  if (block && path && props.renderAnnotation) {
211
167
  children = (
212
168
  <span ref={spanRef}>
213
- {props.renderAnnotation({
214
- block,
215
- children: children,
216
- editorElementRef: spanRef,
217
- focused,
218
- path,
219
- selected,
220
- schemaType: legacyAnnotationSchemaType,
221
- value: annotationMarkDef,
222
- type: legacyAnnotationSchemaType,
223
- })}
169
+ <props.renderAnnotation
170
+ block={block}
171
+ editorElementRef={spanRef}
172
+ focused={focused}
173
+ path={path}
174
+ selected={selected}
175
+ schemaType={legacyAnnotationSchemaType}
176
+ value={annotationMarkDef}
177
+ type={legacyAnnotationSchemaType}
178
+ >
179
+ {children}
180
+ </props.renderAnnotation>
224
181
  </span>
225
182
  )
226
183
  } else {
@@ -238,17 +195,20 @@ export function RenderSpan(props: RenderSpanProps) {
238
195
  ) // Ensure object equality
239
196
 
240
197
  if (child) {
241
- children = props.renderChild({
242
- annotations: annotationMarkDefs,
243
- children: children,
244
- editorElementRef: spanRef,
245
- focused,
246
- path,
247
- schemaType: legacySchema.span,
248
- selected,
249
- value: child,
250
- type: legacySchema.span,
251
- })
198
+ children = (
199
+ <props.renderChild
200
+ annotations={annotationMarkDefs}
201
+ editorElementRef={spanRef}
202
+ focused={focused}
203
+ path={path}
204
+ schemaType={legacySchema.span}
205
+ selected={selected}
206
+ value={child}
207
+ type={legacySchema.span}
208
+ >
209
+ {children}
210
+ </props.renderChild>
211
+ )
252
212
  }
253
213
  }
254
214
 
@@ -60,16 +60,19 @@ export function RenderTextBlock(props: {
60
60
  : undefined
61
61
 
62
62
  if (legacyStyleSchemaType) {
63
- children = props.renderStyle({
64
- block: props.textBlock,
65
- children,
66
- editorElementRef: blockRef,
67
- focused,
68
- path: [{_key: props.textBlock._key}],
69
- schemaType: legacyStyleSchemaType,
70
- selected,
71
- value: props.textBlock.style,
72
- })
63
+ children = (
64
+ <props.renderStyle
65
+ block={props.textBlock}
66
+ editorElementRef={blockRef}
67
+ focused={focused}
68
+ path={[{_key: props.textBlock._key}]}
69
+ schemaType={legacyStyleSchemaType}
70
+ selected={selected}
71
+ value={props.textBlock.style}
72
+ >
73
+ {children}
74
+ </props.renderStyle>
75
+ )
73
76
  } else {
74
77
  console.error(
75
78
  `Unable to find Schema type for text block style ${props.textBlock.style}`,
@@ -83,17 +86,20 @@ export function RenderTextBlock(props: {
83
86
  )
84
87
 
85
88
  if (legacyListItemSchemaType) {
86
- children = props.renderListItem({
87
- block: props.textBlock,
88
- children,
89
- editorElementRef: blockRef,
90
- focused,
91
- level: props.textBlock.level ?? 1,
92
- path: [{_key: props.textBlock._key}],
93
- selected,
94
- value: props.textBlock.listItem,
95
- schemaType: legacyListItemSchemaType,
96
- })
89
+ children = (
90
+ <props.renderListItem
91
+ block={props.textBlock}
92
+ editorElementRef={blockRef}
93
+ focused={focused}
94
+ level={props.textBlock.level ?? 1}
95
+ path={[{_key: props.textBlock._key}]}
96
+ selected={selected}
97
+ value={props.textBlock.listItem}
98
+ schemaType={legacyListItemSchemaType}
99
+ >
100
+ {children}
101
+ </props.renderListItem>
102
+ )
97
103
  } else {
98
104
  console.error(
99
105
  `Unable to find Schema type for text block list item ${props.textBlock.listItem}`,
@@ -145,21 +151,24 @@ export function RenderTextBlock(props: {
145
151
  >
146
152
  {dragPositionBlock === 'start' ? <DropIndicator /> : null}
147
153
  <div ref={blockRef}>
148
- {props.renderBlock
149
- ? props.renderBlock({
150
- children,
151
- editorElementRef: blockRef,
152
- focused,
153
- level: props.textBlock.level,
154
- listItem: props.textBlock.listItem,
155
- path: [{_key: props.textBlock._key}],
156
- selected,
157
- schemaType: props.legacySchema.block,
158
- style: props.textBlock.style,
159
- type: props.legacySchema.block,
160
- value: props.textBlock,
161
- })
162
- : children}
154
+ {props.renderBlock ? (
155
+ <props.renderBlock
156
+ editorElementRef={blockRef}
157
+ focused={focused}
158
+ level={props.textBlock.level}
159
+ listItem={props.textBlock.listItem}
160
+ path={[{_key: props.textBlock._key}]}
161
+ selected={selected}
162
+ schemaType={props.legacySchema.block}
163
+ style={props.textBlock.style}
164
+ type={props.legacySchema.block}
165
+ value={props.textBlock}
166
+ >
167
+ {children}
168
+ </props.renderBlock>
169
+ ) : (
170
+ children
171
+ )}
163
172
  </div>
164
173
  {dragPositionBlock === 'end' ? <DropIndicator /> : null}
165
174
  </div>
@@ -1,9 +1,8 @@
1
1
  import type React from 'react'
2
- import {useEffect} from 'react'
2
+ import {useEffect, useState} from 'react'
3
3
  import {Slate} from 'slate-react'
4
4
  import type {EditorConfig} from '../editor'
5
5
  import {stopActor} from '../internal-utils/stop-actor'
6
- import useConstant from '../internal-utils/use-constant'
7
6
  import {createInternalEditor} from './create-editor'
8
7
  import {EditorActorContext} from './editor-actor-context'
9
8
  import {EditorContext} from './editor-context'
@@ -42,7 +41,7 @@ export type EditorProviderProps = {
42
41
  * @group Components
43
42
  */
44
43
  export function EditorProvider(props: EditorProviderProps) {
45
- const {internalEditor, portableTextEditor} = useConstant(() => {
44
+ const [{internalEditor, portableTextEditor}] = useState(() => {
46
45
  const internalEditor = createInternalEditor(props.initialConfig)
47
46
  const portableTextEditor = new PortableTextEditor({
48
47
  editor: internalEditor.editor,
@@ -170,6 +170,122 @@ describe(isOverlappingSelection.name, () => {
170
170
  ).toBe(false)
171
171
  })
172
172
 
173
+ test('partial span selection', () => {
174
+ expect(
175
+ isOverlappingSelection({
176
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 0},
177
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
178
+ })(
179
+ snapshot({
180
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 2},
181
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
182
+ }),
183
+ ),
184
+ ).toBe(true)
185
+ })
186
+
187
+ test('expanded selection at the end edge of span', () => {
188
+ expect(
189
+ isOverlappingSelection({
190
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 0},
191
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
192
+ })(
193
+ snapshot({
194
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
195
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 1},
196
+ }),
197
+ ),
198
+ ).toBe(false)
199
+ })
200
+
201
+ test('collapsed selection at the end edge of span', () => {
202
+ expect(
203
+ isOverlappingSelection({
204
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 0},
205
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
206
+ })(
207
+ snapshot({
208
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
209
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
210
+ }),
211
+ ),
212
+ ).toBe(true)
213
+ })
214
+
215
+ test('expanded selection at the start edge of span', () => {
216
+ expect(
217
+ isOverlappingSelection({
218
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
219
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 3},
220
+ })(
221
+ snapshot({
222
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k3'}], offset: 3},
223
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
224
+ }),
225
+ ),
226
+ ).toBe(false)
227
+ })
228
+
229
+ test('collapsed selection at the start edge of span', () => {
230
+ expect(
231
+ isOverlappingSelection({
232
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
233
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 3},
234
+ })(
235
+ snapshot({
236
+ anchor: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
237
+ focus: {path: [{_key: 'k1'}, 'children', {_key: 'k5'}], offset: 0},
238
+ }),
239
+ ),
240
+ ).toBe(true)
241
+ })
242
+
243
+ test('selecting entire span', () => {
244
+ const blockKey = defaultKeyGenerator()
245
+ const fooKey = defaultKeyGenerator()
246
+ const barKey = defaultKeyGenerator()
247
+ const bazKey = defaultKeyGenerator()
248
+
249
+ expect(
250
+ isOverlappingSelection({
251
+ anchor: {
252
+ path: [{_key: blockKey}, 'children', {_key: barKey}],
253
+ offset: 0,
254
+ },
255
+ focus: {
256
+ path: [{_key: blockKey}, 'children', {_key: barKey}],
257
+ offset: 3,
258
+ },
259
+ })(
260
+ createTestSnapshot({
261
+ context: {
262
+ value: [
263
+ {
264
+ _key: blockKey,
265
+ _type: 'block',
266
+ children: [
267
+ {_key: fooKey, _type: 'span', text: 'foo'},
268
+ {_key: barKey, _type: 'span', text: 'bar', marks: ['strong']},
269
+ {_key: bazKey, _type: 'span', text: 'baz'},
270
+ ],
271
+ },
272
+ ],
273
+ selection: {
274
+ anchor: {
275
+ path: [{_key: blockKey}, 'children', {_key: barKey}],
276
+ offset: 0,
277
+ },
278
+ focus: {
279
+ path: [{_key: blockKey}, 'children', {_key: barKey}],
280
+ offset: 3,
281
+ },
282
+ },
283
+ },
284
+ }),
285
+ ),
286
+ ).toBe(true)
287
+ })
288
+
173
289
  test('unknown block', () => {
174
290
  const unknownBlockKey = defaultKeyGenerator()
175
291