@threadlabs/looma 0.17.4 → 0.18.1
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-avatar/ui-avatar.html +8 -4
- package/components/ui-badge/ui-badge.html +28 -34
- package/components/ui-button/ui-button.html +1 -1
- package/components/ui-combobox/ui-combobox.html +4 -48
- package/components/ui-icon-button/ui-icon-button.html +10 -0
- package/components/ui-input/ui-input.html +1 -1
- package/components/ui-input-group/ui-input-group.html +1 -1
- package/components/ui-listbox/ui-listbox.html +88 -12
- package/components/ui-listbox/ui-listbox.js +162 -18
- package/components/ui-menu/ui-menu.js +14 -2
- package/components/ui-menu-item/ui-menu-item.html +3 -0
- package/components/ui-popover/ui-popover.js +13 -2
- package/components/ui-select/ui-select.html +1 -1
- package/components/ui-spinner/ui-spinner.html +47 -14
- package/components/ui-textarea/ui-textarea.html +1 -1
- package/dist/index.js +1 -1
- package/editor/chunks/extensions.js +5 -0
- package/package.json +1 -1
- package/styles/ui-avatar.css +6 -2
- package/styles/ui-badge.css +30 -40
- package/styles/ui-button.css +1 -1
- package/styles/ui-combobox.css +0 -52
- package/styles/ui-icon-button.css +10 -0
- package/styles/ui-input-group.css +1 -1
- package/styles/ui-input.css +1 -1
- package/styles/ui-listbox.css +33 -8
- package/styles/ui-menu-item.css +3 -0
- package/styles/ui-select.css +1 -1
- package/styles/ui-spinner.css +40 -15
- package/styles/ui-textarea.css +1 -1
- package/tokens.css +67 -2
- package/vanilla/UiCombobox.js +9 -1
- package/vanilla/UiListbox.d.ts +3 -1
- package/vanilla/UiListbox.js +65 -18
- package/vanilla/UiSpinner.js +19 -1
- package/vue/UiAvatar.vue +11 -2
- package/vue/UiBadge.vue +30 -36
- package/vue/UiButton.vue +1 -1
- package/vue/UiCombobox.vue +4 -49
- package/vue/UiIconButton.vue +13 -0
- package/vue/UiInput.vue +1 -1
- package/vue/UiInputGroup.vue +1 -1
- package/vue/UiListbox.d.ts +18 -5
- package/vue/UiListbox.vue +142 -25
- package/vue/UiMenuItem.vue +3 -0
- package/vue/UiSelect.vue +1 -1
- package/vue/UiSpinner.vue +47 -14
- package/vue/UiTextarea.vue +1 -1
- package/vue/chunks/UiAvatar.js +2 -1
- package/vue/chunks/UiBadge.js +1 -1
- package/vue/chunks/UiButton.js +1 -1
- package/vue/chunks/UiCombobox.js +11 -2
- package/vue/chunks/UiIconButton.js +1 -1
- package/vue/chunks/UiInput.js +1 -1
- package/vue/chunks/UiInputGroup.js +1 -1
- package/vue/chunks/UiListbox.js +134 -20
- package/vue/chunks/UiMenuItem.js +1 -1
- package/vue/chunks/UiSelect.js +1 -1
- package/vue/chunks/UiSpinner.js +29 -3
- package/vue/chunks/UiTextarea.js +1 -1
- package/vue/components.css +354 -342
|
@@ -17,6 +17,7 @@ export default function controller(host) {
|
|
|
17
17
|
const overlayId = `ui-menu-${Math.random().toString(36).slice(2, 11)}`;
|
|
18
18
|
const items = () => menuItems(element);
|
|
19
19
|
let anchor = null;
|
|
20
|
+
let anchorAria = null;
|
|
20
21
|
let surface = null;
|
|
21
22
|
let lastFor;
|
|
22
23
|
let lastPlacement;
|
|
@@ -39,6 +40,16 @@ export default function controller(host) {
|
|
|
39
40
|
if (reason === "escape") anchor?.focus();
|
|
40
41
|
};
|
|
41
42
|
const ids = createIdResolver(document, () => apply());
|
|
43
|
+
const releaseAnchor = () => {
|
|
44
|
+
if (!anchor) return;
|
|
45
|
+
anchor.removeEventListener("click", onAnchorClick);
|
|
46
|
+
for (const [name, value] of Object.entries(anchorAria ?? {})) {
|
|
47
|
+
if (value === null) anchor.removeAttribute(name);
|
|
48
|
+
else anchor.setAttribute(name, value);
|
|
49
|
+
}
|
|
50
|
+
anchor = null;
|
|
51
|
+
anchorAria = null;
|
|
52
|
+
};
|
|
42
53
|
const setup = () => {
|
|
43
54
|
const nextFor = host.state.inline ? "" : String(host.state.for ?? "");
|
|
44
55
|
const nextPlacement = String(host.state.placement ?? "bottom-start");
|
|
@@ -46,8 +57,9 @@ export default function controller(host) {
|
|
|
46
57
|
lastFor = nextFor;
|
|
47
58
|
lastPlacement = nextPlacement;
|
|
48
59
|
surface?.destroy();
|
|
49
|
-
|
|
60
|
+
releaseAnchor();
|
|
50
61
|
anchor = ids.get(nextFor);
|
|
62
|
+
if (anchor) anchorAria = { "aria-haspopup": anchor.getAttribute("aria-haspopup"), "aria-expanded": anchor.getAttribute("aria-expanded") };
|
|
51
63
|
anchor?.addEventListener("click", onAnchorClick);
|
|
52
64
|
surface = anchor ? createAnchoredSurface(element, { anchor, placement: nextPlacement }) : null;
|
|
53
65
|
};
|
|
@@ -119,8 +131,8 @@ export default function controller(host) {
|
|
|
119
131
|
ids.stop();
|
|
120
132
|
element.removeEventListener("click", onClick);
|
|
121
133
|
element.removeEventListener("keydown", onKeydown);
|
|
122
|
-
anchor?.removeEventListener("click", onAnchorClick);
|
|
123
134
|
surface?.destroy();
|
|
124
135
|
closeOverlay(document, overlayId);
|
|
136
|
+
releaseAnchor();
|
|
125
137
|
};
|
|
126
138
|
}
|
|
@@ -13,6 +13,7 @@ export default function controller(host) {
|
|
|
13
13
|
};
|
|
14
14
|
const overlayId = `ui-popover-${Math.random().toString(36).slice(2, 11)}`;
|
|
15
15
|
let anchor = null;
|
|
16
|
+
let anchorExpanded = null;
|
|
16
17
|
let surface = null;
|
|
17
18
|
let lastFor;
|
|
18
19
|
let lastPlacement;
|
|
@@ -37,6 +38,14 @@ export default function controller(host) {
|
|
|
37
38
|
host.dispatch("open", { open: true, reason: "action", trigger });
|
|
38
39
|
};
|
|
39
40
|
const ids = createIdResolver(document, () => apply());
|
|
41
|
+
const releaseAnchor = () => {
|
|
42
|
+
if (!anchor) return;
|
|
43
|
+
anchor.removeEventListener("click", onAnchorClick);
|
|
44
|
+
if (anchorExpanded === null) anchor.removeAttribute("aria-expanded");
|
|
45
|
+
else anchor.setAttribute("aria-expanded", anchorExpanded);
|
|
46
|
+
anchor = null;
|
|
47
|
+
anchorExpanded = null;
|
|
48
|
+
};
|
|
40
49
|
const setup = () => {
|
|
41
50
|
const nextFor = String(host.state.for ?? "");
|
|
42
51
|
const nextPlacement = String(host.state.placement ?? "bottom-start");
|
|
@@ -44,8 +53,9 @@ export default function controller(host) {
|
|
|
44
53
|
lastFor = nextFor;
|
|
45
54
|
lastPlacement = nextPlacement;
|
|
46
55
|
surface?.destroy();
|
|
47
|
-
|
|
56
|
+
releaseAnchor();
|
|
48
57
|
anchor = ids.get(nextFor);
|
|
58
|
+
if (anchor) anchorExpanded = anchor.getAttribute("aria-expanded");
|
|
49
59
|
anchor?.addEventListener("click", onAnchorClick);
|
|
50
60
|
surface = anchor ? createAnchoredSurface(element, { anchor, placement: nextPlacement, gap: offset }) : null;
|
|
51
61
|
};
|
|
@@ -57,6 +67,7 @@ export default function controller(host) {
|
|
|
57
67
|
}
|
|
58
68
|
setup();
|
|
59
69
|
const open = Boolean(host.state.internalOpen);
|
|
70
|
+
if (anchor) anchor.setAttribute("aria-expanded", String(open));
|
|
60
71
|
if (open) {
|
|
61
72
|
surface?.show();
|
|
62
73
|
openOverlay({ id: overlayId, modal: false, element, relatedElements: anchor ? [anchor] : [], dismissible: true, requestClose: close });
|
|
@@ -79,8 +90,8 @@ export default function controller(host) {
|
|
|
79
90
|
stop();
|
|
80
91
|
ids.stop();
|
|
81
92
|
element.removeEventListener("keydown", onKeydown);
|
|
82
|
-
anchor?.removeEventListener("click", onAnchorClick);
|
|
83
93
|
surface?.destroy();
|
|
84
94
|
closeOverlay(document, overlayId);
|
|
95
|
+
releaseAnchor();
|
|
85
96
|
};
|
|
86
97
|
}
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
background-size: 1.125rem 1.125rem;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
:host:hover:not(:focus):not(:disabled) {
|
|
95
|
+
:host:hover:not(:focus):not(:disabled):not([aria-invalid="true"]):not(:user-invalid) {
|
|
96
96
|
border-color: var(--ui-select-border-hover, var(--ui-control-border-hover, var(--ui-text-secondary)));
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -9,11 +9,16 @@
|
|
|
9
9
|
class="ring"
|
|
10
10
|
:role="{ true: 'status', false: null }[format('%s', label != '')]"
|
|
11
11
|
:aria-label="{ true: label, false: null }[format('%s', label != '')]"
|
|
12
|
+
:aria-live="{ true: 'polite', false: null }[format('%s', label != '')]"
|
|
12
13
|
:aria-hidden="{ true: null, false: 'true' }[format('%s', label != '')]"
|
|
13
|
-
|
|
14
|
+
>
|
|
15
|
+
<svg viewBox="0 0 50 50" fill="none" aria-hidden="true" focusable="false">
|
|
16
|
+
<circle class="track" cx="25" cy="25" r="20"></circle>
|
|
17
|
+
<circle class="arc" cx="25" cy="25" r="20"></circle>
|
|
18
|
+
</svg>
|
|
19
|
+
</span>
|
|
14
20
|
<style>
|
|
15
|
-
/*
|
|
16
|
-
and the label (or the text beside it) still says what is happening. */
|
|
21
|
+
/* A rounded SVG arc grows and recedes over a quiet full-circle track. */
|
|
17
22
|
:host {
|
|
18
23
|
display: inline-flex;
|
|
19
24
|
flex: none;
|
|
@@ -22,34 +27,62 @@
|
|
|
22
27
|
box-sizing: border-box;
|
|
23
28
|
inline-size: 1em;
|
|
24
29
|
block-size: 1em;
|
|
25
|
-
border: 2px solid transparent;
|
|
26
|
-
border-block-start-color: currentColor;
|
|
27
|
-
border-inline-end-color: color-mix(in srgb, currentColor 52%, transparent);
|
|
28
|
-
border-radius: 50%;
|
|
29
30
|
color: currentColor;
|
|
30
31
|
font-size: var(--ui-font-size-lg);
|
|
31
32
|
line-height: 1;
|
|
32
|
-
animation: ui-spinner-chase 1s linear infinite;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
:host-state([size="sm"]) {
|
|
36
36
|
font-size: var(--ui-font-size-md);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
svg {
|
|
40
|
+
display: block;
|
|
41
|
+
inline-size: 100%;
|
|
42
|
+
block-size: 100%;
|
|
43
|
+
animation: ui-spinner-rotate 2s linear infinite;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
circle {
|
|
47
|
+
stroke-width: 3;
|
|
48
|
+
stroke-linecap: round;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.track {
|
|
52
|
+
stroke: color-mix(in srgb, currentColor 16%, transparent);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.arc {
|
|
56
|
+
stroke: currentColor;
|
|
57
|
+
stroke-dasharray: 90, 150;
|
|
58
|
+
stroke-dashoffset: 0;
|
|
59
|
+
animation: ui-spinner-dash 1.5s ease-in-out infinite;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@keyframes ui-spinner-rotate {
|
|
63
|
+
100% {
|
|
64
|
+
transform: rotate(1turn);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@keyframes ui-spinner-dash {
|
|
40
69
|
0% {
|
|
41
|
-
|
|
70
|
+
stroke-dasharray: 1, 150;
|
|
71
|
+
stroke-dashoffset: 0;
|
|
42
72
|
}
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
50% {
|
|
74
|
+
stroke-dasharray: 90, 150;
|
|
75
|
+
stroke-dashoffset: -35;
|
|
45
76
|
}
|
|
46
77
|
100% {
|
|
47
|
-
|
|
78
|
+
stroke-dasharray: 90, 150;
|
|
79
|
+
stroke-dashoffset: -124;
|
|
48
80
|
}
|
|
49
81
|
}
|
|
50
82
|
|
|
51
83
|
@media (prefers-reduced-motion: reduce) {
|
|
52
|
-
|
|
84
|
+
svg,
|
|
85
|
+
.arc {
|
|
53
86
|
animation: none;
|
|
54
87
|
}
|
|
55
88
|
}
|