@lmfaole/basics 0.3.0 → 0.5.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -350
  3. package/basic-components/basic-accordion/README.md +53 -0
  4. package/{components → basic-components}/basic-accordion/index.d.ts +5 -5
  5. package/basic-components/basic-accordion/index.js +413 -0
  6. package/basic-components/basic-alert/README.md +48 -0
  7. package/basic-components/basic-alert/index.d.ts +53 -0
  8. package/basic-components/basic-alert/index.js +189 -0
  9. package/basic-components/basic-alert/register.js +3 -0
  10. package/basic-components/basic-carousel/README.md +108 -0
  11. package/basic-components/basic-carousel/index.d.ts +73 -0
  12. package/basic-components/basic-carousel/index.js +255 -0
  13. package/basic-components/basic-carousel/register.js +3 -0
  14. package/basic-components/basic-dialog/README.md +57 -0
  15. package/basic-components/basic-popover/README.md +56 -0
  16. package/basic-components/basic-summary-table/README.md +93 -0
  17. package/{components → basic-components}/basic-summary-table/index.js +188 -42
  18. package/basic-components/basic-table/README.md +89 -0
  19. package/{components → basic-components}/basic-table/index.js +203 -145
  20. package/basic-components/basic-tabs/README.md +63 -0
  21. package/basic-components/basic-tabs/register.d.ts +1 -0
  22. package/basic-components/basic-toast/README.md +62 -0
  23. package/basic-components/basic-toast/index.d.ts +68 -0
  24. package/basic-components/basic-toast/index.js +690 -0
  25. package/basic-components/basic-toast/register.d.ts +1 -0
  26. package/basic-components/basic-toast/register.js +3 -0
  27. package/basic-components/basic-toc/README.md +43 -0
  28. package/basic-components/basic-toc/register.d.ts +1 -0
  29. package/basic-styling/components/basic-accordion.css +99 -0
  30. package/basic-styling/components/basic-alert.css +27 -0
  31. package/basic-styling/components/basic-carousel.css +183 -0
  32. package/basic-styling/components/basic-dialog.css +41 -0
  33. package/basic-styling/components/basic-popover.css +52 -0
  34. package/basic-styling/components/basic-summary-table.css +98 -0
  35. package/basic-styling/components/basic-table.css +66 -0
  36. package/basic-styling/components/basic-tabs.css +61 -0
  37. package/basic-styling/components/basic-toast.css +102 -0
  38. package/basic-styling/components/basic-toc.css +30 -0
  39. package/basic-styling/components.css +11 -0
  40. package/basic-styling/forms.css +55 -0
  41. package/basic-styling/global.css +62 -0
  42. package/basic-styling/index.css +2 -0
  43. package/basic-styling/interaction.css +90 -0
  44. package/basic-styling/tokens/base.css +19 -0
  45. package/basic-styling/tokens/palette.css +229 -0
  46. package/basic-styling/tokens/palette.tokens.json +1787 -0
  47. package/index.d.ts +10 -7
  48. package/index.js +10 -7
  49. package/package.json +61 -76
  50. package/components/basic-accordion/index.js +0 -387
  51. package/readme.mdx +0 -6
  52. /package/{components → basic-components}/basic-accordion/register.d.ts +0 -0
  53. /package/{components → basic-components}/basic-accordion/register.js +0 -0
  54. /package/{components/basic-dialog → basic-components/basic-alert}/register.d.ts +0 -0
  55. /package/{components/basic-popover → basic-components/basic-carousel}/register.d.ts +0 -0
  56. /package/{components → basic-components}/basic-dialog/index.d.ts +0 -0
  57. /package/{components → basic-components}/basic-dialog/index.js +0 -0
  58. /package/{components/basic-summary-table → basic-components/basic-dialog}/register.d.ts +0 -0
  59. /package/{components → basic-components}/basic-dialog/register.js +0 -0
  60. /package/{components → basic-components}/basic-popover/index.d.ts +0 -0
  61. /package/{components → basic-components}/basic-popover/index.js +0 -0
  62. /package/{components/basic-table → basic-components/basic-popover}/register.d.ts +0 -0
  63. /package/{components → basic-components}/basic-popover/register.js +0 -0
  64. /package/{components → basic-components}/basic-summary-table/index.d.ts +0 -0
  65. /package/{components/basic-tabs → basic-components/basic-summary-table}/register.d.ts +0 -0
  66. /package/{components → basic-components}/basic-summary-table/register.js +0 -0
  67. /package/{components → basic-components}/basic-table/index.d.ts +0 -0
  68. /package/{components/basic-toc → basic-components/basic-table}/register.d.ts +0 -0
  69. /package/{components → basic-components}/basic-table/register.js +0 -0
  70. /package/{components → basic-components}/basic-tabs/index.d.ts +0 -0
  71. /package/{components → basic-components}/basic-tabs/index.js +0 -0
  72. /package/{components → basic-components}/basic-tabs/register.js +0 -0
  73. /package/{components → basic-components}/basic-toc/index.d.ts +0 -0
  74. /package/{components → basic-components}/basic-toc/index.js +0 -0
  75. /package/{components → basic-components}/basic-toc/register.js +0 -0
@@ -0,0 +1,108 @@
1
+ # `basic-carousel`
2
+
3
+ Named carousel regions built around a native scroll-snap track.
4
+
5
+ ## Register
6
+
7
+ ```js
8
+ import "@lmfaole/basics/basic-components/basic-carousel/register";
9
+ ```
10
+
11
+ ## Example
12
+
13
+ ```html
14
+ <basic-carousel
15
+ data-label="Featured stories"
16
+ data-controls="both"
17
+ data-snapping="center"
18
+ >
19
+ <div data-carousel-track>
20
+ <article>
21
+ <h2>Launch Week</h2>
22
+ <p>Three product updates shipping across the design system.</p>
23
+ </article>
24
+
25
+ <article data-carousel-marker-label="Go to the accessibility slide">
26
+ <h2>Accessibility</h2>
27
+ <p>Keyboard and announcement details for the next release.</p>
28
+ </article>
29
+
30
+ <article>
31
+ <h2>Change Freeze Window</h2>
32
+ <p>Friday deployment holds and rollback owners are published for the April migration.</p>
33
+ </article>
34
+
35
+ <article>
36
+ <h2>Signup Funnel Feedback</h2>
37
+ <p>Eight research sessions highlighted friction around plan limits and account handoff.</p>
38
+ </article>
39
+
40
+ <article>
41
+ <h2>Tokens</h2>
42
+ <p>New surface and border tokens for interaction states.</p>
43
+ </article>
44
+
45
+ <article>
46
+ <h2>Migration Guides Updated</h2>
47
+ <p>Upgrade notes now include copy-paste examples and QA checklists for current component consumers.</p>
48
+ </article>
49
+
50
+ <article>
51
+ <h2>Bundle Audit Review</h2>
52
+ <p>A new audit flags duplicate helper code across package entry points and the next trim targets.</p>
53
+ </article>
54
+
55
+ <article>
56
+ <h2>Permission Model Review</h2>
57
+ <p>Security notes now spell out which stories need elevated browser APIs and which ones stay sandbox-safe.</p>
58
+ </article>
59
+
60
+ <article>
61
+ <h2>Pilot Teams Onboarded</h2>
62
+ <p>Three product squads have moved their prototypes onto the package and started filing integration feedback.</p>
63
+ </article>
64
+
65
+ <article>
66
+ <h2>Regression Sweep</h2>
67
+ <p>A targeted browser sweep caught contrast regressions, stale labels, and one broken close action before release cut.</p>
68
+ </article>
69
+ </div>
70
+ </basic-carousel>
71
+ ```
72
+
73
+ Import the optional starter carousel CSS when you want native `::scroll-button()` and `::scroll-marker` controls where the browser supports them.
74
+
75
+ ## Props
76
+
77
+ | Prop | Description | Type | Default | Options |
78
+ | --- | --- | --- | --- | --- |
79
+ | `data-label` | Accessible name for the carousel region when no own `aria-label` or `aria-labelledby` is present. | string | `Carousel` | any string |
80
+ | `data-controls` | Chooses which native scroll controls to expose when the browser supports them. | string | `both` | `both`, `markers`, `arrows`, `none` |
81
+ | `data-snapping` | Chooses where each slide snaps within the scrollport and where `scrollToItem()` aligns it. | string | `center` | `start`, `center`, `end` |
82
+
83
+ ## Markup Hooks
84
+
85
+ | Hook | Description | Type | Default | Options |
86
+ | --- | --- | --- | --- | --- |
87
+ | `data-carousel-track` | Marks the scroll container that owns the slides and native CSS controls. | descendant container attribute | required | present on one descendant scroll container |
88
+ | direct children of `[data-carousel-track]` | Each direct child becomes one carousel slide and one generated scroll marker. | descendant item | required | present on one or more direct child elements |
89
+ | `data-carousel-marker-label` | Optional per-slide label used for the generated marker's accessible name. | string attribute on a direct slide child | auto-generated | any string |
90
+
91
+ ## Behavior
92
+
93
+ - Applies `role="region"` and a fallback accessible label on the root element
94
+ - Normalizes `data-controls` to `both`, `markers`, `arrows`, or `none`
95
+ - Normalizes `data-snapping` to `start`, `center`, or `end`
96
+ - Annotates each slide with generated marker text and marker labels for CSS `content: attr(...)`
97
+ - Exposes `refresh()` when the slide structure changes after connection
98
+ - Exposes `scrollToItem(index, options)` for programmatic navigation
99
+
100
+ ## Markup Contract
101
+
102
+ - Provide one descendant element with `data-carousel-track`
103
+ - Keep each slide as a direct child of that track
104
+ - Add your own slide semantics inside each item, for example `article`, `li`, or `section`
105
+ - Use `data-controls="none"` for no generated controls, `data-controls="markers"` for numbered markers only, `data-controls="arrows"` for arrows only, or `data-controls="both"` for both controls
106
+ - Use `data-snapping="start"`, `center`, or `end` to align the active slide consistently
107
+ - Import starter CSS if you want generated scroll buttons and scroll markers
108
+ - Browsers without native scroll controls still get the scroll-snap track and manual scrolling
@@ -0,0 +1,73 @@
1
+ export const CAROUSEL_TAG_NAME: "basic-carousel";
2
+ export type CarouselControls = "both" | "markers" | "arrows" | "none";
3
+ export type CarouselSnapping = "start" | "center" | "end";
4
+
5
+ export interface CarouselScrollOptions {
6
+ behavior?: ScrollBehavior | null;
7
+ snapping?: CarouselSnapping | string | null;
8
+ }
9
+
10
+ /**
11
+ * Normalizes unsupported or empty labels back to the default `"Carousel"`.
12
+ */
13
+ export function normalizeCarouselLabel(
14
+ value?: string | null,
15
+ ): string;
16
+
17
+ /**
18
+ * Normalizes carousel controls to `"both"`, `"markers"`, `"arrows"`, or `"none"`.
19
+ */
20
+ export function normalizeCarouselControls(
21
+ value?: string | null,
22
+ ): CarouselControls;
23
+
24
+ /**
25
+ * Normalizes scroll behaviors to `"auto"` or `"smooth"`.
26
+ */
27
+ export function normalizeCarouselScrollBehavior(
28
+ value?: string | null,
29
+ ): ScrollBehavior;
30
+
31
+ /**
32
+ * Normalizes snap positions to `"start"`, `"center"`, or `"end"`.
33
+ */
34
+ export function normalizeCarouselSnapping(
35
+ value?: string | null,
36
+ ): CarouselSnapping;
37
+
38
+ /**
39
+ * Clamps a requested item index into the available carousel range.
40
+ */
41
+ export function clampCarouselIndex(
42
+ index: number,
43
+ itemCount: number,
44
+ ): number;
45
+
46
+ /**
47
+ * Custom element that upgrades a scroll-snap track into a named carousel region
48
+ * and annotates each slide for CSS-native scroll buttons and markers.
49
+ *
50
+ * Attributes:
51
+ * - `data-label`: fallback accessible name for the carousel region
52
+ * - `data-controls`: show generated markers, arrows, both, or no generated controls where supported
53
+ * - `data-snapping`: align slides to the start, center, or end of the scrollport
54
+ *
55
+ * Descendant hooks:
56
+ * - one `[data-carousel-track]` scroll container
57
+ * - direct child items inside that track
58
+ * - optional `data-carousel-marker-label` on each item for custom marker names
59
+ */
60
+ export class CarouselElement extends HTMLElement {
61
+ static observedAttributes: string[];
62
+ get track(): HTMLElement | null;
63
+ get items(): HTMLElement[];
64
+ refresh(): number;
65
+ scrollToItem(index: number, options?: CarouselScrollOptions): boolean;
66
+ }
67
+
68
+ /**
69
+ * Registers the `basic-carousel` custom element if it is not already defined.
70
+ */
71
+ export function defineCarousel(
72
+ registry?: CustomElementRegistry,
73
+ ): typeof CarouselElement;
@@ -0,0 +1,255 @@
1
+ const HTMLElementBase = globalThis.HTMLElement ?? class {};
2
+
3
+ export const CAROUSEL_TAG_NAME = "basic-carousel";
4
+
5
+ const DEFAULT_LABEL = "Carousel";
6
+ const DEFAULT_CONTROLS = "both";
7
+ const DEFAULT_SNAPPING = "center";
8
+ const TRACK_SELECTOR = "[data-carousel-track]";
9
+ const MANAGED_LABEL_ATTRIBUTE = "data-basic-carousel-managed-label";
10
+ const MANAGED_READY_ATTRIBUTE = "data-basic-carousel-ready";
11
+
12
+ let nextCarouselInstanceId = 1;
13
+
14
+ function collectOwnedElements(root, scope, selector) {
15
+ return Array.from(scope.querySelectorAll(selector)).filter(
16
+ (element) => element instanceof HTMLElementBase && element.closest(CAROUSEL_TAG_NAME) === root,
17
+ );
18
+ }
19
+
20
+ function collectCarouselItems(track) {
21
+ return Array.from(track.children).filter(
22
+ (element) => element instanceof HTMLElementBase,
23
+ );
24
+ }
25
+
26
+ function normalizeMarkerLabel(value, index, total) {
27
+ const normalized = value?.trim();
28
+
29
+ if (normalized) {
30
+ return normalized;
31
+ }
32
+
33
+ return `Go to slide ${index} of ${total}`;
34
+ }
35
+
36
+ export function normalizeCarouselLabel(value) {
37
+ return value?.trim() || DEFAULT_LABEL;
38
+ }
39
+
40
+ export function normalizeCarouselControls(value) {
41
+ const normalized = value?.trim().toLowerCase();
42
+
43
+ if (normalized === "none") {
44
+ return "none";
45
+ }
46
+
47
+ if (
48
+ normalized === "markers"
49
+ || normalized === "numbers"
50
+ || normalized === "number-buttons"
51
+ ) {
52
+ return "markers";
53
+ }
54
+
55
+ if (normalized === "arrows") {
56
+ return "arrows";
57
+ }
58
+
59
+ return DEFAULT_CONTROLS;
60
+ }
61
+
62
+ export function normalizeCarouselScrollBehavior(value) {
63
+ const normalized = value?.trim().toLowerCase();
64
+
65
+ return normalized === "smooth" ? "smooth" : "auto";
66
+ }
67
+
68
+ export function normalizeCarouselSnapping(value) {
69
+ const normalized = value?.trim().toLowerCase();
70
+
71
+ if (normalized === "start" || normalized === "end") {
72
+ return normalized;
73
+ }
74
+
75
+ return DEFAULT_SNAPPING;
76
+ }
77
+
78
+ export function clampCarouselIndex(index, itemCount) {
79
+ if (!Number.isInteger(index) || itemCount <= 0) {
80
+ return -1;
81
+ }
82
+
83
+ return Math.min(Math.max(index, 0), itemCount - 1);
84
+ }
85
+
86
+ function clampScrollOffset(offset, maxOffset) {
87
+ return Math.min(Math.max(offset, 0), Math.max(maxOffset, 0));
88
+ }
89
+
90
+ function getItemInlineMetrics(track, item) {
91
+ const trackRect = track.getBoundingClientRect?.();
92
+ const itemRect = item.getBoundingClientRect?.();
93
+
94
+ if (trackRect?.width > 0 && itemRect?.width > 0) {
95
+ const start = itemRect.left - trackRect.left + track.scrollLeft;
96
+
97
+ return {
98
+ start,
99
+ width: itemRect.width,
100
+ };
101
+ }
102
+
103
+ return {
104
+ start: item.offsetLeft || 0,
105
+ width: item.offsetWidth || track.clientWidth || 0,
106
+ };
107
+ }
108
+
109
+ function getTrackViewportWidth(track) {
110
+ return track.clientWidth || track.getBoundingClientRect?.().width || 0;
111
+ }
112
+
113
+ function getScrollOffsetForItem(track, item, snapping) {
114
+ const viewportWidth = getTrackViewportWidth(track);
115
+ const { start, width } = getItemInlineMetrics(track, item);
116
+
117
+ if (snapping === "end") {
118
+ return start - (viewportWidth - width);
119
+ }
120
+
121
+ if (snapping === "center") {
122
+ return start - ((viewportWidth - width) / 2);
123
+ }
124
+
125
+ return start;
126
+ }
127
+
128
+ export class CarouselElement extends HTMLElementBase {
129
+ static observedAttributes = ["data-controls", "data-label", "data-snapping"];
130
+
131
+ #instanceId = `${CAROUSEL_TAG_NAME}-${nextCarouselInstanceId++}`;
132
+ #track = null;
133
+ #items = [];
134
+
135
+ connectedCallback() {
136
+ this.refresh();
137
+ }
138
+
139
+ attributeChangedCallback() {
140
+ this.#applyState();
141
+ }
142
+
143
+ get track() {
144
+ return this.#track;
145
+ }
146
+
147
+ get items() {
148
+ return [...this.#items];
149
+ }
150
+
151
+ refresh() {
152
+ this.#track = collectOwnedElements(this, this, TRACK_SELECTOR)[0] ?? null;
153
+ this.#items = this.#track ? collectCarouselItems(this.#track) : [];
154
+ this.#applyState();
155
+ return this.#items.length;
156
+ }
157
+
158
+ scrollToItem(index, options = {}) {
159
+ const nextIndex = clampCarouselIndex(index, this.#items.length);
160
+ const item = nextIndex === -1 ? null : this.#items[nextIndex];
161
+ const track = this.#track;
162
+
163
+ if (!(item instanceof HTMLElementBase) || !(track instanceof HTMLElementBase)) {
164
+ return false;
165
+ }
166
+
167
+ const behavior = normalizeCarouselScrollBehavior(options.behavior);
168
+ const snapping = normalizeCarouselSnapping(options.snapping ?? this.getAttribute("data-snapping"));
169
+ const viewportWidth = getTrackViewportWidth(track);
170
+ const maxOffset = Math.max(track.scrollWidth - viewportWidth, 0);
171
+ const left = clampScrollOffset(getScrollOffsetForItem(track, item, snapping), maxOffset);
172
+
173
+ if (typeof track.scrollTo === "function") {
174
+ track.scrollTo({
175
+ left,
176
+ behavior,
177
+ });
178
+ } else {
179
+ track.scrollLeft = left;
180
+ }
181
+
182
+ return true;
183
+ }
184
+
185
+ #applyState() {
186
+ const baseId = this.id || this.#instanceId;
187
+ const controls = normalizeCarouselControls(this.getAttribute("data-controls"));
188
+ const snapping = normalizeCarouselSnapping(this.getAttribute("data-snapping"));
189
+
190
+ if (this.#track instanceof HTMLElementBase && !this.#track.id) {
191
+ this.#track.id = `${baseId}-track`;
192
+ }
193
+
194
+ this.dataset.basicCarouselControls = controls;
195
+ this.dataset.basicCarouselSnapping = snapping;
196
+ this.toggleAttribute(MANAGED_READY_ATTRIBUTE, this.#track instanceof HTMLElementBase && this.#items.length > 0);
197
+ this.setAttribute("role", "region");
198
+ this.#syncAccessibleLabel();
199
+
200
+ const total = this.#items.length;
201
+
202
+ for (const [index, item] of this.#items.entries()) {
203
+ item.dataset.basicCarouselMarker = String(index + 1);
204
+ item.dataset.basicCarouselMarkerLabel = normalizeMarkerLabel(
205
+ item.getAttribute("data-carousel-marker-label"),
206
+ index + 1,
207
+ total,
208
+ );
209
+
210
+ if (!item.id) {
211
+ item.id = `${baseId}-item-${index + 1}`;
212
+ }
213
+ }
214
+ }
215
+
216
+ #syncAccessibleLabel() {
217
+ const nextLabel = normalizeCarouselLabel(this.getAttribute("data-label"));
218
+ const hasManagedLabel = this.hasAttribute(MANAGED_LABEL_ATTRIBUTE);
219
+
220
+ if (hasManagedLabel && this.getAttribute("aria-label") !== nextLabel) {
221
+ this.removeAttribute("aria-label");
222
+ this.removeAttribute(MANAGED_LABEL_ATTRIBUTE);
223
+ }
224
+
225
+ if (this.hasAttribute("aria-labelledby")) {
226
+ if (hasManagedLabel) {
227
+ this.removeAttribute("aria-label");
228
+ this.removeAttribute(MANAGED_LABEL_ATTRIBUTE);
229
+ }
230
+
231
+ return;
232
+ }
233
+
234
+ const hasOwnAriaLabel = this.hasAttribute("aria-label") && !hasManagedLabel;
235
+
236
+ if (hasOwnAriaLabel) {
237
+ return;
238
+ }
239
+
240
+ this.setAttribute("aria-label", nextLabel);
241
+ this.setAttribute(MANAGED_LABEL_ATTRIBUTE, "");
242
+ }
243
+ }
244
+
245
+ export function defineCarousel(registry = globalThis.customElements) {
246
+ if (!registry?.get || !registry?.define) {
247
+ return CarouselElement;
248
+ }
249
+
250
+ if (!registry.get(CAROUSEL_TAG_NAME)) {
251
+ registry.define(CAROUSEL_TAG_NAME, CarouselElement);
252
+ }
253
+
254
+ return CarouselElement;
255
+ }
@@ -0,0 +1,3 @@
1
+ import { defineCarousel } from "./index.js";
2
+
3
+ defineCarousel();
@@ -0,0 +1,57 @@
1
+ # `basic-dialog`
2
+
3
+ Modal dialog flow built on native `<dialog>`.
4
+
5
+ ## Register
6
+
7
+ ```js
8
+ import "@lmfaole/basics/basic-components/basic-dialog/register";
9
+ ```
10
+
11
+ ## Example
12
+
13
+ ```html
14
+ <basic-dialog data-label="Bekreft handling" data-backdrop-close>
15
+ <button type="button" data-dialog-open>Open dialog</button>
16
+
17
+ <dialog data-dialog-panel>
18
+ <h2 data-dialog-title>Bekreft handling</h2>
19
+ <p>Dialog body.</p>
20
+ <button type="button" data-dialog-close>Cancel</button>
21
+ <button type="button" data-dialog-close data-dialog-close-value="confirmed">
22
+ Confirm
23
+ </button>
24
+ </dialog>
25
+ </basic-dialog>
26
+ ```
27
+
28
+ ## Props
29
+
30
+ | Prop | Description | Type | Default | Options |
31
+ | --- | --- | --- | --- | --- |
32
+ | `data-label` | Fallback accessible name when the dialog has no `aria-label`, `aria-labelledby`, or `[data-dialog-title]`. | string | `Dialog` | any string |
33
+ | `data-backdrop-close` | Allows clicks on the dialog backdrop to close the modal. | boolean attribute | off | `present`, `omitted` |
34
+
35
+ ## Markup Hooks
36
+
37
+ | Hook | Description | Type | Default | Options |
38
+ | --- | --- | --- | --- | --- |
39
+ | `data-dialog-open` | Opens the managed dialog. | descendant control attribute | none | present on a descendant button or control |
40
+ | `data-dialog-panel` | Marks the native dialog element managed by the component. | descendant `<dialog>` attribute | required | present on one descendant `<dialog>` |
41
+ | `data-dialog-title` | Makes the visible heading the dialog's accessible name. | descendant heading attribute | none | present on a descendant heading |
42
+ | `data-dialog-close` | Closes the dialog when activated. | descendant control attribute | none | present on a descendant button or control |
43
+ | `data-dialog-close-value` | Return value passed to `dialog.close()` when used on a `[data-dialog-close]` control. | string | empty string | any string |
44
+
45
+ ## Behavior
46
+
47
+ - Uses native modal dialog behavior via `showModal()`
48
+ - Restores focus to the opener on close
49
+ - Lets the platform handle modal focus and `Esc` dismissal
50
+ - `[data-dialog-close]` can optionally set `data-dialog-close-value`
51
+
52
+ ## Markup Contract
53
+
54
+ - Provide one descendant `<dialog data-dialog-panel>`
55
+ - Use `[data-dialog-open]` on buttons that should open the modal
56
+ - Use `[data-dialog-title]` when the heading should become the accessible name
57
+ - Keep layout and styling outside the package
@@ -0,0 +1,56 @@
1
+ # `basic-popover`
2
+
3
+ Non-modal overlay behavior using the Popover API.
4
+
5
+ ## Register
6
+
7
+ ```js
8
+ import "@lmfaole/basics/basic-components/basic-popover/register";
9
+ ```
10
+
11
+ ## Example
12
+
13
+ ```html
14
+ <basic-popover data-label="Filtre" data-anchor-trigger data-position-area="bottom">
15
+ <button type="button" data-popover-open>Toggle popover</button>
16
+
17
+ <section data-popover-panel>
18
+ <h2 data-popover-title>Filtre</h2>
19
+ <p>Popover body.</p>
20
+ <button type="button" data-popover-close>Close</button>
21
+ </section>
22
+ </basic-popover>
23
+ ```
24
+
25
+ ## Props
26
+
27
+ | Prop | Description | Type | Default | Options |
28
+ | --- | --- | --- | --- | --- |
29
+ | `data-label` | Fallback accessible name when the popover has no `aria-label`, `aria-labelledby`, or `[data-popover-title]`. | string | `Popover` | any string |
30
+ | `data-anchor-trigger` | Uses the opener as the popover's implicit anchor. | boolean attribute | off | `present`, `omitted` |
31
+ | `data-position-area` | Default anchored placement used when `data-anchor-trigger` is enabled. | CSS `position-area` token | `bottom` | any CSS `position-area` token such as `bottom`, `top`, `left`, `right`, `block-start`, `block-end`, `inline-start`, or `inline-end` |
32
+ | `data-position-try-fallbacks` | Overrides the built-in fallback sequence used when the default placement would overflow. | comma-separated string list | derived from `data-position-area` | comma-separated CSS fallback list |
33
+
34
+ ## Markup Hooks
35
+
36
+ | Hook | Description | Type | Default | Options |
37
+ | --- | --- | --- | --- | --- |
38
+ | `data-popover-open` | Toggles the popover open state. | descendant control attribute | none | present on a descendant button or control |
39
+ | `data-popover-panel` | Marks the overlay panel managed by the component. | descendant element attribute | required | present on one descendant element |
40
+ | `data-popover-title` | Makes the visible heading the popover's accessible name. | descendant heading attribute | none | present on a descendant heading |
41
+ | `data-popover-close` | Closes the popover when activated. | descendant control attribute | none | present on a descendant button or control |
42
+
43
+ ## Behavior
44
+
45
+ - Uses the native Popover API in auto mode for outside-click and `Esc` dismissal
46
+ - Syncs `aria-expanded`, `aria-controls`, and `data-open` between the trigger and panel
47
+ - Restores focus when dismissal should return to the opener
48
+ - Supports anchored placement when `data-anchor-trigger` is set
49
+
50
+ ## Markup Contract
51
+
52
+ - Provide one descendant `[data-popover-panel]`
53
+ - Use `[data-popover-open]` on buttons that should toggle the panel
54
+ - Use `[data-popover-title]` when the heading should become the accessible name
55
+ - Use `[data-popover-close]` when the panel should expose an explicit dismiss action
56
+ - Keep layout and styling outside the package
@@ -0,0 +1,93 @@
1
+ # `basic-summary-table`
2
+
3
+ Tables with generated totals in `<tfoot>`.
4
+
5
+ ## Register
6
+
7
+ ```js
8
+ import "@lmfaole/basics/basic-components/basic-summary-table/register";
9
+ ```
10
+
11
+ ## Example
12
+
13
+ ```html
14
+ <basic-summary-table
15
+ data-caption="Månedlig kostnadsoversikt"
16
+ data-description="Viser antall og summerte beløp for faste kostnader."
17
+ data-row-headers
18
+ data-summary-columns="2,4"
19
+ data-total-label="Totalt"
20
+ data-locale="nb-NO"
21
+ >
22
+ <table>
23
+ <thead>
24
+ <tr>
25
+ <th>Post</th>
26
+ <th>Antall</th>
27
+ <th>Enhetspris</th>
28
+ <th>Beløp</th>
29
+ </tr>
30
+ </thead>
31
+ <tbody>
32
+ <tr>
33
+ <td>Basisabonnement</td>
34
+ <td>12</td>
35
+ <td>49,00 kr</td>
36
+ <td>588,00 kr</td>
37
+ </tr>
38
+ <tr>
39
+ <td>Supportavtale</td>
40
+ <td>1</td>
41
+ <td>299,00 kr</td>
42
+ <td>299,00 kr</td>
43
+ </tr>
44
+ <tr>
45
+ <td>Lagringstillegg</td>
46
+ <td>4</td>
47
+ <td>120,00 kr</td>
48
+ <td>480,00 kr</td>
49
+ </tr>
50
+ </tbody>
51
+ </table>
52
+ </basic-summary-table>
53
+ ```
54
+
55
+ ## Props
56
+
57
+ | Prop | Description | Type | Default | Options |
58
+ | --- | --- | --- | --- | --- |
59
+ | `data-caption` | Generates a visible `<caption>` when the wrapped table does not already define one. | string | none | any string |
60
+ | `data-description` | Generates hidden helper text and connects it with `aria-describedby`. | string | none | any string |
61
+ | `data-label` | Fallback accessible name when the table has no caption, `aria-label`, or `aria-labelledby`. | string | `Tabell` | any string |
62
+ | `data-row-headers` | Enables generated row headers in body rows. This is also enabled automatically when `data-row-header-column` is present. | boolean attribute | off | `present`, `omitted` |
63
+ | `data-row-header-column` | Chooses which one-based body column becomes the row header. | positive integer | `1` | positive integer |
64
+ | `data-summary-columns` | Selects which columns are totalled in the generated footer row. | comma-separated integer list | infer numeric body columns | comma-separated one-based column indexes |
65
+ | `data-total-label` | Footer row label used for the generated totals row. | string | `Totalt` | any string |
66
+ | `data-locale` | Locale passed to generated footer totals. | locale string | browser default | any `Intl.NumberFormat` locale string |
67
+
68
+ ## Starter Styling Prop
69
+
70
+ | Prop | Description | Type | Default | Options |
71
+ | --- | --- | --- | --- | --- |
72
+ | `data-zebra` | Optional starter-CSS hook that adds alternating body-row backgrounds when you import `basic-styling`. | boolean attribute | off | `present`, `omitted` |
73
+ | `data-separators` | Optional starter-CSS hook that chooses whether interior dividers appear between rows, columns, or both when you import `basic-styling`. | enum string | `rows` | `rows`, `columns`, `both` |
74
+
75
+ ## Cell Hooks
76
+
77
+ | Hook | Description | Type | Default | Options |
78
+ | --- | --- | --- | --- | --- |
79
+ | `data-value` on a body cell | Raw numeric value used for calculations when the displayed text is formatted differently. | numeric string | uses the cell text | any parseable numeric string |
80
+
81
+ ## Behavior
82
+
83
+ - Inherits caption, description, row-header, and `headers` association behavior from `basic-table`
84
+ - Parses numbers from cell text and supports raw values through `data-value` on body cells
85
+ - Generates and updates a totals row in `<tfoot>`
86
+ - Recalculates when body rows or `data-value` attributes change
87
+
88
+ ## Markup Contract
89
+
90
+ - Provide one descendant `<table>` with line items in `<tbody>`
91
+ - Prefer a label column and enable `data-row-headers` so each row remains easy to navigate
92
+ - Use `data-value` on cells when the displayed text differs from the numeric value you want summed
93
+ - Keep layout and styling outside the package