@lmfaole/basics 0.2.1 → 0.4.0
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 +306 -22
- package/basic-styling/components/basic-accordion.css +65 -0
- package/basic-styling/components/basic-alert.css +27 -0
- package/basic-styling/components/basic-dialog.css +41 -0
- package/basic-styling/components/basic-popover.css +54 -0
- package/basic-styling/components/basic-summary-table.css +76 -0
- package/basic-styling/components/basic-table.css +48 -0
- package/basic-styling/components/basic-tabs.css +45 -0
- package/basic-styling/components/basic-toast.css +102 -0
- package/basic-styling/components/basic-toc.css +30 -0
- package/basic-styling/components.css +9 -0
- package/basic-styling/global.css +61 -0
- package/basic-styling/index.css +2 -0
- package/basic-styling/tokens/base.css +19 -0
- package/basic-styling/tokens/palette.css +117 -0
- package/basic-styling/tokens/palette.tokens.json +1019 -0
- package/components/basic-accordion/index.d.ts +5 -5
- package/components/basic-accordion/index.js +169 -165
- package/components/basic-alert/index.d.ts +53 -0
- package/components/basic-alert/index.js +189 -0
- package/components/basic-alert/register.d.ts +1 -0
- package/components/basic-alert/register.js +3 -0
- package/components/basic-dialog/index.js +21 -1
- package/components/basic-summary-table/index.d.ts +69 -0
- package/components/basic-summary-table/index.js +536 -0
- package/components/basic-summary-table/register.d.ts +1 -0
- package/components/basic-summary-table/register.js +3 -0
- package/components/basic-table/index.d.ts +75 -0
- package/components/basic-table/index.js +612 -0
- package/components/basic-table/register.d.ts +1 -0
- package/components/basic-table/register.js +3 -0
- package/components/basic-tabs/index.d.ts +2 -3
- package/components/basic-tabs/index.js +4 -30
- package/components/basic-toast/index.d.ts +65 -0
- package/components/basic-toast/index.js +429 -0
- package/components/basic-toast/register.d.ts +1 -0
- package/components/basic-toast/register.js +3 -0
- package/index.d.ts +4 -0
- package/index.js +4 -0
- package/package.json +28 -41
- package/readme.mdx +1 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const ElementBase = globalThis.Element ?? class {};
|
|
2
|
+
const HTMLElementBase = globalThis.HTMLElement ?? class {};
|
|
3
|
+
const HTMLButtonElementBase = globalThis.HTMLButtonElement ?? class {};
|
|
4
|
+
|
|
5
|
+
export const ALERT_TAG_NAME = "basic-alert";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_LABEL = "Alert";
|
|
8
|
+
const DEFAULT_LIVE = "assertive";
|
|
9
|
+
const TITLE_SELECTOR = "[data-alert-title]";
|
|
10
|
+
const CLOSE_SELECTOR = "[data-alert-close]";
|
|
11
|
+
const MANAGED_LABEL_ATTRIBUTE = "data-basic-alert-managed-label";
|
|
12
|
+
const MANAGED_LABELLEDBY_ATTRIBUTE = "data-basic-alert-managed-labelledby";
|
|
13
|
+
|
|
14
|
+
let nextAlertInstanceId = 1;
|
|
15
|
+
|
|
16
|
+
function collectOwnedElements(root, scope, selector) {
|
|
17
|
+
return Array.from(scope.querySelectorAll(selector)).filter(
|
|
18
|
+
(element) => element instanceof HTMLElementBase && element.closest(ALERT_TAG_NAME) === root,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function normalizeAlertLabel(value) {
|
|
23
|
+
return value?.trim() || DEFAULT_LABEL;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function normalizeAlertLive(value) {
|
|
27
|
+
const normalized = value?.trim().toLowerCase();
|
|
28
|
+
return normalized === "polite" ? "polite" : DEFAULT_LIVE;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getAlertRoleForLive(value) {
|
|
32
|
+
return normalizeAlertLive(value) === "polite" ? "status" : "alert";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeAlertOpen(value, hidden = false) {
|
|
36
|
+
if (hidden) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (value == null) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const normalized = value.trim().toLowerCase();
|
|
45
|
+
return normalized === "" || normalized === "true" || normalized === "1";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class AlertElement extends HTMLElementBase {
|
|
49
|
+
static observedAttributes = ["data-label", "data-live", "data-open", "hidden"];
|
|
50
|
+
|
|
51
|
+
#instanceId = `${ALERT_TAG_NAME}-${nextAlertInstanceId++}`;
|
|
52
|
+
#title = null;
|
|
53
|
+
#closeButtons = [];
|
|
54
|
+
#eventsBound = false;
|
|
55
|
+
|
|
56
|
+
connectedCallback() {
|
|
57
|
+
if (!this.#eventsBound) {
|
|
58
|
+
this.addEventListener("click", this.#handleClick);
|
|
59
|
+
this.#eventsBound = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.#sync();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
disconnectedCallback() {
|
|
66
|
+
if (!this.#eventsBound) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.removeEventListener("click", this.#handleClick);
|
|
71
|
+
this.#eventsBound = false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
attributeChangedCallback() {
|
|
75
|
+
this.#sync();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
show() {
|
|
79
|
+
this.hidden = false;
|
|
80
|
+
this.toggleAttribute("data-open", true);
|
|
81
|
+
this.#sync();
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
hide() {
|
|
86
|
+
this.hidden = true;
|
|
87
|
+
this.toggleAttribute("data-open", false);
|
|
88
|
+
this.#sync();
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#handleClick = (event) => {
|
|
93
|
+
if (!(event.target instanceof ElementBase)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const closeButton = event.target.closest(CLOSE_SELECTOR);
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
closeButton instanceof HTMLElementBase
|
|
101
|
+
&& closeButton.closest(ALERT_TAG_NAME) === this
|
|
102
|
+
) {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
this.hide();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
#sync() {
|
|
109
|
+
const nextTitle = collectOwnedElements(this, this, TITLE_SELECTOR)[0] ?? null;
|
|
110
|
+
|
|
111
|
+
this.#title = nextTitle instanceof HTMLElementBase ? nextTitle : null;
|
|
112
|
+
this.#closeButtons = collectOwnedElements(this, this, CLOSE_SELECTOR);
|
|
113
|
+
this.#applyState();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#applyState() {
|
|
117
|
+
for (const button of this.#closeButtons) {
|
|
118
|
+
if (button instanceof HTMLButtonElementBase && !button.hasAttribute("type")) {
|
|
119
|
+
button.type = "button";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const open = normalizeAlertOpen(this.getAttribute("data-open"), this.hidden);
|
|
124
|
+
const baseId = this.id || this.#instanceId;
|
|
125
|
+
|
|
126
|
+
if (this.#title instanceof HTMLElementBase && !this.#title.id) {
|
|
127
|
+
this.#title.id = `${baseId}-title`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.hidden = !open;
|
|
131
|
+
this.toggleAttribute("data-open", open);
|
|
132
|
+
this.setAttribute("role", getAlertRoleForLive(this.getAttribute("data-live")));
|
|
133
|
+
this.setAttribute("aria-live", normalizeAlertLive(this.getAttribute("data-live")));
|
|
134
|
+
this.setAttribute("aria-atomic", "true");
|
|
135
|
+
this.#syncAccessibleLabel();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#syncAccessibleLabel() {
|
|
139
|
+
const nextLabel = normalizeAlertLabel(this.getAttribute("data-label"));
|
|
140
|
+
const hasManagedLabel = this.hasAttribute(MANAGED_LABEL_ATTRIBUTE);
|
|
141
|
+
const hasManagedLabelledBy = this.hasAttribute(MANAGED_LABELLEDBY_ATTRIBUTE);
|
|
142
|
+
|
|
143
|
+
if (hasManagedLabel && this.getAttribute("aria-label") !== nextLabel) {
|
|
144
|
+
this.removeAttribute("aria-label");
|
|
145
|
+
this.removeAttribute(MANAGED_LABEL_ATTRIBUTE);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (this.#title?.id) {
|
|
149
|
+
if (this.hasAttribute(MANAGED_LABEL_ATTRIBUTE)) {
|
|
150
|
+
this.removeAttribute("aria-label");
|
|
151
|
+
this.removeAttribute(MANAGED_LABEL_ATTRIBUTE);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!this.hasAttribute("aria-labelledby") || hasManagedLabelledBy) {
|
|
155
|
+
this.setAttribute("aria-labelledby", this.#title.id);
|
|
156
|
+
this.setAttribute(MANAGED_LABELLEDBY_ATTRIBUTE, "");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (hasManagedLabelledBy) {
|
|
163
|
+
this.removeAttribute("aria-labelledby");
|
|
164
|
+
this.removeAttribute(MANAGED_LABELLEDBY_ATTRIBUTE);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const hasOwnAriaLabel = this.hasAttribute("aria-label") && !this.hasAttribute(MANAGED_LABEL_ATTRIBUTE);
|
|
168
|
+
const hasOwnLabelledBy = this.hasAttribute("aria-labelledby");
|
|
169
|
+
|
|
170
|
+
if (hasOwnAriaLabel || hasOwnLabelledBy) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.setAttribute("aria-label", nextLabel);
|
|
175
|
+
this.setAttribute(MANAGED_LABEL_ATTRIBUTE, "");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function defineAlert(registry = globalThis.customElements) {
|
|
180
|
+
if (!registry?.get || !registry?.define) {
|
|
181
|
+
return AlertElement;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!registry.get(ALERT_TAG_NAME)) {
|
|
185
|
+
registry.define(ALERT_TAG_NAME, AlertElement);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return AlertElement;
|
|
189
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -38,6 +38,25 @@ export function normalizeDialogLabel(value) {
|
|
|
38
38
|
return value?.trim() || DEFAULT_LABEL;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function isDialogBackdropClick(event, dialog) {
|
|
42
|
+
if (event.target !== dialog) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { clientX, clientY } = event;
|
|
47
|
+
|
|
48
|
+
if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bounds = dialog.getBoundingClientRect();
|
|
53
|
+
|
|
54
|
+
return clientX < bounds.left
|
|
55
|
+
|| clientX > bounds.right
|
|
56
|
+
|| clientY < bounds.top
|
|
57
|
+
|| clientY > bounds.bottom;
|
|
58
|
+
}
|
|
59
|
+
|
|
41
60
|
export class DialogElement extends HTMLElementBase {
|
|
42
61
|
static observedAttributes = ["data-backdrop-close", "data-label"];
|
|
43
62
|
|
|
@@ -142,8 +161,9 @@ export class DialogElement extends HTMLElementBase {
|
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
if (
|
|
145
|
-
|
|
164
|
+
this.#panel instanceof HTMLDialogElementBase
|
|
146
165
|
&& normalizeDialogBackdropClose(this.getAttribute("data-backdrop-close"))
|
|
166
|
+
&& isDialogBackdropClick(event, this.#panel)
|
|
147
167
|
) {
|
|
148
168
|
this.close();
|
|
149
169
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { TableElement } from "../basic-table";
|
|
2
|
+
|
|
3
|
+
export const SUMMARY_TABLE_TAG_NAME: "basic-summary-table";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes configured summary columns from a comma-separated one-based list.
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeSummaryColumns(
|
|
9
|
+
value?: string | null,
|
|
10
|
+
): number[];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Normalizes the generated footer row label back to `"Totalt"`.
|
|
14
|
+
*/
|
|
15
|
+
export function normalizeSummaryTotalLabel(
|
|
16
|
+
value?: string | null,
|
|
17
|
+
): string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalizes the optional `Intl.NumberFormat` locale.
|
|
21
|
+
*/
|
|
22
|
+
export function normalizeSummaryLocale(
|
|
23
|
+
value?: string | null,
|
|
24
|
+
): string | undefined;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parses common formatted number strings such as `1,200.50` or `1 200,50`.
|
|
28
|
+
*/
|
|
29
|
+
export function parseSummaryNumber(
|
|
30
|
+
value?: string | number | null,
|
|
31
|
+
): number | null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Formats a summary value for footer display.
|
|
35
|
+
*/
|
|
36
|
+
export function formatSummaryNumber(
|
|
37
|
+
value: number,
|
|
38
|
+
options?: {
|
|
39
|
+
locale?: string;
|
|
40
|
+
fractionDigits?: number;
|
|
41
|
+
},
|
|
42
|
+
): string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Custom element that upgrades a regular summary table with generated footer
|
|
46
|
+
* totals.
|
|
47
|
+
*
|
|
48
|
+
* Attributes:
|
|
49
|
+
* - `data-caption`: optional generated `<caption>` text when the table has none
|
|
50
|
+
* - `data-description`: optional generated description wired through `aria-describedby`
|
|
51
|
+
* - `data-label`: fallback accessible name when the table has neither a caption
|
|
52
|
+
* nor its own label
|
|
53
|
+
* - `data-row-headers`: enables generated row headers in tbody rows
|
|
54
|
+
* - `data-row-header-column`: one-based body column used for row headers
|
|
55
|
+
* - `data-summary-columns`: optional comma-separated one-based columns to total
|
|
56
|
+
* - `data-total-label`: label text used for the generated footer row
|
|
57
|
+
* - `data-locale`: optional locale used to format footer totals
|
|
58
|
+
*/
|
|
59
|
+
export class SummaryTableElement extends TableElement {
|
|
60
|
+
static observedAttributes: string[];
|
|
61
|
+
refresh(): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Registers the `basic-summary-table` custom element if it is not already defined.
|
|
66
|
+
*/
|
|
67
|
+
export function defineSummaryTable(
|
|
68
|
+
registry?: CustomElementRegistry,
|
|
69
|
+
): typeof SummaryTableElement;
|