@lmfaole/basics 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -0
- package/components/basic-accordion/index.d.ts +50 -0
- package/components/basic-accordion/index.js +387 -0
- package/components/basic-accordion/register.d.ts +1 -0
- package/components/basic-accordion/register.js +3 -0
- package/components/basic-dialog/index.d.ts +36 -0
- package/components/basic-dialog/index.js +272 -0
- package/components/basic-dialog/register.d.ts +1 -0
- package/components/basic-dialog/register.js +3 -0
- package/components/basic-popover/index.d.ts +70 -0
- package/components/basic-popover/index.js +460 -0
- package/components/basic-popover/register.d.ts +1 -0
- package/components/basic-popover/register.js +3 -0
- package/components/basic-tabs/index.js +12 -8
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/package.json +116 -81
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
const ElementBase = globalThis.Element ?? class {};
|
|
2
|
+
const HTMLElementBase = globalThis.HTMLElement ?? class {};
|
|
3
|
+
const HTMLButtonElementBase = globalThis.HTMLButtonElement ?? class {};
|
|
4
|
+
const HTMLDialogElementBase = globalThis.HTMLDialogElement ?? class {};
|
|
5
|
+
|
|
6
|
+
export const DIALOG_TAG_NAME = "basic-dialog";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LABEL = "Dialog";
|
|
9
|
+
const PANEL_SELECTOR = "[data-dialog-panel]";
|
|
10
|
+
const TITLE_SELECTOR = "[data-dialog-title]";
|
|
11
|
+
const OPEN_SELECTOR = "[data-dialog-open]";
|
|
12
|
+
const CLOSE_SELECTOR = "[data-dialog-close]";
|
|
13
|
+
const MANAGED_LABEL_ATTRIBUTE = "data-basic-dialog-managed-label";
|
|
14
|
+
const MANAGED_LABELLEDBY_ATTRIBUTE = "data-basic-dialog-managed-labelledby";
|
|
15
|
+
|
|
16
|
+
let nextDialogInstanceId = 1;
|
|
17
|
+
|
|
18
|
+
function collectOwnedElements(root, scope, selector) {
|
|
19
|
+
return Array.from(scope.querySelectorAll(selector)).filter(
|
|
20
|
+
(element) => element instanceof HTMLElementBase && element.closest(DIALOG_TAG_NAME) === root,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeDialogCloseValue(value) {
|
|
25
|
+
return value?.trim() ?? "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function normalizeDialogBackdropClose(value) {
|
|
29
|
+
if (value == null) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const normalized = value.trim().toLowerCase();
|
|
34
|
+
return normalized === "" || normalized === "true";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizeDialogLabel(value) {
|
|
38
|
+
return value?.trim() || DEFAULT_LABEL;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class DialogElement extends HTMLElementBase {
|
|
42
|
+
static observedAttributes = ["data-backdrop-close", "data-label"];
|
|
43
|
+
|
|
44
|
+
#instanceId = `${DIALOG_TAG_NAME}-${nextDialogInstanceId++}`;
|
|
45
|
+
#panel = null;
|
|
46
|
+
#panelWithEvents = null;
|
|
47
|
+
#title = null;
|
|
48
|
+
#openButtons = [];
|
|
49
|
+
#closeButtons = [];
|
|
50
|
+
#restoreFocusTo = null;
|
|
51
|
+
#eventsBound = false;
|
|
52
|
+
|
|
53
|
+
connectedCallback() {
|
|
54
|
+
if (!this.#eventsBound) {
|
|
55
|
+
this.addEventListener("click", this.#handleClick);
|
|
56
|
+
this.#eventsBound = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.#sync();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
disconnectedCallback() {
|
|
63
|
+
if (this.#eventsBound) {
|
|
64
|
+
this.removeEventListener("click", this.#handleClick);
|
|
65
|
+
this.#eventsBound = false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.#syncPanelEvents(null);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
attributeChangedCallback() {
|
|
72
|
+
this.#sync();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
showModal(opener = null) {
|
|
76
|
+
this.#sync();
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
!(this.#panel instanceof HTMLDialogElementBase)
|
|
80
|
+
|| typeof this.#panel.showModal !== "function"
|
|
81
|
+
) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (this.#panel.open) {
|
|
86
|
+
this.#applyState();
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const fallbackOpener = opener instanceof HTMLElementBase
|
|
91
|
+
? opener
|
|
92
|
+
: this.ownerDocument?.activeElement instanceof HTMLElementBase
|
|
93
|
+
? this.ownerDocument.activeElement
|
|
94
|
+
: null;
|
|
95
|
+
|
|
96
|
+
this.#restoreFocusTo = fallbackOpener;
|
|
97
|
+
this.#panel.showModal();
|
|
98
|
+
this.#applyState();
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
close(returnValue = "") {
|
|
103
|
+
if (
|
|
104
|
+
!(this.#panel instanceof HTMLDialogElementBase)
|
|
105
|
+
|| typeof this.#panel.close !== "function"
|
|
106
|
+
|| !this.#panel.open
|
|
107
|
+
) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.#panel.close(returnValue);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#handleClick = (event) => {
|
|
116
|
+
if (!(event.target instanceof ElementBase)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const openButton = event.target.closest(OPEN_SELECTOR);
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
openButton instanceof HTMLElementBase
|
|
124
|
+
&& openButton.closest(DIALOG_TAG_NAME) === this
|
|
125
|
+
) {
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
this.showModal(openButton);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const closeButton = event.target.closest(CLOSE_SELECTOR);
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
closeButton instanceof HTMLElementBase
|
|
135
|
+
&& closeButton.closest(DIALOG_TAG_NAME) === this
|
|
136
|
+
) {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
this.close(normalizeDialogCloseValue(
|
|
139
|
+
closeButton.getAttribute("data-dialog-close-value"),
|
|
140
|
+
));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
event.target === this.#panel
|
|
146
|
+
&& normalizeDialogBackdropClose(this.getAttribute("data-backdrop-close"))
|
|
147
|
+
) {
|
|
148
|
+
this.close();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
#handleDialogClose = () => {
|
|
153
|
+
this.#applyState();
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
this.#restoreFocusTo instanceof HTMLElementBase
|
|
157
|
+
&& this.#restoreFocusTo.isConnected
|
|
158
|
+
) {
|
|
159
|
+
this.#restoreFocusTo.focus();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.#restoreFocusTo = null;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
#handleDialogCancel = () => {
|
|
166
|
+
this.#applyState();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
#sync() {
|
|
170
|
+
const nextPanel = collectOwnedElements(this, this, PANEL_SELECTOR)[0] ?? null;
|
|
171
|
+
const nextTitle = collectOwnedElements(this, this, TITLE_SELECTOR)[0] ?? null;
|
|
172
|
+
|
|
173
|
+
this.#syncPanelEvents(nextPanel instanceof HTMLDialogElementBase ? nextPanel : null);
|
|
174
|
+
this.#panel = nextPanel instanceof HTMLDialogElementBase ? nextPanel : null;
|
|
175
|
+
this.#title = nextTitle instanceof HTMLElementBase ? nextTitle : null;
|
|
176
|
+
this.#openButtons = collectOwnedElements(this, this, OPEN_SELECTOR);
|
|
177
|
+
this.#closeButtons = collectOwnedElements(this, this, CLOSE_SELECTOR);
|
|
178
|
+
this.#applyState();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#syncPanelEvents(nextPanel) {
|
|
182
|
+
if (this.#panelWithEvents === nextPanel) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (this.#panelWithEvents instanceof HTMLDialogElementBase) {
|
|
187
|
+
this.#panelWithEvents.removeEventListener("close", this.#handleDialogClose);
|
|
188
|
+
this.#panelWithEvents.removeEventListener("cancel", this.#handleDialogCancel);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (nextPanel instanceof HTMLDialogElementBase) {
|
|
192
|
+
nextPanel.addEventListener("close", this.#handleDialogClose);
|
|
193
|
+
nextPanel.addEventListener("cancel", this.#handleDialogCancel);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.#panelWithEvents = nextPanel;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#applyState() {
|
|
200
|
+
for (const button of [...this.#openButtons, ...this.#closeButtons]) {
|
|
201
|
+
if (button instanceof HTMLButtonElementBase && !button.hasAttribute("type")) {
|
|
202
|
+
button.type = "button";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!(this.#panel instanceof HTMLDialogElementBase)) {
|
|
207
|
+
this.toggleAttribute("data-open", false);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const baseId = this.id || this.#instanceId;
|
|
212
|
+
|
|
213
|
+
if (this.#title instanceof HTMLElementBase && !this.#title.id) {
|
|
214
|
+
this.#title.id = `${baseId}-title`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.#panel.setAttribute("aria-modal", "true");
|
|
218
|
+
this.#syncAccessibleLabel();
|
|
219
|
+
this.toggleAttribute("data-open", this.#panel.open);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#syncAccessibleLabel() {
|
|
223
|
+
if (!(this.#panel instanceof HTMLDialogElementBase)) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (this.#title?.id) {
|
|
228
|
+
if (this.#panel.hasAttribute(MANAGED_LABEL_ATTRIBUTE)) {
|
|
229
|
+
this.#panel.removeAttribute("aria-label");
|
|
230
|
+
this.#panel.removeAttribute(MANAGED_LABEL_ATTRIBUTE);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
!this.#panel.hasAttribute("aria-labelledby")
|
|
235
|
+
|| this.#panel.hasAttribute(MANAGED_LABELLEDBY_ATTRIBUTE)
|
|
236
|
+
) {
|
|
237
|
+
this.#panel.setAttribute("aria-labelledby", this.#title.id);
|
|
238
|
+
this.#panel.setAttribute(MANAGED_LABELLEDBY_ATTRIBUTE, "");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (this.#panel.hasAttribute(MANAGED_LABELLEDBY_ATTRIBUTE)) {
|
|
245
|
+
this.#panel.removeAttribute("aria-labelledby");
|
|
246
|
+
this.#panel.removeAttribute(MANAGED_LABELLEDBY_ATTRIBUTE);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
!this.#panel.hasAttribute("aria-label")
|
|
251
|
+
|| this.#panel.hasAttribute(MANAGED_LABEL_ATTRIBUTE)
|
|
252
|
+
) {
|
|
253
|
+
this.#panel.setAttribute(
|
|
254
|
+
"aria-label",
|
|
255
|
+
normalizeDialogLabel(this.getAttribute("data-label")),
|
|
256
|
+
);
|
|
257
|
+
this.#panel.setAttribute(MANAGED_LABEL_ATTRIBUTE, "");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function defineDialog(registry = globalThis.customElements) {
|
|
263
|
+
if (!registry?.get || !registry?.define) {
|
|
264
|
+
return DialogElement;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!registry.get(DIALOG_TAG_NAME)) {
|
|
268
|
+
registry.define(DIALOG_TAG_NAME, DialogElement);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return DialogElement;
|
|
272
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const POPOVER_TAG_NAME: "basic-popover";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes unsupported or empty labels back to the default `"Popover"`.
|
|
5
|
+
*/
|
|
6
|
+
export function normalizePopoverLabel(
|
|
7
|
+
value?: string | null,
|
|
8
|
+
): string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Normalizes the root `data-anchor-trigger` attribute into a boolean flag.
|
|
12
|
+
*/
|
|
13
|
+
export function normalizePopoverAnchorTrigger(
|
|
14
|
+
value?: string | null,
|
|
15
|
+
): boolean;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes unsupported or empty position-area values back to `"bottom"`.
|
|
19
|
+
*/
|
|
20
|
+
export function normalizePopoverPositionArea(
|
|
21
|
+
value?: string | null,
|
|
22
|
+
): string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns the built-in fallback sequence used for a given default anchored placement.
|
|
26
|
+
*/
|
|
27
|
+
export function getDefaultPopoverPositionTryFallbacks(
|
|
28
|
+
positionArea?: string | null,
|
|
29
|
+
): string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Normalizes custom fallback values and otherwise returns the built-in fallback
|
|
33
|
+
* sequence derived from the default position area.
|
|
34
|
+
*/
|
|
35
|
+
export function normalizePopoverPositionTryFallbacks(
|
|
36
|
+
value?: string | null,
|
|
37
|
+
positionArea?: string | null,
|
|
38
|
+
): string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns whether the managed popover panel is currently open.
|
|
42
|
+
*/
|
|
43
|
+
export function isPopoverOpen(
|
|
44
|
+
panel: HTMLElement | null | undefined,
|
|
45
|
+
): boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Custom element that upgrades popover trigger-and-panel markup into a
|
|
49
|
+
* non-modal overlay flow.
|
|
50
|
+
*
|
|
51
|
+
* Attributes:
|
|
52
|
+
* - `data-anchor-trigger`: establishes the opener as the popover's implicit anchor
|
|
53
|
+
* - `data-label`: fallback accessible name when the popover has no title
|
|
54
|
+
* - `data-position-area`: CSS anchor-positioning area to use when anchoring is enabled
|
|
55
|
+
* - `data-position-try-fallbacks`: optional CSS fallback list to try when the
|
|
56
|
+
* default anchored position would overflow
|
|
57
|
+
*/
|
|
58
|
+
export class PopoverElement extends HTMLElement {
|
|
59
|
+
static observedAttributes: string[];
|
|
60
|
+
show(opener?: HTMLElement | null): boolean;
|
|
61
|
+
hide(): boolean;
|
|
62
|
+
toggle(opener?: HTMLElement | null): boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Registers the `basic-popover` custom element if it is not already defined.
|
|
67
|
+
*/
|
|
68
|
+
export function definePopover(
|
|
69
|
+
registry?: CustomElementRegistry,
|
|
70
|
+
): typeof PopoverElement;
|