@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.
- package/LICENSE +21 -0
- package/README.md +78 -350
- package/basic-components/basic-accordion/README.md +53 -0
- package/{components → basic-components}/basic-accordion/index.d.ts +5 -5
- package/basic-components/basic-accordion/index.js +413 -0
- package/basic-components/basic-alert/README.md +48 -0
- package/basic-components/basic-alert/index.d.ts +53 -0
- package/basic-components/basic-alert/index.js +189 -0
- package/basic-components/basic-alert/register.js +3 -0
- package/basic-components/basic-carousel/README.md +108 -0
- package/basic-components/basic-carousel/index.d.ts +73 -0
- package/basic-components/basic-carousel/index.js +255 -0
- package/basic-components/basic-carousel/register.js +3 -0
- package/basic-components/basic-dialog/README.md +57 -0
- package/basic-components/basic-popover/README.md +56 -0
- package/basic-components/basic-summary-table/README.md +93 -0
- package/{components → basic-components}/basic-summary-table/index.js +188 -42
- package/basic-components/basic-table/README.md +89 -0
- package/{components → basic-components}/basic-table/index.js +203 -145
- package/basic-components/basic-tabs/README.md +63 -0
- package/basic-components/basic-tabs/register.d.ts +1 -0
- package/basic-components/basic-toast/README.md +62 -0
- package/basic-components/basic-toast/index.d.ts +68 -0
- package/basic-components/basic-toast/index.js +690 -0
- package/basic-components/basic-toast/register.d.ts +1 -0
- package/basic-components/basic-toast/register.js +3 -0
- package/basic-components/basic-toc/README.md +43 -0
- package/basic-components/basic-toc/register.d.ts +1 -0
- package/basic-styling/components/basic-accordion.css +99 -0
- package/basic-styling/components/basic-alert.css +27 -0
- package/basic-styling/components/basic-carousel.css +183 -0
- package/basic-styling/components/basic-dialog.css +41 -0
- package/basic-styling/components/basic-popover.css +52 -0
- package/basic-styling/components/basic-summary-table.css +98 -0
- package/basic-styling/components/basic-table.css +66 -0
- package/basic-styling/components/basic-tabs.css +61 -0
- package/basic-styling/components/basic-toast.css +102 -0
- package/basic-styling/components/basic-toc.css +30 -0
- package/basic-styling/components.css +11 -0
- package/basic-styling/forms.css +55 -0
- package/basic-styling/global.css +62 -0
- package/basic-styling/index.css +2 -0
- package/basic-styling/interaction.css +90 -0
- package/basic-styling/tokens/base.css +19 -0
- package/basic-styling/tokens/palette.css +229 -0
- package/basic-styling/tokens/palette.tokens.json +1787 -0
- package/index.d.ts +10 -7
- package/index.js +10 -7
- package/package.json +61 -76
- package/components/basic-accordion/index.js +0 -387
- package/readme.mdx +0 -6
- /package/{components → basic-components}/basic-accordion/register.d.ts +0 -0
- /package/{components → basic-components}/basic-accordion/register.js +0 -0
- /package/{components/basic-dialog → basic-components/basic-alert}/register.d.ts +0 -0
- /package/{components/basic-popover → basic-components/basic-carousel}/register.d.ts +0 -0
- /package/{components → basic-components}/basic-dialog/index.d.ts +0 -0
- /package/{components → basic-components}/basic-dialog/index.js +0 -0
- /package/{components/basic-summary-table → basic-components/basic-dialog}/register.d.ts +0 -0
- /package/{components → basic-components}/basic-dialog/register.js +0 -0
- /package/{components → basic-components}/basic-popover/index.d.ts +0 -0
- /package/{components → basic-components}/basic-popover/index.js +0 -0
- /package/{components/basic-table → basic-components/basic-popover}/register.d.ts +0 -0
- /package/{components → basic-components}/basic-popover/register.js +0 -0
- /package/{components → basic-components}/basic-summary-table/index.d.ts +0 -0
- /package/{components/basic-tabs → basic-components/basic-summary-table}/register.d.ts +0 -0
- /package/{components → basic-components}/basic-summary-table/register.js +0 -0
- /package/{components → basic-components}/basic-table/index.d.ts +0 -0
- /package/{components/basic-toc → basic-components/basic-table}/register.d.ts +0 -0
- /package/{components → basic-components}/basic-table/register.js +0 -0
- /package/{components → basic-components}/basic-tabs/index.d.ts +0 -0
- /package/{components → basic-components}/basic-tabs/index.js +0 -0
- /package/{components → basic-components}/basic-tabs/register.js +0 -0
- /package/{components → basic-components}/basic-toc/index.d.ts +0 -0
- /package/{components → basic-components}/basic-toc/index.js +0 -0
- /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,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
|