@t007/dialog 0.0.23 → 0.0.24

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/README.md CHANGED
@@ -130,7 +130,7 @@ async function triggerPrompt() {
130
130
 
131
131
  ### CDN / Browser (Global)
132
132
 
133
- If you are not using a bundler, the IIFE build automatically injects the dialogs into the global `t007` object and provides convenient capitalized window fallbacks (`window.Alert`, `window.Confirm`, `window.Prompt`).
133
+ If you are not using a bundler, the IIFE build automatically injects the dialogs into the global `t007` object and provides convenient capitalized window fallbacks (`window.Alert`, `window.Confirm`, `window.Prompt`), reassign if desired.
134
134
 
135
135
  ```html
136
136
  <!DOCTYPE html>
@@ -145,7 +145,7 @@ If you are not using a bundler, the IIFE build automatically injects the dialogs
145
145
  <script>
146
146
  // The library automatically maps to window.Confirm!
147
147
  document.getElementById('deleteBtn').addEventListener('click', async () => {
148
- const proceed = await Confirm("Proceed with formatting?"); // or use `t007.confirm()`
148
+ const proceed = await Confirm("Proceed with formatting?"); // or use `t007.confirm()`, `t007.dialog` contains the whole API
149
149
  if(proceed) doFormat();
150
150
  });
151
151
  </script>
@@ -178,9 +178,7 @@ Displays a question with confirm and cancel buttons.
178
178
  - `options.id` *(String)*: Unique identifier for the dialog for programmatic dismissal.
179
179
  - `options.confirmText` *(String)*: Custom text for the confirm button (Default: `"OK"`).
180
180
  - `options.cancelText` *(String)*: Custom text for the cancel button (Default: `"Cancel"`).
181
- - `options.closedBy` *(String)*: Browser behavior for handling dialog closure.
182
- - `options.rootElement` *(HTMLElement)*: Render dialog inside this element.
183
- - `options.scoped` *(Boolean)*: Whether to restrict interactions to `rootElement` if provided. Defaults to `true`.
181
+ - *Last 3 in `alert()` apply here as well...*
184
182
  - **Returns**: `Promise<boolean>` (`true` if confirmed, `false` if cancelled).
185
183
 
186
184
  ### `prompt(question, defaultValue, options)`
@@ -193,10 +191,8 @@ Displays an input field to collect data from the user. Note: This automatically
193
191
  - `options.id` *(String)*: Unique identifier for the dialog for programmatic dismissal, also used as the input's ID.
194
192
  - `options.confirmText` *(String)*: Custom text for the submit button.
195
193
  - `options.cancelText` *(String)*: Custom text for the cancel button.
196
- - `options.closedBy` *(String)*: Browser behavior for handling dialog closure.
197
- - `options.rootElement` *(HTMLElement)*: Render dialog inside this element.
198
194
  - *Accepts standard HTML input attributes (type, required, placeholder, etc.)*
199
- - `options.scoped` *(Boolean)*: Whether to restrict interactions to `rootElement` if provided. Defaults to `true`.
195
+ - *Last 3 in `alert()` apply here as well...*
200
196
  - **Returns**: `Promise<String | null>` (Returns the string value, or `null` if cancelled).
201
197
 
202
198
  ### `dismiss(id, response)`
@@ -205,10 +201,18 @@ Programatically dismiss a dialog by id or all dialogs when no id is provided.
205
201
  - **`id`** *(String)*: Optional unique identifier of the dialog to dismiss. If not provided, all dialogs will be dismissed.
206
202
  - **`response`** *(Any)*: Optional value to resolve the dialog's promise with. If not provided, defaults to `true` for confirm and alert dialogs, and `null` for prompt dialogs.
207
203
 
204
+ ### `isActive(id)`
205
+ Check if a dialog is currently active. Optionally check for a specific dialog by id.
206
+ - **`id`** *(String)*: Optional unique identifier of the dialog to check. If not provided, checks if any dialog is active.
207
+ - **Returns**: `boolean` (`true` if the specified dialog or any dialog is active, otherwise `false`).
208
+
208
209
  #### Backdrop behavior
209
210
 
210
211
  - Default mode uses native `<dialog>.showModal()` and native `::backdrop`.
211
212
  - Scoped mode (`rootElement`) uses `<dialog>.show()` with a pseudo backdrop.
213
+ -
214
+
215
+ *NOTE: Scoped mode only restricts interactions within the specified root and top layer, while default mode is truly modal and blocks all interactions until dismissed. Choose based on your application's needs, you might need to gate some business logic depending on the mode.*
212
216
 
213
217
  -----
214
218
 
@@ -230,6 +234,9 @@ The dialogs are built with semantic, easily targetable CSS classes. You can easi
230
234
 
231
235
  ```css
232
236
  :root {
237
+ --app-theme-color: white;
238
+ --app-brand-color: red;
239
+ --app-brand-accent-color: orangered;
233
240
  --t007-dialog-backdrop-background: rgba(0, 0, 0, 0.6);
234
241
  --t007-dialog-backdrop-filter: none;
235
242
  --t007-cancel-button-color: dodgerblue;
@@ -237,6 +244,11 @@ The dialogs are built with semantic, easily targetable CSS classes. You can easi
237
244
  }
238
245
  .t007-dialog {
239
246
  margin-top: 0;
247
+ /* advised override variables */
248
+ --t007-dialog-unit: 0.9rem;
249
+ --t007-dialog-message-color: var(--app-theme-color);
250
+ --t007-dialog-confirm-button-color: var(--app-brand-accent-color);
251
+ --t007-dialog-cancel-button-color: var(--app-brand-color);
240
252
  }
241
253
  ```
242
254
 
@@ -244,7 +256,7 @@ The dialogs are built with semantic, easily targetable CSS classes. You can easi
244
256
 
245
257
  - Start with `:root` for shared dialog tokens.
246
258
  - If a token does not apply, override directly on `.t007-dialog`.
247
- - For targeted styling, use child selectors like `.t007-dialog-question`, `.t007-dialog-confirm-button`, and `.t007-dialog-cancel-button`.
259
+ - For sizing/layout variables, override `.t007-dialog` (unit, width, icon size, margins).
248
260
  - For strong app themes (e.g. `html[data-theme="dark"]`), use equal/stronger selectors like `html[data-theme="dark"] .t007-dialog`.
249
261
  - Check source code for more details.
250
262
 
package/dist/index.css CHANGED
@@ -1,42 +1,42 @@
1
1
  /* src/css/index.css */
2
2
  :where(:root) {
3
3
  --t007-dialog-font-family: inherit;
4
- --t007-dialog-unit: 1rem;
5
4
  --t007-dialog-background: rgb(20, 20, 20);
6
- --t007-dialog-gap: 0.8rem;
7
- --t007-dialog-width: 26.5rem;
8
5
  --t007-dialog-height: fit-content;
9
6
  --t007-dialog-max-width: 90%;
10
- --t007-dialog-max-height: 30rem;
11
- --t007-dialog-max-content-height: 12rem;
12
- --t007-dialog-padding: 1rem;
13
- --t007-dialog-content-padding-block: 1.5rem;
14
- --t007-dialog-border-radius: 1rem;
15
- --t007-dialog-border: 0.05rem solid rgba(255, 255, 255, 0.25);
7
+ --t007-dialog-border: 0.8px solid rgba(255, 255, 255, 0.25);
16
8
  --t007-dialog-box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.5), -2px -2px 6px rgba(0, 0, 0, 0.5);
17
9
  --t007-dialog-backdrop-background: rgba(0, 0, 0, 0.2);
18
10
  --t007-dialog-backdrop-filter: blur(6px);
19
11
  --t007-dialog-message-color: whitesmoke;
20
- --t007-dialog-message-font-size: 0.9rem;
21
- --t007-dialog-button-font-size: 0.85rem;
22
12
  --t007-dialog-button-text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.5);
23
- --t007-dialog-button-min-width: 4.5rem;
24
13
  --t007-dialog-button-width: unset;
25
14
  --t007-dialog-button-max-width: unset;
26
- --t007-dialog-button-min-height: 2.3rem;
27
15
  --t007-dialog-button-height: unset;
28
16
  --t007-dialog-button-max-height: unset;
29
- --t007-dialog-button-padding-inline: 1rem;
30
- --t007-dialog-button-padding-block: 0.5rem;
31
- --t007-dialog-button-border-radius: 1.25rem;
32
- --t007-dialog-button-gap: 0.65rem;
33
- --t007-button-outline-width: 0.15rem;
17
+ --t007-button-outline-width: 2px;
34
18
  --t007-button-outline-style: solid;
35
- --t007-dialog-start-duration: 150ms;
36
- --t007-dialog-start-transform: translateY(-100%);
37
- --t007-dialog-start-opacity: 0;
19
+ --t007-dialog-entry-duration: 150ms;
20
+ --t007-dialog-starting-transform: translateY(-100%);
21
+ --t007-dialog-starting-opacity: 0;
38
22
  }
39
23
  :where(:root, .t007-dialog) {
24
+ --t007-dialog-unit: 1rem;
25
+ --t007-dialog-gap: calc(var(--t007-dialog-unit) * 0.8);
26
+ --t007-dialog-width: calc(var(--t007-dialog-unit) * 26.5);
27
+ --t007-dialog-max-height: min(calc(var(--t007-dialog-unit) * 30), 90%);
28
+ --t007-dialog-max-content-height: calc(var(--t007-dialog-unit) * 12);
29
+ --t007-dialog-padding: var(--t007-dialog-unit);
30
+ --t007-dialog-content-padding-block: calc(var(--t007-dialog-unit) * 1.5);
31
+ --t007-dialog-border-radius: var(--t007-dialog-unit);
32
+ --t007-dialog-message-font-size: calc(var(--t007-dialog-unit) * 0.9);
33
+ --t007-dialog-button-font-size: calc(var(--t007-dialog-unit) * 0.85);
34
+ --t007-dialog-button-min-width: calc(var(--t007-dialog-unit) * 4.5);
35
+ --t007-dialog-button-min-height: calc(var(--t007-dialog-unit) * 2.3);
36
+ --t007-dialog-button-padding-inline: var(--t007-dialog-unit);
37
+ --t007-dialog-button-padding-block: calc(var(--t007-dialog-unit) * 0.5);
38
+ --t007-dialog-button-border-radius: calc(var(--t007-dialog-unit) * 1.25);
39
+ --t007-dialog-button-gap: calc(var(--t007-dialog-unit) * 0.65);
40
40
  --t007-confirm-button-color: grey;
41
41
  --t007-confirm-button-background: var(--t007-cancel-button-color);
42
42
  --t007-cancel-button-color: greenyellow;
@@ -83,19 +83,19 @@
83
83
  cursor: pointer;
84
84
  filter: brightness(1.15);
85
85
  }
86
- :is(html, body):has(.t007-dialog[open]) {
86
+ :is(html, body):has(.t007-dialog) {
87
87
  overflow: hidden;
88
88
  }
89
- body:has(.t007-dialog[open]) {
89
+ body:has(.t007-dialog) {
90
90
  overflow-y: visible;
91
91
  }
92
- *:where(:has(> .t007-dialog.t007-dialog-scoped[open])) {
92
+ *:where(:has(> .t007-dialog.t007-dialog-scoped)) {
93
93
  position: relative;
94
94
  overflow: hidden !important;
95
95
  }
96
96
  .t007-dialog,
97
97
  .t007-dialog::backdrop,
98
- .t007-dialog.t007-dialog-scoped::before {
98
+ .t007-dialog-backdrop {
99
99
  position: fixed;
100
100
  inset: 0;
101
101
  margin: auto;
@@ -105,11 +105,11 @@ body:has(.t007-dialog[open]) {
105
105
  transform,
106
106
  opacity,
107
107
  backdrop-filter;
108
- transition-duration: var(--t007-dialog-start-duration);
108
+ transition-duration: var(--t007-dialog-entry-duration);
109
109
  transition-behavior: allow-discrete;
110
110
  }
111
111
  .t007-dialog.t007-dialog-scoped,
112
- .t007-dialog.t007-dialog-scoped::before {
112
+ .t007-dialog-backdrop {
113
113
  position: absolute;
114
114
  }
115
115
  .t007-dialog,
@@ -128,10 +128,8 @@ body:has(.t007-dialog[open]) {
128
128
  border: var(--t007-dialog-border);
129
129
  border-radius: var(--t007-dialog-border-radius);
130
130
  box-shadow: var(--t007-dialog-box-shadow);
131
- opacity: var(--t007-dialog-start-opacity);
132
- transform: var(--t007-dialog-start-transform);
133
- transform-style: preserve-3d;
134
- backface-visibility: hidden;
131
+ opacity: var(--t007-dialog-starting-opacity);
132
+ transform: var(--t007-dialog-starting-transform);
135
133
  }
136
134
  .t007-dialog.t007-dialog-scoped {
137
135
  z-index: 2147483647;
@@ -143,37 +141,33 @@ body:has(.t007-dialog[open]) {
143
141
  }
144
142
  @starting-style {
145
143
  .t007-dialog[open] {
146
- opacity: var(--t007-dialog-start-opacity);
147
- transform: var(--t007-dialog-start-transform);
144
+ opacity: var(--t007-dialog-starting-opacity);
145
+ transform: var(--t007-dialog-starting-transform);
148
146
  }
149
147
  }
150
148
  .t007-dialog::backdrop,
151
- .t007-dialog.t007-dialog-scoped::before {
149
+ .t007-dialog-backdrop {
152
150
  background-color: transparent;
153
151
  }
154
- .t007-dialog.t007-dialog-scoped::before {
155
- content: "";
156
- top: -100lvh;
157
- left: -100lvw;
158
- width: 200lvw;
159
- height: 200lvh;
160
- transform: translateZ(-1px);
152
+ .t007-dialog-backdrop {
153
+ z-index: 2147483646;
161
154
  }
162
155
  .t007-dialog[open]::backdrop,
163
- .t007-dialog.t007-dialog-scoped[open]::before {
156
+ .t007-dialog.t007-dialog-scoped[open] + .t007-dialog-backdrop {
164
157
  display: block;
165
158
  background-color: var(--t007-dialog-backdrop-background);
166
- -webkit-backdrop-filter: var(--t007-dialog-backdrop-blur);
167
- backdrop-filter: var(--t007-dialog-backdrop-blur);
159
+ -webkit-backdrop-filter: var(--t007-dialog-backdrop-filter);
160
+ backdrop-filter: var(--t007-dialog-backdrop-filter);
168
161
  }
169
162
  @starting-style {
170
163
  .t007-dialog[open]::backdrop,
171
- .t007-dialog.t007-dialog-scoped[open]::before {
164
+ .t007-dialog.t007-dialog-scoped[open] + .t007-dialog-backdrop {
172
165
  background-color: transparent;
173
166
  }
174
167
  }
175
168
  .t007-dialog > * {
176
169
  padding-inline: var(--t007-dialog-padding);
170
+ font-weight: 500;
177
171
  }
178
172
  .t007-dialog-top-section {
179
173
  margin-top: var(--t007-dialog-padding);
package/dist/index.d.ts CHANGED
@@ -19,6 +19,11 @@ export interface DialogOptions {
19
19
 
20
20
  // BUNDLE EXPORTS & GLOBAL DECLARATIONS
21
21
 
22
+ /** Check if a dialog is currently active. Optionally check for a specific dialog by id.
23
+ * @param id Optional unique identifier of the dialog to check.
24
+ * @return `true` if the specified dialog (or any dialog when no id is provided) is active, otherwise `false`.
25
+ */
26
+ export function isActive(id?: string): boolean;
22
27
  /** Show an alert dialog and resolve when the user confirms.
23
28
  * @param message Message shown in the dialog body.
24
29
  * @param options Optional dialog configuration.
@@ -44,6 +49,14 @@ export function prompt(question: string, defaultValue?: string, options?: Dialog
44
49
  */
45
50
  export function dismiss(id?: string, response?: any): void;
46
51
 
52
+ interface Dialog {
53
+ isActive(id?: string): boolean;
54
+ alert(message: string, options?: DialogOptions): Promise<boolean>;
55
+ confirm(question: string, options?: DialogOptions): Promise<boolean>;
56
+ prompt(question: string, defaultValue?: string, options?: DialogOptions & FieldOptions): Promise<string | null>;
57
+ dismiss(id?: string, response?: any): void;
58
+ }
59
+
47
60
  declare global {
48
61
  interface T007Namespace {
49
62
  /** Browser alert helper. */
@@ -52,13 +65,12 @@ declare global {
52
65
  confirm: typeof Confirm;
53
66
  /** Browser prompt helper. */
54
67
  prompt: typeof Prompt;
55
- /** Dismisses a dialog by id or all dialogs when no id is provided. */
56
- dismiss: (id?: string, res?: any) => void;
68
+ /** Dialog management object. */
69
+ dialog: Dialog;
57
70
  }
58
71
  interface Window {
59
72
  Alert?: T007Namespace["alert"];
60
73
  Confirm?: T007Namespace["confirm"];
61
74
  Prompt?: T007Namespace["prompt"];
62
- Dismiss?: T007Namespace["dismiss"];
63
75
  }
64
76
  }
@@ -5,7 +5,7 @@
5
5
  return Math.min(Math.max(val, min), max);
6
6
  }
7
7
 
8
- // ../../../sia-reactor/dist/chunk-RVYL3OLW.js
8
+ // ../../../sia-reactor/dist/chunk-RI45W4O6.js
9
9
  function createEl(tag, props, dataset, styles, el = tag ? document?.createElement(tag) : null) {
10
10
  return assignEl(el, props, dataset, styles), el;
11
11
  }
@@ -21,6 +21,10 @@
21
21
  for (const k of Object.keys(styles)) if (styles[k] !== void 0) el.style[k] = styles[k];
22
22
  }
23
23
  }
24
+ function getActiveEl(root) {
25
+ const activeEl = (root ?? document).activeElement;
26
+ return !activeEl ? null : activeEl.shadowRoot ? getActiveEl(activeEl.shadowRoot) : activeEl;
27
+ }
24
28
 
25
29
  // ../../../sia-reactor/dist/chunk-3OT72G7R.js
26
30
  function onAllMethods(owner, callback, skipOwn = true, nested = false) {
@@ -55,8 +59,8 @@
55
59
  var NOOP = () => {
56
60
  };
57
61
 
58
- // ../utils/dist/chunk-XVFFZZJA.js
59
- var INTERACTIVE_SELECTOR = 'button,[href],input,label,select,textarea,details>summary,[contenteditable],iframe,audio[controls],video[controls],[tabindex]:not([tabindex="-1"])';
62
+ // ../utils/dist/chunk-N5KX6IW4.js
63
+ var INTERACTIVE_SELECTOR = ":is(button,[href],input:not([type='hidden']),select,textarea,details>summary,[contenteditable='true'],iframe,audio[controls],video[controls],[tabindex]):not([disabled],[tabindex='-1'],[data-focus-guard],[inert],[inert] *)";
60
64
  var isInteractive = (target) => target instanceof HTMLElement && target.matches(INTERACTIVE_SELECTOR);
61
65
  var VIRTUAL_RESOURCE = /* @__PURE__ */ Symbol.for("T007_VIRTUAL_RESOURCE");
62
66
  function loadResource(req, type = "style", { module, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, attempts = 3, retryKey = false } = {}, w = window) {
@@ -86,10 +90,6 @@
86
90
  });
87
91
  return w.t007._resourceCache[src];
88
92
  }
89
- function getActiveElement(root = document) {
90
- const activeEl = root.activeElement;
91
- return !activeEl ? null : activeEl.shadowRoot ? getActiveElement(activeEl.shadowRoot) : activeEl;
92
- }
93
93
  function isDef(val) {
94
94
  return "undefined" !== typeof val;
95
95
  }
@@ -102,6 +102,9 @@
102
102
  function uid(prefix = "") {
103
103
  return prefix + Date.now().toString(36) + "_" + performance.now().toString(36).replace(".", "") + "_" + Math.random().toString(36).slice(2);
104
104
  }
105
+ function parseCSSTime(time) {
106
+ return time?.endsWith?.("ms") ? parseFloat(time) : parseFloat(time) * 1e3;
107
+ }
105
108
  function isSameURL(src1, src2) {
106
109
  if (!isStr(src1) || !isStr(src2) || !src1 || !src2) return false;
107
110
  try {
@@ -121,16 +124,15 @@
121
124
  window.T007_DIALOG_CSS_SRC ??= `https://cdn.jsdelivr.net/npm/@t007/dialog@latest/dist/index.min.css`;
122
125
  }
123
126
 
124
- // ../utils/dist/chunk-AI5O3OGE.js
125
- var stacks = /* @__PURE__ */ new WeakMap();
126
- function initOutsideClick(el, { enabled = false, onOutsideClick = NOOP, clickOnClick = true, clickOnEscape = true, clickOnFocusOut = false, allowInputs = true, root = window, scoped = true, capture = true } = NIL) {
127
- const existing = (t007._outsiders ??= /* @__PURE__ */ new WeakMap()).get(el);
127
+ // ../utils/dist/chunk-NLR4ANGT.js
128
+ function initOutsideClick(el, { enabled = false, onOutsideClick = NOOP, outOnClick = true, outOnEscape = true, outOnFocusOut = false, allowInputs = false, root = window, scoped = true, capture = true } = NIL) {
129
+ const stacks = t007._outsiders_stacks ??= /* @__PURE__ */ new WeakMap(), existing = (t007._outsiders ??= /* @__PURE__ */ new WeakMap()).get(el);
128
130
  if (!enabled || existing) return existing ? existing : void 0;
129
131
  scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
130
132
  const stack = stacks.get(root) ?? [], onScopedOut = (e, t, p = e.touches?.[0] || e, rect = el.getBoundingClientRect()) => {
131
133
  if (stack.at(-1) !== el || p.clientX >= rect.left && p.clientX <= rect.right && p.clientY >= rect.top && p.clientY <= rect.bottom) return false;
132
134
  return (!scoped ? true : root.contains(t)) && onOutsideClick(e);
133
- }, handleClick = ((e) => clickOnClick && !(allowInputs && isInteractive(e.target)) && onScopedOut(e, e.target)), handleEscape = ((e) => clickOnEscape && e.key === "Escape" && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey && stack.at(-1) === el && onOutsideClick(e)), handleFocusOut = (e) => clickOnFocusOut && onScopedOut(e, e.relatedTarget);
135
+ }, handleClick = ((e) => outOnClick && !(allowInputs && isInteractive(e.target)) && onScopedOut(e, e.target)), handleEscape = ((e) => outOnEscape && e.key === "Escape" && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey && stack.at(-1) === el && onOutsideClick(e)), handleFocusOut = (e) => outOnFocusOut && !el.contains(e.relatedTarget) && onScopedOut(e, e.relatedTarget);
134
136
  root.addEventListener("mousedown", handleClick, capture), root.addEventListener("touchstart", handleClick, { passive: true, capture });
135
137
  root.addEventListener("keydown", handleEscape, capture), el.addEventListener("focusout", handleFocusOut, capture);
136
138
  if (!stack.includes(el)) stack.push(el), stacks.set(root, stack);
@@ -143,25 +145,29 @@
143
145
  return t007._outsiders.set(el, destroy), destroy;
144
146
  }
145
147
  var removeOutsideClick = (el) => t007._outsiders?.get(el)?.();
146
- var stacks2 = /* @__PURE__ */ new WeakMap();
147
148
  function initFocusTrap(el, { enabled = false, initialSelector = "[data-autofocus]", ringClassName = "focus-outline", root = window, scoped = true, capture = true } = NIL) {
148
- const existing = (t007._ftrappers ??= /* @__PURE__ */ new WeakMap()).get(el);
149
+ const stacks = t007._ftrappers_stacks ??= /* @__PURE__ */ new WeakMap(), existing = (t007._ftrappers ??= /* @__PURE__ */ new WeakMap()).get(el);
149
150
  if (!enabled || existing) return existing ? existing : void 0;
150
151
  scoped = scoped && root instanceof HTMLElement, root = scoped ? root : root === document ? document : window;
151
- const stack = stacks2.get(root) ?? [], focused = document.querySelector(":focus"), initial = el.querySelector(initialSelector), first = createEl("span", { tabIndex: 0 }, { focusGuard: "start" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), last = createEl("span", { tabIndex: 0 }, { focusGuard: "end" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), getFocusable = (c = el) => Array.prototype.filter.call(c.querySelectorAll(INTERACTIVE_SELECTOR), (el2) => !el2.hasAttribute("disabled") && !el2.hasAttribute("aria-hidden") && !el2.hasAttribute("data-focus-guard")), resetFocus = (i = 0, els = getFocusable()) => els?.length ? els.at(i).focus() : (!el.hasAttribute("tabindex") && (el.tabIndex = -1), el.focus()), edgeFocus = (pre = false) => {
152
+ const stack = stacks.get(root) ?? [], focused = document.querySelector(":focus"), initial = el.querySelector(initialSelector), first = createEl("span", { tabIndex: 0 }, { focusGuard: "start" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), last = createEl("span", { tabIndex: 0 }, { focusGuard: "end" }, { position: "absolute", width: "0", height: "0", pointerEvents: "none" }), getFocusable = (c = el) => [...c.querySelectorAll(INTERACTIVE_SELECTOR)], resetFocus = (i = 0, els = getFocusable()) => els?.length ? els.at(i).focus() : (!el.hasAttribute("tabindex") && (el.tabIndex = -1), el.focus()), edgeFocus = (pre = false, rt = root) => {
152
153
  if (!scoped) return resetFocus(pre ? -1 : 0);
153
- else if (root.hasAttribute("tabindex")) return root.focus();
154
+ if (rt.hasAttribute("tabindex")) return rt.focus();
154
155
  const items = getFocusable();
155
156
  if (!items.length) return resetFocus(0, null);
156
- const all = getFocusable(root.parentElement?.closest(`:has(${INTERACTIVE_SELECTOR})`) || document.body);
157
- for (let target, len = all.length, i = all.indexOf(items[pre ? 0 : items.length - 1]) + (pre ? -1 : 1); pre ? i >= 0 : i < len; pre ? i-- : i++) if (!root.contains(target = all[i])) return target.focus();
157
+ const ceiling = document.fullscreenElement || document.querySelector("dialog:modal") || document.body;
158
+ let p = rt.parentElement || ceiling, all = getFocusable(p);
159
+ while (p !== ceiling && (!all.length || rt.contains(all[0]) && rt.contains(all.at(-1)))) all = getFocusable(p = p.parentElement || ceiling);
160
+ for (let target, len = all.length, i = all.indexOf(items[pre ? 0 : items.length - 1]) + (pre ? -1 : 1); pre ? i >= 0 : i < len; pre ? i-- : i++) if (!rt.contains(target = all[i])) return target.focus();
158
161
  (pre ? first : last).blur();
159
- }, handleFocusIn = () => stack.at(-1) === el && getActiveElement() !== root && !el.contains(getActiveElement()) && setTimeout(resetFocus, 0, 0), handleInitialBlur = () => initial.classList.remove(ringClassName);
162
+ }, handleFocusIn = () => {
163
+ if (document.querySelector("dialog:modal") && !el.matches("dialog:modal")) return;
164
+ stack.at(-1) === el && getActiveEl(el.ownerDocument) !== root && !el.contains(getActiveEl(el.ownerDocument)) && resetFocus();
165
+ }, handleInitialBlur = () => initial.classList.remove(ringClassName);
160
166
  first.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus(true) : resetFocus(), capture), el.prepend(first);
161
167
  last.addEventListener("focus", (e) => el.contains(e.relatedTarget) ? edgeFocus() : resetFocus(-1), capture), el.append(last);
162
168
  root.addEventListener("focusin", handleFocusIn, capture);
163
- if (!el.querySelector(":focus")) !initial ? resetFocus() : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(), initial.addEventListener("blur", handleInitialBlur, capture)));
164
- if (!stack.includes(el)) stack.push(el), stacks2.set(root, stack);
169
+ if (!el.querySelector(":focus")) !initial ? setTimeout(resetFocus) : setTimeout(() => (initial.classList.add(ringClassName), initial.focus(), initial.addEventListener("blur", handleInitialBlur, capture)));
170
+ if (!stack.includes(el)) stack.push(el), stacks.set(root, stack);
165
171
  const destroy = () => {
166
172
  focused?.isConnected && focused.focus(), first.remove(), last.remove();
167
173
  root.removeEventListener("focusin", handleFocusIn, capture);
@@ -239,7 +245,7 @@
239
245
 
240
246
  // ../utils/dist/hooks/vanilla.js
241
247
  function initArrowNavigation(container, config = {}) {
242
- const existing = (t007._ashooters ??= /* @__PURE__ */ new WeakMap()).get(container);
248
+ const existing = (t007._arrownavs ??= /* @__PURE__ */ new WeakMap()).get(container);
243
249
  if (!config.enabled || existing) return existing ? existing : void 0;
244
250
  const { enabled: isEnabled, selector, focusOnHover, loop, virtual, typeahead, resetMs, activeClass, inputSelector, defaultTabbableIndex, baseTabIndex, grid, rtl: isRtl, focusOptions, scrollIntoView, onSelect, onFocusOut, rovingTab } = { ...DEFAULT_CONFIG, ...config };
245
251
  let gridX = grid.x || 1, gridY = grid.y || 1, vGridY = grid.vY || 1, activeIndex = -1, buffer = "", timeout = null, items = [];
@@ -270,11 +276,11 @@
270
276
  if (shouldSnub() || !virtual && !roving || !items.length) return;
271
277
  const hasDefaultTabbable = defaultTabbableIndex !== null && defaultTabbableIndex !== void 0, tabbableIndex = hasDefaultTabbable && !isItemDisabled(items[defaultTabbableIndex]) ? defaultTabbableIndex : getAbleIndex(0);
272
278
  for (let i = 0, len = items.length; i < len; i++) {
273
- const el = items[i], isActive = i === activeIndex;
279
+ const el = items[i], isActive2 = i === activeIndex;
274
280
  if (roving) el.setAttribute("tabindex", i === activeIndex || activeIndex === -1 && i === tabbableIndex ? baseTabIndex : "-1");
275
281
  else if (virtual && activeIndex > 0) el.setAttribute("tabindex", i > activeIndex ? baseTabIndex : "-1");
276
282
  else el.setAttribute("tabindex", baseTabIndex);
277
- if (virtual) el.setAttribute("aria-selected", String(isActive)), el.classList.toggle(activeClass, isActive);
283
+ if (virtual) el.setAttribute("aria-selected", String(isActive2)), el.classList.toggle(activeClass, isActive2);
278
284
  }
279
285
  };
280
286
  const resetActiveIndex = (index = -1) => (activeIndex = index, updateDOM());
@@ -290,15 +296,16 @@
290
296
  }
291
297
  };
292
298
  const simulateKey = (e) => {
293
- if (shouldSnub() || getActiveElement()?.matches("option")) return;
299
+ const t = e.target;
300
+ if (shouldSnub() || getActiveEl(t?.ownerDocument)?.matches("option")) return;
294
301
  const { key } = e;
295
302
  if (!items.length) return;
296
303
  if (virtual && (key === " " || key === "Enter")) return items[activeIndex]?.click();
297
- if (e.target?.matches(DEFAULT_CONFIG.inputSelector) && !virtual) return;
304
+ if (t?.matches(DEFAULT_CONFIG.inputSelector) && !virtual) return;
298
305
  if (typeahead && key.length === 1 && /^[a-z0-9]$/i.test(key)) return typeAhead(key);
299
306
  if (!NAV_KEYS.includes(key)) return;
300
307
  if (!(e.currentTarget?.matches(DEFAULT_CONFIG.inputSelector) && gridX <= 1 && H_NAV_KEYS.includes(key))) e.preventDefault?.(), e.stopPropagation?.();
301
- const currIndex = virtual ? activeIndex : items.indexOf(getActiveElement()), targetIndex = getTargetIndex({ currIndex, gridX, gridY, vGridY, length: items.length, loop, rtl, key, ctrlKey: e.ctrlKey });
308
+ const currIndex = virtual ? activeIndex : items.indexOf(getActiveEl(t?.ownerDocument)), targetIndex = getTargetIndex({ currIndex, gridX, gridY, vGridY, length: items.length, loop, rtl, key, ctrlKey: e.ctrlKey });
302
309
  goToIndex(targetIndex, e);
303
310
  };
304
311
  getItems(), updateDOM();
@@ -345,9 +352,9 @@
345
352
  if (timeout) clearTimeout(timeout);
346
353
  };
347
354
  const handle = { gridX: () => gridX, gridY: () => gridY, vGridY: () => vGridY, items: () => items, activeIndex: () => activeIndex, activeItem: () => items[activeIndex] ?? null, getAbleIndex, typeAhead, goToIndex, simulateKey, destroy };
348
- return t007._ashooters.set(container, handle), handle;
355
+ return t007._arrownavs.set(container, handle), handle;
349
356
  }
350
- var removeArrowNavigation = (container) => t007._ashooters?.get(container)?.destroy();
357
+ var removeArrowNavigation = (container) => t007._arrownavs?.get(container)?.destroy();
351
358
 
352
359
  // src/js/index.js
353
360
  var T007_Dialog = class {
@@ -356,7 +363,7 @@
356
363
  this.resolve = resolve;
357
364
  t007.dialogs.set(this.id = id ?? uid("t007_dialog_"), this);
358
365
  this.rootElement = root?.isConnected ? root : document.body, this.rootScoped = this.rootElement !== document.body, this.scoped = this.rootScoped && scoped;
359
- this.rootElement.append(this.dialog = createEl("dialog", { closedBy: by || (this.rootScoped ? "none" : "any"), className: `t007-dialog${this.rootScoped ? " t007-dialog-scoped" : ""}`, id: this.id }));
366
+ this.rootElement.append(this.dialog = createEl("dialog", { closedBy: by || (this.rootScoped ? "none" : "any"), className: `t007-dialog${this.rootScoped ? " t007-dialog-scoped" : ""}`, id: this.id }), this.rootScoped ? this.backdrop = createEl("div", { className: "t007-dialog-backdrop", id: `${this.id}_backdrop` }) : null);
360
367
  this.dialog.addEventListener("cancel", this.cancel);
361
368
  initArrowNavigation(this.dialog, { enabled: true, rovingTab: false });
362
369
  this.rootScoped && !by && initOutsideClick(this.dialog, { enabled: true, onOutsideClick: this.cancel, root: this.rootElement, scoped: this.scoped });
@@ -367,7 +374,8 @@
367
374
  }
368
375
  remove() {
369
376
  removeArrowNavigation(this.dialog), removeFocusTrap(this.dialog), removeOutsideClick(this.dialog);
370
- this.dialog.open && this.dialog.close(), this.dialog.remove(), t007.dialogs.delete(this.id);
377
+ this.dialog.open && this.dialog.close(), t007.dialogs.delete(this.id);
378
+ setTimeout(() => (this.dialog.remove(), this.backdrop?.remove()), parseCSSTime(getComputedStyle(this.dialog).transitionDuration || "150ms"));
371
379
  }
372
380
  confirm(res = true) {
373
381
  this.remove(), this.resolve(res instanceof Event ? true : res);
@@ -382,8 +390,8 @@
382
390
  }
383
391
  render(message, options) {
384
392
  this.dialog.innerHTML = `
385
- <div class="t007-dialog-top-section">
386
- <p class="t007-dialog-question">${message}</p>
393
+ <div class="t007-dialog-top-section" tabindex="-1">
394
+ <div class="t007-dialog-question">${message}</div>
387
395
  </div>
388
396
  <div class="t007-dialog-bottom-section">
389
397
  <button type="button" data-autofocus data-arrow-item class="t007-dialog-confirm-button">${options.confirmText || "OK"}</button>
@@ -399,8 +407,8 @@
399
407
  }
400
408
  render(question, options) {
401
409
  this.dialog.innerHTML = `
402
- <div class="t007-dialog-top-section">
403
- <p class="t007-dialog-question">${question}</p>
410
+ <div class="t007-dialog-top-section" tabindex="-1">
411
+ <div class="t007-dialog-question">${question}</div>
404
412
  </div>
405
413
  <div class="t007-dialog-bottom-section">
406
414
  <button type="button" data-autofocus data-arrow-item class="t007-dialog-confirm-button">${options.confirmText || "OK"}</button>
@@ -420,9 +428,9 @@
420
428
  options = { ...options, value: defaultValue };
421
429
  await loadResource(window.T007_INPUT_JS_SRC, "script");
422
430
  this.dialog.innerHTML = `
423
- <form class="t007-input-form" novalidate>
424
- <div class="t007-dialog-top-section">
425
- <p class="t007-dialog-question">${question}</p>
431
+ <form class="t007-input-form" ${t007.field ? "novalidate" : ""}>
432
+ <div class="t007-dialog-top-section" tabindex="-1">
433
+ <div class="t007-dialog-question">${question}</div>
426
434
  </div>
427
435
  <div class="t007-dialog-bottom-section">
428
436
  <button type="submit" data-arrow-item class="t007-dialog-confirm-button">${options.confirmText || "OK"}</button>
@@ -431,13 +439,13 @@
431
439
  </form>
432
440
  `;
433
441
  (this.form = this.dialog.querySelector("form")).lastElementChild.insertAdjacentElement("beforebegin", t007.field?.(options) || createEl("input", options));
434
- this.form.onSubmit = this.confirm;
442
+ t007.field ? this.form.onSubmit = this.confirm : this.form.addEventListener("submit", (e) => (e.preventDefault(), this.confirm()));
435
443
  this.dialog.querySelector(".t007-dialog-cancel-button").addEventListener("click", this.cancel);
436
444
  t007.handleFormValidation?.(this.form);
437
445
  this.show();
438
446
  }
439
447
  show() {
440
- super.show(), this.form?.elements?.[0]?.select?.();
448
+ super.show(), this.form.elements[0]?.select?.();
441
449
  }
442
450
  confirm() {
443
451
  super.confirm(this.form.elements[0]?.value);
@@ -446,6 +454,9 @@
446
454
  super.cancel(null);
447
455
  }
448
456
  };
457
+ function isActive(id) {
458
+ return isDef(id) ? t007.dialogs.has(id) : t007.dialogs.size > 0;
459
+ }
449
460
  function alert(message, options) {
450
461
  return new Promise((resolve) => new T007_Alert_Dialog({ resolve, message, options }));
451
462
  }
@@ -460,16 +471,11 @@
460
471
  for (const dialog of t007.dialogs.values()) dialog.cancel(response);
461
472
  }
462
473
  if ("undefined" !== typeof window) {
463
- t007.alert = alert;
464
- t007.confirm = confirm;
465
- t007.prompt = prompt;
466
- t007.dismiss = dismiss;
474
+ t007.alert = alert, t007.confirm = confirm, t007.prompt = prompt;
475
+ t007.dialog = { isActive, alert, confirm, prompt, dismiss };
467
476
  t007.dialogs = /* @__PURE__ */ new Map();
468
477
  loadResource(T007_DIALOG_CSS_SRC), loadResource(window.T007_INPUT_JS_SRC, "script");
469
- window.Alert ??= t007.alert;
470
- window.Confirm ??= t007.confirm;
471
- window.Prompt ??= t007.prompt;
472
- window.Dismiss ??= t007.dismiss;
478
+ window.Alert ??= t007.alert, window.Confirm ??= t007.confirm, window.Prompt ??= t007.prompt;
473
479
  console.log("%cT007 Dialogs attached to window!", "color: darkturquoise");
474
480
  }
475
481
  })();
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  // src/js/index.js
2
2
  import { initArrowNavigation, initFocusTrap, initOutsideClick, removeArrowNavigation, removeFocusTrap, removeOutsideClick } from "@t007/utils/hooks/vanilla";
3
- import { bindAllMethods, createEl, isDef, loadResource, uid } from "@t007/utils";
3
+ import { bindAllMethods, createEl, isDef, loadResource, uid, parseCSSTime } from "@t007/utils";
4
4
  var T007_Dialog = class {
5
5
  constructor(resolve, { id, closedBy: by, rootElement: root = document.body, scoped = true }) {
6
6
  bindAllMethods(this);
7
7
  this.resolve = resolve;
8
8
  t007.dialogs.set(this.id = id ?? uid("t007_dialog_"), this);
9
9
  this.rootElement = root?.isConnected ? root : document.body, this.rootScoped = this.rootElement !== document.body, this.scoped = this.rootScoped && scoped;
10
- this.rootElement.append(this.dialog = createEl("dialog", { closedBy: by || (this.rootScoped ? "none" : "any"), className: `t007-dialog${this.rootScoped ? " t007-dialog-scoped" : ""}`, id: this.id }));
10
+ this.rootElement.append(this.dialog = createEl("dialog", { closedBy: by || (this.rootScoped ? "none" : "any"), className: `t007-dialog${this.rootScoped ? " t007-dialog-scoped" : ""}`, id: this.id }), this.rootScoped ? this.backdrop = createEl("div", { className: "t007-dialog-backdrop", id: `${this.id}_backdrop` }) : null);
11
11
  this.dialog.addEventListener("cancel", this.cancel);
12
12
  initArrowNavigation(this.dialog, { enabled: true, rovingTab: false });
13
13
  this.rootScoped && !by && initOutsideClick(this.dialog, { enabled: true, onOutsideClick: this.cancel, root: this.rootElement, scoped: this.scoped });
@@ -18,7 +18,8 @@ var T007_Dialog = class {
18
18
  }
19
19
  remove() {
20
20
  removeArrowNavigation(this.dialog), removeFocusTrap(this.dialog), removeOutsideClick(this.dialog);
21
- this.dialog.open && this.dialog.close(), this.dialog.remove(), t007.dialogs.delete(this.id);
21
+ this.dialog.open && this.dialog.close(), t007.dialogs.delete(this.id);
22
+ setTimeout(() => (this.dialog.remove(), this.backdrop?.remove()), parseCSSTime(getComputedStyle(this.dialog).transitionDuration || "150ms"));
22
23
  }
23
24
  confirm(res = true) {
24
25
  this.remove(), this.resolve(res instanceof Event ? true : res);
@@ -33,8 +34,8 @@ var T007_Alert_Dialog = class extends T007_Dialog {
33
34
  }
34
35
  render(message, options) {
35
36
  this.dialog.innerHTML = `
36
- <div class="t007-dialog-top-section">
37
- <p class="t007-dialog-question">${message}</p>
37
+ <div class="t007-dialog-top-section" tabindex="-1">
38
+ <div class="t007-dialog-question">${message}</div>
38
39
  </div>
39
40
  <div class="t007-dialog-bottom-section">
40
41
  <button type="button" data-autofocus data-arrow-item class="t007-dialog-confirm-button">${options.confirmText || "OK"}</button>
@@ -50,8 +51,8 @@ var T007_Confirm_Dialog = class extends T007_Dialog {
50
51
  }
51
52
  render(question, options) {
52
53
  this.dialog.innerHTML = `
53
- <div class="t007-dialog-top-section">
54
- <p class="t007-dialog-question">${question}</p>
54
+ <div class="t007-dialog-top-section" tabindex="-1">
55
+ <div class="t007-dialog-question">${question}</div>
55
56
  </div>
56
57
  <div class="t007-dialog-bottom-section">
57
58
  <button type="button" data-autofocus data-arrow-item class="t007-dialog-confirm-button">${options.confirmText || "OK"}</button>
@@ -71,9 +72,9 @@ var T007_Prompt_Dialog = class extends T007_Dialog {
71
72
  options = { ...options, value: defaultValue };
72
73
  await loadResource(window.T007_INPUT_JS_SRC, "script");
73
74
  this.dialog.innerHTML = `
74
- <form class="t007-input-form" novalidate>
75
- <div class="t007-dialog-top-section">
76
- <p class="t007-dialog-question">${question}</p>
75
+ <form class="t007-input-form" ${t007.field ? "novalidate" : ""}>
76
+ <div class="t007-dialog-top-section" tabindex="-1">
77
+ <div class="t007-dialog-question">${question}</div>
77
78
  </div>
78
79
  <div class="t007-dialog-bottom-section">
79
80
  <button type="submit" data-arrow-item class="t007-dialog-confirm-button">${options.confirmText || "OK"}</button>
@@ -82,13 +83,13 @@ var T007_Prompt_Dialog = class extends T007_Dialog {
82
83
  </form>
83
84
  `;
84
85
  (this.form = this.dialog.querySelector("form")).lastElementChild.insertAdjacentElement("beforebegin", t007.field?.(options) || createEl("input", options));
85
- this.form.onSubmit = this.confirm;
86
+ t007.field ? this.form.onSubmit = this.confirm : this.form.addEventListener("submit", (e) => (e.preventDefault(), this.confirm()));
86
87
  this.dialog.querySelector(".t007-dialog-cancel-button").addEventListener("click", this.cancel);
87
88
  t007.handleFormValidation?.(this.form);
88
89
  this.show();
89
90
  }
90
91
  show() {
91
- super.show(), this.form?.elements?.[0]?.select?.();
92
+ super.show(), this.form.elements[0]?.select?.();
92
93
  }
93
94
  confirm() {
94
95
  super.confirm(this.form.elements[0]?.value);
@@ -97,6 +98,9 @@ var T007_Prompt_Dialog = class extends T007_Dialog {
97
98
  super.cancel(null);
98
99
  }
99
100
  };
101
+ function isActive(id) {
102
+ return isDef(id) ? t007.dialogs.has(id) : t007.dialogs.size > 0;
103
+ }
100
104
  function alert(message, options) {
101
105
  return new Promise((resolve) => new T007_Alert_Dialog({ resolve, message, options }));
102
106
  }
@@ -111,21 +115,17 @@ function dismiss(id, response) {
111
115
  for (const dialog of t007.dialogs.values()) dialog.cancel(response);
112
116
  }
113
117
  if ("undefined" !== typeof window) {
114
- t007.alert = alert;
115
- t007.confirm = confirm;
116
- t007.prompt = prompt;
117
- t007.dismiss = dismiss;
118
+ t007.alert = alert, t007.confirm = confirm, t007.prompt = prompt;
119
+ t007.dialog = { isActive, alert, confirm, prompt, dismiss };
118
120
  t007.dialogs = /* @__PURE__ */ new Map();
119
121
  loadResource(T007_DIALOG_CSS_SRC), loadResource(window.T007_INPUT_JS_SRC, "script");
120
- window.Alert ??= t007.alert;
121
- window.Confirm ??= t007.confirm;
122
- window.Prompt ??= t007.prompt;
123
- window.Dismiss ??= t007.dismiss;
122
+ window.Alert ??= t007.alert, window.Confirm ??= t007.confirm, window.Prompt ??= t007.prompt;
124
123
  console.log("%cT007 Dialogs attached to window!", "color: darkturquoise");
125
124
  }
126
125
  export {
127
126
  alert,
128
127
  confirm,
129
128
  dismiss,
129
+ isActive,
130
130
  prompt
131
131
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t007/dialog",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "A lightweight, pure JS dialog system.",
5
5
  "author": "Oketade Oluwatobiloba <tobioketade007@gmail.com>",
6
6
  "license": "MIT",
@@ -56,6 +56,6 @@
56
56
  "accessible"
57
57
  ],
58
58
  "dependencies": {
59
- "@t007/utils": "^0.0.26"
59
+ "@t007/utils": "^0.0.27"
60
60
  }
61
61
  }