@milkdown/plugin-slash 7.3.6 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA"}
package/lib/index.es.js CHANGED
@@ -2,96 +2,98 @@ var $ = (s, e, t) => {
2
2
  if (!e.has(s))
3
3
  throw TypeError("Cannot " + t);
4
4
  };
5
- var o = (s, e, t) => ($(s, e, "read from private field"), t ? t.call(s) : e.get(s)), a = (s, e, t) => {
5
+ var n = (s, e, t) => ($(s, e, "read from private field"), t ? t.call(s) : e.get(s)), a = (s, e, t) => {
6
6
  if (e.has(s))
7
7
  throw TypeError("Cannot add the same private member more than once");
8
8
  e instanceof WeakSet ? e.add(s) : e.set(s, t);
9
- }, c = (s, e, t, n) => ($(s, e, "write to private field"), n ? n.call(s, t) : e.set(s, t), t);
10
- var b = (s, e, t) => ($(s, e, "access private method"), t);
11
- import { Plugin as O, PluginKey as A, TextSelection as M } from "@milkdown/prose/state";
12
- import { $ctx as N, $prose as R } from "@milkdown/utils";
13
- import { posToDOMRect as T, findParentNode as _ } from "@milkdown/prose";
14
- import B from "lodash.debounce";
15
- import F from "tippy.js";
16
- function D(s) {
17
- const e = N({}, `${s}_SLASH_SPEC`), t = R((r) => {
18
- const p = r.get(e.key);
19
- return new O({
20
- key: new A(`${s}_SLASH`),
9
+ }, r = (s, e, t, o) => ($(s, e, "write to private field"), o ? o.call(s, t) : e.set(s, t), t);
10
+ var O = (s, e, t) => ($(s, e, "access private method"), t);
11
+ import { Plugin as B, PluginKey as M, TextSelection as N } from "@milkdown/prose/state";
12
+ import { $ctx as T, $prose as _ } from "@milkdown/utils";
13
+ import { findParentNode as F, posToDOMRect as R } from "@milkdown/prose";
14
+ import q from "lodash.debounce";
15
+ import { computePosition as K, flip as L, offset as j, platform as E } from "@floating-ui/dom";
16
+ import { offsetParent as z } from "composed-offset-position";
17
+ function W(s) {
18
+ const e = T({}, `${s}_SLASH_SPEC`), t = _((i) => {
19
+ const p = i.get(e.key);
20
+ return new B({
21
+ key: new M(`${s}_SLASH`),
21
22
  ...p
22
23
  });
23
- }), n = [e, t];
24
- return n.key = e.key, n.pluginKey = t.key, e.meta = {
24
+ }), o = [e, t];
25
+ return o.key = e.key, o.pluginKey = t.key, e.meta = {
25
26
  package: "@milkdown/plugin-slash",
26
27
  displayName: `Ctx<slashSpec>|${s}`
27
28
  }, t.meta = {
28
29
  package: "@milkdown/plugin-slash",
29
30
  displayName: `Prose<slash>|${s}`
30
- }, n;
31
+ }, o;
31
32
  }
32
- var i, u, d, h, g, f, k, w;
33
- class U {
33
+ var m, f, l, u, d, g, S, H;
34
+ class X {
34
35
  constructor(e) {
35
36
  /// @internal
36
- a(this, k);
37
+ a(this, S);
38
+ a(this, m, void 0);
37
39
  /// @internal
38
- a(this, i, void 0);
40
+ a(this, f, void 0);
39
41
  /// @internal
40
- a(this, u, void 0);
42
+ a(this, l, void 0);
41
43
  /// @internal
44
+ a(this, u, void 0);
45
+ /// The offset to get the block. Default is 0.
42
46
  a(this, d, void 0);
43
- /// @internal
44
- a(this, h, void 0);
45
- /// @internal
46
47
  a(this, g, void 0);
47
- a(this, f, void 0);
48
- c(this, f, (t, n) => {
49
- const { state: r, composing: p } = t, { selection: l, doc: S } = r, { ranges: y } = l, x = Math.min(...y.map((m) => m.$from.pos)), P = Math.max(...y.map((m) => m.$to.pos)), C = n && n.doc.eq(S) && n.selection.eq(l);
50
- if (o(this, i) ?? c(this, i, F(t.dom, {
51
- trigger: "manual",
48
+ r(this, m, !1), this.onShow = () => {
49
+ }, this.onHide = () => {
50
+ }, r(this, g, (t, o) => {
51
+ var b;
52
+ const { state: i, composing: p } = t, { selection: c, doc: P } = i, { ranges: y } = c, k = Math.min(...y.map((h) => h.$from.pos)), x = Math.max(...y.map((h) => h.$to.pos)), w = o && o.doc.eq(P) && o.selection.eq(c);
53
+ if (n(this, m) || ((b = t.dom.parentElement) == null || b.appendChild(this.element), r(this, m, !0)), p || w)
54
+ return;
55
+ if (!n(this, u).call(this, t, o)) {
56
+ this.hide();
57
+ return;
58
+ }
59
+ K({
60
+ getBoundingClientRect: () => R(t, k, x)
61
+ }, this.element, {
52
62
  placement: "bottom-start",
53
- interactive: !0,
54
- delay: 0,
55
- arrow: !1,
56
- duration: 0,
57
- ...o(this, u),
58
- content: this.element
59
- })), !(p || C)) {
60
- if (!o(this, g).call(this, t, n)) {
61
- this.hide();
62
- return;
63
+ middleware: [L(), j(n(this, d))],
64
+ platform: {
65
+ ...E,
66
+ getOffsetParent: (h) => E.getOffsetParent(h, z)
63
67
  }
64
- o(this, i).setProps({
65
- getReferenceClientRect: () => T(t, x, P)
66
- }), this.show();
67
- }
68
- }), this.update = (t, n) => {
69
- B(o(this, f), o(this, d))(t, n);
70
- }, this.getContent = (t, n = (r) => r.type.name === "paragraph") => {
71
- const { selection: r } = t.state, { empty: p, $from: l } = r, S = t.state.selection instanceof M, y = this.element.contains(document.activeElement), x = !t.hasFocus() && !y, P = !t.editable, m = !_(n)(t.state.selection);
72
- if (!(x || P || !p || !S || m))
73
- return l.parent.textBetween(Math.max(0, l.parentOffset - 500), l.parentOffset, void 0, "");
68
+ }).then(({ x: h, y: A }) => {
69
+ Object.assign(this.element.style, {
70
+ left: `${h}px`,
71
+ top: `${A}px`
72
+ });
73
+ }), this.show();
74
+ }), this.update = (t, o) => {
75
+ q(n(this, g), n(this, f))(t, o);
76
+ }, this.getContent = (t, o = (i) => i.type.name === "paragraph") => {
77
+ const { selection: i } = t.state, { empty: p, $from: c } = i, P = t.state.selection instanceof N, y = this.element.contains(document.activeElement), k = !t.hasFocus() && !y, x = !t.editable, C = !F(o)(t.state.selection);
78
+ if (!(k || x || !p || !P || C))
79
+ return c.parent.textBetween(Math.max(0, c.parentOffset - 500), c.parentOffset, void 0, "");
74
80
  }, this.destroy = () => {
75
- var t;
76
- (t = o(this, i)) == null || t.destroy();
77
81
  }, this.show = () => {
78
- var t;
79
- (t = o(this, i)) == null || t.show();
82
+ this.element.dataset.show = "true", this.onShow();
80
83
  }, this.hide = () => {
81
- var t;
82
- (t = o(this, i)) == null || t.hide();
83
- }, this.getInstance = () => o(this, i), this.element = e.content, c(this, u, e.tippyOptions ?? {}), c(this, d, e.debounce ?? 200), c(this, g, e.shouldShow ?? b(this, k, w)), c(this, h, e.trigger ?? "/");
84
+ this.element.dataset.show = "false", this.onHide();
85
+ }, this.element = e.content, r(this, f, e.debounce ?? 200), r(this, u, e.shouldShow ?? O(this, S, H)), r(this, l, e.trigger ?? "/"), r(this, d, e.offset);
84
86
  }
85
87
  }
86
- i = new WeakMap(), u = new WeakMap(), d = new WeakMap(), h = new WeakMap(), g = new WeakMap(), f = new WeakMap(), k = new WeakSet(), w = function(e) {
88
+ m = new WeakMap(), f = new WeakMap(), l = new WeakMap(), u = new WeakMap(), d = new WeakMap(), g = new WeakMap(), S = new WeakSet(), H = function(e) {
87
89
  const t = this.getContent(e);
88
90
  if (!t)
89
91
  return !1;
90
- const n = t.at(-1);
91
- return n ? Array.isArray(o(this, h)) ? o(this, h).includes(n) : o(this, h) === n : !1;
92
+ const o = t.at(-1);
93
+ return o ? Array.isArray(n(this, l)) ? n(this, l).includes(o) : n(this, l) === o : !1;
92
94
  };
93
95
  export {
94
- U as SlashProvider,
95
- D as slashFactory
96
+ X as SlashProvider,
97
+ W as slashFactory
96
98
  };
97
99
  //# sourceMappingURL=index.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","sources":["../src/slash-plugin.ts","../src/slash-provider.ts"],"sourcesContent":["/* Copyright 2021, Milkdown by Mirone. */\nimport type { SliceType } from '@milkdown/ctx'\nimport type { PluginSpec } from '@milkdown/prose/state'\nimport { Plugin, PluginKey } from '@milkdown/prose/state'\nimport type { $Ctx, $Prose } from '@milkdown/utils'\nimport { $ctx, $prose } from '@milkdown/utils'\n\n/// @internal\nexport type SlashPluginSpecId<Id extends string> = `${Id}_SLASH_SPEC`\n\n/// @internal\nexport type SlashPlugin<Id extends string, State = any> = [$Ctx<PluginSpec<State>, SlashPluginSpecId<Id>>, $Prose] & {\n key: SliceType<PluginSpec<State>, SlashPluginSpecId<Id>>\n pluginKey: $Prose['key']\n}\n\n/// Create a slash plugin with a unique id.\nexport function slashFactory<Id extends string, State = any>(id: Id) {\n const slashSpec = $ctx<PluginSpec<State>, SlashPluginSpecId<Id>>({}, `${id}_SLASH_SPEC`)\n const slashPlugin = $prose((ctx) => {\n const spec = ctx.get(slashSpec.key)\n return new Plugin({\n key: new PluginKey(`${id}_SLASH`),\n ...spec,\n })\n })\n const result = [slashSpec, slashPlugin] as SlashPlugin<Id>\n result.key = slashSpec.key\n result.pluginKey = slashPlugin.key\n slashSpec.meta = {\n package: '@milkdown/plugin-slash',\n displayName: `Ctx<slashSpec>|${id}`,\n }\n slashPlugin.meta = {\n package: '@milkdown/plugin-slash',\n displayName: `Prose<slash>|${id}`,\n }\n\n return result\n}\n","/* Copyright 2021, Milkdown by Mirone. */\nimport { findParentNode, posToDOMRect } from '@milkdown/prose'\nimport type { EditorState } from '@milkdown/prose/state'\nimport type { Node } from '@milkdown/prose/model'\nimport { TextSelection } from '@milkdown/prose/state'\nimport type { EditorView } from '@milkdown/prose/view'\nimport debounce from 'lodash.debounce'\nimport type { Instance, Props } from 'tippy.js'\nimport tippy from 'tippy.js'\n\n/// Options for slash provider.\nexport interface SlashProviderOptions {\n /// The slash content.\n content: HTMLElement\n /// The options for creating [tippy.js](https://atomiks.github.io/tippyjs/) instance.\n tippyOptions?: Partial<Props>\n /// The debounce time for updating slash, 200ms by default.\n debounce?: number\n /// The function to determine whether the tooltip should be shown.\n shouldShow?: (view: EditorView, prevState?: EditorState) => boolean\n /// The key trigger for shouldShow, '/' by default.\n trigger?: string | string[]\n}\n\n/// A provider for creating slash.\nexport class SlashProvider {\n /// The root element of the slash.\n element: HTMLElement\n\n /// @internal\n #tippy: Instance | undefined\n\n /// @internal\n #tippyOptions: Partial<Props>\n\n /// @internal\n #debounce: number\n\n /// @internal\n #trigger: string | string[]\n\n /// @internal\n #shouldShow: (view: EditorView, prevState?: EditorState) => boolean\n\n constructor(options: SlashProviderOptions) {\n this.element = options.content\n this.#tippyOptions = options.tippyOptions ?? {}\n this.#debounce = options.debounce ?? 200\n this.#shouldShow = options.shouldShow ?? this.#_shouldShow\n this.#trigger = options.trigger ?? '/'\n }\n\n /// @internal\n #onUpdate = (view: EditorView, prevState?: EditorState): void => {\n const { state, composing } = view\n const { selection, doc } = state\n const { ranges } = selection\n const from = Math.min(...ranges.map(range => range.$from.pos))\n const to = Math.max(...ranges.map(range => range.$to.pos))\n const isSame = prevState && prevState.doc.eq(doc) && prevState.selection.eq(selection)\n\n this.#tippy ??= tippy(view.dom, {\n trigger: 'manual',\n placement: 'bottom-start',\n interactive: true,\n delay: 0,\n arrow: false,\n duration: 0,\n ...this.#tippyOptions,\n content: this.element,\n })\n\n if (composing || isSame)\n return\n\n if (!this.#shouldShow(view, prevState)) {\n this.hide()\n return\n }\n\n this.#tippy.setProps({\n getReferenceClientRect: () => posToDOMRect(view, from, to),\n })\n\n this.show()\n }\n\n /// @internal\n #_shouldShow(view: EditorView): boolean {\n const currentTextBlockContent = this.getContent(view)\n\n if (!currentTextBlockContent)\n return false\n\n const target = currentTextBlockContent.at(-1)\n\n if (!target)\n return false\n\n return Array.isArray(this.#trigger) ? this.#trigger.includes(target) : this.#trigger === target\n }\n\n /// Update provider state by editor view.\n update = (view: EditorView, prevState?: EditorState): void => {\n const updater = debounce(this.#onUpdate, this.#debounce)\n\n updater(view, prevState)\n }\n\n /// Get the content of the current text block.\n /// Pass the `matchNode` function to determine whether the current node should be matched, by default, it will match the paragraph node.\n getContent = (view: EditorView, matchNode: (node: Node) => boolean = node => node.type.name === 'paragraph'): string | undefined => {\n const { selection } = view.state\n const { empty, $from } = selection\n const isTextBlock = view.state.selection instanceof TextSelection\n\n const isSlashChildren = this.element.contains(document.activeElement)\n\n const notHasFocus = !view.hasFocus() && !isSlashChildren\n\n const isReadonly = !view.editable\n\n const paragraph = findParentNode(matchNode)(view.state.selection)\n\n const isNotInParagraph = !paragraph\n\n if (notHasFocus || isReadonly || !empty || !isTextBlock || isNotInParagraph)\n return\n\n return $from.parent.textBetween(Math.max(0, $from.parentOffset - 500), $from.parentOffset, undefined, '\\uFFFC')\n }\n\n /// Destroy the slash.\n destroy = () => {\n this.#tippy?.destroy()\n }\n\n /// Show the slash.\n show = () => {\n this.#tippy?.show()\n }\n\n /// Hide the slash.\n hide = () => {\n this.#tippy?.hide()\n }\n\n /// Get the [tippy.js](https://atomiks.github.io/tippyjs/) instance.\n getInstance = () => this.#tippy\n}\n"],"names":["slashFactory","id","slashSpec","$ctx","slashPlugin","$prose","ctx","spec","Plugin","PluginKey","result","SlashProvider","options","__privateAdd","__shouldShow","_tippy","_tippyOptions","_debounce","_trigger","_shouldShow","_onUpdate","__privateSet","view","prevState","state","composing","selection","doc","ranges","from","range","to","isSame","__privateGet","tippy","posToDOMRect","debounce","matchNode","node","empty","$from","isTextBlock","TextSelection","isSlashChildren","notHasFocus","isReadonly","isNotInParagraph","findParentNode","_a","__privateMethod","_shouldShow_fn","currentTextBlockContent","target"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","sources":["../src/slash-plugin.ts","../src/slash-provider.ts"],"sourcesContent":["import type { SliceType } from '@milkdown/ctx'\nimport type { PluginSpec } from '@milkdown/prose/state'\nimport { Plugin, PluginKey } from '@milkdown/prose/state'\nimport type { $Ctx, $Prose } from '@milkdown/utils'\nimport { $ctx, $prose } from '@milkdown/utils'\n\n/// @internal\nexport type SlashPluginSpecId<Id extends string> = `${Id}_SLASH_SPEC`\n\n/// @internal\nexport type SlashPlugin<Id extends string, State = any> = [$Ctx<PluginSpec<State>, SlashPluginSpecId<Id>>, $Prose] & {\n key: SliceType<PluginSpec<State>, SlashPluginSpecId<Id>>\n pluginKey: $Prose['key']\n}\n\n/// Create a slash plugin with a unique id.\nexport function slashFactory<Id extends string, State = any>(id: Id) {\n const slashSpec = $ctx<PluginSpec<State>, SlashPluginSpecId<Id>>({}, `${id}_SLASH_SPEC`)\n const slashPlugin = $prose((ctx) => {\n const spec = ctx.get(slashSpec.key)\n return new Plugin({\n key: new PluginKey(`${id}_SLASH`),\n ...spec,\n })\n })\n const result = [slashSpec, slashPlugin] as SlashPlugin<Id>\n result.key = slashSpec.key\n result.pluginKey = slashPlugin.key\n slashSpec.meta = {\n package: '@milkdown/plugin-slash',\n displayName: `Ctx<slashSpec>|${id}`,\n }\n slashPlugin.meta = {\n package: '@milkdown/plugin-slash',\n displayName: `Prose<slash>|${id}`,\n }\n\n return result\n}\n","import { findParentNode, posToDOMRect } from '@milkdown/prose'\nimport type { EditorState } from '@milkdown/prose/state'\nimport type { Node } from '@milkdown/prose/model'\nimport { TextSelection } from '@milkdown/prose/state'\nimport type { EditorView } from '@milkdown/prose/view'\nimport debounce from 'lodash.debounce'\nimport type { VirtualElement } from '@floating-ui/dom'\nimport { computePosition, flip, offset, platform } from '@floating-ui/dom'\nimport { offsetParent } from 'composed-offset-position'\n\n/// Options for slash provider.\nexport interface SlashProviderOptions {\n /// The slash content.\n content: HTMLElement\n /// The debounce time for updating slash, 200ms by default.\n debounce?: number\n /// The function to determine whether the tooltip should be shown.\n shouldShow?: (view: EditorView, prevState?: EditorState) => boolean\n /// The key trigger for shouldShow, '/' by default.\n trigger?: string | string[]\n /// The offset to get the block. Default is 0.\n offset?: number | {\n mainAxis?: number\n crossAxis?: number\n alignmentAxis?: number | null\n }\n}\n\n/// A provider for creating slash.\nexport class SlashProvider {\n /// The root element of the slash.\n element: HTMLElement\n\n /// @internal\n #initialized = false\n\n /// @internal\n readonly #debounce: number\n\n /// @internal\n readonly #trigger: string | string[]\n\n /// @internal\n readonly #shouldShow: (view: EditorView, prevState?: EditorState) => boolean\n\n /// The offset to get the block. Default is 0.\n readonly #offset?: number | {\n mainAxis?: number\n crossAxis?: number\n alignmentAxis?: number | null\n }\n\n /// On show callback.\n onShow = () => {}\n\n /// On hide callback.\n onHide = () => {}\n\n constructor(options: SlashProviderOptions) {\n this.element = options.content\n this.#debounce = options.debounce ?? 200\n this.#shouldShow = options.shouldShow ?? this.#_shouldShow\n this.#trigger = options.trigger ?? '/'\n this.#offset = options.offset\n }\n\n /// @internal\n #onUpdate = (view: EditorView, prevState?: EditorState): void => {\n const { state, composing } = view\n const { selection, doc } = state\n const { ranges } = selection\n const from = Math.min(...ranges.map(range => range.$from.pos))\n const to = Math.max(...ranges.map(range => range.$to.pos))\n const isSame = prevState && prevState.doc.eq(doc) && prevState.selection.eq(selection)\n\n if (!this.#initialized) {\n view.dom.parentElement?.appendChild(this.element)\n this.#initialized = true\n }\n\n if (composing || isSame)\n return\n\n if (!this.#shouldShow(view, prevState)) {\n this.hide()\n return\n }\n\n const virtualEl: VirtualElement = {\n getBoundingClientRect: () => posToDOMRect(view, from, to),\n }\n computePosition(virtualEl, this.element, {\n placement: 'bottom-start',\n middleware: [flip(), offset(this.#offset)],\n platform: {\n ...platform,\n getOffsetParent: element =>\n platform.getOffsetParent(element, offsetParent),\n },\n })\n .then(({ x, y }) => {\n Object.assign(this.element.style, {\n left: `${x}px`,\n top: `${y}px`,\n })\n })\n\n this.show()\n }\n\n /// @internal\n #_shouldShow(view: EditorView): boolean {\n const currentTextBlockContent = this.getContent(view)\n\n if (!currentTextBlockContent)\n return false\n\n const target = currentTextBlockContent.at(-1)\n\n if (!target)\n return false\n\n return Array.isArray(this.#trigger) ? this.#trigger.includes(target) : this.#trigger === target\n }\n\n /// Update provider state by editor view.\n update = (view: EditorView, prevState?: EditorState): void => {\n const updater = debounce(this.#onUpdate, this.#debounce)\n\n updater(view, prevState)\n }\n\n /// Get the content of the current text block.\n /// Pass the `matchNode` function to determine whether the current node should be matched, by default, it will match the paragraph node.\n getContent = (view: EditorView, matchNode: (node: Node) => boolean = node => node.type.name === 'paragraph'): string | undefined => {\n const { selection } = view.state\n const { empty, $from } = selection\n const isTextBlock = view.state.selection instanceof TextSelection\n\n const isSlashChildren = this.element.contains(document.activeElement)\n\n const notHasFocus = !view.hasFocus() && !isSlashChildren\n\n const isReadonly = !view.editable\n\n const paragraph = findParentNode(matchNode)(view.state.selection)\n\n const isNotInParagraph = !paragraph\n\n if (notHasFocus || isReadonly || !empty || !isTextBlock || isNotInParagraph)\n return\n\n return $from.parent.textBetween(Math.max(0, $from.parentOffset - 500), $from.parentOffset, undefined, '\\uFFFC')\n }\n\n /// Destroy the slash.\n destroy = () => {\n }\n\n /// Show the slash.\n show = () => {\n this.element.dataset.show = 'true'\n this.onShow()\n }\n\n /// Hide the slash.\n hide = () => {\n this.element.dataset.show = 'false'\n this.onHide()\n }\n}\n"],"names":["slashFactory","id","slashSpec","$ctx","slashPlugin","$prose","ctx","spec","Plugin","PluginKey","result","SlashProvider","options","__privateAdd","__shouldShow","_initialized","_debounce","_trigger","_shouldShow","_offset","_onUpdate","__privateSet","view","prevState","state","composing","selection","doc","ranges","from","range","to","isSame","__privateGet","_a","computePosition","posToDOMRect","flip","offset","platform","element","offsetParent","x","y","debounce","matchNode","node","empty","$from","isTextBlock","TextSelection","isSlashChildren","notHasFocus","isReadonly","isNotInParagraph","findParentNode","__privateMethod","_shouldShow_fn","currentTextBlockContent","target"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"slash-plugin.d.ts","sourceRoot":"","sources":["../src/slash-plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAInD,MAAM,MAAM,iBAAiB,CAAC,EAAE,SAAS,MAAM,IAAI,GAAG,EAAE,aAAa,CAAA;AAGrE,MAAM,MAAM,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,KAAK,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG;IACnH,GAAG,EAAE,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;CACzB,CAAA;AAGD,wBAAgB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,KAAK,GAAG,GAAG,EAAE,EAAE,EAAE,EAAE,wBAsBlE"}
1
+ {"version":3,"file":"slash-plugin.d.ts","sourceRoot":"","sources":["../src/slash-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAInD,MAAM,MAAM,iBAAiB,CAAC,EAAE,SAAS,MAAM,IAAI,GAAG,EAAE,aAAa,CAAA;AAGrE,MAAM,MAAM,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,KAAK,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG;IACnH,GAAG,EAAE,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;CACzB,CAAA;AAGD,wBAAgB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,KAAK,GAAG,GAAG,EAAE,EAAE,EAAE,EAAE,wBAsBlE"}
@@ -1,23 +1,27 @@
1
1
  import type { EditorState } from '@milkdown/prose/state';
2
2
  import type { Node } from '@milkdown/prose/model';
3
3
  import type { EditorView } from '@milkdown/prose/view';
4
- import type { Instance, Props } from 'tippy.js';
5
4
  export interface SlashProviderOptions {
6
5
  content: HTMLElement;
7
- tippyOptions?: Partial<Props>;
8
6
  debounce?: number;
9
7
  shouldShow?: (view: EditorView, prevState?: EditorState) => boolean;
10
8
  trigger?: string | string[];
9
+ offset?: number | {
10
+ mainAxis?: number;
11
+ crossAxis?: number;
12
+ alignmentAxis?: number | null;
13
+ };
11
14
  }
12
15
  export declare class SlashProvider {
13
16
  #private;
14
17
  element: HTMLElement;
18
+ onShow: () => void;
19
+ onHide: () => void;
15
20
  constructor(options: SlashProviderOptions);
16
21
  update: (view: EditorView, prevState?: EditorState) => void;
17
22
  getContent: (view: EditorView, matchNode?: (node: Node) => boolean) => string | undefined;
18
23
  destroy: () => void;
19
24
  show: () => void;
20
25
  hide: () => void;
21
- getInstance: () => Instance<Props> | undefined;
22
26
  }
23
27
  //# sourceMappingURL=slash-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slash-provider.d.ts","sourceRoot":"","sources":["../src/slash-provider.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAI/C,MAAM,WAAW,oBAAoB;IAEnC,OAAO,EAAE,WAAW,CAAA;IAEpB,YAAY,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;IAE7B,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,WAAW,KAAK,OAAO,CAAA;IAEnE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAC5B;AAGD,qBAAa,aAAa;;IAExB,OAAO,EAAE,WAAW,CAAA;gBAiBR,OAAO,EAAE,oBAAoB;IA2DzC,MAAM,SAAU,UAAU,cAAc,WAAW,KAAG,IAAI,CAIzD;IAID,UAAU,SAAU,UAAU,cAAa,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,KAA4C,MAAM,GAAG,SAAS,CAmB/H;IAGD,OAAO,aAEN;IAGD,IAAI,aAEH;IAGD,IAAI,aAEH;IAGD,WAAW,oCAAoB;CAChC"}
1
+ {"version":3,"file":"slash-provider.d.ts","sourceRoot":"","sources":["../src/slash-provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAOtD,MAAM,WAAW,oBAAoB;IAEnC,OAAO,EAAE,WAAW,CAAA;IAEpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,WAAW,KAAK,OAAO,CAAA;IAEnE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAE3B,MAAM,CAAC,EAAE,MAAM,GAAG;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAC9B,CAAA;CACF;AAGD,qBAAa,aAAa;;IAExB,OAAO,EAAE,WAAW,CAAA;IAsBpB,MAAM,aAAW;IAGjB,MAAM,aAAW;gBAEL,OAAO,EAAE,oBAAoB;IAoEzC,MAAM,SAAU,UAAU,cAAc,WAAW,KAAG,IAAI,CAIzD;IAID,UAAU,SAAU,UAAU,cAAa,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,KAA4C,MAAM,GAAG,SAAS,CAmB/H;IAGD,OAAO,aACN;IAGD,IAAI,aAGH;IAGD,IAAI,aAGH;CACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@milkdown/plugin-slash",
3
3
  "type": "module",
4
- "version": "7.3.6",
4
+ "version": "7.4.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -30,17 +30,18 @@
30
30
  "@milkdown/prose": "^7.2.0"
31
31
  },
32
32
  "dependencies": {
33
+ "@floating-ui/dom": "^1.5.1",
33
34
  "@types/lodash.debounce": "^4.0.7",
35
+ "composed-offset-position": "^0.0.4",
34
36
  "lodash.debounce": "^4.0.8",
35
- "tippy.js": "^6.3.7",
36
37
  "tslib": "^2.5.0",
37
- "@milkdown/exception": "7.3.6",
38
- "@milkdown/utils": "7.3.6"
38
+ "@milkdown/exception": "7.4.0",
39
+ "@milkdown/utils": "7.4.0"
39
40
  },
40
41
  "devDependencies": {
41
- "@milkdown/core": "7.3.6",
42
- "@milkdown/ctx": "7.3.6",
43
- "@milkdown/prose": "7.3.6"
42
+ "@milkdown/core": "7.4.0",
43
+ "@milkdown/ctx": "7.4.0",
44
+ "@milkdown/prose": "7.4.0"
44
45
  },
45
46
  "nx": {
46
47
  "targets": {
package/src/index.ts CHANGED
@@ -1,3 +1,2 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
1
  export * from './slash-plugin'
3
2
  export * from './slash-provider'
@@ -1,4 +1,3 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
1
  import type { SliceType } from '@milkdown/ctx'
3
2
  import type { PluginSpec } from '@milkdown/prose/state'
4
3
  import { Plugin, PluginKey } from '@milkdown/prose/state'
@@ -1,25 +1,29 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
1
  import { findParentNode, posToDOMRect } from '@milkdown/prose'
3
2
  import type { EditorState } from '@milkdown/prose/state'
4
3
  import type { Node } from '@milkdown/prose/model'
5
4
  import { TextSelection } from '@milkdown/prose/state'
6
5
  import type { EditorView } from '@milkdown/prose/view'
7
6
  import debounce from 'lodash.debounce'
8
- import type { Instance, Props } from 'tippy.js'
9
- import tippy from 'tippy.js'
7
+ import type { VirtualElement } from '@floating-ui/dom'
8
+ import { computePosition, flip, offset, platform } from '@floating-ui/dom'
9
+ import { offsetParent } from 'composed-offset-position'
10
10
 
11
11
  /// Options for slash provider.
12
12
  export interface SlashProviderOptions {
13
13
  /// The slash content.
14
14
  content: HTMLElement
15
- /// The options for creating [tippy.js](https://atomiks.github.io/tippyjs/) instance.
16
- tippyOptions?: Partial<Props>
17
15
  /// The debounce time for updating slash, 200ms by default.
18
16
  debounce?: number
19
17
  /// The function to determine whether the tooltip should be shown.
20
18
  shouldShow?: (view: EditorView, prevState?: EditorState) => boolean
21
19
  /// The key trigger for shouldShow, '/' by default.
22
20
  trigger?: string | string[]
21
+ /// The offset to get the block. Default is 0.
22
+ offset?: number | {
23
+ mainAxis?: number
24
+ crossAxis?: number
25
+ alignmentAxis?: number | null
26
+ }
23
27
  }
24
28
 
25
29
  /// A provider for creating slash.
@@ -28,26 +32,36 @@ export class SlashProvider {
28
32
  element: HTMLElement
29
33
 
30
34
  /// @internal
31
- #tippy: Instance | undefined
35
+ #initialized = false
32
36
 
33
37
  /// @internal
34
- #tippyOptions: Partial<Props>
38
+ readonly #debounce: number
35
39
 
36
40
  /// @internal
37
- #debounce: number
41
+ readonly #trigger: string | string[]
38
42
 
39
43
  /// @internal
40
- #trigger: string | string[]
44
+ readonly #shouldShow: (view: EditorView, prevState?: EditorState) => boolean
41
45
 
42
- /// @internal
43
- #shouldShow: (view: EditorView, prevState?: EditorState) => boolean
46
+ /// The offset to get the block. Default is 0.
47
+ readonly #offset?: number | {
48
+ mainAxis?: number
49
+ crossAxis?: number
50
+ alignmentAxis?: number | null
51
+ }
52
+
53
+ /// On show callback.
54
+ onShow = () => {}
55
+
56
+ /// On hide callback.
57
+ onHide = () => {}
44
58
 
45
59
  constructor(options: SlashProviderOptions) {
46
60
  this.element = options.content
47
- this.#tippyOptions = options.tippyOptions ?? {}
48
61
  this.#debounce = options.debounce ?? 200
49
62
  this.#shouldShow = options.shouldShow ?? this.#_shouldShow
50
63
  this.#trigger = options.trigger ?? '/'
64
+ this.#offset = options.offset
51
65
  }
52
66
 
53
67
  /// @internal
@@ -59,16 +73,10 @@ export class SlashProvider {
59
73
  const to = Math.max(...ranges.map(range => range.$to.pos))
60
74
  const isSame = prevState && prevState.doc.eq(doc) && prevState.selection.eq(selection)
61
75
 
62
- this.#tippy ??= tippy(view.dom, {
63
- trigger: 'manual',
64
- placement: 'bottom-start',
65
- interactive: true,
66
- delay: 0,
67
- arrow: false,
68
- duration: 0,
69
- ...this.#tippyOptions,
70
- content: this.element,
71
- })
76
+ if (!this.#initialized) {
77
+ view.dom.parentElement?.appendChild(this.element)
78
+ this.#initialized = true
79
+ }
72
80
 
73
81
  if (composing || isSame)
74
82
  return
@@ -78,9 +86,24 @@ export class SlashProvider {
78
86
  return
79
87
  }
80
88
 
81
- this.#tippy.setProps({
82
- getReferenceClientRect: () => posToDOMRect(view, from, to),
89
+ const virtualEl: VirtualElement = {
90
+ getBoundingClientRect: () => posToDOMRect(view, from, to),
91
+ }
92
+ computePosition(virtualEl, this.element, {
93
+ placement: 'bottom-start',
94
+ middleware: [flip(), offset(this.#offset)],
95
+ platform: {
96
+ ...platform,
97
+ getOffsetParent: element =>
98
+ platform.getOffsetParent(element, offsetParent),
99
+ },
83
100
  })
101
+ .then(({ x, y }) => {
102
+ Object.assign(this.element.style, {
103
+ left: `${x}px`,
104
+ top: `${y}px`,
105
+ })
106
+ })
84
107
 
85
108
  this.show()
86
109
  }
@@ -132,19 +155,17 @@ export class SlashProvider {
132
155
 
133
156
  /// Destroy the slash.
134
157
  destroy = () => {
135
- this.#tippy?.destroy()
136
158
  }
137
159
 
138
160
  /// Show the slash.
139
161
  show = () => {
140
- this.#tippy?.show()
162
+ this.element.dataset.show = 'true'
163
+ this.onShow()
141
164
  }
142
165
 
143
166
  /// Hide the slash.
144
167
  hide = () => {
145
- this.#tippy?.hide()
168
+ this.element.dataset.show = 'false'
169
+ this.onHide()
146
170
  }
147
-
148
- /// Get the [tippy.js](https://atomiks.github.io/tippyjs/) instance.
149
- getInstance = () => this.#tippy
150
171
  }