@threadlabs/looma 0.28.3 → 0.28.5
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-editor-image-toolbar/ui-editor-image-toolbar.html +96 -0
- package/components/ui-editor-image-toolbar/ui-editor-image-toolbar.js +56 -0
- package/dist/index.js +34 -33
- package/package.json +1 -1
- package/styles/ui-editor-image-toolbar.css +1 -0
- package/vanilla/UiEditorImageToolbar.d.ts +19 -0
- package/vanilla/UiEditorImageToolbar.js +171 -0
- package/vanilla/index.js +1 -0
- package/vue/UiEditorImageToolbar.d.ts +54 -0
- package/vue/UiEditorImageToolbar.js +2 -0
- package/vue/UiEditorImageToolbar.vue +625 -0
- package/vue/UiText.d.ts +1 -1
- package/vue/chunks/UiEditorImageToolbar.js +584 -0
- package/vue/components.css +2142 -2142
- package/vue/editor/index.js +56 -174
- package/vue/editor/primitives.d.ts +9 -1
- package/vue/index.d.ts +1 -0
- package/vue/index.js +9 -8
- package/vue/editor/image-controls.d.ts +0 -19
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<link rel="component" href="../ui-text/ui-text.html">
|
|
2
|
+
<link rel="component" href="../ui-editor-toolbar/ui-editor-toolbar.html">
|
|
3
|
+
<link rel="component" href="../ui-icon-button/ui-icon-button.html">
|
|
4
|
+
<link rel="component" href="../ui-icon/ui-icon.html">
|
|
5
|
+
<link rel="component" href="../ui-tooltip/ui-tooltip.html">
|
|
6
|
+
<link rel="component" href="../ui-popover/ui-popover.html">
|
|
7
|
+
<link rel="component" href="../ui-stack/ui-stack.html">
|
|
8
|
+
<link rel="component" href="../ui-form-field/ui-form-field.html">
|
|
9
|
+
<link rel="component" href="../ui-input/ui-input.html">
|
|
10
|
+
<link rel="component" href="../ui-cluster/ui-cluster.html">
|
|
11
|
+
<link rel="component" href="../ui-button/ui-button.html">
|
|
12
|
+
<template component="ui-editor-image-toolbar" controller="./ui-editor-image-toolbar.js">
|
|
13
|
+
<defs>
|
|
14
|
+
<prop name="placement" type="keyword" values="block, center, wrap-left, wrap-right" default="block"
|
|
15
|
+
>How the selected image sits in the text; its command shows as pressed.</prop>
|
|
16
|
+
<prop name="alt" type="string" default="">The selected image's description, which the description form starts from.</prop>
|
|
17
|
+
<prop name="target" type="string" default=""
|
|
18
|
+
>Identifies the image the toolbar acts on. When it changes, an open description draft is cancelled rather than applied to another image.</prop>
|
|
19
|
+
<state name="base" type="string" value="ui-editor-image-toolbar"></state>
|
|
20
|
+
<state name="describing" type="boolean" value="false"></state>
|
|
21
|
+
<state name="draft" type="string" value=""></state>
|
|
22
|
+
<event name="placement" type="object">
|
|
23
|
+
<prop name="placement" required type="keyword" values="block, center, wrap-left, wrap-right"></prop>
|
|
24
|
+
Requests a placement for the selected image.
|
|
25
|
+
</event>
|
|
26
|
+
<event name="describe" type="object">
|
|
27
|
+
<prop name="alt" required type="string"></prop>
|
|
28
|
+
Requests the saved description for the image the form was opened for.
|
|
29
|
+
</event>
|
|
30
|
+
<event name="delete" type="object">Requests deleting the selected image.</event>
|
|
31
|
+
</defs>
|
|
32
|
+
<div>
|
|
33
|
+
<ui-text as="div" font="sans">
|
|
34
|
+
<ui-editor-toolbar from:floating="true" aria-label="Image actions">
|
|
35
|
+
<ui-icon-button
|
|
36
|
+
from:id="concat($base, '-block')"
|
|
37
|
+
label="Image in line"
|
|
38
|
+
size="sm"
|
|
39
|
+
data-image-placement="block"
|
|
40
|
+
from:variant="{ true: 'outline', false: 'ghost' }[concat($placement = 'block')]"
|
|
41
|
+
from:aria-pressed="concat($placement = 'block')"
|
|
42
|
+
><ui-icon name="panel-top" size="sm"></ui-icon></ui-icon-button>
|
|
43
|
+
<ui-icon-button
|
|
44
|
+
from:id="concat($base, '-center')"
|
|
45
|
+
label="Center image"
|
|
46
|
+
size="sm"
|
|
47
|
+
data-image-placement="center"
|
|
48
|
+
from:variant="{ true: 'outline', false: 'ghost' }[concat($placement = 'center')]"
|
|
49
|
+
from:aria-pressed="concat($placement = 'center')"
|
|
50
|
+
><ui-icon name="align-center" size="sm"></ui-icon></ui-icon-button>
|
|
51
|
+
<ui-icon-button
|
|
52
|
+
from:id="concat($base, '-wrap-left')"
|
|
53
|
+
label="Wrap text to the right"
|
|
54
|
+
size="sm"
|
|
55
|
+
data-image-placement="wrap-left"
|
|
56
|
+
from:variant="{ true: 'outline', false: 'ghost' }[concat($placement = 'wrap-left')]"
|
|
57
|
+
from:aria-pressed="concat($placement = 'wrap-left')"
|
|
58
|
+
><ui-icon name="panel-left" size="sm"></ui-icon></ui-icon-button>
|
|
59
|
+
<ui-icon-button
|
|
60
|
+
from:id="concat($base, '-wrap-right')"
|
|
61
|
+
label="Wrap text to the left"
|
|
62
|
+
size="sm"
|
|
63
|
+
data-image-placement="wrap-right"
|
|
64
|
+
from:variant="{ true: 'outline', false: 'ghost' }[concat($placement = 'wrap-right')]"
|
|
65
|
+
from:aria-pressed="concat($placement = 'wrap-right')"
|
|
66
|
+
><ui-icon name="panel-right" size="sm"></ui-icon></ui-icon-button>
|
|
67
|
+
<ui-icon-button from:id="$base" label="Image description" size="sm" variant="ghost"><ui-icon name="file-text" size="sm"></ui-icon></ui-icon-button>
|
|
68
|
+
<ui-icon-button from:id="concat($base, '-delete')" label="Delete image" size="sm" variant="ghost" data-image-action="delete"><ui-icon
|
|
69
|
+
name="trash"
|
|
70
|
+
size="sm"
|
|
71
|
+
></ui-icon></ui-icon-button>
|
|
72
|
+
</ui-editor-toolbar>
|
|
73
|
+
<ui-tooltip from:for="concat($base, '-block')">Image in line</ui-tooltip>
|
|
74
|
+
<ui-tooltip from:for="concat($base, '-center')">Center image</ui-tooltip>
|
|
75
|
+
<ui-tooltip from:for="concat($base, '-wrap-left')">Wrap text to the right</ui-tooltip>
|
|
76
|
+
<ui-tooltip from:for="concat($base, '-wrap-right')">Wrap text to the left</ui-tooltip>
|
|
77
|
+
<ui-tooltip from:for="$base">Image description</ui-tooltip>
|
|
78
|
+
<ui-tooltip from:for="concat($base, '-delete')">Delete image</ui-tooltip>
|
|
79
|
+
<ui-popover data-image-description from:for="$base" from:open="$describing" size="sm" aria-label="Image description">
|
|
80
|
+
<form aria-label="Image description">
|
|
81
|
+
<ui-stack gap="s">
|
|
82
|
+
<ui-form-field>
|
|
83
|
+
<label slot="label" from:for="concat($base, '-alt')">Alt text</label>
|
|
84
|
+
<ui-input data-image-alt from:id="concat($base, '-alt')" size="sm" from:value="$draft"></ui-input>
|
|
85
|
+
</ui-form-field>
|
|
86
|
+
<ui-text size="xs" tone="secondary">Describe the image for people using a screen reader.</ui-text>
|
|
87
|
+
<ui-cluster justify="end" gap="xs">
|
|
88
|
+
<ui-button type="button" variant="ghost" tone="neutral" size="sm" data-image-action="cancel">Cancel</ui-button>
|
|
89
|
+
<ui-button type="submit" variant="solid" size="sm">Save</ui-button>
|
|
90
|
+
</ui-cluster>
|
|
91
|
+
</ui-stack>
|
|
92
|
+
</form>
|
|
93
|
+
</ui-popover>
|
|
94
|
+
</ui-text>
|
|
95
|
+
</div>
|
|
96
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Gives each toolbar its own ids for its tooltips and description popover, reports placement,
|
|
2
|
+
// description, and delete requests, and keeps the description draft tied to the image it opened for.
|
|
3
|
+
function connect(host) {
|
|
4
|
+
const element = host.element;
|
|
5
|
+
if (host.state.base === "ui-editor-image-toolbar") host.state.base = `ui-editor-image-toolbar-${Math.random().toString(36).slice(2, 11)}`;
|
|
6
|
+
let target = null;
|
|
7
|
+
// Marked by attribute: the converted Vue component's host.refs has no entry for a nested component.
|
|
8
|
+
const isDescription = (event) => event.target.hasAttribute?.("data-image-description") && element.contains(event.target);
|
|
9
|
+
const onClick = (event) => {
|
|
10
|
+
const control = event.target.closest?.("[data-image-placement], [data-image-action]");
|
|
11
|
+
if (!control || !element.contains(control)) return;
|
|
12
|
+
if (control.dataset.imagePlacement) host.dispatch("placement", { placement: control.dataset.imagePlacement });
|
|
13
|
+
else if (control.dataset.imageAction === "delete") host.dispatch("delete", {});
|
|
14
|
+
else if (control.dataset.imageAction === "cancel") host.state.describing = false;
|
|
15
|
+
};
|
|
16
|
+
const onOpen = (event) => {
|
|
17
|
+
if (!isDescription(event)) return;
|
|
18
|
+
target = host.props.target.value;
|
|
19
|
+
host.state.draft = String(host.props.alt.value ?? "");
|
|
20
|
+
host.state.describing = true;
|
|
21
|
+
};
|
|
22
|
+
const onClose = (event) => {
|
|
23
|
+
if (isDescription(event)) host.state.describing = false;
|
|
24
|
+
};
|
|
25
|
+
const onInput = (event) => {
|
|
26
|
+
if (event.target.hasAttribute?.("data-image-alt")) host.state.draft = event.target.value;
|
|
27
|
+
};
|
|
28
|
+
const onSubmit = (event) => {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
host.state.describing = false;
|
|
31
|
+
if (host.props.target.value !== target) return;
|
|
32
|
+
host.dispatch("describe", { alt: String(host.state.draft ?? "") });
|
|
33
|
+
};
|
|
34
|
+
// Another image's selection cancels a draft rather than applying it there.
|
|
35
|
+
const stop = host.effect(() => {
|
|
36
|
+
if (host.props.target.value !== target && host.state.describing) host.state.describing = false;
|
|
37
|
+
});
|
|
38
|
+
element.addEventListener("click", onClick);
|
|
39
|
+
element.addEventListener("open", onOpen);
|
|
40
|
+
element.addEventListener("close", onClose);
|
|
41
|
+
element.addEventListener("input", onInput);
|
|
42
|
+
element.addEventListener("submit", onSubmit);
|
|
43
|
+
return () => {
|
|
44
|
+
stop();
|
|
45
|
+
element.removeEventListener("click", onClick);
|
|
46
|
+
element.removeEventListener("open", onOpen);
|
|
47
|
+
element.removeEventListener("close", onClose);
|
|
48
|
+
element.removeEventListener("input", onInput);
|
|
49
|
+
element.removeEventListener("submit", onSubmit);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Keep DOM setup and its cleanup tied to each connection, including reconnects. */
|
|
54
|
+
export default function controller(host) {
|
|
55
|
+
host.on("connect", () => connect(host));
|
|
56
|
+
}
|