@tiptap/extensions 3.23.6 → 3.25.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.
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  CharacterCount: () => CharacterCount,
24
+ DEFAULT_DATA_ATTRIBUTE: () => DEFAULT_DATA_ATTRIBUTE,
24
25
  Dropcursor: () => Dropcursor,
25
26
  Focus: () => Focus,
26
27
  Gapcursor: () => Gapcursor,
@@ -238,9 +239,20 @@ var Gapcursor = import_core4.Extension.create({
238
239
  }
239
240
  });
240
241
 
242
+ // src/placeholder/constants.ts
243
+ var import_state3 = require("@tiptap/pm/state");
244
+ var DEFAULT_DATA_ATTRIBUTE = "placeholder";
245
+ var PLUGIN_KEY = new import_state3.PluginKey("tiptap__placeholder");
246
+ var VIEWPORT_OVERSCAN_PX = 200;
247
+
241
248
  // src/placeholder/placeholder.ts
249
+ var import_core6 = require("@tiptap/core");
250
+
251
+ // src/placeholder/plugins/PlaceholderPlugin.ts
252
+ var import_state4 = require("@tiptap/pm/state");
253
+
254
+ // src/placeholder/utils/buildPlaceholderDecorations.ts
242
255
  var import_core5 = require("@tiptap/core");
243
- var import_state3 = require("@tiptap/pm/state");
244
256
  var import_view3 = require("@tiptap/pm/view");
245
257
 
246
258
  // src/placeholder/utils/createPlaceholderDecoration.ts
@@ -271,6 +283,96 @@ function createPlaceholderDecoration(options) {
271
283
  });
272
284
  }
273
285
 
286
+ // src/placeholder/utils/buildPlaceholderDecorations.ts
287
+ function resolveEmptyNodeClass(emptyNodeClass, props) {
288
+ return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass;
289
+ }
290
+ function buildPlaceholderDecorations({
291
+ editor,
292
+ options,
293
+ dataAttribute,
294
+ doc,
295
+ selection
296
+ }) {
297
+ var _a, _b;
298
+ const active = editor.isEditable || !options.showOnlyWhenEditable;
299
+ if (!active) {
300
+ return null;
301
+ }
302
+ const { anchor } = selection;
303
+ const decorations = [];
304
+ const isEmptyDoc = editor.isEmpty;
305
+ const useResolvedPath = options.showOnlyCurrent && !options.includeChildren;
306
+ if (useResolvedPath) {
307
+ const resolved = doc.resolve(anchor);
308
+ const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter;
309
+ const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor;
310
+ if (node && node.type.isTextblock && (0, import_core5.isNodeEmpty)(node)) {
311
+ const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize;
312
+ decorations.push(
313
+ createPlaceholderDecoration({
314
+ editor,
315
+ isEmptyDoc,
316
+ dataAttribute,
317
+ hasAnchor,
318
+ placeholder: options.placeholder,
319
+ classes: {
320
+ emptyEditor: options.emptyEditorClass,
321
+ emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {
322
+ editor,
323
+ node,
324
+ pos: nodeStart,
325
+ hasAnchor
326
+ })
327
+ },
328
+ node,
329
+ pos: nodeStart
330
+ })
331
+ );
332
+ }
333
+ } 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
+ });
367
+ }
368
+ return import_view3.DecorationSet.create(doc, decorations);
369
+ }
370
+
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
+ }
375
+
274
376
  // src/placeholder/utils/findScrollParent.ts
275
377
  function isScrollable(el) {
276
378
  const style = getComputedStyle(el);
@@ -311,8 +413,8 @@ function getViewportBoundaryPositions({
311
413
  }) {
312
414
  const editorRect = view.dom.getBoundingClientRect();
313
415
  const containerRect = scrollContainer ? getContainerRect(scrollContainer) : { top: 0, bottom: window.innerHeight };
314
- const visibleTop = Math.max(editorRect.top, containerRect.top);
315
- const visibleBottom = Math.min(editorRect.bottom, containerRect.bottom);
416
+ const visibleTop = Math.max(editorRect.top, containerRect.top) - VIEWPORT_OVERSCAN_PX;
417
+ const visibleBottom = Math.min(editorRect.bottom, containerRect.bottom) + VIEWPORT_OVERSCAN_PX;
316
418
  if (visibleTop >= visibleBottom) {
317
419
  return { top: 0, bottom: doc.content.size };
318
420
  }
@@ -326,34 +428,98 @@ function getViewportBoundaryPositions({
326
428
  };
327
429
  }
328
430
 
329
- // src/placeholder/utils/throttle.ts
330
- function throttle(fn, delay) {
331
- let timer = null;
332
- const call = ((...args) => {
333
- if (timer) {
431
+ // src/placeholder/utils/viewportTracking.ts
432
+ var viewportPluginState = {
433
+ /**
434
+ * Initialises the viewport state with no known positions.
435
+ * @returns The initial viewport state.
436
+ */
437
+ init() {
438
+ return { topPos: null, bottomPos: null };
439
+ },
440
+ /**
441
+ * Updates the viewport state from incoming transactions.
442
+ * @param tr - The transaction being applied.
443
+ * @param prev - The previous viewport state.
444
+ * @returns The next viewport state.
445
+ */
446
+ apply(tr, prev) {
447
+ const meta = tr.getMeta(PLUGIN_KEY);
448
+ if (meta == null ? void 0 : meta.positions) {
449
+ return { topPos: meta.positions.top, bottomPos: meta.positions.bottom };
450
+ }
451
+ if (!tr.docChanged) {
452
+ return prev;
453
+ }
454
+ return {
455
+ topPos: prev.topPos !== null ? tr.mapping.map(prev.topPos) : null,
456
+ bottomPos: prev.bottomPos !== null ? tr.mapping.map(prev.bottomPos) : null
457
+ };
458
+ }
459
+ };
460
+ function createViewportPluginView(view) {
461
+ const scrollContainer = findScrollParent(view.dom);
462
+ const computeAndDispatch = () => {
463
+ const positions = getViewportBoundaryPositions({
464
+ view,
465
+ doc: view.state.doc,
466
+ scrollContainer
467
+ });
468
+ const prev = PLUGIN_KEY.getState(view.state);
469
+ if ((prev == null ? void 0 : prev.topPos) === positions.top && (prev == null ? void 0 : prev.bottomPos) === positions.bottom) {
334
470
  return;
335
471
  }
336
- fn(...args);
337
- timer = setTimeout(() => {
338
- timer = null;
339
- }, delay);
340
- });
341
- const cancel = () => {
342
- if (timer) {
343
- clearTimeout(timer);
344
- timer = null;
472
+ const tr = view.state.tr.setMeta(PLUGIN_KEY, { positions });
473
+ view.dispatch(tr);
474
+ };
475
+ let frame = null;
476
+ let lastCompute = 0;
477
+ const MIN_SCROLL_INTERVAL = 150;
478
+ const scheduleFrame = () => {
479
+ if (frame !== null) return;
480
+ frame = requestAnimationFrame(() => {
481
+ frame = null;
482
+ const now = performance.now();
483
+ if (now - lastCompute >= MIN_SCROLL_INTERVAL) {
484
+ lastCompute = now;
485
+ computeAndDispatch();
486
+ } else {
487
+ scheduleFrame();
488
+ }
489
+ });
490
+ };
491
+ scrollContainer.addEventListener("scroll", scheduleFrame, { passive: true });
492
+ computeAndDispatch();
493
+ return {
494
+ update(_view, prevState) {
495
+ if (view.state.doc.content.size !== prevState.doc.content.size) {
496
+ scheduleFrame();
497
+ }
498
+ },
499
+ destroy: () => {
500
+ if (frame !== null) {
501
+ cancelAnimationFrame(frame);
502
+ }
503
+ scrollContainer.removeEventListener("scroll", scheduleFrame);
345
504
  }
346
505
  };
347
- return { call, cancel };
348
506
  }
349
507
 
350
- // src/placeholder/placeholder.ts
351
- var DEFAULT_DATA_ATTRIBUTE = "placeholder";
352
- function preparePlaceholderAttribute(attr) {
353
- return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
508
+ // src/placeholder/plugins/PlaceholderPlugin.ts
509
+ function createPlaceholderPlugin({ editor, options }) {
510
+ const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
511
+ return new import_state4.Plugin({
512
+ key: PLUGIN_KEY,
513
+ state: viewportPluginState,
514
+ view: createViewportPluginView,
515
+ props: {
516
+ decorations: ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })
517
+ }
518
+ });
354
519
  }
355
- var PLUGIN_KEY = new import_state3.PluginKey("tiptap__placeholder");
356
- var Placeholder = import_core5.Extension.create({
520
+
521
+ // src/placeholder/placeholder.ts
522
+ var Placeholder = import_core6.Extension.create({
357
523
  name: "placeholder",
358
524
  addOptions() {
359
525
  return {
@@ -367,140 +533,15 @@ var Placeholder = import_core5.Extension.create({
367
533
  };
368
534
  },
369
535
  addProseMirrorPlugins() {
370
- const dataAttribute = this.options.dataAttribute ? `data-${preparePlaceholderAttribute(this.options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
371
- return [
372
- new import_state3.Plugin({
373
- state: {
374
- init() {
375
- return {
376
- // null means "no viewport info yet" — decoration callback falls
377
- // back to full document scan until the scroll handler fires.
378
- topPos: null,
379
- bottomPos: null
380
- };
381
- },
382
- apply(tr, prev) {
383
- const meta = tr.getMeta(PLUGIN_KEY);
384
- if (meta == null ? void 0 : meta.positions) {
385
- return {
386
- topPos: meta.positions.top,
387
- bottomPos: meta.positions.bottom
388
- };
389
- }
390
- if (!tr.docChanged) {
391
- return prev;
392
- }
393
- return {
394
- topPos: prev.topPos !== null ? tr.mapping.map(prev.topPos) : null,
395
- bottomPos: prev.bottomPos !== null ? tr.mapping.map(prev.bottomPos) : null
396
- };
397
- }
398
- },
399
- key: PLUGIN_KEY,
400
- view(view) {
401
- const scrollContainer = findScrollParent(view.dom);
402
- const computeAndDispatch = () => {
403
- const positions = getViewportBoundaryPositions({
404
- view,
405
- doc: view.state.doc,
406
- scrollContainer
407
- });
408
- const prev = PLUGIN_KEY.getState(view.state);
409
- if (prev.topPos === positions.top && prev.bottomPos === positions.bottom) {
410
- return;
411
- }
412
- const tr = view.state.tr.setMeta(PLUGIN_KEY, { positions }).setMeta("tiptap__viewportUpdate", true);
413
- view.dispatch(tr);
414
- };
415
- const { call: throttledUpdate, cancel: cancelThrottle } = throttle(computeAndDispatch, 250);
416
- const scrollParent = scrollContainer;
417
- scrollParent.addEventListener("scroll", throttledUpdate, { passive: true });
418
- computeAndDispatch();
419
- return {
420
- update(_, prevState) {
421
- if (view.state.doc.content.size !== prevState.doc.content.size) {
422
- computeAndDispatch();
423
- }
424
- },
425
- destroy: () => {
426
- cancelThrottle();
427
- scrollParent.removeEventListener("scroll", throttledUpdate);
428
- }
429
- };
430
- },
431
- props: {
432
- decorations: ({ doc, selection }) => {
433
- var _a, _b;
434
- const active = this.editor.isEditable || !this.options.showOnlyWhenEditable;
435
- if (!active) {
436
- return null;
437
- }
438
- const { anchor } = selection;
439
- const decorations = [];
440
- const isEmptyDoc = this.editor.isEmpty;
441
- const useResolvedPath = this.options.showOnlyCurrent && !this.options.includeChildren;
442
- if (useResolvedPath) {
443
- const resolved = doc.resolve(anchor);
444
- if (resolved.depth > 0) {
445
- const node = resolved.node(1);
446
- const nodeStart = resolved.before(1);
447
- if (node.type.isTextblock && (0, import_core5.isNodeEmpty)(node)) {
448
- const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize;
449
- const decoration = createPlaceholderDecoration({
450
- node,
451
- dataAttribute,
452
- hasAnchor,
453
- placeholder: this.options.placeholder,
454
- classes: {
455
- emptyEditor: this.options.emptyEditorClass,
456
- emptyNode: this.options.emptyNodeClass
457
- },
458
- editor: this.editor,
459
- isEmptyDoc,
460
- pos: resolved.before(1)
461
- });
462
- decorations.push(decoration);
463
- }
464
- }
465
- } else {
466
- const pluginState = PLUGIN_KEY.getState(this.editor.state);
467
- const from = (_a = pluginState.topPos) != null ? _a : 0;
468
- const to = (_b = pluginState.bottomPos) != null ? _b : doc.content.size;
469
- doc.nodesBetween(from, to, (node, pos) => {
470
- const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
471
- const isEmpty = !node.isLeaf && (0, import_core5.isNodeEmpty)(node);
472
- if (!node.type.isTextblock) {
473
- return this.options.includeChildren;
474
- }
475
- if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) {
476
- const decoration = createPlaceholderDecoration({
477
- classes: { emptyEditor: this.options.emptyEditorClass, emptyNode: this.options.emptyNodeClass },
478
- editor: this.editor,
479
- isEmptyDoc,
480
- dataAttribute,
481
- hasAnchor,
482
- placeholder: this.options.placeholder,
483
- node,
484
- pos
485
- });
486
- decorations.push(decoration);
487
- }
488
- return this.options.includeChildren;
489
- });
490
- }
491
- return import_view3.DecorationSet.create(doc, decorations);
492
- }
493
- }
494
- })
495
- ];
536
+ return [createPlaceholderPlugin({ editor: this.editor, options: this.options })];
496
537
  }
497
538
  });
498
539
 
499
540
  // src/selection/selection.ts
500
- var import_core6 = require("@tiptap/core");
501
- var import_state4 = require("@tiptap/pm/state");
541
+ var import_core7 = require("@tiptap/core");
542
+ var import_state5 = require("@tiptap/pm/state");
502
543
  var import_view4 = require("@tiptap/pm/view");
503
- var Selection = import_core6.Extension.create({
544
+ var Selection = import_core7.Extension.create({
504
545
  name: "selection",
505
546
  addOptions() {
506
547
  return {
@@ -510,11 +551,11 @@ var Selection = import_core6.Extension.create({
510
551
  addProseMirrorPlugins() {
511
552
  const { editor, options } = this;
512
553
  return [
513
- new import_state4.Plugin({
514
- key: new import_state4.PluginKey("selection"),
554
+ new import_state5.Plugin({
555
+ key: new import_state5.PluginKey("selection"),
515
556
  props: {
516
557
  decorations(state) {
517
- if (state.selection.empty || editor.isFocused || !editor.isEditable || (0, import_core6.isNodeSelection)(state.selection) || editor.view.dragging) {
558
+ if (state.selection.empty || editor.isFocused || !editor.isEditable || (0, import_core7.isNodeSelection)(state.selection) || editor.view.dragging) {
518
559
  return null;
519
560
  }
520
561
  return import_view4.DecorationSet.create(state.doc, [
@@ -530,13 +571,16 @@ var Selection = import_core6.Extension.create({
530
571
  });
531
572
 
532
573
  // src/trailing-node/trailing-node.ts
533
- var import_core7 = require("@tiptap/core");
534
- var import_state5 = require("@tiptap/pm/state");
574
+ var import_core8 = require("@tiptap/core");
575
+ var import_state6 = require("@tiptap/pm/state");
535
576
  var skipTrailingNodeMeta = "skipTrailingNode";
536
- function nodeEqualsType({ types, node }) {
577
+ function nodeEqualsType({
578
+ types,
579
+ node
580
+ }) {
537
581
  return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types;
538
582
  }
539
- var TrailingNode = import_core7.Extension.create({
583
+ var TrailingNode = import_core8.Extension.create({
540
584
  name: "trailingNode",
541
585
  addOptions() {
542
586
  return {
@@ -546,11 +590,11 @@ var TrailingNode = import_core7.Extension.create({
546
590
  },
547
591
  addProseMirrorPlugins() {
548
592
  var _a;
549
- const plugin = new import_state5.PluginKey(this.name);
593
+ const plugin = new import_state6.PluginKey(this.name);
550
594
  const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph";
551
595
  const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name));
552
596
  return [
553
- new import_state5.Plugin({
597
+ new import_state6.Plugin({
554
598
  key: plugin,
555
599
  appendTransaction: (transactions, __, state) => {
556
600
  const { doc, tr, schema } = state;
@@ -587,9 +631,9 @@ var TrailingNode = import_core7.Extension.create({
587
631
  });
588
632
 
589
633
  // src/undo-redo/undo-redo.ts
590
- var import_core8 = require("@tiptap/core");
634
+ var import_core9 = require("@tiptap/core");
591
635
  var import_history = require("@tiptap/pm/history");
592
- var UndoRedo = import_core8.Extension.create({
636
+ var UndoRedo = import_core9.Extension.create({
593
637
  name: "undoRedo",
594
638
  addOptions() {
595
639
  return {
@@ -624,6 +668,7 @@ var UndoRedo = import_core8.Extension.create({
624
668
  // Annotate the CommonJS export names for ESM import in node:
625
669
  0 && (module.exports = {
626
670
  CharacterCount,
671
+ DEFAULT_DATA_ATTRIBUTE,
627
672
  Dropcursor,
628
673
  Focus,
629
674
  Gapcursor,