css-drawer 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ A near drop-in replacement for [Vaul](https://vaul.emilkowal.ski) using native `
12
12
  | Animation engine | JavaScript | Pure CSS |
13
13
  | Nesting | Manual setup | Automatic (CSS `:has()`) |
14
14
  | Accessibility | Built-in | Automatic (native `<dialog>` + `inert`) |
15
- | API | Controlled state | Native refs |
15
+ | API | Controlled state | Native refs or controlled state |
16
16
 
17
17
  ## Installation
18
18
 
@@ -58,7 +58,7 @@ function App() {
58
58
 
59
59
  ```ts
60
60
  import { open, close } from 'css-drawer'
61
- import 'css-drawer/styles'
61
+ // Styles are auto-injected
62
62
 
63
63
  document.querySelector('#open-btn').onclick = () => open('my-drawer')
64
64
  ```
@@ -68,7 +68,7 @@ document.querySelector('#open-btn').onclick = () => open('my-drawer')
68
68
 
69
69
  <dialog class="drawer" id="my-drawer">
70
70
  <div class="drawer-handle"></div>
71
- <div className="drawer-content">
71
+ <div class="drawer-content">
72
72
  <h2>Title</h2>
73
73
  <p>Description</p>
74
74
  <button onclick="this.closest('dialog').close()">Close</button>
@@ -76,6 +76,44 @@ document.querySelector('#open-btn').onclick = () => open('my-drawer')
76
76
  </dialog>
77
77
  ```
78
78
 
79
+ ### Angular
80
+
81
+ Angular's build system doesn't process CSS imports from JS modules. Import styles in your global `styles.css`:
82
+
83
+ ```css
84
+ /* src/styles.css */
85
+ @import 'css-drawer/styles';
86
+ ```
87
+
88
+ Then use the native dialog API in your component:
89
+
90
+ ```typescript
91
+ import { Component } from '@angular/core';
92
+
93
+ @Component({
94
+ selector: 'app-example',
95
+ template: `
96
+ <button (click)="openDrawer(drawer)">Open</button>
97
+
98
+ <dialog #drawer class="drawer" data-direction="modal">
99
+ <div class="drawer-content">
100
+ <h2>Title</h2>
101
+ <button (click)="closeDrawer(drawer)">Close</button>
102
+ </div>
103
+ </dialog>
104
+ `
105
+ })
106
+ export class ExampleComponent {
107
+ openDrawer(dialog: HTMLDialogElement) {
108
+ dialog.showModal();
109
+ }
110
+
111
+ closeDrawer(dialog: HTMLDialogElement) {
112
+ dialog.close();
113
+ }
114
+ }
115
+ ```
116
+
79
117
  ---
80
118
 
81
119
  ## React API
@@ -99,12 +137,16 @@ Provides context for direction. Wrap your drawer content.
99
137
 
100
138
  | Prop | Type | Default | Description |
101
139
  |------|------|---------|-------------|
102
- | `direction` | `'bottom' \| 'top' \| 'left' \| 'right'` | `'bottom'` | Direction the drawer opens from |
140
+ | `direction` | `'bottom' \| 'top' \| 'left' \| 'right' \| 'modal'` | `'bottom'` | Direction the drawer opens from |
103
141
  | `children` | `ReactNode` | - | Drawer content |
104
142
 
105
143
  ### Drawer.Content
106
144
 
107
- The dialog element. Pass a ref to control open/close.
145
+ The dialog element. Supports both uncontrolled (refs) and controlled (state) modes.
146
+
147
+ > **Note:** `open`/`onOpenChange` props are on `Content`, not `Root`. This is intentional - `Content` wraps the native `<dialog>` element, so open/close control lives where the element lives. `Root` only provides configuration (direction).
148
+
149
+ #### Uncontrolled Mode (Refs)
108
150
 
109
151
  ```tsx
110
152
  const ref = useRef<HTMLDialogElement>(null)
@@ -118,9 +160,29 @@ ref.current?.close()
118
160
  <Drawer.Content ref={ref}>...</Drawer.Content>
119
161
  ```
120
162
 
163
+ #### Controlled Mode (State)
164
+
165
+ ```tsx
166
+ const [isOpen, setIsOpen] = useState(false)
167
+
168
+ <Drawer.Content open={isOpen} onOpenChange={setIsOpen}>
169
+ ...
170
+ </Drawer.Content>
171
+
172
+ // Open programmatically
173
+ <button onClick={() => setIsOpen(true)}>Open</button>
174
+ ```
175
+
176
+ The `onOpenChange` callback fires when:
177
+ - User presses Escape
178
+ - User clicks the backdrop (if `closeOnOutsideClick` is true)
179
+ - You call `setIsOpen(false)`
180
+
121
181
  | Prop | Type | Default | Description |
122
182
  |------|------|---------|-------------|
123
- | `ref` | `Ref<HTMLDialogElement>` | - | Ref to control the dialog |
183
+ | `ref` | `Ref<HTMLDialogElement>` | - | Ref to control the dialog (uncontrolled mode) |
184
+ | `open` | `boolean` | - | Controlled open state |
185
+ | `onOpenChange` | `(open: boolean) => void` | - | Called when open state changes |
124
186
  | `closeOnOutsideClick` | `boolean` | `true` | Close when clicking outside the drawer |
125
187
  | `className` | `string` | - | Additional CSS classes |
126
188
  | `...props` | `DialogHTMLAttributes` | - | All native dialog props |
@@ -169,7 +231,7 @@ Semantic description for accessibility.
169
231
 
170
232
  ```ts
171
233
  import { open, close, closeAll } from 'css-drawer'
172
- import 'css-drawer/styles'
234
+ // Styles are auto-injected
173
235
  ```
174
236
 
175
237
  ### open(drawer)
@@ -262,6 +324,7 @@ open(drawer)
262
324
  |--------|------|---------|-------------|
263
325
  | `id` | `string` | - | Drawer ID |
264
326
  | `content` | `string` | `''` | HTML content |
327
+ | `direction` | `DrawerDirection` | `'bottom'` | Direction the drawer opens from |
265
328
  | `handle` | `boolean` | `true` | Include drag handle |
266
329
  | `className` | `string` | `''` | Additional CSS classes |
267
330
  | `closeOnOutsideClick` | `boolean` | `true` | Close when clicking outside |
@@ -353,6 +416,7 @@ const isMobile = useMediaQuery('(max-width: 768px)')
353
416
  | `top` | Opens from top |
354
417
  | `left` | Opens from left |
355
418
  | `right` | Opens from right |
419
+ | `modal` | Centered modal with scale animation |
356
420
 
357
421
  ---
358
422
 
@@ -360,6 +424,8 @@ const isMobile = useMediaQuery('(max-width: 768px)')
360
424
 
361
425
  Drawers automatically stack when opened. No configuration needed.
362
426
 
427
+ ### With Refs
428
+
363
429
  ```tsx
364
430
  const drawer1 = useRef<HTMLDialogElement>(null)
365
431
  const drawer2 = useRef<HTMLDialogElement>(null)
@@ -372,6 +438,34 @@ drawer2.current?.showModal()
372
438
  // drawer1 automatically scales down and dims
373
439
  ```
374
440
 
441
+ ### With Controlled State
442
+
443
+ ```tsx
444
+ const [settingsOpen, setSettingsOpen] = useState(false)
445
+ const [confirmOpen, setConfirmOpen] = useState(false)
446
+
447
+ <>
448
+ <button onClick={() => setSettingsOpen(true)}>Settings</button>
449
+
450
+ <Drawer.Root>
451
+ <Drawer.Content open={settingsOpen} onOpenChange={setSettingsOpen}>
452
+ <button onClick={() => setConfirmOpen(true)}>Delete Account</button>
453
+ </Drawer.Content>
454
+ </Drawer.Root>
455
+
456
+ <Drawer.Root>
457
+ <Drawer.Content open={confirmOpen} onOpenChange={setConfirmOpen}>
458
+ <button onClick={() => {
459
+ setConfirmOpen(false)
460
+ setSettingsOpen(false)
461
+ }}>
462
+ Confirm
463
+ </button>
464
+ </Drawer.Content>
465
+ </Drawer.Root>
466
+ </>
467
+ ```
468
+
375
469
  Works up to 5 levels. CSS `:has()` selectors handle the visual stacking.
376
470
 
377
471
  ---
@@ -454,6 +548,7 @@ Override any of these CSS custom properties to customize the drawer:
454
548
  --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);
455
549
  --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);
456
550
  --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);
551
+ --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);
457
552
 
458
553
  /* Animation */
459
554
  --drawer-duration: 0.5s;
@@ -507,6 +602,7 @@ Override any of these CSS custom properties to customize the drawer:
507
602
  | `--drawer-shadow-top` | `0 10px 60px hsl(0 0% 0% / 0.12), ...` | Darker |
508
603
  | `--drawer-shadow-left` | `10px 0 60px hsl(0 0% 0% / 0.12), ...` | Darker |
509
604
  | `--drawer-shadow-right` | `-10px 0 60px hsl(0 0% 0% / 0.12), ...` | Darker |
605
+ | `--drawer-shadow-modal` | `0 25px 50px -12px hsl(0 0% 0% / 0.25)` | Darker |
510
606
 
511
607
  #### Animation
512
608
 
@@ -677,6 +773,7 @@ Uses `@starting-style`, `:has()`, `allow-discrete`, and `dvh` units.
677
773
  Full TypeScript support included.
678
774
 
679
775
  ```tsx
776
+ // React
680
777
  import {
681
778
  Drawer,
682
779
  type DrawerRootProps,
@@ -684,11 +781,14 @@ import {
684
781
  type DrawerDirection
685
782
  } from 'css-drawer/react'
686
783
 
784
+ // Vanilla JS
687
785
  import {
688
786
  open,
689
787
  close,
788
+ create,
690
789
  type DrawerElement,
691
- type DrawerRef
790
+ type DrawerRef,
791
+ type DrawerDirection
692
792
  } from 'css-drawer'
693
793
  ```
694
794
 
File without changes
@@ -0,0 +1,331 @@
1
+ /* CSS Drawer - Vaul-quality drawer with auto-nesting and directions */
2
+
3
+ :root {
4
+ /* Visual */
5
+ --drawer-bg: #fff;
6
+ --drawer-radius: 24px;
7
+ --drawer-backdrop: hsl(0 0% 0% / 0.4);
8
+ --drawer-backdrop-blur: 4px;
9
+
10
+ /* Sizing */
11
+ --drawer-max-width: 500px;
12
+ --drawer-max-height: 96dvh;
13
+
14
+ /* Handle */
15
+ --drawer-handle-bg: hsl(0 0% 80%);
16
+ --drawer-handle-bg-hover: hsl(0 0% 60%);
17
+ --drawer-handle-width: 48px;
18
+ --drawer-handle-width-hover: 56px;
19
+ --drawer-handle-height: 5px;
20
+ --drawer-handle-padding-block: 1rem 0.5rem;
21
+ --drawer-handle-padding-inline: 0;
22
+
23
+ /* Shadows */
24
+ --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.12), 0 -4px 20px hsl(0 0% 0% / 0.08);
25
+ --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);
26
+ --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);
27
+ --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);
28
+ --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);
29
+
30
+ /* Animation */
31
+ --drawer-duration: 0.5s;
32
+ --drawer-duration-close: 0.35s;
33
+ --drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);
34
+
35
+ /* Nesting effects */
36
+ --drawer-nested-scale: 0.94;
37
+ --drawer-nested-offset: 20px;
38
+ --drawer-nested-brightness: 0.92;
39
+ --drawer-nested-backdrop: hsl(0 0% 0% / 0.15);
40
+ }
41
+
42
+ @media (prefers-color-scheme: dark) {
43
+ :root {
44
+ --drawer-bg: hsl(0 0% 12%);
45
+ --drawer-handle-bg: hsl(0 0% 35%);
46
+ --drawer-handle-bg-hover: hsl(0 0% 50%);
47
+ --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.4), 0 -4px 20px hsl(0 0% 0% / 0.3);
48
+ --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);
49
+ --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);
50
+ --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);
51
+ --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.5);
52
+ }
53
+ }
54
+
55
+ /* Background scale effect */
56
+ body {
57
+ transition: scale var(--drawer-duration) var(--drawer-ease), border-radius var(--drawer-duration) var(--drawer-ease);
58
+ transform-origin: center top;
59
+ }
60
+
61
+ body:has(.drawer[open]) {
62
+ overflow: hidden;
63
+ scale: var(--drawer-nested-scale);
64
+ border-radius: var(--drawer-radius);
65
+ }
66
+
67
+ /* Base drawer */
68
+ .drawer {
69
+ border: none;
70
+ padding: 0;
71
+ margin: 0;
72
+ max-width: 100%;
73
+ max-height: 100%;
74
+ position: fixed;
75
+ background: var(--drawer-bg);
76
+ overflow: hidden;
77
+ opacity: 0;
78
+ transition:
79
+ display var(--drawer-duration-close) allow-discrete,
80
+ overlay var(--drawer-duration-close) allow-discrete,
81
+ translate var(--drawer-duration-close) var(--drawer-ease),
82
+ scale var(--drawer-duration-close) var(--drawer-ease),
83
+ filter var(--drawer-duration-close) ease,
84
+ opacity var(--drawer-duration-close) ease;
85
+
86
+ /* Default: bottom */
87
+ --_translate-closed: 0 100%;
88
+ --_border-radius: var(--drawer-radius) var(--drawer-radius) 0 0;
89
+ inset: auto 0 0 0;
90
+ margin-inline: auto;
91
+ width: 100%;
92
+ max-width: var(--drawer-max-width);
93
+ height: auto;
94
+ max-height: var(--drawer-max-height);
95
+ border-radius: var(--drawer-border-radius, var(--_border-radius));
96
+ box-shadow: var(--drawer-shadow-bottom);
97
+ translate: var(--_translate-closed);
98
+ }
99
+
100
+ .drawer::backdrop {
101
+ background: var(--drawer-backdrop);
102
+ opacity: 0;
103
+ backdrop-filter: blur(var(--drawer-backdrop-blur));
104
+ -webkit-backdrop-filter: blur(var(--drawer-backdrop-blur));
105
+ transition:
106
+ display var(--drawer-duration-close) allow-discrete,
107
+ overlay var(--drawer-duration-close) allow-discrete,
108
+ opacity var(--drawer-duration-close) ease;
109
+ }
110
+
111
+ .drawer[open] {
112
+ opacity: 1;
113
+ translate: 0 0;
114
+ transition:
115
+ display var(--drawer-duration) allow-discrete,
116
+ overlay var(--drawer-duration) allow-discrete,
117
+ translate var(--drawer-duration) var(--drawer-ease),
118
+ scale var(--drawer-duration) var(--drawer-ease),
119
+ filter var(--drawer-duration) ease,
120
+ opacity 0.15s ease;
121
+ }
122
+
123
+ .drawer[open]::backdrop {
124
+ opacity: 1;
125
+ transition: display var(--drawer-duration) allow-discrete, overlay var(--drawer-duration) allow-discrete, opacity 0.2s ease;
126
+ }
127
+
128
+ @starting-style {
129
+ .drawer[open] { opacity: 0; translate: var(--_translate-closed); }
130
+ .drawer[open]::backdrop { opacity: 0; }
131
+ }
132
+
133
+ /* ===== DIRECTIONS ===== */
134
+
135
+ /* Right */
136
+ .drawer[data-direction="right"] {
137
+ --_translate-closed: 100% 0;
138
+ --_border-radius: var(--drawer-radius) 0 0 var(--drawer-radius);
139
+ inset: 0 0 0 auto;
140
+ margin: 0;
141
+ width: 100%;
142
+ max-width: var(--drawer-max-width);
143
+ height: 100dvh;
144
+ max-height: 100dvh;
145
+ box-shadow: var(--drawer-shadow-right);
146
+ }
147
+
148
+ /* Left */
149
+ .drawer[data-direction="left"] {
150
+ --_translate-closed: -100% 0;
151
+ --_border-radius: 0 var(--drawer-radius) var(--drawer-radius) 0;
152
+ inset: 0 auto 0 0;
153
+ margin: 0;
154
+ width: 100%;
155
+ max-width: var(--drawer-max-width);
156
+ height: 100dvh;
157
+ max-height: 100dvh;
158
+ box-shadow: var(--drawer-shadow-left);
159
+ }
160
+
161
+ /* Top */
162
+ .drawer[data-direction="top"] {
163
+ --_translate-closed: 0 -100%;
164
+ --_border-radius: 0 0 var(--drawer-radius) var(--drawer-radius);
165
+ inset: 0 0 auto 0;
166
+ margin-inline: auto;
167
+ width: 100%;
168
+ max-width: var(--drawer-max-width);
169
+ height: auto;
170
+ max-height: var(--drawer-max-height);
171
+ box-shadow: var(--drawer-shadow-top);
172
+ }
173
+
174
+ /* Modal (centered) */
175
+ .drawer[data-direction="modal"] {
176
+ --_translate-closed: 0 0;
177
+ --_border-radius: var(--drawer-radius);
178
+ inset: 0;
179
+ margin: auto;
180
+ width: fit-content;
181
+ max-width: var(--drawer-max-width);
182
+ height: fit-content;
183
+ max-height: var(--drawer-max-height);
184
+ box-shadow: var(--drawer-shadow-modal);
185
+ scale: var(--drawer-nested-scale);
186
+ }
187
+
188
+ .drawer[data-direction="modal"][open] {
189
+ scale: 1;
190
+ }
191
+
192
+ @starting-style {
193
+ .drawer[data-direction="modal"][open] {
194
+ scale: var(--drawer-nested-scale);
195
+ }
196
+ }
197
+
198
+ /* ===== AUTO-NESTING (up to 5 levels) ===== */
199
+ /* Uses sibling combinators to count open drawers */
200
+
201
+ /* 1+ open drawers after */
202
+ .drawer[open]:has(~ .drawer[open]) {
203
+ scale: var(--drawer-nested-scale);
204
+ translate: 0 calc(-1 * var(--drawer-nested-offset));
205
+ border-radius: var(--drawer-radius);
206
+ filter: brightness(var(--drawer-nested-brightness));
207
+ pointer-events: none;
208
+ }
209
+
210
+ /* 2+ open drawers after */
211
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open]) {
212
+ scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale));
213
+ translate: 0 calc(-2 * var(--drawer-nested-offset));
214
+ filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
215
+ }
216
+
217
+ /* 3+ open drawers after */
218
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
219
+ scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
220
+ translate: 0 calc(-3 * var(--drawer-nested-offset));
221
+ filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
222
+ }
223
+
224
+ /* 4+ open drawers after */
225
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
226
+ scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
227
+ translate: 0 calc(-4 * var(--drawer-nested-offset));
228
+ filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
229
+ }
230
+
231
+ /* 5+ open drawers after */
232
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
233
+ scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
234
+ translate: 0 calc(-5 * var(--drawer-nested-offset));
235
+ filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
236
+ }
237
+
238
+ /* Lighter backdrop for stacked drawers */
239
+ .drawer[open] ~ .drawer[open]::backdrop {
240
+ background: var(--drawer-nested-backdrop);
241
+ backdrop-filter: none;
242
+ }
243
+
244
+ /* Handle */
245
+ .drawer-handle {
246
+ display: flex;
247
+ justify-content: center;
248
+ padding-block: var(--drawer-handle-padding-block);
249
+ padding-inline: var(--drawer-handle-padding-inline);
250
+ cursor: grab;
251
+ }
252
+
253
+ .drawer-handle::before {
254
+ content: '';
255
+ width: var(--drawer-handle-width);
256
+ height: var(--drawer-handle-height);
257
+ background: var(--drawer-handle-bg);
258
+ border-radius: 100px;
259
+ transition: width 0.2s var(--drawer-ease), height 0.2s var(--drawer-ease), background 0.2s ease;
260
+ }
261
+
262
+ .drawer-handle:hover::before {
263
+ width: var(--drawer-handle-width-hover);
264
+ background: var(--drawer-handle-bg-hover);
265
+ }
266
+
267
+ /* Vertical handle for left/right drawers */
268
+ .drawer[data-direction="left"] .drawer-handle,
269
+ .drawer[data-direction="right"] .drawer-handle {
270
+ flex-direction: column;
271
+ align-items: center;
272
+ justify-content: center;
273
+ padding-block: var(--drawer-handle-padding-inline);
274
+ padding-inline: var(--drawer-handle-padding-block);
275
+ height: 100%;
276
+ position: absolute;
277
+ top: 0;
278
+ writing-mode: vertical-lr;
279
+ }
280
+
281
+ .drawer[data-direction="left"] .drawer-handle { right: 0; }
282
+ .drawer[data-direction="right"] .drawer-handle { left: 0; }
283
+
284
+ .drawer[data-direction="left"] .drawer-handle::before,
285
+ .drawer[data-direction="right"] .drawer-handle::before {
286
+ width: var(--drawer-handle-height);
287
+ height: var(--drawer-handle-width);
288
+ }
289
+
290
+ .drawer[data-direction="left"] .drawer-handle:hover::before,
291
+ .drawer[data-direction="right"] .drawer-handle:hover::before {
292
+ width: var(--drawer-handle-height);
293
+ height: var(--drawer-handle-width-hover);
294
+ }
295
+
296
+ /* Content - structural only, no opinionated padding */
297
+ .drawer-content {
298
+ overflow-x: hidden;
299
+ overflow-y: auto;
300
+ overscroll-behavior: contain;
301
+ flex: 1;
302
+ min-height: 0;
303
+ }
304
+
305
+ /* Content sizing for directions */
306
+ .drawer[data-direction="left"] .drawer-content,
307
+ .drawer[data-direction="right"] .drawer-content {
308
+ height: 100%;
309
+ }
310
+
311
+ /* Reduced motion */
312
+ @media (prefers-reduced-motion: reduce) {
313
+ *, *::before, *::after {
314
+ transition-duration: 0.01ms !important;
315
+ }
316
+
317
+ body:has(.drawer[open]) {
318
+ scale: 1;
319
+ }
320
+
321
+ .drawer[open]:has(~ .drawer[open]),
322
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open]),
323
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),
324
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),
325
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
326
+ scale: 1;
327
+ translate: 0 0;
328
+ filter: none;
329
+ }
330
+ }
331
+
@@ -0,0 +1,333 @@
1
+ /* CSS Drawer - Vaul-quality drawer with auto-nesting and directions */
2
+
3
+ :root {
4
+ /* Visual */
5
+ --drawer-bg: #fff;
6
+ --drawer-radius: 24px;
7
+ --drawer-backdrop: hsl(0 0% 0% / 0.4);
8
+ --drawer-backdrop-blur: 4px;
9
+
10
+ /* Sizing */
11
+ --drawer-max-width: 500px;
12
+ --drawer-max-height: 96dvh;
13
+
14
+ /* Handle */
15
+ --drawer-handle-bg: hsl(0 0% 80%);
16
+ --drawer-handle-bg-hover: hsl(0 0% 60%);
17
+ --drawer-handle-width: 48px;
18
+ --drawer-handle-width-hover: 56px;
19
+ --drawer-handle-height: 5px;
20
+ --drawer-handle-padding-block: 1rem 0.5rem;
21
+ --drawer-handle-padding-inline: 0;
22
+
23
+ /* Shadows */
24
+ --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.12), 0 -4px 20px hsl(0 0% 0% / 0.08);
25
+ --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);
26
+ --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);
27
+ --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);
28
+ --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);
29
+
30
+ /* Animation */
31
+ --drawer-duration: 0.5s;
32
+ --drawer-duration-close: 0.35s;
33
+ --drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);
34
+
35
+ /* Nesting effects */
36
+ --drawer-nested-scale: 0.94;
37
+ --drawer-nested-offset: 20px;
38
+ --drawer-nested-brightness: 0.92;
39
+ --drawer-nested-backdrop: hsl(0 0% 0% / 0.15);
40
+ }
41
+
42
+ @media (prefers-color-scheme: dark) {
43
+ :root {
44
+ --drawer-bg: hsl(0 0% 12%);
45
+ --drawer-handle-bg: hsl(0 0% 35%);
46
+ --drawer-handle-bg-hover: hsl(0 0% 50%);
47
+ --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.4), 0 -4px 20px hsl(0 0% 0% / 0.3);
48
+ --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);
49
+ --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);
50
+ --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);
51
+ --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.5);
52
+ }
53
+ }
54
+
55
+ /* Background scale effect */
56
+ body {
57
+ transition: scale var(--drawer-duration) var(--drawer-ease), border-radius var(--drawer-duration) var(--drawer-ease);
58
+ transform-origin: center top;
59
+ }
60
+
61
+ body:has(.drawer[open]) {
62
+ overflow: hidden;
63
+ scale: var(--drawer-nested-scale);
64
+ border-radius: var(--drawer-radius);
65
+ }
66
+
67
+ /* Base drawer */
68
+ .drawer {
69
+ border: none;
70
+ padding: 0;
71
+ margin: 0;
72
+ max-width: 100%;
73
+ max-height: 100%;
74
+ position: fixed;
75
+ background: var(--drawer-bg);
76
+ overflow: hidden;
77
+ opacity: 0;
78
+ transition:
79
+ display var(--drawer-duration-close) allow-discrete,
80
+ overlay var(--drawer-duration-close) allow-discrete,
81
+ translate var(--drawer-duration-close) var(--drawer-ease),
82
+ scale var(--drawer-duration-close) var(--drawer-ease),
83
+ filter var(--drawer-duration-close) ease,
84
+ opacity var(--drawer-duration-close) ease;
85
+
86
+ /* Default: bottom */
87
+ --_translate-closed: 0 100%;
88
+ --_border-radius: var(--drawer-radius) var(--drawer-radius) 0 0;
89
+ inset: auto 0 0 0;
90
+ margin-inline: auto;
91
+ width: 100%;
92
+ max-width: var(--drawer-max-width);
93
+ height: auto;
94
+ max-height: var(--drawer-max-height);
95
+ border-radius: var(--drawer-border-radius, var(--_border-radius));
96
+ box-shadow: var(--drawer-shadow-bottom);
97
+ translate: var(--_translate-closed);
98
+ }
99
+
100
+ .drawer::backdrop {
101
+ background: var(--drawer-backdrop);
102
+ opacity: 0;
103
+ backdrop-filter: blur(var(--drawer-backdrop-blur));
104
+ -webkit-backdrop-filter: blur(var(--drawer-backdrop-blur));
105
+ transition:
106
+ display var(--drawer-duration-close) allow-discrete,
107
+ overlay var(--drawer-duration-close) allow-discrete,
108
+ opacity var(--drawer-duration-close) ease;
109
+ }
110
+
111
+ .drawer[open] {
112
+ opacity: 1;
113
+ translate: 0 0;
114
+ transition:
115
+ display var(--drawer-duration) allow-discrete,
116
+ overlay var(--drawer-duration) allow-discrete,
117
+ translate var(--drawer-duration) var(--drawer-ease),
118
+ scale var(--drawer-duration) var(--drawer-ease),
119
+ filter var(--drawer-duration) ease,
120
+ opacity 0.15s ease;
121
+ }
122
+
123
+ .drawer[open]::backdrop {
124
+ opacity: 1;
125
+ transition: display var(--drawer-duration) allow-discrete, overlay var(--drawer-duration) allow-discrete, opacity 0.2s ease;
126
+ }
127
+
128
+ @starting-style {
129
+ .drawer[open] { opacity: 0; translate: var(--_translate-closed); }
130
+ .drawer[open]::backdrop { opacity: 0; }
131
+ }
132
+
133
+ /* ===== DIRECTIONS ===== */
134
+
135
+ /* Right */
136
+ .drawer[data-direction="right"] {
137
+ --_translate-closed: 100% 0;
138
+ --_border-radius: var(--drawer-radius) 0 0 var(--drawer-radius);
139
+ inset: 0 0 0 auto;
140
+ margin: 0;
141
+ width: 100%;
142
+ max-width: var(--drawer-max-width);
143
+ height: 100dvh;
144
+ max-height: 100dvh;
145
+ box-shadow: var(--drawer-shadow-right);
146
+ }
147
+
148
+ /* Left */
149
+ .drawer[data-direction="left"] {
150
+ --_translate-closed: -100% 0;
151
+ --_border-radius: 0 var(--drawer-radius) var(--drawer-radius) 0;
152
+ inset: 0 auto 0 0;
153
+ margin: 0;
154
+ width: 100%;
155
+ max-width: var(--drawer-max-width);
156
+ height: 100dvh;
157
+ max-height: 100dvh;
158
+ box-shadow: var(--drawer-shadow-left);
159
+ }
160
+
161
+ /* Top */
162
+ .drawer[data-direction="top"] {
163
+ --_translate-closed: 0 -100%;
164
+ --_border-radius: 0 0 var(--drawer-radius) var(--drawer-radius);
165
+ inset: 0 0 auto 0;
166
+ margin-inline: auto;
167
+ width: 100%;
168
+ max-width: var(--drawer-max-width);
169
+ height: auto;
170
+ max-height: var(--drawer-max-height);
171
+ box-shadow: var(--drawer-shadow-top);
172
+ }
173
+
174
+ /* Modal (centered) */
175
+ .drawer[data-direction="modal"] {
176
+ --_translate-closed: 0 0;
177
+ --_border-radius: var(--drawer-radius);
178
+ inset: 0;
179
+ margin: auto;
180
+ width: fit-content;
181
+ max-width: var(--drawer-max-width);
182
+ height: fit-content;
183
+ max-height: var(--drawer-max-height);
184
+ box-shadow: var(--drawer-shadow-modal);
185
+ scale: var(--drawer-nested-scale);
186
+ }
187
+
188
+ .drawer[data-direction="modal"][open] {
189
+ scale: 1;
190
+ }
191
+
192
+ @starting-style {
193
+ .drawer[data-direction="modal"][open] {
194
+ scale: var(--drawer-nested-scale);
195
+ }
196
+ }
197
+
198
+ /* ===== AUTO-NESTING (up to 5 levels) ===== */
199
+ /* Uses sibling combinators to count open drawers */
200
+
201
+ /* 1+ open drawers after */
202
+ .drawer[open]:has(~ .drawer[open]) {
203
+ scale: var(--drawer-nested-scale);
204
+ translate: 0 calc(-1 * var(--drawer-nested-offset));
205
+ border-radius: var(--drawer-radius);
206
+ filter: brightness(var(--drawer-nested-brightness));
207
+ pointer-events: none;
208
+ }
209
+
210
+ /* 2+ open drawers after */
211
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open]) {
212
+ scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale));
213
+ translate: 0 calc(-2 * var(--drawer-nested-offset));
214
+ filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
215
+ }
216
+
217
+ /* 3+ open drawers after */
218
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
219
+ scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
220
+ translate: 0 calc(-3 * var(--drawer-nested-offset));
221
+ filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
222
+ }
223
+
224
+ /* 4+ open drawers after */
225
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
226
+ scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
227
+ translate: 0 calc(-4 * var(--drawer-nested-offset));
228
+ filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
229
+ }
230
+
231
+ /* 5+ open drawers after */
232
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
233
+ scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));
234
+ translate: 0 calc(-5 * var(--drawer-nested-offset));
235
+ filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));
236
+ }
237
+
238
+ /* Lighter backdrop for stacked drawers */
239
+ .drawer[open] ~ .drawer[open]::backdrop {
240
+ background: var(--drawer-nested-backdrop);
241
+ backdrop-filter: none;
242
+ }
243
+
244
+ /* Handle */
245
+ .drawer-handle {
246
+ display: flex;
247
+ justify-content: center;
248
+ padding-block: var(--drawer-handle-padding-block);
249
+ padding-inline: var(--drawer-handle-padding-inline);
250
+ cursor: grab;
251
+ }
252
+
253
+ .drawer-handle::before {
254
+ content: '';
255
+ width: var(--drawer-handle-width);
256
+ height: var(--drawer-handle-height);
257
+ background: var(--drawer-handle-bg);
258
+ border-radius: 100px;
259
+ transition: width 0.2s var(--drawer-ease), height 0.2s var(--drawer-ease), background 0.2s ease;
260
+ }
261
+
262
+ .drawer-handle:hover::before {
263
+ width: var(--drawer-handle-width-hover);
264
+ background: var(--drawer-handle-bg-hover);
265
+ }
266
+
267
+ /* Vertical handle for left/right drawers */
268
+ .drawer[data-direction="left"] .drawer-handle,
269
+ .drawer[data-direction="right"] .drawer-handle {
270
+ flex-direction: column;
271
+ align-items: center;
272
+ justify-content: center;
273
+ padding-block: var(--drawer-handle-padding-inline);
274
+ padding-inline: var(--drawer-handle-padding-block);
275
+ height: 100%;
276
+ position: absolute;
277
+ top: 0;
278
+ writing-mode: vertical-lr;
279
+ }
280
+
281
+ .drawer[data-direction="left"] .drawer-handle { right: 0; }
282
+ .drawer[data-direction="right"] .drawer-handle { left: 0; }
283
+
284
+ .drawer[data-direction="left"] .drawer-handle::before,
285
+ .drawer[data-direction="right"] .drawer-handle::before {
286
+ width: var(--drawer-handle-height);
287
+ height: var(--drawer-handle-width);
288
+ }
289
+
290
+ .drawer[data-direction="left"] .drawer-handle:hover::before,
291
+ .drawer[data-direction="right"] .drawer-handle:hover::before {
292
+ width: var(--drawer-handle-height);
293
+ height: var(--drawer-handle-width-hover);
294
+ }
295
+
296
+ /* Content - structural only, no opinionated padding */
297
+ .drawer-content {
298
+ overflow-x: hidden;
299
+ overflow-y: auto;
300
+ overscroll-behavior: contain;
301
+ flex: 1;
302
+ min-height: 0;
303
+ }
304
+
305
+ /* Content sizing for directions */
306
+ .drawer[data-direction="left"] .drawer-content,
307
+ .drawer[data-direction="right"] .drawer-content {
308
+ height: 100%;
309
+ }
310
+
311
+ /* Reduced motion */
312
+ @media (prefers-reduced-motion: reduce) {
313
+ *, *::before, *::after {
314
+ transition-duration: 0.01ms !important;
315
+ }
316
+
317
+ body:has(.drawer[open]) {
318
+ scale: 1;
319
+ }
320
+
321
+ .drawer[open]:has(~ .drawer[open]),
322
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open]),
323
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),
324
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),
325
+ .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {
326
+ scale: 1;
327
+ translate: 0 0;
328
+ filter: none;
329
+ }
330
+ }
331
+
332
+
333
+ /*# sourceMappingURL=drawer-CXCJQa45.css.map*/
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawer-CXCJQa45.css","names":[],"sources":["../src/drawer.css"],"sourcesContent":["/* CSS Drawer - Vaul-quality drawer with auto-nesting and directions */\n\n:root {\n /* Visual */\n --drawer-bg: #fff;\n --drawer-radius: 24px;\n --drawer-backdrop: hsl(0 0% 0% / 0.4);\n --drawer-backdrop-blur: 4px;\n\n /* Sizing */\n --drawer-max-width: 500px;\n --drawer-max-height: 96dvh;\n\n /* Handle */\n --drawer-handle-bg: hsl(0 0% 80%);\n --drawer-handle-bg-hover: hsl(0 0% 60%);\n --drawer-handle-width: 48px;\n --drawer-handle-width-hover: 56px;\n --drawer-handle-height: 5px;\n --drawer-handle-padding-block: 1rem 0.5rem;\n --drawer-handle-padding-inline: 0;\n\n /* Shadows */\n --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.12), 0 -4px 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);\n\n /* Animation */\n --drawer-duration: 0.5s;\n --drawer-duration-close: 0.35s;\n --drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);\n\n /* Nesting effects */\n --drawer-nested-scale: 0.94;\n --drawer-nested-offset: 20px;\n --drawer-nested-brightness: 0.92;\n --drawer-nested-backdrop: hsl(0 0% 0% / 0.15);\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --drawer-bg: hsl(0 0% 12%);\n --drawer-handle-bg: hsl(0 0% 35%);\n --drawer-handle-bg-hover: hsl(0 0% 50%);\n --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.4), 0 -4px 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.5);\n }\n}\n\n/* Background scale effect */\nbody {\n transition: scale var(--drawer-duration) var(--drawer-ease), border-radius var(--drawer-duration) var(--drawer-ease);\n transform-origin: center top;\n}\n\nbody:has(.drawer[open]) {\n overflow: hidden;\n scale: var(--drawer-nested-scale);\n border-radius: var(--drawer-radius);\n}\n\n/* Base drawer */\n.drawer {\n border: none;\n padding: 0;\n margin: 0;\n max-width: 100%;\n max-height: 100%;\n position: fixed;\n background: var(--drawer-bg);\n overflow: hidden;\n opacity: 0;\n transition:\n display var(--drawer-duration-close) allow-discrete,\n overlay var(--drawer-duration-close) allow-discrete,\n translate var(--drawer-duration-close) var(--drawer-ease),\n scale var(--drawer-duration-close) var(--drawer-ease),\n filter var(--drawer-duration-close) ease,\n opacity var(--drawer-duration-close) ease;\n\n /* Default: bottom */\n --_translate-closed: 0 100%;\n --_border-radius: var(--drawer-radius) var(--drawer-radius) 0 0;\n inset: auto 0 0 0;\n margin-inline: auto;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: auto;\n max-height: var(--drawer-max-height);\n border-radius: var(--drawer-border-radius, var(--_border-radius));\n box-shadow: var(--drawer-shadow-bottom);\n translate: var(--_translate-closed);\n}\n\n.drawer::backdrop {\n background: var(--drawer-backdrop);\n opacity: 0;\n backdrop-filter: blur(var(--drawer-backdrop-blur));\n -webkit-backdrop-filter: blur(var(--drawer-backdrop-blur));\n transition:\n display var(--drawer-duration-close) allow-discrete,\n overlay var(--drawer-duration-close) allow-discrete,\n opacity var(--drawer-duration-close) ease;\n}\n\n.drawer[open] {\n opacity: 1;\n translate: 0 0;\n transition:\n display var(--drawer-duration) allow-discrete,\n overlay var(--drawer-duration) allow-discrete,\n translate var(--drawer-duration) var(--drawer-ease),\n scale var(--drawer-duration) var(--drawer-ease),\n filter var(--drawer-duration) ease,\n opacity 0.15s ease;\n}\n\n.drawer[open]::backdrop {\n opacity: 1;\n transition: display var(--drawer-duration) allow-discrete, overlay var(--drawer-duration) allow-discrete, opacity 0.2s ease;\n}\n\n@starting-style {\n .drawer[open] { opacity: 0; translate: var(--_translate-closed); }\n .drawer[open]::backdrop { opacity: 0; }\n}\n\n/* ===== DIRECTIONS ===== */\n\n/* Right */\n.drawer[data-direction=\"right\"] {\n --_translate-closed: 100% 0;\n --_border-radius: var(--drawer-radius) 0 0 var(--drawer-radius);\n inset: 0 0 0 auto;\n margin: 0;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: 100dvh;\n max-height: 100dvh;\n box-shadow: var(--drawer-shadow-right);\n}\n\n/* Left */\n.drawer[data-direction=\"left\"] {\n --_translate-closed: -100% 0;\n --_border-radius: 0 var(--drawer-radius) var(--drawer-radius) 0;\n inset: 0 auto 0 0;\n margin: 0;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: 100dvh;\n max-height: 100dvh;\n box-shadow: var(--drawer-shadow-left);\n}\n\n/* Top */\n.drawer[data-direction=\"top\"] {\n --_translate-closed: 0 -100%;\n --_border-radius: 0 0 var(--drawer-radius) var(--drawer-radius);\n inset: 0 0 auto 0;\n margin-inline: auto;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: auto;\n max-height: var(--drawer-max-height);\n box-shadow: var(--drawer-shadow-top);\n}\n\n/* Modal (centered) */\n.drawer[data-direction=\"modal\"] {\n --_translate-closed: 0 0;\n --_border-radius: var(--drawer-radius);\n inset: 0;\n margin: auto;\n width: fit-content;\n max-width: var(--drawer-max-width);\n height: fit-content;\n max-height: var(--drawer-max-height);\n box-shadow: var(--drawer-shadow-modal);\n scale: var(--drawer-nested-scale);\n}\n\n.drawer[data-direction=\"modal\"][open] {\n scale: 1;\n}\n\n@starting-style {\n .drawer[data-direction=\"modal\"][open] {\n scale: var(--drawer-nested-scale);\n }\n}\n\n/* ===== AUTO-NESTING (up to 5 levels) ===== */\n/* Uses sibling combinators to count open drawers */\n\n/* 1+ open drawers after */\n.drawer[open]:has(~ .drawer[open]) {\n scale: var(--drawer-nested-scale);\n translate: 0 calc(-1 * var(--drawer-nested-offset));\n border-radius: var(--drawer-radius);\n filter: brightness(var(--drawer-nested-brightness));\n pointer-events: none;\n}\n\n/* 2+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-2 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 3+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-3 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 4+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-4 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 5+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-5 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* Lighter backdrop for stacked drawers */\n.drawer[open] ~ .drawer[open]::backdrop {\n background: var(--drawer-nested-backdrop);\n backdrop-filter: none;\n}\n\n/* Handle */\n.drawer-handle {\n display: flex;\n justify-content: center;\n padding-block: var(--drawer-handle-padding-block);\n padding-inline: var(--drawer-handle-padding-inline);\n cursor: grab;\n}\n\n.drawer-handle::before {\n content: '';\n width: var(--drawer-handle-width);\n height: var(--drawer-handle-height);\n background: var(--drawer-handle-bg);\n border-radius: 100px;\n transition: width 0.2s var(--drawer-ease), height 0.2s var(--drawer-ease), background 0.2s ease;\n}\n\n.drawer-handle:hover::before {\n width: var(--drawer-handle-width-hover);\n background: var(--drawer-handle-bg-hover);\n}\n\n/* Vertical handle for left/right drawers */\n.drawer[data-direction=\"left\"] .drawer-handle,\n.drawer[data-direction=\"right\"] .drawer-handle {\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding-block: var(--drawer-handle-padding-inline);\n padding-inline: var(--drawer-handle-padding-block);\n height: 100%;\n position: absolute;\n top: 0;\n writing-mode: vertical-lr;\n}\n\n.drawer[data-direction=\"left\"] .drawer-handle { right: 0; }\n.drawer[data-direction=\"right\"] .drawer-handle { left: 0; }\n\n.drawer[data-direction=\"left\"] .drawer-handle::before,\n.drawer[data-direction=\"right\"] .drawer-handle::before {\n width: var(--drawer-handle-height);\n height: var(--drawer-handle-width);\n}\n\n.drawer[data-direction=\"left\"] .drawer-handle:hover::before,\n.drawer[data-direction=\"right\"] .drawer-handle:hover::before {\n width: var(--drawer-handle-height);\n height: var(--drawer-handle-width-hover);\n}\n\n/* Content - structural only, no opinionated padding */\n.drawer-content {\n overflow-x: hidden;\n overflow-y: auto;\n overscroll-behavior: contain;\n flex: 1;\n min-height: 0;\n}\n\n/* Content sizing for directions */\n.drawer[data-direction=\"left\"] .drawer-content,\n.drawer[data-direction=\"right\"] .drawer-content {\n height: 100%;\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n transition-duration: 0.01ms !important;\n }\n\n body:has(.drawer[open]) {\n scale: 1;\n }\n\n .drawer[open]:has(~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: 1;\n translate: 0 0;\n filter: none;\n }\n}\n"],"mappings":"AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
@@ -0,0 +1 @@
1
+ export{};
package/dist/index.cjs CHANGED
@@ -1,4 +1,5 @@
1
- if(typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}function e(e){return e?typeof e==`string`?document.getElementById(e):e:null}function t(t){e(t)?.showModal()}function n(t){e(t)?.close()}function r(){Array.from(document.querySelectorAll(`dialog.drawer[open]`)).reverse().forEach(e=>e.close())}function i(t){return e(t)?.open??!1}function a(){return Array.from(document.querySelectorAll(`dialog.drawer[open]`))}function o(){let e=a();return e[e.length-1]??null}function s(e={}){let{id:t,content:n=``,handle:r=!0,className:i=``,closeOnOutsideClick:a=!0}=e,o=document.createElement(`dialog`);return o.className=`drawer ${i}`.trim(),t&&(o.id=t),a||(o.dataset.closeOnOutsideClick=`false`),o.innerHTML=`
2
- ${r?`<div class="drawer-handle"></div>`:``}
1
+ require('./drawer-C21nwiwE.css');
2
+ if(require(`./drawer-BWZh2Fyp.cjs`),typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}function e(e){return e?typeof e==`string`?document.getElementById(e):e:null}function t(t){e(t)?.showModal()}function n(t){e(t)?.close()}function r(){Array.from(document.querySelectorAll(`dialog.drawer[open]`)).reverse().forEach(e=>e.close())}function i(t){return e(t)?.open??!1}function a(){return Array.from(document.querySelectorAll(`dialog.drawer[open]`))}function o(){let e=a();return e[e.length-1]??null}function s(e={}){let{id:t,content:n=``,direction:r,handle:i=!0,className:a=``,closeOnOutsideClick:o=!0}=e,s=document.createElement(`dialog`);return s.className=`drawer ${a}`.trim(),t&&(s.id=t),r&&(s.dataset.direction=r),o||(s.dataset.closeOnOutsideClick=`false`),s.innerHTML=`
3
+ ${i?`<div class="drawer-handle"></div>`:``}
3
4
  <div class="drawer-content">${n}</div>
4
- `,o.addEventListener(`click`,e=>{e.target===o&&o.dataset.closeOnOutsideClick!==`false`&&o.close()}),o}function c(e){return document.body.appendChild(e),e}function l(t){e(t)?.remove()}function u(){let e=e=>{let t=e.target;t.matches(`dialog.drawer`)&&t.dataset.closeOnOutsideClick!==`false`&&t.close()};return document.addEventListener(`click`,e),()=>document.removeEventListener(`click`,e)}function d(t,n){let r=e(t);if(!r)return()=>{};let{onOpen:i,onClose:a,onCancel:o}=n,s=()=>a?.(),c=()=>o?.(),l=new MutationObserver(e=>{for(let t of e)t.attributeName===`open`&&r.open&&i?.()});return r.addEventListener(`close`,s),r.addEventListener(`cancel`,c),l.observe(r,{attributes:!0}),()=>{r.removeEventListener(`close`,s),r.removeEventListener(`cancel`,c),l.disconnect()}}function f(e,t){let n=t?.closeOnOutsideClick??!0;return{id:e,className:`drawer`,"data-close-on-outside-click":n?void 0:`false`,onClick:e=>{n&&e.target===e.currentTarget&&e.currentTarget.close()}}}exports.close=n,exports.closeAll=r,exports.create=s,exports.getOpen=a,exports.getTop=o,exports.init=u,exports.isOpen=i,exports.mount=c,exports.open=t,exports.props=f,exports.subscribe=d,exports.unmount=l;
5
+ `,s.addEventListener(`click`,e=>{e.target===s&&s.dataset.closeOnOutsideClick!==`false`&&s.close()}),s}function c(e){return document.body.appendChild(e),e}function l(t){e(t)?.remove()}function u(){let e=e=>{let t=e.target;t.matches(`dialog.drawer`)&&t.dataset.closeOnOutsideClick!==`false`&&t.close()};return document.addEventListener(`click`,e),()=>document.removeEventListener(`click`,e)}function d(t,n){let r=e(t);if(!r)return()=>{};let{onOpen:i,onClose:a,onCancel:o}=n,s=()=>a?.(),c=()=>o?.(),l=new MutationObserver(e=>{for(let t of e)t.attributeName===`open`&&r.open&&i?.()});return r.addEventListener(`close`,s),r.addEventListener(`cancel`,c),l.observe(r,{attributes:!0}),()=>{r.removeEventListener(`close`,s),r.removeEventListener(`cancel`,c),l.disconnect()}}function f(e,t){let n=t?.closeOnOutsideClick??!0;return{id:e,className:`drawer`,"data-close-on-outside-click":n?void 0:`false`,onClick:e=>{n&&e.target===e.currentTarget&&e.currentTarget.close()}}}exports.close=n,exports.closeAll=r,exports.create=s,exports.getOpen=a,exports.getTop=o,exports.init=u,exports.isOpen=i,exports.mount=c,exports.open=t,exports.props=f,exports.subscribe=d,exports.unmount=l;
package/dist/index.d.cts CHANGED
@@ -1,15 +1,15 @@
1
1
  //#region src/index.d.ts
2
- /**
3
- * CSS Drawer - Headless drawer component
4
- * Works with any framework: React, Vue, Svelte, vanilla JS
5
- */
2
+
6
3
  type DrawerElement = HTMLDialogElement;
7
4
  type DrawerRef = string | DrawerElement | null | undefined;
5
+ type DrawerDirection = 'bottom' | 'top' | 'left' | 'right' | 'modal';
8
6
  interface CreateDrawerOptions {
9
7
  /** Drawer ID */
10
8
  id?: string;
11
9
  /** HTML content for the drawer */
12
10
  content?: string;
11
+ /** Direction the drawer opens from (default: 'bottom') */
12
+ direction?: DrawerDirection;
13
13
  /** Include drag handle (default: true) */
14
14
  handle?: boolean;
15
15
  /** Additional CSS classes */
@@ -85,5 +85,5 @@ declare function props(id: string, options?: {
85
85
  readonly onClick: (e: MouseEvent) => void;
86
86
  };
87
87
  //#endregion
88
- export { CreateDrawerOptions, DrawerElement, DrawerEventHandlers, DrawerRef, close, closeAll, create, getOpen, getTop, init, isOpen, mount, open, props, subscribe, unmount };
88
+ export { CreateDrawerOptions, DrawerDirection, DrawerElement, DrawerEventHandlers, DrawerRef, close, closeAll, create, getOpen, getTop, init, isOpen, mount, open, props, subscribe, unmount };
89
89
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AAwCA;AAEA;AAEA;AAaiB,KAjBL,aAAA,GAAgB,iBAiBQ;AAuBpB,KAtCJ,SAAA,GAsCiB,MAAA,GAtCI,aAsCK,GAAA,IAAA,GAAA,SAAA;AAQtB,UA5CC,mBAAA,CA4CsB;EAQvB;EAQA,EAAA,CAAA,EAAA,MAAM;EAQN;EAOA,OAAA,CAAA,EAAM,MAAA;EAQN;EA4BA,MAAA,CAAA,EAAK,OAAA;EAQL;EAUA,SAAI,CAAA,EAAA,MAAA;EAgBJ;EAoCA,mBAAK,CAMJ,EAAA,OAAA;;UA9KA,mBAAA;;;;;;;;;;;iBAuBD,IAAA,SAAa;;;;iBAQb,KAAA,SAAc;;;;iBAQd,QAAA,CAAA;;;;iBAQA,MAAA,SAAe;;;;iBAQf,OAAA,CAAA,GAAW;;;;iBAOX,MAAA,CAAA,GAAU;;;;iBAQV,MAAA,WAAgB,sBAA2B;;;;iBA4B3C,KAAA,SAAc,gBAAgB;;;;iBAQ9B,OAAA,SAAgB;;;;;;iBAUhB,IAAA,CAAA;;;;;iBAgBA,SAAA,SACN,qBACE;;;;;iBAkCI,KAAA;;;;;;wBAMC"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AA8DiB,KArBL,aAAA,GAAgB,iBAqBQ;AAuBpB,KA1CJ,SAAA,GA0CiB,MAAA,GA1CI,aA0CK,GAAA,IAAA,GAAA,SAAA;AAQtB,KAhDJ,eAAA,GAgDkB,QAAS,GAAA,KAAA,GAAA,MAAA,GAAA,OAAA,GAAA,OAAA;AAQvB,UAtDC,mBAAA,CAsDO;EAQR;EAQA,EAAA,CAAA,EAAA,MAAO;EAOP;EAQA,OAAA,CAAA,EAAM,MAAA;EA6BN;EAQA,SAAA,CAAO,EApHT,eAoHkB;EAUhB;EAgBA,MAAA,CAAA,EAAA,OAAS;EAoCT;;;;;UAzKC,mBAAA;;;;;;;;;;;iBAuBD,IAAA,SAAa;;;;iBAQb,KAAA,SAAc;;;;iBAQd,QAAA,CAAA;;;;iBAQA,MAAA,SAAe;;;;iBAQf,OAAA,CAAA,GAAW;;;;iBAOX,MAAA,CAAA,GAAU;;;;iBAQV,MAAA,WAAgB,sBAA2B;;;;iBA6B3C,KAAA,SAAc,gBAAgB;;;;iBAQ9B,OAAA,SAAgB;;;;;;iBAUhB,IAAA,CAAA;;;;;iBAgBA,SAAA,SACN,qBACE;;;;;iBAkCI,KAAA;;;;;;wBAMC"}
package/dist/index.d.mts CHANGED
@@ -1,15 +1,15 @@
1
1
  //#region src/index.d.ts
2
- /**
3
- * CSS Drawer - Headless drawer component
4
- * Works with any framework: React, Vue, Svelte, vanilla JS
5
- */
2
+
6
3
  type DrawerElement = HTMLDialogElement;
7
4
  type DrawerRef = string | DrawerElement | null | undefined;
5
+ type DrawerDirection = 'bottom' | 'top' | 'left' | 'right' | 'modal';
8
6
  interface CreateDrawerOptions {
9
7
  /** Drawer ID */
10
8
  id?: string;
11
9
  /** HTML content for the drawer */
12
10
  content?: string;
11
+ /** Direction the drawer opens from (default: 'bottom') */
12
+ direction?: DrawerDirection;
13
13
  /** Include drag handle (default: true) */
14
14
  handle?: boolean;
15
15
  /** Additional CSS classes */
@@ -85,5 +85,5 @@ declare function props(id: string, options?: {
85
85
  readonly onClick: (e: MouseEvent) => void;
86
86
  };
87
87
  //#endregion
88
- export { CreateDrawerOptions, DrawerElement, DrawerEventHandlers, DrawerRef, close, closeAll, create, getOpen, getTop, init, isOpen, mount, open, props, subscribe, unmount };
88
+ export { CreateDrawerOptions, DrawerDirection, DrawerElement, DrawerEventHandlers, DrawerRef, close, closeAll, create, getOpen, getTop, init, isOpen, mount, open, props, subscribe, unmount };
89
89
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AAwCA;AAEA;AAEA;AAaiB,KAjBL,aAAA,GAAgB,iBAiBQ;AAuBpB,KAtCJ,SAAA,GAsCiB,MAAA,GAtCI,aAsCK,GAAA,IAAA,GAAA,SAAA;AAQtB,UA5CC,mBAAA,CA4CsB;EAQvB;EAQA,EAAA,CAAA,EAAA,MAAM;EAQN;EAOA,OAAA,CAAA,EAAM,MAAA;EAQN;EA4BA,MAAA,CAAA,EAAK,OAAA;EAQL;EAUA,SAAI,CAAA,EAAA,MAAA;EAgBJ;EAoCA,mBAAK,CAMJ,EAAA,OAAA;;UA9KA,mBAAA;;;;;;;;;;;iBAuBD,IAAA,SAAa;;;;iBAQb,KAAA,SAAc;;;;iBAQd,QAAA,CAAA;;;;iBAQA,MAAA,SAAe;;;;iBAQf,OAAA,CAAA,GAAW;;;;iBAOX,MAAA,CAAA,GAAU;;;;iBAQV,MAAA,WAAgB,sBAA2B;;;;iBA4B3C,KAAA,SAAc,gBAAgB;;;;iBAQ9B,OAAA,SAAgB;;;;;;iBAUhB,IAAA,CAAA;;;;;iBAgBA,SAAA,SACN,qBACE;;;;;iBAkCI,KAAA;;;;;;wBAMC"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;AA8DiB,KArBL,aAAA,GAAgB,iBAqBQ;AAuBpB,KA1CJ,SAAA,GA0CiB,MAAA,GA1CI,aA0CK,GAAA,IAAA,GAAA,SAAA;AAQtB,KAhDJ,eAAA,GAgDkB,QAAS,GAAA,KAAA,GAAA,MAAA,GAAA,OAAA,GAAA,OAAA;AAQvB,UAtDC,mBAAA,CAsDO;EAQR;EAQA,EAAA,CAAA,EAAA,MAAO;EAOP;EAQA,OAAA,CAAA,EAAM,MAAA;EA6BN;EAQA,SAAA,CAAO,EApHT,eAoHkB;EAUhB;EAgBA,MAAA,CAAA,EAAA,OAAS;EAoCT;;;;;UAzKC,mBAAA;;;;;;;;;;;iBAuBD,IAAA,SAAa;;;;iBAQb,KAAA,SAAc;;;;iBAQd,QAAA,CAAA;;;;iBAQA,MAAA,SAAe;;;;iBAQf,OAAA,CAAA,GAAW;;;;iBAOX,MAAA,CAAA,GAAU;;;;iBAQV,MAAA,WAAgB,sBAA2B;;;;iBA6B3C,KAAA,SAAc,gBAAgB;;;;iBAQ9B,OAAA,SAAgB;;;;;;iBAUhB,IAAA,CAAA;;;;;iBAgBA,SAAA,SACN,qBACE;;;;;iBAkCI,KAAA;;;;;;wBAMC"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
- if(typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}function e(e){return e?typeof e==`string`?document.getElementById(e):e:null}function t(t){e(t)?.showModal()}function n(t){e(t)?.close()}function r(){Array.from(document.querySelectorAll(`dialog.drawer[open]`)).reverse().forEach(e=>e.close())}function i(t){return e(t)?.open??!1}function a(){return Array.from(document.querySelectorAll(`dialog.drawer[open]`))}function o(){let e=a();return e[e.length-1]??null}function s(e={}){let{id:t,content:n=``,handle:r=!0,className:i=``,closeOnOutsideClick:a=!0}=e,o=document.createElement(`dialog`);return o.className=`drawer ${i}`.trim(),t&&(o.id=t),a||(o.dataset.closeOnOutsideClick=`false`),o.innerHTML=`
2
- ${r?`<div class="drawer-handle"></div>`:``}
1
+ import"./drawer-CiHZcyXE.mjs";import './drawer-CXCJQa45.css';
2
+ if(typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}function e(e){return e?typeof e==`string`?document.getElementById(e):e:null}function t(t){e(t)?.showModal()}function n(t){e(t)?.close()}function r(){Array.from(document.querySelectorAll(`dialog.drawer[open]`)).reverse().forEach(e=>e.close())}function i(t){return e(t)?.open??!1}function a(){return Array.from(document.querySelectorAll(`dialog.drawer[open]`))}function o(){let e=a();return e[e.length-1]??null}function s(e={}){let{id:t,content:n=``,direction:r,handle:i=!0,className:a=``,closeOnOutsideClick:o=!0}=e,s=document.createElement(`dialog`);return s.className=`drawer ${a}`.trim(),t&&(s.id=t),r&&(s.dataset.direction=r),o||(s.dataset.closeOnOutsideClick=`false`),s.innerHTML=`
3
+ ${i?`<div class="drawer-handle"></div>`:``}
3
4
  <div class="drawer-content">${n}</div>
4
- `,o.addEventListener(`click`,e=>{e.target===o&&o.dataset.closeOnOutsideClick!==`false`&&o.close()}),o}function c(e){return document.body.appendChild(e),e}function l(t){e(t)?.remove()}function u(){let e=e=>{let t=e.target;t.matches(`dialog.drawer`)&&t.dataset.closeOnOutsideClick!==`false`&&t.close()};return document.addEventListener(`click`,e),()=>document.removeEventListener(`click`,e)}function d(t,n){let r=e(t);if(!r)return()=>{};let{onOpen:i,onClose:a,onCancel:o}=n,s=()=>a?.(),c=()=>o?.(),l=new MutationObserver(e=>{for(let t of e)t.attributeName===`open`&&r.open&&i?.()});return r.addEventListener(`close`,s),r.addEventListener(`cancel`,c),l.observe(r,{attributes:!0}),()=>{r.removeEventListener(`close`,s),r.removeEventListener(`cancel`,c),l.disconnect()}}function f(e,t){let n=t?.closeOnOutsideClick??!0;return{id:e,className:`drawer`,"data-close-on-outside-click":n?void 0:`false`,onClick:e=>{n&&e.target===e.currentTarget&&e.currentTarget.close()}}}export{n as close,r as closeAll,s as create,a as getOpen,o as getTop,u as init,i as isOpen,c as mount,t as open,f as props,d as subscribe,l as unmount};
5
+ `,s.addEventListener(`click`,e=>{e.target===s&&s.dataset.closeOnOutsideClick!==`false`&&s.close()}),s}function c(e){return document.body.appendChild(e),e}function l(t){e(t)?.remove()}function u(){let e=e=>{let t=e.target;t.matches(`dialog.drawer`)&&t.dataset.closeOnOutsideClick!==`false`&&t.close()};return document.addEventListener(`click`,e),()=>document.removeEventListener(`click`,e)}function d(t,n){let r=e(t);if(!r)return()=>{};let{onOpen:i,onClose:a,onCancel:o}=n,s=()=>a?.(),c=()=>o?.(),l=new MutationObserver(e=>{for(let t of e)t.attributeName===`open`&&r.open&&i?.()});return r.addEventListener(`close`,s),r.addEventListener(`cancel`,c),l.observe(r,{attributes:!0}),()=>{r.removeEventListener(`close`,s),r.removeEventListener(`cancel`,c),l.disconnect()}}function f(e,t){let n=t?.closeOnOutsideClick??!0;return{id:e,className:`drawer`,"data-close-on-outside-click":n?void 0:`false`,onClick:e=>{n&&e.target===e.currentTarget&&e.currentTarget.close()}}}export{n as close,r as closeAll,s as create,a as getOpen,o as getTop,u as init,i as isOpen,c as mount,t as open,f as props,d as subscribe,l as unmount};
5
6
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["open"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * CSS Drawer - Headless drawer component\n * Works with any framework: React, Vue, Svelte, vanilla JS\n */\n\n/* ===== Auto-enable accessibility for stacked drawers ===== */\nif (typeof window !== 'undefined') {\n const updateInertState = () => {\n const openDrawers = Array.from(\n document.querySelectorAll<HTMLDialogElement>('dialog.drawer[open]')\n )\n openDrawers.forEach((drawer, index) => {\n if (index === openDrawers.length - 1) {\n drawer.removeAttribute('inert')\n } else {\n drawer.setAttribute('inert', '')\n }\n })\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (\n mutation.type === 'attributes' &&\n mutation.attributeName === 'open' &&\n (mutation.target as HTMLElement).classList.contains('drawer')\n ) {\n updateInertState()\n break\n }\n }\n })\n\n observer.observe(document.body, {\n subtree: true,\n attributes: true,\n attributeFilter: ['open'],\n })\n}\n\nexport type DrawerElement = HTMLDialogElement\n\nexport type DrawerRef = string | DrawerElement | null | undefined\n\nexport interface CreateDrawerOptions {\n /** Drawer ID */\n id?: string\n /** HTML content for the drawer */\n content?: string\n /** Include drag handle (default: true) */\n handle?: boolean\n /** Additional CSS classes */\n className?: string\n /** Close when clicking outside (default: true) */\n closeOnOutsideClick?: boolean\n}\n\nexport interface DrawerEventHandlers {\n /** Called when drawer opens */\n onOpen?: () => void\n /** Called when drawer closes */\n onClose?: () => void\n /** Called when drawer is cancelled (backdrop click or Escape) */\n onCancel?: () => void\n}\n\n/**\n * Resolve a drawer reference to an element\n */\nfunction resolve(drawer: DrawerRef): DrawerElement | null {\n if (!drawer) return null\n if (typeof drawer === 'string') {\n return document.getElementById(drawer) as DrawerElement | null\n }\n return drawer\n}\n\n/**\n * Open a drawer by ID or element reference\n */\nexport function open(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.showModal()\n}\n\n/**\n * Close a drawer by ID or element reference\n */\nexport function close(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.close()\n}\n\n/**\n * Close all open drawers (in reverse DOM order for proper animation)\n */\nexport function closeAll(): void {\n const drawers = Array.from(document.querySelectorAll<DrawerElement>('dialog.drawer[open]'))\n drawers.reverse().forEach((d) => d.close())\n}\n\n/**\n * Check if a drawer is open\n */\nexport function isOpen(drawer: DrawerRef): boolean {\n const el = resolve(drawer)\n return el?.open ?? false\n}\n\n/**\n * Get all open drawers\n */\nexport function getOpen(): DrawerElement[] {\n return Array.from(document.querySelectorAll<DrawerElement>('dialog.drawer[open]'))\n}\n\n/**\n * Get the topmost open drawer\n */\nexport function getTop(): DrawerElement | null {\n const open = getOpen()\n return open[open.length - 1] ?? null\n}\n\n/**\n * Create a drawer element programmatically\n */\nexport function create(options: CreateDrawerOptions = {}): DrawerElement {\n const { id, content = '', handle = true, className = '', closeOnOutsideClick = true } = options\n\n const dialog = document.createElement('dialog') as DrawerElement\n dialog.className = `drawer ${className}`.trim()\n if (id) dialog.id = id\n if (!closeOnOutsideClick) {\n dialog.dataset.closeOnOutsideClick = 'false'\n }\n\n dialog.innerHTML = `\n ${handle ? '<div class=\"drawer-handle\"></div>' : ''}\n <div class=\"drawer-content\">${content}</div>\n `\n\n // Backdrop click to close (respects data attribute)\n dialog.addEventListener('click', (e) => {\n if (e.target === dialog && dialog.dataset.closeOnOutsideClick !== 'false') {\n dialog.close()\n }\n })\n\n return dialog\n}\n\n/**\n * Mount a drawer to the DOM (appends to body)\n */\nexport function mount(drawer: DrawerElement): DrawerElement {\n document.body.appendChild(drawer)\n return drawer\n}\n\n/**\n * Unmount a drawer from the DOM\n */\nexport function unmount(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.remove()\n}\n\n/**\n * Initialize global backdrop-click-to-close behavior\n * Alternative to adding onclick to each drawer\n * Respects data-close-on-outside-click=\"false\" attribute\n */\nexport function init(): () => void {\n const handler = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (target.matches('dialog.drawer') && (target as DrawerElement).dataset.closeOnOutsideClick !== 'false') {\n ;(target as DrawerElement).close()\n }\n }\n\n document.addEventListener('click', handler)\n return () => document.removeEventListener('click', handler)\n}\n\n/**\n * Subscribe to drawer events\n * @returns Cleanup function\n */\nexport function subscribe(\n drawer: DrawerRef,\n handlers: DrawerEventHandlers\n): () => void {\n const el = resolve(drawer)\n if (!el) return () => {}\n\n const { onOpen, onClose, onCancel } = handlers\n\n const handleClose = () => onClose?.()\n const handleCancel = () => onCancel?.()\n\n // Use MutationObserver to detect open attribute\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.attributeName === 'open' && el.open) {\n onOpen?.()\n }\n }\n })\n\n el.addEventListener('close', handleClose)\n el.addEventListener('cancel', handleCancel)\n observer.observe(el, { attributes: true })\n\n return () => {\n el.removeEventListener('close', handleClose)\n el.removeEventListener('cancel', handleCancel)\n observer.disconnect()\n }\n}\n\n/**\n * React-friendly hook helper - returns props to spread on dialog\n * Usage: <dialog {...drawer.props('my-drawer')} />\n */\nexport function props(id: string, options?: { closeOnOutsideClick?: boolean }) {\n const closeOnOutsideClick = options?.closeOnOutsideClick ?? true\n return {\n id,\n className: 'drawer',\n 'data-close-on-outside-click': closeOnOutsideClick ? undefined : 'false',\n onClick: (e: MouseEvent) => {\n if (closeOnOutsideClick && e.target === e.currentTarget) {\n ;(e.currentTarget as DrawerElement).close()\n }\n },\n } as const\n}\n\n"],"mappings":"AAMA,GAAI,OAAO,OAAW,IAAa,CACjC,IAAM,MAAyB,CAC7B,IAAM,EAAc,MAAM,KACxB,SAAS,iBAAoC,sBAAsB,CACpE,CACD,EAAY,SAAS,EAAQ,IAAU,CACjC,IAAU,EAAY,OAAS,EACjC,EAAO,gBAAgB,QAAQ,CAE/B,EAAO,aAAa,QAAS,GAAG,EAElC,EAGa,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACrB,GACE,EAAS,OAAS,cAClB,EAAS,gBAAkB,QAC1B,EAAS,OAAuB,UAAU,SAAS,SAAS,CAC7D,CACA,GAAkB,CAClB,QAGJ,CAEO,QAAQ,SAAS,KAAM,CAC9B,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,OAAO,CAC1B,CAAC,CAgCJ,SAAS,EAAQ,EAAyC,CAKxD,OAJK,EACD,OAAO,GAAW,SACb,SAAS,eAAe,EAAO,CAEjC,EAJa,KAUtB,SAAgB,EAAK,EAAyB,CACjC,EAAQ,EAAO,EACtB,WAAW,CAMjB,SAAgB,EAAM,EAAyB,CAClC,EAAQ,EAAO,EACtB,OAAO,CAMb,SAAgB,GAAiB,CACf,MAAM,KAAK,SAAS,iBAAgC,sBAAsB,CAAC,CACnF,SAAS,CAAC,QAAS,GAAM,EAAE,OAAO,CAAC,CAM7C,SAAgB,EAAO,EAA4B,CAEjD,OADW,EAAQ,EAAO,EACf,MAAQ,GAMrB,SAAgB,GAA2B,CACzC,OAAO,MAAM,KAAK,SAAS,iBAAgC,sBAAsB,CAAC,CAMpF,SAAgB,GAA+B,CAC7C,IAAMA,EAAO,GAAS,CACtB,OAAOA,EAAKA,EAAK,OAAS,IAAM,KAMlC,SAAgB,EAAO,EAA+B,EAAE,CAAiB,CACvE,GAAM,CAAE,KAAI,UAAU,GAAI,SAAS,GAAM,YAAY,GAAI,sBAAsB,IAAS,EAElF,EAAS,SAAS,cAAc,SAAS,CAmB/C,MAlBA,GAAO,UAAY,UAAU,IAAY,MAAM,CAC3C,IAAI,EAAO,GAAK,GACf,IACH,EAAO,QAAQ,oBAAsB,SAGvC,EAAO,UAAY;MACf,EAAS,oCAAsC,GAAG;kCACtB,EAAQ;IAIxC,EAAO,iBAAiB,QAAU,GAAM,CAClC,EAAE,SAAW,GAAU,EAAO,QAAQ,sBAAwB,SAChE,EAAO,OAAO,EAEhB,CAEK,EAMT,SAAgB,EAAM,EAAsC,CAE1D,OADA,SAAS,KAAK,YAAY,EAAO,CAC1B,EAMT,SAAgB,EAAQ,EAAyB,CACpC,EAAQ,EAAO,EACtB,QAAQ,CAQd,SAAgB,GAAmB,CACjC,IAAM,EAAW,GAAkB,CACjC,IAAM,EAAS,EAAE,OACb,EAAO,QAAQ,gBAAgB,EAAK,EAAyB,QAAQ,sBAAwB,SAC7F,EAAyB,OAAO,EAKtC,OADA,SAAS,iBAAiB,QAAS,EAAQ,KAC9B,SAAS,oBAAoB,QAAS,EAAQ,CAO7D,SAAgB,EACd,EACA,EACY,CACZ,IAAM,EAAK,EAAQ,EAAO,CAC1B,GAAI,CAAC,EAAI,UAAa,GAEtB,GAAM,CAAE,SAAQ,UAAS,YAAa,EAEhC,MAAoB,KAAW,CAC/B,MAAqB,KAAY,CAGjC,EAAW,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACjB,EAAS,gBAAkB,QAAU,EAAG,MAC1C,KAAU,EAGd,CAMF,OAJA,EAAG,iBAAiB,QAAS,EAAY,CACzC,EAAG,iBAAiB,SAAU,EAAa,CAC3C,EAAS,QAAQ,EAAI,CAAE,WAAY,GAAM,CAAC,KAE7B,CACX,EAAG,oBAAoB,QAAS,EAAY,CAC5C,EAAG,oBAAoB,SAAU,EAAa,CAC9C,EAAS,YAAY,EAQzB,SAAgB,EAAM,EAAY,EAA6C,CAC7E,IAAM,EAAsB,GAAS,qBAAuB,GAC5D,MAAO,CACL,KACA,UAAW,SACX,8BAA+B,EAAsB,IAAA,GAAY,QACjE,QAAU,GAAkB,CACtB,GAAuB,EAAE,SAAW,EAAE,eACtC,EAAE,cAAgC,OAAO,EAGhD"}
1
+ {"version":3,"file":"index.mjs","names":["open"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * CSS Drawer - Headless drawer component\n * Works with any framework: React, Vue, Svelte, vanilla JS\n */\nimport './drawer.css'\n\n/* ===== Auto-enable accessibility for stacked drawers ===== */\nif (typeof window !== 'undefined') {\n const updateInertState = () => {\n const openDrawers = Array.from(\n document.querySelectorAll<HTMLDialogElement>('dialog.drawer[open]')\n )\n openDrawers.forEach((drawer, index) => {\n if (index === openDrawers.length - 1) {\n drawer.removeAttribute('inert')\n } else {\n drawer.setAttribute('inert', '')\n }\n })\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (\n mutation.type === 'attributes' &&\n mutation.attributeName === 'open' &&\n (mutation.target as HTMLElement).classList.contains('drawer')\n ) {\n updateInertState()\n break\n }\n }\n })\n\n observer.observe(document.body, {\n subtree: true,\n attributes: true,\n attributeFilter: ['open'],\n })\n}\n\nexport type DrawerElement = HTMLDialogElement\n\nexport type DrawerRef = string | DrawerElement | null | undefined\n\nexport type DrawerDirection = 'bottom' | 'top' | 'left' | 'right' | 'modal'\n\nexport interface CreateDrawerOptions {\n /** Drawer ID */\n id?: string\n /** HTML content for the drawer */\n content?: string\n /** Direction the drawer opens from (default: 'bottom') */\n direction?: DrawerDirection\n /** Include drag handle (default: true) */\n handle?: boolean\n /** Additional CSS classes */\n className?: string\n /** Close when clicking outside (default: true) */\n closeOnOutsideClick?: boolean\n}\n\nexport interface DrawerEventHandlers {\n /** Called when drawer opens */\n onOpen?: () => void\n /** Called when drawer closes */\n onClose?: () => void\n /** Called when drawer is cancelled (backdrop click or Escape) */\n onCancel?: () => void\n}\n\n/**\n * Resolve a drawer reference to an element\n */\nfunction resolve(drawer: DrawerRef): DrawerElement | null {\n if (!drawer) return null\n if (typeof drawer === 'string') {\n return document.getElementById(drawer) as DrawerElement | null\n }\n return drawer\n}\n\n/**\n * Open a drawer by ID or element reference\n */\nexport function open(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.showModal()\n}\n\n/**\n * Close a drawer by ID or element reference\n */\nexport function close(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.close()\n}\n\n/**\n * Close all open drawers (in reverse DOM order for proper animation)\n */\nexport function closeAll(): void {\n const drawers = Array.from(document.querySelectorAll<DrawerElement>('dialog.drawer[open]'))\n drawers.reverse().forEach((d) => d.close())\n}\n\n/**\n * Check if a drawer is open\n */\nexport function isOpen(drawer: DrawerRef): boolean {\n const el = resolve(drawer)\n return el?.open ?? false\n}\n\n/**\n * Get all open drawers\n */\nexport function getOpen(): DrawerElement[] {\n return Array.from(document.querySelectorAll<DrawerElement>('dialog.drawer[open]'))\n}\n\n/**\n * Get the topmost open drawer\n */\nexport function getTop(): DrawerElement | null {\n const open = getOpen()\n return open[open.length - 1] ?? null\n}\n\n/**\n * Create a drawer element programmatically\n */\nexport function create(options: CreateDrawerOptions = {}): DrawerElement {\n const { id, content = '', direction, handle = true, className = '', closeOnOutsideClick = true } = options\n\n const dialog = document.createElement('dialog') as DrawerElement\n dialog.className = `drawer ${className}`.trim()\n if (id) dialog.id = id\n if (direction) dialog.dataset.direction = direction\n if (!closeOnOutsideClick) {\n dialog.dataset.closeOnOutsideClick = 'false'\n }\n\n dialog.innerHTML = `\n ${handle ? '<div class=\"drawer-handle\"></div>' : ''}\n <div class=\"drawer-content\">${content}</div>\n `\n\n // Backdrop click to close (respects data attribute)\n dialog.addEventListener('click', (e) => {\n if (e.target === dialog && dialog.dataset.closeOnOutsideClick !== 'false') {\n dialog.close()\n }\n })\n\n return dialog\n}\n\n/**\n * Mount a drawer to the DOM (appends to body)\n */\nexport function mount(drawer: DrawerElement): DrawerElement {\n document.body.appendChild(drawer)\n return drawer\n}\n\n/**\n * Unmount a drawer from the DOM\n */\nexport function unmount(drawer: DrawerRef): void {\n const el = resolve(drawer)\n el?.remove()\n}\n\n/**\n * Initialize global backdrop-click-to-close behavior\n * Alternative to adding onclick to each drawer\n * Respects data-close-on-outside-click=\"false\" attribute\n */\nexport function init(): () => void {\n const handler = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (target.matches('dialog.drawer') && (target as DrawerElement).dataset.closeOnOutsideClick !== 'false') {\n ;(target as DrawerElement).close()\n }\n }\n\n document.addEventListener('click', handler)\n return () => document.removeEventListener('click', handler)\n}\n\n/**\n * Subscribe to drawer events\n * @returns Cleanup function\n */\nexport function subscribe(\n drawer: DrawerRef,\n handlers: DrawerEventHandlers\n): () => void {\n const el = resolve(drawer)\n if (!el) return () => {}\n\n const { onOpen, onClose, onCancel } = handlers\n\n const handleClose = () => onClose?.()\n const handleCancel = () => onCancel?.()\n\n // Use MutationObserver to detect open attribute\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.attributeName === 'open' && el.open) {\n onOpen?.()\n }\n }\n })\n\n el.addEventListener('close', handleClose)\n el.addEventListener('cancel', handleCancel)\n observer.observe(el, { attributes: true })\n\n return () => {\n el.removeEventListener('close', handleClose)\n el.removeEventListener('cancel', handleCancel)\n observer.disconnect()\n }\n}\n\n/**\n * React-friendly hook helper - returns props to spread on dialog\n * Usage: <dialog {...drawer.props('my-drawer')} />\n */\nexport function props(id: string, options?: { closeOnOutsideClick?: boolean }) {\n const closeOnOutsideClick = options?.closeOnOutsideClick ?? true\n return {\n id,\n className: 'drawer',\n 'data-close-on-outside-click': closeOnOutsideClick ? undefined : 'false',\n onClick: (e: MouseEvent) => {\n if (closeOnOutsideClick && e.target === e.currentTarget) {\n ;(e.currentTarget as DrawerElement).close()\n }\n },\n } as const\n}\n\n"],"mappings":"8BAOA,GAAI,OAAO,OAAW,IAAa,CACjC,IAAM,MAAyB,CAC7B,IAAM,EAAc,MAAM,KACxB,SAAS,iBAAoC,sBAAsB,CACpE,CACD,EAAY,SAAS,EAAQ,IAAU,CACjC,IAAU,EAAY,OAAS,EACjC,EAAO,gBAAgB,QAAQ,CAE/B,EAAO,aAAa,QAAS,GAAG,EAElC,EAGa,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACrB,GACE,EAAS,OAAS,cAClB,EAAS,gBAAkB,QAC1B,EAAS,OAAuB,UAAU,SAAS,SAAS,CAC7D,CACA,GAAkB,CAClB,QAGJ,CAEO,QAAQ,SAAS,KAAM,CAC9B,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,OAAO,CAC1B,CAAC,CAoCJ,SAAS,EAAQ,EAAyC,CAKxD,OAJK,EACD,OAAO,GAAW,SACb,SAAS,eAAe,EAAO,CAEjC,EAJa,KAUtB,SAAgB,EAAK,EAAyB,CACjC,EAAQ,EAAO,EACtB,WAAW,CAMjB,SAAgB,EAAM,EAAyB,CAClC,EAAQ,EAAO,EACtB,OAAO,CAMb,SAAgB,GAAiB,CACf,MAAM,KAAK,SAAS,iBAAgC,sBAAsB,CAAC,CACnF,SAAS,CAAC,QAAS,GAAM,EAAE,OAAO,CAAC,CAM7C,SAAgB,EAAO,EAA4B,CAEjD,OADW,EAAQ,EAAO,EACf,MAAQ,GAMrB,SAAgB,GAA2B,CACzC,OAAO,MAAM,KAAK,SAAS,iBAAgC,sBAAsB,CAAC,CAMpF,SAAgB,GAA+B,CAC7C,IAAMA,EAAO,GAAS,CACtB,OAAOA,EAAKA,EAAK,OAAS,IAAM,KAMlC,SAAgB,EAAO,EAA+B,EAAE,CAAiB,CACvE,GAAM,CAAE,KAAI,UAAU,GAAI,YAAW,SAAS,GAAM,YAAY,GAAI,sBAAsB,IAAS,EAE7F,EAAS,SAAS,cAAc,SAAS,CAoB/C,MAnBA,GAAO,UAAY,UAAU,IAAY,MAAM,CAC3C,IAAI,EAAO,GAAK,GAChB,IAAW,EAAO,QAAQ,UAAY,GACrC,IACH,EAAO,QAAQ,oBAAsB,SAGvC,EAAO,UAAY;MACf,EAAS,oCAAsC,GAAG;kCACtB,EAAQ;IAIxC,EAAO,iBAAiB,QAAU,GAAM,CAClC,EAAE,SAAW,GAAU,EAAO,QAAQ,sBAAwB,SAChE,EAAO,OAAO,EAEhB,CAEK,EAMT,SAAgB,EAAM,EAAsC,CAE1D,OADA,SAAS,KAAK,YAAY,EAAO,CAC1B,EAMT,SAAgB,EAAQ,EAAyB,CACpC,EAAQ,EAAO,EACtB,QAAQ,CAQd,SAAgB,GAAmB,CACjC,IAAM,EAAW,GAAkB,CACjC,IAAM,EAAS,EAAE,OACb,EAAO,QAAQ,gBAAgB,EAAK,EAAyB,QAAQ,sBAAwB,SAC7F,EAAyB,OAAO,EAKtC,OADA,SAAS,iBAAiB,QAAS,EAAQ,KAC9B,SAAS,oBAAoB,QAAS,EAAQ,CAO7D,SAAgB,EACd,EACA,EACY,CACZ,IAAM,EAAK,EAAQ,EAAO,CAC1B,GAAI,CAAC,EAAI,UAAa,GAEtB,GAAM,CAAE,SAAQ,UAAS,YAAa,EAEhC,MAAoB,KAAW,CAC/B,MAAqB,KAAY,CAGjC,EAAW,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACjB,EAAS,gBAAkB,QAAU,EAAG,MAC1C,KAAU,EAGd,CAMF,OAJA,EAAG,iBAAiB,QAAS,EAAY,CACzC,EAAG,iBAAiB,SAAU,EAAa,CAC3C,EAAS,QAAQ,EAAI,CAAE,WAAY,GAAM,CAAC,KAE7B,CACX,EAAG,oBAAoB,QAAS,EAAY,CAC5C,EAAG,oBAAoB,SAAU,EAAa,CAC9C,EAAS,YAAY,EAQzB,SAAgB,EAAM,EAAY,EAA6C,CAC7E,IAAM,EAAsB,GAAS,qBAAuB,GAC5D,MAAO,CACL,KACA,UAAW,SACX,8BAA+B,EAAsB,IAAA,GAAY,QACjE,QAAU,GAAkB,CACtB,GAAuB,EAAE,SAAW,EAAE,eACtC,EAAE,cAAgC,OAAO,EAGhD"}
package/dist/react.cjs CHANGED
@@ -1,2 +1,2 @@
1
- require('./react.css');
1
+ require(`./drawer-BWZh2Fyp.cjs`);require('./drawer-C21nwiwE.css');
2
2
  let e=require(`react`),t=require(`react/jsx-runtime`);if(typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}const n=(0,e.createContext)({direction:void 0});function r(){return(0,e.useContext)(n)}function i({children:e,direction:r}){return(0,t.jsx)(n.Provider,{value:{direction:r},children:e})}const a=(0,e.forwardRef)(({children:n,className:i,open:a,onOpenChange:o,closeOnOutsideClick:s=!0,...c},l)=>{let{direction:u}=r(),d=(0,e.useRef)(null),f=l||d;return(0,e.useEffect)(()=>{let e=f.current;e&&(a&&!e.open?(e.showModal(),o?.(!0)):a===!1&&e.open&&e.close())},[a]),(0,t.jsx)(`dialog`,{ref:f,className:`drawer ${i??``}`.trim(),"data-direction":u,onClose:e=>{c.onClose?.(e),o?.(!1)},onClick:e=>{c.onClick?.(e),s&&e.target===e.currentTarget&&e.currentTarget.close()},...c,children:n})});a.displayName=`Drawer.Content`;const o=(0,e.forwardRef)(({className:e,...n},r)=>(0,t.jsx)(`div`,{ref:r,className:`drawer-handle ${e??``}`.trim(),"aria-hidden":`true`,...n}));o.displayName=`Drawer.Handle`;const s=(0,e.forwardRef)((e,n)=>(0,t.jsx)(`h2`,{ref:n,...e}));s.displayName=`Drawer.Title`;const c=(0,e.forwardRef)((e,n)=>(0,t.jsx)(`p`,{ref:n,...e}));c.displayName=`Drawer.Description`;const l={Root:i,Content:a,Handle:o,Title:s,Description:c};exports.Drawer=l;
package/dist/react.d.cts CHANGED
@@ -3,7 +3,7 @@ import * as react0 from "react";
3
3
  import { ComponentPropsWithoutRef, ReactNode } from "react";
4
4
 
5
5
  //#region src/react.d.ts
6
- type Direction = 'bottom' | 'top' | 'left' | 'right';
6
+ type Direction = 'bottom' | 'top' | 'left' | 'right' | 'modal';
7
7
  interface RootProps {
8
8
  children: ReactNode;
9
9
  /** Direction the drawer opens from */
package/dist/react.d.mts CHANGED
@@ -3,7 +3,7 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
3
3
  import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
 
5
5
  //#region src/react.d.ts
6
- type Direction = 'bottom' | 'top' | 'left' | 'right';
6
+ type Direction = 'bottom' | 'top' | 'left' | 'right' | 'modal';
7
7
  interface RootProps {
8
8
  children: ReactNode;
9
9
  /** Direction the drawer opens from */
package/dist/react.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import{createContext as e,forwardRef as t,useContext as n,useEffect as r,useRef as i}from"react";import{jsx as a}from"react/jsx-runtime";import './react.css';
1
+ import"./drawer-CiHZcyXE.mjs";import{createContext as e,forwardRef as t,useContext as n,useEffect as r,useRef as i}from"react";import{jsx as a}from"react/jsx-runtime";import './drawer-CXCJQa45.css';
2
2
  if(typeof window<`u`){let e=()=>{let e=Array.from(document.querySelectorAll(`dialog.drawer[open]`));e.forEach((t,n)=>{n===e.length-1?t.removeAttribute(`inert`):t.setAttribute(`inert`,``)})};new MutationObserver(t=>{for(let n of t)if(n.type===`attributes`&&n.attributeName===`open`&&n.target.classList.contains(`drawer`)){e();break}}).observe(document.body,{subtree:!0,attributes:!0,attributeFilter:[`open`]})}const o=e({direction:void 0});function s(){return n(o)}function c({children:e,direction:t}){return a(o.Provider,{value:{direction:t},children:e})}const l=t(({children:e,className:t,open:n,onOpenChange:o,closeOnOutsideClick:c=!0,...l},u)=>{let{direction:d}=s(),f=i(null),p=u||f;return r(()=>{let e=p.current;e&&(n&&!e.open?(e.showModal(),o?.(!0)):n===!1&&e.open&&e.close())},[n]),a(`dialog`,{ref:p,className:`drawer ${t??``}`.trim(),"data-direction":d,onClose:e=>{l.onClose?.(e),o?.(!1)},onClick:e=>{l.onClick?.(e),c&&e.target===e.currentTarget&&e.currentTarget.close()},...l,children:e})});l.displayName=`Drawer.Content`;const u=t(({className:e,...t},n)=>a(`div`,{ref:n,className:`drawer-handle ${e??``}`.trim(),"aria-hidden":`true`,...t}));u.displayName=`Drawer.Handle`;const d=t((e,t)=>a(`h2`,{ref:t,...e}));d.displayName=`Drawer.Title`;const f=t((e,t)=>a(`p`,{ref:t,...e}));f.displayName=`Drawer.Description`;const p={Root:c,Content:l,Handle:u,Title:d,Description:f};export{p as Drawer};
3
3
  //# sourceMappingURL=react.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.mjs","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n forwardRef,\n useEffect,\n useRef,\n type ReactNode,\n type ComponentPropsWithoutRef,\n} from 'react'\nimport './drawer.css'\n\n/* ===== Auto-enable accessibility for stacked drawers ===== */\nif (typeof window !== 'undefined') {\n const updateInertState = () => {\n const openDrawers = Array.from(\n document.querySelectorAll<HTMLDialogElement>('dialog.drawer[open]')\n )\n openDrawers.forEach((drawer, index) => {\n const isTopmost = index === openDrawers.length - 1\n if (isTopmost) {\n drawer.removeAttribute('inert')\n } else {\n drawer.setAttribute('inert', '')\n }\n })\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (\n mutation.type === 'attributes' &&\n mutation.attributeName === 'open' &&\n (mutation.target as HTMLElement).classList.contains('drawer')\n ) {\n updateInertState()\n break\n }\n }\n })\n\n observer.observe(document.body, {\n subtree: true,\n attributes: true,\n attributeFilter: ['open'],\n })\n}\n\n/* ===== Types ===== */\ntype Direction = 'bottom' | 'top' | 'left' | 'right'\n\ninterface DrawerContextValue {\n direction?: Direction\n}\n\n/* ===== Context ===== */\nconst DrawerContext = createContext<DrawerContextValue>({ direction: undefined })\n\nfunction useDrawerContext() {\n return useContext(DrawerContext)\n}\n\n/* ===== Root ===== */\ninterface RootProps {\n children: ReactNode\n /** Direction the drawer opens from */\n direction?: Direction\n}\n\nfunction Root({ children, direction }: RootProps) {\n return (\n <DrawerContext.Provider value={{ direction }}>\n {children}\n </DrawerContext.Provider>\n )\n}\n\n/* ===== Content ===== */\ninterface ContentProps extends Omit<ComponentPropsWithoutRef<'dialog'>, 'open'> {\n /** Controlled open state */\n open?: boolean\n /** Called when open state changes */\n onOpenChange?: (open: boolean) => void\n /** Close when clicking outside the drawer (default: true) */\n closeOnOutsideClick?: boolean\n}\n\nconst Content = forwardRef<HTMLDialogElement, ContentProps>(\n ({ children, className, open, onOpenChange, closeOnOutsideClick = true, ...props }, ref) => {\n const { direction } = useDrawerContext()\n const internalRef = useRef<HTMLDialogElement>(null)\n const dialogRef = (ref as React.RefObject<HTMLDialogElement>) || internalRef\n\n useEffect(() => {\n const dialog = dialogRef.current\n if (!dialog) return\n\n if (open && !dialog.open) {\n dialog.showModal()\n onOpenChange?.(true)\n } else if (open === false && dialog.open) {\n dialog.close()\n }\n }, [open])\n\n return (\n <dialog\n ref={dialogRef}\n className={`drawer ${className ?? ''}`.trim()}\n data-direction={direction}\n onClose={(e) => {\n props.onClose?.(e)\n onOpenChange?.(false)\n }}\n onClick={(e) => {\n props.onClick?.(e)\n // Backdrop click - only if clicking the dialog element itself\n if (closeOnOutsideClick && e.target === e.currentTarget) {\n e.currentTarget.close()\n }\n }}\n {...props}\n >\n {children}\n </dialog>\n )\n }\n)\nContent.displayName = 'Drawer.Content'\n\n/* ===== Handle ===== */\ninterface HandleProps extends ComponentPropsWithoutRef<'div'> {}\n\nconst Handle = forwardRef<HTMLDivElement, HandleProps>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={`drawer-handle ${className ?? ''}`.trim()}\n aria-hidden=\"true\"\n {...props}\n />\n))\nHandle.displayName = 'Drawer.Handle'\n\n/* ===== Title ===== */\ninterface TitleProps extends ComponentPropsWithoutRef<'h2'> {}\n\nconst Title = forwardRef<HTMLHeadingElement, TitleProps>((props, ref) => (\n <h2 ref={ref} {...props} />\n))\nTitle.displayName = 'Drawer.Title'\n\n/* ===== Description ===== */\ninterface DescriptionProps extends ComponentPropsWithoutRef<'p'> {}\n\nconst Description = forwardRef<HTMLParagraphElement, DescriptionProps>((props, ref) => (\n <p ref={ref} {...props} />\n))\nDescription.displayName = 'Drawer.Description'\n\n/* ===== Namespace Export ===== */\nexport const Drawer = {\n Root,\n Content,\n Handle,\n Title,\n Description,\n}\n\nexport type {\n RootProps as DrawerRootProps,\n ContentProps as DrawerContentProps,\n HandleProps as DrawerHandleProps,\n TitleProps as DrawerTitleProps,\n DescriptionProps as DrawerDescriptionProps,\n Direction as DrawerDirection,\n}\n"],"mappings":"yIAYA,GAAI,OAAO,OAAW,IAAa,CACjC,IAAM,MAAyB,CAC7B,IAAM,EAAc,MAAM,KACxB,SAAS,iBAAoC,sBAAsB,CACpE,CACD,EAAY,SAAS,EAAQ,IAAU,CACnB,IAAU,EAAY,OAAS,EAE/C,EAAO,gBAAgB,QAAQ,CAE/B,EAAO,aAAa,QAAS,GAAG,EAElC,EAGa,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACrB,GACE,EAAS,OAAS,cAClB,EAAS,gBAAkB,QAC1B,EAAS,OAAuB,UAAU,SAAS,SAAS,CAC7D,CACA,GAAkB,CAClB,QAGJ,CAEO,QAAQ,SAAS,KAAM,CAC9B,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,OAAO,CAC1B,CAAC,CAWJ,MAAM,EAAgB,EAAkC,CAAE,UAAW,IAAA,GAAW,CAAC,CAEjF,SAAS,GAAmB,CAC1B,OAAO,EAAW,EAAc,CAUlC,SAAS,EAAK,CAAE,WAAU,aAAwB,CAChD,OACE,EAAC,EAAc,SAAA,CAAS,MAAO,CAAE,YAAW,CACzC,YACsB,CAc7B,MAAM,EAAU,GACb,CAAE,WAAU,YAAW,OAAM,eAAc,sBAAsB,GAAM,GAAG,GAAS,IAAQ,CAC1F,GAAM,CAAE,aAAc,GAAkB,CAClC,EAAc,EAA0B,KAAK,CAC7C,EAAa,GAA8C,EAcjE,OAZA,MAAgB,CACd,IAAM,EAAS,EAAU,QACpB,IAED,GAAQ,CAAC,EAAO,MAClB,EAAO,WAAW,CAClB,IAAe,GAAK,EACX,IAAS,IAAS,EAAO,MAClC,EAAO,OAAO,GAEf,CAAC,EAAK,CAAC,CAGR,EAAC,SAAA,CACC,IAAK,EACL,UAAW,UAAU,GAAa,KAAK,MAAM,CAC7C,iBAAgB,EAChB,QAAU,GAAM,CACd,EAAM,UAAU,EAAE,CAClB,IAAe,GAAM,EAEvB,QAAU,GAAM,CACd,EAAM,UAAU,EAAE,CAEd,GAAuB,EAAE,SAAW,EAAE,eACxC,EAAE,cAAc,OAAO,EAG3B,GAAI,EAEH,YACM,EAGd,CACD,EAAQ,YAAc,iBAKtB,MAAM,EAAS,GAAyC,CAAE,YAAW,GAAG,GAAS,IAC/E,EAAC,MAAA,CACM,MACL,UAAW,iBAAiB,GAAa,KAAK,MAAM,CACpD,cAAY,OACZ,GAAI,GACJ,CACF,CACF,EAAO,YAAc,gBAKrB,MAAM,EAAQ,GAA4C,EAAO,IAC/D,EAAC,KAAA,CAAQ,MAAK,GAAI,GAAS,CAC3B,CACF,EAAM,YAAc,eAKpB,MAAM,EAAc,GAAoD,EAAO,IAC7E,EAAC,IAAA,CAAO,MAAK,GAAI,GAAS,CAC1B,CACF,EAAY,YAAc,qBAG1B,MAAa,EAAS,CACpB,OACA,UACA,SACA,QACA,cACD"}
1
+ {"version":3,"file":"react.mjs","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n forwardRef,\n useEffect,\n useRef,\n type ReactNode,\n type ComponentPropsWithoutRef,\n} from 'react'\nimport './drawer.css'\n\n/* ===== Auto-enable accessibility for stacked drawers ===== */\nif (typeof window !== 'undefined') {\n const updateInertState = () => {\n const openDrawers = Array.from(\n document.querySelectorAll<HTMLDialogElement>('dialog.drawer[open]')\n )\n openDrawers.forEach((drawer, index) => {\n const isTopmost = index === openDrawers.length - 1\n if (isTopmost) {\n drawer.removeAttribute('inert')\n } else {\n drawer.setAttribute('inert', '')\n }\n })\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (\n mutation.type === 'attributes' &&\n mutation.attributeName === 'open' &&\n (mutation.target as HTMLElement).classList.contains('drawer')\n ) {\n updateInertState()\n break\n }\n }\n })\n\n observer.observe(document.body, {\n subtree: true,\n attributes: true,\n attributeFilter: ['open'],\n })\n}\n\n/* ===== Types ===== */\ntype Direction = 'bottom' | 'top' | 'left' | 'right' | 'modal'\n\ninterface DrawerContextValue {\n direction?: Direction\n}\n\n/* ===== Context ===== */\nconst DrawerContext = createContext<DrawerContextValue>({ direction: undefined })\n\nfunction useDrawerContext() {\n return useContext(DrawerContext)\n}\n\n/* ===== Root ===== */\ninterface RootProps {\n children: ReactNode\n /** Direction the drawer opens from */\n direction?: Direction\n}\n\nfunction Root({ children, direction }: RootProps) {\n return (\n <DrawerContext.Provider value={{ direction }}>\n {children}\n </DrawerContext.Provider>\n )\n}\n\n/* ===== Content ===== */\ninterface ContentProps extends Omit<ComponentPropsWithoutRef<'dialog'>, 'open'> {\n /** Controlled open state */\n open?: boolean\n /** Called when open state changes */\n onOpenChange?: (open: boolean) => void\n /** Close when clicking outside the drawer (default: true) */\n closeOnOutsideClick?: boolean\n}\n\nconst Content = forwardRef<HTMLDialogElement, ContentProps>(\n ({ children, className, open, onOpenChange, closeOnOutsideClick = true, ...props }, ref) => {\n const { direction } = useDrawerContext()\n const internalRef = useRef<HTMLDialogElement>(null)\n const dialogRef = (ref as React.RefObject<HTMLDialogElement>) || internalRef\n\n useEffect(() => {\n const dialog = dialogRef.current\n if (!dialog) return\n\n if (open && !dialog.open) {\n dialog.showModal()\n onOpenChange?.(true)\n } else if (open === false && dialog.open) {\n dialog.close()\n }\n }, [open])\n\n return (\n <dialog\n ref={dialogRef}\n className={`drawer ${className ?? ''}`.trim()}\n data-direction={direction}\n onClose={(e) => {\n props.onClose?.(e)\n onOpenChange?.(false)\n }}\n onClick={(e) => {\n props.onClick?.(e)\n // Backdrop click - only if clicking the dialog element itself\n if (closeOnOutsideClick && e.target === e.currentTarget) {\n e.currentTarget.close()\n }\n }}\n {...props}\n >\n {children}\n </dialog>\n )\n }\n)\nContent.displayName = 'Drawer.Content'\n\n/* ===== Handle ===== */\ninterface HandleProps extends ComponentPropsWithoutRef<'div'> {}\n\nconst Handle = forwardRef<HTMLDivElement, HandleProps>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={`drawer-handle ${className ?? ''}`.trim()}\n aria-hidden=\"true\"\n {...props}\n />\n))\nHandle.displayName = 'Drawer.Handle'\n\n/* ===== Title ===== */\ninterface TitleProps extends ComponentPropsWithoutRef<'h2'> {}\n\nconst Title = forwardRef<HTMLHeadingElement, TitleProps>((props, ref) => (\n <h2 ref={ref} {...props} />\n))\nTitle.displayName = 'Drawer.Title'\n\n/* ===== Description ===== */\ninterface DescriptionProps extends ComponentPropsWithoutRef<'p'> {}\n\nconst Description = forwardRef<HTMLParagraphElement, DescriptionProps>((props, ref) => (\n <p ref={ref} {...props} />\n))\nDescription.displayName = 'Drawer.Description'\n\n/* ===== Namespace Export ===== */\nexport const Drawer = {\n Root,\n Content,\n Handle,\n Title,\n Description,\n}\n\nexport type {\n RootProps as DrawerRootProps,\n ContentProps as DrawerContentProps,\n HandleProps as DrawerHandleProps,\n TitleProps as DrawerTitleProps,\n DescriptionProps as DrawerDescriptionProps,\n Direction as DrawerDirection,\n}\n"],"mappings":"uKAYA,GAAI,OAAO,OAAW,IAAa,CACjC,IAAM,MAAyB,CAC7B,IAAM,EAAc,MAAM,KACxB,SAAS,iBAAoC,sBAAsB,CACpE,CACD,EAAY,SAAS,EAAQ,IAAU,CACnB,IAAU,EAAY,OAAS,EAE/C,EAAO,gBAAgB,QAAQ,CAE/B,EAAO,aAAa,QAAS,GAAG,EAElC,EAGa,IAAI,iBAAkB,GAAc,CACnD,IAAK,IAAM,KAAY,EACrB,GACE,EAAS,OAAS,cAClB,EAAS,gBAAkB,QAC1B,EAAS,OAAuB,UAAU,SAAS,SAAS,CAC7D,CACA,GAAkB,CAClB,QAGJ,CAEO,QAAQ,SAAS,KAAM,CAC9B,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,OAAO,CAC1B,CAAC,CAWJ,MAAM,EAAgB,EAAkC,CAAE,UAAW,IAAA,GAAW,CAAC,CAEjF,SAAS,GAAmB,CAC1B,OAAO,EAAW,EAAc,CAUlC,SAAS,EAAK,CAAE,WAAU,aAAwB,CAChD,OACE,EAAC,EAAc,SAAA,CAAS,MAAO,CAAE,YAAW,CACzC,YACsB,CAc7B,MAAM,EAAU,GACb,CAAE,WAAU,YAAW,OAAM,eAAc,sBAAsB,GAAM,GAAG,GAAS,IAAQ,CAC1F,GAAM,CAAE,aAAc,GAAkB,CAClC,EAAc,EAA0B,KAAK,CAC7C,EAAa,GAA8C,EAcjE,OAZA,MAAgB,CACd,IAAM,EAAS,EAAU,QACpB,IAED,GAAQ,CAAC,EAAO,MAClB,EAAO,WAAW,CAClB,IAAe,GAAK,EACX,IAAS,IAAS,EAAO,MAClC,EAAO,OAAO,GAEf,CAAC,EAAK,CAAC,CAGR,EAAC,SAAA,CACC,IAAK,EACL,UAAW,UAAU,GAAa,KAAK,MAAM,CAC7C,iBAAgB,EAChB,QAAU,GAAM,CACd,EAAM,UAAU,EAAE,CAClB,IAAe,GAAM,EAEvB,QAAU,GAAM,CACd,EAAM,UAAU,EAAE,CAEd,GAAuB,EAAE,SAAW,EAAE,eACxC,EAAE,cAAc,OAAO,EAG3B,GAAI,EAEH,YACM,EAGd,CACD,EAAQ,YAAc,iBAKtB,MAAM,EAAS,GAAyC,CAAE,YAAW,GAAG,GAAS,IAC/E,EAAC,MAAA,CACM,MACL,UAAW,iBAAiB,GAAa,KAAK,MAAM,CACpD,cAAY,OACZ,GAAI,GACJ,CACF,CACF,EAAO,YAAc,gBAKrB,MAAM,EAAQ,GAA4C,EAAO,IAC/D,EAAC,KAAA,CAAQ,MAAK,GAAI,GAAS,CAC3B,CACF,EAAM,YAAc,eAKpB,MAAM,EAAc,GAAoD,EAAO,IAC7E,EAAC,IAAA,CAAO,MAAK,GAAI,GAAS,CAC1B,CACF,EAAY,YAAc,qBAG1B,MAAa,EAAS,CACpB,OACA,UACA,SACA,QACA,cACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "css-drawer",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Vaul-quality drawer component using native <dialog> and pure CSS animations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -27,11 +27,12 @@
27
27
  "default": "./dist/react.cjs"
28
28
  }
29
29
  },
30
- "./styles": "./dist/react.css",
31
- "./drawer.css": "./dist/react.css"
30
+ "./styles": "./src/drawer.css",
31
+ "./drawer.css": "./src/drawer.css"
32
32
  },
33
33
  "files": [
34
- "dist"
34
+ "dist",
35
+ "src/drawer.css"
35
36
  ],
36
37
  "peerDependencies": {
37
38
  "react": ">=18.0.0",
@@ -25,6 +25,7 @@
25
25
  --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);
26
26
  --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);
27
27
  --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);
28
+ --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.25);
28
29
 
29
30
  /* Animation */
30
31
  --drawer-duration: 0.5s;
@@ -47,6 +48,7 @@
47
48
  --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);
48
49
  --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);
49
50
  --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);
51
+ --drawer-shadow-modal: 0 25px 50px -12px hsl(0 0% 0% / 0.5);
50
52
  }
51
53
  }
52
54
 
@@ -169,6 +171,30 @@ body:has(.drawer[open]) {
169
171
  box-shadow: var(--drawer-shadow-top);
170
172
  }
171
173
 
174
+ /* Modal (centered) */
175
+ .drawer[data-direction="modal"] {
176
+ --_translate-closed: 0 0;
177
+ --_border-radius: var(--drawer-radius);
178
+ inset: 0;
179
+ margin: auto;
180
+ width: fit-content;
181
+ max-width: var(--drawer-max-width);
182
+ height: fit-content;
183
+ max-height: var(--drawer-max-height);
184
+ box-shadow: var(--drawer-shadow-modal);
185
+ scale: var(--drawer-nested-scale);
186
+ }
187
+
188
+ .drawer[data-direction="modal"][open] {
189
+ scale: 1;
190
+ }
191
+
192
+ @starting-style {
193
+ .drawer[data-direction="modal"][open] {
194
+ scale: var(--drawer-nested-scale);
195
+ }
196
+ }
197
+
172
198
  /* ===== AUTO-NESTING (up to 5 levels) ===== */
173
199
  /* Uses sibling combinators to count open drawers */
174
200
 
@@ -302,6 +328,3 @@ body:has(.drawer[open]) {
302
328
  filter: none;
303
329
  }
304
330
  }
305
-
306
-
307
- /*# sourceMappingURL=react.css.map*/
@@ -1 +0,0 @@
1
- {"version":3,"file":"react.css","names":[],"sources":["../src/drawer.css"],"sourcesContent":["/* CSS Drawer - Vaul-quality drawer with auto-nesting and directions */\n\n:root {\n /* Visual */\n --drawer-bg: #fff;\n --drawer-radius: 24px;\n --drawer-backdrop: hsl(0 0% 0% / 0.4);\n --drawer-backdrop-blur: 4px;\n\n /* Sizing */\n --drawer-max-width: 500px;\n --drawer-max-height: 96dvh;\n\n /* Handle */\n --drawer-handle-bg: hsl(0 0% 80%);\n --drawer-handle-bg-hover: hsl(0 0% 60%);\n --drawer-handle-width: 48px;\n --drawer-handle-width-hover: 56px;\n --drawer-handle-height: 5px;\n --drawer-handle-padding-block: 1rem 0.5rem;\n --drawer-handle-padding-inline: 0;\n\n /* Shadows */\n --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.12), 0 -4px 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.12), 0 4px 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.12), -4px 0 20px hsl(0 0% 0% / 0.08);\n --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.12), 4px 0 20px hsl(0 0% 0% / 0.08);\n\n /* Animation */\n --drawer-duration: 0.5s;\n --drawer-duration-close: 0.35s;\n --drawer-ease: cubic-bezier(0.32, 0.72, 0, 1);\n\n /* Nesting effects */\n --drawer-nested-scale: 0.94;\n --drawer-nested-offset: 20px;\n --drawer-nested-brightness: 0.92;\n --drawer-nested-backdrop: hsl(0 0% 0% / 0.15);\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --drawer-bg: hsl(0 0% 12%);\n --drawer-handle-bg: hsl(0 0% 35%);\n --drawer-handle-bg-hover: hsl(0 0% 50%);\n --drawer-shadow-bottom: 0 -10px 60px hsl(0 0% 0% / 0.4), 0 -4px 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-top: 0 10px 60px hsl(0 0% 0% / 0.4), 0 4px 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-right: -10px 0 60px hsl(0 0% 0% / 0.4), -4px 0 20px hsl(0 0% 0% / 0.3);\n --drawer-shadow-left: 10px 0 60px hsl(0 0% 0% / 0.4), 4px 0 20px hsl(0 0% 0% / 0.3);\n }\n}\n\n/* Background scale effect */\nbody {\n transition: scale var(--drawer-duration) var(--drawer-ease), border-radius var(--drawer-duration) var(--drawer-ease);\n transform-origin: center top;\n}\n\nbody:has(.drawer[open]) {\n overflow: hidden;\n scale: var(--drawer-nested-scale);\n border-radius: var(--drawer-radius);\n}\n\n/* Base drawer */\n.drawer {\n border: none;\n padding: 0;\n margin: 0;\n max-width: 100%;\n max-height: 100%;\n position: fixed;\n background: var(--drawer-bg);\n overflow: hidden;\n opacity: 0;\n transition:\n display var(--drawer-duration-close) allow-discrete,\n overlay var(--drawer-duration-close) allow-discrete,\n translate var(--drawer-duration-close) var(--drawer-ease),\n scale var(--drawer-duration-close) var(--drawer-ease),\n filter var(--drawer-duration-close) ease,\n opacity var(--drawer-duration-close) ease;\n\n /* Default: bottom */\n --_translate-closed: 0 100%;\n --_border-radius: var(--drawer-radius) var(--drawer-radius) 0 0;\n inset: auto 0 0 0;\n margin-inline: auto;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: auto;\n max-height: var(--drawer-max-height);\n border-radius: var(--drawer-border-radius, var(--_border-radius));\n box-shadow: var(--drawer-shadow-bottom);\n translate: var(--_translate-closed);\n}\n\n.drawer::backdrop {\n background: var(--drawer-backdrop);\n opacity: 0;\n backdrop-filter: blur(var(--drawer-backdrop-blur));\n -webkit-backdrop-filter: blur(var(--drawer-backdrop-blur));\n transition:\n display var(--drawer-duration-close) allow-discrete,\n overlay var(--drawer-duration-close) allow-discrete,\n opacity var(--drawer-duration-close) ease;\n}\n\n.drawer[open] {\n opacity: 1;\n translate: 0 0;\n transition:\n display var(--drawer-duration) allow-discrete,\n overlay var(--drawer-duration) allow-discrete,\n translate var(--drawer-duration) var(--drawer-ease),\n scale var(--drawer-duration) var(--drawer-ease),\n filter var(--drawer-duration) ease,\n opacity 0.15s ease;\n}\n\n.drawer[open]::backdrop {\n opacity: 1;\n transition: display var(--drawer-duration) allow-discrete, overlay var(--drawer-duration) allow-discrete, opacity 0.2s ease;\n}\n\n@starting-style {\n .drawer[open] { opacity: 0; translate: var(--_translate-closed); }\n .drawer[open]::backdrop { opacity: 0; }\n}\n\n/* ===== DIRECTIONS ===== */\n\n/* Right */\n.drawer[data-direction=\"right\"] {\n --_translate-closed: 100% 0;\n --_border-radius: var(--drawer-radius) 0 0 var(--drawer-radius);\n inset: 0 0 0 auto;\n margin: 0;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: 100dvh;\n max-height: 100dvh;\n box-shadow: var(--drawer-shadow-right);\n}\n\n/* Left */\n.drawer[data-direction=\"left\"] {\n --_translate-closed: -100% 0;\n --_border-radius: 0 var(--drawer-radius) var(--drawer-radius) 0;\n inset: 0 auto 0 0;\n margin: 0;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: 100dvh;\n max-height: 100dvh;\n box-shadow: var(--drawer-shadow-left);\n}\n\n/* Top */\n.drawer[data-direction=\"top\"] {\n --_translate-closed: 0 -100%;\n --_border-radius: 0 0 var(--drawer-radius) var(--drawer-radius);\n inset: 0 0 auto 0;\n margin-inline: auto;\n width: 100%;\n max-width: var(--drawer-max-width);\n height: auto;\n max-height: var(--drawer-max-height);\n box-shadow: var(--drawer-shadow-top);\n}\n\n/* ===== AUTO-NESTING (up to 5 levels) ===== */\n/* Uses sibling combinators to count open drawers */\n\n/* 1+ open drawers after */\n.drawer[open]:has(~ .drawer[open]) {\n scale: var(--drawer-nested-scale);\n translate: 0 calc(-1 * var(--drawer-nested-offset));\n border-radius: var(--drawer-radius);\n filter: brightness(var(--drawer-nested-brightness));\n pointer-events: none;\n}\n\n/* 2+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-2 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 3+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-3 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 4+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-4 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* 5+ open drawers after */\n.drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: calc(var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale) * var(--drawer-nested-scale));\n translate: 0 calc(-5 * var(--drawer-nested-offset));\n filter: brightness(calc(var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness) * var(--drawer-nested-brightness)));\n}\n\n/* Lighter backdrop for stacked drawers */\n.drawer[open] ~ .drawer[open]::backdrop {\n background: var(--drawer-nested-backdrop);\n backdrop-filter: none;\n}\n\n/* Handle */\n.drawer-handle {\n display: flex;\n justify-content: center;\n padding-block: var(--drawer-handle-padding-block);\n padding-inline: var(--drawer-handle-padding-inline);\n cursor: grab;\n}\n\n.drawer-handle::before {\n content: '';\n width: var(--drawer-handle-width);\n height: var(--drawer-handle-height);\n background: var(--drawer-handle-bg);\n border-radius: 100px;\n transition: width 0.2s var(--drawer-ease), height 0.2s var(--drawer-ease), background 0.2s ease;\n}\n\n.drawer-handle:hover::before {\n width: var(--drawer-handle-width-hover);\n background: var(--drawer-handle-bg-hover);\n}\n\n/* Vertical handle for left/right drawers */\n.drawer[data-direction=\"left\"] .drawer-handle,\n.drawer[data-direction=\"right\"] .drawer-handle {\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding-block: var(--drawer-handle-padding-inline);\n padding-inline: var(--drawer-handle-padding-block);\n height: 100%;\n position: absolute;\n top: 0;\n writing-mode: vertical-lr;\n}\n\n.drawer[data-direction=\"left\"] .drawer-handle { right: 0; }\n.drawer[data-direction=\"right\"] .drawer-handle { left: 0; }\n\n.drawer[data-direction=\"left\"] .drawer-handle::before,\n.drawer[data-direction=\"right\"] .drawer-handle::before {\n width: var(--drawer-handle-height);\n height: var(--drawer-handle-width);\n}\n\n.drawer[data-direction=\"left\"] .drawer-handle:hover::before,\n.drawer[data-direction=\"right\"] .drawer-handle:hover::before {\n width: var(--drawer-handle-height);\n height: var(--drawer-handle-width-hover);\n}\n\n/* Content - structural only, no opinionated padding */\n.drawer-content {\n overflow-x: hidden;\n overflow-y: auto;\n overscroll-behavior: contain;\n flex: 1;\n min-height: 0;\n}\n\n/* Content sizing for directions */\n.drawer[data-direction=\"left\"] .drawer-content,\n.drawer[data-direction=\"right\"] .drawer-content {\n height: 100%;\n}\n\n/* Reduced motion */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n transition-duration: 0.01ms !important;\n }\n\n body:has(.drawer[open]) {\n scale: 1;\n }\n\n .drawer[open]:has(~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]),\n .drawer[open]:has(~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open] ~ .drawer[open]) {\n scale: 1;\n translate: 0 0;\n filter: none;\n }\n}\n"],"mappings":"AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}