@data-slot/alert-dialog 0.2.132

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 ADDED
@@ -0,0 +1,135 @@
1
+ # @data-slot/alert-dialog
2
+
3
+ Headless alert dialog component for vanilla JavaScript. Accessible, unstyled, and modal by default.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @data-slot/alert-dialog
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```html
14
+ <div data-slot="alert-dialog">
15
+ <button data-slot="alert-dialog-trigger">Delete project</button>
16
+
17
+ <div data-slot="alert-dialog-portal">
18
+ <div data-slot="alert-dialog-overlay" hidden></div>
19
+ <div data-slot="alert-dialog-content" hidden>
20
+ <div data-slot="alert-dialog-header">
21
+ <div data-slot="alert-dialog-media">!</div>
22
+ <h2 data-slot="alert-dialog-title">Delete project?</h2>
23
+ <p data-slot="alert-dialog-description">
24
+ This action cannot be undone.
25
+ </p>
26
+ </div>
27
+
28
+ <div data-slot="alert-dialog-footer">
29
+ <button data-slot="alert-dialog-cancel">Cancel</button>
30
+ <button data-slot="alert-dialog-action">Delete</button>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+ <script type="module">
37
+ import { createAlertDialog } from "@data-slot/alert-dialog";
38
+
39
+ const root = document.querySelector('[data-slot="alert-dialog"]');
40
+ const alertDialog = createAlertDialog(root);
41
+
42
+ root
43
+ .querySelector('[data-slot="alert-dialog-action"]')
44
+ ?.addEventListener("click", () => {
45
+ // Run your confirm action, then close explicitly.
46
+ alertDialog.close();
47
+ });
48
+ </script>
49
+ ```
50
+
51
+ ## API
52
+
53
+ ### `create(scope?)`
54
+
55
+ Auto-discovers and binds all alert dialogs in a scope.
56
+
57
+ ```ts
58
+ import { create } from "@data-slot/alert-dialog";
59
+
60
+ const controllers = create();
61
+ ```
62
+
63
+ ### `createAlertDialog(root, options?)`
64
+
65
+ ```ts
66
+ import { createAlertDialog } from "@data-slot/alert-dialog";
67
+
68
+ const alertDialog = createAlertDialog(element, {
69
+ closeOnClickOutside: false,
70
+ onOpenChange: (open) => console.log(open),
71
+ });
72
+ ```
73
+
74
+ #### Options
75
+
76
+ | Option | Type | Default | Description |
77
+ | --- | --- | --- | --- |
78
+ | `defaultOpen` | `boolean` | `false` | Initial open state |
79
+ | `onOpenChange` | `(open: boolean) => void` | `undefined` | Called when open state changes |
80
+ | `closeOnClickOutside` | `boolean` | `false` | Close when clicking the overlay |
81
+ | `closeOnEscape` | `boolean` | `true` | Close when pressing `Escape` |
82
+ | `lockScroll` | `boolean` | `true` | Lock page scroll while open |
83
+
84
+ #### Controller
85
+
86
+ | Method | Description |
87
+ | --- | --- |
88
+ | `open()` | Open the alert dialog |
89
+ | `close()` | Close the alert dialog |
90
+ | `toggle()` | Toggle the alert dialog |
91
+ | `destroy()` | Remove listeners and cleanup |
92
+ | `isOpen` | Current open state |
93
+
94
+ ## Slots
95
+
96
+ ### Runtime slots
97
+
98
+ - `alert-dialog` - Root element
99
+ - `alert-dialog-trigger` - Element that toggles the alert dialog
100
+ - `alert-dialog-portal` - Optional element portaled to `document.body`
101
+ - `alert-dialog-overlay` - Required overlay
102
+ - `alert-dialog-content` - Required modal content
103
+ - `alert-dialog-title` - Title used for `aria-labelledby`
104
+ - `alert-dialog-description` - Description used for `aria-describedby`
105
+ - `alert-dialog-cancel` - Close button
106
+
107
+ ### Style-only slots
108
+
109
+ - `alert-dialog-header`
110
+ - `alert-dialog-footer`
111
+ - `alert-dialog-media`
112
+ - `alert-dialog-action`
113
+
114
+ `alert-dialog-action` is intentionally just a styled action slot. It does not close automatically.
115
+
116
+ ## State and events
117
+
118
+ - `data-state="open" | "closed"` on root, portal, overlay, and content
119
+ - `data-open` / `data-closed` on root, portal, overlay, and content
120
+ - `data-stack-index` on overlay and content when multiple modal layers are open
121
+
122
+ Events:
123
+
124
+ | Event | Detail | Description |
125
+ | --- | --- | --- |
126
+ | `alert-dialog:change` | `{ open: boolean }` | Fired when open state changes |
127
+ | `alert-dialog:set` | `{ open: boolean }` | Programmatically set open state |
128
+
129
+ ## Accessibility
130
+
131
+ - `role="alertdialog"` on content
132
+ - `aria-modal="true"` on content
133
+ - `aria-labelledby` / `aria-describedby` wiring from title and description
134
+ - Focus is trapped while open
135
+ - Focus returns to the trigger or previously focused element when closed
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@data-slot/core`);const t=`@data-slot/alert-dialog`,n=`a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])`;function r(r,i={}){let a=(0,e.reuseRootBinding)(r,t,`[@data-slot/alert-dialog] createAlertDialog() called more than once for the same root. Returning the existing controller. Destroy it before rebinding with new options.`);if(a)return a;let o=i.defaultOpen??(0,e.getDataBool)(r,`defaultOpen`)??!1,s=i.onOpenChange,c=i.closeOnClickOutside??(0,e.getDataBool)(r,`closeOnClickOutside`)??!1,l=i.closeOnEscape??(0,e.getDataBool)(r,`closeOnEscape`)??!0,u=i.lockScroll??(0,e.getDataBool)(r,`lockScroll`)??!0,d=(0,e.getPart)(r,`alert-dialog-trigger`),f=(0,e.getPart)(r,`alert-dialog-portal`),p=(0,e.getPart)(r,`alert-dialog-overlay`),m=(0,e.getPart)(r,`alert-dialog-content`),h=(0,e.getPart)(r,`alert-dialog-title`),g=(0,e.getPart)(r,`alert-dialog-description`);if(!m)throw Error(`Alert dialog requires alert-dialog-content slot`);if(!p)throw Error(`Alert dialog requires alert-dialog-overlay slot`);let _=!1,v=!1,y=null,b=[],x=f?(0,e.createPortalLifecycle)({content:f,root:r}):null,S=!1;(0,e.ensureId)(m,`alert-dialog-content`),m.setAttribute(`role`,`alertdialog`),(0,e.setAria)(m,`modal`,!0),(0,e.linkLabelledBy)(m,h,g),p.setAttribute(`role`,`presentation`),p.setAttribute(`aria-hidden`,`true`),p.tabIndex=-1,d&&(d.setAttribute(`aria-haspopup`,`dialog`),d.setAttribute(`aria-controls`,m.id),(0,e.setAria)(d,`expanded`,!1));let C=!1,w=()=>{m.hasAttribute(`tabindex`)||(m.tabIndex=-1,C=!0)},T=()=>{C&&=(m.removeAttribute(`tabindex`),!1)},E=()=>{let e=m.querySelector(`[autofocus]`);if(e)return e.focus();let t=m.querySelector(n);if(t)return t.focus();w(),m.focus()},D=()=>{requestAnimationFrame(()=>{y&&document.contains(y)&&typeof y.focus==`function`?(0,e.focusElement)(y):d&&document.contains(d)&&(0,e.focusElement)(d),y=null})},O=e=>{if(r.setAttribute(`data-state`,e),f?.setAttribute(`data-state`,e),p.setAttribute(`data-state`,e),m.setAttribute(`data-state`,e),e===`open`){r.setAttribute(`data-open`,``),f?.setAttribute(`data-open`,``),p.setAttribute(`data-open`,``),m.setAttribute(`data-open`,``),r.removeAttribute(`data-closed`),f?.removeAttribute(`data-closed`),p.removeAttribute(`data-closed`),m.removeAttribute(`data-closed`);return}r.setAttribute(`data-closed`,``),f?.setAttribute(`data-closed`,``),p.setAttribute(`data-closed`,``),m.setAttribute(`data-closed`,``),r.removeAttribute(`data-open`),f?.removeAttribute(`data-open`),p.removeAttribute(`data-open`),m.removeAttribute(`data-open`)},k=0,A=0,j=0,M=0,N=(e,t)=>{v||_||t!==k||(e.hidden=!0,A=Math.max(0,A-1),A===0&&(T(),D()))},P=(0,e.createPresenceLifecycle)({element:p,onExitComplete:()=>N(p,j)}),F=(0,e.createPresenceLifecycle)({element:m,onExitComplete:()=>N(m,M)}),I=(0,e.createModalStackItem)({content:m,overlay:p,onTabKeydown:e=>{if(e.key!==`Tab`)return;let t=m.querySelectorAll(n);if(t.length===0){e.preventDefault(),w(),m.focus();return}let r=t[0],i=t[t.length-1],a=document.activeElement;if(!m.contains(a)){e.preventDefault(),r.focus();return}if(r===i){e.preventDefault();return}e.shiftKey?a===r&&(e.preventDefault(),i.focus()):a===i&&(e.preventDefault(),r.focus())},cssVarPrefix:`alert-dialog`}),L=(t,n=!1)=>{_===t&&!n||(t?(k+=1,A=0,x?.mount(),y=document.activeElement,I.open(),u&&!S&&((0,e.lockScroll)(),S=!0)):(k+=1,A=2,j=k,M=k,I.close(),S&&=((0,e.unlockScroll)(),!1)),_=t,d&&(0,e.setAria)(d,`expanded`,_),t?(p.hidden=!1,m.hidden=!1,O(`open`),P.enter(),F.enter()):(O(`closed`),P.exit(),F.exit()),(0,e.emit)(r,`alert-dialog:change`,{open:_}),s?.(_),t&&requestAnimationFrame(E))};m.hidden=!0,p.hidden=!0,O(`closed`),d&&b.push((0,e.on)(d,`click`,()=>L(!_))),b.push((0,e.on)(m,`click`,e=>{let t=e.target;if(!t)return;let n=t.closest?.(`[data-slot="alert-dialog-cancel"]`);n&&m.contains(n)&&L(!1)})),c&&b.push((0,e.on)(p,`click`,e=>{e.button===0&&e.target===p&&_&&L(!1)})),b.push((0,e.createDismissLayer)({root:r,isOpen:()=>_,onDismiss:()=>L(!1),closeOnClickOutside:!1,closeOnEscape:l}));let R={open:()=>L(!0),close:()=>L(!1),toggle:()=>L(!_),get isOpen(){return _},destroy:()=>{v=!0,I.destroy(),k+=1,A=0,P.cleanup(),F.cleanup(),_=!1,O(`closed`),p.hidden=!0,m.hidden=!0,d&&(0,e.setAria)(d,`expanded`,!1),S&&=((0,e.unlockScroll)(),!1),T(),y!==null&&D(),b.forEach(e=>e()),b.length=0,x?.cleanup(),(0,e.clearRootBinding)(r,t,R)}};return b.push((0,e.on)(r,`alert-dialog:set`,e=>{let t=e.detail;typeof t?.open==`boolean`&&L(t.open)})),(0,e.setRootBinding)(r,t,R),o&&L(!0),R}function i(n=document){let i=[];for(let a of(0,e.getRoots)(n,`alert-dialog`))(0,e.hasRootBinding)(a,t)||i.push(r(a));return i}exports.create=i,exports.createAlertDialog=r;
@@ -0,0 +1,29 @@
1
+ //#region src/index.d.ts
2
+ interface AlertDialogOptions {
3
+ /** Initial open state */
4
+ defaultOpen?: boolean;
5
+ /** Callback when open state changes */
6
+ onOpenChange?: (open: boolean) => void;
7
+ /** Close when clicking overlay */
8
+ closeOnClickOutside?: boolean;
9
+ /** Close when pressing Escape */
10
+ closeOnEscape?: boolean;
11
+ /** Lock body scroll when open */
12
+ lockScroll?: boolean;
13
+ }
14
+ interface AlertDialogController {
15
+ /** Open the alert dialog */
16
+ open(): void;
17
+ /** Close the alert dialog */
18
+ close(): void;
19
+ /** Toggle the alert dialog */
20
+ toggle(): void;
21
+ /** Current open state */
22
+ readonly isOpen: boolean;
23
+ /** Cleanup all event listeners */
24
+ destroy(): void;
25
+ }
26
+ declare function createAlertDialog(root: Element, options?: AlertDialogOptions): AlertDialogController;
27
+ declare function create(scope?: ParentNode): AlertDialogController[];
28
+ //#endregion
29
+ export { AlertDialogController, AlertDialogOptions, create, createAlertDialog };
@@ -0,0 +1,29 @@
1
+ //#region src/index.d.ts
2
+ interface AlertDialogOptions {
3
+ /** Initial open state */
4
+ defaultOpen?: boolean;
5
+ /** Callback when open state changes */
6
+ onOpenChange?: (open: boolean) => void;
7
+ /** Close when clicking overlay */
8
+ closeOnClickOutside?: boolean;
9
+ /** Close when pressing Escape */
10
+ closeOnEscape?: boolean;
11
+ /** Lock body scroll when open */
12
+ lockScroll?: boolean;
13
+ }
14
+ interface AlertDialogController {
15
+ /** Open the alert dialog */
16
+ open(): void;
17
+ /** Close the alert dialog */
18
+ close(): void;
19
+ /** Toggle the alert dialog */
20
+ toggle(): void;
21
+ /** Current open state */
22
+ readonly isOpen: boolean;
23
+ /** Cleanup all event listeners */
24
+ destroy(): void;
25
+ }
26
+ declare function createAlertDialog(root: Element, options?: AlertDialogOptions): AlertDialogController;
27
+ declare function create(scope?: ParentNode): AlertDialogController[];
28
+ //#endregion
29
+ export { AlertDialogController, AlertDialogOptions, create, createAlertDialog };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{clearRootBinding as e,createDismissLayer as t,createModalStackItem as n,createPortalLifecycle as r,createPresenceLifecycle as i,emit as a,ensureId as ee,focusElement as o,getDataBool as s,getPart as c,getRoots as l,hasRootBinding as u,linkLabelledBy as d,lockScroll as f,on as p,reuseRootBinding as m,setAria as h,setRootBinding as g,unlockScroll as _}from"@data-slot/core";const v=`@data-slot/alert-dialog`,y=`a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])`;function b(l,u={}){let b=m(l,v,`[@data-slot/alert-dialog] createAlertDialog() called more than once for the same root. Returning the existing controller. Destroy it before rebinding with new options.`);if(b)return b;let x=u.defaultOpen??s(l,`defaultOpen`)??!1,S=u.onOpenChange,C=u.closeOnClickOutside??s(l,`closeOnClickOutside`)??!1,w=u.closeOnEscape??s(l,`closeOnEscape`)??!0,T=u.lockScroll??s(l,`lockScroll`)??!0,E=c(l,`alert-dialog-trigger`),D=c(l,`alert-dialog-portal`),O=c(l,`alert-dialog-overlay`),k=c(l,`alert-dialog-content`),A=c(l,`alert-dialog-title`),j=c(l,`alert-dialog-description`);if(!k)throw Error(`Alert dialog requires alert-dialog-content slot`);if(!O)throw Error(`Alert dialog requires alert-dialog-overlay slot`);let M=!1,N=!1,P=null,F=[],I=D?r({content:D,root:l}):null,L=!1;ee(k,`alert-dialog-content`),k.setAttribute(`role`,`alertdialog`),h(k,`modal`,!0),d(k,A,j),O.setAttribute(`role`,`presentation`),O.setAttribute(`aria-hidden`,`true`),O.tabIndex=-1,E&&(E.setAttribute(`aria-haspopup`,`dialog`),E.setAttribute(`aria-controls`,k.id),h(E,`expanded`,!1));let R=!1,z=()=>{k.hasAttribute(`tabindex`)||(k.tabIndex=-1,R=!0)},B=()=>{R&&=(k.removeAttribute(`tabindex`),!1)},V=()=>{let e=k.querySelector(`[autofocus]`);if(e)return e.focus();let t=k.querySelector(y);if(t)return t.focus();z(),k.focus()},H=()=>{requestAnimationFrame(()=>{P&&document.contains(P)&&typeof P.focus==`function`?o(P):E&&document.contains(E)&&o(E),P=null})},U=e=>{if(l.setAttribute(`data-state`,e),D?.setAttribute(`data-state`,e),O.setAttribute(`data-state`,e),k.setAttribute(`data-state`,e),e===`open`){l.setAttribute(`data-open`,``),D?.setAttribute(`data-open`,``),O.setAttribute(`data-open`,``),k.setAttribute(`data-open`,``),l.removeAttribute(`data-closed`),D?.removeAttribute(`data-closed`),O.removeAttribute(`data-closed`),k.removeAttribute(`data-closed`);return}l.setAttribute(`data-closed`,``),D?.setAttribute(`data-closed`,``),O.setAttribute(`data-closed`,``),k.setAttribute(`data-closed`,``),l.removeAttribute(`data-open`),D?.removeAttribute(`data-open`),O.removeAttribute(`data-open`),k.removeAttribute(`data-open`)},W=0,G=0,K=0,q=0,J=(e,t)=>{N||M||t!==W||(e.hidden=!0,G=Math.max(0,G-1),G===0&&(B(),H()))},Y=i({element:O,onExitComplete:()=>J(O,K)}),X=i({element:k,onExitComplete:()=>J(k,q)}),Z=n({content:k,overlay:O,onTabKeydown:e=>{if(e.key!==`Tab`)return;let t=k.querySelectorAll(y);if(t.length===0){e.preventDefault(),z(),k.focus();return}let n=t[0],r=t[t.length-1],i=document.activeElement;if(!k.contains(i)){e.preventDefault(),n.focus();return}if(n===r){e.preventDefault();return}e.shiftKey?i===n&&(e.preventDefault(),r.focus()):i===r&&(e.preventDefault(),n.focus())},cssVarPrefix:`alert-dialog`}),Q=(e,t=!1)=>{M===e&&!t||(e?(W+=1,G=0,I?.mount(),P=document.activeElement,Z.open(),T&&!L&&(f(),L=!0)):(W+=1,G=2,K=W,q=W,Z.close(),L&&=(_(),!1)),M=e,E&&h(E,`expanded`,M),e?(O.hidden=!1,k.hidden=!1,U(`open`),Y.enter(),X.enter()):(U(`closed`),Y.exit(),X.exit()),a(l,`alert-dialog:change`,{open:M}),S?.(M),e&&requestAnimationFrame(V))};k.hidden=!0,O.hidden=!0,U(`closed`),E&&F.push(p(E,`click`,()=>Q(!M))),F.push(p(k,`click`,e=>{let t=e.target;if(!t)return;let n=t.closest?.(`[data-slot="alert-dialog-cancel"]`);n&&k.contains(n)&&Q(!1)})),C&&F.push(p(O,`click`,e=>{e.button===0&&e.target===O&&M&&Q(!1)})),F.push(t({root:l,isOpen:()=>M,onDismiss:()=>Q(!1),closeOnClickOutside:!1,closeOnEscape:w}));let $={open:()=>Q(!0),close:()=>Q(!1),toggle:()=>Q(!M),get isOpen(){return M},destroy:()=>{N=!0,Z.destroy(),W+=1,G=0,Y.cleanup(),X.cleanup(),M=!1,U(`closed`),O.hidden=!0,k.hidden=!0,E&&h(E,`expanded`,!1),L&&=(_(),!1),B(),P!==null&&H(),F.forEach(e=>e()),F.length=0,I?.cleanup(),e(l,v,$)}};return F.push(p(l,`alert-dialog:set`,e=>{let t=e.detail;typeof t?.open==`boolean`&&Q(t.open)})),g(l,v,$),x&&Q(!0),$}function x(e=document){let t=[];for(let n of l(e,`alert-dialog`))u(n,v)||t.push(b(n));return t}export{x as create,b as createAlertDialog};
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@data-slot/alert-dialog",
3
+ "version": "0.2.132",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsdown"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/bejamas/data-slot.git",
26
+ "directory": "packages/alert-dialog"
27
+ },
28
+ "keywords": [
29
+ "headless",
30
+ "ui",
31
+ "alert-dialog",
32
+ "dialog",
33
+ "confirmation",
34
+ "vanilla",
35
+ "data-slot"
36
+ ],
37
+ "license": "MIT",
38
+ "dependencies": {
39
+ "@data-slot/core": "0.2.132"
40
+ }
41
+ }