@tiptap/suggestion 2.0.0-beta.90 → 2.0.0-beta.95

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.
@@ -1,7 +1,7 @@
1
1
  import { Editor, Range } from '@tiptap/core';
2
2
  import { EditorState, Plugin, PluginKey } from 'prosemirror-state';
3
3
  import { EditorView } from 'prosemirror-view';
4
- export interface SuggestionOptions {
4
+ export interface SuggestionOptions<I = any> {
5
5
  pluginKey?: PluginKey;
6
6
  editor: Editor;
7
7
  char?: string;
@@ -13,16 +13,18 @@ export interface SuggestionOptions {
13
13
  command?: (props: {
14
14
  editor: Editor;
15
15
  range: Range;
16
- props: any;
16
+ props: I;
17
17
  }) => void;
18
18
  items?: (props: {
19
19
  query: string;
20
20
  editor: Editor;
21
- }) => any[] | Promise<any[]>;
21
+ }) => I[] | Promise<I[]>;
22
22
  render?: () => {
23
- onStart?: (props: SuggestionProps) => void;
24
- onUpdate?: (props: SuggestionProps) => void;
25
- onExit?: (props: SuggestionProps) => void;
23
+ onBeforeStart?: (props: SuggestionProps<I>) => void;
24
+ onStart?: (props: SuggestionProps<I>) => void;
25
+ onBeforeUpdate?: (props: SuggestionProps<I>) => void;
26
+ onUpdate?: (props: SuggestionProps<I>) => void;
27
+ onExit?: (props: SuggestionProps<I>) => void;
26
28
  onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
27
29
  };
28
30
  allow?: (props: {
@@ -31,15 +33,15 @@ export interface SuggestionOptions {
31
33
  range: Range;
32
34
  }) => boolean;
33
35
  }
34
- export interface SuggestionProps {
36
+ export interface SuggestionProps<I = any> {
35
37
  editor: Editor;
36
38
  range: Range;
37
39
  query: string;
38
40
  text: string;
39
- items: any[];
40
- command: (props: any) => void;
41
+ items: I[];
42
+ command: (props: I) => void;
41
43
  decorationNode: Element | null;
42
- clientRect: (() => DOMRect) | null;
44
+ clientRect?: (() => DOMRect | null) | null;
43
45
  }
44
46
  export interface SuggestionKeyDownProps {
45
47
  view: EditorView;
@@ -47,4 +49,4 @@ export interface SuggestionKeyDownProps {
47
49
  range: Range;
48
50
  }
49
51
  export declare const SuggestionPluginKey: PluginKey<any, any>;
50
- export declare function Suggestion({ pluginKey, editor, char, allowSpaces, prefixSpace, startOfLine, decorationTag, decorationClass, command, items, render, allow, }: SuggestionOptions): Plugin<any, any>;
52
+ export declare function Suggestion<I = any>({ pluginKey, editor, char, allowSpaces, prefixSpace, startOfLine, decorationTag, decorationClass, command, items, render, allow, }: SuggestionOptions<I>): Plugin<any, any>;
@@ -7,6 +7,7 @@ var prosemirrorView = require('prosemirror-view');
7
7
  var core = require('@tiptap/core');
8
8
 
9
9
  function findSuggestionMatch(config) {
10
+ var _a;
10
11
  const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
11
12
  const escapedChar = core.escapeForRegEx(char);
12
13
  const suffix = new RegExp(`\\s${escapedChar}$`);
@@ -14,12 +15,11 @@ function findSuggestionMatch(config) {
14
15
  const regexp = allowSpaces
15
16
  ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
16
17
  : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
17
- const isTopLevelNode = $position.depth <= 0;
18
- const textFrom = isTopLevelNode
19
- ? 0
20
- : $position.before();
21
- const textTo = $position.pos;
22
- const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
18
+ const text = ((_a = $position.nodeBefore) === null || _a === void 0 ? void 0 : _a.isText) && $position.nodeBefore.text;
19
+ if (!text) {
20
+ return null;
21
+ }
22
+ const textFrom = $position.pos - text.length;
23
23
  const match = Array.from(text.matchAll(regexp)).pop();
24
24
  if (!match || match.input === undefined || match.index === undefined) {
25
25
  return null;
@@ -32,7 +32,7 @@ function findSuggestionMatch(config) {
32
32
  return null;
33
33
  }
34
34
  // The absolute position of the match in the document
35
- const from = match.index + $position.start();
35
+ const from = textFrom + match.index;
36
36
  let to = from + match[0].length;
37
37
  // Edge case handling; if spaces are allowed and we're directly in between
38
38
  // two triggers
@@ -63,7 +63,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
63
63
  view() {
64
64
  return {
65
65
  update: async (view, prevState) => {
66
- var _a, _b, _c, _d, _e;
66
+ var _a, _b, _c, _d, _e, _f, _g;
67
67
  const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
68
68
  const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
69
69
  // See how the state changed
@@ -87,12 +87,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
87
87
  range: state.range,
88
88
  query: state.query,
89
89
  text: state.text,
90
- items: (handleChange || handleStart)
91
- ? await items({
92
- editor,
93
- query: state.query,
94
- })
95
- : [],
90
+ items: [],
96
91
  command: commandProps => {
97
92
  command({
98
93
  editor,
@@ -107,21 +102,32 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
107
102
  ? () => {
108
103
  var _a;
109
104
  // because of `items` can be asynchrounous we’ll search for the current docoration node
110
- const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state);
105
+ const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state); // eslint-disable-line
111
106
  const currentDecorationNode = document.querySelector(`[data-decoration-id="${decorationId}"]`);
112
- // @ts-ignore-error
113
- return currentDecorationNode.getBoundingClientRect();
107
+ return (currentDecorationNode === null || currentDecorationNode === void 0 ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
114
108
  }
115
109
  : null,
116
110
  };
111
+ if (handleStart) {
112
+ (_c = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeStart) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
113
+ }
114
+ if (handleChange) {
115
+ (_d = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
116
+ }
117
+ if (handleChange || handleStart) {
118
+ props.items = await items({
119
+ editor,
120
+ query: state.query,
121
+ });
122
+ }
117
123
  if (handleExit) {
118
- (_c = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
124
+ (_e = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
119
125
  }
120
126
  if (handleChange) {
121
- (_d = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
127
+ (_f = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _f === void 0 ? void 0 : _f.call(renderer, props);
122
128
  }
123
129
  if (handleStart) {
124
- (_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
130
+ (_g = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _g === void 0 ? void 0 : _g.call(renderer, props);
125
131
  }
126
132
  },
127
133
  destroy: () => {
@@ -146,14 +152,16 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
146
152
  },
147
153
  // Apply changes to the plugin state from a view transaction.
148
154
  apply(transaction, prev, oldState, state) {
155
+ const { isEditable } = editor;
149
156
  const { composing } = editor.view;
150
157
  const { selection } = transaction;
151
158
  const { empty, from } = selection;
152
159
  const next = { ...prev };
153
160
  next.composing = composing;
154
- // We can only be suggesting if there is no selection
155
- // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
156
- if (empty || editor.view.composing) {
161
+ // We can only be suggesting if the view is editable, and:
162
+ // * there is no selection, or
163
+ // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
164
+ if (isEditable && (empty || editor.view.composing)) {
157
165
  // Reset active state if we just left the previous suggestion range
158
166
  if ((from < prev.range.from || from > prev.range.to)
159
167
  && !composing
@@ -1 +1 @@
1
- {"version":3,"file":"tiptap-suggestion.cjs.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range, escapeForRegEx } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n const escapedChar = escapeForRegEx(char)\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const isTopLevelNode = $position.depth <= 0\n const textFrom = isTopLevelNode\n ? 0\n : $position.before()\n const textTo = $position.pos\n const text = $position.doc.textBetween(textFrom, textTo, '\\0', '\\0')\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = match.index + $position.start()\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n let props: SuggestionProps | undefined\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: (handleChange || handleStart)\n ? await items({\n editor,\n query: state.query,\n })\n : [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state)\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n // @ts-ignore-error\n return currentDecorationNode.getBoundingClientRect()\n }\n : null,\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev, oldState, state) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, state, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
1
+ {"version":3,"file":"tiptap-suggestion.cjs.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { escapeForRegEx, Range } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n const escapedChar = escapeForRegEx(char)\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const text = $position.nodeBefore?.isText && $position.nodeBefore.text\n\n if (!text) {\n return null\n }\n\n const textFrom = $position.pos - text.length\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = textFrom + match.index\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\n\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions<I = any> {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: I,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => I[] | Promise<I[]>,\n render?: () => {\n onBeforeStart?: (props: SuggestionProps<I>) => void\n onStart?: (props: SuggestionProps<I>) => void,\n onBeforeUpdate?: (props: SuggestionProps<I>) => void\n onUpdate?: (props: SuggestionProps<I>) => void,\n onExit?: (props: SuggestionProps<I>) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps<I = any> {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: I[],\n command: (props: I) => void,\n decorationNode: Element | null,\n clientRect?: (() => DOMRect | null) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion<I = any>({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions<I>) {\n\n let props: SuggestionProps<I> | undefined\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state) // eslint-disable-line\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n return currentDecorationNode?.getBoundingClientRect() || null\n }\n : null,\n }\n\n if (handleStart) {\n renderer?.onBeforeStart?.(props)\n }\n\n if (handleChange) {\n renderer?.onBeforeUpdate?.(props)\n }\n\n if (handleChange || handleStart) {\n props.items = await items({\n editor,\n query: state.query,\n })\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev, oldState, state) {\n const { isEditable } = editor\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if the view is editable, and:\n // * there is no selection, or\n // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (isEditable && (empty || editor.view.composing)) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, state, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { DecorationSet, Decoration } from 'prosemirror-view';
3
3
  import { escapeForRegEx } from '@tiptap/core';
4
4
 
5
5
  function findSuggestionMatch(config) {
6
+ var _a;
6
7
  const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
7
8
  const escapedChar = escapeForRegEx(char);
8
9
  const suffix = new RegExp(`\\s${escapedChar}$`);
@@ -10,12 +11,11 @@ function findSuggestionMatch(config) {
10
11
  const regexp = allowSpaces
11
12
  ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
12
13
  : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
13
- const isTopLevelNode = $position.depth <= 0;
14
- const textFrom = isTopLevelNode
15
- ? 0
16
- : $position.before();
17
- const textTo = $position.pos;
18
- const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
14
+ const text = ((_a = $position.nodeBefore) === null || _a === void 0 ? void 0 : _a.isText) && $position.nodeBefore.text;
15
+ if (!text) {
16
+ return null;
17
+ }
18
+ const textFrom = $position.pos - text.length;
19
19
  const match = Array.from(text.matchAll(regexp)).pop();
20
20
  if (!match || match.input === undefined || match.index === undefined) {
21
21
  return null;
@@ -28,7 +28,7 @@ function findSuggestionMatch(config) {
28
28
  return null;
29
29
  }
30
30
  // The absolute position of the match in the document
31
- const from = match.index + $position.start();
31
+ const from = textFrom + match.index;
32
32
  let to = from + match[0].length;
33
33
  // Edge case handling; if spaces are allowed and we're directly in between
34
34
  // two triggers
@@ -59,7 +59,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
59
59
  view() {
60
60
  return {
61
61
  update: async (view, prevState) => {
62
- var _a, _b, _c, _d, _e;
62
+ var _a, _b, _c, _d, _e, _f, _g;
63
63
  const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
64
64
  const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
65
65
  // See how the state changed
@@ -83,12 +83,7 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
83
83
  range: state.range,
84
84
  query: state.query,
85
85
  text: state.text,
86
- items: (handleChange || handleStart)
87
- ? await items({
88
- editor,
89
- query: state.query,
90
- })
91
- : [],
86
+ items: [],
92
87
  command: commandProps => {
93
88
  command({
94
89
  editor,
@@ -103,21 +98,32 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
103
98
  ? () => {
104
99
  var _a;
105
100
  // because of `items` can be asynchrounous we’ll search for the current docoration node
106
- const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state);
101
+ const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state); // eslint-disable-line
107
102
  const currentDecorationNode = document.querySelector(`[data-decoration-id="${decorationId}"]`);
108
- // @ts-ignore-error
109
- return currentDecorationNode.getBoundingClientRect();
103
+ return (currentDecorationNode === null || currentDecorationNode === void 0 ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
110
104
  }
111
105
  : null,
112
106
  };
107
+ if (handleStart) {
108
+ (_c = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeStart) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
109
+ }
110
+ if (handleChange) {
111
+ (_d = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
112
+ }
113
+ if (handleChange || handleStart) {
114
+ props.items = await items({
115
+ editor,
116
+ query: state.query,
117
+ });
118
+ }
113
119
  if (handleExit) {
114
- (_c = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
120
+ (_e = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
115
121
  }
116
122
  if (handleChange) {
117
- (_d = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
123
+ (_f = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _f === void 0 ? void 0 : _f.call(renderer, props);
118
124
  }
119
125
  if (handleStart) {
120
- (_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
126
+ (_g = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _g === void 0 ? void 0 : _g.call(renderer, props);
121
127
  }
122
128
  },
123
129
  destroy: () => {
@@ -142,14 +148,16 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = '@', allow
142
148
  },
143
149
  // Apply changes to the plugin state from a view transaction.
144
150
  apply(transaction, prev, oldState, state) {
151
+ const { isEditable } = editor;
145
152
  const { composing } = editor.view;
146
153
  const { selection } = transaction;
147
154
  const { empty, from } = selection;
148
155
  const next = { ...prev };
149
156
  next.composing = composing;
150
- // We can only be suggesting if there is no selection
151
- // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
152
- if (empty || editor.view.composing) {
157
+ // We can only be suggesting if the view is editable, and:
158
+ // * there is no selection, or
159
+ // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
160
+ if (isEditable && (empty || editor.view.composing)) {
153
161
  // Reset active state if we just left the previous suggestion range
154
162
  if ((from < prev.range.from || from > prev.range.to)
155
163
  && !composing
@@ -1 +1 @@
1
- {"version":3,"file":"tiptap-suggestion.esm.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range, escapeForRegEx } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n const escapedChar = escapeForRegEx(char)\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const isTopLevelNode = $position.depth <= 0\n const textFrom = isTopLevelNode\n ? 0\n : $position.before()\n const textTo = $position.pos\n const text = $position.doc.textBetween(textFrom, textTo, '\\0', '\\0')\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = match.index + $position.start()\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n let props: SuggestionProps | undefined\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: (handleChange || handleStart)\n ? await items({\n editor,\n query: state.query,\n })\n : [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state)\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n // @ts-ignore-error\n return currentDecorationNode.getBoundingClientRect()\n }\n : null,\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev, oldState, state) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, state, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tiptap-suggestion.esm.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { escapeForRegEx, Range } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n const escapedChar = escapeForRegEx(char)\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const text = $position.nodeBefore?.isText && $position.nodeBefore.text\n\n if (!text) {\n return null\n }\n\n const textFrom = $position.pos - text.length\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = textFrom + match.index\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\n\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions<I = any> {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: I,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => I[] | Promise<I[]>,\n render?: () => {\n onBeforeStart?: (props: SuggestionProps<I>) => void\n onStart?: (props: SuggestionProps<I>) => void,\n onBeforeUpdate?: (props: SuggestionProps<I>) => void\n onUpdate?: (props: SuggestionProps<I>) => void,\n onExit?: (props: SuggestionProps<I>) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps<I = any> {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: I[],\n command: (props: I) => void,\n decorationNode: Element | null,\n clientRect?: (() => DOMRect | null) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion<I = any>({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions<I>) {\n\n let props: SuggestionProps<I> | undefined\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state) // eslint-disable-line\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n return currentDecorationNode?.getBoundingClientRect() || null\n }\n : null,\n }\n\n if (handleStart) {\n renderer?.onBeforeStart?.(props)\n }\n\n if (handleChange) {\n renderer?.onBeforeUpdate?.(props)\n }\n\n if (handleChange || handleStart) {\n props.items = await items({\n editor,\n query: state.query,\n })\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev, oldState, state) {\n const { isEditable } = editor\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if the view is editable, and:\n // * there is no selection, or\n // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (isEditable && (empty || editor.view.composing)) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, state, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@
5
5
  })(this, (function (exports, prosemirrorState, prosemirrorView, core) { 'use strict';
6
6
 
7
7
  function findSuggestionMatch(config) {
8
+ var _a;
8
9
  const { char, allowSpaces, prefixSpace, startOfLine, $position, } = config;
9
10
  const escapedChar = core.escapeForRegEx(char);
10
11
  const suffix = new RegExp(`\\s${escapedChar}$`);
@@ -12,12 +13,11 @@
12
13
  const regexp = allowSpaces
13
14
  ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
14
15
  : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm');
15
- const isTopLevelNode = $position.depth <= 0;
16
- const textFrom = isTopLevelNode
17
- ? 0
18
- : $position.before();
19
- const textTo = $position.pos;
20
- const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
16
+ const text = ((_a = $position.nodeBefore) === null || _a === void 0 ? void 0 : _a.isText) && $position.nodeBefore.text;
17
+ if (!text) {
18
+ return null;
19
+ }
20
+ const textFrom = $position.pos - text.length;
21
21
  const match = Array.from(text.matchAll(regexp)).pop();
22
22
  if (!match || match.input === undefined || match.index === undefined) {
23
23
  return null;
@@ -30,7 +30,7 @@
30
30
  return null;
31
31
  }
32
32
  // The absolute position of the match in the document
33
- const from = match.index + $position.start();
33
+ const from = textFrom + match.index;
34
34
  let to = from + match[0].length;
35
35
  // Edge case handling; if spaces are allowed and we're directly in between
36
36
  // two triggers
@@ -61,7 +61,7 @@
61
61
  view() {
62
62
  return {
63
63
  update: async (view, prevState) => {
64
- var _a, _b, _c, _d, _e;
64
+ var _a, _b, _c, _d, _e, _f, _g;
65
65
  const prev = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(prevState);
66
66
  const next = (_b = this.key) === null || _b === void 0 ? void 0 : _b.getState(view.state);
67
67
  // See how the state changed
@@ -85,12 +85,7 @@
85
85
  range: state.range,
86
86
  query: state.query,
87
87
  text: state.text,
88
- items: (handleChange || handleStart)
89
- ? await items({
90
- editor,
91
- query: state.query,
92
- })
93
- : [],
88
+ items: [],
94
89
  command: commandProps => {
95
90
  command({
96
91
  editor,
@@ -105,21 +100,32 @@
105
100
  ? () => {
106
101
  var _a;
107
102
  // because of `items` can be asynchrounous we’ll search for the current docoration node
108
- const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state);
103
+ const { decorationId } = (_a = this.key) === null || _a === void 0 ? void 0 : _a.getState(editor.state); // eslint-disable-line
109
104
  const currentDecorationNode = document.querySelector(`[data-decoration-id="${decorationId}"]`);
110
- // @ts-ignore-error
111
- return currentDecorationNode.getBoundingClientRect();
105
+ return (currentDecorationNode === null || currentDecorationNode === void 0 ? void 0 : currentDecorationNode.getBoundingClientRect()) || null;
112
106
  }
113
107
  : null,
114
108
  };
109
+ if (handleStart) {
110
+ (_c = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeStart) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
111
+ }
112
+ if (handleChange) {
113
+ (_d = renderer === null || renderer === void 0 ? void 0 : renderer.onBeforeUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
114
+ }
115
+ if (handleChange || handleStart) {
116
+ props.items = await items({
117
+ editor,
118
+ query: state.query,
119
+ });
120
+ }
115
121
  if (handleExit) {
116
- (_c = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _c === void 0 ? void 0 : _c.call(renderer, props);
122
+ (_e = renderer === null || renderer === void 0 ? void 0 : renderer.onExit) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
117
123
  }
118
124
  if (handleChange) {
119
- (_d = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _d === void 0 ? void 0 : _d.call(renderer, props);
125
+ (_f = renderer === null || renderer === void 0 ? void 0 : renderer.onUpdate) === null || _f === void 0 ? void 0 : _f.call(renderer, props);
120
126
  }
121
127
  if (handleStart) {
122
- (_e = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _e === void 0 ? void 0 : _e.call(renderer, props);
128
+ (_g = renderer === null || renderer === void 0 ? void 0 : renderer.onStart) === null || _g === void 0 ? void 0 : _g.call(renderer, props);
123
129
  }
124
130
  },
125
131
  destroy: () => {
@@ -144,14 +150,16 @@
144
150
  },
145
151
  // Apply changes to the plugin state from a view transaction.
146
152
  apply(transaction, prev, oldState, state) {
153
+ const { isEditable } = editor;
147
154
  const { composing } = editor.view;
148
155
  const { selection } = transaction;
149
156
  const { empty, from } = selection;
150
157
  const next = { ...prev };
151
158
  next.composing = composing;
152
- // We can only be suggesting if there is no selection
153
- // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
154
- if (empty || editor.view.composing) {
159
+ // We can only be suggesting if the view is editable, and:
160
+ // * there is no selection, or
161
+ // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
162
+ if (isEditable && (empty || editor.view.composing)) {
155
163
  // Reset active state if we just left the previous suggestion range
156
164
  if ((from < prev.range.from || from > prev.range.to)
157
165
  && !composing
@@ -1 +1 @@
1
- {"version":3,"file":"tiptap-suggestion.umd.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { Range, escapeForRegEx } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n const escapedChar = escapeForRegEx(char)\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const isTopLevelNode = $position.depth <= 0\n const textFrom = isTopLevelNode\n ? 0\n : $position.before()\n const textTo = $position.pos\n const text = $position.doc.textBetween(textFrom, textTo, '\\0', '\\0')\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = match.index + $position.start()\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: any,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => any[] | Promise<any[]>,\n render?: () => {\n onStart?: (props: SuggestionProps) => void,\n onUpdate?: (props: SuggestionProps) => void,\n onExit?: (props: SuggestionProps) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: any[],\n command: (props: any) => void,\n decorationNode: Element | null,\n clientRect: (() => DOMRect) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions) {\n\n let props: SuggestionProps | undefined\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: (handleChange || handleStart)\n ? await items({\n editor,\n query: state.query,\n })\n : [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state)\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n // @ts-ignore-error\n return currentDecorationNode.getBoundingClientRect()\n }\n : null,\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev, oldState, state) {\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if there is no selection\n // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (empty || editor.view.composing) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, state, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
1
+ {"version":3,"file":"tiptap-suggestion.umd.js","sources":["../src/findSuggestionMatch.ts","../src/suggestion.ts"],"sourcesContent":["import { escapeForRegEx, Range } from '@tiptap/core'\nimport { ResolvedPos } from 'prosemirror-model'\n\nexport interface Trigger {\n char: string,\n allowSpaces: boolean,\n prefixSpace: boolean,\n startOfLine: boolean,\n $position: ResolvedPos,\n}\n\nexport type SuggestionMatch = {\n range: Range,\n query: string,\n text: string,\n} | null\n\nexport function findSuggestionMatch(config: Trigger): SuggestionMatch {\n const {\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position,\n } = config\n\n const escapedChar = escapeForRegEx(char)\n const suffix = new RegExp(`\\\\s${escapedChar}$`)\n const prefix = startOfLine ? '^' : ''\n const regexp = allowSpaces\n ? new RegExp(`${prefix}${escapedChar}.*?(?=\\\\s${escapedChar}|$)`, 'gm')\n : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\\\s${escapedChar}]*`, 'gm')\n\n const text = $position.nodeBefore?.isText && $position.nodeBefore.text\n\n if (!text) {\n return null\n }\n\n const textFrom = $position.pos - text.length\n const match = Array.from(text.matchAll(regexp)).pop()\n\n if (!match || match.input === undefined || match.index === undefined) {\n return null\n }\n\n // JavaScript doesn't have lookbehinds. This hacks a check that first character\n // is a space or the start of the line\n const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)\n const matchPrefixIsSpace = /^[\\s\\0]?$/.test(matchPrefix)\n\n if (prefixSpace && !matchPrefixIsSpace) {\n return null\n }\n\n // The absolute position of the match in the document\n const from = textFrom + match.index\n let to = from + match[0].length\n\n // Edge case handling; if spaces are allowed and we're directly in between\n // two triggers\n if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {\n match[0] += ' '\n to += 1\n }\n\n // If the $position is located within the matched substring, return that range\n if (from < $position.pos && to >= $position.pos) {\n return {\n range: {\n from,\n to,\n },\n query: match[0].slice(char.length),\n text: match[0],\n }\n }\n\n return null\n}\n","import { Editor, Range } from '@tiptap/core'\nimport { EditorState, Plugin, PluginKey } from 'prosemirror-state'\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view'\n\nimport { findSuggestionMatch } from './findSuggestionMatch'\n\nexport interface SuggestionOptions<I = any> {\n pluginKey?: PluginKey,\n editor: Editor,\n char?: string,\n allowSpaces?: boolean,\n startOfLine?: boolean,\n prefixSpace?: boolean,\n decorationTag?: string,\n decorationClass?: string,\n command?: (props: {\n editor: Editor,\n range: Range,\n props: I,\n }) => void,\n items?: (props: {\n query: string,\n editor: Editor,\n }) => I[] | Promise<I[]>,\n render?: () => {\n onBeforeStart?: (props: SuggestionProps<I>) => void\n onStart?: (props: SuggestionProps<I>) => void,\n onBeforeUpdate?: (props: SuggestionProps<I>) => void\n onUpdate?: (props: SuggestionProps<I>) => void,\n onExit?: (props: SuggestionProps<I>) => void,\n onKeyDown?: (props: SuggestionKeyDownProps) => boolean,\n },\n allow?: (props: {\n editor: Editor,\n state: EditorState,\n range: Range,\n }) => boolean,\n}\n\nexport interface SuggestionProps<I = any> {\n editor: Editor,\n range: Range,\n query: string,\n text: string,\n items: I[],\n command: (props: I) => void,\n decorationNode: Element | null,\n clientRect?: (() => DOMRect | null) | null,\n}\n\nexport interface SuggestionKeyDownProps {\n view: EditorView,\n event: KeyboardEvent,\n range: Range,\n}\n\nexport const SuggestionPluginKey = new PluginKey('suggestion')\n\nexport function Suggestion<I = any>({\n pluginKey = SuggestionPluginKey,\n editor,\n char = '@',\n allowSpaces = false,\n prefixSpace = true,\n startOfLine = false,\n decorationTag = 'span',\n decorationClass = 'suggestion',\n command = () => null,\n items = () => [],\n render = () => ({}),\n allow = () => true,\n}: SuggestionOptions<I>) {\n\n let props: SuggestionProps<I> | undefined\n const renderer = render?.()\n\n return new Plugin({\n key: pluginKey,\n\n view() {\n return {\n update: async (view, prevState) => {\n const prev = this.key?.getState(prevState)\n const next = this.key?.getState(view.state)\n\n // See how the state changed\n const moved = prev.active && next.active && prev.range.from !== next.range.from\n const started = !prev.active && next.active\n const stopped = prev.active && !next.active\n const changed = !started && !stopped && prev.query !== next.query\n const handleStart = started || moved\n const handleChange = changed && !moved\n const handleExit = stopped || moved\n\n // Cancel when suggestion isn't active\n if (!handleStart && !handleChange && !handleExit) {\n return\n }\n\n const state = handleExit && !handleStart\n ? prev\n : next\n const decorationNode = document.querySelector(`[data-decoration-id=\"${state.decorationId}\"]`)\n\n props = {\n editor,\n range: state.range,\n query: state.query,\n text: state.text,\n items: [],\n command: commandProps => {\n command({\n editor,\n range: state.range,\n props: commandProps,\n })\n },\n decorationNode,\n // virtual node for popper.js or tippy.js\n // this can be used for building popups without a DOM node\n clientRect: decorationNode\n ? () => {\n // because of `items` can be asynchrounous we’ll search for the current docoration node\n const { decorationId } = this.key?.getState(editor.state) // eslint-disable-line\n const currentDecorationNode = document.querySelector(`[data-decoration-id=\"${decorationId}\"]`)\n\n return currentDecorationNode?.getBoundingClientRect() || null\n }\n : null,\n }\n\n if (handleStart) {\n renderer?.onBeforeStart?.(props)\n }\n\n if (handleChange) {\n renderer?.onBeforeUpdate?.(props)\n }\n\n if (handleChange || handleStart) {\n props.items = await items({\n editor,\n query: state.query,\n })\n }\n\n if (handleExit) {\n renderer?.onExit?.(props)\n }\n\n if (handleChange) {\n renderer?.onUpdate?.(props)\n }\n\n if (handleStart) {\n renderer?.onStart?.(props)\n }\n },\n\n destroy: () => {\n if (!props) {\n return\n }\n\n renderer?.onExit?.(props)\n },\n }\n },\n\n state: {\n // Initialize the plugin's internal state.\n init() {\n return {\n active: false,\n range: {},\n query: null,\n text: null,\n composing: false,\n }\n },\n\n // Apply changes to the plugin state from a view transaction.\n apply(transaction, prev, oldState, state) {\n const { isEditable } = editor\n const { composing } = editor.view\n const { selection } = transaction\n const { empty, from } = selection\n const next = { ...prev }\n\n next.composing = composing\n\n // We can only be suggesting if the view is editable, and:\n // * there is no selection, or\n // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)\n if (isEditable && (empty || editor.view.composing)) {\n // Reset active state if we just left the previous suggestion range\n if (\n (from < prev.range.from || from > prev.range.to)\n && !composing\n && !prev.composing\n ) {\n next.active = false\n }\n\n // Try to match against where our cursor currently is\n const match = findSuggestionMatch({\n char,\n allowSpaces,\n prefixSpace,\n startOfLine,\n $position: selection.$from,\n })\n const decorationId = `id_${Math.floor(Math.random() * 0xFFFFFFFF)}`\n\n // If we found a match, update the current state to show it\n if (match && allow({ editor, state, range: match.range })) {\n next.active = true\n next.decorationId = prev.decorationId ? prev.decorationId : decorationId\n next.range = match.range\n next.query = match.query\n next.text = match.text\n } else {\n next.active = false\n }\n } else {\n next.active = false\n }\n\n // Make sure to empty the range if suggestion is inactive\n if (!next.active) {\n next.decorationId = null\n next.range = {}\n next.query = null\n next.text = null\n }\n\n return next\n },\n },\n\n props: {\n // Call the keydown hook if suggestion is active.\n handleKeyDown(view, event) {\n const { active, range } = this.getState(view.state)\n\n if (!active) {\n return false\n }\n\n return renderer?.onKeyDown?.({ view, event, range }) || false\n },\n\n // Setup decorator on the currently active suggestion.\n decorations(state) {\n const { active, range, decorationId } = this.getState(state)\n\n if (!active) {\n return null\n }\n\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, {\n nodeName: decorationTag,\n class: decorationClass,\n 'data-decoration-id': decorationId,\n }),\n ])\n },\n },\n })\n}\n"],"names":["escapeForRegEx","PluginKey","Plugin","DecorationSet","Decoration"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/suggestion",
3
3
  "description": "suggestion plugin for tiptap",
4
- "version": "2.0.0-beta.90",
4
+ "version": "2.0.0-beta.95",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -20,6 +20,11 @@
20
20
  "src",
21
21
  "dist"
22
22
  ],
23
+ "devDependencies": {
24
+ "@types/prosemirror-model": "^1.16.1",
25
+ "@types/prosemirror-state": "^1.2.8",
26
+ "@types/prosemirror-view": "^1.23.1"
27
+ },
23
28
  "peerDependencies": {
24
29
  "@tiptap/core": "^2.0.0-beta.1"
25
30
  },
@@ -33,5 +38,5 @@
33
38
  "url": "https://github.com/ueberdosis/tiptap",
34
39
  "directory": "packages/suggestion"
35
40
  },
36
- "gitHead": "82759a898a77aa0bf6a459178021e86b6713ef39"
41
+ "gitHead": "591c0807a2ab5c34b4b7fe12c12511fe4f493ebd"
37
42
  }
@@ -1,4 +1,4 @@
1
- import { Range, escapeForRegEx } from '@tiptap/core'
1
+ import { escapeForRegEx, Range } from '@tiptap/core'
2
2
  import { ResolvedPos } from 'prosemirror-model'
3
3
 
4
4
  export interface Trigger {
@@ -31,12 +31,13 @@ export function findSuggestionMatch(config: Trigger): SuggestionMatch {
31
31
  ? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${escapedChar}|$)`, 'gm')
32
32
  : new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${escapedChar}]*`, 'gm')
33
33
 
34
- const isTopLevelNode = $position.depth <= 0
35
- const textFrom = isTopLevelNode
36
- ? 0
37
- : $position.before()
38
- const textTo = $position.pos
39
- const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0')
34
+ const text = $position.nodeBefore?.isText && $position.nodeBefore.text
35
+
36
+ if (!text) {
37
+ return null
38
+ }
39
+
40
+ const textFrom = $position.pos - text.length
40
41
  const match = Array.from(text.matchAll(regexp)).pop()
41
42
 
42
43
  if (!match || match.input === undefined || match.index === undefined) {
@@ -53,7 +54,7 @@ export function findSuggestionMatch(config: Trigger): SuggestionMatch {
53
54
  }
54
55
 
55
56
  // The absolute position of the match in the document
56
- const from = match.index + $position.start()
57
+ const from = textFrom + match.index
57
58
  let to = from + match[0].length
58
59
 
59
60
  // Edge case handling; if spaces are allowed and we're directly in between
package/src/suggestion.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { Editor, Range } from '@tiptap/core'
2
2
  import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
3
3
  import { Decoration, DecorationSet, EditorView } from 'prosemirror-view'
4
+
4
5
  import { findSuggestionMatch } from './findSuggestionMatch'
5
6
 
6
- export interface SuggestionOptions {
7
+ export interface SuggestionOptions<I = any> {
7
8
  pluginKey?: PluginKey,
8
9
  editor: Editor,
9
10
  char?: string,
@@ -15,16 +16,18 @@ export interface SuggestionOptions {
15
16
  command?: (props: {
16
17
  editor: Editor,
17
18
  range: Range,
18
- props: any,
19
+ props: I,
19
20
  }) => void,
20
21
  items?: (props: {
21
22
  query: string,
22
23
  editor: Editor,
23
- }) => any[] | Promise<any[]>,
24
+ }) => I[] | Promise<I[]>,
24
25
  render?: () => {
25
- onStart?: (props: SuggestionProps) => void,
26
- onUpdate?: (props: SuggestionProps) => void,
27
- onExit?: (props: SuggestionProps) => void,
26
+ onBeforeStart?: (props: SuggestionProps<I>) => void
27
+ onStart?: (props: SuggestionProps<I>) => void,
28
+ onBeforeUpdate?: (props: SuggestionProps<I>) => void
29
+ onUpdate?: (props: SuggestionProps<I>) => void,
30
+ onExit?: (props: SuggestionProps<I>) => void,
28
31
  onKeyDown?: (props: SuggestionKeyDownProps) => boolean,
29
32
  },
30
33
  allow?: (props: {
@@ -34,15 +37,15 @@ export interface SuggestionOptions {
34
37
  }) => boolean,
35
38
  }
36
39
 
37
- export interface SuggestionProps {
40
+ export interface SuggestionProps<I = any> {
38
41
  editor: Editor,
39
42
  range: Range,
40
43
  query: string,
41
44
  text: string,
42
- items: any[],
43
- command: (props: any) => void,
45
+ items: I[],
46
+ command: (props: I) => void,
44
47
  decorationNode: Element | null,
45
- clientRect: (() => DOMRect) | null,
48
+ clientRect?: (() => DOMRect | null) | null,
46
49
  }
47
50
 
48
51
  export interface SuggestionKeyDownProps {
@@ -53,7 +56,7 @@ export interface SuggestionKeyDownProps {
53
56
 
54
57
  export const SuggestionPluginKey = new PluginKey('suggestion')
55
58
 
56
- export function Suggestion({
59
+ export function Suggestion<I = any>({
57
60
  pluginKey = SuggestionPluginKey,
58
61
  editor,
59
62
  char = '@',
@@ -66,9 +69,9 @@ export function Suggestion({
66
69
  items = () => [],
67
70
  render = () => ({}),
68
71
  allow = () => true,
69
- }: SuggestionOptions) {
72
+ }: SuggestionOptions<I>) {
70
73
 
71
- let props: SuggestionProps | undefined
74
+ let props: SuggestionProps<I> | undefined
72
75
  const renderer = render?.()
73
76
 
74
77
  return new Plugin({
@@ -104,12 +107,7 @@ export function Suggestion({
104
107
  range: state.range,
105
108
  query: state.query,
106
109
  text: state.text,
107
- items: (handleChange || handleStart)
108
- ? await items({
109
- editor,
110
- query: state.query,
111
- })
112
- : [],
110
+ items: [],
113
111
  command: commandProps => {
114
112
  command({
115
113
  editor,
@@ -123,15 +121,29 @@ export function Suggestion({
123
121
  clientRect: decorationNode
124
122
  ? () => {
125
123
  // because of `items` can be asynchrounous we’ll search for the current docoration node
126
- const { decorationId } = this.key?.getState(editor.state)
124
+ const { decorationId } = this.key?.getState(editor.state) // eslint-disable-line
127
125
  const currentDecorationNode = document.querySelector(`[data-decoration-id="${decorationId}"]`)
128
126
 
129
- // @ts-ignore-error
130
- return currentDecorationNode.getBoundingClientRect()
127
+ return currentDecorationNode?.getBoundingClientRect() || null
131
128
  }
132
129
  : null,
133
130
  }
134
131
 
132
+ if (handleStart) {
133
+ renderer?.onBeforeStart?.(props)
134
+ }
135
+
136
+ if (handleChange) {
137
+ renderer?.onBeforeUpdate?.(props)
138
+ }
139
+
140
+ if (handleChange || handleStart) {
141
+ props.items = await items({
142
+ editor,
143
+ query: state.query,
144
+ })
145
+ }
146
+
135
147
  if (handleExit) {
136
148
  renderer?.onExit?.(props)
137
149
  }
@@ -169,6 +181,7 @@ export function Suggestion({
169
181
 
170
182
  // Apply changes to the plugin state from a view transaction.
171
183
  apply(transaction, prev, oldState, state) {
184
+ const { isEditable } = editor
172
185
  const { composing } = editor.view
173
186
  const { selection } = transaction
174
187
  const { empty, from } = selection
@@ -176,9 +189,10 @@ export function Suggestion({
176
189
 
177
190
  next.composing = composing
178
191
 
179
- // We can only be suggesting if there is no selection
180
- // or a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
181
- if (empty || editor.view.composing) {
192
+ // We can only be suggesting if the view is editable, and:
193
+ // * there is no selection, or
194
+ // * a composition is active (see: https://github.com/ueberdosis/tiptap/issues/1449)
195
+ if (isEditable && (empty || editor.view.composing)) {
182
196
  // Reset active state if we just left the previous suggestion range
183
197
  if (
184
198
  (from < prev.range.from || from > prev.range.to)