@bluevolt-tech/lumen 2.0.1 → 2.2.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/README.md +37 -23
- package/dist/components/_shared/adopt-styles.d.ts +8 -0
- package/dist/components/_shared/date-range-validator.d.ts +86 -0
- package/dist/components/_shared/dropdown-controller.d.ts +60 -0
- package/dist/components/_shared/selectable-card.d.ts +10 -0
- package/dist/components/bv-alert-banner/bv-alert-banner.js +19 -17
- package/dist/components/bv-avatar/bv-avatar.js +19 -11
- package/dist/components/bv-badge/bv-badge.js +19 -11
- package/dist/components/bv-button/bv-button.js +18 -16
- package/dist/components/bv-category-pill/bv-category-pill.js +18 -16
- package/dist/components/bv-checkbox/bv-checkbox.js +19 -11
- package/dist/components/bv-chip/bv-chip.js +19 -11
- package/dist/components/bv-course-detail/bv-course-detail.js +20 -34
- package/dist/components/bv-course-detail-modal/bv-course-detail-modal.js +24 -90
- package/dist/components/bv-course-grid-card/bv-course-grid-card.d.ts +2 -6
- package/dist/components/bv-course-grid-card/bv-course-grid-card.js +98 -105
- package/dist/components/bv-course-list-card/bv-course-list-card.d.ts +2 -6
- package/dist/components/bv-course-list-card/bv-course-list-card.js +98 -105
- package/dist/components/bv-course-outline/bv-course-outline.js +19 -11
- package/dist/components/bv-date-range-input/bv-date-range-input.d.ts +10 -6
- package/dist/components/bv-date-range-input/bv-date-range-input.js +602 -125
- package/dist/components/bv-date-range-picker/bv-date-range-picker.d.ts +71 -4
- package/dist/components/bv-date-range-picker/bv-date-range-picker.js +441 -76
- package/dist/components/bv-empty-state/bv-empty-state.js +20 -28
- package/dist/components/bv-filter-dropdown/bv-filter-dropdown.d.ts +1 -4
- package/dist/components/bv-filter-dropdown/bv-filter-dropdown.js +80 -53
- package/dist/components/bv-filter-panel/bv-filter-panel.js +20 -22
- package/dist/components/bv-icon/bv-icon.js +19 -11
- package/dist/components/bv-input/bv-input.js +18 -16
- package/dist/components/bv-input-dropdown/bv-input-dropdown.d.ts +1 -4
- package/dist/components/bv-input-dropdown/bv-input-dropdown.js +79 -78
- package/dist/components/bv-modal/bv-modal.js +19 -11
- package/dist/components/bv-page-title/bv-page-title.js +19 -6
- package/dist/components/bv-pagination/bv-pagination.d.ts +1 -0
- package/dist/components/bv-pagination/bv-pagination.js +23 -186
- package/dist/components/bv-portal-header/bv-portal-header.js +25 -83
- package/dist/components/bv-portal-layout/bv-portal-layout.js +19 -11
- package/dist/components/bv-progress-bar/bv-progress-bar.js +19 -11
- package/dist/components/bv-promo-banner/bv-promo-banner.js +19 -11
- package/dist/components/bv-radio/bv-radio.js +19 -11
- package/dist/components/bv-searchbox/bv-searchbox.js +19 -11
- package/dist/components/bv-segmented-control/bv-segmented-control.js +18 -16
- package/dist/components/bv-select/bv-select.d.ts +1 -4
- package/dist/components/bv-select/bv-select.js +78 -44
- package/dist/components/bv-split-button/bv-split-button.d.ts +1 -4
- package/dist/components/bv-split-button/bv-split-button.js +78 -42
- package/dist/components/bv-star-rating/bv-star-rating.js +19 -11
- package/dist/components/bv-table/bv-table.d.ts +5 -0
- package/dist/components/bv-table/bv-table.js +70 -19
- package/dist/components/bv-toggle/bv-toggle.js +19 -11
- package/dist/components/bv-tooltip/bv-tooltip.js +20 -17
- package/dist/components/bv-user-row/bv-user-row.js +22 -50
- package/dist/components/bv-view-switcher/bv-view-switcher.js +19 -11
- package/dist/themes/base.css +1 -1
- package/package.json +8 -1
- package/themes/base.css +1 -1
|
@@ -1,8 +1,170 @@
|
|
|
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
|
+
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
|
+
(_a = options.onAfterClose) == null ? void 0 : _a.call(options);
|
|
46
|
+
};
|
|
47
|
+
const close = () => closeWithReason("programmatic");
|
|
48
|
+
const toggle = () => {
|
|
49
|
+
if (host.hasAttribute("open")) closeWithReason("programmatic");
|
|
50
|
+
else open();
|
|
51
|
+
};
|
|
52
|
+
const offClick = (e) => {
|
|
53
|
+
if (!host.hasAttribute("open")) return;
|
|
54
|
+
if (e.composedPath().includes(host)) return;
|
|
55
|
+
closeWithReason("outside-click");
|
|
56
|
+
};
|
|
57
|
+
document.addEventListener("click", offClick);
|
|
58
|
+
const onKeyDown = (e) => {
|
|
59
|
+
if (e.key === "Escape") closeWithReason("escape");
|
|
60
|
+
};
|
|
61
|
+
host.addEventListener("keydown", onKeyDown);
|
|
62
|
+
const observer = new MutationObserver(syncExpanded);
|
|
63
|
+
observer.observe(host, { attributes: true, attributeFilter: ["open"] });
|
|
64
|
+
const cleanup = () => {
|
|
65
|
+
observer.disconnect();
|
|
66
|
+
document.removeEventListener("click", offClick);
|
|
67
|
+
host.removeEventListener("keydown", onKeyDown);
|
|
68
|
+
};
|
|
69
|
+
syncExpanded();
|
|
70
|
+
return { open, close, toggle, cleanup };
|
|
71
|
+
}
|
|
3
72
|
|
|
4
73
|
// 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";
|
|
74
|
+
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 Alert banner (BTN-10312 \u2014 unified for all validation errors) \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 Validation error states (BTN-10191 / BTN-10312) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/*\n * `.invalid` is applied to input fields only when the field's error resolves\n * to severity 'error' (blocking). Non-blocking severities (warning/primary/\n * success) surface via the banner but do not paint the input red.\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.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";
|
|
75
|
+
|
|
76
|
+
// components/_shared/date-range-validator.ts
|
|
77
|
+
var ALLOWED_SEVERITIES = ["error", "warning", "primary", "success"];
|
|
78
|
+
function normalizeSeverity(value) {
|
|
79
|
+
if (value && ALLOWED_SEVERITIES.includes(value)) {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
return "error";
|
|
83
|
+
}
|
|
84
|
+
function validateDateRange(input, config = {}) {
|
|
85
|
+
const { start, end } = input;
|
|
86
|
+
const { min, max, maxRangeYears, validateOrder, validateFormat } = config;
|
|
87
|
+
const resolvedMax = max === "today" ? isoToday() : max;
|
|
88
|
+
let startError;
|
|
89
|
+
let endError;
|
|
90
|
+
const startDate = start ? parseIso(start) : null;
|
|
91
|
+
const endDate = end ? parseIso(end) : null;
|
|
92
|
+
if (validateFormat) {
|
|
93
|
+
if (start && !startDate) startError = "format";
|
|
94
|
+
if (end && !endDate) endError = "format";
|
|
95
|
+
}
|
|
96
|
+
if (!startError && startDate) {
|
|
97
|
+
if (min && belowMin(startDate, min)) startError = "min";
|
|
98
|
+
else if (resolvedMax && aboveMax(startDate, resolvedMax)) startError = "max";
|
|
99
|
+
}
|
|
100
|
+
if (!endError && endDate) {
|
|
101
|
+
if (min && belowMin(endDate, min)) endError = "min";
|
|
102
|
+
else if (resolvedMax && aboveMax(endDate, resolvedMax)) endError = "max";
|
|
103
|
+
}
|
|
104
|
+
let formError;
|
|
105
|
+
const bothFieldsClean = startDate && endDate && !startError && !endError;
|
|
106
|
+
if (bothFieldsClean) {
|
|
107
|
+
if (validateOrder && startDate > endDate) {
|
|
108
|
+
formError = "order";
|
|
109
|
+
} else if (maxRangeYears !== void 0 && exceedsYearSpan(startDate, endDate, maxRangeYears)) {
|
|
110
|
+
formError = "range-limit";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const valid = !startError && !endError && !formError;
|
|
114
|
+
const startSev = startError ? severityOfFieldError(startError, config) : null;
|
|
115
|
+
const endSev = endError ? severityOfFieldError(endError, config) : null;
|
|
116
|
+
const formSev = formError ? severityOfFormError(formError, config) : null;
|
|
117
|
+
const hasBlockingError = startSev === "error" || endSev === "error" || formSev === "error";
|
|
118
|
+
const applyEnabled = !hasBlockingError && !!startDate && !!endDate;
|
|
119
|
+
return {
|
|
120
|
+
valid,
|
|
121
|
+
applyEnabled,
|
|
122
|
+
...startError && { startError },
|
|
123
|
+
...endError && { endError },
|
|
124
|
+
...formError && { formError }
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function severityOfFieldError(err, config) {
|
|
128
|
+
if (err === "format") return normalizeSeverity(config.formatSeverity);
|
|
129
|
+
if (err === "min") return normalizeSeverity(config.minSeverity);
|
|
130
|
+
return normalizeSeverity(config.maxSeverity);
|
|
131
|
+
}
|
|
132
|
+
function severityOfFormError(err, config) {
|
|
133
|
+
if (err === "order") return normalizeSeverity(config.orderSeverity);
|
|
134
|
+
return normalizeSeverity(config.rangeLimitSeverity);
|
|
135
|
+
}
|
|
136
|
+
function parseIso(s) {
|
|
137
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
138
|
+
if (!m) return null;
|
|
139
|
+
const [, yStr, mStr, dStr] = m;
|
|
140
|
+
const y = Number(yStr);
|
|
141
|
+
const mo = Number(mStr) - 1;
|
|
142
|
+
const d = Number(dStr);
|
|
143
|
+
const dt = new Date(y, mo, d);
|
|
144
|
+
if (dt.getFullYear() !== y || dt.getMonth() !== mo || dt.getDate() !== d) return null;
|
|
145
|
+
dt.setHours(0, 0, 0, 0);
|
|
146
|
+
return dt;
|
|
147
|
+
}
|
|
148
|
+
function isoToday() {
|
|
149
|
+
const now = /* @__PURE__ */ new Date();
|
|
150
|
+
const y = now.getFullYear();
|
|
151
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
152
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
153
|
+
return `${y}-${m}-${d}`;
|
|
154
|
+
}
|
|
155
|
+
function belowMin(date, minIso) {
|
|
156
|
+
const minDate = parseIso(minIso);
|
|
157
|
+
return !!minDate && date < minDate;
|
|
158
|
+
}
|
|
159
|
+
function aboveMax(date, maxIso) {
|
|
160
|
+
const maxDate = parseIso(maxIso);
|
|
161
|
+
return !!maxDate && date > maxDate;
|
|
162
|
+
}
|
|
163
|
+
function exceedsYearSpan(start, end, years) {
|
|
164
|
+
const cutoff = new Date(start);
|
|
165
|
+
cutoff.setFullYear(cutoff.getFullYear() + years);
|
|
166
|
+
return end > cutoff;
|
|
167
|
+
}
|
|
6
168
|
|
|
7
169
|
// css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-alert-banner/bv-alert-banner.css
|
|
8
170
|
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 +183,6 @@ var VARIANT_ICONS = {
|
|
|
21
183
|
warning: ICON_ALERT_TRIANGLE,
|
|
22
184
|
success: ICON_CHECK_CIRCLE
|
|
23
185
|
};
|
|
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
186
|
var BvAlertBanner = class extends HTMLElement {
|
|
33
187
|
static observedAttributes = [
|
|
34
188
|
"variant",
|
|
@@ -48,10 +202,7 @@ var BvAlertBanner = class extends HTMLElement {
|
|
|
48
202
|
_closeBtn = null;
|
|
49
203
|
constructor() {
|
|
50
204
|
super();
|
|
51
|
-
|
|
52
|
-
if ("adoptedStyleSheets" in ShadowRoot.prototype) {
|
|
53
|
-
shadow.adoptedStyleSheets = [getSheet()];
|
|
54
|
-
}
|
|
205
|
+
adoptStyles(this.attachShadow({ mode: "open" }), bv_alert_banner_default);
|
|
55
206
|
}
|
|
56
207
|
get variant() {
|
|
57
208
|
return this.getAttribute("variant") ?? "default";
|
|
@@ -105,11 +256,6 @@ var BvAlertBanner = class extends HTMLElement {
|
|
|
105
256
|
if (this._rendered) return;
|
|
106
257
|
this._rendered = true;
|
|
107
258
|
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
259
|
const banner = document.createElement("div");
|
|
114
260
|
banner.className = "banner";
|
|
115
261
|
banner.setAttribute("role", "alert");
|
|
@@ -236,14 +382,6 @@ var MONTH_NAMES = [
|
|
|
236
382
|
var WEEKDAYS = ["Mo", "Tu", "We", "Th", "Fr", "Sat", "Su"];
|
|
237
383
|
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
384
|
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
385
|
function dateToIso(d) {
|
|
248
386
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
249
387
|
}
|
|
@@ -259,6 +397,9 @@ function parseDisplay(str) {
|
|
|
259
397
|
const d = match[2].padStart(2, "0");
|
|
260
398
|
return `${match[3]}-${m}-${d}`;
|
|
261
399
|
}
|
|
400
|
+
function capitalize(s) {
|
|
401
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
402
|
+
}
|
|
262
403
|
function getPresetDates(key) {
|
|
263
404
|
const now = /* @__PURE__ */ new Date();
|
|
264
405
|
now.setHours(0, 0, 0, 0);
|
|
@@ -301,7 +442,31 @@ function getPresetDates(key) {
|
|
|
301
442
|
}
|
|
302
443
|
}
|
|
303
444
|
var BvDateRangePicker = class extends HTMLElement {
|
|
304
|
-
static observedAttributes = [
|
|
445
|
+
static observedAttributes = [
|
|
446
|
+
"start",
|
|
447
|
+
"end",
|
|
448
|
+
"preset",
|
|
449
|
+
// BTN-10191 — opt-in validation config
|
|
450
|
+
"min",
|
|
451
|
+
"max",
|
|
452
|
+
"max-range-years",
|
|
453
|
+
"validate-order",
|
|
454
|
+
"validate-format",
|
|
455
|
+
"show-range-limit-banner",
|
|
456
|
+
// BTN-10191 — configurable error copy (all attributes; defaults apply otherwise)
|
|
457
|
+
"error-min",
|
|
458
|
+
"error-max",
|
|
459
|
+
"error-order",
|
|
460
|
+
"error-format",
|
|
461
|
+
"range-limit-message",
|
|
462
|
+
"range-limit-heading",
|
|
463
|
+
// BTN-10312 — per-error severity (default error = blocks Apply + red banner)
|
|
464
|
+
"error-min-severity",
|
|
465
|
+
"error-max-severity",
|
|
466
|
+
"error-format-severity",
|
|
467
|
+
"error-order-severity",
|
|
468
|
+
"range-limit-severity"
|
|
469
|
+
];
|
|
305
470
|
_rendered = false;
|
|
306
471
|
_today;
|
|
307
472
|
_start = "";
|
|
@@ -311,6 +476,12 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
311
476
|
_hover = "";
|
|
312
477
|
_leftYear;
|
|
313
478
|
_leftMonth;
|
|
479
|
+
// BTN-10191 — raw display strings retained when the user types an
|
|
480
|
+
// unparseable value AND `validate-format` is on. Lets V8 fire without
|
|
481
|
+
// corrupting `_start`/`_end` (which stay strictly ISO for consumers).
|
|
482
|
+
_startRawInvalid = "";
|
|
483
|
+
_endRawInvalid = "";
|
|
484
|
+
_validity = { valid: true, applyEnabled: false };
|
|
314
485
|
_leftGrid = null;
|
|
315
486
|
_rightGrid = null;
|
|
316
487
|
_leftTitle = null;
|
|
@@ -318,9 +489,22 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
318
489
|
_startInput = null;
|
|
319
490
|
_endInput = null;
|
|
320
491
|
_applyBtn = null;
|
|
321
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Unified alert banner (BTN-10312). Shows AT MOST ONE error at a time. When
|
|
494
|
+
* multiple errors are simultaneously active, the banner reflects the
|
|
495
|
+
* most-recently-edited field (see `_lastEditedField`).
|
|
496
|
+
*/
|
|
497
|
+
_alertBannerEl = null;
|
|
322
498
|
_leftDayMap = /* @__PURE__ */ new Map();
|
|
323
499
|
_rightDayMap = /* @__PURE__ */ new Map();
|
|
500
|
+
/**
|
|
501
|
+
* BTN-10312 — tracks which field the user most-recently interacted with.
|
|
502
|
+
* When both `startError` and `endError` are active, the banner shows the
|
|
503
|
+
* error corresponding to this field. Set to `null` when no field has been
|
|
504
|
+
* touched (initial render, or external attribute set); the resolver falls
|
|
505
|
+
* back to a stable precedence in that case.
|
|
506
|
+
*/
|
|
507
|
+
_lastEditedField = null;
|
|
324
508
|
constructor() {
|
|
325
509
|
super();
|
|
326
510
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -328,10 +512,7 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
328
512
|
this._today = dateToIso(now);
|
|
329
513
|
this._leftYear = now.getFullYear();
|
|
330
514
|
this._leftMonth = now.getMonth();
|
|
331
|
-
|
|
332
|
-
if ("adoptedStyleSheets" in ShadowRoot.prototype) {
|
|
333
|
-
shadow.adoptedStyleSheets = [getSheet2()];
|
|
334
|
-
}
|
|
515
|
+
adoptStyles(this.attachShadow({ mode: "open" }), bv_date_range_picker_default);
|
|
335
516
|
}
|
|
336
517
|
get start() {
|
|
337
518
|
return this._start;
|
|
@@ -355,14 +536,48 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
355
536
|
this._render();
|
|
356
537
|
}
|
|
357
538
|
attributeChangedCallback(name, _old, val) {
|
|
358
|
-
if (name === "start")
|
|
359
|
-
|
|
539
|
+
if (name === "start") {
|
|
540
|
+
this._start = val ?? "";
|
|
541
|
+
this._startRawInvalid = "";
|
|
542
|
+
this._lastEditedField = null;
|
|
543
|
+
}
|
|
544
|
+
if (name === "end") {
|
|
545
|
+
this._end = val ?? "";
|
|
546
|
+
this._endRawInvalid = "";
|
|
547
|
+
this._lastEditedField = null;
|
|
548
|
+
}
|
|
360
549
|
if (name === "preset") this._preset = val ?? "";
|
|
361
550
|
if (!this._rendered) return;
|
|
551
|
+
if (name === "start" || name === "end" || name === "preset") {
|
|
552
|
+
this._syncCalendars();
|
|
553
|
+
this._syncInputs();
|
|
554
|
+
this._syncPresets();
|
|
555
|
+
}
|
|
556
|
+
this._syncValidity();
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* BTN-10312 — public reset of the picker's internal draft state to the
|
|
560
|
+
* currently committed `start` / `end` / `preset` attributes. Called by
|
|
561
|
+
* `bv-date-range-input` when the popup closes without Apply (Cancel,
|
|
562
|
+
* Escape, outside-click) so the next open shows the last-committed value
|
|
563
|
+
* cleanly, with no leftover error banners from the discarded edit.
|
|
564
|
+
*
|
|
565
|
+
* The external value is not modified — this only clears the draft.
|
|
566
|
+
*/
|
|
567
|
+
resetDraft() {
|
|
568
|
+
this._start = this.getAttribute("start") ?? "";
|
|
569
|
+
this._end = this.getAttribute("end") ?? "";
|
|
570
|
+
this._preset = this.getAttribute("preset") ?? "";
|
|
571
|
+
this._startRawInvalid = "";
|
|
572
|
+
this._endRawInvalid = "";
|
|
573
|
+
this._picking = false;
|
|
574
|
+
this._hover = "";
|
|
575
|
+
this._lastEditedField = null;
|
|
576
|
+
if (!this._rendered) return;
|
|
362
577
|
this._syncCalendars();
|
|
363
578
|
this._syncInputs();
|
|
364
579
|
this._syncPresets();
|
|
365
|
-
this.
|
|
580
|
+
this._syncValidity();
|
|
366
581
|
}
|
|
367
582
|
_render() {
|
|
368
583
|
if (this._rendered) return;
|
|
@@ -380,11 +595,6 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
380
595
|
}
|
|
381
596
|
}
|
|
382
597
|
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
598
|
const picker = document.createElement("div");
|
|
389
599
|
picker.className = "picker";
|
|
390
600
|
const sidebar = document.createElement("div");
|
|
@@ -455,14 +665,8 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
455
665
|
const bannerWrap = document.createElement("div");
|
|
456
666
|
bannerWrap.className = "banner-wrap";
|
|
457
667
|
const banner = document.createElement("bv-alert-banner");
|
|
458
|
-
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
668
|
banner.hidden = true;
|
|
465
|
-
this.
|
|
669
|
+
this._alertBannerEl = banner;
|
|
466
670
|
bannerWrap.appendChild(banner);
|
|
467
671
|
main.append(calsWrap, bannerWrap, footer);
|
|
468
672
|
picker.append(sidebar, main);
|
|
@@ -470,8 +674,7 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
470
674
|
this._syncCalendars();
|
|
471
675
|
this._syncInputs();
|
|
472
676
|
this._syncPresets();
|
|
473
|
-
this.
|
|
474
|
-
this._syncBanner();
|
|
677
|
+
this._syncValidity();
|
|
475
678
|
}
|
|
476
679
|
_buildCalendarShell(side) {
|
|
477
680
|
const cal = document.createElement("div");
|
|
@@ -636,6 +839,7 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
636
839
|
this._preset = "custom";
|
|
637
840
|
this._picking = true;
|
|
638
841
|
this._hover = "";
|
|
842
|
+
this._lastEditedField = "start";
|
|
639
843
|
} else {
|
|
640
844
|
if (iso < this._start) {
|
|
641
845
|
this._end = this._start;
|
|
@@ -646,12 +850,14 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
646
850
|
this._picking = false;
|
|
647
851
|
this._hover = "";
|
|
648
852
|
this._preset = "custom";
|
|
853
|
+
this._lastEditedField = "end";
|
|
649
854
|
}
|
|
855
|
+
this._startRawInvalid = "";
|
|
856
|
+
this._endRawInvalid = "";
|
|
650
857
|
this._syncCalendars();
|
|
651
858
|
this._syncInputs();
|
|
652
859
|
this._syncPresets();
|
|
653
|
-
this.
|
|
654
|
-
this._syncBanner();
|
|
860
|
+
this._syncValidity();
|
|
655
861
|
}
|
|
656
862
|
_selectPreset(key) {
|
|
657
863
|
if (key === "custom") {
|
|
@@ -667,6 +873,9 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
667
873
|
this._preset = key;
|
|
668
874
|
this._picking = false;
|
|
669
875
|
this._hover = "";
|
|
876
|
+
this._startRawInvalid = "";
|
|
877
|
+
this._endRawInvalid = "";
|
|
878
|
+
this._lastEditedField = null;
|
|
670
879
|
if (start) {
|
|
671
880
|
const parts = start.split("-");
|
|
672
881
|
const y = parseInt(parts[0], 10);
|
|
@@ -679,21 +888,48 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
679
888
|
this._syncCalendars();
|
|
680
889
|
this._syncInputs();
|
|
681
890
|
this._syncPresets();
|
|
682
|
-
this.
|
|
683
|
-
this._syncBanner();
|
|
891
|
+
this._syncValidity();
|
|
684
892
|
}
|
|
685
893
|
_onInputBlur(input, field) {
|
|
686
|
-
|
|
894
|
+
this._lastEditedField = field;
|
|
895
|
+
const raw = input.value;
|
|
896
|
+
if (raw === "") {
|
|
897
|
+
if (field === "start") {
|
|
898
|
+
this._start = "";
|
|
899
|
+
this._startRawInvalid = "";
|
|
900
|
+
} else {
|
|
901
|
+
this._end = "";
|
|
902
|
+
this._endRawInvalid = "";
|
|
903
|
+
}
|
|
904
|
+
this._syncCalendars();
|
|
905
|
+
this._syncPresets();
|
|
906
|
+
this._syncValidity();
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
const iso = parseDisplay(raw);
|
|
687
910
|
if (iso) {
|
|
688
|
-
if (field === "start")
|
|
689
|
-
|
|
911
|
+
if (field === "start") {
|
|
912
|
+
this._start = iso;
|
|
913
|
+
this._startRawInvalid = "";
|
|
914
|
+
} else {
|
|
915
|
+
this._end = iso;
|
|
916
|
+
this._endRawInvalid = "";
|
|
917
|
+
}
|
|
690
918
|
this._picking = false;
|
|
691
919
|
this._hover = "";
|
|
692
920
|
this._preset = "custom";
|
|
693
921
|
this._syncCalendars();
|
|
694
922
|
this._syncPresets();
|
|
695
|
-
this.
|
|
696
|
-
|
|
923
|
+
this._syncValidity();
|
|
924
|
+
} else if (this.hasAttribute("validate-format")) {
|
|
925
|
+
if (field === "start") {
|
|
926
|
+
this._start = "";
|
|
927
|
+
this._startRawInvalid = raw;
|
|
928
|
+
} else {
|
|
929
|
+
this._end = "";
|
|
930
|
+
this._endRawInvalid = raw;
|
|
931
|
+
}
|
|
932
|
+
this._syncValidity();
|
|
697
933
|
} else {
|
|
698
934
|
input.value = formatDisplay(field === "start" ? this._start : this._end);
|
|
699
935
|
}
|
|
@@ -708,33 +944,211 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
708
944
|
btn.classList.toggle("active", btn.dataset["preset"] === this._preset);
|
|
709
945
|
});
|
|
710
946
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
947
|
+
// ── BTN-10191 — validation orchestrator ──────────────────────────────
|
|
948
|
+
_syncValidity() {
|
|
949
|
+
const cfg = this._readConfig();
|
|
950
|
+
const previous = this._validity;
|
|
951
|
+
this._validity = validateDateRange(
|
|
952
|
+
{
|
|
953
|
+
start: this._start || this._startRawInvalid,
|
|
954
|
+
end: this._end || this._endRawInvalid
|
|
955
|
+
},
|
|
956
|
+
cfg
|
|
957
|
+
);
|
|
958
|
+
this._paintInputInvalidState();
|
|
959
|
+
this._applyAlertBanner(cfg);
|
|
960
|
+
this._applyApplyState();
|
|
961
|
+
this._applyCalendarDisabledState();
|
|
962
|
+
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) {
|
|
963
|
+
this.dispatchEvent(
|
|
964
|
+
new CustomEvent("bv-validity-change", {
|
|
965
|
+
bubbles: true,
|
|
966
|
+
composed: true,
|
|
967
|
+
detail: { ...this._validity }
|
|
968
|
+
})
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
_readConfig() {
|
|
973
|
+
const min = this.getAttribute("min") || void 0;
|
|
974
|
+
const max = this.getAttribute("max") || void 0;
|
|
975
|
+
const mryStr = this.getAttribute("max-range-years");
|
|
976
|
+
const maxRangeYears = mryStr !== null && mryStr !== "" ? Number(mryStr) : void 0;
|
|
977
|
+
return {
|
|
978
|
+
min,
|
|
979
|
+
max,
|
|
980
|
+
maxRangeYears: Number.isFinite(maxRangeYears) ? maxRangeYears : void 0,
|
|
981
|
+
validateOrder: this.hasAttribute("validate-order"),
|
|
982
|
+
validateFormat: this.hasAttribute("validate-format"),
|
|
983
|
+
minSeverity: normalizeSeverity(this.getAttribute("error-min-severity")),
|
|
984
|
+
maxSeverity: normalizeSeverity(this.getAttribute("error-max-severity")),
|
|
985
|
+
formatSeverity: normalizeSeverity(this.getAttribute("error-format-severity")),
|
|
986
|
+
orderSeverity: normalizeSeverity(this.getAttribute("error-order-severity")),
|
|
987
|
+
rangeLimitSeverity: normalizeSeverity(this.getAttribute("range-limit-severity"))
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* BTN-10312 — apply the `.invalid` + `aria-invalid` state to input fields
|
|
992
|
+
* whose field-level error is currently blocking (severity `'error'`).
|
|
993
|
+
* Non-blocking severities (warning/primary/success) do not paint the input
|
|
994
|
+
* red so the visual matches the semantic ("not a fatal error").
|
|
995
|
+
*/
|
|
996
|
+
_paintInputInvalidState() {
|
|
997
|
+
const v = this._validity;
|
|
998
|
+
const cfg = this._readConfig();
|
|
999
|
+
this._paintOne(this._startInput, v.startError ? severityOfFieldError(v.startError, cfg) : null);
|
|
1000
|
+
this._paintOne(this._endInput, v.endError ? severityOfFieldError(v.endError, cfg) : null);
|
|
1001
|
+
}
|
|
1002
|
+
_paintOne(input, sev) {
|
|
1003
|
+
if (!input) return;
|
|
1004
|
+
if (sev === "error") {
|
|
1005
|
+
input.classList.add("invalid");
|
|
1006
|
+
input.setAttribute("aria-invalid", "true");
|
|
1007
|
+
} else {
|
|
1008
|
+
input.classList.remove("invalid");
|
|
1009
|
+
input.removeAttribute("aria-invalid");
|
|
714
1010
|
}
|
|
715
1011
|
}
|
|
716
|
-
|
|
717
|
-
|
|
1012
|
+
/**
|
|
1013
|
+
* BTN-10312 — single alert banner. Picks the "active" error following the
|
|
1014
|
+
* precedence:
|
|
1015
|
+
*
|
|
1016
|
+
* 1. If a form error is active, show it (form errors don't coexist with
|
|
1017
|
+
* field errors by validator design).
|
|
1018
|
+
* 2. If both field errors are active, show the one for
|
|
1019
|
+
* `_lastEditedField` (falling back to `start` if `null`).
|
|
1020
|
+
* 3. Else show whichever single field error is active.
|
|
1021
|
+
* 4. Else hide the banner.
|
|
1022
|
+
*
|
|
1023
|
+
* `show-range-limit-banner="false"` still suppresses the range-limit
|
|
1024
|
+
* banner specifically (existing consumer contract from BTN-10191).
|
|
1025
|
+
*/
|
|
1026
|
+
_applyAlertBanner(cfg) {
|
|
1027
|
+
if (!this._alertBannerEl) return;
|
|
718
1028
|
const pickerEl = this.shadowRoot.querySelector(".picker");
|
|
719
|
-
|
|
720
|
-
|
|
1029
|
+
const active = this._resolveActiveError();
|
|
1030
|
+
if (!active) {
|
|
1031
|
+
this._alertBannerEl.hidden = true;
|
|
1032
|
+
this._alertBannerEl.removeAttribute("heading");
|
|
1033
|
+
this._alertBannerEl.removeAttribute("message");
|
|
1034
|
+
pickerEl == null ? void 0 : pickerEl.classList.remove("picker--with-banner");
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (active.kind === "form" && active.error === "range-limit" && this.getAttribute("show-range-limit-banner") === "false") {
|
|
1038
|
+
this._alertBannerEl.hidden = true;
|
|
721
1039
|
pickerEl == null ? void 0 : pickerEl.classList.remove("picker--with-banner");
|
|
722
1040
|
return;
|
|
723
1041
|
}
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1042
|
+
const variant = active.kind === "field" ? severityOfFieldError(active.error, cfg) : severityOfFormError(active.error, cfg);
|
|
1043
|
+
this._alertBannerEl.setAttribute("variant", variant);
|
|
1044
|
+
if (active.kind === "form" && active.error === "range-limit") {
|
|
1045
|
+
const yearsAttr = this.getAttribute("max-range-years") ?? "";
|
|
1046
|
+
const heading = this.getAttribute("range-limit-heading") || (yearsAttr ? `Date range exceeds ${yearsAttr} years` : "Date range exceeds the maximum");
|
|
1047
|
+
const message = this.getAttribute("range-limit-message") || "Only CSV download is available for this date range. Live report generation is not supported.";
|
|
1048
|
+
this._alertBannerEl.setAttribute("heading", heading.replace(/\{N\}/g, yearsAttr));
|
|
1049
|
+
this._alertBannerEl.setAttribute("message", message.replace(/\{N\}/g, yearsAttr));
|
|
1050
|
+
} else {
|
|
1051
|
+
this._alertBannerEl.removeAttribute("heading");
|
|
1052
|
+
const message = active.kind === "field" ? this._resolveFieldError(active.error, active.field) : this._resolveOrderError();
|
|
1053
|
+
this._alertBannerEl.setAttribute("message", message);
|
|
1054
|
+
}
|
|
1055
|
+
this._alertBannerEl.hidden = false;
|
|
1056
|
+
pickerEl == null ? void 0 : pickerEl.classList.add("picker--with-banner");
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Pick which error the banner should reflect. See `_applyAlertBanner` for
|
|
1060
|
+
* the precedence rules.
|
|
1061
|
+
*/
|
|
1062
|
+
_resolveActiveError() {
|
|
1063
|
+
const v = this._validity;
|
|
1064
|
+
if (v.formError) return { kind: "form", error: v.formError };
|
|
1065
|
+
if (v.startError && v.endError) {
|
|
1066
|
+
const winner = this._lastEditedField ?? "start";
|
|
1067
|
+
return {
|
|
1068
|
+
kind: "field",
|
|
1069
|
+
error: winner === "start" ? v.startError : v.endError,
|
|
1070
|
+
field: winner
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
if (v.startError) return { kind: "field", error: v.startError, field: "start" };
|
|
1074
|
+
if (v.endError) return { kind: "field", error: v.endError, field: "end" };
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
_resolveFieldError(err, field) {
|
|
1078
|
+
if (err === "format") {
|
|
1079
|
+
const custom2 = this.getAttribute("error-format");
|
|
1080
|
+
if (custom2) return this._interpolateField(custom2, field);
|
|
1081
|
+
return `Enter a valid ${field} date`;
|
|
1082
|
+
}
|
|
1083
|
+
if (err === "min") {
|
|
1084
|
+
const minIso = this.getAttribute("min") ?? "";
|
|
1085
|
+
const minDisplay = formatDisplay(minIso);
|
|
1086
|
+
const custom2 = this.getAttribute("error-min");
|
|
1087
|
+
if (custom2) return this._interpolateField(custom2, field).replace(/\{min\}/g, minDisplay);
|
|
1088
|
+
return `${capitalize(field)} date must be on or after ${minDisplay}`;
|
|
1089
|
+
}
|
|
1090
|
+
const maxAttr = this.getAttribute("max") ?? "";
|
|
1091
|
+
const resolvedMax = maxAttr === "today" ? isoToday() : maxAttr;
|
|
1092
|
+
const maxDisplay = formatDisplay(resolvedMax);
|
|
1093
|
+
const custom = this.getAttribute("error-max");
|
|
1094
|
+
if (custom) return this._interpolateField(custom, field).replace(/\{max\}/g, maxDisplay);
|
|
1095
|
+
if (maxAttr === "today") return `${capitalize(field)} date cannot be in the future`;
|
|
1096
|
+
return `${capitalize(field)} date cannot be after ${maxDisplay}`;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* BTN-10312 — interpolate `{field}` (always lowercase) and `{Field}` (always
|
|
1100
|
+
* capitalized) in a user-provided template. Before this change, the historical
|
|
1101
|
+
* behavior was that `{field}` in custom copy resolved lowercase while the
|
|
1102
|
+
* built-in defaults capitalized inline — inconsistent. Now the split is
|
|
1103
|
+
* explicit: `{field}` = lowercase, `{Field}` = capitalized.
|
|
1104
|
+
*/
|
|
1105
|
+
_interpolateField(template, field) {
|
|
1106
|
+
return template.replace(/\{Field\}/g, capitalize(field)).replace(/\{field\}/g, field);
|
|
1107
|
+
}
|
|
1108
|
+
_resolveOrderError() {
|
|
1109
|
+
return this.getAttribute("error-order") || "Start date must be on or before end date";
|
|
1110
|
+
}
|
|
1111
|
+
_applyApplyState() {
|
|
1112
|
+
if (this._applyBtn) {
|
|
1113
|
+
this._applyBtn.disabled = !this._validity.applyEnabled;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
_applyCalendarDisabledState() {
|
|
1117
|
+
const minIso = this.getAttribute("min") || "";
|
|
1118
|
+
const maxAttr = this.getAttribute("max") || "";
|
|
1119
|
+
const maxIso = maxAttr === "today" ? isoToday() : maxAttr;
|
|
1120
|
+
if (!minIso && !maxIso) {
|
|
1121
|
+
this._leftDayMap.forEach((btn) => this._resetDayDisabled(btn));
|
|
1122
|
+
this._rightDayMap.forEach((btn) => this._resetDayDisabled(btn));
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const paint = (btn, iso) => {
|
|
1126
|
+
const outOfRange = !!minIso && iso < minIso || !!maxIso && iso > maxIso;
|
|
1127
|
+
if (outOfRange) {
|
|
1128
|
+
btn.disabled = true;
|
|
1129
|
+
btn.setAttribute("aria-disabled", "true");
|
|
1130
|
+
btn.classList.add("day--disabled");
|
|
1131
|
+
} else {
|
|
1132
|
+
this._resetDayDisabled(btn);
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
this._leftDayMap.forEach(paint);
|
|
1136
|
+
this._rightDayMap.forEach(paint);
|
|
1137
|
+
}
|
|
1138
|
+
_resetDayDisabled(btn) {
|
|
1139
|
+
btn.disabled = false;
|
|
1140
|
+
btn.removeAttribute("aria-disabled");
|
|
1141
|
+
btn.classList.remove("day--disabled");
|
|
730
1142
|
}
|
|
731
1143
|
// Auto-insert slashes after MM and DD digits while the user types forward.
|
|
732
|
-
// Also
|
|
1144
|
+
// Also runs full validity sync on every keystroke — this is what clears the
|
|
1145
|
+
// error state immediately on correction (per BTN-10191 a11y requirement).
|
|
733
1146
|
_setupDateMask(input, field) {
|
|
734
1147
|
input.addEventListener("input", (e) => {
|
|
735
1148
|
var _a;
|
|
736
1149
|
const ev = e;
|
|
737
1150
|
if ((_a = ev.inputType) == null ? void 0 : _a.startsWith("delete")) return;
|
|
1151
|
+
this._lastEditedField = field;
|
|
738
1152
|
const digits = input.value.replace(/[^\d]/g, "");
|
|
739
1153
|
let formatted;
|
|
740
1154
|
if (digits.length <= 2) {
|
|
@@ -746,17 +1160,19 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
746
1160
|
formatted = digits.slice(0, 2) + "/" + digits.slice(2, 4) + "/" + digits.slice(4, 8);
|
|
747
1161
|
}
|
|
748
1162
|
input.value = formatted;
|
|
1163
|
+
if (field === "start") this._startRawInvalid = "";
|
|
1164
|
+
else this._endRawInvalid = "";
|
|
749
1165
|
if (formatted.length === 10) {
|
|
750
1166
|
const iso = parseDisplay(formatted);
|
|
751
1167
|
if (iso) {
|
|
752
1168
|
if (field === "start") this._start = iso;
|
|
753
1169
|
else this._end = iso;
|
|
754
|
-
this._syncBanner();
|
|
755
1170
|
}
|
|
756
|
-
} else
|
|
757
|
-
this._start = "";
|
|
758
|
-
this.
|
|
1171
|
+
} else {
|
|
1172
|
+
if (field === "start") this._start = "";
|
|
1173
|
+
else this._end = "";
|
|
759
1174
|
}
|
|
1175
|
+
this._syncValidity();
|
|
760
1176
|
});
|
|
761
1177
|
}
|
|
762
1178
|
_apply() {
|
|
@@ -779,14 +1195,26 @@ if (!customElements.get("bv-date-range-picker")) {
|
|
|
779
1195
|
|
|
780
1196
|
// components/bv-date-range-input/bv-date-range-input.ts
|
|
781
1197
|
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
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1198
|
+
var PICKER_PASSTHROUGH_ATTRS = [
|
|
1199
|
+
"min",
|
|
1200
|
+
"max",
|
|
1201
|
+
"max-range-years",
|
|
1202
|
+
"validate-order",
|
|
1203
|
+
"validate-format",
|
|
1204
|
+
"show-range-limit-banner",
|
|
1205
|
+
"error-min",
|
|
1206
|
+
"error-max",
|
|
1207
|
+
"error-order",
|
|
1208
|
+
"error-format",
|
|
1209
|
+
"range-limit-message",
|
|
1210
|
+
"range-limit-heading",
|
|
1211
|
+
// BTN-10312 — per-error severity attributes
|
|
1212
|
+
"error-min-severity",
|
|
1213
|
+
"error-max-severity",
|
|
1214
|
+
"error-format-severity",
|
|
1215
|
+
"error-order-severity",
|
|
1216
|
+
"range-limit-severity"
|
|
1217
|
+
];
|
|
790
1218
|
function isoToDisplay(iso) {
|
|
791
1219
|
if (!iso) return "";
|
|
792
1220
|
const [y, m, d] = iso.split("-");
|
|
@@ -798,19 +1226,26 @@ var BvDateRangeInput = class extends HTMLElement {
|
|
|
798
1226
|
"start",
|
|
799
1227
|
"end",
|
|
800
1228
|
"placeholder",
|
|
801
|
-
"required"
|
|
1229
|
+
"required",
|
|
1230
|
+
// BTN-10312 — local (not passed through to picker); gates popup close.
|
|
1231
|
+
"keep-open-while-invalid",
|
|
1232
|
+
...PICKER_PASSTHROUGH_ATTRS
|
|
802
1233
|
];
|
|
803
1234
|
_rendered = false;
|
|
804
1235
|
_labelEl = null;
|
|
805
1236
|
_displayEl = null;
|
|
1237
|
+
_triggerEl = null;
|
|
806
1238
|
_pickerEl = null;
|
|
807
|
-
|
|
1239
|
+
_dropdown = null;
|
|
1240
|
+
/**
|
|
1241
|
+
* BTN-10312 — cached validity from the internal picker. `canClose` on the
|
|
1242
|
+
* dropdown controller consults this to gate popup close when
|
|
1243
|
+
* `keep-open-while-invalid` is set.
|
|
1244
|
+
*/
|
|
1245
|
+
_pickerValidity = { valid: true, applyEnabled: false };
|
|
808
1246
|
constructor() {
|
|
809
1247
|
super();
|
|
810
|
-
|
|
811
|
-
if ("adoptedStyleSheets" in ShadowRoot.prototype) {
|
|
812
|
-
shadow.adoptedStyleSheets = [getSheet3()];
|
|
813
|
-
}
|
|
1248
|
+
adoptStyles(this.attachShadow({ mode: "open" }), bv_date_range_input_default);
|
|
814
1249
|
}
|
|
815
1250
|
get start() {
|
|
816
1251
|
return this.getAttribute("start") ?? "";
|
|
@@ -828,23 +1263,29 @@ var BvDateRangeInput = class extends HTMLElement {
|
|
|
828
1263
|
this._render();
|
|
829
1264
|
}
|
|
830
1265
|
disconnectedCallback() {
|
|
831
|
-
|
|
1266
|
+
var _a;
|
|
1267
|
+
(_a = this._dropdown) == null ? void 0 : _a.cleanup();
|
|
832
1268
|
}
|
|
833
|
-
attributeChangedCallback(name, _old,
|
|
1269
|
+
attributeChangedCallback(name, _old, val) {
|
|
834
1270
|
if (!this._rendered) return;
|
|
835
1271
|
if (name === "label" || name === "required") this._syncLabel();
|
|
836
|
-
if (name === "start" || name === "end") this._syncDisplay();
|
|
837
1272
|
if (name === "placeholder") this._syncDisplay();
|
|
1273
|
+
if (name === "start" || name === "end") {
|
|
1274
|
+
this._syncDisplay();
|
|
1275
|
+
if (this._pickerEl) {
|
|
1276
|
+
if (val === null) this._pickerEl.removeAttribute(name);
|
|
1277
|
+
else this._pickerEl.setAttribute(name, val);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if (PICKER_PASSTHROUGH_ATTRS.includes(name) && this._pickerEl) {
|
|
1281
|
+
if (val === null) this._pickerEl.removeAttribute(name);
|
|
1282
|
+
else this._pickerEl.setAttribute(name, val);
|
|
1283
|
+
}
|
|
838
1284
|
}
|
|
839
1285
|
_render() {
|
|
840
1286
|
if (this._rendered) return;
|
|
841
1287
|
this._rendered = true;
|
|
842
1288
|
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
1289
|
const label = document.createElement("label");
|
|
849
1290
|
label.className = "field-label";
|
|
850
1291
|
this._labelEl = label;
|
|
@@ -854,6 +1295,7 @@ var BvDateRangeInput = class extends HTMLElement {
|
|
|
854
1295
|
trigger.setAttribute("aria-haspopup", "dialog");
|
|
855
1296
|
trigger.setAttribute("aria-expanded", "false");
|
|
856
1297
|
trigger.setAttribute("part", "trigger");
|
|
1298
|
+
this._triggerEl = trigger;
|
|
857
1299
|
const display = document.createElement("span");
|
|
858
1300
|
display.className = "trigger-display";
|
|
859
1301
|
this._displayEl = display;
|
|
@@ -862,20 +1304,26 @@ var BvDateRangeInput = class extends HTMLElement {
|
|
|
862
1304
|
icon.innerHTML = CALENDAR_ICON;
|
|
863
1305
|
trigger.append(display, icon);
|
|
864
1306
|
trigger.addEventListener("click", (e) => {
|
|
1307
|
+
var _a;
|
|
865
1308
|
e.stopPropagation();
|
|
866
|
-
this.
|
|
1309
|
+
(_a = this._dropdown) == null ? void 0 : _a.toggle();
|
|
867
1310
|
});
|
|
868
1311
|
const popup = document.createElement("div");
|
|
869
1312
|
popup.className = "picker-popup";
|
|
870
1313
|
const picker = document.createElement("bv-date-range-picker");
|
|
871
1314
|
picker.setAttribute("start", this.getAttribute("start") ?? "");
|
|
872
1315
|
picker.setAttribute("end", this.getAttribute("end") ?? "");
|
|
1316
|
+
for (const attr of PICKER_PASSTHROUGH_ATTRS) {
|
|
1317
|
+
const val = this.getAttribute(attr);
|
|
1318
|
+
if (val !== null) picker.setAttribute(attr, val);
|
|
1319
|
+
}
|
|
873
1320
|
this._pickerEl = picker;
|
|
874
1321
|
picker.addEventListener("bv-change", (e) => {
|
|
1322
|
+
var _a;
|
|
875
1323
|
const detail = e.detail;
|
|
876
1324
|
this.setAttribute("start", detail.start);
|
|
877
1325
|
this.setAttribute("end", detail.end);
|
|
878
|
-
this.
|
|
1326
|
+
(_a = this._dropdown) == null ? void 0 : _a.close();
|
|
879
1327
|
this.dispatchEvent(
|
|
880
1328
|
new CustomEvent("bv-change", {
|
|
881
1329
|
bubbles: true,
|
|
@@ -885,36 +1333,65 @@ var BvDateRangeInput = class extends HTMLElement {
|
|
|
885
1333
|
);
|
|
886
1334
|
});
|
|
887
1335
|
picker.addEventListener("bv-cancel", () => {
|
|
888
|
-
|
|
1336
|
+
var _a;
|
|
1337
|
+
(_a = this._dropdown) == null ? void 0 : _a.close();
|
|
1338
|
+
});
|
|
1339
|
+
picker.addEventListener("bv-validity-change", (e) => {
|
|
1340
|
+
const detail = e.detail;
|
|
1341
|
+
this._pickerValidity = detail;
|
|
1342
|
+
const hasBlockingError = !detail.applyEnabled && !!(detail.startError || detail.endError || detail.formError);
|
|
1343
|
+
if (this._triggerEl) {
|
|
1344
|
+
if (hasBlockingError) {
|
|
1345
|
+
this._triggerEl.classList.add("invalid");
|
|
1346
|
+
this._triggerEl.setAttribute("aria-invalid", "true");
|
|
1347
|
+
} else {
|
|
1348
|
+
this._triggerEl.classList.remove("invalid");
|
|
1349
|
+
this._triggerEl.removeAttribute("aria-invalid");
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
this.dispatchEvent(
|
|
1353
|
+
new CustomEvent("bv-validity-change", {
|
|
1354
|
+
bubbles: true,
|
|
1355
|
+
composed: true,
|
|
1356
|
+
detail
|
|
1357
|
+
})
|
|
1358
|
+
);
|
|
889
1359
|
});
|
|
890
1360
|
popup.appendChild(picker);
|
|
891
1361
|
shadow.append(label, trigger, popup);
|
|
892
1362
|
this._syncLabel();
|
|
893
1363
|
this._syncDisplay();
|
|
894
|
-
this.
|
|
895
|
-
|
|
896
|
-
|
|
1364
|
+
this._dropdown = bindDropdownController(this, {
|
|
1365
|
+
trigger,
|
|
1366
|
+
onBeforeOpen: () => {
|
|
1367
|
+
if (this._pickerEl) {
|
|
1368
|
+
this._pickerEl.setAttribute("start", this.getAttribute("start") ?? "");
|
|
1369
|
+
this._pickerEl.setAttribute("end", this.getAttribute("end") ?? "");
|
|
1370
|
+
}
|
|
1371
|
+
},
|
|
1372
|
+
// BTN-10312 — gate popup close when the consumer opted in via
|
|
1373
|
+
// `keep-open-while-invalid` AND the picker has a blocking error.
|
|
1374
|
+
// Only *implicit* dismissals (Escape / outside-click) are gated —
|
|
1375
|
+
// explicit user actions (Cancel button, Apply, trigger toggle) always
|
|
1376
|
+
// close so the user can never get trapped in an invalid popup.
|
|
1377
|
+
// Non-blocking errors (warning/primary/success severity) do NOT block
|
|
1378
|
+
// close either: the value externally is still the last-committed one.
|
|
1379
|
+
canClose: (reason) => {
|
|
1380
|
+
if (reason === "programmatic") return true;
|
|
1381
|
+
if (!this.hasAttribute("keep-open-while-invalid")) return true;
|
|
1382
|
+
const v = this._pickerValidity;
|
|
1383
|
+
const hasBlockingError = !v.applyEnabled && !!(v.startError || v.endError || v.formError);
|
|
1384
|
+
return !hasBlockingError;
|
|
1385
|
+
},
|
|
1386
|
+
// BTN-10312 — after every successful close (including Apply, Cancel,
|
|
1387
|
+
// Escape, outside-click), reset the picker's internal draft state so the
|
|
1388
|
+
// next open shows the last-committed value cleanly. Apply cases end up
|
|
1389
|
+
// being a no-op because the draft already equals the committed value.
|
|
1390
|
+
onAfterClose: () => {
|
|
1391
|
+
var _a;
|
|
1392
|
+
(_a = this._pickerEl) == null ? void 0 : _a.resetDraft();
|
|
897
1393
|
}
|
|
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");
|
|
1394
|
+
});
|
|
918
1395
|
}
|
|
919
1396
|
_syncLabel() {
|
|
920
1397
|
if (!this._labelEl) return;
|