@bluevolt-tech/lumen 2.0.1 → 2.2.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 +86 -0
  4. package/dist/components/_shared/dropdown-controller.d.ts +60 -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 -6
  16. package/dist/components/bv-course-grid-card/bv-course-grid-card.js +98 -105
  17. package/dist/components/bv-course-list-card/bv-course-list-card.d.ts +2 -6
  18. package/dist/components/bv-course-list-card/bv-course-list-card.js +98 -105
  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 +10 -6
  21. package/dist/components/bv-date-range-input/bv-date-range-input.js +602 -125
  22. package/dist/components/bv-date-range-picker/bv-date-range-picker.d.ts +71 -4
  23. package/dist/components/bv-date-range-picker/bv-date-range-picker.js +441 -76
  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 +80 -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 +79 -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 +78 -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 +78 -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 +70 -19
  50. package/dist/components/bv-toggle/bv-toggle.js +19 -11
  51. package/dist/components/bv-tooltip/bv-tooltip.js +20 -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 +1 -1
  55. package/package.json +8 -1
  56. package/themes/base.css +1 -1
@@ -1,6 +1,24 @@
1
1
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-course-grid-card/bv-course-grid-card.css
2
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
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-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";
6
24
 
@@ -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;
@@ -311,23 +368,21 @@ var BvCourseGridCard = class extends HTMLElement {
311
368
  }
312
369
  },
313
370
  title: () => {
314
- var _a2;
315
371
  if (this._els.title) this._els.title.textContent = val ?? "";
316
- (_a2 = this._els.card) == null ? void 0 : _a2.setAttribute("aria-label", this._buildAriaLabel());
372
+ card == null ? void 0 : card.setAttribute("aria-label", buildAriaLabel(this));
317
373
  },
318
374
  publisher: () => {
319
- var _a2;
320
- this._setPrefixedStrong(this._els.publisher, "By ", val);
321
- (_a2 = this._els.card) == null ? void 0 : _a2.setAttribute("aria-label", this._buildAriaLabel());
375
+ setPrefixedStrong(this._els.publisher, "By ", val);
376
+ card == null ? void 0 : card.setAttribute("aria-label", buildAriaLabel(this));
322
377
  },
323
378
  description: () => {
324
379
  if (this._els.description) this._els.description.textContent = val ?? "";
325
380
  },
326
381
  duration: () => {
327
- this._setPrefixedStrong(this._els.duration, "", val);
382
+ setPrefixedStrong(this._els.duration, "", val);
328
383
  },
329
384
  "last-updated": () => {
330
- this._setPrefixedStrong(this._els.lastUpdated, "Last Updated: ", val);
385
+ setPrefixedStrong(this._els.lastUpdated, "Last Updated: ", val);
331
386
  },
332
387
  "primary-label": () => {
333
388
  if (this._els.primaryBtn) this._els.primaryBtn.textContent = val ?? "";
@@ -358,15 +413,16 @@ var BvCourseGridCard = class extends HTMLElement {
358
413
  if (this._els.secondaryBtn) this._els.secondaryBtn.hidden = this.hideSecondary;
359
414
  },
360
415
  selected: () => {
361
- var _a2;
362
- (_a2 = this._els.card) == null ? void 0 : _a2.setAttribute("aria-checked", this.selected ? "true" : "false");
416
+ card == null ? void 0 : card.setAttribute("aria-checked", this.selected ? "true" : "false");
363
417
  },
364
418
  "disable-selection": () => {
365
- this._syncSelectionA11y();
419
+ if (card) syncSelectionA11y(this, card);
366
420
  },
367
421
  loading: () => {
368
- this._syncLoading();
369
- this._syncSelectionA11y();
422
+ if (card) {
423
+ syncCardLoading(this, card);
424
+ syncSelectionA11y(this, card);
425
+ }
370
426
  }
371
427
  };
372
428
  (_a = map[name]) == null ? void 0 : _a.call(map);
@@ -375,17 +431,11 @@ var BvCourseGridCard = class extends HTMLElement {
375
431
  if (this._rendered) return;
376
432
  this._rendered = true;
377
433
  const shadow = this.shadowRoot;
378
- if (!shadow.adoptedStyleSheets.length) {
379
- const s = document.createElement("style");
380
- s.textContent = bv_course_grid_card_default;
381
- shadow.appendChild(s);
382
- }
383
434
  const card = document.createElement("div");
384
435
  card.className = "card";
385
436
  card.setAttribute("role", "checkbox");
386
- card.setAttribute("tabindex", "0");
387
437
  card.setAttribute("aria-checked", this.selected ? "true" : "false");
388
- card.setAttribute("aria-label", this._buildAriaLabel());
438
+ card.setAttribute("aria-label", buildAriaLabel(this));
389
439
  this._els.card = card;
390
440
  const thumbWrap = document.createElement("div");
391
441
  thumbWrap.className = "thumbnail-wrap";
@@ -410,7 +460,7 @@ var BvCourseGridCard = class extends HTMLElement {
410
460
  const publisher = document.createElement("span");
411
461
  publisher.className = "publisher";
412
462
  this._els.publisher = publisher;
413
- this._setPrefixedStrong(publisher, "By ", this.getAttribute("publisher"));
463
+ setPrefixedStrong(publisher, "By ", this.getAttribute("publisher"));
414
464
  const description = document.createElement("p");
415
465
  description.className = "description";
416
466
  description.textContent = this.getAttribute("description") ?? "";
@@ -420,11 +470,11 @@ var BvCourseGridCard = class extends HTMLElement {
420
470
  const duration = document.createElement("span");
421
471
  duration.className = "info-item";
422
472
  this._els.duration = duration;
423
- this._setPrefixedStrong(duration, "", this.getAttribute("duration"));
473
+ setPrefixedStrong(duration, "", this.getAttribute("duration"));
424
474
  const lastUpdated = document.createElement("span");
425
475
  lastUpdated.className = "info-item";
426
476
  this._els.lastUpdated = lastUpdated;
427
- this._setPrefixedStrong(lastUpdated, "Last Updated: ", this.getAttribute("last-updated"));
477
+ setPrefixedStrong(lastUpdated, "Last Updated: ", this.getAttribute("last-updated"));
428
478
  infoRow.append(duration, lastUpdated);
429
479
  content.append(title, publisher, description, infoRow);
430
480
  const actions = document.createElement("div");
@@ -463,66 +513,9 @@ var BvCourseGridCard = class extends HTMLElement {
463
513
  this._els.skeleton = skeleton;
464
514
  card.append(thumbWrap, content, actions, skeleton);
465
515
  shadow.appendChild(card);
466
- this._syncLoading();
467
- this._syncSelectionA11y();
468
- card.addEventListener("click", (e) => {
469
- if (this.disableSelection || this.loading) return;
470
- const actionsEl = this._els.actions;
471
- if (actionsEl && e.composedPath().includes(actionsEl)) return;
472
- this._toggleSelected();
473
- });
474
- card.addEventListener("keydown", (e) => {
475
- if (e.key !== "Enter" && e.key !== " ") return;
476
- if (this.disableSelection || this.loading) return;
477
- const actionsEl = this._els.actions;
478
- if (actionsEl && e.composedPath().includes(actionsEl)) return;
479
- e.preventDefault();
480
- this._toggleSelected();
481
- });
482
- }
483
- _toggleSelected() {
484
- this.selected = !this.selected;
485
- this.dispatchEvent(
486
- new CustomEvent("bv-select", {
487
- bubbles: true,
488
- composed: true,
489
- detail: { selected: this.selected }
490
- })
491
- );
492
- }
493
- _buildAriaLabel() {
494
- const title = this.getAttribute("title") ?? "";
495
- const publisher = this.getAttribute("publisher") ?? "";
496
- const parts = [];
497
- if (title) parts.push(title);
498
- if (publisher) parts.push(`by ${publisher}`);
499
- return parts.join(", ");
500
- }
501
- _setPrefixedStrong(el, prefix, val) {
502
- if (!el) return;
503
- const value = val ?? "";
504
- if (!value) {
505
- el.hidden = true;
506
- el.replaceChildren();
507
- return;
508
- }
509
- el.hidden = false;
510
- const strong = document.createElement("strong");
511
- strong.textContent = value;
512
- if (prefix) el.replaceChildren(document.createTextNode(prefix), strong);
513
- else el.replaceChildren(strong);
514
- }
515
- _syncSelectionA11y() {
516
- var _a, _b, _c;
517
- const inert = this.disableSelection || this.loading;
518
- (_a = this._els.card) == null ? void 0 : _a.setAttribute("tabindex", inert ? "-1" : "0");
519
- if (inert) (_b = this._els.card) == null ? void 0 : _b.setAttribute("aria-disabled", "true");
520
- else (_c = this._els.card) == null ? void 0 : _c.removeAttribute("aria-disabled");
521
- }
522
- _syncLoading() {
523
- var _a;
524
- const loading = this.loading;
525
- (_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);
526
519
  }
527
520
  };
528
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,9 +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 _toggleSelected;
23
- private _buildAriaLabel;
24
- private _setPrefixedStrong;
25
- private _syncSelectionA11y;
26
- private _syncLoading;
27
23
  }