@bluevolt-tech/lumen 2.3.1 → 2.6.1

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 (50) hide show
  1. package/README.md +1 -1
  2. package/dist/components/_shared/attribute-dispatch.d.ts +44 -0
  3. package/dist/components/_shared/tree-selection.d.ts +67 -0
  4. package/dist/components/bv-alert-banner/bv-alert-banner.js +1 -1
  5. package/dist/components/bv-avatar/bv-avatar.js +1 -1
  6. package/dist/components/bv-badge/bv-badge.js +1 -1
  7. package/dist/components/bv-button/bv-button.js +8 -6
  8. package/dist/components/bv-category-pill/bv-category-pill.js +1 -1
  9. package/dist/components/bv-checkbox/bv-checkbox.d.ts +15 -1
  10. package/dist/components/bv-checkbox/bv-checkbox.js +50 -5
  11. package/dist/components/bv-chip/bv-chip.js +1 -1
  12. package/dist/components/bv-course-detail/bv-course-detail.js +2 -2
  13. package/dist/components/bv-course-detail-modal/bv-course-detail-modal.js +26 -22
  14. package/dist/components/bv-course-grid-card/bv-course-grid-card.js +28 -21
  15. package/dist/components/bv-course-list-card/bv-course-list-card.js +28 -21
  16. package/dist/components/bv-course-outline/bv-course-outline.js +14 -12
  17. package/dist/components/bv-date-range-input/bv-date-range-input.js +3 -3
  18. package/dist/components/bv-date-range-picker/bv-date-range-picker.js +2 -2
  19. package/dist/components/bv-empty-state/bv-empty-state.js +21 -15
  20. package/dist/components/bv-filter-dropdown/bv-filter-dropdown.js +1 -1
  21. package/dist/components/bv-filter-panel/bv-filter-panel.d.ts +14 -0
  22. package/dist/components/bv-filter-panel/bv-filter-panel.js +87 -6
  23. package/dist/components/bv-icon/bv-icon.js +9 -7
  24. package/dist/components/bv-input/bv-input.js +1 -1
  25. package/dist/components/bv-input-dropdown/bv-input-dropdown.d.ts +123 -1
  26. package/dist/components/bv-input-dropdown/bv-input-dropdown.js +418 -14
  27. package/dist/components/bv-modal/bv-modal.js +1 -1
  28. package/dist/components/bv-page-title/bv-page-title.js +1 -1
  29. package/dist/components/bv-pagination/bv-pagination.js +1 -1
  30. package/dist/components/bv-portal-header/bv-portal-header.js +43 -37
  31. package/dist/components/bv-portal-layout/bv-portal-layout.js +1 -1
  32. package/dist/components/bv-progress-bar/bv-progress-bar.js +1 -1
  33. package/dist/components/bv-promo-banner/bv-promo-banner.js +1 -1
  34. package/dist/components/bv-radio/bv-radio.js +1 -1
  35. package/dist/components/bv-search-picker/bv-search-picker.d.ts +59 -0
  36. package/dist/components/bv-search-picker/bv-search-picker.js +175 -23
  37. package/dist/components/bv-segmented-control/bv-segmented-control.d.ts +13 -0
  38. package/dist/components/bv-segmented-control/bv-segmented-control.js +1 -1
  39. package/dist/components/bv-select/bv-select.js +1 -1
  40. package/dist/components/bv-split-button/bv-split-button.js +8 -6
  41. package/dist/components/bv-star-rating/bv-star-rating.js +1 -1
  42. package/dist/components/bv-table/bv-table.d.ts +50 -0
  43. package/dist/components/bv-table/bv-table.js +275 -34
  44. package/dist/components/bv-toggle/bv-toggle.d.ts +15 -0
  45. package/dist/components/bv-toggle/bv-toggle.js +40 -3
  46. package/dist/components/bv-user-row/bv-user-row.js +22 -15
  47. package/dist/components/bv-view-switcher/bv-view-switcher.js +1 -1
  48. package/dist/themes/base.css +0 -5
  49. package/package.json +11 -8
  50. package/themes/base.css +0 -5
@@ -1,5 +1,5 @@
1
1
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-filter-panel/bv-filter-panel.css
2
- var bv_filter_panel_default = ":host {\n display: block;\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-200);\n}\n\n/* Self-hide when there are no groups to show. */\n:host([empty]) {\n display: none;\n}\n\n.panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--bv-spacing-md) var(--bv-spacing-lg);\n border-bottom: 1px solid var(--bv-color-neutral-200);\n}\n\n.panel-title {\n margin: 0;\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-2xl);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-900);\n}\n\n.clear-btn {\n background: none;\n border: none;\n padding: 0;\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-primary);\n cursor: pointer;\n}\n\n.clear-btn:hover {\n text-decoration: underline;\n}\n\n.groups {\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-lg);\n padding: var(--bv-spacing-md) var(--bv-spacing-lg);\n}\n\n.group {\n}\n\n.group-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n padding-bottom: var(--bv-spacing-sm);\n}\n\n.group-label {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-lg);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-900);\n}\n\n.group-chevron {\n width: 20px;\n height: 20px;\n color: var(--bv-color-neutral-500);\n transition: transform var(--bv-transition-fast);\n}\n\n.group-chevron.collapsed {\n transform: rotate(180deg);\n}\n\n.group-options {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.group-options.hidden {\n display: none;\n}\n\n.option {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--bv-spacing-xs);\n}\n\n.option bv-checkbox {\n flex: 1;\n min-width: 0;\n}\n\n.option-count {\n display: inline-flex;\n align-items: center;\n padding: 2px var(--bv-spacing-sm);\n background: var(--bv-color-neutral-200);\n border-radius: var(--bv-radius-full);\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-600);\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n/* Loading skeleton */\n.skeleton {\n display: none;\n flex-direction: column;\n gap: var(--bv-spacing-lg);\n padding: var(--bv-spacing-md) var(--bv-spacing-lg);\n}\n\n:host([loading]) .groups {\n display: none;\n}\n\n:host([loading]) .skeleton {\n display: flex;\n}\n\n.skeleton-group {\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-sm);\n}\n\n.skeleton-line {\n height: 14px;\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-heading {\n height: 20px;\n width: 55%;\n margin-bottom: 4px;\n}\n\n.skeleton-short-line {\n width: 70%;\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_filter_panel_default = ":host {\n display: block;\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-200);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n/* Self-hide when there are no groups to show. */\n:host([empty]) {\n display: none;\n}\n\n.panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--bv-spacing-md) var(--bv-spacing-lg);\n border-bottom: 1px solid var(--bv-color-neutral-200);\n}\n\n.panel-title {\n margin: 0;\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-2xl);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-900);\n}\n\n.clear-btn {\n background: none;\n border: none;\n padding: 0;\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-primary);\n cursor: pointer;\n}\n\n.clear-btn:hover {\n text-decoration: underline;\n}\n\n.groups {\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-lg);\n padding: var(--bv-spacing-md) var(--bv-spacing-lg);\n}\n\n.group {\n}\n\n.group-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n padding-bottom: var(--bv-spacing-sm);\n}\n\n.group-label {\n font-family: var(--bv-font-family-base);\n font-size: var(--bv-font-size-lg);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-900);\n}\n\n.group-chevron {\n width: 20px;\n height: 20px;\n color: var(--bv-color-neutral-500);\n transition: transform var(--bv-transition-fast);\n}\n\n.group-chevron.collapsed {\n transform: rotate(180deg);\n}\n\n.group-options {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.group-options.hidden {\n display: none;\n}\n\n.option {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--bv-spacing-xs);\n}\n\n.option bv-checkbox {\n flex: 1;\n min-width: 0;\n}\n\n.option-count {\n display: inline-flex;\n align-items: center;\n padding: 2px var(--bv-spacing-sm);\n background: var(--bv-color-neutral-200);\n border-radius: var(--bv-radius-full);\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-600);\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n/* Loading skeleton */\n.skeleton {\n display: none;\n flex-direction: column;\n gap: var(--bv-spacing-lg);\n padding: var(--bv-spacing-md) var(--bv-spacing-lg);\n}\n\n:host([loading]) .groups {\n display: none;\n}\n\n:host([loading]) .skeleton {\n display: flex;\n}\n\n.skeleton-group {\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-sm);\n}\n\n.skeleton-line {\n height: 14px;\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-heading {\n height: 20px;\n width: 55%;\n margin-bottom: 4px;\n}\n\n.skeleton-short-line {\n width: 70%;\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
4
  // components/_shared/adopt-styles.ts
5
5
  var _sheetCache = /* @__PURE__ */ new Map();
@@ -20,7 +20,7 @@ function adoptStyles(shadow, css) {
20
20
  }
21
21
 
22
22
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-checkbox/bv-checkbox.css
23
- var bv_checkbox_default = ":host {\n display: inline-flex;\n font-family: var(--bv-font-family-base);\n}\n\n/* \u2500\u2500 Size 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\u2500 */\n\n:host,\n:host([size='sm']) {\n --_box-size: 16px;\n --_box-radius: 4px;\n --_gap: 8px;\n --_check-size: 10px;\n --_label-size: var(--bv-font-size-sm);\n --_label-lh: 20px;\n}\n\n:host([size='md']) {\n --_box-size: 20px;\n --_box-radius: 6px;\n --_gap: 12px;\n --_check-size: 12px;\n --_label-size: var(--bv-font-size-md);\n --_label-lh: 24px;\n}\n\n/* \u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.wrap {\n display: inline-flex;\n align-items: flex-start;\n gap: var(--_gap);\n cursor: pointer;\n user-select: none;\n}\n\n:host([disabled]) .wrap {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* \u2500\u2500 Box \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.box {\n width: var(--_box-size);\n height: var(--_box-size);\n border-radius: var(--_box-radius);\n border: 1px solid var(--bv-color-neutral-300);\n background: var(--bv-color-neutral-0);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n margin-top: 2px;\n transition:\n background var(--bv-transition-fast),\n border-color var(--bv-transition-fast);\n}\n\n:host([checked]) .box,\n:host([indeterminate]) .box {\n background: var(--bv-color-primary-50);\n border-color: var(--bv-color-primary);\n}\n\n/* \u2500\u2500 Check 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 */\n\n.check {\n display: none;\n width: var(--_check-size);\n height: var(--_check-size);\n color: var(--bv-color-primary);\n line-height: 0;\n}\n\n.check svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n:host([checked]) .check,\n:host([indeterminate]) .check {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* \u2500\u2500 Label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.label {\n font-size: var(--_label-size);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-secondary-foreground);\n line-height: var(--_label-lh);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n";
23
+ var bv_checkbox_default = ":host {\n display: inline-flex;\n font-family: var(--bv-font-family-base);\n}\n/* `:host { display: inline-flex }` is an author rule, and author rules beat the\n user-agent's `[hidden] { display: none }` regardless of specificity. Without\n this, `el.hidden = true` on a bv-checkbox sets the property and changes\n nothing on screen \u2014 which is how bv-input-dropdown shipped a \"Select All\"\n that stayed visible with `select-all` unset (found in BTN-11521). */\n:host([hidden]) {\n display: none;\n}\n\n/* \u2500\u2500 Size 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\u2500 */\n\n:host,\n:host([size='sm']) {\n --_box-size: 16px;\n --_box-radius: 4px;\n --_gap: 8px;\n --_check-size: 10px;\n --_label-size: var(--bv-font-size-sm);\n --_label-lh: 20px;\n}\n\n:host([size='md']) {\n --_box-size: 20px;\n --_box-radius: 6px;\n --_gap: 12px;\n --_check-size: 12px;\n --_label-size: var(--bv-font-size-md);\n --_label-lh: 24px;\n}\n\n/* \u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-10252 \u2014 `flex`, not `inline-flex`.\n *\n * In the default case this changes nothing: `:host` is `inline-flex`, so\n * `.wrap` is a flex item and its display is blockified either way. It matters\n * when a consumer overrides the host's display, which bv-input-dropdown does \u2014\n * `::slotted(*) { display: block }` wins over our `:host`. An inline-level\n * `.wrap` inside a block host lives in a line box, and a line box is sized off\n * the baseline. Checking the box switches `.check` from `display: none` to\n * `display: flex`, which moves `.wrap`'s baseline, which resizes the line:\n * measured at 35.33px unchecked and 32px checked, with four of five rows in the\n * dropdown jumping every time one was ticked. As a block-level flex container\n * there is no line box and no baseline to move. */\n.wrap {\n display: flex;\n align-items: flex-start;\n gap: var(--_gap);\n cursor: pointer;\n user-select: none;\n border-radius: var(--bv-radius-sm);\n}\n\n.wrap:focus-visible {\n outline: 2px solid var(--bv-color-primary);\n outline-offset: 2px;\n}\n\n:host([disabled]) .wrap {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* \u2500\u2500 Box \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.box {\n width: var(--_box-size);\n height: var(--_box-size);\n border-radius: var(--_box-radius);\n border: 1px solid var(--bv-color-neutral-300);\n background: var(--bv-color-neutral-0);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n margin-top: 2px;\n transition:\n background var(--bv-transition-fast),\n border-color var(--bv-transition-fast);\n}\n\n:host([checked]) .box,\n:host([indeterminate]) .box {\n background: var(--bv-color-primary-50);\n border-color: var(--bv-color-primary);\n}\n\n/* \u2500\u2500 Check 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 */\n\n.check {\n display: none;\n width: var(--_check-size);\n height: var(--_check-size);\n color: var(--bv-color-primary);\n line-height: 0;\n}\n\n.check svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n:host([checked]) .check,\n:host([indeterminate]) .check {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* \u2500\u2500 Label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.label {\n font-size: var(--_label-size);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-secondary-foreground);\n line-height: var(--_label-lh);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n\n/* BTN-10252 \u2014 the label span is always in the DOM, even when the consumer\n * passes the text through the default slot instead of the `label` attribute.\n * Empty, it is still a flex item, so `gap` applied twice and the text sat 16px\n * from the box instead of 8px. That is the \"extra padding\" in the ticket, and\n * it is the path CET Light takes: `<bv-checkbox>{{ label }}</bv-checkbox>`. */\n.label:empty {\n display: none;\n}\n\n/* BTN-10252 \u2014 give slotted content the same typography as the attribute path,\n * so the two ways of labelling a checkbox line up identically.\n *\n * On the `slot` element rather than `::slotted(*)` on purpose: `::slotted()`\n * only matches elements, and the case that was broken passes a bare text node.\n * Inheritance runs down the flattened tree, so a text node assigned here picks\n * these up from the slot; `::slotted()` never would. */\nslot {\n font-size: var(--_label-size);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-secondary-foreground);\n line-height: var(--_label-lh);\n}\n";
24
24
 
25
25
  // components/bv-checkbox/bv-checkbox.ts
26
26
  var CHECK_ICON = `<svg viewBox="0 0 11.3333 8.41667" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -30,8 +30,15 @@ var INDETERMINATE_ICON = `<svg viewBox="0 0 12 12" fill="none" xmlns="http://www
30
30
  <path d="M2 6H10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
31
31
  </svg>`;
32
32
  var BvCheckbox = class extends HTMLElement {
33
- static observedAttributes = ["checked", "indeterminate", "disabled", "label", "size"];
33
+ static observedAttributes = [
34
+ "checked",
35
+ "indeterminate",
36
+ "disabled",
37
+ "label",
38
+ "size"
39
+ ];
34
40
  _rendered = false;
41
+ _wrapEl = null;
35
42
  _checkEl = null;
36
43
  _labelEl = null;
37
44
  constructor() {
@@ -43,7 +50,11 @@ var BvCheckbox = class extends HTMLElement {
43
50
  }
44
51
  attributeChangedCallback(name, _old, val) {
45
52
  if (!this._rendered) return;
46
- if (name === "checked" || name === "indeterminate") this._syncIcon();
53
+ if (name === "checked" || name === "indeterminate") {
54
+ this._syncIcon();
55
+ this._syncAria();
56
+ }
57
+ if (name === "disabled") this._syncAria();
47
58
  if (name === "label" && this._labelEl) this._labelEl.textContent = val ?? "";
48
59
  }
49
60
  get checked() {
@@ -56,12 +67,31 @@ var BvCheckbox = class extends HTMLElement {
56
67
  get indeterminate() {
57
68
  return this.hasAttribute("indeterminate");
58
69
  }
70
+ /**
71
+ * Focus the checkbox. The focusable element is the internal `.wrap`, not the
72
+ * host, so `hostEl.focus()` would otherwise be a silent no-op. Consumers that
73
+ * rebuild a list of checkboxes (bv-filter-panel, and any screen re-rendering
74
+ * a tree into bv-input-dropdown) need this to restore focus after the rebuild
75
+ * without reaching into this component's shadow root.
76
+ *
77
+ * No-op before the first render, and when disabled — a native disabled
78
+ * checkbox cannot be focused programmatically either, and `tabindex="-1"`
79
+ * alone would not have stopped it (that only removes it from Tab order).
80
+ */
81
+ focus(options) {
82
+ var _a;
83
+ if (this.hasAttribute("disabled")) return;
84
+ (_a = this._wrapEl) == null ? void 0 : _a.focus(options);
85
+ }
59
86
  _render() {
60
87
  if (this._rendered) return;
61
88
  this._rendered = true;
62
89
  const shadow = this.shadowRoot;
63
- const wrap = document.createElement("label");
90
+ const wrap = document.createElement("div");
64
91
  wrap.className = "wrap";
92
+ wrap.setAttribute("role", "checkbox");
93
+ wrap.setAttribute("part", "checkbox");
94
+ this._wrapEl = wrap;
65
95
  const box = document.createElement("span");
66
96
  box.className = "box";
67
97
  this._checkEl = document.createElement("span");
@@ -74,7 +104,7 @@ var BvCheckbox = class extends HTMLElement {
74
104
  const slot = document.createElement("slot");
75
105
  wrap.append(box, this._labelEl, slot);
76
106
  shadow.appendChild(wrap);
77
- wrap.addEventListener("click", () => {
107
+ const toggle = () => {
78
108
  if (this.hasAttribute("disabled")) return;
79
109
  this.checked = !this.checked;
80
110
  this.removeAttribute("indeterminate");
@@ -85,12 +115,27 @@ var BvCheckbox = class extends HTMLElement {
85
115
  detail: { checked: this.checked }
86
116
  })
87
117
  );
118
+ };
119
+ wrap.addEventListener("click", toggle);
120
+ wrap.addEventListener("keydown", (e) => {
121
+ if (e.key !== "Enter" && e.key !== " ") return;
122
+ if (e.key === " ") e.preventDefault();
123
+ toggle();
88
124
  });
125
+ this._syncAria();
89
126
  }
90
127
  _syncIcon() {
91
128
  if (!this._checkEl) return;
92
129
  this._checkEl.innerHTML = this.indeterminate ? INDETERMINATE_ICON : CHECK_ICON;
93
130
  }
131
+ _syncAria() {
132
+ if (!this._wrapEl) return;
133
+ const disabled = this.hasAttribute("disabled");
134
+ this._wrapEl.tabIndex = disabled ? -1 : 0;
135
+ this._wrapEl.setAttribute("aria-checked", this.indeterminate ? "mixed" : String(this.checked));
136
+ if (disabled) this._wrapEl.setAttribute("aria-disabled", "true");
137
+ else this._wrapEl.removeAttribute("aria-disabled");
138
+ }
94
139
  };
95
140
  if (!customElements.get("bv-checkbox")) {
96
141
  customElements.define("bv-checkbox", BvCheckbox);
@@ -177,6 +222,7 @@ var BvFilterPanel = class extends HTMLElement {
177
222
  }
178
223
  _buildGroups() {
179
224
  if (!this._groupsEl) return;
225
+ const focused = this._focusedOption();
180
226
  this._groupsEl.innerHTML = "";
181
227
  let rendered = 0;
182
228
  for (const group of this._groups) {
@@ -203,6 +249,8 @@ var BvFilterPanel = class extends HTMLElement {
203
249
  const bvCheckbox = document.createElement("bv-checkbox");
204
250
  bvCheckbox.setAttribute("label", opt.label);
205
251
  bvCheckbox.setAttribute("size", "sm");
252
+ bvCheckbox.setAttribute("data-group", group.key);
253
+ bvCheckbox.setAttribute("data-value", opt.value);
206
254
  if (opt.checked) bvCheckbox.setAttribute("checked", "");
207
255
  bvCheckbox.addEventListener("bv-change", (e) => {
208
256
  opt.checked = e.detail.checked;
@@ -237,6 +285,39 @@ var BvFilterPanel = class extends HTMLElement {
237
285
  }
238
286
  if (rendered === 0) this.setAttribute("empty", "");
239
287
  else this.removeAttribute("empty");
288
+ if (focused) this._restoreFocus(focused.group, focused.value);
289
+ }
290
+ /** `data-group` / `data-value` of the checkbox currently holding focus, if any. */
291
+ _focusedOption() {
292
+ var _a;
293
+ const active = (_a = this.shadowRoot) == null ? void 0 : _a.activeElement;
294
+ if (!active || active.tagName !== "BV-CHECKBOX") return null;
295
+ const group = active.getAttribute("data-group");
296
+ const value = active.getAttribute("data-value");
297
+ if (group === null || value === null) return null;
298
+ return { group, value };
299
+ }
300
+ /**
301
+ * Put focus back on the rebuilt checkbox for the same option. Matched by
302
+ * iterating rather than with an attribute selector: group keys and option
303
+ * values come from consumer JSON and can contain quotes or brackets that
304
+ * would break — or inject into — a selector string.
305
+ *
306
+ * If the option is gone from the new groups (filtered out by a search, say),
307
+ * focus is deliberately left wherever the browser put it rather than moved to
308
+ * a neighbour. Silently focusing a different filter than the one the user was
309
+ * on is worse than dropping out of the list.
310
+ */
311
+ _restoreFocus(group, value) {
312
+ var _a;
313
+ const boxes = (_a = this.shadowRoot) == null ? void 0 : _a.querySelectorAll("bv-checkbox");
314
+ if (!boxes) return;
315
+ for (const box of Array.from(boxes)) {
316
+ if (box.getAttribute("data-group") === group && box.getAttribute("data-value") === value) {
317
+ box.focus();
318
+ return;
319
+ }
320
+ }
240
321
  }
241
322
  };
242
323
  if (!customElements.get("bv-filter-panel")) {
@@ -1,5 +1,5 @@
1
1
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-icon/bv-icon.css
2
- var bv_icon_default = ":host {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: inherit;\n}\n\n.icon-wrap {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 0;\n}\n\n.icon-wrap svg {\n display: block;\n}\n";
2
+ var bv_icon_default = ":host {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: inherit;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.icon-wrap {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 0;\n}\n\n.icon-wrap svg {\n display: block;\n}\n";
3
3
 
4
4
  // components/_shared/adopt-styles.ts
5
5
  var _sheetCache = /* @__PURE__ */ new Map();
@@ -20,7 +20,7 @@ function adoptStyles(shadow, css) {
20
20
  }
21
21
 
22
22
  // components/bv-icon/icons.ts
23
- var icons = {
23
+ var icons = Object.assign(/* @__PURE__ */ Object.create(null), {
24
24
  // Navigation
25
25
  "chevron-up": `<path d="M18 15l-6-6-6 6"/>`,
26
26
  "chevron-down": `<path d="M6 9l6 6 6-6"/>`,
@@ -71,16 +71,16 @@ var icons = {
71
71
  bell: `<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>`,
72
72
  mail: `<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/>`,
73
73
  link: `<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>`
74
- };
74
+ });
75
75
  function getIconSvg(name, size = 16) {
76
- const raw = rawIcons[name];
76
+ const raw = RAW_ICON_MAP.get(name);
77
77
  if (raw)
78
78
  return raw.replace(/width="[^"]*"/, `width="${size}"`).replace(/height="[^"]*"/, `height="${size}"`);
79
- const paths = icons[name];
79
+ const paths = ICON_MAP.get(name);
80
80
  if (!paths) return "";
81
81
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
82
82
  }
83
- var rawIcons = {
83
+ var rawIcons = Object.assign(/* @__PURE__ */ Object.create(null), {
84
84
  // Module type (small, ~18px)
85
85
  "module-scorm": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 15.03 16.28" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.9014 4.60034L7.51368 8.14201M7.51368 8.14201L1.126 4.60034M7.51368 8.14201L7.5137 15.267M14.2771 11.1859V5.09812C14.2771 4.84113 14.2771 4.71264 14.2392 4.59804C14.2056 4.49666 14.1508 4.4036 14.0783 4.32508C13.9963 4.23633 13.8838 4.17393 13.6587 4.04913L8.09763 0.965793C7.88451 0.847624 7.77794 0.78854 7.66509 0.765376C7.56521 0.744875 7.46219 0.744875 7.36231 0.765376C7.24946 0.78854 7.1429 0.847624 6.92977 0.965793L1.36873 4.04913C1.14364 4.17393 1.0311 4.23633 0.949147 4.32508C0.876647 4.4036 0.82178 4.49666 0.788217 4.59804C0.750278 4.71264 0.750278 4.84113 0.750278 5.09812V11.1859C0.750278 11.4429 0.750278 11.5714 0.788217 11.686C0.82178 11.7874 0.876647 11.8805 0.949147 11.959C1.0311 12.0477 1.14364 12.1101 1.36873 12.2349L6.92977 15.3183C7.1429 15.4364 7.24946 15.4955 7.36231 15.5187C7.46219 15.5392 7.56521 15.5392 7.66509 15.5187C7.77794 15.4955 7.88451 15.4364 8.09763 15.3183L13.6587 12.2349C13.8838 12.1101 13.9963 12.0477 14.0783 11.959C14.1508 11.8805 14.2056 11.7874 14.2392 11.686C14.2771 11.5714 14.2771 11.4429 14.2771 11.1859Z"/></svg>`,
86
86
  "module-assessment": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 13.72 16.13" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.507 6.1875V4.1625C12.507 2.90238 12.507 2.27232 12.2629 1.79102C12.0482 1.36766 11.7056 1.02345 11.2841 0.807736C10.8051 0.5625 10.1779 0.5625 8.92364 0.5625H4.14585C2.89156 0.5625 2.26441 0.5625 1.78534 0.807736C1.36393 1.02345 1.02132 1.36766 0.806601 1.79102C0.5625 2.27232 0.5625 2.90238 0.5625 4.1625V11.9625C0.5625 13.2226 0.5625 13.8527 0.806601 14.334C1.02132 14.7573 1.36393 15.1015 1.78534 15.3173C2.26441 15.5625 2.89156 15.5625 4.14585 15.5625H8.0278M8.0278 7.3125H3.54862M5.04168 10.3125H3.54862M9.52086 4.3125H3.54862M9.89413 10.3142C10.0257 9.93852 10.2853 9.62175 10.627 9.41998C10.9688 9.21821 11.3705 9.14445 11.7612 9.21178C12.1519 9.2791 12.5062 9.48315 12.7615 9.7878C13.0168 10.0925 13.1565 10.478 13.1559 10.8763C13.1559 12.0004 11.4775 12.5625 11.4775 12.5625M11.4991 14.8125H11.5066"/></svg>`,
@@ -90,7 +90,9 @@ var rawIcons = {
90
90
  "module-adobe-connect": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 16.06 14.63" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.7605 0.56252L8.0278 3.56252L4.29515 0.56252M4.14585 14.0625H11.9098C13.164 14.0625 13.7912 14.0625 14.2703 13.8173C14.6917 13.6016 15.0343 13.2574 15.249 12.834C15.4931 12.3527 15.4931 11.7226 15.4931 10.4625V7.16252C15.4931 5.9024 15.4931 5.27234 15.249 4.79104C15.0343 4.36768 14.6917 4.02347 14.2703 3.80776C13.7912 3.56252 13.164 3.56252 11.9098 3.56252H4.14585C2.89156 3.56252 2.26441 3.56252 1.78534 3.80776C1.36393 4.02347 1.02132 4.36768 0.806601 4.79104C0.5625 5.27234 0.5625 5.9024 0.5625 7.16252V10.4625C0.5625 11.7226 0.5625 12.3527 0.806601 12.834C1.02132 13.2574 1.36393 13.6016 1.78534 13.8173C2.26441 14.0625 2.89156 14.0625 4.14585 14.0625Z"/></svg>`,
91
91
  "module-video": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 16.06 11.63" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15.4931 3.51103C15.4931 3.05667 15.4931 2.82949 15.4037 2.72429C15.3261 2.63301 15.2097 2.58457 15.0906 2.59399C14.9533 2.60484 14.7934 2.76548 14.4736 3.08676L11.7605 5.8125L14.4736 8.53824C14.7934 8.85952 14.9533 9.02016 15.0906 9.03101C15.2097 9.04043 15.3261 8.99199 15.4037 8.90071C15.4931 8.79552 15.4931 8.56833 15.4931 8.11397V3.51103ZM0.5625 4.1625C0.5625 2.90238 0.5625 2.27232 0.806601 1.79102C1.02132 1.36766 1.36393 1.02345 1.78534 0.807736C2.26441 0.5625 2.89156 0.5625 4.14585 0.5625H8.17711C9.4314 0.5625 10.0585 0.5625 10.5376 0.807736C10.959 1.02345 11.3016 1.36766 11.5164 1.79102C11.7605 2.27232 11.7605 2.90238 11.7605 4.1625V7.4625C11.7605 8.72262 11.7605 9.35268 11.5164 9.83398C11.3016 10.2573 10.959 10.6015 10.5376 10.8173C10.0585 11.0625 9.4314 11.0625 8.17711 11.0625H4.14584C2.89156 11.0625 2.26441 11.0625 1.78534 10.8173C1.36393 10.6015 1.02132 10.2573 0.806601 9.83398C0.5625 9.35268 0.5625 8.72262 0.5625 7.4625V4.1625Z"/></svg>`,
92
92
  "module-upload": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 14.75 16.13" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.39 4.3125V4.1625C13.39 2.90238 13.39 2.27232 13.1278 1.79102C12.8972 1.36766 12.5293 1.02345 12.0767 0.807736C11.5622 0.5625 10.8887 0.5625 9.54172 0.5625H4.41074C3.06373 0.5625 2.39022 0.5625 1.87573 0.807736C1.42318 1.02345 1.05524 1.36766 0.824646 1.79102C0.5625 2.27232 0.5625 2.90238 0.5625 4.1625V11.9625C0.5625 13.2226 0.5625 13.8527 0.824646 14.334C1.05524 14.7573 1.42318 15.1015 1.87573 15.3173C2.39022 15.5625 3.06373 15.5625 4.41074 15.5625H7.37709M7.37709 7.3125H3.76936M6.57537 10.3125H3.76936M10.1831 4.3125H3.76936M11.7865 12.5625V8.4375C11.7865 7.81618 12.3249 7.3125 12.9891 7.3125C13.6533 7.3125 14.1917 7.81618 14.1917 8.4375V12.5625C14.1917 13.8051 13.1149 14.8125 11.7865 14.8125C10.4582 14.8125 9.38138 13.8051 9.38138 12.5625V9.5625"/></svg>`
93
- };
93
+ });
94
+ var RAW_ICON_MAP = new Map(Object.entries(rawIcons));
95
+ var ICON_MAP = new Map(Object.entries(icons));
94
96
 
95
97
  // components/bv-icon/bv-icon.ts
96
98
  var BvIcon = class extends HTMLElement {
@@ -1,5 +1,5 @@
1
1
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-input/bv-input.css
2
- var bv_input_default = ":host {\n display: block;\n font-family: var(--bv-font-family-base);\n}\n\n/* \u2500\u2500 Label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.field-label {\n display: block;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n margin-bottom: 6px;\n}\n\n.required-mark {\n color: var(--bv-color-danger);\n margin-left: 2px;\n}\n\n/* \u2500\u2500 Input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.field-input {\n display: block;\n width: 100%;\n box-sizing: border-box;\n height: 44px;\n padding: 0 var(--bv-spacing-md);\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n outline: none;\n transition:\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n.field-input::placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.field-input:focus {\n border-color: var(--bv-color-primary);\n box-shadow:\n 0 0 0 3px var(--bv-color-primary-light),\n 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n.field-input:disabled {\n background: var(--bv-color-neutral-100);\n color: var(--bv-color-neutral-400);\n cursor: not-allowed;\n}\n\n:host([error]:not([error=''])) .field-input {\n border-color: var(--bv-color-danger);\n}\n\n:host([error]:not([error=''])) .field-input:focus {\n border-color: var(--bv-color-danger);\n box-shadow:\n 0 0 0 3px var(--bv-color-danger-light),\n 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n/* \u2500\u2500 Error message \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.field-error {\n display: block;\n font-size: var(--bv-font-size-xs);\n color: var(--bv-color-danger);\n margin-top: var(--bv-spacing-xs);\n}\n";
2
+ var bv_input_default = ":host {\n display: block;\n font-family: var(--bv-font-family-base);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n/* \u2500\u2500 Label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.field-label {\n display: block;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n margin-bottom: 6px;\n}\n\n/* Author `display: block` above beats the user-agent `[hidden] { display:\n none }`, so `_syncLabel()` setting `.hidden` did nothing on screen. With no\n label text the element collapses to 0px on its own, which is why this went\n unnoticed - but `required` appends an asterisk after the text is cleared, so\n a required field with no label rendered a stray \"*\" above the control\n (BTN-11521). */\n.field-label[hidden] {\n display: none;\n}\n\n.required-mark {\n color: var(--bv-color-danger);\n margin-left: 2px;\n}\n\n/* \u2500\u2500 Input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.field-input {\n display: block;\n width: 100%;\n box-sizing: border-box;\n height: 44px;\n padding: 0 var(--bv-spacing-md);\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n outline: none;\n transition:\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n.field-input::placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.field-input:focus {\n border-color: var(--bv-color-primary);\n box-shadow:\n 0 0 0 3px var(--bv-color-primary-light),\n 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n.field-input:disabled {\n background: var(--bv-color-neutral-100);\n color: var(--bv-color-neutral-400);\n cursor: not-allowed;\n}\n\n:host([error]:not([error=''])) .field-input {\n border-color: var(--bv-color-danger);\n}\n\n:host([error]:not([error=''])) .field-input:focus {\n border-color: var(--bv-color-danger);\n box-shadow:\n 0 0 0 3px var(--bv-color-danger-light),\n 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n/* \u2500\u2500 Error message \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.field-error {\n display: block;\n font-size: var(--bv-font-size-xs);\n color: var(--bv-color-danger);\n margin-top: var(--bv-spacing-xs);\n}\n\n/* These elements have an author `display`, which beats the user-agent\n `[hidden] { display: none }` regardless of specificity. The component\n toggles their `hidden`, so without this the property changes nothing on\n screen (BTN-11521). */\n.field-error[hidden] {\n display: none;\n}\n";
3
3
 
4
4
  // components/_shared/adopt-styles.ts
5
5
  var _sheetCache = /* @__PURE__ */ new Map();
@@ -11,8 +11,24 @@ export interface InputDropdownSelectAllDetail {
11
11
  export interface InputDropdownToggleDetail {
12
12
  checked: boolean;
13
13
  }
14
+ /**
15
+ * BTN-11485 — emitted once per click while `tree` is set, in place of the
16
+ * slotted row's own `bv-change` (which the component stops at its boundary so
17
+ * a consumer cannot end up handling both).
18
+ *
19
+ * `affected` is every value the click changes — the clicked node first, then
20
+ * its descendants in row order — and `checked` is the state to apply to all
21
+ * of them. One event, not one per node: the CET Report resets its Courses
22
+ * cascade on every Categories change, and twenty-three events would reset it
23
+ * twenty-three times.
24
+ */
25
+ export interface InputDropdownTreeSelectDetail {
26
+ value: string;
27
+ checked: boolean;
28
+ affected: string[];
29
+ }
14
30
  export declare class BvInputDropdown extends HTMLElement {
15
- static readonly observedAttributes: readonly ["label", "value", "placeholder", "required", "disabled", "error", "search", "select-all", "select-all-state", "toggle-label", "toggle-checked", "open"];
31
+ static readonly observedAttributes: readonly ["label", "value", "placeholder", "required", "disabled", "error", "search", "select-all", "select-all-state", "tree", "toggle-label", "toggle-checked", "label-no-results", "label-no-results-hint", "label-empty", "label-empty-hint", "open"];
16
32
  private _rendered;
17
33
  private _labelEl;
18
34
  private _triggerLabelEl;
@@ -22,7 +38,16 @@ export declare class BvInputDropdown extends HTMLElement {
22
38
  private _selectAllEl;
23
39
  private _toggleEl;
24
40
  private _errorEl;
41
+ private _slotEl;
42
+ private _optionsEl;
43
+ private _emptyEl;
44
+ private _emptyTitleEl;
45
+ private _emptyHintEl;
25
46
  private _dropdown;
47
+ private _rowObserver;
48
+ /** Last query seen from the internal search box. Drives the copy choice. */
49
+ private _query;
50
+ private _emptyState;
26
51
  constructor();
27
52
  get value(): string;
28
53
  set value(v: string);
@@ -32,12 +57,109 @@ export declare class BvInputDropdown extends HTMLElement {
32
57
  set required(v: boolean);
33
58
  connectedCallback(): void;
34
59
  disconnectedCallback(): void;
60
+ /**
61
+ * Everything that has to be torn down on detach and stood back up on
62
+ * attach, as opposed to the DOM, which is built once.
63
+ *
64
+ * `_render` returns early on its `_rendered` guard, so anything bound inside
65
+ * it is bound exactly once in the element's lifetime — which is correct for
66
+ * the DOM and wrong for a listener that `disconnectedCallback` just removed.
67
+ * A detached and re-attached dropdown came back with no row observer and a
68
+ * dropdown controller whose `cleanup()` had already run: the dashes stopped
69
+ * repainting and Escape and outside-click stopped closing the panel.
70
+ *
71
+ * The controller half predates BTN-11485 — the null check did not catch it
72
+ * because the object was still there, emptied out. Raised by Copilot on
73
+ * PR #49 against the observer; the controller came out with it.
74
+ *
75
+ * Idempotent, because `connectedCallback` can fire more than once and this
76
+ * must not stack listeners.
77
+ */
78
+ private _bindLiveWiring;
79
+ /**
80
+ * The row observer runs only while tree mode does.
81
+ *
82
+ * It exists to repaint the dash when a consumer rewrites `checked` on rows
83
+ * already in the slot — which no other consumer of this component needs.
84
+ * Binding it unconditionally made every ordinary checkbox toggle, in every
85
+ * dropdown in the app, pay for a feature it does not use. Raised by Copilot
86
+ * on PR #49.
87
+ *
88
+ * `attributeFilter: ['checked']` is load-bearing, not tidiness: the handler
89
+ * writes `indeterminate`, and watching all attributes would feed those
90
+ * writes straight back in — BTN-11850 with different attributes. Filtering
91
+ * to the one attribute we never write makes the feedback edge unreachable
92
+ * rather than merely unlikely, and `npm run test:observers` holds it shut.
93
+ */
94
+ private _syncRowObserver;
35
95
  attributeChangedCallback(name: string, _old: string | null, _val: string | null): void;
36
96
  private _render;
97
+ /** Whether hierarchical selection is on. */
98
+ private get _isTree();
99
+ /**
100
+ * Every slotted element carrying `data-value`, in document order.
101
+ *
102
+ * Descendants count, not just direct slot children. Consumers disagree about
103
+ * markup — the CET Report renders one flat `*ngFor` and expresses depth as
104
+ * padding, while this component's own Tree view story nests rows in wrapper
105
+ * divs — and both are legitimate. That disagreement is the whole reason the
106
+ * hierarchy travels in `data-parent` rather than in the DOM shape: read the
107
+ * relationships from the attributes and the markup stops mattering.
108
+ */
109
+ private _treeElements;
110
+ /**
111
+ * The slotted rows, as the pure helper wants them.
112
+ *
113
+ * Only elements carrying `data-value` take part; anything else a consumer
114
+ * slots in (a separator, a heading) is ignored rather than treated as a
115
+ * root. `data-parent` empty or absent means root.
116
+ *
117
+ * A branch reaches only what the consumer rendered, and that is the intended
118
+ * rule rather than a side effect of reading the DOM. A row the consumer
119
+ * withheld — a group hidden behind Show Inactive, say — is withheld on
120
+ * purpose, and a click has no business reaching past that decision into
121
+ * something the user has asked not to see. Confirmed as correct with the
122
+ * team on 2026-09-18 and pinned by a test, because it currently *follows*
123
+ * from reading rendered rows: give this component the whole tree some other
124
+ * way and the behaviour would reverse with nothing to catch it.
125
+ */
126
+ private _treeRows;
127
+ /**
128
+ * Paint the dash on every partially selected node.
129
+ *
130
+ * `indeterminate` is the one piece of row state the component owns.
131
+ * `checked` belongs to the consumer — in Angular it is an `[attr.checked]`
132
+ * binding, so anything written here would be overwritten on the next change
133
+ * detection pass. Nothing binds `indeterminate`, so this is ours to keep
134
+ * correct, and it is what makes a check become a dash without the consumer
135
+ * computing anything.
136
+ */
137
+ private _syncTreeStates;
138
+ /**
139
+ * A slotted row reported a change.
140
+ *
141
+ * The row has already flipped its own `checked` by the time this runs, and
142
+ * that value is deliberately ignored: a partially selected parent must FILL
143
+ * its branch, and its own checkbox may have been sitting either way. The
144
+ * answer comes from the tree instead — see `resolveClick`.
145
+ */
146
+ private _onRowChange;
37
147
  private _syncLabel;
38
148
  private _syncTriggerLabel;
39
149
  private _syncError;
40
150
  private _syncSearch;
151
+ private _resolveEmptyState;
152
+ /**
153
+ * Hot path: CET Report rebuilds the whole option list on every checkbox
154
+ * toggle (the same behavior that forced `composedPath()` outside-click
155
+ * detection in BTN-10274), so this runs on every tick, not only when the
156
+ * list genuinely empties. It stays a length check plus a comparison and
157
+ * returns before touching the DOM when the state has not flipped — the
158
+ * toolbar and header are never re-rendered under the user's focus for a
159
+ * change that did not happen.
160
+ */
161
+ private _syncEmptyState;
162
+ private _paintEmptyState;
41
163
  private _syncToolbar;
42
164
  private _updateHeaderVisibility;
43
165
  }