@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.
Files changed (41) hide show
  1. package/README.md +306 -22
  2. package/basic-styling/components/basic-accordion.css +65 -0
  3. package/basic-styling/components/basic-alert.css +27 -0
  4. package/basic-styling/components/basic-dialog.css +41 -0
  5. package/basic-styling/components/basic-popover.css +54 -0
  6. package/basic-styling/components/basic-summary-table.css +76 -0
  7. package/basic-styling/components/basic-table.css +48 -0
  8. package/basic-styling/components/basic-tabs.css +45 -0
  9. package/basic-styling/components/basic-toast.css +102 -0
  10. package/basic-styling/components/basic-toc.css +30 -0
  11. package/basic-styling/components.css +9 -0
  12. package/basic-styling/global.css +61 -0
  13. package/basic-styling/index.css +2 -0
  14. package/basic-styling/tokens/base.css +19 -0
  15. package/basic-styling/tokens/palette.css +117 -0
  16. package/basic-styling/tokens/palette.tokens.json +1019 -0
  17. package/components/basic-accordion/index.d.ts +5 -5
  18. package/components/basic-accordion/index.js +169 -165
  19. package/components/basic-alert/index.d.ts +53 -0
  20. package/components/basic-alert/index.js +189 -0
  21. package/components/basic-alert/register.d.ts +1 -0
  22. package/components/basic-alert/register.js +3 -0
  23. package/components/basic-dialog/index.js +21 -1
  24. package/components/basic-summary-table/index.d.ts +69 -0
  25. package/components/basic-summary-table/index.js +536 -0
  26. package/components/basic-summary-table/register.d.ts +1 -0
  27. package/components/basic-summary-table/register.js +3 -0
  28. package/components/basic-table/index.d.ts +75 -0
  29. package/components/basic-table/index.js +612 -0
  30. package/components/basic-table/register.d.ts +1 -0
  31. package/components/basic-table/register.js +3 -0
  32. package/components/basic-tabs/index.d.ts +2 -3
  33. package/components/basic-tabs/index.js +4 -30
  34. package/components/basic-toast/index.d.ts +65 -0
  35. package/components/basic-toast/index.js +429 -0
  36. package/components/basic-toast/register.d.ts +1 -0
  37. package/components/basic-toast/register.js +3 -0
  38. package/index.d.ts +4 -0
  39. package/index.js +4 -0
  40. package/package.json +28 -41
  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 {};
@@ -0,0 +1,3 @@
1
+ import { defineAlert } from "./index.js";
2
+
3
+ defineAlert();
@@ -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
- event.target === this.#panel
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;