@lmfaole/basics 0.4.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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -499
  3. package/basic-components/basic-accordion/README.md +53 -0
  4. package/{components → basic-components}/basic-accordion/index.js +59 -37
  5. package/basic-components/basic-alert/README.md +48 -0
  6. package/basic-components/basic-carousel/README.md +108 -0
  7. package/basic-components/basic-carousel/index.d.ts +73 -0
  8. package/basic-components/basic-carousel/index.js +255 -0
  9. package/basic-components/basic-carousel/register.js +3 -0
  10. package/basic-components/basic-dialog/README.md +57 -0
  11. package/basic-components/basic-popover/README.md +56 -0
  12. package/basic-components/basic-summary-table/README.md +93 -0
  13. package/basic-components/basic-table/README.md +89 -0
  14. package/basic-components/basic-tabs/README.md +63 -0
  15. package/basic-components/basic-toast/README.md +62 -0
  16. package/{components → basic-components}/basic-toast/index.d.ts +3 -0
  17. package/{components → basic-components}/basic-toast/index.js +264 -3
  18. package/basic-components/basic-toc/README.md +43 -0
  19. package/basic-components/basic-toc/register.d.ts +1 -0
  20. package/basic-styling/components/basic-accordion.css +38 -4
  21. package/basic-styling/components/basic-carousel.css +183 -0
  22. package/basic-styling/components/basic-popover.css +2 -4
  23. package/basic-styling/components/basic-summary-table.css +27 -5
  24. package/basic-styling/components/basic-table.css +22 -4
  25. package/basic-styling/components/basic-tabs.css +26 -10
  26. package/basic-styling/components.css +2 -0
  27. package/basic-styling/forms.css +55 -0
  28. package/basic-styling/global.css +1 -0
  29. package/basic-styling/interaction.css +90 -0
  30. package/basic-styling/tokens/palette.css +112 -0
  31. package/basic-styling/tokens/palette.tokens.json +768 -0
  32. package/index.d.ts +10 -9
  33. package/index.js +10 -9
  34. package/package.json +49 -29
  35. package/readme.mdx +0 -6
  36. /package/{components → basic-components}/basic-accordion/index.d.ts +0 -0
  37. /package/{components → basic-components}/basic-accordion/register.d.ts +0 -0
  38. /package/{components → basic-components}/basic-accordion/register.js +0 -0
  39. /package/{components → basic-components}/basic-alert/index.d.ts +0 -0
  40. /package/{components → basic-components}/basic-alert/index.js +0 -0
  41. /package/{components → basic-components}/basic-alert/register.d.ts +0 -0
  42. /package/{components → basic-components}/basic-alert/register.js +0 -0
  43. /package/{components/basic-dialog → basic-components/basic-carousel}/register.d.ts +0 -0
  44. /package/{components → basic-components}/basic-dialog/index.d.ts +0 -0
  45. /package/{components → basic-components}/basic-dialog/index.js +0 -0
  46. /package/{components/basic-popover → basic-components/basic-dialog}/register.d.ts +0 -0
  47. /package/{components → basic-components}/basic-dialog/register.js +0 -0
  48. /package/{components → basic-components}/basic-popover/index.d.ts +0 -0
  49. /package/{components → basic-components}/basic-popover/index.js +0 -0
  50. /package/{components/basic-summary-table → basic-components/basic-popover}/register.d.ts +0 -0
  51. /package/{components → basic-components}/basic-popover/register.js +0 -0
  52. /package/{components → basic-components}/basic-summary-table/index.d.ts +0 -0
  53. /package/{components → basic-components}/basic-summary-table/index.js +0 -0
  54. /package/{components/basic-table → basic-components/basic-summary-table}/register.d.ts +0 -0
  55. /package/{components → basic-components}/basic-summary-table/register.js +0 -0
  56. /package/{components → basic-components}/basic-table/index.d.ts +0 -0
  57. /package/{components → basic-components}/basic-table/index.js +0 -0
  58. /package/{components/basic-tabs → basic-components/basic-table}/register.d.ts +0 -0
  59. /package/{components → basic-components}/basic-table/register.js +0 -0
  60. /package/{components → basic-components}/basic-tabs/index.d.ts +0 -0
  61. /package/{components → basic-components}/basic-tabs/index.js +0 -0
  62. /package/{components/basic-toast → basic-components/basic-tabs}/register.d.ts +0 -0
  63. /package/{components → basic-components}/basic-tabs/register.js +0 -0
  64. /package/{components/basic-toc → basic-components/basic-toast}/register.d.ts +0 -0
  65. /package/{components → basic-components}/basic-toast/register.js +0 -0
  66. /package/{components → basic-components}/basic-toc/index.d.ts +0 -0
  67. /package/{components → basic-components}/basic-toc/index.js +0 -0
  68. /package/{components → basic-components}/basic-toc/register.js +0 -0
@@ -11,9 +11,36 @@ const PANEL_SELECTOR = "[data-toast-panel]";
11
11
  const TITLE_SELECTOR = "[data-toast-title]";
12
12
  const OPEN_SELECTOR = "[data-toast-open]";
13
13
  const CLOSE_SELECTOR = "[data-toast-close]";
14
+ const ANNOUNCER_ATTRIBUTE = "data-basic-toast-announcer";
14
15
  const MANAGED_LABEL_ATTRIBUTE = "data-basic-toast-managed-label";
15
16
  const MANAGED_LABELLEDBY_ATTRIBUTE = "data-basic-toast-managed-labelledby";
16
17
  const MANAGED_POPOVER_ATTRIBUTE = "data-basic-toast-managed-popover";
18
+ const INTERACTIVE_PANEL_SELECTOR = [
19
+ "a[href]",
20
+ "area[href]",
21
+ "button",
22
+ "input",
23
+ "select",
24
+ "textarea",
25
+ "summary",
26
+ "iframe",
27
+ "audio[controls]",
28
+ "video[controls]",
29
+ "[contenteditable]:not([contenteditable=\"false\"])",
30
+ "[tabindex]:not([tabindex=\"-1\"])",
31
+ ].join(", ");
32
+ const VISUALLY_HIDDEN_ANNOUNCER_STYLE = [
33
+ "position:absolute",
34
+ "inline-size:1px",
35
+ "block-size:1px",
36
+ "margin:-1px",
37
+ "padding:0",
38
+ "overflow:hidden",
39
+ "clip:rect(0 0 0 0)",
40
+ "clip-path:inset(50%)",
41
+ "white-space:nowrap",
42
+ "border:0",
43
+ ].join(";");
17
44
 
18
45
  let nextToastInstanceId = 1;
19
46
 
@@ -41,6 +68,98 @@ function isToastPanelOpen(panel) {
41
68
  }
42
69
  }
43
70
 
71
+ function normalizeToastText(value) {
72
+ return value?.replace(/\s+/g, " ").trim() || "";
73
+ }
74
+
75
+ function getToastTextFromIdRefs(ownerDocument, value) {
76
+ if (!ownerDocument || !value) {
77
+ return "";
78
+ }
79
+
80
+ return value
81
+ .split(/\s+/)
82
+ .map((id) => normalizeToastText(ownerDocument.getElementById(id)?.textContent))
83
+ .filter(Boolean)
84
+ .join(" ");
85
+ }
86
+
87
+ function getToastAccessibleName(panel, title) {
88
+ if (!(panel instanceof HTMLElementBase)) {
89
+ return { includesTitle: false, text: "" };
90
+ }
91
+
92
+ const labelledBy = panel.getAttribute("aria-labelledby");
93
+
94
+ if (labelledBy) {
95
+ const ids = labelledBy.split(/\s+/).filter(Boolean);
96
+
97
+ return {
98
+ includesTitle: title instanceof HTMLElementBase && !!title.id && ids.includes(title.id),
99
+ text: getToastTextFromIdRefs(panel.ownerDocument, labelledBy),
100
+ };
101
+ }
102
+
103
+ const label = normalizeToastText(panel.getAttribute("aria-label"));
104
+
105
+ if (label) {
106
+ return { includesTitle: false, text: label };
107
+ }
108
+
109
+ if (title instanceof HTMLElementBase) {
110
+ return { includesTitle: true, text: normalizeToastText(title.textContent) };
111
+ }
112
+
113
+ return { includesTitle: false, text: "" };
114
+ }
115
+
116
+ function getToastBodyText(panel, excludeTitle = false) {
117
+ if (!(panel instanceof HTMLElementBase)) {
118
+ return "";
119
+ }
120
+
121
+ if (!excludeTitle) {
122
+ return normalizeToastText(panel.textContent);
123
+ }
124
+
125
+ const panelClone = panel.cloneNode(true);
126
+
127
+ if (!(panelClone instanceof HTMLElementBase)) {
128
+ return normalizeToastText(panel.textContent);
129
+ }
130
+
131
+ for (const title of panelClone.querySelectorAll(TITLE_SELECTOR)) {
132
+ title.remove();
133
+ }
134
+
135
+ return normalizeToastText(panelClone.textContent);
136
+ }
137
+
138
+ function getToastAnnouncementText(panel, title) {
139
+ const { includesTitle, text: name } = getToastAccessibleName(panel, title);
140
+ const body = getToastBodyText(panel, includesTitle);
141
+
142
+ if (!name) {
143
+ return body;
144
+ }
145
+
146
+ if (!body || body === name || body.startsWith(`${name} `) || body.startsWith(`${name}.`)) {
147
+ return name;
148
+ }
149
+
150
+ return `${name}. ${body}`;
151
+ }
152
+
153
+ function findInteractiveToastDescendants(panel) {
154
+ if (!(panel instanceof HTMLElementBase)) {
155
+ return [];
156
+ }
157
+
158
+ return Array.from(panel.querySelectorAll(INTERACTIVE_PANEL_SELECTOR)).filter(
159
+ (element) => element instanceof HTMLElementBase && !element.hidden,
160
+ );
161
+ }
162
+
44
163
  export function normalizeToastLabel(value) {
45
164
  return value?.trim() || DEFAULT_LABEL;
46
165
  }
@@ -88,6 +207,10 @@ export class ToastElement extends HTMLElementBase {
88
207
  #closeButtons = [];
89
208
  #restoreFocusTo = null;
90
209
  #dismissTimer = 0;
210
+ #announcementTimer = 0;
211
+ #announcer = null;
212
+ #open = false;
213
+ #hasWarnedAboutInteractiveContent = false;
91
214
  #eventsBound = false;
92
215
 
93
216
  connectedCallback() {
@@ -110,6 +233,7 @@ export class ToastElement extends HTMLElementBase {
110
233
  this.#eventsBound = false;
111
234
  this.#syncPanelEvents(null);
112
235
  this.#clearDismissTimer();
236
+ this.#clearAnnouncement();
113
237
  }
114
238
 
115
239
  attributeChangedCallback() {
@@ -223,6 +347,34 @@ export class ToastElement extends HTMLElementBase {
223
347
  this.#applyState();
224
348
  }
225
349
 
350
+ #ensureAnnouncer() {
351
+ if (this.#announcer instanceof HTMLElementBase && this.#announcer.isConnected) {
352
+ this.#announcer.setAttribute("style", VISUALLY_HIDDEN_ANNOUNCER_STYLE);
353
+ return this.#announcer;
354
+ }
355
+
356
+ const existingAnnouncer = Array.from(this.children).find(
357
+ (element) => element instanceof HTMLElementBase && element.hasAttribute(ANNOUNCER_ATTRIBUTE),
358
+ );
359
+
360
+ if (existingAnnouncer instanceof HTMLElementBase) {
361
+ existingAnnouncer.setAttribute("style", VISUALLY_HIDDEN_ANNOUNCER_STYLE);
362
+ this.#announcer = existingAnnouncer;
363
+ return existingAnnouncer;
364
+ }
365
+
366
+ if (!this.ownerDocument?.createElement) {
367
+ return null;
368
+ }
369
+
370
+ const announcer = this.ownerDocument.createElement("div");
371
+ announcer.setAttribute(ANNOUNCER_ATTRIBUTE, "");
372
+ announcer.setAttribute("style", VISUALLY_HIDDEN_ANNOUNCER_STYLE);
373
+ this.append(announcer);
374
+ this.#announcer = announcer;
375
+ return announcer;
376
+ }
377
+
226
378
  #syncPanelEvents(nextPanel) {
227
379
  if (this.#panelWithEvents === nextPanel) {
228
380
  return;
@@ -264,6 +416,7 @@ export class ToastElement extends HTMLElementBase {
264
416
 
265
417
  if (!(this.#panel instanceof HTMLElementBase)) {
266
418
  this.#clearDismissTimer();
419
+ this.#clearAnnouncement();
267
420
  return;
268
421
  }
269
422
 
@@ -273,11 +426,13 @@ export class ToastElement extends HTMLElementBase {
273
426
  this.#title.id = `${baseId}-title`;
274
427
  }
275
428
 
276
- this.#panel.setAttribute("role", getToastRoleForLive(this.getAttribute("data-live")));
277
- this.#panel.setAttribute("aria-live", normalizeToastLive(this.getAttribute("data-live")));
278
- this.#panel.setAttribute("aria-atomic", "true");
429
+ this.#panel.setAttribute("role", "group");
430
+ this.#panel.removeAttribute("aria-live");
431
+ this.#panel.removeAttribute("aria-atomic");
279
432
  this.#syncAccessibleLabel();
280
433
  this.#syncPopoverState();
434
+ this.#syncAnnouncerState();
435
+ this.#warnOnInteractiveContent();
281
436
  this.#syncOpenState();
282
437
  }
283
438
 
@@ -309,6 +464,15 @@ export class ToastElement extends HTMLElementBase {
309
464
  this.#dismissTimer = 0;
310
465
  }
311
466
 
467
+ #clearAnnouncementTimer() {
468
+ if (this.#announcementTimer === 0 || typeof window === "undefined") {
469
+ return;
470
+ }
471
+
472
+ window.clearTimeout(this.#announcementTimer);
473
+ this.#announcementTimer = 0;
474
+ }
475
+
312
476
  #syncPopoverState() {
313
477
  if (!(this.#panel instanceof HTMLElementBase) || !supportsToastPopover(this.#panel)) {
314
478
  return;
@@ -323,6 +487,7 @@ export class ToastElement extends HTMLElementBase {
323
487
  #syncOpenState() {
324
488
  if (!(this.#panel instanceof HTMLElementBase)) {
325
489
  this.#clearDismissTimer();
490
+ this.#clearAnnouncement();
326
491
  return;
327
492
  }
328
493
 
@@ -350,21 +515,29 @@ export class ToastElement extends HTMLElementBase {
350
515
  #syncStateFromPanel() {
351
516
  if (!(this.#panel instanceof HTMLElementBase)) {
352
517
  this.#clearDismissTimer();
518
+ this.#clearAnnouncement();
353
519
  return;
354
520
  }
355
521
 
522
+ const wasOpen = this.#open;
356
523
  const open = supportsToastPopover(this.#panel)
357
524
  ? isToastPanelOpen(this.#panel)
358
525
  : !this.#panel.hidden;
359
526
 
527
+ this.#open = open;
360
528
  this.#panel.hidden = !open;
361
529
  this.#panel.toggleAttribute("data-open", open);
362
530
  this.toggleAttribute("data-open", open);
363
531
 
364
532
  if (open) {
533
+ this.#syncAnnouncerState();
365
534
  this.#scheduleDismiss();
535
+ if (!wasOpen) {
536
+ this.#announce();
537
+ }
366
538
  } else {
367
539
  this.#clearDismissTimer();
540
+ this.#clearAnnouncement();
368
541
  }
369
542
  }
370
543
 
@@ -414,6 +587,94 @@ export class ToastElement extends HTMLElementBase {
414
587
  this.#panel.setAttribute("aria-label", nextLabel);
415
588
  this.#panel.setAttribute(MANAGED_LABEL_ATTRIBUTE, "");
416
589
  }
590
+
591
+ #syncAnnouncerState() {
592
+ if (!this.#open) {
593
+ if (this.#announcer instanceof HTMLElementBase) {
594
+ this.#announcer.textContent = "";
595
+ this.#announcer.removeAttribute("role");
596
+ this.#announcer.removeAttribute("aria-live");
597
+ this.#announcer.removeAttribute("aria-atomic");
598
+ this.#announcer.removeAttribute("aria-relevant");
599
+ }
600
+ return;
601
+ }
602
+
603
+ const announcer = this.#ensureAnnouncer();
604
+
605
+ if (!(announcer instanceof HTMLElementBase)) {
606
+ return;
607
+ }
608
+
609
+ announcer.setAttribute("role", getToastRoleForLive(this.getAttribute("data-live")));
610
+ announcer.setAttribute("aria-live", normalizeToastLive(this.getAttribute("data-live")));
611
+ announcer.setAttribute("aria-atomic", "true");
612
+ announcer.setAttribute("aria-relevant", "additions text");
613
+ }
614
+
615
+ #clearAnnouncement() {
616
+ this.#clearAnnouncementTimer();
617
+ this.#open = false;
618
+
619
+ if (!(this.#announcer instanceof HTMLElementBase)) {
620
+ return;
621
+ }
622
+
623
+ this.#announcer.textContent = "";
624
+ this.#announcer.removeAttribute("role");
625
+ this.#announcer.removeAttribute("aria-live");
626
+ this.#announcer.removeAttribute("aria-atomic");
627
+ this.#announcer.removeAttribute("aria-relevant");
628
+ }
629
+
630
+ #announce() {
631
+ const announcement = getToastAnnouncementText(this.#panel, this.#title);
632
+ const announcer = this.#ensureAnnouncer();
633
+
634
+ if (!announcement || !(announcer instanceof HTMLElementBase)) {
635
+ return;
636
+ }
637
+
638
+ this.#clearAnnouncementTimer();
639
+ this.#syncAnnouncerState();
640
+ announcer.textContent = "";
641
+
642
+ if (typeof window === "undefined") {
643
+ announcer.textContent = announcement;
644
+ return;
645
+ }
646
+
647
+ this.#announcementTimer = window.setTimeout(() => {
648
+ this.#announcementTimer = 0;
649
+
650
+ if (!this.#open || !(this.#announcer instanceof HTMLElementBase)) {
651
+ return;
652
+ }
653
+
654
+ this.#announcer.textContent = announcement;
655
+ }, 0);
656
+ }
657
+
658
+ #warnOnInteractiveContent() {
659
+ if (
660
+ this.#hasWarnedAboutInteractiveContent
661
+ || !(this.#panel instanceof HTMLElementBase)
662
+ || typeof globalThis.console?.warn !== "function"
663
+ ) {
664
+ return;
665
+ }
666
+
667
+ if (findInteractiveToastDescendants(this.#panel).length === 0) {
668
+ return;
669
+ }
670
+
671
+ this.#hasWarnedAboutInteractiveContent = true;
672
+ globalThis.console.warn(
673
+ "basic-toast panels should only contain non-interactive message content. "
674
+ + "Move links, buttons, and other focusable controls outside [data-toast-panel], "
675
+ + "or use a more appropriate pattern such as basic-alert or basic-dialog.",
676
+ );
677
+ }
417
678
  }
418
679
 
419
680
  export function defineToast(registry = globalThis.customElements) {
@@ -0,0 +1,43 @@
1
+ # `basic-toc`
2
+
3
+ Generated table-of-contents navigation from the nearest `<main>`.
4
+
5
+ ## Register
6
+
7
+ ```js
8
+ import "@lmfaole/basics/basic-components/basic-toc/register";
9
+ ```
10
+
11
+ ## Example
12
+
13
+ ```html
14
+ <basic-toc data-title="Innhold">
15
+ <nav aria-label="Innhold" data-page-toc-nav></nav>
16
+ </basic-toc>
17
+ ```
18
+
19
+ ## Props
20
+
21
+ | Prop | Description | Type | Default | Options |
22
+ | --- | --- | --- | --- | --- |
23
+ | `data-title` | Accessible label applied to the generated nav. | string | `Innhold` | any string |
24
+ | `data-heading-selector` | Selector used to collect headings from the nearest `<main>`. | CSS selector | `h1, h2, h3, h4, h5, h6` | any CSS selector |
25
+
26
+ ## Markup Hooks
27
+
28
+ | Hook | Description | Type | Default | Options |
29
+ | --- | --- | --- | --- | --- |
30
+ | `data-page-toc-nav` | Container where the generated outline links are rendered. | descendant element attribute | required | present on one descendant element |
31
+
32
+ ## Behavior
33
+
34
+ - Generates missing heading ids automatically
35
+ - Gives duplicate headings unique fragment ids
36
+ - Ignores hidden headings
37
+ - Rebuilds the outline when matching headings are added or changed
38
+
39
+ ## Markup Contract
40
+
41
+ - Render the element inside the same `<main>` that contains the content it should index
42
+ - Provide a descendant element with `data-page-toc-nav` for the generated links
43
+ - Keep layout and styling outside the package
@@ -0,0 +1 @@
1
+ export {};
@@ -1,16 +1,20 @@
1
1
  basic-accordion {
2
2
  --basic-accordion-padding: var(--basic-panel-padding);
3
3
  display: grid;
4
+ gap: 0;
4
5
  }
5
6
 
6
7
  basic-accordion > details {
7
8
  margin: 0;
8
- border: var(--basic-border-width) solid var(--basic-color-border);
9
- border-radius: var(--basic-radius);
9
+ border-inline: var(--basic-border-width) solid var(--basic-color-border);
10
10
  background: var(--basic-color-surface);
11
11
  }
12
12
 
13
13
  basic-accordion > details > summary {
14
+ --basic-interaction-border-rest: transparent;
15
+ --basic-interaction-border-hover: transparent;
16
+ --basic-interaction-border-active: transparent;
17
+ --basic-interaction-border-selected: transparent;
14
18
  list-style: none;
15
19
  display: flex;
16
20
  align-items: center;
@@ -20,6 +24,7 @@ basic-accordion > details > summary {
20
24
  font-weight: 600;
21
25
  text-align: left;
22
26
  cursor: pointer;
27
+ background: transparent;
23
28
  }
24
29
 
25
30
  basic-accordion > details > summary::-webkit-details-marker {
@@ -34,9 +39,34 @@ basic-accordion > details > summary::after {
34
39
  line-height: 1;
35
40
  }
36
41
 
42
+ basic-accordion > details:first-child {
43
+ border-block-start: var(--basic-border-width) solid var(--basic-color-border);
44
+ border-start-start-radius: var(--basic-radius);
45
+ border-start-end-radius: var(--basic-radius);
46
+ }
47
+
48
+ basic-accordion > details:first-child > summary {
49
+ border-start-start-radius: var(--basic-radius);
50
+ border-start-end-radius: var(--basic-radius);
51
+ }
52
+
53
+ basic-accordion > details + details {
54
+ border-block-start: var(--basic-border-width) solid var(--basic-color-border);
55
+ }
56
+
57
+ basic-accordion > details:last-child {
58
+ border-block-end: var(--basic-border-width) solid var(--basic-color-border);
59
+ border-end-start-radius: var(--basic-radius);
60
+ border-end-end-radius: var(--basic-radius);
61
+ }
62
+
63
+ basic-accordion > details:last-child:not([open]) > summary {
64
+ border-end-start-radius: var(--basic-radius);
65
+ border-end-end-radius: var(--basic-radius);
66
+ }
67
+
37
68
  basic-accordion > details[open] > summary {
38
- background: var(--basic-color-surface-muted);
39
- margin-block-end: var(--basic-flow-space);
69
+ border-block-end: var(--basic-border-width) solid var(--basic-color-border-selected);
40
70
  }
41
71
 
42
72
  basic-accordion > details[open] > summary::after {
@@ -48,6 +78,10 @@ basic-accordion > details[open] {
48
78
  padding-block-end: var(--basic-accordion-padding);
49
79
  }
50
80
 
81
+ basic-accordion > details[open] > summary + :where(*) {
82
+ margin-block-start: var(--basic-accordion-padding);
83
+ }
84
+
51
85
  basic-accordion > details > :where(:not(summary)) {
52
86
  margin-block: 0;
53
87
  margin-inline: var(--basic-accordion-padding);
@@ -0,0 +1,183 @@
1
+ basic-carousel {
2
+ --basic-carousel-gap: var(--basic-space-4);
3
+ --basic-carousel-controls-gap: var(--basic-space-3);
4
+ --basic-carousel-button-size: 2.75rem;
5
+ --basic-carousel-marker-size: 1.75rem;
6
+ --basic-carousel-item-width: 90%;
7
+ --basic-carousel-track-padding: var(--basic-space-1);
8
+ --basic-carousel-snap-align: center;
9
+ display: block;
10
+ }
11
+
12
+ basic-carousel[data-basic-carousel-snapping="start"] {
13
+ --basic-carousel-snap-align: start;
14
+ }
15
+
16
+ basic-carousel[data-basic-carousel-snapping="end"] {
17
+ --basic-carousel-snap-align: end;
18
+ }
19
+
20
+ basic-carousel [data-carousel-track] {
21
+ display: flex;
22
+ align-items: stretch;
23
+ gap: var(--basic-carousel-gap);
24
+ min-inline-size: 0;
25
+ margin: 0;
26
+ padding: var(--basic-carousel-track-padding);
27
+ overflow-x: auto;
28
+ overscroll-behavior-inline: contain;
29
+ scroll-behavior: smooth;
30
+ scroll-snap-type: x mandatory;
31
+ }
32
+
33
+ basic-carousel :is(ol, ul)[data-carousel-track] {
34
+ list-style: none;
35
+ padding-inline-start: var(--basic-carousel-track-padding);
36
+ }
37
+
38
+ basic-carousel [data-carousel-track] > * {
39
+ box-sizing: border-box;
40
+ flex: 0 0 var(--basic-carousel-item-width);
41
+ inline-size: var(--basic-carousel-item-width);
42
+ min-inline-size: 0;
43
+ padding: var(--basic-panel-padding);
44
+ border: var(--basic-border-width) solid var(--basic-color-border);
45
+ border-radius: var(--basic-radius);
46
+ background: var(--basic-color-surface);
47
+ scroll-snap-align: var(--basic-carousel-snap-align);
48
+ scroll-snap-stop: always;
49
+ }
50
+
51
+ basic-carousel [data-carousel-track] > * > :where(*) {
52
+ margin-block: 0;
53
+ }
54
+
55
+ basic-carousel [data-carousel-track] > * > :where(* + *) {
56
+ margin-block-start: var(--basic-flow-space);
57
+ }
58
+
59
+ basic-carousel [data-carousel-track] > * > p:first-child {
60
+ color: var(--basic-color-text-muted);
61
+ font-size: 0.875rem;
62
+ font-weight: 700;
63
+ letter-spacing: 0.06em;
64
+ text-transform: uppercase;
65
+ }
66
+
67
+ basic-carousel [data-carousel-track] > * > a {
68
+ color: inherit;
69
+ font-weight: 600;
70
+ }
71
+
72
+ @supports (scroll-marker-group: after) {
73
+ basic-carousel {
74
+ display: grid;
75
+ grid-template-columns: auto minmax(0, 1fr) auto;
76
+ grid-template-rows: minmax(0, auto) auto;
77
+ gap: var(--basic-carousel-controls-gap);
78
+ align-items: start;
79
+ }
80
+
81
+ basic-carousel:is([data-basic-carousel-controls="markers"], [data-basic-carousel-controls="none"]) {
82
+ grid-template-columns: minmax(0, 1fr);
83
+ }
84
+
85
+ basic-carousel [data-carousel-track] {
86
+ grid-column: 1 / -1;
87
+ grid-row: 1;
88
+ scroll-marker-group: after;
89
+ }
90
+
91
+ basic-carousel:is([data-basic-carousel-controls="markers"], [data-basic-carousel-controls="none"]) [data-carousel-track] {
92
+ grid-column: 1;
93
+ }
94
+
95
+ basic-carousel:is([data-basic-carousel-controls="arrows"], [data-basic-carousel-controls="none"]) [data-carousel-track] {
96
+ scroll-marker-group: none;
97
+ }
98
+
99
+ basic-carousel [data-carousel-track]::scroll-button(inline-start),
100
+ basic-carousel [data-carousel-track]::scroll-button(inline-end) {
101
+ display: inline-flex;
102
+ align-items: center;
103
+ align-self: center;
104
+ justify-content: center;
105
+ inline-size: var(--basic-carousel-button-size);
106
+ block-size: var(--basic-carousel-button-size);
107
+ margin: 0;
108
+ padding: 0;
109
+ border: var(--basic-border-width) solid var(--basic-color-border);
110
+ border-radius: var(--basic-radius-pill);
111
+ background: var(--basic-color-surface);
112
+ color: inherit;
113
+ cursor: pointer;
114
+ grid-row: 2;
115
+ }
116
+
117
+ basic-carousel:is([data-basic-carousel-controls="markers"], [data-basic-carousel-controls="none"]) [data-carousel-track]::scroll-button(inline-start),
118
+ basic-carousel:is([data-basic-carousel-controls="markers"], [data-basic-carousel-controls="none"]) [data-carousel-track]::scroll-button(inline-end) {
119
+ display: none;
120
+ }
121
+
122
+ basic-carousel [data-carousel-track]::scroll-button(inline-start) {
123
+ content: "<" / "Previous slide";
124
+ grid-column: 1;
125
+ justify-self: end;
126
+ }
127
+
128
+ basic-carousel [data-carousel-track]::scroll-button(inline-end) {
129
+ content: ">" / "Next slide";
130
+ grid-column: 3;
131
+ justify-self: start;
132
+ }
133
+
134
+ basic-carousel [data-carousel-track]::scroll-button(*):disabled {
135
+ opacity: 0.45;
136
+ cursor: not-allowed;
137
+ }
138
+
139
+ basic-carousel [data-carousel-track]::scroll-marker-group {
140
+ display: flex;
141
+ flex-wrap: nowrap;
142
+ align-items: center;
143
+ align-self: center;
144
+ justify-content: center;
145
+ gap: var(--basic-space-2);
146
+ min-block-size: var(--basic-carousel-button-size);
147
+ grid-column: 2;
148
+ grid-row: 2;
149
+ }
150
+
151
+ basic-carousel[data-basic-carousel-controls="markers"] [data-carousel-track]::scroll-marker-group {
152
+ grid-column: 1;
153
+ }
154
+
155
+ basic-carousel:is([data-basic-carousel-controls="arrows"], [data-basic-carousel-controls="none"]) [data-carousel-track]::scroll-marker-group {
156
+ display: none;
157
+ }
158
+
159
+ basic-carousel [data-carousel-track] > *::scroll-marker {
160
+ content: attr(data-basic-carousel-marker) / attr(data-basic-carousel-marker-label);
161
+ display: inline-flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ min-inline-size: var(--basic-carousel-marker-size);
165
+ block-size: var(--basic-carousel-marker-size);
166
+ padding-inline: var(--basic-space-2);
167
+ border: var(--basic-border-width) solid var(--basic-color-border);
168
+ border-radius: var(--basic-radius-pill);
169
+ background: var(--basic-color-surface);
170
+ color: inherit;
171
+ font-size: 0.875rem;
172
+ font-weight: 600;
173
+ }
174
+
175
+ basic-carousel:is([data-basic-carousel-controls="arrows"], [data-basic-carousel-controls="none"]) [data-carousel-track] > *::scroll-marker {
176
+ content: none;
177
+ }
178
+
179
+ basic-carousel [data-carousel-track] > *::scroll-marker:target-current {
180
+ border-color: var(--basic-color-border-selected);
181
+ background: var(--basic-color-surface-selected);
182
+ }
183
+ }
@@ -7,6 +7,8 @@ basic-popover {
7
7
 
8
8
  basic-popover [data-popover-open],
9
9
  basic-popover [data-popover-close] {
10
+ --basic-interaction-surface-selected: var(--basic-color-surface-muted);
11
+ --basic-interaction-border-selected: var(--basic-color-border);
10
12
  display: inline-flex;
11
13
  align-items: center;
12
14
  justify-content: center;
@@ -19,10 +21,6 @@ basic-popover [data-popover-close] {
19
21
  cursor: pointer;
20
22
  }
21
23
 
22
- basic-popover [data-popover-open][aria-expanded="true"] {
23
- background: var(--basic-color-surface-muted);
24
- }
25
-
26
24
  basic-popover [data-popover-panel] {
27
25
  width: min(var(--basic-popover-width, 20rem), var(--basic-popover-max-width, calc(100vw - 2rem)));
28
26
  max-width: var(--basic-popover-max-width, calc(100vw - 2rem));