@tiptap/extensions 3.27.2 → 3.27.3

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.cjs CHANGED
@@ -243,13 +243,13 @@ var Gapcursor = import_core4.Extension.create({
243
243
  var import_state3 = require("@tiptap/pm/state");
244
244
  var DEFAULT_DATA_ATTRIBUTE = "placeholder";
245
245
  var PLUGIN_KEY = new import_state3.PluginKey("tiptap__placeholder");
246
- var VIEWPORT_OVERSCAN_PX = 200;
247
246
 
248
247
  // src/placeholder/placeholder.ts
249
- var import_core6 = require("@tiptap/core");
248
+ var import_core7 = require("@tiptap/core");
250
249
 
251
250
  // src/placeholder/plugins/PlaceholderPlugin.ts
252
251
  var import_state4 = require("@tiptap/pm/state");
252
+ var import_view5 = require("@tiptap/pm/view");
253
253
 
254
254
  // src/placeholder/utils/buildPlaceholderDecorations.ts
255
255
  var import_core5 = require("@tiptap/core");
@@ -287,6 +287,50 @@ function createPlaceholderDecoration(options) {
287
287
  function resolveEmptyNodeClass(emptyNodeClass, props) {
288
288
  return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass;
289
289
  }
290
+ function scanRangeForDecorations({
291
+ editor,
292
+ options,
293
+ dataAttribute,
294
+ doc,
295
+ selection,
296
+ from,
297
+ to
298
+ }) {
299
+ const { anchor } = selection;
300
+ const decorations = [];
301
+ const isEmptyDoc = editor.isEmpty;
302
+ doc.nodesBetween(from, to, (node, pos) => {
303
+ const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
304
+ const isEmpty = !node.isLeaf && (0, import_core5.isNodeEmpty)(node);
305
+ if (!node.type.isTextblock) {
306
+ return options.includeChildren;
307
+ }
308
+ if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {
309
+ decorations.push(
310
+ createPlaceholderDecoration({
311
+ editor,
312
+ isEmptyDoc,
313
+ dataAttribute,
314
+ hasAnchor,
315
+ placeholder: options.placeholder,
316
+ classes: {
317
+ emptyEditor: options.emptyEditorClass,
318
+ emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {
319
+ editor,
320
+ node,
321
+ pos,
322
+ hasAnchor
323
+ })
324
+ },
325
+ node,
326
+ pos
327
+ })
328
+ );
329
+ }
330
+ return options.includeChildren;
331
+ });
332
+ return decorations;
333
+ }
290
334
  function buildPlaceholderDecorations({
291
335
  editor,
292
336
  options,
@@ -294,7 +338,6 @@ function buildPlaceholderDecorations({
294
338
  doc,
295
339
  selection
296
340
  }) {
297
- var _a, _b;
298
341
  const active = editor.isEditable || !options.showOnlyWhenEditable;
299
342
  if (!active) {
300
343
  return null;
@@ -331,218 +374,222 @@ function buildPlaceholderDecorations({
331
374
  );
332
375
  }
333
376
  } else {
334
- const pluginState = PLUGIN_KEY.getState(editor.state);
335
- const from = (_a = pluginState == null ? void 0 : pluginState.topPos) != null ? _a : 0;
336
- const to = (_b = pluginState == null ? void 0 : pluginState.bottomPos) != null ? _b : doc.content.size;
337
- doc.nodesBetween(from, to, (node, pos) => {
338
- const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
339
- const isEmpty = !node.isLeaf && (0, import_core5.isNodeEmpty)(node);
340
- if (!node.type.isTextblock) {
341
- return options.includeChildren;
342
- }
343
- if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {
344
- decorations.push(
345
- createPlaceholderDecoration({
346
- editor,
347
- isEmptyDoc,
348
- dataAttribute,
349
- hasAnchor,
350
- placeholder: options.placeholder,
351
- classes: {
352
- emptyEditor: options.emptyEditorClass,
353
- emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {
354
- editor,
355
- node,
356
- pos,
357
- hasAnchor
358
- })
359
- },
360
- node,
361
- pos
362
- })
363
- );
364
- }
365
- return options.includeChildren;
366
- });
377
+ decorations.push(
378
+ ...scanRangeForDecorations({
379
+ editor,
380
+ options,
381
+ dataAttribute,
382
+ doc,
383
+ selection,
384
+ from: 0,
385
+ to: doc.content.size
386
+ })
387
+ );
367
388
  }
368
389
  return import_view3.DecorationSet.create(doc, decorations);
369
390
  }
370
391
 
371
- // src/placeholder/utils/preparePlaceholderAttribute.ts
372
- function preparePlaceholderAttribute(attr) {
373
- return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
374
- }
392
+ // src/placeholder/utils/placeholderStateField.ts
393
+ var import_core6 = require("@tiptap/core");
394
+ var import_view4 = require("@tiptap/pm/view");
375
395
 
376
- // src/placeholder/utils/findScrollParent.ts
377
- function isScrollable(el) {
378
- const style = getComputedStyle(el);
379
- const overflow = `${style.overflow} ${style.overflowY} ${style.overflowX}`;
380
- return /auto|scroll|overlay/.test(overflow);
396
+ // src/placeholder/utils/resolveTopLevelRange.ts
397
+ function resolveTopLevelRange(doc, pos) {
398
+ var _a;
399
+ const resolved = doc.resolve(pos);
400
+ if (resolved.depth === 0) {
401
+ const node2 = (_a = resolved.nodeAfter) != null ? _a : resolved.nodeBefore;
402
+ if (!node2) {
403
+ return { from: pos, to: pos };
404
+ }
405
+ const nodePos = resolved.nodeAfter ? pos : pos - node2.nodeSize;
406
+ return { from: nodePos, to: nodePos + node2.nodeSize };
407
+ }
408
+ const topLevelPos = resolved.before(1);
409
+ const node = resolved.node(1);
410
+ return { from: topLevelPos, to: topLevelPos + node.nodeSize };
381
411
  }
382
- function findScrollParent(element) {
383
- let el = element;
384
- while (el) {
385
- if (isScrollable(el)) {
386
- return el;
412
+ function toContentRelativeRange(doc, range) {
413
+ return {
414
+ from: Math.max(0, range.from - 1),
415
+ to: Math.min(doc.content.size, range.to - 1)
416
+ };
417
+ }
418
+ function getTopLevelBlocksInRange(doc, from, to) {
419
+ const ranges = [];
420
+ doc.forEach((node, offset) => {
421
+ const nodeStart = offset;
422
+ const nodeEnd = nodeStart + node.nodeSize;
423
+ const absNodeStart = nodeStart + 1;
424
+ const absNodeEnd = nodeEnd + 1;
425
+ if (absNodeStart < to && absNodeEnd > from) {
426
+ ranges.push({ from: nodeStart, to: nodeEnd });
387
427
  }
388
- const parent = el.parentElement;
389
- if (!parent) {
390
- const root = el.getRootNode();
391
- if (root instanceof ShadowRoot) {
392
- el = root.host;
393
- continue;
394
- }
395
- return window;
428
+ });
429
+ return ranges;
430
+ }
431
+ function mergeRanges(ranges) {
432
+ if (ranges.length === 0) {
433
+ return [];
434
+ }
435
+ const sorted = [...ranges].sort((a, b) => a.from - b.from);
436
+ const merged = [{ ...sorted[0] }];
437
+ for (let i = 1; i < sorted.length; i += 1) {
438
+ const last = merged[merged.length - 1];
439
+ const current = sorted[i];
440
+ if (current.from <= last.to) {
441
+ last.to = Math.max(last.to, current.to);
442
+ } else {
443
+ merged.push({ ...current });
396
444
  }
397
- el = parent;
398
445
  }
399
- return window;
446
+ return merged;
400
447
  }
401
448
 
402
- // src/placeholder/utils/getViewportBoundaryPositions.ts
403
- function getContainerRect(container) {
404
- if (container === window) {
405
- return { top: 0, bottom: window.innerHeight };
449
+ // src/placeholder/utils/placeholderStateField.ts
450
+ function collectBlocksForChange(doc, change) {
451
+ const ranges = getTopLevelBlocksInRange(doc, change.from, change.to);
452
+ ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)));
453
+ if (change.to > change.from) {
454
+ ranges.push(
455
+ toContentRelativeRange(
456
+ doc,
457
+ resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1)
458
+ )
459
+ );
460
+ } else if (change.from < doc.content.size + 1) {
461
+ ranges.push(
462
+ toContentRelativeRange(
463
+ doc,
464
+ resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size))
465
+ )
466
+ );
406
467
  }
407
- return container.getBoundingClientRect();
468
+ return ranges;
408
469
  }
409
- function getViewportBoundaryPositions({
410
- view,
411
- scrollContainer
412
- }) {
413
- const editorRect = view.dom.getBoundingClientRect();
414
- if (editorRect.width <= 0 || editorRect.height <= 0) {
415
- return null;
416
- }
417
- const containerRect = scrollContainer ? getContainerRect(scrollContainer) : { top: 0, bottom: window.innerHeight };
418
- const visibleTop = Math.max(editorRect.top, containerRect.top) - VIEWPORT_OVERSCAN_PX;
419
- const visibleBottom = Math.min(editorRect.bottom, containerRect.bottom) + VIEWPORT_OVERSCAN_PX;
420
- if (visibleTop >= visibleBottom) {
421
- return null;
422
- }
423
- const minX = editorRect.left + 1;
424
- const maxX = editorRect.right - 1;
425
- if (minX > maxX) {
426
- return null;
427
- }
428
- const isRTL = getComputedStyle(view.dom).direction === "rtl";
429
- const targetX = isRTL ? editorRect.right - 2 : editorRect.left + 2;
430
- const x = Math.min(Math.max(targetX, minX), maxX);
431
- const probeTop = Math.max(visibleTop + 2, editorRect.top + 1);
432
- const probeBottom = Math.min(visibleBottom - 2, editorRect.bottom - 1);
433
- if (probeTop > probeBottom) {
434
- return null;
470
+ function collectRescanRanges(tr, oldState, newState) {
471
+ const ranges = [];
472
+ if (tr.docChanged) {
473
+ const changes = (0, import_core6.getChangedRanges)(tr);
474
+ for (const change of changes) {
475
+ ranges.push(...collectBlocksForChange(newState.doc, change.newRange));
476
+ }
435
477
  }
436
- const topPos = view.posAtCoords({ left: x, top: probeTop });
437
- const bottomPos = view.posAtCoords({ left: x, top: probeBottom });
438
- if (!topPos || !bottomPos) {
439
- return null;
478
+ if (tr.selectionSet) {
479
+ ranges.push(
480
+ toContentRelativeRange(
481
+ newState.doc,
482
+ resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor))
483
+ )
484
+ );
485
+ ranges.push(
486
+ toContentRelativeRange(
487
+ newState.doc,
488
+ resolveTopLevelRange(newState.doc, newState.selection.anchor)
489
+ )
490
+ );
440
491
  }
441
- return { top: topPos.pos, bottom: bottomPos.pos };
492
+ return mergeRanges(ranges);
442
493
  }
443
-
444
- // src/placeholder/utils/viewportTracking.ts
445
- var viewportPluginState = {
446
- /**
447
- * Initialises the viewport state with no known positions.
448
- * @returns The initial viewport state.
449
- */
450
- init() {
451
- return { topPos: null, bottomPos: null };
452
- },
453
- /**
454
- * Updates the viewport state from incoming transactions.
455
- * @param tr - The transaction being applied.
456
- * @param prev - The previous viewport state.
457
- * @returns The next viewport state.
458
- */
459
- apply(tr, prev) {
460
- const meta = tr.getMeta(PLUGIN_KEY);
461
- if (meta == null ? void 0 : meta.positions) {
462
- return { topPos: meta.positions.top, bottomPos: meta.positions.bottom };
463
- }
464
- if (!tr.docChanged) {
465
- return prev;
494
+ function clampRange(from, to, doc) {
495
+ const clampedFrom = Math.max(0, Math.min(from, doc.content.size));
496
+ const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size));
497
+ return { from: clampedFrom, to: clampedTo };
498
+ }
499
+ function updateDecorationsInRanges({
500
+ decorations,
501
+ ranges,
502
+ editor,
503
+ options,
504
+ dataAttribute,
505
+ doc,
506
+ selection
507
+ }) {
508
+ let next = decorations;
509
+ for (const range of ranges) {
510
+ const { from, to } = clampRange(range.from, range.to, doc);
511
+ const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to);
512
+ if (existing.length) {
513
+ next = next.remove(existing);
466
514
  }
467
- return {
468
- topPos: prev.topPos !== null ? tr.mapping.map(prev.topPos) : null,
469
- bottomPos: prev.bottomPos !== null ? tr.mapping.map(prev.bottomPos) : null
470
- };
471
- }
472
- };
473
- function createViewportPluginView(view) {
474
- const scrollContainer = findScrollParent(view.dom);
475
- const computeAndDispatch = () => {
476
- const positions = getViewportBoundaryPositions({
477
- view,
478
- scrollContainer
515
+ const newDecos = scanRangeForDecorations({
516
+ editor,
517
+ options,
518
+ dataAttribute,
519
+ doc,
520
+ selection,
521
+ from,
522
+ to
479
523
  });
480
- if (positions === null) {
481
- return;
524
+ if (newDecos.length) {
525
+ next = next.add(doc, newDecos);
482
526
  }
483
- const prev = PLUGIN_KEY.getState(view.state);
484
- if ((prev == null ? void 0 : prev.topPos) === positions.top && (prev == null ? void 0 : prev.bottomPos) === positions.bottom) {
485
- return;
486
- }
487
- const tr = view.state.tr.setMeta(PLUGIN_KEY, { positions });
488
- view.dispatch(tr);
489
- };
490
- let frame = null;
491
- let lastCompute = 0;
492
- const MIN_SCROLL_INTERVAL = 150;
493
- const scheduleFrame = () => {
494
- if (frame !== null) return;
495
- frame = requestAnimationFrame(() => {
496
- frame = null;
497
- const now = performance.now();
498
- if (now - lastCompute >= MIN_SCROLL_INTERVAL) {
499
- lastCompute = now;
500
- computeAndDispatch();
501
- } else {
502
- scheduleFrame();
503
- }
504
- });
505
- };
506
- scrollContainer.addEventListener("scroll", scheduleFrame, { passive: true });
507
- const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(scheduleFrame) : null;
508
- resizeObserver == null ? void 0 : resizeObserver.observe(view.dom);
509
- const intersectionObserver = typeof IntersectionObserver !== "undefined" ? new IntersectionObserver(scheduleFrame) : null;
510
- intersectionObserver == null ? void 0 : intersectionObserver.observe(view.dom);
511
- view.dom.addEventListener("focus", scheduleFrame);
512
- computeAndDispatch();
527
+ }
528
+ return next;
529
+ }
530
+ function createPlaceholderStateField({
531
+ editor,
532
+ options,
533
+ dataAttribute
534
+ }) {
513
535
  return {
514
- update(_view, prevState) {
515
- if (view.state.doc.content.size !== prevState.doc.content.size) {
516
- scheduleFrame();
517
- }
536
+ init(_config, state) {
537
+ const decorations = buildPlaceholderDecorations({
538
+ editor,
539
+ options,
540
+ dataAttribute,
541
+ doc: state.doc,
542
+ selection: state.selection
543
+ });
544
+ return decorations != null ? decorations : import_view4.DecorationSet.empty;
518
545
  },
519
- destroy: () => {
520
- if (frame !== null) {
521
- cancelAnimationFrame(frame);
546
+ apply(tr, prev, oldState, newState) {
547
+ if (!tr.docChanged && !tr.selectionSet) {
548
+ return prev;
522
549
  }
523
- scrollContainer.removeEventListener("scroll", scheduleFrame);
524
- resizeObserver == null ? void 0 : resizeObserver.disconnect();
525
- intersectionObserver == null ? void 0 : intersectionObserver.disconnect();
526
- view.dom.removeEventListener("focus", scheduleFrame);
550
+ const mapped = prev.map(tr.mapping, tr.doc);
551
+ const ranges = collectRescanRanges(tr, oldState, newState);
552
+ return updateDecorationsInRanges({
553
+ decorations: mapped,
554
+ ranges,
555
+ editor,
556
+ options,
557
+ dataAttribute,
558
+ doc: newState.doc,
559
+ selection: newState.selection
560
+ });
527
561
  }
528
562
  };
529
563
  }
530
564
 
565
+ // src/placeholder/utils/preparePlaceholderAttribute.ts
566
+ function preparePlaceholderAttribute(attr) {
567
+ return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
568
+ }
569
+
531
570
  // src/placeholder/plugins/PlaceholderPlugin.ts
532
571
  function createPlaceholderPlugin({ editor, options }) {
533
572
  const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
573
+ const useResolvedPath = options.showOnlyCurrent && !options.includeChildren;
534
574
  return new import_state4.Plugin({
535
575
  key: PLUGIN_KEY,
536
- state: viewportPluginState,
537
- view: createViewportPluginView,
576
+ ...useResolvedPath ? {} : {
577
+ state: createPlaceholderStateField({ editor, options, dataAttribute })
578
+ },
538
579
  props: {
539
- decorations: ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })
580
+ decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) : (state) => {
581
+ var _a;
582
+ if (options.showOnlyWhenEditable && !editor.isEditable) {
583
+ return import_view5.DecorationSet.empty;
584
+ }
585
+ return (_a = PLUGIN_KEY.getState(state)) != null ? _a : import_view5.DecorationSet.empty;
586
+ }
540
587
  }
541
588
  });
542
589
  }
543
590
 
544
591
  // src/placeholder/placeholder.ts
545
- var Placeholder = import_core6.Extension.create({
592
+ var Placeholder = import_core7.Extension.create({
546
593
  name: "placeholder",
547
594
  addOptions() {
548
595
  return {
@@ -561,9 +608,9 @@ var Placeholder = import_core6.Extension.create({
561
608
  });
562
609
 
563
610
  // src/selection/selection.ts
564
- var import_core7 = require("@tiptap/core");
611
+ var import_core8 = require("@tiptap/core");
565
612
  var import_state5 = require("@tiptap/pm/state");
566
- var import_view4 = require("@tiptap/pm/view");
613
+ var import_view6 = require("@tiptap/pm/view");
567
614
  var selectionStyle = `.ProseMirror:not(.ProseMirror-focused) *::selection {
568
615
  background: transparent;
569
616
  }
@@ -571,7 +618,7 @@ var selectionStyle = `.ProseMirror:not(.ProseMirror-focused) *::selection {
571
618
  .ProseMirror:not(.ProseMirror-focused) *::-moz-selection {
572
619
  background: transparent;
573
620
  }`;
574
- var Selection = import_core7.Extension.create({
621
+ var Selection = import_core8.Extension.create({
575
622
  name: "selection",
576
623
  addOptions() {
577
624
  return {
@@ -581,18 +628,18 @@ var Selection = import_core7.Extension.create({
581
628
  addProseMirrorPlugins() {
582
629
  const { editor, options } = this;
583
630
  if (editor.options.injectCSS && typeof document !== "undefined") {
584
- (0, import_core7.createStyleTag)(selectionStyle, editor.options.injectNonce, "selection");
631
+ (0, import_core8.createStyleTag)(selectionStyle, editor.options.injectNonce, "selection");
585
632
  }
586
633
  return [
587
634
  new import_state5.Plugin({
588
635
  key: new import_state5.PluginKey("selection"),
589
636
  props: {
590
637
  decorations(state) {
591
- if (state.selection.empty || editor.isFocused || !editor.isEditable || (0, import_core7.isNodeSelection)(state.selection) || editor.view.dragging) {
638
+ if (state.selection.empty || editor.isFocused || !editor.isEditable || (0, import_core8.isNodeSelection)(state.selection) || editor.view.dragging) {
592
639
  return null;
593
640
  }
594
- return import_view4.DecorationSet.create(state.doc, [
595
- import_view4.Decoration.inline(state.selection.from, state.selection.to, {
641
+ return import_view6.DecorationSet.create(state.doc, [
642
+ import_view6.Decoration.inline(state.selection.from, state.selection.to, {
596
643
  class: options.className
597
644
  })
598
645
  ]);
@@ -604,7 +651,7 @@ var Selection = import_core7.Extension.create({
604
651
  });
605
652
 
606
653
  // src/trailing-node/trailing-node.ts
607
- var import_core8 = require("@tiptap/core");
654
+ var import_core9 = require("@tiptap/core");
608
655
  var import_state6 = require("@tiptap/pm/state");
609
656
  var skipTrailingNodeMeta = "skipTrailingNode";
610
657
  function nodeEqualsType({
@@ -613,7 +660,7 @@ function nodeEqualsType({
613
660
  }) {
614
661
  return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types;
615
662
  }
616
- var TrailingNode = import_core8.Extension.create({
663
+ var TrailingNode = import_core9.Extension.create({
617
664
  name: "trailingNode",
618
665
  addOptions() {
619
666
  return {
@@ -664,9 +711,9 @@ var TrailingNode = import_core8.Extension.create({
664
711
  });
665
712
 
666
713
  // src/undo-redo/undo-redo.ts
667
- var import_core9 = require("@tiptap/core");
714
+ var import_core10 = require("@tiptap/core");
668
715
  var import_history = require("@tiptap/pm/history");
669
- var UndoRedo = import_core9.Extension.create({
716
+ var UndoRedo = import_core10.Extension.create({
670
717
  name: "undoRedo",
671
718
  addOptions() {
672
719
  return {