@corndev/ui-overlays 0.0.0-reserved.0 → 1.0.0-beta.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/dist/index.js ADDED
@@ -0,0 +1,1089 @@
1
+ // ../../src/lib/styles-check.js
2
+ var PACKAGES = {
3
+ "@corndev/ui-overlays": "--corn-styles-ui-overlays",
4
+ "@corndev/ui-notifications": "--corn-styles-ui-notifications",
5
+ "@corndev/ui-markdown": "--corn-styles-ui-markdown",
6
+ "@corndev/ui-icons": "--corn-styles-ui-icons"
7
+ };
8
+ function checkStylesLoaded(pkg, component) {
9
+ if (typeof document === "undefined" || typeof window === "undefined")
10
+ return;
11
+ if (typeof process !== "undefined" && false)
12
+ return;
13
+ const marker = PACKAGES[pkg];
14
+ if (!marker)
15
+ return;
16
+ const checked = globalThis.__cornStylesChecked ??= /* @__PURE__ */ new Set();
17
+ if (checked.has(pkg))
18
+ return;
19
+ checked.add(pkg);
20
+ const check = () => {
21
+ const value = getComputedStyle(document.documentElement).getPropertyValue(marker).trim();
22
+ if (value)
23
+ return;
24
+ console.warn(
25
+ `[corn-ui] ${component} rendered, but the ${pkg} stylesheet is not loaded, so its components render unstyled. Add this once, beside '@corndev/ui/theme':
26
+
27
+ import '${pkg}/styles';`
28
+ );
29
+ };
30
+ if (document.readyState === "complete")
31
+ setTimeout(check, 0);
32
+ else
33
+ window.addEventListener("load", check, { once: true });
34
+ }
35
+
36
+ // ../../src/lib/inert.js
37
+ var LAYER = "data-corn-layer";
38
+ var MARK = "data-corn-inert";
39
+ function refreshInert(doc = document) {
40
+ for (const el of doc.querySelectorAll(`[${MARK}]`)) {
41
+ el.removeAttribute("inert");
42
+ el.removeAttribute(MARK);
43
+ }
44
+ const layers = doc.querySelectorAll(`[${LAYER}]`);
45
+ const top = layers[layers.length - 1];
46
+ if (!top?.parentElement)
47
+ return;
48
+ for (const sibling of top.parentElement.children) {
49
+ if (sibling === top || sibling.hasAttribute("inert"))
50
+ continue;
51
+ sibling.setAttribute("inert", "");
52
+ sibling.setAttribute(MARK, "");
53
+ }
54
+ }
55
+ function openLayer(el) {
56
+ if (!el)
57
+ return;
58
+ el.setAttribute(LAYER, "");
59
+ refreshInert(el.ownerDocument);
60
+ }
61
+ function closeLayer(el) {
62
+ if (!el)
63
+ return;
64
+ const doc = el.ownerDocument || document;
65
+ el.removeAttribute(LAYER);
66
+ refreshInert(doc);
67
+ }
68
+ function isTopLayer(el) {
69
+ const doc = el?.ownerDocument;
70
+ if (!doc)
71
+ return false;
72
+ const layers = doc.querySelectorAll(`[${LAYER}]`);
73
+ return layers[layers.length - 1] === el;
74
+ }
75
+ function hasOpenLayer(doc, selector) {
76
+ return !!doc.querySelector(`[${LAYER}]${selector}`);
77
+ }
78
+
79
+ // ../../src/lib/dialog-focus.js
80
+ var CLOSE_ATTR = "data-dialog-close";
81
+ var FOCUSABLE = [
82
+ "a[href]",
83
+ "area[href]",
84
+ "button:not([disabled])",
85
+ 'input:not([disabled]):not([type="hidden"])',
86
+ "select:not([disabled])",
87
+ "textarea:not([disabled])",
88
+ "iframe",
89
+ '[contenteditable=""]',
90
+ '[contenteditable="true"]',
91
+ '[tabindex]:not([tabindex="-1"])'
92
+ ].join(",");
93
+ function focusableIn(root) {
94
+ return [...root.querySelectorAll(FOCUSABLE)].filter((el) => !el.closest("[hidden], [inert]"));
95
+ }
96
+ function chooseInitialFocus(root) {
97
+ const marked = root.querySelector("[data-autofocus]");
98
+ const fromMark = marked && (marked.matches(FOCUSABLE) ? marked : focusableIn(marked)[0]);
99
+ if (fromMark)
100
+ return fromMark;
101
+ const active = root.ownerDocument.activeElement;
102
+ if (active && active !== root && root.contains(active) && !active.hasAttribute(CLOSE_ATTR))
103
+ return active;
104
+ const attributed = [...root.querySelectorAll("[autofocus]")].find((el) => el.matches(FOCUSABLE));
105
+ if (attributed)
106
+ return attributed;
107
+ const all = focusableIn(root);
108
+ return all.find((el) => !el.hasAttribute(CLOSE_ATTR)) ?? all[0] ?? root;
109
+ }
110
+ function wrapTab(event, root) {
111
+ if (event.key !== "Tab")
112
+ return;
113
+ const els = focusableIn(root);
114
+ const active = root.ownerDocument.activeElement;
115
+ if (!els.length) {
116
+ event.preventDefault();
117
+ root.focus();
118
+ return;
119
+ }
120
+ const first = els[0];
121
+ const last = els[els.length - 1];
122
+ if (!root.contains(active)) {
123
+ event.preventDefault();
124
+ (event.shiftKey ? last : first).focus();
125
+ } else if (event.shiftKey && (active === first || active === root)) {
126
+ event.preventDefault();
127
+ last.focus();
128
+ } else if (!event.shiftKey && active === last) {
129
+ event.preventDefault();
130
+ first.focus();
131
+ }
132
+ }
133
+
134
+ // ../../src/lib/after-transition.js
135
+ function afterTransition(el, done, fallbackMs = 500) {
136
+ let settled = false;
137
+ const onEnd = (event) => {
138
+ if (event.target === el)
139
+ finish();
140
+ };
141
+ const timer = setTimeout(() => finish(), fallbackMs);
142
+ el.addEventListener("transitionend", onEnd);
143
+ function cancel() {
144
+ settled = true;
145
+ clearTimeout(timer);
146
+ el.removeEventListener("transitionend", onEnd);
147
+ }
148
+ function finish() {
149
+ if (settled)
150
+ return;
151
+ cancel();
152
+ done();
153
+ }
154
+ return cancel;
155
+ }
156
+
157
+ // ../../src/components/modal/modal.js
158
+ var BASE = "corn-modal";
159
+ function createModal(options = {}) {
160
+ checkStylesLoaded("@corndev/ui-overlays", "createModal()");
161
+ const {
162
+ title,
163
+ content,
164
+ footer,
165
+ size = "md",
166
+ closeable = true,
167
+ onOpen,
168
+ onClose
169
+ } = options;
170
+ let isOpen = false;
171
+ let previouslyFocused = null;
172
+ const backdrop = document.createElement("div");
173
+ backdrop.className = `${BASE}-backdrop`;
174
+ const dialog = document.createElement("div");
175
+ dialog.className = BASE;
176
+ dialog.setAttribute("data-size", size);
177
+ dialog.setAttribute("role", "dialog");
178
+ dialog.setAttribute("aria-modal", "true");
179
+ const titleId = `${BASE}-title-${Math.random().toString(36).slice(2, 9)}`;
180
+ if (title)
181
+ dialog.setAttribute("aria-labelledby", titleId);
182
+ dialog.tabIndex = -1;
183
+ const header = document.createElement("div");
184
+ header.className = `${BASE}-header`;
185
+ if (title) {
186
+ const titleEl = document.createElement("h2");
187
+ titleEl.className = `${BASE}-title`;
188
+ titleEl.id = titleId;
189
+ titleEl.textContent = title;
190
+ header.appendChild(titleEl);
191
+ }
192
+ if (closeable) {
193
+ const closeBtn = document.createElement("button");
194
+ closeBtn.className = `${BASE}-close`;
195
+ closeBtn.setAttribute("aria-label", "Close modal");
196
+ closeBtn.setAttribute(CLOSE_ATTR, "");
197
+ closeBtn.innerHTML = "✕";
198
+ closeBtn.addEventListener("click", close);
199
+ header.appendChild(closeBtn);
200
+ }
201
+ const body = document.createElement("div");
202
+ body.className = `${BASE}-body`;
203
+ if (content instanceof Node) {
204
+ body.appendChild(content);
205
+ } else if (content != null) {
206
+ body.textContent = String(content);
207
+ }
208
+ if (footer) {
209
+ const footerEl = document.createElement("div");
210
+ footerEl.className = `${BASE}-footer`;
211
+ footerEl.appendChild(footer);
212
+ dialog.appendChild(header);
213
+ dialog.appendChild(body);
214
+ dialog.appendChild(footerEl);
215
+ } else {
216
+ dialog.appendChild(header);
217
+ dialog.appendChild(body);
218
+ }
219
+ backdrop.appendChild(dialog);
220
+ function trapFocus(e) {
221
+ if (!isTopLayer(backdrop))
222
+ return;
223
+ if (e.key === "Escape") {
224
+ if (closeable)
225
+ close();
226
+ return;
227
+ }
228
+ wrapTab(e, dialog);
229
+ }
230
+ function onBackdropClick(e) {
231
+ if (closeable && e.target === backdrop)
232
+ close();
233
+ }
234
+ let cancelTeardown = null;
235
+ function open() {
236
+ if (isOpen)
237
+ return;
238
+ cancelTeardown?.();
239
+ cancelTeardown = null;
240
+ isOpen = true;
241
+ previouslyFocused = document.activeElement;
242
+ document.body.appendChild(backdrop);
243
+ document.body.classList.add(`${BASE}-open`);
244
+ openLayer(backdrop);
245
+ requestAnimationFrame(() => {
246
+ backdrop.setAttribute("data-visible", "");
247
+ dialog.setAttribute("data-visible", "");
248
+ });
249
+ backdrop.addEventListener("click", onBackdropClick);
250
+ document.addEventListener("keydown", trapFocus);
251
+ chooseInitialFocus(dialog).focus();
252
+ onOpen?.();
253
+ }
254
+ function teardown() {
255
+ cancelTeardown = null;
256
+ if (backdrop.parentNode)
257
+ backdrop.parentNode.removeChild(backdrop);
258
+ if (!hasOpenLayer(document, `.${BASE}-backdrop`))
259
+ document.body.classList.remove(`${BASE}-open`);
260
+ }
261
+ function close() {
262
+ if (!isOpen)
263
+ return;
264
+ isOpen = false;
265
+ backdrop.removeAttribute("data-visible");
266
+ dialog.removeAttribute("data-visible");
267
+ closeLayer(backdrop);
268
+ cancelTeardown = afterTransition(backdrop, teardown);
269
+ backdrop.removeEventListener("click", onBackdropClick);
270
+ document.removeEventListener("keydown", trapFocus);
271
+ previouslyFocused?.focus();
272
+ onClose?.();
273
+ }
274
+ function destroy() {
275
+ close();
276
+ closeLayer(backdrop);
277
+ backdrop.removeEventListener("click", onBackdropClick);
278
+ document.removeEventListener("keydown", trapFocus);
279
+ cancelTeardown?.();
280
+ teardown();
281
+ }
282
+ return { open, close, destroy, el: dialog };
283
+ }
284
+
285
+ // ../../src/components/drawer/drawer.js
286
+ var BASE2 = "corn-drawer";
287
+ function createDrawer(options = {}) {
288
+ checkStylesLoaded("@corndev/ui-overlays", "createDrawer()");
289
+ const {
290
+ title,
291
+ content,
292
+ footer,
293
+ side = "right",
294
+ size = "md",
295
+ closeable = true,
296
+ onOpen,
297
+ onClose
298
+ } = options;
299
+ let isOpen = false;
300
+ let previouslyFocused = null;
301
+ const backdrop = document.createElement("div");
302
+ backdrop.className = `${BASE2}-backdrop`;
303
+ const panel = document.createElement("div");
304
+ panel.className = BASE2;
305
+ panel.setAttribute("data-side", side);
306
+ panel.setAttribute("data-size", size);
307
+ panel.setAttribute("role", "dialog");
308
+ panel.setAttribute("aria-modal", "true");
309
+ const titleId = `${BASE2}-title-${Math.random().toString(36).slice(2, 9)}`;
310
+ if (title)
311
+ panel.setAttribute("aria-labelledby", titleId);
312
+ panel.tabIndex = -1;
313
+ const header = document.createElement("div");
314
+ header.className = `${BASE2}-header`;
315
+ if (title) {
316
+ const titleEl = document.createElement("h2");
317
+ titleEl.className = `${BASE2}-title`;
318
+ titleEl.id = titleId;
319
+ titleEl.textContent = title;
320
+ header.appendChild(titleEl);
321
+ }
322
+ if (closeable) {
323
+ const closeBtn = document.createElement("button");
324
+ closeBtn.className = `${BASE2}-close`;
325
+ closeBtn.setAttribute("aria-label", "Close drawer");
326
+ closeBtn.setAttribute(CLOSE_ATTR, "");
327
+ closeBtn.innerHTML = "✕";
328
+ closeBtn.addEventListener("click", close);
329
+ header.appendChild(closeBtn);
330
+ }
331
+ const body = document.createElement("div");
332
+ body.className = `${BASE2}-body`;
333
+ if (content instanceof Node) {
334
+ body.appendChild(content);
335
+ } else if (content != null) {
336
+ body.textContent = String(content);
337
+ }
338
+ panel.appendChild(header);
339
+ panel.appendChild(body);
340
+ if (footer) {
341
+ const footerEl = document.createElement("div");
342
+ footerEl.className = `${BASE2}-footer`;
343
+ footerEl.appendChild(footer);
344
+ panel.appendChild(footerEl);
345
+ }
346
+ backdrop.appendChild(panel);
347
+ function trapFocus(e) {
348
+ if (!isTopLayer(backdrop))
349
+ return;
350
+ if (e.key === "Escape") {
351
+ if (closeable)
352
+ close();
353
+ return;
354
+ }
355
+ wrapTab(e, panel);
356
+ }
357
+ function onBackdropClick(e) {
358
+ if (closeable && e.target === backdrop)
359
+ close();
360
+ }
361
+ let cancelTeardown = null;
362
+ function open() {
363
+ if (isOpen)
364
+ return;
365
+ cancelTeardown?.();
366
+ cancelTeardown = null;
367
+ isOpen = true;
368
+ previouslyFocused = document.activeElement;
369
+ document.body.appendChild(backdrop);
370
+ document.body.classList.add(`${BASE2}-open`);
371
+ openLayer(backdrop);
372
+ requestAnimationFrame(() => {
373
+ backdrop.setAttribute("data-visible", "");
374
+ panel.setAttribute("data-open", "");
375
+ });
376
+ backdrop.addEventListener("click", onBackdropClick);
377
+ document.addEventListener("keydown", trapFocus);
378
+ chooseInitialFocus(panel).focus();
379
+ onOpen?.();
380
+ }
381
+ function teardown() {
382
+ cancelTeardown = null;
383
+ if (backdrop.parentNode)
384
+ backdrop.parentNode.removeChild(backdrop);
385
+ if (!hasOpenLayer(document, `.${BASE2}-backdrop`))
386
+ document.body.classList.remove(`${BASE2}-open`);
387
+ }
388
+ function close() {
389
+ if (!isOpen)
390
+ return;
391
+ isOpen = false;
392
+ backdrop.removeAttribute("data-visible");
393
+ panel.removeAttribute("data-open");
394
+ closeLayer(backdrop);
395
+ cancelTeardown = afterTransition(backdrop, teardown);
396
+ backdrop.removeEventListener("click", onBackdropClick);
397
+ document.removeEventListener("keydown", trapFocus);
398
+ previouslyFocused?.focus();
399
+ onClose?.();
400
+ }
401
+ function destroy() {
402
+ close();
403
+ closeLayer(backdrop);
404
+ backdrop.removeEventListener("click", onBackdropClick);
405
+ document.removeEventListener("keydown", trapFocus);
406
+ cancelTeardown?.();
407
+ teardown();
408
+ }
409
+ return { open, close, destroy, el: panel };
410
+ }
411
+
412
+ // ../../src/components/accordion/accordion.js
413
+ var BASE3 = "corn-accordion";
414
+ function createAccordion(containerEl, options = {}) {
415
+ checkStylesLoaded("@corndev/ui-overlays", "createAccordion()");
416
+ const { multiple = false, defaultOpen, onChange, onExpand } = options;
417
+ const expanded = /* @__PURE__ */ new Set();
418
+ containerEl.classList.add(BASE3);
419
+ const uid4 = `${BASE3}-${Math.random().toString(36).slice(2, 9)}`;
420
+ const ownsItem = (el) => el.closest(`.${BASE3}`) === containerEl;
421
+ const items = Array.from(containerEl.querySelectorAll("[data-accordion-item]")).filter(ownsItem);
422
+ const itemMap = /* @__PURE__ */ new Map();
423
+ items.forEach((el) => {
424
+ const key = el.getAttribute("data-accordion-item");
425
+ if (!itemMap.has(key))
426
+ itemMap.set(key, {});
427
+ if (el.hasAttribute("data-accordion-trigger")) {
428
+ itemMap.get(key).trigger = el;
429
+ }
430
+ if (el.hasAttribute("data-accordion-panel")) {
431
+ itemMap.get(key).panel = el;
432
+ }
433
+ });
434
+ const triggerId = (key) => `${uid4}-trigger-${key}`;
435
+ const panelId = (key) => `${uid4}-panel-${key}`;
436
+ const wrappers = [];
437
+ itemMap.forEach(({ trigger, panel }) => {
438
+ const parent = trigger?.parentElement;
439
+ if (!parent || parent === containerEl || parent !== panel?.parentElement)
440
+ return;
441
+ if (parent.querySelectorAll("[data-accordion-trigger]").length !== 1)
442
+ return;
443
+ if (!parent.classList.contains(`${BASE3}-item`)) {
444
+ parent.classList.add(`${BASE3}-item`);
445
+ wrappers.push(parent);
446
+ }
447
+ });
448
+ itemMap.forEach(({ trigger, panel }, key) => {
449
+ if (trigger) {
450
+ trigger.classList.add(`${BASE3}-trigger`);
451
+ trigger.setAttribute("aria-expanded", "false");
452
+ panelId(key);
453
+ trigger.setAttribute("aria-controls", panelId(key));
454
+ trigger.id = triggerId(key);
455
+ }
456
+ if (panel) {
457
+ panel.classList.add(`${BASE3}-panel`);
458
+ panel.id = panelId(key);
459
+ panel.setAttribute("role", "region");
460
+ panel.setAttribute("aria-labelledby", triggerId(key));
461
+ panel.hidden = true;
462
+ }
463
+ });
464
+ function open(key) {
465
+ const item = itemMap.get(key);
466
+ if (!item || !item.panel || !item.trigger)
467
+ return;
468
+ if (item.trigger.getAttribute("aria-expanded") === "true")
469
+ return;
470
+ if (!multiple) {
471
+ itemMap.forEach((other, otherKey) => {
472
+ if (otherKey !== key)
473
+ _close(otherKey);
474
+ });
475
+ }
476
+ item.trigger.setAttribute("aria-expanded", "true");
477
+ item.trigger.setAttribute("data-open", "");
478
+ item.panel.hidden = false;
479
+ item.panel.setAttribute("data-open", "");
480
+ if (!expanded.has(key)) {
481
+ expanded.add(key);
482
+ onExpand?.(item.panel, key);
483
+ }
484
+ onChange?.(key, true);
485
+ }
486
+ function _close(key) {
487
+ const item = itemMap.get(key);
488
+ if (!item || !item.panel || !item.trigger)
489
+ return;
490
+ if (item.trigger.getAttribute("aria-expanded") === "false")
491
+ return;
492
+ item.trigger.setAttribute("aria-expanded", "false");
493
+ item.trigger.removeAttribute("data-open");
494
+ item.panel.hidden = true;
495
+ item.panel.removeAttribute("data-open");
496
+ onChange?.(key, false);
497
+ }
498
+ function close(key) {
499
+ _close(key);
500
+ }
501
+ function toggle(key) {
502
+ const item = itemMap.get(key);
503
+ if (!item)
504
+ return;
505
+ if (item.trigger?.getAttribute("aria-expanded") === "true") {
506
+ _close(key);
507
+ } else {
508
+ open(key);
509
+ }
510
+ }
511
+ function handleClick(e) {
512
+ const trigger = e.target.closest("[data-accordion-trigger]");
513
+ if (!trigger || !ownsItem(trigger))
514
+ return;
515
+ toggle(trigger.getAttribute("data-accordion-item"));
516
+ }
517
+ function handleKeydown(e) {
518
+ const trigger = e.target.closest("[data-accordion-trigger]");
519
+ if (!trigger || !ownsItem(trigger))
520
+ return;
521
+ const keys = Array.from(itemMap.keys());
522
+ const key = trigger.getAttribute("data-accordion-item");
523
+ const idx = keys.indexOf(key);
524
+ if (e.key === "ArrowDown") {
525
+ e.preventDefault();
526
+ const next = itemMap.get(keys[(idx + 1) % keys.length]);
527
+ next?.trigger?.focus();
528
+ } else if (e.key === "ArrowUp") {
529
+ e.preventDefault();
530
+ const prev = itemMap.get(keys[(idx - 1 + keys.length) % keys.length]);
531
+ prev?.trigger?.focus();
532
+ } else if (e.key === "Home") {
533
+ e.preventDefault();
534
+ itemMap.get(keys[0])?.trigger?.focus();
535
+ } else if (e.key === "End") {
536
+ e.preventDefault();
537
+ itemMap.get(keys[keys.length - 1])?.trigger?.focus();
538
+ }
539
+ }
540
+ containerEl.addEventListener("click", handleClick);
541
+ containerEl.addEventListener("keydown", handleKeydown);
542
+ const defaults = defaultOpen ? Array.isArray(defaultOpen) ? defaultOpen : [defaultOpen] : [];
543
+ defaults.forEach((key) => open(key));
544
+ function destroy() {
545
+ containerEl.removeEventListener("click", handleClick);
546
+ containerEl.removeEventListener("keydown", handleKeydown);
547
+ containerEl.classList.remove(BASE3);
548
+ for (const wrapper of wrappers)
549
+ wrapper.classList.remove(`${BASE3}-item`);
550
+ itemMap.forEach(({ trigger, panel }) => {
551
+ trigger?.classList.remove(`${BASE3}-trigger`);
552
+ trigger?.removeAttribute("data-open");
553
+ trigger?.removeAttribute("aria-expanded");
554
+ trigger?.removeAttribute("aria-controls");
555
+ panel?.classList.remove(`${BASE3}-panel`);
556
+ panel?.removeAttribute("data-open");
557
+ panel?.removeAttribute("role");
558
+ panel?.removeAttribute("aria-labelledby");
559
+ if (panel)
560
+ panel.hidden = false;
561
+ });
562
+ }
563
+ return { open, close, toggle, destroy };
564
+ }
565
+
566
+ // ../../src/lib/described-by.js
567
+ var tokens = (value) => (value ?? "").split(/\s+/).filter(Boolean);
568
+ function mergeIds(...lists) {
569
+ const ids = [...new Set(lists.flatMap(tokens))];
570
+ return ids.length ? ids.join(" ") : void 0;
571
+ }
572
+ function addDescribedBy(el, ...ids) {
573
+ const merged = mergeIds(el.getAttribute("aria-describedby"), ...ids);
574
+ if (merged)
575
+ el.setAttribute("aria-describedby", merged);
576
+ }
577
+ function removeDescribedBy(el, ...ids) {
578
+ const gone = new Set(ids.flatMap(tokens));
579
+ const left = tokens(el.getAttribute("aria-describedby")).filter((id) => !gone.has(id));
580
+ if (left.length)
581
+ el.setAttribute("aria-describedby", left.join(" "));
582
+ else
583
+ el.removeAttribute("aria-describedby");
584
+ }
585
+
586
+ // ../../src/components/tooltip/tooltip.js
587
+ var BASE4 = "corn-tooltip";
588
+ var uid = 0;
589
+ function getPosition(trigger, tooltip, placement) {
590
+ const tRect = trigger.getBoundingClientRect();
591
+ const ttRect = tooltip.getBoundingClientRect();
592
+ const gap = 8;
593
+ const vw = window.innerWidth;
594
+ const vh = window.innerHeight;
595
+ const positions = {
596
+ top: { top: tRect.top - ttRect.height - gap, left: tRect.left + tRect.width / 2 - ttRect.width / 2 },
597
+ bottom: { top: tRect.bottom + gap, left: tRect.left + tRect.width / 2 - ttRect.width / 2 },
598
+ left: { top: tRect.top + tRect.height / 2 - ttRect.height / 2, left: tRect.left - ttRect.width - gap },
599
+ right: { top: tRect.top + tRect.height / 2 - ttRect.height / 2, left: tRect.right + gap }
600
+ };
601
+ let actual = placement;
602
+ if (placement === "top" && tRect.top < ttRect.height + gap)
603
+ actual = "bottom";
604
+ else if (placement === "bottom" && tRect.bottom + ttRect.height + gap > vh)
605
+ actual = "top";
606
+ else if (placement === "left" && tRect.left < ttRect.width + gap)
607
+ actual = "right";
608
+ else if (placement === "right" && tRect.right + ttRect.width + gap > vw)
609
+ actual = "left";
610
+ const pos = positions[actual];
611
+ pos.left = Math.max(gap, Math.min(pos.left, vw - ttRect.width - gap));
612
+ return { ...pos, actualPlacement: actual };
613
+ }
614
+ function createTooltip(triggerEl, options = {}) {
615
+ checkStylesLoaded("@corndev/ui-overlays", "createTooltip()");
616
+ const {
617
+ content,
618
+ placement = "top",
619
+ delay = 0
620
+ } = options;
621
+ if (!content)
622
+ throw new Error("createTooltip: content is required");
623
+ const id = `${BASE4}-${++uid}`;
624
+ let tooltipEl = null;
625
+ let showTimer = null;
626
+ let visible = false;
627
+ function buildTooltip() {
628
+ tooltipEl = document.createElement("div");
629
+ tooltipEl.id = id;
630
+ tooltipEl.className = BASE4;
631
+ tooltipEl.setAttribute("role", "tooltip");
632
+ tooltipEl.textContent = content;
633
+ tooltipEl.style.position = "fixed";
634
+ document.body.appendChild(tooltipEl);
635
+ }
636
+ function position() {
637
+ if (!tooltipEl)
638
+ return;
639
+ const { top, left, actualPlacement } = getPosition(triggerEl, tooltipEl, placement);
640
+ tooltipEl.style.top = `${top}px`;
641
+ tooltipEl.style.left = `${left}px`;
642
+ tooltipEl.className = BASE4;
643
+ tooltipEl.setAttribute("data-placement", actualPlacement);
644
+ }
645
+ function show() {
646
+ if (visible)
647
+ return;
648
+ clearTimeout(showTimer);
649
+ const doShow = () => {
650
+ if (!tooltipEl)
651
+ buildTooltip();
652
+ position();
653
+ requestAnimationFrame(() => {
654
+ if (tooltipEl)
655
+ tooltipEl.setAttribute("data-visible", "");
656
+ });
657
+ visible = true;
658
+ };
659
+ if (delay) {
660
+ showTimer = setTimeout(doShow, delay);
661
+ } else {
662
+ doShow();
663
+ }
664
+ }
665
+ function hide() {
666
+ clearTimeout(showTimer);
667
+ if (!visible || !tooltipEl)
668
+ return;
669
+ tooltipEl.removeAttribute("data-visible");
670
+ visible = false;
671
+ }
672
+ function onTransitionEnd() {
673
+ if (!visible && tooltipEl) {
674
+ tooltipEl.remove();
675
+ tooltipEl = null;
676
+ }
677
+ }
678
+ addDescribedBy(triggerEl, id);
679
+ triggerEl.addEventListener("mouseenter", show);
680
+ triggerEl.addEventListener("mouseleave", hide);
681
+ triggerEl.addEventListener("focusin", show);
682
+ triggerEl.addEventListener("focusout", hide);
683
+ function handleTransitionEnd(e) {
684
+ if (e.target === tooltipEl)
685
+ onTransitionEnd();
686
+ }
687
+ document.addEventListener("transitionend", handleTransitionEnd);
688
+ function destroy() {
689
+ clearTimeout(showTimer);
690
+ triggerEl.removeEventListener("mouseenter", show);
691
+ triggerEl.removeEventListener("mouseleave", hide);
692
+ triggerEl.removeEventListener("focusin", show);
693
+ triggerEl.removeEventListener("focusout", hide);
694
+ document.removeEventListener("transitionend", handleTransitionEnd);
695
+ removeDescribedBy(triggerEl, id);
696
+ if (tooltipEl) {
697
+ tooltipEl.remove();
698
+ tooltipEl = null;
699
+ }
700
+ }
701
+ return { show, hide, destroy };
702
+ }
703
+
704
+ // ../../src/components/popover/popover.js
705
+ var BASE5 = "corn-popover";
706
+ var uid2 = 0;
707
+ function getPosition2(trigger, popover, placement) {
708
+ const tRect = trigger.getBoundingClientRect();
709
+ const pRect = popover.getBoundingClientRect();
710
+ const gap = 8;
711
+ const vw = window.innerWidth;
712
+ const vh = window.innerHeight;
713
+ const positions = {
714
+ top: { top: tRect.top - pRect.height - gap, left: tRect.left + tRect.width / 2 - pRect.width / 2 },
715
+ bottom: { top: tRect.bottom + gap, left: tRect.left + tRect.width / 2 - pRect.width / 2 },
716
+ left: { top: tRect.top + tRect.height / 2 - pRect.height / 2, left: tRect.left - pRect.width - gap },
717
+ right: { top: tRect.top + tRect.height / 2 - pRect.height / 2, left: tRect.right + gap }
718
+ };
719
+ let actual = placement;
720
+ if (placement === "top" && tRect.top < pRect.height + gap)
721
+ actual = "bottom";
722
+ else if (placement === "bottom" && tRect.bottom + pRect.height + gap > vh)
723
+ actual = "top";
724
+ else if (placement === "left" && tRect.left < pRect.width + gap)
725
+ actual = "right";
726
+ else if (placement === "right" && tRect.right + pRect.width + gap > vw)
727
+ actual = "left";
728
+ const pos = positions[actual];
729
+ pos.left = Math.max(gap, Math.min(pos.left, scrollX + vw - pRect.width - gap));
730
+ return { ...pos, actualPlacement: actual };
731
+ }
732
+ function createPopover(triggerEl, options = {}) {
733
+ checkStylesLoaded("@corndev/ui-overlays", "createPopover()");
734
+ const {
735
+ content,
736
+ title,
737
+ placement = "bottom",
738
+ defaultOpen = false,
739
+ onChange,
740
+ trapFocus = false,
741
+ closeOnOutsideClick = true,
742
+ onOpen,
743
+ onClose
744
+ } = options;
745
+ if (!content)
746
+ throw new Error("createPopover: content is required");
747
+ const id = `${BASE5}-${++uid2}`;
748
+ let popoverEl = null;
749
+ let isOpen = false;
750
+ let opener = null;
751
+ triggerEl.setAttribute("aria-expanded", "false");
752
+ triggerEl.setAttribute("aria-controls", id);
753
+ function buildPopover() {
754
+ popoverEl = document.createElement("div");
755
+ popoverEl.id = id;
756
+ popoverEl.className = BASE5;
757
+ popoverEl.setAttribute("role", "dialog");
758
+ popoverEl.setAttribute("aria-modal", trapFocus ? "true" : "false");
759
+ if (trapFocus)
760
+ popoverEl.tabIndex = -1;
761
+ if (title) {
762
+ const titleId = `${id}-title`;
763
+ const h = document.createElement("div");
764
+ h.className = `${BASE5}-header`;
765
+ const t = document.createElement("div");
766
+ t.id = titleId;
767
+ t.className = `${BASE5}-title`;
768
+ t.textContent = title;
769
+ const closeBtn = document.createElement("button");
770
+ closeBtn.type = "button";
771
+ closeBtn.className = `${BASE5}-close`;
772
+ closeBtn.setAttribute("aria-label", "Close");
773
+ closeBtn.setAttribute(CLOSE_ATTR, "");
774
+ closeBtn.innerHTML = "&times;";
775
+ closeBtn.addEventListener("click", close);
776
+ h.appendChild(t);
777
+ h.appendChild(closeBtn);
778
+ popoverEl.appendChild(h);
779
+ popoverEl.setAttribute("aria-labelledby", titleId);
780
+ }
781
+ const body = document.createElement("div");
782
+ body.className = `${BASE5}-body`;
783
+ if (content instanceof Node) {
784
+ body.appendChild(content);
785
+ } else if (content != null) {
786
+ body.textContent = String(content);
787
+ }
788
+ popoverEl.appendChild(body);
789
+ popoverEl.style.position = "fixed";
790
+ document.body.appendChild(popoverEl);
791
+ }
792
+ function reposition() {
793
+ if (!popoverEl)
794
+ return;
795
+ const { top, left, actualPlacement } = getPosition2(triggerEl, popoverEl, placement);
796
+ popoverEl.style.top = `${top}px`;
797
+ popoverEl.style.left = `${left}px`;
798
+ popoverEl.dataset.placement = actualPlacement;
799
+ popoverEl.className = BASE5;
800
+ popoverEl.setAttribute("data-placement", actualPlacement);
801
+ popoverEl.toggleAttribute("data-visible", isOpen);
802
+ }
803
+ function open() {
804
+ if (isOpen)
805
+ return;
806
+ if (!popoverEl)
807
+ buildPopover();
808
+ reposition();
809
+ if (trapFocus) {
810
+ opener = document.activeElement;
811
+ openLayer(popoverEl);
812
+ }
813
+ requestAnimationFrame(() => {
814
+ if (!popoverEl || !isOpen)
815
+ return;
816
+ popoverEl.setAttribute("data-visible", "");
817
+ if (trapFocus)
818
+ chooseInitialFocus(popoverEl).focus();
819
+ });
820
+ triggerEl.setAttribute("aria-expanded", "true");
821
+ isOpen = true;
822
+ onChange?.(true);
823
+ onOpen?.();
824
+ }
825
+ function close() {
826
+ if (!isOpen || !popoverEl)
827
+ return;
828
+ popoverEl.removeAttribute("data-visible");
829
+ triggerEl.setAttribute("aria-expanded", "false");
830
+ isOpen = false;
831
+ if (trapFocus) {
832
+ closeLayer(popoverEl);
833
+ const back = opener;
834
+ opener = null;
835
+ if (back?.isConnected && typeof back.focus === "function")
836
+ back.focus();
837
+ }
838
+ onChange?.(false);
839
+ onClose?.();
840
+ }
841
+ function toggle() {
842
+ isOpen ? close() : open();
843
+ }
844
+ triggerEl.addEventListener("click", toggle);
845
+ function handleOutsideClick(e) {
846
+ if (!closeOnOutsideClick || !isOpen)
847
+ return;
848
+ if (popoverEl && !popoverEl.contains(e.target) && !triggerEl.contains(e.target)) {
849
+ if (trapFocus)
850
+ e.preventDefault();
851
+ close();
852
+ }
853
+ }
854
+ document.addEventListener("mousedown", handleOutsideClick);
855
+ function handleKeydown(e) {
856
+ if (!isOpen)
857
+ return;
858
+ if (trapFocus) {
859
+ if (!isTopLayer(popoverEl))
860
+ return;
861
+ if (e.key === "Escape")
862
+ close();
863
+ else
864
+ wrapTab(e, popoverEl);
865
+ return;
866
+ }
867
+ if (e.key === "Escape")
868
+ close();
869
+ }
870
+ document.addEventListener("keydown", handleKeydown);
871
+ function destroy() {
872
+ if (trapFocus && popoverEl)
873
+ closeLayer(popoverEl);
874
+ triggerEl.removeEventListener("click", toggle);
875
+ document.removeEventListener("mousedown", handleOutsideClick);
876
+ document.removeEventListener("keydown", handleKeydown);
877
+ triggerEl.removeAttribute("aria-expanded");
878
+ triggerEl.removeAttribute("aria-controls");
879
+ if (popoverEl) {
880
+ popoverEl.remove();
881
+ popoverEl = null;
882
+ }
883
+ }
884
+ if (defaultOpen)
885
+ open();
886
+ return { open, close, toggle, destroy };
887
+ }
888
+
889
+ // ../../src/components/menu/menu.js
890
+ var BASE6 = "corn-menu";
891
+ var uid3 = 0;
892
+ function getPosition3(trigger, menu, placement) {
893
+ const t = trigger.getBoundingClientRect();
894
+ const m = menu.getBoundingClientRect();
895
+ const gap = 4;
896
+ const vw = window.innerWidth;
897
+ const vh = window.innerHeight;
898
+ let actual = placement;
899
+ if (placement === "bottom" && t.bottom + m.height + gap > vh && t.top - m.height - gap > 0)
900
+ actual = "top";
901
+ else if (placement === "top" && t.top - m.height - gap < 0 && t.bottom + m.height + gap < vh)
902
+ actual = "bottom";
903
+ const top = actual === "bottom" ? t.bottom + gap : t.top - m.height - gap;
904
+ const left = Math.max(gap, Math.min(t.left, vw - m.width - gap));
905
+ return { top, left, actualPlacement: actual };
906
+ }
907
+ function createMenu(triggerEl, options = {}) {
908
+ checkStylesLoaded("@corndev/ui-overlays", "createMenu()");
909
+ const { items = [], placement = "bottom", closeOnSelect = true, onSelect } = options;
910
+ const id = `${BASE6}-${++uid3}`;
911
+ let menuEl = null;
912
+ let isOpen = false;
913
+ triggerEl.setAttribute("aria-haspopup", "menu");
914
+ triggerEl.setAttribute("aria-expanded", "false");
915
+ triggerEl.setAttribute("aria-controls", id);
916
+ function enabledItems() {
917
+ return Array.from(menuEl.querySelectorAll(`.${BASE6}-item:not([disabled])`));
918
+ }
919
+ function selectItem(item) {
920
+ item.onSelect?.(item.value, item);
921
+ onSelect?.(item.value, item);
922
+ if (closeOnSelect)
923
+ close();
924
+ }
925
+ function buildMenu() {
926
+ menuEl = document.createElement("div");
927
+ menuEl.id = id;
928
+ menuEl.className = BASE6;
929
+ menuEl.setAttribute("role", "menu");
930
+ menuEl.style.position = "fixed";
931
+ let groups = 0;
932
+ const entry = (item, parent) => {
933
+ if (item.separator) {
934
+ const sep = document.createElement("div");
935
+ sep.className = `${BASE6}-separator`;
936
+ sep.setAttribute("role", "separator");
937
+ parent.appendChild(sep);
938
+ return;
939
+ }
940
+ if (Array.isArray(item.items)) {
941
+ const group = document.createElement("div");
942
+ group.className = `${BASE6}-group`;
943
+ group.setAttribute("role", "group");
944
+ const label = document.createElement("div");
945
+ label.className = `${BASE6}-label`;
946
+ label.id = `${id}-group-${++groups}`;
947
+ label.textContent = item.group;
948
+ group.setAttribute("aria-labelledby", label.id);
949
+ group.appendChild(label);
950
+ for (const child of item.items)
951
+ entry(child, group);
952
+ parent.appendChild(group);
953
+ return;
954
+ }
955
+ const btn = document.createElement("button");
956
+ btn.type = "button";
957
+ btn.className = `${BASE6}-item`;
958
+ btn.setAttribute("role", "menuitem");
959
+ btn.tabIndex = -1;
960
+ btn.textContent = item.label;
961
+ if (item.danger)
962
+ btn.setAttribute("data-danger", "");
963
+ if (item.disabled) {
964
+ btn.disabled = true;
965
+ btn.setAttribute("aria-disabled", "true");
966
+ } else {
967
+ btn.addEventListener("click", () => selectItem(item));
968
+ }
969
+ parent.appendChild(btn);
970
+ };
971
+ for (const item of items)
972
+ entry(item, menuEl);
973
+ menuEl.addEventListener("keydown", handleMenuKeydown);
974
+ document.body.appendChild(menuEl);
975
+ }
976
+ function reposition() {
977
+ if (!menuEl)
978
+ return;
979
+ const { top, left, actualPlacement } = getPosition3(triggerEl, menuEl, placement);
980
+ menuEl.style.top = `${top}px`;
981
+ menuEl.style.left = `${left}px`;
982
+ menuEl.className = BASE6;
983
+ menuEl.setAttribute("data-placement", actualPlacement);
984
+ menuEl.toggleAttribute("data-visible", isOpen);
985
+ }
986
+ function focusItem(index) {
987
+ const list = enabledItems();
988
+ if (!list.length)
989
+ return;
990
+ const i = (index + list.length) % list.length;
991
+ list[i].focus();
992
+ }
993
+ function handleMenuKeydown(e) {
994
+ const list = enabledItems();
995
+ const idx = list.indexOf(document.activeElement);
996
+ switch (e.key) {
997
+ case "ArrowDown":
998
+ e.preventDefault();
999
+ focusItem(idx + 1);
1000
+ break;
1001
+ case "ArrowUp":
1002
+ e.preventDefault();
1003
+ focusItem(idx - 1);
1004
+ break;
1005
+ case "Home":
1006
+ e.preventDefault();
1007
+ focusItem(0);
1008
+ break;
1009
+ case "End":
1010
+ e.preventDefault();
1011
+ focusItem(list.length - 1);
1012
+ break;
1013
+ case "Escape":
1014
+ e.preventDefault();
1015
+ close();
1016
+ break;
1017
+ case "Tab":
1018
+ close();
1019
+ break;
1020
+ default:
1021
+ break;
1022
+ }
1023
+ }
1024
+ function open() {
1025
+ if (isOpen)
1026
+ return;
1027
+ if (!menuEl)
1028
+ buildMenu();
1029
+ reposition();
1030
+ requestAnimationFrame(() => menuEl && menuEl.setAttribute("data-visible", ""));
1031
+ triggerEl.setAttribute("aria-expanded", "true");
1032
+ isOpen = true;
1033
+ focusItem(0);
1034
+ }
1035
+ function close() {
1036
+ if (!isOpen || !menuEl)
1037
+ return;
1038
+ menuEl.removeAttribute("data-visible");
1039
+ triggerEl.setAttribute("aria-expanded", "false");
1040
+ isOpen = false;
1041
+ triggerEl.focus?.();
1042
+ }
1043
+ function toggle() {
1044
+ isOpen ? close() : open();
1045
+ }
1046
+ function handleTriggerKeydown(e) {
1047
+ if (!isOpen && (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ")) {
1048
+ e.preventDefault();
1049
+ open();
1050
+ }
1051
+ }
1052
+ function handleOutsideClick(e) {
1053
+ if (!isOpen)
1054
+ return;
1055
+ if (menuEl && !menuEl.contains(e.target) && !triggerEl.contains(e.target)) {
1056
+ menuEl.removeAttribute("data-visible");
1057
+ triggerEl.setAttribute("aria-expanded", "false");
1058
+ isOpen = false;
1059
+ }
1060
+ }
1061
+ triggerEl.addEventListener("click", toggle);
1062
+ triggerEl.addEventListener("keydown", handleTriggerKeydown);
1063
+ document.addEventListener("mousedown", handleOutsideClick);
1064
+ function destroy() {
1065
+ triggerEl.removeEventListener("click", toggle);
1066
+ triggerEl.removeEventListener("keydown", handleTriggerKeydown);
1067
+ document.removeEventListener("mousedown", handleOutsideClick);
1068
+ triggerEl.removeAttribute("aria-haspopup");
1069
+ triggerEl.removeAttribute("aria-expanded");
1070
+ triggerEl.removeAttribute("aria-controls");
1071
+ if (menuEl) {
1072
+ menuEl.remove();
1073
+ menuEl = null;
1074
+ }
1075
+ }
1076
+ return { open, close, toggle, destroy };
1077
+ }
1078
+
1079
+ // ../../src/overlays.js
1080
+ var version = "1.0.0-beta.1";
1081
+ export {
1082
+ createAccordion,
1083
+ createDrawer,
1084
+ createMenu,
1085
+ createModal,
1086
+ createPopover,
1087
+ createTooltip,
1088
+ version
1089
+ };