@bluevolt-tech/lumen 2.0.0 → 2.1.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 (56) hide show
  1. package/README.md +37 -23
  2. package/dist/components/_shared/adopt-styles.d.ts +8 -0
  3. package/dist/components/_shared/date-range-validator.d.ts +61 -0
  4. package/dist/components/_shared/dropdown-controller.d.ts +31 -0
  5. package/dist/components/_shared/selectable-card.d.ts +10 -0
  6. package/dist/components/bv-alert-banner/bv-alert-banner.js +19 -17
  7. package/dist/components/bv-avatar/bv-avatar.js +19 -11
  8. package/dist/components/bv-badge/bv-badge.js +19 -11
  9. package/dist/components/bv-button/bv-button.js +18 -16
  10. package/dist/components/bv-category-pill/bv-category-pill.js +18 -16
  11. package/dist/components/bv-checkbox/bv-checkbox.js +19 -11
  12. package/dist/components/bv-chip/bv-chip.js +19 -11
  13. package/dist/components/bv-course-detail/bv-course-detail.js +20 -34
  14. package/dist/components/bv-course-detail-modal/bv-course-detail-modal.js +24 -90
  15. package/dist/components/bv-course-grid-card/bv-course-grid-card.d.ts +2 -2
  16. package/dist/components/bv-course-grid-card/bv-course-grid-card.js +105 -55
  17. package/dist/components/bv-course-list-card/bv-course-list-card.d.ts +2 -2
  18. package/dist/components/bv-course-list-card/bv-course-list-card.js +105 -55
  19. package/dist/components/bv-course-outline/bv-course-outline.js +19 -11
  20. package/dist/components/bv-date-range-input/bv-date-range-input.d.ts +4 -6
  21. package/dist/components/bv-date-range-input/bv-date-range-input.js +460 -122
  22. package/dist/components/bv-date-range-picker/bv-date-range-picker.d.ts +17 -3
  23. package/dist/components/bv-date-range-picker/bv-date-range-picker.js +339 -73
  24. package/dist/components/bv-empty-state/bv-empty-state.js +20 -28
  25. package/dist/components/bv-filter-dropdown/bv-filter-dropdown.d.ts +1 -4
  26. package/dist/components/bv-filter-dropdown/bv-filter-dropdown.js +78 -53
  27. package/dist/components/bv-filter-panel/bv-filter-panel.js +20 -22
  28. package/dist/components/bv-icon/bv-icon.js +19 -11
  29. package/dist/components/bv-input/bv-input.js +18 -16
  30. package/dist/components/bv-input-dropdown/bv-input-dropdown.d.ts +1 -4
  31. package/dist/components/bv-input-dropdown/bv-input-dropdown.js +77 -78
  32. package/dist/components/bv-modal/bv-modal.js +19 -11
  33. package/dist/components/bv-page-title/bv-page-title.js +19 -6
  34. package/dist/components/bv-pagination/bv-pagination.d.ts +1 -0
  35. package/dist/components/bv-pagination/bv-pagination.js +23 -186
  36. package/dist/components/bv-portal-header/bv-portal-header.js +25 -83
  37. package/dist/components/bv-portal-layout/bv-portal-layout.js +19 -11
  38. package/dist/components/bv-progress-bar/bv-progress-bar.js +19 -11
  39. package/dist/components/bv-promo-banner/bv-promo-banner.js +19 -11
  40. package/dist/components/bv-radio/bv-radio.js +19 -11
  41. package/dist/components/bv-searchbox/bv-searchbox.js +19 -11
  42. package/dist/components/bv-segmented-control/bv-segmented-control.js +18 -16
  43. package/dist/components/bv-select/bv-select.d.ts +1 -4
  44. package/dist/components/bv-select/bv-select.js +76 -44
  45. package/dist/components/bv-split-button/bv-split-button.d.ts +1 -4
  46. package/dist/components/bv-split-button/bv-split-button.js +76 -42
  47. package/dist/components/bv-star-rating/bv-star-rating.js +19 -11
  48. package/dist/components/bv-table/bv-table.d.ts +5 -0
  49. package/dist/components/bv-table/bv-table.js +69 -19
  50. package/dist/components/bv-toggle/bv-toggle.js +19 -11
  51. package/dist/components/bv-tooltip/bv-tooltip.js +19 -17
  52. package/dist/components/bv-user-row/bv-user-row.js +22 -50
  53. package/dist/components/bv-view-switcher/bv-view-switcher.js +19 -11
  54. package/dist/themes/base.css +2 -1
  55. package/package.json +8 -1
  56. package/themes/base.css +2 -1
@@ -1,28 +1,35 @@
1
1
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-course-detail/bv-course-detail.css
2
2
  var bv_course_detail_default = ":host {\n display: block;\n font-family: var(--bv-font-family-base);\n}\n\n[hidden] {\n display: none !important;\n}\n\n/* \u2500\u2500 Root layout \u2500\u2500 */\n\n.course-detail {\n display: flex;\n gap: 20px;\n align-items: flex-start;\n}\n\n/* \u2500\u2500 Thumbnail column \u2500\u2500 */\n\n.thumb-col {\n flex-shrink: 0;\n width: 160px;\n}\n\n.thumb-wrap {\n width: 160px;\n height: 113px;\n background: var(--bv-color-neutral-200);\n border-radius: var(--bv-radius-lg);\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.thumb-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.thumb-placeholder {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n}\n\n.thumb-placeholder svg {\n width: 100%;\n height: 100%;\n}\n\n/* \u2500\u2500 Right column \u2500\u2500 */\n\n.right-col {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* \u2500\u2500 Top row: pills + title + separator \u2500\u2500 */\n\n.top-row {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.card-header {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.pills-row {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n.course-title {\n font-size: var(--bv-font-size-xl);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-900);\n line-height: 32px;\n}\n\n.separator {\n height: 0;\n border-top: 0.5px solid var(--bv-color-neutral-300);\n}\n\n/* \u2500\u2500 Description \u2500\u2500 */\n\n.desc-section {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.desc-text {\n margin: 0;\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n line-height: 20px;\n display: -webkit-box;\n -webkit-line-clamp: 6;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Read More expands the clamp */\n.desc-wrap--expanded .desc-text {\n -webkit-line-clamp: unset;\n}\n\n.read-more-btn {\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-primary);\n line-height: 24px;\n white-space: nowrap;\n text-align: left;\n}\n\n.read-more-btn:hover {\n text-decoration: underline;\n}\n";
3
3
 
4
+ // components/_shared/adopt-styles.ts
5
+ var _sheetCache = /* @__PURE__ */ new Map();
6
+ function adoptStyles(shadow, css) {
7
+ if ("adoptedStyleSheets" in ShadowRoot.prototype && typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype) {
8
+ let sheet = _sheetCache.get(css);
9
+ if (!sheet) {
10
+ sheet = new CSSStyleSheet();
11
+ sheet.replaceSync(css);
12
+ _sheetCache.set(css, sheet);
13
+ }
14
+ shadow.adoptedStyleSheets = [sheet];
15
+ return;
16
+ }
17
+ const style = document.createElement("style");
18
+ style.textContent = css;
19
+ shadow.appendChild(style);
20
+ }
21
+
4
22
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-category-pill/bv-category-pill.css
5
23
  var bv_category_pill_default = ":host {\n display: inline-flex;\n font-family: var(--bv-font-family-base);\n}\n\n.pill {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 4px 6px;\n border: 0.5px solid var(--bv-color-neutral-900);\n border-radius: var(--bv-radius-sm);\n font-size: 10px;\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n line-height: 1;\n white-space: nowrap;\n}\n";
6
24
 
7
25
  // components/bv-category-pill/bv-category-pill.ts
8
- var _sheet = null;
9
- function getSheet() {
10
- if (!_sheet) {
11
- _sheet = new CSSStyleSheet();
12
- _sheet.replaceSync(bv_category_pill_default);
13
- }
14
- return _sheet;
15
- }
16
26
  var BvCategoryPill = class extends HTMLElement {
17
27
  static observedAttributes = ["label"];
18
28
  _rendered = false;
19
29
  _labelEl = null;
20
30
  constructor() {
21
31
  super();
22
- const shadow = this.attachShadow({ mode: "open" });
23
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
24
- shadow.adoptedStyleSheets = [getSheet()];
25
- }
32
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_category_pill_default);
26
33
  }
27
34
  get label() {
28
35
  return this.getAttribute("label") ?? "";
@@ -41,11 +48,6 @@ var BvCategoryPill = class extends HTMLElement {
41
48
  if (this._rendered) return;
42
49
  this._rendered = true;
43
50
  const shadow = this.shadowRoot;
44
- if (!shadow.adoptedStyleSheets.length) {
45
- const s = document.createElement("style");
46
- s.textContent = bv_category_pill_default;
47
- shadow.appendChild(s);
48
- }
49
51
  const pill = document.createElement("span");
50
52
  pill.className = "pill";
51
53
  pill.setAttribute("part", "pill");
@@ -61,14 +63,6 @@ if (!customElements.get("bv-category-pill")) {
61
63
  }
62
64
 
63
65
  // components/bv-course-detail/bv-course-detail.ts
64
- var _sheet2 = null;
65
- function getSheet2() {
66
- if (!_sheet2) {
67
- _sheet2 = new CSSStyleSheet();
68
- _sheet2.replaceSync(bv_course_detail_default);
69
- }
70
- return _sheet2;
71
- }
72
66
  var BvCourseDetail = class extends HTMLElement {
73
67
  static observedAttributes = ["thumbnail", "title", "description", "categories"];
74
68
  _rendered = false;
@@ -82,10 +76,7 @@ var BvCourseDetail = class extends HTMLElement {
82
76
  _readMoreBtn = null;
83
77
  constructor() {
84
78
  super();
85
- const shadow = this.attachShadow({ mode: "open" });
86
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
87
- shadow.adoptedStyleSheets = [getSheet2()];
88
- }
79
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_course_detail_default);
89
80
  }
90
81
  get thumbnail() {
91
82
  return this.getAttribute("thumbnail") ?? "";
@@ -150,11 +141,6 @@ var BvCourseDetail = class extends HTMLElement {
150
141
  }
151
142
  }
152
143
  const shadow = this.shadowRoot;
153
- if (!shadow.adoptedStyleSheets.length) {
154
- const s = document.createElement("style");
155
- s.textContent = bv_course_detail_default;
156
- shadow.appendChild(s);
157
- }
158
144
  const root = document.createElement("div");
159
145
  root.className = "course-detail";
160
146
  const thumbCol = document.createElement("div");
@@ -1,6 +1,24 @@
1
1
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-course-detail-modal/bv-course-detail-modal.css
2
2
  var bv_course_detail_modal_default = ":host {\n display: contents;\n font-family: var(--bv-font-family-base);\n}\n\n/* Ensure hidden attribute works on slotted custom elements */\n[hidden] {\n display: none !important;\n}\n\n.modal-body {\n display: flex;\n flex-direction: column;\n gap: 20px;\n width: 100%;\n}\n";
3
3
 
4
+ // components/_shared/adopt-styles.ts
5
+ var _sheetCache = /* @__PURE__ */ new Map();
6
+ function adoptStyles(shadow, css) {
7
+ if ("adoptedStyleSheets" in ShadowRoot.prototype && typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype) {
8
+ let sheet = _sheetCache.get(css);
9
+ if (!sheet) {
10
+ sheet = new CSSStyleSheet();
11
+ sheet.replaceSync(css);
12
+ _sheetCache.set(css, sheet);
13
+ }
14
+ shadow.adoptedStyleSheets = [sheet];
15
+ return;
16
+ }
17
+ const style = document.createElement("style");
18
+ style.textContent = css;
19
+ shadow.appendChild(style);
20
+ }
21
+
4
22
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-modal/bv-modal.css
5
23
  var bv_modal_default = '/* \u2500\u2500 Public theming API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Apps can override per-instance:\n\n HTML: <bv-modal style="--bv-modal-width: 600px;">\n Angular: <bv-modal [style.--bv-modal-bg]="bgColor">\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host {\n --bv-modal-width: 800px;\n --bv-modal-bg: var(--bv-color-neutral-0);\n --bv-modal-radius: var(--bv-radius-xl);\n --bv-modal-title-color: #101828;\n --bv-modal-backdrop: rgba(16, 24, 40, 0.6);\n --bv-modal-body-max-height: none;\n}\n\ndialog {\n padding: 0;\n border: none;\n border-radius: var(--bv-modal-radius);\n background: transparent;\n width: var(--bv-modal-width);\n max-width: min(var(--bv-modal-width), calc(100vw - 32px));\n outline: none;\n}\n\ndialog::backdrop {\n background: var(--bv-modal-backdrop);\n}\n\n.panel {\n display: flex;\n width: 100%;\n padding: 20px 16px;\n flex-direction: column;\n justify-content: center;\n align-items: flex-start;\n gap: 20px;\n border-radius: var(--bv-modal-radius);\n background: var(--bv-modal-bg);\n box-sizing: border-box;\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.header {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n}\n\n.title {\n margin: 0;\n color: var(--bv-modal-title-color);\n font-family: var(--bv-font-family-base);\n font-size: 24px;\n font-style: normal;\n font-weight: var(--bv-font-weight-medium);\n line-height: 32px;\n}\n\n.close-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: none;\n border: none;\n border-radius: var(--bv-radius-md);\n cursor: pointer;\n color: var(--bv-color-neutral-500);\n font-size: 18px;\n line-height: 1;\n flex-shrink: 0;\n transition:\n color var(--bv-transition-fast),\n background var(--bv-transition-fast);\n}\n\n.close-btn:hover {\n color: var(--bv-color-neutral-800);\n background: var(--bv-color-neutral-100);\n}\n\n/* \u2500\u2500 Separator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.separator {\n width: 100%;\n border: none;\n border-top: 1px solid var(--bv-color-neutral-200);\n margin: 0;\n}\n\n/* \u2500\u2500 Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.body {\n width: 100%;\n max-height: var(--bv-modal-body-max-height);\n overflow-y: auto;\n color: var(--bv-color-neutral-600);\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-sm);\n line-height: 20px;\n}\n\n/* \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.footer {\n width: 100%;\n}\n';
6
24
 
@@ -12,12 +30,7 @@ var BvModal = class extends HTMLElement {
12
30
  _rendered = false;
13
31
  constructor() {
14
32
  super();
15
- this.attachShadow({ mode: "open" });
16
- if (typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype) {
17
- const sheet = new CSSStyleSheet();
18
- sheet.replaceSync(bv_modal_default);
19
- this.shadowRoot.adoptedStyleSheets = [sheet];
20
- }
33
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_modal_default);
21
34
  }
22
35
  connectedCallback() {
23
36
  this._render();
@@ -51,11 +64,6 @@ var BvModal = class extends HTMLElement {
51
64
  if (this._rendered) return;
52
65
  this._rendered = true;
53
66
  const shadow = this.shadowRoot;
54
- if (!shadow.adoptedStyleSheets.length) {
55
- const style = document.createElement("style");
56
- style.textContent = bv_modal_default;
57
- shadow.appendChild(style);
58
- }
59
67
  const dialog = document.createElement("dialog");
60
68
  this._dialog = dialog;
61
69
  dialog.addEventListener("click", (e) => {
@@ -110,24 +118,13 @@ var bv_course_detail_default = ":host {\n display: block;\n font-family: var(-
110
118
  var bv_category_pill_default = ":host {\n display: inline-flex;\n font-family: var(--bv-font-family-base);\n}\n\n.pill {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 4px 6px;\n border: 0.5px solid var(--bv-color-neutral-900);\n border-radius: var(--bv-radius-sm);\n font-size: 10px;\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n line-height: 1;\n white-space: nowrap;\n}\n";
111
119
 
112
120
  // components/bv-category-pill/bv-category-pill.ts
113
- var _sheet = null;
114
- function getSheet() {
115
- if (!_sheet) {
116
- _sheet = new CSSStyleSheet();
117
- _sheet.replaceSync(bv_category_pill_default);
118
- }
119
- return _sheet;
120
- }
121
121
  var BvCategoryPill = class extends HTMLElement {
122
122
  static observedAttributes = ["label"];
123
123
  _rendered = false;
124
124
  _labelEl = null;
125
125
  constructor() {
126
126
  super();
127
- const shadow = this.attachShadow({ mode: "open" });
128
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
129
- shadow.adoptedStyleSheets = [getSheet()];
130
- }
127
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_category_pill_default);
131
128
  }
132
129
  get label() {
133
130
  return this.getAttribute("label") ?? "";
@@ -146,11 +143,6 @@ var BvCategoryPill = class extends HTMLElement {
146
143
  if (this._rendered) return;
147
144
  this._rendered = true;
148
145
  const shadow = this.shadowRoot;
149
- if (!shadow.adoptedStyleSheets.length) {
150
- const s = document.createElement("style");
151
- s.textContent = bv_category_pill_default;
152
- shadow.appendChild(s);
153
- }
154
146
  const pill = document.createElement("span");
155
147
  pill.className = "pill";
156
148
  pill.setAttribute("part", "pill");
@@ -166,14 +158,6 @@ if (!customElements.get("bv-category-pill")) {
166
158
  }
167
159
 
168
160
  // components/bv-course-detail/bv-course-detail.ts
169
- var _sheet2 = null;
170
- function getSheet2() {
171
- if (!_sheet2) {
172
- _sheet2 = new CSSStyleSheet();
173
- _sheet2.replaceSync(bv_course_detail_default);
174
- }
175
- return _sheet2;
176
- }
177
161
  var BvCourseDetail = class extends HTMLElement {
178
162
  static observedAttributes = ["thumbnail", "title", "description", "categories"];
179
163
  _rendered = false;
@@ -187,10 +171,7 @@ var BvCourseDetail = class extends HTMLElement {
187
171
  _readMoreBtn = null;
188
172
  constructor() {
189
173
  super();
190
- const shadow = this.attachShadow({ mode: "open" });
191
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
192
- shadow.adoptedStyleSheets = [getSheet2()];
193
- }
174
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_course_detail_default);
194
175
  }
195
176
  get thumbnail() {
196
177
  return this.getAttribute("thumbnail") ?? "";
@@ -255,11 +236,6 @@ var BvCourseDetail = class extends HTMLElement {
255
236
  }
256
237
  }
257
238
  const shadow = this.shadowRoot;
258
- if (!shadow.adoptedStyleSheets.length) {
259
- const s = document.createElement("style");
260
- s.textContent = bv_course_detail_default;
261
- shadow.appendChild(s);
262
- }
263
239
  const root = document.createElement("div");
264
240
  root.className = "course-detail";
265
241
  const thumbCol = document.createElement("div");
@@ -398,12 +374,7 @@ var BvCourseOutline = class extends HTMLElement {
398
374
  }
399
375
  constructor() {
400
376
  super();
401
- this.attachShadow({ mode: "open" });
402
- if ("replaceSync" in CSSStyleSheet.prototype) {
403
- const sheet = new CSSStyleSheet();
404
- sheet.replaceSync(bv_course_outline_default);
405
- this.shadowRoot.adoptedStyleSheets = [sheet];
406
- }
377
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_course_outline_default);
407
378
  }
408
379
  connectedCallback() {
409
380
  this._render();
@@ -426,11 +397,6 @@ var BvCourseOutline = class extends HTMLElement {
426
397
  if (this._rendered) return;
427
398
  this._rendered = true;
428
399
  const shadow = this.shadowRoot;
429
- if (!shadow.adoptedStyleSheets.length) {
430
- const s = document.createElement("style");
431
- s.textContent = bv_course_outline_default;
432
- shadow.appendChild(s);
433
- }
434
400
  const attr = this.getAttribute("sections");
435
401
  if (attr !== null) {
436
402
  try {
@@ -933,14 +899,6 @@ var rawIcons = {
933
899
  };
934
900
 
935
901
  // components/bv-button/bv-button.ts
936
- var _sheet3 = null;
937
- function getSheet3() {
938
- if (!_sheet3) {
939
- _sheet3 = new CSSStyleSheet();
940
- _sheet3.replaceSync(bv_button_default);
941
- }
942
- return _sheet3;
943
- }
944
902
  var BvButton = class extends HTMLElement {
945
903
  static observedAttributes = [
946
904
  "variant",
@@ -956,10 +914,7 @@ var BvButton = class extends HTMLElement {
956
914
  ];
957
915
  constructor() {
958
916
  super();
959
- const shadow = this.attachShadow({ mode: "open" });
960
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
961
- shadow.adoptedStyleSheets = [getSheet3()];
962
- }
917
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_button_default);
963
918
  }
964
919
  // ── Accessors ────────────────────────────────────────────────
965
920
  get variant() {
@@ -1016,11 +971,6 @@ var BvButton = class extends HTMLElement {
1016
971
  const shadow = this.shadowRoot;
1017
972
  let btn = shadow.querySelector("button");
1018
973
  if (!btn) {
1019
- if (!shadow.adoptedStyleSheets.length) {
1020
- const styleEl = document.createElement("style");
1021
- styleEl.textContent = bv_button_default;
1022
- shadow.appendChild(styleEl);
1023
- }
1024
974
  btn = document.createElement("button");
1025
975
  btn.setAttribute("part", "button");
1026
976
  btn.appendChild(document.createElement("slot"));
@@ -1075,14 +1025,6 @@ if (!customElements.get("bv-button")) {
1075
1025
  }
1076
1026
 
1077
1027
  // components/bv-course-detail-modal/bv-course-detail-modal.ts
1078
- var _sheet4 = null;
1079
- function getSheet4() {
1080
- if (!_sheet4) {
1081
- _sheet4 = new CSSStyleSheet();
1082
- _sheet4.replaceSync(bv_course_detail_modal_default);
1083
- }
1084
- return _sheet4;
1085
- }
1086
1028
  var BvCourseDetailModal = class extends HTMLElement {
1087
1029
  static observedAttributes = [
1088
1030
  "open",
@@ -1103,10 +1045,7 @@ var BvCourseDetailModal = class extends HTMLElement {
1103
1045
  _outlineEl = null;
1104
1046
  constructor() {
1105
1047
  super();
1106
- const shadow = this.attachShadow({ mode: "open" });
1107
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
1108
- shadow.adoptedStyleSheets = [getSheet4()];
1109
- }
1048
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_course_detail_modal_default);
1110
1049
  }
1111
1050
  get open() {
1112
1051
  return this.hasAttribute("open");
@@ -1229,11 +1168,6 @@ var BvCourseDetailModal = class extends HTMLElement {
1229
1168
  }
1230
1169
  }
1231
1170
  const shadow = this.shadowRoot;
1232
- if (!shadow.adoptedStyleSheets.length) {
1233
- const s = document.createElement("style");
1234
- s.textContent = bv_course_detail_modal_default;
1235
- shadow.appendChild(s);
1236
- }
1237
1171
  const modal = document.createElement("bv-modal");
1238
1172
  modal.setAttribute("title", this.title);
1239
1173
  if (this.open) modal.setAttribute("open", "");
@@ -1,5 +1,6 @@
1
1
  import '../bv-button/bv-button.ts';
2
- export declare class BvCourseGridCard extends HTMLElement {
2
+ import { type SelectableHost } from '../_shared/selectable-card.ts';
3
+ export declare class BvCourseGridCard extends HTMLElement implements SelectableHost {
3
4
  static readonly observedAttributes: string[];
4
5
  get selected(): boolean;
5
6
  set selected(v: boolean);
@@ -19,5 +20,4 @@ export declare class BvCourseGridCard extends HTMLElement {
19
20
  connectedCallback(): void;
20
21
  attributeChangedCallback(name: string, _old: string | null, val: string | null): void;
21
22
  private _render;
22
- private _syncLoading;
23
23
  }
@@ -1,5 +1,23 @@
1
1
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-course-grid-card/bv-course-grid-card.css
2
- var bv_course_grid_card_default = ":host {\n display: block;\n}\n\n.card {\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-sm);\n padding: 12px;\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-xl);\n box-shadow: var(--bv-shadow-sm);\n cursor: pointer;\n transition:\n background var(--bv-transition-fast),\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n:host([selected]) .card {\n background: var(--bv-color-primary-50);\n border-color: var(--bv-color-primary);\n box-shadow: none;\n}\n\n/* Thumbnail */\n.thumbnail-wrap {\n position: relative;\n width: 100%;\n height: 153px;\n border-radius: var(--bv-radius-lg);\n overflow: hidden;\n background: var(--bv-color-neutral-200);\n flex-shrink: 0;\n}\n\n.thumbnail {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.status-badge {\n position: absolute;\n top: 6px;\n right: 6px;\n padding: 0 var(--bv-spacing-sm);\n border-radius: var(--bv-radius-sm);\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-medium);\n line-height: 18px;\n background: var(--bv-color-success-light);\n color: var(--bv-color-success-dark);\n}\n\n/* Content */\n.content {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n min-width: 0;\n}\n\n.title {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-md);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-900);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.publisher {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-500);\n}\n\n.publisher strong {\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-900);\n}\n\n.description {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n line-height: 20px;\n min-height: 40px; /* Reserve space for 2 lines so cards align at the bottom in the grid, matching the list card's .description-wrap. */\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n margin: 0;\n}\n\n.info-row {\n display: flex;\n gap: var(--bv-spacing-md);\n margin-top: 2px;\n}\n\n.info-item {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-900);\n}\n\n.info-item strong {\n font-weight: var(--bv-font-weight-bold);\n}\n\n/* Actions */\n.actions {\n display: flex;\n gap: var(--bv-spacing-sm);\n align-items: stretch;\n}\n\n.primary-action {\n display: block;\n flex: 1;\n min-width: 0;\n}\n\n.secondary-action {\n display: block;\n width: 48px;\n flex-shrink: 0;\n}\n\n/* Disabled selection: card body is no longer interactive */\n:host([disable-selection]) .card {\n cursor: default;\n}\n\n/* Loading skeleton */\n.skeleton {\n display: none;\n flex-direction: column;\n gap: var(--bv-spacing-sm);\n flex: 1;\n min-width: 0;\n}\n\n:host([loading]) .card {\n cursor: default;\n}\n\n:host([loading]) .thumbnail-wrap,\n:host([loading]) .content,\n:host([loading]) .actions {\n display: none;\n}\n\n:host([loading]) .skeleton {\n display: flex;\n}\n\n.skeleton-thumb {\n width: 100%;\n height: 153px;\n border-radius: var(--bv-radius-lg);\n background: var(--bv-color-neutral-200);\n animation: bv-skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-line {\n height: 12px;\n border-radius: var(--bv-radius-sm);\n background: var(--bv-color-neutral-200);\n animation: bv-skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-title-line {\n height: 20px;\n width: 70%;\n}\n\n.skeleton-short-line {\n width: 45%;\n}\n\n.skeleton-actions {\n display: flex;\n gap: var(--bv-spacing-sm);\n align-items: stretch;\n margin-top: 2px;\n}\n\n.skeleton-button {\n height: 44px;\n border-radius: var(--bv-radius-md);\n background: var(--bv-color-neutral-200);\n animation: bv-skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-primary {\n flex: 1;\n min-width: 0;\n}\n\n.skeleton-icon-btn {\n width: 48px;\n flex-shrink: 0;\n}\n\n@keyframes bv-skeleton-shimmer {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n}\n";
2
+ var bv_course_grid_card_default = ":host {\n display: block;\n}\n\n.card {\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-sm);\n padding: 12px;\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-xl);\n box-shadow: var(--bv-shadow-sm);\n cursor: pointer;\n transition:\n background var(--bv-transition-fast),\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n:host([selected]) .card {\n background: var(--bv-color-primary-50);\n border-color: var(--bv-color-primary);\n box-shadow: none;\n}\n\n.card:focus-visible {\n outline: var(--bv-focus-ring);\n outline-offset: 2px;\n}\n\n/* Thumbnail */\n.thumbnail-wrap {\n position: relative;\n width: 100%;\n height: 153px;\n border-radius: var(--bv-radius-lg);\n overflow: hidden;\n background: var(--bv-color-neutral-200);\n flex-shrink: 0;\n}\n\n.thumbnail {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.status-badge {\n position: absolute;\n top: 6px;\n right: 6px;\n padding: 0 var(--bv-spacing-sm);\n border-radius: var(--bv-radius-sm);\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-medium);\n line-height: 18px;\n background: var(--bv-color-success-light);\n color: var(--bv-color-success-dark);\n}\n\n/* Content */\n.content {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n min-width: 0;\n}\n\n.title {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-md);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-900);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.publisher {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-500);\n}\n\n.publisher strong {\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-900);\n}\n\n.description {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n line-height: 20px;\n min-height: 40px; /* Reserve space for 2 lines so cards align at the bottom in the grid, matching the list card's .description-wrap. */\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n margin: 0;\n}\n\n.info-row {\n display: flex;\n gap: var(--bv-spacing-md);\n margin-top: 2px;\n}\n\n.info-item {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-900);\n}\n\n.info-item strong {\n font-weight: var(--bv-font-weight-bold);\n}\n\n/* Actions */\n.actions {\n display: flex;\n gap: var(--bv-spacing-sm);\n align-items: stretch;\n}\n\n.primary-action {\n display: block;\n flex: 1;\n min-width: 0;\n}\n\n.secondary-action {\n display: block;\n width: 48px;\n flex-shrink: 0;\n}\n\n/* Disabled selection: card body is no longer interactive */\n:host([disable-selection]) .card {\n cursor: default;\n}\n\n/* Loading skeleton */\n.skeleton {\n display: none;\n flex-direction: column;\n gap: var(--bv-spacing-sm);\n flex: 1;\n min-width: 0;\n}\n\n:host([loading]) .card {\n cursor: default;\n}\n\n:host([loading]) .thumbnail-wrap,\n:host([loading]) .content,\n:host([loading]) .actions {\n display: none;\n}\n\n:host([loading]) .skeleton {\n display: flex;\n}\n\n.skeleton-thumb {\n width: 100%;\n height: 153px;\n border-radius: var(--bv-radius-lg);\n background: var(--bv-color-neutral-200);\n animation: bv-skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-line {\n height: 12px;\n border-radius: var(--bv-radius-sm);\n background: var(--bv-color-neutral-200);\n animation: bv-skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-title-line {\n height: 20px;\n width: 70%;\n}\n\n.skeleton-short-line {\n width: 45%;\n}\n\n.skeleton-actions {\n display: flex;\n gap: var(--bv-spacing-sm);\n align-items: stretch;\n margin-top: 2px;\n}\n\n.skeleton-button {\n height: 44px;\n border-radius: var(--bv-radius-md);\n background: var(--bv-color-neutral-200);\n animation: bv-skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-primary {\n flex: 1;\n min-width: 0;\n}\n\n.skeleton-icon-btn {\n width: 48px;\n flex-shrink: 0;\n}\n\n@keyframes bv-skeleton-shimmer {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n}\n";
3
+
4
+ // components/_shared/adopt-styles.ts
5
+ var _sheetCache = /* @__PURE__ */ new Map();
6
+ function adoptStyles(shadow, css) {
7
+ if ("adoptedStyleSheets" in ShadowRoot.prototype && typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype) {
8
+ let sheet = _sheetCache.get(css);
9
+ if (!sheet) {
10
+ sheet = new CSSStyleSheet();
11
+ sheet.replaceSync(css);
12
+ _sheetCache.set(css, sheet);
13
+ }
14
+ shadow.adoptedStyleSheets = [sheet];
15
+ return;
16
+ }
17
+ const style = document.createElement("style");
18
+ style.textContent = css;
19
+ shadow.appendChild(style);
20
+ }
3
21
 
4
22
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-button/bv-button.css
5
23
  var bv_button_default = "/* \u2500\u2500 Public theming API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Apps can override these three properties per-instance:\n\n HTML: <bv-button style=\"--bv-button-bg: #e53e3e;\">\n Angular: <bv-button [style.--bv-button-bg]=\"brandColor\">\n JavaScript: btn.style.setProperty('--bv-button-bg', value)\n\n Hover is auto-computed from the base colors via color-mix().\n Override --bv-button-bg-hover / --bv-button-border-color-hover\n if you need explicit control over the hovered state.\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host {\n /* Default values \u2014 map to global tokens (primary variant) */\n --bv-button-bg: var(--bv-color-primary);\n --bv-button-bg-hover: color-mix(in srgb, var(--bv-button-bg) 82%, black);\n --bv-button-color: var(--bv-color-neutral-0);\n --bv-button-border-color: var(--bv-color-primary);\n --bv-button-border-color-hover: color-mix(in srgb, var(--bv-button-border-color) 82%, black);\n --bv-button-font-family: var(--bv-font-family-base);\n --bv-button-font-size: var(--bv-font-size-sm);\n\n display: inline-block;\n font-family: var(--bv-font-family-base);\n}\n\n:host([hidden]) {\n /* `!important` so `hidden` always wins over :host([full-width]) / other display rules. */\n display: none !important;\n}\n\n:host([full-width]) {\n display: block;\n width: 100%;\n}\n\n/* \u2500\u2500 Variant tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Each variant re-points the component tokens to different global\n tokens. Apps can still override with inline styles \u2014 inline style\n always wins due to higher specificity.\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n:host([variant='secondary']) {\n --bv-button-bg: var(--bv-color-secondary);\n --bv-button-border-color: var(--bv-color-secondary);\n --bv-button-color: var(--bv-color-secondary-foreground);\n}\n\n:host([variant='tertiary']) {\n --bv-button-bg: var(--bv-color-tertiary);\n --bv-button-border-color: var(--bv-color-tertiary);\n}\n\n:host([variant='danger']) {\n --bv-button-bg: var(--bv-color-danger);\n --bv-button-border-color: var(--bv-color-danger);\n}\n\n:host([variant='ghost']) {\n --bv-button-bg: transparent;\n --bv-button-bg-hover: var(--bv-color-primary-light);\n --bv-button-color: var(--bv-color-primary);\n --bv-button-border-color: transparent;\n --bv-button-border-color-hover: transparent;\n}\n\n:host([variant='outline']) {\n --bv-button-bg: transparent;\n --bv-button-bg-hover: var(--bv-color-primary-light);\n --bv-button-color: var(--bv-color-primary);\n --bv-button-border-color: var(--bv-color-primary);\n --bv-button-border-color-hover: var(--bv-color-primary);\n}\n\n:host([variant='outline-secondary']) {\n --bv-button-bg: transparent;\n --bv-button-bg-hover: var(--bv-color-neutral-100);\n --bv-button-color: var(--bv-color-neutral-700);\n --bv-button-border-color: var(--bv-color-neutral-300);\n --bv-button-border-color-hover: var(--bv-color-neutral-300);\n}\n\n/* \u2500\u2500 Base button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n All visual properties read from the component-level tokens above.\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--bv-spacing-xs);\n padding: 10px var(--bv-spacing-md);\n min-height: 40px;\n width: auto;\n font-family: var(--bv-button-font-family);\n font-size: var(--bv-button-font-size);\n font-style: normal;\n font-weight: var(--bv-font-weight-semibold);\n line-height: 20px;\n border: 1px solid var(--bv-button-border-color);\n border-radius: var(--bv-radius-lg);\n cursor: pointer;\n transition:\n background var(--bv-transition-fast),\n color var(--bv-transition-fast),\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast),\n opacity var(--bv-transition-fast);\n white-space: nowrap;\n user-select: none;\n text-decoration: none;\n outline: none;\n box-sizing: border-box;\n background: var(--bv-button-bg);\n color: var(--bv-button-color);\n box-shadow: var(--bv-shadow-sm);\n}\n\n:host([full-width]) .btn {\n width: 100%;\n}\n\n.btn:hover:not(:disabled) {\n background: var(--bv-button-bg-hover);\n border-color: var(--bv-button-border-color-hover);\n}\n\n.btn:active:not(:disabled) {\n transform: translateY(1px);\n}\n\n.btn:focus-visible {\n outline: 2px solid var(--bv-button-border-color);\n outline-offset: 2px;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* \u2500\u2500 Sizes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n:host([size='sm']) .btn {\n padding: 6px 12px;\n min-height: 32px;\n font-size: var(--bv-font-size-sm);\n}\n\n:host([size='lg']) .btn {\n padding: 12px var(--bv-spacing-lg);\n min-height: 48px;\n font-size: var(--bv-font-size-md);\n}\n\n/* \u2500\u2500 Icon \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.btn-icon {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n line-height: 0;\n}\n\n/* \u2500\u2500 Count badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.btn-count {\n opacity: 0.8;\n font-weight: var(--bv-font-weight-regular);\n}\n\n/* \u2500\u2500 Loading spinner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.spinner {\n display: inline-block;\n width: 1em;\n height: 1em;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: 50%;\n flex-shrink: 0;\n animation: bv-spin 700ms linear infinite;\n}\n\n@keyframes bv-spin {\n to {\n transform: rotate(360deg);\n }\n}\n";
@@ -78,14 +96,6 @@ var rawIcons = {
78
96
  };
79
97
 
80
98
  // components/bv-button/bv-button.ts
81
- var _sheet = null;
82
- function getSheet() {
83
- if (!_sheet) {
84
- _sheet = new CSSStyleSheet();
85
- _sheet.replaceSync(bv_button_default);
86
- }
87
- return _sheet;
88
- }
89
99
  var BvButton = class extends HTMLElement {
90
100
  static observedAttributes = [
91
101
  "variant",
@@ -101,10 +111,7 @@ var BvButton = class extends HTMLElement {
101
111
  ];
102
112
  constructor() {
103
113
  super();
104
- const shadow = this.attachShadow({ mode: "open" });
105
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
106
- shadow.adoptedStyleSheets = [getSheet()];
107
- }
114
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_button_default);
108
115
  }
109
116
  // ── Accessors ────────────────────────────────────────────────
110
117
  get variant() {
@@ -161,11 +168,6 @@ var BvButton = class extends HTMLElement {
161
168
  const shadow = this.shadowRoot;
162
169
  let btn = shadow.querySelector("button");
163
170
  if (!btn) {
164
- if (!shadow.adoptedStyleSheets.length) {
165
- const styleEl = document.createElement("style");
166
- styleEl.textContent = bv_button_default;
167
- shadow.appendChild(styleEl);
168
- }
169
171
  btn = document.createElement("button");
170
172
  btn.setAttribute("part", "button");
171
173
  btn.appendChild(document.createElement("slot"));
@@ -219,6 +221,65 @@ if (!customElements.get("bv-button")) {
219
221
  customElements.define("bv-button", BvButton);
220
222
  }
221
223
 
224
+ // components/_shared/selectable-card.ts
225
+ function buildAriaLabel(host) {
226
+ const title = host.getAttribute("title") ?? "";
227
+ const publisher = host.getAttribute("publisher") ?? "";
228
+ const parts = [];
229
+ if (title) parts.push(title);
230
+ if (publisher) parts.push(`by ${publisher}`);
231
+ return parts.join(", ");
232
+ }
233
+ function setPrefixedStrong(el, prefix, val) {
234
+ if (!el) return;
235
+ const value = val ?? "";
236
+ if (!value) {
237
+ el.hidden = true;
238
+ el.replaceChildren();
239
+ return;
240
+ }
241
+ el.hidden = false;
242
+ const strong = document.createElement("strong");
243
+ strong.textContent = value;
244
+ if (prefix) el.replaceChildren(document.createTextNode(prefix), strong);
245
+ else el.replaceChildren(strong);
246
+ }
247
+ function syncSelectionA11y(host, card) {
248
+ const inert = host.disableSelection || host.loading;
249
+ card.setAttribute("tabindex", inert ? "-1" : "0");
250
+ if (inert) card.setAttribute("aria-disabled", "true");
251
+ else card.removeAttribute("aria-disabled");
252
+ }
253
+ function syncCardLoading(host, card) {
254
+ card.setAttribute("aria-busy", host.loading ? "true" : "false");
255
+ }
256
+ function bindCardSelection(host, card, actions) {
257
+ const toggle = () => {
258
+ host.selected = !host.selected;
259
+ host.dispatchEvent(
260
+ new CustomEvent("bv-select", {
261
+ bubbles: true,
262
+ composed: true,
263
+ detail: { selected: host.selected }
264
+ })
265
+ );
266
+ };
267
+ const isInert = () => host.disableSelection || host.loading;
268
+ const originatesFromActions = (e) => e.composedPath().includes(actions);
269
+ card.addEventListener("click", (e) => {
270
+ if (isInert()) return;
271
+ if (originatesFromActions(e)) return;
272
+ toggle();
273
+ });
274
+ card.addEventListener("keydown", (e) => {
275
+ if (e.key !== "Enter" && e.key !== " ") return;
276
+ if (isInert()) return;
277
+ if (originatesFromActions(e)) return;
278
+ e.preventDefault();
279
+ toggle();
280
+ });
281
+ }
282
+
222
283
  // components/bv-course-grid-card/bv-course-grid-card.ts
223
284
  var BvCourseGridCard = class extends HTMLElement {
224
285
  static observedAttributes = [
@@ -286,12 +347,7 @@ var BvCourseGridCard = class extends HTMLElement {
286
347
  _els = {};
287
348
  constructor() {
288
349
  super();
289
- this.attachShadow({ mode: "open" });
290
- if ("replaceSync" in CSSStyleSheet.prototype) {
291
- const sheet = new CSSStyleSheet();
292
- sheet.replaceSync(bv_course_grid_card_default);
293
- this.shadowRoot.adoptedStyleSheets = [sheet];
294
- }
350
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_course_grid_card_default);
295
351
  }
296
352
  connectedCallback() {
297
353
  this._render();
@@ -299,6 +355,7 @@ var BvCourseGridCard = class extends HTMLElement {
299
355
  attributeChangedCallback(name, _old, val) {
300
356
  var _a;
301
357
  if (!this._rendered) return;
358
+ const card = this._els.card;
302
359
  const map = {
303
360
  thumbnail: () => {
304
361
  var _a2;
@@ -312,19 +369,20 @@ var BvCourseGridCard = class extends HTMLElement {
312
369
  },
313
370
  title: () => {
314
371
  if (this._els.title) this._els.title.textContent = val ?? "";
372
+ card == null ? void 0 : card.setAttribute("aria-label", buildAriaLabel(this));
315
373
  },
316
374
  publisher: () => {
317
- if (this._els.publisher) this._els.publisher.innerHTML = `By <strong>${val ?? ""}</strong>`;
375
+ setPrefixedStrong(this._els.publisher, "By ", val);
376
+ card == null ? void 0 : card.setAttribute("aria-label", buildAriaLabel(this));
318
377
  },
319
378
  description: () => {
320
379
  if (this._els.description) this._els.description.textContent = val ?? "";
321
380
  },
322
381
  duration: () => {
323
- if (this._els.duration) this._els.duration.innerHTML = `<strong>${val ?? ""}</strong>`;
382
+ setPrefixedStrong(this._els.duration, "", val);
324
383
  },
325
384
  "last-updated": () => {
326
- if (this._els.lastUpdated)
327
- this._els.lastUpdated.innerHTML = `Last Updated: <strong>${val ?? ""}</strong>`;
385
+ setPrefixedStrong(this._els.lastUpdated, "Last Updated: ", val);
328
386
  },
329
387
  "primary-label": () => {
330
388
  if (this._els.primaryBtn) this._els.primaryBtn.textContent = val ?? "";
@@ -354,8 +412,17 @@ var BvCourseGridCard = class extends HTMLElement {
354
412
  "hide-secondary": () => {
355
413
  if (this._els.secondaryBtn) this._els.secondaryBtn.hidden = this.hideSecondary;
356
414
  },
415
+ selected: () => {
416
+ card == null ? void 0 : card.setAttribute("aria-checked", this.selected ? "true" : "false");
417
+ },
418
+ "disable-selection": () => {
419
+ if (card) syncSelectionA11y(this, card);
420
+ },
357
421
  loading: () => {
358
- this._syncLoading();
422
+ if (card) {
423
+ syncCardLoading(this, card);
424
+ syncSelectionA11y(this, card);
425
+ }
359
426
  }
360
427
  };
361
428
  (_a = map[name]) == null ? void 0 : _a.call(map);
@@ -364,13 +431,11 @@ var BvCourseGridCard = class extends HTMLElement {
364
431
  if (this._rendered) return;
365
432
  this._rendered = true;
366
433
  const shadow = this.shadowRoot;
367
- if (!shadow.adoptedStyleSheets.length) {
368
- const s = document.createElement("style");
369
- s.textContent = bv_course_grid_card_default;
370
- shadow.appendChild(s);
371
- }
372
434
  const card = document.createElement("div");
373
435
  card.className = "card";
436
+ card.setAttribute("role", "checkbox");
437
+ card.setAttribute("aria-checked", this.selected ? "true" : "false");
438
+ card.setAttribute("aria-label", buildAriaLabel(this));
374
439
  this._els.card = card;
375
440
  const thumbWrap = document.createElement("div");
376
441
  thumbWrap.className = "thumbnail-wrap";
@@ -394,8 +459,8 @@ var BvCourseGridCard = class extends HTMLElement {
394
459
  this._els.title = title;
395
460
  const publisher = document.createElement("span");
396
461
  publisher.className = "publisher";
397
- publisher.innerHTML = `By <strong>${this.getAttribute("publisher") ?? ""}</strong>`;
398
462
  this._els.publisher = publisher;
463
+ setPrefixedStrong(publisher, "By ", this.getAttribute("publisher"));
399
464
  const description = document.createElement("p");
400
465
  description.className = "description";
401
466
  description.textContent = this.getAttribute("description") ?? "";
@@ -404,12 +469,12 @@ var BvCourseGridCard = class extends HTMLElement {
404
469
  infoRow.className = "info-row";
405
470
  const duration = document.createElement("span");
406
471
  duration.className = "info-item";
407
- duration.innerHTML = `<strong>${this.getAttribute("duration") ?? ""}</strong>`;
408
472
  this._els.duration = duration;
473
+ setPrefixedStrong(duration, "", this.getAttribute("duration"));
409
474
  const lastUpdated = document.createElement("span");
410
475
  lastUpdated.className = "info-item";
411
- lastUpdated.innerHTML = `Last Updated: <strong>${this.getAttribute("last-updated") ?? ""}</strong>`;
412
476
  this._els.lastUpdated = lastUpdated;
477
+ setPrefixedStrong(lastUpdated, "Last Updated: ", this.getAttribute("last-updated"));
413
478
  infoRow.append(duration, lastUpdated);
414
479
  content.append(title, publisher, description, infoRow);
415
480
  const actions = document.createElement("div");
@@ -448,24 +513,9 @@ var BvCourseGridCard = class extends HTMLElement {
448
513
  this._els.skeleton = skeleton;
449
514
  card.append(thumbWrap, content, actions, skeleton);
450
515
  shadow.appendChild(card);
451
- this._syncLoading();
452
- card.addEventListener("click", (e) => {
453
- if (this.disableSelection || this.loading) return;
454
- if (e.composedPath().includes(this._els.actions)) return;
455
- this.selected = !this.selected;
456
- this.dispatchEvent(
457
- new CustomEvent("bv-select", {
458
- bubbles: true,
459
- composed: true,
460
- detail: { selected: this.selected }
461
- })
462
- );
463
- });
464
- }
465
- _syncLoading() {
466
- var _a;
467
- const loading = this.loading;
468
- (_a = this._els.card) == null ? void 0 : _a.setAttribute("aria-busy", loading ? "true" : "false");
516
+ syncCardLoading(this, card);
517
+ syncSelectionA11y(this, card);
518
+ bindCardSelection(this, card, actions);
469
519
  }
470
520
  };
471
521
  if (!customElements.get("bv-course-grid-card")) {
@@ -1,5 +1,6 @@
1
1
  import '../bv-button/bv-button.ts';
2
- export declare class BvCourseListCard extends HTMLElement {
2
+ import { type SelectableHost } from '../_shared/selectable-card.ts';
3
+ export declare class BvCourseListCard extends HTMLElement implements SelectableHost {
3
4
  static readonly observedAttributes: string[];
4
5
  get selected(): boolean;
5
6
  set selected(v: boolean);
@@ -19,5 +20,4 @@ export declare class BvCourseListCard extends HTMLElement {
19
20
  connectedCallback(): void;
20
21
  attributeChangedCallback(name: string, _old: string | null, val: string | null): void;
21
22
  private _render;
22
- private _syncLoading;
23
23
  }