@threadlabs/looma 0.26.14 → 0.26.15

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.
@@ -3,7 +3,7 @@ import { Extension, Mark, Node as Node$1, callOrReturn, canInsertNode, combineTr
3
3
  import { NodeSelection, Plugin, PluginKey, Selection, TextSelection } from "@tiptap/pm/state";
4
4
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
5
5
  import { DOMParser, DOMSerializer, Fragment, Node as Node$2, Slice } from "@tiptap/pm/model";
6
- import { history, redo, undo } from "@tiptap/pm/history";
6
+ import { closeHistory, history, redo, undo } from "@tiptap/pm/history";
7
7
  import { dropCursor } from "@tiptap/pm/dropcursor";
8
8
  import { gapCursor } from "@tiptap/pm/gapcursor";
9
9
  import { createLowlight } from "lowlight";
@@ -662,6 +662,50 @@ var LoomaTableOfContents = Node$1.create({
662
662
  }
663
663
  });
664
664
  //#endregion
665
+ //#region src/editor/extensions/input-rule-history.ts
666
+ /**
667
+ * Keep the literal trigger in typing history before converting it. Tiptap's
668
+ * usual input rule consumes the final character, so simply closing history
669
+ * would restore an incomplete Markdown delimiter on Undo.
670
+ * @invariant Conversion and subsequent typing are separate history events.
671
+ * @lifecycle Tiptap clears its undoable input rule on typing or caret movement.
672
+ */
673
+ var LoomaInputRuleHistory = Extension.create({
674
+ name: "loomaInputRuleHistory",
675
+ priority: 50,
676
+ onBeforeCreate() {
677
+ const props = this.editor.options.editorProps;
678
+ this.editor.setOptions({ editorProps: {
679
+ ...props,
680
+ handleTextInput(view, from, to, text, defaultTransaction) {
681
+ if (props.handleTextInput?.call(props, view, from, to, text, defaultTransaction)) return true;
682
+ if (view.composing) return false;
683
+ const rules = view.state.plugins.find((plugin) => plugin.spec.isInputRules);
684
+ if (!rules?.props.handleTextInput) return false;
685
+ view.dispatch((defaultTransaction?.() ?? view.state.tr.insertText(text, from, to)).scrollIntoView());
686
+ const position = view.state.selection.from;
687
+ rules.props.handleTextInput.call(rules, view, position, position, "", defaultTransaction);
688
+ return true;
689
+ }
690
+ } });
691
+ },
692
+ addKeyboardShortcuts() {
693
+ return { Escape: () => this.editor.commands.undoInputRule() };
694
+ },
695
+ addProseMirrorPlugins() {
696
+ return [new Plugin({
697
+ filterTransaction(tr, state) {
698
+ if (state.plugins.some((plugin) => plugin.spec.isInputRules && tr.getMeta(plugin))) closeHistory(tr);
699
+ return true;
700
+ },
701
+ appendTransaction(transactions, _old, state) {
702
+ if (transactions.some((tr) => state.plugins.some((plugin) => plugin.spec.isInputRules && tr.getMeta(plugin)))) return closeHistory(state.tr);
703
+ return null;
704
+ }
705
+ })];
706
+ }
707
+ });
708
+ //#endregion
665
709
  //#region ../../node_modules/.pnpm/@tiptap+extension-document@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-document/dist/index.js
666
710
  /**
667
711
  * The default document node which represents the top level node of the editor.
@@ -9822,6 +9866,7 @@ function getDefaultEditorExtensions(options = {}) {
9822
9866
  dividerNodeView ? HorizontalRule.extend({ addNodeView: () => dividerNodeView }) : HorizontalRule,
9823
9867
  HardBreak,
9824
9868
  History,
9869
+ LoomaInputRuleHistory,
9825
9870
  Dropcursor,
9826
9871
  Gapcursor,
9827
9872
  Link.configure({
@@ -0,0 +1,9 @@
1
+ import { Extension } from "@tiptap/core";
2
+ /**
3
+ * Keep the literal trigger in typing history before converting it. Tiptap's
4
+ * usual input rule consumes the final character, so simply closing history
5
+ * would restore an incomplete Markdown delimiter on Undo.
6
+ * @invariant Conversion and subsequent typing are separate history events.
7
+ * @lifecycle Tiptap clears its undoable input rule on typing or caret movement.
8
+ */
9
+ export declare const LoomaInputRuleHistory: Extension<any, any>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.26.14",
3
+ "version": "0.26.15",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -370,13 +370,13 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
370
370
  }>, {
371
371
  label: string;
372
372
  placeholder: string;
373
+ extensions: AnyExtension[];
374
+ editable: boolean;
373
375
  linkBaseUrl: string;
374
376
  disableHighlight: boolean;
375
377
  codeLanguages: Readonly<Record<string, import("highlight.js").LanguageFn>> | null;
376
378
  modelValue: JSONContent;
377
- editable: boolean;
378
379
  contentDensity: "compact" | "comfortable";
379
- extensions: AnyExtension[];
380
380
  slashCommands: LoomaEditorSlashCommands;
381
381
  mentionItems: LoomaMentionItem[];
382
382
  mentionProvider: LoomaMentionProvider | undefined;
@@ -64,9 +64,9 @@ export declare const LoomaCompactEditor: import("vue").DefineComponent<import("v
64
64
  }>, {
65
65
  label: string;
66
66
  id: string;
67
- modelValue: JSONContent;
68
- editable: boolean;
69
67
  extensions: AnyExtension[];
68
+ editable: boolean;
69
+ modelValue: JSONContent;
70
70
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
71
71
  /** Renders basic compact JSON as escaped Vue nodes, including during SSR. */
72
72
  export declare const LoomaCompactContent: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
@@ -1,7 +1,8 @@
1
1
  import { defineComponent, h, onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
2
2
  import { Extension, Mark, Node, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
3
3
  import { Editor, EditorContent } from "@tiptap/vue-3";
4
- import { history, redo, undo } from "@tiptap/pm/history";
4
+ import { closeHistory, history, redo, undo } from "@tiptap/pm/history";
5
+ import { Plugin } from "@tiptap/pm/state";
5
6
  //#region ../../node_modules/.pnpm/@tiptap+extension-document@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-document/dist/index.js
6
7
  /**
7
8
  * The default document node which represents the top level node of the editor.
@@ -392,6 +393,50 @@ var History = Extension.create({
392
393
  }
393
394
  });
394
395
  //#endregion
396
+ //#region src/editor/extensions/input-rule-history.ts
397
+ /**
398
+ * Keep the literal trigger in typing history before converting it. Tiptap's
399
+ * usual input rule consumes the final character, so simply closing history
400
+ * would restore an incomplete Markdown delimiter on Undo.
401
+ * @invariant Conversion and subsequent typing are separate history events.
402
+ * @lifecycle Tiptap clears its undoable input rule on typing or caret movement.
403
+ */
404
+ var LoomaInputRuleHistory = Extension.create({
405
+ name: "loomaInputRuleHistory",
406
+ priority: 50,
407
+ onBeforeCreate() {
408
+ const props = this.editor.options.editorProps;
409
+ this.editor.setOptions({ editorProps: {
410
+ ...props,
411
+ handleTextInput(view, from, to, text, defaultTransaction) {
412
+ if (props.handleTextInput?.call(props, view, from, to, text, defaultTransaction)) return true;
413
+ if (view.composing) return false;
414
+ const rules = view.state.plugins.find((plugin) => plugin.spec.isInputRules);
415
+ if (!rules?.props.handleTextInput) return false;
416
+ view.dispatch((defaultTransaction?.() ?? view.state.tr.insertText(text, from, to)).scrollIntoView());
417
+ const position = view.state.selection.from;
418
+ rules.props.handleTextInput.call(rules, view, position, position, "", defaultTransaction);
419
+ return true;
420
+ }
421
+ } });
422
+ },
423
+ addKeyboardShortcuts() {
424
+ return { Escape: () => this.editor.commands.undoInputRule() };
425
+ },
426
+ addProseMirrorPlugins() {
427
+ return [new Plugin({
428
+ filterTransaction(tr, state) {
429
+ if (state.plugins.some((plugin) => plugin.spec.isInputRules && tr.getMeta(plugin))) closeHistory(tr);
430
+ return true;
431
+ },
432
+ appendTransaction(transactions, _old, state) {
433
+ if (transactions.some((tr) => state.plugins.some((plugin) => plugin.spec.isInputRules && tr.getMeta(plugin)))) return closeHistory(state.tr);
434
+ return null;
435
+ }
436
+ })];
437
+ }
438
+ });
439
+ //#endregion
395
440
  //#region src/vue/editor/compact/extensions.ts
396
441
  /** Minimal short-form schema; additional consumer extensions are opt-in. */
397
442
  function getCompactEditorExtensions() {
@@ -403,7 +448,8 @@ function getCompactEditorExtensions() {
403
448
  Italic,
404
449
  Code,
405
450
  HardBreak,
406
- History
451
+ History,
452
+ LoomaInputRuleHistory
407
453
  ];
408
454
  }
409
455
  //#endregion
@@ -2026,11 +2026,14 @@ var LoomaEditor = defineComponent({
2026
2026
  ],
2027
2027
  content: props.modelValue,
2028
2028
  editable: props.editable,
2029
- editorProps: { attributes: {
2030
- role: "textbox",
2031
- "aria-multiline": "true",
2032
- "aria-label": props.label
2033
- } },
2029
+ editorProps: {
2030
+ attributes: {
2031
+ role: "textbox",
2032
+ "aria-multiline": "true",
2033
+ "aria-label": props.label
2034
+ },
2035
+ handleKeyDown: (_view, event) => onFormattingKeyDown(event)
2036
+ },
2034
2037
  onCreate: ({ editor: instance }) => {
2035
2038
  emit("ready", instance);
2036
2039
  instance.state.doc.descendants((node) => {
@@ -2782,13 +2785,14 @@ var LoomaEditor = defineComponent({
2782
2785
  };
2783
2786
  const onFormattingKeyDown = (event) => {
2784
2787
  cancelHeldPress();
2785
- if (event.key !== "Escape" || !floatingFormattingMode() || !(event.target instanceof Node) || !(root.value?.contains(event.target) || formattingPopup?.popper.contains(event.target))) return;
2788
+ if (event.key !== "Escape" || !floatingFormattingMode() || !(event.target instanceof Node) || !(root.value?.contains(event.target) || formattingPopup?.popper.contains(event.target)) || !(formatOpen.value || formattingPopup?.popper.style.visibility === "visible")) return false;
2786
2789
  const selection = editor.value?.state.selection;
2787
2790
  dismissedFormattingSelection = selection instanceof TextSelection || selection instanceof AllSelection ? selection : null;
2788
2791
  heldFormattingSelection = null;
2789
2792
  formatOpen.value = false;
2790
2793
  formattingPopup?.hide();
2791
2794
  hideTool();
2795
+ return true;
2792
2796
  };
2793
2797
  const onResizePointerDown = (event) => {
2794
2798
  tableResizeActive = event.target instanceof HTMLElement && event.target.classList.contains("column-resize-handle");