@relements/core 0.1.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.
Files changed (70) hide show
  1. package/dist/base.css +1 -0
  2. package/dist/behaviors/dialog.d.ts +39 -0
  3. package/dist/behaviors/dialog.js +1 -0
  4. package/dist/behaviors/dismissible.d.ts +37 -0
  5. package/dist/behaviors/dismissible.js +1 -0
  6. package/dist/behaviors/menu-button.d.ts +36 -0
  7. package/dist/behaviors/menu-button.js +1 -0
  8. package/dist/behaviors/popover.d.ts +28 -0
  9. package/dist/behaviors/popover.js +1 -0
  10. package/dist/behaviors/tabs.d.ts +37 -0
  11. package/dist/behaviors/tabs.js +1 -0
  12. package/dist/behaviors/toast.d.ts +42 -0
  13. package/dist/behaviors/toast.js +1 -0
  14. package/dist/chunk-GMICGIQW.js +149 -0
  15. package/dist/chunk-J4EGUBPP.js +68 -0
  16. package/dist/chunk-PIDPGDBZ.js +62 -0
  17. package/dist/chunk-PSODVT3V.js +67 -0
  18. package/dist/chunk-TC4TFP7Y.js +40 -0
  19. package/dist/chunk-ZHRJNWMH.js +174 -0
  20. package/dist/components/button.css +1 -0
  21. package/dist/components/dialog.css +1 -0
  22. package/dist/components/disclosure.css +1 -0
  23. package/dist/components/form.css +1 -0
  24. package/dist/components/link.css +1 -0
  25. package/dist/components/menu.css +1 -0
  26. package/dist/components/popover.css +1 -0
  27. package/dist/components/progress.css +1 -0
  28. package/dist/components/tabs.css +1 -0
  29. package/dist/components/toast.css +1 -0
  30. package/dist/elements/re-menu.d.ts +10 -0
  31. package/dist/elements/re-menu.js +36 -0
  32. package/dist/elements/re-popover.d.ts +12 -0
  33. package/dist/elements/re-popover.js +35 -0
  34. package/dist/elements/re-tabs.d.ts +20 -0
  35. package/dist/elements/re-tabs.js +60 -0
  36. package/dist/elements/re-toast.d.ts +15 -0
  37. package/dist/elements/re-toast.js +30 -0
  38. package/dist/index.css +1 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.js +6 -0
  41. package/dist/reset.css +1 -0
  42. package/dist/themes/renascent.css +1 -0
  43. package/dist/tokens.css +1 -0
  44. package/package.json +84 -0
  45. package/src/base.css +129 -0
  46. package/src/behaviors/dialog.js +106 -0
  47. package/src/behaviors/dismissible.js +68 -0
  48. package/src/behaviors/menu-button.js +199 -0
  49. package/src/behaviors/popover.js +103 -0
  50. package/src/behaviors/tabs.js +171 -0
  51. package/src/behaviors/toast.js +97 -0
  52. package/src/components/button.css +141 -0
  53. package/src/components/dialog.css +106 -0
  54. package/src/components/disclosure.css +83 -0
  55. package/src/components/form.css +334 -0
  56. package/src/components/link.css +61 -0
  57. package/src/components/menu.css +78 -0
  58. package/src/components/popover.css +50 -0
  59. package/src/components/progress.css +112 -0
  60. package/src/components/tabs.css +86 -0
  61. package/src/components/toast.css +87 -0
  62. package/src/elements/re-menu.js +54 -0
  63. package/src/elements/re-popover.js +59 -0
  64. package/src/elements/re-tabs.js +92 -0
  65. package/src/elements/re-toast.js +46 -0
  66. package/src/index.css +30 -0
  67. package/src/index.js +13 -0
  68. package/src/reset.css +103 -0
  69. package/src/themes/renascent.css +198 -0
  70. package/src/tokens.css +196 -0
package/dist/base.css ADDED
@@ -0,0 +1 @@
1
+ @layer re.base{:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}html{font-family:var(--re-font-sans);font-size:var(--re-size-text-md);line-height:var(--re-line-height-normal);color:var(--re-color-text);background-color:var(--re-color-bg)}::selection{background-color:var(--re-color-selection-bg);color:var(--re-color-selection-text)}h1,h2,h3,h4,h5,h6{line-height:var(--re-line-height-tight);font-weight:var(--re-font-weight-semibold);color:var(--re-color-text)}h1{font-size:var(--re-size-text-4xl)}h2{font-size:var(--re-size-text-3xl)}h3{font-size:var(--re-size-text-2xl)}h4{font-size:var(--re-size-text-xl)}h5{font-size:var(--re-size-text-lg)}h6{font-size:var(--re-size-text-md)}p{line-height:var(--re-line-height-normal);color:var(--re-color-text)}small{font-size:var(--re-size-text-sm);color:var(--re-color-text-muted)}code,kbd,samp,pre{font-family:var(--re-font-mono);font-size:.95em}pre{padding:var(--re-space-3) var(--re-space-4);background-color:var(--re-color-bg-muted);border-radius:var(--re-radius-md);overflow:auto}code{background-color:var(--re-color-bg-muted);border-radius:var(--re-radius-sm);padding:.1em .3em}pre code{background:0 0;border-radius:0;padding:0}hr{border:0;border-top:var(--re-border-default);margin-block:var(--re-space-6)}a{color:var(--re-color-link);text-underline-offset:.2em;text-decoration-thickness:.08em}a:hover{color:var(--re-color-link-hover)}a:visited{color:var(--re-color-link-visited)}:focus-visible{box-shadow:var(--re-shadow-focus);border-radius:var(--re-radius-sm);outline:none}input,textarea,select{color:var(--re-color-text);background-color:var(--re-color-bg)}}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * enhanceDialog
3
+ * -------------
4
+ * Lightweight ergonomic helpers for native <dialog>.
5
+ *
6
+ * Wires:
7
+ * - `[data-re-dialog-trigger]` → opens the <dialog> referenced by its
8
+ * `data-re-dialog-target="dialog-id"` attribute (or aria-controls).
9
+ * - `[data-re-dialog-close]` inside a <dialog> → closes the parent dialog,
10
+ * setting the dialog's returnValue to the element's value attribute.
11
+ * - Click on the dialog's backdrop closes the dialog when the dialog
12
+ * element itself has `data-re-dialog-close-on-backdrop`.
13
+ *
14
+ * Usage:
15
+ *
16
+ * <button type="button" data-re-dialog-trigger data-re-dialog-target="confirm">
17
+ * Open
18
+ * </button>
19
+ * <dialog id="confirm" data-re-dialog-close-on-backdrop>
20
+ * …
21
+ * <button data-re-dialog-close value="cancel">Cancel</button>
22
+ * </dialog>
23
+ *
24
+ * import { enhanceDialog } from "@relements/core/behaviors/dialog";
25
+ * const c = enhanceDialog(document);
26
+ * c.destroy();
27
+ *
28
+ * Native semantics are preserved: showModal/close/Escape behavior all
29
+ * come from the browser.
30
+ */
31
+ /**
32
+ * @param {Document | Element | ShadowRoot} [root=document]
33
+ * @returns {{ destroy: () => void }}
34
+ */
35
+ declare function enhanceDialog(root?: Document | Element | ShadowRoot): {
36
+ destroy: () => void;
37
+ };
38
+
39
+ export { enhanceDialog };
@@ -0,0 +1 @@
1
+ export { enhanceDialog } from '../chunk-J4EGUBPP.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * enhanceDismissible
3
+ * ------------------
4
+ * Wires up dismiss buttons inside any element marked `[data-re-dismissible]`.
5
+ *
6
+ * Usage in HTML:
7
+ *
8
+ * <aside data-re-dismissible>
9
+ * Banner copy…
10
+ * <button type="button" data-re-dismiss aria-label="Dismiss">×</button>
11
+ * </aside>
12
+ *
13
+ * Usage in JavaScript:
14
+ *
15
+ * import { enhanceDismissible } from "@relements/core/behaviors/dismissible";
16
+ * const controller = enhanceDismissible(document);
17
+ * // …later
18
+ * controller.destroy();
19
+ *
20
+ * Behavior:
21
+ * - Click or Enter/Space on `[data-re-dismiss]` hides the closest
22
+ * `[data-re-dismissible]` ancestor (sets `hidden`).
23
+ * - Dispatches a `re-dismiss` `CustomEvent` (bubbles, cancelable) on the
24
+ * dismissible element before hiding; calling `preventDefault()` cancels.
25
+ * - `controller.destroy()` removes all listeners.
26
+ *
27
+ * Root can be a Document, Element, or ShadowRoot.
28
+ */
29
+ /**
30
+ * @param {Document | Element | ShadowRoot} [root=document]
31
+ * @returns {{ destroy: () => void }}
32
+ */
33
+ declare function enhanceDismissible(root?: Document | Element | ShadowRoot): {
34
+ destroy: () => void;
35
+ };
36
+
37
+ export { enhanceDismissible };
@@ -0,0 +1 @@
1
+ export { enhanceDismissible } from '../chunk-TC4TFP7Y.js';
@@ -0,0 +1,36 @@
1
+ /**
2
+ * enhanceMenuButton
3
+ * -----------------
4
+ * ARIA menu-button pattern over markup like:
5
+ *
6
+ * <div class="re-menu" data-re-menu>
7
+ * <button aria-haspopup="menu" aria-expanded="false" aria-controls="m-1" id="b-1">…</button>
8
+ * <div role="menu" id="m-1" aria-labelledby="b-1" hidden>
9
+ * <button role="menuitem">Rename</button>
10
+ * …
11
+ * </div>
12
+ * </div>
13
+ *
14
+ * Behavior:
15
+ * - Click the button toggles the menu.
16
+ * - ArrowDown opens the menu and focuses the first item.
17
+ * - Up/Down on items moves focus within the menu (wraps).
18
+ * - Home/End jump.
19
+ * - Escape closes the menu and returns focus to the button.
20
+ * - Tab outside the menu closes it.
21
+ * - Clicking outside closes it.
22
+ *
23
+ * Dispatches `re-select` (bubbles, cancelable) when a menuitem is activated:
24
+ * detail = { item: HTMLElement, value: string }
25
+ */
26
+ /** @typedef {{ destroy: () => void }} Controller */
27
+ /**
28
+ * @param {Document | Element | ShadowRoot} [root=document]
29
+ * @returns {Controller}
30
+ */
31
+ declare function enhanceMenuButton(root?: Document | Element | ShadowRoot): Controller;
32
+ type Controller = {
33
+ destroy: () => void;
34
+ };
35
+
36
+ export { type Controller, enhanceMenuButton };
@@ -0,0 +1 @@
1
+ export { enhanceMenuButton } from '../chunk-ZHRJNWMH.js';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * enhancePopover
3
+ * --------------
4
+ * Thin helper for native [popover]. Provides:
5
+ * - Anchored positioning: places the popover below its `popovertarget`
6
+ * button when the popover opens. (Native CSS Anchor Positioning isn't
7
+ * universally supported yet; this is a tiny JS fallback.)
8
+ * - `re-toggle` CustomEvent on the popover element when it opens/closes
9
+ * (detail = { open: boolean }), mirroring the native `toggle` event so
10
+ * consumers don't need feature-detect it.
11
+ *
12
+ * <button class="re-button" popovertarget="tip">Toggle</button>
13
+ * <div class="re-popover" id="tip" popover data-re-popover>Hello</div>
14
+ *
15
+ * import { enhancePopover } from "@relements/core/behaviors/popover";
16
+ * const c = enhancePopover(document);
17
+ */
18
+ /** @typedef {{ destroy: () => void }} Controller */
19
+ /**
20
+ * @param {Document | Element | ShadowRoot} [root=document]
21
+ * @returns {Controller}
22
+ */
23
+ declare function enhancePopover(root?: Document | Element | ShadowRoot): Controller;
24
+ type Controller = {
25
+ destroy: () => void;
26
+ };
27
+
28
+ export { type Controller, enhancePopover };
@@ -0,0 +1 @@
1
+ export { enhancePopover } from '../chunk-PSODVT3V.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * enhanceTabs
3
+ * -----------
4
+ * Wires the ARIA tabs pattern over server-rendered markup.
5
+ *
6
+ * <div class="re-tabs" data-re-tabs>
7
+ * <div role="tablist" aria-label="Settings">
8
+ * <button role="tab" id="t-1" aria-controls="p-1" aria-selected="true">…</button>
9
+ * <button role="tab" id="t-2" aria-controls="p-2" aria-selected="false" tabindex="-1">…</button>
10
+ * </div>
11
+ * <section role="tabpanel" id="p-1" aria-labelledby="t-1">…</section>
12
+ * <section role="tabpanel" id="p-2" aria-labelledby="t-2" hidden>…</section>
13
+ * </div>
14
+ *
15
+ * Keyboard:
16
+ * ArrowLeft / ArrowRight — move focus across tabs (roving tabindex)
17
+ * Home / End — jump to first / last
18
+ * Enter / Space — activate focused tab (manual activation)
19
+ *
20
+ * Dispatches `re-change` (bubbles, cancelable) on the host `[data-re-tabs]`
21
+ * with `detail = { tabId, panelId }` whenever the selected tab changes.
22
+ *
23
+ * import { enhanceTabs } from "@relements/core/behaviors/tabs";
24
+ * const c = enhanceTabs(document);
25
+ * c.destroy();
26
+ */
27
+ /** @typedef {{ destroy: () => void }} Controller */
28
+ /**
29
+ * @param {Document | Element | ShadowRoot} [root=document]
30
+ * @returns {Controller}
31
+ */
32
+ declare function enhanceTabs(root?: Document | Element | ShadowRoot): Controller;
33
+ type Controller = {
34
+ destroy: () => void;
35
+ };
36
+
37
+ export { type Controller, enhanceTabs };
@@ -0,0 +1 @@
1
+ export { enhanceTabs } from '../chunk-GMICGIQW.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Toast helpers.
3
+ *
4
+ * <div class="re-toast-region" data-re-toast-region role="region" aria-label="Notifications">
5
+ * <ul class="re-toast-list" aria-live="polite" aria-relevant="additions"></ul>
6
+ * </div>
7
+ *
8
+ * import { showToast } from "@relements/core/behaviors/toast";
9
+ * showToast("Saved", { tone: "success" });
10
+ * showToast("Network error", { tone: "danger", duration: 8000 });
11
+ *
12
+ * If no `[data-re-toast-region]` exists, one is created and appended to
13
+ * `document.body` on first call.
14
+ */
15
+ /**
16
+ * @typedef {object} ToastOptions
17
+ * @property {"default"|"success"|"warning"|"danger"} [tone="default"]
18
+ * @property {number} [duration=4000] Auto-dismiss in ms. Pass 0 to disable.
19
+ * @property {Document|Element} [root] Override the host to search for a region in.
20
+ */
21
+ /**
22
+ * @param {string} message
23
+ * @param {ToastOptions} [options]
24
+ * @returns {{ dismiss: () => void; element: HTMLDivElement }}
25
+ */
26
+ declare function showToast(message: string, options?: ToastOptions): {
27
+ dismiss: () => void;
28
+ element: HTMLDivElement;
29
+ };
30
+ type ToastOptions = {
31
+ tone?: "default" | "success" | "warning" | "danger" | undefined;
32
+ /**
33
+ * Auto-dismiss in ms. Pass 0 to disable.
34
+ */
35
+ duration?: number | undefined;
36
+ /**
37
+ * Override the host to search for a region in.
38
+ */
39
+ root?: Document | Element | undefined;
40
+ };
41
+
42
+ export { type ToastOptions, showToast };
@@ -0,0 +1 @@
1
+ export { showToast } from '../chunk-PIDPGDBZ.js';
@@ -0,0 +1,149 @@
1
+ // src/behaviors/tabs.js
2
+ function enhanceTabs(root = document) {
3
+ if (root == null) {
4
+ throw new TypeError("enhanceTabs: root must be a Document, Element, or ShadowRoot");
5
+ }
6
+ const hosts = [];
7
+ if (root instanceof Element && /** @type {Element} */
8
+ root.matches?.("[data-re-tabs]")) {
9
+ const cleanup = wireOne(
10
+ /** @type {HTMLElement} */
11
+ root
12
+ );
13
+ hosts.push({ host: root, cleanup });
14
+ }
15
+ const tabsList = root.querySelectorAll("[data-re-tabs]");
16
+ tabsList.forEach((host) => {
17
+ const cleanup = wireOne(
18
+ /** @type {HTMLElement} */
19
+ host
20
+ );
21
+ hosts.push({ host, cleanup });
22
+ });
23
+ return {
24
+ destroy() {
25
+ while (hosts.length) {
26
+ const entry = hosts.pop();
27
+ entry?.cleanup();
28
+ }
29
+ }
30
+ };
31
+ }
32
+ function wireOne(host) {
33
+ const tablist = host.querySelector('[role="tablist"]');
34
+ if (!tablist) return () => {
35
+ };
36
+ const tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
37
+ if (tabs.length === 0) return () => {
38
+ };
39
+ const syncRoving = () => {
40
+ for (const t of tabs) {
41
+ t.tabIndex = t.getAttribute("aria-selected") === "true" ? 0 : -1;
42
+ }
43
+ };
44
+ syncRoving();
45
+ const select = (tab, opts = {}) => {
46
+ if (tab.getAttribute("aria-selected") === "true") {
47
+ if (opts.focus) tab.focus();
48
+ return;
49
+ }
50
+ const panelId = tab.getAttribute("aria-controls");
51
+ const cancelled = !host.dispatchEvent(
52
+ new CustomEvent("re-change", {
53
+ bubbles: true,
54
+ cancelable: true,
55
+ detail: { tabId: tab.id, panelId }
56
+ })
57
+ );
58
+ if (cancelled) return;
59
+ for (const t of tabs) {
60
+ const isMe = t === tab;
61
+ t.setAttribute("aria-selected", isMe ? "true" : "false");
62
+ t.tabIndex = isMe ? 0 : -1;
63
+ const pid = t.getAttribute("aria-controls");
64
+ if (pid) {
65
+ const panel = host.querySelector(`#${cssEscape(pid)}`);
66
+ if (panel) panel.hidden = !isMe;
67
+ }
68
+ }
69
+ if (opts.focus) tab.focus();
70
+ };
71
+ const onClick = (event) => {
72
+ const t = (
73
+ /** @type {Element | null} */
74
+ event.target
75
+ );
76
+ const tab = t?.closest('[role="tab"]');
77
+ if (tab && tabs.includes(
78
+ /** @type {HTMLElement} */
79
+ tab
80
+ )) {
81
+ select(
82
+ /** @type {HTMLElement} */
83
+ tab,
84
+ { focus: true }
85
+ );
86
+ }
87
+ };
88
+ const onKey = (event) => {
89
+ const current = document.activeElement;
90
+ if (!current || !tabs.includes(
91
+ /** @type {HTMLElement} */
92
+ current
93
+ )) return;
94
+ const idx = tabs.indexOf(
95
+ /** @type {HTMLElement} */
96
+ current
97
+ );
98
+ if (idx === -1) return;
99
+ let nextIdx = idx;
100
+ switch (event.key) {
101
+ case "ArrowRight":
102
+ nextIdx = (idx + 1) % tabs.length;
103
+ break;
104
+ case "ArrowLeft":
105
+ nextIdx = (idx - 1 + tabs.length) % tabs.length;
106
+ break;
107
+ case "Home":
108
+ nextIdx = 0;
109
+ break;
110
+ case "End":
111
+ nextIdx = tabs.length - 1;
112
+ break;
113
+ case "Enter":
114
+ case " ":
115
+ case "Spacebar":
116
+ event.preventDefault();
117
+ select(
118
+ /** @type {HTMLElement} */
119
+ current,
120
+ { focus: true }
121
+ );
122
+ return;
123
+ default:
124
+ return;
125
+ }
126
+ event.preventDefault();
127
+ tabs[nextIdx].focus();
128
+ select(tabs[nextIdx]);
129
+ };
130
+ tablist.addEventListener("click", onClick);
131
+ tablist.addEventListener(
132
+ "keydown",
133
+ /** @type {EventListener} */
134
+ onKey
135
+ );
136
+ return () => {
137
+ tablist.removeEventListener("click", onClick);
138
+ tablist.removeEventListener(
139
+ "keydown",
140
+ /** @type {EventListener} */
141
+ onKey
142
+ );
143
+ };
144
+ }
145
+ function cssEscape(value) {
146
+ return typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value;
147
+ }
148
+
149
+ export { enhanceTabs };
@@ -0,0 +1,68 @@
1
+ // src/behaviors/dialog.js
2
+ function enhanceDialog(root = document) {
3
+ if (root == null) {
4
+ throw new TypeError("enhanceDialog: root must be a Document, Element, or ShadowRoot");
5
+ }
6
+ const onClick = (event) => {
7
+ const target = (
8
+ /** @type {Element | null} */
9
+ event.target
10
+ );
11
+ if (!target) return;
12
+ const trigger = target.closest("[data-re-dialog-trigger]");
13
+ if (trigger) {
14
+ const id = trigger.getAttribute("data-re-dialog-target") ?? trigger.getAttribute("aria-controls");
15
+ if (!id) return;
16
+ const ownerDoc = trigger.ownerDocument;
17
+ const dialog = (
18
+ /** @type {HTMLDialogElement | null} */
19
+ ownerDoc.getElementById(id)
20
+ );
21
+ if (dialog && typeof dialog.showModal === "function" && !dialog.open) {
22
+ dialog.showModal();
23
+ }
24
+ return;
25
+ }
26
+ const closeBtn = target.closest("[data-re-dialog-close]");
27
+ if (closeBtn) {
28
+ const dialog = (
29
+ /** @type {HTMLDialogElement | null} */
30
+ closeBtn.closest("dialog")
31
+ );
32
+ if (dialog && dialog.open) {
33
+ const value = (
34
+ /** @type {HTMLButtonElement} */
35
+ closeBtn.value || ""
36
+ );
37
+ dialog.close(value);
38
+ }
39
+ return;
40
+ }
41
+ if (target.tagName === "DIALOG") {
42
+ const dialog = (
43
+ /** @type {HTMLDialogElement} */
44
+ target
45
+ );
46
+ if (dialog.open && dialog.hasAttribute("data-re-dialog-close-on-backdrop") && isEventOnBackdrop(
47
+ /** @type {MouseEvent} */
48
+ event,
49
+ dialog
50
+ )) {
51
+ dialog.close("backdrop");
52
+ }
53
+ }
54
+ };
55
+ root.addEventListener("click", onClick);
56
+ return {
57
+ destroy() {
58
+ root.removeEventListener("click", onClick);
59
+ }
60
+ };
61
+ }
62
+ function isEventOnBackdrop(event, dialog) {
63
+ const rect = dialog.getBoundingClientRect();
64
+ const { clientX: x, clientY: y } = event;
65
+ return x < rect.left || x > rect.right || y < rect.top || y > rect.bottom;
66
+ }
67
+
68
+ export { enhanceDialog };
@@ -0,0 +1,62 @@
1
+ // src/behaviors/toast.js
2
+ function showToast(message, options = {}) {
3
+ const { tone = "default", duration = 4e3, root = document } = options;
4
+ const list = ensureRegion(root).querySelector(".re-toast-list");
5
+ if (!list) {
6
+ throw new Error("showToast: toast region missing a `.re-toast-list`");
7
+ }
8
+ const item = document.createElement("div");
9
+ item.className = "re-toast";
10
+ if (tone !== "default") item.dataset.tone = tone;
11
+ item.setAttribute("role", tone === "danger" ? "alert" : "status");
12
+ const body = document.createElement("div");
13
+ body.className = "re-toast__body";
14
+ body.textContent = message;
15
+ item.appendChild(body);
16
+ const dismissBtn = document.createElement("button");
17
+ dismissBtn.type = "button";
18
+ dismissBtn.className = "re-toast__dismiss";
19
+ dismissBtn.setAttribute("aria-label", "Dismiss notification");
20
+ dismissBtn.textContent = "\xD7";
21
+ item.appendChild(dismissBtn);
22
+ list.appendChild(item);
23
+ let timer;
24
+ const dismiss = () => {
25
+ if (timer != null) {
26
+ clearTimeout(timer);
27
+ timer = void 0;
28
+ }
29
+ item.dispatchEvent(new CustomEvent("re-toast-dismiss", { bubbles: true }));
30
+ item.remove();
31
+ };
32
+ dismissBtn.addEventListener("click", dismiss);
33
+ if (duration > 0) {
34
+ timer = setTimeout(dismiss, duration);
35
+ }
36
+ return { dismiss, element: item };
37
+ }
38
+ function ensureRegion(root) {
39
+ const scope = root instanceof Document ? root : (
40
+ /** @type {Element} */
41
+ root.ownerDocument ?? document
42
+ );
43
+ let region = (
44
+ /** @type {HTMLElement | null} */
45
+ scope.querySelector("[data-re-toast-region]")
46
+ );
47
+ if (region) return region;
48
+ region = scope.createElement("div");
49
+ region.className = "re-toast-region";
50
+ region.setAttribute("role", "region");
51
+ region.setAttribute("aria-label", "Notifications");
52
+ region.dataset.reToastRegion = "";
53
+ const list = scope.createElement("div");
54
+ list.className = "re-toast-list";
55
+ list.setAttribute("aria-live", "polite");
56
+ list.setAttribute("aria-relevant", "additions");
57
+ region.appendChild(list);
58
+ scope.body.appendChild(region);
59
+ return region;
60
+ }
61
+
62
+ export { showToast };
@@ -0,0 +1,67 @@
1
+ // src/behaviors/popover.js
2
+ function enhancePopover(root = document) {
3
+ if (root == null) {
4
+ throw new TypeError("enhancePopover: root must be a Document, Element, or ShadowRoot");
5
+ }
6
+ const cleanups = [];
7
+ if (root instanceof Element && /** @type {Element} */
8
+ root.matches?.("[data-re-popover]")) {
9
+ cleanups.push(wireOne(
10
+ /** @type {HTMLElement} */
11
+ root
12
+ ));
13
+ }
14
+ const popovers = root.querySelectorAll("[data-re-popover]");
15
+ popovers.forEach((pop) => {
16
+ cleanups.push(wireOne(pop));
17
+ });
18
+ return {
19
+ destroy() {
20
+ while (cleanups.length) cleanups.pop()?.();
21
+ }
22
+ };
23
+ }
24
+ function wireOne(popover) {
25
+ if (!("popover" in popover) || typeof popover.showPopover !== "function") {
26
+ return () => {
27
+ };
28
+ }
29
+ const onToggle = (event) => {
30
+ const e = (
31
+ /** @type {ToggleEvent} */
32
+ event
33
+ );
34
+ const open = e.newState === "open";
35
+ if (open) {
36
+ positionUnderTrigger(popover);
37
+ }
38
+ popover.dispatchEvent(
39
+ new CustomEvent("re-toggle", {
40
+ bubbles: true,
41
+ detail: { open }
42
+ })
43
+ );
44
+ };
45
+ popover.addEventListener("toggle", onToggle);
46
+ return () => {
47
+ popover.removeEventListener("toggle", onToggle);
48
+ };
49
+ }
50
+ function positionUnderTrigger(popover) {
51
+ const id = popover.id;
52
+ if (!id) return;
53
+ const trigger = document.querySelector(`[popovertarget="${cssEscape(id)}"]`);
54
+ if (!trigger) return;
55
+ const tRect = trigger.getBoundingClientRect();
56
+ popover.style.position = "fixed";
57
+ popover.style.top = `${tRect.bottom + 4}px`;
58
+ popover.style.left = `${tRect.left}px`;
59
+ popover.style.right = "auto";
60
+ popover.style.bottom = "auto";
61
+ popover.style.margin = "0";
62
+ }
63
+ function cssEscape(value) {
64
+ return typeof CSS !== "undefined" && CSS.escape ? CSS.escape(value) : value;
65
+ }
66
+
67
+ export { enhancePopover };
@@ -0,0 +1,40 @@
1
+ // src/behaviors/dismissible.js
2
+ function enhanceDismissible(root = document) {
3
+ if (root == null) {
4
+ throw new TypeError("enhanceDismissible: root must be a Document, Element, or ShadowRoot");
5
+ }
6
+ const handle = (event) => {
7
+ const target = (
8
+ /** @type {Element | null} */
9
+ event.target
10
+ );
11
+ if (!target) return;
12
+ const trigger = target.closest("[data-re-dismiss]");
13
+ if (!trigger) return;
14
+ if (event.type === "keydown") {
15
+ const ke = (
16
+ /** @type {KeyboardEvent} */
17
+ event
18
+ );
19
+ if (ke.key !== "Enter" && ke.key !== " " && ke.key !== "Spacebar") return;
20
+ ke.preventDefault();
21
+ }
22
+ const host = trigger.closest("[data-re-dismissible]");
23
+ if (!host) return;
24
+ const cancelable = host.dispatchEvent(
25
+ new CustomEvent("re-dismiss", { bubbles: true, cancelable: true })
26
+ );
27
+ if (!cancelable) return;
28
+ host.hidden = true;
29
+ };
30
+ root.addEventListener("click", handle);
31
+ root.addEventListener("keydown", handle);
32
+ return {
33
+ destroy() {
34
+ root.removeEventListener("click", handle);
35
+ root.removeEventListener("keydown", handle);
36
+ }
37
+ };
38
+ }
39
+
40
+ export { enhanceDismissible };