@threadlabs/looma 0.26.5 → 0.26.7
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/components/ui-list-item/ui-list-item.html +9 -6
- package/components/ui-nav-item/ui-nav-item.html +5 -0
- package/components/ui-tree-item/ui-tree-item.html +3 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/styles/ui-list-item.css +11 -6
- package/styles/ui-nav-item.css +7 -0
- package/styles/ui-tree-item.css +3 -0
- package/tokens.css +1 -0
- package/vue/UiListItem.vue +9 -6
- package/vue/UiNavItem.vue +5 -0
- package/vue/UiTreeItem.vue +3 -0
- package/vue/chunks/UiListItem.js +1 -1
- package/vue/chunks/UiNavItem.js +1 -1
- package/vue/chunks/UiTreeItem.js +1 -1
- package/vue/components.css +175 -163
- package/vue/editor/LoomaEditor.d.ts +38 -0
- package/vue/editor/index.d.ts +1 -1
- package/vue/editor/index.js +83 -10
|
@@ -29,6 +29,20 @@ export type LoomaImageUploader = (file: File) => Promise<string | LoomaImageUplo
|
|
|
29
29
|
* set. Narrow viewports retain a dock; document commands/content are unchanged.
|
|
30
30
|
*/
|
|
31
31
|
export type LoomaEditorToolbarMode = "bubble" | "sticky" | "popover" | "contextual";
|
|
32
|
+
/** Host-owned non-mutating commands for the selected passage. */
|
|
33
|
+
export interface LoomaEditorSelectionAction {
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
}
|
|
37
|
+
/** Exact editor range captured before focus leaves for a host action. */
|
|
38
|
+
export interface LoomaEditorSelectionActionDetail {
|
|
39
|
+
id: string;
|
|
40
|
+
from: number;
|
|
41
|
+
to: number;
|
|
42
|
+
text: string;
|
|
43
|
+
before: string;
|
|
44
|
+
after: string;
|
|
45
|
+
}
|
|
32
46
|
/**
|
|
33
47
|
* Replace the slash inventory, or return retained defaults plus host commands.
|
|
34
48
|
* @ownership The host owns its command objects and mutations; Looma supplies
|
|
@@ -160,6 +174,16 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
160
174
|
* In `popover` mode, the ID of the app's button that toggles the full toolbar and anchors it.
|
|
161
175
|
* The button needs no click handler of its own.
|
|
162
176
|
*/
|
|
177
|
+
/** Keep frequent inline styles visible; Format opens the complete existing inventory. */
|
|
178
|
+
compactToolbar: {
|
|
179
|
+
type: BooleanConstructor;
|
|
180
|
+
default: boolean;
|
|
181
|
+
};
|
|
182
|
+
/** Optional passage actions also work on read-only selections without granting editing. */
|
|
183
|
+
selectionActions: {
|
|
184
|
+
type: PropType<LoomaEditorSelectionAction[]>;
|
|
185
|
+
default: () => never[];
|
|
186
|
+
};
|
|
163
187
|
toolbarTriggerId: {
|
|
164
188
|
type: StringConstructor;
|
|
165
189
|
default: string;
|
|
@@ -184,6 +208,7 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
184
208
|
"update:toolbarOpen": (_value: boolean) => true;
|
|
185
209
|
update: (_value: JSONContent) => true;
|
|
186
210
|
ready: (_editor: Editor) => true;
|
|
211
|
+
selectionAction: (_detail: LoomaEditorSelectionActionDetail) => true;
|
|
187
212
|
uploadError: (_error: unknown, _file: File) => true;
|
|
188
213
|
imageActivate: (_detail: LoomaImageActivationDetail) => true;
|
|
189
214
|
imageRenditionError: (_detail: LoomaImageRenditionErrorDetail) => true;
|
|
@@ -281,6 +306,16 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
281
306
|
* In `popover` mode, the ID of the app's button that toggles the full toolbar and anchors it.
|
|
282
307
|
* The button needs no click handler of its own.
|
|
283
308
|
*/
|
|
309
|
+
/** Keep frequent inline styles visible; Format opens the complete existing inventory. */
|
|
310
|
+
compactToolbar: {
|
|
311
|
+
type: BooleanConstructor;
|
|
312
|
+
default: boolean;
|
|
313
|
+
};
|
|
314
|
+
/** Optional passage actions also work on read-only selections without granting editing. */
|
|
315
|
+
selectionActions: {
|
|
316
|
+
type: PropType<LoomaEditorSelectionAction[]>;
|
|
317
|
+
default: () => never[];
|
|
318
|
+
};
|
|
284
319
|
toolbarTriggerId: {
|
|
285
320
|
type: StringConstructor;
|
|
286
321
|
default: string;
|
|
@@ -303,6 +338,7 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
303
338
|
"onUpdate:modelValue"?: ((_value: JSONContent) => any) | undefined;
|
|
304
339
|
"onUpdate:toolbarOpen"?: ((_value: boolean) => any) | undefined;
|
|
305
340
|
onReady?: ((_editor: Editor) => any) | undefined;
|
|
341
|
+
onSelectionAction?: ((_detail: LoomaEditorSelectionActionDetail) => any) | undefined;
|
|
306
342
|
onUploadError?: ((_error: unknown, _file: File) => any) | undefined;
|
|
307
343
|
onImageActivate?: ((_detail: LoomaImageActivationDetail) => any) | undefined;
|
|
308
344
|
onImageRenditionError?: ((_detail: LoomaImageRenditionErrorDetail) => any) | undefined;
|
|
@@ -326,6 +362,8 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
326
362
|
uploadImage: LoomaImageUploader | undefined;
|
|
327
363
|
resolveImageAttributes: LoomaImageAttributeResolver | undefined;
|
|
328
364
|
toolbarMode: LoomaEditorToolbarMode;
|
|
365
|
+
compactToolbar: boolean;
|
|
366
|
+
selectionActions: LoomaEditorSelectionAction[];
|
|
329
367
|
toolbarTriggerId: string;
|
|
330
368
|
toolbarOpen: boolean;
|
|
331
369
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/vue/editor/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "@threadlabs/looma/editor";
|
|
2
2
|
export * from "./primitives.js";
|
|
3
|
-
export { LoomaEditor, type LoomaEditorToolbarMode, type LoomaEditorSlashCommands, type LoomaLinkTarget, type LoomaLinkSearch, type LoomaLinkResolve, type LoomaImageUploader, type LoomaImageUploadResult, } from "./LoomaEditor.js";
|
|
3
|
+
export { LoomaEditor, type LoomaEditorToolbarMode, type LoomaEditorSelectionAction, type LoomaEditorSelectionActionDetail, type LoomaEditorSlashCommands, type LoomaLinkTarget, type LoomaLinkSearch, type LoomaLinkResolve, type LoomaImageUploader, type LoomaImageUploadResult, } from "./LoomaEditor.js";
|
|
4
4
|
export type { LoomaImageActivationDetail, LoomaImageActivationTrigger, LoomaImageAttributeResolver, LoomaImageDescriptor, LoomaImageRenditionErrorDetail, LoomaImageRenditionAttributes, } from "./image-delivery.js";
|
package/vue/editor/index.js
CHANGED
|
@@ -1700,6 +1700,16 @@ var LoomaEditor = defineComponent({
|
|
|
1700
1700
|
* In `popover` mode, the ID of the app's button that toggles the full toolbar and anchors it.
|
|
1701
1701
|
* The button needs no click handler of its own.
|
|
1702
1702
|
*/
|
|
1703
|
+
/** Keep frequent inline styles visible; Format opens the complete existing inventory. */
|
|
1704
|
+
compactToolbar: {
|
|
1705
|
+
type: Boolean,
|
|
1706
|
+
default: false
|
|
1707
|
+
},
|
|
1708
|
+
/** Optional passage actions also work on read-only selections without granting editing. */
|
|
1709
|
+
selectionActions: {
|
|
1710
|
+
type: Array,
|
|
1711
|
+
default: () => []
|
|
1712
|
+
},
|
|
1703
1713
|
toolbarTriggerId: {
|
|
1704
1714
|
type: String,
|
|
1705
1715
|
default: ""
|
|
@@ -1723,12 +1733,15 @@ var LoomaEditor = defineComponent({
|
|
|
1723
1733
|
"update:toolbarOpen": (_value) => true,
|
|
1724
1734
|
update: (_value) => true,
|
|
1725
1735
|
ready: (_editor) => true,
|
|
1736
|
+
selectionAction: (_detail) => true,
|
|
1726
1737
|
uploadError: (_error, _file) => true,
|
|
1727
1738
|
imageActivate: (_detail) => true,
|
|
1728
1739
|
imageRenditionError: (_detail) => true
|
|
1729
1740
|
},
|
|
1730
1741
|
setup(props, { attrs, emit, expose }) {
|
|
1731
1742
|
const root = ref(null);
|
|
1743
|
+
const formatAnchorId = `looma-editor-format-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
1744
|
+
const formatOpen = ref(false);
|
|
1732
1745
|
const fileInput = ref(null);
|
|
1733
1746
|
const tablePickerAnchorId = `looma-editor-table-picker-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
1734
1747
|
const blockActionAnchorId = `ui-block-tools-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
@@ -1806,7 +1819,7 @@ var LoomaEditor = defineComponent({
|
|
|
1806
1819
|
let heldPressTimer;
|
|
1807
1820
|
let heldPress = null;
|
|
1808
1821
|
const floatingFormattingMode = () => props.toolbarMode !== "sticky" && !(props.toolbarMode === "popover" && props.toolbarOpen) && !mobile.value;
|
|
1809
|
-
const formattingPickerOpen = () => linkOpen.value || tablePickerOpen.value;
|
|
1822
|
+
const formattingPickerOpen = () => linkOpen.value || tablePickerOpen.value || formatOpen.value;
|
|
1810
1823
|
const cancelHeldPress = () => {
|
|
1811
1824
|
clearTimeout(heldPressTimer);
|
|
1812
1825
|
heldPressTimer = void 0;
|
|
@@ -2479,8 +2492,12 @@ var LoomaEditor = defineComponent({
|
|
|
2479
2492
|
});
|
|
2480
2493
|
if (!editable) chipOpen.value = false;
|
|
2481
2494
|
});
|
|
2482
|
-
watch([
|
|
2483
|
-
|
|
2495
|
+
watch([
|
|
2496
|
+
linkOpen,
|
|
2497
|
+
tablePickerOpen,
|
|
2498
|
+
formatOpen
|
|
2499
|
+
], ([link, table, format]) => {
|
|
2500
|
+
if (link || table || format || !floatingFormattingMode() || editor.value?.isFocused || formattingPopup?.popper.contains(document.activeElement)) return;
|
|
2484
2501
|
heldFormattingSelection = null;
|
|
2485
2502
|
formattingPopup?.hide();
|
|
2486
2503
|
});
|
|
@@ -3036,7 +3053,34 @@ var LoomaEditor = defineComponent({
|
|
|
3036
3053
|
onClick: run
|
|
3037
3054
|
}, () => loomaIcon(icon));
|
|
3038
3055
|
};
|
|
3039
|
-
const
|
|
3056
|
+
const renderSelectionActions = (instance) => {
|
|
3057
|
+
const selection = instance.state.selection;
|
|
3058
|
+
if (selection.empty || !(selection instanceof TextSelection || selection instanceof AllSelection)) return [];
|
|
3059
|
+
return props.selectionActions.map((action) => h(Button, {
|
|
3060
|
+
key: action.id,
|
|
3061
|
+
"aria-label": action.label,
|
|
3062
|
+
type: "button",
|
|
3063
|
+
variant: "ghost",
|
|
3064
|
+
tone: "neutral",
|
|
3065
|
+
size: "sm",
|
|
3066
|
+
onPointerdown: (event) => event.preventDefault(),
|
|
3067
|
+
onClick: () => {
|
|
3068
|
+
const { from, to } = instance.state.selection;
|
|
3069
|
+
if (from === to) return;
|
|
3070
|
+
const doc = instance.state.doc;
|
|
3071
|
+
emit("selectionAction", {
|
|
3072
|
+
id: action.id,
|
|
3073
|
+
from,
|
|
3074
|
+
to,
|
|
3075
|
+
text: doc.textBetween(from, to, "\n"),
|
|
3076
|
+
before: doc.textBetween(Math.max(0, from - 48), from, "\n"),
|
|
3077
|
+
after: doc.textBetween(to, Math.min(doc.content.size, to + 48), "\n")
|
|
3078
|
+
});
|
|
3079
|
+
formattingPopup?.hide();
|
|
3080
|
+
}
|
|
3081
|
+
}, () => action.label));
|
|
3082
|
+
};
|
|
3083
|
+
const renderToolbar = (instance, floating = true, textOnly = false, full = false) => {
|
|
3040
3084
|
const textButtons = [
|
|
3041
3085
|
commandButton("Bold", "bold", instance.isActive("bold"), !instance.can().toggleBold(), () => instance.chain().focus().toggleBold().run(), "Mod-b"),
|
|
3042
3086
|
commandButton("Italic", "italic", instance.isActive("italic"), !instance.can().toggleItalic(), () => instance.chain().focus().toggleItalic().run(), "Mod-i"),
|
|
@@ -3045,7 +3089,7 @@ var LoomaEditor = defineComponent({
|
|
|
3045
3089
|
...authorHighlight ? [commandButton("Highlight", "highlighter", instance.isActive("highlight"), !instance.can().toggleHighlight(), () => instance.chain().focus().toggleHighlight().run(), "Mod-Shift-h")] : [],
|
|
3046
3090
|
commandButton("Inline code", "code-xml", instance.isActive("code"), !instance.can().toggleCode(), () => instance.chain().focus().toggleCode().run(), "Mod-e")
|
|
3047
3091
|
];
|
|
3048
|
-
const linkTriggerId = textOnly ? `${linkAnchorId}-selection` : linkAnchorId;
|
|
3092
|
+
const linkTriggerId = full ? `${linkAnchorId}-full` : textOnly ? `${linkAnchorId}-selection` : linkAnchorId;
|
|
3049
3093
|
textButtons.push(h(IconButton, {
|
|
3050
3094
|
id: linkTriggerId,
|
|
3051
3095
|
class: "looma-editor__toolbar-button",
|
|
@@ -3072,7 +3116,7 @@ var LoomaEditor = defineComponent({
|
|
|
3072
3116
|
},
|
|
3073
3117
|
onFocusout: hideTool
|
|
3074
3118
|
}, () => loomaIcon("link")));
|
|
3075
|
-
|
|
3119
|
+
let buttons = textOnly ? textButtons : [
|
|
3076
3120
|
...textButtons,
|
|
3077
3121
|
h("span", {
|
|
3078
3122
|
class: "divider",
|
|
@@ -3120,7 +3164,24 @@ var LoomaEditor = defineComponent({
|
|
|
3120
3164
|
commandButton("Undo", "undo", false, !instance.can().undo(), () => instance.chain().focus().undo().run(), "Mod-z"),
|
|
3121
3165
|
commandButton("Redo", "redo", false, !instance.can().redo(), () => instance.chain().focus().redo().run(), isApplePlatform() ? "Mod-Shift-z" : "Mod-y")
|
|
3122
3166
|
];
|
|
3123
|
-
|
|
3167
|
+
if (!props.editable) buttons = [];
|
|
3168
|
+
else if (props.compactToolbar && !mobile.value && !full) buttons = [
|
|
3169
|
+
textButtons[0],
|
|
3170
|
+
textButtons[1],
|
|
3171
|
+
textButtons.at(-1),
|
|
3172
|
+
h(Button, {
|
|
3173
|
+
id: formatAnchorId,
|
|
3174
|
+
type: "button",
|
|
3175
|
+
variant: "ghost",
|
|
3176
|
+
tone: "neutral",
|
|
3177
|
+
size: "sm",
|
|
3178
|
+
"aria-label": "Format",
|
|
3179
|
+
"aria-expanded": formatOpen.value ? "true" : "false",
|
|
3180
|
+
onPointerdown: (event) => event.preventDefault()
|
|
3181
|
+
}, () => "Format")
|
|
3182
|
+
];
|
|
3183
|
+
const actions = full ? [] : renderSelectionActions(instance);
|
|
3184
|
+
return h(EditorToolbar, { floating }, () => [...mobile.value ? [...actions, ...buttons] : [...buttons, ...actions], h(Tooltip, {
|
|
3124
3185
|
class: "looma-editor__toolbar-tip",
|
|
3125
3186
|
for: tooltipFor.value,
|
|
3126
3187
|
open: tooltipOpen.value,
|
|
@@ -3215,11 +3276,11 @@ var LoomaEditor = defineComponent({
|
|
|
3215
3276
|
placement: "top",
|
|
3216
3277
|
inverse: true
|
|
3217
3278
|
}, () => "Drag to resize column") : null,
|
|
3218
|
-
instance && props.editable && !mobile.value && (props.toolbarMode === "bubble" || props.toolbarMode === "popover" || props.toolbarMode === "contextual") ? h(BubbleMenu, {
|
|
3279
|
+
instance && (props.editable || props.selectionActions.length) && !mobile.value && (props.toolbarMode === "bubble" || props.toolbarMode === "popover" || props.toolbarMode === "contextual") ? h(BubbleMenu, {
|
|
3219
3280
|
editor: instance,
|
|
3220
3281
|
pluginKey: "looma-text-formatting-menu",
|
|
3221
3282
|
updateDelay: 0,
|
|
3222
|
-
shouldShow: ({ editor: menuEditor }) => (props.toolbarMode !== "popover" || !props.toolbarOpen) && !slash.active && !mention.active && (menuEditor.isFocused || linkContextEditing.value || formattingPickerOpen()) && formattingSelectionActive(menuEditor),
|
|
3283
|
+
shouldShow: ({ editor: menuEditor }) => (props.toolbarMode !== "popover" || !props.toolbarOpen) && !slash.active && !mention.active && (!props.editable && props.selectionActions.length > 0 || menuEditor.isFocused || formattingPopup?.popper.contains(document.activeElement) || linkContextEditing.value || formattingPickerOpen()) && formattingSelectionActive(menuEditor),
|
|
3223
3284
|
tippyOptions: {
|
|
3224
3285
|
onCreate: (popup) => {
|
|
3225
3286
|
formattingPopup = popup;
|
|
@@ -3302,7 +3363,7 @@ var LoomaEditor = defineComponent({
|
|
|
3302
3363
|
value: codeUi.language,
|
|
3303
3364
|
onValueChange: (event) => chooseCodeLanguage(event.detail)
|
|
3304
3365
|
}, () => codeLanguageOptions.value.map((option) => h("option", { value: option.value }, option.label)))]) : null,
|
|
3305
|
-
instance && props.editable && mobile.value && (editorFocused.value || linkOpen.value) ? h("div", {
|
|
3366
|
+
instance && (props.editable || props.selectionActions.length && !instance.state.selection.empty) && mobile.value && (editorFocused.value || linkOpen.value || !props.editable) ? h("div", {
|
|
3306
3367
|
ref: mobileToolbarShell,
|
|
3307
3368
|
class: "looma-editor__mobile-toolbar-shell",
|
|
3308
3369
|
"data-mode": mobileToolbarMode.value,
|
|
@@ -3407,6 +3468,18 @@ var LoomaEditor = defineComponent({
|
|
|
3407
3468
|
if (current && !current.empty && linkSelection && current.from === linkSelection.from && current.to === linkSelection.to) editor.value?.commands.setTextSelection(current.to);
|
|
3408
3469
|
}
|
|
3409
3470
|
}, renderLinkForm),
|
|
3471
|
+
props.editable && props.compactToolbar && !mobile.value ? h(Popover, {
|
|
3472
|
+
class: "looma-editor__compact-formatting",
|
|
3473
|
+
for: formatAnchorId,
|
|
3474
|
+
open: formatOpen.value,
|
|
3475
|
+
placement: "bottom-start",
|
|
3476
|
+
onOpen: () => {
|
|
3477
|
+
formatOpen.value = true;
|
|
3478
|
+
},
|
|
3479
|
+
onClose: () => {
|
|
3480
|
+
formatOpen.value = false;
|
|
3481
|
+
}
|
|
3482
|
+
}, () => instance ? renderToolbar(instance, true, false, true) : null) : null,
|
|
3410
3483
|
h(Menu, {
|
|
3411
3484
|
for: blockActionAnchorId,
|
|
3412
3485
|
placement: mobile.value ? "top-start" : "bottom-start",
|