@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.
@@ -31,13 +31,13 @@ module.exports = __toCommonJS(index_exports);
31
31
  var import_state = require("@tiptap/pm/state");
32
32
  var DEFAULT_DATA_ATTRIBUTE = "placeholder";
33
33
  var PLUGIN_KEY = new import_state.PluginKey("tiptap__placeholder");
34
- var VIEWPORT_OVERSCAN_PX = 200;
35
34
 
36
35
  // src/placeholder/placeholder.ts
37
- var import_core2 = require("@tiptap/core");
36
+ var import_core3 = require("@tiptap/core");
38
37
 
39
38
  // src/placeholder/plugins/PlaceholderPlugin.ts
40
39
  var import_state2 = require("@tiptap/pm/state");
40
+ var import_view4 = require("@tiptap/pm/view");
41
41
 
42
42
  // src/placeholder/utils/buildPlaceholderDecorations.ts
43
43
  var import_core = require("@tiptap/core");
@@ -75,6 +75,50 @@ function createPlaceholderDecoration(options) {
75
75
  function resolveEmptyNodeClass(emptyNodeClass, props) {
76
76
  return typeof emptyNodeClass === "function" ? emptyNodeClass(props) : emptyNodeClass;
77
77
  }
78
+ function scanRangeForDecorations({
79
+ editor,
80
+ options,
81
+ dataAttribute,
82
+ doc,
83
+ selection,
84
+ from,
85
+ to
86
+ }) {
87
+ const { anchor } = selection;
88
+ const decorations = [];
89
+ const isEmptyDoc = editor.isEmpty;
90
+ doc.nodesBetween(from, to, (node, pos) => {
91
+ const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
92
+ const isEmpty = !node.isLeaf && (0, import_core.isNodeEmpty)(node);
93
+ if (!node.type.isTextblock) {
94
+ return options.includeChildren;
95
+ }
96
+ if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {
97
+ decorations.push(
98
+ createPlaceholderDecoration({
99
+ editor,
100
+ isEmptyDoc,
101
+ dataAttribute,
102
+ hasAnchor,
103
+ placeholder: options.placeholder,
104
+ classes: {
105
+ emptyEditor: options.emptyEditorClass,
106
+ emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {
107
+ editor,
108
+ node,
109
+ pos,
110
+ hasAnchor
111
+ })
112
+ },
113
+ node,
114
+ pos
115
+ })
116
+ );
117
+ }
118
+ return options.includeChildren;
119
+ });
120
+ return decorations;
121
+ }
78
122
  function buildPlaceholderDecorations({
79
123
  editor,
80
124
  options,
@@ -82,7 +126,6 @@ function buildPlaceholderDecorations({
82
126
  doc,
83
127
  selection
84
128
  }) {
85
- var _a, _b;
86
129
  const active = editor.isEditable || !options.showOnlyWhenEditable;
87
130
  if (!active) {
88
131
  return null;
@@ -119,218 +162,222 @@ function buildPlaceholderDecorations({
119
162
  );
120
163
  }
121
164
  } else {
122
- const pluginState = PLUGIN_KEY.getState(editor.state);
123
- const from = (_a = pluginState == null ? void 0 : pluginState.topPos) != null ? _a : 0;
124
- const to = (_b = pluginState == null ? void 0 : pluginState.bottomPos) != null ? _b : doc.content.size;
125
- doc.nodesBetween(from, to, (node, pos) => {
126
- const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
127
- const isEmpty = !node.isLeaf && (0, import_core.isNodeEmpty)(node);
128
- if (!node.type.isTextblock) {
129
- return options.includeChildren;
130
- }
131
- if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {
132
- decorations.push(
133
- createPlaceholderDecoration({
134
- editor,
135
- isEmptyDoc,
136
- dataAttribute,
137
- hasAnchor,
138
- placeholder: options.placeholder,
139
- classes: {
140
- emptyEditor: options.emptyEditorClass,
141
- emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {
142
- editor,
143
- node,
144
- pos,
145
- hasAnchor
146
- })
147
- },
148
- node,
149
- pos
150
- })
151
- );
152
- }
153
- return options.includeChildren;
154
- });
165
+ decorations.push(
166
+ ...scanRangeForDecorations({
167
+ editor,
168
+ options,
169
+ dataAttribute,
170
+ doc,
171
+ selection,
172
+ from: 0,
173
+ to: doc.content.size
174
+ })
175
+ );
155
176
  }
156
177
  return import_view2.DecorationSet.create(doc, decorations);
157
178
  }
158
179
 
159
- // src/placeholder/utils/preparePlaceholderAttribute.ts
160
- function preparePlaceholderAttribute(attr) {
161
- return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
162
- }
180
+ // src/placeholder/utils/placeholderStateField.ts
181
+ var import_core2 = require("@tiptap/core");
182
+ var import_view3 = require("@tiptap/pm/view");
163
183
 
164
- // src/placeholder/utils/findScrollParent.ts
165
- function isScrollable(el) {
166
- const style = getComputedStyle(el);
167
- const overflow = `${style.overflow} ${style.overflowY} ${style.overflowX}`;
168
- return /auto|scroll|overlay/.test(overflow);
184
+ // src/placeholder/utils/resolveTopLevelRange.ts
185
+ function resolveTopLevelRange(doc, pos) {
186
+ var _a;
187
+ const resolved = doc.resolve(pos);
188
+ if (resolved.depth === 0) {
189
+ const node2 = (_a = resolved.nodeAfter) != null ? _a : resolved.nodeBefore;
190
+ if (!node2) {
191
+ return { from: pos, to: pos };
192
+ }
193
+ const nodePos = resolved.nodeAfter ? pos : pos - node2.nodeSize;
194
+ return { from: nodePos, to: nodePos + node2.nodeSize };
195
+ }
196
+ const topLevelPos = resolved.before(1);
197
+ const node = resolved.node(1);
198
+ return { from: topLevelPos, to: topLevelPos + node.nodeSize };
169
199
  }
170
- function findScrollParent(element) {
171
- let el = element;
172
- while (el) {
173
- if (isScrollable(el)) {
174
- return el;
200
+ function toContentRelativeRange(doc, range) {
201
+ return {
202
+ from: Math.max(0, range.from - 1),
203
+ to: Math.min(doc.content.size, range.to - 1)
204
+ };
205
+ }
206
+ function getTopLevelBlocksInRange(doc, from, to) {
207
+ const ranges = [];
208
+ doc.forEach((node, offset) => {
209
+ const nodeStart = offset;
210
+ const nodeEnd = nodeStart + node.nodeSize;
211
+ const absNodeStart = nodeStart + 1;
212
+ const absNodeEnd = nodeEnd + 1;
213
+ if (absNodeStart < to && absNodeEnd > from) {
214
+ ranges.push({ from: nodeStart, to: nodeEnd });
175
215
  }
176
- const parent = el.parentElement;
177
- if (!parent) {
178
- const root = el.getRootNode();
179
- if (root instanceof ShadowRoot) {
180
- el = root.host;
181
- continue;
182
- }
183
- return window;
216
+ });
217
+ return ranges;
218
+ }
219
+ function mergeRanges(ranges) {
220
+ if (ranges.length === 0) {
221
+ return [];
222
+ }
223
+ const sorted = [...ranges].sort((a, b) => a.from - b.from);
224
+ const merged = [{ ...sorted[0] }];
225
+ for (let i = 1; i < sorted.length; i += 1) {
226
+ const last = merged[merged.length - 1];
227
+ const current = sorted[i];
228
+ if (current.from <= last.to) {
229
+ last.to = Math.max(last.to, current.to);
230
+ } else {
231
+ merged.push({ ...current });
184
232
  }
185
- el = parent;
186
233
  }
187
- return window;
234
+ return merged;
188
235
  }
189
236
 
190
- // src/placeholder/utils/getViewportBoundaryPositions.ts
191
- function getContainerRect(container) {
192
- if (container === window) {
193
- return { top: 0, bottom: window.innerHeight };
237
+ // src/placeholder/utils/placeholderStateField.ts
238
+ function collectBlocksForChange(doc, change) {
239
+ const ranges = getTopLevelBlocksInRange(doc, change.from, change.to);
240
+ ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)));
241
+ if (change.to > change.from) {
242
+ ranges.push(
243
+ toContentRelativeRange(
244
+ doc,
245
+ resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1)
246
+ )
247
+ );
248
+ } else if (change.from < doc.content.size + 1) {
249
+ ranges.push(
250
+ toContentRelativeRange(
251
+ doc,
252
+ resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size))
253
+ )
254
+ );
194
255
  }
195
- return container.getBoundingClientRect();
256
+ return ranges;
196
257
  }
197
- function getViewportBoundaryPositions({
198
- view,
199
- scrollContainer
200
- }) {
201
- const editorRect = view.dom.getBoundingClientRect();
202
- if (editorRect.width <= 0 || editorRect.height <= 0) {
203
- return null;
204
- }
205
- const containerRect = scrollContainer ? getContainerRect(scrollContainer) : { top: 0, bottom: window.innerHeight };
206
- const visibleTop = Math.max(editorRect.top, containerRect.top) - VIEWPORT_OVERSCAN_PX;
207
- const visibleBottom = Math.min(editorRect.bottom, containerRect.bottom) + VIEWPORT_OVERSCAN_PX;
208
- if (visibleTop >= visibleBottom) {
209
- return null;
210
- }
211
- const minX = editorRect.left + 1;
212
- const maxX = editorRect.right - 1;
213
- if (minX > maxX) {
214
- return null;
215
- }
216
- const isRTL = getComputedStyle(view.dom).direction === "rtl";
217
- const targetX = isRTL ? editorRect.right - 2 : editorRect.left + 2;
218
- const x = Math.min(Math.max(targetX, minX), maxX);
219
- const probeTop = Math.max(visibleTop + 2, editorRect.top + 1);
220
- const probeBottom = Math.min(visibleBottom - 2, editorRect.bottom - 1);
221
- if (probeTop > probeBottom) {
222
- return null;
258
+ function collectRescanRanges(tr, oldState, newState) {
259
+ const ranges = [];
260
+ if (tr.docChanged) {
261
+ const changes = (0, import_core2.getChangedRanges)(tr);
262
+ for (const change of changes) {
263
+ ranges.push(...collectBlocksForChange(newState.doc, change.newRange));
264
+ }
223
265
  }
224
- const topPos = view.posAtCoords({ left: x, top: probeTop });
225
- const bottomPos = view.posAtCoords({ left: x, top: probeBottom });
226
- if (!topPos || !bottomPos) {
227
- return null;
266
+ if (tr.selectionSet) {
267
+ ranges.push(
268
+ toContentRelativeRange(
269
+ newState.doc,
270
+ resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor))
271
+ )
272
+ );
273
+ ranges.push(
274
+ toContentRelativeRange(
275
+ newState.doc,
276
+ resolveTopLevelRange(newState.doc, newState.selection.anchor)
277
+ )
278
+ );
228
279
  }
229
- return { top: topPos.pos, bottom: bottomPos.pos };
280
+ return mergeRanges(ranges);
230
281
  }
231
-
232
- // src/placeholder/utils/viewportTracking.ts
233
- var viewportPluginState = {
234
- /**
235
- * Initialises the viewport state with no known positions.
236
- * @returns The initial viewport state.
237
- */
238
- init() {
239
- return { topPos: null, bottomPos: null };
240
- },
241
- /**
242
- * Updates the viewport state from incoming transactions.
243
- * @param tr - The transaction being applied.
244
- * @param prev - The previous viewport state.
245
- * @returns The next viewport state.
246
- */
247
- apply(tr, prev) {
248
- const meta = tr.getMeta(PLUGIN_KEY);
249
- if (meta == null ? void 0 : meta.positions) {
250
- return { topPos: meta.positions.top, bottomPos: meta.positions.bottom };
251
- }
252
- if (!tr.docChanged) {
253
- return prev;
282
+ function clampRange(from, to, doc) {
283
+ const clampedFrom = Math.max(0, Math.min(from, doc.content.size));
284
+ const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size));
285
+ return { from: clampedFrom, to: clampedTo };
286
+ }
287
+ function updateDecorationsInRanges({
288
+ decorations,
289
+ ranges,
290
+ editor,
291
+ options,
292
+ dataAttribute,
293
+ doc,
294
+ selection
295
+ }) {
296
+ let next = decorations;
297
+ for (const range of ranges) {
298
+ const { from, to } = clampRange(range.from, range.to, doc);
299
+ const existing = next.find(from, to).filter((decoration) => decoration.from >= from && decoration.to <= to);
300
+ if (existing.length) {
301
+ next = next.remove(existing);
254
302
  }
255
- return {
256
- topPos: prev.topPos !== null ? tr.mapping.map(prev.topPos) : null,
257
- bottomPos: prev.bottomPos !== null ? tr.mapping.map(prev.bottomPos) : null
258
- };
259
- }
260
- };
261
- function createViewportPluginView(view) {
262
- const scrollContainer = findScrollParent(view.dom);
263
- const computeAndDispatch = () => {
264
- const positions = getViewportBoundaryPositions({
265
- view,
266
- scrollContainer
303
+ const newDecos = scanRangeForDecorations({
304
+ editor,
305
+ options,
306
+ dataAttribute,
307
+ doc,
308
+ selection,
309
+ from,
310
+ to
267
311
  });
268
- if (positions === null) {
269
- return;
270
- }
271
- const prev = PLUGIN_KEY.getState(view.state);
272
- if ((prev == null ? void 0 : prev.topPos) === positions.top && (prev == null ? void 0 : prev.bottomPos) === positions.bottom) {
273
- return;
312
+ if (newDecos.length) {
313
+ next = next.add(doc, newDecos);
274
314
  }
275
- const tr = view.state.tr.setMeta(PLUGIN_KEY, { positions });
276
- view.dispatch(tr);
277
- };
278
- let frame = null;
279
- let lastCompute = 0;
280
- const MIN_SCROLL_INTERVAL = 150;
281
- const scheduleFrame = () => {
282
- if (frame !== null) return;
283
- frame = requestAnimationFrame(() => {
284
- frame = null;
285
- const now = performance.now();
286
- if (now - lastCompute >= MIN_SCROLL_INTERVAL) {
287
- lastCompute = now;
288
- computeAndDispatch();
289
- } else {
290
- scheduleFrame();
291
- }
292
- });
293
- };
294
- scrollContainer.addEventListener("scroll", scheduleFrame, { passive: true });
295
- const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(scheduleFrame) : null;
296
- resizeObserver == null ? void 0 : resizeObserver.observe(view.dom);
297
- const intersectionObserver = typeof IntersectionObserver !== "undefined" ? new IntersectionObserver(scheduleFrame) : null;
298
- intersectionObserver == null ? void 0 : intersectionObserver.observe(view.dom);
299
- view.dom.addEventListener("focus", scheduleFrame);
300
- computeAndDispatch();
315
+ }
316
+ return next;
317
+ }
318
+ function createPlaceholderStateField({
319
+ editor,
320
+ options,
321
+ dataAttribute
322
+ }) {
301
323
  return {
302
- update(_view, prevState) {
303
- if (view.state.doc.content.size !== prevState.doc.content.size) {
304
- scheduleFrame();
305
- }
324
+ init(_config, state) {
325
+ const decorations = buildPlaceholderDecorations({
326
+ editor,
327
+ options,
328
+ dataAttribute,
329
+ doc: state.doc,
330
+ selection: state.selection
331
+ });
332
+ return decorations != null ? decorations : import_view3.DecorationSet.empty;
306
333
  },
307
- destroy: () => {
308
- if (frame !== null) {
309
- cancelAnimationFrame(frame);
334
+ apply(tr, prev, oldState, newState) {
335
+ if (!tr.docChanged && !tr.selectionSet) {
336
+ return prev;
310
337
  }
311
- scrollContainer.removeEventListener("scroll", scheduleFrame);
312
- resizeObserver == null ? void 0 : resizeObserver.disconnect();
313
- intersectionObserver == null ? void 0 : intersectionObserver.disconnect();
314
- view.dom.removeEventListener("focus", scheduleFrame);
338
+ const mapped = prev.map(tr.mapping, tr.doc);
339
+ const ranges = collectRescanRanges(tr, oldState, newState);
340
+ return updateDecorationsInRanges({
341
+ decorations: mapped,
342
+ ranges,
343
+ editor,
344
+ options,
345
+ dataAttribute,
346
+ doc: newState.doc,
347
+ selection: newState.selection
348
+ });
315
349
  }
316
350
  };
317
351
  }
318
352
 
353
+ // src/placeholder/utils/preparePlaceholderAttribute.ts
354
+ function preparePlaceholderAttribute(attr) {
355
+ return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
356
+ }
357
+
319
358
  // src/placeholder/plugins/PlaceholderPlugin.ts
320
359
  function createPlaceholderPlugin({ editor, options }) {
321
360
  const dataAttribute = options.dataAttribute ? `data-${preparePlaceholderAttribute(options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
361
+ const useResolvedPath = options.showOnlyCurrent && !options.includeChildren;
322
362
  return new import_state2.Plugin({
323
363
  key: PLUGIN_KEY,
324
- state: viewportPluginState,
325
- view: createViewportPluginView,
364
+ ...useResolvedPath ? {} : {
365
+ state: createPlaceholderStateField({ editor, options, dataAttribute })
366
+ },
326
367
  props: {
327
- decorations: ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })
368
+ decorations: useResolvedPath ? ({ doc, selection }) => buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }) : (state) => {
369
+ var _a;
370
+ if (options.showOnlyWhenEditable && !editor.isEditable) {
371
+ return import_view4.DecorationSet.empty;
372
+ }
373
+ return (_a = PLUGIN_KEY.getState(state)) != null ? _a : import_view4.DecorationSet.empty;
374
+ }
328
375
  }
329
376
  });
330
377
  }
331
378
 
332
379
  // src/placeholder/placeholder.ts
333
- var Placeholder = import_core2.Extension.create({
380
+ var Placeholder = import_core3.Extension.create({
334
381
  name: "placeholder",
335
382
  addOptions() {
336
383
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/placeholder/index.ts","../../src/placeholder/constants.ts","../../src/placeholder/placeholder.ts","../../src/placeholder/plugins/PlaceholderPlugin.ts","../../src/placeholder/utils/buildPlaceholderDecorations.ts","../../src/placeholder/utils/createPlaceholderDecoration.ts","../../src/placeholder/utils/preparePlaceholderAttribute.ts","../../src/placeholder/utils/findScrollParent.ts","../../src/placeholder/utils/getViewportBoundaryPositions.ts","../../src/placeholder/utils/viewportTracking.ts"],"sourcesContent":["export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from './constants.js'\nexport * from './placeholder.js'\nexport * from './types.js'\nexport { preparePlaceholderAttribute } from './utils/preparePlaceholderAttribute.js'\n","import { PluginKey } from '@tiptap/pm/state'\n\nimport type { ViewportState } from './types.js'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder viewport state */\nexport const PLUGIN_KEY = new PluginKey<ViewportState>('tiptap__placeholder')\n\n/**\n * Extra pixels added above and below the visible viewport when computing the\n * range of nodes to decorate. Decorating slightly beyond the fold means a\n * node already has its placeholder before it scrolls into view, which hides\n * the latency introduced by the throttled viewport recompute.\n */\nexport const VIEWPORT_OVERSCAN_PX = 200\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\nimport { createViewportPluginView, viewportPluginState } from '../utils/viewportTracking.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n return new Plugin({\n key: PLUGIN_KEY,\n state: viewportPluginState,\n view: createViewportPluginView,\n props: {\n decorations: ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection }),\n },\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n const pluginState = PLUGIN_KEY.getState(editor.state)\n const from = pluginState?.topPos ?? 0\n const to = pluginState?.bottomPos ?? doc.content.size\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n","/**\n * Checks if an element is scrollable by testing its overflow properties.\n * Elements with `overflow: hidden` or `overflow: clip` are intentionally\n * excluded — they clip content but don't emit scroll events.\n */\nfunction isScrollable(el: HTMLElement): boolean {\n const style = getComputedStyle(el)\n const overflow = `${style.overflow} ${style.overflowY} ${style.overflowX}`\n\n return /auto|scroll|overlay/.test(overflow)\n}\n\nexport function findScrollParent(element: HTMLElement): HTMLElement | Window {\n let el: HTMLElement | null = element\n\n while (el) {\n if (isScrollable(el)) {\n return el\n }\n\n // Check if we hit a Shadow DOM boundary. If so, jump to the shadow host\n // and continue traversing the light DOM.\n const parent = el.parentElement\n if (!parent) {\n const root = el.getRootNode()\n if (root instanceof ShadowRoot) {\n el = root.host as HTMLElement\n continue\n }\n\n return window\n }\n\n el = parent\n }\n\n return window\n}\n","import type { EditorView } from '@tiptap/pm/view'\n\nimport { VIEWPORT_OVERSCAN_PX } from '../constants.js'\n\nfunction getContainerRect(container: HTMLElement | Window): { top: number; bottom: number } {\n if (container === window) {\n return { top: 0, bottom: window.innerHeight }\n }\n\n return (container as HTMLElement).getBoundingClientRect()\n}\n\n/**\n * Computes the document positions bounding the currently visible viewport.\n */\nexport function getViewportBoundaryPositions({\n view,\n scrollContainer,\n}: {\n view: EditorView\n scrollContainer?: HTMLElement | Window\n}): { top: number; bottom: number } | null {\n const editorRect = view.dom.getBoundingClientRect()\n\n // No usable layout — can't probe a coordinate inside the box.\n if (editorRect.width <= 0 || editorRect.height <= 0) {\n return null\n }\n\n const containerRect = scrollContainer\n ? getContainerRect(scrollContainer)\n : { top: 0, bottom: window.innerHeight }\n\n const visibleTop = Math.max(editorRect.top, containerRect.top) - VIEWPORT_OVERSCAN_PX\n const visibleBottom = Math.min(editorRect.bottom, containerRect.bottom) + VIEWPORT_OVERSCAN_PX\n\n if (visibleTop >= visibleBottom) {\n // Editor is scrolled fully out of its container so not measurable.\n return null\n }\n\n // Probe the start edge (left in LTR, right in RTL), clamped inside the box\n // so a too-narrow editor doesn't push the probe out.\n const minX = editorRect.left + 1\n const maxX = editorRect.right - 1\n\n if (minX > maxX) {\n return null\n }\n\n const isRTL = getComputedStyle(view.dom).direction === 'rtl'\n const targetX = isRTL ? editorRect.right - 2 : editorRect.left + 2\n const x = Math.min(Math.max(targetX, minX), maxX)\n\n // Clamp the probe y inside the box so the overscan doesn't push it past the\n // editor's own content (which would make `posAtCoords` null for no reason).\n const probeTop = Math.max(visibleTop + 2, editorRect.top + 1)\n const probeBottom = Math.min(visibleBottom - 2, editorRect.bottom - 1)\n\n if (probeTop > probeBottom) {\n return null\n }\n\n const topPos = view.posAtCoords({ left: x, top: probeTop })\n const bottomPos = view.posAtCoords({ left: x, top: probeBottom })\n\n // Null usually means occlusion (but can be benign). Either way it's\n // unreliable, so freeze the previous window instead of decorating the whole doc.\n if (!topPos || !bottomPos) {\n return null\n }\n\n return { top: topPos.pos, bottom: bottomPos.pos }\n}\n","import type { EditorState, PluginView, StateField, Transaction } from '@tiptap/pm/state'\nimport type { EditorView } from '@tiptap/pm/view'\n\nimport { PLUGIN_KEY } from '../constants.js'\nimport type { ViewportState } from '../types.js'\nimport { findScrollParent } from './findScrollParent.js'\nimport { getViewportBoundaryPositions } from './getViewportBoundaryPositions.js'\n\n/**\n * The plugin `state` config that tracks the visible viewport boundaries so the\n * decoration callback only scans the nodes currently on screen.\n */\nexport const viewportPluginState: StateField<ViewportState> = {\n /**\n * Initialises the viewport state with no known positions.\n * @returns The initial viewport state.\n */\n init(): ViewportState {\n return { topPos: null, bottomPos: null }\n },\n\n /**\n * Updates the viewport state from incoming transactions.\n * @param tr - The transaction being applied.\n * @param prev - The previous viewport state.\n * @returns The next viewport state.\n */\n apply(tr: Transaction, prev: ViewportState): ViewportState {\n const meta = tr.getMeta(PLUGIN_KEY) as\n | { positions?: { top: number; bottom: number } }\n | undefined\n\n if (meta?.positions) {\n return { topPos: meta.positions.top, bottomPos: meta.positions.bottom }\n }\n\n if (!tr.docChanged) {\n return prev\n }\n\n // Preserve last known viewport positions across transactions.\n // Without this, every keystroke resets back to a full document\n // scan, defeating the viewport optimisation.\n // Only map when we have actual positions — null means \"no viewport\n // info yet\" and should stay null to fall back to full doc scan.\n return {\n topPos: prev.topPos !== null ? tr.mapping.map(prev.topPos) : null,\n bottomPos: prev.bottomPos !== null ? tr.mapping.map(prev.bottomPos) : null,\n }\n },\n}\n\n/**\n * Creates the plugin `view` that recomputes the visible viewport on scroll and\n * document size changes, dispatching the result into the plugin state.\n * @param view - The editor view the plugin is attached to.\n * @returns A plugin view with `update` and `destroy` handlers.\n */\nexport function createViewportPluginView(view: EditorView): PluginView {\n const scrollContainer = findScrollParent(view.dom)\n\n const computeAndDispatch = () => {\n const positions = getViewportBoundaryPositions({\n view,\n scrollContainer,\n })\n\n // Not measurable, keep the last good window frozen\n if (positions === null) {\n return\n }\n\n const prev = PLUGIN_KEY.getState(view.state)\n if (prev?.topPos === positions.top && prev?.bottomPos === positions.bottom) {\n return\n }\n\n const tr = view.state.tr.setMeta(PLUGIN_KEY, { positions })\n view.dispatch(tr)\n }\n\n // rAF-based scheduler with interval guard (150ms → ~6–7 Hz) used by\n // scroll events and update(). The overscan margin hides the extra\n // latency. When the guard blocks, the callback reschedules itself so\n // the trailing position is always captured.\n let frame: number | null = null\n let lastCompute = 0\n const MIN_SCROLL_INTERVAL = 150\n\n const scheduleFrame = () => {\n if (frame !== null) return\n frame = requestAnimationFrame(() => {\n frame = null\n const now = performance.now()\n if (now - lastCompute >= MIN_SCROLL_INTERVAL) {\n lastCompute = now\n computeAndDispatch()\n } else {\n scheduleFrame()\n }\n })\n }\n\n scrollContainer.addEventListener('scroll', scheduleFrame, { passive: true })\n\n // Recover a frozen window once the editor becomes usable again (e.g. a modal\n // closes): re-measure on size/visibility changes and when focus returns.\n const resizeObserver =\n typeof ResizeObserver !== 'undefined' ? new ResizeObserver(scheduleFrame) : null\n resizeObserver?.observe(view.dom)\n\n const intersectionObserver =\n typeof IntersectionObserver !== 'undefined' ? new IntersectionObserver(scheduleFrame) : null\n intersectionObserver?.observe(view.dom)\n\n view.dom.addEventListener('focus', scheduleFrame)\n\n // Fire once to populate initial viewport\n computeAndDispatch()\n\n return {\n update(_view: EditorView, prevState: EditorState) {\n if (view.state.doc.content.size !== prevState.doc.content.size) {\n scheduleFrame()\n }\n },\n destroy: () => {\n if (frame !== null) {\n cancelAnimationFrame(frame)\n }\n scrollContainer.removeEventListener('scroll', scheduleFrame)\n resizeObserver?.disconnect()\n intersectionObserver?.disconnect()\n view.dom.removeEventListener('focus', scheduleFrame)\n },\n }\n}\n"],"mappings":"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","names":["import_core","import_state","import_view"]}
1
+ {"version":3,"sources":["../../src/placeholder/index.ts","../../src/placeholder/constants.ts","../../src/placeholder/placeholder.ts","../../src/placeholder/plugins/PlaceholderPlugin.ts","../../src/placeholder/utils/buildPlaceholderDecorations.ts","../../src/placeholder/utils/createPlaceholderDecoration.ts","../../src/placeholder/utils/placeholderStateField.ts","../../src/placeholder/utils/resolveTopLevelRange.ts","../../src/placeholder/utils/preparePlaceholderAttribute.ts"],"sourcesContent":["export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from './constants.js'\nexport * from './placeholder.js'\nexport * from './types.js'\nexport { preparePlaceholderAttribute } from './utils/preparePlaceholderAttribute.js'\n","import { PluginKey } from '@tiptap/pm/state'\nimport type { DecorationSet } from '@tiptap/pm/view'\n\n/** The default data attribute label */\nexport const DEFAULT_DATA_ATTRIBUTE = 'placeholder'\n\n/** The plugin key used to store and read the placeholder decoration set */\nexport const PLUGIN_KEY = new PluginKey<DecorationSet>('tiptap__placeholder')\n","import { Extension } from '@tiptap/core'\n\nimport { DEFAULT_DATA_ATTRIBUTE } from './constants.js'\nimport { createPlaceholderPlugin } from './plugins/PlaceholderPlugin.js'\nimport type { PlaceholderOptions } from './types.js'\n\n/**\n * This extension allows you to add a placeholder to your editor.\n * A placeholder is a text that appears when the editor or a node is empty.\n * @see https://www.tiptap.dev/api/extensions/placeholder\n */\nexport const Placeholder = Extension.create<PlaceholderOptions>({\n name: 'placeholder',\n\n addOptions() {\n return {\n emptyEditorClass: 'is-editor-empty',\n emptyNodeClass: 'is-empty',\n dataAttribute: DEFAULT_DATA_ATTRIBUTE,\n placeholder: 'Write something …',\n showOnlyWhenEditable: true,\n showOnlyCurrent: true,\n includeChildren: false,\n }\n },\n\n addProseMirrorPlugins() {\n return [createPlaceholderPlugin({ editor: this.editor, options: this.options })]\n },\n})\n","import type { Editor } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY } from '../constants.js'\nimport type { PlaceholderOptions } from '../types.js'\nimport { buildPlaceholderDecorations } from '../utils/buildPlaceholderDecorations.js'\nimport { createPlaceholderStateField } from '../utils/placeholderStateField.js'\nimport { preparePlaceholderAttribute } from '../utils/preparePlaceholderAttribute.js'\n\nexport type CreatePluginOptions = {\n editor: Editor\n options: PlaceholderOptions\n}\n\n/**\n * Creates the ProseMirror plugin that renders placeholder decorations.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @returns The configured placeholder plugin.\n */\nexport function createPlaceholderPlugin({ editor, options }: CreatePluginOptions) {\n const dataAttribute = options.dataAttribute\n ? `data-${preparePlaceholderAttribute(options.dataAttribute)}`\n : `data-${DEFAULT_DATA_ATTRIBUTE}`\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n return new Plugin({\n key: PLUGIN_KEY,\n ...(useResolvedPath\n ? {}\n : {\n state: createPlaceholderStateField({ editor, options, dataAttribute }),\n }),\n props: {\n decorations: useResolvedPath\n ? ({ doc, selection }) =>\n buildPlaceholderDecorations({ editor, options, dataAttribute, doc, selection })\n : state => {\n if (options.showOnlyWhenEditable && !editor.isEditable) {\n return DecorationSet.empty\n }\n\n return PLUGIN_KEY.getState(state) ?? DecorationSet.empty\n },\n },\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { isNodeEmpty } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { Selection } from '@tiptap/pm/state'\nimport type { Decoration } from '@tiptap/pm/view'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport { createPlaceholderDecoration } from './createPlaceholderDecoration.js'\n\nfunction resolveEmptyNodeClass(\n emptyNodeClass: PlaceholderOptions['emptyNodeClass'],\n props: { editor: Editor; node: Node; pos: number; hasAnchor: boolean },\n): string {\n return typeof emptyNodeClass === 'function' ? emptyNodeClass(props) : emptyNodeClass\n}\n\n/**\n * Scans a document range for empty textblocks that should receive placeholder\n * decorations. Used by the slow path and incremental state updates.\n */\nexport function scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n from: number\n to: number\n}): Decoration[] {\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n doc.nodesBetween(from, to, (node, pos) => {\n const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize\n const isEmpty = !node.isLeaf && isNodeEmpty(node)\n\n if (!node.type.isTextblock) {\n return options.includeChildren\n }\n\n if ((hasAnchor || !options.showOnlyCurrent) && isEmpty) {\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos,\n hasAnchor,\n }),\n },\n node,\n pos,\n }),\n )\n }\n\n return options.includeChildren\n })\n\n return decorations\n}\n\n/**\n * Builds the placeholder decorations for the current document state.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @param options.doc - The current document node.\n * @param options.selection - The current selection.\n * @returns A decoration set, or `null` when no placeholders should be shown.\n */\nexport function buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet | null {\n const active = editor.isEditable || !options.showOnlyWhenEditable\n\n if (!active) {\n return null\n }\n\n const { anchor } = selection\n const decorations: Decoration[] = []\n const isEmptyDoc = editor.isEmpty\n\n const useResolvedPath = options.showOnlyCurrent && !options.includeChildren\n\n if (useResolvedPath) {\n const resolved = doc.resolve(anchor)\n\n // When the selection spans the whole document (e.g. an `AllSelection`\n // after Cmd+A), the anchor resolves to the document level (depth 0). In\n // that case the relevant textblock is the node directly after the\n // position rather than an ancestor. otherwise the placeholder would\n // disappear after selecting all and deleting.\n const node = resolved.depth > 0 ? resolved.node(1) : resolved.nodeAfter\n const nodeStart = resolved.depth > 0 ? resolved.before(1) : anchor\n\n if (node && node.type.isTextblock && isNodeEmpty(node)) {\n const hasAnchor = anchor >= nodeStart && anchor <= nodeStart + node.nodeSize\n\n decorations.push(\n createPlaceholderDecoration({\n editor,\n isEmptyDoc,\n dataAttribute,\n hasAnchor,\n placeholder: options.placeholder,\n classes: {\n emptyEditor: options.emptyEditorClass,\n emptyNode: resolveEmptyNodeClass(options.emptyNodeClass, {\n editor,\n node,\n pos: nodeStart,\n hasAnchor,\n }),\n },\n node,\n pos: nodeStart,\n }),\n )\n }\n } else {\n decorations.push(\n ...scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from: 0,\n to: doc.content.size,\n }),\n )\n }\n\n return DecorationSet.create(doc, decorations)\n}\n","import type { Editor } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport { Decoration } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\n\n/**\n * Creates a ProseMirror node decoration that applies a placeholder\n * CSS class and data attribute to an empty node.\n * @param options.editor - The editor instance\n * @param options.pos - The position of the node in the document\n * @param options.node - The ProseMirror node\n * @param options.isEmptyDoc - Whether the entire document is empty\n * @param options.hasAnchor - Whether the selection anchor is within the node\n * @param options.dataAttribute - The data attribute name (e.g. `data-placeholder`)\n * @param options.classes - CSS classes for empty nodes and the empty editor\n * @param options.placeholder - The placeholder text or a function that returns it\n * @returns A ProseMirror node decoration with placeholder classes and data attribute\n */\nexport function createPlaceholderDecoration(options: {\n editor: Editor\n pos: number\n node: Node\n isEmptyDoc: boolean\n hasAnchor: boolean\n dataAttribute: string\n classes: {\n emptyEditor: PlaceholderOptions['emptyEditorClass']\n emptyNode: string\n }\n placeholder: PlaceholderOptions['placeholder']\n}) {\n const {\n editor,\n placeholder,\n dataAttribute,\n pos,\n node,\n isEmptyDoc,\n hasAnchor,\n classes: { emptyNode, emptyEditor },\n } = options\n const classes = [emptyNode]\n\n if (isEmptyDoc) {\n classes.push(emptyEditor)\n }\n\n return Decoration.node(pos, pos + node.nodeSize, {\n class: classes.join(' '),\n [dataAttribute]:\n typeof placeholder === 'function'\n ? placeholder({\n editor,\n node,\n pos,\n hasAnchor,\n })\n : placeholder,\n })\n}\n","import type { Editor } from '@tiptap/core'\nimport { getChangedRanges } from '@tiptap/core'\nimport type { Node } from '@tiptap/pm/model'\nimport type { EditorState, StateField, Transaction } from '@tiptap/pm/state'\nimport type { Selection } from '@tiptap/pm/state'\nimport { DecorationSet } from '@tiptap/pm/view'\n\nimport type { PlaceholderOptions } from '../types.js'\nimport {\n buildPlaceholderDecorations,\n scanRangeForDecorations,\n} from './buildPlaceholderDecorations.js'\nimport {\n getTopLevelBlocksInRange,\n mergeRanges,\n resolveTopLevelRange,\n toContentRelativeRange,\n} from './resolveTopLevelRange.js'\n\n/** Options passed to {@link createPlaceholderStateField}. */\nexport type CreatePlaceholderStateFieldOptions = {\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n}\n\n/**\n * Expands a single changed range to the top-level blocks it touches.\n * Also resolves blocks at range boundaries so split/merge edits update\n * adjacent empty nodes (e.g. a new paragraph after Enter).\n */\nfunction collectBlocksForChange(\n doc: Node,\n change: { from: number; to: number },\n): Array<{ from: number; to: number }> {\n const ranges = getTopLevelBlocksInRange(doc, change.from, change.to)\n\n ranges.push(toContentRelativeRange(doc, resolveTopLevelRange(doc, change.from)))\n\n if (change.to > change.from) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.to, doc.content.size + 1) - 1),\n ),\n )\n } else if (change.from < doc.content.size + 1) {\n ranges.push(\n toContentRelativeRange(\n doc,\n resolveTopLevelRange(doc, Math.min(change.from + 1, doc.content.size)),\n ),\n )\n }\n\n return ranges\n}\n\n/**\n * Collects content-relative top-level block ranges that need placeholder\n * decorations recomputed after a transaction.\n */\nfunction collectRescanRanges(\n tr: Transaction,\n oldState: EditorState,\n newState: EditorState,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n if (tr.docChanged) {\n const changes = getChangedRanges(tr)\n\n for (const change of changes) {\n ranges.push(...collectBlocksForChange(newState.doc, change.newRange))\n }\n }\n\n if (tr.selectionSet) {\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, tr.mapping.map(oldState.selection.anchor)),\n ),\n )\n ranges.push(\n toContentRelativeRange(\n newState.doc,\n resolveTopLevelRange(newState.doc, newState.selection.anchor),\n ),\n )\n }\n\n return mergeRanges(ranges)\n}\n\n/** Clamps a content-relative range to `[0, doc.content.size]`. */\nfunction clampRange(from: number, to: number, doc: Node): { from: number; to: number } {\n const clampedFrom = Math.max(0, Math.min(from, doc.content.size))\n const clampedTo = Math.max(clampedFrom, Math.min(to, doc.content.size))\n\n return { from: clampedFrom, to: clampedTo }\n}\n\n/**\n * Removes and rebuilds placeholder decorations within the given ranges.\n * Only drops decorations fully contained in a range so mapped decorations\n * on neighbouring blocks (e.g. at a block boundary) are kept intact.\n */\nfunction updateDecorationsInRanges({\n decorations,\n ranges,\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n}: {\n decorations: DecorationSet\n ranges: Array<{ from: number; to: number }>\n editor: Editor\n options: PlaceholderOptions\n dataAttribute: string\n doc: Node\n selection: Selection\n}): DecorationSet {\n let next = decorations\n\n for (const range of ranges) {\n const { from, to } = clampRange(range.from, range.to, doc)\n const existing = next\n .find(from, to)\n .filter(decoration => decoration.from >= from && decoration.to <= to)\n\n if (existing.length) {\n next = next.remove(existing)\n }\n\n const newDecos = scanRangeForDecorations({\n editor,\n options,\n dataAttribute,\n doc,\n selection,\n from,\n to,\n })\n\n if (newDecos.length) {\n next = next.add(doc, newDecos)\n }\n }\n\n return next\n}\n\n/**\n * Creates the incremental `StateField<DecorationSet>` used by the slow path\n * (`showOnlyCurrent: false` or `includeChildren: true`).\n *\n * Decorations are mapped through each transaction and only recomputed for\n * top-level blocks touched by document or selection changes.\n * @param options.editor - The editor instance.\n * @param options.options - The resolved placeholder options.\n * @param options.dataAttribute - The prepared `data-*` attribute name.\n * @returns A ProseMirror state field storing the placeholder decoration set.\n */\nexport function createPlaceholderStateField({\n editor,\n options,\n dataAttribute,\n}: CreatePlaceholderStateFieldOptions): StateField<DecorationSet> {\n return {\n init(_config, state: EditorState) {\n const decorations = buildPlaceholderDecorations({\n editor,\n options,\n dataAttribute,\n doc: state.doc,\n selection: state.selection,\n })\n\n return decorations ?? DecorationSet.empty\n },\n\n apply(tr: Transaction, prev: DecorationSet, oldState: EditorState, newState: EditorState) {\n if (!tr.docChanged && !tr.selectionSet) {\n return prev\n }\n\n const mapped = prev.map(tr.mapping, tr.doc)\n const ranges = collectRescanRanges(tr, oldState, newState)\n\n return updateDecorationsInRanges({\n decorations: mapped,\n ranges,\n editor,\n options,\n dataAttribute,\n doc: newState.doc,\n selection: newState.selection,\n })\n },\n }\n}\n","import type { Node } from '@tiptap/pm/model'\n\n/**\n * Resolves a document position to the `[from, to)` range of its containing\n * top-level block node in absolute document positions.\n */\nexport function resolveTopLevelRange(doc: Node, pos: number): { from: number; to: number } {\n const resolved = doc.resolve(pos)\n\n if (resolved.depth === 0) {\n const node = resolved.nodeAfter ?? resolved.nodeBefore\n\n if (!node) {\n return { from: pos, to: pos }\n }\n\n const nodePos = resolved.nodeAfter ? pos : pos - node.nodeSize\n\n return { from: nodePos, to: nodePos + node.nodeSize }\n }\n\n const topLevelPos = resolved.before(1)\n const node = resolved.node(1)\n\n return { from: topLevelPos, to: topLevelPos + node.nodeSize }\n}\n\n/**\n * Converts an absolute document range to content-relative positions used by\n * `Node#nodesBetween` and `Node#forEach` offsets.\n */\nexport function toContentRelativeRange(\n doc: Node,\n range: { from: number; to: number },\n): { from: number; to: number } {\n return {\n from: Math.max(0, range.from - 1),\n to: Math.min(doc.content.size, range.to - 1),\n }\n}\n\n/**\n * Returns the top-level block ranges that intersect a document change range.\n * Input `from`/`to` are absolute positions (e.g. from `getChangedRanges`).\n * Returned ranges are content-relative, matching `Node#forEach` offsets.\n */\nexport function getTopLevelBlocksInRange(\n doc: Node,\n from: number,\n to: number,\n): Array<{ from: number; to: number }> {\n const ranges: Array<{ from: number; to: number }> = []\n\n doc.forEach((node, offset) => {\n const nodeStart = offset\n const nodeEnd = nodeStart + node.nodeSize\n const absNodeStart = nodeStart + 1\n const absNodeEnd = nodeEnd + 1\n\n if (absNodeStart < to && absNodeEnd > from) {\n ranges.push({ from: nodeStart, to: nodeEnd })\n }\n })\n\n return ranges\n}\n\n/**\n * Sorts ranges by start position and merges overlapping or adjacent ranges.\n */\nexport function mergeRanges(\n ranges: Array<{ from: number; to: number }>,\n): Array<{ from: number; to: number }> {\n if (ranges.length === 0) {\n return []\n }\n\n const sorted = [...ranges].sort((a, b) => a.from - b.from)\n const merged: Array<{ from: number; to: number }> = [{ ...sorted[0] }]\n\n for (let i = 1; i < sorted.length; i += 1) {\n const last = merged[merged.length - 1]\n const current = sorted[i]\n\n if (current.from <= last.to) {\n last.to = Math.max(last.to, current.to)\n } else {\n merged.push({ ...current })\n }\n }\n\n return merged\n}\n","/**\n * Prepares the placeholder attribute by ensuring it is properly formatted.\n * @param attr - The placeholder attribute string.\n * @returns The prepared placeholder attribute string.\n */\nexport function preparePlaceholderAttribute(attr: string): string {\n return (\n attr\n // replace whitespace with dashes\n .replace(/\\s+/g, '-')\n // replace non-alphanumeric characters\n // or special chars like $, %, &, etc.\n // but not dashes\n .replace(/[^a-zA-Z0-9-]/g, '')\n // and replace any numeric character at the start\n .replace(/^[0-9-]+/, '')\n // and finally replace any stray, leading dashes\n .replace(/^-+/, '')\n .toLowerCase()\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAA0B;AAInB,IAAM,yBAAyB;AAG/B,IAAM,aAAa,IAAI,uBAAyB,qBAAqB;;;ACP5E,IAAAA,eAA0B;;;ACC1B,IAAAC,gBAAuB;AACvB,IAAAC,eAA8B;;;ACD9B,kBAA4B;AAI5B,IAAAC,eAA8B;;;ACH9B,kBAA2B;AAiBpB,SAAS,4BAA4B,SAYzC;AACD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,EAAE,WAAW,YAAY;AAAA,EACpC,IAAI;AACJ,QAAM,UAAU,CAAC,SAAS;AAE1B,MAAI,YAAY;AACd,YAAQ,KAAK,WAAW;AAAA,EAC1B;AAEA,SAAO,uBAAW,KAAK,KAAK,MAAM,KAAK,UAAU;AAAA,IAC/C,OAAO,QAAQ,KAAK,GAAG;AAAA,IACvB,CAAC,aAAa,GACZ,OAAO,gBAAgB,aACnB,YAAY;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,IACD;AAAA,EACR,CAAC;AACH;;;ADlDA,SAAS,sBACP,gBACA,OACQ;AACR,SAAO,OAAO,mBAAmB,aAAa,eAAe,KAAK,IAAI;AACxE;AAMO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQiB;AACf,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,cAA4B,CAAC;AACnC,QAAM,aAAa,OAAO;AAE1B,MAAI,aAAa,MAAM,IAAI,CAAC,MAAM,QAAQ;AACxC,UAAM,YAAY,UAAU,OAAO,UAAU,MAAM,KAAK;AACxD,UAAM,UAAU,CAAC,KAAK,cAAU,yBAAY,IAAI;AAEhD,QAAI,CAAC,KAAK,KAAK,aAAa;AAC1B,aAAO,QAAQ;AAAA,IACjB;AAEA,SAAK,aAAa,CAAC,QAAQ,oBAAoB,SAAS;AACtD,kBAAY;AAAA,QACV,4BAA4B;AAAA,UAC1B;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa,QAAQ;AAAA,UACrB,SAAS;AAAA,YACP,aAAa,QAAQ;AAAA,YACrB,WAAW,sBAAsB,QAAQ,gBAAgB;AAAA,cACvD;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,QAAQ;AAAA,EACjB,CAAC;AAED,SAAO;AACT;AAWO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMyB;AACvB,QAAM,SAAS,OAAO,cAAc,CAAC,QAAQ;AAE7C,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,cAA4B,CAAC;AACnC,QAAM,aAAa,OAAO;AAE1B,QAAM,kBAAkB,QAAQ,mBAAmB,CAAC,QAAQ;AAE5D,MAAI,iBAAiB;AACnB,UAAM,WAAW,IAAI,QAAQ,MAAM;AAOnC,UAAM,OAAO,SAAS,QAAQ,IAAI,SAAS,KAAK,CAAC,IAAI,SAAS;AAC9D,UAAM,YAAY,SAAS,QAAQ,IAAI,SAAS,OAAO,CAAC,IAAI;AAE5D,QAAI,QAAQ,KAAK,KAAK,mBAAe,yBAAY,IAAI,GAAG;AACtD,YAAM,YAAY,UAAU,aAAa,UAAU,YAAY,KAAK;AAEpE,kBAAY;AAAA,QACV,4BAA4B;AAAA,UAC1B;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa,QAAQ;AAAA,UACrB,SAAS;AAAA,YACP,aAAa,QAAQ;AAAA,YACrB,WAAW,sBAAsB,QAAQ,gBAAgB;AAAA,cACvD;AAAA,cACA;AAAA,cACA,KAAK;AAAA,cACL;AAAA,YACF,CAAC;AAAA,UACH;AAAA,UACA;AAAA,UACA,KAAK;AAAA,QACP,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,OAAO;AACL,gBAAY;AAAA,MACV,GAAG,wBAAwB;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,IAAI,IAAI,QAAQ;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,2BAAc,OAAO,KAAK,WAAW;AAC9C;;;AElKA,IAAAC,eAAiC;AAIjC,IAAAC,eAA8B;;;ACCvB,SAAS,qBAAqB,KAAW,KAA2C;AAN3F;AAOE,QAAM,WAAW,IAAI,QAAQ,GAAG;AAEhC,MAAI,SAAS,UAAU,GAAG;AACxB,UAAMC,SAAO,cAAS,cAAT,YAAsB,SAAS;AAE5C,QAAI,CAACA,OAAM;AACT,aAAO,EAAE,MAAM,KAAK,IAAI,IAAI;AAAA,IAC9B;AAEA,UAAM,UAAU,SAAS,YAAY,MAAM,MAAMA,MAAK;AAEtD,WAAO,EAAE,MAAM,SAAS,IAAI,UAAUA,MAAK,SAAS;AAAA,EACtD;AAEA,QAAM,cAAc,SAAS,OAAO,CAAC;AACrC,QAAM,OAAO,SAAS,KAAK,CAAC;AAE5B,SAAO,EAAE,MAAM,aAAa,IAAI,cAAc,KAAK,SAAS;AAC9D;AAMO,SAAS,uBACd,KACA,OAC8B;AAC9B,SAAO;AAAA,IACL,MAAM,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC;AAAA,IAChC,IAAI,KAAK,IAAI,IAAI,QAAQ,MAAM,MAAM,KAAK,CAAC;AAAA,EAC7C;AACF;AAOO,SAAS,yBACd,KACA,MACA,IACqC;AACrC,QAAM,SAA8C,CAAC;AAErD,MAAI,QAAQ,CAAC,MAAM,WAAW;AAC5B,UAAM,YAAY;AAClB,UAAM,UAAU,YAAY,KAAK;AACjC,UAAM,eAAe,YAAY;AACjC,UAAM,aAAa,UAAU;AAE7B,QAAI,eAAe,MAAM,aAAa,MAAM;AAC1C,aAAO,KAAK,EAAE,MAAM,WAAW,IAAI,QAAQ,CAAC;AAAA,IAC9C;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAKO,SAAS,YACd,QACqC;AACrC,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI;AACzD,QAAM,SAA8C,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;AAErE,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,UAAM,UAAU,OAAO,CAAC;AAExB,QAAI,QAAQ,QAAQ,KAAK,IAAI;AAC3B,WAAK,KAAK,KAAK,IAAI,KAAK,IAAI,QAAQ,EAAE;AAAA,IACxC,OAAO;AACL,aAAO,KAAK,EAAE,GAAG,QAAQ,CAAC;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;;;AD7DA,SAAS,uBACP,KACA,QACqC;AACrC,QAAM,SAAS,yBAAyB,KAAK,OAAO,MAAM,OAAO,EAAE;AAEnE,SAAO,KAAK,uBAAuB,KAAK,qBAAqB,KAAK,OAAO,IAAI,CAAC,CAAC;AAE/E,MAAI,OAAO,KAAK,OAAO,MAAM;AAC3B,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,qBAAqB,KAAK,KAAK,IAAI,OAAO,IAAI,IAAI,QAAQ,OAAO,CAAC,IAAI,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF,WAAW,OAAO,OAAO,IAAI,QAAQ,OAAO,GAAG;AAC7C,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,qBAAqB,KAAK,KAAK,IAAI,OAAO,OAAO,GAAG,IAAI,QAAQ,IAAI,CAAC;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,oBACP,IACA,UACA,UACqC;AACrC,QAAM,SAA8C,CAAC;AAErD,MAAI,GAAG,YAAY;AACjB,UAAM,cAAU,+BAAiB,EAAE;AAEnC,eAAW,UAAU,SAAS;AAC5B,aAAO,KAAK,GAAG,uBAAuB,SAAS,KAAK,OAAO,QAAQ,CAAC;AAAA,IACtE;AAAA,EACF;AAEA,MAAI,GAAG,cAAc;AACnB,WAAO;AAAA,MACL;AAAA,QACE,SAAS;AAAA,QACT,qBAAqB,SAAS,KAAK,GAAG,QAAQ,IAAI,SAAS,UAAU,MAAM,CAAC;AAAA,MAC9E;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,QACE,SAAS;AAAA,QACT,qBAAqB,SAAS,KAAK,SAAS,UAAU,MAAM;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAEA,SAAO,YAAY,MAAM;AAC3B;AAGA,SAAS,WAAW,MAAc,IAAY,KAAyC;AACrF,QAAM,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,IAAI,QAAQ,IAAI,CAAC;AAChE,QAAM,YAAY,KAAK,IAAI,aAAa,KAAK,IAAI,IAAI,IAAI,QAAQ,IAAI,CAAC;AAEtE,SAAO,EAAE,MAAM,aAAa,IAAI,UAAU;AAC5C;AAOA,SAAS,0BAA0B;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAQkB;AAChB,MAAI,OAAO;AAEX,aAAW,SAAS,QAAQ;AAC1B,UAAM,EAAE,MAAM,GAAG,IAAI,WAAW,MAAM,MAAM,MAAM,IAAI,GAAG;AACzD,UAAM,WAAW,KACd,KAAK,MAAM,EAAE,EACb,OAAO,gBAAc,WAAW,QAAQ,QAAQ,WAAW,MAAM,EAAE;AAEtE,QAAI,SAAS,QAAQ;AACnB,aAAO,KAAK,OAAO,QAAQ;AAAA,IAC7B;AAEA,UAAM,WAAW,wBAAwB;AAAA,MACvC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,SAAS,QAAQ;AACnB,aAAO,KAAK,IAAI,KAAK,QAAQ;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO;AACT;AAaO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AACF,GAAkE;AAChE,SAAO;AAAA,IACL,KAAK,SAAS,OAAoB;AAChC,YAAM,cAAc,4BAA4B;AAAA,QAC9C;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,MAAM;AAAA,QACX,WAAW,MAAM;AAAA,MACnB,CAAC;AAED,aAAO,oCAAe,2BAAc;AAAA,IACtC;AAAA,IAEA,MAAM,IAAiB,MAAqB,UAAuB,UAAuB;AACxF,UAAI,CAAC,GAAG,cAAc,CAAC,GAAG,cAAc;AACtC,eAAO;AAAA,MACT;AAEA,YAAM,SAAS,KAAK,IAAI,GAAG,SAAS,GAAG,GAAG;AAC1C,YAAM,SAAS,oBAAoB,IAAI,UAAU,QAAQ;AAEzD,aAAO,0BAA0B;AAAA,QAC/B,aAAa;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,SAAS;AAAA,QACd,WAAW,SAAS;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AEtMO,SAAS,4BAA4B,MAAsB;AAChE,SACE,KAEG,QAAQ,QAAQ,GAAG,EAInB,QAAQ,kBAAkB,EAAE,EAE5B,QAAQ,YAAY,EAAE,EAEtB,QAAQ,OAAO,EAAE,EACjB,YAAY;AAEnB;;;ALCO,SAAS,wBAAwB,EAAE,QAAQ,QAAQ,GAAwB;AAChF,QAAM,gBAAgB,QAAQ,gBAC1B,QAAQ,4BAA4B,QAAQ,aAAa,CAAC,KAC1D,QAAQ,sBAAsB;AAElC,QAAM,kBAAkB,QAAQ,mBAAmB,CAAC,QAAQ;AAE5D,SAAO,IAAI,qBAAO;AAAA,IAChB,KAAK;AAAA,IACL,GAAI,kBACA,CAAC,IACD;AAAA,MACE,OAAO,4BAA4B,EAAE,QAAQ,SAAS,cAAc,CAAC;AAAA,IACvE;AAAA,IACJ,OAAO;AAAA,MACL,aAAa,kBACT,CAAC,EAAE,KAAK,UAAU,MAChB,4BAA4B,EAAE,QAAQ,SAAS,eAAe,KAAK,UAAU,CAAC,IAChF,WAAS;AAvCnB;AAwCY,YAAI,QAAQ,wBAAwB,CAAC,OAAO,YAAY;AACtD,iBAAO,2BAAc;AAAA,QACvB;AAEA,gBAAO,gBAAW,SAAS,KAAK,MAAzB,YAA8B,2BAAc;AAAA,MACrD;AAAA,IACN;AAAA,EACF,CAAC;AACH;;;ADrCO,IAAM,cAAc,uBAAU,OAA2B;AAAA,EAC9D,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,aAAa;AAAA,MACb,sBAAsB;AAAA,MACtB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO,CAAC,wBAAwB,EAAE,QAAQ,KAAK,QAAQ,SAAS,KAAK,QAAQ,CAAC,CAAC;AAAA,EACjF;AACF,CAAC;","names":["import_core","import_state","import_view","import_view","import_core","import_view","node"]}
@@ -1,16 +1,13 @@
1
1
  import { PluginKey } from '@tiptap/pm/state';
2
+ import { DecorationSet } from '@tiptap/pm/view';
2
3
  import { Editor, Extension } from '@tiptap/core';
3
4
  import { Node } from '@tiptap/pm/model';
4
5
 
5
- /**
6
- * The viewport positions tracked by the placeholder plugin.
7
- * `null` means "no viewport info yet" the decoration callback falls back to
8
- * a full document scan until the scroll handler fires.
9
- */
10
- interface ViewportState {
11
- topPos: number | null;
12
- bottomPos: number | null;
13
- }
6
+ /** The default data attribute label */
7
+ declare const DEFAULT_DATA_ATTRIBUTE = "placeholder";
8
+ /** The plugin key used to store and read the placeholder decoration set */
9
+ declare const PLUGIN_KEY: PluginKey<DecorationSet>;
10
+
14
11
  interface PlaceholderOptions {
15
12
  /**
16
13
  * **The class name for the empty editor**
@@ -73,11 +70,6 @@ interface PlaceholderOptions {
73
70
  includeChildren: boolean;
74
71
  }
75
72
 
76
- /** The default data attribute label */
77
- declare const DEFAULT_DATA_ATTRIBUTE = "placeholder";
78
- /** The plugin key used to store and read the placeholder viewport state */
79
- declare const PLUGIN_KEY: PluginKey<ViewportState>;
80
-
81
73
  /**
82
74
  * This extension allows you to add a placeholder to your editor.
83
75
  * A placeholder is a text that appears when the editor or a node is empty.
@@ -92,4 +84,4 @@ declare const Placeholder: Extension<PlaceholderOptions, any>;
92
84
  */
93
85
  declare function preparePlaceholderAttribute(attr: string): string;
94
86
 
95
- export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY, Placeholder, type PlaceholderOptions, type ViewportState, preparePlaceholderAttribute };
87
+ export { DEFAULT_DATA_ATTRIBUTE, PLUGIN_KEY, Placeholder, type PlaceholderOptions, preparePlaceholderAttribute };