@threadlabs/looma 0.23.25 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/vue/editor/LoomaEditor.d.ts +4 -3
- package/vue/editor/index.js +86 -3
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./looma-editor.css";
|
|
2
|
-
import type
|
|
2
|
+
import { type AnyExtension, type Editor, type JSONContent } from "@tiptap/core";
|
|
3
3
|
import { type PropType, type VNode } from "vue";
|
|
4
4
|
import { type LoomaSlashCommand, type LoomaMentionItem, type LoomaMentionProvider, type LoomaCodeLanguages } from "@threadlabs/looma/editor";
|
|
5
5
|
import { type LoomaImageActivationDetail, type LoomaImageAttributeResolver, type LoomaImageDescriptor, type LoomaImageRenditionErrorDetail } from "./image-delivery.js";
|
|
@@ -23,8 +23,9 @@ export type LoomaImageUploader = (file: File) => Promise<string | LoomaImageUplo
|
|
|
23
23
|
/**
|
|
24
24
|
* Chooses whether formatting controls follow a selection, occupy persistent
|
|
25
25
|
* editor chrome, or open from an app-owned button (`popover`, with a text-only
|
|
26
|
-
* bubble for selections).
|
|
27
|
-
*
|
|
26
|
+
* bubble for selections). Floating tools open on text selection or a held press
|
|
27
|
+
* and dismiss on Escape/caret movement. `contextual` includes the full command
|
|
28
|
+
* set. Narrow viewports retain a dock; document commands/content are unchanged.
|
|
28
29
|
*/
|
|
29
30
|
export type LoomaEditorToolbarMode = "bubble" | "sticky" | "popover" | "contextual";
|
|
30
31
|
/**
|
package/vue/editor/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { computed, createApp, defineComponent, h, nextTick, onBeforeUnmount, onMounted, reactive, ref, shallowRef, useId, watch } from "vue";
|
|
2
|
-
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, normalizeTableOfContentsConfig, resolveTableCellAt,
|
|
2
|
+
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, normalizeTableOfContentsConfig, resolveTableCellAt, siteRelativeHref } from "@threadlabs/looma/editor";
|
|
3
3
|
import { Button, Card, Checkbox, Cluster, Combobox, Disclosure, EditorInsertTableGrid, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, EditorToolbar as EditorToolbar$1, FormField, Icon, IconButton, Image, Input, InputGroup, List, Menu, MenuItem, Popover, ScrollArea, SearchResultRow, Separator, Stack, Text, Tooltip } from "@threadlabs/looma/vue";
|
|
4
|
+
import { Extension, Node as Node$1, getText, getTextSerializersFromSchema, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
|
|
4
5
|
import { BubbleMenu, EditorContent, useEditor } from "@tiptap/vue-3";
|
|
5
6
|
import { closeHistory } from "@tiptap/pm/history";
|
|
6
7
|
import { createLowlight } from "lowlight";
|
|
7
8
|
import { NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
8
|
-
import { Extension, Node as Node$1, getText, getTextSerializersFromSchema, mergeAttributes, nodeInputRule } from "@tiptap/core";
|
|
9
9
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
10
10
|
import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
|
|
11
11
|
export * from "@threadlabs/looma/editor";
|
|
@@ -1795,6 +1795,21 @@ var LoomaEditor = defineComponent({
|
|
|
1795
1795
|
const mobileToolbarStyle = ref({});
|
|
1796
1796
|
const mobile = ref(typeof window !== "undefined" && window.innerWidth <= 767);
|
|
1797
1797
|
const editorFocused = ref(false);
|
|
1798
|
+
let dismissedFormattingSelection = null;
|
|
1799
|
+
let heldFormattingSelection = null;
|
|
1800
|
+
let formattingPopup = null;
|
|
1801
|
+
let heldPressTimer;
|
|
1802
|
+
let heldPress = null;
|
|
1803
|
+
const floatingFormattingMode = () => props.toolbarMode !== "sticky" && !(props.toolbarMode === "popover" && props.toolbarOpen) && !mobile.value;
|
|
1804
|
+
const cancelHeldPress = () => {
|
|
1805
|
+
clearTimeout(heldPressTimer);
|
|
1806
|
+
heldPressTimer = void 0;
|
|
1807
|
+
heldPress = null;
|
|
1808
|
+
};
|
|
1809
|
+
const formattingSelectionActive = (instance) => {
|
|
1810
|
+
const selection = instance.state.selection;
|
|
1811
|
+
return selection instanceof TextSelection && !dismissedFormattingSelection?.eq(selection) && (!selection.empty || Boolean(heldFormattingSelection?.eq(selection)));
|
|
1812
|
+
};
|
|
1798
1813
|
const editorStateVersion = ref(0);
|
|
1799
1814
|
const mobileToolbarMode = ref("formatting");
|
|
1800
1815
|
let dragLeaveTimer = null;
|
|
@@ -1995,6 +2010,12 @@ var LoomaEditor = defineComponent({
|
|
|
1995
2010
|
onFocus: ({ editor: instance }) => rememberSelection(instance),
|
|
1996
2011
|
onSelectionUpdate: ({ editor: instance }) => {
|
|
1997
2012
|
rememberSelection(instance);
|
|
2013
|
+
if (floatingFormattingMode()) {
|
|
2014
|
+
const selection = instance.state.selection;
|
|
2015
|
+
if (!dismissedFormattingSelection?.eq(selection)) dismissedFormattingSelection = null;
|
|
2016
|
+
if (!heldFormattingSelection?.eq(selection)) heldFormattingSelection = null;
|
|
2017
|
+
if (!formattingSelectionActive(instance)) formattingPopup?.hide();
|
|
2018
|
+
}
|
|
1998
2019
|
if (linkContextEditing.value && instance.state.selection.from !== linkSelection?.from) linkContextEditing.value = false;
|
|
1999
2020
|
},
|
|
2000
2021
|
onTransaction: () => {
|
|
@@ -2442,7 +2463,10 @@ var LoomaEditor = defineComponent({
|
|
|
2442
2463
|
editor.value?.commands.dismissLoomaSlashMenu();
|
|
2443
2464
|
});
|
|
2444
2465
|
watch(() => props.editable, (editable) => {
|
|
2466
|
+
cancelHeldPress();
|
|
2467
|
+
heldFormattingSelection = null;
|
|
2445
2468
|
editor.value?.setEditable(editable);
|
|
2469
|
+
if (!editable) formattingPopup?.hide();
|
|
2446
2470
|
if (editor.value) imageDelivery.reset(editor.value);
|
|
2447
2471
|
root.value?.querySelectorAll("[data-looma-chip]").forEach((chip) => {
|
|
2448
2472
|
chip.setAttribute("aria-disabled", String(!editable));
|
|
@@ -2638,10 +2662,12 @@ var LoomaEditor = defineComponent({
|
|
|
2638
2662
|
updateMobileViewport();
|
|
2639
2663
|
};
|
|
2640
2664
|
const onEditorBlur = () => {
|
|
2665
|
+
cancelHeldPress();
|
|
2641
2666
|
queueMicrotask(updateTableUi);
|
|
2642
2667
|
updateCodeUi();
|
|
2643
2668
|
setTimeout(() => {
|
|
2644
2669
|
if (!tableInteractionActive && !editor.value?.isFocused) editorFocused.value = false;
|
|
2670
|
+
if (!editor.value?.isFocused && !formattingPopup?.popper.contains(document.activeElement)) heldFormattingSelection = null;
|
|
2645
2671
|
}, 0);
|
|
2646
2672
|
};
|
|
2647
2673
|
const bindEditorUi = (instance) => {
|
|
@@ -2667,9 +2693,13 @@ var LoomaEditor = defineComponent({
|
|
|
2667
2693
|
}, { immediate: true });
|
|
2668
2694
|
const onViewportChange = (event) => {
|
|
2669
2695
|
if (event?.type === "scroll" && event.target instanceof Node && (tableToolbarShell.value?.contains(event.target) || tableMenuShell.value?.contains(event.target))) return;
|
|
2696
|
+
cancelHeldPress();
|
|
2697
|
+
const wasMobile = mobile.value;
|
|
2670
2698
|
updateMobileViewport();
|
|
2699
|
+
if (wasMobile !== mobile.value) heldFormattingSelection = null;
|
|
2671
2700
|
updateTableUi();
|
|
2672
2701
|
updateCodeUi();
|
|
2702
|
+
if (floatingFormattingMode() && editor.value && !formattingSelectionActive(editor.value)) formattingPopup?.hide();
|
|
2673
2703
|
tableUi.menuOpen = false;
|
|
2674
2704
|
};
|
|
2675
2705
|
const onDocumentPointerDown = (event) => {
|
|
@@ -2682,6 +2712,40 @@ var LoomaEditor = defineComponent({
|
|
|
2682
2712
|
}, 0);
|
|
2683
2713
|
if ((tableUi.toolbarOpen || tableUi.overlayOpen || tableUi.menuOpen) && !inElement(root.value) && !inTableUi) closeTableUi();
|
|
2684
2714
|
};
|
|
2715
|
+
const onFormattingPointerDown = (event) => {
|
|
2716
|
+
cancelHeldPress();
|
|
2717
|
+
const instance = editor.value;
|
|
2718
|
+
if (!floatingFormattingMode() || tableResizeActive || !props.editable || !instance || event.button !== 0 || !event.isPrimary || !(event.target instanceof Element) || !instance.view.dom.contains(event.target) || event.target.closest("button, input, select, textarea, [data-looma-image-node]")) return;
|
|
2719
|
+
heldPress = {
|
|
2720
|
+
id: event.pointerId,
|
|
2721
|
+
x: event.clientX,
|
|
2722
|
+
y: event.clientY
|
|
2723
|
+
};
|
|
2724
|
+
heldPressTimer = setTimeout(() => {
|
|
2725
|
+
heldPressTimer = void 0;
|
|
2726
|
+
const selection = instance.state.selection;
|
|
2727
|
+
if (!heldPress || !floatingFormattingMode() || tableResizeActive || !props.editable || !instance.isFocused || slash.active || mention.active || !(selection instanceof TextSelection)) return;
|
|
2728
|
+
dismissedFormattingSelection = null;
|
|
2729
|
+
heldFormattingSelection = selection;
|
|
2730
|
+
formattingPopup?.setProps({ getReferenceClientRect: () => posToDOMRect(instance.view, selection.from, selection.to) });
|
|
2731
|
+
formattingPopup?.show();
|
|
2732
|
+
}, 500);
|
|
2733
|
+
};
|
|
2734
|
+
const onFormattingPointerMove = (event) => {
|
|
2735
|
+
if (heldPress?.id === event.pointerId && Math.hypot(event.clientX - heldPress.x, event.clientY - heldPress.y) > 8) cancelHeldPress();
|
|
2736
|
+
};
|
|
2737
|
+
const onFormattingPointerEnd = (event) => {
|
|
2738
|
+
if (heldPress?.id === event.pointerId) cancelHeldPress();
|
|
2739
|
+
};
|
|
2740
|
+
const onFormattingKeyDown = (event) => {
|
|
2741
|
+
cancelHeldPress();
|
|
2742
|
+
if (event.key !== "Escape" || !floatingFormattingMode() || !(event.target instanceof Node) || !(root.value?.contains(event.target) || formattingPopup?.popper.contains(event.target))) return;
|
|
2743
|
+
const selection = editor.value?.state.selection;
|
|
2744
|
+
dismissedFormattingSelection = selection instanceof TextSelection ? selection : null;
|
|
2745
|
+
heldFormattingSelection = null;
|
|
2746
|
+
formattingPopup?.hide();
|
|
2747
|
+
hideTool();
|
|
2748
|
+
};
|
|
2685
2749
|
const onResizePointerDown = (event) => {
|
|
2686
2750
|
tableResizeActive = event.target instanceof HTMLElement && event.target.classList.contains("column-resize-handle");
|
|
2687
2751
|
};
|
|
@@ -2770,6 +2834,11 @@ var LoomaEditor = defineComponent({
|
|
|
2770
2834
|
window.visualViewport?.addEventListener("resize", onViewportChange);
|
|
2771
2835
|
window.visualViewport?.addEventListener("scroll", onViewportChange);
|
|
2772
2836
|
document.addEventListener("pointerdown", onDocumentPointerDown, true);
|
|
2837
|
+
document.addEventListener("pointerdown", onFormattingPointerDown);
|
|
2838
|
+
document.addEventListener("pointermove", onFormattingPointerMove);
|
|
2839
|
+
document.addEventListener("pointerup", onFormattingPointerEnd);
|
|
2840
|
+
document.addEventListener("pointercancel", onFormattingPointerEnd);
|
|
2841
|
+
document.addEventListener("keydown", onFormattingKeyDown);
|
|
2773
2842
|
document.addEventListener("pointerdown", onResizePointerDown, true);
|
|
2774
2843
|
document.addEventListener("pointerup", onResizePointerUp, true);
|
|
2775
2844
|
root.value?.addEventListener("error", onImageError, true);
|
|
@@ -2779,7 +2848,14 @@ var LoomaEditor = defineComponent({
|
|
|
2779
2848
|
window.removeEventListener("scroll", onViewportChange, true);
|
|
2780
2849
|
window.visualViewport?.removeEventListener("resize", onViewportChange);
|
|
2781
2850
|
window.visualViewport?.removeEventListener("scroll", onViewportChange);
|
|
2851
|
+
cancelHeldPress();
|
|
2852
|
+
formattingPopup = null;
|
|
2782
2853
|
document.removeEventListener("pointerdown", onDocumentPointerDown, true);
|
|
2854
|
+
document.removeEventListener("pointerdown", onFormattingPointerDown);
|
|
2855
|
+
document.removeEventListener("pointermove", onFormattingPointerMove);
|
|
2856
|
+
document.removeEventListener("pointerup", onFormattingPointerEnd);
|
|
2857
|
+
document.removeEventListener("pointercancel", onFormattingPointerEnd);
|
|
2858
|
+
document.removeEventListener("keydown", onFormattingKeyDown);
|
|
2783
2859
|
document.removeEventListener("pointerdown", onResizePointerDown, true);
|
|
2784
2860
|
document.removeEventListener("pointerup", onResizePointerUp, true);
|
|
2785
2861
|
root.value?.removeEventListener("error", onImageError, true);
|
|
@@ -3131,8 +3207,15 @@ var LoomaEditor = defineComponent({
|
|
|
3131
3207
|
instance && props.editable && !mobile.value && (props.toolbarMode === "bubble" || props.toolbarMode === "popover" || props.toolbarMode === "contextual") ? h(BubbleMenu, {
|
|
3132
3208
|
editor: instance,
|
|
3133
3209
|
pluginKey: "looma-text-formatting-menu",
|
|
3134
|
-
|
|
3210
|
+
updateDelay: 0,
|
|
3211
|
+
shouldShow: ({ editor: menuEditor }) => (props.toolbarMode !== "popover" || !props.toolbarOpen) && !slash.active && !mention.active && (menuEditor.isFocused || linkContextEditing.value) && formattingSelectionActive(menuEditor),
|
|
3135
3212
|
tippyOptions: {
|
|
3213
|
+
onCreate: (popup) => {
|
|
3214
|
+
formattingPopup = popup;
|
|
3215
|
+
},
|
|
3216
|
+
onDestroy: () => {
|
|
3217
|
+
formattingPopup = null;
|
|
3218
|
+
},
|
|
3136
3219
|
appendTo: () => root.value?.closest("dialog[open], [popover]") ?? document.body,
|
|
3137
3220
|
onShow: () => announceOverlayOpen(root.value?.ownerDocument ?? document, root.value),
|
|
3138
3221
|
duration: 100,
|