@threadlabs/looma 0.22.10 → 0.22.11
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/overlay.js +33 -1
- package/components/ui-dialog/ui-dialog.html +6 -0
- package/components/ui-dialog/ui-dialog.js +2 -2
- package/components/ui-input-group/ui-input-group.html +14 -0
- package/components/ui-search-shell/ui-search-shell.html +8 -0
- package/components/ui-search-shell/ui-search-shell.js +11 -1
- package/components/ui-sidebar/ui-sidebar.js +3 -0
- package/components/ui-toast-region/ui-toast-region.js +4 -0
- package/components/ui-tooltip/ui-tooltip.js +13 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/styles/ui-dialog.css +6 -0
- package/styles/ui-input-group.css +16 -0
- package/styles/ui-search-shell.css +8 -0
- package/vue/UiDialog.vue +6 -0
- package/vue/UiInputGroup.vue +14 -0
- package/vue/UiSearchShell.vue +8 -0
- package/vue/chunks/UiDialog.js +1 -1
- package/vue/chunks/UiInputGroup.js +1 -1
- package/vue/chunks/UiSearchShell.js +1 -1
- package/vue/components.css +82 -59
- package/vue/editor/index.js +9 -2
|
@@ -14,6 +14,13 @@ function syncScrollLock(document, state) {
|
|
|
14
14
|
else document.documentElement.style.removeProperty("overflow");
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function syncModalBackdrops(state) {
|
|
18
|
+
const topModal = state.records.findLast((record) => record.modal);
|
|
19
|
+
for (const record of state.records) {
|
|
20
|
+
(record.modalElement ?? record.element).toggleAttribute("data-ui-backdrop-hidden", Boolean(record.modal && record !== topModal));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
function requestClose(document, reason, trigger) {
|
|
18
25
|
const record = stateFor(document).records.at(-1);
|
|
19
26
|
if (!record || (record.dismissible === false && (reason === "escape" || reason === "light-dismiss")) || record.canClose?.(reason) === false) return false;
|
|
@@ -55,13 +62,36 @@ function removeListeners(document, state) {
|
|
|
55
62
|
export function openOverlay(record) {
|
|
56
63
|
const document = record.element.ownerDocument;
|
|
57
64
|
const state = stateFor(document);
|
|
58
|
-
|
|
65
|
+
const index = state.records.findIndex((entry) => entry.id === record.id);
|
|
66
|
+
if (index >= 0) {
|
|
67
|
+
const previous = state.records[index];
|
|
68
|
+
state.records[index] = record;
|
|
69
|
+
if (previous.modal !== record.modal) {
|
|
70
|
+
state.modalCount += record.modal ? 1 : -1;
|
|
71
|
+
syncScrollLock(document, state);
|
|
72
|
+
}
|
|
73
|
+
if (previous.modalElement && previous.modalElement !== record.modalElement) previous.modalElement.removeAttribute("data-ui-backdrop-hidden");
|
|
74
|
+
syncModalBackdrops(state);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
59
77
|
state.records.push(record);
|
|
60
78
|
if (record.modal) {
|
|
61
79
|
state.modalCount += 1;
|
|
62
80
|
syncScrollLock(document, state);
|
|
63
81
|
}
|
|
82
|
+
syncModalBackdrops(state);
|
|
64
83
|
ensureListeners(document, state);
|
|
84
|
+
announceOverlayOpen(document, record.id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function onOverlayOpen(document, listener) {
|
|
88
|
+
const onOpen = (event) => listener(event.detail.id);
|
|
89
|
+
document.addEventListener("ui-overlay-open", onOpen);
|
|
90
|
+
return () => document.removeEventListener("ui-overlay-open", onOpen);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function announceOverlayOpen(document, id) {
|
|
94
|
+
document.dispatchEvent(new CustomEvent("ui-overlay-open", { detail: { id } }));
|
|
65
95
|
}
|
|
66
96
|
|
|
67
97
|
export function closeOverlay(document, id) {
|
|
@@ -69,10 +99,12 @@ export function closeOverlay(document, id) {
|
|
|
69
99
|
const index = state.records.findIndex((record) => record.id === id);
|
|
70
100
|
if (index >= 0) {
|
|
71
101
|
const [record] = state.records.splice(index, 1);
|
|
102
|
+
(record.modalElement ?? record.element).removeAttribute("data-ui-backdrop-hidden");
|
|
72
103
|
if (record?.modal) {
|
|
73
104
|
state.modalCount = Math.max(0, state.modalCount - 1);
|
|
74
105
|
syncScrollLock(document, state);
|
|
75
106
|
}
|
|
107
|
+
syncModalBackdrops(state);
|
|
76
108
|
}
|
|
77
109
|
removeListeners(document, state);
|
|
78
110
|
}
|
|
@@ -254,6 +254,12 @@
|
|
|
254
254
|
animation: ui-dialog-backdrop-enter var(--_ui-dialog-duration, var(--ui-motion-base)) var(--ui-motion-ease);
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
:host[open][data-ui-backdrop-hidden]::backdrop {
|
|
258
|
+
background-color: transparent;
|
|
259
|
+
animation: none;
|
|
260
|
+
transition: none;
|
|
261
|
+
}
|
|
262
|
+
|
|
257
263
|
@keyframes ui-dialog-backdrop-enter {
|
|
258
264
|
from {
|
|
259
265
|
background-color: transparent;
|
|
@@ -72,7 +72,7 @@ export default function controller(host) {
|
|
|
72
72
|
if (modal) dialog.showModal();
|
|
73
73
|
else dialog.show();
|
|
74
74
|
}
|
|
75
|
-
openOverlay({ id: overlayId, modal, element, dismissible: true, canClose: (reason) => reason === "light-dismiss" ? closedBy === "any" : reason !== "escape" || closedBy !== "none", requestClose });
|
|
75
|
+
openOverlay({ id: overlayId, modal, element, modalElement: dialog, dismissible: true, canClose: (reason) => reason === "light-dismiss" ? closedBy === "any" : reason !== "escape" || closedBy !== "none", requestClose });
|
|
76
76
|
} else {
|
|
77
77
|
if (dialog.open) dialog.close();
|
|
78
78
|
closeOverlay(document, overlayId);
|
|
@@ -83,7 +83,7 @@ export default function controller(host) {
|
|
|
83
83
|
lastOpen = open;
|
|
84
84
|
};
|
|
85
85
|
const onClose = (event) => {
|
|
86
|
-
if (event instanceof CustomEvent) return;
|
|
86
|
+
if (event instanceof CustomEvent || event.target !== dialog) return;
|
|
87
87
|
// Consumers receive one Looma close event with a reason, not an additional native close event.
|
|
88
88
|
event.stopImmediatePropagation();
|
|
89
89
|
if (dialog.open || !host.state.internalOpen) return;
|
|
@@ -134,6 +134,20 @@
|
|
|
134
134
|
white-space: nowrap;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/* Fixed text is part of the value, but should not look editable. */
|
|
138
|
+
.affix:has([slot="prefix"]),
|
|
139
|
+
.affix:has([slot="suffix"]) {
|
|
140
|
+
background: var(--ui-surface-muted);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.affix:has([slot="prefix"]) {
|
|
144
|
+
border-inline-end: 1px solid var(--ui-border-divider);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.affix:has([slot="suffix"]) {
|
|
148
|
+
border-inline-start: 1px solid var(--ui-border-divider);
|
|
149
|
+
}
|
|
150
|
+
|
|
137
151
|
/* Centred rather than padded above and below: the group is exactly a lone Input's height, so a
|
|
138
152
|
small button sits inside it with the difference split evenly, and the field keeps its height. */
|
|
139
153
|
.action {
|
|
@@ -105,6 +105,14 @@
|
|
|
105
105
|
background: var(--ui-overlay-backdrop);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
dialog:modal {
|
|
109
|
+
background: transparent;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
dialog[data-ui-backdrop-hidden]::backdrop {
|
|
113
|
+
background: transparent;
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
.panel {
|
|
109
117
|
position: relative;
|
|
110
118
|
display: flex;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { closeOverlay, openOverlay } from "../shared/overlay.js";
|
|
1
2
|
import { trackTrigger } from "../shared/trigger.js";
|
|
2
3
|
|
|
3
4
|
/** Owns native dialog state while leaving query and result state to the application. */
|
|
4
5
|
export default function controller(host) {
|
|
5
6
|
const dialog = host.refs.dialog;
|
|
7
|
+
const document = dialog.ownerDocument;
|
|
8
|
+
const overlayId = `ui-search-shell-${Math.random().toString(36).slice(2, 11)}`;
|
|
6
9
|
const [trigger, stopTracking] = trackTrigger(host);
|
|
7
10
|
let external = host.props.open.value;
|
|
8
11
|
let activeModal = Boolean(host.props.modal.value);
|
|
@@ -31,8 +34,12 @@ export default function controller(host) {
|
|
|
31
34
|
if (host.state.internalOpen && !dialog.open) {
|
|
32
35
|
if (modal) dialog.showModal();
|
|
33
36
|
else dialog.show();
|
|
34
|
-
}
|
|
37
|
+
}
|
|
38
|
+
if (host.state.internalOpen) {
|
|
39
|
+
openOverlay({ id: overlayId, modal, element: dialog, modalElement: dialog, dismissible: false, requestClose: () => {} });
|
|
40
|
+
} else {
|
|
35
41
|
closeNative();
|
|
42
|
+
closeOverlay(document, overlayId);
|
|
36
43
|
}
|
|
37
44
|
});
|
|
38
45
|
|
|
@@ -69,6 +76,8 @@ export default function controller(host) {
|
|
|
69
76
|
// Only the dialog's own close: a tooltip, menu, or popover inside it reports "close" too, and it bubbles.
|
|
70
77
|
const onClose = (event) => {
|
|
71
78
|
if (event.target !== dialog) return;
|
|
79
|
+
if (dialog.open) return;
|
|
80
|
+
closeOverlay(document, overlayId);
|
|
72
81
|
if (!suppressNativeClose && host.state.internalOpen) dispatchClose("action", trigger());
|
|
73
82
|
};
|
|
74
83
|
dialog.addEventListener("cancel", onCancel);
|
|
@@ -83,5 +92,6 @@ export default function controller(host) {
|
|
|
83
92
|
dialog.removeEventListener("click", onClick);
|
|
84
93
|
dialog.removeEventListener("close", onClose);
|
|
85
94
|
closeNative();
|
|
95
|
+
closeOverlay(document, overlayId);
|
|
86
96
|
};
|
|
87
97
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { announceOverlayOpen } from "../shared/overlay.js";
|
|
2
|
+
|
|
1
3
|
const BREAKPOINTS = { sm: "40rem", md: "48rem", lg: "64rem" };
|
|
2
4
|
|
|
3
5
|
function positive(value, fallback) {
|
|
@@ -134,6 +136,7 @@ export default function controller(host) {
|
|
|
134
136
|
// report it again, forever. Only the drawer's own popover ToggleEvent, which has a newState, counts.
|
|
135
137
|
const onPopoverToggle = (event) => {
|
|
136
138
|
if (!host.state.drawer || event.target !== element || typeof event.newState !== "string") return;
|
|
139
|
+
if (event.newState === "open") announceOverlayOpen(document, element);
|
|
137
140
|
host.dispatch("toggle", { open: event.newState === "open", mode: "drawer", trigger: "programmatic" });
|
|
138
141
|
};
|
|
139
142
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { announceOverlayOpen } from "../shared/overlay.js";
|
|
1
2
|
import { trackTrigger } from "../shared/trigger.js";
|
|
2
3
|
|
|
3
4
|
const instances = new WeakMap();
|
|
@@ -16,8 +17,11 @@ export default function controller(host) {
|
|
|
16
17
|
const [trigger, stopTracking] = trackTrigger(host);
|
|
17
18
|
const toasts = () => host.state.toasts ?? [];
|
|
18
19
|
const authored = () => Array.from(element.children).some((child) => !child.classList.contains("toast"));
|
|
20
|
+
let announcedVisible = false;
|
|
19
21
|
const sync = () => {
|
|
20
22
|
const visible = authored() || toasts().length > 0;
|
|
23
|
+
if (visible && !announcedVisible) announceOverlayOpen(element.ownerDocument, element);
|
|
24
|
+
announcedVisible = visible;
|
|
21
25
|
if (visible && !element.matches(":popover-open")) element.showPopover();
|
|
22
26
|
else if (!visible && element.matches(":popover-open")) element.hidePopover();
|
|
23
27
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeOverlay, createAnchoredSurface, createIdResolver, openOverlay } from "../shared/overlay.js";
|
|
1
|
+
import { closeOverlay, createAnchoredSurface, createIdResolver, onOverlayOpen, openOverlay } from "../shared/overlay.js";
|
|
2
2
|
|
|
3
3
|
const warmTooltips = new WeakMap();
|
|
4
4
|
|
|
@@ -138,12 +138,22 @@ export default function controller(host) {
|
|
|
138
138
|
surface = createAnchoredSurface(element, { anchor: trigger, placement: nextPlacement, gap: offset });
|
|
139
139
|
};
|
|
140
140
|
const close = (reason, input) => {
|
|
141
|
-
if (reason !== "escape" && reason !== "light-dismiss") return;
|
|
141
|
+
if (reason !== "escape" && reason !== "light-dismiss" && reason !== "programmatic") return;
|
|
142
142
|
clearTimers();
|
|
143
|
+
if (!host.state.internalOpen) return;
|
|
143
144
|
host.state.internalOpen = false;
|
|
144
145
|
trackVisible(false);
|
|
146
|
+
surface?.hide();
|
|
147
|
+
closeOverlay(document, overlayId);
|
|
148
|
+
if (trigger && host.props.trigger.value === "click") trigger.setAttribute("aria-expanded", "false");
|
|
145
149
|
host.dispatch("close", { open: false, reason, trigger: input });
|
|
146
150
|
};
|
|
151
|
+
const stopOverlayOpen = onOverlayOpen(document, (id) => {
|
|
152
|
+
if (id === overlayId) return;
|
|
153
|
+
clearTimers();
|
|
154
|
+
focused = false;
|
|
155
|
+
close("programmatic", "programmatic");
|
|
156
|
+
});
|
|
147
157
|
const apply = () => {
|
|
148
158
|
const externalOpen = Boolean(host.props.open.value);
|
|
149
159
|
if (externalOpen !== lastExternalOpen) {
|
|
@@ -169,6 +179,7 @@ export default function controller(host) {
|
|
|
169
179
|
apply();
|
|
170
180
|
return () => {
|
|
171
181
|
stop();
|
|
182
|
+
stopOverlayOpen();
|
|
172
183
|
ids.stop();
|
|
173
184
|
clearTimers();
|
|
174
185
|
trackVisible(false);
|