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