@rettangoli/ui 1.7.5 → 1.8.0
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/dist/rettangoli-iife-layout.min.js +164 -60
- package/dist/rettangoli-iife-ui.min.js +186 -82
- package/package.json +2 -2
- package/src/common/popover.js +5 -0
- package/src/components/dropdownMenu/dropdownMenu.handlers.js +12 -0
- package/src/components/dropdownMenu/dropdownMenu.schema.yaml +28 -0
- package/src/components/dropdownMenu/dropdownMenu.store.js +33 -0
- package/src/components/dropdownMenu/dropdownMenu.view.yaml +1 -1
- package/src/components/globalUi/globalUi.handlers.js +3 -0
- package/src/components/globalUi/globalUi.store.js +55 -0
- package/src/components/globalUi/globalUi.view.yaml +4 -1
- package/src/deps/createGlobalUI.js +3 -0
- package/src/primitives/dialog.js +249 -4
- package/src/primitives/popover.js +155 -15
|
@@ -1,8 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
css,
|
|
3
|
+
mediaQueries,
|
|
4
|
+
getResponsiveAttribute,
|
|
5
|
+
permutateBreakpoints,
|
|
6
|
+
} from "../common.js";
|
|
2
7
|
import { calculatePopoverPosition } from "../common/popover.js";
|
|
3
8
|
|
|
4
9
|
const CONTENT_WRAPPER_ATTR = "data-rtgl-popover-content";
|
|
5
10
|
const DEFAULT_CONTENT_STYLE = "min-width: 200px; max-width: 400px; box-sizing: border-box;";
|
|
11
|
+
const ACTIVE_OVERLAY_ATTR = "data-rtgl-active-overlay";
|
|
12
|
+
const ACTIVE_PLACE_ATTR = "data-rtgl-active-place";
|
|
13
|
+
const RESPONSIVE_POPOVER_SIZES = ["sm", "md", "lg", "xl"];
|
|
14
|
+
const mediaQueryCondition = (mediaQuery) => mediaQuery.replace(/^@media\s+/, "");
|
|
15
|
+
|
|
16
|
+
const parseResponsiveBooleanValue = (value, fallback = false) => {
|
|
17
|
+
if (value === null || value === undefined) {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof value === "boolean") {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const normalized = `${value}`.trim().toLowerCase();
|
|
26
|
+
if (["false", "0", "no", "off"].includes(normalized)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
6
32
|
|
|
7
33
|
class RettangoliPopoverElement extends HTMLElement {
|
|
8
34
|
static styleSheet = null;
|
|
@@ -40,6 +66,10 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
40
66
|
pointer-events: auto;
|
|
41
67
|
}
|
|
42
68
|
|
|
69
|
+
:host([${ACTIVE_OVERLAY_ATTR}="true"]) dialog::backdrop {
|
|
70
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
71
|
+
}
|
|
72
|
+
|
|
43
73
|
.popover-container {
|
|
44
74
|
position: fixed;
|
|
45
75
|
z-index: inherit;
|
|
@@ -120,7 +150,13 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
120
150
|
this._revealFrameId = null;
|
|
121
151
|
this._positionVersion = 0;
|
|
122
152
|
this._isObservingResize = false;
|
|
153
|
+
this._isModalOpen = false;
|
|
123
154
|
this._observedContentWrapper = null;
|
|
155
|
+
this._onWindowResize = () => {
|
|
156
|
+
this._updateActiveStateAttributes();
|
|
157
|
+
this._syncDialogMode();
|
|
158
|
+
this._schedulePositionUpdate();
|
|
159
|
+
};
|
|
124
160
|
this._resizeObserver = typeof ResizeObserver === "function"
|
|
125
161
|
? new ResizeObserver(() => {
|
|
126
162
|
if (this._isOpen) {
|
|
@@ -142,8 +178,11 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
142
178
|
"open",
|
|
143
179
|
"x",
|
|
144
180
|
"y",
|
|
145
|
-
|
|
146
|
-
|
|
181
|
+
...permutateBreakpoints([
|
|
182
|
+
"place",
|
|
183
|
+
"overlay",
|
|
184
|
+
"no-overlay",
|
|
185
|
+
]),
|
|
147
186
|
"content-w",
|
|
148
187
|
"content-h",
|
|
149
188
|
"content-wh",
|
|
@@ -158,6 +197,7 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
158
197
|
|
|
159
198
|
connectedCallback() {
|
|
160
199
|
this._syncContentWrapper({ reposition: false });
|
|
200
|
+
this._updateActiveStateAttributes();
|
|
161
201
|
|
|
162
202
|
// Check initial open attribute
|
|
163
203
|
if (this.hasAttribute('open')) {
|
|
@@ -168,11 +208,13 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
168
208
|
disconnectedCallback() {
|
|
169
209
|
this._cancelScheduledPositionUpdate();
|
|
170
210
|
this._stopResizeObserver();
|
|
211
|
+
window.removeEventListener("resize", this._onWindowResize);
|
|
171
212
|
|
|
172
213
|
// Clean up dialog if it's open
|
|
173
214
|
if (this._isOpen && this._dialogElement.open) {
|
|
174
215
|
this._dialogElement.close();
|
|
175
216
|
}
|
|
217
|
+
this._isModalOpen = false;
|
|
176
218
|
}
|
|
177
219
|
|
|
178
220
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -185,16 +227,113 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
185
227
|
} else if (newValue === null && this._isOpen) {
|
|
186
228
|
this._hide();
|
|
187
229
|
}
|
|
188
|
-
} else if (
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
230
|
+
} else if (name === 'x' || name === 'y' || name.endsWith('place')) {
|
|
231
|
+
this._updateActiveStateAttributes();
|
|
232
|
+
if (this._isOpen) {
|
|
233
|
+
this._schedulePositionUpdate();
|
|
234
|
+
}
|
|
235
|
+
} else if (name.endsWith('overlay')) {
|
|
236
|
+
this._updateActiveStateAttributes();
|
|
237
|
+
if (this._isOpen) {
|
|
238
|
+
this._syncDialogMode();
|
|
239
|
+
this._schedulePositionUpdate();
|
|
240
|
+
}
|
|
193
241
|
} else if (name.startsWith("content-")) {
|
|
194
242
|
this._syncContentWrapper();
|
|
195
243
|
}
|
|
196
244
|
}
|
|
197
245
|
|
|
246
|
+
_getActiveResponsiveSize() {
|
|
247
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
248
|
+
return "default";
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const size of RESPONSIVE_POPOVER_SIZES) {
|
|
252
|
+
const query = mediaQueries[size];
|
|
253
|
+
if (query && window.matchMedia(mediaQueryCondition(query)).matches) {
|
|
254
|
+
return size;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return "default";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_getActiveResponsiveAttribute(attr) {
|
|
262
|
+
return getResponsiveAttribute({
|
|
263
|
+
element: this,
|
|
264
|
+
size: this._getActiveResponsiveSize(),
|
|
265
|
+
attr,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
_getActivePlace() {
|
|
270
|
+
return this._getActiveResponsiveAttribute("place") || "bs";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
_isOverlayActive() {
|
|
274
|
+
return parseResponsiveBooleanValue(
|
|
275
|
+
this._getActiveResponsiveAttribute("overlay"),
|
|
276
|
+
false,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
_isNoOverlayActive() {
|
|
281
|
+
return parseResponsiveBooleanValue(
|
|
282
|
+
this._getActiveResponsiveAttribute("no-overlay"),
|
|
283
|
+
false,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
_shouldUseModalDialog() {
|
|
288
|
+
return this._isOverlayActive() || !this._isNoOverlayActive();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_updateActiveStateAttributes() {
|
|
292
|
+
const activePlace = this._getActivePlace();
|
|
293
|
+
const overlayActive = this._isOverlayActive();
|
|
294
|
+
|
|
295
|
+
if (this.getAttribute(ACTIVE_PLACE_ATTR) !== activePlace) {
|
|
296
|
+
this.setAttribute(ACTIVE_PLACE_ATTR, activePlace);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (overlayActive) {
|
|
300
|
+
if (this.getAttribute(ACTIVE_OVERLAY_ATTR) !== "true") {
|
|
301
|
+
this.setAttribute(ACTIVE_OVERLAY_ATTR, "true");
|
|
302
|
+
}
|
|
303
|
+
} else if (this.hasAttribute(ACTIVE_OVERLAY_ATTR)) {
|
|
304
|
+
this.removeAttribute(ACTIVE_OVERLAY_ATTR);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
_openDialogElement() {
|
|
309
|
+
if (this._dialogElement.open) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (this._shouldUseModalDialog()) {
|
|
314
|
+
this._dialogElement.showModal();
|
|
315
|
+
this._isModalOpen = true;
|
|
316
|
+
} else {
|
|
317
|
+
this._dialogElement.show();
|
|
318
|
+
this._isModalOpen = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
_syncDialogMode() {
|
|
323
|
+
if (!this._isOpen || !this._dialogElement.open) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const shouldUseModal = this._shouldUseModalDialog();
|
|
328
|
+
if (shouldUseModal === this._isModalOpen) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this._dialogElement.close();
|
|
333
|
+
this._isModalOpen = false;
|
|
334
|
+
this._openDialogElement();
|
|
335
|
+
}
|
|
336
|
+
|
|
198
337
|
_isIgnorableTextNode(node) {
|
|
199
338
|
return node?.nodeType === Node.TEXT_NODE && node.textContent?.trim() === "";
|
|
200
339
|
}
|
|
@@ -288,6 +427,7 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
288
427
|
_show() {
|
|
289
428
|
if (!this._isOpen) {
|
|
290
429
|
this._syncContentWrapper({ reposition: false });
|
|
430
|
+
this._updateActiveStateAttributes();
|
|
291
431
|
|
|
292
432
|
// Create and append slot for content only if it doesn't exist
|
|
293
433
|
if (!this._slotElement) {
|
|
@@ -298,16 +438,13 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
298
438
|
|
|
299
439
|
this._isOpen = true;
|
|
300
440
|
this._startResizeObserver();
|
|
441
|
+
window.addEventListener("resize", this._onWindowResize);
|
|
301
442
|
|
|
302
443
|
// Show the dialog using setTimeout to ensure it's in the DOM
|
|
303
444
|
if (!this._dialogElement.open) {
|
|
304
445
|
setTimeout(() => {
|
|
305
|
-
if (this._dialogElement && !this._dialogElement.open) {
|
|
306
|
-
|
|
307
|
-
this._dialogElement.show();
|
|
308
|
-
} else {
|
|
309
|
-
this._dialogElement.showModal();
|
|
310
|
-
}
|
|
446
|
+
if (this._isOpen && this._dialogElement && !this._dialogElement.open) {
|
|
447
|
+
this._openDialogElement();
|
|
311
448
|
}
|
|
312
449
|
|
|
313
450
|
this._schedulePositionUpdate();
|
|
@@ -323,11 +460,13 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
323
460
|
this._isOpen = false;
|
|
324
461
|
this._cancelScheduledPositionUpdate();
|
|
325
462
|
this._stopResizeObserver();
|
|
463
|
+
window.removeEventListener("resize", this._onWindowResize);
|
|
326
464
|
|
|
327
465
|
// Close the dialog
|
|
328
466
|
if (this._dialogElement.open) {
|
|
329
467
|
this._dialogElement.close();
|
|
330
468
|
}
|
|
469
|
+
this._isModalOpen = false;
|
|
331
470
|
|
|
332
471
|
// Remove slot to unmount content
|
|
333
472
|
if (this._slotElement) {
|
|
@@ -413,7 +552,8 @@ class RettangoliPopoverElement extends HTMLElement {
|
|
|
413
552
|
|
|
414
553
|
const x = this._readCoordinateAttr('x');
|
|
415
554
|
const y = this._readCoordinateAttr('y');
|
|
416
|
-
const place = this.
|
|
555
|
+
const place = this._getActivePlace();
|
|
556
|
+
this._updateActiveStateAttributes();
|
|
417
557
|
const rect = this._popoverContainer.getBoundingClientRect();
|
|
418
558
|
const { left, top } = this._calculatePosition(x, y, rect.width, rect.height, place);
|
|
419
559
|
|