@reliability-design/web 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/components/rel-accordion/README.md +132 -0
  2. package/dist/components/rel-accordion/rel-accordion.ts +47 -16
  3. package/dist/components/rel-alert/README.md +141 -0
  4. package/dist/components/rel-alert/rel-alert.ts +101 -20
  5. package/dist/components/rel-avatar/README.md +119 -0
  6. package/dist/components/rel-avatar/rel-avatar.ts +3 -2
  7. package/dist/components/rel-badge/README.md +86 -0
  8. package/dist/components/rel-badge/rel-badge.ts +11 -7
  9. package/dist/components/rel-breadcrumb/README.md +102 -0
  10. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +12 -6
  11. package/dist/components/rel-button/README.md +130 -0
  12. package/dist/components/rel-button/rel-button.ts +64 -8
  13. package/dist/components/rel-calendar/README.md +109 -0
  14. package/dist/components/rel-calendar/index.ts +1 -0
  15. package/dist/components/rel-calendar/rel-calendar.ts +467 -0
  16. package/dist/components/rel-card/README.md +94 -0
  17. package/dist/components/rel-card/rel-card.ts +12 -3
  18. package/dist/components/rel-cardmedia/README.md +127 -0
  19. package/dist/components/rel-cardmedia/rel-cardmedia.ts +14 -4
  20. package/dist/components/rel-carousel/README.md +166 -0
  21. package/dist/components/rel-carousel/index.ts +1 -0
  22. package/dist/components/rel-carousel/rel-carousel.ts +468 -0
  23. package/dist/components/rel-chart/README.md +286 -0
  24. package/dist/components/rel-chart/index.ts +1 -0
  25. package/dist/components/rel-chart/rel-chart.ts +736 -0
  26. package/dist/components/rel-checkbox/README.md +77 -0
  27. package/dist/components/rel-checkbox/rel-checkbox.ts +51 -12
  28. package/dist/components/rel-chip/README.md +114 -0
  29. package/dist/components/rel-chip/rel-chip.ts +83 -9
  30. package/dist/components/rel-container/README.md +99 -0
  31. package/dist/components/rel-date-picker/README.md +253 -0
  32. package/dist/components/rel-date-picker/index.ts +1 -0
  33. package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
  34. package/dist/components/rel-divider/README.md +100 -0
  35. package/dist/components/rel-divider/rel-divider.ts +9 -0
  36. package/dist/components/rel-drawer/README.md +200 -0
  37. package/dist/components/rel-drawer/index.ts +1 -0
  38. package/dist/components/rel-drawer/rel-drawer-footer.ts +29 -0
  39. package/dist/components/rel-drawer/rel-drawer.ts +612 -0
  40. package/dist/components/rel-kbd/README.md +151 -0
  41. package/dist/components/rel-kbd/rel-kbd.ts +118 -0
  42. package/dist/components/rel-layout/README.md +88 -0
  43. package/dist/components/rel-menu/README.md +151 -0
  44. package/dist/components/rel-menu/rel-menu.ts +64 -24
  45. package/dist/components/rel-menubar/README.md +329 -0
  46. package/dist/components/rel-menubar/index.ts +2 -0
  47. package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
  48. package/dist/components/rel-menubar/rel-menubar.ts +605 -0
  49. package/dist/components/rel-nav-menu/README.md +289 -0
  50. package/dist/components/rel-nav-menu/index.ts +3 -0
  51. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +240 -0
  52. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
  53. package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -0
  54. package/dist/components/rel-navbar/README.md +126 -0
  55. package/dist/components/rel-navbar/rel-navbar.ts +104 -0
  56. package/dist/components/rel-otp/README.md +440 -0
  57. package/dist/components/rel-otp/index.ts +1 -0
  58. package/dist/components/rel-otp/rel-otp.ts +504 -0
  59. package/dist/components/rel-pagination/README.md +410 -0
  60. package/dist/components/rel-pagination/index.ts +1 -0
  61. package/dist/components/rel-pagination/rel-pagination.ts +381 -0
  62. package/dist/components/rel-popover/README.md +568 -0
  63. package/dist/components/rel-popover/index.ts +2 -0
  64. package/dist/components/rel-popover/rel-popover-content.ts +55 -0
  65. package/dist/components/rel-popover/rel-popover.ts +330 -0
  66. package/dist/components/rel-progress/README.md +110 -0
  67. package/dist/components/rel-progress/rel-progress.ts +32 -5
  68. package/dist/components/rel-radio/README.md +120 -0
  69. package/dist/components/rel-radio/rel-radio.ts +31 -6
  70. package/dist/components/rel-resizable/README.md +140 -0
  71. package/dist/components/rel-resizable/index.ts +3 -0
  72. package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
  73. package/dist/components/rel-resizable/rel-resizable-handle.ts +145 -0
  74. package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
  75. package/dist/components/rel-segmented-button/README.md +141 -0
  76. package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
  77. package/dist/components/rel-select/README.md +107 -0
  78. package/dist/components/rel-select/rel-select.ts +28 -14
  79. package/dist/components/rel-sheet/README.md +224 -0
  80. package/dist/components/rel-sheet/rel-sheet.ts +684 -0
  81. package/dist/components/rel-sidebar/README.md +209 -0
  82. package/dist/components/rel-sidebar/rel-sidebar.ts +1166 -0
  83. package/dist/components/rel-slider/README.md +135 -0
  84. package/dist/components/rel-slider/rel-slider.ts +44 -9
  85. package/dist/components/rel-snackbar/README.md +134 -0
  86. package/dist/components/rel-snackbar/rel-snackbar.ts +109 -14
  87. package/dist/components/rel-stepper/README.md +160 -0
  88. package/dist/components/rel-stepper/rel-stepper.ts +38 -27
  89. package/dist/components/rel-switch/README.md +102 -0
  90. package/dist/components/rel-switch/rel-switch.ts +34 -12
  91. package/dist/components/rel-tab/README.md +163 -0
  92. package/dist/components/rel-tab/rel-tab.ts +79 -23
  93. package/dist/components/rel-table/README.md +178 -0
  94. package/dist/components/rel-table/rel-table-body.ts +21 -0
  95. package/dist/components/rel-table/rel-table-caption.ts +27 -0
  96. package/dist/components/rel-table/rel-table-cell.ts +42 -0
  97. package/dist/components/rel-table/rel-table-footer.ts +23 -0
  98. package/dist/components/rel-table/rel-table-head.ts +45 -0
  99. package/dist/components/rel-table/rel-table-header.ts +21 -0
  100. package/dist/components/rel-table/rel-table-row.ts +36 -0
  101. package/dist/components/rel-table/rel-table.ts +53 -0
  102. package/dist/components/rel-textfield/README.md +90 -0
  103. package/dist/components/rel-textfield/rel-textfield.ts +55 -39
  104. package/dist/components/rel-toast/README.md +148 -0
  105. package/dist/components/rel-toast/rel-toast.ts +63 -24
  106. package/dist/components/rel-tooltip/README.md +103 -0
  107. package/dist/components/rel-tooltip/rel-tooltip.ts +123 -80
  108. package/dist/components/rel-topbar/rel-topbar-actions.ts +23 -0
  109. package/dist/components/rel-topbar/rel-topbar-brand.ts +46 -0
  110. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +33 -0
  111. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
  112. package/dist/components/rel-topbar/rel-topbar-item.ts +315 -0
  113. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +23 -0
  114. package/dist/components/rel-topbar/rel-topbar-profile.ts +248 -0
  115. package/dist/components/rel-topbar/rel-topbar-utilities.ts +23 -0
  116. package/dist/components/rel-topbar/rel-topbar.ts +329 -0
  117. package/dist/index.d.mts +831 -7
  118. package/dist/index.d.ts +831 -7
  119. package/dist/index.js +9500 -1564
  120. package/dist/index.js.map +1 -1
  121. package/dist/index.mjs +9240 -1344
  122. package/dist/index.mjs.map +1 -1
  123. package/dist/styles/vars.css +187 -25
  124. package/dist/vars-TBB4BMSD.css +346 -0
  125. package/package.json +2 -1
  126. package/dist/vars-6ZIUPIU4.css +0 -184
@@ -0,0 +1,612 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property, state, query } from "lit/decorators.js";
3
+ import "./rel-drawer-footer";
4
+
5
+ export type DrawerSide = "bottom" | "left" | "right" | "top";
6
+ export type DrawerCloseReason = "backdrop" | "escape" | "drag" | "programmatic";
7
+
8
+ @customElement("rel-drawer")
9
+ export class RelDrawer extends LitElement {
10
+ @property({ type: Boolean, reflect: true }) open = false;
11
+ @property({ type: String }) side: DrawerSide = "bottom";
12
+ @property({ type: Boolean }) dismissible = true;
13
+ @property({ type: Boolean }) modal = true;
14
+ @property({ type: Boolean, attribute: "close-on-backdrop" }) closeOnBackdrop = true;
15
+ @property({ type: Boolean, attribute: "close-on-escape" }) closeOnEscape = true;
16
+ @property({ type: Boolean, attribute: "show-handle" }) showHandle = true;
17
+ @property({ type: Boolean }) draggable = true;
18
+ @property({ type: Number, attribute: "snap-threshold" }) snapThreshold = 0.25;
19
+ @property({ type: String, attribute: "max-height" }) maxHeight = "80vh";
20
+ @property({ type: String }) width = "420px";
21
+ @property({ type: Boolean, reflect: true }) disabled = false;
22
+ @property({ type: String }) title: string = "";
23
+ @property({ type: String }) description: string = "";
24
+
25
+ @state() private isDragging = false;
26
+ @state() private dragOffset = 0;
27
+
28
+ @query(".panel") private panelElement!: HTMLElement;
29
+ @query(".overlay") private overlayElement!: HTMLElement;
30
+
31
+ private previousFocus: HTMLElement | null = null;
32
+ private startY = 0;
33
+ private currentY = 0;
34
+ private originalBodyOverflow = "";
35
+ private originalBodyPaddingRight = "";
36
+
37
+ static styles = css`
38
+ :host {
39
+ --drawer-transition-duration: 0.3s;
40
+ --drawer-transition-timing: cubic-bezier(0.32, 0.72, 0, 1);
41
+ }
42
+
43
+ :host([disabled]) {
44
+ pointer-events: none;
45
+ opacity: 0.6;
46
+ }
47
+
48
+ .drawer-container {
49
+ position: fixed;
50
+ top: 0;
51
+ left: 0;
52
+ right: 0;
53
+ bottom: 0;
54
+ z-index: 1000;
55
+ pointer-events: none;
56
+ }
57
+
58
+ .drawer-container.open {
59
+ pointer-events: auto;
60
+ }
61
+
62
+ /* Overlay */
63
+ .overlay {
64
+ position: absolute;
65
+ top: 0;
66
+ left: 0;
67
+ right: 0;
68
+ bottom: 0;
69
+ background: var(--rel-overlay-scrim);
70
+ opacity: 0;
71
+ transition: opacity var(--drawer-transition-duration) var(--drawer-transition-timing);
72
+ }
73
+
74
+ .drawer-container.open .overlay {
75
+ opacity: var(--rel-drawer-scrim-opacity, 1);
76
+ }
77
+
78
+ /* Panel base */
79
+ .panel {
80
+ position: absolute;
81
+ background: var(--rel-colors-white);
82
+ box-shadow: var(--rel-shadows-shadow-500);
83
+ display: flex;
84
+ flex-direction: column;
85
+ outline: none;
86
+ font-family: var(--rel-fontFamilies-primary);
87
+ transition: transform var(--drawer-transition-duration) var(--drawer-transition-timing);
88
+ }
89
+
90
+ /* Bottom drawer */
91
+ .panel[data-side="bottom"] {
92
+ bottom: 0;
93
+ left: 0;
94
+ right: 0;
95
+ max-height: var(--drawer-max-height, 80vh);
96
+ border-top-left-radius: var(--rel-borders-radius-border-radius-300);
97
+ border-top-right-radius: var(--rel-borders-radius-border-radius-300);
98
+ transform: translateY(100%);
99
+ }
100
+
101
+ .drawer-container.open .panel[data-side="bottom"] {
102
+ transform: translateY(0);
103
+ }
104
+
105
+ /* Left drawer */
106
+ .panel[data-side="left"] {
107
+ top: 0;
108
+ left: 0;
109
+ bottom: 0;
110
+ width: var(--drawer-width, 420px);
111
+ max-width: 90vw;
112
+ border-top-right-radius: var(--rel-borders-radius-border-radius-300);
113
+ border-bottom-right-radius: var(--rel-borders-radius-border-radius-300);
114
+ transform: translateX(-100%);
115
+ }
116
+
117
+ .drawer-container.open .panel[data-side="left"] {
118
+ transform: translateX(0);
119
+ }
120
+
121
+ /* Right drawer */
122
+ .panel[data-side="right"] {
123
+ top: 0;
124
+ right: 0;
125
+ bottom: 0;
126
+ width: var(--drawer-width, 420px);
127
+ max-width: 90vw;
128
+ border-top-left-radius: var(--rel-borders-radius-border-radius-300);
129
+ border-bottom-left-radius: var(--rel-borders-radius-border-radius-300);
130
+ transform: translateX(100%);
131
+ }
132
+
133
+ .drawer-container.open .panel[data-side="right"] {
134
+ transform: translateX(0);
135
+ }
136
+
137
+ /* Top drawer */
138
+ .panel[data-side="top"] {
139
+ top: 0;
140
+ left: 0;
141
+ right: 0;
142
+ max-height: var(--drawer-max-height, 80vh);
143
+ border-bottom-left-radius: var(--rel-borders-radius-border-radius-300);
144
+ border-bottom-right-radius: var(--rel-borders-radius-border-radius-300);
145
+ transform: translateY(-100%);
146
+ }
147
+
148
+ .drawer-container.open .panel[data-side="top"] {
149
+ transform: translateY(0);
150
+ }
151
+
152
+ /* Dragging state */
153
+ .panel.dragging {
154
+ transition: none;
155
+ }
156
+
157
+ .overlay.dragging {
158
+ transition: none;
159
+ }
160
+
161
+ /* Handle */
162
+ .handle {
163
+ width: 48px;
164
+ height: 4px;
165
+ background: var(--rel-colors-gray-divider);
166
+ border-radius: var(--rel-borders-radius-border-radius-500);
167
+ margin: var(--rel-spacing-space-sm) auto var(--rel-spacing-space-xs);
168
+ cursor: grab;
169
+ touch-action: none;
170
+ }
171
+
172
+ .handle:active {
173
+ cursor: grabbing;
174
+ }
175
+
176
+ .handle.hidden {
177
+ display: none;
178
+ }
179
+
180
+ /* Header */
181
+ .header {
182
+ display: flex;
183
+ flex-direction: column;
184
+ gap: var(--rel-spacing-space-2xs);
185
+ padding: var(--rel-spacing-space-md) var(--rel-spacing-space-lg);
186
+ border-bottom: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
187
+ }
188
+
189
+ .header.empty {
190
+ display: none;
191
+ }
192
+
193
+ .header-top {
194
+ display: flex;
195
+ align-items: center;
196
+ justify-content: space-between;
197
+ }
198
+
199
+ .title {
200
+ font-size: var(--rel-fontSizes-lg);
201
+ font-weight: var(--rel-fontWeights-semibold);
202
+ color: var(--rel-text-heading);
203
+ margin: 0;
204
+ }
205
+
206
+ .description {
207
+ font-size: var(--rel-fontSizes-sm);
208
+ color: var(--rel-text-primary);
209
+ margin: 0;
210
+ opacity: 0.8;
211
+ }
212
+
213
+ .close-button {
214
+ background: transparent;
215
+ border: none;
216
+ cursor: pointer;
217
+ padding: var(--rel-spacing-space-2xs);
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ border-radius: var(--rel-borders-radius-border-radius-100);
222
+ color: var(--rel-colors-gray-primary, #333);
223
+ transition: background 0.15s ease;
224
+ }
225
+
226
+ .close-button:hover {
227
+ background: var(--rel-surface-secondary, #fafafa);
228
+ }
229
+
230
+ .close-button:active {
231
+ background: var(--rel-colors-gray-divider, #bdbdbd);
232
+ }
233
+
234
+ .close-button:focus-visible {
235
+ outline: 2px solid var(--rel-colors-blue-primary, #0091ea);
236
+ outline-offset: 2px;
237
+ }
238
+
239
+ /* Content */
240
+ .content {
241
+ flex: 1;
242
+ overflow-y: auto;
243
+ padding: var(--rel-spacing-space-lg);
244
+ }
245
+
246
+ /* Footer */
247
+ .footer {
248
+ padding: var(--rel-spacing-space-md) var(--rel-spacing-space-lg);
249
+ border-top: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
250
+ }
251
+
252
+ .footer.empty {
253
+ display: none;
254
+ }
255
+
256
+ /* Reduced motion */
257
+ @media (prefers-reduced-motion: reduce) {
258
+ .overlay,
259
+ .panel {
260
+ transition: none;
261
+ }
262
+
263
+ .drawer-container .panel {
264
+ transform: none;
265
+ }
266
+
267
+ .drawer-container:not(.open) .panel {
268
+ display: none;
269
+ }
270
+ }
271
+ `;
272
+
273
+ connectedCallback() {
274
+ super.connectedCallback();
275
+ if (this.open) {
276
+ this.lockScroll();
277
+ }
278
+ }
279
+
280
+ disconnectedCallback() {
281
+ super.disconnectedCallback();
282
+ this.unlockScroll();
283
+ document.removeEventListener("keydown", this.handleKeydown);
284
+ }
285
+
286
+ updated(changed: Map<string, any>) {
287
+ if (changed.has("open")) {
288
+ if (this.open) {
289
+ this.handleOpen();
290
+ } else {
291
+ this.handleClose();
292
+ }
293
+ }
294
+ }
295
+
296
+ private handleOpen() {
297
+ this.previousFocus = document.activeElement as HTMLElement;
298
+ this.lockScroll();
299
+
300
+ if (this.modal) {
301
+ this.updateComplete.then(() => {
302
+ const focusTarget = this.shadowRoot?.querySelector<HTMLElement>("[autofocus], .close-button, .panel");
303
+ focusTarget?.focus();
304
+ });
305
+ }
306
+
307
+ document.addEventListener("keydown", this.handleKeydown);
308
+
309
+ this.dispatchEvent(
310
+ new CustomEvent("rel-open-change", {
311
+ detail: { open: true },
312
+ bubbles: true,
313
+ composed: true,
314
+ })
315
+ );
316
+ }
317
+
318
+ private handleClose() {
319
+ this.unlockScroll();
320
+ document.removeEventListener("keydown", this.handleKeydown);
321
+
322
+ if (this.previousFocus && this.previousFocus.focus) {
323
+ this.updateComplete.then(() => {
324
+ this.previousFocus?.focus();
325
+ this.previousFocus = null;
326
+ });
327
+ }
328
+
329
+ this.dispatchEvent(
330
+ new CustomEvent("rel-open-change", {
331
+ detail: { open: false },
332
+ bubbles: true,
333
+ composed: true,
334
+ })
335
+ );
336
+ }
337
+
338
+ private lockScroll() {
339
+ if (typeof document === "undefined") return;
340
+
341
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
342
+
343
+ this.originalBodyOverflow = document.body.style.overflow;
344
+ this.originalBodyPaddingRight = document.body.style.paddingRight;
345
+
346
+ document.body.style.overflow = "hidden";
347
+ if (scrollbarWidth > 0) {
348
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
349
+ }
350
+ }
351
+
352
+ private unlockScroll() {
353
+ if (typeof document === "undefined") return;
354
+
355
+ document.body.style.overflow = this.originalBodyOverflow;
356
+ document.body.style.paddingRight = this.originalBodyPaddingRight;
357
+ }
358
+
359
+ private focusPanel() {
360
+ if (!this.panelElement) return;
361
+
362
+ const focusable = this.getFocusableElements();
363
+ if (focusable.length > 0) {
364
+ focusable[0].focus();
365
+ } else {
366
+ this.panelElement.focus();
367
+ }
368
+ }
369
+
370
+ private getFocusableElements(): HTMLElement[] {
371
+ if (!this.panelElement) return [];
372
+
373
+ const selectors = [
374
+ 'a[href]',
375
+ 'button:not([disabled])',
376
+ 'textarea:not([disabled])',
377
+ 'input:not([disabled])',
378
+ 'select:not([disabled])',
379
+ '[tabindex]:not([tabindex="-1"])',
380
+ ].join(",");
381
+
382
+ const elements = Array.from(
383
+ this.panelElement.querySelectorAll(selectors)
384
+ ) as HTMLElement[];
385
+
386
+ // Also check slotted content
387
+ const slots = this.panelElement.querySelectorAll("slot");
388
+ slots.forEach((slot) => {
389
+ const assigned = slot.assignedElements({ flatten: true });
390
+ assigned.forEach((el) => {
391
+ const focusableInSlot = el.querySelectorAll(selectors);
392
+ elements.push(...(Array.from(focusableInSlot) as HTMLElement[]));
393
+ });
394
+ });
395
+
396
+ return elements;
397
+ }
398
+
399
+ private handleKeydown = (e: KeyboardEvent) => {
400
+ if (!this.open) return;
401
+
402
+ if (e.key === "Escape" && this.dismissible && this.closeOnEscape) {
403
+ e.preventDefault();
404
+ this.close("escape");
405
+ return;
406
+ }
407
+
408
+ // Focus trap
409
+ if (e.key === "Tab" && this.modal) {
410
+ const focusable = this.getFocusableElements();
411
+ if (focusable.length === 0) {
412
+ e.preventDefault();
413
+ return;
414
+ }
415
+
416
+ const firstFocusable = focusable[0];
417
+ const lastFocusable = focusable[focusable.length - 1];
418
+ const activeElement = this.shadowRoot?.activeElement || document.activeElement;
419
+
420
+ if (e.shiftKey) {
421
+ if (activeElement === firstFocusable) {
422
+ e.preventDefault();
423
+ lastFocusable.focus();
424
+ }
425
+ } else {
426
+ if (activeElement === lastFocusable) {
427
+ e.preventDefault();
428
+ firstFocusable.focus();
429
+ }
430
+ }
431
+ }
432
+ };
433
+
434
+ private handleOverlayClick(e: MouseEvent) {
435
+ if (!this.dismissible || !this.closeOnBackdrop) return;
436
+
437
+ if (e.target === this.overlayElement) {
438
+ this.close("backdrop");
439
+ }
440
+ }
441
+
442
+ private handleCloseButtonClick() {
443
+ this.close("programmatic");
444
+ }
445
+
446
+ private close(_reason: DrawerCloseReason) {
447
+ this.open = false;
448
+ }
449
+
450
+ // Drag-to-close handlers
451
+ private handlePointerDown = (e: PointerEvent) => {
452
+ if (!this.draggable || this.side !== "bottom") return;
453
+
454
+ this.isDragging = true;
455
+ this.startY = e.clientY;
456
+ this.currentY = e.clientY;
457
+ this.dragOffset = 0;
458
+
459
+ const target = e.target as HTMLElement;
460
+ target.setPointerCapture(e.pointerId);
461
+
462
+ document.addEventListener("pointermove", this.handlePointerMove);
463
+ document.addEventListener("pointerup", this.handlePointerUp);
464
+ };
465
+
466
+ private handlePointerMove = (e: PointerEvent) => {
467
+ if (!this.isDragging) return;
468
+
469
+ this.currentY = e.clientY;
470
+ const deltaY = this.currentY - this.startY;
471
+
472
+ // Only allow dragging down for bottom drawer
473
+ if (deltaY > 0) {
474
+ this.dragOffset = deltaY;
475
+ this.requestUpdate();
476
+ }
477
+ };
478
+
479
+ private handlePointerUp = (e: PointerEvent) => {
480
+ if (!this.isDragging) return;
481
+
482
+ this.isDragging = false;
483
+
484
+ const panelHeight = this.panelElement?.offsetHeight || 0;
485
+ const draggedPercent = this.dragOffset / panelHeight;
486
+
487
+ if (draggedPercent >= this.snapThreshold) {
488
+ this.close("drag");
489
+ }
490
+
491
+ this.dragOffset = 0;
492
+ this.requestUpdate();
493
+
494
+ document.removeEventListener("pointermove", this.handlePointerMove);
495
+ document.removeEventListener("pointerup", this.handlePointerUp);
496
+ };
497
+
498
+ private getPanelStyle() {
499
+ if (!this.isDragging || this.dragOffset === 0) {
500
+ return "";
501
+ }
502
+
503
+ return `transform: translateY(${this.dragOffset}px);`;
504
+ }
505
+
506
+ private getOverlayStyle() {
507
+ if (!this.isDragging || this.dragOffset === 0) {
508
+ return "";
509
+ }
510
+
511
+ const panelHeight = this.panelElement?.offsetHeight || 0;
512
+ const draggedPercent = Math.min(this.dragOffset / panelHeight, 1);
513
+ const opacity = 0.5 * (1 - draggedPercent);
514
+
515
+ return `opacity: ${opacity};`;
516
+ }
517
+
518
+ private hasSlotContent(slotName?: string): boolean {
519
+ const slot = this.shadowRoot?.querySelector(
520
+ slotName ? `slot[name="${slotName}"]` : "slot:not([name])"
521
+ ) as HTMLSlotElement;
522
+ return slot ? slot.assignedElements({ flatten: true }).length > 0 : false;
523
+ }
524
+
525
+ render() {
526
+ const hasTitle = !!this.title;
527
+ const hasDescription = !!this.description;
528
+ const hasFooter = this.hasSlotContent("footer");
529
+ const showCloseButton = this.dismissible;
530
+ const hasHeader = hasTitle || hasDescription || showCloseButton;
531
+
532
+ const showHandleBar =
533
+ this.showHandle && this.side === "bottom" && this.draggable;
534
+
535
+ return html`
536
+ <div class="drawer-container ${this.open ? "open" : ""}">
537
+ <div
538
+ part="overlay"
539
+ class="overlay ${this.isDragging ? "dragging" : ""}"
540
+ style="${this.getOverlayStyle()}"
541
+ @click="${this.handleOverlayClick}"></div>
542
+
543
+ <div
544
+ part="panel"
545
+ class="panel ${this.isDragging ? "dragging" : ""}"
546
+ data-side="${this.side}"
547
+ style="${this.width ? `--drawer-width: ${this.width};` : ''}${this.maxHeight ? `--drawer-max-height: ${this.maxHeight};` : ''}${this.getPanelStyle()}"
548
+ role="${this.modal ? "dialog" : "region"}"
549
+ aria-modal="${this.modal}"
550
+ tabindex="-1">
551
+ ${showHandleBar
552
+ ? html`<div
553
+ part="handle"
554
+ class="handle"
555
+ @pointerdown="${this.handlePointerDown}"></div>`
556
+ : ""}
557
+
558
+ <div part="header" class="header ${!hasHeader ? "empty" : ""}">
559
+ ${hasTitle || hasDescription || showCloseButton
560
+ ? html`
561
+ <div class="header-top">
562
+ <div>
563
+ ${hasTitle
564
+ ? html`<h2 class="title">${this.title}</h2>`
565
+ : ""}
566
+ ${hasDescription
567
+ ? html`<p class="description">${this.description}</p>`
568
+ : ""}
569
+ </div>
570
+ ${showCloseButton
571
+ ? html`<button
572
+ part="close-button"
573
+ class="close-button"
574
+ @click="${this.handleCloseButtonClick}"
575
+ aria-label="Close">
576
+ <svg
577
+ width="16"
578
+ height="16"
579
+ viewBox="0 0 16 16"
580
+ fill="none"
581
+ xmlns="http://www.w3.org/2000/svg">
582
+ <path
583
+ d="M12.8 4.27L11.73 3.2L8 6.93L4.27 3.2L3.2 4.27L6.93 8L3.2 11.73L4.27 12.8L8 9.07L11.73 12.8L12.8 11.73L9.07 8L12.8 4.27Z"
584
+ fill="currentColor" />
585
+ </svg>
586
+ </button>`
587
+ : ""}
588
+ </div>
589
+ `
590
+ : ""}
591
+ </div>
592
+
593
+ <div part="content" class="content">
594
+ <slot></slot>
595
+ </div>
596
+
597
+ ${hasFooter
598
+ ? html`<div part="footer" class="footer">
599
+ <rel-drawer-footer><slot name="footer"></slot></rel-drawer-footer>
600
+ </div>`
601
+ : ""}
602
+ </div>
603
+ </div>
604
+ `;
605
+ }
606
+ }
607
+
608
+ declare global {
609
+ interface HTMLElementTagNameMap {
610
+ "rel-drawer": RelDrawer;
611
+ }
612
+ }