@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,568 @@
1
+ # rel-popover
2
+
3
+ A flexible popover component that displays rich content anchored to a trigger element. Features smart positioning with collision detection, controlled and uncontrolled modes, focus management, and accessibility support.
4
+
5
+ ## What It Is
6
+
7
+ The popover consists of two custom elements:
8
+ - `<rel-popover>` - Root controller that manages state, positioning, and interactions
9
+ - `<rel-popover-content>` - Optional styled container for popover content with consistent design tokens
10
+
11
+ ## Basic Usage
12
+
13
+ ```html
14
+ <rel-popover>
15
+ <button slot="trigger">Open Popover</button>
16
+ <rel-popover-content>
17
+ <h3>Popover Title</h3>
18
+ <p>This is the popover content. It can contain any HTML elements.</p>
19
+ </rel-popover-content>
20
+ </rel-popover>
21
+ ```
22
+
23
+ ## Controlled Mode
24
+
25
+ Control the open state externally:
26
+
27
+ ```html
28
+ <rel-popover id="my-popover" open>
29
+ <button slot="trigger">Open Popover</button>
30
+ <rel-popover-content>
31
+ <p>Controlled popover content</p>
32
+ <button data-popover-close>Close</button>
33
+ </rel-popover-content>
34
+ </rel-popover>
35
+
36
+ <script>
37
+ const popover = document.getElementById('my-popover');
38
+
39
+ // Listen to open state changes
40
+ popover.addEventListener('rel-open-change', (e) => {
41
+ console.log('Popover is now:', e.detail.open ? 'open' : 'closed');
42
+
43
+ // Update external state
44
+ // You control when to update the 'open' attribute
45
+ });
46
+
47
+ // Programmatically control
48
+ document.getElementById('external-btn').addEventListener('click', () => {
49
+ popover.open = true;
50
+ });
51
+ </script>
52
+ ```
53
+
54
+ ## Positioning Options
55
+
56
+ ```html
57
+ <!-- Position above trigger, aligned to start -->
58
+ <rel-popover side="top" align="start">
59
+ <button slot="trigger">Top Start</button>
60
+ <rel-popover-content>
61
+ <p>Positioned above, aligned to start</p>
62
+ </rel-popover-content>
63
+ </rel-popover>
64
+
65
+ <!-- Position to the right with offset -->
66
+ <rel-popover side="right" sideOffset="16" alignOffset="8">
67
+ <button slot="trigger">Right with Offset</button>
68
+ <rel-popover-content>
69
+ <p>Positioned to right with custom spacing</p>
70
+ </rel-popover-content>
71
+ </rel-popover>
72
+ ```
73
+
74
+ ## Form Example with Focus Trap
75
+
76
+ ```html
77
+ <rel-popover trapFocus>
78
+ <button slot="trigger">Edit Profile</button>
79
+ <rel-popover-content aria-label="Edit profile form">
80
+ <h3>Edit Profile</h3>
81
+ <form>
82
+ <label>
83
+ Name:
84
+ <input type="text" value="John Doe" />
85
+ </label>
86
+ <label>
87
+ Email:
88
+ <input type="email" value="john@example.com" />
89
+ </label>
90
+ <div style="display: flex; gap: 8px; margin-top: 12px;">
91
+ <button type="submit">Save</button>
92
+ <button type="button" data-popover-close>Cancel</button>
93
+ </div>
94
+ </form>
95
+ </rel-popover-content>
96
+ </rel-popover>
97
+ ```
98
+
99
+ ## API Reference
100
+
101
+ ### rel-popover
102
+
103
+ Root controller component that manages popover behavior.
104
+
105
+ **Attributes:**
106
+
107
+ - `open` (boolean, optional) - Controlled open state. When set, component is in controlled mode
108
+ - `defaultOpen` (boolean) - Initial open state for uncontrolled mode, default: `false`
109
+ - `disabled` (boolean) - Disables the popover, default: `false`
110
+ - `side` ("top" | "right" | "bottom" | "left") - Preferred side to position popover, default: `"bottom"`
111
+ - `align` ("start" | "center" | "end") - Alignment relative to trigger, default: `"center"`
112
+ - `sideOffset` (number) - Distance from trigger in pixels, default: `8`
113
+ - `alignOffset` (number) - Offset along the alignment axis in pixels, default: `0`
114
+ - `trapFocus` (boolean) - Trap focus within popover (useful for forms), default: `false`
115
+ - `closeOnEscape` (boolean) - Close when Escape key is pressed, default: `true`
116
+ - `closeOnOutsideClick` (boolean) - Close when clicking outside, default: `true`
117
+
118
+ **Events:**
119
+
120
+ - `rel-open-change` - Emitted when open state changes
121
+ ```typescript
122
+ detail: { open: boolean }
123
+ ```
124
+
125
+ **Slots:**
126
+
127
+ - `trigger` (named) - The trigger element (usually a button)
128
+ - Default slot - The popover content
129
+
130
+ **Example:**
131
+ ```javascript
132
+ const popover = document.querySelector('rel-popover');
133
+
134
+ // Listen to state changes
135
+ popover.addEventListener('rel-open-change', (e) => {
136
+ console.log('Open:', e.detail.open);
137
+ });
138
+
139
+ // Programmatic control (controlled mode)
140
+ popover.open = true;
141
+ ```
142
+
143
+ ### rel-popover-content
144
+
145
+ Styled container for popover content with consistent design tokens.
146
+
147
+ **Attributes:**
148
+
149
+ - `aria-label` (string) - Accessible label for the popover dialog
150
+
151
+ **Usage:**
152
+ ```html
153
+ <rel-popover-content aria-label="User settings">
154
+ <h3>Settings</h3>
155
+ <p>Configure your preferences</p>
156
+ </rel-popover-content>
157
+ ```
158
+
159
+ **Styling:**
160
+ The content component provides consistent styling:
161
+ - White background with border and shadow
162
+ - Proper padding and spacing
163
+ - Heading and paragraph styles with design tokens
164
+ - Maximum width of 400px (can be overridden)
165
+
166
+ **Custom Content:**
167
+ You don't have to use `rel-popover-content`. Any HTML can go in the default slot:
168
+ ```html
169
+ <rel-popover>
170
+ <button slot="trigger">Open</button>
171
+ <div class="my-custom-popover">
172
+ Custom styled content
173
+ </div>
174
+ </rel-popover>
175
+ ```
176
+
177
+ ## Controlled vs Uncontrolled
178
+
179
+ ### Uncontrolled (Default)
180
+ Component manages its own open state:
181
+ ```html
182
+ <rel-popover defaultOpen>
183
+ <button slot="trigger">Toggle</button>
184
+ <rel-popover-content>Content</rel-popover-content>
185
+ </rel-popover>
186
+ ```
187
+
188
+ The component will:
189
+ - Toggle open/close when trigger is clicked
190
+ - Emit `rel-open-change` events
191
+ - Update its internal state automatically
192
+
193
+ ### Controlled
194
+ You manage the open state externally:
195
+ ```html
196
+ <rel-popover id="controlled" open="${this.isOpen}">
197
+ <button slot="trigger">Toggle</button>
198
+ <rel-popover-content>Content</rel-popover-content>
199
+ </rel-popover>
200
+
201
+ <script>
202
+ let isOpen = false;
203
+ const popover = document.getElementById('controlled');
204
+
205
+ popover.addEventListener('rel-open-change', (e) => {
206
+ isOpen = e.detail.open;
207
+ popover.open = isOpen; // Update the attribute
208
+ });
209
+ </script>
210
+ ```
211
+
212
+ When `open` attribute is set, the component:
213
+ - Will NOT update its own state
214
+ - Emits `rel-open-change` events
215
+ - You must update the `open` attribute in response to events
216
+
217
+ ## Positioning Behavior
218
+
219
+ The popover uses smart positioning with collision detection:
220
+
221
+ ### Side Preference
222
+ Specify the preferred side with the `side` attribute:
223
+ - `"bottom"` (default) - Opens below trigger
224
+ - `"top"` - Opens above trigger
225
+ - `"right"` - Opens to the right
226
+ - `"left"` - Opens to the left
227
+
228
+ ### Collision Detection
229
+ If the popover would overflow the viewport, it automatically flips to the opposite side:
230
+ - Bottom → Top (if more space above)
231
+ - Top → Bottom (if more space below)
232
+ - Right → Left (if more space on left)
233
+ - Left → Right (if more space on right)
234
+
235
+ ### Alignment
236
+ Control how the popover aligns to its trigger:
237
+
238
+ For top/bottom sides:
239
+ - `"start"` - Aligns to left edge of trigger
240
+ - `"center"` (default) - Centers horizontally
241
+ - `"end"` - Aligns to right edge of trigger
242
+
243
+ For left/right sides:
244
+ - `"start"` - Aligns to top edge of trigger
245
+ - `"center"` (default) - Centers vertically
246
+ - `"end"` - Aligns to bottom edge of trigger
247
+
248
+ ### Offsets
249
+ Fine-tune positioning with offsets:
250
+ - `sideOffset` - Distance from trigger (default: 8px)
251
+ - `alignOffset` - Shift along alignment axis (default: 0)
252
+
253
+ ```html
254
+ <!-- 16px away from trigger, shifted 8px to the right -->
255
+ <rel-popover side="bottom" sideOffset="16" alignOffset="8">
256
+ <button slot="trigger">Custom Offset</button>
257
+ <rel-popover-content>Content</rel-popover-content>
258
+ </rel-popover>
259
+ ```
260
+
261
+ ### Dynamic Repositioning
262
+ The popover automatically repositions when:
263
+ - Window is resized
264
+ - Page is scrolled
265
+ - Popover content size changes
266
+
267
+ ## Close Triggers
268
+
269
+ The popover can close in several ways:
270
+
271
+ 1. **Click Outside** (default enabled)
272
+ - Click anywhere outside the popover
273
+ - Controlled by `closeOnOutsideClick` attribute
274
+
275
+ 2. **Escape Key** (default enabled)
276
+ - Press Escape to close
277
+ - Controlled by `closeOnEscape` attribute
278
+
279
+ 3. **Close Button**
280
+ - Add `data-popover-close` to any element
281
+ ```html
282
+ <button data-popover-close>Close</button>
283
+ <button type="button" data-popover-close>Cancel</button>
284
+ ```
285
+
286
+ 4. **Programmatic**
287
+ ```javascript
288
+ popover.open = false;
289
+ ```
290
+
291
+ ## Focus Management
292
+
293
+ ### Default Behavior
294
+ When popover opens:
295
+ 1. Saves currently focused element
296
+ 2. Focuses first focusable element in popover
297
+ 3. Returns focus to trigger when closed
298
+
299
+ ### Focus Trap
300
+ Enable focus trapping for forms and modal-like behavior:
301
+ ```html
302
+ <rel-popover trapFocus>
303
+ <button slot="trigger">Edit</button>
304
+ <rel-popover-content>
305
+ <form>
306
+ <input type="text" />
307
+ <button>Save</button>
308
+ <button data-popover-close>Cancel</button>
309
+ </form>
310
+ </rel-popover-content>
311
+ </rel-popover>
312
+ ```
313
+
314
+ With `trapFocus` enabled:
315
+ - Tab cycles through focusable elements
316
+ - When reaching last element, Tab wraps to first
317
+ - Shift+Tab cycles backward
318
+ - Cannot tab out of the popover
319
+
320
+ ## Keyboard Navigation
321
+
322
+ | Key | Action |
323
+ |-----|--------|
324
+ | `Click Trigger` | Toggle popover open/close |
325
+ | `Escape` | Close popover (if `closeOnEscape` is true) |
326
+ | `Tab` | Move to next focusable element |
327
+ | `Shift + Tab` | Move to previous focusable element |
328
+ | `Tab` (with `trapFocus`) | Cycle through elements, wrapping at edges |
329
+
330
+ ## Accessibility
331
+
332
+ The component implements WCAG accessibility guidelines:
333
+
334
+ ### ARIA Attributes
335
+ - Trigger has `aria-haspopup="dialog"`
336
+ - Trigger has `aria-expanded` reflecting open state
337
+ - Trigger has `aria-controls` pointing to content
338
+ - Content has `role="dialog"`
339
+ - Content can have `aria-label` for screen readers
340
+
341
+ ### Focus Management
342
+ - Automatically focuses first focusable element
343
+ - Returns focus to trigger on close
344
+ - Optional focus trapping for modal behavior
345
+
346
+ ### Keyboard Support
347
+ - Full keyboard navigation
348
+ - Escape key to close
349
+ - Tab navigation through content
350
+
351
+ ### Best Practices
352
+ ```html
353
+ <!-- Good: Descriptive trigger and labeled content -->
354
+ <rel-popover>
355
+ <button slot="trigger" aria-label="Open user settings">
356
+ <svg>...</svg>
357
+ </button>
358
+ <rel-popover-content aria-label="User settings">
359
+ <h3>Settings</h3>
360
+ <!-- ... -->
361
+ </rel-popover-content>
362
+ </rel-popover>
363
+
364
+ <!-- Good: Close button with clear label -->
365
+ <button type="button" data-popover-close aria-label="Close settings">
366
+ ×
367
+ </button>
368
+ ```
369
+
370
+ ## Styling Tokens
371
+
372
+ The component uses design tokens from the Reliability Design System:
373
+
374
+ ### Colors (rel-popover-content)
375
+ - `--rel-colors-white` - Content background
376
+ - `--rel-colors-gray-border` - Border color
377
+ - `--rel-colors-gray-heading` - Text color
378
+ - `--rel-colors-gray-placeholder` - Paragraph text color
379
+
380
+ ### Typography
381
+ - `--rel-fontFamilies-primary` - Font family (Poppins)
382
+ - `--rel-fontWeights-semibold` - Heading weight
383
+ - `--rel-fontSizes-sm` - Base text size
384
+ - `--rel-lineHeights-md` - Paragraph line height
385
+
386
+ ### Spacing
387
+ - `--rel-spacing-space-md` (16px) - Content padding
388
+ - `--rel-spacing-space-xs` (8px) - Heading bottom margin
389
+ - `--rel-spacing-space-sm` (12px) - Paragraph bottom margin
390
+
391
+ ### Borders & Effects
392
+ - `--rel-borders-width-stroke-100` (1px) - Border width
393
+ - `--rel-borders-radius-border-radius-200` (8px) - Border radius
394
+ - `--rel-shadows-shadow-300` - Drop shadow
395
+
396
+ ### Z-Index
397
+ - Popover content: `z-index: 1001`
398
+
399
+ ### Custom Styling
400
+ Use CSS parts to style the component:
401
+ ```css
402
+ /* Style the trigger wrapper */
403
+ rel-popover::part(trigger) {
404
+ display: block;
405
+ width: 100%;
406
+ }
407
+
408
+ /* Style the content wrapper */
409
+ rel-popover::part(content-wrapper) {
410
+ /* Custom styles */
411
+ }
412
+
413
+ /* Style the content container */
414
+ rel-popover-content::part(content) {
415
+ max-width: 600px;
416
+ padding: 24px;
417
+ }
418
+ ```
419
+
420
+ ## Complete Example
421
+
422
+ ```html
423
+ <!DOCTYPE html>
424
+ <html lang="en">
425
+ <head>
426
+ <meta charset="UTF-8">
427
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
428
+ <title>Popover Demo</title>
429
+ <script type="module" src="./dist/index.js"></script>
430
+ </head>
431
+ <body>
432
+ <div style="padding: 100px;">
433
+ <!-- Basic Popover -->
434
+ <rel-popover id="basic-popover">
435
+ <button slot="trigger">Open Popover</button>
436
+ <rel-popover-content aria-label="Information">
437
+ <h3>About Popovers</h3>
438
+ <p>Popovers are floating containers that display rich content anchored to a trigger element.</p>
439
+ <button data-popover-close>Got it</button>
440
+ </rel-popover-content>
441
+ </rel-popover>
442
+
443
+ <!-- Positioning Variations -->
444
+ <rel-popover side="top" align="start">
445
+ <button slot="trigger">Top Start</button>
446
+ <rel-popover-content>
447
+ <p>Positioned above, aligned to start</p>
448
+ </rel-popover-content>
449
+ </rel-popover>
450
+
451
+ <rel-popover side="right" align="center">
452
+ <button slot="trigger">Right Center</button>
453
+ <rel-popover-content>
454
+ <p>Positioned to the right, centered</p>
455
+ </rel-popover-content>
456
+ </rel-popover>
457
+
458
+ <!-- Form with Focus Trap -->
459
+ <rel-popover id="form-popover" trapFocus>
460
+ <button slot="trigger">Edit Profile</button>
461
+ <rel-popover-content aria-label="Edit profile form">
462
+ <h3>Edit Profile</h3>
463
+ <form id="profile-form">
464
+ <div style="margin-bottom: 12px;">
465
+ <label>
466
+ Name:<br>
467
+ <input type="text" name="name" value="John Doe" style="width: 100%;">
468
+ </label>
469
+ </div>
470
+ <div style="margin-bottom: 12px;">
471
+ <label>
472
+ Email:<br>
473
+ <input type="email" name="email" value="john@example.com" style="width: 100%;">
474
+ </label>
475
+ </div>
476
+ <div style="display: flex; gap: 8px;">
477
+ <button type="submit">Save</button>
478
+ <button type="button" data-popover-close>Cancel</button>
479
+ </div>
480
+ </form>
481
+ </rel-popover-content>
482
+ </rel-popover>
483
+
484
+ <!-- Controlled Mode -->
485
+ <div>
486
+ <button id="external-open">Open Controlled Popover</button>
487
+ <button id="external-close">Close Controlled Popover</button>
488
+ <p>State: <span id="state-display">closed</span></p>
489
+
490
+ <rel-popover id="controlled-popover">
491
+ <button slot="trigger">Controlled Trigger</button>
492
+ <rel-popover-content>
493
+ <h3>Controlled Popover</h3>
494
+ <p>This popover's state is controlled externally.</p>
495
+ <button data-popover-close>Close</button>
496
+ </rel-popover-content>
497
+ </rel-popover>
498
+ </div>
499
+ </div>
500
+
501
+ <script>
502
+ // Event logging
503
+ document.getElementById('basic-popover').addEventListener('rel-open-change', (e) => {
504
+ console.log('Basic popover:', e.detail.open ? 'opened' : 'closed');
505
+ });
506
+
507
+ // Form handling
508
+ document.getElementById('profile-form').addEventListener('submit', (e) => {
509
+ e.preventDefault();
510
+ const formData = new FormData(e.target);
511
+ console.log('Saved:', Object.fromEntries(formData));
512
+ document.getElementById('form-popover').open = false;
513
+ });
514
+
515
+ // Controlled mode
516
+ const controlledPopover = document.getElementById('controlled-popover');
517
+ const stateDisplay = document.getElementById('state-display');
518
+
519
+ let isOpen = false;
520
+
521
+ controlledPopover.addEventListener('rel-open-change', (e) => {
522
+ isOpen = e.detail.open;
523
+ controlledPopover.open = isOpen;
524
+ stateDisplay.textContent = isOpen ? 'open' : 'closed';
525
+ });
526
+
527
+ document.getElementById('external-open').addEventListener('click', () => {
528
+ isOpen = true;
529
+ controlledPopover.open = isOpen;
530
+ stateDisplay.textContent = 'open';
531
+ });
532
+
533
+ document.getElementById('external-close').addEventListener('click', () => {
534
+ isOpen = false;
535
+ controlledPopover.open = isOpen;
536
+ stateDisplay.textContent = 'closed';
537
+ });
538
+ </script>
539
+ </body>
540
+ </html>
541
+ ```
542
+
543
+ ## Browser Support
544
+
545
+ Compatible with all modern browsers supporting:
546
+ - Web Components (Custom Elements v1)
547
+ - Shadow DOM
548
+ - ES2020+ features
549
+ - ResizeObserver API
550
+
551
+ ## Framework Integration
552
+
553
+ Works seamlessly with:
554
+ - Vanilla JavaScript
555
+ - Angular
556
+ - React (via wrapper)
557
+ - Vue
558
+ - Any framework supporting Web Components
559
+
560
+ ## Comparison with Tooltip
561
+
562
+ While `rel-tooltip` is for brief labels on hover, `rel-popover` is for:
563
+ - **Rich interactive content** - Forms, menus, complex layouts
564
+ - **Explicit user action** - Click to open (not hover)
565
+ - **Focus management** - Can trap focus for forms
566
+ - **Controlled state** - External state management support
567
+
568
+ Use `rel-tooltip` for simple labels, use `rel-popover` for interactive content.
@@ -0,0 +1,2 @@
1
+ export * from "./rel-popover";
2
+ export * from "./rel-popover-content";
@@ -0,0 +1,55 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+
4
+ @customElement("rel-popover-content")
5
+ export class RelPopoverContent extends LitElement {
6
+ @property({ type: String, attribute: "aria-label" }) ariaLabel = "";
7
+
8
+ static styles = css`
9
+ :host {
10
+ display: block;
11
+ width: 100%;
12
+ max-width: 400px;
13
+ }
14
+
15
+ .popover-content {
16
+ background: var(--rel-colors-white);
17
+ border: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
18
+ border-radius: var(--rel-borders-radius-border-radius-200);
19
+ box-shadow: var(--rel-shadows-shadow-300);
20
+ padding: var(--rel-spacing-space-md);
21
+ font-family: var(--rel-fontFamilies-primary);
22
+ font-size: var(--rel-fontSizes-sm);
23
+ color: var(--rel-colors-gray-heading);
24
+ }
25
+
26
+ .popover-content ::slotted(h1),
27
+ .popover-content ::slotted(h2),
28
+ .popover-content ::slotted(h3),
29
+ .popover-content ::slotted(h4),
30
+ .popover-content ::slotted(h5),
31
+ .popover-content ::slotted(h6) {
32
+ margin: 0 0 var(--rel-spacing-space-xs);
33
+ font-weight: var(--rel-fontWeights-semibold);
34
+ color: var(--rel-colors-gray-heading);
35
+ }
36
+
37
+ .popover-content ::slotted(p) {
38
+ margin: 0 0 var(--rel-spacing-space-sm);
39
+ color: var(--rel-colors-gray-placeholder);
40
+ line-height: var(--rel-lineHeights-md);
41
+ }
42
+
43
+ .popover-content ::slotted(p:last-child) {
44
+ margin-bottom: 0;
45
+ }
46
+ `;
47
+
48
+ render() {
49
+ return html`
50
+ <div class="popover-content" part="content" role="dialog" aria-label="${this.ariaLabel}">
51
+ <slot></slot>
52
+ </div>
53
+ `;
54
+ }
55
+ }