@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
@@ -21,6 +21,13 @@ export class RelRadio extends LitElement {
21
21
  @property({ type: Boolean })
22
22
  disabled = false;
23
23
 
24
+ @property({ type: String }) value = "";
25
+
26
+ checked = false;
27
+
28
+ private _onChange: (value: string) => void = () => {};
29
+ private _onTouched: () => void = () => {};
30
+
24
31
  static styles = css`
25
32
  :host {
26
33
  display: block;
@@ -60,27 +67,35 @@ export class RelRadio extends LitElement {
60
67
  .radio-custom {
61
68
  width: 20px;
62
69
  height: 20px;
63
- border: 2px solid var(--rel-colors-black);
70
+ border: 2px solid var(--rel-text-heading, #2b2b2b);
64
71
  border-radius: 50%;
65
72
  position: relative;
73
+ flex-shrink: 0;
74
+ }
75
+
76
+ input:focus-visible + .radio-custom {
77
+ outline: 2px solid var(--rel-border-info, #0091ea);
78
+ outline-offset: 2px;
66
79
  }
67
80
 
68
81
  input:checked + .radio-custom::after {
69
82
  content: "";
70
83
  position: absolute;
71
84
  inset: 4px;
72
- background: var(--rel-colors-black);
85
+ background: var(--rel-text-heading, #2b2b2b);
73
86
  border-radius: 50%;
74
87
  }
75
88
 
76
89
  .radio-label {
77
- font-size: var(--rel-fontSizes-sm);
78
- font-weight: var(--rel-fontWeights-medium);
90
+ font-size: var(--rel-fontSizes-sm, 12px);
91
+ font-weight: var(--rel-fontWeights-medium, 500);
92
+ color: var(--rel-text-primary, #333);
79
93
  }
80
94
 
81
95
  .radio-supporting {
82
- font-size: var(--rel-fontSizes-xs);
96
+ font-size: var(--rel-fontSizes-xs, 10px);
83
97
  margin-left: 28px;
98
+ color: var(--rel-text-placeholder, #a9a9a9);
84
99
  }
85
100
 
86
101
  .error .radio-custom {
@@ -97,13 +112,22 @@ export class RelRadio extends LitElement {
97
112
 
98
113
  this.selectedValue = value;
99
114
  this.dispatchEvent(
100
- new CustomEvent("changed", {
115
+ new CustomEvent("rel-change", {
116
+ detail: {
117
+ checked: this.selectedValue === value,
118
+ value,
119
+ },
101
120
  bubbles: true,
102
121
  composed: true,
103
122
  })
104
123
  );
124
+ this._onChange(this.value);
105
125
  }
106
126
 
127
+ registerOnChange(fn: (value: string) => void): void { this._onChange = fn; }
128
+ registerOnTouched(fn: () => void): void { this._onTouched = fn; }
129
+ writeValue(value: string): void { this.checked = value === this.value; }
130
+
107
131
  private renderOption(option: RadioOption) {
108
132
  const isChecked = this.selectedValue === option.value;
109
133
  const isDisabled = this.disabled || option.disabled;
@@ -125,6 +149,7 @@ export class RelRadio extends LitElement {
125
149
  .checked="${isChecked}"
126
150
  ?disabled="${isDisabled}"
127
151
  @change="${() => this.onChange(option.value)}"
152
+ @blur="${() => this._onTouched()}"
128
153
  />
129
154
  <span class="radio-custom"></span>
130
155
  <span class="radio-label">${option.label}</span>
@@ -0,0 +1,140 @@
1
+ # Resizable Components
2
+
3
+ A flexible resizable panel system built with Lit web components for the Reliability Design System.
4
+
5
+ ## Components
6
+
7
+ ### `<rel-resizable-group>`
8
+ Container for resizable panels. Manages layout direction and coordinates resizing between panels.
9
+
10
+ **Attributes:**
11
+ - `direction` (string): Layout direction - `"horizontal"` (default) or `"vertical"`
12
+
13
+ **Events:**
14
+ - `rel-resize`: Emitted during resize with detail `{ direction, sizes: number[] }`
15
+ - `rel-resize-end`: Emitted when resize completes with detail `{ direction, sizes: number[] }`
16
+
17
+ ### `<rel-resizable-panel>`
18
+ Individual resizable panel within a group.
19
+
20
+ **Attributes:**
21
+ - `defaultsize` (number): Initial size in percentage (optional)
22
+ - `minsize` (number): Minimum size in percentage (default: 10)
23
+ - `maxsize` (number): Maximum size in percentage (default: 90)
24
+
25
+ ### `<rel-resizable-handle>`
26
+ Draggable separator between panels.
27
+
28
+ **Features:**
29
+ - Pointer drag support (click and drag to resize)
30
+ - Keyboard navigation:
31
+ - Horizontal: ArrowLeft/ArrowRight (2% step, 5% with Shift)
32
+ - Vertical: ArrowUp/ArrowDown (2% step, 5% with Shift)
33
+ - Accessible with `role="separator"` and ARIA attributes
34
+ - Visual feedback on hover/focus/drag
35
+
36
+ ## Usage
37
+
38
+ ### Basic Horizontal Layout
39
+ ```html
40
+ <rel-resizable-group direction="horizontal">
41
+ <rel-resizable-panel defaultsize="50">
42
+ Left Panel
43
+ </rel-resizable-panel>
44
+ <rel-resizable-handle></rel-resizable-handle>
45
+ <rel-resizable-panel defaultsize="50">
46
+ Right Panel
47
+ </rel-resizable-panel>
48
+ </rel-resizable-group>
49
+ ```
50
+
51
+ ### Vertical Layout
52
+ ```html
53
+ <rel-resizable-group direction="vertical">
54
+ <rel-resizable-panel defaultsize="40">
55
+ Top Panel
56
+ </rel-resizable-panel>
57
+ <rel-resizable-handle></rel-resizable-handle>
58
+ <rel-resizable-panel defaultsize="60">
59
+ Bottom Panel
60
+ </rel-resizable-panel>
61
+ </rel-resizable-group>
62
+ ```
63
+
64
+ ### Nested Layout
65
+ ```html
66
+ <rel-resizable-group direction="horizontal">
67
+ <rel-resizable-panel defaultsize="33">
68
+ Left Sidebar
69
+ </rel-resizable-panel>
70
+ <rel-resizable-handle></rel-resizable-handle>
71
+ <rel-resizable-panel defaultsize="67">
72
+ <rel-resizable-group direction="vertical">
73
+ <rel-resizable-panel defaultsize="50">
74
+ Top Content
75
+ </rel-resizable-panel>
76
+ <rel-resizable-handle></rel-resizable-handle>
77
+ <rel-resizable-panel defaultsize="50">
78
+ Bottom Content
79
+ </rel-resizable-panel>
80
+ </rel-resizable-group>
81
+ </rel-resizable-panel>
82
+ </rel-resizable-group>
83
+ ```
84
+
85
+ ### With Constraints
86
+ ```html
87
+ <rel-resizable-group direction="horizontal">
88
+ <rel-resizable-panel defaultsize="30" minsize="20" maxsize="50">
89
+ Constrained Panel
90
+ </rel-resizable-panel>
91
+ <rel-resizable-handle></rel-resizable-handle>
92
+ <rel-resizable-panel defaultsize="70">
93
+ Main Panel
94
+ </rel-resizable-panel>
95
+ </rel-resizable-group>
96
+ ```
97
+
98
+ ## How It Works
99
+
100
+ ### Size Calculation
101
+ 1. **Initialization**: On first render, the group reads all direct child panels and handles
102
+ 2. **Default Sizes**: If panels specify `defaultsize`, those are used; otherwise space is distributed evenly
103
+ 3. **Normalization**: All sizes are normalized to total 100% across all panels
104
+ 4. **Percentage-Based**: Sizes are always in percentages, ensuring responsive layouts
105
+
106
+ ### Resizing Behavior
107
+ - **Pointer Drag**: Click and drag a handle to resize adjacent panels
108
+ - **Keyboard**: Focus a handle and use arrow keys (direction-aware)
109
+ - **Constraints**: Min/max sizes are enforced; total percentage is always preserved
110
+ - **Pair Adjustment**: Only the two panels adjacent to a handle are resized
111
+
112
+ ### Events
113
+ - Use `rel-resize` to track live resizing (useful for updating dependent content)
114
+ - Use `rel-resize-end` to react after resize completes (useful for saving preferences)
115
+
116
+ ## Accessibility
117
+
118
+ - Handles are keyboard focusable with `tabindex="0"`
119
+ - `role="separator"` with appropriate `aria-orientation`
120
+ - `aria-valuenow`, `aria-valuemin`, `aria-valuemax` reflect current split
121
+ - Visual focus indicators using design tokens
122
+ - Standard cursor changes (col-resize/row-resize)
123
+
124
+ ## Styling
125
+
126
+ All styling uses Reliability Design System tokens:
127
+ - Borders: `--rel-borders-width-stroke-100`
128
+ - Colors: `--rel-colors-gray-*`, `--rel-colors-blue-primary`, `--rel-colors-white`
129
+ - Spacing: `--rel-spacing-space-*`
130
+ - Border radius: `--rel-borders-radius-border-radius-*`
131
+
132
+ Panels have `overflow: auto` by default for scrollable content.
133
+
134
+ ## Limitations
135
+
136
+ 1. **Direct Children Only**: Panels and handles must be direct children of the group
137
+ 2. **Handle Placement**: Handles should be placed between panels in the expected order
138
+ 3. **No Collapse**: Panels cannot collapse to 0% (use min/max to control)
139
+ 4. **Single Direction**: Each group has one direction; nest groups for complex layouts
140
+ 5. **Manual Updates**: Group doesn't automatically observe DOM changes after initialization
@@ -0,0 +1,3 @@
1
+ export * from "./rel-resizable-group";
2
+ export * from "./rel-resizable-panel";
3
+ export * from "./rel-resizable-handle";
@@ -0,0 +1,374 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import type { RelResizablePanel } from "./rel-resizable-panel";
4
+ import type { RelResizableHandle } from "./rel-resizable-handle";
5
+
6
+ @customElement("rel-resizable-group")
7
+ export class RelResizableGroup extends LitElement {
8
+ /* =======================
9
+ * PUBLIC API
10
+ * ======================= */
11
+
12
+ @property({ type: String }) direction: "horizontal" | "vertical" = "horizontal";
13
+
14
+ /* =======================
15
+ * INTERNAL STATE
16
+ * ======================= */
17
+
18
+ private panels: RelResizablePanel[] = [];
19
+ private handles: RelResizableHandle[] = [];
20
+ private isDragging = false;
21
+ private activeHandleIndex = -1;
22
+ private startPointerPos = 0;
23
+ private startSizes: number[] = [];
24
+
25
+ /* =======================
26
+ * STYLES
27
+ * ======================= */
28
+
29
+ static styles = css`
30
+ :host {
31
+ display: flex;
32
+ width: 100%;
33
+ height: 100%;
34
+ position: relative;
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ :host([direction="horizontal"]) {
39
+ flex-direction: row;
40
+ }
41
+
42
+ :host([direction="vertical"]) {
43
+ flex-direction: column;
44
+ }
45
+
46
+ ::slotted(rel-resizable-panel) {
47
+ flex-shrink: 0;
48
+ }
49
+ `;
50
+
51
+ /* =======================
52
+ * LIFECYCLE
53
+ * ======================= */
54
+
55
+ connectedCallback() {
56
+ super.connectedCallback();
57
+ this.addEventListener("pointerdown", this.onPointerDown);
58
+ this.addEventListener("keydown", this.onKeyDown);
59
+ }
60
+
61
+ disconnectedCallback() {
62
+ super.disconnectedCallback();
63
+ this.removeEventListener("pointerdown", this.onPointerDown);
64
+ this.removeEventListener("keydown", this.onKeyDown);
65
+ this.removeGlobalListeners();
66
+ }
67
+
68
+ firstUpdated() {
69
+ this.initializeLayout();
70
+ }
71
+
72
+ /* =======================
73
+ * INITIALIZATION
74
+ * ======================= */
75
+
76
+ private initializeLayout() {
77
+ // Query light DOM for panels and handles
78
+ this.panels = Array.from(
79
+ this.querySelectorAll(":scope > rel-resizable-panel")
80
+ ) as RelResizablePanel[];
81
+ this.handles = Array.from(
82
+ this.querySelectorAll(":scope > rel-resizable-handle")
83
+ ) as RelResizableHandle[];
84
+
85
+ // Set handle indices and orientations
86
+ this.handles.forEach((handle, idx) => {
87
+ handle.handleIndex = idx;
88
+ handle.orientation = this.direction;
89
+ });
90
+
91
+ // Calculate initial sizes
92
+ const sizes = this.calculateInitialSizes();
93
+ this.applySizes(sizes);
94
+ }
95
+
96
+ private calculateInitialSizes(): number[] {
97
+ const sizes: number[] = [];
98
+ let specifiedTotal = 0;
99
+ let unspecifiedCount = 0;
100
+
101
+ // First pass: collect specified sizes
102
+ this.panels.forEach((panel) => {
103
+ if (panel.defaultSize !== undefined) {
104
+ sizes.push(panel.defaultSize);
105
+ specifiedTotal += panel.defaultSize;
106
+ } else {
107
+ sizes.push(0);
108
+ unspecifiedCount++;
109
+ }
110
+ });
111
+
112
+ // Second pass: distribute remaining space to unspecified panels
113
+ if (unspecifiedCount > 0) {
114
+ const remaining = Math.max(0, 100 - specifiedTotal);
115
+ const perPanel = remaining / unspecifiedCount;
116
+ sizes.forEach((size, idx) => {
117
+ if (size === 0) {
118
+ sizes[idx] = perPanel;
119
+ }
120
+ });
121
+ }
122
+
123
+ // Normalize to 100%
124
+ const total = sizes.reduce((sum, s) => sum + s, 0);
125
+ if (total > 0) {
126
+ return sizes.map((s) => (s / total) * 100);
127
+ }
128
+
129
+ // Fallback: equal distribution
130
+ return this.panels.map(() => 100 / this.panels.length);
131
+ }
132
+
133
+ /* =======================
134
+ * POINTER HANDLING
135
+ * ======================= */
136
+
137
+ private onPointerDown = (e: PointerEvent) => {
138
+ const target = e.composedPath()[0] as HTMLElement;
139
+ if (target.tagName !== "REL-RESIZABLE-HANDLE") return;
140
+
141
+ const handle = target as RelResizableHandle;
142
+ const handleIndex = this.handles.indexOf(handle);
143
+ if (handleIndex === -1) return;
144
+
145
+ e.preventDefault();
146
+ this.startDrag(handleIndex, e);
147
+ };
148
+
149
+ private startDrag(handleIndex: number, e: PointerEvent) {
150
+ this.isDragging = true;
151
+ this.activeHandleIndex = handleIndex;
152
+ this.startPointerPos = this.direction === "horizontal" ? e.clientX : e.clientY;
153
+ this.startSizes = this.panels.map((p) => p.currentSize);
154
+
155
+ const handle = this.handles[handleIndex];
156
+ handle.setAttribute("data-dragging", "");
157
+
158
+ this.addGlobalListeners();
159
+
160
+ // Capture pointer
161
+ (e.target as HTMLElement).setPointerCapture(e.pointerId);
162
+ }
163
+
164
+ private onPointerMove = (e: PointerEvent) => {
165
+ if (!this.isDragging || this.activeHandleIndex === -1) return;
166
+
167
+ e.preventDefault();
168
+
169
+ const currentPos = this.direction === "horizontal" ? e.clientX : e.clientY;
170
+ const delta = currentPos - this.startPointerPos;
171
+
172
+ this.applyDragResize(delta);
173
+ };
174
+
175
+ private onPointerUp = (e: PointerEvent) => {
176
+ if (!this.isDragging) return;
177
+
178
+ e.preventDefault();
179
+
180
+ const handle = this.handles[this.activeHandleIndex];
181
+ if (handle) {
182
+ handle.removeAttribute("data-dragging");
183
+ }
184
+
185
+ this.isDragging = false;
186
+ this.activeHandleIndex = -1;
187
+
188
+ this.removeGlobalListeners();
189
+
190
+ // Dispatch resize-end event
191
+ this.dispatchResizeEvent("rel-resize-end");
192
+ };
193
+
194
+ private applyDragResize(deltaPixels: number) {
195
+ const rect = this.getBoundingClientRect();
196
+ const totalSize = this.direction === "horizontal" ? rect.width : rect.height;
197
+ const deltaPercent = (deltaPixels / totalSize) * 100;
198
+
199
+ const leftIdx = this.activeHandleIndex;
200
+ const rightIdx = this.activeHandleIndex + 1;
201
+
202
+ if (leftIdx < 0 || rightIdx >= this.panels.length) return;
203
+
204
+ const leftPanel = this.panels[leftIdx];
205
+ const rightPanel = this.panels[rightIdx];
206
+
207
+ let newLeftSize = this.startSizes[leftIdx] + deltaPercent;
208
+ let newRightSize = this.startSizes[rightIdx] - deltaPercent;
209
+
210
+ // Clamp to min/max
211
+ newLeftSize = Math.max(leftPanel.minSize, Math.min(leftPanel.maxSize, newLeftSize));
212
+ newRightSize = Math.max(rightPanel.minSize, Math.min(rightPanel.maxSize, newRightSize));
213
+
214
+ // Ensure total is preserved
215
+ const pairTotal = this.startSizes[leftIdx] + this.startSizes[rightIdx];
216
+ const adjustedTotal = newLeftSize + newRightSize;
217
+ if (adjustedTotal !== pairTotal) {
218
+ const ratio = pairTotal / adjustedTotal;
219
+ newLeftSize *= ratio;
220
+ newRightSize *= ratio;
221
+ }
222
+
223
+ // Update sizes
224
+ const newSizes = [...this.startSizes];
225
+ newSizes[leftIdx] = newLeftSize;
226
+ newSizes[rightIdx] = newRightSize;
227
+
228
+ this.applySizes(newSizes);
229
+
230
+ // Update handle aria value
231
+ const handle = this.handles[this.activeHandleIndex];
232
+ if (handle) {
233
+ const splitPercent = (newLeftSize / pairTotal) * 100;
234
+ handle.updateAriaValue(splitPercent);
235
+ }
236
+
237
+ // Dispatch resize event
238
+ this.dispatchResizeEvent("rel-resize");
239
+ }
240
+
241
+ /* =======================
242
+ * KEYBOARD HANDLING
243
+ * ======================= */
244
+
245
+ private onKeyDown = (e: KeyboardEvent) => {
246
+ const target = e.composedPath()[0] as HTMLElement;
247
+ if (target.tagName !== "REL-RESIZABLE-HANDLE") return;
248
+
249
+ const handle = target as RelResizableHandle;
250
+ const handleIndex = this.handles.indexOf(handle);
251
+ if (handleIndex === -1) return;
252
+
253
+ const isHorizontal = this.direction === "horizontal";
254
+ const step = e.shiftKey ? 5 : 2;
255
+ let applied = false;
256
+
257
+ if (isHorizontal && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
258
+ e.preventDefault();
259
+ const delta = e.key === "ArrowLeft" ? -step : step;
260
+ this.applyKeyboardResize(handleIndex, delta);
261
+ applied = true;
262
+ } else if (!isHorizontal && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
263
+ e.preventDefault();
264
+ const delta = e.key === "ArrowUp" ? -step : step;
265
+ this.applyKeyboardResize(handleIndex, delta);
266
+ applied = true;
267
+ }
268
+
269
+ if (applied) {
270
+ this.dispatchResizeEvent("rel-resize-end");
271
+ }
272
+ };
273
+
274
+ private applyKeyboardResize(handleIndex: number, deltaPercent: number) {
275
+ const leftIdx = handleIndex;
276
+ const rightIdx = handleIndex + 1;
277
+
278
+ if (leftIdx < 0 || rightIdx >= this.panels.length) return;
279
+
280
+ const leftPanel = this.panels[leftIdx];
281
+ const rightPanel = this.panels[rightIdx];
282
+ const currentSizes = this.panels.map((p) => p.currentSize);
283
+
284
+ let newLeftSize = currentSizes[leftIdx] + deltaPercent;
285
+ let newRightSize = currentSizes[rightIdx] - deltaPercent;
286
+
287
+ // Clamp to min/max
288
+ newLeftSize = Math.max(leftPanel.minSize, Math.min(leftPanel.maxSize, newLeftSize));
289
+ newRightSize = Math.max(rightPanel.minSize, Math.min(rightPanel.maxSize, newRightSize));
290
+
291
+ // Ensure total is preserved
292
+ const pairTotal = currentSizes[leftIdx] + currentSizes[rightIdx];
293
+ const adjustedTotal = newLeftSize + newRightSize;
294
+ if (adjustedTotal !== pairTotal) {
295
+ const ratio = pairTotal / adjustedTotal;
296
+ newLeftSize *= ratio;
297
+ newRightSize *= ratio;
298
+ }
299
+
300
+ // Update sizes
301
+ const newSizes = [...currentSizes];
302
+ newSizes[leftIdx] = newLeftSize;
303
+ newSizes[rightIdx] = newRightSize;
304
+
305
+ this.applySizes(newSizes);
306
+
307
+ // Update handle aria value
308
+ const handle = this.handles[handleIndex];
309
+ if (handle) {
310
+ const splitPercent = (newLeftSize / pairTotal) * 100;
311
+ handle.updateAriaValue(splitPercent);
312
+ }
313
+ }
314
+
315
+ /* =======================
316
+ * SIZE APPLICATION
317
+ * ======================= */
318
+
319
+ private applySizes(sizes: number[]) {
320
+ this.panels.forEach((panel, idx) => {
321
+ const size = sizes[idx] || 0;
322
+ panel.currentSize = size;
323
+ panel.style.flexBasis = `${size}%`;
324
+ panel.style.flexGrow = "0";
325
+ panel.style.flexShrink = "0";
326
+ });
327
+ }
328
+
329
+ /* =======================
330
+ * EVENTS
331
+ * ======================= */
332
+
333
+ private dispatchResizeEvent(eventName: string) {
334
+ const sizes = this.panels.map((p) => p.currentSize);
335
+ this.dispatchEvent(
336
+ new CustomEvent(eventName, {
337
+ detail: {
338
+ direction: this.direction,
339
+ sizes,
340
+ },
341
+ bubbles: true,
342
+ composed: true,
343
+ })
344
+ );
345
+ }
346
+
347
+ /* =======================
348
+ * GLOBAL LISTENERS
349
+ * ======================= */
350
+
351
+ private addGlobalListeners() {
352
+ document.addEventListener("pointermove", this.onPointerMove);
353
+ document.addEventListener("pointerup", this.onPointerUp);
354
+ }
355
+
356
+ private removeGlobalListeners() {
357
+ document.removeEventListener("pointermove", this.onPointerMove);
358
+ document.removeEventListener("pointerup", this.onPointerUp);
359
+ }
360
+
361
+ /* =======================
362
+ * RENDER
363
+ * ======================= */
364
+
365
+ render() {
366
+ return html`<slot></slot>`;
367
+ }
368
+ }
369
+
370
+ declare global {
371
+ interface HTMLElementTagNameMap {
372
+ "rel-resizable-group": RelResizableGroup;
373
+ }
374
+ }