@tiptap/extension-details 2.24.2 → 3.0.0-beta.10

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/dist/index.js CHANGED
@@ -1,381 +1,361 @@
1
- import { findParentNode, findChildren, Node, mergeAttributes, defaultBlockAt, isActive } from '@tiptap/core';
2
- import { Selection, Plugin, PluginKey, TextSelection } from '@tiptap/pm/state';
3
- import { GapCursor } from '@tiptap/pm/gapcursor';
1
+ // src/details.ts
2
+ import { defaultBlockAt, findChildren as findChildren2, findParentNode as findParentNode2, isActive, mergeAttributes, Node } from "@tiptap/core";
3
+ import { Plugin, PluginKey, Selection, TextSelection } from "@tiptap/pm/state";
4
4
 
5
- const isNodeVisible = (position, editor) => {
6
- const node = editor.view.domAtPos(position).node;
7
- const isOpen = node.offsetParent !== null;
8
- return isOpen;
5
+ // src/helpers/isNodeVisible.ts
6
+ var isNodeVisible = (position, editor) => {
7
+ const node = editor.view.domAtPos(position).node;
8
+ const isOpen = node.offsetParent !== null;
9
+ return isOpen;
9
10
  };
10
11
 
11
- const findClosestVisibleNode = ($pos, predicate, editor) => {
12
- for (let i = $pos.depth; i > 0; i -= 1) {
13
- const node = $pos.node(i);
14
- const match = predicate(node);
15
- const isVisible = isNodeVisible($pos.start(i), editor);
16
- if (match && isVisible) {
17
- return {
18
- pos: i > 0 ? $pos.before(i) : 0,
19
- start: $pos.start(i),
20
- depth: i,
21
- node,
22
- };
23
- }
12
+ // src/helpers/findClosestVisibleNode.ts
13
+ var findClosestVisibleNode = ($pos, predicate, editor) => {
14
+ for (let i = $pos.depth; i > 0; i -= 1) {
15
+ const node = $pos.node(i);
16
+ const match = predicate(node);
17
+ const isVisible = isNodeVisible($pos.start(i), editor);
18
+ if (match && isVisible) {
19
+ return {
20
+ pos: i > 0 ? $pos.before(i) : 0,
21
+ start: $pos.start(i),
22
+ depth: i,
23
+ node
24
+ };
24
25
  }
26
+ }
25
27
  };
26
28
 
27
- const setGapCursor = (editor, direction) => {
28
- const { state, view, extensionManager } = editor;
29
- const { schema, selection } = state;
30
- const { empty, $anchor } = selection;
31
- const hasGapCursorExtension = !!extensionManager.extensions.find(extension => extension.name === 'gapCursor');
32
- if (!empty
33
- || $anchor.parent.type !== schema.nodes.detailsSummary
34
- || !hasGapCursorExtension) {
35
- return false;
36
- }
37
- if (direction === 'right'
38
- && $anchor.parentOffset !== ($anchor.parent.nodeSize - 2)) {
39
- return false;
40
- }
41
- const details = findParentNode(node => node.type === schema.nodes.details)(selection);
42
- if (!details) {
43
- return false;
44
- }
45
- const detailsContent = findChildren(details.node, node => node.type === schema.nodes.detailsContent);
46
- if (!detailsContent.length) {
47
- return false;
48
- }
49
- const isOpen = isNodeVisible(details.start + detailsContent[0].pos + 1, editor);
50
- if (isOpen) {
51
- return false;
52
- }
53
- const $position = state.doc.resolve(details.pos + details.node.nodeSize);
54
- const $validPosition = GapCursor.findFrom($position, 1, false);
55
- if (!$validPosition) {
56
- return false;
57
- }
58
- const { tr } = state;
59
- const gapCursorSelection = new GapCursor($validPosition);
60
- tr.setSelection(gapCursorSelection);
61
- tr.scrollIntoView();
62
- view.dispatch(tr);
63
- return true;
29
+ // src/helpers/setGapCursor.ts
30
+ import { findChildren, findParentNode } from "@tiptap/core";
31
+ import { GapCursor } from "@tiptap/pm/gapcursor";
32
+ var setGapCursor = (editor, direction) => {
33
+ const { state, view, extensionManager } = editor;
34
+ const { schema, selection } = state;
35
+ const { empty, $anchor } = selection;
36
+ const hasGapCursorExtension = !!extensionManager.extensions.find((extension) => extension.name === "gapCursor");
37
+ if (!empty || $anchor.parent.type !== schema.nodes.detailsSummary || !hasGapCursorExtension) {
38
+ return false;
39
+ }
40
+ if (direction === "right" && $anchor.parentOffset !== $anchor.parent.nodeSize - 2) {
41
+ return false;
42
+ }
43
+ const details = findParentNode((node) => node.type === schema.nodes.details)(selection);
44
+ if (!details) {
45
+ return false;
46
+ }
47
+ const detailsContent = findChildren(details.node, (node) => node.type === schema.nodes.detailsContent);
48
+ if (!detailsContent.length) {
49
+ return false;
50
+ }
51
+ const isOpen = isNodeVisible(details.start + detailsContent[0].pos + 1, editor);
52
+ if (isOpen) {
53
+ return false;
54
+ }
55
+ const $position = state.doc.resolve(details.pos + details.node.nodeSize);
56
+ const $validPosition = GapCursor.findFrom($position, 1, false);
57
+ if (!$validPosition) {
58
+ return false;
59
+ }
60
+ const { tr } = state;
61
+ const gapCursorSelection = new GapCursor($validPosition);
62
+ tr.setSelection(gapCursorSelection);
63
+ tr.scrollIntoView();
64
+ view.dispatch(tr);
65
+ return true;
64
66
  };
65
67
 
66
- const Details = Node.create({
67
- name: 'details',
68
- content: 'detailsSummary detailsContent',
69
- group: 'block',
70
- defining: true,
71
- isolating: true,
72
- allowGapCursor: false,
73
- addOptions() {
74
- return {
75
- persist: false,
76
- openClassName: 'is-open',
77
- HTMLAttributes: {},
78
- };
79
- },
80
- addAttributes() {
68
+ // src/details.ts
69
+ var Details = Node.create({
70
+ name: "details",
71
+ content: "detailsSummary detailsContent",
72
+ group: "block",
73
+ defining: true,
74
+ isolating: true,
75
+ // @ts-ignore: allowGapCursor is not a valid option by default, dts on build doesnt pick this up
76
+ allowGapCursor: false,
77
+ addOptions() {
78
+ return {
79
+ persist: false,
80
+ openClassName: "is-open",
81
+ HTMLAttributes: {}
82
+ };
83
+ },
84
+ addAttributes() {
85
+ if (!this.options.persist) {
86
+ return [];
87
+ }
88
+ return {
89
+ open: {
90
+ default: false,
91
+ parseHTML: (element) => element.hasAttribute("open"),
92
+ renderHTML: ({ open }) => {
93
+ if (!open) {
94
+ return {};
95
+ }
96
+ return { open: "" };
97
+ }
98
+ }
99
+ };
100
+ },
101
+ parseHTML() {
102
+ return [
103
+ {
104
+ tag: "details"
105
+ }
106
+ ];
107
+ },
108
+ renderHTML({ HTMLAttributes }) {
109
+ return ["details", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
110
+ },
111
+ addNodeView() {
112
+ return ({ editor, getPos, node, HTMLAttributes }) => {
113
+ const dom = document.createElement("div");
114
+ const attributes = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
115
+ "data-type": this.name
116
+ });
117
+ Object.entries(attributes).forEach(([key, value]) => dom.setAttribute(key, value));
118
+ const toggle = document.createElement("button");
119
+ toggle.type = "button";
120
+ dom.append(toggle);
121
+ const content = document.createElement("div");
122
+ dom.append(content);
123
+ const toggleDetailsContent = (setToValue) => {
124
+ if (setToValue !== void 0) {
125
+ if (setToValue) {
126
+ if (dom.classList.contains(this.options.openClassName)) {
127
+ return;
128
+ }
129
+ dom.classList.add(this.options.openClassName);
130
+ } else {
131
+ if (!dom.classList.contains(this.options.openClassName)) {
132
+ return;
133
+ }
134
+ dom.classList.remove(this.options.openClassName);
135
+ }
136
+ } else {
137
+ dom.classList.toggle(this.options.openClassName);
138
+ }
139
+ const event = new Event("toggleDetailsContent");
140
+ const detailsContent = content.querySelector(':scope > div[data-type="detailsContent"]');
141
+ detailsContent == null ? void 0 : detailsContent.dispatchEvent(event);
142
+ };
143
+ if (node.attrs.open) {
144
+ setTimeout(() => toggleDetailsContent());
145
+ }
146
+ toggle.addEventListener("click", () => {
147
+ toggleDetailsContent();
81
148
  if (!this.options.persist) {
82
- return [];
149
+ editor.commands.focus(void 0, { scrollIntoView: false });
150
+ return;
83
151
  }
84
- return {
85
- open: {
86
- default: false,
87
- parseHTML: element => element.hasAttribute('open'),
88
- renderHTML: ({ open }) => {
89
- if (!open) {
90
- return {};
91
- }
92
- return { open: '' };
93
- },
94
- },
95
- };
96
- },
97
- parseHTML() {
98
- return [
99
- {
100
- tag: 'details',
101
- },
102
- ];
103
- },
104
- renderHTML({ HTMLAttributes }) {
105
- return [
106
- 'details',
107
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
108
- 0,
109
- ];
110
- },
111
- addNodeView() {
112
- return ({ editor, getPos, node, HTMLAttributes, }) => {
113
- const dom = document.createElement('div');
114
- const attributes = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
115
- 'data-type': this.name,
116
- });
117
- Object.entries(attributes).forEach(([key, value]) => dom.setAttribute(key, value));
118
- const toggle = document.createElement('button');
119
- toggle.type = 'button';
120
- dom.append(toggle);
121
- const content = document.createElement('div');
122
- dom.append(content);
123
- const toggleDetailsContent = (setToValue) => {
124
- if (setToValue !== undefined) {
125
- if (setToValue) {
126
- if (dom.classList.contains(this.options.openClassName)) {
127
- return;
128
- }
129
- dom.classList.add(this.options.openClassName);
130
- }
131
- else {
132
- if (!dom.classList.contains(this.options.openClassName)) {
133
- return;
134
- }
135
- dom.classList.remove(this.options.openClassName);
136
- }
137
- }
138
- else {
139
- dom.classList.toggle(this.options.openClassName);
140
- }
141
- const event = new Event('toggleDetailsContent');
142
- const detailsContent = content.querySelector(':scope > div[data-type="detailsContent"]');
143
- detailsContent === null || detailsContent === void 0 ? void 0 : detailsContent.dispatchEvent(event);
144
- };
145
- if (node.attrs.open) {
146
- setTimeout(() => toggleDetailsContent());
152
+ if (editor.isEditable && typeof getPos === "function") {
153
+ const { from, to } = editor.state.selection;
154
+ editor.chain().command(({ tr }) => {
155
+ const pos = getPos();
156
+ if (pos === void 0) {
157
+ return false;
158
+ }
159
+ const currentNode = tr.doc.nodeAt(pos);
160
+ if ((currentNode == null ? void 0 : currentNode.type) !== this.type) {
161
+ return false;
147
162
  }
148
- toggle.addEventListener('click', () => {
149
- toggleDetailsContent();
150
- if (!this.options.persist) {
151
- editor.commands
152
- .focus(undefined, { scrollIntoView: false });
153
- return;
154
- }
155
- if (editor.isEditable && typeof getPos === 'function') {
156
- const { from, to } = editor.state.selection;
157
- editor
158
- .chain()
159
- .command(({ tr }) => {
160
- const pos = getPos();
161
- const currentNode = tr.doc.nodeAt(pos);
162
- if ((currentNode === null || currentNode === void 0 ? void 0 : currentNode.type) !== this.type) {
163
- return false;
164
- }
165
- tr.setNodeMarkup(pos, undefined, {
166
- open: !currentNode.attrs.open,
167
- });
168
- return true;
169
- })
170
- .setTextSelection({
171
- from,
172
- to,
173
- })
174
- .focus(undefined, { scrollIntoView: false })
175
- .run();
176
- }
163
+ tr.setNodeMarkup(pos, void 0, {
164
+ open: !currentNode.attrs.open
177
165
  });
178
- return {
179
- dom,
180
- contentDOM: content,
181
- ignoreMutation(mutation) {
182
- if (mutation.type === 'selection') {
183
- return false;
184
- }
185
- return !dom.contains(mutation.target) || dom === mutation.target;
186
- },
187
- update: updatedNode => {
188
- if (updatedNode.type !== this.type) {
189
- return false;
190
- }
191
- // Only update the open state if set
192
- if (updatedNode.attrs.open !== undefined) {
193
- toggleDetailsContent(updatedNode.attrs.open);
194
- }
195
- return true;
196
- },
197
- };
198
- };
199
- },
200
- addCommands() {
201
- return {
202
- setDetails: () => ({ state, chain }) => {
203
- var _a;
204
- const { schema, selection } = state;
205
- const { $from, $to } = selection;
206
- const range = $from.blockRange($to);
207
- if (!range) {
208
- return false;
209
- }
210
- const slice = state.doc.slice(range.start, range.end);
211
- const match = schema.nodes.detailsContent.contentMatch.matchFragment(slice.content);
212
- if (!match) {
213
- return false;
214
- }
215
- const content = ((_a = slice.toJSON()) === null || _a === void 0 ? void 0 : _a.content) || [];
216
- return chain()
217
- .insertContentAt({ from: range.start, to: range.end }, {
218
- type: this.name,
219
- content: [
220
- {
221
- type: 'detailsSummary',
222
- },
223
- {
224
- type: 'detailsContent',
225
- content,
226
- },
227
- ],
228
- })
229
- .setTextSelection(range.start + 2)
230
- .run();
231
- },
232
- unsetDetails: () => ({ state, chain }) => {
233
- const { selection, schema } = state;
234
- const details = findParentNode(node => node.type === this.type)(selection);
235
- if (!details) {
236
- return false;
237
- }
238
- const detailsSummaries = findChildren(details.node, node => node.type === schema.nodes.detailsSummary);
239
- const detailsContents = findChildren(details.node, node => node.type === schema.nodes.detailsContent);
240
- if (!detailsSummaries.length || !detailsContents.length) {
241
- return false;
242
- }
243
- const detailsSummary = detailsSummaries[0];
244
- const detailsContent = detailsContents[0];
245
- const from = details.pos;
246
- const $from = state.doc.resolve(from);
247
- const to = from + details.node.nodeSize;
248
- const range = { from, to };
249
- const content = detailsContent.node.content.toJSON() || [];
250
- const defaultTypeForSummary = $from.parent.type.contentMatch.defaultType;
251
- // TODO: this may break for some custom schemas
252
- const summaryContent = defaultTypeForSummary === null || defaultTypeForSummary === void 0 ? void 0 : defaultTypeForSummary.create(null, detailsSummary.node.content).toJSON();
253
- const mergedContent = [
254
- summaryContent,
255
- ...content,
256
- ];
257
- return chain()
258
- .insertContentAt(range, mergedContent)
259
- .setTextSelection(from + 1)
260
- .run();
261
- },
262
- };
263
- },
264
- addKeyboardShortcuts() {
265
- return {
266
- Backspace: () => {
267
- const { schema, selection } = this.editor.state;
268
- const { empty, $anchor } = selection;
269
- if (!empty || $anchor.parent.type !== schema.nodes.detailsSummary) {
270
- return false;
271
- }
272
- // for some reason safari removes the whole text content within a `<summary>`tag on backspace
273
- // so we have to remove the text manually
274
- // see: https://discuss.prosemirror.net/t/safari-backspace-bug-with-details-tag/4223
275
- if ($anchor.parentOffset !== 0) {
276
- return this.editor.commands.command(({ tr }) => {
277
- const from = $anchor.pos - 1;
278
- const to = $anchor.pos;
279
- tr.delete(from, to);
280
- return true;
281
- });
282
- }
283
- return this.editor.commands.unsetDetails();
284
- },
285
- // Creates a new node below it if it is closed.
286
- // Otherwise inside `DetailsContent`.
287
- Enter: ({ editor }) => {
288
- const { state, view } = editor;
289
- const { schema, selection } = state;
290
- const { $head } = selection;
291
- if ($head.parent.type !== schema.nodes.detailsSummary) {
292
- return false;
293
- }
294
- const isVisible = isNodeVisible($head.after() + 1, editor);
295
- const above = isVisible
296
- ? state.doc.nodeAt($head.after())
297
- : $head.node(-2);
298
- if (!above) {
299
- return false;
300
- }
301
- const after = isVisible
302
- ? 0
303
- : $head.indexAfter(-1);
304
- const type = defaultBlockAt(above.contentMatchAt(after));
305
- if (!type || !above.canReplaceWith(after, after, type)) {
306
- return false;
307
- }
308
- const node = type.createAndFill();
309
- if (!node) {
310
- return false;
311
- }
312
- const pos = isVisible
313
- ? $head.after() + 1
314
- : $head.after(-1);
315
- const tr = state.tr.replaceWith(pos, pos, node);
316
- const $pos = tr.doc.resolve(pos);
317
- const newSelection = Selection.near($pos, 1);
318
- tr.setSelection(newSelection);
319
- tr.scrollIntoView();
320
- view.dispatch(tr);
321
- return true;
322
- },
323
- // The default gapcursor implementation can’t handle hidden content, so we need to fix this.
324
- ArrowRight: ({ editor }) => {
325
- return setGapCursor(editor, 'right');
326
- },
327
- // The default gapcursor implementation can’t handle hidden content, so we need to fix this.
328
- ArrowDown: ({ editor }) => {
329
- return setGapCursor(editor, 'down');
330
- },
331
- };
332
- },
333
- addProseMirrorPlugins() {
334
- return [
335
- // This plugin prevents text selections within the hidden content in `DetailsContent`.
336
- // The cursor is moved to the next visible position.
337
- new Plugin({
338
- key: new PluginKey('detailsSelection'),
339
- appendTransaction: (transactions, oldState, newState) => {
340
- const { editor, type } = this;
341
- const selectionSet = transactions.some(transaction => transaction.selectionSet);
342
- if (!selectionSet
343
- || !oldState.selection.empty
344
- || !newState.selection.empty) {
345
- return;
346
- }
347
- const detailsIsActive = isActive(newState, type.name);
348
- if (!detailsIsActive) {
349
- return;
350
- }
351
- const { $from } = newState.selection;
352
- const isVisible = isNodeVisible($from.pos, editor);
353
- if (isVisible) {
354
- return;
355
- }
356
- const details = findClosestVisibleNode($from, node => node.type === type, editor);
357
- if (!details) {
358
- return;
359
- }
360
- const detailsSummaries = findChildren(details.node, node => node.type === newState.schema.nodes.detailsSummary);
361
- if (!detailsSummaries.length) {
362
- return;
363
- }
364
- const detailsSummary = detailsSummaries[0];
365
- const selectionDirection = oldState.selection.from < newState.selection.from
366
- ? 'forward'
367
- : 'backward';
368
- const correctedPosition = selectionDirection === 'forward'
369
- ? details.start + detailsSummary.pos
370
- : details.pos + detailsSummary.pos + detailsSummary.node.nodeSize;
371
- const selection = TextSelection.create(newState.doc, correctedPosition);
372
- const transaction = newState.tr.setSelection(selection);
373
- return transaction;
374
- },
375
- }),
376
- ];
377
- },
166
+ return true;
167
+ }).setTextSelection({
168
+ from,
169
+ to
170
+ }).focus(void 0, { scrollIntoView: false }).run();
171
+ }
172
+ });
173
+ return {
174
+ dom,
175
+ contentDOM: content,
176
+ ignoreMutation(mutation) {
177
+ if (mutation.type === "selection") {
178
+ return false;
179
+ }
180
+ return !dom.contains(mutation.target) || dom === mutation.target;
181
+ },
182
+ update: (updatedNode) => {
183
+ if (updatedNode.type !== this.type) {
184
+ return false;
185
+ }
186
+ if (updatedNode.attrs.open !== void 0) {
187
+ toggleDetailsContent(updatedNode.attrs.open);
188
+ }
189
+ return true;
190
+ }
191
+ };
192
+ };
193
+ },
194
+ addCommands() {
195
+ return {
196
+ setDetails: () => ({ state, chain }) => {
197
+ var _a;
198
+ const { schema, selection } = state;
199
+ const { $from, $to } = selection;
200
+ const range = $from.blockRange($to);
201
+ if (!range) {
202
+ return false;
203
+ }
204
+ const slice = state.doc.slice(range.start, range.end);
205
+ const match = schema.nodes.detailsContent.contentMatch.matchFragment(slice.content);
206
+ if (!match) {
207
+ return false;
208
+ }
209
+ const content = ((_a = slice.toJSON()) == null ? void 0 : _a.content) || [];
210
+ return chain().insertContentAt(
211
+ { from: range.start, to: range.end },
212
+ {
213
+ type: this.name,
214
+ content: [
215
+ {
216
+ type: "detailsSummary"
217
+ },
218
+ {
219
+ type: "detailsContent",
220
+ content
221
+ }
222
+ ]
223
+ }
224
+ ).setTextSelection(range.start + 2).run();
225
+ },
226
+ unsetDetails: () => ({ state, chain }) => {
227
+ const { selection, schema } = state;
228
+ const details = findParentNode2((node) => node.type === this.type)(selection);
229
+ if (!details) {
230
+ return false;
231
+ }
232
+ const detailsSummaries = findChildren2(details.node, (node) => node.type === schema.nodes.detailsSummary);
233
+ const detailsContents = findChildren2(details.node, (node) => node.type === schema.nodes.detailsContent);
234
+ if (!detailsSummaries.length || !detailsContents.length) {
235
+ return false;
236
+ }
237
+ const detailsSummary = detailsSummaries[0];
238
+ const detailsContent = detailsContents[0];
239
+ const from = details.pos;
240
+ const $from = state.doc.resolve(from);
241
+ const to = from + details.node.nodeSize;
242
+ const range = { from, to };
243
+ const content = detailsContent.node.content.toJSON() || [];
244
+ const defaultTypeForSummary = $from.parent.type.contentMatch.defaultType;
245
+ const summaryContent = defaultTypeForSummary == null ? void 0 : defaultTypeForSummary.create(null, detailsSummary.node.content).toJSON();
246
+ const mergedContent = [summaryContent, ...content];
247
+ return chain().insertContentAt(range, mergedContent).setTextSelection(from + 1).run();
248
+ }
249
+ };
250
+ },
251
+ addKeyboardShortcuts() {
252
+ return {
253
+ Backspace: () => {
254
+ const { schema, selection } = this.editor.state;
255
+ const { empty, $anchor } = selection;
256
+ if (!empty || $anchor.parent.type !== schema.nodes.detailsSummary) {
257
+ return false;
258
+ }
259
+ if ($anchor.parentOffset !== 0) {
260
+ return this.editor.commands.command(({ tr }) => {
261
+ const from = $anchor.pos - 1;
262
+ const to = $anchor.pos;
263
+ tr.delete(from, to);
264
+ return true;
265
+ });
266
+ }
267
+ return this.editor.commands.unsetDetails();
268
+ },
269
+ // Creates a new node below it if it is closed.
270
+ // Otherwise inside `DetailsContent`.
271
+ Enter: ({ editor }) => {
272
+ const { state, view } = editor;
273
+ const { schema, selection } = state;
274
+ const { $head } = selection;
275
+ if ($head.parent.type !== schema.nodes.detailsSummary) {
276
+ return false;
277
+ }
278
+ const isVisible = isNodeVisible($head.after() + 1, editor);
279
+ const above = isVisible ? state.doc.nodeAt($head.after()) : $head.node(-2);
280
+ if (!above) {
281
+ return false;
282
+ }
283
+ const after = isVisible ? 0 : $head.indexAfter(-1);
284
+ const type = defaultBlockAt(above.contentMatchAt(after));
285
+ if (!type || !above.canReplaceWith(after, after, type)) {
286
+ return false;
287
+ }
288
+ const node = type.createAndFill();
289
+ if (!node) {
290
+ return false;
291
+ }
292
+ const pos = isVisible ? $head.after() + 1 : $head.after(-1);
293
+ const tr = state.tr.replaceWith(pos, pos, node);
294
+ const $pos = tr.doc.resolve(pos);
295
+ const newSelection = Selection.near($pos, 1);
296
+ tr.setSelection(newSelection);
297
+ tr.scrollIntoView();
298
+ view.dispatch(tr);
299
+ return true;
300
+ },
301
+ // The default gapcursor implementation can’t handle hidden content, so we need to fix this.
302
+ ArrowRight: ({ editor }) => {
303
+ return setGapCursor(editor, "right");
304
+ },
305
+ // The default gapcursor implementation can’t handle hidden content, so we need to fix this.
306
+ ArrowDown: ({ editor }) => {
307
+ return setGapCursor(editor, "down");
308
+ }
309
+ };
310
+ },
311
+ addProseMirrorPlugins() {
312
+ return [
313
+ // This plugin prevents text selections within the hidden content in `DetailsContent`.
314
+ // The cursor is moved to the next visible position.
315
+ new Plugin({
316
+ key: new PluginKey("detailsSelection"),
317
+ appendTransaction: (transactions, oldState, newState) => {
318
+ const { editor, type } = this;
319
+ const selectionSet = transactions.some((transaction2) => transaction2.selectionSet);
320
+ if (!selectionSet || !oldState.selection.empty || !newState.selection.empty) {
321
+ return;
322
+ }
323
+ const detailsIsActive = isActive(newState, type.name);
324
+ if (!detailsIsActive) {
325
+ return;
326
+ }
327
+ const { $from } = newState.selection;
328
+ const isVisible = isNodeVisible($from.pos, editor);
329
+ if (isVisible) {
330
+ return;
331
+ }
332
+ const details = findClosestVisibleNode($from, (node) => node.type === type, editor);
333
+ if (!details) {
334
+ return;
335
+ }
336
+ const detailsSummaries = findChildren2(
337
+ details.node,
338
+ (node) => node.type === newState.schema.nodes.detailsSummary
339
+ );
340
+ if (!detailsSummaries.length) {
341
+ return;
342
+ }
343
+ const detailsSummary = detailsSummaries[0];
344
+ const selectionDirection = oldState.selection.from < newState.selection.from ? "forward" : "backward";
345
+ const correctedPosition = selectionDirection === "forward" ? details.start + detailsSummary.pos : details.pos + detailsSummary.pos + detailsSummary.node.nodeSize;
346
+ const selection = TextSelection.create(newState.doc, correctedPosition);
347
+ const transaction = newState.tr.setSelection(selection);
348
+ return transaction;
349
+ }
350
+ })
351
+ ];
352
+ }
378
353
  });
379
354
 
380
- export { Details, Details as default };
381
- //# sourceMappingURL=index.js.map
355
+ // src/index.ts
356
+ var index_default = Details;
357
+ export {
358
+ Details,
359
+ index_default as default
360
+ };
361
+ //# sourceMappingURL=index.js.map