@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.
- package/README.md +1 -1
- package/dist/components/_shared/attribute-dispatch.d.ts +44 -0
- package/dist/components/_shared/tree-selection.d.ts +67 -0
- package/dist/components/bv-alert-banner/bv-alert-banner.js +1 -1
- package/dist/components/bv-avatar/bv-avatar.js +1 -1
- package/dist/components/bv-badge/bv-badge.js +1 -1
- package/dist/components/bv-button/bv-button.js +8 -6
- package/dist/components/bv-category-pill/bv-category-pill.js +1 -1
- package/dist/components/bv-checkbox/bv-checkbox.d.ts +15 -1
- package/dist/components/bv-checkbox/bv-checkbox.js +50 -5
- package/dist/components/bv-chip/bv-chip.js +1 -1
- package/dist/components/bv-course-detail/bv-course-detail.js +2 -2
- package/dist/components/bv-course-detail-modal/bv-course-detail-modal.js +26 -22
- package/dist/components/bv-course-grid-card/bv-course-grid-card.js +28 -21
- package/dist/components/bv-course-list-card/bv-course-list-card.js +28 -21
- package/dist/components/bv-course-outline/bv-course-outline.js +14 -12
- package/dist/components/bv-date-range-input/bv-date-range-input.js +3 -3
- package/dist/components/bv-date-range-picker/bv-date-range-picker.js +2 -2
- package/dist/components/bv-empty-state/bv-empty-state.js +21 -15
- package/dist/components/bv-filter-dropdown/bv-filter-dropdown.js +1 -1
- package/dist/components/bv-filter-panel/bv-filter-panel.d.ts +14 -0
- package/dist/components/bv-filter-panel/bv-filter-panel.js +87 -6
- package/dist/components/bv-icon/bv-icon.js +9 -7
- package/dist/components/bv-input/bv-input.js +1 -1
- package/dist/components/bv-input-dropdown/bv-input-dropdown.d.ts +123 -1
- package/dist/components/bv-input-dropdown/bv-input-dropdown.js +418 -14
- package/dist/components/bv-modal/bv-modal.js +1 -1
- package/dist/components/bv-page-title/bv-page-title.js +1 -1
- package/dist/components/bv-pagination/bv-pagination.js +1 -1
- package/dist/components/bv-portal-header/bv-portal-header.js +43 -37
- package/dist/components/bv-portal-layout/bv-portal-layout.js +1 -1
- package/dist/components/bv-progress-bar/bv-progress-bar.js +1 -1
- package/dist/components/bv-promo-banner/bv-promo-banner.js +1 -1
- package/dist/components/bv-radio/bv-radio.js +1 -1
- package/dist/components/bv-search-picker/bv-search-picker.d.ts +59 -0
- package/dist/components/bv-search-picker/bv-search-picker.js +175 -23
- package/dist/components/bv-segmented-control/bv-segmented-control.d.ts +13 -0
- package/dist/components/bv-segmented-control/bv-segmented-control.js +1 -1
- package/dist/components/bv-select/bv-select.js +1 -1
- package/dist/components/bv-split-button/bv-split-button.js +8 -6
- package/dist/components/bv-star-rating/bv-star-rating.js +1 -1
- package/dist/components/bv-table/bv-table.d.ts +50 -0
- package/dist/components/bv-table/bv-table.js +275 -34
- package/dist/components/bv-toggle/bv-toggle.d.ts +15 -0
- package/dist/components/bv-toggle/bv-toggle.js +40 -3
- package/dist/components/bv-user-row/bv-user-row.js +22 -15
- package/dist/components/bv-view-switcher/bv-view-switcher.js +1 -1
- package/dist/themes/base.css +0 -5
- package/package.json +11 -8
- 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:
|
|
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 = [
|
|
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")
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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
|
-
(
|
|
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
|
|
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
|
-
|
|
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.
|
|
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;
|