@threadlabs/looma 0.26.20 → 0.26.22
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/shared/editor.js +10 -0
- package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.js +3 -3
- package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.js +3 -3
- package/package.json +1 -1
- package/vue/components.css +9 -0
- package/vue/editor/index.js +72 -16
|
@@ -4,6 +4,16 @@ export const backgrounds = [
|
|
|
4
4
|
["background-green", "Green", "#dcfce7"], ["background-red", "Red", "#fee2e2"],
|
|
5
5
|
];
|
|
6
6
|
|
|
7
|
+
// HTML parsing serializes hex colors as RGB. Compare browser-normalized CSS
|
|
8
|
+
// colors without changing the document value or treating invalid CSS as Default.
|
|
9
|
+
export function normalizeCellBackground(value, ownerDocument) {
|
|
10
|
+
const color = String(value ?? "").trim();
|
|
11
|
+
if (!color) return "";
|
|
12
|
+
const style = ownerDocument.createElement("span").style;
|
|
13
|
+
style.backgroundColor = color;
|
|
14
|
+
return style.backgroundColor || color;
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
// Looma's icon set as plain data, generated from LOOMA_ICONS (see shared/icons.js).
|
|
8
18
|
export { icons } from "./icons.js";
|
|
9
19
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { backgrounds, viewport } from "../shared/editor.js";
|
|
1
|
+
import { backgrounds, normalizeCellBackground, viewport } from "../shared/editor.js";
|
|
2
2
|
|
|
3
3
|
const sections = [
|
|
4
4
|
["Structure", [
|
|
@@ -51,9 +51,9 @@ function connect(host) {
|
|
|
51
51
|
const scope = String(host.props.scope.value || "cell");
|
|
52
52
|
const enabled = new Set((Array.isArray(host.props.actions.value) ? host.props.actions.value : []).filter((action) =>
|
|
53
53
|
scope === "cell" || (scope === "row" ? !action.includes("column") : !action.includes("row"))));
|
|
54
|
-
const background =
|
|
54
|
+
const background = normalizeCellBackground(host.props.cellBackground.value, element.ownerDocument);
|
|
55
55
|
const swatches = backgrounds.filter(([action]) => enabled.has(action))
|
|
56
|
-
.map(([action, label, color]) => ({ action, label, color, selected: color === background }));
|
|
56
|
+
.map(([action, label, color]) => ({ action, label, color, selected: normalizeCellBackground(color, element.ownerDocument) === background }));
|
|
57
57
|
host.state.swatches = swatches.length ? swatches : null;
|
|
58
58
|
host.state.sections = sections
|
|
59
59
|
.map(([heading, items]) => ({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { backgrounds } from "../shared/editor.js";
|
|
1
|
+
import { backgrounds, normalizeCellBackground } from "../shared/editor.js";
|
|
2
2
|
import { createAnchoredSurface } from "../shared/overlay.js";
|
|
3
3
|
|
|
4
4
|
const overflowSections = [
|
|
@@ -45,9 +45,9 @@ function connect(host) {
|
|
|
45
45
|
host.state.structure = [["add-row-after", "Add row", "rows"], ["add-column-after", "Add column", "columns"]]
|
|
46
46
|
.filter(([action]) => enabled.has(action))
|
|
47
47
|
.map(([action, label, icon]) => ({ action, label, icon }));
|
|
48
|
-
const background =
|
|
48
|
+
const background = normalizeCellBackground(host.props.cellBackground.value, host.element.ownerDocument);
|
|
49
49
|
host.state.swatches = backgrounds.filter(([action]) => enabled.has(action))
|
|
50
|
-
.map(([action, label, color]) => ({ action, label, color, selected: color === background }));
|
|
50
|
+
.map(([action, label, color]) => ({ action, label, color, selected: normalizeCellBackground(color, host.element.ownerDocument) === background }));
|
|
51
51
|
host.state.sections = overflowSections
|
|
52
52
|
.map(([heading, items]) => ({
|
|
53
53
|
heading,
|
package/package.json
CHANGED
package/vue/components.css
CHANGED
|
@@ -10097,6 +10097,15 @@ html[data-ui-input-modality="touch"]
|
|
|
10097
10097
|
.looma-editor .ProseMirror li > :is(ul, ol) {
|
|
10098
10098
|
margin-block: var(--ui-space-1) 0;
|
|
10099
10099
|
}
|
|
10100
|
+
|
|
10101
|
+
/* A held navigation modifier makes links actionable without interrupting editing. */
|
|
10102
|
+
[data-component~="ui-editor-surface"] .ProseMirror[contenteditable="true"] a[href] {
|
|
10103
|
+
cursor: text;
|
|
10104
|
+
}
|
|
10105
|
+
|
|
10106
|
+
[data-component~="ui-editor-surface"] .ProseMirror[data-link-navigation] a[href] {
|
|
10107
|
+
cursor: pointer;
|
|
10108
|
+
}
|
|
10100
10109
|
/**
|
|
10101
10110
|
* Looma editor styles (tokens-based).
|
|
10102
10111
|
* Tables, context menu, insert-table grid, slash menu. Use with ProseMirror/Tiptap content.
|
package/vue/editor/index.js
CHANGED
|
@@ -1822,6 +1822,7 @@ var LoomaEditor = defineComponent({
|
|
|
1822
1822
|
let dismissedFormattingSelection = null;
|
|
1823
1823
|
let heldFormattingSelection = null;
|
|
1824
1824
|
let formattingPopup = null;
|
|
1825
|
+
let linkPopup = null;
|
|
1825
1826
|
let heldPressTimer;
|
|
1826
1827
|
let heldPress = null;
|
|
1827
1828
|
const floatingFormattingMode = () => props.toolbarMode !== "sticky" && !(props.toolbarMode === "popover" && props.toolbarOpen) && !mobile.value;
|
|
@@ -2032,7 +2033,30 @@ var LoomaEditor = defineComponent({
|
|
|
2032
2033
|
"aria-multiline": "true",
|
|
2033
2034
|
"aria-label": props.label
|
|
2034
2035
|
},
|
|
2035
|
-
handleKeyDown: (_view, event) => onFormattingKeyDown(event)
|
|
2036
|
+
handleKeyDown: (_view, event) => onFormattingKeyDown(event),
|
|
2037
|
+
handleDOMEvents: { click: (view, event) => {
|
|
2038
|
+
if (!(event.target instanceof Element) || event.button !== 0) return false;
|
|
2039
|
+
const link = event.target.closest("a[href]");
|
|
2040
|
+
if (!link || !view.dom.contains(link)) return false;
|
|
2041
|
+
const href = validLinkHref(link.getAttribute("href") ?? "");
|
|
2042
|
+
if (!href) return false;
|
|
2043
|
+
if (isApplePlatform() ? event.metaKey : event.ctrlKey) {
|
|
2044
|
+
event.preventDefault();
|
|
2045
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
2046
|
+
return true;
|
|
2047
|
+
}
|
|
2048
|
+
dismissedFormattingSelection = null;
|
|
2049
|
+
requestAnimationFrame(() => {
|
|
2050
|
+
if (view.isDestroyed) return;
|
|
2051
|
+
editorStateVersion.value += 1;
|
|
2052
|
+
const instance = editor.value;
|
|
2053
|
+
if (instance?.isFocused && instance.state.selection.empty && instance.isActive("link") && !linkOpen.value) {
|
|
2054
|
+
linkPopup?.setProps({ getReferenceClientRect: () => link.getBoundingClientRect() });
|
|
2055
|
+
linkPopup?.show();
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
2058
|
+
return false;
|
|
2059
|
+
} }
|
|
2036
2060
|
},
|
|
2037
2061
|
onCreate: ({ editor: instance }) => {
|
|
2038
2062
|
emit("ready", instance);
|
|
@@ -2360,31 +2384,38 @@ var LoomaEditor = defineComponent({
|
|
|
2360
2384
|
h(Separator),
|
|
2361
2385
|
h(Cluster, {
|
|
2362
2386
|
gap: "xs",
|
|
2363
|
-
justify: "between"
|
|
2364
|
-
|
|
2387
|
+
justify: "between",
|
|
2388
|
+
wrap: "nowrap"
|
|
2389
|
+
}, () => [linkSelection?.existing ? h(Button, {
|
|
2365
2390
|
type: "button",
|
|
2366
2391
|
variant: "ghost",
|
|
2367
2392
|
tone: "danger",
|
|
2368
2393
|
size: "sm",
|
|
2369
2394
|
onClick: removeLink
|
|
2370
|
-
}, () => "Remove link") : null,
|
|
2395
|
+
}, () => "Remove link") : null, validLinkHref(linkHref.value) ? h(Button, {
|
|
2371
2396
|
as: "a",
|
|
2372
|
-
variant: "
|
|
2397
|
+
variant: "outline",
|
|
2398
|
+
tone: "accent",
|
|
2373
2399
|
size: "sm",
|
|
2374
2400
|
href: validLinkHref(linkHref.value),
|
|
2375
2401
|
target: "_blank",
|
|
2376
2402
|
rel: "noopener noreferrer"
|
|
2377
|
-
}, () => "
|
|
2403
|
+
}, () => "Open link") : null]),
|
|
2404
|
+
h(Cluster, {
|
|
2405
|
+
gap: "xs",
|
|
2406
|
+
justify: "end",
|
|
2407
|
+
wrap: "nowrap"
|
|
2408
|
+
}, () => [h(Button, {
|
|
2378
2409
|
type: "button",
|
|
2379
|
-
variant: "
|
|
2380
|
-
tone: "
|
|
2410
|
+
variant: "outline",
|
|
2411
|
+
tone: "accent",
|
|
2381
2412
|
size: "sm",
|
|
2382
2413
|
onClick: cancelLink
|
|
2383
2414
|
}, () => "Cancel"), h(Button, {
|
|
2384
2415
|
type: "submit",
|
|
2385
2416
|
variant: "solid",
|
|
2386
2417
|
size: "sm"
|
|
2387
|
-
}, () => "Save link")])
|
|
2418
|
+
}, () => "Save link")])
|
|
2388
2419
|
])]);
|
|
2389
2420
|
const renderLinkContext = (instance) => {
|
|
2390
2421
|
if (linkContextEditing.value) return renderLinkForm();
|
|
@@ -2428,7 +2459,10 @@ var LoomaEditor = defineComponent({
|
|
|
2428
2459
|
target: "_blank",
|
|
2429
2460
|
rel: "noopener noreferrer",
|
|
2430
2461
|
"aria-label": "Open link"
|
|
2431
|
-
}, () =>
|
|
2462
|
+
}, () => h(Cluster, {
|
|
2463
|
+
gap: "xs",
|
|
2464
|
+
wrap: "nowrap"
|
|
2465
|
+
}, () => [loomaIcon("external-link"), "Open link"])) : null,
|
|
2432
2466
|
safeHref ? h(Tooltip, { for: openId }, () => "Open link") : null,
|
|
2433
2467
|
h(IconButton, {
|
|
2434
2468
|
id: editId,
|
|
@@ -2787,12 +2821,13 @@ var LoomaEditor = defineComponent({
|
|
|
2787
2821
|
};
|
|
2788
2822
|
const onFormattingKeyDown = (event) => {
|
|
2789
2823
|
cancelHeldPress();
|
|
2790
|
-
if (event.key !== "Escape" || !
|
|
2824
|
+
if (event.key !== "Escape" || !(event.target instanceof Node) || !(root.value?.contains(event.target) || formattingPopup?.popper.contains(event.target) || linkPopup?.popper.contains(event.target)) || !(formatOpen.value || formattingPopup?.popper.style.visibility === "visible" || linkPopup?.popper.style.visibility === "visible")) return false;
|
|
2791
2825
|
const selection = editor.value?.state.selection;
|
|
2792
2826
|
dismissedFormattingSelection = selection instanceof TextSelection || selection instanceof AllSelection ? selection : null;
|
|
2793
2827
|
heldFormattingSelection = null;
|
|
2794
2828
|
formatOpen.value = false;
|
|
2795
2829
|
formattingPopup?.hide();
|
|
2830
|
+
linkPopup?.hide();
|
|
2796
2831
|
hideTool();
|
|
2797
2832
|
return true;
|
|
2798
2833
|
};
|
|
@@ -2877,7 +2912,17 @@ var LoomaEditor = defineComponent({
|
|
|
2877
2912
|
trigger: "programmatic"
|
|
2878
2913
|
});
|
|
2879
2914
|
};
|
|
2915
|
+
const onLinkNavigationModifier = (event) => {
|
|
2916
|
+
const dom = editor.value?.view.dom;
|
|
2917
|
+
const active = isApplePlatform() ? event.metaKey : event.ctrlKey;
|
|
2918
|
+
if (dom && dom.hasAttribute("data-link-navigation") !== active) dom.toggleAttribute("data-link-navigation", active);
|
|
2919
|
+
};
|
|
2920
|
+
const clearLinkNavigationModifier = () => editor.value?.view.dom.removeAttribute("data-link-navigation");
|
|
2880
2921
|
onMounted(() => {
|
|
2922
|
+
window.addEventListener("keydown", onLinkNavigationModifier);
|
|
2923
|
+
window.addEventListener("keyup", onLinkNavigationModifier);
|
|
2924
|
+
window.addEventListener("pointermove", onLinkNavigationModifier);
|
|
2925
|
+
window.addEventListener("blur", clearLinkNavigationModifier);
|
|
2881
2926
|
updateMobileViewport();
|
|
2882
2927
|
window.addEventListener("resize", onViewportChange);
|
|
2883
2928
|
window.addEventListener("scroll", onViewportChange, true);
|
|
@@ -2894,6 +2939,10 @@ var LoomaEditor = defineComponent({
|
|
|
2894
2939
|
root.value?.addEventListener("error", onImageError, true);
|
|
2895
2940
|
});
|
|
2896
2941
|
onBeforeUnmount(() => {
|
|
2942
|
+
window.removeEventListener("keydown", onLinkNavigationModifier);
|
|
2943
|
+
window.removeEventListener("keyup", onLinkNavigationModifier);
|
|
2944
|
+
window.removeEventListener("pointermove", onLinkNavigationModifier);
|
|
2945
|
+
window.removeEventListener("blur", clearLinkNavigationModifier);
|
|
2897
2946
|
window.removeEventListener("resize", onViewportChange);
|
|
2898
2947
|
window.removeEventListener("scroll", onViewportChange, true);
|
|
2899
2948
|
window.visualViewport?.removeEventListener("resize", onViewportChange);
|
|
@@ -3358,19 +3407,26 @@ var LoomaEditor = defineComponent({
|
|
|
3358
3407
|
}
|
|
3359
3408
|
}] } } : {}
|
|
3360
3409
|
}
|
|
3361
|
-
}, { default: () => props.toolbarMode === "contextual" ? h(Stack, { gap: "xs" }, () => [renderToolbar(instance, true), (linkContextEditing.value || instance.state.selection.empty && instance.isActive("link")) && !linkOpen.value ? renderLinkContextCard(instance) : null]) : renderToolbar(instance, true, props.toolbarMode === "popover") }) : null,
|
|
3362
|
-
instance && props.editable
|
|
3410
|
+
}, { default: () => props.toolbarMode === "contextual" ? h(Stack, { gap: "xs" }, () => [renderToolbar(instance, true), (linkContextEditing.value || instance.state.selection.empty && instance.isActive("link")) && !linkOpen.value && formattingSelectionActive(instance) ? renderLinkContextCard(instance) : null]) : renderToolbar(instance, true, props.toolbarMode === "popover") }) : null,
|
|
3411
|
+
instance && props.editable ? h(BubbleMenu, {
|
|
3363
3412
|
editor: instance,
|
|
3364
3413
|
pluginKey: "looma-link-context-menu",
|
|
3365
|
-
|
|
3414
|
+
updateDelay: 0,
|
|
3415
|
+
shouldShow: ({ editor: menuEditor, from, to }) => from === to && menuEditor.isActive("link") && !linkOpen.value && !dismissedFormattingSelection?.eq(menuEditor.state.selection) && Boolean(menuEditor.isFocused || linkContextEditing.value || linkPopup?.popper.contains(document.activeElement)) && !(props.toolbarMode === "contextual" && floatingFormattingMode() && formattingSelectionActive(menuEditor)),
|
|
3366
3416
|
tippyOptions: {
|
|
3417
|
+
onCreate: (popup) => {
|
|
3418
|
+
linkPopup = popup;
|
|
3419
|
+
},
|
|
3420
|
+
onDestroy: () => {
|
|
3421
|
+
linkPopup = null;
|
|
3422
|
+
},
|
|
3367
3423
|
appendTo: () => root.value?.closest("dialog[open], [popover]") ?? document.body,
|
|
3368
3424
|
onShow: () => announceOverlayOpen(root.value?.ownerDocument ?? document, root.value),
|
|
3369
3425
|
duration: 100,
|
|
3370
3426
|
maxWidth: Math.min(480, window.innerWidth - 24),
|
|
3371
|
-
placement: "
|
|
3427
|
+
placement: "top"
|
|
3372
3428
|
}
|
|
3373
|
-
}, { default: () => renderLinkContextCard(instance) }) : null,
|
|
3429
|
+
}, { default: () => props.toolbarMode === "contextual" && floatingFormattingMode() && formattingSelectionActive(instance) ? null : renderLinkContextCard(instance) }) : null,
|
|
3374
3430
|
instance && props.editable && !mobile.value && props.toolbarMode === "popover" && props.toolbarTriggerId ? h(Popover, {
|
|
3375
3431
|
class: "looma-editor__formatting-popover",
|
|
3376
3432
|
role: "region",
|