@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-input-dropdown/bv-input-dropdown.css
2
- var bv_input_dropdown_default = ":host {\n display: block;\n position: relative;\n font-family: var(--bv-font-family-base);\n}\n\n/* \u2500\u2500 Field 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 */\n\n.field-label {\n display: block;\n font-size: var(--bv-font-size-sm);\n line-height: 20px;\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 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n height: 44px;\n box-sizing: border-box;\n padding: 0 var(--bv-spacing-md);\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n cursor: pointer;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n text-align: left;\n transition:\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n.trigger:focus-visible {\n outline: none;\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:host([open]) .trigger {\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:host([disabled]) .trigger {\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=''])) .trigger {\n border-color: var(--bv-color-danger);\n}\n\n.trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.trigger-label.is-placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.trigger-chevron {\n display: flex;\n color: var(--bv-color-neutral-500);\n flex-shrink: 0;\n margin-left: var(--bv-spacing-sm);\n transition: transform var(--bv-transition-fast);\n}\n\n:host([open]) .trigger-chevron {\n transform: rotate(180deg);\n}\n\n/* \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.panel {\n display: none;\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n min-width: 100%;\n width: max-content;\n max-width: 600px;\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-200);\n border-radius: var(--bv-radius-lg);\n box-shadow: var(--bv-shadow-md);\n z-index: 200;\n overflow: hidden;\n}\n\n:host([toggle-label]) .panel {\n min-width: 260px;\n}\n\n:host([open]) .panel {\n display: block;\n}\n\n/* \u2500\u2500 Panel header (search + toolbar) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.panel-header {\n border-bottom: 1px solid var(--bv-color-neutral-200);\n padding: var(--bv-spacing-sm);\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-xs);\n}\n\n.panel-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--bv-spacing-xs) 0;\n}\n\n/* \u2500\u2500 Panel content (scrollable slot area) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.panel-content {\n overflow-y: auto;\n max-height: var(--bv-input-dropdown-content-height, 280px);\n padding: var(--bv-spacing-xs) 0;\n}\n\n/* Stack all slotted content vertically */\n::slotted(*) {\n display: block;\n}\n\n/* Row padding \u2014 left aligns with the panel-header padding */\n::slotted(bv-checkbox) {\n padding: 6px var(--bv-spacing-sm);\n box-sizing: border-box;\n white-space: nowrap;\n max-width: 100%;\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.field-error:empty {\n display: none;\n}\n";
2
+ var bv_input_dropdown_default = ":host {\n display: block;\n position: relative;\n font-family: var(--bv-font-family-base);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n/* \u2500\u2500 Field 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 */\n\n.field-label {\n display: block;\n font-size: var(--bv-font-size-sm);\n line-height: 20px;\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 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n height: 44px;\n box-sizing: border-box;\n padding: 0 var(--bv-spacing-md);\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n cursor: pointer;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n text-align: left;\n transition:\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n.trigger:focus-visible {\n outline: none;\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:host([open]) .trigger {\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:host([disabled]) .trigger {\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=''])) .trigger {\n border-color: var(--bv-color-danger);\n}\n\n.trigger-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.trigger-label.is-placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.trigger-chevron {\n display: flex;\n color: var(--bv-color-neutral-500);\n flex-shrink: 0;\n margin-left: var(--bv-spacing-sm);\n transition: transform var(--bv-transition-fast);\n}\n\n:host([open]) .trigger-chevron {\n transform: rotate(180deg);\n}\n\n/* \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.panel {\n display: none;\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n min-width: 100%;\n width: max-content;\n max-width: 600px;\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-200);\n border-radius: var(--bv-radius-lg);\n box-shadow: var(--bv-shadow-md);\n z-index: 200;\n overflow: hidden;\n}\n\n:host([toggle-label]) .panel {\n min-width: 260px;\n}\n\n:host([open]) .panel {\n display: block;\n}\n\n/* \u2500\u2500 Panel header (search + toolbar) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.panel-header {\n border-bottom: 1px solid var(--bv-color-neutral-200);\n padding: var(--bv-spacing-sm);\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-xs);\n}\n\n.panel-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--bv-spacing-xs) 0;\n}\n\n/* Same author-vs-user-agent problem as bv-checkbox: `display: flex` above beats\n the UA `[hidden] { display: none }`, so `_updateHeaderVisibility()` and\n `_syncToolbar()` were setting `.hidden` with no visual effect (BTN-11521).\n\n `.panel-options` declares no display of its own, so the user-agent rule\n already hides it and this line is a no-op today. It is here anyway: the\n component toggles that element's `hidden` from `_paintEmptyState()`, and\n adding a `display` to it later would silently break the empty state the same\n way it broke the header \u2014 twice in this file already. */\n.panel-header[hidden],\n.panel-toolbar[hidden],\n.panel-options[hidden] {\n display: none;\n}\n/* \u2500\u2500 Panel content (scrollable slot area) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.panel-content {\n overflow-y: auto;\n max-height: var(--bv-input-dropdown-content-height, 280px);\n padding: var(--bv-spacing-xs) 0;\n}\n\n/* Stack all slotted content vertically */\n::slotted(*) {\n display: block;\n}\n\n/* Row padding \u2014 left aligns with the panel-header padding */\n::slotted(bv-checkbox) {\n padding: 6px var(--bv-spacing-sm);\n box-sizing: border-box;\n white-space: nowrap;\n max-width: 100%;\n}\n\n/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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/* Two-level, centered: bold title + muted subtitle. Lives inside\n .panel-content so it inherits the panel's scroll box and typography. */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--bv-spacing-2xs);\n text-align: center;\n padding: var(--bv-spacing-lg) var(--bv-spacing-md);\n}\n\n.empty-state[hidden] {\n display: none;\n}\n\n/* Design spec (BTN-10682): 16px / 600 / #1E293B, centered. `line-height` is\n left unset so it resolves to `normal`, which is what the spec asks for. */\n.empty-title {\n font-size: var(--bv-font-size-md);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n}\n\n/* Design spec (BTN-10682): 14px / 400 / #94A3B8, centered. */\n.empty-hint {\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-400);\n}\n\n.empty-hint[hidden] {\n display: none;\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.field-error:empty {\n display: none;\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();
@@ -77,6 +77,75 @@ function bindDropdownController(host, options = {}) {
77
77
  return { open, close, toggle, cleanup };
78
78
  }
79
79
 
80
+ // components/_shared/tree-selection.ts
81
+ function childIndex(rows) {
82
+ const present = new Set(rows.map((r) => r.value));
83
+ const index = /* @__PURE__ */ new Map();
84
+ for (const row of rows) {
85
+ if (row.parent === null || !present.has(row.parent)) continue;
86
+ const siblings = index.get(row.parent);
87
+ if (siblings) siblings.push(row.value);
88
+ else index.set(row.parent, [row.value]);
89
+ }
90
+ return index;
91
+ }
92
+ function descendantsOf(rows, value) {
93
+ const index = childIndex(rows);
94
+ const out = [];
95
+ const seen = /* @__PURE__ */ new Set([value]);
96
+ const walk = (parent) => {
97
+ for (const child of index.get(parent) ?? []) {
98
+ if (seen.has(child)) continue;
99
+ seen.add(child);
100
+ out.push(child);
101
+ walk(child);
102
+ }
103
+ };
104
+ walk(value);
105
+ return out;
106
+ }
107
+ function computeStates(rows) {
108
+ const index = childIndex(rows);
109
+ const byValue = new Map(rows.map((r) => [r.value, r]));
110
+ const states = /* @__PURE__ */ new Map();
111
+ const inProgress = /* @__PURE__ */ new Set();
112
+ const resolve = (value) => {
113
+ var _a, _b;
114
+ const cached = states.get(value);
115
+ if (cached) return cached;
116
+ if (inProgress.has(value)) {
117
+ return ((_a = byValue.get(value)) == null ? void 0 : _a.checked) === true ? "checked" : "unchecked";
118
+ }
119
+ inProgress.add(value);
120
+ const self = ((_b = byValue.get(value)) == null ? void 0 : _b.checked) === true;
121
+ const children = index.get(value) ?? [];
122
+ if (children.length === 0) {
123
+ const leaf = self ? "checked" : "unchecked";
124
+ inProgress.delete(value);
125
+ states.set(value, leaf);
126
+ return leaf;
127
+ }
128
+ let anyChecked = self;
129
+ let allChecked = self;
130
+ for (const child of children) {
131
+ const childState = resolve(child);
132
+ if (childState !== "unchecked") anyChecked = true;
133
+ if (childState !== "checked") allChecked = false;
134
+ }
135
+ const state = allChecked ? "checked" : anyChecked ? "mixed" : "unchecked";
136
+ inProgress.delete(value);
137
+ states.set(value, state);
138
+ return state;
139
+ };
140
+ for (const row of rows) resolve(row.value);
141
+ return states;
142
+ }
143
+ function resolveClick(rows, value, branch) {
144
+ const wasFull = computeStates(rows).get(value) === "checked";
145
+ const affected = branch ? [value, ...descendantsOf(rows, value)] : [value];
146
+ return { affected, checked: !wasFull };
147
+ }
148
+
80
149
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-searchbox/bv-searchbox.css
81
150
  var bv_searchbox_default = ":host {\n display: block;\n width: 100%;\n position: relative;\n font-family: var(--bv-font-family-base);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.search-icon {\n position: absolute;\n left: 12px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--bv-color-neutral-400);\n display: flex;\n pointer-events: none;\n}\n\n.search-input {\n width: 100%;\n box-sizing: border-box;\n height: 40px;\n padding: 0 12px 0 38px;\n font-size: var(--bv-font-size-sm);\n font-family: inherit;\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n outline: none;\n color: var(--bv-color-neutral-800);\n background: var(--bv-color-neutral-0);\n transition: border-color var(--bv-transition-fast);\n}\n\n:host([size='sm']) .search-input {\n height: 36px;\n}\n:host([size='lg']) .search-input {\n height: 46px;\n}\n\n.search-input::placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.search-input:focus {\n border-color: #6fb0d8;\n box-shadow:\n 0px 0px 0px 4px #dbe8ff,\n 0px 1px 2px 0px rgba(16, 24, 40, 0.05);\n}\n";
82
151
 
@@ -134,7 +203,7 @@ if (!customElements.get("bv-searchbox")) {
134
203
  }
135
204
 
136
205
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-checkbox/bv-checkbox.css
137
- 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";
206
+ 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";
138
207
 
139
208
  // components/bv-checkbox/bv-checkbox.ts
140
209
  var CHECK_ICON = `<svg viewBox="0 0 11.3333 8.41667" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -144,8 +213,15 @@ var INDETERMINATE_ICON = `<svg viewBox="0 0 12 12" fill="none" xmlns="http://www
144
213
  <path d="M2 6H10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
145
214
  </svg>`;
146
215
  var BvCheckbox = class extends HTMLElement {
147
- static observedAttributes = ["checked", "indeterminate", "disabled", "label", "size"];
216
+ static observedAttributes = [
217
+ "checked",
218
+ "indeterminate",
219
+ "disabled",
220
+ "label",
221
+ "size"
222
+ ];
148
223
  _rendered = false;
224
+ _wrapEl = null;
149
225
  _checkEl = null;
150
226
  _labelEl = null;
151
227
  constructor() {
@@ -157,7 +233,11 @@ var BvCheckbox = class extends HTMLElement {
157
233
  }
158
234
  attributeChangedCallback(name, _old, val) {
159
235
  if (!this._rendered) return;
160
- if (name === "checked" || name === "indeterminate") this._syncIcon();
236
+ if (name === "checked" || name === "indeterminate") {
237
+ this._syncIcon();
238
+ this._syncAria();
239
+ }
240
+ if (name === "disabled") this._syncAria();
161
241
  if (name === "label" && this._labelEl) this._labelEl.textContent = val ?? "";
162
242
  }
163
243
  get checked() {
@@ -170,12 +250,31 @@ var BvCheckbox = class extends HTMLElement {
170
250
  get indeterminate() {
171
251
  return this.hasAttribute("indeterminate");
172
252
  }
253
+ /**
254
+ * Focus the checkbox. The focusable element is the internal `.wrap`, not the
255
+ * host, so `hostEl.focus()` would otherwise be a silent no-op. Consumers that
256
+ * rebuild a list of checkboxes (bv-filter-panel, and any screen re-rendering
257
+ * a tree into bv-input-dropdown) need this to restore focus after the rebuild
258
+ * without reaching into this component's shadow root.
259
+ *
260
+ * No-op before the first render, and when disabled — a native disabled
261
+ * checkbox cannot be focused programmatically either, and `tabindex="-1"`
262
+ * alone would not have stopped it (that only removes it from Tab order).
263
+ */
264
+ focus(options) {
265
+ var _a;
266
+ if (this.hasAttribute("disabled")) return;
267
+ (_a = this._wrapEl) == null ? void 0 : _a.focus(options);
268
+ }
173
269
  _render() {
174
270
  if (this._rendered) return;
175
271
  this._rendered = true;
176
272
  const shadow = this.shadowRoot;
177
- const wrap = document.createElement("label");
273
+ const wrap = document.createElement("div");
178
274
  wrap.className = "wrap";
275
+ wrap.setAttribute("role", "checkbox");
276
+ wrap.setAttribute("part", "checkbox");
277
+ this._wrapEl = wrap;
179
278
  const box = document.createElement("span");
180
279
  box.className = "box";
181
280
  this._checkEl = document.createElement("span");
@@ -188,7 +287,7 @@ var BvCheckbox = class extends HTMLElement {
188
287
  const slot = document.createElement("slot");
189
288
  wrap.append(box, this._labelEl, slot);
190
289
  shadow.appendChild(wrap);
191
- wrap.addEventListener("click", () => {
290
+ const toggle = () => {
192
291
  if (this.hasAttribute("disabled")) return;
193
292
  this.checked = !this.checked;
194
293
  this.removeAttribute("indeterminate");
@@ -199,24 +298,40 @@ var BvCheckbox = class extends HTMLElement {
199
298
  detail: { checked: this.checked }
200
299
  })
201
300
  );
301
+ };
302
+ wrap.addEventListener("click", toggle);
303
+ wrap.addEventListener("keydown", (e) => {
304
+ if (e.key !== "Enter" && e.key !== " ") return;
305
+ if (e.key === " ") e.preventDefault();
306
+ toggle();
202
307
  });
308
+ this._syncAria();
203
309
  }
204
310
  _syncIcon() {
205
311
  if (!this._checkEl) return;
206
312
  this._checkEl.innerHTML = this.indeterminate ? INDETERMINATE_ICON : CHECK_ICON;
207
313
  }
314
+ _syncAria() {
315
+ if (!this._wrapEl) return;
316
+ const disabled = this.hasAttribute("disabled");
317
+ this._wrapEl.tabIndex = disabled ? -1 : 0;
318
+ this._wrapEl.setAttribute("aria-checked", this.indeterminate ? "mixed" : String(this.checked));
319
+ if (disabled) this._wrapEl.setAttribute("aria-disabled", "true");
320
+ else this._wrapEl.removeAttribute("aria-disabled");
321
+ }
208
322
  };
209
323
  if (!customElements.get("bv-checkbox")) {
210
324
  customElements.define("bv-checkbox", BvCheckbox);
211
325
  }
212
326
 
213
327
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-toggle/bv-toggle.css
214
- var bv_toggle_default = ":host {\n --bv-toggle-width: 44px;\n --bv-toggle-height: 24px;\n --bv-toggle-track-off: var(--bv-color-neutral-300);\n --bv-toggle-track-on: var(--bv-color-primary);\n --bv-toggle-thumb: var(--bv-color-neutral-0);\n display: inline-flex;\n font-family: var(--bv-font-family-base);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n:host([size='sm']) {\n --bv-toggle-width: 32px;\n --bv-toggle-height: 18px;\n}\n\n.wrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n user-select: none;\n}\n\n:host([disabled]) .wrap {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.track {\n position: relative;\n width: var(--bv-toggle-width);\n height: var(--bv-toggle-height);\n border-radius: var(--bv-radius-full);\n background: var(--bv-toggle-track-off);\n transition: background var(--bv-transition-fast);\n flex-shrink: 0;\n}\n\n:host([checked]) .track {\n background: var(--bv-toggle-track-on);\n}\n\n.thumb {\n position: absolute;\n top: 3px;\n left: 3px;\n width: calc(var(--bv-toggle-height) - 6px);\n height: calc(var(--bv-toggle-height) - 6px);\n border-radius: 50%;\n background: var(--bv-toggle-thumb);\n box-shadow: var(--bv-shadow-sm);\n transition: transform var(--bv-transition-fast);\n}\n\n:host([checked]) .thumb {\n transform: translateX(calc(var(--bv-toggle-width) - var(--bv-toggle-height)));\n}\n\n.label {\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n line-height: 20px;\n}\n\n:host([size='sm']) .label {\n font-size: var(--bv-font-size-xs);\n line-height: 18px;\n}\n";
328
+ var bv_toggle_default = ":host {\n --bv-toggle-width: 44px;\n --bv-toggle-height: 24px;\n --bv-toggle-track-off: var(--bv-color-neutral-300);\n --bv-toggle-track-on: var(--bv-color-primary);\n --bv-toggle-thumb: var(--bv-color-neutral-0);\n display: inline-flex;\n font-family: var(--bv-font-family-base);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n:host([size='sm']) {\n --bv-toggle-width: 32px;\n --bv-toggle-height: 18px;\n}\n\n.wrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\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.track {\n position: relative;\n width: var(--bv-toggle-width);\n height: var(--bv-toggle-height);\n border-radius: var(--bv-radius-full);\n background: var(--bv-toggle-track-off);\n transition: background var(--bv-transition-fast);\n flex-shrink: 0;\n}\n\n:host([checked]) .track {\n background: var(--bv-toggle-track-on);\n}\n\n.thumb {\n position: absolute;\n top: 3px;\n left: 3px;\n width: calc(var(--bv-toggle-height) - 6px);\n height: calc(var(--bv-toggle-height) - 6px);\n border-radius: 50%;\n background: var(--bv-toggle-thumb);\n box-shadow: var(--bv-shadow-sm);\n transition: transform var(--bv-transition-fast);\n}\n\n:host([checked]) .thumb {\n transform: translateX(calc(var(--bv-toggle-width) - var(--bv-toggle-height)));\n}\n\n.label {\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n line-height: 20px;\n}\n\n:host([size='sm']) .label {\n font-size: var(--bv-font-size-xs);\n line-height: 18px;\n}\n";
215
329
 
216
330
  // components/bv-toggle/bv-toggle.ts
217
331
  var BvToggle = class extends HTMLElement {
218
332
  static observedAttributes = ["checked", "disabled", "label", "size"];
219
333
  _rendered = false;
334
+ _wrapEl = null;
220
335
  _labelEl = null;
221
336
  constructor() {
222
337
  super();
@@ -227,6 +342,7 @@ var BvToggle = class extends HTMLElement {
227
342
  }
228
343
  attributeChangedCallback(name, _old, val) {
229
344
  if (!this._rendered) return;
345
+ if (name === "checked" || name === "disabled") this._syncAria();
230
346
  if (name === "label" && this._labelEl) this._labelEl.textContent = val ?? "";
231
347
  }
232
348
  get checked() {
@@ -236,12 +352,32 @@ var BvToggle = class extends HTMLElement {
236
352
  if (v) this.setAttribute("checked", "");
237
353
  else this.removeAttribute("checked");
238
354
  }
355
+ /**
356
+ * Focus the toggle. The focusable element is the internal `.wrap`, not the
357
+ * host, so `hostEl.focus()` would otherwise be a silent no-op. Consumers that
358
+ * rebuild the panel a toggle lives in (bv-input-dropdown's `toggle-label`,
359
+ * bv-search-picker's `inactive-toggle-label`) need this to restore focus
360
+ * after the rebuild without reaching into this component's shadow root.
361
+ *
362
+ * No-op before the first render, and when disabled — `tabindex="-1"` only
363
+ * removes the element from Tab order, it does not block programmatic focus,
364
+ * so a restore pass could otherwise land focus on a control that was
365
+ * disabled mid-rebuild.
366
+ */
367
+ focus(options) {
368
+ var _a;
369
+ if (this.hasAttribute("disabled")) return;
370
+ (_a = this._wrapEl) == null ? void 0 : _a.focus(options);
371
+ }
239
372
  _render() {
240
373
  if (this._rendered) return;
241
374
  this._rendered = true;
242
375
  const shadow = this.shadowRoot;
243
- const wrap = document.createElement("label");
376
+ const wrap = document.createElement("div");
244
377
  wrap.className = "wrap";
378
+ wrap.setAttribute("role", "switch");
379
+ wrap.setAttribute("part", "toggle");
380
+ this._wrapEl = wrap;
245
381
  const track = document.createElement("span");
246
382
  track.className = "track";
247
383
  const thumb = document.createElement("span");
@@ -253,7 +389,7 @@ var BvToggle = class extends HTMLElement {
253
389
  const slot = document.createElement("slot");
254
390
  wrap.append(track, this._labelEl, slot);
255
391
  shadow.appendChild(wrap);
256
- wrap.addEventListener("click", () => {
392
+ const toggle = () => {
257
393
  if (this.hasAttribute("disabled")) return;
258
394
  this.checked = !this.checked;
259
395
  this.dispatchEvent(
@@ -263,7 +399,22 @@ var BvToggle = class extends HTMLElement {
263
399
  detail: { checked: this.checked }
264
400
  })
265
401
  );
402
+ };
403
+ wrap.addEventListener("click", toggle);
404
+ wrap.addEventListener("keydown", (e) => {
405
+ if (e.key !== "Enter" && e.key !== " ") return;
406
+ if (e.key === " ") e.preventDefault();
407
+ toggle();
266
408
  });
409
+ this._syncAria();
410
+ }
411
+ _syncAria() {
412
+ if (!this._wrapEl) return;
413
+ const disabled = this.hasAttribute("disabled");
414
+ this._wrapEl.tabIndex = disabled ? -1 : 0;
415
+ this._wrapEl.setAttribute("aria-checked", String(this.checked));
416
+ if (disabled) this._wrapEl.setAttribute("aria-disabled", "true");
417
+ else this._wrapEl.removeAttribute("aria-disabled");
267
418
  }
268
419
  };
269
420
  if (!customElements.get("bv-toggle")) {
@@ -272,6 +423,10 @@ if (!customElements.get("bv-toggle")) {
272
423
 
273
424
  // components/bv-input-dropdown/bv-input-dropdown.ts
274
425
  var CHEVRON_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>`;
426
+ var DEFAULT_NO_RESULTS = "No results found";
427
+ var DEFAULT_NO_RESULTS_HINT = "Try refining your search";
428
+ var DEFAULT_EMPTY = "Nothing here yet";
429
+ var DEFAULT_EMPTY_HINT = "Once you create one, it\u2019ll appear here";
275
430
  var BvInputDropdown = class extends HTMLElement {
276
431
  static observedAttributes = [
277
432
  "label",
@@ -283,8 +438,13 @@ var BvInputDropdown = class extends HTMLElement {
283
438
  "search",
284
439
  "select-all",
285
440
  "select-all-state",
441
+ "tree",
286
442
  "toggle-label",
287
443
  "toggle-checked",
444
+ "label-no-results",
445
+ "label-no-results-hint",
446
+ "label-empty",
447
+ "label-empty-hint",
288
448
  "open"
289
449
  ];
290
450
  _rendered = false;
@@ -296,7 +456,16 @@ var BvInputDropdown = class extends HTMLElement {
296
456
  _selectAllEl = null;
297
457
  _toggleEl = null;
298
458
  _errorEl = null;
459
+ _slotEl = null;
460
+ _optionsEl = null;
461
+ _emptyEl = null;
462
+ _emptyTitleEl = null;
463
+ _emptyHintEl = null;
299
464
  _dropdown = null;
465
+ _rowObserver = null;
466
+ /** Last query seen from the internal search box. Drives the copy choice. */
467
+ _query = "";
468
+ _emptyState = "none";
300
469
  constructor() {
301
470
  super();
302
471
  adoptStyles(this.attachShadow({ mode: "open" }), bv_input_dropdown_default);
@@ -323,19 +492,88 @@ var BvInputDropdown = class extends HTMLElement {
323
492
  }
324
493
  connectedCallback() {
325
494
  this._render();
495
+ this._bindLiveWiring();
326
496
  }
327
497
  disconnectedCallback() {
498
+ var _a, _b;
499
+ (_a = this._rowObserver) == null ? void 0 : _a.disconnect();
500
+ this._rowObserver = null;
501
+ (_b = this._dropdown) == null ? void 0 : _b.cleanup();
502
+ this._dropdown = null;
503
+ }
504
+ /**
505
+ * Everything that has to be torn down on detach and stood back up on
506
+ * attach, as opposed to the DOM, which is built once.
507
+ *
508
+ * `_render` returns early on its `_rendered` guard, so anything bound inside
509
+ * it is bound exactly once in the element's lifetime — which is correct for
510
+ * the DOM and wrong for a listener that `disconnectedCallback` just removed.
511
+ * A detached and re-attached dropdown came back with no row observer and a
512
+ * dropdown controller whose `cleanup()` had already run: the dashes stopped
513
+ * repainting and Escape and outside-click stopped closing the panel.
514
+ *
515
+ * The controller half predates BTN-11485 — the null check did not catch it
516
+ * because the object was still there, emptied out. Raised by Copilot on
517
+ * PR #49 against the observer; the controller came out with it.
518
+ *
519
+ * Idempotent, because `connectedCallback` can fire more than once and this
520
+ * must not stack listeners.
521
+ */
522
+ _bindLiveWiring() {
328
523
  var _a;
329
- (_a = this._dropdown) == null ? void 0 : _a.cleanup();
524
+ if (!this._rendered) return;
525
+ const trigger = (_a = this.shadowRoot) == null ? void 0 : _a.querySelector(".trigger");
526
+ if (trigger && this._dropdown === null) {
527
+ this._dropdown = bindDropdownController(this, { trigger });
528
+ }
529
+ this._syncRowObserver();
530
+ }
531
+ /**
532
+ * The row observer runs only while tree mode does.
533
+ *
534
+ * It exists to repaint the dash when a consumer rewrites `checked` on rows
535
+ * already in the slot — which no other consumer of this component needs.
536
+ * Binding it unconditionally made every ordinary checkbox toggle, in every
537
+ * dropdown in the app, pay for a feature it does not use. Raised by Copilot
538
+ * on PR #49.
539
+ *
540
+ * `attributeFilter: ['checked']` is load-bearing, not tidiness: the handler
541
+ * writes `indeterminate`, and watching all attributes would feed those
542
+ * writes straight back in — BTN-11850 with different attributes. Filtering
543
+ * to the one attribute we never write makes the feedback edge unreachable
544
+ * rather than merely unlikely, and `npm run test:observers` holds it shut.
545
+ */
546
+ _syncRowObserver() {
547
+ var _a;
548
+ if (!this._rendered) return;
549
+ if (!this._isTree) {
550
+ (_a = this._rowObserver) == null ? void 0 : _a.disconnect();
551
+ this._rowObserver = null;
552
+ return;
553
+ }
554
+ if (this._rowObserver !== null) return;
555
+ this._rowObserver = new MutationObserver(() => this._syncTreeStates());
556
+ this._rowObserver.observe(this, {
557
+ attributes: true,
558
+ attributeFilter: ["checked"],
559
+ subtree: true
560
+ });
330
561
  }
331
562
  attributeChangedCallback(name, _old, _val) {
332
563
  if (!this._rendered) return;
333
564
  if (name === "label" || name === "required") this._syncLabel();
565
+ if (name === "tree") {
566
+ if (!this._isTree) for (const el of this._treeElements()) el.removeAttribute("indeterminate");
567
+ this._syncRowObserver();
568
+ this._syncTreeStates();
569
+ }
334
570
  if (name === "value" || name === "placeholder") this._syncTriggerLabel();
335
571
  if (name === "error") this._syncError();
336
572
  if (name === "search") this._syncSearch();
337
573
  if (name === "select-all" || name === "select-all-state" || name === "toggle-label" || name === "toggle-checked")
338
574
  this._syncToolbar();
575
+ if (name === "label-no-results" || name === "label-no-results-hint" || name === "label-empty" || name === "label-empty-hint")
576
+ this._paintEmptyState();
339
577
  }
340
578
  _render() {
341
579
  if (this._rendered) return;
@@ -371,13 +609,16 @@ var BvInputDropdown = class extends HTMLElement {
371
609
  searchEl.setAttribute("placeholder", "Search...");
372
610
  searchEl.addEventListener("bv-search", (e) => {
373
611
  e.stopPropagation();
612
+ const detail = e.detail;
613
+ this._query = (detail == null ? void 0 : detail.value) ?? "";
374
614
  this.dispatchEvent(
375
615
  new CustomEvent("bv-search", {
376
616
  bubbles: true,
377
617
  composed: true,
378
- detail: e.detail
618
+ detail
379
619
  })
380
620
  );
621
+ this._syncEmptyState();
381
622
  });
382
623
  this._searchEl = searchEl;
383
624
  const toolbar = document.createElement("div");
@@ -413,8 +654,29 @@ var BvInputDropdown = class extends HTMLElement {
413
654
  header.append(searchEl, toolbar);
414
655
  const content = document.createElement("div");
415
656
  content.className = "panel-content";
657
+ const options = document.createElement("div");
658
+ options.className = "panel-options";
416
659
  const slot = document.createElement("slot");
417
- content.appendChild(slot);
660
+ slot.addEventListener("slotchange", () => {
661
+ this._syncEmptyState();
662
+ this._syncTreeStates();
663
+ });
664
+ options.appendChild(slot);
665
+ this._slotEl = slot;
666
+ this._optionsEl = options;
667
+ const empty = document.createElement("div");
668
+ empty.className = "empty-state";
669
+ empty.setAttribute("role", "status");
670
+ empty.hidden = true;
671
+ const emptyTitle = document.createElement("span");
672
+ emptyTitle.className = "empty-title";
673
+ const emptyHint = document.createElement("span");
674
+ emptyHint.className = "empty-hint";
675
+ empty.append(emptyTitle, emptyHint);
676
+ this._emptyEl = empty;
677
+ this._emptyTitleEl = emptyTitle;
678
+ this._emptyHintEl = emptyHint;
679
+ content.append(options, empty);
418
680
  panel.append(header, content);
419
681
  const errorEl = document.createElement("span");
420
682
  errorEl.className = "field-error";
@@ -424,8 +686,113 @@ var BvInputDropdown = class extends HTMLElement {
424
686
  this._syncTriggerLabel();
425
687
  this._syncError();
426
688
  this._syncSearch();
689
+ this._emptyState = this._resolveEmptyState();
690
+ this._paintEmptyState();
427
691
  this._syncToolbar();
428
- this._dropdown = bindDropdownController(this, { trigger });
692
+ this.addEventListener("bv-change", (e) => this._onRowChange(e));
693
+ this._syncTreeStates();
694
+ }
695
+ /** Whether hierarchical selection is on. */
696
+ get _isTree() {
697
+ return this.hasAttribute("tree");
698
+ }
699
+ /**
700
+ * Every slotted element carrying `data-value`, in document order.
701
+ *
702
+ * Descendants count, not just direct slot children. Consumers disagree about
703
+ * markup — the CET Report renders one flat `*ngFor` and expresses depth as
704
+ * padding, while this component's own Tree view story nests rows in wrapper
705
+ * divs — and both are legitimate. That disagreement is the whole reason the
706
+ * hierarchy travels in `data-parent` rather than in the DOM shape: read the
707
+ * relationships from the attributes and the markup stops mattering.
708
+ */
709
+ _treeElements() {
710
+ var _a;
711
+ const out = [];
712
+ for (const el of ((_a = this._slotEl) == null ? void 0 : _a.assignedElements()) ?? []) {
713
+ if (el.hasAttribute("data-value")) out.push(el);
714
+ for (const nested of el.querySelectorAll("[data-value]")) out.push(nested);
715
+ }
716
+ return out;
717
+ }
718
+ /**
719
+ * The slotted rows, as the pure helper wants them.
720
+ *
721
+ * Only elements carrying `data-value` take part; anything else a consumer
722
+ * slots in (a separator, a heading) is ignored rather than treated as a
723
+ * root. `data-parent` empty or absent means root.
724
+ *
725
+ * A branch reaches only what the consumer rendered, and that is the intended
726
+ * rule rather than a side effect of reading the DOM. A row the consumer
727
+ * withheld — a group hidden behind Show Inactive, say — is withheld on
728
+ * purpose, and a click has no business reaching past that decision into
729
+ * something the user has asked not to see. Confirmed as correct with the
730
+ * team on 2026-09-18 and pinned by a test, because it currently *follows*
731
+ * from reading rendered rows: give this component the whole tree some other
732
+ * way and the behaviour would reverse with nothing to catch it.
733
+ */
734
+ _treeRows() {
735
+ const rows = [];
736
+ for (const el of this._treeElements()) {
737
+ const value = el.getAttribute("data-value");
738
+ if (value === null || value === "") continue;
739
+ const parent = el.getAttribute("data-parent");
740
+ rows.push({
741
+ value,
742
+ parent: parent === null || parent === "" ? null : parent,
743
+ checked: el.hasAttribute("checked")
744
+ });
745
+ }
746
+ return rows;
747
+ }
748
+ /**
749
+ * Paint the dash on every partially selected node.
750
+ *
751
+ * `indeterminate` is the one piece of row state the component owns.
752
+ * `checked` belongs to the consumer — in Angular it is an `[attr.checked]`
753
+ * binding, so anything written here would be overwritten on the next change
754
+ * detection pass. Nothing binds `indeterminate`, so this is ours to keep
755
+ * correct, and it is what makes a check become a dash without the consumer
756
+ * computing anything.
757
+ */
758
+ _syncTreeStates() {
759
+ if (!this._rendered || !this._isTree) return;
760
+ const elements = this._treeElements();
761
+ const states = computeStates(this._treeRows());
762
+ for (const el of elements) {
763
+ const value = el.getAttribute("data-value");
764
+ if (value === null || value === "") continue;
765
+ if (states.get(value) === "mixed") el.setAttribute("indeterminate", "");
766
+ else el.removeAttribute("indeterminate");
767
+ }
768
+ }
769
+ /**
770
+ * A slotted row reported a change.
771
+ *
772
+ * The row has already flipped its own `checked` by the time this runs, and
773
+ * that value is deliberately ignored: a partially selected parent must FILL
774
+ * its branch, and its own checkbox may have been sitting either way. The
775
+ * answer comes from the tree instead — see `resolveClick`.
776
+ */
777
+ _onRowChange(e) {
778
+ if (!this._isTree) return;
779
+ const target = e.target;
780
+ if (!(target instanceof Element)) return;
781
+ const value = target.getAttribute("data-value");
782
+ if (value === null || value === "") return;
783
+ e.stopPropagation();
784
+ const rows = this._treeRows().map(
785
+ (row) => row.value === value ? { ...row, checked: !row.checked } : row
786
+ );
787
+ const branch = this._query.trim() === "";
788
+ const { affected, checked } = resolveClick(rows, value, branch);
789
+ this.dispatchEvent(
790
+ new CustomEvent("bv-tree-select", {
791
+ bubbles: true,
792
+ composed: true,
793
+ detail: { value, checked, affected }
794
+ })
795
+ );
429
796
  }
430
797
  _syncLabel() {
431
798
  if (!this._labelEl) return;
@@ -462,10 +829,47 @@ var BvInputDropdown = class extends HTMLElement {
462
829
  if (!this._searchEl) return;
463
830
  this._searchEl.hidden = !this.hasAttribute("search");
464
831
  this._updateHeaderVisibility();
832
+ if (!this.hasAttribute("search")) this._query = "";
833
+ this._syncEmptyState();
834
+ }
835
+ _resolveEmptyState() {
836
+ if (!this._slotEl) return "none";
837
+ if (this._slotEl.assignedElements().length > 0) return "none";
838
+ const hasQuery = this.hasAttribute("search") && this._query !== "";
839
+ return hasQuery ? "no-results" : "empty";
840
+ }
841
+ /**
842
+ * Hot path: CET Report rebuilds the whole option list on every checkbox
843
+ * toggle (the same behavior that forced `composedPath()` outside-click
844
+ * detection in BTN-10274), so this runs on every tick, not only when the
845
+ * list genuinely empties. It stays a length check plus a comparison and
846
+ * returns before touching the DOM when the state has not flipped — the
847
+ * toolbar and header are never re-rendered under the user's focus for a
848
+ * change that did not happen.
849
+ */
850
+ _syncEmptyState() {
851
+ const next = this._resolveEmptyState();
852
+ if (next === this._emptyState) return;
853
+ this._emptyState = next;
854
+ this._paintEmptyState();
855
+ this._syncToolbar();
856
+ }
857
+ _paintEmptyState() {
858
+ if (!this._emptyEl || !this._emptyTitleEl || !this._emptyHintEl || !this._optionsEl) return;
859
+ const isEmpty = this._emptyState !== "none";
860
+ this._optionsEl.hidden = isEmpty;
861
+ this._emptyEl.hidden = !isEmpty;
862
+ if (!isEmpty) return;
863
+ const noResults = this._emptyState === "no-results";
864
+ const title = noResults ? this.getAttribute("label-no-results") || DEFAULT_NO_RESULTS : this.getAttribute("label-empty") || DEFAULT_EMPTY;
865
+ const hint = noResults ? this.getAttribute("label-no-results-hint") || DEFAULT_NO_RESULTS_HINT : this.getAttribute("label-empty-hint") || DEFAULT_EMPTY_HINT;
866
+ this._emptyTitleEl.textContent = title;
867
+ this._emptyHintEl.textContent = hint;
868
+ this._emptyHintEl.hidden = !hint;
465
869
  }
466
870
  _syncToolbar() {
467
871
  if (!this._toolbarEl || !this._selectAllEl || !this._toggleEl) return;
468
- const showSelectAll = this.hasAttribute("select-all");
872
+ const showSelectAll = this.hasAttribute("select-all") && this._emptyState === "none";
469
873
  const state = this.getAttribute("select-all-state") ?? "unchecked";
470
874
  const toggleLabel = this.getAttribute("toggle-label") ?? "";
471
875
  this._selectAllEl.hidden = !showSelectAll;