@reliability-design/web 1.4.5 → 1.4.7
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/CHANGELOG.md +211 -49
- package/dist/components/rel-accordion/README.md +132 -132
- package/dist/components/rel-alert/README.md +145 -141
- package/dist/components/rel-autocomplete/README.md +156 -61
- package/dist/components/rel-avatar/README.md +116 -130
- package/dist/components/rel-badge/README.md +98 -93
- package/dist/components/rel-breadcrumb/README.md +96 -102
- package/dist/components/rel-button/README.md +160 -130
- package/dist/components/rel-calendar/README.md +109 -109
- package/dist/components/rel-card/README.md +75 -94
- package/dist/components/rel-cardmedia/README.md +100 -127
- package/dist/components/rel-carousel/README.md +184 -166
- package/dist/components/rel-checkbox/README.md +93 -75
- package/dist/components/rel-chip/README.md +132 -121
- package/dist/components/rel-container/README.md +107 -99
- package/dist/components/rel-date-picker/README.md +200 -289
- package/dist/components/rel-divider/README.md +78 -100
- package/dist/components/rel-drawer/README.md +187 -200
- package/dist/components/rel-kbd/README.md +141 -151
- package/dist/components/rel-menu/README.md +178 -151
- package/dist/components/rel-menubar/README.md +218 -329
- package/dist/components/rel-otp/README.md +170 -440
- package/dist/components/rel-pagination/README.md +100 -424
- package/dist/components/rel-popover/README.md +203 -568
- package/dist/components/rel-progress/README.md +88 -110
- package/dist/components/rel-radio/README.md +131 -135
- package/dist/components/rel-range-picker/README.md +162 -0
- package/dist/components/rel-resizable/README.md +199 -140
- package/dist/components/rel-segmented-button/README.md +155 -141
- package/dist/components/rel-select/README.md +154 -93
- package/dist/components/rel-sheet/README.md +168 -224
- package/dist/components/rel-skeleton/README.md +103 -0
- package/dist/components/rel-slider/README.md +130 -135
- package/dist/components/rel-snackbar/README.md +162 -134
- package/dist/components/rel-stepper/README.md +158 -160
- package/dist/components/rel-switch/README.md +108 -102
- package/dist/components/rel-tab/README.md +171 -163
- package/dist/components/rel-textfield/README.md +159 -69
- package/dist/components/rel-time-picker/README.md +122 -157
- package/dist/components/rel-toast/README.md +157 -148
- package/dist/components/rel-tooltip/README.md +108 -103
- package/dist/components/rel-upload/README.md +220 -0
- package/dist/index.d.mts +1387 -350
- package/dist/index.d.ts +1387 -350
- package/dist/index.js +5863 -3600
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5864 -3592
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +877 -866
- package/dist/{vars-XNMTSRVN.css → vars-JHTUGNRG.css} +877 -866
- package/package.json +4 -3
- package/dist/components/rel-accordion/rel-accordion.ts +0 -336
- package/dist/components/rel-alert/rel-alert.ts +0 -305
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +0 -689
- package/dist/components/rel-avatar/rel-avatar.ts +0 -142
- package/dist/components/rel-badge/rel-badge.ts +0 -170
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +0 -106
- package/dist/components/rel-button/rel-button.ts +0 -560
- package/dist/components/rel-calendar/index.ts +0 -1
- package/dist/components/rel-calendar/rel-calendar.ts +0 -519
- package/dist/components/rel-card/rel-card.ts +0 -88
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +0 -88
- package/dist/components/rel-carousel/index.ts +0 -1
- package/dist/components/rel-carousel/rel-carousel.ts +0 -700
- package/dist/components/rel-chart/README.md +0 -286
- package/dist/components/rel-chart/index.ts +0 -1
- package/dist/components/rel-chart/rel-chart.ts +0 -754
- package/dist/components/rel-checkbox/rel-checkbox.ts +0 -382
- package/dist/components/rel-chip/rel-chip.ts +0 -472
- package/dist/components/rel-container/rel-container.ts +0 -95
- package/dist/components/rel-date-picker/date-format.test.ts +0 -43
- package/dist/components/rel-date-picker/date-format.ts +0 -47
- package/dist/components/rel-date-picker/index.ts +0 -1
- package/dist/components/rel-date-picker/rel-date-picker.ts +0 -1237
- package/dist/components/rel-divider/rel-divider.ts +0 -54
- package/dist/components/rel-drawer/index.ts +0 -1
- package/dist/components/rel-drawer/rel-drawer-footer.ts +0 -27
- package/dist/components/rel-drawer/rel-drawer.ts +0 -604
- package/dist/components/rel-kbd/rel-kbd.ts +0 -188
- package/dist/components/rel-layout/rel-layout.ts +0 -49
- package/dist/components/rel-menu/rel-menu.ts +0 -535
- package/dist/components/rel-menubar/index.ts +0 -2
- package/dist/components/rel-menubar/rel-menubar-menu.css +0 -3
- package/dist/components/rel-menubar/rel-menubar-menu.ts +0 -17
- package/dist/components/rel-menubar/rel-menubar.ts +0 -777
- package/dist/components/rel-nav-menu/README.md +0 -289
- package/dist/components/rel-nav-menu/index.ts +0 -3
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +0 -243
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +0 -72
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +0 -270
- package/dist/components/rel-navbar/README.md +0 -126
- package/dist/components/rel-navbar/rel-navbar.ts +0 -105
- package/dist/components/rel-otp/index.ts +0 -1
- package/dist/components/rel-otp/rel-otp.ts +0 -517
- package/dist/components/rel-pagination/index.ts +0 -1
- package/dist/components/rel-pagination/rel-pagination.ts +0 -509
- package/dist/components/rel-popover/index.ts +0 -2
- package/dist/components/rel-popover/rel-popover-content.ts +0 -81
- package/dist/components/rel-popover/rel-popover.ts +0 -444
- package/dist/components/rel-progress/rel-progress.ts +0 -225
- package/dist/components/rel-radio/rel-radio.ts +0 -305
- package/dist/components/rel-range-picker/rel-range-picker.ts +0 -1331
- package/dist/components/rel-resizable/index.ts +0 -3
- package/dist/components/rel-resizable/rel-resizable-group.ts +0 -405
- package/dist/components/rel-resizable/rel-resizable-handle.ts +0 -150
- package/dist/components/rel-resizable/rel-resizable-panel.ts +0 -58
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +0 -213
- package/dist/components/rel-select/rel-select.ts +0 -1385
- package/dist/components/rel-sheet/rel-sheet.ts +0 -465
- package/dist/components/rel-sidebar/README.md +0 -209
- package/dist/components/rel-sidebar/rel-sidebar.ts +0 -1185
- package/dist/components/rel-skeleton/rel-skeleton.ts +0 -134
- package/dist/components/rel-slider/rel-slider.ts +0 -670
- package/dist/components/rel-snackbar/rel-snackbar.ts +0 -325
- package/dist/components/rel-stepper/rel-stepper.ts +0 -565
- package/dist/components/rel-switch/rel-switch.ts +0 -197
- package/dist/components/rel-tab/rel-tab.ts +0 -264
- package/dist/components/rel-table/README.md +0 -178
- package/dist/components/rel-table/rel-table-body.ts +0 -22
- package/dist/components/rel-table/rel-table-caption.ts +0 -30
- package/dist/components/rel-table/rel-table-cell.ts +0 -43
- package/dist/components/rel-table/rel-table-footer.ts +0 -24
- package/dist/components/rel-table/rel-table-head.ts +0 -47
- package/dist/components/rel-table/rel-table-header.ts +0 -22
- package/dist/components/rel-table/rel-table-row.ts +0 -37
- package/dist/components/rel-table/rel-table.ts +0 -62
- package/dist/components/rel-textfield/rel-textfield.ts +0 -866
- package/dist/components/rel-time-picker/rel-time-picker.ts +0 -1126
- package/dist/components/rel-toast/rel-toast.ts +0 -204
- package/dist/components/rel-tooltip/rel-tooltip.ts +0 -190
- package/dist/components/rel-topbar/rel-topbar-actions.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar-brand.ts +0 -54
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +0 -69
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +0 -31
- package/dist/components/rel-topbar/rel-topbar-item.ts +0 -324
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar-profile.ts +0 -243
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar.ts +0 -446
- package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +0 -134
- package/dist/components/rel-upload/rel-upload-actions.ts +0 -163
- package/dist/components/rel-upload/rel-upload.ts +0 -943
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
2
|
-
import { customElement, property, state, query } from "lit/decorators.js";
|
|
3
|
-
import { getDeepActiveElement, getFocusableElements } from "../../utils/focus.js";
|
|
4
|
-
|
|
5
|
-
type SideType = "top" | "right" | "bottom" | "left";
|
|
6
|
-
type AlignType = "start" | "center" | "end";
|
|
7
|
-
|
|
8
|
-
@customElement("rel-popover")
|
|
9
|
-
export class RelPopover extends LitElement {
|
|
10
|
-
/* =======================
|
|
11
|
-
* PUBLIC API
|
|
12
|
-
* ======================= */
|
|
13
|
-
|
|
14
|
-
@property({ type: Boolean, reflect: true }) open?: boolean;
|
|
15
|
-
@property({ type: Boolean, attribute: "defaultopen" }) defaultOpen = false;
|
|
16
|
-
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
17
|
-
|
|
18
|
-
@property({ type: String, reflect: true }) side: SideType = "bottom";
|
|
19
|
-
@property({ type: String, reflect: true }) align: AlignType = "center";
|
|
20
|
-
@property({ type: Number, attribute: "sideoffset" }) sideOffset = 8;
|
|
21
|
-
@property({ type: Number, attribute: "alignoffset" }) alignOffset = 0;
|
|
22
|
-
|
|
23
|
-
@property({ type: Boolean, attribute: "trapfocus" }) trapFocus = false;
|
|
24
|
-
@property({ type: Boolean, attribute: "closeonescape" }) closeOnEscape = true;
|
|
25
|
-
@property({ type: Boolean, attribute: "closeonoutsideclick" })
|
|
26
|
-
closeOnOutsideClick = true;
|
|
27
|
-
|
|
28
|
-
/* =======================
|
|
29
|
-
* INTERNAL STATE
|
|
30
|
-
* ======================= */
|
|
31
|
-
|
|
32
|
-
@state() private internalOpen = false;
|
|
33
|
-
@state() private contentStyle = "";
|
|
34
|
-
|
|
35
|
-
@query(".content-wrapper") private contentWrapper!: HTMLDivElement;
|
|
36
|
-
|
|
37
|
-
private triggerEl: HTMLElement | null = null;
|
|
38
|
-
private returnFocusEl: HTMLElement | null = null;
|
|
39
|
-
private uniqueId = `popover-${Math.random().toString(36).slice(2)}`;
|
|
40
|
-
|
|
41
|
-
/* =======================
|
|
42
|
-
* STYLES
|
|
43
|
-
* ======================= */
|
|
44
|
-
|
|
45
|
-
static styles = css`
|
|
46
|
-
:host {
|
|
47
|
-
color: var(--rel-text-default);
|
|
48
|
-
display: inline-block;
|
|
49
|
-
position: relative;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
:host([disabled]) {
|
|
53
|
-
opacity: 0.6;
|
|
54
|
-
cursor: not-allowed;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
:host([disabled]) * {
|
|
58
|
-
pointer-events: none;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.content-wrapper {
|
|
62
|
-
position: fixed;
|
|
63
|
-
z-index: 1001;
|
|
64
|
-
display: none;
|
|
65
|
-
/* Reset native [popover] UA styles (the wrapper is promoted to the
|
|
66
|
-
top layer so transformed ancestors — e.g. a centered rel-modal
|
|
67
|
-
panel — can't hijack its containing block or clip it). */
|
|
68
|
-
inset: auto;
|
|
69
|
-
margin: 0;
|
|
70
|
-
padding: 0;
|
|
71
|
-
border: none;
|
|
72
|
-
background: transparent;
|
|
73
|
-
overflow: visible;
|
|
74
|
-
color: inherit;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.content-wrapper[open] {
|
|
78
|
-
display: block;
|
|
79
|
-
}
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
/* =======================
|
|
83
|
-
* LIFECYCLE
|
|
84
|
-
* ======================= */
|
|
85
|
-
|
|
86
|
-
connectedCallback() {
|
|
87
|
-
super.connectedCallback();
|
|
88
|
-
this.internalOpen = this.isControlled() ? !!this.open : this.defaultOpen;
|
|
89
|
-
|
|
90
|
-
// ONE interaction entry point per input method — capture phase
|
|
91
|
-
this.addEventListener("pointerdown", this.onHostPointerDown, true);
|
|
92
|
-
this.addEventListener("keydown", this.onHostKeyDown, true);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
disconnectedCallback() {
|
|
96
|
-
super.disconnectedCallback();
|
|
97
|
-
this.removeEventListener("pointerdown", this.onHostPointerDown, true);
|
|
98
|
-
this.removeEventListener("keydown", this.onHostKeyDown, true);
|
|
99
|
-
this.removeGlobalListeners();
|
|
100
|
-
this.hideNativePopover();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
firstUpdated() {
|
|
104
|
-
// triggerEl is resolved via onTriggerSlotChange; call aria in case slot
|
|
105
|
-
// content was already assigned before firstUpdated
|
|
106
|
-
this.applyTriggerAria();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
updated(changed: Map<string, unknown>) {
|
|
110
|
-
if (changed.has("open") && this.isControlled()) {
|
|
111
|
-
this.internalOpen = !!this.open;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (changed.has("internalOpen") || changed.has("open")) {
|
|
115
|
-
this.applyTriggerAria();
|
|
116
|
-
this.activeOpen ? this.onOpen() : this.onClose();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/* =======================
|
|
121
|
-
* STATE HELPERS
|
|
122
|
-
* ======================= */
|
|
123
|
-
|
|
124
|
-
private isControlled() {
|
|
125
|
-
return this.open !== undefined;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private get activeOpen() {
|
|
129
|
-
return this.isControlled() ? !!this.open : this.internalOpen;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private setOpen(next: boolean) {
|
|
133
|
-
if (!this.isControlled()) {
|
|
134
|
-
this.internalOpen = next;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
this.dispatchEvent(
|
|
138
|
-
new CustomEvent("rel-open-change", {
|
|
139
|
-
detail: { open: next },
|
|
140
|
-
bubbles: true,
|
|
141
|
-
composed: true,
|
|
142
|
-
}),
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/* =======================
|
|
147
|
-
* POINTER HANDLING (THE FIX)
|
|
148
|
-
* ======================= */
|
|
149
|
-
|
|
150
|
-
private onHostPointerDown = (e: PointerEvent) => {
|
|
151
|
-
if (this.disabled) return;
|
|
152
|
-
|
|
153
|
-
const path = e.composedPath();
|
|
154
|
-
|
|
155
|
-
// Toggle ONLY if pointerdown originated from the trigger element
|
|
156
|
-
if (!this.triggerEl || !path.includes(this.triggerEl)) return;
|
|
157
|
-
|
|
158
|
-
// Prevent this interaction from becoming an "outside click"
|
|
159
|
-
e.preventDefault();
|
|
160
|
-
e.stopPropagation();
|
|
161
|
-
|
|
162
|
-
this.setOpen(!this.activeOpen);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
private onHostKeyDown = (e: KeyboardEvent) => {
|
|
166
|
-
if (this.disabled) return;
|
|
167
|
-
if (e.key !== "Enter" && e.key !== " ") return;
|
|
168
|
-
if (!this.triggerEl || !e.composedPath().includes(this.triggerEl)) return;
|
|
169
|
-
|
|
170
|
-
// Keyboard twin of onHostPointerDown. preventDefault also suppresses the
|
|
171
|
-
// native synthetic click of a <button> trigger, which would toggle twice.
|
|
172
|
-
e.preventDefault();
|
|
173
|
-
e.stopPropagation();
|
|
174
|
-
|
|
175
|
-
this.setOpen(!this.activeOpen);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
/* =======================
|
|
179
|
-
* OPEN / CLOSE
|
|
180
|
-
* ======================= */
|
|
181
|
-
|
|
182
|
-
private onOpen() {
|
|
183
|
-
this.returnFocusEl = getDeepActiveElement() ?? this.triggerEl;
|
|
184
|
-
|
|
185
|
-
this.addGlobalListeners();
|
|
186
|
-
this.showNativePopover();
|
|
187
|
-
|
|
188
|
-
requestAnimationFrame(() => {
|
|
189
|
-
this.updatePosition();
|
|
190
|
-
this.focusFirst();
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private onClose() {
|
|
195
|
-
this.removeGlobalListeners();
|
|
196
|
-
this.hideNativePopover();
|
|
197
|
-
this.restoreFocus();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/* =======================
|
|
201
|
-
* TOP LAYER (native Popover API)
|
|
202
|
-
*
|
|
203
|
-
* A transformed ancestor (e.g. rel-modal's centered panel with
|
|
204
|
-
* translate(-50%,-50%)) becomes the containing block for position:fixed
|
|
205
|
-
* descendants, breaking viewport-based coordinates and letting the
|
|
206
|
-
* panel's overflow:hidden clip the content. Promoting the wrapper to
|
|
207
|
-
* the browser top layer sidesteps both. `manual` keeps light-dismiss
|
|
208
|
-
* and Escape handling under this component's own control.
|
|
209
|
-
* ======================= */
|
|
210
|
-
|
|
211
|
-
private showNativePopover() {
|
|
212
|
-
const w = this.contentWrapper;
|
|
213
|
-
if (!w || typeof w.showPopover !== "function") return;
|
|
214
|
-
try {
|
|
215
|
-
if (!w.matches(":popover-open")) w.showPopover();
|
|
216
|
-
} catch {
|
|
217
|
-
/* older engines / detached element — fixed-position fallback applies */
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private hideNativePopover() {
|
|
222
|
-
const w = this.contentWrapper;
|
|
223
|
-
if (!w || typeof w.hidePopover !== "function") return;
|
|
224
|
-
try {
|
|
225
|
-
if (w.matches(":popover-open")) w.hidePopover();
|
|
226
|
-
} catch {
|
|
227
|
-
/* already hidden or unsupported — nothing to do */
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/* =======================
|
|
232
|
-
* GLOBAL LISTENERS
|
|
233
|
-
* ======================= */
|
|
234
|
-
|
|
235
|
-
private addGlobalListeners() {
|
|
236
|
-
if (this.closeOnOutsideClick) {
|
|
237
|
-
document.addEventListener("pointerdown", this.onOutsidePointerDown);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (this.closeOnEscape || this.trapFocus) {
|
|
241
|
-
document.addEventListener("keydown", this.onKeyDown);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
window.addEventListener("resize", this.updatePosition);
|
|
245
|
-
window.addEventListener("scroll", this.updatePosition, true);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
private removeGlobalListeners() {
|
|
249
|
-
document.removeEventListener("pointerdown", this.onOutsidePointerDown);
|
|
250
|
-
document.removeEventListener("keydown", this.onKeyDown);
|
|
251
|
-
window.removeEventListener("resize", this.updatePosition);
|
|
252
|
-
window.removeEventListener("scroll", this.updatePosition, true);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private onOutsidePointerDown = (e: PointerEvent) => {
|
|
256
|
-
if (!this.activeOpen) return;
|
|
257
|
-
|
|
258
|
-
const path = e.composedPath();
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
path.includes(this) ||
|
|
262
|
-
(this.triggerEl && path.includes(this.triggerEl)) ||
|
|
263
|
-
(this.contentWrapper && path.includes(this.contentWrapper))
|
|
264
|
-
) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
this.setOpen(false);
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
private onKeyDown = (e: KeyboardEvent) => {
|
|
272
|
-
if (!this.activeOpen) return;
|
|
273
|
-
|
|
274
|
-
if (e.key === "Escape" && this.closeOnEscape) {
|
|
275
|
-
e.preventDefault();
|
|
276
|
-
this.setOpen(false);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (this.trapFocus && e.key === "Tab") {
|
|
280
|
-
this.trapFocusLoop(e);
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
/* =======================
|
|
285
|
-
* FOCUS
|
|
286
|
-
* ======================= */
|
|
287
|
-
|
|
288
|
-
private focusFirst() {
|
|
289
|
-
if (!this.contentWrapper) return;
|
|
290
|
-
|
|
291
|
-
const focusable = this.getFocusable(this.contentWrapper);
|
|
292
|
-
if (focusable[0]) {
|
|
293
|
-
focusable[0].focus();
|
|
294
|
-
} else {
|
|
295
|
-
this.contentWrapper.focus();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
private restoreFocus() {
|
|
300
|
-
(this.returnFocusEl ?? this.triggerEl)?.focus();
|
|
301
|
-
this.returnFocusEl = null;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private trapFocusLoop(e: KeyboardEvent) {
|
|
305
|
-
const focusable = this.getFocusable(this.contentWrapper);
|
|
306
|
-
if (focusable.length === 0) return;
|
|
307
|
-
|
|
308
|
-
const first = focusable[0];
|
|
309
|
-
const last = focusable[focusable.length - 1];
|
|
310
|
-
|
|
311
|
-
if (e.shiftKey && document.activeElement === first) {
|
|
312
|
-
e.preventDefault();
|
|
313
|
-
last.focus();
|
|
314
|
-
} else if (!e.shiftKey && document.activeElement === last) {
|
|
315
|
-
e.preventDefault();
|
|
316
|
-
first.focus();
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
private getFocusable(root: HTMLElement): HTMLElement[] {
|
|
321
|
-
return Array.from(
|
|
322
|
-
root.querySelectorAll<HTMLElement>(
|
|
323
|
-
'a[href],button:not([disabled]),input:not([disabled]),[tabindex]:not([tabindex="-1"])',
|
|
324
|
-
),
|
|
325
|
-
).filter((el) => el.offsetWidth || el.offsetHeight);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/* =======================
|
|
329
|
-
* POSITIONING
|
|
330
|
-
* ======================= */
|
|
331
|
-
|
|
332
|
-
private updatePosition = () => {
|
|
333
|
-
if (!this.triggerEl || !this.contentWrapper) return;
|
|
334
|
-
|
|
335
|
-
this.contentWrapper.setAttribute("open", "");
|
|
336
|
-
|
|
337
|
-
const t = this.triggerEl.getBoundingClientRect();
|
|
338
|
-
const c = this.contentWrapper.getBoundingClientRect();
|
|
339
|
-
|
|
340
|
-
let top = 0;
|
|
341
|
-
let left = 0;
|
|
342
|
-
|
|
343
|
-
// Alignment along the cross axis. Previously every branch hardcoded the
|
|
344
|
-
// centre calculation, so `align` and `alignOffset` were dead code —
|
|
345
|
-
// "center" worked by accident and "start"/"end" did nothing.
|
|
346
|
-
const alignAxis = (start: number, size: number, contentSize: number) => {
|
|
347
|
-
switch (this.align) {
|
|
348
|
-
case "start": return start;
|
|
349
|
-
case "end": return start + size - contentSize;
|
|
350
|
-
default: return start + size / 2 - contentSize / 2; // center
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
if (this.side === "top") {
|
|
355
|
-
top = t.top - c.height - this.sideOffset;
|
|
356
|
-
left = alignAxis(t.left, t.width, c.width) + this.alignOffset;
|
|
357
|
-
}
|
|
358
|
-
if (this.side === "bottom") {
|
|
359
|
-
top = t.bottom + this.sideOffset;
|
|
360
|
-
left = alignAxis(t.left, t.width, c.width) + this.alignOffset;
|
|
361
|
-
}
|
|
362
|
-
if (this.side === "left") {
|
|
363
|
-
top = alignAxis(t.top, t.height, c.height) + this.alignOffset;
|
|
364
|
-
left = t.left - c.width - this.sideOffset;
|
|
365
|
-
}
|
|
366
|
-
if (this.side === "right") {
|
|
367
|
-
top = alignAxis(t.top, t.height, c.height) + this.alignOffset;
|
|
368
|
-
left = t.right + this.sideOffset;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
top = Math.max(8, Math.min(top, window.innerHeight - c.height - 8));
|
|
372
|
-
left = Math.max(8, Math.min(left, window.innerWidth - c.width - 8));
|
|
373
|
-
|
|
374
|
-
// Without top-layer support a transformed ancestor may be the containing
|
|
375
|
-
// block for position:fixed, shifting the wrapper away from the viewport
|
|
376
|
-
// coordinates computed above. Apply, re-measure, and cancel the offset.
|
|
377
|
-
this.contentWrapper.style.top = `${top}px`;
|
|
378
|
-
this.contentWrapper.style.left = `${left}px`;
|
|
379
|
-
const applied = this.contentWrapper.getBoundingClientRect();
|
|
380
|
-
top -= applied.top - top;
|
|
381
|
-
left -= applied.left - left;
|
|
382
|
-
this.contentWrapper.style.top = `${top}px`;
|
|
383
|
-
this.contentWrapper.style.left = `${left}px`;
|
|
384
|
-
|
|
385
|
-
this.contentStyle = `top:${top}px;left:${left}px;`;
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
/* =======================
|
|
389
|
-
* ARIA
|
|
390
|
-
* ======================= */
|
|
391
|
-
|
|
392
|
-
private onTriggerSlotChange(e: Event) {
|
|
393
|
-
const slot = e.target as HTMLSlotElement;
|
|
394
|
-
const first = slot.assignedElements({ flatten: true })[0] as HTMLElement | undefined;
|
|
395
|
-
this.triggerEl = first ?? null;
|
|
396
|
-
this.applyTriggerAria();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
private applyTriggerAria() {
|
|
400
|
-
if (!this.triggerEl) return;
|
|
401
|
-
this.triggerEl.setAttribute("aria-haspopup", "dialog");
|
|
402
|
-
this.triggerEl.setAttribute("aria-expanded", String(this.activeOpen));
|
|
403
|
-
this.triggerEl.setAttribute("aria-controls", this.uniqueId);
|
|
404
|
-
|
|
405
|
-
// A bare <div>/<span> trigger must be Tab-reachable. Skip anything that
|
|
406
|
-
// is (or contains) natively focusable so buttons don't get a second stop.
|
|
407
|
-
if (
|
|
408
|
-
this.triggerEl.tabIndex < 0 &&
|
|
409
|
-
!this.triggerEl.hasAttribute("tabindex") &&
|
|
410
|
-
getFocusableElements(this.triggerEl).length === 0
|
|
411
|
-
) {
|
|
412
|
-
this.triggerEl.setAttribute("tabindex", "0");
|
|
413
|
-
if (!this.triggerEl.hasAttribute("role")) {
|
|
414
|
-
this.triggerEl.setAttribute("role", "button");
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/* =======================
|
|
420
|
-
* RENDER
|
|
421
|
-
* ======================= */
|
|
422
|
-
|
|
423
|
-
render() {
|
|
424
|
-
return html`
|
|
425
|
-
<slot @slotchange=${this.onTriggerSlotChange}></slot>
|
|
426
|
-
|
|
427
|
-
<div
|
|
428
|
-
id="${this.uniqueId}"
|
|
429
|
-
class="content-wrapper"
|
|
430
|
-
popover="manual"
|
|
431
|
-
style="${this.contentStyle}"
|
|
432
|
-
?open="${this.activeOpen}"
|
|
433
|
-
>
|
|
434
|
-
<slot name="content"></slot>
|
|
435
|
-
</div>
|
|
436
|
-
`;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
declare global {
|
|
441
|
-
interface HTMLElementTagNameMap {
|
|
442
|
-
"rel-popover": RelPopover;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
2
|
-
import { customElement, property } from "lit/decorators.js";
|
|
3
|
-
|
|
4
|
-
export type ProgressMode =
|
|
5
|
-
| "linear-determinate"
|
|
6
|
-
| "linear-indeterminate"
|
|
7
|
-
| "circular-determinate"
|
|
8
|
-
| "circular-indeterminate";
|
|
9
|
-
|
|
10
|
-
@customElement("rel-progress")
|
|
11
|
-
export class RelProgress extends LitElement {
|
|
12
|
-
@property({ type: String, reflect: true })
|
|
13
|
-
mode: ProgressMode = "linear-determinate";
|
|
14
|
-
|
|
15
|
-
@property({ type: Number })
|
|
16
|
-
value = 0;
|
|
17
|
-
|
|
18
|
-
private radius = 15.9155;
|
|
19
|
-
private circumference = 2 * Math.PI * this.radius;
|
|
20
|
-
|
|
21
|
-
static styles = css`
|
|
22
|
-
:host {
|
|
23
|
-
color: var(--rel-text-default);
|
|
24
|
-
display: block;
|
|
25
|
-
font-family: var(--rel-fonts-font-family);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
:host([mode*="circular"]) {
|
|
29
|
-
display: inline-block;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/* Linear */
|
|
33
|
-
.linear-track {
|
|
34
|
-
background: var(--rel-bg-neutral-elevated-2);
|
|
35
|
-
border-radius: var(--rel-border-radius-square);
|
|
36
|
-
height: 6px;
|
|
37
|
-
overflow: hidden;
|
|
38
|
-
width: 100%;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.linear-bar {
|
|
42
|
-
background: var(--rel-bg-neutral-inverse-1);
|
|
43
|
-
height: 100%;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/* Width animates only when value-driven (Figma Type=Determinate) */
|
|
47
|
-
.linear-bar.determinate {
|
|
48
|
-
transition: width 0.3s ease;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.linear-bar.indeterminate {
|
|
52
|
-
width: 30%;
|
|
53
|
-
animation: linear-indeterminate 1.5s infinite;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@keyframes linear-indeterminate {
|
|
57
|
-
0% {
|
|
58
|
-
margin-left: -30%;
|
|
59
|
-
}
|
|
60
|
-
50% {
|
|
61
|
-
margin-left: 50%;
|
|
62
|
-
}
|
|
63
|
-
100% {
|
|
64
|
-
margin-left: 100%;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/* Circular */
|
|
69
|
-
.circular {
|
|
70
|
-
display: inline-block;
|
|
71
|
-
width: 48px;
|
|
72
|
-
height: 48px;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.circular-svg {
|
|
76
|
-
transform: rotate(-90deg);
|
|
77
|
-
width: 100%;
|
|
78
|
-
height: 100%;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.circular-bg {
|
|
82
|
-
fill: none;
|
|
83
|
-
stroke: var(--rel-border-default);
|
|
84
|
-
stroke-width: 3;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.circular-progress {
|
|
88
|
-
fill: none;
|
|
89
|
-
stroke: var(--rel-text-default);
|
|
90
|
-
stroke-width: 3;
|
|
91
|
-
stroke-linecap: butt;
|
|
92
|
-
transition: stroke-dashoffset 0.35s;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/* Spinner */
|
|
96
|
-
.spinner {
|
|
97
|
-
animation: rotate 2s linear infinite;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.spinner-path {
|
|
101
|
-
stroke: var(--rel-text-default);
|
|
102
|
-
stroke-linecap: butt;
|
|
103
|
-
animation: dash 1.5s ease-in-out infinite;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@keyframes rotate {
|
|
107
|
-
100% {
|
|
108
|
-
transform: rotate(360deg);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
@keyframes dash {
|
|
113
|
-
0% {
|
|
114
|
-
stroke-dasharray: 1, 150;
|
|
115
|
-
stroke-dashoffset: 0;
|
|
116
|
-
}
|
|
117
|
-
50% {
|
|
118
|
-
stroke-dasharray: 90, 150;
|
|
119
|
-
stroke-dashoffset: -35;
|
|
120
|
-
}
|
|
121
|
-
100% {
|
|
122
|
-
stroke-dasharray: 90, 150;
|
|
123
|
-
stroke-dashoffset: -124;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@media (prefers-reduced-motion: reduce) {
|
|
128
|
-
.linear-bar {
|
|
129
|
-
transition: none;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.linear-bar.indeterminate {
|
|
133
|
-
animation: none;
|
|
134
|
-
width: 100%;
|
|
135
|
-
opacity: 0.5;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.spinner {
|
|
139
|
-
animation: none;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.spinner-path {
|
|
143
|
-
animation: none;
|
|
144
|
-
stroke-dasharray: 90, 150;
|
|
145
|
-
stroke-dashoffset: -35;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.circular-progress {
|
|
149
|
-
transition: none;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
`;
|
|
153
|
-
|
|
154
|
-
private get safeValue() {
|
|
155
|
-
return Math.min(100, Math.max(0, this.value));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private renderLinearDeterminate() {
|
|
159
|
-
return html`
|
|
160
|
-
<div part="track" class="linear-track" role="progressbar"
|
|
161
|
-
aria-valuenow="${this.safeValue}" aria-valuemin="0" aria-valuemax="100">
|
|
162
|
-
<div part="indicator" class="linear-bar determinate" style="width: ${this.safeValue}%"></div>
|
|
163
|
-
</div>
|
|
164
|
-
`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private renderLinearIndeterminate() {
|
|
168
|
-
return html`
|
|
169
|
-
<div part="track" class="linear-track" role="progressbar">
|
|
170
|
-
<div part="indicator" class="linear-bar indeterminate"></div>
|
|
171
|
-
</div>
|
|
172
|
-
`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private renderCircularDeterminate() {
|
|
176
|
-
const offset =
|
|
177
|
-
this.circumference - (this.safeValue / 100) * this.circumference;
|
|
178
|
-
|
|
179
|
-
return html`
|
|
180
|
-
<div class="circular">
|
|
181
|
-
<svg viewBox="0 0 36 36" class="circular-svg">
|
|
182
|
-
<circle class="circular-bg" cx="18" cy="18" r="${this.radius}" />
|
|
183
|
-
<circle
|
|
184
|
-
class="circular-progress"
|
|
185
|
-
cx="18"
|
|
186
|
-
cy="18"
|
|
187
|
-
r="${this.radius}"
|
|
188
|
-
stroke-dasharray="${this.circumference}"
|
|
189
|
-
stroke-dashoffset="${offset}"
|
|
190
|
-
/>
|
|
191
|
-
</svg>
|
|
192
|
-
</div>
|
|
193
|
-
`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private renderCircularIndeterminate() {
|
|
197
|
-
return html`
|
|
198
|
-
<div class="circular">
|
|
199
|
-
<svg viewBox="0 0 50 50" class="spinner">
|
|
200
|
-
<circle
|
|
201
|
-
class="spinner-path"
|
|
202
|
-
cx="25"
|
|
203
|
-
cy="25"
|
|
204
|
-
r="20"
|
|
205
|
-
fill="none"
|
|
206
|
-
stroke-width="4"
|
|
207
|
-
/>
|
|
208
|
-
</svg>
|
|
209
|
-
</div>
|
|
210
|
-
`;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
render() {
|
|
214
|
-
switch (this.mode) {
|
|
215
|
-
case "linear-indeterminate":
|
|
216
|
-
return this.renderLinearIndeterminate();
|
|
217
|
-
case "circular-determinate":
|
|
218
|
-
return this.renderCircularDeterminate();
|
|
219
|
-
case "circular-indeterminate":
|
|
220
|
-
return this.renderCircularIndeterminate();
|
|
221
|
-
default:
|
|
222
|
-
return this.renderLinearDeterminate();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|