@bluevolt-tech/lumen 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/_shared/date-range-validator.d.ts +26 -1
- package/dist/components/_shared/dropdown-controller.d.ts +45 -0
- package/dist/components/bv-date-range-input/bv-date-range-input.d.ts +7 -1
- package/dist/components/bv-date-range-input/bv-date-range-input.js +241 -96
- package/dist/components/bv-date-range-picker/bv-date-range-picker.d.ts +61 -8
- package/dist/components/bv-date-range-picker/bv-date-range-picker.js +180 -81
- package/dist/components/bv-filter-dropdown/bv-filter-dropdown.js +18 -10
- package/dist/components/bv-input-dropdown/bv-input-dropdown.js +18 -10
- package/dist/components/bv-search-picker/bv-search-picker.d.ts +222 -0
- package/dist/components/bv-search-picker/bv-search-picker.js +1458 -0
- package/dist/components/bv-segmented-control/bv-segmented-control.d.ts +7 -1
- package/dist/components/bv-segmented-control/bv-segmented-control.js +21 -2
- package/dist/components/bv-select/bv-select.js +18 -10
- package/dist/components/bv-split-button/bv-split-button.js +18 -10
- package/dist/components/bv-table/bv-table.js +1 -0
- package/dist/components/bv-tooltip/bv-tooltip.js +1 -0
- package/dist/storybook/stories/screens/cet-report.data.js +834 -31
- package/dist/themes/base.css +1 -1
- package/package.json +5 -1
- package/themes/base.css +1 -1
|
@@ -0,0 +1,1458 @@
|
|
|
1
|
+
// css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-search-picker/bv-search-picker.css
|
|
2
|
+
var bv_search_picker_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: 1.5;\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n margin-bottom: var(--bv-spacing-xs-plus);\n}\n\n.field-label:empty {\n display: none;\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 gap: var(--bv-spacing-xs);\n width: max-content;\n min-width: 120px;\n max-width: 220px;\n box-sizing: border-box;\n min-height: var(--bv-control-height-lg);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-md);\n background: var(--bv-color-neutral-0);\n padding: 5px var(--bv-spacing-sm);\n cursor: pointer;\n font-family: inherit;\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: 0 0 0 3px var(--bv-color-primary-light);\n}\n\n:host([open]) .trigger {\n border-color: var(--bv-color-primary);\n box-shadow: 0 0 0 3px var(--bv-color-primary-light);\n}\n\n:host([disabled]) .trigger {\n background: var(--bv-color-neutral-100);\n cursor: not-allowed;\n}\n\n.trigger-pill {\n display: inline-flex;\n align-items: center;\n gap: var(--bv-spacing-xs);\n height: 26px;\n /* Symmetric padding so the pill still looks balanced with the clear button\n hidden (count === 0) \u2014 an asymmetric right pad only reads correctly when\n the clear button is actually there to visually fill the extra space. */\n padding: 0 var(--bv-spacing-sm-plus);\n border-radius: var(--bv-radius-full);\n background: var(--bv-color-primary-light);\n color: var(--bv-color-primary-dark);\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n white-space: nowrap;\n}\n\n.trigger-pill.mode-exclude {\n background: var(--bv-color-danger-light);\n color: var(--bv-color-danger-dark);\n}\n\n.trigger-pill-clear {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: none;\n color: inherit;\n cursor: pointer;\n opacity: 0.7;\n}\n\n/* The `display` rule above wins over the UA's [hidden]{display:none} (author\n origin beats user-agent origin regardless of specificity) \u2014 restate it. */\n.trigger-pill-clear[hidden] {\n display: none;\n}\n\n.trigger-pill-clear:hover {\n /* Overlay derived from the neutral-900 token (matches the pattern\n bv-alert-banner uses for its close-btn hover \u2014 token + color-mix, no\n hardcoded rgba). Works over both include (primary-light) and exclude\n (danger-light) pill backgrounds. */\n background: color-mix(in srgb, var(--bv-color-neutral-900) 12%, transparent);\n opacity: 1;\n}\n\n/*\n * The pill-clear is a `span[role=\"button\"]` (not a native <button>) so that\n * it can nest inside the trigger button without producing invalid HTML.\n * Native <button> would give us a visible focus ring for free; role=\"button\"\n * doesn't, so paint one explicitly for keyboard users.\n */\n.trigger-pill-clear:focus-visible {\n outline: 2px solid var(--bv-color-primary);\n outline-offset: 1px;\n opacity: 1;\n}\n\n.trigger-caret {\n display: flex;\n align-items: center;\n margin-left: auto;\n color: var(--bv-color-neutral-500);\n flex-shrink: 0;\n transition: transform var(--bv-transition-fast);\n}\n\n:host([open]) .trigger-caret {\n transform: rotate(180deg);\n}\n\n/* \u2500\u2500 BTN-10322 \xB7 cascade cleared affordance \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.cleared-affordance {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n margin-top: var(--bv-spacing-xs-plus);\n padding: var(--bv-spacing-sm) var(--bv-spacing-md-minus);\n background: var(--bv-color-warning-light);\n color: var(--bv-color-warning-dark);\n border-radius: var(--bv-radius-md);\n font-size: var(--bv-font-size-xs);\n line-height: 1.4;\n}\n\n.cleared-affordance[hidden] {\n display: none;\n}\n\n.cleared-affordance-icon {\n display: inline-flex;\n align-items: center;\n color: var(--bv-color-warning);\n flex-shrink: 0;\n}\n\n.cleared-affordance-msg {\n flex: 1;\n}\n\n.cleared-affordance-dismiss {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: var(--bv-radius-full);\n background: none;\n color: inherit;\n cursor: pointer;\n opacity: 0.7;\n flex-shrink: 0;\n}\n\n.cleared-affordance-dismiss:hover {\n background: color-mix(in srgb, var(--bv-color-neutral-900) 12%, transparent);\n opacity: 1;\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 width: 680px;\n max-width: calc(100vw - var(--bv-spacing-md) * 2);\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([open]) .panel {\n display: flex;\n flex-direction: column;\n}\n\n/* Smart positioning \u2014 flips to whichever side has room (see _positionPanel\n in bv-search-picker.ts) instead of always overflowing past a screen edge. */\n:host([data-placement='top']) .panel {\n top: auto;\n bottom: calc(100% + 4px);\n}\n\n:host([data-align='right']) .panel {\n left: auto;\n right: 0;\n}\n\n.panel-toolbar {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n padding: var(--bv-spacing-sm-plus) var(--bv-spacing-md-minus);\n border-bottom: 1px solid var(--bv-color-neutral-200);\n background: var(--bv-color-neutral-100);\n}\n\n.panel-toolbar bv-toggle {\n margin-left: auto;\n flex-shrink: 0;\n}\n\n.panel-body {\n display: flex;\n min-height: 230px;\n}\n\n/* \u2500\u2500 Results pane \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.results-pane {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n border-right: 1px solid var(--bv-color-neutral-200);\n}\n\n/* bv-searchbox's :host is width:100%, so the inset has to be padding on this\n wrapper \u2014 a margin on the searchbox itself would push it past the pane's\n edge, since 100% resolves against the container regardless of the child's\n own margin. */\n.results-search {\n padding: var(--bv-spacing-sm);\n}\n\n.results-list {\n flex: 1;\n max-height: 190px;\n overflow-y: auto;\n}\n\n.result-row {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n padding: var(--bv-spacing-sm) var(--bv-spacing-md-minus);\n border-top: 1px solid var(--bv-color-neutral-100);\n cursor: pointer;\n}\n\n.result-row:hover {\n background: var(--bv-color-primary-50);\n}\n\n/* BTN-10322 \xB7 a11y \u2014 keyboard-focused row (Arrow Up/Down) gets a visible\n ring so a keyboard-only user can see where they are. `aria-selected`\n mirrors the focus position so screen readers announce it. */\n.result-row:focus-visible,\n.result-row[aria-selected='true'] {\n background: var(--bv-color-primary-50);\n outline: 2px solid var(--bv-color-primary);\n outline-offset: -2px;\n}\n\n.results-rows:focus-visible {\n outline: 2px solid var(--bv-color-primary);\n outline-offset: -2px;\n}\n\n.result-name {\n /* min-width:0 overrides the flex-item default of min-width:auto, which\n would otherwise refuse to shrink below the text's natural width and\n defeat the wrap/clamp below entirely. */\n min-width: 0;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n font-weight: var(--bv-font-weight-semibold);\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n overflow: hidden;\n white-space: normal;\n word-break: break-word;\n}\n\n.result-inactive {\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-500);\n flex-shrink: 0;\n}\n\n.result-add {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: none;\n color: var(--bv-color-primary);\n flex-shrink: 0;\n padding: 0;\n cursor: pointer;\n}\n\n.results-empty {\n padding: var(--bv-spacing-md-minus) var(--bv-spacing-md-minus);\n font-size: var(--bv-font-size-xs);\n color: var(--bv-color-neutral-400);\n line-height: 1.5;\n}\n\n.results-empty:empty,\n.results-empty[hidden] {\n display: none;\n}\n\n/* \u2500\u2500 Loading skeleton (mirrors bv-filter-panel's pattern) \u2500\u2500\u2500 */\n\n.skeleton {\n display: none;\n flex-direction: column;\n}\n\n:host([loading]) .skeleton {\n display: flex;\n}\n\n:host([loading]) .results-rows,\n:host([loading]) .results-empty {\n display: none;\n}\n\n.skeleton-row {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm-plus);\n padding: var(--bv-spacing-sm) var(--bv-spacing-md-minus);\n border-top: 1px solid var(--bv-color-neutral-100);\n}\n\n.skeleton-line {\n height: 11px;\n border-radius: var(--bv-radius-sm);\n background: var(--bv-color-neutral-200);\n animation: bv-skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n@keyframes bv-skeleton-shimmer {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n}\n\n/* \u2500\u2500 Basket pane \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.basket-pane {\n width: 210px;\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n background: var(--bv-color-neutral-100);\n}\n\n.basket-header {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n padding: var(--bv-spacing-sm-plus) var(--bv-spacing-md-minus);\n}\n\n.basket-header-label {\n font-size: var(--bv-font-size-xs);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-500);\n}\n\n.basket-clear-all {\n margin-left: auto;\n border: none;\n background: none;\n padding: 0;\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-primary);\n cursor: pointer;\n}\n\n.basket-clear-all:hover {\n color: var(--bv-color-primary-dark);\n text-decoration: underline;\n}\n\n.basket-clear-all[hidden] {\n display: none;\n}\n\n.basket-list {\n flex: 1;\n max-height: 190px;\n overflow-y: auto;\n padding: 0 var(--bv-spacing-sm) var(--bv-spacing-sm);\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-xs);\n}\n\n.basket-row {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-xs);\n padding: var(--bv-spacing-xs) var(--bv-spacing-sm);\n border-radius: var(--bv-radius-sm);\n background: var(--bv-color-primary-light);\n}\n\n.basket-row.mode-exclude {\n background: var(--bv-color-danger-light);\n}\n\n.basket-row-name {\n min-width: 0;\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n overflow: hidden;\n white-space: normal;\n word-break: break-word;\n}\n\n.basket-row-remove {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: none;\n color: var(--bv-color-neutral-500);\n flex-shrink: 0;\n padding: 0;\n cursor: pointer;\n}\n\n.basket-empty {\n padding: var(--bv-spacing-xs) var(--bv-spacing-2xs);\n font-size: var(--bv-font-size-xs);\n color: var(--bv-color-neutral-400);\n line-height: 1.5;\n}\n\n/* \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.panel-footer {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n padding: var(--bv-spacing-sm-plus) var(--bv-spacing-md-minus);\n border-top: 1px solid var(--bv-color-neutral-200);\n background: var(--bv-color-neutral-0);\n}\n\n.panel-footer bv-button {\n margin-left: auto;\n flex-shrink: 0;\n}\n\n.results-limit-note {\n font-size: var(--bv-font-size-xs);\n color: var(--bv-color-neutral-500);\n line-height: 1.4;\n}\n";
|
|
3
|
+
|
|
4
|
+
// components/_shared/adopt-styles.ts
|
|
5
|
+
var _sheetCache = /* @__PURE__ */ new Map();
|
|
6
|
+
function adoptStyles(shadow, css) {
|
|
7
|
+
if ("adoptedStyleSheets" in ShadowRoot.prototype && typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype) {
|
|
8
|
+
let sheet = _sheetCache.get(css);
|
|
9
|
+
if (!sheet) {
|
|
10
|
+
sheet = new CSSStyleSheet();
|
|
11
|
+
sheet.replaceSync(css);
|
|
12
|
+
_sheetCache.set(css, sheet);
|
|
13
|
+
}
|
|
14
|
+
shadow.adoptedStyleSheets = [sheet];
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const style = document.createElement("style");
|
|
18
|
+
style.textContent = css;
|
|
19
|
+
shadow.appendChild(style);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// components/_shared/dropdown-controller.ts
|
|
23
|
+
function bindDropdownController(host, options = {}) {
|
|
24
|
+
if (!host.shadowRoot) {
|
|
25
|
+
throw new Error("bindDropdownController requires the host to have an open shadow root.");
|
|
26
|
+
}
|
|
27
|
+
const syncExpanded = () => {
|
|
28
|
+
var _a;
|
|
29
|
+
(_a = options.trigger) == null ? void 0 : _a.setAttribute("aria-expanded", host.hasAttribute("open") ? "true" : "false");
|
|
30
|
+
};
|
|
31
|
+
const open = () => {
|
|
32
|
+
var _a;
|
|
33
|
+
if (host.hasAttribute("open")) return;
|
|
34
|
+
(_a = options.onBeforeOpen) == null ? void 0 : _a.call(options);
|
|
35
|
+
host.setAttribute("open", "");
|
|
36
|
+
syncExpanded();
|
|
37
|
+
host.dispatchEvent(new CustomEvent("bv-open", { bubbles: true, composed: true }));
|
|
38
|
+
};
|
|
39
|
+
const closeWithReason = (reason) => {
|
|
40
|
+
var _a;
|
|
41
|
+
if (!host.hasAttribute("open")) return;
|
|
42
|
+
if (options.canClose && !options.canClose(reason)) return;
|
|
43
|
+
host.removeAttribute("open");
|
|
44
|
+
syncExpanded();
|
|
45
|
+
host.dispatchEvent(
|
|
46
|
+
new CustomEvent("bv-dropdown-close", {
|
|
47
|
+
bubbles: true,
|
|
48
|
+
composed: true,
|
|
49
|
+
detail: { reason }
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
(_a = options.onAfterClose) == null ? void 0 : _a.call(options);
|
|
53
|
+
};
|
|
54
|
+
const close = () => closeWithReason("programmatic");
|
|
55
|
+
const toggle = () => {
|
|
56
|
+
if (host.hasAttribute("open")) closeWithReason("programmatic");
|
|
57
|
+
else open();
|
|
58
|
+
};
|
|
59
|
+
const offClick = (e) => {
|
|
60
|
+
if (!host.hasAttribute("open")) return;
|
|
61
|
+
if (e.composedPath().includes(host)) return;
|
|
62
|
+
closeWithReason("outside-click");
|
|
63
|
+
};
|
|
64
|
+
document.addEventListener("click", offClick);
|
|
65
|
+
const onKeyDown = (e) => {
|
|
66
|
+
if (e.key === "Escape") closeWithReason("escape");
|
|
67
|
+
};
|
|
68
|
+
host.addEventListener("keydown", onKeyDown);
|
|
69
|
+
const observer = new MutationObserver(syncExpanded);
|
|
70
|
+
observer.observe(host, { attributes: true, attributeFilter: ["open"] });
|
|
71
|
+
const cleanup = () => {
|
|
72
|
+
observer.disconnect();
|
|
73
|
+
document.removeEventListener("click", offClick);
|
|
74
|
+
host.removeEventListener("keydown", onKeyDown);
|
|
75
|
+
};
|
|
76
|
+
syncExpanded();
|
|
77
|
+
return { open, close, toggle, cleanup };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// components/bv-icon/icons.ts
|
|
81
|
+
var icons = {
|
|
82
|
+
// Navigation
|
|
83
|
+
"chevron-up": `<path d="M18 15l-6-6-6 6"/>`,
|
|
84
|
+
"chevron-down": `<path d="M6 9l6 6 6-6"/>`,
|
|
85
|
+
"chevron-left": `<path d="M15 18l-6-6 6-6"/>`,
|
|
86
|
+
"chevron-right": `<path d="M9 18l6-6-6-6"/>`,
|
|
87
|
+
"arrow-left": `<path d="M19 12H5M12 19l-7-7 7-7"/>`,
|
|
88
|
+
"arrow-right": `<path d="M5 12h14M12 5l7 7-7 7"/>`,
|
|
89
|
+
"external-link": `<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15,3 21,3 21,9"/><line x1="10" y1="14" x2="21" y2="3"/>`,
|
|
90
|
+
// Actions
|
|
91
|
+
search: `<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>`,
|
|
92
|
+
edit: `<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>`,
|
|
93
|
+
download: `<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7,10 12,15 17,10"/><line x1="12" y1="15" x2="12" y2="3"/>`,
|
|
94
|
+
upload: `<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17,8 12,3 7,8"/><line x1="12" y1="3" x2="12" y2="15"/>`,
|
|
95
|
+
plus: `<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>`,
|
|
96
|
+
minus: `<line x1="5" y1="12" x2="19" y2="12"/>`,
|
|
97
|
+
x: `<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>`,
|
|
98
|
+
check: `<polyline points="20,6 9,17 4,12"/>`,
|
|
99
|
+
trash: `<polyline points="3,6 5,6 21,6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>`,
|
|
100
|
+
copy: `<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>`,
|
|
101
|
+
refresh: `<polyline points="23,4 23,10 17,10"/><polyline points="1,20 1,14 7,14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>`,
|
|
102
|
+
filter: `<polygon points="22,3 2,3 10,12.46 10,19 14,21 14,12.46 22,3"/>`,
|
|
103
|
+
// Layout
|
|
104
|
+
list: `<line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>`,
|
|
105
|
+
grid: `<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>`,
|
|
106
|
+
// User / profile
|
|
107
|
+
user: `<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>`,
|
|
108
|
+
users: `<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>`,
|
|
109
|
+
"user-pen": `<path d="M11.5 15H7a4 4 0 0 0-4 4v2"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/><circle cx="10" cy="7" r="4"/>`,
|
|
110
|
+
// Content
|
|
111
|
+
star: `<polygon points="12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26 12,2"/>`,
|
|
112
|
+
eye: `<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>`,
|
|
113
|
+
"eye-off": `<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>`,
|
|
114
|
+
bookmark: `<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>`,
|
|
115
|
+
tag: `<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>`,
|
|
116
|
+
// Status / feedback
|
|
117
|
+
info: `<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>`,
|
|
118
|
+
"alert-triangle": `<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>`,
|
|
119
|
+
"check-circle": `<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22,4 12,14.01 9,11.01"/>`,
|
|
120
|
+
"x-circle": `<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>`,
|
|
121
|
+
// Files / media
|
|
122
|
+
file: `<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13,2 13,9 20,9"/>`,
|
|
123
|
+
image: `<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21,15 16,10 5,21"/>`,
|
|
124
|
+
video: `<polygon points="23,7 16,12 23,17 23,7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>`,
|
|
125
|
+
// Settings
|
|
126
|
+
settings: `<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>`,
|
|
127
|
+
lock: `<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>`,
|
|
128
|
+
unlock: `<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>`,
|
|
129
|
+
bell: `<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>`,
|
|
130
|
+
mail: `<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/>`,
|
|
131
|
+
link: `<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>`
|
|
132
|
+
};
|
|
133
|
+
function getIconSvg(name, size = 16) {
|
|
134
|
+
const raw = rawIcons[name];
|
|
135
|
+
if (raw)
|
|
136
|
+
return raw.replace(/width="[^"]*"/, `width="${size}"`).replace(/height="[^"]*"/, `height="${size}"`);
|
|
137
|
+
const paths = icons[name];
|
|
138
|
+
if (!paths) return "";
|
|
139
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
|
|
140
|
+
}
|
|
141
|
+
var rawIcons = {
|
|
142
|
+
// Module type (small, ~18px)
|
|
143
|
+
"module-scorm": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 15.03 16.28" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.9014 4.60034L7.51368 8.14201M7.51368 8.14201L1.126 4.60034M7.51368 8.14201L7.5137 15.267M14.2771 11.1859V5.09812C14.2771 4.84113 14.2771 4.71264 14.2392 4.59804C14.2056 4.49666 14.1508 4.4036 14.0783 4.32508C13.9963 4.23633 13.8838 4.17393 13.6587 4.04913L8.09763 0.965793C7.88451 0.847624 7.77794 0.78854 7.66509 0.765376C7.56521 0.744875 7.46219 0.744875 7.36231 0.765376C7.24946 0.78854 7.1429 0.847624 6.92977 0.965793L1.36873 4.04913C1.14364 4.17393 1.0311 4.23633 0.949147 4.32508C0.876647 4.4036 0.82178 4.49666 0.788217 4.59804C0.750278 4.71264 0.750278 4.84113 0.750278 5.09812V11.1859C0.750278 11.4429 0.750278 11.5714 0.788217 11.686C0.82178 11.7874 0.876647 11.8805 0.949147 11.959C1.0311 12.0477 1.14364 12.1101 1.36873 12.2349L6.92977 15.3183C7.1429 15.4364 7.24946 15.4955 7.36231 15.5187C7.46219 15.5392 7.56521 15.5392 7.66509 15.5187C7.77794 15.4955 7.88451 15.4364 8.09763 15.3183L13.6587 12.2349C13.8838 12.1101 13.9963 12.0477 14.0783 11.959C14.1508 11.8805 14.2056 11.7874 14.2392 11.686C14.2771 11.5714 14.2771 11.4429 14.2771 11.1859Z"/></svg>`,
|
|
144
|
+
"module-assessment": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 13.72 16.13" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.507 6.1875V4.1625C12.507 2.90238 12.507 2.27232 12.2629 1.79102C12.0482 1.36766 11.7056 1.02345 11.2841 0.807736C10.8051 0.5625 10.1779 0.5625 8.92364 0.5625H4.14585C2.89156 0.5625 2.26441 0.5625 1.78534 0.807736C1.36393 1.02345 1.02132 1.36766 0.806601 1.79102C0.5625 2.27232 0.5625 2.90238 0.5625 4.1625V11.9625C0.5625 13.2226 0.5625 13.8527 0.806601 14.334C1.02132 14.7573 1.36393 15.1015 1.78534 15.3173C2.26441 15.5625 2.89156 15.5625 4.14585 15.5625H8.0278M8.0278 7.3125H3.54862M5.04168 10.3125H3.54862M9.52086 4.3125H3.54862M9.89413 10.3142C10.0257 9.93852 10.2853 9.62175 10.627 9.41998C10.9688 9.21821 11.3705 9.14445 11.7612 9.21178C12.1519 9.2791 12.5062 9.48315 12.7615 9.7878C13.0168 10.0925 13.1565 10.478 13.1559 10.8763C13.1559 12.0004 11.4775 12.5625 11.4775 12.5625M11.4991 14.8125H11.5066"/></svg>`,
|
|
145
|
+
"module-survey": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 14.56 11.63" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 5.8125L5.04168 5.8125M14 1.3125L5.04168 1.3125M14 10.3125L5.04168 10.3125M2.05556 5.8125C2.05556 6.22671 1.72133 6.5625 1.30903 6.5625C0.896733 6.5625 0.5625 6.22671 0.5625 5.8125C0.5625 5.39829 0.896733 5.0625 1.30903 5.0625C1.72133 5.0625 2.05556 5.39829 2.05556 5.8125ZM2.05556 1.3125C2.05556 1.72671 1.72133 2.0625 1.30903 2.0625C0.896733 2.0625 0.5625 1.72671 0.5625 1.3125C0.5625 0.898286 0.896733 0.5625 1.30903 0.5625C1.72133 0.5625 2.05556 0.898286 2.05556 1.3125ZM2.05556 10.3125C2.05556 10.7267 1.72133 11.0625 1.30903 11.0625C0.896733 11.0625 0.5625 10.7267 0.5625 10.3125C0.5625 9.89829 0.896733 9.5625 1.30903 9.5625C1.72133 9.5625 2.05556 9.89829 2.05556 10.3125Z"/></svg>`,
|
|
146
|
+
"module-download": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 16.06 14.63" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.77433 3.56259L7.94155 1.88928C7.70187 1.40769 7.58202 1.16689 7.40323 0.990961C7.24512 0.835387 7.05457 0.717069 6.84535 0.644571C6.60877 0.562589 6.3408 0.562589 5.80485 0.562589H2.9514C2.1152 0.562589 1.69711 0.562589 1.37773 0.726079C1.09679 0.869889 0.868379 1.09936 0.725234 1.3816C0.562501 1.70247 0.562501 2.12251 0.562501 2.96259V3.56259M0.562501 3.56259H11.9098C13.164 3.56259 13.7912 3.56259 14.2703 3.80782C14.6917 4.02354 15.0343 4.36775 15.249 4.79111C15.4931 5.27241 15.4931 5.90247 15.4931 7.16259V10.4626C15.4931 11.7227 15.4931 12.3528 15.249 12.8341C15.0343 13.2574 14.6917 13.6016 14.2703 13.8174C13.7912 14.0626 13.164 14.0626 11.9098 14.0626H4.14585C2.89156 14.0626 2.26441 14.0626 1.78534 13.8174C1.36393 13.6016 1.02132 13.2574 0.806601 12.8341C0.562501 12.3528 0.562501 11.7227 0.562501 10.4626V3.56259ZM10.2674 8.81259L8.0278 11.0626L5.78821 8.81259M8.0278 11.0626V6.56259"/></svg>`,
|
|
147
|
+
"module-link": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 15.97 16.03" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.49261 8.76267C6.81321 9.19327 7.22223 9.54956 7.69194 9.80737C8.16166 10.0652 8.68106 10.2185 9.21494 10.2569C9.74882 10.2953 10.2847 10.2179 10.7862 10.03C11.2876 9.84207 11.743 9.54797 12.1214 9.16767L14.361 6.91767C15.041 6.21041 15.4172 5.26315 15.4087 4.27991C15.4002 3.29668 15.0076 2.35613 14.3156 1.66085C13.6235 0.965569 12.6873 0.571186 11.7086 0.562642C10.7299 0.554098 9.78705 0.932077 9.08307 1.61517L7.79903 2.89767M9.47873 7.26267C9.15813 6.83207 8.7491 6.47578 8.27939 6.21796C7.80968 5.96015 7.29027 5.80683 6.75639 5.76842C6.22252 5.73001 5.68666 5.80739 5.18517 5.99533C4.68369 6.18327 4.2283 6.47737 3.84989 6.85767L1.6103 9.10767C0.930367 9.81493 0.554137 10.7622 0.562641 11.7454C0.571146 12.7287 0.963705 13.6692 1.65577 14.3645C2.34783 15.0598 3.28403 15.4542 4.26272 15.4627C5.24141 15.4712 6.18428 15.0933 6.88827 14.4102L8.16483 13.1277"/></svg>`,
|
|
148
|
+
"module-adobe-connect": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 16.06 14.63" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.7605 0.56252L8.0278 3.56252L4.29515 0.56252M4.14585 14.0625H11.9098C13.164 14.0625 13.7912 14.0625 14.2703 13.8173C14.6917 13.6016 15.0343 13.2574 15.249 12.834C15.4931 12.3527 15.4931 11.7226 15.4931 10.4625V7.16252C15.4931 5.9024 15.4931 5.27234 15.249 4.79104C15.0343 4.36768 14.6917 4.02347 14.2703 3.80776C13.7912 3.56252 13.164 3.56252 11.9098 3.56252H4.14585C2.89156 3.56252 2.26441 3.56252 1.78534 3.80776C1.36393 4.02347 1.02132 4.36768 0.806601 4.79104C0.5625 5.27234 0.5625 5.9024 0.5625 7.16252V10.4625C0.5625 11.7226 0.5625 12.3527 0.806601 12.834C1.02132 13.2574 1.36393 13.6016 1.78534 13.8173C2.26441 14.0625 2.89156 14.0625 4.14585 14.0625Z"/></svg>`,
|
|
149
|
+
"module-video": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 16.06 11.63" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15.4931 3.51103C15.4931 3.05667 15.4931 2.82949 15.4037 2.72429C15.3261 2.63301 15.2097 2.58457 15.0906 2.59399C14.9533 2.60484 14.7934 2.76548 14.4736 3.08676L11.7605 5.8125L14.4736 8.53824C14.7934 8.85952 14.9533 9.02016 15.0906 9.03101C15.2097 9.04043 15.3261 8.99199 15.4037 8.90071C15.4931 8.79552 15.4931 8.56833 15.4931 8.11397V3.51103ZM0.5625 4.1625C0.5625 2.90238 0.5625 2.27232 0.806601 1.79102C1.02132 1.36766 1.36393 1.02345 1.78534 0.807736C2.26441 0.5625 2.89156 0.5625 4.14585 0.5625H8.17711C9.4314 0.5625 10.0585 0.5625 10.5376 0.807736C10.959 1.02345 11.3016 1.36766 11.5164 1.79102C11.7605 2.27232 11.7605 2.90238 11.7605 4.1625V7.4625C11.7605 8.72262 11.7605 9.35268 11.5164 9.83398C11.3016 10.2573 10.959 10.6015 10.5376 10.8173C10.0585 11.0625 9.4314 11.0625 8.17711 11.0625H4.14584C2.89156 11.0625 2.26441 11.0625 1.78534 10.8173C1.36393 10.6015 1.02132 10.2573 0.806601 9.83398C0.5625 9.35268 0.5625 8.72262 0.5625 7.4625V4.1625Z"/></svg>`,
|
|
150
|
+
"module-upload": `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 14.75 16.13" fill="none" stroke="currentColor" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.39 4.3125V4.1625C13.39 2.90238 13.39 2.27232 13.1278 1.79102C12.8972 1.36766 12.5293 1.02345 12.0767 0.807736C11.5622 0.5625 10.8887 0.5625 9.54172 0.5625H4.41074C3.06373 0.5625 2.39022 0.5625 1.87573 0.807736C1.42318 1.02345 1.05524 1.36766 0.824646 1.79102C0.5625 2.27232 0.5625 2.90238 0.5625 4.1625V11.9625C0.5625 13.2226 0.5625 13.8527 0.824646 14.334C1.05524 14.7573 1.42318 15.1015 1.87573 15.3173C2.39022 15.5625 3.06373 15.5625 4.41074 15.5625H7.37709M7.37709 7.3125H3.76936M6.57537 10.3125H3.76936M10.1831 4.3125H3.76936M11.7865 12.5625V8.4375C11.7865 7.81618 12.3249 7.3125 12.9891 7.3125C13.6533 7.3125 14.1917 7.81618 14.1917 8.4375V12.5625C14.1917 13.8051 13.1149 14.8125 11.7865 14.8125C10.4582 14.8125 9.38138 13.8051 9.38138 12.5625V9.5625"/></svg>`
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-searchbox/bv-searchbox.css
|
|
154
|
+
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";
|
|
155
|
+
|
|
156
|
+
// components/bv-searchbox/bv-searchbox.ts
|
|
157
|
+
var SEARCH_ICON = `<svg width="16" height="16" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
158
|
+
<path d="M17.5 17.5L13.875 13.875M15.8333 9.16667C15.8333 12.8486 12.8486 15.8333 9.16667 15.8333C5.48477 15.8333 2.5 12.8486 2.5 9.16667C2.5 5.48477 5.48477 2.5 9.16667 2.5C12.8486 2.5 15.8333 5.48477 15.8333 9.16667Z"
|
|
159
|
+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
160
|
+
</svg>`;
|
|
161
|
+
var BvSearchbox = class extends HTMLElement {
|
|
162
|
+
static observedAttributes = ["placeholder", "value", "size"];
|
|
163
|
+
_rendered = false;
|
|
164
|
+
_input = null;
|
|
165
|
+
constructor() {
|
|
166
|
+
super();
|
|
167
|
+
adoptStyles(this.attachShadow({ mode: "open" }), bv_searchbox_default);
|
|
168
|
+
}
|
|
169
|
+
connectedCallback() {
|
|
170
|
+
this._render();
|
|
171
|
+
}
|
|
172
|
+
attributeChangedCallback(name, _old, val) {
|
|
173
|
+
if (!this._rendered || !this._input) return;
|
|
174
|
+
if (name === "placeholder") this._input.placeholder = val ?? "";
|
|
175
|
+
if (name === "value") this._input.value = val ?? "";
|
|
176
|
+
}
|
|
177
|
+
get value() {
|
|
178
|
+
var _a;
|
|
179
|
+
return ((_a = this._input) == null ? void 0 : _a.value) ?? "";
|
|
180
|
+
}
|
|
181
|
+
_render() {
|
|
182
|
+
if (this._rendered) return;
|
|
183
|
+
this._rendered = true;
|
|
184
|
+
const shadow = this.shadowRoot;
|
|
185
|
+
const icon = document.createElement("span");
|
|
186
|
+
icon.className = "search-icon";
|
|
187
|
+
icon.innerHTML = SEARCH_ICON;
|
|
188
|
+
this._input = document.createElement("input");
|
|
189
|
+
this._input.type = "search";
|
|
190
|
+
this._input.className = "search-input";
|
|
191
|
+
this._input.placeholder = this.getAttribute("placeholder") ?? "Search\u2026";
|
|
192
|
+
this._input.value = this.getAttribute("value") ?? "";
|
|
193
|
+
this._input.addEventListener("input", () => {
|
|
194
|
+
this.dispatchEvent(
|
|
195
|
+
new CustomEvent("bv-search", {
|
|
196
|
+
bubbles: true,
|
|
197
|
+
composed: true,
|
|
198
|
+
detail: { value: this._input.value }
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
shadow.append(icon, this._input);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
if (!customElements.get("bv-searchbox")) {
|
|
206
|
+
customElements.define("bv-searchbox", BvSearchbox);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-segmented-control/bv-segmented-control.css
|
|
210
|
+
var bv_segmented_control_default = ":host {\n display: block;\n font-family: var(--bv-font-family-base);\n}\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.field-label[hidden] {\n display: none;\n}\n\n.required-mark {\n color: var(--bv-color-danger);\n margin-left: 2px;\n}\n\n.container {\n display: flex;\n align-items: center;\n height: 44px;\n box-sizing: border-box;\n background: var(--bv-color-neutral-100);\n border: 1px solid var(--bv-color-neutral-200);\n border-radius: var(--bv-radius-lg);\n padding: 3px;\n gap: 2px;\n}\n\n:host([size='sm']) .container {\n height: var(--bv-control-height-md);\n border-radius: var(--bv-radius-md);\n padding: 3px;\n}\n\n.pill {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 0 var(--bv-spacing-md);\n border: none;\n border-radius: calc(var(--bv-radius-lg) - 3px);\n background: transparent;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-500);\n cursor: pointer;\n white-space: nowrap;\n user-select: none;\n transition:\n color var(--bv-transition-fast),\n background var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n:host([size='sm']) .pill {\n padding: 0 var(--bv-spacing-md-minus);\n font-size: var(--bv-font-size-sm);\n}\n\n.pill:hover:not(.selected) {\n color: var(--bv-color-neutral-800);\n}\n\n.pill.selected {\n background: var(--bv-color-neutral-0);\n color: var(--bv-color-neutral-900);\n box-shadow: var(--bv-shadow-sm);\n}\n\n/* Filled variant \u2014 selected pill uses the primary color instead of white */\n:host([variant='primary']) .pill.selected {\n background: var(--bv-color-primary);\n color: var(--bv-color-neutral-0);\n}\n";
|
|
211
|
+
|
|
212
|
+
// components/bv-segmented-control/bv-segmented-control.ts
|
|
213
|
+
var BvSegmentedControl = class extends HTMLElement {
|
|
214
|
+
static observedAttributes = [
|
|
215
|
+
"options",
|
|
216
|
+
"value",
|
|
217
|
+
"label",
|
|
218
|
+
"required",
|
|
219
|
+
"size",
|
|
220
|
+
"variant"
|
|
221
|
+
];
|
|
222
|
+
_rendered = false;
|
|
223
|
+
_labelEl = null;
|
|
224
|
+
_containerEl = null;
|
|
225
|
+
_options = [];
|
|
226
|
+
constructor() {
|
|
227
|
+
super();
|
|
228
|
+
adoptStyles(this.attachShadow({ mode: "open" }), bv_segmented_control_default);
|
|
229
|
+
}
|
|
230
|
+
get value() {
|
|
231
|
+
return this.getAttribute("value") ?? "";
|
|
232
|
+
}
|
|
233
|
+
set value(v) {
|
|
234
|
+
this.setAttribute("value", v);
|
|
235
|
+
}
|
|
236
|
+
get required() {
|
|
237
|
+
return this.hasAttribute("required");
|
|
238
|
+
}
|
|
239
|
+
set required(v) {
|
|
240
|
+
if (v) this.setAttribute("required", "");
|
|
241
|
+
else this.removeAttribute("required");
|
|
242
|
+
}
|
|
243
|
+
get size() {
|
|
244
|
+
return this.getAttribute("size") ?? "md";
|
|
245
|
+
}
|
|
246
|
+
set size(v) {
|
|
247
|
+
this.setAttribute("size", v);
|
|
248
|
+
}
|
|
249
|
+
get variant() {
|
|
250
|
+
return this.getAttribute("variant") ?? "default";
|
|
251
|
+
}
|
|
252
|
+
set variant(v) {
|
|
253
|
+
this.setAttribute("variant", v);
|
|
254
|
+
}
|
|
255
|
+
connectedCallback() {
|
|
256
|
+
this._render();
|
|
257
|
+
}
|
|
258
|
+
attributeChangedCallback(name, _old, _val) {
|
|
259
|
+
if (!this._rendered) return;
|
|
260
|
+
if (name === "options") this._syncOptions();
|
|
261
|
+
if (name === "value") this._syncSelected();
|
|
262
|
+
if (name === "label" || name === "required") this._syncLabel();
|
|
263
|
+
}
|
|
264
|
+
_render() {
|
|
265
|
+
if (this._rendered) return;
|
|
266
|
+
this._rendered = true;
|
|
267
|
+
const shadow = this.shadowRoot;
|
|
268
|
+
const labelEl = document.createElement("span");
|
|
269
|
+
labelEl.className = "field-label";
|
|
270
|
+
this._labelEl = labelEl;
|
|
271
|
+
const container = document.createElement("div");
|
|
272
|
+
container.className = "container";
|
|
273
|
+
container.setAttribute("role", "group");
|
|
274
|
+
this._containerEl = container;
|
|
275
|
+
shadow.append(labelEl, container);
|
|
276
|
+
this._syncLabel();
|
|
277
|
+
this._syncOptions();
|
|
278
|
+
}
|
|
279
|
+
_syncLabel() {
|
|
280
|
+
if (!this._labelEl || !this._containerEl) return;
|
|
281
|
+
const text = this.getAttribute("label") ?? "";
|
|
282
|
+
this._labelEl.textContent = text;
|
|
283
|
+
if (this.hasAttribute("required")) {
|
|
284
|
+
const mark = document.createElement("span");
|
|
285
|
+
mark.className = "required-mark";
|
|
286
|
+
mark.setAttribute("aria-hidden", "true");
|
|
287
|
+
mark.textContent = "*";
|
|
288
|
+
this._labelEl.appendChild(mark);
|
|
289
|
+
}
|
|
290
|
+
this._labelEl.hidden = !text;
|
|
291
|
+
if (text) this._containerEl.setAttribute("aria-label", text);
|
|
292
|
+
else this._containerEl.removeAttribute("aria-label");
|
|
293
|
+
}
|
|
294
|
+
_syncOptions() {
|
|
295
|
+
if (!this._containerEl) return;
|
|
296
|
+
try {
|
|
297
|
+
this._options = JSON.parse(this.getAttribute("options") ?? "[]");
|
|
298
|
+
} catch {
|
|
299
|
+
this._options = [];
|
|
300
|
+
}
|
|
301
|
+
this._containerEl.innerHTML = "";
|
|
302
|
+
for (const opt of this._options) {
|
|
303
|
+
const btn = document.createElement("button");
|
|
304
|
+
btn.type = "button";
|
|
305
|
+
btn.className = "pill";
|
|
306
|
+
btn.dataset["value"] = opt.value;
|
|
307
|
+
btn.textContent = opt.label;
|
|
308
|
+
btn.setAttribute("aria-pressed", "false");
|
|
309
|
+
btn.addEventListener("click", () => {
|
|
310
|
+
if (this.value === opt.value) return;
|
|
311
|
+
this.value = opt.value;
|
|
312
|
+
this.dispatchEvent(
|
|
313
|
+
new CustomEvent("bv-change", {
|
|
314
|
+
bubbles: true,
|
|
315
|
+
composed: true,
|
|
316
|
+
detail: { value: opt.value, label: opt.label }
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
this._containerEl.appendChild(btn);
|
|
321
|
+
}
|
|
322
|
+
this._syncSelected();
|
|
323
|
+
}
|
|
324
|
+
_syncSelected() {
|
|
325
|
+
if (!this._containerEl) return;
|
|
326
|
+
const current = this.value;
|
|
327
|
+
for (const btn of this._containerEl.querySelectorAll(".pill")) {
|
|
328
|
+
const isSelected = btn.dataset["value"] === current;
|
|
329
|
+
btn.classList.toggle("selected", isSelected);
|
|
330
|
+
btn.setAttribute("aria-pressed", isSelected ? "true" : "false");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
if (!customElements.get("bv-segmented-control")) {
|
|
335
|
+
customElements.define("bv-segmented-control", BvSegmentedControl);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-toggle/bv-toggle.css
|
|
339
|
+
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";
|
|
340
|
+
|
|
341
|
+
// components/bv-toggle/bv-toggle.ts
|
|
342
|
+
var BvToggle = class extends HTMLElement {
|
|
343
|
+
static observedAttributes = ["checked", "disabled", "label", "size"];
|
|
344
|
+
_rendered = false;
|
|
345
|
+
_labelEl = null;
|
|
346
|
+
constructor() {
|
|
347
|
+
super();
|
|
348
|
+
adoptStyles(this.attachShadow({ mode: "open" }), bv_toggle_default);
|
|
349
|
+
}
|
|
350
|
+
connectedCallback() {
|
|
351
|
+
this._render();
|
|
352
|
+
}
|
|
353
|
+
attributeChangedCallback(name, _old, val) {
|
|
354
|
+
if (!this._rendered) return;
|
|
355
|
+
if (name === "label" && this._labelEl) this._labelEl.textContent = val ?? "";
|
|
356
|
+
}
|
|
357
|
+
get checked() {
|
|
358
|
+
return this.hasAttribute("checked");
|
|
359
|
+
}
|
|
360
|
+
set checked(v) {
|
|
361
|
+
if (v) this.setAttribute("checked", "");
|
|
362
|
+
else this.removeAttribute("checked");
|
|
363
|
+
}
|
|
364
|
+
_render() {
|
|
365
|
+
if (this._rendered) return;
|
|
366
|
+
this._rendered = true;
|
|
367
|
+
const shadow = this.shadowRoot;
|
|
368
|
+
const wrap = document.createElement("label");
|
|
369
|
+
wrap.className = "wrap";
|
|
370
|
+
const track = document.createElement("span");
|
|
371
|
+
track.className = "track";
|
|
372
|
+
const thumb = document.createElement("span");
|
|
373
|
+
thumb.className = "thumb";
|
|
374
|
+
track.appendChild(thumb);
|
|
375
|
+
this._labelEl = document.createElement("span");
|
|
376
|
+
this._labelEl.className = "label";
|
|
377
|
+
this._labelEl.textContent = this.getAttribute("label") ?? "";
|
|
378
|
+
const slot = document.createElement("slot");
|
|
379
|
+
wrap.append(track, this._labelEl, slot);
|
|
380
|
+
shadow.appendChild(wrap);
|
|
381
|
+
wrap.addEventListener("click", () => {
|
|
382
|
+
if (this.hasAttribute("disabled")) return;
|
|
383
|
+
this.checked = !this.checked;
|
|
384
|
+
this.dispatchEvent(
|
|
385
|
+
new CustomEvent("bv-change", {
|
|
386
|
+
bubbles: true,
|
|
387
|
+
composed: true,
|
|
388
|
+
detail: { checked: this.checked }
|
|
389
|
+
})
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
if (!customElements.get("bv-toggle")) {
|
|
395
|
+
customElements.define("bv-toggle", BvToggle);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-button/bv-button.css
|
|
399
|
+
var bv_button_default = "/* \u2500\u2500 Public theming API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Apps can override these three properties per-instance:\n\n HTML: <bv-button style=\"--bv-button-bg: #e53e3e;\">\n Angular: <bv-button [style.--bv-button-bg]=\"brandColor\">\n JavaScript: btn.style.setProperty('--bv-button-bg', value)\n\n Hover is auto-computed from the base colors via color-mix().\n Override --bv-button-bg-hover / --bv-button-border-color-hover\n if you need explicit control over the hovered state.\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host {\n /* Default values \u2014 map to global tokens (primary variant) */\n --bv-button-bg: var(--bv-color-primary);\n --bv-button-bg-hover: color-mix(in srgb, var(--bv-button-bg) 82%, black);\n --bv-button-color: var(--bv-color-neutral-0);\n --bv-button-border-color: var(--bv-color-primary);\n --bv-button-border-color-hover: color-mix(in srgb, var(--bv-button-border-color) 82%, black);\n --bv-button-font-family: var(--bv-font-family-base);\n --bv-button-font-size: var(--bv-font-size-sm);\n\n display: inline-block;\n font-family: var(--bv-font-family-base);\n}\n\n:host([hidden]) {\n /* `!important` so `hidden` always wins over :host([full-width]) / other display rules. */\n display: none !important;\n}\n\n:host([full-width]) {\n display: block;\n width: 100%;\n}\n\n/* \u2500\u2500 Variant tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Each variant re-points the component tokens to different global\n tokens. Apps can still override with inline styles \u2014 inline style\n always wins due to higher specificity.\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n:host([variant='secondary']) {\n --bv-button-bg: var(--bv-color-secondary);\n --bv-button-border-color: var(--bv-color-secondary);\n --bv-button-color: var(--bv-color-secondary-foreground);\n}\n\n:host([variant='tertiary']) {\n --bv-button-bg: var(--bv-color-tertiary);\n --bv-button-border-color: var(--bv-color-tertiary);\n}\n\n:host([variant='danger']) {\n --bv-button-bg: var(--bv-color-danger);\n --bv-button-border-color: var(--bv-color-danger);\n}\n\n:host([variant='ghost']) {\n --bv-button-bg: transparent;\n --bv-button-bg-hover: var(--bv-color-primary-light);\n --bv-button-color: var(--bv-color-primary);\n --bv-button-border-color: transparent;\n --bv-button-border-color-hover: transparent;\n}\n\n:host([variant='outline']) {\n --bv-button-bg: transparent;\n --bv-button-bg-hover: var(--bv-color-primary-light);\n --bv-button-color: var(--bv-color-primary);\n --bv-button-border-color: var(--bv-color-primary);\n --bv-button-border-color-hover: var(--bv-color-primary);\n}\n\n:host([variant='outline-secondary']) {\n --bv-button-bg: transparent;\n --bv-button-bg-hover: var(--bv-color-neutral-100);\n --bv-button-color: var(--bv-color-neutral-700);\n --bv-button-border-color: var(--bv-color-neutral-300);\n --bv-button-border-color-hover: var(--bv-color-neutral-300);\n}\n\n/* \u2500\u2500 Base button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n All visual properties read from the component-level tokens above.\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--bv-spacing-xs);\n padding: 10px var(--bv-spacing-md);\n min-height: 40px;\n width: auto;\n font-family: var(--bv-button-font-family);\n font-size: var(--bv-button-font-size);\n font-style: normal;\n font-weight: var(--bv-font-weight-semibold);\n line-height: 20px;\n border: 1px solid var(--bv-button-border-color);\n border-radius: var(--bv-radius-lg);\n cursor: pointer;\n transition:\n background var(--bv-transition-fast),\n color var(--bv-transition-fast),\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast),\n opacity var(--bv-transition-fast);\n white-space: nowrap;\n user-select: none;\n text-decoration: none;\n outline: none;\n box-sizing: border-box;\n background: var(--bv-button-bg);\n color: var(--bv-button-color);\n box-shadow: var(--bv-shadow-sm);\n}\n\n:host([full-width]) .btn {\n width: 100%;\n}\n\n.btn:hover:not(:disabled) {\n background: var(--bv-button-bg-hover);\n border-color: var(--bv-button-border-color-hover);\n}\n\n.btn:active:not(:disabled) {\n transform: translateY(1px);\n}\n\n.btn:focus-visible {\n outline: 2px solid var(--bv-button-border-color);\n outline-offset: 2px;\n}\n\n.btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* \u2500\u2500 Sizes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n:host([size='sm']) .btn {\n padding: 6px 12px;\n min-height: 32px;\n font-size: var(--bv-font-size-sm);\n}\n\n:host([size='lg']) .btn {\n padding: 12px var(--bv-spacing-lg);\n min-height: 48px;\n font-size: var(--bv-font-size-md);\n}\n\n/* \u2500\u2500 Icon \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.btn-icon {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n line-height: 0;\n}\n\n/* \u2500\u2500 Count badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.btn-count {\n opacity: 0.8;\n font-weight: var(--bv-font-weight-regular);\n}\n\n/* \u2500\u2500 Loading spinner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500*/\n\n.spinner {\n display: inline-block;\n width: 1em;\n height: 1em;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: 50%;\n flex-shrink: 0;\n animation: bv-spin 700ms linear infinite;\n}\n\n@keyframes bv-spin {\n to {\n transform: rotate(360deg);\n }\n}\n";
|
|
400
|
+
|
|
401
|
+
// components/bv-button/bv-button.ts
|
|
402
|
+
var BvButton = class extends HTMLElement {
|
|
403
|
+
static observedAttributes = [
|
|
404
|
+
"variant",
|
|
405
|
+
"size",
|
|
406
|
+
"disabled",
|
|
407
|
+
"loading",
|
|
408
|
+
"type",
|
|
409
|
+
"full-width",
|
|
410
|
+
"icon",
|
|
411
|
+
"icon-position",
|
|
412
|
+
"count",
|
|
413
|
+
"aria-label"
|
|
414
|
+
];
|
|
415
|
+
constructor() {
|
|
416
|
+
super();
|
|
417
|
+
adoptStyles(this.attachShadow({ mode: "open" }), bv_button_default);
|
|
418
|
+
}
|
|
419
|
+
// ── Accessors ────────────────────────────────────────────────
|
|
420
|
+
get variant() {
|
|
421
|
+
return this.getAttribute("variant") ?? "primary";
|
|
422
|
+
}
|
|
423
|
+
set variant(value) {
|
|
424
|
+
this.setAttribute("variant", value);
|
|
425
|
+
}
|
|
426
|
+
get size() {
|
|
427
|
+
return this.getAttribute("size") ?? "md";
|
|
428
|
+
}
|
|
429
|
+
set size(value) {
|
|
430
|
+
this.setAttribute("size", value);
|
|
431
|
+
}
|
|
432
|
+
get disabled() {
|
|
433
|
+
return this.hasAttribute("disabled");
|
|
434
|
+
}
|
|
435
|
+
set disabled(value) {
|
|
436
|
+
if (value) this.setAttribute("disabled", "");
|
|
437
|
+
else this.removeAttribute("disabled");
|
|
438
|
+
}
|
|
439
|
+
get loading() {
|
|
440
|
+
return this.hasAttribute("loading");
|
|
441
|
+
}
|
|
442
|
+
set loading(value) {
|
|
443
|
+
if (value) this.setAttribute("loading", "");
|
|
444
|
+
else this.removeAttribute("loading");
|
|
445
|
+
}
|
|
446
|
+
get type() {
|
|
447
|
+
const t = this.getAttribute("type");
|
|
448
|
+
return t === "submit" || t === "reset" ? t : "button";
|
|
449
|
+
}
|
|
450
|
+
set type(value) {
|
|
451
|
+
this.setAttribute("type", value);
|
|
452
|
+
}
|
|
453
|
+
get fullWidth() {
|
|
454
|
+
return this.hasAttribute("full-width");
|
|
455
|
+
}
|
|
456
|
+
set fullWidth(value) {
|
|
457
|
+
if (value) this.setAttribute("full-width", "");
|
|
458
|
+
else this.removeAttribute("full-width");
|
|
459
|
+
}
|
|
460
|
+
// ── Lifecycle ────────────────────────────────────────────────
|
|
461
|
+
connectedCallback() {
|
|
462
|
+
this._render();
|
|
463
|
+
}
|
|
464
|
+
attributeChangedCallback(_name, oldValue, newValue) {
|
|
465
|
+
if (oldValue === newValue) return;
|
|
466
|
+
this._render();
|
|
467
|
+
}
|
|
468
|
+
// ── Rendering ────────────────────────────────────────────────
|
|
469
|
+
_render() {
|
|
470
|
+
var _a;
|
|
471
|
+
const shadow = this.shadowRoot;
|
|
472
|
+
let btn = shadow.querySelector("button");
|
|
473
|
+
if (!btn) {
|
|
474
|
+
btn = document.createElement("button");
|
|
475
|
+
btn.setAttribute("part", "button");
|
|
476
|
+
btn.appendChild(document.createElement("slot"));
|
|
477
|
+
shadow.appendChild(btn);
|
|
478
|
+
}
|
|
479
|
+
btn.type = this.type;
|
|
480
|
+
btn.disabled = this.disabled || this.loading;
|
|
481
|
+
btn.className = "btn";
|
|
482
|
+
const ariaLabel = this.getAttribute("aria-label");
|
|
483
|
+
if (ariaLabel) btn.setAttribute("aria-label", ariaLabel);
|
|
484
|
+
else btn.removeAttribute("aria-label");
|
|
485
|
+
if (this.loading) {
|
|
486
|
+
btn.setAttribute("aria-busy", "true");
|
|
487
|
+
if (!btn.querySelector(".spinner")) {
|
|
488
|
+
const spinner = document.createElement("span");
|
|
489
|
+
spinner.className = "spinner";
|
|
490
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
491
|
+
btn.insertBefore(spinner, btn.querySelector("slot"));
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
btn.removeAttribute("aria-busy");
|
|
495
|
+
(_a = btn.querySelector(".spinner")) == null ? void 0 : _a.remove();
|
|
496
|
+
}
|
|
497
|
+
btn.querySelectorAll(".btn-icon").forEach((el) => el.remove());
|
|
498
|
+
const iconName = this.getAttribute("icon");
|
|
499
|
+
if (iconName && !this.loading) {
|
|
500
|
+
const svg = getIconSvg(iconName, 16);
|
|
501
|
+
if (svg) {
|
|
502
|
+
const iconEl = document.createElement("span");
|
|
503
|
+
iconEl.className = "btn-icon";
|
|
504
|
+
iconEl.innerHTML = svg;
|
|
505
|
+
const slot = btn.querySelector("slot");
|
|
506
|
+
if (this.getAttribute("icon-position") === "right") {
|
|
507
|
+
btn.appendChild(iconEl);
|
|
508
|
+
} else {
|
|
509
|
+
btn.insertBefore(iconEl, slot);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
btn.querySelectorAll(".btn-count").forEach((el) => el.remove());
|
|
514
|
+
const countVal = this.getAttribute("count");
|
|
515
|
+
if (countVal && Number(countVal) > 0) {
|
|
516
|
+
const countEl = document.createElement("span");
|
|
517
|
+
countEl.className = "btn-count";
|
|
518
|
+
countEl.textContent = `(${countVal})`;
|
|
519
|
+
btn.appendChild(countEl);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
if (!customElements.get("bv-button")) {
|
|
524
|
+
customElements.define("bv-button", BvButton);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// components/bv-search-picker/bv-search-picker.ts
|
|
528
|
+
var SKELETON_ROW_COUNT = 5;
|
|
529
|
+
var DEFAULT_DEBOUNCE_MS = 300;
|
|
530
|
+
function parseItems(raw) {
|
|
531
|
+
if (!raw) return [];
|
|
532
|
+
try {
|
|
533
|
+
const parsed = JSON.parse(raw);
|
|
534
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
535
|
+
} catch {
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
var BvSearchPicker = class extends HTMLElement {
|
|
540
|
+
static observedAttributes = [
|
|
541
|
+
"label",
|
|
542
|
+
"entity-label",
|
|
543
|
+
"search-placeholder",
|
|
544
|
+
"inactive-toggle-label",
|
|
545
|
+
"mode",
|
|
546
|
+
"min-query-length",
|
|
547
|
+
"debounce-ms",
|
|
548
|
+
"disabled",
|
|
549
|
+
"loading",
|
|
550
|
+
"show-inactive",
|
|
551
|
+
"open",
|
|
552
|
+
"results",
|
|
553
|
+
"results-total",
|
|
554
|
+
"results-request-id",
|
|
555
|
+
"basket",
|
|
556
|
+
// BTN-10322 — configurable label templates. All support `{count}` (basket
|
|
557
|
+
// size) and `{n}` (max results / current shown, per template) plus
|
|
558
|
+
// `{entity}` interpolation so consumers can translate without forking.
|
|
559
|
+
"label-all",
|
|
560
|
+
"label-count",
|
|
561
|
+
"label-except",
|
|
562
|
+
"label-only-these",
|
|
563
|
+
"label-all-except",
|
|
564
|
+
"label-empty-basket",
|
|
565
|
+
"label-clear-all",
|
|
566
|
+
"label-min-chars",
|
|
567
|
+
"label-no-results",
|
|
568
|
+
"label-results-cap",
|
|
569
|
+
"label-cleared"
|
|
570
|
+
];
|
|
571
|
+
_rendered = false;
|
|
572
|
+
_query = "";
|
|
573
|
+
/**
|
|
574
|
+
* BTN-10322 — staged basket edited inside the panel. Kept separate from the
|
|
575
|
+
* committed `basket` attribute so the trigger pill doesn't flicker mid-edit
|
|
576
|
+
* (add/remove doesn't touch it) and so a cascade reset (`reset()`) has a
|
|
577
|
+
* clean staged copy to reseed from. Committed on Apply, AND automatically
|
|
578
|
+
* whenever the panel closes any other way (Escape / outside-click / trigger
|
|
579
|
+
* toggle) — see `onAfterClose` in `_render()`. Closing is what "confirms"
|
|
580
|
+
* the edit; Apply is just the explicit, no-need-to-move-your-mouse way to
|
|
581
|
+
* do the same thing.
|
|
582
|
+
*/
|
|
583
|
+
_stagedBasket = [];
|
|
584
|
+
/** BTN-10322 — staged mode edited inside the panel. Committed on close, same as `_stagedBasket`. */
|
|
585
|
+
_stagedMode = "include";
|
|
586
|
+
/** BTN-10322 — monotonic id emitted with `bv-search`. The consumer echoes it via `results-request-id`; stale responses drop silently. */
|
|
587
|
+
_searchRequestId = 0;
|
|
588
|
+
/** BTN-10322 — timer handle for the internal debounce on `bv-search`. */
|
|
589
|
+
_searchDebounceHandle = null;
|
|
590
|
+
/**
|
|
591
|
+
* BTN-10322 — the reason to display in the cascade cleared affordance,
|
|
592
|
+
* or `null` when the banner is hidden. Set by `reset({ reason })`,
|
|
593
|
+
* cleared when the user next opens the panel or explicitly dismisses.
|
|
594
|
+
*/
|
|
595
|
+
_clearedReason = null;
|
|
596
|
+
_labelEl = null;
|
|
597
|
+
_triggerEl = null;
|
|
598
|
+
_panelEl = null;
|
|
599
|
+
_pillEl = null;
|
|
600
|
+
_pillTextEl = null;
|
|
601
|
+
_pillClearEl = null;
|
|
602
|
+
_modeControlEl = null;
|
|
603
|
+
_inactiveToggleEl = null;
|
|
604
|
+
_searchEl = null;
|
|
605
|
+
_resultsRowsEl = null;
|
|
606
|
+
_resultsEmptyEl = null;
|
|
607
|
+
_resultsLimitNoteEl = null;
|
|
608
|
+
_basketHeaderLabelEl = null;
|
|
609
|
+
_basketClearAllEl = null;
|
|
610
|
+
_basketListEl = null;
|
|
611
|
+
_basketEmptyEl = null;
|
|
612
|
+
_resultsListEl = null;
|
|
613
|
+
_clearedAffordanceEl = null;
|
|
614
|
+
_clearedAffordanceMsgEl = null;
|
|
615
|
+
_dropdown = null;
|
|
616
|
+
constructor() {
|
|
617
|
+
super();
|
|
618
|
+
adoptStyles(this.attachShadow({ mode: "open" }), bv_search_picker_default);
|
|
619
|
+
}
|
|
620
|
+
// ── Accessors ────────────────────────────────────────────
|
|
621
|
+
get mode() {
|
|
622
|
+
return this.getAttribute("mode") ?? "include";
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Setting `mode` from outside is treated as a commit — it aligns the staged
|
|
626
|
+
* mode inside the panel with the new committed value. Useful for initialising
|
|
627
|
+
* from URL params or reverting from a saved report configuration. If the panel
|
|
628
|
+
* is currently open, the segmented control snaps to the new mode immediately;
|
|
629
|
+
* the user is expected to see this as an intentional external change, not an
|
|
630
|
+
* accidental override of their in-flight edit.
|
|
631
|
+
*/
|
|
632
|
+
set mode(v) {
|
|
633
|
+
this.setAttribute("mode", v);
|
|
634
|
+
}
|
|
635
|
+
get entityLabel() {
|
|
636
|
+
return this.getAttribute("entity-label") ?? "items";
|
|
637
|
+
}
|
|
638
|
+
set entityLabel(v) {
|
|
639
|
+
this.setAttribute("entity-label", v);
|
|
640
|
+
}
|
|
641
|
+
get minQueryLength() {
|
|
642
|
+
const raw = Number(this.getAttribute("min-query-length"));
|
|
643
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 3;
|
|
644
|
+
}
|
|
645
|
+
set minQueryLength(v) {
|
|
646
|
+
this.setAttribute("min-query-length", String(v));
|
|
647
|
+
}
|
|
648
|
+
get disabled() {
|
|
649
|
+
return this.hasAttribute("disabled");
|
|
650
|
+
}
|
|
651
|
+
set disabled(v) {
|
|
652
|
+
if (v) this.setAttribute("disabled", "");
|
|
653
|
+
else this.removeAttribute("disabled");
|
|
654
|
+
}
|
|
655
|
+
get loading() {
|
|
656
|
+
return this.hasAttribute("loading");
|
|
657
|
+
}
|
|
658
|
+
set loading(v) {
|
|
659
|
+
if (v) this.setAttribute("loading", "");
|
|
660
|
+
else this.removeAttribute("loading");
|
|
661
|
+
}
|
|
662
|
+
get showInactive() {
|
|
663
|
+
return this.hasAttribute("show-inactive");
|
|
664
|
+
}
|
|
665
|
+
set showInactive(v) {
|
|
666
|
+
if (v) this.setAttribute("show-inactive", "");
|
|
667
|
+
else this.removeAttribute("show-inactive");
|
|
668
|
+
}
|
|
669
|
+
get results() {
|
|
670
|
+
return parseItems(this.getAttribute("results"));
|
|
671
|
+
}
|
|
672
|
+
set results(v) {
|
|
673
|
+
this.setAttribute("results", JSON.stringify(v));
|
|
674
|
+
}
|
|
675
|
+
// The server's true match count, when it's more than `results.length` — the
|
|
676
|
+
// consumer sets this whenever their search caps what it returns (e.g. "top
|
|
677
|
+
// 200 of 1,000"). Omit it (or leave it <= results.length) when the search
|
|
678
|
+
// isn't capped; the results-limit note only ever appears when it's higher.
|
|
679
|
+
get resultsTotal() {
|
|
680
|
+
const raw = this.getAttribute("results-total");
|
|
681
|
+
if (raw === null) return null;
|
|
682
|
+
const n = Number(raw);
|
|
683
|
+
return Number.isFinite(n) ? n : null;
|
|
684
|
+
}
|
|
685
|
+
set resultsTotal(v) {
|
|
686
|
+
if (v === null) this.removeAttribute("results-total");
|
|
687
|
+
else this.setAttribute("results-total", String(v));
|
|
688
|
+
}
|
|
689
|
+
// BTN-10322 — internal debounce for `bv-search`. Default 300ms. Consumers
|
|
690
|
+
// should NOT re-debounce upstream — this keeps them from accidentally
|
|
691
|
+
// hammering the backend on every keystroke.
|
|
692
|
+
get debounceMs() {
|
|
693
|
+
const raw = Number(this.getAttribute("debounce-ms"));
|
|
694
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : DEFAULT_DEBOUNCE_MS;
|
|
695
|
+
}
|
|
696
|
+
set debounceMs(v) {
|
|
697
|
+
this.setAttribute("debounce-ms", String(v));
|
|
698
|
+
}
|
|
699
|
+
get basket() {
|
|
700
|
+
return parseItems(this.getAttribute("basket"));
|
|
701
|
+
}
|
|
702
|
+
set basket(v) {
|
|
703
|
+
this.setAttribute("basket", JSON.stringify(v));
|
|
704
|
+
}
|
|
705
|
+
// ── Lifecycle ────────────────────────────────────────────
|
|
706
|
+
connectedCallback() {
|
|
707
|
+
this._render();
|
|
708
|
+
}
|
|
709
|
+
disconnectedCallback() {
|
|
710
|
+
var _a;
|
|
711
|
+
(_a = this._dropdown) == null ? void 0 : _a.cleanup();
|
|
712
|
+
if (this._searchDebounceHandle !== null) {
|
|
713
|
+
clearTimeout(this._searchDebounceHandle);
|
|
714
|
+
this._searchDebounceHandle = null;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
attributeChangedCallback(name, _old, _val) {
|
|
718
|
+
if (!this._rendered) return;
|
|
719
|
+
if (name === "label") this._syncLabel();
|
|
720
|
+
if (name === "entity-label") {
|
|
721
|
+
this._syncModeOptions();
|
|
722
|
+
this._syncTriggerSummary();
|
|
723
|
+
this._syncBasketEmptyText();
|
|
724
|
+
this._syncClearedAffordance();
|
|
725
|
+
}
|
|
726
|
+
if (name === "search-placeholder") this._syncSearchPlaceholder();
|
|
727
|
+
if (name === "inactive-toggle-label") this._syncInactiveToggleLabel();
|
|
728
|
+
if (name === "mode") {
|
|
729
|
+
this._stagedMode = this.mode;
|
|
730
|
+
this._syncMode();
|
|
731
|
+
this._syncTriggerSummary();
|
|
732
|
+
}
|
|
733
|
+
if (name === "disabled") this._syncDisabled();
|
|
734
|
+
if (name === "show-inactive") this._syncShowInactive();
|
|
735
|
+
if (name === "results" || name === "results-total" || name === "results-request-id" || name === "loading" || name === "min-query-length") {
|
|
736
|
+
this._syncResultsWithGuard(name);
|
|
737
|
+
}
|
|
738
|
+
if (name === "basket") {
|
|
739
|
+
this._stagedBasket = [...this.basket];
|
|
740
|
+
this._syncBasket();
|
|
741
|
+
this._syncTriggerSummary();
|
|
742
|
+
}
|
|
743
|
+
if (name === "label-all" || name === "label-count" || name === "label-except" || name === "label-clear-all" || name === "label-empty-basket") {
|
|
744
|
+
this._syncBasket();
|
|
745
|
+
this._syncTriggerSummary();
|
|
746
|
+
}
|
|
747
|
+
if (name === "label-only-these" || name === "label-all-except") this._syncModeOptions();
|
|
748
|
+
if (name === "label-min-chars" || name === "label-no-results") this._syncResultsEmptyText();
|
|
749
|
+
if (name === "label-results-cap") this._syncResultsLimitNote();
|
|
750
|
+
if (name === "label-cleared") this._syncClearedAffordance();
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* BTN-10322 — request-id guard. When `results` (or friends) are set from
|
|
754
|
+
* the consumer's async search, `results-request-id` must match the most
|
|
755
|
+
* recent id emitted by our own `bv-search`. Any mismatch means the
|
|
756
|
+
* response arrived out of order (user typed faster than the network) and
|
|
757
|
+
* we drop it silently to avoid rendering stale rows.
|
|
758
|
+
*
|
|
759
|
+
* `results-request-id` unset entirely is fine — consumers that don't
|
|
760
|
+
* bother with the guard behave as before (no correlation).
|
|
761
|
+
*/
|
|
762
|
+
_syncResultsWithGuard(_triggeringAttr) {
|
|
763
|
+
const echoed = this.getAttribute("results-request-id");
|
|
764
|
+
if (echoed !== null && echoed !== "") {
|
|
765
|
+
const echoedN = Number(echoed);
|
|
766
|
+
if (Number.isFinite(echoedN) && echoedN !== this._searchRequestId) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
this._syncResults();
|
|
771
|
+
}
|
|
772
|
+
// ── Render ───────────────────────────────────────────────
|
|
773
|
+
_render() {
|
|
774
|
+
if (this._rendered) return;
|
|
775
|
+
this._rendered = true;
|
|
776
|
+
const shadow = this.shadowRoot;
|
|
777
|
+
const label = document.createElement("label");
|
|
778
|
+
label.className = "field-label";
|
|
779
|
+
this._labelEl = label;
|
|
780
|
+
const trigger = document.createElement("button");
|
|
781
|
+
trigger.type = "button";
|
|
782
|
+
trigger.className = "trigger";
|
|
783
|
+
trigger.setAttribute("aria-haspopup", "listbox");
|
|
784
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
785
|
+
trigger.setAttribute("part", "trigger");
|
|
786
|
+
trigger.addEventListener("click", () => {
|
|
787
|
+
var _a;
|
|
788
|
+
if (!this.disabled) (_a = this._dropdown) == null ? void 0 : _a.toggle();
|
|
789
|
+
});
|
|
790
|
+
this._triggerEl = trigger;
|
|
791
|
+
const pill = document.createElement("span");
|
|
792
|
+
pill.className = "trigger-pill";
|
|
793
|
+
this._pillEl = pill;
|
|
794
|
+
const pillText = document.createElement("span");
|
|
795
|
+
this._pillTextEl = pillText;
|
|
796
|
+
const pillClear = document.createElement("span");
|
|
797
|
+
pillClear.className = "trigger-pill-clear";
|
|
798
|
+
pillClear.setAttribute("role", "button");
|
|
799
|
+
pillClear.setAttribute("aria-label", `Reset to all ${this.entityLabel}`);
|
|
800
|
+
pillClear.tabIndex = 0;
|
|
801
|
+
pillClear.innerHTML = getIconSvg("x", 10);
|
|
802
|
+
const activatePillClear = (e) => {
|
|
803
|
+
e.stopPropagation();
|
|
804
|
+
this._clearCommitted();
|
|
805
|
+
};
|
|
806
|
+
pillClear.addEventListener("click", activatePillClear);
|
|
807
|
+
pillClear.addEventListener("keydown", (e) => {
|
|
808
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
809
|
+
e.preventDefault();
|
|
810
|
+
activatePillClear(e);
|
|
811
|
+
});
|
|
812
|
+
this._pillClearEl = pillClear;
|
|
813
|
+
pill.append(pillText, pillClear);
|
|
814
|
+
const caret = document.createElement("span");
|
|
815
|
+
caret.className = "trigger-caret";
|
|
816
|
+
caret.innerHTML = getIconSvg("chevron-down", 12);
|
|
817
|
+
trigger.append(pill, caret);
|
|
818
|
+
const panel = document.createElement("div");
|
|
819
|
+
panel.className = "panel";
|
|
820
|
+
panel.setAttribute("part", "panel");
|
|
821
|
+
this._panelEl = panel;
|
|
822
|
+
const toolbar = document.createElement("div");
|
|
823
|
+
toolbar.className = "panel-toolbar";
|
|
824
|
+
const modeControl = document.createElement("bv-segmented-control");
|
|
825
|
+
modeControl.setAttribute("size", "sm");
|
|
826
|
+
modeControl.setAttribute("variant", "primary");
|
|
827
|
+
modeControl.addEventListener("bv-change", (e) => {
|
|
828
|
+
e.stopPropagation();
|
|
829
|
+
this._stagedMode = e.detail.value;
|
|
830
|
+
this._syncBasketHeaderLabel();
|
|
831
|
+
this._syncBasketRowColors();
|
|
832
|
+
this._dispatchModeChange();
|
|
833
|
+
});
|
|
834
|
+
this._modeControlEl = modeControl;
|
|
835
|
+
const inactiveToggle = document.createElement("bv-toggle");
|
|
836
|
+
inactiveToggle.setAttribute("size", "sm");
|
|
837
|
+
inactiveToggle.addEventListener("bv-change", (e) => {
|
|
838
|
+
e.stopPropagation();
|
|
839
|
+
this.showInactive = e.detail.checked;
|
|
840
|
+
this._dispatchShowInactiveChange();
|
|
841
|
+
this._scheduleSearchDispatch();
|
|
842
|
+
});
|
|
843
|
+
this._inactiveToggleEl = inactiveToggle;
|
|
844
|
+
toolbar.append(modeControl, inactiveToggle);
|
|
845
|
+
const body = document.createElement("div");
|
|
846
|
+
body.className = "panel-body";
|
|
847
|
+
const resultsPane = document.createElement("div");
|
|
848
|
+
resultsPane.className = "results-pane";
|
|
849
|
+
const searchEl = document.createElement("bv-searchbox");
|
|
850
|
+
searchEl.addEventListener("bv-search", (e) => {
|
|
851
|
+
e.stopPropagation();
|
|
852
|
+
this._query = e.detail.value.trim();
|
|
853
|
+
this._syncResultsEmptyText();
|
|
854
|
+
this._scheduleSearchDispatch();
|
|
855
|
+
});
|
|
856
|
+
searchEl.addEventListener("keydown", (e) => {
|
|
857
|
+
const key = e.key;
|
|
858
|
+
if (key !== "ArrowDown") return;
|
|
859
|
+
if (!this._resultsRowsEl) return;
|
|
860
|
+
const firstRow = this._resultsRowsEl.querySelector(".result-row");
|
|
861
|
+
if (!firstRow) return;
|
|
862
|
+
e.preventDefault();
|
|
863
|
+
this._resultsRowsEl.querySelectorAll(".result-row").forEach((row, i) => row.setAttribute("aria-selected", i === 0 ? "true" : "false"));
|
|
864
|
+
firstRow.focus();
|
|
865
|
+
});
|
|
866
|
+
this._searchEl = searchEl;
|
|
867
|
+
const searchWrap = document.createElement("div");
|
|
868
|
+
searchWrap.className = "results-search";
|
|
869
|
+
searchWrap.appendChild(searchEl);
|
|
870
|
+
const resultsList = document.createElement("div");
|
|
871
|
+
resultsList.className = "results-list";
|
|
872
|
+
resultsList.setAttribute("aria-busy", "false");
|
|
873
|
+
this._resultsListEl = resultsList;
|
|
874
|
+
const skeleton = document.createElement("div");
|
|
875
|
+
skeleton.className = "skeleton";
|
|
876
|
+
skeleton.setAttribute("aria-hidden", "true");
|
|
877
|
+
skeleton.innerHTML = Array.from({ length: SKELETON_ROW_COUNT }).map(
|
|
878
|
+
(_, i) => `<div class="skeleton-row"><span class="skeleton-line" style="width:52px"></span><span class="skeleton-line" style="width:${96 + i * 17 % 60}px"></span></div>`
|
|
879
|
+
).join("");
|
|
880
|
+
const resultsRows = document.createElement("div");
|
|
881
|
+
resultsRows.className = "results-rows";
|
|
882
|
+
resultsRows.setAttribute("role", "listbox");
|
|
883
|
+
resultsRows.setAttribute("aria-label", `${this.entityLabel} matching your search`);
|
|
884
|
+
resultsRows.tabIndex = 0;
|
|
885
|
+
resultsRows.addEventListener("keydown", (e) => this._onResultsKeyDown(e));
|
|
886
|
+
this._resultsRowsEl = resultsRows;
|
|
887
|
+
const resultsEmpty = document.createElement("div");
|
|
888
|
+
resultsEmpty.className = "results-empty";
|
|
889
|
+
this._resultsEmptyEl = resultsEmpty;
|
|
890
|
+
resultsList.append(skeleton, resultsRows, resultsEmpty);
|
|
891
|
+
resultsPane.append(searchWrap, resultsList);
|
|
892
|
+
const basketPane = document.createElement("div");
|
|
893
|
+
basketPane.className = "basket-pane";
|
|
894
|
+
const basketHeader = document.createElement("div");
|
|
895
|
+
basketHeader.className = "basket-header";
|
|
896
|
+
const basketHeaderLabel = document.createElement("span");
|
|
897
|
+
basketHeaderLabel.className = "basket-header-label";
|
|
898
|
+
this._basketHeaderLabelEl = basketHeaderLabel;
|
|
899
|
+
const basketClearAll = document.createElement("button");
|
|
900
|
+
basketClearAll.type = "button";
|
|
901
|
+
basketClearAll.className = "basket-clear-all";
|
|
902
|
+
basketClearAll.addEventListener("click", () => this._clearAllStaged());
|
|
903
|
+
this._basketClearAllEl = basketClearAll;
|
|
904
|
+
basketHeader.append(basketHeaderLabel, basketClearAll);
|
|
905
|
+
const basketList = document.createElement("div");
|
|
906
|
+
basketList.className = "basket-list";
|
|
907
|
+
this._basketListEl = basketList;
|
|
908
|
+
const basketEmpty = document.createElement("div");
|
|
909
|
+
basketEmpty.className = "basket-empty";
|
|
910
|
+
this._basketEmptyEl = basketEmpty;
|
|
911
|
+
basketList.appendChild(basketEmpty);
|
|
912
|
+
basketPane.append(basketHeader, basketList);
|
|
913
|
+
body.append(resultsPane, basketPane);
|
|
914
|
+
const footer = document.createElement("div");
|
|
915
|
+
footer.className = "panel-footer";
|
|
916
|
+
const limitNote = document.createElement("span");
|
|
917
|
+
limitNote.className = "results-limit-note";
|
|
918
|
+
this._resultsLimitNoteEl = limitNote;
|
|
919
|
+
const applyBtn = document.createElement("bv-button");
|
|
920
|
+
applyBtn.setAttribute("variant", "primary");
|
|
921
|
+
applyBtn.textContent = "Apply";
|
|
922
|
+
applyBtn.addEventListener("click", () => this._apply());
|
|
923
|
+
footer.append(limitNote, applyBtn);
|
|
924
|
+
panel.append(toolbar, body, footer);
|
|
925
|
+
const clearedAffordance = document.createElement("div");
|
|
926
|
+
clearedAffordance.className = "cleared-affordance";
|
|
927
|
+
clearedAffordance.setAttribute("role", "status");
|
|
928
|
+
clearedAffordance.setAttribute("aria-live", "polite");
|
|
929
|
+
clearedAffordance.hidden = true;
|
|
930
|
+
this._clearedAffordanceEl = clearedAffordance;
|
|
931
|
+
const clearedIcon = document.createElement("span");
|
|
932
|
+
clearedIcon.className = "cleared-affordance-icon";
|
|
933
|
+
clearedIcon.setAttribute("aria-hidden", "true");
|
|
934
|
+
clearedIcon.innerHTML = getIconSvg("info", 14);
|
|
935
|
+
const clearedMsg = document.createElement("span");
|
|
936
|
+
clearedMsg.className = "cleared-affordance-msg";
|
|
937
|
+
this._clearedAffordanceMsgEl = clearedMsg;
|
|
938
|
+
const clearedDismiss = document.createElement("button");
|
|
939
|
+
clearedDismiss.type = "button";
|
|
940
|
+
clearedDismiss.className = "cleared-affordance-dismiss";
|
|
941
|
+
clearedDismiss.setAttribute("aria-label", "Dismiss");
|
|
942
|
+
clearedDismiss.innerHTML = getIconSvg("x", 10);
|
|
943
|
+
clearedDismiss.addEventListener("click", () => this._dismissClearedAffordance());
|
|
944
|
+
clearedAffordance.append(clearedIcon, clearedMsg, clearedDismiss);
|
|
945
|
+
shadow.append(label, trigger, clearedAffordance, panel);
|
|
946
|
+
this._syncLabel();
|
|
947
|
+
this._syncModeOptions();
|
|
948
|
+
this._syncSearchPlaceholder();
|
|
949
|
+
this._syncInactiveToggleLabel();
|
|
950
|
+
this._stagedMode = this.mode;
|
|
951
|
+
this._stagedBasket = [...this.basket];
|
|
952
|
+
this._syncMode();
|
|
953
|
+
this._syncDisabled();
|
|
954
|
+
this._syncShowInactive();
|
|
955
|
+
this._syncResults();
|
|
956
|
+
this._syncBasket();
|
|
957
|
+
this._syncTriggerSummary();
|
|
958
|
+
this._dropdown = bindDropdownController(this, {
|
|
959
|
+
trigger,
|
|
960
|
+
onBeforeOpen: () => {
|
|
961
|
+
this._stagedMode = this.mode;
|
|
962
|
+
this._stagedBasket = [...this.basket];
|
|
963
|
+
if (this._clearedReason !== null) this._dismissClearedAffordance();
|
|
964
|
+
this._syncMode();
|
|
965
|
+
this._syncBasket();
|
|
966
|
+
this._syncResults();
|
|
967
|
+
},
|
|
968
|
+
onAfterClose: () => {
|
|
969
|
+
if (this._stagedDiffersFromCommitted()) this._commitStaged();
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
this.addEventListener("bv-open", () => {
|
|
973
|
+
var _a;
|
|
974
|
+
this._positionPanel();
|
|
975
|
+
(_a = this._searchEl) == null ? void 0 : _a.focus();
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
// Flips the panel to whichever side of the trigger actually has room, so it
|
|
979
|
+
// never renders off-screen near a viewport edge. Binary flip (not a
|
|
980
|
+
// continuous clamp) is enough here since the panel has a fixed max width/height.
|
|
981
|
+
_positionPanel() {
|
|
982
|
+
if (!this._triggerEl || !this._panelEl) return;
|
|
983
|
+
this.removeAttribute("data-placement");
|
|
984
|
+
this.removeAttribute("data-align");
|
|
985
|
+
const triggerRect = this._triggerEl.getBoundingClientRect();
|
|
986
|
+
const panelRect = this._panelEl.getBoundingClientRect();
|
|
987
|
+
const gap = 4;
|
|
988
|
+
const fitsBelow = triggerRect.bottom + gap + panelRect.height <= window.innerHeight;
|
|
989
|
+
const fitsAbove = triggerRect.top - gap - panelRect.height >= 0;
|
|
990
|
+
if (!fitsBelow && fitsAbove) this.setAttribute("data-placement", "top");
|
|
991
|
+
const fitsLeftAligned = triggerRect.left + panelRect.width <= window.innerWidth;
|
|
992
|
+
if (!fitsLeftAligned) this.setAttribute("data-align", "right");
|
|
993
|
+
}
|
|
994
|
+
// ── Sync helpers ─────────────────────────────────────────
|
|
995
|
+
_syncLabel() {
|
|
996
|
+
if (!this._labelEl) return;
|
|
997
|
+
this._labelEl.textContent = this.getAttribute("label") ?? "";
|
|
998
|
+
}
|
|
999
|
+
_syncModeOptions() {
|
|
1000
|
+
if (!this._modeControlEl) return;
|
|
1001
|
+
const entity = this.entityLabel;
|
|
1002
|
+
const onlyThese = this._interp(this.getAttribute("label-only-these") ?? "Only these {entity}", {
|
|
1003
|
+
entity
|
|
1004
|
+
});
|
|
1005
|
+
const allExcept = this._interp(
|
|
1006
|
+
this.getAttribute("label-all-except") ?? "All {entity} except\u2026",
|
|
1007
|
+
{ entity }
|
|
1008
|
+
);
|
|
1009
|
+
const options = JSON.stringify([
|
|
1010
|
+
{ label: onlyThese, value: "include" },
|
|
1011
|
+
{ label: allExcept, value: "exclude" }
|
|
1012
|
+
]);
|
|
1013
|
+
this._modeControlEl.setAttribute("options", options);
|
|
1014
|
+
this._modeControlEl.setAttribute("value", this._stagedMode);
|
|
1015
|
+
this._syncBasketEmptyText();
|
|
1016
|
+
this._syncResultsEmptyText();
|
|
1017
|
+
}
|
|
1018
|
+
_syncSearchPlaceholder() {
|
|
1019
|
+
if (!this._searchEl) return;
|
|
1020
|
+
this._searchEl.setAttribute(
|
|
1021
|
+
"placeholder",
|
|
1022
|
+
this.getAttribute("search-placeholder") ?? "Search\u2026"
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
_syncInactiveToggleLabel() {
|
|
1026
|
+
if (!this._inactiveToggleEl) return;
|
|
1027
|
+
this._inactiveToggleEl.setAttribute(
|
|
1028
|
+
"label",
|
|
1029
|
+
this.getAttribute("inactive-toggle-label") ?? "Show Inactive"
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
_syncMode() {
|
|
1033
|
+
if (!this._modeControlEl) return;
|
|
1034
|
+
this._modeControlEl.setAttribute("value", this._stagedMode);
|
|
1035
|
+
this._syncBasketHeaderLabel();
|
|
1036
|
+
this._syncBasketRowColors();
|
|
1037
|
+
this._syncTriggerSummary();
|
|
1038
|
+
}
|
|
1039
|
+
_syncDisabled() {
|
|
1040
|
+
if (!this._triggerEl) return;
|
|
1041
|
+
this._triggerEl.disabled = this.disabled;
|
|
1042
|
+
if (this.disabled) this._triggerEl.setAttribute("aria-disabled", "true");
|
|
1043
|
+
else this._triggerEl.removeAttribute("aria-disabled");
|
|
1044
|
+
}
|
|
1045
|
+
_syncShowInactive() {
|
|
1046
|
+
if (!this._inactiveToggleEl) return;
|
|
1047
|
+
if (this.showInactive) this._inactiveToggleEl.setAttribute("checked", "");
|
|
1048
|
+
else this._inactiveToggleEl.removeAttribute("checked");
|
|
1049
|
+
}
|
|
1050
|
+
_syncResults() {
|
|
1051
|
+
if (!this._resultsRowsEl || !this._resultsEmptyEl) return;
|
|
1052
|
+
if (this._resultsListEl) {
|
|
1053
|
+
this._resultsListEl.setAttribute("aria-busy", this.loading ? "true" : "false");
|
|
1054
|
+
}
|
|
1055
|
+
const results = this.results;
|
|
1056
|
+
const stagedIds = new Set(this._stagedBasket.map((c) => c.id));
|
|
1057
|
+
this._resultsRowsEl.innerHTML = "";
|
|
1058
|
+
for (const item of results) {
|
|
1059
|
+
if (stagedIds.has(item.id)) continue;
|
|
1060
|
+
const row = document.createElement("div");
|
|
1061
|
+
row.className = "result-row";
|
|
1062
|
+
row.dataset["id"] = item.id;
|
|
1063
|
+
row.title = item.name;
|
|
1064
|
+
row.setAttribute("role", "option");
|
|
1065
|
+
row.setAttribute("aria-selected", "false");
|
|
1066
|
+
row.tabIndex = -1;
|
|
1067
|
+
const nameEl = document.createElement("span");
|
|
1068
|
+
nameEl.className = "result-name";
|
|
1069
|
+
nameEl.textContent = item.name;
|
|
1070
|
+
row.appendChild(nameEl);
|
|
1071
|
+
if (item.inactive) {
|
|
1072
|
+
const inactiveEl = document.createElement("span");
|
|
1073
|
+
inactiveEl.className = "result-inactive";
|
|
1074
|
+
inactiveEl.textContent = "(Inactive)";
|
|
1075
|
+
row.appendChild(inactiveEl);
|
|
1076
|
+
}
|
|
1077
|
+
const addBtn = document.createElement("span");
|
|
1078
|
+
addBtn.className = "result-add";
|
|
1079
|
+
addBtn.setAttribute("aria-hidden", "true");
|
|
1080
|
+
addBtn.innerHTML = getIconSvg("plus", 16);
|
|
1081
|
+
row.appendChild(addBtn);
|
|
1082
|
+
row.addEventListener("click", () => this._addBasket(item));
|
|
1083
|
+
this._resultsRowsEl.appendChild(row);
|
|
1084
|
+
}
|
|
1085
|
+
this._syncResultsEmptyText();
|
|
1086
|
+
this._syncResultsLimitNote();
|
|
1087
|
+
}
|
|
1088
|
+
_syncResultsEmptyText() {
|
|
1089
|
+
if (!this._resultsEmptyEl || !this._resultsRowsEl) return;
|
|
1090
|
+
const isEmpty = !this.loading && this._resultsRowsEl.children.length === 0;
|
|
1091
|
+
this._resultsEmptyEl.hidden = !isEmpty;
|
|
1092
|
+
if (!isEmpty) return;
|
|
1093
|
+
if (this._query.length < this.minQueryLength) {
|
|
1094
|
+
this._resultsEmptyEl.textContent = this._interp(
|
|
1095
|
+
this.getAttribute("label-min-chars") ?? "Type at least {n} characters to search.",
|
|
1096
|
+
{ n: this.minQueryLength }
|
|
1097
|
+
);
|
|
1098
|
+
} else {
|
|
1099
|
+
this._resultsEmptyEl.textContent = this._interp(
|
|
1100
|
+
this.getAttribute("label-no-results") ?? "No {entity} found.",
|
|
1101
|
+
{ entity: this.entityLabel }
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
// Surfaces when the consumer's search caps what it returns (e.g. a server
|
|
1106
|
+
// that found 1,000 matches but only sent the top 200) — otherwise a user
|
|
1107
|
+
// has no way to know their match wasn't in the list and just isn't there.
|
|
1108
|
+
// Copy pushes the user toward the fix ("add more characters to narrow"),
|
|
1109
|
+
// not just informing them of the cap.
|
|
1110
|
+
_syncResultsLimitNote() {
|
|
1111
|
+
if (!this._resultsLimitNoteEl) return;
|
|
1112
|
+
const shown = this.results.length;
|
|
1113
|
+
const total = this.resultsTotal;
|
|
1114
|
+
const isCapped = !this.loading && total !== null && total > shown;
|
|
1115
|
+
this._resultsLimitNoteEl.hidden = !isCapped;
|
|
1116
|
+
if (!isCapped) return;
|
|
1117
|
+
const template = this.getAttribute("label-results-cap") ?? "Showing first {n} results. Add more characters to narrow.";
|
|
1118
|
+
this._resultsLimitNoteEl.textContent = this._interp(template, { n: shown });
|
|
1119
|
+
}
|
|
1120
|
+
// BTN-10322 — the basket pane renders the STAGED basket (edited live in
|
|
1121
|
+
// the panel; committed to the `basket` attribute when the panel closes —
|
|
1122
|
+
// see onAfterClose). The committed `basket` attribute is what the trigger
|
|
1123
|
+
// pill / _syncTriggerSummary reflects in the meantime.
|
|
1124
|
+
_syncBasket() {
|
|
1125
|
+
if (!this._basketListEl || !this._basketEmptyEl || !this._basketClearAllEl) return;
|
|
1126
|
+
const basket = this._stagedBasket;
|
|
1127
|
+
this._basketListEl.querySelectorAll(".basket-row").forEach((row) => row.remove());
|
|
1128
|
+
for (const item of basket) {
|
|
1129
|
+
const row = document.createElement("div");
|
|
1130
|
+
row.className = "basket-row";
|
|
1131
|
+
row.dataset["id"] = item.id;
|
|
1132
|
+
row.title = item.name;
|
|
1133
|
+
const nameEl = document.createElement("span");
|
|
1134
|
+
nameEl.className = "basket-row-name";
|
|
1135
|
+
nameEl.textContent = item.name;
|
|
1136
|
+
const removeBtn = document.createElement("button");
|
|
1137
|
+
removeBtn.type = "button";
|
|
1138
|
+
removeBtn.className = "basket-row-remove";
|
|
1139
|
+
removeBtn.setAttribute("aria-label", `Remove ${item.name}`);
|
|
1140
|
+
removeBtn.innerHTML = getIconSvg("x", 10);
|
|
1141
|
+
removeBtn.addEventListener("click", () => this._removeBasket(item.id));
|
|
1142
|
+
row.append(nameEl, removeBtn);
|
|
1143
|
+
this._basketListEl.appendChild(row);
|
|
1144
|
+
}
|
|
1145
|
+
this._basketClearAllEl.hidden = basket.length === 0;
|
|
1146
|
+
this._basketClearAllEl.textContent = this._interp(
|
|
1147
|
+
this.getAttribute("label-clear-all") ?? "Clear all ({count})",
|
|
1148
|
+
{ count: basket.length }
|
|
1149
|
+
);
|
|
1150
|
+
this._basketEmptyEl.hidden = basket.length > 0;
|
|
1151
|
+
this._syncBasketRowColors();
|
|
1152
|
+
this._syncBasketHeaderLabel();
|
|
1153
|
+
this._syncBasketEmptyText();
|
|
1154
|
+
this._syncResults();
|
|
1155
|
+
}
|
|
1156
|
+
_syncBasketHeaderLabel() {
|
|
1157
|
+
if (!this._basketHeaderLabelEl) return;
|
|
1158
|
+
this._basketHeaderLabelEl.textContent = this._stagedMode === "exclude" ? "Excluded" : "Included";
|
|
1159
|
+
}
|
|
1160
|
+
_syncBasketRowColors() {
|
|
1161
|
+
if (!this._basketListEl) return;
|
|
1162
|
+
const isExclude = this._stagedMode === "exclude";
|
|
1163
|
+
this._basketListEl.querySelectorAll(".basket-row").forEach((row) => row.classList.toggle("mode-exclude", isExclude));
|
|
1164
|
+
}
|
|
1165
|
+
_syncBasketEmptyText() {
|
|
1166
|
+
if (!this._basketEmptyEl) return;
|
|
1167
|
+
const template = this.getAttribute("label-empty-basket") ?? "Nothing yet \u2014 report runs on all {entity}.";
|
|
1168
|
+
this._basketEmptyEl.textContent = this._interp(template, { entity: this.entityLabel });
|
|
1169
|
+
}
|
|
1170
|
+
// The trigger pill / summary reflects COMMITTED state (the `basket` +
|
|
1171
|
+
// `mode` attributes) — staged edits inside the panel do not update it
|
|
1172
|
+
// until Apply commits them.
|
|
1173
|
+
_syncTriggerSummary() {
|
|
1174
|
+
if (!this._pillTextEl || !this._pillEl || !this._pillClearEl) return;
|
|
1175
|
+
const basket = this.basket;
|
|
1176
|
+
const count = basket.length;
|
|
1177
|
+
const isExclude = this.mode === "exclude";
|
|
1178
|
+
const entity = this.entityLabel;
|
|
1179
|
+
if (count === 0) {
|
|
1180
|
+
this._pillTextEl.textContent = this._interp(
|
|
1181
|
+
this.getAttribute("label-all") ?? "All {entity}",
|
|
1182
|
+
{ entity }
|
|
1183
|
+
);
|
|
1184
|
+
} else if (isExclude) {
|
|
1185
|
+
this._pillTextEl.textContent = this._interp(
|
|
1186
|
+
this.getAttribute("label-except") ?? "All except {count}",
|
|
1187
|
+
{ count, entity }
|
|
1188
|
+
);
|
|
1189
|
+
} else {
|
|
1190
|
+
this._pillTextEl.textContent = this._interp(
|
|
1191
|
+
this.getAttribute("label-count") ?? "{count} selected",
|
|
1192
|
+
{ count, entity }
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
this._pillEl.classList.toggle("mode-exclude", count > 0 && isExclude);
|
|
1196
|
+
this._pillClearEl.hidden = count === 0;
|
|
1197
|
+
}
|
|
1198
|
+
// BTN-10322 — interpolate `{count}`, `{n}`, `{entity}` tokens in a
|
|
1199
|
+
// consumer-provided template. `{n}` is a synonym of `{count}` so consumers
|
|
1200
|
+
// can pick whichever reads better in their locale.
|
|
1201
|
+
_interp(template, tokens = {}) {
|
|
1202
|
+
let out = template;
|
|
1203
|
+
if (tokens.count !== void 0) {
|
|
1204
|
+
out = out.replace(/\{count\}/g, String(tokens.count));
|
|
1205
|
+
out = out.replace(/\{n\}/g, String(tokens.n ?? tokens.count));
|
|
1206
|
+
} else if (tokens.n !== void 0) {
|
|
1207
|
+
out = out.replace(/\{n\}/g, String(tokens.n));
|
|
1208
|
+
}
|
|
1209
|
+
if (tokens.entity !== void 0) {
|
|
1210
|
+
out = out.replace(/\{entity\}/g, tokens.entity);
|
|
1211
|
+
}
|
|
1212
|
+
return out;
|
|
1213
|
+
}
|
|
1214
|
+
// ── Staged state mutations (inside the panel) ────────────
|
|
1215
|
+
// Every mutation below edits the STAGED state only — the committed `basket`
|
|
1216
|
+
// and `mode` attributes don't move immediately, so the trigger pill doesn't
|
|
1217
|
+
// update on every single add/remove/mode-toggle keystroke. They commit when
|
|
1218
|
+
// the panel closes, by whichever path: Apply, Escape, outside-click, or the
|
|
1219
|
+
// trigger toggle (see `onAfterClose` in `_render()`). Nothing is discarded —
|
|
1220
|
+
// closing the panel is itself the confirmation.
|
|
1221
|
+
_addBasket(item) {
|
|
1222
|
+
if (this._stagedBasket.some((c) => c.id === item.id)) return;
|
|
1223
|
+
this._stagedBasket = [...this._stagedBasket, item];
|
|
1224
|
+
this._syncBasket();
|
|
1225
|
+
this._dispatchBasketChange();
|
|
1226
|
+
}
|
|
1227
|
+
_removeBasket(id) {
|
|
1228
|
+
this._stagedBasket = this._stagedBasket.filter((c) => c.id !== id);
|
|
1229
|
+
this._syncBasket();
|
|
1230
|
+
this._dispatchBasketChange();
|
|
1231
|
+
}
|
|
1232
|
+
_clearAllStaged() {
|
|
1233
|
+
this._stagedBasket = [];
|
|
1234
|
+
this._syncBasket();
|
|
1235
|
+
this._dispatchBasketChange();
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* The trigger's pill-clear (`x`) is an explicit user action that means "back
|
|
1239
|
+
* to the neutral state". Unlike the basket's "Clear all" (which only touches
|
|
1240
|
+
* staged), this commits immediately — the pill lives on the trigger, which
|
|
1241
|
+
* only reflects committed state. Dispatches `bv-apply` because a commit
|
|
1242
|
+
* happened.
|
|
1243
|
+
*/
|
|
1244
|
+
_clearCommitted() {
|
|
1245
|
+
var _a;
|
|
1246
|
+
this._query = "";
|
|
1247
|
+
(_a = this._searchEl) == null ? void 0 : _a.setAttribute("value", "");
|
|
1248
|
+
this.basket = [];
|
|
1249
|
+
this._dispatchApply();
|
|
1250
|
+
}
|
|
1251
|
+
// ── Commit ───────────────────────────────────────────────
|
|
1252
|
+
_stagedDiffersFromCommitted() {
|
|
1253
|
+
if (this._stagedMode !== this.mode) return true;
|
|
1254
|
+
const committed = this.basket;
|
|
1255
|
+
if (committed.length !== this._stagedBasket.length) return true;
|
|
1256
|
+
const committedIds = new Set(committed.map((c) => c.id));
|
|
1257
|
+
return this._stagedBasket.some((c) => !committedIds.has(c.id));
|
|
1258
|
+
}
|
|
1259
|
+
// Pushes staged → committed and fires `bv-apply`. Shared by the Apply
|
|
1260
|
+
// button and by `onAfterClose` (any other close path) — see the comment on
|
|
1261
|
+
// `_stagedBasket` for why closing itself commits.
|
|
1262
|
+
_commitStaged() {
|
|
1263
|
+
this.mode = this._stagedMode;
|
|
1264
|
+
this.basket = [...this._stagedBasket];
|
|
1265
|
+
this._dispatchApply();
|
|
1266
|
+
}
|
|
1267
|
+
_apply() {
|
|
1268
|
+
var _a;
|
|
1269
|
+
this._commitStaged();
|
|
1270
|
+
(_a = this._dropdown) == null ? void 0 : _a.close();
|
|
1271
|
+
}
|
|
1272
|
+
// ── Event dispatch ───────────────────────────────────────
|
|
1273
|
+
_dispatchBasketChange() {
|
|
1274
|
+
this.dispatchEvent(
|
|
1275
|
+
new CustomEvent("bv-basket-change", {
|
|
1276
|
+
bubbles: true,
|
|
1277
|
+
composed: true,
|
|
1278
|
+
detail: { mode: this._stagedMode, basket: [...this._stagedBasket] }
|
|
1279
|
+
})
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
_dispatchModeChange() {
|
|
1283
|
+
this.dispatchEvent(
|
|
1284
|
+
new CustomEvent("bv-mode-change", {
|
|
1285
|
+
bubbles: true,
|
|
1286
|
+
composed: true,
|
|
1287
|
+
detail: { mode: this._stagedMode }
|
|
1288
|
+
})
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
_dispatchShowInactiveChange() {
|
|
1292
|
+
this.dispatchEvent(
|
|
1293
|
+
new CustomEvent("bv-show-inactive-change", {
|
|
1294
|
+
bubbles: true,
|
|
1295
|
+
composed: true,
|
|
1296
|
+
detail: { showInactive: this.showInactive }
|
|
1297
|
+
})
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
_dispatchApply() {
|
|
1301
|
+
this.dispatchEvent(
|
|
1302
|
+
new CustomEvent("bv-apply", {
|
|
1303
|
+
bubbles: true,
|
|
1304
|
+
composed: true,
|
|
1305
|
+
detail: { mode: this.mode, basket: this.basket }
|
|
1306
|
+
})
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
_dispatchSearch() {
|
|
1310
|
+
if (this._query.length < this.minQueryLength) return;
|
|
1311
|
+
this._searchRequestId += 1;
|
|
1312
|
+
const requestId = this._searchRequestId;
|
|
1313
|
+
this.dispatchEvent(
|
|
1314
|
+
new CustomEvent("bv-search", {
|
|
1315
|
+
bubbles: true,
|
|
1316
|
+
composed: true,
|
|
1317
|
+
detail: { query: this._query, requestId }
|
|
1318
|
+
})
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
// BTN-10322 — internal debounce. Runs the search only after the user
|
|
1322
|
+
// stops typing for `debounce-ms` (default 300ms). Also fires on show-inactive
|
|
1323
|
+
// toggle, since that changes the search space.
|
|
1324
|
+
_scheduleSearchDispatch() {
|
|
1325
|
+
if (this._searchDebounceHandle !== null) {
|
|
1326
|
+
clearTimeout(this._searchDebounceHandle);
|
|
1327
|
+
}
|
|
1328
|
+
this._searchDebounceHandle = setTimeout(() => {
|
|
1329
|
+
this._searchDebounceHandle = null;
|
|
1330
|
+
this._dispatchSearch();
|
|
1331
|
+
}, this.debounceMs);
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* BTN-10322 — Arrow Up/Down navigation between result rows; Enter or Space
|
|
1335
|
+
* adds the currently-focused row to the staged basket. Home/End jump to
|
|
1336
|
+
* first/last. Escape delegates to the shared dropdown-controller (already
|
|
1337
|
+
* wired on the host).
|
|
1338
|
+
*
|
|
1339
|
+
* `role="option"` rows use `aria-selected` only to communicate focus
|
|
1340
|
+
* position to assistive tech — clicking / Enter also fires the add flow,
|
|
1341
|
+
* which then removes the row from the results list.
|
|
1342
|
+
*/
|
|
1343
|
+
_onResultsKeyDown(e) {
|
|
1344
|
+
var _a;
|
|
1345
|
+
if (!this._resultsRowsEl) return;
|
|
1346
|
+
const rows = Array.from(this._resultsRowsEl.querySelectorAll(".result-row"));
|
|
1347
|
+
if (rows.length === 0) return;
|
|
1348
|
+
const currentIdx = rows.findIndex((row) => row.getAttribute("aria-selected") === "true");
|
|
1349
|
+
const focusRow = (idx) => {
|
|
1350
|
+
var _a2;
|
|
1351
|
+
const bounded = Math.max(0, Math.min(idx, rows.length - 1));
|
|
1352
|
+
rows.forEach((row, i) => row.setAttribute("aria-selected", i === bounded ? "true" : "false"));
|
|
1353
|
+
(_a2 = rows[bounded]) == null ? void 0 : _a2.focus();
|
|
1354
|
+
};
|
|
1355
|
+
switch (e.key) {
|
|
1356
|
+
case "ArrowDown":
|
|
1357
|
+
e.preventDefault();
|
|
1358
|
+
focusRow(currentIdx < 0 ? 0 : currentIdx + 1);
|
|
1359
|
+
break;
|
|
1360
|
+
case "ArrowUp":
|
|
1361
|
+
e.preventDefault();
|
|
1362
|
+
focusRow(currentIdx <= 0 ? 0 : currentIdx - 1);
|
|
1363
|
+
break;
|
|
1364
|
+
case "Home":
|
|
1365
|
+
e.preventDefault();
|
|
1366
|
+
focusRow(0);
|
|
1367
|
+
break;
|
|
1368
|
+
case "End":
|
|
1369
|
+
e.preventDefault();
|
|
1370
|
+
focusRow(rows.length - 1);
|
|
1371
|
+
break;
|
|
1372
|
+
case "Enter":
|
|
1373
|
+
case " ": {
|
|
1374
|
+
if (currentIdx < 0) return;
|
|
1375
|
+
e.preventDefault();
|
|
1376
|
+
const focusedId = (_a = rows[currentIdx]) == null ? void 0 : _a.dataset["id"];
|
|
1377
|
+
if (!focusedId) return;
|
|
1378
|
+
const item = this.results.find((r) => r.id === focusedId);
|
|
1379
|
+
if (item) this._addBasket(item);
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
// ── Public API (consumer-facing methods) ────────────────
|
|
1385
|
+
/**
|
|
1386
|
+
* BTN-10322 — count of the currently committed basket. Consumers can read
|
|
1387
|
+
* this to render an external counter badge without duplicating state.
|
|
1388
|
+
*/
|
|
1389
|
+
get count() {
|
|
1390
|
+
return this.basket.length;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* BTN-10322 — cascade reset from outside the panel. This is THE canonical
|
|
1394
|
+
* way for a consumer to clear the picker in response to an upstream filter
|
|
1395
|
+
* change (e.g. CET Report when Categories or Groups change). It:
|
|
1396
|
+
*
|
|
1397
|
+
* - clears the committed `basket` and the staged edit,
|
|
1398
|
+
* - clears `results` + `resultsTotal` (post-cascade the last search's
|
|
1399
|
+
* rows are stale — leaving them visible next to the cleared banner
|
|
1400
|
+
* is confusing),
|
|
1401
|
+
* - clears the internal search query,
|
|
1402
|
+
* - surfaces the "cleared" affordance banner with `{ reason }` interpolated
|
|
1403
|
+
* into the copy,
|
|
1404
|
+
* - dispatches `bv-cleared` with `{ reason }`.
|
|
1405
|
+
*
|
|
1406
|
+
* Does NOT dispatch `bv-apply` — this is not a user commit, it's an
|
|
1407
|
+
* out-of-band cascade. The consumer that triggered it already knows the
|
|
1408
|
+
* basket was cleared.
|
|
1409
|
+
*
|
|
1410
|
+
* The affordance auto-hides the next time the user opens the panel or
|
|
1411
|
+
* dismisses it explicitly via the banner's close button.
|
|
1412
|
+
*
|
|
1413
|
+
* **Prefer this over `element.basket = []`** — the setter is silent (no
|
|
1414
|
+
* bv-cleared, no visible affordance) and is intended for state restoration
|
|
1415
|
+
* from URL/deep-link, not for cascade reset.
|
|
1416
|
+
*/
|
|
1417
|
+
reset({ reason }) {
|
|
1418
|
+
var _a;
|
|
1419
|
+
this.basket = [];
|
|
1420
|
+
this._stagedBasket = [];
|
|
1421
|
+
this._query = "";
|
|
1422
|
+
(_a = this._searchEl) == null ? void 0 : _a.setAttribute("value", "");
|
|
1423
|
+
this.results = [];
|
|
1424
|
+
this.resultsTotal = null;
|
|
1425
|
+
this._clearedReason = reason;
|
|
1426
|
+
this._syncClearedAffordance();
|
|
1427
|
+
this.dispatchEvent(
|
|
1428
|
+
new CustomEvent("bv-cleared", {
|
|
1429
|
+
bubbles: true,
|
|
1430
|
+
composed: true,
|
|
1431
|
+
detail: { reason }
|
|
1432
|
+
})
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
_syncClearedAffordance() {
|
|
1436
|
+
if (!this._clearedAffordanceEl || !this._clearedAffordanceMsgEl) return;
|
|
1437
|
+
if (this._clearedReason === null) {
|
|
1438
|
+
this._clearedAffordanceEl.hidden = true;
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
const template = this.getAttribute("label-cleared") ?? "Your {entity} selection was cleared ({reason}).";
|
|
1442
|
+
let msg = template;
|
|
1443
|
+
msg = msg.replace(/\{entity\}/g, this.entityLabel);
|
|
1444
|
+
msg = msg.replace(/\{reason\}/g, this._clearedReason);
|
|
1445
|
+
this._clearedAffordanceMsgEl.textContent = msg;
|
|
1446
|
+
this._clearedAffordanceEl.hidden = false;
|
|
1447
|
+
}
|
|
1448
|
+
_dismissClearedAffordance() {
|
|
1449
|
+
this._clearedReason = null;
|
|
1450
|
+
this._syncClearedAffordance();
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
if (!customElements.get("bv-search-picker")) {
|
|
1454
|
+
customElements.define("bv-search-picker", BvSearchPicker);
|
|
1455
|
+
}
|
|
1456
|
+
export {
|
|
1457
|
+
BvSearchPicker
|
|
1458
|
+
};
|