@bluevolt-tech/lumen 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +37 -23
  2. package/dist/components/_shared/adopt-styles.d.ts +8 -0
  3. package/dist/components/_shared/date-range-validator.d.ts +61 -0
  4. package/dist/components/_shared/dropdown-controller.d.ts +31 -0
  5. package/dist/components/_shared/selectable-card.d.ts +10 -0
  6. package/dist/components/bv-alert-banner/bv-alert-banner.js +19 -17
  7. package/dist/components/bv-avatar/bv-avatar.js +19 -11
  8. package/dist/components/bv-badge/bv-badge.js +19 -11
  9. package/dist/components/bv-button/bv-button.js +18 -16
  10. package/dist/components/bv-category-pill/bv-category-pill.js +18 -16
  11. package/dist/components/bv-checkbox/bv-checkbox.js +19 -11
  12. package/dist/components/bv-chip/bv-chip.js +19 -11
  13. package/dist/components/bv-course-detail/bv-course-detail.js +20 -34
  14. package/dist/components/bv-course-detail-modal/bv-course-detail-modal.js +24 -90
  15. package/dist/components/bv-course-grid-card/bv-course-grid-card.d.ts +2 -6
  16. package/dist/components/bv-course-grid-card/bv-course-grid-card.js +98 -105
  17. package/dist/components/bv-course-list-card/bv-course-list-card.d.ts +2 -6
  18. package/dist/components/bv-course-list-card/bv-course-list-card.js +98 -105
  19. package/dist/components/bv-course-outline/bv-course-outline.js +19 -11
  20. package/dist/components/bv-date-range-input/bv-date-range-input.d.ts +4 -6
  21. package/dist/components/bv-date-range-input/bv-date-range-input.js +460 -122
  22. package/dist/components/bv-date-range-picker/bv-date-range-picker.d.ts +17 -3
  23. package/dist/components/bv-date-range-picker/bv-date-range-picker.js +339 -73
  24. package/dist/components/bv-empty-state/bv-empty-state.js +20 -28
  25. package/dist/components/bv-filter-dropdown/bv-filter-dropdown.d.ts +1 -4
  26. package/dist/components/bv-filter-dropdown/bv-filter-dropdown.js +78 -53
  27. package/dist/components/bv-filter-panel/bv-filter-panel.js +20 -22
  28. package/dist/components/bv-icon/bv-icon.js +19 -11
  29. package/dist/components/bv-input/bv-input.js +18 -16
  30. package/dist/components/bv-input-dropdown/bv-input-dropdown.d.ts +1 -4
  31. package/dist/components/bv-input-dropdown/bv-input-dropdown.js +77 -78
  32. package/dist/components/bv-modal/bv-modal.js +19 -11
  33. package/dist/components/bv-page-title/bv-page-title.js +19 -6
  34. package/dist/components/bv-pagination/bv-pagination.d.ts +1 -0
  35. package/dist/components/bv-pagination/bv-pagination.js +23 -186
  36. package/dist/components/bv-portal-header/bv-portal-header.js +25 -83
  37. package/dist/components/bv-portal-layout/bv-portal-layout.js +19 -11
  38. package/dist/components/bv-progress-bar/bv-progress-bar.js +19 -11
  39. package/dist/components/bv-promo-banner/bv-promo-banner.js +19 -11
  40. package/dist/components/bv-radio/bv-radio.js +19 -11
  41. package/dist/components/bv-searchbox/bv-searchbox.js +19 -11
  42. package/dist/components/bv-segmented-control/bv-segmented-control.js +18 -16
  43. package/dist/components/bv-select/bv-select.d.ts +1 -4
  44. package/dist/components/bv-select/bv-select.js +76 -44
  45. package/dist/components/bv-split-button/bv-split-button.d.ts +1 -4
  46. package/dist/components/bv-split-button/bv-split-button.js +76 -42
  47. package/dist/components/bv-star-rating/bv-star-rating.js +19 -11
  48. package/dist/components/bv-table/bv-table.d.ts +5 -0
  49. package/dist/components/bv-table/bv-table.js +69 -19
  50. package/dist/components/bv-toggle/bv-toggle.js +19 -11
  51. package/dist/components/bv-tooltip/bv-tooltip.js +19 -17
  52. package/dist/components/bv-user-row/bv-user-row.js +22 -50
  53. package/dist/components/bv-view-switcher/bv-view-switcher.js +19 -11
  54. package/dist/themes/base.css +1 -1
  55. package/package.json +8 -1
  56. package/themes/base.css +1 -1
@@ -1,8 +1,148 @@
1
1
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-date-range-input/bv-date-range-input.css
2
- var bv_date_range_input_default = ":host {\n display: block;\n position: relative;\n font-family: var(--bv-font-family-base);\n}\n\n/* \u2500\u2500 Label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.field-label {\n display: block;\n font-size: var(--bv-font-size-sm);\n line-height: 20px;\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n margin-bottom: 6px;\n}\n\n.required-mark {\n color: var(--bv-color-danger);\n margin-left: 2px;\n}\n\n/* \u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n height: 44px;\n box-sizing: border-box;\n padding: 0 var(--bv-spacing-md);\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n cursor: pointer;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n text-align: left;\n transition:\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n.trigger:focus-visible {\n outline: none;\n border-color: var(--bv-color-primary);\n box-shadow:\n 0 0 0 3px var(--bv-color-primary-light),\n 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n:host([open]) .trigger {\n border-color: var(--bv-color-primary);\n box-shadow:\n 0 0 0 3px var(--bv-color-primary-light),\n 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n.trigger-display {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.trigger-display.is-placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.trigger-icon {\n display: flex;\n flex-shrink: 0;\n margin-left: var(--bv-spacing-sm);\n color: var(--bv-color-neutral-400);\n transition: color var(--bv-transition-fast);\n}\n\n:host([open]) .trigger-icon {\n color: var(--bv-color-primary);\n}\n\n/* \u2500\u2500 Picker popup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.picker-popup {\n display: none;\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: 300;\n}\n\n:host([open]) .picker-popup {\n display: block;\n}\n";
2
+ var bv_date_range_input_default = ":host {\n display: block;\n position: relative;\n font-family: var(--bv-font-family-base);\n}\n\n/* \u2500\u2500 Label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.field-label {\n display: block;\n font-size: var(--bv-font-size-sm);\n line-height: 20px;\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n margin-bottom: 6px;\n}\n\n.required-mark {\n color: var(--bv-color-danger);\n margin-left: 2px;\n}\n\n/* \u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n height: 44px;\n box-sizing: border-box;\n padding: 0 var(--bv-spacing-md);\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n cursor: pointer;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-800);\n text-align: left;\n transition:\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n.trigger:focus-visible {\n outline: none;\n border-color: var(--bv-color-primary);\n box-shadow:\n 0 0 0 3px var(--bv-color-primary-light),\n 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n:host([open]) .trigger {\n border-color: var(--bv-color-primary);\n box-shadow:\n 0 0 0 3px var(--bv-color-primary-light),\n 0 1px 2px 0 rgba(16, 24, 40, 0.05);\n}\n\n/* BTN-10191 \u2014 reflected from the internal picker's validity state */\n.trigger.invalid,\n.trigger.invalid:focus-visible,\n:host([open]) .trigger.invalid {\n border-color: var(--bv-color-danger);\n box-shadow: 0 0 0 3px var(--bv-color-danger-light);\n}\n\n.trigger-display {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.trigger-display.is-placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.trigger-icon {\n display: flex;\n flex-shrink: 0;\n margin-left: var(--bv-spacing-sm);\n color: var(--bv-color-neutral-400);\n transition: color var(--bv-transition-fast);\n}\n\n:host([open]) .trigger-icon {\n color: var(--bv-color-primary);\n}\n\n/* \u2500\u2500 Picker popup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.picker-popup {\n display: none;\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n z-index: 300;\n}\n\n:host([open]) .picker-popup {\n display: block;\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
+ const shadow = host.shadowRoot;
25
+ if (!shadow) {
26
+ throw new Error("bindDropdownController requires the host to have an open shadow root.");
27
+ }
28
+ const syncExpanded = () => {
29
+ var _a;
30
+ (_a = options.trigger) == null ? void 0 : _a.setAttribute("aria-expanded", host.hasAttribute("open") ? "true" : "false");
31
+ };
32
+ const open = () => {
33
+ var _a;
34
+ if (host.hasAttribute("open")) return;
35
+ (_a = options.onBeforeOpen) == null ? void 0 : _a.call(options);
36
+ host.setAttribute("open", "");
37
+ syncExpanded();
38
+ host.dispatchEvent(new CustomEvent("bv-open", { bubbles: true, composed: true }));
39
+ };
40
+ const close = () => {
41
+ if (!host.hasAttribute("open")) return;
42
+ host.removeAttribute("open");
43
+ syncExpanded();
44
+ };
45
+ const toggle = () => {
46
+ if (host.hasAttribute("open")) close();
47
+ else open();
48
+ };
49
+ const offClick = (e) => {
50
+ if (!host.hasAttribute("open")) return;
51
+ const target = e.target;
52
+ if (host.contains(target) || shadow.contains(target)) return;
53
+ close();
54
+ };
55
+ document.addEventListener("click", offClick);
56
+ const onKeyDown = (e) => {
57
+ if (e.key === "Escape") close();
58
+ };
59
+ host.addEventListener("keydown", onKeyDown);
60
+ const observer = new MutationObserver(syncExpanded);
61
+ observer.observe(host, { attributes: true, attributeFilter: ["open"] });
62
+ const cleanup = () => {
63
+ observer.disconnect();
64
+ document.removeEventListener("click", offClick);
65
+ host.removeEventListener("keydown", onKeyDown);
66
+ };
67
+ syncExpanded();
68
+ return { open, close, toggle, cleanup };
69
+ }
3
70
 
4
71
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-date-range-picker/bv-date-range-picker.css
5
- var bv_date_range_picker_default = ":host {\n display: inline-block;\n font-family: var(--bv-font-family-base);\n}\n\n/* \u2500\u2500 Picker shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.picker {\n display: flex;\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-200);\n border-radius: var(--bv-radius-xl);\n box-shadow: var(--bv-shadow-lg);\n overflow: hidden;\n user-select: none;\n}\n\n/* \u2500\u2500 Sidebar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.sidebar {\n display: flex;\n flex-direction: column;\n width: 152px;\n flex-shrink: 0;\n padding: var(--bv-spacing-sm) 0;\n border-right: 1px solid var(--bv-color-neutral-200);\n}\n\n.preset-btn {\n display: block;\n width: 100%;\n padding: 10px var(--bv-spacing-md);\n text-align: left;\n background: none;\n border: none;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n cursor: pointer;\n transition:\n background var(--bv-transition-fast),\n color var(--bv-transition-fast);\n white-space: nowrap;\n}\n\n.preset-btn:hover {\n background: var(--bv-color-secondary);\n color: var(--bv-color-neutral-900);\n}\n\n.sidebar-sep {\n margin: var(--bv-spacing-xs) 0;\n border: none;\n border-top: 1px solid var(--bv-color-neutral-200);\n}\n\n.preset-btn.active {\n background: var(--bv-color-secondary-light);\n color: var(--bv-color-neutral-900);\n font-weight: var(--bv-font-weight-medium);\n}\n\n/* \u2500\u2500 Main 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 */\n\n.main {\n display: flex;\n flex-direction: column;\n flex: 1;\n}\n\n/* \u2500\u2500 Calendars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.calendars {\n display: flex;\n border-bottom: 1px solid var(--bv-color-neutral-200);\n}\n\n.calendar {\n padding: var(--bv-spacing-md) var(--bv-spacing-lg);\n flex: 1;\n}\n\n.calendar--left {\n border-right: 1px solid var(--bv-color-neutral-200);\n}\n\n/* \u2500\u2500 Calendar header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.calendar-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: var(--bv-spacing-md);\n}\n\n.calendar-title {\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n}\n\n.nav-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n background: none;\n border: none;\n border-radius: var(--bv-radius-md);\n color: var(--bv-color-neutral-500);\n cursor: pointer;\n transition:\n background var(--bv-transition-fast),\n color var(--bv-transition-fast);\n flex-shrink: 0;\n}\n\n.nav-btn:hover {\n background: var(--bv-color-secondary);\n color: var(--bv-color-neutral-800);\n}\n\n/* \u2500\u2500 Weekdays row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.weekdays {\n display: grid;\n grid-template-columns: repeat(7, 36px);\n gap: 0;\n margin-bottom: var(--bv-spacing-xs);\n}\n\n.weekday {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 28px;\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-500);\n}\n\n/* \u2500\u2500 Days grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.days-grid {\n display: grid;\n grid-template-columns: repeat(7, 36px);\n gap: 0;\n}\n\n/* \u2500\u2500 Day cell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.day {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n border: none;\n background: none;\n cursor: pointer;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n border-radius: 0;\n transition: background var(--bv-transition-fast);\n}\n\n.day:hover:not(.start):not(.end) .day-inner {\n background: var(--bv-color-secondary);\n color: var(--bv-color-neutral-900);\n}\n\n.day--blank {\n pointer-events: none;\n}\n\n.day.today:not(.start):not(.end) .day-inner::after {\n content: '';\n position: absolute;\n bottom: 2px;\n left: 50%;\n transform: translateX(-50%);\n width: 4px;\n height: 4px;\n border-radius: var(--bv-radius-full);\n background: var(--bv-color-primary);\n}\n\n/* \u2500\u2500 Range band \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.day.in-range {\n background: var(--bv-color-primary-light);\n}\n\n.day.in-range.week-start,\n.day.range-end.week-start {\n border-radius: var(--bv-radius-full) 0 0 var(--bv-radius-full);\n}\n\n.day.in-range.week-end,\n.day.range-start.week-end {\n border-radius: 0 var(--bv-radius-full) var(--bv-radius-full) 0;\n}\n\n.day.range-start {\n background: linear-gradient(to right, transparent 50%, var(--bv-color-primary-light) 50%);\n}\n\n.day.range-end {\n background: linear-gradient(to left, transparent 50%, var(--bv-color-primary-light) 50%);\n}\n\n/* \u2500\u2500 Day inner (circle) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.day-inner {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: var(--bv-radius-full);\n z-index: 1;\n transition:\n background var(--bv-transition-fast),\n color var(--bv-transition-fast);\n}\n\n.day.start .day-inner,\n.day.end .day-inner {\n background: var(--bv-color-primary);\n color: var(--bv-color-neutral-0);\n font-weight: var(--bv-font-weight-semibold);\n}\n\n.day.start:hover .day-inner,\n.day.end:hover .day-inner {\n background: var(--bv-color-primary-dark);\n}\n\n/* \u2500\u2500 5-year warning banner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.banner-wrap {\n display: none;\n padding: var(--bv-spacing-lg);\n}\n\n.picker--with-banner .banner-wrap {\n display: block;\n}\n\n/* Footer loses its top padding when the banner is shown above it */\n.picker--with-banner .footer {\n padding-top: 0;\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.footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--bv-spacing-lg);\n gap: var(--bv-spacing-md);\n}\n\n/* \u2500\u2500 Date inputs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.date-inputs {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n}\n\n.date-input {\n width: 112px;\n height: 36px;\n padding: 0 var(--bv-spacing-sm);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-700);\n text-align: center;\n outline: none;\n transition:\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n.date-input:focus {\n border-color: var(--bv-color-primary);\n box-shadow: 0 0 0 3px var(--bv-color-primary-light);\n}\n\n.date-input::placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.date-sep {\n color: var(--bv-color-neutral-400);\n font-size: var(--bv-font-size-sm);\n}\n\n/* \u2500\u2500 Action buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.footer-actions {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n flex-shrink: 0;\n}\n\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 36px;\n padding: 0 var(--bv-spacing-md);\n border-radius: var(--bv-radius-lg);\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n cursor: pointer;\n white-space: nowrap;\n transition:\n background var(--bv-transition-fast),\n border-color var(--bv-transition-fast),\n color var(--bv-transition-fast);\n}\n\n.btn-cancel {\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n color: var(--bv-color-neutral-700);\n}\n\n.btn-cancel:hover {\n background: var(--bv-color-secondary);\n border-color: var(--bv-color-neutral-400);\n}\n\n.btn-apply {\n background: var(--bv-color-primary);\n border: 1px solid var(--bv-color-primary);\n color: var(--bv-color-neutral-0);\n}\n\n.btn-apply:hover:not(:disabled) {\n background: var(--bv-color-primary-dark);\n border-color: var(--bv-color-primary-dark);\n}\n\n.btn-apply:disabled {\n background: var(--bv-color-neutral-300);\n border-color: var(--bv-color-neutral-300);\n cursor: not-allowed;\n}\n";
72
+ var bv_date_range_picker_default = ":host {\n display: inline-block;\n font-family: var(--bv-font-family-base);\n}\n\n/* \u2500\u2500 Picker shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.picker {\n display: flex;\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-200);\n border-radius: var(--bv-radius-xl);\n box-shadow: var(--bv-shadow-lg);\n overflow: hidden;\n user-select: none;\n}\n\n/* \u2500\u2500 Sidebar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.sidebar {\n display: flex;\n flex-direction: column;\n width: 152px;\n flex-shrink: 0;\n padding: var(--bv-spacing-sm) 0;\n border-right: 1px solid var(--bv-color-neutral-200);\n}\n\n.preset-btn {\n display: block;\n width: 100%;\n padding: 10px var(--bv-spacing-md);\n text-align: left;\n background: none;\n border: none;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n cursor: pointer;\n transition:\n background var(--bv-transition-fast),\n color var(--bv-transition-fast);\n white-space: nowrap;\n}\n\n.preset-btn:hover {\n background: var(--bv-color-secondary);\n color: var(--bv-color-neutral-900);\n}\n\n.sidebar-sep {\n margin: var(--bv-spacing-xs) 0;\n border: none;\n border-top: 1px solid var(--bv-color-neutral-200);\n}\n\n.preset-btn.active {\n background: var(--bv-color-secondary-light);\n color: var(--bv-color-neutral-900);\n font-weight: var(--bv-font-weight-medium);\n}\n\n/* \u2500\u2500 Main 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 */\n\n.main {\n display: flex;\n flex-direction: column;\n flex: 1;\n}\n\n/* \u2500\u2500 Calendars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.calendars {\n display: flex;\n border-bottom: 1px solid var(--bv-color-neutral-200);\n}\n\n.calendar {\n padding: var(--bv-spacing-md) var(--bv-spacing-lg);\n flex: 1;\n}\n\n.calendar--left {\n border-right: 1px solid var(--bv-color-neutral-200);\n}\n\n/* \u2500\u2500 Calendar header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.calendar-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: var(--bv-spacing-md);\n}\n\n.calendar-title {\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n color: var(--bv-color-neutral-800);\n}\n\n.nav-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n background: none;\n border: none;\n border-radius: var(--bv-radius-md);\n color: var(--bv-color-neutral-500);\n cursor: pointer;\n transition:\n background var(--bv-transition-fast),\n color var(--bv-transition-fast);\n flex-shrink: 0;\n}\n\n.nav-btn:hover {\n background: var(--bv-color-secondary);\n color: var(--bv-color-neutral-800);\n}\n\n/* \u2500\u2500 Weekdays row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.weekdays {\n display: grid;\n grid-template-columns: repeat(7, 36px);\n gap: 0;\n margin-bottom: var(--bv-spacing-xs);\n}\n\n.weekday {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 28px;\n font-size: var(--bv-font-size-xs);\n font-weight: var(--bv-font-weight-medium);\n color: var(--bv-color-neutral-500);\n}\n\n/* \u2500\u2500 Days grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.days-grid {\n display: grid;\n grid-template-columns: repeat(7, 36px);\n gap: 0;\n}\n\n/* \u2500\u2500 Day cell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.day {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n border: none;\n background: none;\n cursor: pointer;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n color: var(--bv-color-neutral-700);\n border-radius: 0;\n transition: background var(--bv-transition-fast);\n}\n\n.day:hover:not(.start):not(.end) .day-inner {\n background: var(--bv-color-secondary);\n color: var(--bv-color-neutral-900);\n}\n\n.day--blank {\n pointer-events: none;\n}\n\n.day.today:not(.start):not(.end) .day-inner::after {\n content: '';\n position: absolute;\n bottom: 2px;\n left: 50%;\n transform: translateX(-50%);\n width: 4px;\n height: 4px;\n border-radius: var(--bv-radius-full);\n background: var(--bv-color-primary);\n}\n\n/* \u2500\u2500 Range band \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.day.in-range {\n background: var(--bv-color-primary-light);\n}\n\n.day.in-range.week-start,\n.day.range-end.week-start {\n border-radius: var(--bv-radius-full) 0 0 var(--bv-radius-full);\n}\n\n.day.in-range.week-end,\n.day.range-start.week-end {\n border-radius: 0 var(--bv-radius-full) var(--bv-radius-full) 0;\n}\n\n.day.range-start {\n background: linear-gradient(to right, transparent 50%, var(--bv-color-primary-light) 50%);\n}\n\n.day.range-end {\n background: linear-gradient(to left, transparent 50%, var(--bv-color-primary-light) 50%);\n}\n\n/* \u2500\u2500 Day inner (circle) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.day-inner {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border-radius: var(--bv-radius-full);\n z-index: 1;\n transition:\n background var(--bv-transition-fast),\n color var(--bv-transition-fast);\n}\n\n.day.start .day-inner,\n.day.end .day-inner {\n background: var(--bv-color-primary);\n color: var(--bv-color-neutral-0);\n font-weight: var(--bv-font-weight-semibold);\n}\n\n.day.start:hover .day-inner,\n.day.end:hover .day-inner {\n background: var(--bv-color-primary-dark);\n}\n\n/* \u2500\u2500 5-year warning banner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.banner-wrap {\n display: none;\n padding: var(--bv-spacing-lg);\n}\n\n.picker--with-banner .banner-wrap {\n display: block;\n}\n\n/* Footer loses its top padding when the banner is shown above it */\n.picker--with-banner .footer {\n padding-top: 0;\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.footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--bv-spacing-lg);\n gap: var(--bv-spacing-md);\n}\n\n/* \u2500\u2500 Date inputs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.date-inputs {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n}\n\n.date-input {\n width: 112px;\n height: 36px;\n padding: 0 var(--bv-spacing-sm);\n border: 1px solid var(--bv-color-neutral-300);\n border-radius: var(--bv-radius-lg);\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n color: var(--bv-color-neutral-700);\n text-align: center;\n outline: none;\n transition:\n border-color var(--bv-transition-fast),\n box-shadow var(--bv-transition-fast);\n}\n\n.date-input:focus {\n border-color: var(--bv-color-primary);\n box-shadow: 0 0 0 3px var(--bv-color-primary-light);\n}\n\n.date-input::placeholder {\n color: var(--bv-color-neutral-400);\n}\n\n.date-sep {\n color: var(--bv-color-neutral-400);\n font-size: var(--bv-font-size-sm);\n}\n\n/* \u2500\u2500 Action buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.footer-actions {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-sm);\n flex-shrink: 0;\n}\n\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 36px;\n padding: 0 var(--bv-spacing-md);\n border-radius: var(--bv-radius-lg);\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n cursor: pointer;\n white-space: nowrap;\n transition:\n background var(--bv-transition-fast),\n border-color var(--bv-transition-fast),\n color var(--bv-transition-fast);\n}\n\n.btn-cancel {\n background: var(--bv-color-neutral-0);\n border: 1px solid var(--bv-color-neutral-300);\n color: var(--bv-color-neutral-700);\n}\n\n.btn-cancel:hover {\n background: var(--bv-color-secondary);\n border-color: var(--bv-color-neutral-400);\n}\n\n.btn-apply {\n background: var(--bv-color-primary);\n border: 1px solid var(--bv-color-primary);\n color: var(--bv-color-neutral-0);\n}\n\n.btn-apply:hover:not(:disabled) {\n background: var(--bv-color-primary-dark);\n border-color: var(--bv-color-primary-dark);\n}\n\n.btn-apply:disabled {\n background: var(--bv-color-neutral-300);\n border-color: var(--bv-color-neutral-300);\n cursor: not-allowed;\n}\n\n/* \u2500\u2500 BTN-10191 \xB7 validation error states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.date-input.invalid,\n.date-input.invalid:focus {\n border-color: var(--bv-color-danger);\n box-shadow: 0 0 0 3px var(--bv-color-danger-light);\n}\n\n.error-messages {\n padding: var(--bv-spacing-2xs) 0 0 0;\n}\n\n.input-error,\n.form-error {\n color: var(--bv-color-danger);\n font-size: var(--bv-font-size-xs);\n line-height: 1.25;\n margin-top: var(--bv-spacing-2xs);\n}\n\n.form-error {\n font-size: var(--bv-font-size-sm);\n margin-top: var(--bv-spacing-xs);\n}\n\n.day--disabled,\n.day[disabled] {\n color: var(--bv-color-neutral-300);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n.day--disabled .day-inner,\n.day[disabled] .day-inner {\n color: var(--bv-color-neutral-300);\n}\n";
73
+
74
+ // components/_shared/date-range-validator.ts
75
+ function validateDateRange(input, config = {}) {
76
+ const { start, end } = input;
77
+ const { min, max, maxRangeYears, validateOrder, validateFormat } = config;
78
+ const resolvedMax = max === "today" ? isoToday() : max;
79
+ let startError;
80
+ let endError;
81
+ const startDate = start ? parseIso(start) : null;
82
+ const endDate = end ? parseIso(end) : null;
83
+ if (validateFormat) {
84
+ if (start && !startDate) startError = "format";
85
+ if (end && !endDate) endError = "format";
86
+ }
87
+ if (!startError && startDate) {
88
+ if (min && belowMin(startDate, min)) startError = "min";
89
+ else if (resolvedMax && aboveMax(startDate, resolvedMax)) startError = "max";
90
+ }
91
+ if (!endError && endDate) {
92
+ if (min && belowMin(endDate, min)) endError = "min";
93
+ else if (resolvedMax && aboveMax(endDate, resolvedMax)) endError = "max";
94
+ }
95
+ let formError;
96
+ const bothFieldsClean = startDate && endDate && !startError && !endError;
97
+ if (bothFieldsClean) {
98
+ if (validateOrder && startDate > endDate) {
99
+ formError = "order";
100
+ } else if (maxRangeYears !== void 0 && exceedsYearSpan(startDate, endDate, maxRangeYears)) {
101
+ formError = "range-limit";
102
+ }
103
+ }
104
+ const valid = !startError && !endError && !formError;
105
+ const applyEnabled = valid && !!startDate && !!endDate;
106
+ return {
107
+ valid,
108
+ applyEnabled,
109
+ ...startError && { startError },
110
+ ...endError && { endError },
111
+ ...formError && { formError }
112
+ };
113
+ }
114
+ function parseIso(s) {
115
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
116
+ if (!m) return null;
117
+ const [, yStr, mStr, dStr] = m;
118
+ const y = Number(yStr);
119
+ const mo = Number(mStr) - 1;
120
+ const d = Number(dStr);
121
+ const dt = new Date(y, mo, d);
122
+ if (dt.getFullYear() !== y || dt.getMonth() !== mo || dt.getDate() !== d) return null;
123
+ dt.setHours(0, 0, 0, 0);
124
+ return dt;
125
+ }
126
+ function isoToday() {
127
+ const now = /* @__PURE__ */ new Date();
128
+ const y = now.getFullYear();
129
+ const m = String(now.getMonth() + 1).padStart(2, "0");
130
+ const d = String(now.getDate()).padStart(2, "0");
131
+ return `${y}-${m}-${d}`;
132
+ }
133
+ function belowMin(date, minIso) {
134
+ const minDate = parseIso(minIso);
135
+ return !!minDate && date < minDate;
136
+ }
137
+ function aboveMax(date, maxIso) {
138
+ const maxDate = parseIso(maxIso);
139
+ return !!maxDate && date > maxDate;
140
+ }
141
+ function exceedsYearSpan(start, end, years) {
142
+ const cutoff = new Date(start);
143
+ cutoff.setFullYear(cutoff.getFullYear() + years);
144
+ return end > cutoff;
145
+ }
6
146
 
7
147
  // css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-alert-banner/bv-alert-banner.css
8
148
  var bv_alert_banner_default = ":host {\n --banner-bg: var(--bv-color-neutral-0);\n --banner-border: var(--bv-color-neutral-200);\n --banner-color: var(--bv-color-neutral-700);\n --banner-color-subtle: var(--bv-color-neutral-500);\n --banner-icon-color: var(--bv-color-neutral-500);\n\n display: block;\n font-family: var(--bv-font-family-base);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n:host([variant='primary']) {\n --banner-bg: var(--bv-color-primary-50);\n --banner-border: color-mix(in srgb, var(--bv-color-primary) 40%, var(--bv-color-neutral-0));\n --banner-color: var(--bv-color-primary);\n --banner-color-subtle: var(--bv-color-primary);\n --banner-icon-color: var(--bv-color-primary);\n}\n\n:host([variant='error']) {\n --banner-bg: color-mix(in srgb, var(--bv-color-danger-light) 35%, var(--bv-color-neutral-0));\n --banner-border: color-mix(in srgb, var(--bv-color-danger) 45%, var(--bv-color-neutral-0));\n --banner-color: var(--bv-color-danger-dark);\n --banner-color-subtle: var(--bv-color-danger-dark);\n --banner-icon-color: var(--bv-color-danger);\n}\n\n:host([variant='warning']) {\n --banner-bg: color-mix(in srgb, var(--bv-color-warning-light) 35%, var(--bv-color-neutral-0));\n --banner-border: color-mix(in srgb, var(--bv-color-warning) 50%, var(--bv-color-neutral-0));\n --banner-color: var(--bv-color-warning-dark);\n --banner-color-subtle: var(--bv-color-warning-dark);\n --banner-icon-color: var(--bv-color-warning);\n}\n\n:host([variant='success']) {\n --banner-bg: color-mix(in srgb, var(--bv-color-success-light) 30%, var(--bv-color-neutral-0));\n --banner-border: color-mix(in srgb, var(--bv-color-success) 40%, var(--bv-color-neutral-0));\n --banner-color: var(--bv-color-success-dark);\n --banner-color-subtle: var(--bv-color-success-dark);\n --banner-icon-color: var(--bv-color-success);\n}\n\n/* \u2500\u2500 Banner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.banner {\n display: flex;\n align-items: flex-start;\n gap: var(--bv-spacing-md);\n padding: var(--bv-spacing-md);\n background: var(--banner-bg);\n border: 1px solid var(--banner-border);\n border-radius: var(--bv-radius-xl);\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 */\n\n.banner-icon {\n display: flex;\n flex-shrink: 0;\n margin-top: 0;\n color: var(--banner-icon-color);\n line-height: 0;\n}\n\n/* \u2500\u2500 Content \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.banner-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: var(--bv-spacing-xs);\n min-width: 0;\n}\n\n.banner-heading {\n margin: 0;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n line-height: 20px;\n color: var(--banner-color);\n}\n\n.banner-heading[hidden] {\n display: none;\n}\n\n.banner-message {\n margin: 0;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-regular);\n line-height: 20px;\n color: var(--banner-color);\n}\n\n.banner-message[hidden] {\n display: none;\n}\n\n/* \u2500\u2500 Actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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.banner-actions {\n display: flex;\n align-items: center;\n gap: var(--bv-spacing-md);\n margin-top: var(--bv-spacing-xs);\n}\n\n.banner-actions[hidden] {\n display: none;\n}\n\n.action-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n font-family: inherit;\n font-size: var(--bv-font-size-sm);\n font-weight: var(--bv-font-weight-semibold);\n line-height: 20px;\n color: var(--banner-color);\n transition: opacity 0.15s;\n}\n\n.action-btn[hidden] {\n display: none;\n}\n\n.action-btn:hover {\n opacity: 0.75;\n}\n\n.action-btn:focus-visible {\n outline: 2px solid var(--banner-color);\n outline-offset: 2px;\n border-radius: var(--bv-radius-sm);\n}\n\n/* \u2500\u2500 Close 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 */\n\n.close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n margin: -6px -6px 0 0;\n background: none;\n border: none;\n border-radius: var(--bv-radius-lg);\n cursor: pointer;\n color: var(--banner-icon-color);\n transition: background 0.15s;\n padding: 0;\n}\n\n.close-btn[hidden] {\n display: none;\n}\n\n.close-btn:hover {\n background: color-mix(in srgb, var(--banner-border) 60%, var(--banner-bg));\n}\n\n.close-btn:focus-visible {\n outline: 2px solid var(--banner-color);\n outline-offset: 2px;\n}\n";
@@ -21,14 +161,6 @@ var VARIANT_ICONS = {
21
161
  warning: ICON_ALERT_TRIANGLE,
22
162
  success: ICON_CHECK_CIRCLE
23
163
  };
24
- var _sheet = null;
25
- function getSheet() {
26
- if (!_sheet) {
27
- _sheet = new CSSStyleSheet();
28
- _sheet.replaceSync(bv_alert_banner_default);
29
- }
30
- return _sheet;
31
- }
32
164
  var BvAlertBanner = class extends HTMLElement {
33
165
  static observedAttributes = [
34
166
  "variant",
@@ -48,10 +180,7 @@ var BvAlertBanner = class extends HTMLElement {
48
180
  _closeBtn = null;
49
181
  constructor() {
50
182
  super();
51
- const shadow = this.attachShadow({ mode: "open" });
52
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
53
- shadow.adoptedStyleSheets = [getSheet()];
54
- }
183
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_alert_banner_default);
55
184
  }
56
185
  get variant() {
57
186
  return this.getAttribute("variant") ?? "default";
@@ -105,11 +234,6 @@ var BvAlertBanner = class extends HTMLElement {
105
234
  if (this._rendered) return;
106
235
  this._rendered = true;
107
236
  const shadow = this.shadowRoot;
108
- if (!shadow.adoptedStyleSheets.length) {
109
- const s = document.createElement("style");
110
- s.textContent = bv_alert_banner_default;
111
- shadow.appendChild(s);
112
- }
113
237
  const banner = document.createElement("div");
114
238
  banner.className = "banner";
115
239
  banner.setAttribute("role", "alert");
@@ -236,14 +360,6 @@ var MONTH_NAMES = [
236
360
  var WEEKDAYS = ["Mo", "Tu", "We", "Th", "Fr", "Sat", "Su"];
237
361
  var CHEVRON_LEFT = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg>`;
238
362
  var CHEVRON_RIGHT = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18l6-6-6-6"/></svg>`;
239
- var _sheet2 = null;
240
- function getSheet2() {
241
- if (!_sheet2) {
242
- _sheet2 = new CSSStyleSheet();
243
- _sheet2.replaceSync(bv_date_range_picker_default);
244
- }
245
- return _sheet2;
246
- }
247
363
  function dateToIso(d) {
248
364
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
249
365
  }
@@ -259,6 +375,9 @@ function parseDisplay(str) {
259
375
  const d = match[2].padStart(2, "0");
260
376
  return `${match[3]}-${m}-${d}`;
261
377
  }
378
+ function capitalize(s) {
379
+ return s.charAt(0).toUpperCase() + s.slice(1);
380
+ }
262
381
  function getPresetDates(key) {
263
382
  const now = /* @__PURE__ */ new Date();
264
383
  now.setHours(0, 0, 0, 0);
@@ -301,7 +420,25 @@ function getPresetDates(key) {
301
420
  }
302
421
  }
303
422
  var BvDateRangePicker = class extends HTMLElement {
304
- static observedAttributes = ["start", "end", "preset"];
423
+ static observedAttributes = [
424
+ "start",
425
+ "end",
426
+ "preset",
427
+ // BTN-10191 — opt-in validation config
428
+ "min",
429
+ "max",
430
+ "max-range-years",
431
+ "validate-order",
432
+ "validate-format",
433
+ "show-range-limit-banner",
434
+ // BTN-10191 — configurable error copy (all attributes; defaults apply otherwise)
435
+ "error-min",
436
+ "error-max",
437
+ "error-order",
438
+ "error-format",
439
+ "range-limit-message",
440
+ "range-limit-heading"
441
+ ];
305
442
  _rendered = false;
306
443
  _today;
307
444
  _start = "";
@@ -311,6 +448,12 @@ var BvDateRangePicker = class extends HTMLElement {
311
448
  _hover = "";
312
449
  _leftYear;
313
450
  _leftMonth;
451
+ // BTN-10191 — raw display strings retained when the user types an
452
+ // unparseable value AND `validate-format` is on. Lets V8 fire without
453
+ // corrupting `_start`/`_end` (which stay strictly ISO for consumers).
454
+ _startRawInvalid = "";
455
+ _endRawInvalid = "";
456
+ _validity = { valid: true, applyEnabled: false };
314
457
  _leftGrid = null;
315
458
  _rightGrid = null;
316
459
  _leftTitle = null;
@@ -319,6 +462,9 @@ var BvDateRangePicker = class extends HTMLElement {
319
462
  _endInput = null;
320
463
  _applyBtn = null;
321
464
  _bannerEl = null;
465
+ _startErrorEl = null;
466
+ _endErrorEl = null;
467
+ _formErrorEl = null;
322
468
  _leftDayMap = /* @__PURE__ */ new Map();
323
469
  _rightDayMap = /* @__PURE__ */ new Map();
324
470
  constructor() {
@@ -328,10 +474,7 @@ var BvDateRangePicker = class extends HTMLElement {
328
474
  this._today = dateToIso(now);
329
475
  this._leftYear = now.getFullYear();
330
476
  this._leftMonth = now.getMonth();
331
- const shadow = this.attachShadow({ mode: "open" });
332
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
333
- shadow.adoptedStyleSheets = [getSheet2()];
334
- }
477
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_date_range_picker_default);
335
478
  }
336
479
  get start() {
337
480
  return this._start;
@@ -355,14 +498,22 @@ var BvDateRangePicker = class extends HTMLElement {
355
498
  this._render();
356
499
  }
357
500
  attributeChangedCallback(name, _old, val) {
358
- if (name === "start") this._start = val ?? "";
359
- if (name === "end") this._end = val ?? "";
501
+ if (name === "start") {
502
+ this._start = val ?? "";
503
+ this._startRawInvalid = "";
504
+ }
505
+ if (name === "end") {
506
+ this._end = val ?? "";
507
+ this._endRawInvalid = "";
508
+ }
360
509
  if (name === "preset") this._preset = val ?? "";
361
510
  if (!this._rendered) return;
362
- this._syncCalendars();
363
- this._syncInputs();
364
- this._syncPresets();
365
- this._syncApply();
511
+ if (name === "start" || name === "end" || name === "preset") {
512
+ this._syncCalendars();
513
+ this._syncInputs();
514
+ this._syncPresets();
515
+ }
516
+ this._syncValidity();
366
517
  }
367
518
  _render() {
368
519
  if (this._rendered) return;
@@ -380,11 +531,6 @@ var BvDateRangePicker = class extends HTMLElement {
380
531
  }
381
532
  }
382
533
  const shadow = this.shadowRoot;
383
- if (!shadow.adoptedStyleSheets.length) {
384
- const s = document.createElement("style");
385
- s.textContent = bv_date_range_picker_default;
386
- shadow.appendChild(s);
387
- }
388
534
  const picker = document.createElement("div");
389
535
  picker.className = "picker";
390
536
  const sidebar = document.createElement("div");
@@ -452,15 +598,29 @@ var BvDateRangePicker = class extends HTMLElement {
452
598
  this._applyBtn = applyBtn;
453
599
  actions.append(cancelBtn, applyBtn);
454
600
  footer.append(inputsWrap, actions);
601
+ const errorsWrap = document.createElement("div");
602
+ errorsWrap.className = "error-messages";
603
+ const startErrorEl = document.createElement("div");
604
+ startErrorEl.className = "input-error input-error--start";
605
+ startErrorEl.setAttribute("role", "alert");
606
+ startErrorEl.hidden = true;
607
+ this._startErrorEl = startErrorEl;
608
+ const endErrorEl = document.createElement("div");
609
+ endErrorEl.className = "input-error input-error--end";
610
+ endErrorEl.setAttribute("role", "alert");
611
+ endErrorEl.hidden = true;
612
+ this._endErrorEl = endErrorEl;
613
+ const formErrorEl = document.createElement("div");
614
+ formErrorEl.className = "form-error";
615
+ formErrorEl.setAttribute("role", "alert");
616
+ formErrorEl.hidden = true;
617
+ this._formErrorEl = formErrorEl;
618
+ errorsWrap.append(startErrorEl, endErrorEl, formErrorEl);
619
+ footer.insertBefore(errorsWrap, actions);
455
620
  const bannerWrap = document.createElement("div");
456
621
  bannerWrap.className = "banner-wrap";
457
622
  const banner = document.createElement("bv-alert-banner");
458
623
  banner.setAttribute("variant", "warning");
459
- banner.setAttribute("heading", "Date range exceeds 5 years");
460
- banner.setAttribute(
461
- "message",
462
- "Only CSV download is available for this date range. Live report generation is not supported."
463
- );
464
624
  banner.hidden = true;
465
625
  this._bannerEl = banner;
466
626
  bannerWrap.appendChild(banner);
@@ -470,8 +630,7 @@ var BvDateRangePicker = class extends HTMLElement {
470
630
  this._syncCalendars();
471
631
  this._syncInputs();
472
632
  this._syncPresets();
473
- this._syncApply();
474
- this._syncBanner();
633
+ this._syncValidity();
475
634
  }
476
635
  _buildCalendarShell(side) {
477
636
  const cal = document.createElement("div");
@@ -647,11 +806,12 @@ var BvDateRangePicker = class extends HTMLElement {
647
806
  this._hover = "";
648
807
  this._preset = "custom";
649
808
  }
809
+ this._startRawInvalid = "";
810
+ this._endRawInvalid = "";
650
811
  this._syncCalendars();
651
812
  this._syncInputs();
652
813
  this._syncPresets();
653
- this._syncApply();
654
- this._syncBanner();
814
+ this._syncValidity();
655
815
  }
656
816
  _selectPreset(key) {
657
817
  if (key === "custom") {
@@ -667,6 +827,8 @@ var BvDateRangePicker = class extends HTMLElement {
667
827
  this._preset = key;
668
828
  this._picking = false;
669
829
  this._hover = "";
830
+ this._startRawInvalid = "";
831
+ this._endRawInvalid = "";
670
832
  if (start) {
671
833
  const parts = start.split("-");
672
834
  const y = parseInt(parts[0], 10);
@@ -679,21 +841,47 @@ var BvDateRangePicker = class extends HTMLElement {
679
841
  this._syncCalendars();
680
842
  this._syncInputs();
681
843
  this._syncPresets();
682
- this._syncApply();
683
- this._syncBanner();
844
+ this._syncValidity();
684
845
  }
685
846
  _onInputBlur(input, field) {
686
- const iso = parseDisplay(input.value);
847
+ const raw = input.value;
848
+ if (raw === "") {
849
+ if (field === "start") {
850
+ this._start = "";
851
+ this._startRawInvalid = "";
852
+ } else {
853
+ this._end = "";
854
+ this._endRawInvalid = "";
855
+ }
856
+ this._syncCalendars();
857
+ this._syncPresets();
858
+ this._syncValidity();
859
+ return;
860
+ }
861
+ const iso = parseDisplay(raw);
687
862
  if (iso) {
688
- if (field === "start") this._start = iso;
689
- else this._end = iso;
863
+ if (field === "start") {
864
+ this._start = iso;
865
+ this._startRawInvalid = "";
866
+ } else {
867
+ this._end = iso;
868
+ this._endRawInvalid = "";
869
+ }
690
870
  this._picking = false;
691
871
  this._hover = "";
692
872
  this._preset = "custom";
693
873
  this._syncCalendars();
694
874
  this._syncPresets();
695
- this._syncApply();
696
- this._syncBanner();
875
+ this._syncValidity();
876
+ } else if (this.hasAttribute("validate-format")) {
877
+ if (field === "start") {
878
+ this._start = "";
879
+ this._startRawInvalid = raw;
880
+ } else {
881
+ this._end = "";
882
+ this._endRawInvalid = raw;
883
+ }
884
+ this._syncValidity();
697
885
  } else {
698
886
  input.value = formatDisplay(field === "start" ? this._start : this._end);
699
887
  }
@@ -708,28 +896,153 @@ var BvDateRangePicker = class extends HTMLElement {
708
896
  btn.classList.toggle("active", btn.dataset["preset"] === this._preset);
709
897
  });
710
898
  }
711
- _syncApply() {
899
+ // ── BTN-10191 — validation orchestrator ──────────────────────────────
900
+ _syncValidity() {
901
+ const cfg = this._readConfig();
902
+ const previous = this._validity;
903
+ this._validity = validateDateRange(
904
+ {
905
+ start: this._start || this._startRawInvalid,
906
+ end: this._end || this._endRawInvalid
907
+ },
908
+ cfg
909
+ );
910
+ this._applyErrorStates();
911
+ this._applyApplyState();
912
+ this._applyBannerState();
913
+ this._applyCalendarDisabledState();
914
+ if (previous.valid !== this._validity.valid || previous.applyEnabled !== this._validity.applyEnabled || previous.startError !== this._validity.startError || previous.endError !== this._validity.endError || previous.formError !== this._validity.formError) {
915
+ this.dispatchEvent(
916
+ new CustomEvent("bv-validity-change", {
917
+ bubbles: true,
918
+ composed: true,
919
+ detail: { ...this._validity }
920
+ })
921
+ );
922
+ }
923
+ }
924
+ _readConfig() {
925
+ const min = this.getAttribute("min") || void 0;
926
+ const max = this.getAttribute("max") || void 0;
927
+ const mryStr = this.getAttribute("max-range-years");
928
+ const maxRangeYears = mryStr !== null && mryStr !== "" ? Number(mryStr) : void 0;
929
+ return {
930
+ min,
931
+ max,
932
+ maxRangeYears: Number.isFinite(maxRangeYears) ? maxRangeYears : void 0,
933
+ validateOrder: this.hasAttribute("validate-order"),
934
+ validateFormat: this.hasAttribute("validate-format")
935
+ };
936
+ }
937
+ _applyErrorStates() {
938
+ const v = this._validity;
939
+ this._paintInputError(this._startInput, this._startErrorEl, v.startError, "start");
940
+ this._paintInputError(this._endInput, this._endErrorEl, v.endError, "end");
941
+ if (this._formErrorEl) {
942
+ if (v.formError === "order") {
943
+ this._formErrorEl.textContent = this._resolveOrderError();
944
+ this._formErrorEl.hidden = false;
945
+ } else {
946
+ this._formErrorEl.textContent = "";
947
+ this._formErrorEl.hidden = true;
948
+ }
949
+ }
950
+ }
951
+ _paintInputError(input, errorEl, err, field) {
952
+ if (input) {
953
+ if (err) {
954
+ input.classList.add("invalid");
955
+ input.setAttribute("aria-invalid", "true");
956
+ } else {
957
+ input.classList.remove("invalid");
958
+ input.removeAttribute("aria-invalid");
959
+ }
960
+ }
961
+ if (errorEl) {
962
+ if (err) {
963
+ errorEl.textContent = this._resolveFieldError(err, field);
964
+ errorEl.hidden = false;
965
+ } else {
966
+ errorEl.textContent = "";
967
+ errorEl.hidden = true;
968
+ }
969
+ }
970
+ }
971
+ _resolveFieldError(err, field) {
972
+ if (err === "format") {
973
+ return this.getAttribute("error-format") ?? "Enter a valid date in MM/DD/YYYY format";
974
+ }
975
+ if (err === "min") {
976
+ const minIso = this.getAttribute("min") ?? "";
977
+ const minDisplay = formatDisplay(minIso);
978
+ const custom2 = this.getAttribute("error-min");
979
+ if (custom2) return custom2.replace("{min}", minDisplay).replace("{field}", field);
980
+ return `${capitalize(field)} date must be on or after ${minDisplay}`;
981
+ }
982
+ const maxAttr = this.getAttribute("max") ?? "";
983
+ const resolvedMax = maxAttr === "today" ? isoToday() : maxAttr;
984
+ const maxDisplay = formatDisplay(resolvedMax);
985
+ const custom = this.getAttribute("error-max");
986
+ if (custom) return custom.replace("{max}", maxDisplay).replace("{field}", field);
987
+ if (maxAttr === "today") return `${capitalize(field)} date cannot be in the future`;
988
+ return `${capitalize(field)} date cannot be after ${maxDisplay}`;
989
+ }
990
+ _resolveOrderError() {
991
+ return this.getAttribute("error-order") ?? "Start date must be on or before end date";
992
+ }
993
+ _applyApplyState() {
712
994
  if (this._applyBtn) {
713
- this._applyBtn.disabled = !this._start || !this._end;
995
+ this._applyBtn.disabled = !this._validity.applyEnabled;
714
996
  }
715
997
  }
716
- _syncBanner() {
998
+ _applyBannerState() {
717
999
  if (!this._bannerEl) return;
718
1000
  const pickerEl = this.shadowRoot.querySelector(".picker");
719
- if (!this._start) {
1001
+ const bannerAllowed = this.getAttribute("show-range-limit-banner") !== "false";
1002
+ const show = this._validity.formError === "range-limit" && bannerAllowed;
1003
+ if (show) {
1004
+ const yearsAttr = this.getAttribute("max-range-years") ?? "";
1005
+ const heading = this.getAttribute("range-limit-heading") ?? (yearsAttr ? `Date range exceeds ${yearsAttr} years` : "Date range exceeds the maximum");
1006
+ const message = this.getAttribute("range-limit-message") ?? "Only CSV download is available for this date range. Live report generation is not supported.";
1007
+ this._bannerEl.setAttribute("heading", heading.replace("{N}", yearsAttr));
1008
+ this._bannerEl.setAttribute("message", message.replace("{N}", yearsAttr));
1009
+ this._bannerEl.hidden = false;
1010
+ pickerEl == null ? void 0 : pickerEl.classList.add("picker--with-banner");
1011
+ } else {
720
1012
  this._bannerEl.hidden = true;
721
1013
  pickerEl == null ? void 0 : pickerEl.classList.remove("picker--with-banner");
1014
+ }
1015
+ }
1016
+ _applyCalendarDisabledState() {
1017
+ const minIso = this.getAttribute("min") || "";
1018
+ const maxAttr = this.getAttribute("max") || "";
1019
+ const maxIso = maxAttr === "today" ? isoToday() : maxAttr;
1020
+ if (!minIso && !maxIso) {
1021
+ this._leftDayMap.forEach((btn) => this._resetDayDisabled(btn));
1022
+ this._rightDayMap.forEach((btn) => this._resetDayDisabled(btn));
722
1023
  return;
723
1024
  }
724
- const startDate = /* @__PURE__ */ new Date(this._start + "T00:00:00");
725
- const fiveYearsAgo = /* @__PURE__ */ new Date();
726
- fiveYearsAgo.setFullYear(fiveYearsAgo.getFullYear() - 5);
727
- const show = startDate < fiveYearsAgo;
728
- this._bannerEl.hidden = !show;
729
- pickerEl == null ? void 0 : pickerEl.classList.toggle("picker--with-banner", show);
1025
+ const paint = (btn, iso) => {
1026
+ const outOfRange = !!minIso && iso < minIso || !!maxIso && iso > maxIso;
1027
+ if (outOfRange) {
1028
+ btn.disabled = true;
1029
+ btn.setAttribute("aria-disabled", "true");
1030
+ btn.classList.add("day--disabled");
1031
+ } else {
1032
+ this._resetDayDisabled(btn);
1033
+ }
1034
+ };
1035
+ this._leftDayMap.forEach(paint);
1036
+ this._rightDayMap.forEach(paint);
1037
+ }
1038
+ _resetDayDisabled(btn) {
1039
+ btn.disabled = false;
1040
+ btn.removeAttribute("aria-disabled");
1041
+ btn.classList.remove("day--disabled");
730
1042
  }
731
1043
  // Auto-insert slashes after MM and DD digits while the user types forward.
732
- // Also syncs the banner live once the date is fully typed.
1044
+ // Also runs full validity sync on every keystroke — this is what clears the
1045
+ // error state immediately on correction (per BTN-10191 a11y requirement).
733
1046
  _setupDateMask(input, field) {
734
1047
  input.addEventListener("input", (e) => {
735
1048
  var _a;
@@ -746,17 +1059,19 @@ var BvDateRangePicker = class extends HTMLElement {
746
1059
  formatted = digits.slice(0, 2) + "/" + digits.slice(2, 4) + "/" + digits.slice(4, 8);
747
1060
  }
748
1061
  input.value = formatted;
1062
+ if (field === "start") this._startRawInvalid = "";
1063
+ else this._endRawInvalid = "";
749
1064
  if (formatted.length === 10) {
750
1065
  const iso = parseDisplay(formatted);
751
1066
  if (iso) {
752
1067
  if (field === "start") this._start = iso;
753
1068
  else this._end = iso;
754
- this._syncBanner();
755
1069
  }
756
- } else if (field === "start" && formatted.length < 10) {
757
- this._start = "";
758
- this._syncBanner();
1070
+ } else {
1071
+ if (field === "start") this._start = "";
1072
+ else this._end = "";
759
1073
  }
1074
+ this._syncValidity();
760
1075
  });
761
1076
  }
762
1077
  _apply() {
@@ -779,14 +1094,20 @@ if (!customElements.get("bv-date-range-picker")) {
779
1094
 
780
1095
  // components/bv-date-range-input/bv-date-range-input.ts
781
1096
  var CALENDAR_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>`;
782
- var _sheet3 = null;
783
- function getSheet3() {
784
- if (!_sheet3) {
785
- _sheet3 = new CSSStyleSheet();
786
- _sheet3.replaceSync(bv_date_range_input_default);
787
- }
788
- return _sheet3;
789
- }
1097
+ var PICKER_PASSTHROUGH_ATTRS = [
1098
+ "min",
1099
+ "max",
1100
+ "max-range-years",
1101
+ "validate-order",
1102
+ "validate-format",
1103
+ "show-range-limit-banner",
1104
+ "error-min",
1105
+ "error-max",
1106
+ "error-order",
1107
+ "error-format",
1108
+ "range-limit-message",
1109
+ "range-limit-heading"
1110
+ ];
790
1111
  function isoToDisplay(iso) {
791
1112
  if (!iso) return "";
792
1113
  const [y, m, d] = iso.split("-");
@@ -798,19 +1119,18 @@ var BvDateRangeInput = class extends HTMLElement {
798
1119
  "start",
799
1120
  "end",
800
1121
  "placeholder",
801
- "required"
1122
+ "required",
1123
+ ...PICKER_PASSTHROUGH_ATTRS
802
1124
  ];
803
1125
  _rendered = false;
804
1126
  _labelEl = null;
805
1127
  _displayEl = null;
1128
+ _triggerEl = null;
806
1129
  _pickerEl = null;
807
- _offClick = null;
1130
+ _dropdown = null;
808
1131
  constructor() {
809
1132
  super();
810
- const shadow = this.attachShadow({ mode: "open" });
811
- if ("adoptedStyleSheets" in ShadowRoot.prototype) {
812
- shadow.adoptedStyleSheets = [getSheet3()];
813
- }
1133
+ adoptStyles(this.attachShadow({ mode: "open" }), bv_date_range_input_default);
814
1134
  }
815
1135
  get start() {
816
1136
  return this.getAttribute("start") ?? "";
@@ -828,23 +1148,29 @@ var BvDateRangeInput = class extends HTMLElement {
828
1148
  this._render();
829
1149
  }
830
1150
  disconnectedCallback() {
831
- if (this._offClick) document.removeEventListener("click", this._offClick);
1151
+ var _a;
1152
+ (_a = this._dropdown) == null ? void 0 : _a.cleanup();
832
1153
  }
833
- attributeChangedCallback(name, _old, _val) {
1154
+ attributeChangedCallback(name, _old, val) {
834
1155
  if (!this._rendered) return;
835
1156
  if (name === "label" || name === "required") this._syncLabel();
836
- if (name === "start" || name === "end") this._syncDisplay();
837
1157
  if (name === "placeholder") this._syncDisplay();
1158
+ if (name === "start" || name === "end") {
1159
+ this._syncDisplay();
1160
+ if (this._pickerEl) {
1161
+ if (val === null) this._pickerEl.removeAttribute(name);
1162
+ else this._pickerEl.setAttribute(name, val);
1163
+ }
1164
+ }
1165
+ if (PICKER_PASSTHROUGH_ATTRS.includes(name) && this._pickerEl) {
1166
+ if (val === null) this._pickerEl.removeAttribute(name);
1167
+ else this._pickerEl.setAttribute(name, val);
1168
+ }
838
1169
  }
839
1170
  _render() {
840
1171
  if (this._rendered) return;
841
1172
  this._rendered = true;
842
1173
  const shadow = this.shadowRoot;
843
- if (!shadow.adoptedStyleSheets.length) {
844
- const s = document.createElement("style");
845
- s.textContent = bv_date_range_input_default;
846
- shadow.appendChild(s);
847
- }
848
1174
  const label = document.createElement("label");
849
1175
  label.className = "field-label";
850
1176
  this._labelEl = label;
@@ -854,6 +1180,7 @@ var BvDateRangeInput = class extends HTMLElement {
854
1180
  trigger.setAttribute("aria-haspopup", "dialog");
855
1181
  trigger.setAttribute("aria-expanded", "false");
856
1182
  trigger.setAttribute("part", "trigger");
1183
+ this._triggerEl = trigger;
857
1184
  const display = document.createElement("span");
858
1185
  display.className = "trigger-display";
859
1186
  this._displayEl = display;
@@ -862,20 +1189,26 @@ var BvDateRangeInput = class extends HTMLElement {
862
1189
  icon.innerHTML = CALENDAR_ICON;
863
1190
  trigger.append(display, icon);
864
1191
  trigger.addEventListener("click", (e) => {
1192
+ var _a;
865
1193
  e.stopPropagation();
866
- this._toggle();
1194
+ (_a = this._dropdown) == null ? void 0 : _a.toggle();
867
1195
  });
868
1196
  const popup = document.createElement("div");
869
1197
  popup.className = "picker-popup";
870
1198
  const picker = document.createElement("bv-date-range-picker");
871
1199
  picker.setAttribute("start", this.getAttribute("start") ?? "");
872
1200
  picker.setAttribute("end", this.getAttribute("end") ?? "");
1201
+ for (const attr of PICKER_PASSTHROUGH_ATTRS) {
1202
+ const val = this.getAttribute(attr);
1203
+ if (val !== null) picker.setAttribute(attr, val);
1204
+ }
873
1205
  this._pickerEl = picker;
874
1206
  picker.addEventListener("bv-change", (e) => {
1207
+ var _a;
875
1208
  const detail = e.detail;
876
1209
  this.setAttribute("start", detail.start);
877
1210
  this.setAttribute("end", detail.end);
878
- this._close();
1211
+ (_a = this._dropdown) == null ? void 0 : _a.close();
879
1212
  this.dispatchEvent(
880
1213
  new CustomEvent("bv-change", {
881
1214
  bubbles: true,
@@ -885,36 +1218,41 @@ var BvDateRangeInput = class extends HTMLElement {
885
1218
  );
886
1219
  });
887
1220
  picker.addEventListener("bv-cancel", () => {
888
- this._close();
1221
+ var _a;
1222
+ (_a = this._dropdown) == null ? void 0 : _a.close();
1223
+ });
1224
+ picker.addEventListener("bv-validity-change", (e) => {
1225
+ const detail = e.detail;
1226
+ if (this._triggerEl) {
1227
+ if (detail.valid) {
1228
+ this._triggerEl.classList.remove("invalid");
1229
+ this._triggerEl.removeAttribute("aria-invalid");
1230
+ } else {
1231
+ this._triggerEl.classList.add("invalid");
1232
+ this._triggerEl.setAttribute("aria-invalid", "true");
1233
+ }
1234
+ }
1235
+ this.dispatchEvent(
1236
+ new CustomEvent("bv-validity-change", {
1237
+ bubbles: true,
1238
+ composed: true,
1239
+ detail
1240
+ })
1241
+ );
889
1242
  });
890
1243
  popup.appendChild(picker);
891
1244
  shadow.append(label, trigger, popup);
892
1245
  this._syncLabel();
893
1246
  this._syncDisplay();
894
- this._offClick = (e) => {
895
- if (!this.contains(e.target) && !shadow.contains(e.target)) {
896
- this._close();
1247
+ this._dropdown = bindDropdownController(this, {
1248
+ trigger,
1249
+ onBeforeOpen: () => {
1250
+ if (this._pickerEl) {
1251
+ this._pickerEl.setAttribute("start", this.getAttribute("start") ?? "");
1252
+ this._pickerEl.setAttribute("end", this.getAttribute("end") ?? "");
1253
+ }
897
1254
  }
898
- };
899
- document.addEventListener("click", this._offClick);
900
- }
901
- _toggle() {
902
- if (this.hasAttribute("open")) this._close();
903
- else this._open();
904
- }
905
- _open() {
906
- var _a;
907
- this.setAttribute("open", "");
908
- (_a = this.shadowRoot.querySelector(".trigger")) == null ? void 0 : _a.setAttribute("aria-expanded", "true");
909
- if (this._pickerEl) {
910
- this._pickerEl.setAttribute("start", this.getAttribute("start") ?? "");
911
- this._pickerEl.setAttribute("end", this.getAttribute("end") ?? "");
912
- }
913
- }
914
- _close() {
915
- var _a;
916
- this.removeAttribute("open");
917
- (_a = this.shadowRoot.querySelector(".trigger")) == null ? void 0 : _a.setAttribute("aria-expanded", "false");
1255
+ });
918
1256
  }
919
1257
  _syncLabel() {
920
1258
  if (!this._labelEl) return;