@tiptap/extensions 3.23.6 → 3.24.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,81 @@ function createPlaceholderDecoration(options) {
271
283
  });
272
284
  }
273
285
 
286
+ // src/placeholder/utils/buildPlaceholderDecorations.ts
287
+ function buildPlaceholderDecorations({
288
+ editor,
289
+ options,
290
+ dataAttribute,
291
+ doc,
292
+ selection
293
+ }) {
294
+ var _a, _b;
295
+ const active = editor.isEditable || !options.showOnlyWhenEditable;
296
+ if (!active) {
297
+ return null;
298
+ }
299
+ const { anchor } = selection;
300
+ const decorations = [];
301
+ const isEmptyDoc = editor.isEmpty;
302
+ const classes = {
303
+ emptyEditor: options.emptyEditorClass,
304
+ emptyNode: options.emptyNodeClass
305
+ };
306
+ const useResolvedPath = options.showOnlyCurrent && !options.includeChildren;
307
+ if (useResolvedPath) {
308
+ const resolved = doc.resolve(anchor);
309
+ const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter;
310
+ const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor;
311
+ if (node && node.type.isTextblock && (0, import_core5.isNodeEmpty)(node)) {
312
+ const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize;
313
+ decorations.push(
314
+ createPlaceholderDecoration({
315
+ editor,
316
+ isEmptyDoc,
317
+ dataAttribute,
318
+ hasAnchor,
319
+ placeholder: options.placeholder,
320
+ classes,
321
+ node,
322
+ pos: nodeStart
323
+ })
324
+ );
325
+ }
326
+ } else {
327
+ const pluginState = PLUGIN_KEY.getState(editor.state);
328
+ const from = (_a = pluginState == null ? void 0 : pluginState.topPos) != null ? _a : 0;
329
+ const to = (_b = pluginState == null ? void 0 : pluginState.bottomPos) != null ? _b : doc.content.size;
330
+ doc.nodesBetween(from, to, (node, pos) => {
331
+ const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
332
+ const isEmpty = !node.isLeaf && (0, import_core5.isNodeEmpty)(node);
333
+ if (!node.type.isTextblock) {
334
+ return options.includeChildren;
335
+ }
336
+ if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {
337
+ decorations.push(
338
+ createPlaceholderDecoration({
339
+ editor,
340
+ isEmptyDoc,
341
+ dataAttribute,
342
+ hasAnchor,
343
+ placeholder: options.placeholder,
344
+ classes,
345
+ node,
346
+ pos
347
+ })
348
+ );
349
+ }
350
+ return options.includeChildren;
351
+ });
352
+ }
353
+ return import_view3.DecorationSet.create(doc, decorations);
354
+ }
355
+
356
+ // src/placeholder/utils/preparePlaceholderAttribute.ts
357
+ function preparePlaceholderAttribute(attr) {
358
+ return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
359
+ }
360
+
274
361
  // src/placeholder/utils/findScrollParent.ts
275
362
  function isScrollable(el) {
276
363
  const style = getComputedStyle(el);
@@ -311,8 +398,8 @@ function getViewportBoundaryPositions({
311
398
  }) {
312
399
  const editorRect = view.dom.getBoundingClientRect();
313
400
  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);
401
+ const visibleTop = Math.max(editorRect.top, containerRect.top) - VIEWPORT_OVERSCAN_PX;
402
+ const visibleBottom = Math.min(editorRect.bottom, containerRect.bottom) + VIEWPORT_OVERSCAN_PX;
316
403
  if (visibleTop >= visibleBottom) {
317
404
  return { top: 0, bottom: doc.content.size };
318
405
  }
@@ -326,34 +413,98 @@ function getViewportBoundaryPositions({
326
413
  };
327
414
  }
328
415
 
329
- // src/placeholder/utils/throttle.ts
330
- function throttle(fn, delay) {
331
- let timer = null;
332
- const call = ((...args) => {
333
- if (timer) {
416
+ // src/placeholder/utils/viewportTracking.ts
417
+ var viewportPluginState = {
418
+ /**
419
+ * Initialises the viewport state with no known positions.
420
+ * @returns The initial viewport state.
421
+ */
422
+ init() {
423
+ return { topPos: null, bottomPos: null };
424
+ },
425
+ /**
426
+ * Updates the viewport state from incoming transactions.
427
+ * @param tr - The transaction being applied.
428
+ * @param prev - The previous viewport state.
429
+ * @returns The next viewport state.
430
+ */
431
+ apply(tr, prev) {
432
+ const meta = tr.getMeta(PLUGIN_KEY);
433
+ if (meta == null ? void 0 : meta.positions) {
434
+ return { topPos: meta.positions.top, bottomPos: meta.positions.bottom };
435
+ }
436
+ if (!tr.docChanged) {
437
+ return prev;
438
+ }
439
+ return {
440
+ topPos: prev.topPos !== null ? tr.mapping.map(prev.topPos) : null,
441
+ bottomPos: prev.bottomPos !== null ? tr.mapping.map(prev.bottomPos) : null
442
+ };
443
+ }
444
+ };
445
+ function createViewportPluginView(view) {
446
+ const scrollContainer = findScrollParent(view.dom);
447
+ const computeAndDispatch = () => {
448
+ const positions = getViewportBoundaryPositions({
449
+ view,
450
+ doc: view.state.doc,
451
+ scrollContainer
452
+ });
453
+ const prev = PLUGIN_KEY.getState(view.state);
454
+ if ((prev == null ? void 0 : prev.topPos) === positions.top && (prev == null ? void 0 : prev.bottomPos) === positions.bottom) {
334
455
  return;
335
456
  }
336
- fn(...args);
337
- timer = setTimeout(() => {
338
- timer = null;
339
- }, delay);
340
- });
341
- const cancel = () => {
342
- if (timer) {
343
- clearTimeout(timer);
344
- timer = null;
457
+ const tr = view.state.tr.setMeta(PLUGIN_KEY, { positions });
458
+ view.dispatch(tr);
459
+ };
460
+ let frame = null;
461
+ let lastCompute = 0;
462
+ const MIN_SCROLL_INTERVAL = 150;
463
+ const scheduleFrame = () => {
464
+ if (frame !== null) return;
465
+ frame = requestAnimationFrame(() => {
466
+ frame = null;
467
+ const now = performance.now();
468
+ if (now - lastCompute >= MIN_SCROLL_INTERVAL) {
469
+ lastCompute = now;
470
+ computeAndDispatch();
471
+ } else {
472
+ scheduleFrame();
473
+ }
474
+ });
475
+ };
476
+ scrollContainer.addEventListener("scroll", scheduleFrame, { passive: true });
477
+ computeAndDispatch();
478
+ return {
479
+ update(_view, prevState) {
480
+ if (view.state.doc.content.size !== prevState.doc.content.size) {
481
+ scheduleFrame();
482
+ }
483
+ },
484
+ destroy: () => {
485
+ if (frame !== null) {
486
+ cancelAnimationFrame(frame);
487
+ }
488
+ scrollContainer.removeEventListener("scroll", scheduleFrame);
345
489
  }
346
490
  };
347
- return { call, cancel };
348
491
  }
349
492
 
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();
493
+ // src/placeholder/plugins/PlaceholderPlugin.ts
494
+ function createPlaceholderPlugin({ editor, options }) {
495
+ const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
496
+ return new import_state4.Plugin({
497
+ key: PLUGIN_KEY,
498
+ state: viewportPluginState,
499
+ view: createViewportPluginView,
500
+ props: {
501
+ decorations: ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })
502
+ }
503
+ });
354
504
  }
355
- var PLUGIN_KEY = new import_state3.PluginKey("tiptap__placeholder");
356
- var Placeholder = import_core5.Extension.create({
505
+
506
+ // src/placeholder/placeholder.ts
507
+ var Placeholder = import_core6.Extension.create({
357
508
  name: "placeholder",
358
509
  addOptions() {
359
510
  return {
@@ -367,140 +518,15 @@ var Placeholder = import_core5.Extension.create({
367
518
  };
368
519
  },
369
520
  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
- ];
521
+ return [createPlaceholderPlugin({ editor: this.editor, options: this.options })];
496
522
  }
497
523
  });
498
524
 
499
525
  // src/selection/selection.ts
500
- var import_core6 = require("@tiptap/core");
501
- var import_state4 = require("@tiptap/pm/state");
526
+ var import_core7 = require("@tiptap/core");
527
+ var import_state5 = require("@tiptap/pm/state");
502
528
  var import_view4 = require("@tiptap/pm/view");
503
- var Selection = import_core6.Extension.create({
529
+ var Selection = import_core7.Extension.create({
504
530
  name: "selection",
505
531
  addOptions() {
506
532
  return {
@@ -510,11 +536,11 @@ var Selection = import_core6.Extension.create({
510
536
  addProseMirrorPlugins() {
511
537
  const { editor, options } = this;
512
538
  return [
513
- new import_state4.Plugin({
514
- key: new import_state4.PluginKey("selection"),
539
+ new import_state5.Plugin({
540
+ key: new import_state5.PluginKey("selection"),
515
541
  props: {
516
542
  decorations(state) {
517
- if (state.selection.empty || editor.isFocused || !editor.isEditable || (0, import_core6.isNodeSelection)(state.selection) || editor.view.dragging) {
543
+ if (state.selection.empty || editor.isFocused || !editor.isEditable || (0, import_core7.isNodeSelection)(state.selection) || editor.view.dragging) {
518
544
  return null;
519
545
  }
520
546
  return import_view4.DecorationSet.create(state.doc, [
@@ -530,13 +556,16 @@ var Selection = import_core6.Extension.create({
530
556
  });
531
557
 
532
558
  // src/trailing-node/trailing-node.ts
533
- var import_core7 = require("@tiptap/core");
534
- var import_state5 = require("@tiptap/pm/state");
559
+ var import_core8 = require("@tiptap/core");
560
+ var import_state6 = require("@tiptap/pm/state");
535
561
  var skipTrailingNodeMeta = "skipTrailingNode";
536
- function nodeEqualsType({ types, node }) {
562
+ function nodeEqualsType({
563
+ types,
564
+ node
565
+ }) {
537
566
  return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types;
538
567
  }
539
- var TrailingNode = import_core7.Extension.create({
568
+ var TrailingNode = import_core8.Extension.create({
540
569
  name: "trailingNode",
541
570
  addOptions() {
542
571
  return {
@@ -546,11 +575,11 @@ var TrailingNode = import_core7.Extension.create({
546
575
  },
547
576
  addProseMirrorPlugins() {
548
577
  var _a;
549
- const plugin = new import_state5.PluginKey(this.name);
578
+ const plugin = new import_state6.PluginKey(this.name);
550
579
  const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph";
551
580
  const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name));
552
581
  return [
553
- new import_state5.Plugin({
582
+ new import_state6.Plugin({
554
583
  key: plugin,
555
584
  appendTransaction: (transactions, __, state) => {
556
585
  const { doc, tr, schema } = state;
@@ -587,9 +616,9 @@ var TrailingNode = import_core7.Extension.create({
587
616
  });
588
617
 
589
618
  // src/undo-redo/undo-redo.ts
590
- var import_core8 = require("@tiptap/core");
619
+ var import_core9 = require("@tiptap/core");
591
620
  var import_history = require("@tiptap/pm/history");
592
- var UndoRedo = import_core8.Extension.create({
621
+ var UndoRedo = import_core9.Extension.create({
593
622
  name: "undoRedo",
594
623
  addOptions() {
595
624
  return {
@@ -624,6 +653,7 @@ var UndoRedo = import_core8.Extension.create({
624
653
  // Annotate the CommonJS export names for ESM import in node:
625
654
  0 && (module.exports = {
626
655
  CharacterCount,
656
+ DEFAULT_DATA_ATTRIBUTE,
627
657
  Dropcursor,
628
658
  Focus,
629
659
  Gapcursor,