@lmfaole/basics 0.2.1 → 0.3.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 CHANGED
@@ -224,7 +224,6 @@ The element upgrades existing markup into an accessible tab interface without ad
224
224
  ### Attributes
225
225
 
226
226
  - `data-label`: sets the generated tablist's accessible name when the tablist does not already have `aria-label` or `aria-labelledby`. Defaults to `Faner`.
227
- - `data-orientation`: sets arrow-key behavior and mirrors `aria-orientation` on the tablist. Supported values are `horizontal` and `vertical`.
228
227
  - `data-activation`: chooses whether arrow-key focus changes also activate the panel. Supported values are `automatic` and `manual`.
229
228
  - `data-selected-index`: sets the initially selected tab by zero-based index. Defaults to the first enabled tab.
230
229
 
@@ -232,7 +231,7 @@ The element upgrades existing markup into an accessible tab interface without ad
232
231
 
233
232
  - Missing tab and panel ids are generated automatically.
234
233
  - `aria-selected`, `aria-controls`, `aria-labelledby`, `hidden`, and `data-selected` stay in sync with the active tab.
235
- - Click, `Home`, `End`, and orientation-aware arrow keys move between tabs.
234
+ - Click, `ArrowLeft`, `ArrowRight`, `Home`, and `End` move between tabs.
236
235
  - Disabled tabs are skipped during keyboard navigation.
237
236
  - In `manual` mode, arrow keys move focus and `Enter` or `Space` activates the focused tab.
238
237
 
@@ -243,6 +242,142 @@ The element upgrades existing markup into an accessible tab interface without ad
243
242
  - Prefer `<button>` elements for tabs so click and keyboard activation stay native.
244
243
  - Keep layout and styling outside the package; the component only manages semantics, state, and keyboard behavior.
245
244
 
245
+ ## Basic Table
246
+
247
+ ```html
248
+ <basic-table
249
+ data-caption="Bemanning per sprint"
250
+ data-description="Viser team, lokasjon og ledig kapasitet per sprint."
251
+ data-row-headers
252
+ data-row-header-column="2"
253
+ >
254
+ <table>
255
+ <thead>
256
+ <tr>
257
+ <th>Statuskode</th>
258
+ <th>Team</th>
259
+ <th>Lokasjon</th>
260
+ <th>Ledige timer</th>
261
+ </tr>
262
+ </thead>
263
+ <tbody>
264
+ <tr>
265
+ <td>A1</td>
266
+ <td>Plattform</td>
267
+ <td>Oslo</td>
268
+ <td>18</td>
269
+ </tr>
270
+ <tr>
271
+ <td>B4</td>
272
+ <td>Designsystem</td>
273
+ <td>Trondheim</td>
274
+ <td>10</td>
275
+ </tr>
276
+ </tbody>
277
+ </table>
278
+ </basic-table>
279
+
280
+ <script type="module">
281
+ import "@lmfaole/basics/components/basic-table/register";
282
+ </script>
283
+ ```
284
+
285
+ The element upgrades a regular table with stronger accessible naming and header associations without imposing any styles.
286
+
287
+ ### Attributes
288
+
289
+ - `data-caption`: generates a visible `<caption>` when the wrapped table does not already define one.
290
+ - `data-column-headers`: promotes the first row to column headers when the author provides a plain table without a header row.
291
+ - `data-description`: generates a hidden description and connects it with `aria-describedby`.
292
+ - `data-label`: sets a fallback accessible name when the table has no caption, `aria-label`, or `aria-labelledby`. Defaults to `Tabell`.
293
+ - `data-row-header-column`: sets which one-based body column should become the generated row header. Defaults to `1`.
294
+ - `data-row-headers`: enables generated row headers in body rows. If `data-row-header-column` is present, row-header mode is enabled automatically.
295
+
296
+ ### Behavior
297
+
298
+ - Preserves author-provided captions and only generates one when needed.
299
+ - Can generate hidden helper text for extra context without requiring a separate authored description element.
300
+ - Can promote a plain first row to column headers when consumers start from simple body-only markup.
301
+ - Infers common `scope` values for header cells and assigns missing header ids.
302
+ - Populates each data cell's `headers` attribute from the matching row and column headers.
303
+ - Re-runs automatically when the wrapped table changes.
304
+
305
+ ### Markup Contract
306
+
307
+ - Provide one descendant `<table>` inside the custom element.
308
+ - Use real table sections and header cells where possible; the component strengthens semantics but does not replace the HTML table model.
309
+ - Add `data-row-headers` when one body column identifies each row, and use `data-row-header-column` when that column is not the first one.
310
+ - Add `data-column-headers` when you want the component to promote a plain first row instead of authoring a header row yourself.
311
+ - Keep layout and styling outside the package; the component only manages semantics and accessibility metadata.
312
+
313
+ ## Basic Summary Table
314
+
315
+ ```html
316
+ <basic-summary-table
317
+ data-caption="Prosjektsammendrag"
318
+ data-description="Viser timer og total kostnad for prosjektets leveranser."
319
+ data-row-headers
320
+ data-summary-columns="2,4"
321
+ data-total-label="Totalt"
322
+ >
323
+ <table>
324
+ <thead>
325
+ <tr>
326
+ <th>Post</th>
327
+ <th>Timer</th>
328
+ <th>Sats</th>
329
+ <th>Kostnad</th>
330
+ </tr>
331
+ </thead>
332
+ <tbody>
333
+ <tr>
334
+ <td>Analyse</td>
335
+ <td>8</td>
336
+ <td>100</td>
337
+ <td>800</td>
338
+ </tr>
339
+ <tr>
340
+ <td>Implementasjon</td>
341
+ <td>12</td>
342
+ <td>120</td>
343
+ <td>1440</td>
344
+ </tr>
345
+ </tbody>
346
+ </table>
347
+ </basic-summary-table>
348
+
349
+ <script type="module">
350
+ import "@lmfaole/basics/components/basic-summary-table/register";
351
+ </script>
352
+ ```
353
+
354
+ The element upgrades a calculation-heavy table with an automatically maintained totals row in `<tfoot>`.
355
+
356
+ ### Attributes
357
+
358
+ - `data-caption`: generates a visible `<caption>` when the wrapped table does not already define one.
359
+ - `data-description`: generates hidden helper text and connects it with `aria-describedby`.
360
+ - `data-label`: sets a fallback accessible name when the table has no caption, `aria-label`, or `aria-labelledby`. Defaults to `Tabell`.
361
+ - `data-row-headers`: enables generated row headers in body rows.
362
+ - `data-row-header-column`: sets which one-based body column should become the generated row header. Defaults to `1`.
363
+ - `data-summary-columns`: chooses which one-based columns should be totalled in the generated footer row. If omitted, numeric body columns are inferred automatically.
364
+ - `data-total-label`: sets the footer row label. Defaults to `Totalt`.
365
+ - `data-locale`: passes a locale through to `Intl.NumberFormat` for generated footer totals.
366
+
367
+ ### Behavior
368
+
369
+ - Inherits caption, description, row-header, and `headers` association behavior from `basic-table`.
370
+ - Parses numbers from cell text and supports raw calculation values through `data-value` on individual body cells.
371
+ - Generates or updates a totals row in `<tfoot>` without requiring consumers to author the footer manually.
372
+ - Recalculates totals automatically when body rows or `data-value` attributes change.
373
+
374
+ ### Markup Contract
375
+
376
+ - Provide one descendant `<table>` with line items in `<tbody>`.
377
+ - Prefer a label column such as `Post` or `Kategori` and enable `data-row-headers` so each line item remains easy to navigate.
378
+ - Use `data-value` on cells when the displayed text is formatted differently from the numeric value you want summed.
379
+ - Keep layout and styling outside the package; the component only manages semantics, totals, and footer structure.
380
+
246
381
  ## Basic Toc
247
382
 
248
383
  ```html
@@ -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;
@@ -0,0 +1,390 @@
1
+ import {
2
+ TableElement,
3
+ normalizeTableCellSpan,
4
+ normalizeTableRowHeaderColumn,
5
+ } from "../basic-table/index.js";
6
+
7
+ const HTMLTableElementBase = globalThis.HTMLTableElement ?? class {};
8
+
9
+ export const SUMMARY_TABLE_TAG_NAME = "basic-summary-table";
10
+
11
+ const DEFAULT_TOTAL_LABEL = "Totalt";
12
+ const GENERATED_SUMMARY_ROW_ATTRIBUTE = "data-basic-summary-table-generated-row";
13
+ const GENERATED_SUMMARY_CELL_ATTRIBUTE = "data-basic-summary-table-generated-cell";
14
+ const GENERATED_SUMMARY_LABEL_ATTRIBUTE = "data-basic-summary-table-generated-label";
15
+
16
+ export function normalizeSummaryColumns(value) {
17
+ if (!value?.trim()) {
18
+ return [];
19
+ }
20
+
21
+ return Array.from(
22
+ new Set(
23
+ value
24
+ .split(",")
25
+ .map((part) => Number.parseInt(part.trim(), 10))
26
+ .filter((column) => Number.isInteger(column) && column > 0),
27
+ ),
28
+ ).sort((left, right) => left - right);
29
+ }
30
+
31
+ export function normalizeSummaryTotalLabel(value) {
32
+ return value?.trim() || DEFAULT_TOTAL_LABEL;
33
+ }
34
+
35
+ export function normalizeSummaryLocale(value) {
36
+ return value?.trim() || undefined;
37
+ }
38
+
39
+ function getDecimalSeparator(value) {
40
+ const text = value.trim();
41
+ const lastComma = text.lastIndexOf(",");
42
+ const lastDot = text.lastIndexOf(".");
43
+
44
+ if (lastComma !== -1 && lastDot !== -1) {
45
+ return lastComma > lastDot ? "," : ".";
46
+ }
47
+
48
+ if (lastComma !== -1) {
49
+ const digitsAfter = text.length - lastComma - 1;
50
+ return digitsAfter > 0 && digitsAfter <= 2 ? "," : null;
51
+ }
52
+
53
+ if (lastDot !== -1) {
54
+ const digitsAfter = text.length - lastDot - 1;
55
+ return digitsAfter > 0 && digitsAfter <= 2 ? "." : null;
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ function countFractionDigits(value) {
62
+ const text = String(value ?? "").trim();
63
+
64
+ if (!text) {
65
+ return 0;
66
+ }
67
+
68
+ const decimalSeparator = getDecimalSeparator(text);
69
+
70
+ if (!decimalSeparator) {
71
+ return 0;
72
+ }
73
+
74
+ return text.length - text.lastIndexOf(decimalSeparator) - 1;
75
+ }
76
+
77
+ export function parseSummaryNumber(value) {
78
+ const text = String(value ?? "").trim();
79
+
80
+ if (!text) {
81
+ return null;
82
+ }
83
+
84
+ const decimalSeparator = getDecimalSeparator(text);
85
+ let normalized = text.replace(/[^\d,.\-]/g, "");
86
+
87
+ if (!normalized || normalized === "-") {
88
+ return null;
89
+ }
90
+
91
+ if (decimalSeparator === ",") {
92
+ normalized = normalized.replace(/\./g, "").replace(",", ".");
93
+ } else if (decimalSeparator === ".") {
94
+ normalized = normalized.replace(/,/g, "");
95
+ } else {
96
+ normalized = normalized.replace(/[.,]/g, "");
97
+ }
98
+
99
+ const parsed = Number.parseFloat(normalized);
100
+ return Number.isFinite(parsed) ? parsed : null;
101
+ }
102
+
103
+ export function formatSummaryNumber(value, { locale, fractionDigits = 0 } = {}) {
104
+ return new Intl.NumberFormat(locale, {
105
+ minimumFractionDigits: fractionDigits,
106
+ maximumFractionDigits: fractionDigits,
107
+ }).format(value);
108
+ }
109
+
110
+ function findCellAtColumnIndex(row, targetColumnIndex) {
111
+ let columnIndex = 0;
112
+
113
+ for (const cell of Array.from(row.cells)) {
114
+ const colSpan = normalizeTableCellSpan(cell.getAttribute("colspan"));
115
+
116
+ if (targetColumnIndex >= columnIndex && targetColumnIndex < columnIndex + colSpan) {
117
+ return cell;
118
+ }
119
+
120
+ columnIndex += colSpan;
121
+ }
122
+
123
+ return null;
124
+ }
125
+
126
+ function getTable(root) {
127
+ const table = root.querySelector("table");
128
+ return table instanceof HTMLTableElementBase && table.closest(root.tagName.toLowerCase()) === root
129
+ ? table
130
+ : null;
131
+ }
132
+
133
+ function getLogicalColumnCount(table) {
134
+ let maxColumns = 0;
135
+
136
+ for (const row of Array.from(table.rows)) {
137
+ let columnCount = 0;
138
+
139
+ for (const cell of Array.from(row.cells)) {
140
+ columnCount += normalizeTableCellSpan(cell.getAttribute("colspan"));
141
+ }
142
+
143
+ maxColumns = Math.max(maxColumns, columnCount);
144
+ }
145
+
146
+ return maxColumns;
147
+ }
148
+
149
+ function getBodyRows(table) {
150
+ if (table.tBodies.length > 0) {
151
+ return Array.from(table.tBodies).flatMap((section) => Array.from(section.rows));
152
+ }
153
+
154
+ return Array.from(table.rows).filter(
155
+ (row) => row.parentElement?.tagName !== "THEAD" && row.parentElement?.tagName !== "TFOOT",
156
+ );
157
+ }
158
+
159
+ function collectSummaryColumns(table, configuredColumns, labelColumnIndex) {
160
+ if (configuredColumns.length > 0) {
161
+ return configuredColumns
162
+ .map((column) => column - 1)
163
+ .filter((column) => column >= 0 && column !== labelColumnIndex);
164
+ }
165
+
166
+ const inferredColumns = new Set();
167
+
168
+ for (const row of getBodyRows(table)) {
169
+ const logicalColumnCount = getLogicalColumnCount(table);
170
+
171
+ for (let columnIndex = 0; columnIndex < logicalColumnCount; columnIndex += 1) {
172
+ if (columnIndex === labelColumnIndex) {
173
+ continue;
174
+ }
175
+
176
+ const cell = findCellAtColumnIndex(row, columnIndex);
177
+ const rawValue = cell?.getAttribute("data-value") ?? cell?.textContent ?? "";
178
+
179
+ if (parseSummaryNumber(rawValue) !== null) {
180
+ inferredColumns.add(columnIndex);
181
+ }
182
+ }
183
+ }
184
+
185
+ return Array.from(inferredColumns).sort((left, right) => left - right);
186
+ }
187
+
188
+ function calculateSummaryTotals(table, summaryColumns) {
189
+ const totals = new Map();
190
+
191
+ for (const columnIndex of summaryColumns) {
192
+ totals.set(columnIndex, {
193
+ total: 0,
194
+ fractionDigits: 0,
195
+ });
196
+ }
197
+
198
+ for (const row of getBodyRows(table)) {
199
+ for (const columnIndex of summaryColumns) {
200
+ const cell = findCellAtColumnIndex(row, columnIndex);
201
+ const rawValue = cell?.getAttribute("data-value") ?? cell?.textContent ?? "";
202
+ const parsedValue = parseSummaryNumber(rawValue);
203
+
204
+ if (parsedValue === null) {
205
+ continue;
206
+ }
207
+
208
+ const current = totals.get(columnIndex);
209
+
210
+ if (!current) {
211
+ continue;
212
+ }
213
+
214
+ current.total += parsedValue;
215
+ current.fractionDigits = Math.max(current.fractionDigits, countFractionDigits(rawValue));
216
+ }
217
+ }
218
+
219
+ return totals;
220
+ }
221
+
222
+ function ensureGeneratedSummaryRow(table) {
223
+ let tfoot = table.tFoot;
224
+
225
+ if (!tfoot) {
226
+ tfoot = document.createElement("tfoot");
227
+ table.append(tfoot);
228
+ }
229
+
230
+ let row = tfoot.querySelector(`tr[${GENERATED_SUMMARY_ROW_ATTRIBUTE}]`);
231
+
232
+ if (!row) {
233
+ row = document.createElement("tr");
234
+ row.setAttribute(GENERATED_SUMMARY_ROW_ATTRIBUTE, "");
235
+ tfoot.append(row);
236
+ }
237
+
238
+ return row;
239
+ }
240
+
241
+ function removeGeneratedSummaryRow(table) {
242
+ table.querySelector(`tr[${GENERATED_SUMMARY_ROW_ATTRIBUTE}]`)?.remove();
243
+ }
244
+
245
+ function syncSummaryFooter(table, {
246
+ labelColumnIndex,
247
+ locale,
248
+ summaryColumns,
249
+ totalLabel,
250
+ totals,
251
+ }) {
252
+ if (summaryColumns.length === 0) {
253
+ removeGeneratedSummaryRow(table);
254
+ return false;
255
+ }
256
+
257
+ const logicalColumnCount = getLogicalColumnCount(table);
258
+ const effectiveLabelColumnIndex = Math.min(labelColumnIndex, Math.max(logicalColumnCount - 1, 0));
259
+ const summaryColumnSet = new Set(summaryColumns.filter((column) => column < logicalColumnCount));
260
+ const row = ensureGeneratedSummaryRow(table);
261
+
262
+ row.replaceChildren();
263
+
264
+ for (let columnIndex = 0; columnIndex < logicalColumnCount; columnIndex += 1) {
265
+ if (columnIndex === effectiveLabelColumnIndex) {
266
+ const labelCell = document.createElement("th");
267
+ labelCell.scope = "row";
268
+ labelCell.textContent = totalLabel;
269
+ labelCell.setAttribute(GENERATED_SUMMARY_LABEL_ATTRIBUTE, "");
270
+ row.append(labelCell);
271
+ continue;
272
+ }
273
+
274
+ const valueCell = document.createElement("td");
275
+ valueCell.setAttribute(GENERATED_SUMMARY_CELL_ATTRIBUTE, "");
276
+
277
+ if (summaryColumnSet.has(columnIndex)) {
278
+ const summary = totals.get(columnIndex) ?? { total: 0, fractionDigits: 0 };
279
+ valueCell.textContent = formatSummaryNumber(summary.total, {
280
+ locale,
281
+ fractionDigits: summary.fractionDigits,
282
+ });
283
+ valueCell.dataset.value = String(summary.total);
284
+ valueCell.dataset.summaryTotal = "";
285
+ } else {
286
+ valueCell.dataset.summaryEmpty = "";
287
+ }
288
+
289
+ row.append(valueCell);
290
+ }
291
+
292
+ return true;
293
+ }
294
+
295
+ export class SummaryTableElement extends TableElement {
296
+ static observedAttributes = [
297
+ ...TableElement.observedAttributes,
298
+ "data-locale",
299
+ "data-summary-columns",
300
+ "data-total-label",
301
+ ];
302
+
303
+ #summaryObserver = null;
304
+ #scheduledFrame = 0;
305
+
306
+ connectedCallback() {
307
+ super.connectedCallback();
308
+ this.#syncSummaryObserver();
309
+ }
310
+
311
+ disconnectedCallback() {
312
+ super.disconnectedCallback();
313
+ this.#summaryObserver?.disconnect();
314
+ this.#summaryObserver = null;
315
+
316
+ if (this.#scheduledFrame !== 0 && typeof window !== "undefined") {
317
+ window.cancelAnimationFrame(this.#scheduledFrame);
318
+ this.#scheduledFrame = 0;
319
+ }
320
+ }
321
+
322
+ refresh() {
323
+ super.refresh();
324
+
325
+ const table = getTable(this);
326
+
327
+ if (!(table instanceof HTMLTableElementBase)) {
328
+ return;
329
+ }
330
+
331
+ const labelColumnIndex = normalizeTableRowHeaderColumn(
332
+ this.getAttribute("data-row-header-column"),
333
+ ) - 1;
334
+ const summaryColumns = collectSummaryColumns(
335
+ table,
336
+ normalizeSummaryColumns(this.getAttribute("data-summary-columns")),
337
+ labelColumnIndex,
338
+ );
339
+ const totals = calculateSummaryTotals(table, summaryColumns);
340
+
341
+ syncSummaryFooter(table, {
342
+ labelColumnIndex,
343
+ locale: normalizeSummaryLocale(this.getAttribute("data-locale")),
344
+ summaryColumns,
345
+ totalLabel: normalizeSummaryTotalLabel(this.getAttribute("data-total-label")),
346
+ totals,
347
+ });
348
+
349
+ super.refresh();
350
+ }
351
+
352
+ #scheduleRefresh() {
353
+ if (this.#scheduledFrame !== 0 || typeof window === "undefined") {
354
+ return;
355
+ }
356
+
357
+ this.#scheduledFrame = window.requestAnimationFrame(() => {
358
+ this.#scheduledFrame = 0;
359
+ this.refresh();
360
+ });
361
+ }
362
+
363
+ #syncSummaryObserver() {
364
+ if (this.#summaryObserver || typeof MutationObserver === "undefined") {
365
+ return;
366
+ }
367
+
368
+ this.#summaryObserver = new MutationObserver(() => {
369
+ this.#scheduleRefresh();
370
+ });
371
+
372
+ this.#summaryObserver.observe(this, {
373
+ subtree: true,
374
+ attributes: true,
375
+ attributeFilter: ["data-value"],
376
+ });
377
+ }
378
+ }
379
+
380
+ export function defineSummaryTable(registry = globalThis.customElements) {
381
+ if (!registry?.get || !registry?.define) {
382
+ return SummaryTableElement;
383
+ }
384
+
385
+ if (!registry.get(SUMMARY_TABLE_TAG_NAME)) {
386
+ registry.define(SUMMARY_TABLE_TAG_NAME, SummaryTableElement);
387
+ }
388
+
389
+ return SummaryTableElement;
390
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import { defineSummaryTable } from "./index.js";
2
+
3
+ defineSummaryTable();