@reliability-design/web 1.0.2 → 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.
- package/dist/components/rel-accordion/README.md +132 -0
- package/dist/components/rel-accordion/rel-accordion.ts +61 -29
- package/dist/components/rel-alert/README.md +141 -0
- package/dist/components/rel-alert/rel-alert.ts +110 -67
- package/dist/components/rel-avatar/README.md +119 -0
- package/dist/components/rel-avatar/rel-avatar.ts +8 -15
- package/dist/components/rel-badge/README.md +86 -0
- package/dist/components/rel-badge/rel-badge.ts +28 -21
- package/dist/components/rel-breadcrumb/README.md +102 -0
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +24 -18
- package/dist/components/rel-button/README.md +130 -0
- package/dist/components/rel-button/rel-button.ts +39 -59
- package/dist/components/rel-calendar/README.md +109 -0
- package/dist/components/rel-calendar/index.ts +1 -0
- package/dist/components/rel-calendar/rel-calendar.ts +467 -0
- package/dist/components/rel-card/README.md +94 -0
- package/dist/components/rel-card/rel-card.ts +21 -11
- package/dist/components/rel-cardmedia/README.md +127 -0
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +22 -18
- package/dist/components/rel-carousel/README.md +166 -0
- package/dist/components/rel-carousel/index.ts +1 -0
- package/dist/components/rel-carousel/rel-carousel.ts +469 -0
- package/dist/components/rel-chart/README.md +286 -0
- package/dist/components/rel-chart/index.ts +1 -0
- package/dist/components/rel-chart/rel-chart.ts +736 -0
- package/dist/components/rel-checkbox/README.md +77 -0
- package/dist/components/rel-checkbox/rel-checkbox.ts +36 -45
- package/dist/components/rel-chip/README.md +114 -0
- package/dist/components/rel-chip/rel-chip.ts +47 -122
- package/dist/components/rel-container/README.md +99 -0
- package/dist/components/rel-container/rel-container.ts +1 -6
- package/dist/components/rel-date-picker/README.md +253 -0
- package/dist/components/rel-date-picker/index.ts +1 -0
- package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
- package/dist/components/rel-divider/README.md +100 -0
- package/dist/components/rel-divider/rel-divider.ts +37 -37
- package/dist/components/rel-drawer/README.md +200 -0
- package/dist/components/rel-drawer/index.ts +1 -0
- package/dist/components/rel-drawer/rel-drawer.ts +621 -0
- package/dist/components/rel-kbd/README.md +151 -0
- package/dist/components/rel-kbd/rel-kbd.ts +118 -0
- package/dist/components/rel-layout/README.md +88 -0
- package/dist/components/rel-layout/rel-layout.ts +12 -14
- package/dist/components/rel-menu/README.md +151 -0
- package/dist/components/rel-menu/rel-menu.ts +18 -10
- package/dist/components/rel-menubar/README.md +329 -0
- package/dist/components/rel-menubar/index.ts +2 -0
- package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
- package/dist/components/rel-menubar/rel-menubar.ts +576 -0
- package/dist/components/rel-nav-menu/README.md +289 -0
- package/dist/components/rel-nav-menu/index.ts +3 -0
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +143 -0
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +331 -0
- package/dist/components/rel-navbar/README.md +126 -0
- package/dist/components/rel-navbar/rel-navbar.ts +91 -0
- package/dist/components/rel-otp/README.md +440 -0
- package/dist/components/rel-otp/index.ts +1 -0
- package/dist/components/rel-otp/rel-otp.ts +504 -0
- package/dist/components/rel-pagination/README.md +410 -0
- package/dist/components/rel-pagination/index.ts +1 -0
- package/dist/components/rel-pagination/rel-pagination.ts +376 -0
- package/dist/components/rel-popover/README.md +568 -0
- package/dist/components/rel-popover/index.ts +2 -0
- package/dist/components/rel-popover/rel-popover-content.ts +55 -0
- package/dist/components/rel-popover/rel-popover.ts +327 -0
- package/dist/components/rel-progress/README.md +110 -0
- package/dist/components/rel-progress/rel-progress.ts +58 -20
- package/dist/components/rel-quote-page/README.md +166 -0
- package/dist/components/rel-quote-page/rel-quote-page.ts +816 -0
- package/dist/components/rel-radio/README.md +120 -0
- package/dist/components/rel-radio/rel-radio.ts +41 -73
- package/dist/components/rel-resizable/README.md +140 -0
- package/dist/components/rel-resizable/index.ts +3 -0
- package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
- package/dist/components/rel-resizable/rel-resizable-handle.ts +149 -0
- package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
- package/dist/components/rel-segmented-button/README.md +141 -0
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
- package/dist/components/rel-select/README.md +107 -0
- package/dist/components/rel-select/rel-select.ts +14 -14
- package/dist/components/rel-sheet/README.md +224 -0
- package/dist/components/rel-sheet/rel-sheet.ts +683 -0
- package/dist/components/rel-sidebar/README.md +209 -0
- package/dist/components/rel-sidebar/rel-sidebar.ts +1158 -0
- package/dist/components/rel-slider/README.md +135 -0
- package/dist/components/rel-slider/rel-slider.ts +19 -7
- package/dist/components/rel-snackbar/README.md +134 -0
- package/dist/components/rel-snackbar/rel-snackbar.ts +73 -2
- package/dist/components/rel-stepper/README.md +160 -0
- package/dist/components/rel-stepper/rel-stepper.ts +29 -8
- package/dist/components/rel-switch/README.md +102 -0
- package/dist/components/rel-switch/rel-switch.ts +22 -11
- package/dist/components/rel-tab/README.md +163 -0
- package/dist/components/rel-tab/rel-tab.ts +46 -22
- package/dist/components/rel-table/README.md +178 -0
- package/dist/components/rel-table/rel-table-body.ts +16 -0
- package/dist/components/rel-table/rel-table-caption.ts +22 -0
- package/dist/components/rel-table/rel-table-cell.ts +49 -0
- package/dist/components/rel-table/rel-table-footer.ts +18 -0
- package/dist/components/rel-table/rel-table-head.ts +33 -0
- package/dist/components/rel-table/rel-table-header.ts +16 -0
- package/dist/components/rel-table/rel-table-row.ts +32 -0
- package/dist/components/rel-table/rel-table.ts +47 -0
- package/dist/components/rel-textfield/README.md +90 -0
- package/dist/components/rel-textfield/rel-textfield.ts +37 -21
- package/dist/components/rel-toast/README.md +148 -0
- package/dist/components/rel-toast/rel-toast.ts +32 -14
- package/dist/components/rel-tooltip/README.md +103 -0
- package/dist/components/rel-tooltip/rel-tooltip.ts +9 -9
- package/dist/index.d.mts +846 -7
- package/dist/index.d.ts +846 -7
- package/dist/index.js +10860 -1803
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10820 -1809
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +293 -183
- package/dist/vars-ITL2K3KO.css +294 -0
- package/package.json +2 -2
- package/dist/components/rel-accordion/rel-accordion.stories.ts +0 -313
- package/dist/components/rel-alert/rel-alert.stories.ts +0 -412
- package/dist/components/rel-avatar/rel-avatar.stories.ts +0 -327
- package/dist/components/rel-badge/rel-badge.stories.ts +0 -350
- package/dist/components/rel-breadcrumb/rel-breadcrumb.stories.ts +0 -306
- package/dist/components/rel-button/rel-button.stories.ts +0 -356
- package/dist/components/rel-card/rel-card.stories.ts +0 -299
- package/dist/components/rel-cardmedia/rel-cardmedia.stories.ts +0 -368
- package/dist/components/rel-checkbox/rel-checkbox.stories.ts +0 -414
- package/dist/components/rel-chip/rel-chip.stories.ts +0 -552
- package/dist/components/rel-container/rel-container.stories.ts +0 -239
- package/dist/components/rel-divider/rel-divider.stories.ts +0 -229
- package/dist/components/rel-layout/rel-layout.stories.ts +0 -713
- package/dist/components/rel-menu/rel-menu.stories.ts +0 -910
- package/dist/components/rel-progress/rel-progress.stories.ts +0 -817
- package/dist/components/rel-radio/rel-radio.stories.ts +0 -1048
- package/dist/components/rel-segmented-button/rel-segmented-button.stories.ts +0 -1079
- package/dist/components/rel-select/rel-select.stories.ts +0 -1201
- package/dist/components/rel-slider/rel-slider.stories.ts +0 -730
- package/dist/components/rel-snackbar/rel-snackbar.stories.ts +0 -808
- package/dist/components/rel-stepper/rel-stepper.stories.ts +0 -752
- package/dist/components/rel-switch/rel-switch.stories.ts +0 -441
- package/dist/components/rel-tab/rel-tab.stories.ts +0 -492
- package/dist/components/rel-textfield/rel-textfield.stories.ts +0 -710
- package/dist/components/rel-toast/rel-toast.stories.ts +0 -394
- package/dist/components/rel-tooltip/rel-tooltip.stories.ts +0 -364
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { LitElement, html, css } from "lit";
|
|
2
|
+
import { customElement, property, state, query } from "lit/decorators.js";
|
|
3
|
+
|
|
4
|
+
type SideType = "top" | "right" | "bottom" | "left";
|
|
5
|
+
type AlignType = "start" | "center" | "end";
|
|
6
|
+
|
|
7
|
+
@customElement("rel-popover")
|
|
8
|
+
export class RelPopover extends LitElement {
|
|
9
|
+
/* =======================
|
|
10
|
+
* PUBLIC API
|
|
11
|
+
* ======================= */
|
|
12
|
+
|
|
13
|
+
@property({ type: Boolean, reflect: true }) open?: boolean;
|
|
14
|
+
@property({ type: Boolean, attribute: "defaultopen" }) defaultOpen = false;
|
|
15
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
16
|
+
|
|
17
|
+
@property({ type: String }) side: SideType = "bottom";
|
|
18
|
+
@property({ type: String }) align: AlignType = "center";
|
|
19
|
+
@property({ type: Number, attribute: "sideoffset" }) sideOffset = 8;
|
|
20
|
+
@property({ type: Number, attribute: "alignoffset" }) alignOffset = 0;
|
|
21
|
+
|
|
22
|
+
@property({ type: Boolean, attribute: "trapfocus" }) trapFocus = false;
|
|
23
|
+
@property({ type: Boolean, attribute: "closeonescape" }) closeOnEscape = true;
|
|
24
|
+
@property({ type: Boolean, attribute: "closeonoutsideclick" })
|
|
25
|
+
closeOnOutsideClick = true;
|
|
26
|
+
|
|
27
|
+
/* =======================
|
|
28
|
+
* INTERNAL STATE
|
|
29
|
+
* ======================= */
|
|
30
|
+
|
|
31
|
+
@state() private internalOpen = false;
|
|
32
|
+
@state() private contentStyle = "";
|
|
33
|
+
|
|
34
|
+
@query(".content-wrapper") private contentWrapper!: HTMLDivElement;
|
|
35
|
+
|
|
36
|
+
private triggerEl: HTMLElement | null = null;
|
|
37
|
+
private returnFocusEl: HTMLElement | null = null;
|
|
38
|
+
|
|
39
|
+
/* =======================
|
|
40
|
+
* STYLES
|
|
41
|
+
* ======================= */
|
|
42
|
+
|
|
43
|
+
static styles = css`
|
|
44
|
+
:host {
|
|
45
|
+
display: inline-block;
|
|
46
|
+
position: relative;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
:host([disabled]) {
|
|
50
|
+
pointer-events: none;
|
|
51
|
+
opacity: 0.6;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.content-wrapper {
|
|
55
|
+
position: fixed;
|
|
56
|
+
z-index: 1001;
|
|
57
|
+
display: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.content-wrapper[open] {
|
|
61
|
+
display: block;
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
/* =======================
|
|
66
|
+
* LIFECYCLE
|
|
67
|
+
* ======================= */
|
|
68
|
+
|
|
69
|
+
connectedCallback() {
|
|
70
|
+
super.connectedCallback();
|
|
71
|
+
this.internalOpen = this.isControlled() ? !!this.open : this.defaultOpen;
|
|
72
|
+
|
|
73
|
+
// ONE interaction entry point — capture phase
|
|
74
|
+
this.addEventListener("pointerdown", this.onHostPointerDown, true);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
disconnectedCallback() {
|
|
78
|
+
super.disconnectedCallback();
|
|
79
|
+
this.removeEventListener("pointerdown", this.onHostPointerDown, true);
|
|
80
|
+
this.removeGlobalListeners();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
firstUpdated() {
|
|
84
|
+
// Resolve trigger once from light DOM
|
|
85
|
+
this.triggerEl = this.querySelector(
|
|
86
|
+
'[slot="trigger"]',
|
|
87
|
+
) as HTMLElement | null;
|
|
88
|
+
this.applyTriggerAria();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
updated(changed: Map<string, unknown>) {
|
|
92
|
+
if (changed.has("open") && this.isControlled()) {
|
|
93
|
+
this.internalOpen = !!this.open;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (changed.has("internalOpen") || changed.has("open")) {
|
|
97
|
+
this.applyTriggerAria();
|
|
98
|
+
this.activeOpen ? this.onOpen() : this.onClose();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* =======================
|
|
103
|
+
* STATE HELPERS
|
|
104
|
+
* ======================= */
|
|
105
|
+
|
|
106
|
+
private isControlled() {
|
|
107
|
+
return this.open !== undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private get activeOpen() {
|
|
111
|
+
return this.isControlled() ? !!this.open : this.internalOpen;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private setOpen(next: boolean) {
|
|
115
|
+
if (!this.isControlled()) {
|
|
116
|
+
this.internalOpen = next;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.dispatchEvent(
|
|
120
|
+
new CustomEvent("rel-open-change", {
|
|
121
|
+
detail: { open: next },
|
|
122
|
+
bubbles: true,
|
|
123
|
+
composed: true,
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* =======================
|
|
129
|
+
* POINTER HANDLING (THE FIX)
|
|
130
|
+
* ======================= */
|
|
131
|
+
|
|
132
|
+
private onHostPointerDown = (e: PointerEvent) => {
|
|
133
|
+
if (this.disabled) return;
|
|
134
|
+
|
|
135
|
+
const path = e.composedPath();
|
|
136
|
+
|
|
137
|
+
// Toggle ONLY if pointerdown originated from trigger
|
|
138
|
+
const triggerNode = path.find(
|
|
139
|
+
(n) => n instanceof HTMLElement && n.getAttribute?.("slot") === "trigger",
|
|
140
|
+
) as HTMLElement | undefined;
|
|
141
|
+
|
|
142
|
+
if (!triggerNode) return;
|
|
143
|
+
|
|
144
|
+
// Prevent this interaction from becoming an "outside click"
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
e.stopPropagation();
|
|
147
|
+
|
|
148
|
+
this.triggerEl = triggerNode;
|
|
149
|
+
this.setOpen(!this.activeOpen);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/* =======================
|
|
153
|
+
* OPEN / CLOSE
|
|
154
|
+
* ======================= */
|
|
155
|
+
|
|
156
|
+
private onOpen() {
|
|
157
|
+
this.returnFocusEl =
|
|
158
|
+
(document.activeElement as HTMLElement) ?? this.triggerEl;
|
|
159
|
+
|
|
160
|
+
this.addGlobalListeners();
|
|
161
|
+
|
|
162
|
+
requestAnimationFrame(() => {
|
|
163
|
+
this.updatePosition();
|
|
164
|
+
this.focusFirst();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private onClose() {
|
|
169
|
+
this.removeGlobalListeners();
|
|
170
|
+
this.restoreFocus();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* =======================
|
|
174
|
+
* GLOBAL LISTENERS
|
|
175
|
+
* ======================= */
|
|
176
|
+
|
|
177
|
+
private addGlobalListeners() {
|
|
178
|
+
if (this.closeOnOutsideClick) {
|
|
179
|
+
document.addEventListener("pointerdown", this.onOutsidePointerDown);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.closeOnEscape || this.trapFocus) {
|
|
183
|
+
document.addEventListener("keydown", this.onKeyDown);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
window.addEventListener("resize", this.updatePosition);
|
|
187
|
+
window.addEventListener("scroll", this.updatePosition, true);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private removeGlobalListeners() {
|
|
191
|
+
document.removeEventListener("pointerdown", this.onOutsidePointerDown);
|
|
192
|
+
document.removeEventListener("keydown", this.onKeyDown);
|
|
193
|
+
window.removeEventListener("resize", this.updatePosition);
|
|
194
|
+
window.removeEventListener("scroll", this.updatePosition, true);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private onOutsidePointerDown = (e: PointerEvent) => {
|
|
198
|
+
if (!this.activeOpen) return;
|
|
199
|
+
|
|
200
|
+
const path = e.composedPath();
|
|
201
|
+
|
|
202
|
+
if (
|
|
203
|
+
path.includes(this) ||
|
|
204
|
+
(this.triggerEl && path.includes(this.triggerEl)) ||
|
|
205
|
+
(this.contentWrapper && path.includes(this.contentWrapper))
|
|
206
|
+
) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.setOpen(false);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
private onKeyDown = (e: KeyboardEvent) => {
|
|
214
|
+
if (!this.activeOpen) return;
|
|
215
|
+
|
|
216
|
+
if (e.key === "Escape" && this.closeOnEscape) {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
this.setOpen(false);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (this.trapFocus && e.key === "Tab") {
|
|
222
|
+
this.trapFocusLoop(e);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/* =======================
|
|
227
|
+
* FOCUS
|
|
228
|
+
* ======================= */
|
|
229
|
+
|
|
230
|
+
private focusFirst() {
|
|
231
|
+
if (!this.contentWrapper) return;
|
|
232
|
+
|
|
233
|
+
const focusable = this.getFocusable(this.contentWrapper);
|
|
234
|
+
if (focusable[0]) {
|
|
235
|
+
focusable[0].focus();
|
|
236
|
+
} else {
|
|
237
|
+
this.contentWrapper.focus();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private restoreFocus() {
|
|
242
|
+
this.triggerEl?.focus();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private trapFocusLoop(e: KeyboardEvent) {
|
|
246
|
+
const focusable = this.getFocusable(this.contentWrapper);
|
|
247
|
+
if (focusable.length === 0) return;
|
|
248
|
+
|
|
249
|
+
const first = focusable[0];
|
|
250
|
+
const last = focusable[focusable.length - 1];
|
|
251
|
+
|
|
252
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
253
|
+
e.preventDefault();
|
|
254
|
+
last.focus();
|
|
255
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
256
|
+
e.preventDefault();
|
|
257
|
+
first.focus();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private getFocusable(root: HTMLElement): HTMLElement[] {
|
|
262
|
+
return Array.from(
|
|
263
|
+
root.querySelectorAll<HTMLElement>(
|
|
264
|
+
'a[href],button:not([disabled]),input:not([disabled]),[tabindex]:not([tabindex="-1"])',
|
|
265
|
+
),
|
|
266
|
+
).filter((el) => el.offsetWidth || el.offsetHeight);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* =======================
|
|
270
|
+
* POSITIONING
|
|
271
|
+
* ======================= */
|
|
272
|
+
|
|
273
|
+
private updatePosition = () => {
|
|
274
|
+
if (!this.triggerEl || !this.contentWrapper) return;
|
|
275
|
+
|
|
276
|
+
this.contentWrapper.setAttribute("open", "");
|
|
277
|
+
|
|
278
|
+
const t = this.triggerEl.getBoundingClientRect();
|
|
279
|
+
const c = this.contentWrapper.getBoundingClientRect();
|
|
280
|
+
|
|
281
|
+
let top = t.bottom + this.sideOffset;
|
|
282
|
+
let left = t.left + t.width / 2 - c.width / 2;
|
|
283
|
+
|
|
284
|
+
if (this.side === "top") top = t.top - c.height - this.sideOffset;
|
|
285
|
+
if (this.side === "left") left = t.left - c.width - this.sideOffset;
|
|
286
|
+
if (this.side === "right") left = t.right + this.sideOffset;
|
|
287
|
+
|
|
288
|
+
top = Math.max(8, Math.min(top, window.innerHeight - c.height - 8));
|
|
289
|
+
left = Math.max(8, Math.min(left, window.innerWidth - c.width - 8));
|
|
290
|
+
|
|
291
|
+
this.contentStyle = `top:${top}px;left:${left}px;`;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/* =======================
|
|
295
|
+
* ARIA
|
|
296
|
+
* ======================= */
|
|
297
|
+
|
|
298
|
+
private applyTriggerAria() {
|
|
299
|
+
if (!this.triggerEl) return;
|
|
300
|
+
this.triggerEl.setAttribute("aria-haspopup", "dialog");
|
|
301
|
+
this.triggerEl.setAttribute("aria-expanded", String(this.activeOpen));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* =======================
|
|
305
|
+
* RENDER
|
|
306
|
+
* ======================= */
|
|
307
|
+
|
|
308
|
+
render() {
|
|
309
|
+
return html`
|
|
310
|
+
<slot name="trigger"></slot>
|
|
311
|
+
|
|
312
|
+
<div
|
|
313
|
+
class="content-wrapper"
|
|
314
|
+
style="${this.contentStyle}"
|
|
315
|
+
?open="${this.activeOpen}"
|
|
316
|
+
>
|
|
317
|
+
<slot></slot>
|
|
318
|
+
</div>
|
|
319
|
+
`;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
declare global {
|
|
324
|
+
interface HTMLElementTagNameMap {
|
|
325
|
+
"rel-popover": RelPopover;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# rel-progress
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
A progress indicator component supporting four modes: linear-determinate, linear-indeterminate, circular-determinate, and circular-indeterminate. Features smooth animations, proper ARIA attributes, and respects user motion preferences by disabling animations when requested.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
```html
|
|
8
|
+
<!-- Linear progress (determinate) -->
|
|
9
|
+
<rel-progress mode="linear-determinate" value="45"></rel-progress>
|
|
10
|
+
<rel-progress mode="linear-determinate" value="100"></rel-progress>
|
|
11
|
+
|
|
12
|
+
<!-- Linear progress (indeterminate/loading) -->
|
|
13
|
+
<rel-progress mode="linear-indeterminate"></rel-progress>
|
|
14
|
+
|
|
15
|
+
<!-- Circular progress (determinate) -->
|
|
16
|
+
<rel-progress mode="circular-determinate" value="75"></rel-progress>
|
|
17
|
+
|
|
18
|
+
<!-- Circular progress (indeterminate/spinner) -->
|
|
19
|
+
<rel-progress mode="circular-indeterminate"></rel-progress>
|
|
20
|
+
|
|
21
|
+
<!-- Dynamic progress update -->
|
|
22
|
+
<rel-progress id="uploadProgress" mode="linear-determinate" value="0"></rel-progress>
|
|
23
|
+
<script>
|
|
24
|
+
const progress = document.getElementById('uploadProgress');
|
|
25
|
+
let value = 0;
|
|
26
|
+
const interval = setInterval(() => {
|
|
27
|
+
value += 10;
|
|
28
|
+
progress.value = value;
|
|
29
|
+
if (value >= 100) clearInterval(interval);
|
|
30
|
+
}, 500);
|
|
31
|
+
</script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
### Attributes/Properties
|
|
37
|
+
| Name | Type | Default | Description |
|
|
38
|
+
|------|------|---------|-------------|
|
|
39
|
+
| `mode` (reflected) | `"linear-determinate" \| "linear-indeterminate" \| "circular-determinate" \| "circular-indeterminate"` | `"linear-determinate"` | Progress indicator type |
|
|
40
|
+
| `value` | `number` | `0` | Progress value (0-100) for determinate modes (ignored in indeterminate modes) |
|
|
41
|
+
|
|
42
|
+
## Events
|
|
43
|
+
None.
|
|
44
|
+
|
|
45
|
+
## Slots
|
|
46
|
+
Default slot not used.
|
|
47
|
+
|
|
48
|
+
## CSS Parts
|
|
49
|
+
None exposed.
|
|
50
|
+
|
|
51
|
+
## Tokens / CSS Variables
|
|
52
|
+
|
|
53
|
+
### Semantic Tokens
|
|
54
|
+
- `--rel-fontFamilies-primary` - Font family
|
|
55
|
+
|
|
56
|
+
### Color Tokens
|
|
57
|
+
- `--rel-colors-gray-border` - Track/background color
|
|
58
|
+
- `--rel-colors-black` - Active progress bar color
|
|
59
|
+
|
|
60
|
+
### Border Tokens
|
|
61
|
+
- `--rel-borders-radius-border-radius-200` - Linear track border radius
|
|
62
|
+
|
|
63
|
+
## Accessibility
|
|
64
|
+
- Determinate modes use `role="progressbar"` with `aria-valuenow`, `aria-valuemin="0"`, `aria-valuemax="100"`
|
|
65
|
+
- Indeterminate modes use `role="progressbar"` without value attributes
|
|
66
|
+
- Respects `prefers-reduced-motion: reduce` by disabling animations
|
|
67
|
+
|
|
68
|
+
### Keyboard Interactions
|
|
69
|
+
- None (not focusable)
|
|
70
|
+
|
|
71
|
+
### Focus Behavior
|
|
72
|
+
- Not focusable
|
|
73
|
+
|
|
74
|
+
## Notes / Edge Cases
|
|
75
|
+
- **Linear modes**:
|
|
76
|
+
- Height: `6px`
|
|
77
|
+
- Track uses gray border color
|
|
78
|
+
- Bar uses black color
|
|
79
|
+
- Border radius: `--rel-borders-radius-border-radius-200`
|
|
80
|
+
- **Determinate**: bar width transitions smoothly (0.3s ease)
|
|
81
|
+
- **Indeterminate**: 30% width bar slides left-to-right (1.5s infinite loop)
|
|
82
|
+
|
|
83
|
+
- **Circular modes**:
|
|
84
|
+
- Size: `48px × 48px` (viewBox varies by mode)
|
|
85
|
+
- Stroke width: `3px` (determinate) or `4px` (indeterminate)
|
|
86
|
+
- Stroke linecap: `round`
|
|
87
|
+
- **Determinate**:
|
|
88
|
+
- Uses `stroke-dasharray` and `stroke-dashoffset` to animate progress
|
|
89
|
+
- Circle radius: `15.9155`, circumference: `~100`
|
|
90
|
+
- Rotated -90° to start at top
|
|
91
|
+
- Progress transition: `0.35s`
|
|
92
|
+
- **Indeterminate (spinner)**:
|
|
93
|
+
- Rotates continuously (2s linear infinite)
|
|
94
|
+
- Stroke dash animates (1.5s ease-in-out infinite)
|
|
95
|
+
|
|
96
|
+
- **Value clamping**: Values are automatically clamped to 0-100 range via `safeValue` getter
|
|
97
|
+
|
|
98
|
+
- **Motion preferences**:
|
|
99
|
+
- When `prefers-reduced-motion: reduce`:
|
|
100
|
+
- Linear determinate: no transition
|
|
101
|
+
- Linear indeterminate: no animation, shows full-width bar at 50% opacity
|
|
102
|
+
- Circular determinate: no transition
|
|
103
|
+
- Circular indeterminate: no rotation/dash animation, shows static partial arc
|
|
104
|
+
|
|
105
|
+
- **Display modes**:
|
|
106
|
+
- Linear modes: `display: block` (full width)
|
|
107
|
+
- Circular modes: `display: inline-block` (sized to content)
|
|
108
|
+
|
|
109
|
+
- The component automatically switches render methods based on `mode` property
|
|
110
|
+
- Indeterminate modes ignore the `value` property
|
|
@@ -9,9 +9,11 @@ export type ProgressMode =
|
|
|
9
9
|
|
|
10
10
|
@customElement("rel-progress")
|
|
11
11
|
export class RelProgress extends LitElement {
|
|
12
|
-
@property({ type: String
|
|
12
|
+
@property({ type: String, reflect: true })
|
|
13
|
+
mode: ProgressMode = "linear-determinate";
|
|
13
14
|
|
|
14
|
-
@property({ type: Number })
|
|
15
|
+
@property({ type: Number })
|
|
16
|
+
value = 0;
|
|
15
17
|
|
|
16
18
|
private radius = 15.9155;
|
|
17
19
|
private circumference = 2 * Math.PI * this.radius;
|
|
@@ -19,17 +21,17 @@ export class RelProgress extends LitElement {
|
|
|
19
21
|
static styles = css`
|
|
20
22
|
:host {
|
|
21
23
|
display: block;
|
|
22
|
-
font-family: var(--rel-fontFamilies-primary);
|
|
24
|
+
font-family: var(--rel-fontFamilies-primary, sans-serif);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
:host([mode*="circular"]) {
|
|
26
28
|
display: inline-block;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
/* Linear
|
|
31
|
+
/* Linear */
|
|
30
32
|
.linear-track {
|
|
31
|
-
background: var(--rel-colors-gray-border);
|
|
32
|
-
border-radius: var(--rel-borders-radius-border-radius-200);
|
|
33
|
+
background: var(--rel-colors-gray-border, #ddd);
|
|
34
|
+
border-radius: var(--rel-borders-radius-border-radius-200, 8px);
|
|
33
35
|
height: 6px;
|
|
34
36
|
overflow: hidden;
|
|
35
37
|
width: 100%;
|
|
@@ -58,7 +60,7 @@ export class RelProgress extends LitElement {
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
/* Circular
|
|
63
|
+
/* Circular */
|
|
62
64
|
.circular {
|
|
63
65
|
display: inline-block;
|
|
64
66
|
width: 48px;
|
|
@@ -83,11 +85,9 @@ export class RelProgress extends LitElement {
|
|
|
83
85
|
stroke-width: 3;
|
|
84
86
|
stroke-linecap: round;
|
|
85
87
|
transition: stroke-dashoffset 0.35s;
|
|
86
|
-
transform: rotate(0deg);
|
|
87
|
-
transform-origin: center;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/*
|
|
90
|
+
/* Spinner */
|
|
91
91
|
.spinner {
|
|
92
92
|
animation: rotate 2s linear infinite;
|
|
93
93
|
}
|
|
@@ -118,38 +118,71 @@ export class RelProgress extends LitElement {
|
|
|
118
118
|
stroke-dashoffset: -124;
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
@media (prefers-reduced-motion: reduce) {
|
|
123
|
+
.linear-bar {
|
|
124
|
+
transition: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.linear-bar.indeterminate {
|
|
128
|
+
animation: none;
|
|
129
|
+
width: 100%;
|
|
130
|
+
opacity: 0.5;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.spinner {
|
|
134
|
+
animation: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.spinner-path {
|
|
138
|
+
animation: none;
|
|
139
|
+
stroke-dasharray: 90, 150;
|
|
140
|
+
stroke-dashoffset: -35;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.circular-progress {
|
|
144
|
+
transition: none;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
121
147
|
`;
|
|
122
148
|
|
|
149
|
+
private get safeValue() {
|
|
150
|
+
return Math.min(100, Math.max(0, this.value));
|
|
151
|
+
}
|
|
152
|
+
|
|
123
153
|
private renderLinearDeterminate() {
|
|
124
154
|
return html`
|
|
125
|
-
<div class="linear-track"
|
|
126
|
-
|
|
155
|
+
<div class="linear-track" role="progressbar"
|
|
156
|
+
aria-valuenow="${this.safeValue}" aria-valuemin="0" aria-valuemax="100">
|
|
157
|
+
<div class="linear-bar" style="width: ${this.safeValue}%"></div>
|
|
127
158
|
</div>
|
|
128
159
|
`;
|
|
129
160
|
}
|
|
130
161
|
|
|
131
162
|
private renderLinearIndeterminate() {
|
|
132
163
|
return html`
|
|
133
|
-
<div class="linear-track">
|
|
164
|
+
<div class="linear-track" role="progressbar">
|
|
134
165
|
<div class="linear-bar indeterminate"></div>
|
|
135
166
|
</div>
|
|
136
167
|
`;
|
|
137
168
|
}
|
|
138
169
|
|
|
139
170
|
private renderCircularDeterminate() {
|
|
140
|
-
const offset =
|
|
171
|
+
const offset =
|
|
172
|
+
this.circumference - (this.safeValue / 100) * this.circumference;
|
|
141
173
|
|
|
142
174
|
return html`
|
|
143
175
|
<div class="circular">
|
|
144
176
|
<svg viewBox="0 0 36 36" class="circular-svg">
|
|
145
|
-
<circle class="circular-bg" cx="18" cy="18" r="
|
|
177
|
+
<circle class="circular-bg" cx="18" cy="18" r="${this.radius}" />
|
|
146
178
|
<circle
|
|
147
179
|
class="circular-progress"
|
|
148
180
|
cx="18"
|
|
149
181
|
cy="18"
|
|
150
|
-
r="
|
|
182
|
+
r="${this.radius}"
|
|
151
183
|
stroke-dasharray="${this.circumference}"
|
|
152
|
-
stroke-dashoffset="${offset}"
|
|
184
|
+
stroke-dashoffset="${offset}"
|
|
185
|
+
/>
|
|
153
186
|
</svg>
|
|
154
187
|
</div>
|
|
155
188
|
`;
|
|
@@ -159,7 +192,14 @@ export class RelProgress extends LitElement {
|
|
|
159
192
|
return html`
|
|
160
193
|
<div class="circular">
|
|
161
194
|
<svg viewBox="0 0 50 50" class="spinner">
|
|
162
|
-
<circle
|
|
195
|
+
<circle
|
|
196
|
+
class="spinner-path"
|
|
197
|
+
cx="25"
|
|
198
|
+
cy="25"
|
|
199
|
+
r="20"
|
|
200
|
+
fill="none"
|
|
201
|
+
stroke-width="4"
|
|
202
|
+
/>
|
|
163
203
|
</svg>
|
|
164
204
|
</div>
|
|
165
205
|
`;
|
|
@@ -167,8 +207,6 @@ export class RelProgress extends LitElement {
|
|
|
167
207
|
|
|
168
208
|
render() {
|
|
169
209
|
switch (this.mode) {
|
|
170
|
-
case "linear-determinate":
|
|
171
|
-
return this.renderLinearDeterminate();
|
|
172
210
|
case "linear-indeterminate":
|
|
173
211
|
return this.renderLinearIndeterminate();
|
|
174
212
|
case "circular-determinate":
|