@reliability-design/web 1.0.3 → 1.0.6

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